ssh_cert_import.c
Go to the documentation of this file.
1 /**
2  * @file ssh_cert_import.c
3  * @brief SSH certificate import functions
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_cert_import.h"
37 #include "ssh/ssh_misc.h"
38 #include "encoding/base64.h"
39 #include "pkc/rsa.h"
40 #include "pkc/dsa.h"
41 #include "ecc/ec.h"
42 #include "ecc/eddsa.h"
43 #include "debug.h"
44 
45 //Check SSH stack configuration
46 #if (SSH_SUPPORT == ENABLED && SSH_CERT_SUPPORT == ENABLED)
47 
48 
49 /**
50  * @brief List of supported certificate types
51  **/
52 
53 static const char_t *const sshCertTypes[] =
54 {
55 #if (SSH_RSA_SIGN_SUPPORT == ENABLED)
56  "ssh-rsa-cert-v01@openssh.com",
57 #endif
58 #if (SSH_DSA_SIGN_SUPPORT == ENABLED)
59  "ssh-dss-cert-v01@openssh.com",
60 #endif
61 #if (SSH_ECDSA_SIGN_SUPPORT == ENABLED && SSH_NISTP256_SUPPORT == ENABLED)
62  "ecdsa-sha2-nistp256-cert-v01@openssh.com",
63 #endif
64 #if (SSH_ECDSA_SIGN_SUPPORT == ENABLED && SSH_NISTP384_SUPPORT == ENABLED)
65  "ecdsa-sha2-nistp384-cert-v01@openssh.com",
66 #endif
67 #if (SSH_ECDSA_SIGN_SUPPORT == ENABLED && SSH_NISTP521_SUPPORT == ENABLED)
68  "ecdsa-sha2-nistp521-cert-v01@openssh.com",
69 #endif
70 #if (SSH_ED25519_SIGN_SUPPORT == ENABLED)
71  "ssh-ed25519-cert-v01@openssh.com",
72 #endif
73 };
74 
75 
76 /**
77  * @brief Import SSH certificate (OpenSSH format)
78  * @param[in] input SSH certificate file to decode
79  * @param[in] inputLen Length of the SSH certificate file to decode
80  * @param[out] output Pointer to the decoded data (optional parameter)
81  * @param[out] outputLen Length of the decoded data
82  * @return Error code
83  **/
84 
85 error_t sshImportCertificate(const char_t *input, size_t inputLen,
86  uint8_t *output, size_t *outputLen)
87 {
88  error_t error;
89  size_t i;
90  size_t j;
91  const char_t *certType;
92 
93  //Retrieve certificate type
94  certType = sshGetCertType(input, inputLen);
95  //Unrecognized certificate type?
96  if(certType == NULL)
97  return ERROR_INVALID_SYNTAX;
98 
99  //Get the length of the identifier string
100  i = osStrlen(certType);
101 
102  //The identifier must be followed by a whitespace character
103  if(input[i] != ' ' && input[i] != '\t')
104  return ERROR_INVALID_SYNTAX;
105 
106  //Skip whitespace characters
107  while(i < inputLen && (input[i] == ' ' || input[i] == '\t'))
108  {
109  i++;
110  }
111 
112  //Point to the certificate
113  j = i;
114 
115  //The certificate may be followed by a whitespace character and a comment
116  while(j < inputLen && (input[j] != ' ' && input[j] != '\t'))
117  {
118  j++;
119  }
120 
121  //The certificate is Base64-encoded
122  error = base64Decode(input + i, j - i, output, outputLen);
123  //Failed to decode the file?
124  if(error)
125  return error;
126 
127  //Sanity check
128  if(*outputLen == 0)
129  return ERROR_INVALID_SYNTAX;
130 
131  //Successful processing
132  return NO_ERROR;
133 }
134 
135 
136 /**
137  * @brief Import an RSA public key from a certificate
138  * @param[in] cert Pointer to the certificate structure
139  * @param[out] publicKey Pointer to the RSA public key
140  * @return Error code
141  **/
142 
144  RsaPublicKey *publicKey)
145 {
146 #if (SSH_RSA_SIGN_SUPPORT == ENABLED)
147  error_t error;
148  uint_t k;
149 
150  //Unexpected key format identifier?
151  if(!sshCompareString(&cert->keyFormatId, "ssh-rsa-cert-v01@openssh.com"))
152  return ERROR_WRONG_IDENTIFIER;
153 
154  //Import RSA public exponent
155  error = mpiImport(&publicKey->e, cert->publicKey.rsaPublicKey.e.value,
157  //Any error to report?
158  if(error)
159  return error;
160 
161  //Import RSA modulus
162  error = mpiImport(&publicKey->n, cert->publicKey.rsaPublicKey.n.value,
164  //Any error to report?
165  if(error)
166  return error;
167 
168  //Get the length of the modulus, in bits
169  k = mpiGetBitLength(&publicKey->n);
170 
171  //Applications should enforce minimum and maximum key sizes
172  if(k < SSH_MIN_RSA_MODULUS_SIZE || k > SSH_MAX_RSA_MODULUS_SIZE)
174 
175  //Successful processing
176  return NO_ERROR;
177 #else
178  //Not implemented
179  return ERROR_NOT_IMPLEMENTED;
180 #endif
181 }
182 
183 
184 /**
185  * @brief Import a DSA public key from a certificate
186  * @param[in] cert Pointer to the certificate structure
187  * @param[out] publicKey Pointer to the DSA public key
188  * @return Error code
189  **/
190 
192  DsaPublicKey *publicKey)
193 {
194 #if (SSH_DSA_SIGN_SUPPORT == ENABLED)
195  error_t error;
196  size_t k;
197 
198  //Unexpected key format identifier?
199  if(!sshCompareString(&cert->keyFormatId, "ssh-dss-cert-v01@openssh.com"))
200  return ERROR_WRONG_IDENTIFIER;
201 
202  //Import DSA prime modulus
203  error = mpiImport(&publicKey->params.p, cert->publicKey.dsaPublicKey.p.value,
205  //Any error to report?
206  if(error)
207  return error;
208 
209  //Import DSA group order
210  error = mpiImport(&publicKey->params.q, cert->publicKey.dsaPublicKey.q.value,
212  //Any error to report?
213  if(error)
214  return error;
215 
216  //Import DSA group generator
217  error = mpiImport(&publicKey->params.g, cert->publicKey.dsaPublicKey.g.value,
219  //Any error to report?
220  if(error)
221  return error;
222 
223  //Import DSA public key value
224  error = mpiImport(&publicKey->y, cert->publicKey.dsaPublicKey.y.value,
226  //Any error to report?
227  if(error)
228  return error;
229 
230  //Get the length of the modulus, in bits
231  k = mpiGetBitLength(&publicKey->params.p);
232 
233  //Applications should enforce minimum and maximum key sizes
234  if(k < SSH_MIN_DSA_MODULUS_SIZE || k > SSH_MAX_DSA_MODULUS_SIZE)
236 
237  //Successful processing
238  return NO_ERROR;
239 #else
240  //Not implemented
241  return ERROR_NOT_IMPLEMENTED;
242 #endif
243 }
244 
245 
246 /**
247  * @brief Import an ECDSA public key from a certificate
248  * @param[in] cert Pointer to the certificate structure
249  * @param[out] params EC domain parameters
250  * @param[out] publicKey Pointer to the ECDSA public key
251  * @return Error code
252  **/
253 
255  EcDomainParameters *params, EcPublicKey *publicKey)
256 {
257 #if (SSH_ECDSA_SIGN_SUPPORT == ENABLED)
258  error_t error;
259  const EcCurveInfo *curveInfo;
260 
261  //Check key format identifier
262  if(sshCompareString(&cert->keyFormatId, "ecdsa-sha2-nistp256-cert-v01@openssh.com") ||
263  sshCompareString(&cert->keyFormatId, "ecdsa-sha2-nistp384-cert-v01@openssh.com") ||
264  sshCompareString(&cert->keyFormatId, "ecdsa-sha2-nistp521-cert-v01@openssh.com"))
265  {
266  //Retrieve the elliptic curve that matches the specified key format
267  //identifier
268  curveInfo = sshGetCurveInfo(&cert->keyFormatId,
270 
271  //Make sure the key format identifier is acceptable
272  if(curveInfo != NULL)
273  {
274  //Load EC domain parameters
275  error = ecLoadDomainParameters(params, curveInfo);
276  }
277  else
278  {
279  //Report an error
280  error = ERROR_WRONG_IDENTIFIER;
281  }
282 
283  //Check status code
284  if(!error)
285  {
286  //Import EC public key value
287  error = ecImport(params, &publicKey->q, cert->publicKey.ecdsaPublicKey.q.value,
289  }
290  }
291  else
292  {
293  //Unexpected key format identifier
294  error = ERROR_WRONG_IDENTIFIER;
295  }
296 
297  //Return status code
298  return error;
299 #else
300  //Not implemented
301  return ERROR_NOT_IMPLEMENTED;
302 #endif
303 }
304 
305 
306 /**
307  * @brief Import an Ed25519 public key from a certificate
308  * @param[in] cert Pointer to the certificate structure
309  * @param[out] publicKey Pointer to the RSA public key
310  * @return Error code
311  **/
312 
314  EddsaPublicKey *publicKey)
315 {
316 #if (SSH_ED25519_SIGN_SUPPORT == ENABLED)
317  error_t error;
318 
319  //Check key format identifier
320  if(sshCompareString(&cert->keyFormatId, "ssh-ed25519-cert-v01@openssh.com"))
321  {
322  //Import Ed25519 public key value
323  error = mpiImport(&publicKey->q, cert->publicKey.ed25519PublicKey.q.value,
325  }
326  else
327  {
328  //Unexpected key format identifier
329  error = ERROR_WRONG_IDENTIFIER;
330  }
331 
332  //Return status code
333  return error;
334 #else
335  //Not implemented
336  return ERROR_NOT_IMPLEMENTED;
337 #endif
338 }
339 
340 
341 /**
342  * @brief Get SSH certificate type
343  * @param[in] input SSH certificate file
344  * @param[in] length Length of the SSH certificate file
345  * @return SSH certificate type
346  **/
347 
348 const char_t *sshGetCertType(const char_t *input, size_t length)
349 {
350  uint_t i;
351  size_t n;
352  const char_t *certType;
353 
354  //Initialize certificate type
355  certType = NULL;
356 
357  //Loop through the list of identifiers
358  for(i = 0; i < arraysize(sshCertTypes); i++)
359  {
360  //Get the length of the identifier
361  n = osStrlen(sshCertTypes[i]);
362 
363  //Matching identifier?
364  if(length > n && osMemcmp(input, sshCertTypes[i], n) == 0)
365  {
366  //The identifier must be followed by a whitespace character
367  if(input[n] == ' ' || input[n] == '\t')
368  {
369  certType = sshCertTypes[i];
370  break;
371  }
372  }
373  }
374 
375  //Return certificate type
376  return certType;
377 }
378 
379 #endif
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
error_t ecImport(const EcDomainParameters *params, EcPoint *r, const uint8_t *data, size_t length)
Convert an octet string to an EC point.
Definition: ec.c:365
Mpi q
Group order.
Definition: dsa.h:51
SshRsaCertPublicKey rsaPublicKey
SshBinaryString g
#define osMemcmp(p1, p2, length)
Definition: os_port.h:153
SshBinaryString n
Mpi e
Public exponent.
Definition: rsa.h:59
Mpi p
Prime modulus.
Definition: dsa.h:50
#define osStrlen(s)
Definition: os_port.h:165
error_t mpiImport(Mpi *r, const uint8_t *data, uint_t length, MpiFormat format)
Octet string to integer conversion.
Definition: mpi.c:624
EC domain parameters.
Definition: ec.h:76
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1586
Mpi n
Modulus.
Definition: rsa.h:58
SshBinaryString p
size_t length
Definition: ssh_types.h:69
error_t base64Decode(const char_t *input, size_t inputLen, void *output, size_t *outputLen)
Base64 decoding algorithm.
Definition: base64.c:258
error_t ecLoadDomainParameters(EcDomainParameters *params, const EcCurveInfo *curveInfo)
Load EC domain parameters.
Definition: ec.c:90
Elliptic curve parameters.
Definition: ec_curves.h:302
DSA public key.
Definition: dsa.h:61
error_t sshImportRsaCertPublicKey(const SshCertificate *cert, RsaPublicKey *publicKey)
Import an RSA public key from a certificate.
@ MPI_FORMAT_LITTLE_ENDIAN
Definition: mpi.h:70
error_t
Error codes.
Definition: error.h:43
Mpi g
Group generator.
Definition: dsa.h:52
Mpi q
Public key.
Definition: eddsa.h:50
EdDSA public key.
Definition: eddsa.h:49
#define SSH_MAX_DSA_MODULUS_SIZE
Definition: ssh.h:717
RSA public key.
Definition: rsa.h:57
SshDsaCertPublicKey dsaPublicKey
EdDSA (Edwards-Curve Digital Signature Algorithm)
@ ERROR_INVALID_KEY_LENGTH
Definition: error.h:107
RSA public-key cryptography standard.
DSA (Digital Signature Algorithm)
Base64 encoding scheme.
DsaDomainParameters params
DSA domain parameters.
Definition: dsa.h:62
SshEcdsaCertPublicKey ecdsaPublicKey
uint8_t length
Definition: tcp.h:368
SshBinaryString y
error_t sshImportCertificate(const char_t *input, size_t inputLen, uint8_t *output, size_t *outputLen)
Import SSH certificate (OpenSSH format)
#define SSH_MAX_RSA_MODULUS_SIZE
Definition: ssh.h:703
SshBinaryString q
error_t sshImportDsaCertPublicKey(const SshCertificate *cert, DsaPublicKey *publicKey)
Import a DSA public key from a certificate.
error_t sshImportEd25519CertPublicKey(const SshCertificate *cert, EddsaPublicKey *publicKey)
Import an Ed25519 public key from a certificate.
uint_t mpiGetBitLength(const Mpi *a)
Get the actual length in bits.
Definition: mpi.c:234
const uint8_t * value
Definition: ssh_types.h:68
SshBinaryString e
EC public key.
Definition: ec.h:94
char char_t
Definition: compiler_port.h:48
SshString keyFormatId
SshCertPublicKey publicKey
uint8_t n
SSH certificate import functions.
EcPoint q
Public key.
Definition: ec.h:95
SSH helper functions.
@ MPI_FORMAT_BIG_ENDIAN
Definition: mpi.h:71
Mpi y
Public key value.
Definition: dsa.h:63
@ ERROR_WRONG_IDENTIFIER
Definition: error.h:89
@ ERROR_INVALID_SYNTAX
Definition: error.h:68
error_t sshImportEcdsaCertPublicKey(const SshCertificate *cert, EcDomainParameters *params, EcPublicKey *publicKey)
Import an ECDSA public key from a certificate.
unsigned int uint_t
Definition: compiler_port.h:50
Secure Shell (SSH)
ECC (Elliptic Curve Cryptography)
const EcCurveInfo * sshGetCurveInfo(const SshString *keyFormatId, const SshString *curveName)
Get the elliptic curve that matches the specified key format identifier.
Definition: ssh_misc.c:1073
SSH certificate (OpenSSH format)
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
const char_t * sshGetCertType(const char_t *input, size_t length)
Get SSH certificate type.
SshBinaryString q
#define arraysize(a)
Definition: os_port.h:71
SshEd25519CertPublicKey ed25519PublicKey