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.0
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 = ecExportPublicKey(&connection->ecdhContext.da.q,
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 = ecExportPublicKey(&connection->ecdhContext.da.q,
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 = ecImportPublicKey(&connection->ecdhContext.qb,
458  connection->ecdhContext.curve, clientInit.value + n,
459  clientInit.length - n, EC_PUBLIC_KEY_FORMAT_X963);
460  //Any error to report?
461  if(error)
462  return error;
463 
464  //Ensure the public key is acceptable
465  error = ecdhCheckPublicKey(&connection->ecdhContext,
466  &connection->ecdhContext.qb);
467  //Any error to report?
468  if(error)
469  return error;
470 
471  //The server responds with an SSH_MSG_KEX_HYBRID_REPLY message
472  return sshSendKexHybridReply(connection);
473 #else
474  //Server operation mode is not implemented
476 #endif
477 }
478 
479 
480 /**
481  * @brief Parse SSH_MSG_KEX_HYBRID_REPLY message
482  * @param[in] connection Pointer to the SSH connection
483  * @param[in] message Pointer to message
484  * @param[in] length Length of the message, in bytes
485  * @return Error code
486  **/
487 
489  const uint8_t *message, size_t length)
490 {
491 #if (SSH_CLIENT_SUPPORT == ENABLED)
492  error_t error;
493  size_t n;
494  const uint8_t *p;
495  SshString hostKeyAlgo;
496  SshBinaryString hostKey;
497  SshBinaryString serverReply;
498  SshBinaryString signature;
499  SshContext *context;
500  HashContext hashContext;
501 
502  //Point to the SSH context
503  context = connection->context;
504 
505  //Debug message
506  TRACE_INFO("SSH_MSG_KEX_HYBRID_REPLY message received (%" PRIuSIZE " bytes)...\r\n", length);
508 
509  //Check operation mode
510  if(context->mode != SSH_OPERATION_MODE_CLIENT)
512 
513  //Check connection state
514  if(connection->state != SSH_CONN_STATE_KEX_HYBRID_REPLY)
516 
517  //Sanity check
518  if(length < sizeof(uint8_t))
519  return ERROR_INVALID_MESSAGE;
520 
521  //Point to the first field of the message
522  p = message + sizeof(uint8_t);
523  //Remaining bytes to process
524  length -= sizeof(uint8_t);
525 
526  //Decode server's public host key (K_S)
527  error = sshParseBinaryString(p, length, &hostKey);
528  //Any error to report?
529  if(error)
530  return error;
531 
532  //Point to the next field
533  p += sizeof(uint32_t) + hostKey.length;
534  length -= sizeof(uint32_t) + hostKey.length;
535 
536  //Decode S_REPLY (concatenation of S_PQ and S_CL)
537  error = sshParseBinaryString(p, length, &serverReply);
538  //Any error to report?
539  if(error)
540  return error;
541 
542  //Point to the next field
543  p += sizeof(uint32_t) + serverReply.length;
544  length -= sizeof(uint32_t) + serverReply.length;
545 
546  //Decode the signature field
547  error = sshParseBinaryString(p, length, &signature);
548  //Any error to report?
549  if(error)
550  return error;
551 
552  //Point to the next field
553  p += sizeof(uint32_t) + signature.length;
554  length -= sizeof(uint32_t) + signature.length;
555 
556  //Malformed message?
557  if(length != 0)
558  return ERROR_INVALID_MESSAGE;
559 
560  //Get the selected server's host key algorithm
561  hostKeyAlgo.value = connection->serverHostKeyAlgo;
562  hostKeyAlgo.length = osStrlen(connection->serverHostKeyAlgo);
563 
564 #if (SSH_CERT_SUPPORT == ENABLED)
565  //Certificate-based authentication?
566  if(sshIsCertPublicKeyAlgo(&hostKeyAlgo))
567  {
568  //Verify server's certificate
569  error = sshVerifyServerCertificate(connection, &hostKeyAlgo, &hostKey);
570  }
571  else
572 #endif
573  {
574  //Verify server's host key
575  error = sshVerifyServerHostKey(connection, &hostKeyAlgo, &hostKey);
576  }
577 
578  //If the client fails to verify the server's host key, it should disconnect
579  //from the server by sending an SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE message
580  if(error)
581  return ERROR_INVALID_KEY;
582 
583  //Update exchange hash H with K_S (server's public host key)
584  error = sshUpdateExchangeHash(connection, hostKey.value, hostKey.length);
585  //Any error to report?
586  if(error)
587  return error;
588 
589  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
590  error = sshDigestClientInit(connection);
591  //Any error to report?
592  if(error)
593  return error;
594 
595  //Update exchange hash H with S_REPLY (concatenation of S_PQ and S_CL)
596  error = sshUpdateExchangeHash(connection, serverReply.value,
597  serverReply.length);
598  //Any error to report?
599  if(error)
600  return error;
601 
602  //Get the length of the KEM ciphertext
603  n = connection->kemContext.kemAlgo->ciphertextSize;
604  //Get the length of the KEM shared secret
605  connection->kLen = connection->kemContext.kemAlgo->sharedSecretSize;
606 
607  //Check the length of the S_REPLY field
608  if(serverReply.length < n)
609  return ERROR_INVALID_MESSAGE;
610 
611  //The client decapsulates the ciphertext by using its private key which
612  //leads to K_PQ, a post-quantum shared secret
613  error = kemDecapsulate(&connection->kemContext, serverReply.value,
614  connection->k);
615  //Any error to report?
616  if(error)
617  return error;
618 
619  //The shared secret K is derived as the hash algorithm specified in the named
620  //hybrid key exchange method name over the concatenation of K_PQ and K_CL
621  connection->hashAlgo->init(&hashContext);
622  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
623 
624  //Load server's classical public key (S_CL)
625  error = ecImportPublicKey(&connection->ecdhContext.qb,
626  connection->ecdhContext.curve, serverReply.value + n,
627  serverReply.length - n, EC_PUBLIC_KEY_FORMAT_X963);
628  //Any error to report?
629  if(error)
630  return error;
631 
632  //Ensure the public key is acceptable
633  error = ecdhCheckPublicKey(&connection->ecdhContext,
634  &connection->ecdhContext.qb.q);
635  //Any error to report?
636  if(error)
637  return error;
638 
639  //Compute the classical shared secret K_CL
640  error = sshComputeClassicalEcdhSharedSecret(connection);
641  //Any error to report?
642  if(error)
643  return error;
644 
645  //Derive the shared secret K = HASH(K_PQ, K_CL)
646  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
647  connection->hashAlgo->final(&hashContext, connection->k + sizeof(uint32_t));
648 
649  //Log shared secret (for debugging purpose only)
650  sshDumpKey(connection, "SHARED_SECRET", connection->k + sizeof(uint32_t),
651  connection->hashAlgo->digestSize);
652 
653  //Convert K to string representation
654  STORE32BE(connection->hashAlgo->digestSize, connection->k);
655  connection->kLen = sizeof(uint32_t) + connection->hashAlgo->digestSize;
656 
657  //Update exchange hash H with K (shared secret)
658  error = sshUpdateExchangeHashRaw(connection, connection->k,
659  connection->kLen);
660  //Any error to report?
661  if(error)
662  return error;
663 
664  //Verify the signature on the exchange hash
665  error = sshVerifyExchangeHashSignature(connection, &hostKey, &signature);
666  //Any error to report?
667  if(error)
668  return error;
669 
670  //Destroy classical and post-quantum private keys
671  ecdhFree(&connection->ecdhContext);
672  ecdhInit(&connection->ecdhContext);
673  kemFree(&connection->kemContext);
674  kemInit(&connection->kemContext, NULL);
675 
676  //Key exchange ends by each side sending an SSH_MSG_NEWKEYS message
677  return sshSendNewKeys(connection);
678 #else
679  //Client operation mode is not implemented
681 #endif
682 }
683 
684 
685 /**
686  * @brief Parse PQ-hybrid specific messages
687  * @param[in] connection Pointer to the SSH connection
688  * @param[in] type SSH message type
689  * @param[in] message Pointer to message
690  * @param[in] length Length of the message, in bytes
691  * @return Error code
692  **/
693 
695  const uint8_t *message, size_t length)
696 {
697  error_t error;
698 
699 #if (SSH_CLIENT_SUPPORT == ENABLED)
700  //Client operation mode?
701  if(connection->context->mode == SSH_OPERATION_MODE_CLIENT)
702  {
703  //Check message type
705  {
706  //Parse SSH_MSG_KEX_HYBRID_REPLY message
707  error = sshParseKexHybridReply(connection, message, length);
708  }
709  else
710  {
711  //Unknown message type
712  error = ERROR_INVALID_TYPE;
713  }
714  }
715  else
716 #endif
717 #if (SSH_SERVER_SUPPORT == ENABLED)
718  //Server operation mode?
719  if(connection->context->mode == SSH_OPERATION_MODE_SERVER)
720  {
721  //Check message type
723  {
724  //Parse SSH_MSG_KEX_HYBRID_INIT message
725  error = sshParseKexHybridInit(connection, message, length);
726  }
727  else
728  {
729  //Unknown message type
730  error = ERROR_INVALID_TYPE;
731  }
732  }
733  else
734 #endif
735  //Invalid operation mode?
736  {
737  //Report an error
738  error = ERROR_INVALID_TYPE;
739  }
740 
741  //Return status code
742  return error;
743 }
744 
745 
746 /**
747  * @brief Select key encapsulation mechanism
748  * @param[in] connection Pointer to the SSH connection
749  * @return Error code
750  **/
751 
753 {
754  error_t error;
755 
756  //Release KEM context
757  kemFree(&connection->kemContext);
758 
759 #if (SSH_MLKEM768_SUPPORT == ENABLED)
760  //ML-KEM-768 key encapsulation mechanism?
761  if(sshCompareAlgo(connection->kexAlgo, "mlkem768nistp256-sha256") ||
762  sshCompareAlgo(connection->kexAlgo, "mlkem768x25519-sha256"))
763  {
764  //Initialize KEM context
765  kemInit(&connection->kemContext, MLKEM768_KEM_ALGO);
766  //Successful processing
767  error = NO_ERROR;
768  }
769  else
770 #endif
771 #if (SSH_MLKEM1024_SUPPORT == ENABLED)
772  //ML-KEM-1024 key encapsulation mechanism?
773  if(sshCompareAlgo(connection->kexAlgo, "mlkem1024nistp384-sha384"))
774  {
775  //Initialize KEM context
776  kemInit(&connection->kemContext, MLKEM1024_KEM_ALGO);
777  //Successful processing
778  error = NO_ERROR;
779  }
780  else
781 #endif
782 #if (SSH_SNTRUP761_SUPPORT == ENABLED)
783  //Streamlined NTRU Prime 761 key encapsulation mechanism?
784  if(sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512") ||
785  sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512@openssh.com"))
786  {
787  //Initialize KEM context
788  kemInit(&connection->kemContext, SNTRUP761_KEM_ALGO);
789  //Successful processing
790  error = NO_ERROR;
791  }
792  else
793 #endif
794  //Unknown key encapsulation mechanism?
795  {
796  //Report an error
798  }
799 
800  //Return status code
801  return error;
802 }
803 
804 
805 /**
806  * @brief Select ECDH domain parameters
807  * @param[in] connection Pointer to the SSH connection
808  * @return Error code
809  **/
810 
812 {
813  error_t error;
814  const EcCurve *curve;
815 
816  //Initialize status code
817  error = NO_ERROR;
818 
819 #if (SSH_NISTP256_SUPPORT == ENABLED)
820  //NIST P-256 elliptic curve?
821  if(sshCompareAlgo(connection->kexAlgo, "mlkem768nistp256-sha256"))
822  {
823  curve = SECP256R1_CURVE;
824  }
825  else
826 #endif
827 #if (SSH_NISTP384_SUPPORT == ENABLED)
828  //NIST P-384 elliptic curve?
829  if(sshCompareAlgo(connection->kexAlgo, "mlkem1024nistp384-sha384"))
830  {
831  curve = SECP384R1_CURVE;
832  }
833  else
834 #endif
835 #if (SSH_CURVE25519_SUPPORT == ENABLED)
836  //Curve25519 elliptic curve?
837  if(sshCompareAlgo(connection->kexAlgo, "mlkem768x25519-sha256") ||
838  sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512") ||
839  sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512@openssh.com"))
840  {
841  curve = X25519_CURVE;
842  }
843  else
844 #endif
845  //Unknown elliptic curve?
846  {
847  curve = NULL;
848  }
849 
850  //Make sure the specified elliptic curve is supported
851  if(curve != NULL)
852  {
853  //Save ECDH domain parameters
854  connection->ecdhContext.curve = curve;
855  }
856  else
857  {
858  //Report an error
860  }
861 
862  //Return status code
863  return error;
864 }
865 
866 
867 /**
868  * @brief ECDH key pair generation
869  * @param[in] connection Pointer to the SSH connection
870  * @return Error code
871  **/
872 
874 {
875  error_t error;
876  SshContext *context;
877 
878  //Point to the SSH context
879  context = connection->context;
880 
881 #if (SSH_ECDH_CALLBACK_SUPPORT == ENABLED)
882  //Valid ECDH key pair generation callback function?
883  if(context->ecdhKeyPairGenCallback != NULL)
884  {
885  //Invoke user-defined callback
886  error = context->ecdhKeyPairGenCallback(connection, connection->kexAlgo,
887  &connection->ecdhContext.da.q);
888  }
889  else
890 #endif
891  {
892  //No callback function registered
894  }
895 
896  //Check status code
898  {
899  //Generate an ephemeral key pair
900  error = ecdhGenerateKeyPair(&connection->ecdhContext, context->prngAlgo,
901  context->prngContext);
902  }
903 
904  //Return status code
905  return error;
906 }
907 
908 
909 /**
910  * @brief ECDH shared secret calculation
911  * @param[in] connection Pointer to the SSH connection
912  * @return Error code
913  **/
914 
916 {
917  error_t error;
918 
919 #if (SSH_ECDH_CALLBACK_SUPPORT == ENABLED)
920  //Valid ECDH shared secret calculation callback function?
921  if(connection->context->ecdhSharedSecretCalcCallback != NULL)
922  {
923  //Invoke user-defined callback
924  error = connection->context->ecdhSharedSecretCalcCallback(connection,
925  connection->kexAlgo, &connection->ecdhContext.qb, connection->k,
926  &connection->kLen);
927  }
928  else
929 #endif
930  {
931  //No callback function registered
933  }
934 
935  //Check status code
937  {
938  //Compute the shared secret K
939  error = ecdhComputeSharedSecret(&connection->ecdhContext, connection->k,
940  SSH_MAX_SHARED_SECRET_LEN, &connection->kLen);
941  }
942 
943  //Return status code
944  return error;
945 }
946 
947 
948 /**
949  * @brief Update exchange hash with C_INIT (concatenation of C_PQ and C_CL)
950  * @param[in] connection Pointer to the SSH connection
951  * @return Error code
952  **/
953 
955 {
956  error_t error;
957  size_t m;
958  size_t n;
959  uint8_t *buffer;
960 
961  //Allocate a temporary buffer
962  buffer = sshAllocMem(SSH_BUFFER_SIZE);
963 
964  //Successful memory allocation?
965  if(buffer != NULL)
966  {
967  //Get the length of the KEM public key
968  m = connection->kemContext.kemAlgo->publicKeySize;
969 
970  //Format client's post-quantum public key (C_PQ)
971  osMemcpy(buffer, connection->kemContext.pk, m);
972 
973  //Format client's classical public key (C_CL)
974  error = ecExportPublicKey(&connection->ecdhContext.da.q, buffer + m, &n,
976 
977  //Check status code
978  if(!error)
979  {
980  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
981  error = sshUpdateExchangeHash(connection, buffer, m + n);
982  }
983 
984  //Release previously allocated memory
985  sshFreeMem(buffer);
986  }
987  else
988  {
989  //Failed to allocate memory
990  error = ERROR_OUT_OF_MEMORY;
991  }
992 
993  //Return status code
994  return error;
995 }
996 
997 #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.
error_t ecImportPublicKey(EcPublicKey *key, const EcCurve *curve, const uint8_t *data, size_t length, EcPublicKeyFormat format)
Import an EC public key.
Definition: ec.c:263
@ 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
error_t ecdhCheckPublicKey(EcdhContext *context, const EcPublicKey *publicKey)
Check ECDH public key.
Definition: ecdh.c:224
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:1693
@ 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.
error_t ecExportPublicKey(const EcPublicKey *key, uint8_t *data, size_t *length, EcPublicKeyFormat format)
Export an EC public key.
Definition: ec.c:378
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:338
@ 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: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
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:1185
#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.
#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:84
#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