/src/libssh/src/pki_container_openssh.c
Line | Count | Source |
1 | | /* |
2 | | * pki_container_openssh.c |
3 | | * This file is part of the SSH Library |
4 | | * |
5 | | * Copyright (c) 2013,2014 Aris Adamantiadis <aris@badcode.be> |
6 | | * |
7 | | * The SSH Library is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation; either version 2.1 of the License, or (at your |
10 | | * option) any later version. |
11 | | * |
12 | | * The SSH Library is distributed in the hope that it will be useful, but |
13 | | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
14 | | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
15 | | * License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with the SSH Library; see the file COPYING. If not, write to |
19 | | * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
20 | | * MA 02111-1307, USA. |
21 | | */ |
22 | | |
23 | | /** |
24 | | * @ingroup libssh_pki |
25 | | * * |
26 | | * @{ |
27 | | */ |
28 | | |
29 | | #include "config.h" |
30 | | |
31 | | #include <ctype.h> |
32 | | #include <string.h> |
33 | | #include <stdbool.h> |
34 | | |
35 | | #include "libssh/libssh.h" |
36 | | #include "libssh/priv.h" |
37 | | #include "libssh/pki.h" |
38 | | #include "libssh/pki_priv.h" |
39 | | #include "libssh/buffer.h" |
40 | | |
41 | | |
42 | | /** |
43 | | * @internal |
44 | | * |
45 | | * @brief Import a private key from a ssh buffer. |
46 | | * |
47 | | * @param[in] key_blob_buffer The key blob to import as specified in |
48 | | * key.c:key_private_serialize in OpenSSH source |
49 | | * code. |
50 | | * |
51 | | * @param[out] pkey A pointer where the allocated key can be stored. You |
52 | | * need to free the memory using ssh_key_free(). |
53 | | * |
54 | | * @return SSH_OK on success, SSH_ERROR on error. |
55 | | * |
56 | | * @see ssh_key_free() |
57 | | */ |
58 | | static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, |
59 | | ssh_key *pkey) |
60 | 0 | { |
61 | 0 | enum ssh_keytypes_e type; |
62 | 0 | char *type_s = NULL; |
63 | 0 | ssh_key key = NULL; |
64 | 0 | int rc; |
65 | |
|
66 | 0 | if (pkey == NULL) { |
67 | 0 | return SSH_ERROR; |
68 | 0 | } |
69 | | |
70 | 0 | rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); |
71 | 0 | if (rc == SSH_ERROR){ |
72 | 0 | SSH_LOG(SSH_LOG_TRACE, "Unpack error"); |
73 | 0 | return SSH_ERROR; |
74 | 0 | } |
75 | | |
76 | 0 | type = ssh_key_type_from_name(type_s); |
77 | 0 | if (type == SSH_KEYTYPE_UNKNOWN) { |
78 | 0 | SSH_LOG(SSH_LOG_TRACE, "Unknown key type '%s' found!", type_s); |
79 | 0 | return SSH_ERROR; |
80 | 0 | } |
81 | 0 | SAFE_FREE(type_s); |
82 | |
|
83 | 0 | rc = pki_import_privkey_buffer(type, key_blob_buffer, &key); |
84 | 0 | if (rc != SSH_OK) { |
85 | 0 | SSH_LOG(SSH_LOG_TRACE, "Failed to read key in OpenSSH format"); |
86 | 0 | goto fail; |
87 | 0 | } |
88 | | |
89 | 0 | *pkey = key; |
90 | 0 | return SSH_OK; |
91 | 0 | fail: |
92 | 0 | ssh_key_free(key); |
93 | |
|
94 | 0 | return SSH_ERROR; |
95 | 0 | } |
96 | | |
97 | | /** |
98 | | * @brief decrypts an encrypted private key blob in OpenSSH format. |
99 | | * |
100 | | */ |
101 | | static int pki_private_key_decrypt(ssh_string blob, |
102 | | const char* passphrase, |
103 | | const char *ciphername, |
104 | | const char *kdfname, |
105 | | ssh_string kdfoptions, |
106 | | ssh_auth_callback auth_fn, |
107 | | void *auth_data) |
108 | 0 | { |
109 | 0 | struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); |
110 | 0 | struct ssh_cipher_struct cipher; |
111 | 0 | uint8_t key_material[128] = {0}; |
112 | 0 | char passphrase_buffer[128] = {0}; |
113 | 0 | size_t key_material_len; |
114 | 0 | ssh_buffer buffer = NULL; |
115 | 0 | ssh_string salt = NULL; |
116 | 0 | uint32_t rounds; |
117 | 0 | int cmp; |
118 | 0 | int rc; |
119 | 0 | int i; |
120 | |
|
121 | 0 | cmp = strcmp(ciphername, "none"); |
122 | 0 | if (cmp == 0){ |
123 | | /* no decryption required */ |
124 | 0 | return SSH_OK; |
125 | 0 | } |
126 | | |
127 | 0 | for (i = 0; ciphers[i].name != NULL; i++) { |
128 | 0 | cmp = strcmp(ciphername, ciphers[i].name); |
129 | 0 | if (cmp == 0){ |
130 | 0 | memcpy(&cipher, &ciphers[i], sizeof(cipher)); |
131 | 0 | break; |
132 | 0 | } |
133 | 0 | } |
134 | |
|
135 | 0 | if (ciphers[i].name == NULL){ |
136 | 0 | SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); |
137 | 0 | return SSH_ERROR; |
138 | 0 | } |
139 | | |
140 | 0 | cmp = strcmp(kdfname, "bcrypt"); |
141 | 0 | if (cmp != 0) { |
142 | 0 | SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); |
143 | 0 | return SSH_ERROR; |
144 | 0 | } |
145 | 0 | if (ssh_string_len(blob) % cipher.blocksize != 0) { |
146 | 0 | SSH_LOG(SSH_LOG_TRACE, |
147 | 0 | "Encrypted string not multiple of blocksize: %zu", |
148 | 0 | ssh_string_len(blob)); |
149 | 0 | return SSH_ERROR; |
150 | 0 | } |
151 | | |
152 | 0 | buffer = ssh_buffer_new(); |
153 | 0 | if (buffer == NULL){ |
154 | 0 | return SSH_ERROR; |
155 | 0 | } |
156 | 0 | rc = ssh_buffer_add_data(buffer, |
157 | 0 | ssh_string_data(kdfoptions), |
158 | 0 | (uint32_t)ssh_string_len(kdfoptions)); |
159 | 0 | if (rc != SSH_ERROR){ |
160 | 0 | rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); |
161 | 0 | } |
162 | 0 | SSH_BUFFER_FREE(buffer); |
163 | 0 | if (rc == SSH_ERROR){ |
164 | 0 | return SSH_ERROR; |
165 | 0 | } |
166 | | |
167 | | /* We need material for key (keysize bits / 8) and IV (blocksize) */ |
168 | 0 | key_material_len = cipher.keysize/8 + cipher.blocksize; |
169 | 0 | if (key_material_len > sizeof(key_material)) { |
170 | 0 | SSH_LOG(SSH_LOG_TRACE, "Key material too big"); |
171 | 0 | return SSH_ERROR; |
172 | 0 | } |
173 | | |
174 | 0 | SSH_LOG(SSH_LOG_DEBUG, |
175 | 0 | "Decryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", |
176 | 0 | cipher.keysize/8, |
177 | 0 | cipher.blocksize, |
178 | 0 | rounds, |
179 | 0 | ssh_string_len(salt)); |
180 | |
|
181 | 0 | if (passphrase == NULL) { |
182 | 0 | if (auth_fn == NULL) { |
183 | 0 | SAFE_FREE(salt); |
184 | 0 | SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); |
185 | 0 | return SSH_ERROR; |
186 | 0 | } |
187 | 0 | rc = auth_fn("Passphrase", |
188 | 0 | passphrase_buffer, |
189 | 0 | sizeof(passphrase_buffer), |
190 | 0 | 0, |
191 | 0 | 0, |
192 | 0 | auth_data); |
193 | 0 | if (rc != SSH_OK) { |
194 | 0 | SAFE_FREE(salt); |
195 | 0 | return SSH_ERROR; |
196 | 0 | } |
197 | 0 | passphrase = passphrase_buffer; |
198 | 0 | } |
199 | | |
200 | 0 | rc = bcrypt_pbkdf(passphrase, |
201 | 0 | strlen(passphrase), |
202 | 0 | ssh_string_data(salt), |
203 | 0 | ssh_string_len(salt), |
204 | 0 | key_material, |
205 | 0 | key_material_len, |
206 | 0 | rounds); |
207 | 0 | SAFE_FREE(salt); |
208 | 0 | if (rc < 0){ |
209 | 0 | return SSH_ERROR; |
210 | 0 | } |
211 | 0 | ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); |
212 | |
|
213 | 0 | cipher.set_decrypt_key(&cipher, |
214 | 0 | key_material, |
215 | 0 | key_material + cipher.keysize/8); |
216 | 0 | cipher.decrypt(&cipher, |
217 | 0 | ssh_string_data(blob), |
218 | 0 | ssh_string_data(blob), |
219 | 0 | ssh_string_len(blob)); |
220 | 0 | ssh_cipher_clear(&cipher); |
221 | 0 | return SSH_OK; |
222 | 0 | } |
223 | | |
224 | | |
225 | | /** @internal |
226 | | * @brief Import a private key in OpenSSH (new) format. This format is |
227 | | * typically used with ed25519 keys but can be used for others. |
228 | | */ |
229 | | static ssh_key |
230 | | ssh_pki_openssh_import(const char *text_key, |
231 | | const char *passphrase, |
232 | | ssh_auth_callback auth_fn, |
233 | | void *auth_data, |
234 | | bool private) |
235 | 363 | { |
236 | 363 | const char *ptr = text_key; |
237 | 363 | const char *end = NULL; |
238 | 363 | char *base64 = NULL; |
239 | 363 | int cmp; |
240 | 363 | int rc; |
241 | 363 | int i; |
242 | 363 | ssh_buffer buffer = NULL, privkey_buffer = NULL; |
243 | 363 | char *magic = NULL, *ciphername = NULL, *kdfname = NULL; |
244 | 363 | uint32_t nkeys = 0, checkint1 = 0, checkint2 = 0xFFFF; |
245 | 363 | ssh_string kdfoptions = NULL; |
246 | 363 | ssh_string pubkey0 = NULL; |
247 | 363 | ssh_string privkeys = NULL; |
248 | 363 | ssh_string comment = NULL; |
249 | 363 | ssh_key key = NULL; |
250 | 363 | uint8_t padding; |
251 | | |
252 | 363 | cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); |
253 | 363 | if (cmp != 0) { |
254 | 0 | SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no header)"); |
255 | 0 | goto out; |
256 | 0 | } |
257 | 363 | ptr += strlen(OPENSSH_HEADER_BEGIN); |
258 | 1.01k | while(ptr[0] != '\0' && !isspace((int)ptr[0])) { |
259 | 647 | ptr++; |
260 | 647 | } |
261 | 363 | end = strstr(ptr, OPENSSH_HEADER_END); |
262 | 363 | if (end == NULL) { |
263 | 12 | SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no footer)"); |
264 | 12 | goto out; |
265 | 12 | } |
266 | 351 | base64 = malloc(end - ptr + 1); |
267 | 351 | if (base64 == NULL) { |
268 | 3 | goto out; |
269 | 3 | } |
270 | 9.10M | for (i = 0; ptr < end; ptr++) { |
271 | 9.10M | if (!isspace((int)ptr[0])) { |
272 | 9.10M | base64[i] = ptr[0]; |
273 | 9.10M | i++; |
274 | 9.10M | } |
275 | 9.10M | } |
276 | 348 | base64[i] = '\0'; |
277 | 348 | buffer = base64_to_bin(base64); |
278 | 348 | SAFE_FREE(base64); |
279 | 348 | if (buffer == NULL) { |
280 | 19 | SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (base64 error)"); |
281 | 19 | goto out; |
282 | 19 | } |
283 | 329 | rc = ssh_buffer_unpack(buffer, "PssSdSS", |
284 | 329 | strlen(OPENSSH_AUTH_MAGIC) + 1, |
285 | 329 | &magic, |
286 | 329 | &ciphername, |
287 | 329 | &kdfname, |
288 | 329 | &kdfoptions, |
289 | 329 | &nkeys, |
290 | 329 | &pubkey0, |
291 | 329 | &privkeys); |
292 | 329 | if (rc == SSH_ERROR) { |
293 | 152 | SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (unpack error)"); |
294 | 152 | goto out; |
295 | 152 | } |
296 | 177 | cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); |
297 | 177 | if (cmp != 0) { |
298 | 135 | SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (bad magic)"); |
299 | 135 | goto out; |
300 | 135 | } |
301 | 42 | SSH_LOG(SSH_LOG_DEBUG, |
302 | 42 | "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %" PRIu32, |
303 | 42 | ciphername, |
304 | 42 | kdfname, |
305 | 42 | nkeys); |
306 | 42 | if (nkeys != 1) { |
307 | 40 | SSH_LOG(SSH_LOG_TRACE, "Opening OpenSSH private key: only 1 key supported (%" PRIu32 " available)", nkeys); |
308 | 40 | goto out; |
309 | 40 | } |
310 | | |
311 | | /* If we are interested only in public key do not progress |
312 | | * to the key decryption later |
313 | | */ |
314 | 2 | if (!private) { |
315 | 2 | rc = ssh_pki_import_pubkey_blob(pubkey0, &key); |
316 | 2 | if (rc != SSH_OK) { |
317 | 2 | SSH_LOG(SSH_LOG_TRACE, "Failed to import public key blob"); |
318 | 2 | } |
319 | | /* in either case we clean up here */ |
320 | 2 | goto out; |
321 | 2 | } |
322 | | |
323 | 0 | rc = pki_private_key_decrypt(privkeys, |
324 | 0 | passphrase, |
325 | 0 | ciphername, |
326 | 0 | kdfname, |
327 | 0 | kdfoptions, |
328 | 0 | auth_fn, |
329 | 0 | auth_data); |
330 | 0 | if (rc == SSH_ERROR) { |
331 | 0 | goto out; |
332 | 0 | } |
333 | | |
334 | 0 | privkey_buffer = ssh_buffer_new(); |
335 | 0 | if (privkey_buffer == NULL) { |
336 | 0 | goto out; |
337 | 0 | } |
338 | | |
339 | 0 | ssh_buffer_set_secure(privkey_buffer); |
340 | 0 | ssh_buffer_add_data(privkey_buffer, |
341 | 0 | ssh_string_data(privkeys), |
342 | 0 | (uint32_t)ssh_string_len(privkeys)); |
343 | |
|
344 | 0 | rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); |
345 | 0 | if (rc == SSH_ERROR || checkint1 != checkint2) { |
346 | 0 | SSH_LOG(SSH_LOG_TRACE, "OpenSSH private key unpack error (correct password?)"); |
347 | 0 | goto out; |
348 | 0 | } |
349 | 0 | rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); |
350 | 0 | if (rc == SSH_ERROR) { |
351 | 0 | goto out; |
352 | 0 | } |
353 | 0 | comment = ssh_buffer_get_ssh_string(privkey_buffer); |
354 | 0 | SAFE_FREE(comment); |
355 | | /* verify that the remaining data is correct padding */ |
356 | 0 | for (i = 1; ssh_buffer_get_len(privkey_buffer) > 0; ++i) { |
357 | 0 | ssh_buffer_get_u8(privkey_buffer, &padding); |
358 | 0 | if (padding != i) { |
359 | 0 | ssh_key_free(key); |
360 | 0 | key = NULL; |
361 | 0 | SSH_LOG(SSH_LOG_TRACE, "Invalid padding"); |
362 | 0 | goto out; |
363 | 0 | } |
364 | 0 | } |
365 | 363 | out: |
366 | 363 | if (buffer != NULL) { |
367 | 329 | SSH_BUFFER_FREE(buffer); |
368 | 329 | buffer = NULL; |
369 | 329 | } |
370 | 363 | if (privkey_buffer != NULL) { |
371 | 0 | SSH_BUFFER_FREE(privkey_buffer); |
372 | 0 | privkey_buffer = NULL; |
373 | 0 | } |
374 | 363 | SAFE_FREE(magic); |
375 | 363 | SAFE_FREE(ciphername); |
376 | 363 | SAFE_FREE(kdfname); |
377 | 363 | SAFE_FREE(kdfoptions); |
378 | 363 | SAFE_FREE(pubkey0); |
379 | 363 | SAFE_FREE(privkeys); |
380 | 363 | return key; |
381 | 0 | } |
382 | | |
383 | | ssh_key ssh_pki_openssh_privkey_import(const char *text_key, |
384 | | const char *passphrase, |
385 | | ssh_auth_callback auth_fn, |
386 | | void *auth_data) |
387 | 0 | { |
388 | 0 | return ssh_pki_openssh_import(text_key, passphrase, auth_fn, auth_data, true); |
389 | 0 | } |
390 | | |
391 | | ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) |
392 | 363 | { |
393 | 363 | return ssh_pki_openssh_import(text_key, NULL, NULL, NULL, false); |
394 | 363 | } |
395 | | |
396 | | |
397 | | /** @internal |
398 | | * @brief encrypts an ed25519 private key blob |
399 | | * |
400 | | */ |
401 | | static int pki_private_key_encrypt(ssh_buffer privkey_buffer, |
402 | | const char* passphrase, |
403 | | const char *ciphername, |
404 | | const char *kdfname, |
405 | | ssh_auth_callback auth_fn, |
406 | | void *auth_data, |
407 | | uint32_t rounds, |
408 | | ssh_string salt) |
409 | 0 | { |
410 | 0 | struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); |
411 | 0 | struct ssh_cipher_struct cipher; |
412 | 0 | uint8_t key_material[128] = {0}; |
413 | 0 | size_t key_material_len; |
414 | 0 | char passphrase_buffer[128] = {0}; |
415 | 0 | int rc; |
416 | 0 | int i; |
417 | 0 | int cmp; |
418 | |
|
419 | 0 | cmp = strcmp(ciphername, "none"); |
420 | 0 | if (cmp == 0){ |
421 | | /* no encryption required */ |
422 | 0 | return SSH_OK; |
423 | 0 | } |
424 | | |
425 | 0 | for (i = 0; ciphers[i].name != NULL; i++) { |
426 | 0 | cmp = strcmp(ciphername, ciphers[i].name); |
427 | 0 | if (cmp == 0){ |
428 | 0 | memcpy(&cipher, &ciphers[i], sizeof(cipher)); |
429 | 0 | break; |
430 | 0 | } |
431 | 0 | } |
432 | |
|
433 | 0 | if (ciphers[i].name == NULL){ |
434 | 0 | SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); |
435 | 0 | return SSH_ERROR; |
436 | 0 | } |
437 | | |
438 | 0 | cmp = strcmp(kdfname, "bcrypt"); |
439 | 0 | if (cmp != 0){ |
440 | 0 | SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); |
441 | 0 | return SSH_ERROR; |
442 | 0 | } |
443 | | /* We need material for key (keysize bits / 8) and IV (blocksize) */ |
444 | 0 | key_material_len = cipher.keysize/8 + cipher.blocksize; |
445 | 0 | if (key_material_len > sizeof(key_material)){ |
446 | 0 | SSH_LOG(SSH_LOG_TRACE, "Key material too big"); |
447 | 0 | return SSH_ERROR; |
448 | 0 | } |
449 | | |
450 | 0 | SSH_LOG(SSH_LOG_DEBUG, "Encryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", |
451 | 0 | cipher.keysize/8, |
452 | 0 | cipher.blocksize, rounds, ssh_string_len(salt)); |
453 | |
|
454 | 0 | if (passphrase == NULL){ |
455 | 0 | if (auth_fn == NULL){ |
456 | 0 | SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); |
457 | 0 | return SSH_ERROR; |
458 | 0 | } |
459 | 0 | rc = auth_fn("Passphrase", |
460 | 0 | passphrase_buffer, |
461 | 0 | sizeof(passphrase_buffer), |
462 | 0 | 0, |
463 | 0 | 0, |
464 | 0 | auth_data); |
465 | 0 | if (rc != SSH_OK){ |
466 | 0 | return SSH_ERROR; |
467 | 0 | } |
468 | 0 | passphrase = passphrase_buffer; |
469 | 0 | } |
470 | | |
471 | 0 | rc = bcrypt_pbkdf(passphrase, |
472 | 0 | strlen(passphrase), |
473 | 0 | ssh_string_data(salt), |
474 | 0 | ssh_string_len(salt), |
475 | 0 | key_material, |
476 | 0 | key_material_len, |
477 | 0 | rounds); |
478 | 0 | if (rc < 0){ |
479 | 0 | return SSH_ERROR; |
480 | 0 | } |
481 | | |
482 | 0 | cipher.set_encrypt_key(&cipher, |
483 | 0 | key_material, |
484 | 0 | key_material + cipher.keysize/8); |
485 | 0 | cipher.encrypt(&cipher, |
486 | 0 | ssh_buffer_get(privkey_buffer), |
487 | 0 | ssh_buffer_get(privkey_buffer), |
488 | 0 | ssh_buffer_get_len(privkey_buffer)); |
489 | 0 | ssh_cipher_clear(&cipher); |
490 | 0 | ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); |
491 | |
|
492 | 0 | return SSH_OK; |
493 | 0 | } |
494 | | |
495 | | |
496 | | /** @internal |
497 | | * generate an OpenSSH private key (defined in PROTOCOL.key) and output it in text format. |
498 | | * @param privkey[in] private key to export |
499 | | * @returns an SSH string containing the text representation of the exported key. |
500 | | * @warning currently only supports ED25519 key types. |
501 | | */ |
502 | | |
503 | | ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, |
504 | | const char *passphrase, |
505 | | ssh_auth_callback auth_fn, |
506 | | void *auth_data) |
507 | 0 | { |
508 | 0 | ssh_buffer buffer = NULL; |
509 | 0 | ssh_string str = NULL, blob = NULL; |
510 | 0 | ssh_string pubkey_s = NULL; |
511 | 0 | ssh_buffer privkey_buffer = NULL; |
512 | 0 | uint32_t rnd; |
513 | 0 | uint32_t rounds = 16; |
514 | 0 | ssh_string salt = NULL; |
515 | 0 | ssh_string kdf_options = NULL; |
516 | 0 | int to_encrypt=0; |
517 | 0 | unsigned char *b64 = NULL; |
518 | 0 | uint32_t str_len, len; |
519 | 0 | uint8_t padding = 1; |
520 | 0 | int ok; |
521 | 0 | int rc; |
522 | |
|
523 | 0 | if (privkey == NULL) { |
524 | 0 | return NULL; |
525 | 0 | } |
526 | 0 | if (passphrase != NULL || auth_fn != NULL){ |
527 | 0 | SSH_LOG(SSH_LOG_DEBUG, "Enabling encryption for private key export"); |
528 | 0 | to_encrypt = 1; |
529 | 0 | } |
530 | 0 | buffer = ssh_buffer_new(); |
531 | 0 | rc = ssh_pki_export_pubkey_blob(privkey, &pubkey_s); |
532 | 0 | if (buffer == NULL || rc != SSH_OK) { |
533 | 0 | goto error; |
534 | 0 | } |
535 | | |
536 | 0 | ok = ssh_get_random(&rnd, sizeof(rnd), 0); |
537 | 0 | if (!ok) { |
538 | 0 | goto error; |
539 | 0 | } |
540 | | |
541 | 0 | privkey_buffer = ssh_buffer_new(); |
542 | 0 | if (privkey_buffer == NULL) { |
543 | 0 | goto error; |
544 | 0 | } |
545 | | |
546 | 0 | rc = ssh_pki_export_privkey_blob(privkey, &blob); |
547 | 0 | if (rc != SSH_OK) { |
548 | 0 | goto error; |
549 | 0 | } |
550 | | |
551 | 0 | rc = ssh_buffer_pack(privkey_buffer, |
552 | 0 | "ddPs", |
553 | 0 | rnd, /* checkint 1 & 2 */ |
554 | 0 | rnd, |
555 | 0 | ssh_string_len(blob), |
556 | 0 | ssh_string_data(blob), |
557 | 0 | "" /* comment */); |
558 | 0 | if (rc == SSH_ERROR){ |
559 | 0 | goto error; |
560 | 0 | } |
561 | | |
562 | | /* Add padding regardless encryption because it is expected |
563 | | * by OpenSSH tools. |
564 | | * XXX Using 16 B as we use only AES cipher below anyway. |
565 | | */ |
566 | 0 | while (ssh_buffer_get_len(privkey_buffer) % 16 != 0) { |
567 | 0 | rc = ssh_buffer_add_u8(privkey_buffer, padding); |
568 | 0 | if (rc < 0) { |
569 | 0 | goto error; |
570 | 0 | } |
571 | 0 | padding++; |
572 | 0 | } |
573 | | |
574 | 0 | if (to_encrypt){ |
575 | 0 | ssh_buffer kdf_buf; |
576 | |
|
577 | 0 | kdf_buf = ssh_buffer_new(); |
578 | 0 | if (kdf_buf == NULL) { |
579 | 0 | goto error; |
580 | 0 | } |
581 | | |
582 | 0 | salt = ssh_string_new(16); |
583 | 0 | if (salt == NULL){ |
584 | 0 | SSH_BUFFER_FREE(kdf_buf); |
585 | 0 | goto error; |
586 | 0 | } |
587 | | |
588 | 0 | ok = ssh_get_random(ssh_string_data(salt), 16, 0); |
589 | 0 | if (!ok) { |
590 | 0 | SSH_BUFFER_FREE(kdf_buf); |
591 | 0 | goto error; |
592 | 0 | } |
593 | | |
594 | 0 | rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); |
595 | 0 | if (rc != SSH_OK) { |
596 | 0 | SSH_BUFFER_FREE(kdf_buf); |
597 | 0 | goto error; |
598 | 0 | } |
599 | 0 | kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); |
600 | 0 | if (kdf_options == NULL){ |
601 | 0 | SSH_BUFFER_FREE(kdf_buf); |
602 | 0 | goto error; |
603 | 0 | } |
604 | 0 | memcpy(ssh_string_data(kdf_options), |
605 | 0 | ssh_buffer_get(kdf_buf), |
606 | 0 | ssh_buffer_get_len(kdf_buf)); |
607 | 0 | SSH_BUFFER_FREE(kdf_buf); |
608 | 0 | rc = pki_private_key_encrypt(privkey_buffer, |
609 | 0 | passphrase, |
610 | 0 | "aes128-cbc", |
611 | 0 | "bcrypt", |
612 | 0 | auth_fn, |
613 | 0 | auth_data, |
614 | 0 | rounds, |
615 | 0 | salt); |
616 | 0 | if (rc != SSH_OK){ |
617 | 0 | goto error; |
618 | 0 | } |
619 | 0 | } else { |
620 | 0 | kdf_options = ssh_string_new(0); |
621 | 0 | } |
622 | | |
623 | 0 | rc = ssh_buffer_pack(buffer, |
624 | 0 | "PssSdSdP", |
625 | 0 | strlen(OPENSSH_AUTH_MAGIC) + 1, |
626 | 0 | OPENSSH_AUTH_MAGIC, |
627 | 0 | to_encrypt ? "aes128-cbc" : "none", /* ciphername */ |
628 | 0 | to_encrypt ? "bcrypt" : "none", /* kdfname */ |
629 | 0 | kdf_options, /* kdfoptions */ |
630 | 0 | (uint32_t)1, /* nkeys */ |
631 | 0 | pubkey_s, |
632 | 0 | ssh_buffer_get_len(privkey_buffer), |
633 | | /* rest of buffer is a string */ |
634 | 0 | (size_t)ssh_buffer_get_len(privkey_buffer), |
635 | 0 | ssh_buffer_get(privkey_buffer)); |
636 | 0 | if (rc != SSH_OK) { |
637 | 0 | goto error; |
638 | 0 | } |
639 | | |
640 | 0 | b64 = bin_to_base64(ssh_buffer_get(buffer), |
641 | 0 | ssh_buffer_get_len(buffer)); |
642 | 0 | if (b64 == NULL){ |
643 | 0 | goto error; |
644 | 0 | } |
645 | | |
646 | | /* we can reuse the buffer */ |
647 | 0 | ssh_buffer_reinit(buffer); |
648 | 0 | rc = ssh_buffer_pack(buffer, |
649 | 0 | "tttttt", |
650 | 0 | OPENSSH_HEADER_BEGIN, |
651 | 0 | "\n", |
652 | 0 | b64, |
653 | 0 | "\n", |
654 | 0 | OPENSSH_HEADER_END, |
655 | 0 | "\n"); |
656 | 0 | ssh_burn(b64, strlen((char *)b64)); |
657 | 0 | SAFE_FREE(b64); |
658 | |
|
659 | 0 | if (rc != SSH_OK){ |
660 | 0 | goto error; |
661 | 0 | } |
662 | | |
663 | 0 | str = ssh_string_new(ssh_buffer_get_len(buffer)); |
664 | 0 | if (str == NULL){ |
665 | 0 | goto error; |
666 | 0 | } |
667 | | |
668 | 0 | str_len = ssh_buffer_get_len(buffer); |
669 | 0 | len = ssh_buffer_get_data(buffer, ssh_string_data(str), str_len); |
670 | 0 | if (str_len != len) { |
671 | 0 | SSH_STRING_FREE(str); |
672 | 0 | str = NULL; |
673 | 0 | } |
674 | |
|
675 | 0 | error: |
676 | 0 | ssh_string_burn(blob); |
677 | 0 | ssh_string_free(blob); |
678 | 0 | if (privkey_buffer != NULL) { |
679 | 0 | void *bufptr = ssh_buffer_get(privkey_buffer); |
680 | 0 | ssh_burn(bufptr, ssh_buffer_get_len(privkey_buffer)); |
681 | 0 | SSH_BUFFER_FREE(privkey_buffer); |
682 | 0 | } |
683 | 0 | SAFE_FREE(pubkey_s); |
684 | 0 | SAFE_FREE(kdf_options); |
685 | 0 | SAFE_FREE(salt); |
686 | 0 | if (buffer != NULL) { |
687 | 0 | SSH_BUFFER_FREE(buffer); |
688 | 0 | } |
689 | |
|
690 | 0 | return str; |
691 | 0 | } |
692 | | |
693 | | |
694 | | /** |
695 | | * @} |
696 | | */ |