Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) The Written Word, Inc. |
2 | | * Copyright (C) Simon Josefsson |
3 | | * All rights reserved. |
4 | | * |
5 | | * Redistribution and use in source and binary forms, |
6 | | * with or without modification, are permitted provided |
7 | | * that the following conditions are met: |
8 | | * |
9 | | * Redistributions of source code must retain the above |
10 | | * copyright notice, this list of conditions and the |
11 | | * following disclaimer. |
12 | | * |
13 | | * Redistributions in binary form must reproduce the above |
14 | | * copyright notice, this list of conditions and the following |
15 | | * disclaimer in the documentation and/or other materials |
16 | | * provided with the distribution. |
17 | | * |
18 | | * Neither the name of the copyright holder nor the names |
19 | | * of any other contributors may be used to endorse or |
20 | | * promote products derived from this software without |
21 | | * specific prior written permission. |
22 | | * |
23 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
24 | | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
25 | | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
26 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
27 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
28 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
29 | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
30 | | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
31 | | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
32 | | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
33 | | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
34 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
35 | | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
36 | | * OF SUCH DAMAGE. |
37 | | * |
38 | | * SPDX-License-Identifier: BSD-3-Clause |
39 | | */ |
40 | | |
41 | | #include "libssh2_priv.h" |
42 | | |
43 | | static int |
44 | | readline(char *line, int line_size, FILE * fp) |
45 | 0 | { |
46 | 0 | size_t len; |
47 | |
|
48 | 0 | if(!line) { |
49 | 0 | return -1; |
50 | 0 | } |
51 | 0 | if(!fgets(line, line_size, fp)) { |
52 | 0 | return -1; |
53 | 0 | } |
54 | | |
55 | 0 | if(*line) { |
56 | 0 | len = strlen(line); |
57 | 0 | if(len > 0 && line[len - 1] == '\n') { |
58 | 0 | line[len - 1] = '\0'; |
59 | 0 | } |
60 | 0 | } |
61 | |
|
62 | 0 | if(*line) { |
63 | 0 | len = strlen(line); |
64 | 0 | if(len > 0 && line[len - 1] == '\r') { |
65 | 0 | line[len - 1] = '\0'; |
66 | 0 | } |
67 | 0 | } |
68 | |
|
69 | 0 | return 0; |
70 | 0 | } |
71 | | |
72 | | static int |
73 | | readline_memory(char *line, size_t line_size, |
74 | | const char *filedata, size_t filedata_len, |
75 | | size_t *filedata_offset) |
76 | 0 | { |
77 | 0 | size_t off, len; |
78 | |
|
79 | 0 | off = *filedata_offset; |
80 | |
|
81 | 0 | for(len = 0; off + len < filedata_len && len < line_size - 1; len++) { |
82 | 0 | if(filedata[off + len] == '\n' || |
83 | 0 | filedata[off + len] == '\r') { |
84 | 0 | break; |
85 | 0 | } |
86 | 0 | } |
87 | |
|
88 | 0 | if(len) { |
89 | 0 | memcpy(line, filedata + off, len); |
90 | 0 | *filedata_offset += len; |
91 | 0 | } |
92 | |
|
93 | 0 | line[len] = '\0'; |
94 | 0 | *filedata_offset += 1; |
95 | |
|
96 | 0 | return 0; |
97 | 0 | } |
98 | | |
99 | 0 | #define LINE_SIZE 128 |
100 | | |
101 | | static const char *crypt_annotation = "Proc-Type: 4,ENCRYPTED"; |
102 | | |
103 | | static unsigned char hex_decode(char digit) |
104 | 0 | { |
105 | 0 | return (unsigned char) |
106 | 0 | ((digit >= 'A') ? (0xA + (digit - 'A')) : (digit - '0')); |
107 | 0 | } |
108 | | |
109 | | int |
110 | | _libssh2_pem_parse(LIBSSH2_SESSION * session, |
111 | | const char *headerbegin, |
112 | | const char *headerend, |
113 | | const unsigned char *passphrase, |
114 | | FILE * fp, unsigned char **data, size_t *datalen) |
115 | 0 | { |
116 | 0 | char line[LINE_SIZE]; |
117 | 0 | unsigned char iv[LINE_SIZE]; |
118 | 0 | char *b64data = NULL; |
119 | 0 | size_t b64datalen = 0; |
120 | 0 | int ret; |
121 | 0 | const LIBSSH2_CRYPT_METHOD *method = NULL; |
122 | |
|
123 | 0 | do { |
124 | 0 | *line = '\0'; |
125 | |
|
126 | 0 | if(readline(line, LINE_SIZE, fp)) { |
127 | 0 | return -1; |
128 | 0 | } |
129 | 0 | } while(strcmp(line, headerbegin) != 0); |
130 | | |
131 | 0 | if(readline(line, LINE_SIZE, fp)) { |
132 | 0 | return -1; |
133 | 0 | } |
134 | | |
135 | 0 | if(passphrase && |
136 | 0 | memcmp(line, crypt_annotation, strlen(crypt_annotation)) == 0) { |
137 | 0 | const LIBSSH2_CRYPT_METHOD **all_methods, *cur_method; |
138 | 0 | int i; |
139 | |
|
140 | 0 | if(readline(line, LINE_SIZE, fp)) { |
141 | 0 | ret = -1; |
142 | 0 | goto out; |
143 | 0 | } |
144 | | |
145 | 0 | all_methods = libssh2_crypt_methods(); |
146 | | /* !checksrc! disable EQUALSNULL 1 */ |
147 | 0 | while((cur_method = *all_methods++) != NULL) { |
148 | 0 | if(*cur_method->pem_annotation && |
149 | 0 | memcmp(line, cur_method->pem_annotation, |
150 | 0 | strlen(cur_method->pem_annotation)) == 0) { |
151 | 0 | method = cur_method; |
152 | 0 | memcpy(iv, line + strlen(method->pem_annotation) + 1, |
153 | 0 | 2*method->iv_len); |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | /* None of the available crypt methods were able to decrypt the key */ |
158 | 0 | if(!method) |
159 | 0 | return -1; |
160 | | |
161 | | /* Decode IV from hex */ |
162 | 0 | for(i = 0; i < method->iv_len; ++i) { |
163 | 0 | iv[i] = (unsigned char)(hex_decode(iv[2*i]) << 4); |
164 | 0 | iv[i] |= hex_decode(iv[2*i + 1]); |
165 | 0 | } |
166 | | |
167 | | /* skip to the next line */ |
168 | 0 | if(readline(line, LINE_SIZE, fp)) { |
169 | 0 | ret = -1; |
170 | 0 | goto out; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | 0 | do { |
175 | 0 | if(*line) { |
176 | 0 | char *tmp; |
177 | 0 | size_t linelen; |
178 | |
|
179 | 0 | linelen = strlen(line); |
180 | 0 | tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
181 | 0 | if(!tmp) { |
182 | 0 | _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
183 | 0 | "Unable to allocate memory for PEM parsing"); |
184 | 0 | ret = -1; |
185 | 0 | goto out; |
186 | 0 | } |
187 | 0 | memcpy(tmp + b64datalen, line, linelen); |
188 | 0 | b64data = tmp; |
189 | 0 | b64datalen += linelen; |
190 | 0 | } |
191 | | |
192 | 0 | *line = '\0'; |
193 | |
|
194 | 0 | if(readline(line, LINE_SIZE, fp)) { |
195 | 0 | ret = -1; |
196 | 0 | goto out; |
197 | 0 | } |
198 | 0 | } while(strcmp(line, headerend) != 0); |
199 | | |
200 | 0 | if(!b64data) { |
201 | 0 | return -1; |
202 | 0 | } |
203 | | |
204 | 0 | if(_libssh2_base64_decode(session, (char **) data, datalen, |
205 | 0 | b64data, b64datalen)) { |
206 | 0 | ret = -1; |
207 | 0 | goto out; |
208 | 0 | } |
209 | | |
210 | 0 | if(method) { |
211 | | #if LIBSSH2_MD5_PEM |
212 | | /* Set up decryption */ |
213 | | int free_iv = 0, free_secret = 0, len_decrypted = 0, padding = 0; |
214 | | int blocksize = method->blocksize; |
215 | | void *abstract; |
216 | | unsigned char secret[2*MD5_DIGEST_LENGTH]; |
217 | | libssh2_md5_ctx fingerprint_ctx; |
218 | | |
219 | | /* Perform key derivation (PBKDF1/MD5) */ |
220 | | if(!libssh2_md5_init(&fingerprint_ctx) || |
221 | | !libssh2_md5_update(fingerprint_ctx, passphrase, |
222 | | strlen((const char *)passphrase)) || |
223 | | !libssh2_md5_update(fingerprint_ctx, iv, 8) || |
224 | | !libssh2_md5_final(fingerprint_ctx, secret)) { |
225 | | ret = -1; |
226 | | goto out; |
227 | | } |
228 | | if(method->secret_len > MD5_DIGEST_LENGTH) { |
229 | | if(!libssh2_md5_init(&fingerprint_ctx) || |
230 | | !libssh2_md5_update(fingerprint_ctx, |
231 | | secret, MD5_DIGEST_LENGTH) || |
232 | | !libssh2_md5_update(fingerprint_ctx, |
233 | | passphrase, |
234 | | strlen((const char *)passphrase)) || |
235 | | !libssh2_md5_update(fingerprint_ctx, iv, 8) || |
236 | | !libssh2_md5_final(fingerprint_ctx, |
237 | | secret + MD5_DIGEST_LENGTH)) { |
238 | | ret = -1; |
239 | | goto out; |
240 | | } |
241 | | } |
242 | | |
243 | | /* Initialize the decryption */ |
244 | | if(method->init(session, method, iv, &free_iv, secret, |
245 | | &free_secret, 0, &abstract)) { |
246 | | _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
247 | | LIBSSH2_FREE(session, data); |
248 | | ret = -1; |
249 | | goto out; |
250 | | } |
251 | | |
252 | | if(free_secret) { |
253 | | _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
254 | | } |
255 | | |
256 | | /* Do the actual decryption */ |
257 | | if((*datalen % blocksize) != 0) { |
258 | | _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
259 | | method->dtor(session, &abstract); |
260 | | _libssh2_explicit_zero(*data, *datalen); |
261 | | LIBSSH2_FREE(session, *data); |
262 | | ret = -1; |
263 | | goto out; |
264 | | } |
265 | | |
266 | | if(method->flags & LIBSSH2_CRYPT_FLAG_REQUIRES_FULL_PACKET) { |
267 | | if(method->crypt(session, 0, *data, *datalen, &abstract, 0)) { |
268 | | ret = LIBSSH2_ERROR_DECRYPT; |
269 | | _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
270 | | method->dtor(session, &abstract); |
271 | | _libssh2_explicit_zero(*data, *datalen); |
272 | | LIBSSH2_FREE(session, *data); |
273 | | goto out; |
274 | | } |
275 | | } |
276 | | else { |
277 | | while(len_decrypted <= (int)*datalen - blocksize) { |
278 | | if(method->crypt(session, 0, *data + len_decrypted, blocksize, |
279 | | &abstract, |
280 | | len_decrypted == 0 ? FIRST_BLOCK : |
281 | | ((len_decrypted == (int)*datalen - blocksize) ? |
282 | | LAST_BLOCK : MIDDLE_BLOCK) |
283 | | )) { |
284 | | ret = LIBSSH2_ERROR_DECRYPT; |
285 | | _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
286 | | method->dtor(session, &abstract); |
287 | | _libssh2_explicit_zero(*data, *datalen); |
288 | | LIBSSH2_FREE(session, *data); |
289 | | goto out; |
290 | | } |
291 | | |
292 | | len_decrypted += blocksize; |
293 | | } |
294 | | } |
295 | | |
296 | | /* Account for padding */ |
297 | | padding = (*data)[*datalen - 1]; |
298 | | memset(&(*data)[*datalen-padding], 0, padding); |
299 | | *datalen -= padding; |
300 | | |
301 | | /* Clean up */ |
302 | | _libssh2_explicit_zero((char *)secret, sizeof(secret)); |
303 | | method->dtor(session, &abstract); |
304 | | #else |
305 | 0 | ret = -1; |
306 | 0 | goto out; |
307 | 0 | #endif |
308 | 0 | } |
309 | | |
310 | 0 | ret = 0; |
311 | 0 | out: |
312 | 0 | if(b64data) { |
313 | 0 | _libssh2_explicit_zero(b64data, b64datalen); |
314 | 0 | LIBSSH2_FREE(session, b64data); |
315 | 0 | } |
316 | 0 | return ret; |
317 | 0 | } |
318 | | |
319 | | int |
320 | | _libssh2_pem_parse_memory(LIBSSH2_SESSION * session, |
321 | | const char *headerbegin, |
322 | | const char *headerend, |
323 | | const char *filedata, size_t filedata_len, |
324 | | unsigned char **data, size_t *datalen) |
325 | 0 | { |
326 | 0 | char line[LINE_SIZE]; |
327 | 0 | char *b64data = NULL; |
328 | 0 | size_t b64datalen = 0; |
329 | 0 | size_t off = 0; |
330 | 0 | int ret; |
331 | |
|
332 | 0 | do { |
333 | 0 | *line = '\0'; |
334 | |
|
335 | 0 | if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
336 | 0 | return -1; |
337 | 0 | } |
338 | 0 | } while(strcmp(line, headerbegin) != 0); |
339 | | |
340 | 0 | *line = '\0'; |
341 | |
|
342 | 0 | do { |
343 | 0 | if(*line) { |
344 | 0 | char *tmp; |
345 | 0 | size_t linelen; |
346 | |
|
347 | 0 | linelen = strlen(line); |
348 | 0 | tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
349 | 0 | if(!tmp) { |
350 | 0 | _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
351 | 0 | "Unable to allocate memory for PEM parsing"); |
352 | 0 | ret = -1; |
353 | 0 | goto out; |
354 | 0 | } |
355 | 0 | memcpy(tmp + b64datalen, line, linelen); |
356 | 0 | b64data = tmp; |
357 | 0 | b64datalen += linelen; |
358 | 0 | } |
359 | | |
360 | 0 | *line = '\0'; |
361 | |
|
362 | 0 | if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
363 | 0 | ret = -1; |
364 | 0 | goto out; |
365 | 0 | } |
366 | 0 | } while(strcmp(line, headerend) != 0); |
367 | | |
368 | 0 | if(!b64data) { |
369 | 0 | return -1; |
370 | 0 | } |
371 | | |
372 | 0 | if(_libssh2_base64_decode(session, (char **) data, datalen, |
373 | 0 | b64data, b64datalen)) { |
374 | 0 | ret = -1; |
375 | 0 | goto out; |
376 | 0 | } |
377 | | |
378 | 0 | ret = 0; |
379 | 0 | out: |
380 | 0 | if(b64data) { |
381 | 0 | _libssh2_explicit_zero(b64data, b64datalen); |
382 | 0 | LIBSSH2_FREE(session, b64data); |
383 | 0 | } |
384 | 0 | return ret; |
385 | 0 | } |
386 | | |
387 | | /* OpenSSH formatted keys */ |
388 | 0 | #define AUTH_MAGIC "openssh-key-v1" |
389 | 0 | #define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" |
390 | 0 | #define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" |
391 | | |
392 | | static int |
393 | | _libssh2_openssh_pem_parse_data(LIBSSH2_SESSION * session, |
394 | | const unsigned char *passphrase, |
395 | | const char *b64data, size_t b64datalen, |
396 | | struct string_buf **decrypted_buf) |
397 | 0 | { |
398 | 0 | const LIBSSH2_CRYPT_METHOD *method = NULL; |
399 | 0 | struct string_buf decoded, decrypted, kdf_buf; |
400 | 0 | unsigned char *ciphername = NULL; |
401 | 0 | unsigned char *kdfname = NULL; |
402 | 0 | unsigned char *kdf = NULL; |
403 | 0 | unsigned char *buf = NULL; |
404 | 0 | unsigned char *salt = NULL; |
405 | 0 | uint32_t nkeys, check1, check2; |
406 | 0 | uint32_t rounds = 0; |
407 | 0 | unsigned char *key = NULL; |
408 | 0 | unsigned char *key_part = NULL; |
409 | 0 | unsigned char *iv_part = NULL; |
410 | 0 | unsigned char *f = NULL; |
411 | 0 | size_t f_len = 0; |
412 | 0 | int ret = 0, keylen = 0, ivlen = 0, total_len = 0; |
413 | 0 | size_t kdf_len = 0, tmp_len = 0, salt_len = 0; |
414 | |
|
415 | 0 | if(decrypted_buf) |
416 | 0 | *decrypted_buf = NULL; |
417 | | |
418 | | /* decode file */ |
419 | 0 | if(_libssh2_base64_decode(session, (char **)&f, &f_len, |
420 | 0 | b64data, b64datalen)) { |
421 | 0 | ret = -1; |
422 | 0 | goto out; |
423 | 0 | } |
424 | | |
425 | | /* Parse the file */ |
426 | 0 | decoded.data = (unsigned char *)f; |
427 | 0 | decoded.dataptr = (unsigned char *)f; |
428 | 0 | decoded.len = f_len; |
429 | |
|
430 | 0 | if(decoded.len < strlen(AUTH_MAGIC)) { |
431 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, "key too short"); |
432 | 0 | goto out; |
433 | 0 | } |
434 | | |
435 | 0 | if(strncmp((const char *) decoded.dataptr, AUTH_MAGIC, |
436 | 0 | strlen(AUTH_MAGIC)) != 0) { |
437 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
438 | 0 | "key auth magic mismatch"); |
439 | 0 | goto out; |
440 | 0 | } |
441 | | |
442 | 0 | decoded.dataptr += strlen(AUTH_MAGIC) + 1; |
443 | |
|
444 | 0 | if(_libssh2_get_string(&decoded, &ciphername, &tmp_len) || |
445 | 0 | tmp_len == 0) { |
446 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
447 | 0 | "ciphername is missing"); |
448 | 0 | goto out; |
449 | 0 | } |
450 | | |
451 | 0 | if(_libssh2_get_string(&decoded, &kdfname, &tmp_len) || |
452 | 0 | tmp_len == 0) { |
453 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
454 | 0 | "kdfname is missing"); |
455 | 0 | goto out; |
456 | 0 | } |
457 | | |
458 | 0 | if(_libssh2_get_string(&decoded, &kdf, &kdf_len)) { |
459 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
460 | 0 | "kdf is missing"); |
461 | 0 | goto out; |
462 | 0 | } |
463 | 0 | else { |
464 | 0 | kdf_buf.data = kdf; |
465 | 0 | kdf_buf.dataptr = kdf; |
466 | 0 | kdf_buf.len = kdf_len; |
467 | 0 | } |
468 | | |
469 | 0 | if((!passphrase || strlen((const char *)passphrase) == 0) && |
470 | 0 | strcmp((const char *)ciphername, "none") != 0) { |
471 | | /* passphrase required */ |
472 | 0 | ret = LIBSSH2_ERROR_KEYFILE_AUTH_FAILED; |
473 | 0 | goto out; |
474 | 0 | } |
475 | | |
476 | 0 | if(strcmp((const char *)kdfname, "none") != 0 && |
477 | 0 | strcmp((const char *)kdfname, "bcrypt") != 0) { |
478 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
479 | 0 | "unknown cipher"); |
480 | 0 | goto out; |
481 | 0 | } |
482 | | |
483 | 0 | if(!strcmp((const char *)kdfname, "none") && |
484 | 0 | strcmp((const char *)ciphername, "none") != 0) { |
485 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
486 | 0 | "invalid format"); |
487 | 0 | goto out; |
488 | 0 | } |
489 | | |
490 | 0 | if(_libssh2_get_u32(&decoded, &nkeys) != 0 || nkeys != 1) { |
491 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
492 | 0 | "Multiple keys are unsupported"); |
493 | 0 | goto out; |
494 | 0 | } |
495 | | |
496 | | /* unencrypted public key */ |
497 | | |
498 | 0 | if(_libssh2_get_string(&decoded, &buf, &tmp_len) || tmp_len == 0) { |
499 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
500 | 0 | "Invalid private key; " |
501 | 0 | "expect embedded public key"); |
502 | 0 | goto out; |
503 | 0 | } |
504 | | |
505 | 0 | if(_libssh2_get_string(&decoded, &buf, &tmp_len) || tmp_len == 0) { |
506 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
507 | 0 | "Private key data not found"); |
508 | 0 | goto out; |
509 | 0 | } |
510 | | |
511 | | /* decode encrypted private key */ |
512 | 0 | decrypted.data = decrypted.dataptr = buf; |
513 | 0 | decrypted.len = tmp_len; |
514 | |
|
515 | 0 | if(ciphername && strcmp((const char *)ciphername, "none") != 0) { |
516 | 0 | const LIBSSH2_CRYPT_METHOD **all_methods, *cur_method; |
517 | |
|
518 | 0 | all_methods = libssh2_crypt_methods(); |
519 | | /* !checksrc! disable EQUALSNULL 1 */ |
520 | 0 | while((cur_method = *all_methods++) != NULL) { |
521 | 0 | if(*cur_method->name && |
522 | 0 | memcmp(ciphername, cur_method->name, |
523 | 0 | strlen(cur_method->name)) == 0) { |
524 | 0 | method = cur_method; |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | | /* None of the available crypt methods were able to decrypt the key */ |
529 | |
|
530 | 0 | if(!method) { |
531 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
532 | 0 | "No supported cipher found"); |
533 | 0 | goto out; |
534 | 0 | } |
535 | 0 | } |
536 | | |
537 | 0 | if(method) { |
538 | 0 | int free_iv = 0, free_secret = 0, len_decrypted = 0; |
539 | 0 | int blocksize; |
540 | 0 | void *abstract = NULL; |
541 | |
|
542 | 0 | keylen = method->secret_len; |
543 | 0 | ivlen = method->iv_len; |
544 | 0 | total_len = keylen + ivlen; |
545 | |
|
546 | 0 | key = LIBSSH2_CALLOC(session, total_len); |
547 | 0 | if(!key) { |
548 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
549 | 0 | "Could not alloc key"); |
550 | 0 | goto out; |
551 | 0 | } |
552 | | |
553 | 0 | if(strcmp((const char *)kdfname, "bcrypt") == 0 && passphrase) { |
554 | 0 | if((_libssh2_get_string(&kdf_buf, &salt, &salt_len)) || |
555 | 0 | (_libssh2_get_u32(&kdf_buf, &rounds) != 0)) { |
556 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
557 | 0 | "kdf contains unexpected values"); |
558 | 0 | goto out; |
559 | 0 | } |
560 | | |
561 | 0 | if(_libssh2_bcrypt_pbkdf((const char *)passphrase, |
562 | 0 | strlen((const char *)passphrase), |
563 | 0 | salt, salt_len, key, |
564 | 0 | keylen + ivlen, rounds) < 0) { |
565 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_DECRYPT, |
566 | 0 | "invalid format"); |
567 | 0 | goto out; |
568 | 0 | } |
569 | 0 | } |
570 | 0 | else { |
571 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_KEYFILE_AUTH_FAILED, |
572 | 0 | "bcrypted without passphrase"); |
573 | 0 | goto out; |
574 | 0 | } |
575 | | |
576 | | /* Set up decryption */ |
577 | 0 | blocksize = method->blocksize; |
578 | |
|
579 | 0 | key_part = LIBSSH2_CALLOC(session, keylen); |
580 | 0 | if(!key_part) { |
581 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
582 | 0 | "Could not alloc key part"); |
583 | 0 | goto out; |
584 | 0 | } |
585 | | |
586 | 0 | iv_part = LIBSSH2_CALLOC(session, ivlen); |
587 | 0 | if(!iv_part) { |
588 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
589 | 0 | "Could not alloc iv part"); |
590 | 0 | goto out; |
591 | 0 | } |
592 | | |
593 | 0 | memcpy(key_part, key, keylen); |
594 | 0 | memcpy(iv_part, key + keylen, ivlen); |
595 | | |
596 | | /* Initialize the decryption */ |
597 | 0 | if(method->init(session, method, iv_part, &free_iv, key_part, |
598 | 0 | &free_secret, 0, &abstract)) { |
599 | 0 | ret = LIBSSH2_ERROR_DECRYPT; |
600 | 0 | goto out; |
601 | 0 | } |
602 | | |
603 | | /* Do the actual decryption */ |
604 | 0 | if((decrypted.len % blocksize) != 0) { |
605 | 0 | method->dtor(session, &abstract); |
606 | 0 | ret = LIBSSH2_ERROR_DECRYPT; |
607 | 0 | goto out; |
608 | 0 | } |
609 | | |
610 | 0 | if(method->flags & LIBSSH2_CRYPT_FLAG_REQUIRES_FULL_PACKET) { |
611 | 0 | if(method->crypt(session, 0, decrypted.data, |
612 | 0 | decrypted.len, |
613 | 0 | &abstract, |
614 | 0 | MIDDLE_BLOCK)) { |
615 | 0 | ret = LIBSSH2_ERROR_DECRYPT; |
616 | 0 | method->dtor(session, &abstract); |
617 | 0 | goto out; |
618 | 0 | } |
619 | 0 | } |
620 | 0 | else { |
621 | 0 | while((size_t)len_decrypted <= decrypted.len - blocksize) { |
622 | | /* We always pass MIDDLE_BLOCK here because OpenSSH Key Files |
623 | | * do not use AAD to authenticate the length. |
624 | | * Furthermore, the authentication tag is appended after the |
625 | | * encrypted key, and the length of the authentication tag is |
626 | | * not included in the key length, so we check it after the |
627 | | * loop. |
628 | | */ |
629 | 0 | if(method->crypt(session, 0, decrypted.data + len_decrypted, |
630 | 0 | blocksize, |
631 | 0 | &abstract, |
632 | 0 | MIDDLE_BLOCK)) { |
633 | 0 | ret = LIBSSH2_ERROR_DECRYPT; |
634 | 0 | method->dtor(session, &abstract); |
635 | 0 | goto out; |
636 | 0 | } |
637 | | |
638 | 0 | len_decrypted += blocksize; |
639 | 0 | } |
640 | | |
641 | | /* No padding */ |
642 | | |
643 | | /* for the AES GCM methods, the 16 byte authentication tag is |
644 | | * appended to the encrypted key */ |
645 | 0 | if(strcmp(method->name, "aes256-gcm@openssh.com") == 0 || |
646 | 0 | strcmp(method->name, "aes128-gcm@openssh.com") == 0) { |
647 | 0 | if(!_libssh2_check_length(&decoded, 16)) { |
648 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
649 | 0 | "GCM auth tag missing"); |
650 | 0 | method->dtor(session, &abstract); |
651 | 0 | goto out; |
652 | 0 | } |
653 | 0 | if(method->crypt(session, 0, decoded.dataptr, 16, &abstract, |
654 | 0 | LAST_BLOCK)) { |
655 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_DECRYPT, |
656 | 0 | "GCM auth tag invalid"); |
657 | 0 | method->dtor(session, &abstract); |
658 | 0 | goto out; |
659 | 0 | } |
660 | 0 | decoded.dataptr += 16; |
661 | 0 | } |
662 | 0 | } |
663 | | |
664 | 0 | method->dtor(session, &abstract); |
665 | 0 | } |
666 | | |
667 | | /* Check random bytes match */ |
668 | | |
669 | 0 | if(_libssh2_get_u32(&decrypted, &check1) != 0 || |
670 | 0 | _libssh2_get_u32(&decrypted, &check2) != 0 || |
671 | 0 | check1 != check2) { |
672 | 0 | _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
673 | 0 | "Private key unpack failed (correct password?)"); |
674 | 0 | ret = LIBSSH2_ERROR_KEYFILE_AUTH_FAILED; |
675 | 0 | goto out; |
676 | 0 | } |
677 | | |
678 | 0 | if(decrypted_buf) { |
679 | | /* copy data to out-going buffer */ |
680 | 0 | struct string_buf *out_buf = _libssh2_string_buf_new(session); |
681 | 0 | if(!out_buf) { |
682 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
683 | 0 | "Unable to allocate memory for " |
684 | 0 | "decrypted struct"); |
685 | 0 | goto out; |
686 | 0 | } |
687 | | |
688 | 0 | out_buf->data = LIBSSH2_CALLOC(session, decrypted.len); |
689 | 0 | if(!out_buf->data) { |
690 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
691 | 0 | "Unable to allocate memory for " |
692 | 0 | "decrypted struct"); |
693 | 0 | _libssh2_string_buf_free(session, out_buf); |
694 | 0 | goto out; |
695 | 0 | } |
696 | 0 | memcpy(out_buf->data, decrypted.data, decrypted.len); |
697 | 0 | out_buf->dataptr = out_buf->data + |
698 | 0 | (decrypted.dataptr - decrypted.data); |
699 | 0 | out_buf->len = decrypted.len; |
700 | |
|
701 | 0 | *decrypted_buf = out_buf; |
702 | 0 | } |
703 | | |
704 | 0 | out: |
705 | | |
706 | | /* Clean up */ |
707 | 0 | if(key) { |
708 | 0 | _libssh2_explicit_zero(key, total_len); |
709 | 0 | LIBSSH2_FREE(session, key); |
710 | 0 | } |
711 | 0 | if(key_part) { |
712 | 0 | _libssh2_explicit_zero(key_part, keylen); |
713 | 0 | LIBSSH2_FREE(session, key_part); |
714 | 0 | } |
715 | 0 | if(iv_part) { |
716 | 0 | _libssh2_explicit_zero(iv_part, ivlen); |
717 | 0 | LIBSSH2_FREE(session, iv_part); |
718 | 0 | } |
719 | 0 | if(f) { |
720 | 0 | _libssh2_explicit_zero(f, f_len); |
721 | 0 | LIBSSH2_FREE(session, f); |
722 | 0 | } |
723 | |
|
724 | 0 | return ret; |
725 | 0 | } |
726 | | |
727 | | int |
728 | | _libssh2_openssh_pem_parse(LIBSSH2_SESSION * session, |
729 | | const unsigned char *passphrase, |
730 | | FILE * fp, struct string_buf **decrypted_buf) |
731 | 0 | { |
732 | 0 | char line[LINE_SIZE]; |
733 | 0 | char *b64data = NULL; |
734 | 0 | size_t b64datalen = 0; |
735 | 0 | int ret = 0; |
736 | | |
737 | | /* read file */ |
738 | |
|
739 | 0 | do { |
740 | 0 | *line = '\0'; |
741 | |
|
742 | 0 | if(readline(line, LINE_SIZE, fp)) { |
743 | 0 | return -1; |
744 | 0 | } |
745 | 0 | } while(strcmp(line, OPENSSH_HEADER_BEGIN) != 0); |
746 | | |
747 | 0 | if(readline(line, LINE_SIZE, fp)) { |
748 | 0 | return -1; |
749 | 0 | } |
750 | | |
751 | 0 | do { |
752 | 0 | if(*line) { |
753 | 0 | char *tmp; |
754 | 0 | size_t linelen; |
755 | |
|
756 | 0 | linelen = strlen(line); |
757 | 0 | tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
758 | 0 | if(!tmp) { |
759 | 0 | _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
760 | 0 | "Unable to allocate memory for PEM parsing"); |
761 | 0 | ret = -1; |
762 | 0 | goto out; |
763 | 0 | } |
764 | 0 | memcpy(tmp + b64datalen, line, linelen); |
765 | 0 | b64data = tmp; |
766 | 0 | b64datalen += linelen; |
767 | 0 | } |
768 | | |
769 | 0 | *line = '\0'; |
770 | |
|
771 | 0 | if(readline(line, LINE_SIZE, fp)) { |
772 | 0 | ret = -1; |
773 | 0 | goto out; |
774 | 0 | } |
775 | 0 | } while(strcmp(line, OPENSSH_HEADER_END) != 0); |
776 | | |
777 | 0 | if(!b64data) { |
778 | 0 | return -1; |
779 | 0 | } |
780 | | |
781 | 0 | ret = _libssh2_openssh_pem_parse_data(session, |
782 | 0 | passphrase, |
783 | 0 | (const char *)b64data, |
784 | 0 | b64datalen, |
785 | 0 | decrypted_buf); |
786 | |
|
787 | 0 | if(b64data) { |
788 | 0 | _libssh2_explicit_zero(b64data, b64datalen); |
789 | 0 | LIBSSH2_FREE(session, b64data); |
790 | 0 | } |
791 | |
|
792 | 0 | out: |
793 | |
|
794 | 0 | return ret; |
795 | 0 | } |
796 | | |
797 | | int |
798 | | _libssh2_openssh_pem_parse_memory(LIBSSH2_SESSION * session, |
799 | | const unsigned char *passphrase, |
800 | | const char *filedata, size_t filedata_len, |
801 | | struct string_buf **decrypted_buf) |
802 | 0 | { |
803 | 0 | char line[LINE_SIZE]; |
804 | 0 | char *b64data = NULL; |
805 | 0 | size_t b64datalen = 0; |
806 | 0 | size_t off = 0; |
807 | 0 | int ret; |
808 | |
|
809 | 0 | if(!filedata || filedata_len <= 0) |
810 | 0 | return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
811 | 0 | "Error parsing PEM: filedata missing"); |
812 | | |
813 | 0 | do { |
814 | |
|
815 | 0 | *line = '\0'; |
816 | |
|
817 | 0 | if(off >= filedata_len) |
818 | 0 | return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
819 | 0 | "Error parsing PEM: " |
820 | 0 | "OpenSSH header not found"); |
821 | | |
822 | 0 | if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
823 | 0 | return -1; |
824 | 0 | } |
825 | 0 | } while(strcmp(line, OPENSSH_HEADER_BEGIN) != 0); |
826 | | |
827 | 0 | *line = '\0'; |
828 | |
|
829 | 0 | do { |
830 | 0 | if(*line) { |
831 | 0 | char *tmp; |
832 | 0 | size_t linelen; |
833 | |
|
834 | 0 | linelen = strlen(line); |
835 | 0 | tmp = LIBSSH2_REALLOC(session, b64data, b64datalen + linelen); |
836 | 0 | if(!tmp) { |
837 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, |
838 | 0 | "Unable to allocate memory for " |
839 | 0 | "PEM parsing"); |
840 | 0 | goto out; |
841 | 0 | } |
842 | 0 | memcpy(tmp + b64datalen, line, linelen); |
843 | 0 | b64data = tmp; |
844 | 0 | b64datalen += linelen; |
845 | 0 | } |
846 | | |
847 | 0 | *line = '\0'; |
848 | |
|
849 | 0 | if(off >= filedata_len) { |
850 | 0 | ret = _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
851 | 0 | "Error parsing PEM: offset out of bounds"); |
852 | 0 | goto out; |
853 | 0 | } |
854 | | |
855 | 0 | if(readline_memory(line, LINE_SIZE, filedata, filedata_len, &off)) { |
856 | 0 | ret = -1; |
857 | 0 | goto out; |
858 | 0 | } |
859 | 0 | } while(strcmp(line, OPENSSH_HEADER_END) != 0); |
860 | | |
861 | 0 | if(!b64data) |
862 | 0 | return _libssh2_error(session, LIBSSH2_ERROR_PROTO, |
863 | 0 | "Error parsing PEM: base 64 data missing"); |
864 | | |
865 | 0 | ret = _libssh2_openssh_pem_parse_data(session, passphrase, b64data, |
866 | 0 | b64datalen, decrypted_buf); |
867 | |
|
868 | 0 | out: |
869 | 0 | if(b64data) { |
870 | 0 | _libssh2_explicit_zero(b64data, b64datalen); |
871 | 0 | LIBSSH2_FREE(session, b64data); |
872 | 0 | } |
873 | 0 | return ret; |
874 | |
|
875 | 0 | } |
876 | | |
877 | | static int |
878 | | read_asn1_length(const unsigned char *data, |
879 | | size_t datalen, size_t *len) |
880 | 0 | { |
881 | 0 | unsigned int lenlen; |
882 | 0 | int nextpos; |
883 | |
|
884 | 0 | if(datalen < 1) { |
885 | 0 | return -1; |
886 | 0 | } |
887 | 0 | *len = data[0]; |
888 | |
|
889 | 0 | if(*len >= 0x80) { |
890 | 0 | lenlen = *len & 0x7F; |
891 | 0 | *len = data[1]; |
892 | 0 | if(1 + lenlen > datalen) { |
893 | 0 | return -1; |
894 | 0 | } |
895 | 0 | if(lenlen > 1) { |
896 | 0 | *len <<= 8; |
897 | 0 | *len |= data[2]; |
898 | 0 | } |
899 | 0 | } |
900 | 0 | else { |
901 | 0 | lenlen = 0; |
902 | 0 | } |
903 | | |
904 | 0 | nextpos = 1 + lenlen; |
905 | 0 | if(lenlen > 2 || 1 + lenlen + *len > datalen) { |
906 | 0 | return -1; |
907 | 0 | } |
908 | | |
909 | 0 | return nextpos; |
910 | 0 | } |
911 | | |
912 | | int |
913 | | _libssh2_pem_decode_sequence(unsigned char **data, size_t *datalen) |
914 | 0 | { |
915 | 0 | size_t len; |
916 | 0 | int lenlen; |
917 | |
|
918 | 0 | if(*datalen < 1) { |
919 | 0 | return -1; |
920 | 0 | } |
921 | | |
922 | 0 | if((*data)[0] != '\x30') { |
923 | 0 | return -1; |
924 | 0 | } |
925 | | |
926 | 0 | (*data)++; |
927 | 0 | (*datalen)--; |
928 | |
|
929 | 0 | lenlen = read_asn1_length(*data, *datalen, &len); |
930 | 0 | if(lenlen < 0 || lenlen + len != *datalen) { |
931 | 0 | return -1; |
932 | 0 | } |
933 | | |
934 | 0 | *data += lenlen; |
935 | 0 | *datalen -= lenlen; |
936 | |
|
937 | 0 | return 0; |
938 | 0 | } |
939 | | |
940 | | int |
941 | | _libssh2_pem_decode_integer(unsigned char **data, size_t *datalen, |
942 | | unsigned char **i, unsigned int *ilen) |
943 | 0 | { |
944 | 0 | size_t len; |
945 | 0 | int lenlen; |
946 | |
|
947 | 0 | if(*datalen < 1) { |
948 | 0 | return -1; |
949 | 0 | } |
950 | | |
951 | 0 | if((*data)[0] != '\x02') { |
952 | 0 | return -1; |
953 | 0 | } |
954 | | |
955 | 0 | (*data)++; |
956 | 0 | (*datalen)--; |
957 | |
|
958 | 0 | lenlen = read_asn1_length(*data, *datalen, &len); |
959 | 0 | if(lenlen < 0 || lenlen + len > *datalen) { |
960 | 0 | return -1; |
961 | 0 | } |
962 | | |
963 | 0 | *data += lenlen; |
964 | 0 | *datalen -= lenlen; |
965 | |
|
966 | 0 | *i = *data; |
967 | 0 | *ilen = (unsigned int)len; |
968 | |
|
969 | 0 | *data += len; |
970 | 0 | *datalen -= len; |
971 | |
|
972 | 0 | return 0; |
973 | 0 | } |