ssh_kex_hybrid.c
Go to the documentation of this file.
1 /**
2  * @file ssh_kex_hybrid.c
3  * @brief Post-quantum hybrid key exchange
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_transport.h"
38 #include "ssh/ssh_kex.h"
39 #include "ssh/ssh_kex_hybrid.h"
40 #include "ssh/ssh_packet.h"
41 #include "ssh/ssh_key_material.h"
42 #include "ssh/ssh_exchange_hash.h"
43 #include "ssh/ssh_key_verify.h"
44 #include "ssh/ssh_cert_verify.h"
45 #include "ssh/ssh_misc.h"
46 #include "debug.h"
47 
48 //Check SSH stack configuration
49 #if (SSH_SUPPORT == ENABLED && SSH_HYBRID_KEX_SUPPORT == ENABLED)
50 
51 
52 /**
53  * @brief Send SSH_MSG_KEX_HYBRID_INIT message
54  * @param[in] connection Pointer to the SSH connection
55  * @return Error code
56  **/
57 
59 {
60 #if (SSH_CLIENT_SUPPORT == ENABLED)
61  error_t error;
62  size_t length;
63  uint8_t *message;
64  SshContext *context;
65 
66  //Point to the SSH context
67  context = connection->context;
68 
69  //Point to the buffer where to format the message
70  message = connection->buffer + SSH_PACKET_HEADER_SIZE;
71 
72  //Key exchange algorithms are formulated as key encapsulation mechanisms
73  error = sshSelectKemAlgo(connection);
74 
75  //Check status code
76  if(!error)
77  {
78  //Generate a post-quantum KEM key pair
79  error = kemGenerateKeyPair(&connection->kemContext, context->prngAlgo,
80  context->prngContext);
81  }
82 
83  //Check status code
84  if(!error)
85  {
86  //Load ECDH domain parameters
87  error = sshLoadKexClassicalEcdhParams(connection->kexAlgo,
88  &connection->ecdhContext.params);
89  }
90 
91  //Check status code
92  if(!error)
93  {
94  //Generate a classical ECDH key pair
95  error = sshGenerateClassicalEcdhKeyPair(connection);
96  }
97 
98  //Check status code
99  if(!error)
100  {
101  //Format SSH_MSG_KEX_HYBRID_INIT message
102  error = sshFormatKexHybridInit(connection, message, &length);
103  }
104 
105  //Check status code
106  if(!error)
107  {
108  //Debug message
109  TRACE_INFO("Sending SSH_MSG_KEX_HYBRID_INIT message (%" PRIuSIZE " bytes)...\r\n", length);
111 
112  //Send message
113  error = sshSendPacket(connection, message, length);
114  }
115 
116  //Check status code
117  if(!error)
118  {
119  //The server responds with an SSH_MSG_KEX_HYBRID_REPLY message
120  connection->state = SSH_CONN_STATE_KEX_HYBRID_REPLY;
121  }
122 
123  //Return status code
124  return error;
125 #else
126  //Client operation mode is not implemented
127  return ERROR_NOT_IMPLEMENTED;
128 #endif
129 }
130 
131 
132 /**
133  * @brief Send SSH_MSG_KEX_HYBRID_REPLY message
134  * @param[in] connection Pointer to the SSH connection
135  * @return Error code
136  **/
137 
139 {
140 #if (SSH_SERVER_SUPPORT == ENABLED)
141  error_t error;
142  size_t length;
143  uint8_t *message;
144 
145  //Point to the buffer where to format the message
146  message = connection->buffer + SSH_PACKET_HEADER_SIZE;
147 
148  //Generate a classical ECDH key pair
149  error = sshGenerateClassicalEcdhKeyPair(connection);
150 
151  //Check status code
152  if(!error)
153  {
154  //Format SSH_MSG_KEX_HYBRID_REPLY message
155  error = sshFormatKexHybridReply(connection, message, &length);
156  }
157 
158  //Check status code
159  if(!error)
160  {
161  //Debug message
162  TRACE_INFO("Sending SSH_MSG_KEX_HYBRID_REPLY message (%" PRIuSIZE " bytes)...\r\n", length);
164 
165  //Send message
166  error = sshSendPacket(connection, message, length);
167  }
168 
169  //Check status code
170  if(!error)
171  {
172  //Key exchange ends by each side sending an SSH_MSG_NEWKEYS message
173  connection->state = SSH_CONN_STATE_SERVER_NEW_KEYS;
174  }
175 
176  //Return status code
177  return error;
178 #else
179  //Server operation mode is not implemented
180  return ERROR_NOT_IMPLEMENTED;
181 #endif
182 }
183 
184 
185 /**
186  * @brief Format SSH_MSG_KEX_HYBRID_INIT message
187  * @param[in] connection Pointer to the SSH connection
188  * @param[out] p Buffer where to format the message
189  * @param[out] length Length of the resulting message, in bytes
190  * @return Error code
191  **/
192 
194  size_t *length)
195 {
196 #if (SSH_CLIENT_SUPPORT == ENABLED)
197  error_t error;
198  size_t m;
199  size_t n;
200 
201  //Total length of the message
202  *length = 0;
203 
204  //Set message type
206 
207  //Point to the first field of the message
208  p += sizeof(uint8_t);
209  *length += sizeof(uint8_t);
210 
211  //Get the length of the KEM public key
212  m = connection->kemContext.kemAlgo->publicKeySize;
213 
214  //Format client's post-quantum public key (C_PQ)
215  osMemcpy(p + sizeof(uint32_t), connection->kemContext.pk, m);
216 
217  //Format client's classical public key (C_CL)
218  error = ecExport(&connection->ecdhContext.params,
219  &connection->ecdhContext.qa.q, p + sizeof(uint32_t) + m, &n);
220  //Any error to report?
221  if(error)
222  return error;
223 
224  //C_INIT is the concatenation of C_PQ and C_CL
225  STORE32BE(m + n, p);
226 
227  //Total length of the message
228  *length += sizeof(uint32_t) + m + n;
229 
230  //Successful processing
231  return NO_ERROR;
232 #else
233  //Client operation mode is not implemented
234  return ERROR_NOT_IMPLEMENTED;
235 #endif
236 }
237 
238 
239 /**
240  * @brief Format SSH_MSG_KEX_HYBRID_REPLY message
241  * @param[in] connection Pointer to the SSH connection
242  * @param[out] p Buffer where to format the message
243  * @param[out] length Length of the resulting message, in bytes
244  * @return Error code
245  **/
246 
248  size_t *length)
249 {
250 #if (SSH_SERVER_SUPPORT == ENABLED)
251  error_t error;
252  size_t m;
253  size_t n;
254  SshContext *context;
255  HashContext hashContext;
256 
257  //Point to the SSH context
258  context = connection->context;
259 
260  //Total length of the message
261  *length = 0;
262 
263  //Set message type
265 
266  //Point to the first field of the message
267  p += sizeof(uint8_t);
268  *length += sizeof(uint8_t);
269 
270  //Format server's public host key (K_S)
271  error = sshFormatHostKey(connection, p + sizeof(uint32_t), &n);
272  //Any error to report?
273  if(error)
274  return error;
275 
276  //The octet string value is preceded by a uint32 containing its length
277  STORE32BE(n, p);
278 
279  //Point to the next field
280  p += sizeof(uint32_t) + n;
281  *length += sizeof(uint32_t) + n;
282 
283  //Perform KEM encapsulation
284  error = kemEncapsulate(&connection->kemContext, context->prngAlgo,
285  context->prngContext, p + sizeof(uint32_t), connection->k);
286  //Any error to report?
287  if(error)
288  return error;
289 
290  //Get the length of the KEM ciphertext
291  m = connection->kemContext.kemAlgo->ciphertextSize;
292  //Get the length of the KEM shared secret
293  connection->kLen = connection->kemContext.kemAlgo->sharedSecretSize;
294 
295  //The shared secret K is derived as the hash algorithm specified in the named
296  //hybrid key exchange method name over the concatenation of K_PQ and K_CL
297  connection->hashAlgo->init(&hashContext);
298  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
299 
300  //Format server's ephemeral public key (S_CL)
301  error = ecExport(&connection->ecdhContext.params,
302  &connection->ecdhContext.qa.q, p + sizeof(uint32_t) + m, &n);
303  //Any error to report?
304  if(error)
305  return error;
306 
307  //Update exchange hash H with S_REPLY (concatenation of S_PQ and S_CL)
308  error = sshUpdateExchangeHash(connection, p + sizeof(uint32_t), m + n);
309  //Any error to report?
310  if(error)
311  return error;
312 
313  //The octet string value is preceded by a uint32 containing its length
314  STORE32BE(m + n, p);
315 
316  //Point to the next field
317  p += sizeof(uint32_t) + m + n;
318  *length += sizeof(uint32_t) + m + n;
319 
320  //Compute the shared secret K_CL
321  error = sshComputeClassicalEcdhSharedSecret(connection);
322  //Any error to report?
323  if(error)
324  return error;
325 
326  //Derive the shared secret K = HASH(K_PQ, K_CL)
327  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
328  connection->hashAlgo->final(&hashContext, connection->k + sizeof(uint32_t));
329 
330  //Log shared secret (for debugging purpose only)
331  sshDumpKey(connection, "SHARED_SECRET", connection->k + sizeof(uint32_t),
332  connection->hashAlgo->digestSize);
333 
334  //Convert K to string representation
335  STORE32BE(connection->hashAlgo->digestSize, connection->k);
336  connection->kLen = sizeof(uint32_t) + connection->hashAlgo->digestSize;
337 
338  //Update exchange hash H with K (shared secret)
339  error = sshUpdateExchangeHashRaw(connection, connection->k,
340  connection->kLen);
341  //Any error to report?
342  if(error)
343  return error;
344 
345  //Compute the signature on the exchange hash
346  error = sshGenerateExchangeHashSignature(connection, p + sizeof(uint32_t),
347  &n);
348  //Any error to report?
349  if(error)
350  return error;
351 
352  //The octet string value is preceded by a uint32 containing its length
353  STORE32BE(n, p);
354 
355  //Total length of the message
356  *length += sizeof(uint32_t) + n;
357 
358  //Destroy classical and post-quantum private keys
359  ecdhFree(&connection->ecdhContext);
360  ecdhInit(&connection->ecdhContext);
361  kemFree(&connection->kemContext);
362  kemInit(&connection->kemContext, NULL);
363 
364  //Successful processing
365  return NO_ERROR;
366 #else
367  //Server operation mode is not implemented
368  return ERROR_NOT_IMPLEMENTED;
369 #endif
370 }
371 
372 
373 /**
374  * @brief Parse SSH_MSG_KEX_HYBRID_INIT message
375  * @param[in] connection Pointer to the SSH connection
376  * @param[in] message Pointer to message
377  * @param[in] length Length of the message, in bytes
378  * @return Error code
379  **/
380 
382  const uint8_t *message, size_t length)
383 {
384 #if (SSH_SERVER_SUPPORT == ENABLED)
385  error_t error;
386  size_t n;
387  const uint8_t *p;
388  SshBinaryString clientInit;
389 
390  //Debug message
391  TRACE_INFO("SSH_MSG_KEX_HYBRID_INIT message received (%" PRIuSIZE " bytes)...\r\n", length);
393 
394  //Check operation mode
395  if(connection->context->mode != SSH_OPERATION_MODE_SERVER)
397 
398  //Check connection state
399  if(connection->state != SSH_CONN_STATE_KEX_HYBRID_INIT)
401 
402  //Sanity check
403  if(length < sizeof(uint8_t))
404  return ERROR_INVALID_MESSAGE;
405 
406  //Point to the first field of the message
407  p = message + sizeof(uint8_t);
408  //Remaining bytes to process
409  length -= sizeof(uint8_t);
410 
411  //Decode C_INIT (concatenation of C_PQ and C_CL)
412  error = sshParseBinaryString(p, length, &clientInit);
413  //Any error to report?
414  if(error)
415  return error;
416 
417  //Point to the next field
418  p += sizeof(uint32_t) + clientInit.length;
419  length -= sizeof(uint32_t) + clientInit.length;
420 
421  //Malformed message?
422  if(length != 0)
423  return ERROR_INVALID_MESSAGE;
424 
425  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
426  error = sshUpdateExchangeHash(connection, clientInit.value,
427  clientInit.length);
428  //Any error to report?
429  if(error)
430  return error;
431 
432  //Key exchange algorithms are formulated as key encapsulation mechanisms
433  error = sshSelectKemAlgo(connection);
434  //Any error to report?
435  if(error)
436  return error;
437 
438  //Get the length of the KEM public key
439  n = connection->kemContext.kemAlgo->publicKeySize;
440 
441  //Check the length of the C_INIT field
442  if(clientInit.length < n)
443  return ERROR_INVALID_MESSAGE;
444 
445  //Load client's post-quantum public key (C_PQ)
446  error = kemLoadPublicKey(&connection->kemContext, clientInit.value);
447  //Any error to report?
448  if(error)
449  return error;
450 
451  //Load ECDH domain parameters
452  error = sshLoadKexClassicalEcdhParams(connection->kexAlgo,
453  &connection->ecdhContext.params);
454  //Any error to report?
455  if(error)
456  return error;
457 
458  //Load client's classical public key (C_CL)
459  error = ecImport(&connection->ecdhContext.params,
460  &connection->ecdhContext.qb.q, clientInit.value + n,
461  clientInit.length - n);
462  //Any error to report?
463  if(error)
464  return error;
465 
466  //Ensure the public key is acceptable
467  error = ecdhCheckPublicKey(&connection->ecdhContext.params,
468  &connection->ecdhContext.qb.q);
469  //Any error to report?
470  if(error)
471  return error;
472 
473  //The server responds with an SSH_MSG_KEX_HYBRID_REPLY message
474  return sshSendKexHybridReply(connection);
475 #else
476  //Server operation mode is not implemented
478 #endif
479 }
480 
481 
482 /**
483  * @brief Parse SSH_MSG_KEX_HYBRID_REPLY message
484  * @param[in] connection Pointer to the SSH connection
485  * @param[in] message Pointer to message
486  * @param[in] length Length of the message, in bytes
487  * @return Error code
488  **/
489 
491  const uint8_t *message, size_t length)
492 {
493 #if (SSH_CLIENT_SUPPORT == ENABLED)
494  error_t error;
495  size_t n;
496  const uint8_t *p;
497  SshString hostKeyAlgo;
498  SshBinaryString hostKey;
499  SshBinaryString serverReply;
500  SshBinaryString signature;
501  SshContext *context;
502  HashContext hashContext;
503 
504  //Point to the SSH context
505  context = connection->context;
506 
507  //Debug message
508  TRACE_INFO("SSH_MSG_KEX_HYBRID_REPLY message received (%" PRIuSIZE " bytes)...\r\n", length);
510 
511  //Check operation mode
512  if(context->mode != SSH_OPERATION_MODE_CLIENT)
514 
515  //Check connection state
516  if(connection->state != SSH_CONN_STATE_KEX_HYBRID_REPLY)
518 
519  //Sanity check
520  if(length < sizeof(uint8_t))
521  return ERROR_INVALID_MESSAGE;
522 
523  //Point to the first field of the message
524  p = message + sizeof(uint8_t);
525  //Remaining bytes to process
526  length -= sizeof(uint8_t);
527 
528  //Decode server's public host key (K_S)
529  error = sshParseBinaryString(p, length, &hostKey);
530  //Any error to report?
531  if(error)
532  return error;
533 
534  //Point to the next field
535  p += sizeof(uint32_t) + hostKey.length;
536  length -= sizeof(uint32_t) + hostKey.length;
537 
538  //Decode S_REPLY (concatenation of S_PQ and S_CL)
539  error = sshParseBinaryString(p, length, &serverReply);
540  //Any error to report?
541  if(error)
542  return error;
543 
544  //Point to the next field
545  p += sizeof(uint32_t) + serverReply.length;
546  length -= sizeof(uint32_t) + serverReply.length;
547 
548  //Decode the signature field
549  error = sshParseBinaryString(p, length, &signature);
550  //Any error to report?
551  if(error)
552  return error;
553 
554  //Point to the next field
555  p += sizeof(uint32_t) + signature.length;
556  length -= sizeof(uint32_t) + signature.length;
557 
558  //Malformed message?
559  if(length != 0)
560  return ERROR_INVALID_MESSAGE;
561 
562  //Get the selected server's host key algorithm
563  hostKeyAlgo.value = connection->serverHostKeyAlgo;
564  hostKeyAlgo.length = osStrlen(connection->serverHostKeyAlgo);
565 
566 #if (SSH_CERT_SUPPORT == ENABLED)
567  //Certificate-based authentication?
568  if(sshIsCertPublicKeyAlgo(&hostKeyAlgo))
569  {
570  //Verify server's certificate
571  error = sshVerifyServerCertificate(connection, &hostKeyAlgo, &hostKey);
572  }
573  else
574 #endif
575  {
576  //Verify server's host key
577  error = sshVerifyServerHostKey(connection, &hostKeyAlgo, &hostKey);
578  }
579 
580  //If the client fails to verify the server's host key, it should disconnect
581  //from the server by sending an SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE message
582  if(error)
583  return ERROR_INVALID_KEY;
584 
585  //Update exchange hash H with K_S (server's public host key)
586  error = sshUpdateExchangeHash(connection, hostKey.value, hostKey.length);
587  //Any error to report?
588  if(error)
589  return error;
590 
591  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
592  error = sshDigestClientInit(connection);
593  //Any error to report?
594  if(error)
595  return error;
596 
597  //Update exchange hash H with S_REPLY (concatenation of S_PQ and S_CL)
598  error = sshUpdateExchangeHash(connection, serverReply.value,
599  serverReply.length);
600  //Any error to report?
601  if(error)
602  return error;
603 
604  //Get the length of the KEM ciphertext
605  n = connection->kemContext.kemAlgo->ciphertextSize;
606  //Get the length of the KEM shared secret
607  connection->kLen = connection->kemContext.kemAlgo->sharedSecretSize;
608 
609  //Check the length of the S_REPLY field
610  if(serverReply.length < n)
611  return ERROR_INVALID_MESSAGE;
612 
613  //The client decapsulates the ciphertext by using its private key which
614  //leads to K_PQ, a post-quantum shared secret
615  error = kemDecapsulate(&connection->kemContext, serverReply.value,
616  connection->k);
617  //Any error to report?
618  if(error)
619  return error;
620 
621  //The shared secret K is derived as the hash algorithm specified in the named
622  //hybrid key exchange method name over the concatenation of K_PQ and K_CL
623  connection->hashAlgo->init(&hashContext);
624  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
625 
626  //Load server's classical public key (S_CL)
627  error = ecImport(&connection->ecdhContext.params,
628  &connection->ecdhContext.qb.q, serverReply.value + n,
629  serverReply.length - n);
630  //Any error to report?
631  if(error)
632  return error;
633 
634  //Ensure the public key is acceptable
635  error = ecdhCheckPublicKey(&connection->ecdhContext.params,
636  &connection->ecdhContext.qb.q);
637  //Any error to report?
638  if(error)
639  return error;
640 
641  //Compute the classical shared secret K_CL
642  error = sshComputeClassicalEcdhSharedSecret(connection);
643  //Any error to report?
644  if(error)
645  return error;
646 
647  //Derive the shared secret K = HASH(K_PQ, K_CL)
648  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
649  connection->hashAlgo->final(&hashContext, connection->k + sizeof(uint32_t));
650 
651  //Log shared secret (for debugging purpose only)
652  sshDumpKey(connection, "SHARED_SECRET", connection->k + sizeof(uint32_t),
653  connection->hashAlgo->digestSize);
654 
655  //Convert K to string representation
656  STORE32BE(connection->hashAlgo->digestSize, connection->k);
657  connection->kLen = sizeof(uint32_t) + connection->hashAlgo->digestSize;
658 
659  //Update exchange hash H with K (shared secret)
660  error = sshUpdateExchangeHashRaw(connection, connection->k,
661  connection->kLen);
662  //Any error to report?
663  if(error)
664  return error;
665 
666  //Verify the signature on the exchange hash
667  error = sshVerifyExchangeHashSignature(connection, &hostKey, &signature);
668  //Any error to report?
669  if(error)
670  return error;
671 
672  //Destroy classical and post-quantum private keys
673  ecdhFree(&connection->ecdhContext);
674  ecdhInit(&connection->ecdhContext);
675  kemFree(&connection->kemContext);
676  kemInit(&connection->kemContext, NULL);
677 
678  //Key exchange ends by each side sending an SSH_MSG_NEWKEYS message
679  return sshSendNewKeys(connection);
680 #else
681  //Client operation mode is not implemented
683 #endif
684 }
685 
686 
687 /**
688  * @brief Parse PQ-hybrid specific messages
689  * @param[in] connection Pointer to the SSH connection
690  * @param[in] type SSH message type
691  * @param[in] message Pointer to message
692  * @param[in] length Length of the message, in bytes
693  * @return Error code
694  **/
695 
697  const uint8_t *message, size_t length)
698 {
699  error_t error;
700 
701 #if (SSH_CLIENT_SUPPORT == ENABLED)
702  //Client operation mode?
703  if(connection->context->mode == SSH_OPERATION_MODE_CLIENT)
704  {
705  //Check message type
707  {
708  //Parse SSH_MSG_KEX_HYBRID_REPLY message
709  error = sshParseKexHybridReply(connection, message, length);
710  }
711  else
712  {
713  //Unknown message type
714  error = ERROR_INVALID_TYPE;
715  }
716  }
717  else
718 #endif
719 #if (SSH_SERVER_SUPPORT == ENABLED)
720  //Server operation mode?
721  if(connection->context->mode == SSH_OPERATION_MODE_SERVER)
722  {
723  //Check message type
725  {
726  //Parse SSH_MSG_KEX_HYBRID_INIT message
727  error = sshParseKexHybridInit(connection, message, length);
728  }
729  else
730  {
731  //Unknown message type
732  error = ERROR_INVALID_TYPE;
733  }
734  }
735  else
736 #endif
737  //Invalid operation mode?
738  {
739  //Report an error
740  error = ERROR_INVALID_TYPE;
741  }
742 
743  //Return status code
744  return error;
745 }
746 
747 
748 /**
749  * @brief Select key encapsulation mechanism
750  * @param[in] connection Pointer to the SSH connection
751  * @return Error code
752  **/
753 
755 {
756  error_t error;
757 
758  //Release KEM context
759  kemFree(&connection->kemContext);
760 
761 #if (SSH_MLKEM768_SUPPORT == ENABLED)
762  //ML-KEM-768 key encapsulation mechanism?
763  if(sshCompareAlgo(connection->kexAlgo, "mlkem768nistp256-sha256") ||
764  sshCompareAlgo(connection->kexAlgo, "mlkem768x25519-sha256"))
765  {
766  //Initialize KEM context
767  kemInit(&connection->kemContext, MLKEM768_KEM_ALGO);
768  //Successful processing
769  error = NO_ERROR;
770  }
771  else
772 #endif
773 #if (SSH_MLKEM1024_SUPPORT == ENABLED)
774  //ML-KEM-1024 key encapsulation mechanism?
775  if(sshCompareAlgo(connection->kexAlgo, "mlkem1024nistp384-sha384"))
776  {
777  //Initialize KEM context
778  kemInit(&connection->kemContext, MLKEM1024_KEM_ALGO);
779  //Successful processing
780  error = NO_ERROR;
781  }
782  else
783 #endif
784 #if (SSH_SNTRUP761_SUPPORT == ENABLED)
785  //Streamlined NTRU Prime 761 key encapsulation mechanism?
786  if(sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512") ||
787  sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512@openssh.com"))
788  {
789  //Initialize KEM context
790  kemInit(&connection->kemContext, SNTRUP761_KEM_ALGO);
791  //Successful processing
792  error = NO_ERROR;
793  }
794  else
795 #endif
796  //Unknown key encapsulation mechanism?
797  {
798  //Report an error
800  }
801 
802  //Return status code
803  return error;
804 }
805 
806 
807 /**
808  * @brief Load the EC parameters that match specified key exchange algorithm
809  * @param[in] kexAlgo Key exchange algorithm name
810  * @param[out] params Elliptic curve domain parameters
811  * @return Error code
812  **/
813 
815  EcDomainParameters *params)
816 {
817  error_t error;
818  const EcCurveInfo *curveInfo;
819 
820 #if (SSH_NISTP256_SUPPORT == ENABLED)
821  //NIST P-256 elliptic curve?
822  if(sshCompareAlgo(kexAlgo, "mlkem768nistp256-sha256"))
823  {
824  curveInfo = SECP256R1_CURVE;
825  }
826  else
827 #endif
828 #if (SSH_NISTP384_SUPPORT == ENABLED)
829  //NIST P-384 elliptic curve?
830  if(sshCompareAlgo(kexAlgo, "mlkem1024nistp384-sha384"))
831  {
832  curveInfo = SECP384R1_CURVE;
833  }
834  else
835 #endif
836 #if (SSH_CURVE25519_SUPPORT == ENABLED)
837  //Curve25519 elliptic curve?
838  if(sshCompareAlgo(kexAlgo, "mlkem768x25519-sha256") ||
839  sshCompareAlgo(kexAlgo, "sntrup761x25519-sha512") ||
840  sshCompareAlgo(kexAlgo, "sntrup761x25519-sha512@openssh.com"))
841  {
842  curveInfo = X25519_CURVE;
843  }
844  else
845 #endif
846  //Unknown elliptic curve?
847  {
848  curveInfo = NULL;
849  }
850 
851  //Make sure the key exchange algorithm is acceptable
852  if(curveInfo != NULL)
853  {
854  //Load EC domain parameters
855  error = ecLoadDomainParameters(params, curveInfo);
856  }
857  else
858  {
859  //Report an error
861  }
862 
863  //Return status code
864  return error;
865 }
866 
867 
868 /**
869  * @brief ECDH key pair generation
870  * @param[in] connection Pointer to the SSH connection
871  * @return Error code
872  **/
873 
875 {
876  error_t error;
877  SshContext *context;
878 
879  //Point to the SSH context
880  context = connection->context;
881 
882 #if (SSH_ECDH_CALLBACK_SUPPORT == ENABLED)
883  //Valid ECDH key pair generation callback function?
884  if(context->ecdhKeyPairGenCallback != NULL)
885  {
886  //Invoke user-defined callback
887  error = context->ecdhKeyPairGenCallback(connection, connection->kexAlgo,
888  &connection->ecdhContext.qa);
889  }
890  else
891 #endif
892  {
893  //No callback function registered
895  }
896 
897  //Check status code
899  {
900  //Generate an ephemeral key pair
901  error = ecdhGenerateKeyPair(&connection->ecdhContext, context->prngAlgo,
902  context->prngContext);
903  }
904 
905  //Return status code
906  return error;
907 }
908 
909 
910 /**
911  * @brief ECDH shared secret calculation
912  * @param[in] connection Pointer to the SSH connection
913  * @return Error code
914  **/
915 
917 {
918  error_t error;
919 
920 #if (SSH_ECDH_CALLBACK_SUPPORT == ENABLED)
921  //Valid ECDH shared secret calculation callback function?
922  if(connection->context->ecdhSharedSecretCalcCallback != NULL)
923  {
924  //Invoke user-defined callback
925  error = connection->context->ecdhSharedSecretCalcCallback(connection,
926  connection->kexAlgo, &connection->ecdhContext.qb, connection->k,
927  &connection->kLen);
928  }
929  else
930 #endif
931  {
932  //No callback function registered
934  }
935 
936  //Check status code
938  {
939  //Compute the shared secret K
940  error = ecdhComputeSharedSecret(&connection->ecdhContext, connection->k,
941  SSH_MAX_SHARED_SECRET_LEN, &connection->kLen);
942  }
943 
944  //Return status code
945  return error;
946 }
947 
948 
949 /**
950  * @brief Update exchange hash with C_INIT (concatenation of C_PQ and C_CL)
951  * @param[in] connection Pointer to the SSH connection
952  * @return Error code
953  **/
954 
956 {
957  error_t error;
958  size_t m;
959  size_t n;
960  uint8_t *buffer;
961 
962  //Allocate a temporary buffer
963  buffer = sshAllocMem(SSH_BUFFER_SIZE);
964 
965  //Successful memory allocation?
966  if(buffer != NULL)
967  {
968  //Get the length of the KEM public key
969  m = connection->kemContext.kemAlgo->publicKeySize;
970 
971  //Format client's post-quantum public key (C_PQ)
972  osMemcpy(buffer, connection->kemContext.pk, m);
973 
974  //Format client's classical public key (C_CL)
975  error = ecExport(&connection->ecdhContext.params,
976  &connection->ecdhContext.qa.q, buffer + m, &n);
977 
978  //Check status code
979  if(!error)
980  {
981  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
982  error = sshUpdateExchangeHash(connection, buffer, m + n);
983  }
984 
985  //Release previously allocated memory
986  sshFreeMem(buffer);
987  }
988  else
989  {
990  //Failed to allocate memory
991  error = ERROR_OUT_OF_MEMORY;
992  }
993 
994  //Return status code
995  return error;
996 }
997 
998 #endif
Generic hash algorithm context.
error_t sshFormatKexHybridInit(SshConnection *connection, uint8_t *p, size_t *length)
Format SSH_MSG_KEX_HYBRID_INIT message.
error_t sshGenerateExchangeHashSignature(SshConnection *connection, uint8_t *p, size_t *written)
Compute the signature on the exchange hash.
@ SSH_CONN_STATE_KEX_HYBRID_INIT
Definition: ssh.h:1049
SSH host key verification.
Binary string.
Definition: ssh_types.h:67
@ 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
@ ERROR_UNEXPECTED_MESSAGE
Definition: error.h:194
void sshDumpKey(SshConnection *connection, const char_t *label, const uint8_t *key, size_t keyLen)
Dump secret key (for debugging purpose only)
#define SECP384R1_CURVE
Definition: ec_curves.h:248
uint8_t p
Definition: ndp.h:300
error_t sshLoadKexClassicalEcdhParams(const char_t *kexAlgo, EcDomainParameters *params)
Load the EC parameters that match specified key exchange algorithm.
uint8_t message[]
Definition: chap.h:154
@ SSH_MSG_KEX_HYBRID_INIT
Definition: ssh.h:959
#define SECP256R1_CURVE
Definition: ec_curves.h:247
error_t sshVerifyServerHostKey(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *hostKey)
Verify server's host key.
void ecdhFree(EcdhContext *context)
Release ECDH context.
Definition: ecdh.c:65
error_t sshVerifyExchangeHashSignature(SshConnection *connection, const SshBinaryString *serverHostKey, const SshBinaryString *signature)
Verify the signature on the exchange hash.
void kemInit(KemContext *context, const KemAlgo *kemAlgo)
Initialize KEM context.
Definition: kem.c:48
SSH transport layer protocol.
error_t sshUpdateExchangeHashRaw(SshConnection *connection, const void *data, size_t length)
Update exchange hash calculation (raw data)
uint8_t type
Definition: coap_common.h:176
SSH certificate verification.
@ ERROR_OUT_OF_MEMORY
Definition: error.h:63
size_t length
Definition: ssh_types.h:58
@ ERROR_INVALID_MESSAGE
Definition: error.h:105
error_t sshGenerateClassicalEcdhKeyPair(SshConnection *connection)
ECDH key pair generation.
#define SSH_MAX_SHARED_SECRET_LEN
Definition: ssh.h:842
#define osStrlen(s)
Definition: os_port.h:165
EC domain parameters.
Definition: ec.h:76
void kemFree(KemContext *context)
Release KEM context.
Definition: kem.c:62
#define SSH_PACKET_HEADER_SIZE
Definition: ssh_packet.h:38
@ SSH_CONN_STATE_KEX_HYBRID_REPLY
Definition: ssh.h:1050
error_t sshParseKexHybridMessage(SshConnection *connection, uint8_t type, const uint8_t *message, size_t length)
Parse PQ-hybrid specific messages.
error_t kemEncapsulate(KemContext *context, const PrngAlgo *prngAlgo, void *prngContext, uint8_t *ct, uint8_t *ss)
Encapsulation algorithm.
Definition: kem.c:209
error_t sshSendPacket(SshConnection *connection, uint8_t *payload, size_t payloadLen)
Send SSH packet.
Definition: ssh_packet.c:57
bool_t sshIsCertPublicKeyAlgo(const SshString *publicKeyAlgo)
Test if the specified public key algorithm is using certificates.
error_t sshSendNewKeys(SshConnection *connection)
Send SSH_MSG_NEWKEYS message.
Definition: ssh_kex.c:194
size_t length
Definition: ssh_types.h:69
error_t ecLoadDomainParameters(EcDomainParameters *params, const EcCurveInfo *curveInfo)
Load EC domain parameters.
Definition: ec.c:90
Key material generation.
Elliptic curve parameters.
Definition: ec_curves.h:302
#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
error_t
Error codes.
Definition: error.h:43
@ SSH_MSG_KEX_HYBRID_REPLY
Definition: ssh.h:960
bool_t sshCompareAlgo(const char_t *name1, const char_t *name2)
Compare algorithm names.
Definition: ssh_misc.c:1653
@ SSH_OPERATION_MODE_SERVER
Definition: ssh.h:893
@ SSH_OPERATION_MODE_CLIENT
Definition: ssh.h:892
error_t sshParseKexHybridReply(SshConnection *connection, const uint8_t *message, size_t length)
Parse SSH_MSG_KEX_HYBRID_REPLY message.
error_t sshVerifyServerCertificate(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *hostKey)
Verify server's certificate.
error_t sshComputeClassicalEcdhSharedSecret(SshConnection *connection)
ECDH shared secret calculation.
error_t sshDigestClientInit(SshConnection *connection)
Update exchange hash with C_INIT (concatenation of C_PQ and C_CL)
@ ERROR_INVALID_TYPE
Definition: error.h:115
error_t sshFormatKexHybridReply(SshConnection *connection, uint8_t *p, size_t *length)
Format SSH_MSG_KEX_HYBRID_REPLY message.
#define TRACE_INFO(...)
Definition: debug.h:95
uint8_t length
Definition: tcp.h:368
@ SSH_CONN_STATE_SERVER_NEW_KEYS
Definition: ssh.h:1052
String.
Definition: ssh_types.h:56
SSH key exchange.
const uint8_t * value
Definition: ssh_types.h:68
error_t ecdhComputeSharedSecret(EcdhContext *context, uint8_t *output, size_t outputSize, size_t *outputLen)
Compute ECDH shared secret.
Definition: ecdh.c:340
@ ERROR_UNSUPPORTED_KEY_EXCH_ALGO
Definition: error.h:131
error_t sshParseKexHybridInit(SshConnection *connection, const uint8_t *message, size_t length)
Parse SSH_MSG_KEX_HYBRID_INIT message.
void ecdhInit(EcdhContext *context)
Initialize ECDH context.
Definition: ecdh.c:48
char char_t
Definition: compiler_port.h:48
#define sshFreeMem(p)
Definition: ssh.h:729
Post-quantum hybrid key exchange.
uint8_t m
Definition: ndp.h:304
uint8_t n
Exchange hash calculation.
#define SshConnection
Definition: ssh.h:874
error_t kemGenerateKeyPair(KemContext *context, const PrngAlgo *prngAlgo, void *prngContext)
Key pair generation.
Definition: kem.c:100
error_t kemLoadPublicKey(KemContext *context, const uint8_t *pk)
Load public key.
Definition: kem.c:160
error_t kemDecapsulate(KemContext *context, const uint8_t *ct, uint8_t *ss)
Decapsulation algorithm.
Definition: kem.c:240
SSH helper functions.
error_t sshSendKexHybridReply(SshConnection *connection)
Send SSH_MSG_KEX_HYBRID_REPLY message.
error_t ecExport(const EcDomainParameters *params, const EcPoint *a, uint8_t *data, size_t *length)
Convert an EC point to an octet string.
Definition: ec.c:438
error_t sshFormatHostKey(SshConnection *connection, uint8_t *p, size_t *written)
Format host key structure.
Definition: ssh_misc.c:863
error_t sshSendKexHybridInit(SshConnection *connection)
Send SSH_MSG_KEX_HYBRID_INIT message.
#define MLKEM1024_KEM_ALGO
Definition: mlkem1024.h:47
SSH packet encryption/decryption.
error_t sshUpdateExchangeHash(SshConnection *connection, const void *data, size_t length)
Update exchange hash calculation.
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
#define SNTRUP761_KEM_ALGO
Definition: sntrup761.h:47
#define PRIuSIZE
Secure Shell (SSH)
SSH algorithm negotiation.
error_t ecdhCheckPublicKey(const EcDomainParameters *params, EcPoint *publicKey)
Check ECDH public key.
Definition: ecdh.c:227
#define SSH_BUFFER_SIZE
Definition: ssh.h:866
#define STORE32BE(a, p)
Definition: cpu_endian.h:286
#define MLKEM768_KEM_ALGO
Definition: mlkem768.h:47
@ ERROR_INVALID_KEY
Definition: error.h:106
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
error_t ecdhGenerateKeyPair(EcdhContext *context, const PrngAlgo *prngAlgo, void *prngContext)
ECDH key pair generation.
Definition: ecdh.c:85
#define X25519_CURVE
Definition: ec_curves.h:258
error_t sshSelectKemAlgo(SshConnection *connection)
Select key encapsulation mechanism.
#define TRACE_VERBOSE_ARRAY(p, a, n)
Definition: debug.h:125