Skip to content

Commit

Permalink
Add ECDSAPrivateKey.ECDH which takes a *ecdh.PublicKey
Browse files Browse the repository at this point in the history
The current ECDSAPrivateKey.SharedKey method takes a *ecdsa.PublicKey.
However, using crypto/ecdsa and crypto/elliptic for ECDH has been
deprecated in the standard library in favor of crypto/ecdh.

This commit adds a new ECDH method to ECDSAPrivateKey which takes
a *ecdh.PublicKey.  This method has the same signature
as ecdh.PrivateKey.ECDH, meaning the following interface can be be used
to do ECDH with both standard library private keys and piv-go keys, providing
the same flexibility as crypto.Signer and crypto.Decrypter:

interface {
	ECDH(*ecdh.PublicKey) ([]byte, error)
}

ECDSAPrivateKey.SharedKey has been re-implemented as a small wrapper around
ECDSAPrivateKey.ECDH.
  • Loading branch information
AGWA authored and ericchiang committed Oct 2, 2024
1 parent 2cbba92 commit 7988525
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 2 deletions.
26 changes: 24 additions & 2 deletions v2/piv/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package piv
import (
"bytes"
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
Expand Down Expand Up @@ -1151,10 +1152,31 @@ func (k *ECDSAPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.Signer
// used for the operation. Callers should use a cryptographic key
// derivation function to extract the amount of bytes they need.
func (k *ECDSAPrivateKey) SharedKey(peer *ecdsa.PublicKey) ([]byte, error) {
if peer.Curve.Params().BitSize != k.pub.Curve.Params().BitSize {
peerECDH, err := peer.ECDH()
if err != nil {
return nil, unsupportedCurveError{curve: peer.Params().BitSize}
}
return k.ECDH(peerECDH)
}

// ECDH performs a Diffie-Hellman key agreement with the peer
// to produce a shared secret key.
//
// Peer's public key must use the same algorithm as the key in
// this slot, or an error will be returned.
//
// Length of the result depends on the types and sizes of the keys
// used for the operation. Callers should use a cryptographic key
// derivation function to extract the amount of bytes they need.
func (k *ECDSAPrivateKey) ECDH(peer *ecdh.PublicKey) ([]byte, error) {
ourECDH, err := k.pub.ECDH()
if err != nil {
return nil, unsupportedCurveError{curve: k.pub.Params().BitSize}
}
if peer.Curve() != ourECDH.Curve() {
return nil, errMismatchingAlgorithms
}
msg := elliptic.Marshal(peer.Curve, peer.X, peer.Y)
msg := peer.Bytes()
return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) {
var alg byte
size := k.pub.Params().BitSize
Expand Down
66 changes: 66 additions & 0 deletions v2/piv/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package piv
import (
"bytes"
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
Expand Down Expand Up @@ -84,6 +85,71 @@ func TestYubiKeySignECDSA(t *testing.T) {
}
}

func TestYubiKeyECDSAECDH(t *testing.T) {
yk, close := newTestYubiKey(t)
defer close()

slot := SlotAuthentication

key := Key{
Algorithm: AlgorithmEC256,
TouchPolicy: TouchPolicyNever,
PINPolicy: PINPolicyNever,
}
pubKey, err := yk.GenerateKey(DefaultManagementKey, slot, key)
if err != nil {
t.Fatalf("generating key: %v", err)
}
pub, ok := pubKey.(*ecdsa.PublicKey)
if !ok {
t.Fatalf("public key is not an ecdsa key")
}
pubECDH, err := pub.ECDH()
if err != nil {
t.Fatalf("converting pubkey to ECDH key: %v", err)
}
priv, err := yk.PrivateKey(slot, pub, KeyAuth{})
if err != nil {
t.Fatalf("getting private key: %v", err)
}
privECDSA, ok := priv.(*ECDSAPrivateKey)
if !ok {
t.Fatalf("expected private key to be ECDSA private key")
}

t.Run("good", func(t *testing.T) {
privECDH, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("cannot generate key: %v", err)
}
secret1, err := privECDH.ECDH(pubECDH)
if err != nil {
t.Fatalf("key agreement 1 failed: %v", err)
}

secret2, err := privECDSA.ECDH(privECDH.PublicKey())
if err != nil {
t.Fatalf("key agreement 2 failed: %v", err)
}
if !bytes.Equal(secret1, secret2) {
t.Errorf("key agreement didn't match")
}
})

t.Run("bad", func(t *testing.T) {
t.Run("size", func(t *testing.T) {
privECDH, err := ecdh.P384().GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("cannot generate key: %v", err)
}
_, err = privECDSA.ECDH(privECDH.PublicKey())
if !errors.Is(err, errMismatchingAlgorithms) {
t.Fatalf("unexpected error value: wanted errMismatchingAlgorithms: %v", err)
}
})
})
}

func TestYubiKeyECDSASharedKey(t *testing.T) {
yk, close := newTestYubiKey(t)
defer close()
Expand Down

0 comments on commit 7988525

Please sign in to comment.