ssh_key_decrypt.c
Go to the documentation of this file.
1 /**
2  * @file ssh_key_decrypt.c
3  * @brief SSH private key decryption
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2019-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneSSH Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.4
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL SSH_TRACE_LEVEL
33 
34 //Dependencies
35 #include "ssh/ssh.h"
36 #include "ssh/ssh_key_import.h"
37 #include "ssh/ssh_key_export.h"
38 #include "ssh/ssh_key_format.h"
39 #include "ssh/ssh_key_decrypt.h"
40 #include "ssh/ssh_misc.h"
41 #include "cipher/aes.h"
42 #include "cipher/blowfish.h"
43 #include "cipher_modes/ctr.h"
44 #include "pkix/pem_decrypt.h"
45 #include "debug.h"
46 
47 //Check SSH stack configuration
48 #if (SSH_SUPPORT == ENABLED)
49 
50 
51 /**
52  * @brief SSH private key decryption
53  * @param[in] input Pointer to the encrypted private key (PEM or OpenSSH format)
54  * @param[in] inputLen Length of the encrypted private key
55  * @param[in] password NULL-terminated string containing the password
56  * @param[out] output Pointer to decrypted private key
57  * @param[out] outputLen Length of the decrypted private key
58  * @return Error code
59  **/
60 
61 error_t sshDecryptPrivateKey(const char_t *input, size_t inputLen,
62  const char_t *password, char_t *output, size_t *outputLen)
63 {
64 #if (SSH_ENCRYPTED_KEY_SUPPORT == ENABLED)
65  error_t error;
66  size_t n;
67  size_t length;
68  uint8_t *p;
69  uint8_t *buffer;
70  SshPrivateKeyHeader privateKeyHeader;
71 
72  //Check parameters
73  if(input == NULL || password == NULL || output == NULL || outputLen == NULL)
75 
76  //Retrieve the length of the private key structure
77  error = sshDecodeOpenSshPrivateKeyFile(input, inputLen, NULL, &n);
78 
79  //Check status code
80  if(!error)
81  {
82  //Allocate a memory buffer to hold the private key structure
83  buffer = sshAllocMem(n);
84 
85  //Successful memory allocation?
86  if(buffer != NULL)
87  {
88  //Decode the content of the private key file (OpenSSH format)
89  error = sshDecodeOpenSshPrivateKeyFile(input, inputLen, buffer, &n);
90 
91  //Check status code
92  if(!error)
93  {
94  //Parse private key header
95  error = sshParseOpenSshPrivateKeyHeader(buffer, n,
96  &privateKeyHeader);
97  }
98 
99  //Check status code
100  if(!error)
101  {
102  //Point to the encrypted data
103  p = (uint8_t *) privateKeyHeader.encrypted.value;
104  length = privateKeyHeader.encrypted.length;
105 
106  //Perform decryption operation
107  error = sshDecryptOpenSshPrivateKey(&privateKeyHeader, password,
108  p, p, length);
109  }
110 
111  //Check status code
112  if(!error)
113  {
114  //Point to the output buffer
115  p = (uint8_t *) output;
116  length = 0;
117 
118  //Format private key header
120  }
121 
122  //Check status code
123  if(!error)
124  {
125  //Point to the next field
126  p += n;
127  length += n;
128 
129  //Format 'publickey' field
130  error = sshFormatBinaryString(privateKeyHeader.publicKey.value,
131  privateKeyHeader.publicKey.length, p, &n);
132  }
133 
134  //Check status code
135  if(!error)
136  {
137  //Point to the next field
138  p += n;
139  length += n;
140 
141  //Format 'encrypted' field
142  error = sshFormatBinaryString(privateKeyHeader.encrypted.value,
143  privateKeyHeader.encrypted.length, p, &n);
144  }
145 
146  //Check status code
147  if(!error)
148  {
149  //Point to the next field
150  p += n;
151  length += n;
152 
153  //Convert the private key structure to OpenSSH format
154  error = sshEncodeOpenSshPrivateKeyFile(output, length, output,
155  outputLen);
156  }
157 
158  //Release previously allocated memory
159  sshFreeMem(buffer);
160  }
161  else
162  {
163  //Failed to allocate memory
164  error = ERROR_OUT_OF_MEMORY;
165  }
166  }
167  else
168  {
169  //Decrypt the private key (PEM format)
170  error = pemDecryptPrivateKey(input, inputLen, password, output,
171  outputLen);
172  }
173 
174  //Return status code
175  return error;
176 #else
177  //Encrypted private keys are not supported
178  return ERROR_NOT_IMPLEMENTED;
179 #endif
180 }
181 
182 
183 /**
184  * @brief OpenSSH private key decryption
185  * @param[in] privateKeyHeader Private key header
186  * @param[in] password NULL-terminated string containing the password
187  * @param[in] ciphertext Pointer to the ciphertext data
188  * @param[out] plaintext Pointer to the plaintext data
189  * @param[in] length Total number of data bytes to be decrypted
190  * @return Error code
191  **/
192 
194  const char_t *password, const uint8_t *ciphertext, uint8_t *plaintext,
195  size_t length)
196 {
197  //Check KDF and cipher algorithms
198  if(sshCompareString(&privateKeyHeader->kdfName, "none") &&
199  sshCompareString(&privateKeyHeader->cipherName, "none"))
200  {
201  //The length of the 'encrypted' section must be a multiple of 8
202  if((privateKeyHeader->encrypted.length % 8) != 0)
203  return ERROR_INVALID_SYNTAX;
204 
205  //The key is not encrypted
206  osMemmove(plaintext, ciphertext, length);
207  }
208  else if(sshCompareString(&privateKeyHeader->kdfName, "bcrypt") &&
209  sshCompareString(&privateKeyHeader->cipherName, "aes256-ctr"))
210  {
211 #if (SSH_ENCRYPTED_KEY_SUPPORT == ENABLED)
212  error_t error;
213  uint32_t checkInt1;
214  uint32_t checkInt2;
215  size_t passwordLen;
216  uint8_t k[48];
217  SshKdfOptions kdfOptions;
218  AesContext *aesContext;
219 
220  //Sanity check
221  if(privateKeyHeader->encrypted.length < 8)
222  return ERROR_INVALID_SYNTAX;
223 
224  //The length of the 'encrypted' section must be a multiple of the
225  //block size
226  if((privateKeyHeader->encrypted.length % AES_BLOCK_SIZE) != 0)
228 
229  //Parse KDF options
230  error = sshParseKdfOptions(privateKeyHeader->kdfOptions.value,
231  privateKeyHeader->kdfOptions.length, &kdfOptions);
232  //Any error to report?
233  if(error)
234  return error;
235 
236  //Retrieve the length of the password
237  passwordLen = osStrlen(password);
238 
239  //The KDF is used to derive a key, IV from the passphrase
240  error = sshKdf(password, passwordLen, kdfOptions.salt.value,
241  kdfOptions.salt.length, kdfOptions.rounds, k, 48);
242 
243  //Allocate a memory buffer to hold the AES context
244  aesContext = sshAllocMem(sizeof(AesContext));
245 
246  //Successful memory allocation?
247  if(aesContext != NULL)
248  {
249  //Load encryption key
250  error = aesInit(aesContext, k, 32);
251 
252  //Check status code
253  if(!error)
254  {
255  //Perform CTR decryption
256  error = ctrDecrypt(AES_CIPHER_ALGO, aesContext, 128, k + 32,
257  ciphertext, plaintext, length);
258  }
259 
260  //Check status code
261  if(!error)
262  {
263  //Decode 'checkint' fields
264  checkInt1 = LOAD32BE(plaintext);
265  checkInt2 = LOAD32BE(plaintext + 4);
266 
267  //Before the key is encrypted, a random integer is assigned to both
268  //'checkint' fields so successful decryption can be quickly checked
269  //by verifying that both checkint fields hold the same value
270  if(checkInt1 != checkInt2)
271  {
272  error = ERROR_DECRYPTION_FAILED;
273  }
274  }
275 
276  //Erase cipher context
277  aesDeinit(aesContext);
278  //Release previously allocated memory
279  sshFreeMem(aesContext);
280  }
281  else
282  {
283  //Report an error
284  error = ERROR_OUT_OF_MEMORY;
285  }
286 
287  //Any error to report?
288  if(error)
289  return error;
290 #else
291  //Encrypted private keys are not supported
293 #endif
294  }
295  else
296  {
297  //Unknown KDF or cipher algorithm
299  }
300 
301  //Successful processing
302  return NO_ERROR;
303 }
304 
305 
306 /**
307  * @brief Parse KDF options
308  * @param[in] data Pointer to the KDF options
309  * @param[in] length Length of the KDF options, in bytes
310  * @param[out] kdfOptions Information resulting from the parsing process
311  * @brief
312  **/
313 
314 error_t sshParseKdfOptions(const uint8_t *data, size_t length,
315  SshKdfOptions *kdfOptions)
316 {
317  error_t error;
318 
319  //Decode 'salt' field
320  error = sshParseBinaryString(data, length, &kdfOptions->salt);
321  //Any error to report?
322  if(error)
323  return error;
324 
325  //Point to the next field
326  data += sizeof(uint32_t) + kdfOptions->salt.length;
327  length -= sizeof(uint32_t) + kdfOptions->salt.length;
328 
329  //Malformed KDF options?
330  if(length != sizeof(uint32_t))
331  return ERROR_INVALID_SYNTAX;
332 
333  //Decode 'rounds' fields
334  kdfOptions->rounds = LOAD32BE(data);
335 
336  //Successful processing
337  return NO_ERROR;
338 }
339 
340 
341 /**
342  * @brief Key derivation function
343  * @param[in] password Password
344  * @param[in] passwordLen Length password
345  * @param[in] salt Salt
346  * @param[in] saltLen Length of the salt
347  * @param[in] rounds Iteration count
348  * @param[out] key Derived key
349  * @param[in] keyLen Intended length of the derived key
350  * @return Error code
351  **/
352 
353 error_t sshKdf(const char *password, size_t passwordLen, const uint8_t *salt,
354  size_t saltLen, uint_t rounds, uint8_t *key, size_t keyLen)
355 {
356 #if (SSH_ENCRYPTED_KEY_SUPPORT == ENABLED)
357  error_t error;
358  size_t i;
359  size_t j;
360  size_t k;
361  size_t n;
362  size_t m;
363  uint8_t a[4];
364  uint8_t u[32];
365  uint8_t t[32];
366  uint8_t saltHash[SHA512_DIGEST_SIZE];
367  uint8_t passwordHash[SHA512_DIGEST_SIZE];
368  Sha512Context *sha512Context;
369 
370  //Check parameters
371  if(password == NULL || salt == NULL || key == NULL || keyLen == 0)
373 
374  //The iteration count must be a positive integer
375  if(rounds < 1)
377 
378  //Initialize status code
379  error = NO_ERROR;
380 
381  //Calculate the number of blocks to generate
382  n = (keyLen + sizeof(t) - 1) / sizeof(t);
383  m = (keyLen + n - 1) / n;
384 
385  //Allocate a memory buffer to hold the SHA-512 context
386  sha512Context = sshAllocMem(sizeof(Sha512Context));
387 
388  //Successful memory allocation?
389  if(sha512Context != NULL)
390  {
391  //Digest password
392  sha512Init(sha512Context);
393  sha512Update(sha512Context, password, passwordLen);
394  sha512Final(sha512Context, passwordHash);
395 
396  //For each block of the derived key apply the function F
397  for(i = 1; i <= n && !error; i++)
398  {
399  //Calculate the 4-octet encoding of the integer i (MSB first)
400  STORE32BE(i, a);
401 
402  //Initialize current block
403  osMemset(t, 0, sizeof(t));
404 
405  //Iterate as many times as required
406  for(j = 0; j < rounds && !error; j++)
407  {
408  //First round?
409  if(j == 0)
410  {
411  //Compute U1 = PRF(P, S || INT(i))
412  sha512Init(sha512Context);
413  sha512Update(sha512Context, salt, saltLen);
414  sha512Update(sha512Context, a, sizeof(a));
415  sha512Final(sha512Context, saltHash);
416  }
417  else
418  {
419  //Compute U(j) = PRF(P, U(j-1))
420  sha512Init(sha512Context);
421  sha512Update(sha512Context, u, sizeof(u));
422  sha512Final(sha512Context, saltHash);
423  }
424 
425  //Apply KDF hash function
426  error = sshKdfHash(passwordHash, saltHash, u);
427 
428  //Compute T = U(1) xor U(2) xor ... xor U(c)
429  for(k = 0; k < sizeof(t); k++)
430  {
431  t[k] ^= u[k];
432  }
433  }
434 
435  //Shuffle output bytes
436  for(j = 0; j < m; j++)
437  {
438  k = j * n + i - 1;
439 
440  if(k < keyLen)
441  {
442  key[k] = t[j];
443  }
444  }
445  }
446 
447  //Release previously allocated memory
448  sshFreeMem(sha512Context);
449  }
450  else
451  {
452  //Report an error
453  error = ERROR_OUT_OF_MEMORY;
454  }
455 
456  //Return status code
457  return error;
458 #else
459  //Encrypted private keys are not supported
460  return ERROR_NOT_IMPLEMENTED;
461 #endif
462 }
463 
464 
465 /**
466  * @brief KDF hash function
467  * @param[in] password Password
468  * @param[in] salt Salt
469  * @param[out] output Digest value
470  * @return Error code
471  **/
472 
473 error_t sshKdfHash(uint8_t *password, uint8_t *salt, uint8_t *output)
474 {
475 #if (SSH_ENCRYPTED_KEY_SUPPORT == ENABLED)
476  error_t error;
477  uint_t i;
478  uint32_t temp;
479  BlowfishContext *blowfishContext;
480 
481  //Allocate a memory buffer to hold the Blowfish context
482  blowfishContext = sshAllocMem(sizeof(BlowfishContext));
483 
484  //Successful memory allocation?
485  if(blowfishContext != NULL)
486  {
487  //Initialize Blowfish state
488  error = blowfishInitState(blowfishContext);
489 
490  //Check status code
491  if(!error)
492  {
493  //Perform the first key expansion
494  blowfishExpandKey(blowfishContext, salt, SHA512_DIGEST_SIZE,
495  password, SHA512_DIGEST_SIZE);
496  }
497 
498  //Iterate 64 times
499  for(i = 0; i < 64 && !error; i++)
500  {
501  //Perform key expansion with salt
502  error = blowfishExpandKey(blowfishContext, NULL, 0, salt,
504 
505  //Check status code
506  if(!error)
507  {
508  //Perform key expansion with password
509  error = blowfishExpandKey(blowfishContext, NULL, 0, password,
511  }
512  }
513 
514  //Check status code
515  if(!error)
516  {
517  //Initialize plaintext
518  osMemcpy(output, "OxychromaticBlowfishSwatDynamite", 32);
519 
520  //Repeatedly encrypt the text 64 times
521  for(i = 0; i < 64; i++)
522  {
523  //Perform encryption using Blowfish in ECB mode
524  blowfishEncryptBlock(blowfishContext, output, output);
525  blowfishEncryptBlock(blowfishContext, output + 8, output + 8);
526  blowfishEncryptBlock(blowfishContext, output + 16, output + 16);
527  blowfishEncryptBlock(blowfishContext, output + 24, output + 24);
528  }
529  }
530 
531  //Check status code
532  if(!error)
533  {
534  //Swap 32-bit words
535  for(i = 0; i < 32; i += 4)
536  {
537  temp = output[i + 0];
538  output[i + 0] = output[i + 3];
539  output[i + 3] = temp;
540  temp = output[i + 1];
541  output[i + 1] = output[i + 2];
542  output[i + 2] = temp;
543  }
544  }
545 
546  //Erase Blowfish state
547  blowfishDeinit(blowfishContext);
548  //Release previously allocated memory
549  sshFreeMem(blowfishContext);
550  }
551  else
552  {
553  //Report an error
554  error = ERROR_OUT_OF_MEMORY;
555  }
556 
557  //Return status code
558  return error;
559 #else
560  //Encrypted private keys are not supported
561  return ERROR_NOT_IMPLEMENTED;
562 #endif
563 }
564 
565 #endif
error_t sshFormatOpenSshPrivateKeyHeader(uint8_t *p, size_t *written)
Format private key header (OpenSSH format)
uint8_t a
Definition: ndp.h:411
error_t sshParseKdfOptions(const uint8_t *data, size_t length, SshKdfOptions *kdfOptions)
Parse KDF options.
#define LOAD32BE(p)
Definition: cpu_endian.h:210
error_t sshParseOpenSshPrivateKeyHeader(const uint8_t *data, size_t length, SshPrivateKeyHeader *privateKeyHeader)
Parse private key header (OpenSSH format)
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
@ ERROR_DECRYPTION_FAILED
Definition: error.h:242
uint8_t p
Definition: ndp.h:300
uint8_t t
Definition: lldp_ext_med.h:212
uint8_t data[]
Definition: ethernet.h:222
error_t blowfishInitState(BlowfishContext *context)
Blowfish state initialization.
Definition: blowfish.c:257
error_t ctrDecrypt(const CipherAlgo *cipher, void *context, uint_t m, uint8_t *t, const uint8_t *c, uint8_t *p, size_t length)
CTR decryption.
Definition: ctr.c:122
@ ERROR_OUT_OF_MEMORY
Definition: error.h:63
error_t blowfishExpandKey(BlowfishContext *context, const uint8_t *salt, size_t saltLen, const uint8_t *key, size_t keyLen)
Key expansion.
Definition: blowfish.c:291
#define osStrlen(s)
Definition: os_port.h:165
SSH key file import functions.
Private key header (OpenSSH format)
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1586
SshBinaryString encrypted
AES algorithm context.
Definition: aes.h:58
#define AES_BLOCK_SIZE
Definition: aes.h:43
error_t sshDecryptOpenSshPrivateKey(const SshPrivateKeyHeader *privateKeyHeader, const char_t *password, const uint8_t *ciphertext, uint8_t *plaintext, size_t length)
OpenSSH private key decryption.
size_t length
Definition: ssh_types.h:69
error_t sshFormatBinaryString(const void *value, size_t valueLen, uint8_t *p, size_t *written)
Format a binary string.
Definition: ssh_misc.c:1415
@ ERROR_INVALID_PARAMETER
Invalid parameter.
Definition: error.h:47
#define osMemcpy(dest, src, length)
Definition: os_port.h:141
void blowfishDeinit(BlowfishContext *context)
Release Blowfish context.
Definition: blowfish.c:548
KDF options.
error_t
Error codes.
Definition: error.h:43
AES (Advanced Encryption Standard)
__weak_func error_t aesInit(AesContext *context, const uint8_t *key, size_t keyLen)
Key expansion.
Definition: aes.c:242
SshBinaryString kdfOptions
SHA-512 algorithm context.
Definition: sha512.h:62
error_t sshEncodeOpenSshPrivateKeyFile(const void *input, size_t inputLen, char_t *output, size_t *outputLen)
Encode SSH private key file (OpenSSH format)
uint8_t u
Definition: lldp_ext_med.h:213
Blowfish encryption algorithm.
SSH key formatting.
error_t sshKdfHash(uint8_t *password, uint8_t *salt, uint8_t *output)
KDF hash function.
uint8_t length
Definition: tcp.h:368
error_t sshDecryptPrivateKey(const char_t *input, size_t inputLen, const char_t *password, char_t *output, size_t *outputLen)
SSH private key decryption.
PEM file decryption.
const uint8_t * value
Definition: ssh_types.h:68
void blowfishEncryptBlock(BlowfishContext *context, const uint8_t *input, uint8_t *output)
Encrypt a 8-byte block using Blowfish algorithm.
Definition: blowfish.c:411
__weak_func void aesDeinit(AesContext *context)
Release AES context.
Definition: aes.c:571
char char_t
Definition: compiler_port.h:48
error_t sshDecodeOpenSshPrivateKeyFile(const char_t *input, size_t inputLen, uint8_t *output, size_t *outputLen)
Decode SSH private key file (OpenSSH format)
#define sshFreeMem(p)
Definition: ssh.h:729
SSH private key decryption.
uint8_t m
Definition: ndp.h:304
uint8_t n
SshBinaryString salt
error_t pemDecryptPrivateKey(const char_t *input, size_t inputLen, const char_t *password, char_t *output, size_t *outputLen)
PEM private key decryption.
Definition: pem_decrypt.c:58
Blowfish algorithm context.
Definition: blowfish.h:53
void sha512Final(Sha512Context *context, uint8_t *digest)
Finish the SHA-512 message digest.
void sha512Init(Sha512Context *context)
Initialize SHA-512 message digest context.
SSH helper functions.
#define AES_CIPHER_ALGO
Definition: aes.h:45
@ ERROR_INVALID_SYNTAX
Definition: error.h:68
error_t sshKdf(const char *password, size_t passwordLen, const uint8_t *salt, size_t saltLen, uint_t rounds, uint8_t *key, size_t keyLen)
Key derivation function.
error_t sshParseBinaryString(const uint8_t *p, size_t length, SshBinaryString *string)
Parse a binary string.
Definition: ssh_misc.c:1189
#define sshAllocMem(size)
Definition: ssh.h:724
unsigned int uint_t
Definition: compiler_port.h:50
#define osMemset(p, value, length)
Definition: os_port.h:135
Secure Shell (SSH)
SshBinaryString publicKey
#define STORE32BE(a, p)
Definition: cpu_endian.h:286
#define SHA512_DIGEST_SIZE
Definition: sha512.h:45
Counter(CTR) mode.
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
#define osMemmove(dest, src, length)
Definition: os_port.h:147
SSH key file export functions.
void sha512Update(Sha512Context *context, const void *data, size_t length)
Update the SHA-512 context with a portion of the message being hashed.