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.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_exchange_hash.h"
42 #include "ssh/ssh_key_verify.h"
43 #include "ssh/ssh_cert_verify.h"
44 #include "ssh/ssh_misc.h"
45 #include "debug.h"
46 
47 //Check SSH stack configuration
48 #if (SSH_SUPPORT == ENABLED && SSH_HYBRID_KEX_SUPPORT == ENABLED)
49 
50 
51 /**
52  * @brief Send SSH_MSG_KEX_HYBRID_INIT message
53  * @param[in] connection Pointer to the SSH connection
54  * @return Error code
55  **/
56 
58 {
59 #if (SSH_CLIENT_SUPPORT == ENABLED)
60  error_t error;
61  size_t length;
62  uint8_t *message;
63  SshContext *context;
64 
65  //Point to the SSH context
66  context = connection->context;
67 
68  //Point to the buffer where to format the message
69  message = connection->buffer + SSH_PACKET_HEADER_SIZE;
70 
71  //Key exchange algorithms are formulated as key encapsulation mechanisms
72  error = sshSelectKemAlgo(connection);
73 
74  //Check status code
75  if(!error)
76  {
77  //Generate a post-quantum KEM key pair
78  error = kemGenerateKeyPair(&connection->kemContext, context->prngAlgo,
79  context->prngContext);
80  }
81 
82  //Check status code
83  if(!error)
84  {
85  //Load ECDH domain parameters
86  error = sshLoadKexClassicalEcdhParams(connection->kexAlgo,
87  &connection->ecdhContext.params);
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 = ecExport(&connection->ecdhContext.params,
218  &connection->ecdhContext.qa.q, p + sizeof(uint32_t) + m, &n);
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 = ecExport(&connection->ecdhContext.params,
301  &connection->ecdhContext.qa.q, p + sizeof(uint32_t) + m, &n);
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  //Load ECDH domain parameters
451  error = sshLoadKexClassicalEcdhParams(connection->kexAlgo,
452  &connection->ecdhContext.params);
453  //Any error to report?
454  if(error)
455  return error;
456 
457  //Load client's classical public key (C_CL)
458  error = ecImport(&connection->ecdhContext.params,
459  &connection->ecdhContext.qb.q, clientInit.value + n,
460  clientInit.length - n);
461  //Any error to report?
462  if(error)
463  return error;
464 
465  //Ensure the public key is acceptable
466  error = ecdhCheckPublicKey(&connection->ecdhContext.params,
467  &connection->ecdhContext.qb.q);
468  //Any error to report?
469  if(error)
470  return error;
471 
472  //The server responds with an SSH_MSG_KEX_HYBRID_REPLY message
473  return sshSendKexHybridReply(connection);
474 #else
475  //Server operation mode is not implemented
477 #endif
478 }
479 
480 
481 /**
482  * @brief Parse SSH_MSG_KEX_HYBRID_REPLY message
483  * @param[in] connection Pointer to the SSH connection
484  * @param[in] message Pointer to message
485  * @param[in] length Length of the message, in bytes
486  * @return Error code
487  **/
488 
490  const uint8_t *message, size_t length)
491 {
492 #if (SSH_CLIENT_SUPPORT == ENABLED)
493  error_t error;
494  size_t n;
495  const uint8_t *p;
496  SshString hostKeyAlgo;
497  SshBinaryString hostKey;
498  SshBinaryString serverReply;
499  SshBinaryString signature;
500  SshContext *context;
501  HashContext hashContext;
502 
503  //Point to the SSH context
504  context = connection->context;
505 
506  //Debug message
507  TRACE_INFO("SSH_MSG_KEX_HYBRID_REPLY message received (%" PRIuSIZE " bytes)...\r\n", length);
509 
510  //Check operation mode
511  if(context->mode != SSH_OPERATION_MODE_CLIENT)
513 
514  //Check connection state
515  if(connection->state != SSH_CONN_STATE_KEX_HYBRID_REPLY)
517 
518  //Sanity check
519  if(length < sizeof(uint8_t))
520  return ERROR_INVALID_MESSAGE;
521 
522  //Point to the first field of the message
523  p = message + sizeof(uint8_t);
524  //Remaining bytes to process
525  length -= sizeof(uint8_t);
526 
527  //Decode server's public host key (K_S)
528  error = sshParseBinaryString(p, length, &hostKey);
529  //Any error to report?
530  if(error)
531  return error;
532 
533  //Point to the next field
534  p += sizeof(uint32_t) + hostKey.length;
535  length -= sizeof(uint32_t) + hostKey.length;
536 
537  //Decode S_REPLY (concatenation of S_PQ and S_CL)
538  error = sshParseBinaryString(p, length, &serverReply);
539  //Any error to report?
540  if(error)
541  return error;
542 
543  //Point to the next field
544  p += sizeof(uint32_t) + serverReply.length;
545  length -= sizeof(uint32_t) + serverReply.length;
546 
547  //Decode the signature field
548  error = sshParseBinaryString(p, length, &signature);
549  //Any error to report?
550  if(error)
551  return error;
552 
553  //Point to the next field
554  p += sizeof(uint32_t) + signature.length;
555  length -= sizeof(uint32_t) + signature.length;
556 
557  //Malformed message?
558  if(length != 0)
559  return ERROR_INVALID_MESSAGE;
560 
561  //Get the selected server's host key algorithm
562  hostKeyAlgo.value = connection->serverHostKeyAlgo;
563  hostKeyAlgo.length = osStrlen(connection->serverHostKeyAlgo);
564 
565 #if (SSH_CERT_SUPPORT == ENABLED)
566  //Certificate-based authentication?
567  if(sshIsCertPublicKeyAlgo(&hostKeyAlgo))
568  {
569  //Verify server's certificate
570  error = sshVerifyServerCertificate(connection, &hostKeyAlgo, &hostKey);
571  }
572  else
573 #endif
574  {
575  //Verify server's host key
576  error = sshVerifyServerHostKey(connection, &hostKeyAlgo, &hostKey);
577  }
578 
579  //If the client fails to verify the server's host key, it should disconnect
580  //from the server by sending an SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE message
581  if(error)
582  return ERROR_INVALID_KEY;
583 
584  //Update exchange hash H with K_S (server's public host key)
585  error = sshUpdateExchangeHash(connection, hostKey.value, hostKey.length);
586  //Any error to report?
587  if(error)
588  return error;
589 
590  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
591  error = sshDigestClientInit(connection);
592  //Any error to report?
593  if(error)
594  return error;
595 
596  //Update exchange hash H with S_REPLY (concatenation of S_PQ and S_CL)
597  error = sshUpdateExchangeHash(connection, serverReply.value,
598  serverReply.length);
599  //Any error to report?
600  if(error)
601  return error;
602 
603  //Get the length of the KEM ciphertext
604  n = connection->kemContext.kemAlgo->ciphertextSize;
605  //Get the length of the KEM shared secret
606  connection->kLen = connection->kemContext.kemAlgo->sharedSecretSize;
607 
608  //Check the length of the S_REPLY field
609  if(serverReply.length < n)
610  return ERROR_INVALID_MESSAGE;
611 
612  //The client decapsulates the ciphertext by using its private key which
613  //leads to K_PQ, a post-quantum shared secret
614  error = kemDecapsulate(&connection->kemContext, serverReply.value,
615  connection->k);
616  //Any error to report?
617  if(error)
618  return error;
619 
620  //The shared secret K is derived as the hash algorithm specified in the named
621  //hybrid key exchange method name over the concatenation of K_PQ and K_CL
622  connection->hashAlgo->init(&hashContext);
623  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
624 
625  //Load server's classical public key (S_CL)
626  error = ecImport(&connection->ecdhContext.params,
627  &connection->ecdhContext.qb.q, serverReply.value + n,
628  serverReply.length - n);
629  //Any error to report?
630  if(error)
631  return error;
632 
633  //Ensure the public key is acceptable
634  error = ecdhCheckPublicKey(&connection->ecdhContext.params,
635  &connection->ecdhContext.qb.q);
636  //Any error to report?
637  if(error)
638  return error;
639 
640  //Compute the classical shared secret K_CL
641  error = sshComputeClassicalEcdhSharedSecret(connection);
642  //Any error to report?
643  if(error)
644  return error;
645 
646  //Derive the shared secret K = HASH(K_PQ, K_CL)
647  connection->hashAlgo->update(&hashContext, connection->k, connection->kLen);
648  connection->hashAlgo->final(&hashContext, connection->k + sizeof(uint32_t));
649 
650  //Log shared secret (for debugging purpose only)
651  sshDumpKey(connection, "SHARED_SECRET", connection->k + sizeof(uint32_t),
652  connection->hashAlgo->digestSize);
653 
654  //Convert K to string representation
655  STORE32BE(connection->hashAlgo->digestSize, connection->k);
656  connection->kLen = sizeof(uint32_t) + connection->hashAlgo->digestSize;
657 
658  //Update exchange hash H with K (shared secret)
659  error = sshUpdateExchangeHashRaw(connection, connection->k,
660  connection->kLen);
661  //Any error to report?
662  if(error)
663  return error;
664 
665  //Verify the signature on the exchange hash
666  error = sshVerifyExchangeHashSignature(connection, &hostKey, &signature);
667  //Any error to report?
668  if(error)
669  return error;
670 
671  //Destroy classical and post-quantum private keys
672  ecdhFree(&connection->ecdhContext);
673  ecdhInit(&connection->ecdhContext);
674  kemFree(&connection->kemContext);
675  kemInit(&connection->kemContext, NULL);
676 
677  //Key exchange ends by each side sending an SSH_MSG_NEWKEYS message
678  return sshSendNewKeys(connection);
679 #else
680  //Client operation mode is not implemented
682 #endif
683 }
684 
685 
686 /**
687  * @brief Parse PQ-hybrid specific messages
688  * @param[in] connection Pointer to the SSH connection
689  * @param[in] type SSH message type
690  * @param[in] message Pointer to message
691  * @param[in] length Length of the message, in bytes
692  * @return Error code
693  **/
694 
696  const uint8_t *message, size_t length)
697 {
698  error_t error;
699 
700 #if (SSH_CLIENT_SUPPORT == ENABLED)
701  //Client operation mode?
702  if(connection->context->mode == SSH_OPERATION_MODE_CLIENT)
703  {
704  //Check message type
706  {
707  //Parse SSH_MSG_KEX_HYBRID_REPLY message
708  error = sshParseKexHybridReply(connection, message, length);
709  }
710  else
711  {
712  //Unknown message type
713  error = ERROR_INVALID_TYPE;
714  }
715  }
716  else
717 #endif
718 #if (SSH_SERVER_SUPPORT == ENABLED)
719  //Server operation mode?
720  if(connection->context->mode == SSH_OPERATION_MODE_SERVER)
721  {
722  //Check message type
724  {
725  //Parse SSH_MSG_KEX_HYBRID_INIT message
726  error = sshParseKexHybridInit(connection, message, length);
727  }
728  else
729  {
730  //Unknown message type
731  error = ERROR_INVALID_TYPE;
732  }
733  }
734  else
735 #endif
736  //Invalid operation mode?
737  {
738  //Report an error
739  error = ERROR_INVALID_TYPE;
740  }
741 
742  //Return status code
743  return error;
744 }
745 
746 
747 /**
748  * @brief Select key encapsulation mechanism
749  * @param[in] connection Pointer to the SSH connection
750  * @return Error code
751  **/
752 
754 {
755  error_t error;
756 
757  //Release KEM context
758  kemFree(&connection->kemContext);
759 
760 #if (SSH_SNTRUP761_SUPPORT == ENABLED)
761  //Streamlined NTRU Prime 761 key encapsulation mechanism?
762  if(sshCompareAlgo(connection->kexAlgo, "sntrup761x25519-sha512@openssh.com"))
763  {
764  //Initialize KEM context
765  kemInit(&connection->kemContext, SNTRUP761_KEM_ALGO);
766  //Successful processing
767  error = NO_ERROR;
768  }
769  else
770 #endif
771 #if (SSH_KYBER512_SUPPORT == ENABLED)
772  //Kyber-512 key encapsulation mechanism?
773  if(sshCompareAlgo(connection->kexAlgo, "x25519-kyber-512r3-sha256-d00@amazon.com") ||
774  sshCompareAlgo(connection->kexAlgo, "ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org"))
775  {
776  //Initialize KEM context
777  kemInit(&connection->kemContext, KYBER512_KEM_ALGO);
778  //Successful processing
779  error = NO_ERROR;
780  }
781  else
782 #endif
783 #if (SSH_KYBER768_SUPPORT == ENABLED)
784  //Kyber-768 key encapsulation mechanism?
785  if(sshCompareAlgo(connection->kexAlgo, "ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org"))
786  {
787  //Initialize KEM context
788  kemInit(&connection->kemContext, KYBER768_KEM_ALGO);
789  //Successful processing
790  error = NO_ERROR;
791  }
792  else
793 #endif
794 #if (SSH_KYBER1024_SUPPORT == ENABLED)
795  //Kyber-1024 key encapsulation mechanism?
796  if(sshCompareAlgo(connection->kexAlgo, "ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org"))
797  {
798  //Initialize KEM context
799  kemInit(&connection->kemContext, KYBER1024_KEM_ALGO);
800  //Successful processing
801  error = NO_ERROR;
802  }
803  else
804 #endif
805  //Unknown key encapsulation mechanism?
806  {
807  //Report an error
809  }
810 
811  //Return status code
812  return error;
813 }
814 
815 
816 /**
817  * @brief Load the EC parameters that match specified key exchange algorithm
818  * @param[in] kexAlgo Key exchange algorithm name
819  * @param[out] params Elliptic curve domain parameters
820  * @return Error code
821  **/
822 
824  EcDomainParameters *params)
825 {
826  error_t error;
827  const EcCurveInfo *curveInfo;
828 
829 #if (SSH_CURVE25519_SUPPORT == ENABLED)
830  //Curve25519 elliptic curve?
831  if(sshCompareAlgo(kexAlgo, "sntrup761x25519-sha512@openssh.com") ||
832  sshCompareAlgo(kexAlgo, "x25519-kyber-512r3-sha256-d00@amazon.com"))
833  {
834  curveInfo = X25519_CURVE;
835  }
836  else
837 #endif
838 #if (SSH_NISTP256_SUPPORT == ENABLED)
839  //NIST P-256 elliptic curve?
840  if(sshCompareAlgo(kexAlgo, "ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org"))
841  {
842  curveInfo = SECP256R1_CURVE;
843  }
844  else
845 #endif
846 #if (SSH_NISTP384_SUPPORT == ENABLED)
847  //NIST P-384 elliptic curve?
848  if(sshCompareAlgo(kexAlgo, "ecdh-nistp384-kyber-768r3-sha384-d00@openquantumsafe.org"))
849  {
850  curveInfo = SECP384R1_CURVE;
851  }
852  else
853 #endif
854 #if (SSH_NISTP521_SUPPORT == ENABLED)
855  //NIST P-521 elliptic curve?
856  if(sshCompareAlgo(kexAlgo, "ecdh-nistp521-kyber-1024r3-sha512-d00@openquantumsafe.org"))
857  {
858  curveInfo = SECP521R1_CURVE;
859  }
860  else
861 #endif
862  //Unknown elliptic curve?
863  {
864  curveInfo = NULL;
865  }
866 
867  //Make sure the key exchange algorithm is acceptable
868  if(curveInfo != NULL)
869  {
870  //Load EC domain parameters
871  error = ecLoadDomainParameters(params, curveInfo);
872  }
873  else
874  {
875  //Report an error
877  }
878 
879  //Return status code
880  return error;
881 }
882 
883 
884 /**
885  * @brief ECDH key pair generation
886  * @param[in] connection Pointer to the SSH connection
887  * @return Error code
888  **/
889 
891 {
892  error_t error;
893  SshContext *context;
894 
895  //Point to the SSH context
896  context = connection->context;
897 
898 #if (SSH_ECDH_CALLBACK_SUPPORT == ENABLED)
899  //Valid ECDH key pair generation callback function?
900  if(context->ecdhKeyPairGenCallback != NULL)
901  {
902  //Invoke user-defined callback
903  error = context->ecdhKeyPairGenCallback(connection, connection->kexAlgo,
904  &connection->ecdhContext.qa);
905  }
906  else
907 #endif
908  {
909  //No callback function registered
911  }
912 
913  //Check status code
915  {
916  //Generate an ephemeral key pair
917  error = ecdhGenerateKeyPair(&connection->ecdhContext, context->prngAlgo,
918  context->prngContext);
919  }
920 
921  //Return status code
922  return error;
923 }
924 
925 
926 /**
927  * @brief ECDH shared secret calculation
928  * @param[in] connection Pointer to the SSH connection
929  * @return Error code
930  **/
931 
933 {
934  error_t error;
935 
936 #if (SSH_ECDH_CALLBACK_SUPPORT == ENABLED)
937  //Valid ECDH shared secret calculation callback function?
938  if(connection->context->ecdhSharedSecretCalcCallback != NULL)
939  {
940  //Invoke user-defined callback
941  error = connection->context->ecdhSharedSecretCalcCallback(connection,
942  connection->kexAlgo, &connection->ecdhContext.qb, connection->k,
943  &connection->kLen);
944  }
945  else
946 #endif
947  {
948  //No callback function registered
950  }
951 
952  //Check status code
954  {
955  //Compute the shared secret K
956  error = ecdhComputeSharedSecret(&connection->ecdhContext, connection->k,
957  SSH_MAX_SHARED_SECRET_LEN, &connection->kLen);
958  }
959 
960  //Return status code
961  return error;
962 }
963 
964 
965 /**
966  * @brief Update exchange hash with C_INIT (concatenation of C_PQ and C_CL)
967  * @param[in] connection Pointer to the SSH connection
968  * @return Error code
969  **/
970 
972 {
973  error_t error;
974  size_t m;
975  size_t n;
976  uint8_t *buffer;
977 
978  //Allocate a temporary buffer
979  buffer = sshAllocMem(SSH_BUFFER_SIZE);
980 
981  //Successful memory allocation?
982  if(buffer != NULL)
983  {
984  //Get the length of the KEM public key
985  m = connection->kemContext.kemAlgo->publicKeySize;
986 
987  //Format client's post-quantum public key (C_PQ)
988  osMemcpy(buffer, connection->kemContext.pk, m);
989 
990  //Format client's classical public key (C_CL)
991  error = ecExport(&connection->ecdhContext.params,
992  &connection->ecdhContext.qa.q, buffer + m, &n);
993 
994  //Check status code
995  if(!error)
996  {
997  //Update exchange hash H with C_INIT (concatenation of C_PQ and C_CL)
998  error = sshUpdateExchangeHash(connection, buffer, m + n);
999  }
1000 
1001  //Release previously allocated memory
1002  sshFreeMem(buffer);
1003  }
1004  else
1005  {
1006  //Failed to allocate memory
1007  error = ERROR_OUT_OF_MEMORY;
1008  }
1009 
1010  //Return status code
1011  return error;
1012 }
1013 
1014 #endif
uint8_t message[]
Definition: chap.h:154
uint8_t type
Definition: coap_common.h:176
#define PRIuSIZE
char char_t
Definition: compiler_port.h:48
#define STORE32BE(a, p)
Definition: cpu_endian.h:286
Debugging facilities.
#define TRACE_INFO(...)
Definition: debug.h:95
#define TRACE_VERBOSE_ARRAY(p, a, n)
Definition: debug.h:125
uint8_t n
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 ecLoadDomainParameters(EcDomainParameters *params, const EcCurveInfo *curveInfo)
Load EC domain parameters.
Definition: ec.c:90
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
#define X25519_CURVE
Definition: ec_curves.h:251
#define SECP521R1_CURVE
Definition: ec_curves.h:242
#define SECP256R1_CURVE
Definition: ec_curves.h:240
#define SECP384R1_CURVE
Definition: ec_curves.h:241
error_t ecdhComputeSharedSecret(EcdhContext *context, uint8_t *output, size_t outputSize, size_t *outputLen)
Compute ECDH shared secret.
Definition: ecdh.c:340
void ecdhFree(EcdhContext *context)
Release ECDH context.
Definition: ecdh.c:65
error_t ecdhGenerateKeyPair(EcdhContext *context, const PrngAlgo *prngAlgo, void *prngContext)
ECDH key pair generation.
Definition: ecdh.c:85
error_t ecdhCheckPublicKey(const EcDomainParameters *params, EcPoint *publicKey)
Check ECDH public key.
Definition: ecdh.c:227
void ecdhInit(EcdhContext *context)
Initialize ECDH context.
Definition: ecdh.c:48
error_t
Error codes.
Definition: error.h:43
@ ERROR_INVALID_TYPE
Definition: error.h:115
@ ERROR_INVALID_KEY
Definition: error.h:106
@ ERROR_UNSUPPORTED_KEY_EXCH_ALGO
Definition: error.h:131
@ ERROR_INVALID_MESSAGE
Definition: error.h:105
@ ERROR_NOT_IMPLEMENTED
Definition: error.h:66
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_OUT_OF_MEMORY
Definition: error.h:63
@ ERROR_UNEXPECTED_MESSAGE
Definition: error.h:194
void kemFree(KemContext *context)
Release KEM context.
Definition: kem.c:62
error_t kemEncapsulate(KemContext *context, const PrngAlgo *prngAlgo, void *prngContext, uint8_t *ct, uint8_t *ss)
Encapsulation algorithm.
Definition: kem.c:209
error_t kemDecapsulate(KemContext *context, const uint8_t *ct, uint8_t *ss)
Decapsulation algorithm.
Definition: kem.c:240
void kemInit(KemContext *context, const KemAlgo *kemAlgo)
Initialize KEM context.
Definition: kem.c:48
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
#define KYBER1024_KEM_ALGO
Definition: kyber1024.h:47
#define KYBER512_KEM_ALGO
Definition: kyber512.h:47
#define KYBER768_KEM_ALGO
Definition: kyber768.h:47
uint8_t p
Definition: ndp.h:300
uint8_t m
Definition: ndp.h:304
#define osMemcpy(dest, src, length)
Definition: os_port.h:141
#define osStrlen(s)
Definition: os_port.h:165
#define SNTRUP761_KEM_ALGO
Definition: sntrup761.h:47
Secure Shell (SSH)
#define sshFreeMem(p)
Definition: ssh.h:736
@ SSH_CONN_STATE_KEX_HYBRID_REPLY
Definition: ssh.h:1059
@ SSH_CONN_STATE_SERVER_NEW_KEYS
Definition: ssh.h:1061
@ SSH_CONN_STATE_KEX_HYBRID_INIT
Definition: ssh.h:1058
@ SSH_OPERATION_MODE_SERVER
Definition: ssh.h:902
@ SSH_OPERATION_MODE_CLIENT
Definition: ssh.h:901
#define sshAllocMem(size)
Definition: ssh.h:731
#define SSH_BUFFER_SIZE
Definition: ssh.h:875
#define SSH_MAX_SHARED_SECRET_LEN
Definition: ssh.h:851
#define SshConnection
Definition: ssh.h:883
#define SshContext
Definition: ssh.h:879
@ SSH_MSG_KEX_HYBRID_INIT
Definition: ssh.h:968
@ SSH_MSG_KEX_HYBRID_REPLY
Definition: ssh.h:969
bool_t sshIsCertPublicKeyAlgo(const SshString *publicKeyAlgo)
Test if the specified public key algorithm is using certificates.
SSH algorithm negotiation.
error_t sshVerifyServerCertificate(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *hostKey)
Verify server's certificate.
SSH certificate verification.
error_t sshUpdateExchangeHash(SshConnection *connection, const void *data, size_t length)
Update exchange hash calculation.
error_t sshUpdateExchangeHashRaw(SshConnection *connection, const void *data, size_t length)
Update exchange hash calculation (raw data)
error_t sshGenerateExchangeHashSignature(SshConnection *connection, uint8_t *p, size_t *written)
Compute the signature on the exchange hash.
error_t sshVerifyExchangeHashSignature(SshConnection *connection, const SshBinaryString *serverHostKey, const SshBinaryString *signature)
Verify the signature on the exchange hash.
Exchange hash calculation.
error_t sshSendNewKeys(SshConnection *connection)
Send SSH_MSG_NEWKEYS message.
Definition: ssh_kex.c:194
SSH key exchange.
error_t sshFormatKexHybridInit(SshConnection *connection, uint8_t *p, size_t *length)
Format SSH_MSG_KEX_HYBRID_INIT message.
error_t sshFormatKexHybridReply(SshConnection *connection, uint8_t *p, size_t *length)
Format SSH_MSG_KEX_HYBRID_REPLY message.
error_t sshParseKexHybridInit(SshConnection *connection, const uint8_t *message, size_t length)
Parse SSH_MSG_KEX_HYBRID_INIT message.
error_t sshSendKexHybridInit(SshConnection *connection)
Send SSH_MSG_KEX_HYBRID_INIT message.
error_t sshLoadKexClassicalEcdhParams(const char_t *kexAlgo, EcDomainParameters *params)
Load the EC parameters that match specified key exchange algorithm.
error_t sshGenerateClassicalEcdhKeyPair(SshConnection *connection)
ECDH key pair generation.
error_t sshComputeClassicalEcdhSharedSecret(SshConnection *connection)
ECDH shared secret calculation.
error_t sshParseKexHybridReply(SshConnection *connection, const uint8_t *message, size_t length)
Parse SSH_MSG_KEX_HYBRID_REPLY message.
error_t sshSendKexHybridReply(SshConnection *connection)
Send SSH_MSG_KEX_HYBRID_REPLY message.
error_t sshParseKexHybridMessage(SshConnection *connection, uint8_t type, const uint8_t *message, size_t length)
Parse PQ-hybrid specific messages.
error_t sshSelectKemAlgo(SshConnection *connection)
Select key encapsulation mechanism.
error_t sshDigestClientInit(SshConnection *connection)
Update exchange hash with C_INIT (concatenation of C_PQ and C_CL)
Post-quantum hybrid key exchange.
void sshDumpKey(SshConnection *connection, const char_t *label, const uint8_t *key, size_t keyLen)
Dump secret key (for debugging purpose only)
error_t sshVerifyServerHostKey(SshConnection *connection, const SshString *publicKeyAlgo, const SshBinaryString *hostKey)
Verify server's host key.
SSH host key verification.
error_t sshParseBinaryString(const uint8_t *p, size_t length, SshBinaryString *string)
Parse a binary string.
Definition: ssh_misc.c:1189
bool_t sshCompareAlgo(const char_t *name1, const char_t *name2)
Compare algorithm names.
Definition: ssh_misc.c:1653
error_t sshFormatHostKey(SshConnection *connection, uint8_t *p, size_t *written)
Format host key structure.
Definition: ssh_misc.c:863
SSH helper functions.
error_t sshSendPacket(SshConnection *connection, uint8_t *payload, size_t payloadLen)
Send SSH packet.
Definition: ssh_packet.c:57
SSH packet encryption/decryption.
#define SSH_PACKET_HEADER_SIZE
Definition: ssh_packet.h:38
SSH transport layer protocol.
Elliptic curve parameters.
Definition: ec_curves.h:295
EC domain parameters.
Definition: ec.h:76
Binary string.
Definition: ssh_types.h:67
const uint8_t * value
Definition: ssh_types.h:68
size_t length
Definition: ssh_types.h:69
String.
Definition: ssh_types.h:56
const char_t * value
Definition: ssh_types.h:57
size_t length
Definition: ssh_types.h:58
uint8_t length
Definition: tcp.h:368
Generic hash algorithm context.