ssh_cert_verify.c
Go to the documentation of this file.
1 /**
2  * @file ssh_cert_verify.c
3  * @brief SSH certificate verification
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_algorithms.h"
37 #include "ssh/ssh_cert_parse.h"
38 #include "ssh/ssh_cert_verify.h"
39 #include "ssh/ssh_sign_verify.h"
40 #include "ssh/ssh_misc.h"
41 #include "date_time.h"
42 #include "debug.h"
43 
44 //Check SSH stack configuration
45 #if (SSH_SUPPORT == ENABLED && SSH_CERT_SUPPORT == ENABLED)
46 
47 
48 /**
49  * @brief Verify client's certificate
50  * @param[in] connection Pointer to the SSH connection
51  * @param[in] publicKeyAlgo Public key algorithm
52  * @param[in] hostKey Client's certified host key
53  * @param[in] flag This flag specifies whether the signature is present in
54  * the SSH_MSG_USERAUTH_REQUEST message
55  * @return Error code
56  **/
57 
59  const SshString *publicKeyAlgo, const SshBinaryString *hostKey,
60  bool_t flag)
61 {
62  error_t error;
63  SshCertificate cert;
64  SshContext *context;
65  const char_t *expectedKeyFormatId;
66 
67  //Point to the SSH context
68  context = connection->context;
69 
70  //Parse client's certified host key
71  error = sshParseCertificate(hostKey->value, hostKey->length, &cert);
72  //Any error to report?
73  if(error)
74  return error;
75 
76  //Each host key algorithm is associated with a particular key format
77  expectedKeyFormatId = sshGetKeyFormatId(publicKeyAlgo);
78 
79  //Check whether the supplied key is consistent with the host key algorithm
80  if(!sshCompareString(&cert.keyFormatId, expectedKeyFormatId))
81  return ERROR_BAD_CERTIFICATE;
82 
83  //Check certificate type
84  if(cert.type != SSH_CERT_TYPE_USER)
85  return ERROR_BAD_CERTIFICATE;
86 
87  //Check whether the certificate is valid for the current user name
88  error = sshVerifyPrincipal(&cert, connection->user);
89  //Any error to report?
90  if(error)
91  return error;
92 
93  //Check the validity period of the certificate
94  error = sshVerifyValidity(&cert);
95  //Any error to report?
96  if(error)
97  return error;
98 
99  //Check critical options
100  error = sshVerifyCriticalOptions(connection, &cert);
101  //Any error to report?
102  if(error)
103  return error;
104 
105  //Invoke user-defined callback, if any
106  if(context->caPublicKeyVerifyCallback != NULL)
107  {
108  //Verify CA public key
109  error = context->caPublicKeyVerifyCallback(connection,
111  }
112  else
113  {
114  //The CA public key cannot be verified
115  error = ERROR_UNKNOWN_CA;
116  }
117 
118  //Failed to verify CA public key?
119  if(error)
120  return ERROR_UNKNOWN_CA;
121 
122  //The client may send an SSH_MSG_USERAUTH_REQUEST message without signature
123  //to check whether the provided certificate is acceptable for authentication
124  if(flag)
125  {
126  //Verify certificate signature
127  error = sshVerifyCertSignature(connection, &cert);
128  //Any error to report?
129  if(error)
130  return error;
131 
132  //Invoke user-defined callback, if any
133  if(context->certAuthCallback != NULL)
134  {
135  //Verify client's certificate
136  error = context->certAuthCallback(connection, connection->user, &cert);
137  }
138  else
139  {
140  //The client's certificate cannot be verified
141  error = ERROR_BAD_CERTIFICATE;
142  }
143  }
144 
145  //Return status code
146  return error;
147 }
148 
149 
150 /**
151  * @brief Verify server's certificate
152  * @param[in] connection Pointer to the SSH connection
153  * @param[in] publicKeyAlgo Public key algorithm
154  * @param[in] hostKey Server's certified host key
155  * @return Error code
156  **/
157 
159  const SshString *publicKeyAlgo, const SshBinaryString *hostKey)
160 {
161  error_t error;
162  SshCertificate cert;
163  SshContext *context;
164  const char_t *expectedKeyFormatId;
165 
166  //Point to the SSH context
167  context = connection->context;
168 
169  //Parse server's certified host key
170  error = sshParseCertificate(hostKey->value, hostKey->length, &cert);
171  //Any error to report?
172  if(error)
173  return error;
174 
175  //Each host key algorithm is associated with a particular key format
176  expectedKeyFormatId = sshGetKeyFormatId(publicKeyAlgo);
177 
178  //Check whether the supplied key is consistent with the host key algorithm
179  if(!sshCompareString(&cert.keyFormatId, expectedKeyFormatId))
180  return ERROR_BAD_CERTIFICATE;
181 
182  //Check certificate type
183  if(cert.type != SSH_CERT_TYPE_HOST)
184  return ERROR_BAD_CERTIFICATE;
185 
186  //Check the validity period of the certificate
187  error = sshVerifyValidity(&cert);
188  //Any error to report?
189  if(error)
190  return error;
191 
192  //No critical options are defined for host certificates at present
193  if(cert.criticalOptions.length > 0)
194  return ERROR_INVALID_OPTION;
195 
196  //Invoke user-defined callback, if any
197  if(context->caPublicKeyVerifyCallback != NULL)
198  {
199  //Verify CA public key
200  error = context->caPublicKeyVerifyCallback(connection,
202  }
203  else
204  {
205  //The CA public key cannot be verified
206  error = ERROR_UNKNOWN_CA;
207  }
208 
209  //Failed to verify CA public key?
210  if(error)
211  return ERROR_UNKNOWN_CA;
212 
213  //Verify certificate signature
214  error = sshVerifyCertSignature(connection, &cert);
215  //Any error to report?
216  if(error)
217  return error;
218 
219  //Invoke user-defined callback, if any
220  if(context->certVerifyCallback != NULL)
221  {
222  //Verify server's certificate
223  error = context->certVerifyCallback(connection, &cert);
224  }
225  else
226  {
227  //The server's certificate cannot be verified
228  error = ERROR_BAD_CERTIFICATE;
229  }
230 
231  //Return status code
232  return error;
233 }
234 
235 
236 /**
237  * @brief Verify principal name
238  * @param[in] cert Pointer to the SSH certificate
239  * @param[in] name NULL-terminated string containing a user name or host name
240  * @return Error code
241  **/
242 
244 {
245  error_t error;
246  uint_t i;
247  SshString principal;
248 
249  //Check the length of the 'valid principals' field
250  if(cert->validPrincipals.length > 0)
251  {
252  //These principals list the names for which this certificate is valid
253  for(i = 0; ; i++)
254  {
255  //Extract principal name
256  if(sshGetValidPrincipal(cert, i, &principal))
257  {
258  //Check principal name
259  if(sshCompareString(&principal, name))
260  {
261  //The certificate is valid for the specified name
262  error = NO_ERROR;
263  break;
264  }
265  }
266  else
267  {
268  //The end of the list was reached
269  error = ERROR_UNKNOWN_USER_NAME;
270  break;
271  }
272  }
273  }
274  else
275  {
276  //As a special case, a zero-length 'valid principals' field means the
277  //certificate is valid for any principal of the specified type
278  error = NO_ERROR;
279  }
280 
281  //Return status code
282  return error;
283 }
284 
285 
286 /**
287  * @brief Verify validity period
288  * @param[in] cert Pointer to the SSH certificate
289  * @return Error code
290  **/
291 
293 {
294  error_t error;
295  uint64_t currentTime;
296 
297  //Initialize status code
298  error = NO_ERROR;
299 
300  //Retrieve current time
301  currentTime = getCurrentUnixTime();
302 
303  //Any real-time clock implemented?
304  if(currentTime != 0)
305  {
306  //Check the validity period
307  if(currentTime < cert->validAfter || currentTime > cert->validBefore)
308  {
309  //The certificate has expired or is not yet valid
311  }
312  }
313 
314  //Return status code
315  return error;
316 }
317 
318 
319 /**
320  * @brief Verify critical options
321  * @param[in] connection Pointer to the SSH connection
322  * @param[in] cert Pointer to the SSH certificate
323  * @return Error code
324  **/
325 
327  const SshCertificate *cert)
328 {
329  error_t error;
330  uint_t i;
331  SshString optionName;
332  SshBinaryString optionData;
333 
334  //Initialize status code
335  error = NO_ERROR;
336 
337  //'critical options' is a set of zero or more key options. All such options
338  //are critical in the sense that an implementation must refuse to authorize
339  //a key that has an unrecognized option
340  for(i = 0; !error; i++)
341  {
342  //Extract critical option
343  if(sshGetCriticalOption(cert, i, &optionName, &optionData))
344  {
345  //Compare option name
346  if(sshCompareString(&optionName, "source-address"))
347  {
348  //Parse "source-address" option
349  error = sshVerifySrcAddrOption(connection, &optionData);
350  }
351  else
352  {
353  //If an implementation does not recognize an option, then the
354  //validating party should refuse to accept the certificate
355  error = ERROR_INVALID_OPTION;
356  }
357  }
358  else
359  {
360  //The end of the list was reached
361  break;
362  }
363  }
364 
365  //Return status code
366  return error;
367 }
368 
369 
370 /**
371  * @brief Verify "source-address" option
372  * @param[in] connection Pointer to the SSH connection
373  * @param[in] optionData Option-specific information
374  * @return Error code
375  **/
376 
378  const SshBinaryString *optionData)
379 {
380  error_t error;
381  char_t *p;
382  uint_t i;
384  SshString name;
385  SshNameList nameList;
386  IpAddr ipAddr;
387  IpAddr clientIpAddr;
388  uint16_t clientPort;
389  char_t buffer[44];
390 
391  //The option contains a comma-separated list of source addresses from
392  //which this certificate is accepted for authentication
393  error = sshParseNameList(optionData->value, optionData->length,
394  &nameList);
395 
396  //Check status code
397  if(!error)
398  {
399  //Retrieve the IP address of the client
400  error = socketGetRemoteAddr(connection->socket, &clientIpAddr,
401  &clientPort);
402  }
403 
404  //Check status code
405  if(!error)
406  {
407  //Loop through the list of source addresses
408  for(i = 0; !error; i++)
409  {
410  //Source addresses are separated by commas
411  if(sshGetName(&nameList, i, &name))
412  {
413  //Check the length of the string
414  if(name.length < sizeof(buffer))
415  {
416  //Copy the string representation of the IP address
417  osMemcpy(buffer, name.value, name.length);
418  //Properly terminate the string with a NULL character
419  buffer[name.length] = '\0';
420 
421  //Addresses are specified in CIDR format
422  p = osStrchr(buffer, '/');
423 
424  //Separator character found?
425  if(p != NULL)
426  {
427  //Split the CIDR representation
428  *p = '\0';
429 
430  //Convert the prefix from string representation
431  error = ipStringToAddr(buffer, &ipAddr);
432  //Malformed CIDR representation?
433  if(error)
434  break;
435 
436  //Convert the CIDR prefix length
437  prefixLen = osStrtoul(p + 1, &p, 10);
438  //Malformed CIDR representation?
439  if(*p != '\0')
440  {
441  error = ERROR_INVALID_SYNTAX;
442  break;
443  }
444 
445  //Compare IP address prefixes
446  if(ipCompPrefix(&clientIpAddr, &ipAddr, prefixLen))
447  {
448  //The client's IP address is acceptable
449  break;
450  }
451  }
452  else
453  {
454  //Convert the IP address from string representation
455  error = ipStringToAddr(buffer, &ipAddr);
456  //Malformed IP address?
457  if(error)
458  break;
459 
460  //Compare IP addresses
461  if(ipCompAddr(&clientIpAddr, &ipAddr))
462  {
463  //The client's IP address is acceptable
464  break;
465  }
466  }
467  }
468  }
469  else
470  {
471  //The end of the list was reached
472  error = ERROR_INVALID_ADDRESS;
473  }
474  }
475  }
476 
477  //Return status code
478  return error;
479 }
480 
481 
482 /**
483  * @brief Verify certificate signature
484  * @param[in] connection Pointer to the SSH connection
485  * @param[in] cert Pointer to the SSH certificate
486  * @return Error code
487  **/
488 
490  const SshCertificate *cert)
491 {
492  error_t error;
493  SshString signFormatId;
494  SshBinaryString tbsData;
495 
496  //Extract signature format identifier
497  error = sshParseString(cert->signature.value, cert->signature.length,
498  &signFormatId);
499 
500  //Check status code
501  if(!error)
502  {
503  //Point to the first byte of the certificate
504  tbsData.value = (const uint8_t *) cert->keyFormatId.value -
505  sizeof(uint32_t);
506 
507  //The certificate's signature is computed over all preceding fields from
508  //the initial string up to, and including the signature key
509  tbsData.length = cert->signatureKey.value + cert->signatureKey.length -
510  tbsData.value;
511 
512  //Verify certificate signature
513  error = sshVerifySignature(connection, &signFormatId, &cert->signatureKey,
514  NULL, &tbsData, &cert->signature);
515  }
516 
517  //Return status code
518  return error;
519 }
520 
521 #endif
Date and time management.
#define osStrchr(s, c)
Definition: os_port.h:195
@ ERROR_UNKNOWN_USER_NAME
Definition: error.h:262
int bool_t
Definition: compiler_port.h:53
Binary string.
Definition: ssh_types.h:67
IP network address.
Definition: ip.h:90
bool_t ipCompPrefix(const IpAddr *ipAddr1, const IpAddr *ipAddr2, size_t length)
Compare IP address prefixes.
Definition: ip.c:370
uint8_t p
Definition: ndp.h:300
bool_t sshGetName(const SshNameList *nameList, uint_t index, SshString *name)
Get the element at specified index.
Definition: ssh_misc.c:1338
error_t sshParseString(const uint8_t *p, size_t length, SshString *string)
Parse a string.
Definition: ssh_misc.c:1152
SSH certificate verification.
error_t sshVerifyCriticalOptions(SshConnection *connection, const SshCertificate *cert)
Verify critical options.
char_t name[]
error_t socketGetRemoteAddr(Socket *socket, IpAddr *remoteIpAddr, uint16_t *remotePort)
Retrieve the address of the peer to which a socket is connected.
Definition: socket.c:1985
bool_t sshCompareString(const SshString *string, const char_t *value)
Compare a binary string against the supplied value.
Definition: ssh_misc.c:1586
error_t sshVerifyValidity(const SshCertificate *cert)
Verify validity period.
size_t length
Definition: ssh_types.h:69
error_t ipStringToAddr(const char_t *str, IpAddr *ipAddr)
Convert a string representation of an IP address to a binary IP address.
Definition: ip.c:761
bool_t sshGetValidPrincipal(const SshCertificate *cert, uint_t index, SshString *name)
Extract the principal name at specified index.
const char_t * sshGetKeyFormatId(const SshString *publicKeyAlgo)
Get the key format identifier used by a given public key algorithm.
#define SshContext
Definition: ssh.h:870
#define osMemcpy(dest, src, length)
Definition: os_port.h:141
const char_t * value
Definition: ssh_types.h:57
SshBinaryString validPrincipals
error_t
Error codes.
Definition: error.h:43
bool_t ipCompAddr(const IpAddr *ipAddr1, const IpAddr *ipAddr2)
Compare IP addresses.
Definition: ip.c:317
String containing a comma-separated list of names.
Definition: ssh_types.h:78
@ ERROR_INVALID_ADDRESS
Definition: error.h:103
error_t sshVerifySignature(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *publicKeyBlob, const SshBinaryString *sessionId, const SshBinaryString *message, const SshBinaryString *signature)
Signature verification.
error_t sshVerifyServerCertificate(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *hostKey)
Verify server's certificate.
@ SSH_CERT_TYPE_USER
SSH certificate parsing.
@ ERROR_BAD_CERTIFICATE
Definition: error.h:235
error_t sshParseNameList(const uint8_t *p, size_t length, SshNameList *nameList)
Parse a comma-separated list of names.
Definition: ssh_misc.c:1227
error_t sshVerifyCertSignature(SshConnection *connection, const SshCertificate *cert)
Verify certificate signature.
String.
Definition: ssh_types.h:56
SshBinaryString criticalOptions
const uint8_t * value
Definition: ssh_types.h:68
#define osStrtoul(s, endptr, base)
Definition: os_port.h:255
uint8_t prefixLen
char char_t
Definition: compiler_port.h:48
SshString keyFormatId
SshBinaryString signatureKey
error_t sshVerifyClientCertificate(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *hostKey, bool_t flag)
Verify client's certificate.
#define SshConnection
Definition: ssh.h:874
uint64_t validBefore
error_t sshParseCertificate(const uint8_t *data, size_t length, SshCertificate *cert)
Parse SSH certificate.
@ ERROR_CERTIFICATE_EXPIRED
Definition: error.h:238
SSH helper functions.
@ ERROR_INVALID_SYNTAX
Definition: error.h:68
@ ERROR_UNKNOWN_CA
Definition: error.h:240
Ipv4Addr ipAddr
Definition: ipcp.h:105
@ ERROR_INVALID_OPTION
Definition: error.h:98
bool_t sshGetCriticalOption(const SshCertificate *cert, uint_t index, SshString *name, SshBinaryString *data)
Extract the critical option at specified index.
error_t sshVerifySrcAddrOption(SshConnection *connection, const SshBinaryString *optionData)
Verify "source-address" option.
error_t sshVerifyPrincipal(const SshCertificate *cert, const char_t *name)
Verify principal name.
SshBinaryString signature
@ SSH_CERT_TYPE_HOST
unsigned int uint_t
Definition: compiler_port.h:50
Secure Shell (SSH)
SSH algorithm negotiation.
RSA/DSA/ECDSA/EdDSA signature verification.
__weak_func time_t getCurrentUnixTime(void)
Get current time.
Definition: date_time.c:180
SSH certificate (OpenSSH format)
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.