@@ -1006,9 +1006,13 @@ func (yk *YubiKey) PrivateKey(slot Slot, public crypto.PublicKey, auth KeyAuth)
10061006 return & keyEd25519 {yk , slot , pub , auth , pp }, nil
10071007 case * rsa.PublicKey :
10081008 return & keyRSA {yk , slot , pub , auth , pp }, nil
1009+ case * ecdh.PublicKey :
1010+ if crv := pub .Curve (); crv != ecdh .X25519 () {
1011+ return nil , fmt .Errorf ("unsupported ecdh curve: %v" , crv )
1012+ }
1013+ return & X25519PrivateKey {yk , slot , pub , auth , pp }, nil
10091014 default :
1010- // Add support for X25519 keys using build tags
1011- return yk .tryX25519PrivateKey (slot , public , auth , pp )
1015+ return nil , fmt .Errorf ("unsupported public key type: %T" , public )
10121016 }
10131017}
10141018
@@ -1087,13 +1091,17 @@ func (yk *YubiKey) SetPrivateKeyInsecure(key []byte, slot Slot, private crypto.P
10871091 privateKey := make ([]byte , elemLen )
10881092 copy (privateKey , priv [:32 ])
10891093 params = append (params , privateKey )
1090- default :
1091- // Add support for X25519 keys using build tags
1092- var err error
1093- params , paramTag , elemLen , err = yk .tryX22519PrivateKeyInsecure (private )
1094- if err != nil {
1095- return err
1094+ case * ecdh.PrivateKey :
1095+ if crv := priv .Curve (); crv != ecdh .X25519 () {
1096+ return fmt .Errorf ("unsupported ecdh curve: %v" , crv )
10961097 }
1098+ paramTag = 0x08
1099+ elemLen = 32
1100+
1101+ // seed
1102+ params = append (params , priv .Bytes ())
1103+ default :
1104+ return fmt .Errorf ("unsupported private key type: %T" , private )
10971105 }
10981106
10991107 elemLenASN1 := marshalASN1Length (uint64 (elemLen ))
@@ -1250,6 +1258,33 @@ func (k *ECDSAPrivateKey) ECDH(peer *ecdh.PublicKey) ([]byte, error) {
12501258 })
12511259}
12521260
1261+ // X25519PrivateKey is a crypto.PrivateKey implementation for X25519 keys. It
1262+ // implements the method ECDH to perform Diffie-Hellman key agreements.
1263+ //
1264+ // Keys returned by YubiKey.PrivateKey() may be type asserted to
1265+ // *X25519PrivateKey, if the slot contains an X25519 key.
1266+ type X25519PrivateKey struct {
1267+ yk * YubiKey
1268+ slot Slot
1269+ pub * ecdh.PublicKey
1270+ auth KeyAuth
1271+ pp PINPolicy
1272+ }
1273+
1274+ func (k * X25519PrivateKey ) Public () crypto.PublicKey {
1275+ return k .pub
1276+ }
1277+
1278+ // ECDH performs an ECDH exchange and returns the shared secret.
1279+ //
1280+ // Peer's public key must use the same algorithm as the key in this slot, or an
1281+ // error will be returned.
1282+ func (k * X25519PrivateKey ) ECDH (peer * ecdh.PublicKey ) ([]byte , error ) {
1283+ return k .auth .do (k .yk , k .pp , func (tx * scTx ) ([]byte , error ) {
1284+ return ykECDHX25519 (tx , k .slot , k .pub , peer )
1285+ })
1286+ }
1287+
12531288type keyEd25519 struct {
12541289 yk * YubiKey
12551290 slot Slot
@@ -1335,6 +1370,38 @@ func ykSignECDSA(tx *scTx, slot Slot, pub *ecdsa.PublicKey, digest []byte) ([]by
13351370 return rs , nil
13361371}
13371372
1373+ func ykECDHX25519 (tx * scTx , slot Slot , pub * ecdh.PublicKey , peer * ecdh.PublicKey ) ([]byte , error ) {
1374+ if crv := pub .Curve (); crv != ecdh .X25519 () {
1375+ return nil , fmt .Errorf ("unsupported ecdh curve: %v" , crv )
1376+ }
1377+ if pub .Curve () != peer .Curve () {
1378+ return nil , errMismatchingAlgorithms
1379+ }
1380+ cmd := apdu {
1381+ instruction : insAuthenticate ,
1382+ param1 : algX25519 ,
1383+ param2 : byte (slot .Key ),
1384+ data : marshalASN1 (0x7c ,
1385+ append ([]byte {0x82 , 0x00 },
1386+ marshalASN1 (0x85 , peer .Bytes ())... )),
1387+ }
1388+ resp , err := tx .Transmit (cmd )
1389+ if err != nil {
1390+ return nil , fmt .Errorf ("command failed: %w" , err )
1391+ }
1392+
1393+ sig , _ , err := unmarshalASN1 (resp , 1 , 0x1c ) // 0x7c
1394+ if err != nil {
1395+ return nil , fmt .Errorf ("unmarshal response: %v" , err )
1396+ }
1397+ sharedSecret , _ , err := unmarshalASN1 (sig , 2 , 0x02 ) // 0x82
1398+ if err != nil {
1399+ return nil , fmt .Errorf ("unmarshal response signature: %v" , err )
1400+ }
1401+
1402+ return sharedSecret , nil
1403+ }
1404+
13381405// This function only works on SoloKeys prototypes and other PIV devices that choose
13391406// to implement Ed25519 signatures under alg 0x22.
13401407func skSignEd25519 (tx * scTx , slot Slot , pub ed25519.PublicKey , digest []byte ) ([]byte , error ) {
@@ -1432,6 +1499,16 @@ func decodeRSAPublic(b []byte) (*rsa.PublicKey, error) {
14321499 return & rsa.PublicKey {N : & n , E : int (e .Int64 ())}, nil
14331500}
14341501
1502+ func decodeX25519Public (b []byte ) (* ecdh.PublicKey , error ) {
1503+ // Adaptation of
1504+ // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=95
1505+ p , _ , err := unmarshalASN1 (b , 2 , 0x06 )
1506+ if err != nil {
1507+ return nil , fmt .Errorf ("unmarshal points: %v" , err )
1508+ }
1509+ return ecdh .X25519 ().NewPublicKey (p )
1510+ }
1511+
14351512func rsaAlg (pub * rsa.PublicKey ) (byte , error ) {
14361513 size := pub .N .BitLen ()
14371514 switch size {
0 commit comments