[pci] Add support for PCI MSI-X interrupts
[ipxe.git] / src / crypto / ntlm.c
1 /*
2 * Copyright (C) 2017 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 *
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 /** @file
27 *
28 * NT LAN Manager (NTLM) authentication
29 *
30 */
31
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <byteswap.h>
37 #include <ipxe/md4.h>
38 #include <ipxe/md5.h>
39 #include <ipxe/hmac.h>
40 #include <ipxe/ntlm.h>
41
42 /** Negotiate message
43 *
44 * This message content is fixed since there is no need to specify the
45 * calling workstation name or domain name, and the set of flags is
46 * mandated by the MS-NLMP specification.
47 */
48 const struct ntlm_negotiate ntlm_negotiate = {
49 .header = {
50 .magic = NTLM_MAGIC,
51 .type = cpu_to_le32 ( NTLM_NEGOTIATE ),
52 },
53 .flags = cpu_to_le32 ( NTLM_NEGOTIATE_EXTENDED_SESSIONSECURITY |
54 NTLM_NEGOTIATE_ALWAYS_SIGN |
55 NTLM_NEGOTIATE_NTLM |
56 NTLM_REQUEST_TARGET |
57 NTLM_NEGOTIATE_UNICODE ),
58 };
59
60 /**
61 * Parse NTLM Challenge
62 *
63 * @v challenge Challenge message
64 * @v len Length of Challenge message
65 * @v info Challenge information to fill in
66 * @ret rc Return status code
67 */
68 int ntlm_challenge ( struct ntlm_challenge *challenge, size_t len,
69 struct ntlm_challenge_info *info ) {
70 size_t offset;
71
72 DBGC ( challenge, "NTLM challenge message:\n" );
73 DBGC_HDA ( challenge, 0, challenge, len );
74
75 /* Sanity checks */
76 if ( len < sizeof ( *challenge ) ) {
77 DBGC ( challenge, "NTLM underlength challenge (%zd bytes)\n",
78 len );
79 return -EINVAL;
80 }
81
82 /* Extract nonce */
83 info->nonce = &challenge->nonce;
84 DBGC ( challenge, "NTLM challenge nonce:\n" );
85 DBGC_HDA ( challenge, 0, info->nonce, sizeof ( *info->nonce ) );
86
87 /* Extract target information */
88 info->len = le16_to_cpu ( challenge->info.len );
89 offset = le32_to_cpu ( challenge->info.offset );
90 if ( ( offset > len ) ||
91 ( info->len > ( len - offset ) ) ) {
92 DBGC ( challenge, "NTLM target information outside "
93 "challenge\n" );
94 DBGC_HDA ( challenge, 0, challenge, len );
95 return -EINVAL;
96 }
97 info->target = ( ( ( void * ) challenge ) + offset );
98 DBGC ( challenge, "NTLM challenge target information:\n" );
99 DBGC_HDA ( challenge, 0, info->target, info->len );
100
101 return 0;
102 }
103
104 /**
105 * Calculate NTLM verification key
106 *
107 * @v domain Domain name (or NULL)
108 * @v username User name (or NULL)
109 * @v password Password (or NULL)
110 * @v key Key to fill in
111 *
112 * This is the NTOWFv2() function as defined in MS-NLMP.
113 */
114 void ntlm_key ( const char *domain, const char *username,
115 const char *password, struct ntlm_key *key ) {
116 struct digest_algorithm *md4 = &md4_algorithm;
117 struct digest_algorithm *md5 = &md5_algorithm;
118 union {
119 uint8_t md4[MD4_CTX_SIZE];
120 uint8_t md5[MD5_CTX_SIZE];
121 } ctx;
122 uint8_t digest[MD4_DIGEST_SIZE];
123 size_t digest_len;
124 uint8_t c;
125 uint16_t wc;
126
127 /* Use empty usernames/passwords if not specified */
128 if ( ! domain )
129 domain = "";
130 if ( ! username )
131 username = "";
132 if ( ! password )
133 password = "";
134
135 /* Construct MD4 digest of (Unicode) password */
136 digest_init ( md4, ctx.md4 );
137 while ( ( c = *(password++) ) ) {
138 wc = cpu_to_le16 ( c );
139 digest_update ( md4, ctx.md4, &wc, sizeof ( wc ) );
140 }
141 digest_final ( md4, ctx.md4, digest );
142
143 /* Construct HMAC-MD5 of (Unicode) upper-case username */
144 digest_len = sizeof ( digest );
145 hmac_init ( md5, ctx.md5, digest, &digest_len );
146 while ( ( c = *(username++) ) ) {
147 wc = cpu_to_le16 ( toupper ( c ) );
148 hmac_update ( md5, ctx.md5, &wc, sizeof ( wc ) );
149 }
150 while ( ( c = *(domain++) ) ) {
151 wc = cpu_to_le16 ( c );
152 hmac_update ( md5, ctx.md5, &wc, sizeof ( wc ) );
153 }
154 hmac_final ( md5, ctx.md5, digest, &digest_len, key->raw );
155 DBGC ( key, "NTLM key:\n" );
156 DBGC_HDA ( key, 0, key, sizeof ( *key ) );
157 }
158
159 /**
160 * Construct NTLM responses
161 *
162 * @v info Challenge information
163 * @v key Verification key
164 * @v nonce Nonce, or NULL to use a random nonce
165 * @v lm LAN Manager response to fill in
166 * @v nt NT response to fill in
167 */
168 void ntlm_response ( struct ntlm_challenge_info *info, struct ntlm_key *key,
169 struct ntlm_nonce *nonce, struct ntlm_lm_response *lm,
170 struct ntlm_nt_response *nt ) {
171 struct digest_algorithm *md5 = &md5_algorithm;
172 struct ntlm_nonce tmp_nonce;
173 uint8_t ctx[MD5_CTX_SIZE];
174 size_t key_len = sizeof ( *key );
175 unsigned int i;
176
177 /* Generate random nonce, if needed */
178 if ( ! nonce ) {
179 for ( i = 0 ; i < sizeof ( tmp_nonce ) ; i++ )
180 tmp_nonce.raw[i] = random();
181 nonce = &tmp_nonce;
182 }
183
184 /* Construct LAN Manager response */
185 memcpy ( &lm->nonce, nonce, sizeof ( lm->nonce ) );
186 hmac_init ( md5, ctx, key->raw, &key_len );
187 hmac_update ( md5, ctx, info->nonce, sizeof ( *info->nonce ) );
188 hmac_update ( md5, ctx, &lm->nonce, sizeof ( lm->nonce ) );
189 hmac_final ( md5, ctx, key->raw, &key_len, lm->digest );
190 DBGC ( key, "NTLM LAN Manager response:\n" );
191 DBGC_HDA ( key, 0, lm, sizeof ( *lm ) );
192
193 /* Construct NT response */
194 memset ( nt, 0, sizeof ( *nt ) );
195 nt->version = NTLM_VERSION_NTLMV2;
196 nt->high = NTLM_VERSION_NTLMV2;
197 memcpy ( &nt->nonce, nonce, sizeof ( nt->nonce ) );
198 hmac_init ( md5, ctx, key->raw, &key_len );
199 hmac_update ( md5, ctx, info->nonce, sizeof ( *info->nonce ) );
200 hmac_update ( md5, ctx, &nt->version,
201 ( sizeof ( *nt ) -
202 offsetof ( typeof ( *nt ), version ) ) );
203 hmac_update ( md5, ctx, info->target, info->len );
204 hmac_update ( md5, ctx, &nt->zero, sizeof ( nt->zero ) );
205 hmac_final ( md5, ctx, key->raw, &key_len, nt->digest );
206 DBGC ( key, "NTLM NT response prefix:\n" );
207 DBGC_HDA ( key, 0, nt, sizeof ( *nt ) );
208 }
209
210 /**
211 * Append data to NTLM message
212 *
213 * @v header Message header, or NULL to only calculate next payload
214 * @v data Data descriptor
215 * @v payload Data payload
216 * @v len Length of data
217 * @ret payload Next data payload
218 */
219 static void * ntlm_append ( struct ntlm_header *header, struct ntlm_data *data,
220 void *payload, size_t len ) {
221
222 /* Populate data descriptor */
223 if ( header ) {
224 data->offset = cpu_to_le32 ( payload - ( ( void * ) header ) );
225 data->len = data->max_len = cpu_to_le16 ( len );
226 }
227
228 return ( payload + len );
229 }
230
231 /**
232 * Append Unicode string data to NTLM message
233 *
234 * @v header Message header, or NULL to only calculate next payload
235 * @v data Data descriptor
236 * @v payload Data payload
237 * @v string String to append, or NULL
238 * @ret payload Next data payload
239 */
240 static void * ntlm_append_string ( struct ntlm_header *header,
241 struct ntlm_data *data, void *payload,
242 const char *string ) {
243 uint16_t *tmp = payload;
244 uint8_t c;
245
246 /* Convert string to Unicode */
247 for ( tmp = payload ; ( string && ( c = *(string++) ) ) ; tmp++ ) {
248 if ( header )
249 *tmp = cpu_to_le16 ( c );
250 }
251
252 /* Append string data */
253 return ntlm_append ( header, data, payload,
254 ( ( ( void * ) tmp ) - payload ) );
255 }
256
257 /**
258 * Construct NTLM Authenticate message
259 *
260 * @v info Challenge information
261 * @v domain Domain name, or NULL
262 * @v username User name, or NULL
263 * @v workstation Workstation name, or NULL
264 * @v lm LAN Manager response
265 * @v nt NT response
266 * @v auth Message to fill in, or NULL to only calculate length
267 * @ret len Length of message
268 */
269 size_t ntlm_authenticate ( struct ntlm_challenge_info *info, const char *domain,
270 const char *username, const char *workstation,
271 struct ntlm_lm_response *lm,
272 struct ntlm_nt_response *nt,
273 struct ntlm_authenticate *auth ) {
274 void *tmp;
275 size_t nt_len;
276 size_t len;
277
278 /* Construct response header */
279 if ( auth ) {
280 memset ( auth, 0, sizeof ( *auth ) );
281 memcpy ( auth->header.magic, ntlm_negotiate.header.magic,
282 sizeof ( auth->header.magic ) );
283 auth->header.type = cpu_to_le32 ( NTLM_AUTHENTICATE );
284 auth->flags = ntlm_negotiate.flags;
285 }
286 tmp = ( ( ( void * ) auth ) + sizeof ( *auth ) );
287
288 /* Construct LAN Manager response */
289 if ( auth )
290 memcpy ( tmp, lm, sizeof ( *lm ) );
291 tmp = ntlm_append ( &auth->header, &auth->lm, tmp, sizeof ( *lm ) );
292
293 /* Construct NT response */
294 nt_len = ( sizeof ( *nt ) + info->len + sizeof ( nt->zero ) );
295 if ( auth ) {
296 memcpy ( tmp, nt, sizeof ( *nt ) );
297 memcpy ( ( tmp + sizeof ( *nt ) ), info->target, info->len );
298 memset ( ( tmp + sizeof ( *nt ) + info->len ), 0,
299 sizeof ( nt->zero ) );
300 }
301 tmp = ntlm_append ( &auth->header, &auth->nt, tmp, nt_len );
302
303 /* Populate domain, user, and workstation names */
304 tmp = ntlm_append_string ( &auth->header, &auth->domain, tmp, domain );
305 tmp = ntlm_append_string ( &auth->header, &auth->user, tmp, username );
306 tmp = ntlm_append_string ( &auth->header, &auth->workstation, tmp,
307 workstation );
308
309 /* Calculate length */
310 len = ( tmp - ( ( void * ) auth ) );
311 if ( auth ) {
312 DBGC ( auth, "NTLM authenticate message:\n" );
313 DBGC_HDA ( auth, 0, auth, len );
314 }
315
316 return len;
317 }
318
319 /**
320 * Calculate NTLM Authenticate message length
321 *
322 * @v info Challenge information
323 * @v domain Domain name, or NULL
324 * @v username User name, or NULL
325 * @v workstation Workstation name, or NULL
326 * @ret len Length of Authenticate message
327 */
328 size_t ntlm_authenticate_len ( struct ntlm_challenge_info *info,
329 const char *domain, const char *username,
330 const char *workstation ) {
331
332 return ntlm_authenticate ( info, domain, username, workstation,
333 NULL, NULL, NULL );
334 }