iPXE
ntlm.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2017 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /** @file
00027  *
00028  * NT LAN Manager (NTLM) authentication
00029  *
00030  */
00031 
00032 #include <stdlib.h>
00033 #include <string.h>
00034 #include <ctype.h>
00035 #include <errno.h>
00036 #include <byteswap.h>
00037 #include <ipxe/md4.h>
00038 #include <ipxe/md5.h>
00039 #include <ipxe/hmac.h>
00040 #include <ipxe/ntlm.h>
00041 
00042 /** Negotiate message
00043  *
00044  * This message content is fixed since there is no need to specify the
00045  * calling workstation name or domain name, and the set of flags is
00046  * mandated by the MS-NLMP specification.
00047  */
00048 const struct ntlm_negotiate ntlm_negotiate = {
00049         .header = {
00050                 .magic = NTLM_MAGIC,
00051                 .type = cpu_to_le32 ( NTLM_NEGOTIATE ),
00052         },
00053         .flags = cpu_to_le32 ( NTLM_NEGOTIATE_EXTENDED_SESSIONSECURITY |
00054                                NTLM_NEGOTIATE_ALWAYS_SIGN |
00055                                NTLM_NEGOTIATE_NTLM |
00056                                NTLM_REQUEST_TARGET |
00057                                NTLM_NEGOTIATE_UNICODE ),
00058 };
00059 
00060 /**
00061  * Parse NTLM Challenge
00062  *
00063  * @v challenge         Challenge message
00064  * @v len               Length of Challenge message
00065  * @v info              Challenge information to fill in
00066  * @ret rc              Return status code
00067  */
00068 int ntlm_challenge ( struct ntlm_challenge *challenge, size_t len,
00069                      struct ntlm_challenge_info *info ) {
00070         size_t offset;
00071 
00072         DBGC ( challenge, "NTLM challenge message:\n" );
00073         DBGC_HDA ( challenge, 0, challenge, len );
00074 
00075         /* Sanity checks */
00076         if ( len < sizeof ( *challenge ) ) {
00077                 DBGC ( challenge, "NTLM underlength challenge (%zd bytes)\n",
00078                        len );
00079                 return -EINVAL;
00080         }
00081 
00082         /* Extract nonce */
00083         info->nonce = &challenge->nonce;
00084         DBGC ( challenge, "NTLM challenge nonce:\n" );
00085         DBGC_HDA ( challenge, 0, info->nonce, sizeof ( *info->nonce ) );
00086 
00087         /* Extract target information */
00088         info->len = le16_to_cpu ( challenge->info.len );
00089         offset = le32_to_cpu ( challenge->info.offset );
00090         if ( ( offset > len ) ||
00091              ( info->len > ( len - offset ) ) ) {
00092                 DBGC ( challenge, "NTLM target information outside "
00093                        "challenge\n" );
00094                 DBGC_HDA ( challenge, 0, challenge, len );
00095                 return -EINVAL;
00096         }
00097         info->target = ( ( ( void * ) challenge ) + offset );
00098         DBGC ( challenge, "NTLM challenge target information:\n" );
00099         DBGC_HDA ( challenge, 0, info->target, info->len );
00100 
00101         return 0;
00102 }
00103 
00104 /**
00105  * Calculate NTLM verification key
00106  *
00107  * @v domain            Domain name (or NULL)
00108  * @v username          User name (or NULL)
00109  * @v password          Password (or NULL)
00110  * @v key               Key to fill in
00111  *
00112  * This is the NTOWFv2() function as defined in MS-NLMP.
00113  */
00114 void ntlm_key ( const char *domain, const char *username,
00115                 const char *password, struct ntlm_key *key ) {
00116         struct digest_algorithm *md4 = &md4_algorithm;
00117         struct digest_algorithm *md5 = &md5_algorithm;
00118         union {
00119                 uint8_t md4[MD4_CTX_SIZE];
00120                 uint8_t md5[MD5_CTX_SIZE];
00121         } ctx;
00122         uint8_t digest[MD4_DIGEST_SIZE];
00123         size_t digest_len;
00124         uint8_t c;
00125         uint16_t wc;
00126 
00127         /* Use empty usernames/passwords if not specified */
00128         if ( ! domain )
00129                 domain = "";
00130         if ( ! username )
00131                 username = "";
00132         if ( ! password )
00133                 password = "";
00134 
00135         /* Construct MD4 digest of (Unicode) password */
00136         digest_init ( md4, ctx.md4 );
00137         while ( ( c = *(password++) ) ) {
00138                 wc = cpu_to_le16 ( c );
00139                 digest_update ( md4, ctx.md4, &wc, sizeof ( wc ) );
00140         }
00141         digest_final ( md4, ctx.md4, digest );
00142 
00143         /* Construct HMAC-MD5 of (Unicode) upper-case username */
00144         digest_len = sizeof ( digest );
00145         hmac_init ( md5, ctx.md5, digest, &digest_len );
00146         while ( ( c = *(username++) ) ) {
00147                 wc = cpu_to_le16 ( toupper ( c ) );
00148                 hmac_update ( md5, ctx.md5, &wc, sizeof ( wc ) );
00149         }
00150         while ( ( c = *(domain++) ) ) {
00151                 wc = cpu_to_le16 ( c );
00152                 hmac_update ( md5, ctx.md5, &wc, sizeof ( wc ) );
00153         }
00154         hmac_final ( md5, ctx.md5, digest, &digest_len, key->raw );
00155         DBGC ( key, "NTLM key:\n" );
00156         DBGC_HDA ( key, 0, key, sizeof ( *key ) );
00157 }
00158 
00159 /**
00160  * Construct NTLM responses
00161  *
00162  * @v info              Challenge information
00163  * @v key               Verification key
00164  * @v nonce             Nonce, or NULL to use a random nonce
00165  * @v lm                LAN Manager response to fill in
00166  * @v nt                NT response to fill in
00167  */
00168 void ntlm_response ( struct ntlm_challenge_info *info, struct ntlm_key *key,
00169                      struct ntlm_nonce *nonce, struct ntlm_lm_response *lm,
00170                      struct ntlm_nt_response *nt ) {
00171         struct digest_algorithm *md5 = &md5_algorithm;
00172         struct ntlm_nonce tmp_nonce;
00173         uint8_t ctx[MD5_CTX_SIZE];
00174         size_t key_len = sizeof ( *key );
00175         unsigned int i;
00176 
00177         /* Generate random nonce, if needed */
00178         if ( ! nonce ) {
00179                 for ( i = 0 ; i < sizeof ( tmp_nonce ) ; i++ )
00180                         tmp_nonce.raw[i] = random();
00181                 nonce = &tmp_nonce;
00182         }
00183 
00184         /* Construct LAN Manager response */
00185         memcpy ( &lm->nonce, nonce, sizeof ( lm->nonce ) );
00186         hmac_init ( md5, ctx, key->raw, &key_len );
00187         hmac_update ( md5, ctx, info->nonce, sizeof ( *info->nonce ) );
00188         hmac_update ( md5, ctx, &lm->nonce, sizeof ( lm->nonce ) );
00189         hmac_final ( md5, ctx, key->raw, &key_len, lm->digest );
00190         DBGC ( key, "NTLM LAN Manager response:\n" );
00191         DBGC_HDA ( key, 0, lm, sizeof ( *lm ) );
00192 
00193         /* Construct NT response */
00194         memset ( nt, 0, sizeof ( *nt ) );
00195         nt->version = NTLM_VERSION_NTLMV2;
00196         nt->high = NTLM_VERSION_NTLMV2;
00197         memcpy ( &nt->nonce, nonce, sizeof ( nt->nonce ) );
00198         hmac_init ( md5, ctx, key->raw, &key_len );
00199         hmac_update ( md5, ctx, info->nonce, sizeof ( *info->nonce ) );
00200         hmac_update ( md5, ctx, &nt->version,
00201                       ( sizeof ( *nt ) -
00202                         offsetof ( typeof ( *nt ), version ) ) );
00203         hmac_update ( md5, ctx, info->target, info->len );
00204         hmac_update ( md5, ctx, &nt->zero, sizeof ( nt->zero ) );
00205         hmac_final ( md5, ctx, key->raw, &key_len, nt->digest );
00206         DBGC ( key, "NTLM NT response prefix:\n" );
00207         DBGC_HDA ( key, 0, nt, sizeof ( *nt ) );
00208 }
00209 
00210 /**
00211  * Append data to NTLM message
00212  *
00213  * @v header            Message header, or NULL to only calculate next payload
00214  * @v data              Data descriptor
00215  * @v payload           Data payload
00216  * @v len               Length of data
00217  * @ret payload         Next data payload
00218  */
00219 static void * ntlm_append ( struct ntlm_header *header, struct ntlm_data *data,
00220                             void *payload, size_t len ) {
00221 
00222         /* Populate data descriptor */
00223         if ( header ) {
00224                 data->offset = cpu_to_le32 ( payload - ( ( void * ) header ) );
00225                 data->len = data->max_len = cpu_to_le16 ( len );
00226         }
00227 
00228         return ( payload + len );
00229 }
00230 
00231 /**
00232  * Append Unicode string data to NTLM message
00233  *
00234  * @v header            Message header, or NULL to only calculate next payload
00235  * @v data              Data descriptor
00236  * @v payload           Data payload
00237  * @v string            String to append, or NULL
00238  * @ret payload         Next data payload
00239  */
00240 static void * ntlm_append_string ( struct ntlm_header *header,
00241                                    struct ntlm_data *data, void *payload,
00242                                    const char *string ) {
00243         uint16_t *tmp = payload;
00244         uint8_t c;
00245 
00246         /* Convert string to Unicode */
00247         for ( tmp = payload ; ( string && ( c = *(string++) ) ) ; tmp++ ) {
00248                 if ( header )
00249                         *tmp = cpu_to_le16 ( c );
00250         }
00251 
00252         /* Append string data */
00253         return ntlm_append ( header, data, payload,
00254                              ( ( ( void * ) tmp ) - payload ) );
00255 }
00256 
00257 /**
00258  * Construct NTLM Authenticate message
00259  *
00260  * @v info              Challenge information
00261  * @v domain            Domain name, or NULL
00262  * @v username          User name, or NULL
00263  * @v workstation       Workstation name, or NULL
00264  * @v lm                LAN Manager response
00265  * @v nt                NT response
00266  * @v auth              Message to fill in, or NULL to only calculate length
00267  * @ret len             Length of message
00268  */
00269 size_t ntlm_authenticate ( struct ntlm_challenge_info *info, const char *domain,
00270                            const char *username, const char *workstation,
00271                            struct ntlm_lm_response *lm,
00272                            struct ntlm_nt_response *nt,
00273                            struct ntlm_authenticate *auth ) {
00274         void *tmp;
00275         size_t nt_len;
00276         size_t len;
00277 
00278         /* Construct response header */
00279         if ( auth ) {
00280                 memset ( auth, 0, sizeof ( *auth ) );
00281                 memcpy ( auth->header.magic, ntlm_negotiate.header.magic,
00282                          sizeof ( auth->header.magic ) );
00283                 auth->header.type = cpu_to_le32 ( NTLM_AUTHENTICATE );
00284                 auth->flags = ntlm_negotiate.flags;
00285         }
00286         tmp = ( ( ( void * ) auth ) + sizeof ( *auth ) );
00287 
00288         /* Construct LAN Manager response */
00289         if ( auth )
00290                 memcpy ( tmp, lm, sizeof ( *lm ) );
00291         tmp = ntlm_append ( &auth->header, &auth->lm, tmp, sizeof ( *lm ) );
00292 
00293         /* Construct NT response */
00294         nt_len = ( sizeof ( *nt ) + info->len + sizeof ( nt->zero ) );
00295         if ( auth ) {
00296                 memcpy ( tmp, nt, sizeof ( *nt ) );
00297                 memcpy ( ( tmp + sizeof ( *nt ) ), info->target, info->len );
00298                 memset ( ( tmp + sizeof ( *nt ) + info->len ), 0,
00299                          sizeof ( nt->zero ) );
00300         }
00301         tmp = ntlm_append ( &auth->header, &auth->nt, tmp, nt_len );
00302 
00303         /* Populate domain, user, and workstation names */
00304         tmp = ntlm_append_string ( &auth->header, &auth->domain, tmp, domain );
00305         tmp = ntlm_append_string ( &auth->header, &auth->user, tmp, username );
00306         tmp = ntlm_append_string ( &auth->header, &auth->workstation, tmp,
00307                                    workstation );
00308 
00309         /* Calculate length */
00310         len = ( tmp - ( ( void * ) auth ) );
00311         if ( auth ) {
00312                 DBGC ( auth, "NTLM authenticate message:\n" );
00313                 DBGC_HDA ( auth, 0, auth, len );
00314         }
00315 
00316         return len;
00317 }
00318 
00319 /**
00320  * Calculate NTLM Authenticate message length
00321  *
00322  * @v info              Challenge information
00323  * @v domain            Domain name, or NULL
00324  * @v username          User name, or NULL
00325  * @v workstation       Workstation name, or NULL
00326  * @ret len             Length of Authenticate message
00327  */
00328 size_t ntlm_authenticate_len ( struct ntlm_challenge_info *info,
00329                                const char *domain, const char *username,
00330                                const char *workstation ) {
00331 
00332         return ntlm_authenticate ( info, domain, username, workstation,
00333                                    NULL, NULL, NULL );
00334 }