]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Support ed25519 keys hosted on PKCS#11 tokens.
authordjm@openbsd.org <djm@openbsd.org>
Sat, 26 Jul 2025 01:51:44 +0000 (01:51 +0000)
committerDamien Miller <djm@mindrot.org>
Sat, 26 Jul 2025 01:57:57 +0000 (11:57 +1000)
Tested on Yubikeys and against SoftHSM2.

feedback/ok tb@

OpenBSD-Commit-ID: 90ddb6529f2e12e98e8bba21d8592e60579ce2e4

ssh-pkcs11.c

index e22b3e419fb569865bf53e86d5adc68525fd1b60..34e430875510ed7e8e5c34f9e3f57a61d83bcf19 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh-pkcs11.c,v 1.66 2025/07/24 06:59:51 djm Exp $ */
+/* $OpenBSD: ssh-pkcs11.c,v 1.67 2025/07/26 01:51:44 djm Exp $ */
 /*
  * Copyright (c) 2010 Markus Friedl.  All rights reserved.
  * Copyright (c) 2014 Pedro Martelletto. All rights reserved.
@@ -51,6 +51,7 @@
 #include "ssh-pkcs11.h"
 #include "digest.h"
 #include "xmalloc.h"
+#include "crypto_api.h"
 
 struct pkcs11_slotinfo {
        CK_TOKEN_INFO           token;
@@ -709,6 +710,69 @@ pkcs11_sign_ecdsa(struct sshkey *key,
 #endif /* OPENSSL_HAS_ECC */
 #endif /* WITH_OPENSSL */
 
+static int
+pkcs11_sign_ed25519(struct sshkey *key,
+    u_char **sigp, size_t *lenp,
+    const u_char *data, size_t datalen,
+    const char *alg, const char *sk_provider,
+    const char *sk_pin, u_int compat)
+{
+       struct pkcs11_key       *k11;
+       struct pkcs11_slotinfo  *si;
+       CK_FUNCTION_LIST        *f;
+       CK_ULONG                slen = 0;
+       CK_RV                   rv;
+       u_char                  *sig = NULL;
+       CK_BYTE                 *xdata = NULL;
+       int                     ret = -1;
+
+       if (sigp != NULL)
+               *sigp = 0;
+       if (lenp != NULL)
+               *lenp = 0;
+
+       if ((k11 = pkcs11_lookup_key(key)) == NULL) {
+               error_f("no key found");
+               return SSH_ERR_KEY_NOT_FOUND;
+       }
+
+       if (pkcs11_get_key(k11, CKM_EDDSA) == -1) {
+               error("pkcs11_get_key failed");
+               return SSH_ERR_AGENT_FAILURE;
+       }
+
+       debug3_f("sign using provider %s slotidx %lu",
+           k11->provider->name, (u_long)k11->slotidx);
+
+       f = k11->provider->function_list;
+       si = &k11->provider->slotinfo[k11->slotidx];
+
+       xdata = xmalloc(datalen);
+       memcpy(xdata, data, datalen);
+       sig = xmalloc(crypto_sign_ed25519_BYTES);
+       slen = crypto_sign_ed25519_BYTES;
+
+       rv = f->C_Sign(si->session, xdata, datalen, sig, &slen);
+       if (rv != CKR_OK) {
+               error("C_Sign failed: %lu", rv);
+               goto done;
+       }
+       if (slen != crypto_sign_ed25519_BYTES) {
+               error_f("bad signature length: %lu", (u_long)slen);
+               goto done;
+       }
+       if ((ret = ssh_ed25519_encode_store_sig(sig, slen, sigp, lenp)) != 0)
+               fatal_fr(ret, "couldn't store signature");
+
+       /* success */
+       ret = 0;
+ done:
+       if (xdata != NULL)
+               freezero(xdata, datalen);
+       free(sig);
+       return ret;
+}
+
 /* remove trailing spaces */
 static char *
 rmspace(u_char *buf, size_t len)
@@ -1022,6 +1086,115 @@ fail:
        return key;
 }
 
+static struct sshkey *
+pkcs11_fetch_ed25519_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
+    CK_OBJECT_HANDLE *obj)
+{
+       CK_ATTRIBUTE             key_attr[3];
+       CK_SESSION_HANDLE        session;
+       CK_FUNCTION_LIST        *f = NULL;
+       CK_RV                    rv;
+       struct sshkey           *key = NULL;
+       const unsigned char     *d = NULL;
+       size_t                  len;
+       char                    *hex = NULL;
+       int                      success = -1, i;
+       /* https://docs.oasis-open.org/pkcs11/pkcs11-curr/v3.0/os/pkcs11-curr-v3.0-os.html#_Toc30061180 */
+       const u_char             id1[14] = {
+               0x13, 0x0c, 0x65, 0x64, 0x77, 0x61, 0x72, 0x64,
+               0x73, 0x32, 0x35, 0x35, 0x31, 0x39,
+       }; /* PrintableString { "edwards25519" } */
+       const u_char             id2[5] = {
+               0x06, 0x03, 0x2b, 0x65, 0x70,
+       }; /* OBJECT_IDENTIFIER { 1.3.101.112 } */
+
+       memset(&key_attr, 0, sizeof(key_attr));
+       key_attr[0].type = CKA_ID;
+       key_attr[1].type = CKA_EC_POINT; /* XXX or CKA_VALUE ? */
+       key_attr[2].type = CKA_EC_PARAMS;
+
+       session = p->slotinfo[slotidx].session;
+       f = p->function_list;
+
+       /* figure out size of the attributes */
+       rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
+       if (rv != CKR_OK) {
+               error("C_GetAttributeValue failed: %lu", rv);
+               return (NULL);
+       }
+
+       /*
+        * Allow CKA_ID (always first attribute) to be empty, but
+        * ensure that none of the others are zero length.
+        * XXX assumes CKA_ID is always first.
+        */
+       if (key_attr[1].ulValueLen == 0 ||
+           key_attr[2].ulValueLen == 0) {
+               error("invalid attribute length");
+               return (NULL);
+       }
+
+       /* allocate buffers for attributes */
+       for (i = 0; i < 3; i++) {
+               if (key_attr[i].ulValueLen > 0)
+                       key_attr[i].pValue = xcalloc(1, key_attr[i].ulValueLen);
+       }
+
+       /* retrieve ID, public point and curve parameters of EC key */
+       rv = f->C_GetAttributeValue(session, *obj, key_attr, 3);
+       if (rv != CKR_OK) {
+               error("C_GetAttributeValue failed: %lu", rv);
+               goto fail;
+       }
+
+       /* Expect one of the supported identifiers in CKA_EC_PARAMS */
+       d = (u_char *)key_attr[2].pValue;
+       len = key_attr[2].ulValueLen;
+       if ((len != sizeof(id1) || memcmp(d, id1, sizeof(id1)) != 0) &&
+           (len != sizeof(id2) || memcmp(d, id2, sizeof(id2)) != 0)) {
+               hex = tohex(d, len);
+               logit_f("unsupported CKA_EC_PARAMS: %s (len %zu)", hex, len);
+               goto fail;
+       }
+
+       /*
+        * Expect either a raw 32 byte pubkey or an OCTET STRING with
+        * a 32 byte pubkey in CKA_VALUE
+        */
+       d = (u_char *)key_attr[1].pValue;
+       len = key_attr[1].ulValueLen;
+       if (len == ED25519_PK_SZ + 2 && d[0] == 0x04 && d[1] == ED25519_PK_SZ) {
+               d += 2;
+               len -= 2;
+       }
+       if (len != ED25519_PK_SZ) {
+               hex = tohex(key_attr[1].pValue, key_attr[1].ulValueLen);
+               logit_f("CKA_EC_POINT invalid octet str: %s (len %lu)",
+                   hex, (u_long)key_attr[1].ulValueLen);
+               goto fail;
+       }
+
+       if ((key = sshkey_new(KEY_UNSPEC)) == NULL)
+               fatal_f("sshkey_new failed");
+       key->ed25519_pk = xmalloc(ED25519_PK_SZ);
+       memcpy(key->ed25519_pk, d, ED25519_PK_SZ);
+       key->type = KEY_ED25519;
+       key->flags |= SSHKEY_FLAG_EXT;
+       if (pkcs11_record_key(p, slotidx, &key_attr[0], key))
+               goto fail;
+       /* success */
+       success = 0;
+ fail:
+       if (success != 0) {
+               sshkey_free(key);
+               key = NULL;
+       }
+       free(hex);
+       for (i = 0; i < 3; i++)
+               free(key_attr[i].pValue);
+       return key;
+}
+
 static int
 pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
     CK_OBJECT_HANDLE *obj, struct sshkey **keyp, char **labelp)
@@ -1039,6 +1212,7 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
        int                      i, success = -1;
        const u_char            *cp;
        char                    *subject = NULL;
+       size_t                  len;
 #ifdef OPENSSL_HAS_ECC
        int                     r, nid;
 #endif
@@ -1176,6 +1350,26 @@ pkcs11_fetch_x509_pubkey(struct pkcs11_provider *p, CK_ULONG slotidx,
                /* success */
                success = 0;
 #endif /* OPENSSL_HAS_ECC */
+       } else if (EVP_PKEY_base_id(evp) == EVP_PKEY_ED25519) {
+               if ((key = sshkey_new(KEY_UNSPEC)) == NULL ||
+                   (key->ed25519_pk = calloc(1, ED25519_PK_SZ)) == NULL)
+                       fatal_f("allocation failed");
+               len = ED25519_PK_SZ;
+               if (!EVP_PKEY_get_raw_public_key(evp, key->ed25519_pk, &len)) {
+                       ossl_error("EVP_PKEY_get_raw_public_key failed");
+                       goto out;
+               }
+               if (len != ED25519_PK_SZ) {
+                       error_f("incorrect returned public key "
+                           "length for ed25519");
+                       goto out;
+               }
+               key->type = KEY_ED25519;
+               key->flags |= SSHKEY_FLAG_EXT;
+               if (pkcs11_record_key(p, slotidx, &cert_attr[0], key))
+                       goto out;
+               /* success */
+               success = 0;
        } else {
                error("unknown certificate key type");
                goto out;
@@ -1406,6 +1600,9 @@ pkcs11_fetch_keys(struct pkcs11_provider *p, CK_ULONG slotidx,
                        key = pkcs11_fetch_ecdsa_pubkey(p, slotidx, &obj);
                        break;
 #endif /* OPENSSL_HAS_ECC */
+               case CKK_EC_EDWARDS:
+                       key = pkcs11_fetch_ed25519_pubkey(p, slotidx, &obj);
+                       break;
                default:
                        /* XXX print key type? */
                        key = NULL;
@@ -1879,6 +2076,10 @@ pkcs11_sign(struct sshkey *key,
                return pkcs11_sign_ecdsa(key, sigp, lenp, data, datalen,
                    alg, sk_provider, sk_pin, compat);
 #endif /* OPENSSL_HAS_ECC */
+       case KEY_ED25519:
+       case KEY_ED25519_CERT:
+               return pkcs11_sign_ed25519(key, sigp, lenp, data, datalen,
+                   alg, sk_provider, sk_pin, compat);
        default:
                return SSH_ERR_KEY_TYPE_UNKNOWN;
        }
@@ -2033,10 +2234,20 @@ pkcs11_destroy_keypair(char *provider_id, char *pin, unsigned long slotidx,
                        *err = rv;
                        key_type = -1;
                }
-               if (key_type == CKK_RSA)
+               switch (key_type) {
+               case CKK_RSA:
                        k = pkcs11_fetch_rsa_pubkey(p, slotidx, &obj);
-               else if (key_type == CKK_ECDSA)
+                       break;
+               case CKK_ECDSA:
                        k = pkcs11_fetch_ecdsa_pubkey(p, slotidx, &obj);
+                       break;
+               case CKK_EC_EDWARDS:
+                       k = pkcs11_fetch_ed25519_pubkey(p, slotidx, &obj);
+                       break;
+               default:
+                       debug_f("unsupported key type %lu", (u_long)key_type);
+                       continue;
+               }
 
                if ((rv = f->C_DestroyObject(session, obj)) != CKR_OK) {
                        debug_f("could not destroy public key 0x%hhx", keyid);