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