crypto: reasonably complete the keypair absraction layer, and use it
This commit is contained in:
@@ -1,43 +1,74 @@
|
||||
package crypto
|
||||
|
||||
type PublicKey interface {
|
||||
// Equal returns true if other is the same PublicKey
|
||||
Equal(other PublicKey) bool
|
||||
|
||||
// ToBytes serializes the PublicKey into "raw bytes", without metadata or structure.
|
||||
// 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.
|
||||
ToBytes() []byte
|
||||
|
||||
// ToPublicKeyMultibase format the PublicKey into a string compatible with a PublicKeyMultibase field
|
||||
// in a DID Document.
|
||||
ToPublicKeyMultibase() string
|
||||
|
||||
// ToX509DER serializes the PublicKey into the X.509 DER (binary) format.
|
||||
ToX509DER() []byte
|
||||
|
||||
// ToX509PEM serializes the PublicKey into the X.509 PEM (string) format.
|
||||
ToX509PEM() string
|
||||
}
|
||||
|
||||
type PrivateKey interface {
|
||||
// Equal returns true if other is the same PrivateKey
|
||||
Equal(other PrivateKey) bool
|
||||
|
||||
// Public returns the matching PublicKey.
|
||||
Public() PublicKey
|
||||
|
||||
// ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure.
|
||||
// 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.
|
||||
ToBytes() []byte
|
||||
|
||||
// ToPKCS8DER serializes the PrivateKey into the PKCS#8 DER (binary) format.
|
||||
ToPKCS8DER() []byte
|
||||
|
||||
// ToPKCS8PEM serializes the PrivateKey into the PKCS#8 PEM (string) format.
|
||||
ToPKCS8PEM() string
|
||||
}
|
||||
|
||||
type SigningPublicKey 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) bool
|
||||
|
||||
// VerifyASN1 checks a signature in the ASN.1 format.
|
||||
VerifyASN1(message, signature []byte) bool
|
||||
}
|
||||
|
||||
type SigningPrivateKey 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) ([]byte, error)
|
||||
|
||||
// SignToASN1 creates a signature in the ASN.1 format.
|
||||
SignToASN1(message []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type KeyExchangePublicKey interface {
|
||||
PublicKey
|
||||
type KeyExchangePrivateKey interface {
|
||||
PrivateKey
|
||||
|
||||
// PrivateKeyIsCompatible checks that the given PrivateKey is compatible to perform key exchange.
|
||||
PrivateKeyIsCompatible(local PrivateKey) bool
|
||||
// PublicKeyIsCompatible checks that the given PublicKey is compatible to perform key exchange.
|
||||
PublicKeyIsCompatible(remote PublicKey) bool
|
||||
|
||||
// ECDH computes the shared key using the given PrivateKey.
|
||||
ECDH(local PrivateKey) ([]byte, error)
|
||||
// KeyExchange computes the shared key using the given PublicKey.
|
||||
KeyExchange(remote PublicKey) ([]byte, error)
|
||||
}
|
||||
|
||||
@@ -229,25 +229,34 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
|
||||
require.NoError(t, err)
|
||||
pub2, priv2, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
pub3, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(t, err)
|
||||
|
||||
kePub1, ok := (crypto.PublicKey(pub1)).(crypto.KeyExchangePublicKey)
|
||||
kePriv1, ok := crypto.PrivateKey(priv1).(crypto.KeyExchangePrivateKey)
|
||||
if !ok {
|
||||
t.Skip("Key exchange is not implemented")
|
||||
}
|
||||
kePub2 := (crypto.PublicKey(pub2)).(crypto.KeyExchangePublicKey)
|
||||
kePriv2 := crypto.PrivateKey(priv2).(crypto.KeyExchangePrivateKey)
|
||||
|
||||
// TODO: test with incompatible private keys
|
||||
require.True(t, kePub1.PrivateKeyIsCompatible(priv2))
|
||||
require.True(t, kePub2.PrivateKeyIsCompatible(priv1))
|
||||
// TODO: test with incompatible public keys
|
||||
require.True(t, kePriv1.PublicKeyIsCompatible(pub2))
|
||||
require.True(t, kePriv2.PublicKeyIsCompatible(pub1))
|
||||
|
||||
k1, err := kePub1.ECDH(priv2)
|
||||
// 1 --> 2
|
||||
kA, err := kePriv1.KeyExchange(pub2)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, k1)
|
||||
k2, err := kePub2.ECDH(priv1)
|
||||
require.NotEmpty(t, kA)
|
||||
// 2 --> 1
|
||||
kB, err := kePriv2.KeyExchange(pub1)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, k2)
|
||||
require.NotEmpty(t, kB)
|
||||
// 2 --> 3
|
||||
kC, err := kePriv2.KeyExchange(pub3)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, kC)
|
||||
|
||||
require.Equal(t, k1, k2)
|
||||
require.Equal(t, kA, kB)
|
||||
require.NotEqual(t, kB, kC)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -459,5 +468,24 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: add key exchange benchmarks
|
||||
b.Run("Key exchange", func(b *testing.B) {
|
||||
if _, ok := (crypto.PrivateKey(*new(PrivT))).(crypto.KeyExchangePrivateKey); !ok {
|
||||
b.Skip("Key echange is not implemented")
|
||||
}
|
||||
|
||||
b.Run("KeyExchange", func(b *testing.B) {
|
||||
_, priv1, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
kePriv1 := (crypto.PrivateKey(priv1)).(crypto.KeyExchangePrivateKey)
|
||||
pub2, _, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = kePriv1.KeyExchange(pub2)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
var _ crypto.SigningPrivateKey = (*PrivateKey)(nil)
|
||||
var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil)
|
||||
|
||||
type PrivateKey ecdsa.PrivateKey
|
||||
|
||||
@@ -116,7 +117,29 @@ func (p *PrivateKey) SignToASN1(message []byte) ([]byte, error) {
|
||||
// Hash the message with SHA-256
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
// Use ecdsa.SignASN1 for direct ASN.1 DER encoding
|
||||
return ecdsa.SignASN1(rand.Reader, (*ecdsa.PrivateKey)(p), hash[:])
|
||||
|
||||
}
|
||||
|
||||
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 := (*ecdsa.PrivateKey)(p).ECDH()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ecdhPub, err := (*ecdsa.PublicKey)(remote).ECDH()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ecdhPriv.ECDH(ecdhPub)
|
||||
}
|
||||
return nil, fmt.Errorf("incompatible public key")
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ func (p *PublicKey) VerifyBytes(message, signature []byte) bool {
|
||||
r := new(big.Int).SetBytes(signature[:32])
|
||||
s := new(big.Int).SetBytes(signature[32:])
|
||||
|
||||
// Use ecdsa.Verify
|
||||
return ecdsa.Verify((*ecdsa.PublicKey)(p), hash[:], r, s)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
)
|
||||
|
||||
var _ crypto.PrivateKey = (*PrivateKey)(nil)
|
||||
var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil)
|
||||
|
||||
type PrivateKey ecdh.PrivateKey
|
||||
|
||||
@@ -95,3 +95,17 @@ func (p *PrivateKey) ToPKCS8PEM() string {
|
||||
Bytes: der,
|
||||
}))
|
||||
}
|
||||
|
||||
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 local, ok := remote.(*PublicKey); ok {
|
||||
return (*ecdh.PrivateKey)(p).ECDH((*ecdh.PublicKey)(local))
|
||||
}
|
||||
return nil, fmt.Errorf("incompatible public key")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
helpers "github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
|
||||
var _ crypto.KeyExchangePublicKey = (*PublicKey)(nil)
|
||||
var _ crypto.PublicKey = (*PublicKey)(nil)
|
||||
|
||||
type PublicKey ecdh.PublicKey
|
||||
|
||||
@@ -145,20 +145,6 @@ func (p *PublicKey) ToX509PEM() string {
|
||||
}))
|
||||
}
|
||||
|
||||
func (p *PublicKey) PrivateKeyIsCompatible(local crypto.PrivateKey) bool {
|
||||
if _, ok := local.(*PrivateKey); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PublicKey) ECDH(local crypto.PrivateKey) ([]byte, error) {
|
||||
if local, ok := local.(*PrivateKey); ok {
|
||||
return (*ecdh.PrivateKey)(local).ECDH((*ecdh.PublicKey)(p))
|
||||
}
|
||||
return nil, fmt.Errorf("incompatible private key")
|
||||
}
|
||||
|
||||
func reverseBytes(b []byte) []byte {
|
||||
r := make([]byte, len(b))
|
||||
for i := 0; i < len(b); i++ {
|
||||
|
||||
Reference in New Issue
Block a user