blob: 757b3304d0b26d03534385675563202fddc90493 [file]
/*
* Copyright (c) 2024 Grant Ramsay <grant.ramsay@hotmail.com>
* Copyright (c) 2026 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(ssh, CONFIG_SSH_LOG_LEVEL);
#include "ssh_kex.h"
#include "ssh_host_key.h"
#include <psa/crypto.h>
#define KEXINIT_COOKIE_SIZE 16
static int gen_all_keys(struct ssh_transport *transport);
static int gen_key(struct ssh_transport *transport, char c, uint8_t *buf, size_t len);
/* Note: The order of the strings is important */
static const struct ssh_string supported_kex_algs[] = {
[SSH_KEX_ALG_CURVE25519_SHA256] = SSH_STRING_LITERAL("curve25519-sha256"),
/* SSH_STRING_LITERAL("curve25519-sha256@libssh.org"), */
};
static const struct ssh_string supported_server_host_key_algs[] = {
/* SSH_STRING_LITERAL("ssh-ed25519"), */
#ifdef CONFIG_SSH_HOST_KEY_ALG_RSA_SHA2_256
[SSH_HOST_KEY_ALG_RSA_SHA2_256] = SSH_STRING_LITERAL("rsa-sha2-256"),
#endif
#ifdef CONFIG_SSH_HOST_KEY_ALG_RSA_SHA2_512
[SSH_HOST_KEY_ALG_RSA_SHA2_512] = SSH_STRING_LITERAL("rsa-sha2-512"),
#endif
};
static const struct ssh_string supported_encryption_algs[] = {
/* SSH_STRING_LITERAL("chacha20-poly1305@openssh.com"), */
[SSH_ENCRYPTION_ALG_AES128_CTR] = SSH_STRING_LITERAL("aes128-ctr"),
};
static const struct ssh_string supported_mac_algs[] = {
[SSH_MAC_ALG_HMAC_SHA2_256] = SSH_STRING_LITERAL("hmac-sha2-256"),
};
static const struct ssh_string supported_compression_algs[] = {
[SSH_COMPRESSION_ALG_NONE] = SSH_STRING_LITERAL("none"),
};
static const struct ssh_string supported_languages[] = {
SSH_STRING_LITERAL(""),
};
BUILD_ASSERT(ARRAY_SIZE(supported_kex_algs) > 0);
BUILD_ASSERT(ARRAY_SIZE(supported_server_host_key_algs) > 0);
BUILD_ASSERT(ARRAY_SIZE(supported_encryption_algs) > 0);
BUILD_ASSERT(ARRAY_SIZE(supported_mac_algs) > 0);
BUILD_ASSERT(ARRAY_SIZE(supported_compression_algs) > 0);
BUILD_ASSERT(ARRAY_SIZE(supported_languages) > 0);
int ssh_kex_send_kexinit(struct ssh_transport *transport)
{
const bool first_kex_pkt_follows = false;
const uint32_t reserved = 0;
int ret;
/* Add "ext-info-c" to client kex algs to enable RFC8308 extensions
* (server "ext-info-s" does not appear to be needed).
*/
struct ssh_string kex_algs[ARRAY_SIZE(supported_kex_algs) + 1];
uint32_t num_kex_algs = 0;
struct ssh_string **local_kexinit;
SSH_PAYLOAD_BUF(payload, transport->tx_buf);
for (; num_kex_algs < ARRAY_SIZE(supported_kex_algs); num_kex_algs++) {
kex_algs[num_kex_algs] = supported_kex_algs[num_kex_algs];
}
if (IS_ENABLED(CONFIG_SSH_CLIENT) && !transport->server) {
kex_algs[num_kex_algs++] = (struct ssh_string)SSH_STRING_LITERAL("ext-info-c");
}
/* Header / Message ID / Cookie / Kex algorithms / Server host key algorithms
* Encryption algorithms client to server / Encryption algorithms server to client
* MAC algorithms client to server / MAC algorithms server to client
* Compression algorithms client to server / Compression algorithms server to client
* Languages client to server / Languages server to client
* First kex packet follows / Reserved for future extension
*/
if (!ssh_payload_skip_bytes(&payload, SSH_PKT_MSG_ID_OFFSET) ||
!ssh_payload_write_byte(&payload, SSH_MSG_KEXINIT) ||
!ssh_payload_write_csrand(&payload, KEXINIT_COOKIE_SIZE) ||
!ssh_payload_write_name_list(&payload, kex_algs, num_kex_algs) ||
!ssh_payload_write_name_list(&payload, supported_server_host_key_algs,
ARRAY_SIZE(supported_server_host_key_algs)) ||
!ssh_payload_write_name_list(&payload, supported_encryption_algs,
ARRAY_SIZE(supported_encryption_algs)) ||
!ssh_payload_write_name_list(&payload, supported_encryption_algs,
ARRAY_SIZE(supported_encryption_algs)) ||
!ssh_payload_write_name_list(&payload, supported_mac_algs,
ARRAY_SIZE(supported_mac_algs)) ||
!ssh_payload_write_name_list(&payload, supported_mac_algs,
ARRAY_SIZE(supported_mac_algs)) ||
!ssh_payload_write_name_list(&payload, supported_compression_algs,
ARRAY_SIZE(supported_compression_algs)) ||
!ssh_payload_write_name_list(&payload, supported_compression_algs,
ARRAY_SIZE(supported_compression_algs)) ||
!ssh_payload_write_name_list(&payload, supported_languages,
ARRAY_SIZE(supported_languages)) ||
!ssh_payload_write_name_list(&payload, supported_languages,
ARRAY_SIZE(supported_languages)) ||
!ssh_payload_write_bool(&payload, first_kex_pkt_follows) ||
!ssh_payload_write_u32(&payload, reserved)) {
return -ENOBUFS;
}
/* Save local kexinit payload for KEX hash
* ssh_transport_send_packet encrypts in-place, so we need to make a copy here.
*/
local_kexinit = transport->server ? &transport->server_kexinit :
&transport->client_kexinit;
*local_kexinit = ssh_payload_string_alloc(&transport->kex_heap,
&payload.data[SSH_PKT_MSG_ID_OFFSET],
payload.len - SSH_PKT_MSG_ID_OFFSET);
if (*local_kexinit == NULL) {
return -ENOMEM;
}
ret = ssh_transport_send_packet(transport, &payload);
if (ret < 0) {
NET_DBG("Failed to send %s (%d)", "KEXINIT", ret);
return ret;
}
transport->kexinit_sent = true;
return ret;
}
int ssh_kex_send_newkeys(struct ssh_transport *transport)
{
int ret;
SSH_PAYLOAD_BUF(payload, transport->tx_buf);
if (!ssh_payload_skip_bytes(&payload, SSH_PKT_MSG_ID_OFFSET) ||
!ssh_payload_write_byte(&payload, SSH_MSG_NEWKEYS)) {
return -ENOBUFS;
}
ret = ssh_transport_send_packet(transport, &payload);
if (ret < 0) {
NET_DBG("Failed to send %s (%d)", "NEWKEYS", ret);
}
return ret;
}
static int kex_name_list_find_first(bool server, const struct ssh_string *lut, int lut_len,
struct ssh_payload *name_list, struct ssh_string *name_out)
{
/* We want the first item in the clients name list that matches a name
* in the servers name list. This changes the order of how we iterate
* the name list vs look up table depending on if we are a server or client.
*/
if (IS_ENABLED(CONFIG_SSH_SERVER) && server) {
while (ssh_payload_name_list_iter(name_list, name_out)) {
for (int i = 0; i < lut_len; i++) {
if (ssh_strings_equal(&lut[i], name_out)) {
return i;
}
}
}
} else if (IS_ENABLED(CONFIG_SSH_CLIENT)) {
for (int i = 0; i < lut_len; i++) {
struct ssh_payload name_list_copy = *name_list;
while (ssh_payload_name_list_iter(&name_list_copy, name_out)) {
if (ssh_strings_equal(&lut[i], name_out)) {
*name_list = name_list_copy;
return i;
}
}
}
}
return -ENOTSUP;
}
int ssh_kex_process_kexinit(struct ssh_transport *transport, struct ssh_payload *rx_pkt)
{
int ret;
struct ssh_payload name_list;
struct ssh_string name;
bool first_kex_pkt_follows;
struct ssh_string **remote_kexinit;
NET_DBG("KEXINIT");
if (transport->kexinit_received) {
return -1;
}
/* Skip cookie */
if (!ssh_payload_skip_bytes(rx_pkt, KEXINIT_COOKIE_SIZE)) {
NET_ERR("Length error");
return -1;
}
/* Kex algorithm */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "kex", name_list.size, name_list.data);
if (IS_ENABLED(CONFIG_SSH_SERVER) && transport->server &&
transport->auths_allowed_mask & BIT(SSH_AUTH_PUBKEY)) {
/* Check if the client supports RFC8308 extensions. */
/* Required for the "server-sig-algs" extension to */
/* avoid using SHA-1 signatures in pubkey auth. */
struct ssh_payload name_list_copy = name_list;
static const struct ssh_string ext_info_c = SSH_STRING_LITERAL("ext-info-c");
ret = kex_name_list_find_first(
transport->server, &ext_info_c, 1,
&name_list_copy, &name);
if (ret < 0) {
NET_DBG("Client does not support RFC8308 extensions, "
"disabling pubkey auth");
transport->auths_allowed_mask &= ~BIT(SSH_AUTH_PUBKEY);
}
}
ret = kex_name_list_find_first(transport->server, supported_kex_algs,
ARRAY_SIZE(supported_kex_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "kex");
return ret;
}
transport->algs.kex = ret;
NET_DBG("Using %s alg %.*s", "kex", name.len, name.data);
/* Server host key algorithm */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "host key", name_list.size, name_list.data);
ret = kex_name_list_find_first(transport->server, supported_server_host_key_algs,
ARRAY_SIZE(supported_server_host_key_algs),
&name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "host key");
return ret;
}
transport->algs.server_host_key = ret;
NET_DBG("Using %s alg %.*s", "host key", name.len, name.data);
/* Encryption algorithm (client to server) */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "encryption (C2S)", name_list.size, name_list.data);
ret = kex_name_list_find_first(
transport->server, supported_encryption_algs,
ARRAY_SIZE(supported_encryption_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "encryption (C2S)");
return ret;
}
transport->algs.encryption_c2s = ret;
NET_DBG("Using %s alg %.*s", "encryption (C2S)", name.len, name.data);
/* Encryption algorithm (server to client) */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "encryption (S2C)", name_list.size, name_list.data);
ret = kex_name_list_find_first(
transport->server, supported_encryption_algs,
ARRAY_SIZE(supported_encryption_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "encryption (S2C)");
return ret;
}
transport->algs.encryption_s2c = ret;
NET_DBG("Using %s alg %.*s", "encryption (S2C)", name.len, name.data);
/* MAC algorithm (client to server) */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "MAC (C2S)", name_list.size, name_list.data);
ret = kex_name_list_find_first(
transport->server, supported_mac_algs,
ARRAY_SIZE(supported_mac_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "MAC (C2S)");
return ret;
}
transport->algs.mac_c2s = ret;
NET_DBG("Using %s alg %.*s", "MAC (C2S)", name.len, name.data);
/* MAC algorithm (server to client) */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "MAC (S2C)", name_list.size, name_list.data);
ret = kex_name_list_find_first(
transport->server, supported_mac_algs,
ARRAY_SIZE(supported_mac_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "MAC (S2C)");
return ret;
}
transport->algs.mac_s2c = ret;
NET_DBG("Using %s alg %.*s", "MAC (S2C)", name.len, name.data);
/* Compression algorithm (client to server) */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "compression (C2S)", name_list.size, name_list.data);
ret = kex_name_list_find_first(
transport->server, supported_compression_algs,
ARRAY_SIZE(supported_compression_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "compression (C2S)");
return ret;
}
transport->algs.compression_c2s = ret;
NET_DBG("Using %s alg %.*s", "compression (C2S)", name.len, name.data);
/* Compression algorithm (server to client) */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "compression (S2C)", name_list.size, name_list.data);
ret = kex_name_list_find_first(
transport->server, supported_compression_algs,
ARRAY_SIZE(supported_compression_algs), &name_list, &name);
if (ret < 0) {
NET_DBG("No suitable %s alg", "compression (S2C)");
return ret;
}
transport->algs.compression_s2c = ret;
NET_DBG("Using %s alg %.*s", "compression (S2C)", name.len, name.data);
/* Languages (server to client) - Not supported */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "languages (C2S)", name_list.size, name_list.data);
/* Languages (server to client) - Not supported */
if (!ssh_payload_read_name_list(rx_pkt, &name_list)) {
NET_ERR("Length error");
return -1;
}
NET_DBG("Remote %s algs: %.*s", "languages (S2C)", name_list.size, name_list.data);
/* First kex packet follows (TODO: add support for "first_kex_pkt_follows") */
if (!ssh_payload_read_bool(rx_pkt, &first_kex_pkt_follows)) {
NET_ERR("Length error");
return -1;
}
/* Reserved for future extension */
if (!ssh_payload_read_u32(rx_pkt, NULL)) {
NET_ERR("Length error");
return -1;
}
if (!ssh_payload_read_complete(rx_pkt)) {
NET_ERR("Unexpected extra data");
return -1;
}
/* Save remote kexinit payload for KEX hash */
remote_kexinit = transport->server ?
&transport->client_kexinit : &transport->server_kexinit;
*remote_kexinit = ssh_payload_string_alloc(
&transport->kex_heap, &rx_pkt->data[SSH_PKT_MSG_ID_OFFSET],
rx_pkt->len - SSH_PKT_MSG_ID_OFFSET);
if (*remote_kexinit == NULL) {
return -ENOMEM;
}
transport->kexinit_received = true;
if (!transport->kexinit_sent) {
ret = ssh_kex_send_kexinit(transport);
if (ret != 0) {
return ret;
}
}
#ifdef CONFIG_SSH_CLIENT
if (!transport->server) {
/* Generate ephemeral Curve25519 key */
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_status_t status;
psa_set_key_type(&attr, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_MONTGOMERY));
psa_set_key_bits(&attr, 255);
psa_set_key_algorithm(&attr, PSA_ALG_ECDH);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_DERIVE | PSA_KEY_USAGE_EXPORT);
status = psa_generate_key(&attr, &transport->ecdh_ephemeral_key);
psa_reset_key_attributes(&attr);
if (status != PSA_SUCCESS) {
NET_ERR("genkey curve25519 failed: %d", status);
return -EIO;
}
ret = ssh_kex_ecdh_send_kex_ecdh_init(transport);
if (ret < 0) {
return ret;
}
}
#endif
return 0;
}
int ssh_kex_process_newkeys(struct ssh_transport *transport, struct ssh_payload *rx_pkt)
{
psa_key_attributes_t mac_attr = PSA_KEY_ATTRIBUTES_INIT;
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
struct ssh_cipher_aes128_ctr *cipher;
const uint8_t *tx_integ;
const uint8_t *rx_integ;
psa_key_id_t *tx_mac;
psa_key_id_t *rx_mac;
psa_status_t status;
int ret;
NET_DBG("NEWKEYS");
/* TODO: if transport->awaiting_newkeys */
if (!transport->kexinit_received || !transport->kexinit_sent || !transport->newkeys_sent) {
return -1;
}
if (!ssh_payload_read_complete(rx_pkt)) {
NET_ERR("Unexpected extra data");
return -1;
}
ret = gen_all_keys(transport);
if (ret < 0) {
return ret;
}
/* Import AES keys and set up persistent cipher operations */
psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attr, 128);
psa_set_key_algorithm(&attr, PSA_ALG_CTR);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
/* Server encryption key → server's TX cipher or client's RX cipher */
cipher = transport->server ? &transport->tx_cipher : &transport->rx_cipher;
status = psa_import_key(&attr, transport->encrypt_key_server,
sizeof(transport->encrypt_key_server), &cipher->key_id);
if (status != PSA_SUCCESS) {
NET_ERR("Cipher key import error");
return -1;
}
cipher->op = psa_cipher_operation_init();
if (transport->server) {
status = psa_cipher_encrypt_setup(&cipher->op, cipher->key_id, PSA_ALG_CTR);
} else {
status = psa_cipher_decrypt_setup(&cipher->op, cipher->key_id, PSA_ALG_CTR);
}
if (status != PSA_SUCCESS) {
NET_ERR("Cipher setup error");
return -1;
}
status = psa_cipher_set_iv(&cipher->op, transport->iv_server, 16);
if (status != PSA_SUCCESS) {
NET_ERR("Cipher IV error");
return -1;
}
/* Client encryption key → server's RX cipher or client's TX cipher */
cipher = transport->server ? &transport->rx_cipher : &transport->tx_cipher;
status = psa_import_key(&attr, transport->encrypt_key_client,
sizeof(transport->encrypt_key_client), &cipher->key_id);
if (status != PSA_SUCCESS) {
NET_ERR("Cipher key import error");
return -1;
}
cipher->op = psa_cipher_operation_init();
if (transport->server) {
status = psa_cipher_decrypt_setup(&cipher->op, cipher->key_id, PSA_ALG_CTR);
} else {
status = psa_cipher_encrypt_setup(&cipher->op, cipher->key_id, PSA_ALG_CTR);
}
if (status != PSA_SUCCESS) {
NET_ERR("Cipher setup error");
return -1;
}
status = psa_cipher_set_iv(&cipher->op, transport->iv_client, 16);
if (status != PSA_SUCCESS) {
NET_ERR("Cipher IV error");
return -1;
}
psa_reset_key_attributes(&attr);
/* Import HMAC keys */
psa_set_key_type(&mac_attr, PSA_KEY_TYPE_HMAC);
psa_set_key_algorithm(&mac_attr, PSA_ALG_HMAC(PSA_ALG_SHA_256));
psa_set_key_usage_flags(&mac_attr, PSA_KEY_USAGE_SIGN_MESSAGE |
PSA_KEY_USAGE_VERIFY_MESSAGE |
PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH);
tx_mac = &transport->tx_mac_key_id;
rx_mac = &transport->rx_mac_key_id;
tx_integ = transport->server ? transport->integ_key_server : transport->integ_key_client;
rx_integ = transport->server ? transport->integ_key_client : transport->integ_key_server;
status = psa_import_key(&mac_attr, tx_integ, sizeof(transport->integ_key_server), tx_mac);
if (status != PSA_SUCCESS) {
NET_ERR("MAC key import error");
return -1;
}
status = psa_import_key(&mac_attr, rx_integ, sizeof(transport->integ_key_client), rx_mac);
if (status != PSA_SUCCESS) {
NET_ERR("MAC key import error");
return -1;
}
psa_reset_key_attributes(&mac_attr);
transport->encrypted = true;
transport->kexinit_sent = false;
transport->kexinit_received = false;
transport->newkeys_sent = false;
transport->kex_expiry = sys_timepoint_calc(K_HOURS(1));
transport->tx_bytes_since_kex = 0;
transport->rx_bytes_since_kex = 0;
/* Free/clear everything we no longer need */
psa_destroy_key(transport->ecdh_ephemeral_key);
transport->ecdh_ephemeral_key = PSA_KEY_ID_NULL;
if (transport->client_kexinit != NULL) {
sys_heap_free(&transport->kex_heap, transport->client_kexinit);
transport->client_kexinit = NULL;
}
if (transport->server_kexinit != NULL) {
sys_heap_free(&transport->kex_heap, transport->server_kexinit);
transport->server_kexinit = NULL;
}
if (transport->shared_secret != NULL) {
ssh_zeroize((void *)transport->shared_secret->data, transport->shared_secret->len);
sys_heap_free(&transport->kex_heap, transport->shared_secret);
transport->shared_secret = NULL;
}
if (transport->exchange_hash != NULL) {
ssh_zeroize((void *)transport->exchange_hash->data, transport->exchange_hash->len);
sys_heap_free(&transport->kex_heap, transport->exchange_hash);
transport->exchange_hash = NULL;
}
#ifdef CONFIG_SSH_CLIENT
if (!transport->server && !transport->authenticated) {
static const struct ssh_string service_name = SSH_STRING_LITERAL("ssh-userauth");
ret = ssh_transport_send_service_request(transport, &service_name);
if (ret < 0) {
return ret;
}
}
#endif
#ifdef CONFIG_SSH_SERVER
if (transport->server && !transport->authenticated &&
transport->auths_allowed_mask & BIT(SSH_AUTH_PUBKEY)) {
/* Send "server-sig-algs" RFC8308 extension to */
/* avoid using SHA-1 signatures in pubkey auth */
ret = ssh_transport_send_server_sig_algs(transport);
if (ret < 0) {
return ret;
}
}
#endif
return 0;
}
int ssh_kex_process_msg(struct ssh_transport *transport, uint8_t msg_id, struct ssh_payload *rx_pkt)
{
return ssh_kex_ecdh_process_msg(transport, msg_id, rx_pkt);
}
int ssh_kex_gen_exchange_hash(struct ssh_transport *transport,
const struct ssh_string *remote_ephemeral_key,
const struct ssh_string *server_host_key)
{
int ret = -EINVAL;
uint8_t buf[4];
psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT;
psa_status_t status;
size_t hash_len;
SSH_PAYLOAD_BUF(payload, transport->tx_buf);
/*
* The exchange hash H is computed as the hash of the concatenation of
* the following.
* string V_C, client's identification string (CR and LF excluded)
* string V_S, server's identification string (CR and LF excluded)
* string I_C, payload of the client's SSH_MSG_KEXINIT
* string I_S, payload of the server's SSH_MSG_KEXINIT
* string K_S, server's public host key
* string Q_C, client's ephemeral public key octet string
* string Q_S, server's ephemeral public key octet string
* mpint K, shared secret
*/
/* The exchange hash data is quite large, each item is hashed piecemeal to
* reduce memory usage.
*/
status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256);
if (status != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
/* Client identity */
sys_put_be32(transport->client_identity->len, buf);
if (psa_hash_update(&hash_op, buf, sizeof(buf)) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, transport->client_identity->data,
transport->client_identity->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
/* Server identity */
sys_put_be32(transport->server_identity->len, buf);
if (psa_hash_update(&hash_op, buf, sizeof(buf)) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, transport->server_identity->data,
transport->server_identity->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
/* Client kexinit payload */
sys_put_be32(transport->client_kexinit->len, buf);
if (psa_hash_update(&hash_op, buf, sizeof(buf)) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, transport->client_kexinit->data,
transport->client_kexinit->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
/* Server kexinit payload */
sys_put_be32(transport->server_kexinit->len, buf);
if (psa_hash_update(&hash_op, buf, sizeof(buf)) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, transport->server_kexinit->data,
transport->server_kexinit->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
/* Server public host key */
if (transport->server) {
payload.len = 0;
ret = ssh_host_key_write_pub_key(&payload, transport->host_key_index);
if (ret != 0) {
goto exit;
}
} else {
payload.len = 0;
if (!ssh_payload_write_string(&payload, server_host_key)) {
ret = -ENOBUFS;
goto exit;
}
}
if (psa_hash_update(&hash_op, payload.data, payload.len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
/* Client then server ephemeral pub key */
for (int i = 0; i < 2; i++) {
if ((transport->server && i == 0) || (!transport->server && i == 1)) {
/* Remote key, first if we are a server otherwise second */
sys_put_be32(remote_ephemeral_key->len, buf);
if (psa_hash_update(&hash_op, buf, sizeof(buf)) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, remote_ephemeral_key->data,
remote_ephemeral_key->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
} else {
/* Local key, first if we are a client otherwise second */
uint8_t pubkey_buff[32];
size_t pubkey_len;
status = psa_export_public_key(
transport->ecdh_ephemeral_key,
pubkey_buff, sizeof(pubkey_buff), &pubkey_len);
if (status != PSA_SUCCESS) {
NET_ERR("Failed to write pubkey: %d", status);
ret = -EIO;
goto exit;
}
sys_put_be32(pubkey_len, buf);
if (psa_hash_update(&hash_op, buf, sizeof(buf)) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, pubkey_buff, pubkey_len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
}
}
/* mpint K, shared secret */
payload.len = 0;
if (!ssh_payload_write_mpint(&payload, transport->shared_secret->data,
transport->shared_secret->len, false)) {
ret = -ENOBUFS;
goto exit;
}
if (psa_hash_update(&hash_op, payload.data, payload.len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
transport->exchange_hash = ssh_payload_string_alloc(&transport->kex_heap, NULL, 32);
if (transport->exchange_hash == NULL) {
ret = -ENOMEM;
goto exit;
}
if (psa_hash_finish(&hash_op, (void *)transport->exchange_hash->data, 32,
&hash_len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
NET_HEXDUMP_DBG(transport->exchange_hash->data,
transport->exchange_hash->len, "exchange_hash");
ret = 0;
exit:
psa_hash_abort(&hash_op);
return ret;
}
static int gen_all_keys(struct ssh_transport *transport)
{
/*
* o Initial IV client to server: HASH(K || H || "A" || session_id)
* (Here K is encoded as mpint and "A" as byte and session_id as raw
* data. "A" means the single character A, ASCII 65).
* o Initial IV server to client: HASH(K || H || "B" || session_id)
* o Encryption key client to server: HASH(K || H || "C" || session_id)
* o Encryption key server to client: HASH(K || H || "D" || session_id)
* o Integrity key client to server: HASH(K || H || "E" || session_id)
* o Integrity key server to client: HASH(K || H || "F" || session_id)
*/
struct {
char c;
uint8_t *buf;
size_t len;
} keygen[] = {
{
.c = 'A',
.buf = transport->iv_client,
.len = sizeof(transport->iv_client)
},
{
.c = 'B',
.buf = transport->iv_server,
.len = sizeof(transport->iv_server)
},
{
.c = 'C',
.buf = transport->encrypt_key_client,
.len = sizeof(transport->encrypt_key_client)
},
{
.c = 'D',
.buf = transport->encrypt_key_server,
.len = sizeof(transport->encrypt_key_server)
},
{
.c = 'E',
.buf = transport->integ_key_client,
.len = sizeof(transport->integ_key_client)
},
{
.c = 'F',
.buf = transport->integ_key_server,
.len = sizeof(transport->integ_key_server)
},
};
for (unsigned int i = 0; i < ARRAY_SIZE(keygen); i++) {
int ret;
ret = gen_key(transport, keygen[i].c, keygen[i].buf, keygen[i].len);
if (ret != 0) {
NET_ERR("Failed to generate keys");
return ret;
}
NET_DBG("Key %d (%c)", i, keygen[i].c);
NET_HEXDUMP_DBG(keygen[i].buf, keygen[i].len, "key");
}
return 0;
}
static int gen_key(struct ssh_transport *transport, char c, uint8_t *buf, size_t len)
{
int ret = 0;
uint8_t hash[32];
psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT;
size_t offset = 0;
SSH_PAYLOAD_BUF(payload, transport->tx_buf);
/* mpint K, shared secret */
if (!ssh_payload_write_mpint(&payload, transport->shared_secret->data,
transport->shared_secret->len, false)) {
ret = -ENOBUFS;
goto exit;
}
while (offset < len) {
size_t hash_len;
size_t copy_len;
hash_op = psa_hash_operation_init();
if (psa_hash_setup(&hash_op, PSA_ALG_SHA_256) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, payload.data, payload.len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, transport->exchange_hash->data,
transport->exchange_hash->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (offset == 0) {
if (psa_hash_update(&hash_op, &c, 1) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
if (psa_hash_update(&hash_op, transport->session_id->data,
transport->session_id->len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
} else {
if (psa_hash_update(&hash_op, buf, offset) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
}
if (psa_hash_finish(&hash_op, hash, sizeof(hash), &hash_len) != PSA_SUCCESS) {
ret = -EIO;
goto exit;
}
copy_len = MIN(len - offset, sizeof(hash));
memcpy(&buf[offset], hash, copy_len);
offset += copy_len;
}
exit:
psa_hash_abort(&hash_op);
return ret;
}