From 61617d6af69395014c73647877e66f448756785c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Wed, 9 Jul 2025 17:58:09 +0200 Subject: [PATCH] crypto: finish RSA --- crypto/_testsuite/testsuite.go | 101 +++++++++++++-------- crypto/ed25519/private.go | 3 +- crypto/ed25519/public.go | 3 +- crypto/interface.go | 12 ++- crypto/p256/key_test.go | 25 +++++ crypto/p256/private.go | 3 +- crypto/p256/public.go | 3 +- crypto/p384/private.go | 3 +- crypto/p384/public.go | 3 +- crypto/p521/private.go | 3 +- crypto/p521/public.go | 3 +- crypto/rsa/key.go | 13 +++ crypto/rsa/key_test.go | 87 ++++++++++++++++-- crypto/rsa/private.go | 46 ++++------ crypto/rsa/public.go | 22 +++-- crypto/secp256k1/key.go | 2 +- crypto/secp256k1/key_test.go | 23 +++++ crypto/secp256k1/private.go | 47 +++++----- crypto/secp256k1/public.go | 56 +++++++----- verifications/jsonwebkey/JsonWebKey2020.go | 2 +- verifications/multikey/multikey.go | 2 +- 21 files changed, 320 insertions(+), 142 deletions(-) diff --git a/crypto/_testsuite/testsuite.go b/crypto/_testsuite/testsuite.go index 0dc4bcf..846c86a 100644 --- a/crypto/_testsuite/testsuite.go +++ b/crypto/_testsuite/testsuite.go @@ -190,16 +190,7 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har pub, priv, err := harness.GenerateKeyPair() require.NoError(t, err) - spub, ok := (crypto.PublicKey(pub)).(crypto.PublicKeySigning) - if !ok { - t.Skip("Signature is not implemented") - } - spriv, ok := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) - if !ok { - t.Skip("Signature is not implemented") - } - - for _, tc := range []struct { + type testcase struct { name string signer func(msg []byte, opts ...crypto.SigningOption) ([]byte, error) verifier func(msg []byte, sig []byte, opts ...crypto.SigningOption) bool @@ -207,25 +198,43 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har stats *int defaultHash crypto.Hash otherHashes []crypto.Hash - }{ - { - name: "Bytes signature", - signer: spriv.SignToBytes, - verifier: spub.VerifyBytes, - expectedSize: harness.SignatureBytesSize, - stats: &stats.sigRawSize, - defaultHash: harness.DefaultHash, - otherHashes: harness.OtherHashes, - }, - { - name: "ASN.1 signature", - signer: spriv.SignToASN1, - verifier: spub.VerifyASN1, - stats: &stats.sigAsn1Size, - defaultHash: harness.DefaultHash, - otherHashes: harness.OtherHashes, - }, - } { + } + var tcs []testcase + + if pubImplements[PubT, crypto.PublicKeySigningBytes]() { + t.Run("Bytes signature", func(t *testing.T) { + spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningBytes) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes) + + tcs = append(tcs, testcase{ + name: "Bytes signature", + signer: spriv.SignToBytes, + verifier: spub.VerifyBytes, + expectedSize: harness.SignatureBytesSize, + stats: &stats.sigRawSize, + defaultHash: harness.DefaultHash, + otherHashes: harness.OtherHashes, + }) + }) + } + + if pubImplements[PubT, crypto.PublicKeySigningASN1]() { + t.Run("ASN.1 signature", func(t *testing.T) { + spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningASN1) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningASN1) + + tcs = append(tcs, testcase{ + name: "ASN.1 signature", + signer: spriv.SignToASN1, + verifier: spub.VerifyASN1, + stats: &stats.sigAsn1Size, + defaultHash: harness.DefaultHash, + otherHashes: harness.OtherHashes, + }) + }) + } + + for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { msg := []byte("message") @@ -455,15 +464,15 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Signatures", func(b *testing.B) { - if !pubImplements[PubT, crypto.PublicKeySigning]() { - b.Skip("Signature is not implemented") - } - b.Run("Sign to Bytes signature", func(b *testing.B) { + if !pubImplements[PubT, crypto.PublicKeySigningBytes]() { + b.Skip("Signature to bytes is not implemented") + } + _, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes) b.ResetTimer() b.ReportAllocs() @@ -474,11 +483,15 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Verify from Bytes signature", func(b *testing.B) { + if !pubImplements[PubT, crypto.PublicKeySigningBytes]() { + b.Skip("Signature to bytes is not implemented") + } + pub, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigning) - spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) + spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningBytes) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes) sig, err := spriv.SignToBytes([]byte("message")) require.NoError(b, err) @@ -491,10 +504,14 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Sign to ASN.1 signature", func(b *testing.B) { + if !pubImplements[PubT, crypto.PublicKeySigningASN1]() { + b.Skip("Signature to ASN.1 is not implemented") + } + _, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningASN1) b.ResetTimer() b.ReportAllocs() @@ -505,11 +522,15 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha }) b.Run("Verify from ASN.1 signature", func(b *testing.B) { + if !pubImplements[PubT, crypto.PublicKeySigningASN1]() { + b.Skip("Signature to ASN.1 is not implemented") + } + pub, priv, err := harness.GenerateKeyPair() require.NoError(b, err) - spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigning) - spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigning) + spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningASN1) + spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningASN1) sig, err := spriv.SignToASN1([]byte("message")) require.NoError(b, err) @@ -524,7 +545,7 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha b.Run("Key exchange", func(b *testing.B) { if !privImplements[PrivT, crypto.PrivateKeyKeyExchange]() { - b.Skip("Key echange is not implemented") + b.Skip("Key exchange is not implemented") } b.Run("KeyExchange", func(b *testing.B) { diff --git a/crypto/ed25519/private.go b/crypto/ed25519/private.go index d88154b..7c66b9c 100644 --- a/crypto/ed25519/private.go +++ b/crypto/ed25519/private.go @@ -11,7 +11,8 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeySigningBytes = &PrivateKey{} +var _ crypto.PrivateKeySigningASN1 = &PrivateKey{} var _ crypto.PrivateKeyToBytes = &PrivateKey{} type PrivateKey struct { diff --git a/crypto/ed25519/public.go b/crypto/ed25519/public.go index da89745..e9824e6 100644 --- a/crypto/ed25519/public.go +++ b/crypto/ed25519/public.go @@ -13,7 +13,8 @@ import ( "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.PublicKeySigning = PublicKey{} +var _ crypto.PublicKeySigningBytes = PublicKey{} +var _ crypto.PublicKeySigningASN1 = PublicKey{} var _ crypto.PublicKeyToBytes = PublicKey{} type PublicKey struct { diff --git a/crypto/interface.go b/crypto/interface.go index 5c30272..43b0809 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -26,13 +26,17 @@ type PublicKeyToBytes interface { ToBytes() []byte } -type PublicKeySigning interface { +type PublicKeySigningBytes interface { PublicKey // VerifyBytes checks a signature in the "raw bytes" format. // This format can make some assumptions and may not be what you expect. // Ideally, this format is defined by the same specification as the underlying crypto scheme. VerifyBytes(message, signature []byte, opts ...SigningOption) bool +} + +type PublicKeySigningASN1 interface { + PublicKey // VerifyASN1 checks a signature in the ASN.1 format. VerifyASN1(message, signature []byte, opts ...SigningOption) bool @@ -63,13 +67,17 @@ type PrivateKeyToBytes interface { ToBytes() []byte } -type PrivateKeySigning interface { +type PrivateKeySigningBytes interface { PrivateKey // SignToBytes creates a signature in the "raw bytes" format. // This format can make some assumptions and may not be what you expect. // Ideally, this format is defined by the same specification as the underlying crypto scheme. SignToBytes(message []byte, opts ...SigningOption) ([]byte, error) +} + +type PrivateKeySigningASN1 interface { + PrivateKey // SignToASN1 creates a signature in the ASN.1 format. SignToASN1(message []byte, opts ...SigningOption) ([]byte, error) diff --git a/crypto/p256/key_test.go b/crypto/p256/key_test.go index dd26997..cd48c4d 100644 --- a/crypto/p256/key_test.go +++ b/crypto/p256/key_test.go @@ -1,8 +1,11 @@ package p256 import ( + "encoding/base64" "testing" + "github.com/stretchr/testify/require" + "github.com/INFURA/go-did/crypto" "github.com/INFURA/go-did/crypto/_testsuite" ) @@ -32,3 +35,25 @@ func TestSuite(t *testing.T) { func BenchmarkSuite(b *testing.B) { testsuite.BenchSuite(b, harness) } + +func TestSignatureASN1(t *testing.T) { + // openssl ecparam -genkey -name prime256v1 -noout -out private.pem + // openssl ec -in private.pem -pubout -out public.pem + // echo -n "message" | openssl dgst -sha256 -sign private.pem -out signature.der + // echo -n "message" | openssl dgst -sha256 -verify public.pem -signature signature.der + + pubPem := `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+UhEHZqcaKn+qhNtMmW843ZTRkX/ +6GzxOWoRD2nv3EewARM90akj2UAKwQjJR9ibm78XtdlryvWG1v8TWb8INA== +-----END PUBLIC KEY----- +` + pub, err := PublicKeyFromX509PEM(pubPem) + require.NoError(t, err) + + b64sig := `MEQCIHPslthrLAYgwfqYaUmtGJqwmH7sRf5FEnnKgzcHIF8fAiB9+qovdvN6yJKkBwoQCw798uWr +0nOUE55ftB8EgX/Jbg==` + sig, err := base64.StdEncoding.DecodeString(b64sig) + require.NoError(t, err) + + require.True(t, pub.VerifyASN1([]byte("message"), sig)) +} diff --git a/crypto/p256/private.go b/crypto/p256/private.go index 0fe9481..b50900d 100644 --- a/crypto/p256/private.go +++ b/crypto/p256/private.go @@ -12,7 +12,8 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeySigningBytes = &PrivateKey{} +var _ crypto.PrivateKeySigningASN1 = &PrivateKey{} var _ crypto.PrivateKeyToBytes = &PrivateKey{} var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} diff --git a/crypto/p256/public.go b/crypto/p256/public.go index c262f7e..0630543 100644 --- a/crypto/p256/public.go +++ b/crypto/p256/public.go @@ -12,7 +12,8 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeySigningBytes = &PublicKey{} +var _ crypto.PublicKeySigningASN1 = &PublicKey{} var _ crypto.PublicKeyToBytes = &PublicKey{} type PublicKey struct { diff --git a/crypto/p384/private.go b/crypto/p384/private.go index 7012ec2..ccdc5dc 100644 --- a/crypto/p384/private.go +++ b/crypto/p384/private.go @@ -12,7 +12,8 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeySigningBytes = &PrivateKey{} +var _ crypto.PrivateKeySigningASN1 = &PrivateKey{} var _ crypto.PrivateKeyToBytes = &PrivateKey{} var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} diff --git a/crypto/p384/public.go b/crypto/p384/public.go index 06a1d96..c47d977 100644 --- a/crypto/p384/public.go +++ b/crypto/p384/public.go @@ -12,7 +12,8 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeySigningBytes = &PublicKey{} +var _ crypto.PublicKeySigningASN1 = &PublicKey{} var _ crypto.PublicKeyToBytes = &PublicKey{} type PublicKey struct { diff --git a/crypto/p521/private.go b/crypto/p521/private.go index 15da5b5..fe2259d 100644 --- a/crypto/p521/private.go +++ b/crypto/p521/private.go @@ -12,7 +12,8 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeySigningBytes = &PrivateKey{} +var _ crypto.PrivateKeySigningASN1 = &PrivateKey{} var _ crypto.PrivateKeyToBytes = &PrivateKey{} var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} diff --git a/crypto/p521/public.go b/crypto/p521/public.go index 01b4b35..c7a2f3a 100644 --- a/crypto/p521/public.go +++ b/crypto/p521/public.go @@ -12,7 +12,8 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeySigningBytes = &PublicKey{} +var _ crypto.PublicKeySigningASN1 = &PublicKey{} var _ crypto.PublicKeyToBytes = &PublicKey{} type PublicKey struct { diff --git a/crypto/rsa/key.go b/crypto/rsa/key.go index 9852285..c60a88b 100644 --- a/crypto/rsa/key.go +++ b/crypto/rsa/key.go @@ -4,6 +4,8 @@ import ( "crypto/rand" "crypto/rsa" "fmt" + + "github.com/INFURA/go-did/crypto" ) const ( @@ -28,3 +30,14 @@ const ( pemPubBlockType = "PUBLIC KEY" pemPrivBlockType = "PRIVATE KEY" ) + +func defaultSigHash(keyLen int) crypto.Hash { + switch { + case keyLen <= 2048: + return crypto.SHA256 + case keyLen <= 3072: + return crypto.SHA384 + default: + return crypto.SHA512 + } +} diff --git a/crypto/rsa/key_test.go b/crypto/rsa/key_test.go index 48e9e4d..afa5a3f 100644 --- a/crypto/rsa/key_test.go +++ b/crypto/rsa/key_test.go @@ -1,14 +1,16 @@ package rsa import ( + "encoding/base64" "testing" "github.com/stretchr/testify/require" + "github.com/INFURA/go-did/crypto" "github.com/INFURA/go-did/crypto/_testsuite" ) -var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{ +var harness2048 = testsuite.TestHarness[*PublicKey, *PrivateKey]{ Name: "rsa-2048", GenerateKeyPair: func() (*PublicKey, *PrivateKey, error) { return GenerateKeyPair(2048) }, PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase, @@ -17,15 +19,58 @@ var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{ PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER, PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM, MultibaseCode: MultibaseCode, - SignatureBytesSize: 123456, + DefaultHash: crypto.SHA256, + OtherHashes: []crypto.Hash{crypto.SHA384, crypto.SHA512}, } -func TestSuite(t *testing.T) { - testsuite.TestSuite(t, harness) +var harness3072 = testsuite.TestHarness[*PublicKey, *PrivateKey]{ + Name: "rsa-3072", + GenerateKeyPair: func() (*PublicKey, *PrivateKey, error) { return GenerateKeyPair(3072) }, + PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase, + PublicKeyFromX509DER: PublicKeyFromX509DER, + PublicKeyFromX509PEM: PublicKeyFromX509PEM, + PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER, + PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM, + MultibaseCode: MultibaseCode, + DefaultHash: crypto.SHA384, + OtherHashes: []crypto.Hash{crypto.SHA512}, } -func BenchmarkSuite(b *testing.B) { - testsuite.BenchSuite(b, harness) +var harness4096 = testsuite.TestHarness[*PublicKey, *PrivateKey]{ + Name: "rsa-4096", + GenerateKeyPair: func() (*PublicKey, *PrivateKey, error) { return GenerateKeyPair(4096) }, + PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase, + PublicKeyFromX509DER: PublicKeyFromX509DER, + PublicKeyFromX509PEM: PublicKeyFromX509PEM, + PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER, + PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM, + MultibaseCode: MultibaseCode, + DefaultHash: crypto.SHA512, + OtherHashes: []crypto.Hash{}, +} + +func TestSuite2048(t *testing.T) { + testsuite.TestSuite(t, harness2048) +} + +func TestSuite3072(t *testing.T) { + testsuite.TestSuite(t, harness3072) +} + +func TestSuite4096(t *testing.T) { + testsuite.TestSuite(t, harness4096) +} + +func BenchmarkSuite2048(b *testing.B) { + testsuite.BenchSuite(b, harness2048) +} + +func BenchmarkSuite3072(b *testing.B) { + testsuite.BenchSuite(b, harness3072) +} + +func BenchmarkSuite4096(b *testing.B) { + testsuite.BenchSuite(b, harness4096) } func TestPublicKeyX509(t *testing.T) { @@ -87,3 +132,33 @@ XnARctVkIcUYORcYwvuu9meDkw== rt := priv.ToPKCS8PEM() require.Equal(t, pem, rt) } + +func TestSignatureASN1(t *testing.T) { + // openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048 + // openssl pkey -in private.pem -pubout -out public.pem + // echo -n "message" | openssl dgst -sha256 -sign private.pem -out signature.der + // echo -n "message" | openssl dgst -sha256 -verify public.pem -signature signature.der + + pubPem := `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmtKXCTkUDcKbZGsEEUTo +16xblCyh6zmA8pXVGgmC66QiTNdKzxdwTykXTDs9sEre9ea34h2M7dwrA1weAmBu +grAXe0QmIXIqjFKRKdfty09yWVtKF7FGwEMlhKftWC225R+tRuLwbKG4cCSzHxcf +JfqCYqGDM7BrF39ilQzFYw5sUiWn3ppRPWa2oEV3cw19zFnHMbEHIQIdFyCcIv5x +GUSJ6sJVp0YvsODsZbA+Zyb2UMRfXD8fDHm9bJQCY0x/wGJLfvJmWtZLciwc145U +BN3SezY30NviZtZBKWjXgb6gL69L94U10/8ghmA30DY7bKs4+/7R2nOw91CO4rCo +1QIDAQAB +-----END PUBLIC KEY----- +` + pub, err := PublicKeyFromX509PEM(pubPem) + require.NoError(t, err) + + b64sig := `BdvBkZWxIVE2mfM48H1WlOs3k9NzyS4oUxAMOZWNNTYDU6+DLbhZ7Hnt3rRKX3m6f1cX5DCsHcPC +6sNtsR8Xp9u09GWCN/K28fF7Pcl0E87MdhAUL7jKNK5bb1XWx/GCUmoKXRZiR/gA10iB2Lmjd1MC +HItTCig91gmFm5PO67u9yM+cqE2nGyOh13/kT5Np9MUyaE9dkjoQGum23Ta6m7v0atWsPhO5aVVI +76vLwGhYAhQe22RxBlPRXyRInr0EnVgHQOe211o//erPZYQAm+N1kK+yjV8NbPxJX+r5sYUE19NL +MCB+kOgWk51uJwuiuHlffGMBPxku/t+skxI7Bw==` + sig, err := base64.StdEncoding.DecodeString(b64sig) + require.NoError(t, err) + + require.True(t, pub.VerifyASN1([]byte("message"), sig)) +} diff --git a/crypto/rsa/private.go b/crypto/rsa/private.go index 34e800e..7004f28 100644 --- a/crypto/rsa/private.go +++ b/crypto/rsa/private.go @@ -1,6 +1,8 @@ package rsa import ( + stdcrypto "crypto" + "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" @@ -10,7 +12,7 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeySigningASN1 = &PrivateKey{} type PrivateKey struct { k *rsa.PrivateKey @@ -148,34 +150,18 @@ func (p *PrivateKey) ToPKCS8PEM() string { })) } -func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) { - return nil, fmt.Errorf("not implemented") -} - +// SignToASN1 produce a PKCS#1 v1.5 signature. +// The default signing hash is: +// - SHA-256 for keys of length 2048 bits and under +// - SHA-384 for keys of length 3072 bits and under +// - SHA-512 for higher key length func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) { - return nil, fmt.Errorf("not implemented") -} + params := crypto.CollectSigningOptions(opts) -// func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool { -// if _, ok := remote.(*PublicKey); ok { -// return true -// } -// return false -// } -// -// func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) { -// if remote, ok := remote.(*PublicKey); ok { -// // First, we need to convert the ECDSA (signing only) to the equivalent ECDH keys -// ecdhPriv, err := p.k.ECDH() -// if err != nil { -// return nil, err -// } -// ecdhPub, err := remote.k.ECDH() -// if err != nil { -// return nil, err -// } -// -// return ecdhPriv.ECDH(ecdhPub) -// } -// return nil, fmt.Errorf("incompatible public key") -// } + hashCode := params.HashOrDefault(defaultSigHash(p.k.N.BitLen())) + hasher := hashCode.New() + hasher.Write(message) + hash := hasher.Sum(nil) + + return rsa.SignPKCS1v15(rand.Reader, p.k, stdcrypto.Hash(hashCode), hash) +} diff --git a/crypto/rsa/public.go b/crypto/rsa/public.go index edee2c7..06137cc 100644 --- a/crypto/rsa/public.go +++ b/crypto/rsa/public.go @@ -1,6 +1,7 @@ package rsa import ( + stdcrypto "crypto" "crypto/rsa" "crypto/x509" "encoding/pem" @@ -11,7 +12,7 @@ import ( helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeySigningASN1 = &PublicKey{} type PublicKey struct { k *rsa.PublicKey @@ -125,10 +126,19 @@ func (p *PublicKey) ToX509PEM() string { })) } -func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool { - return false -} - +// VerifyASN1 verifies a PKCS#1 v1.5 signature. +// The default signing hash is: +// - SHA-256 for keys of length 2048 bits and under +// - SHA-384 for keys of length 3072 bits and under +// - SHA-512 for higher key length func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool { - return false + params := crypto.CollectSigningOptions(opts) + + hashCode := params.HashOrDefault(defaultSigHash(p.k.N.BitLen())) + hasher := hashCode.New() + hasher.Write(message) + hash := hasher.Sum(nil) + + err := rsa.VerifyPKCS1v15(p.k, stdcrypto.Hash(hashCode), hash, signature) + return err == nil } diff --git a/crypto/secp256k1/key.go b/crypto/secp256k1/key.go index 7069679..e313ae2 100644 --- a/crypto/secp256k1/key.go +++ b/crypto/secp256k1/key.go @@ -12,7 +12,7 @@ const ( // PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes. PrivateKeyBytesSize = secp256k1.PrivKeyBytesLen // SignatureBytesSize is the size, in bytes, of signatures in raw bytes. - SignatureBytesSize = 123456 + SignatureBytesSize = 64 MultibaseCode = uint64(0xe7) diff --git a/crypto/secp256k1/key_test.go b/crypto/secp256k1/key_test.go index 0d24f31..9e204f4 100644 --- a/crypto/secp256k1/key_test.go +++ b/crypto/secp256k1/key_test.go @@ -1,6 +1,7 @@ package secp256k1 import ( + "encoding/base64" "testing" "github.com/stretchr/testify/require" @@ -80,3 +81,25 @@ Uwyag4V8qWsP8e5ZSOOXDSYMplwbsAzsko9NYw4Jy9RHYHwFQ7dV _, _ = PrivateKeyFromPKCS8PEM(data) }) } + +func TestSignatureASN1(t *testing.T) { + // openssl ecparam -genkey -name secp256k1 -noout -out private.pem + // openssl ec -in private.pem -pubout -out public.pem + // echo -n "message" | openssl dgst -sha256 -sign private.pem -out signature.der + // echo -n "message" | openssl dgst -sha256 -verify public.pem -signature signature.der + + pubPem := `-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEszL1+ZFqUMAHjLAyzMW7xMBPZek/8cNj +1qI7EgQooB3f8Sh7JwvXu8cosRnjjvYVvS7OliRsbvuceCQ7HBC4fA== +-----END PUBLIC KEY----- +` + pub, err := PublicKeyFromX509PEM(pubPem) + require.NoError(t, err) + + b64sig := `MEYCIQDv5SLy768FbOafzDlrxIeeoEn7tKpYBSK6WcKaOZ6AJAIhAKXV6VAwiPq4uk9TpGyFN5JK +8jZPrQ7hdRR5veKKDX2w` + sig, err := base64.StdEncoding.DecodeString(b64sig) + require.NoError(t, err) + + require.True(t, pub.VerifyASN1([]byte("message"), sig)) +} diff --git a/crypto/secp256k1/private.go b/crypto/secp256k1/private.go index ede226d..64552df 100644 --- a/crypto/secp256k1/private.go +++ b/crypto/secp256k1/private.go @@ -12,7 +12,8 @@ import ( "github.com/INFURA/go-did/crypto" ) -var _ crypto.PrivateKeySigning = &PrivateKey{} +var _ crypto.PrivateKeySigningBytes = &PrivateKey{} +var _ crypto.PrivateKeySigningASN1 = &PrivateKey{} var _ crypto.PrivateKeyKeyExchange = &PrivateKey{} type PrivateKey struct { @@ -179,17 +180,28 @@ func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ( hasher.Write(message) hash := hasher.Sum(nil) - // TODO - return ecdsa.SignCompact(p.k, hash[:], false), nil + sig := ecdsa.Sign(p.k, hash) + r := sig.R() + s := sig.S() + + res := make([]byte, SignatureBytesSize) + r.PutBytesUnchecked(res[:SignatureBytesSize/2]) + s.PutBytesUnchecked(res[SignatureBytesSize/2:]) + + return res, nil } // The default signing hash is SHA-256. func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) { - // // Hash the message with SHA-256 - // hash := sha256.Sum256(message) - // - // return ecdsa.SignASN1(rand.Reader, p.k.ToECDSA(), hash[:]) - return nil, fmt.Errorf("not implemented") + params := crypto.CollectSigningOptions(opts) + + hasher := params.HashOrDefault(crypto.SHA256).New() + hasher.Write(message) + hash := hasher.Sum(nil) + + sig := ecdsa.Sign(p.k, hash) + + return sig.Serialize(), nil } func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool { @@ -200,19 +212,8 @@ func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool { } func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) { - // if remote, ok := remote.(*PublicKey); ok { - // // First, we need to convert the ECDSA (signing only) to the equivalent ECDH keys - // ecdhPriv, err := p.k.ECDH() - // if err != nil { - // return nil, err - // } - // ecdhPub, err := remote.k.ECDH() - // if err != nil { - // return nil, err - // } - // - // return ecdhPriv.ECDH(ecdhPub) - // } - // return nil, fmt.Errorf("incompatible public key") - panic("not implemented") + if remote, ok := remote.(*PublicKey); ok { + return secp256k1.GenerateSharedSecret(p.k, remote.k), nil + } + return nil, fmt.Errorf("incompatible public key") } diff --git a/crypto/secp256k1/public.go b/crypto/secp256k1/public.go index bb1e459..b4b3927 100644 --- a/crypto/secp256k1/public.go +++ b/crypto/secp256k1/public.go @@ -7,12 +7,14 @@ import ( "fmt" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/INFURA/go-did/crypto" helpers "github.com/INFURA/go-did/crypto/internal" ) -var _ crypto.PublicKeySigning = &PublicKey{} +var _ crypto.PublicKeySigningBytes = &PublicKey{} +var _ crypto.PublicKeySigningASN1 = &PublicKey{} type PublicKey struct { k *secp256k1.PublicKey @@ -166,33 +168,39 @@ func (p *PublicKey) ToX509PEM() string { })) } -/* - Note: signatures for the crypto.PrivateKeySigning interface assumes SHA256, - which should be correct almost always. If there is a need to use a different - hash function, we can add separate functions that have that flexibility. -*/ - +// The default signing hash is SHA-256. func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool { - // if len(signature) != SignatureBytesSize { - // return false - // } - // - // // Hash the message with SHA-256 - // hash := sha256.Sum256(message) - // - // r := new(big.Int).SetBytes(signature[:SignatureBytesSize/2]) - // s := new(big.Int).SetBytes(signature[SignatureBytesSize/2:]) - // - // return ecdsa.Verify(p.k, hash[:], r, s) - panic("not implemented") + if len(signature) != SignatureBytesSize { + return false + } + + params := crypto.CollectSigningOptions(opts) + + hasher := params.HashOrDefault(crypto.SHA256).New() + hasher.Write(message) + hash := hasher.Sum(nil) + + var r, s secp256k1.ModNScalar + r.SetByteSlice(signature[:32]) + s.SetByteSlice(signature[32:]) + + return ecdsa.NewSignature(&r, &s).Verify(hash, p.k) } +// The default signing hash is SHA-256. func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool { - // // Hash the message with SHA-256 - // hash := sha256.Sum256(message) - // - // return ecdsa.VerifyASN1(p.k, hash[:], signature) - panic("not implemented") + params := crypto.CollectSigningOptions(opts) + + hasher := params.HashOrDefault(crypto.SHA256).New() + hasher.Write(message) + hash := hasher.Sum(nil) + + sig, err := ecdsa.ParseDERSignature(signature) + if err != nil { + return false + } + + return sig.Verify(hash, p.k) } func must[T any](v T, err error) T { diff --git a/verifications/jsonwebkey/JsonWebKey2020.go b/verifications/jsonwebkey/JsonWebKey2020.go index 9ca4700..ddc1c7f 100644 --- a/verifications/jsonwebkey/JsonWebKey2020.go +++ b/verifications/jsonwebkey/JsonWebKey2020.go @@ -94,7 +94,7 @@ func (j JsonWebKey2020) JsonLdContext() string { } func (j JsonWebKey2020) Verify(data []byte, sig []byte) (bool, error) { - if pub, ok := j.pubkey.(crypto.PublicKeySigning); ok { + if pub, ok := j.pubkey.(crypto.PublicKeySigningBytes); ok { return pub.VerifyBytes(data, sig), nil } return false, errors.New("not a signing public key") diff --git a/verifications/multikey/multikey.go b/verifications/multikey/multikey.go index a72992b..1ef1207 100644 --- a/verifications/multikey/multikey.go +++ b/verifications/multikey/multikey.go @@ -96,7 +96,7 @@ func (m MultiKey) JsonLdContext() string { } func (m MultiKey) Verify(data []byte, sig []byte) (bool, error) { - if pub, ok := m.pubkey.(crypto.PublicKeySigning); ok { + if pub, ok := m.pubkey.(crypto.PublicKeySigningBytes); ok { return pub.VerifyBytes(data, sig), nil } return false, errors.New("not a signing public key")