crypto: finish RSA

This commit is contained in:
Michael Muré
2025-07-09 17:58:09 +02:00
parent 2284bd6487
commit 61617d6af6
21 changed files with 320 additions and 142 deletions

View File

@@ -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
}
}

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -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
}