crypto: complete p256, add more tests and refine the signatures
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
)
|
||||
|
||||
@@ -58,10 +60,19 @@ func (p PrivateKey) Public() crypto.PublicKey {
|
||||
return PublicKey{k: p.k.Public().(ed25519.PublicKey)}
|
||||
}
|
||||
|
||||
func (p PrivateKey) Sign(message []byte) ([]byte, error) {
|
||||
func (p PrivateKey) SignToBytes(message []byte) ([]byte, error) {
|
||||
return ed25519.Sign(p.k, message), nil
|
||||
}
|
||||
|
||||
// SignToASN1 creates a signature with ASN.1 encoding.
|
||||
// This ASN.1 encoding uses a BIT STRING, which would be correct for an X.509 certificate.
|
||||
func (p PrivateKey) SignToASN1(message []byte) ([]byte, error) {
|
||||
sig := ed25519.Sign(p.k, message)
|
||||
var b cryptobyte.Builder
|
||||
b.AddASN1BitString(sig)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func (p PrivateKey) ToBytes() []byte {
|
||||
// Copy the private key to a fixed size buffer that can get allocated on the
|
||||
// caller's stack after inlining.
|
||||
|
||||
@@ -3,9 +3,12 @@ package ed25519
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/internal"
|
||||
)
|
||||
@@ -94,6 +97,22 @@ func (p PublicKey) Equal(other crypto.PublicKey) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p PublicKey) Verify(message, signature []byte) bool {
|
||||
func (p PublicKey) VerifyBytes(message, signature []byte) bool {
|
||||
return ed25519.Verify(p.k, message, signature)
|
||||
}
|
||||
|
||||
// VerifyASN1 verifies a signature with ASN.1 encoding.
|
||||
// This ASN.1 encoding uses a BIT STRING, which would be correct for an X.509 certificate.
|
||||
func (p PublicKey) VerifyASN1(message, signature []byte) bool {
|
||||
var s cryptobyte.String = signature
|
||||
var bitString asn1.BitString
|
||||
|
||||
if !s.ReadASN1BitString(&bitString) {
|
||||
return false
|
||||
}
|
||||
if bitString.BitLength != SignatureSize*8 {
|
||||
return false
|
||||
}
|
||||
|
||||
return ed25519.Verify(p.k, message, bitString.Bytes)
|
||||
}
|
||||
|
||||
@@ -21,13 +21,15 @@ type PrivateKey interface {
|
||||
type SigningPublicKey interface {
|
||||
PublicKey
|
||||
|
||||
Verify(message, signature []byte) bool
|
||||
VerifyBytes(message, signature []byte) bool
|
||||
VerifyASN1(message, signature []byte) bool
|
||||
}
|
||||
|
||||
type SigningPrivateKey interface {
|
||||
PrivateKey
|
||||
|
||||
Sign(message []byte) ([]byte, error)
|
||||
SignToBytes(message []byte) ([]byte, error)
|
||||
SignToASN1(message []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type KeyExchangePublicKey interface {
|
||||
|
||||
@@ -44,18 +44,28 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
|
||||
|
||||
x509PemPubSize int
|
||||
pkcs8PemPrivSize int
|
||||
|
||||
sigRawSize int
|
||||
sigAsn1Size int
|
||||
}{}
|
||||
|
||||
t.Cleanup(func() {
|
||||
out := strings.Builder{}
|
||||
w := tabwriter.NewWriter(&out, 0, 0, 3, ' ', 0)
|
||||
|
||||
out.WriteString("\nKeypairs (in bytes):\n")
|
||||
w := tabwriter.NewWriter(&out, 0, 0, 3, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, "\tPublic key\tPrivate key")
|
||||
_, _ = fmt.Fprintf(w, "Bytes\t%v\t%v\n", stats.bytesPubSize, stats.bytesPrivSize)
|
||||
_, _ = fmt.Fprintf(w, "DER (pub:x509, priv:PKCS#8)\t%v\t%v\n", stats.x509DerPubSize, stats.pkcs8DerPrivSize)
|
||||
_, _ = fmt.Fprintf(w, "PEM (pub:x509, priv:PKCS#8)\t%v\t%v\n", stats.x509PemPubSize, stats.pkcs8PemPrivSize)
|
||||
_ = w.Flush()
|
||||
|
||||
out.WriteString("\nSignatures (in bytes):\n")
|
||||
w.Init(&out, 0, 0, 3, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, "Raw bytes\tASN.1")
|
||||
_, _ = fmt.Fprintf(w, "%v\t%v\n", stats.sigRawSize, stats.sigAsn1Size)
|
||||
_ = w.Flush()
|
||||
|
||||
t.Logf("Test result for %s:\n%s\n", harness.Name, out.String())
|
||||
})
|
||||
|
||||
@@ -172,18 +182,46 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
|
||||
t.Skip("Signature is not implemented")
|
||||
}
|
||||
|
||||
msg := []byte("message")
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
signer func(msg []byte) ([]byte, error)
|
||||
verifier func(msg []byte, sig []byte) bool
|
||||
expectedSize int
|
||||
stats *int
|
||||
}{
|
||||
{
|
||||
name: "Bytes signature",
|
||||
signer: spriv.SignToBytes,
|
||||
verifier: spub.VerifyBytes,
|
||||
expectedSize: harness.SignatureSize,
|
||||
stats: &stats.sigRawSize,
|
||||
},
|
||||
{
|
||||
name: "ASN.1 signature",
|
||||
signer: spriv.SignToASN1,
|
||||
verifier: spub.VerifyASN1,
|
||||
stats: &stats.sigAsn1Size,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
msg := []byte("message")
|
||||
|
||||
sig, err := spriv.Sign(msg)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, sig)
|
||||
require.Equal(t, harness.SignatureSize, len(sig))
|
||||
sig, err := tc.signer(msg)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, sig)
|
||||
|
||||
valid := spub.Verify(msg, sig)
|
||||
require.True(t, valid)
|
||||
if tc.expectedSize > 0 {
|
||||
require.Equal(t, tc.expectedSize, len(sig))
|
||||
}
|
||||
*tc.stats = len(sig)
|
||||
|
||||
valid = spub.Verify([]byte("wrong message"), sig)
|
||||
require.False(t, valid)
|
||||
valid := tc.verifier(msg, sig)
|
||||
require.True(t, valid)
|
||||
|
||||
valid = tc.verifier([]byte("wrong message"), sig)
|
||||
require.False(t, valid)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("KeyExchange", func(t *testing.T) {
|
||||
@@ -358,7 +396,7 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
|
||||
b.Skip("Signature is not implemented")
|
||||
}
|
||||
|
||||
b.Run("Sign", func(b *testing.B) {
|
||||
b.Run("Sign to Bytes signature", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
@@ -368,24 +406,55 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
spriv.Sign([]byte("message"))
|
||||
_, _ = spriv.SignToBytes([]byte("message"))
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Verify", func(b *testing.B) {
|
||||
b.Run("Verify from Bytes signature", func(b *testing.B) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
|
||||
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
|
||||
sig, err := spriv.Sign([]byte("message"))
|
||||
sig, err := spriv.SignToBytes([]byte("message"))
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
spub.Verify([]byte("message"), sig)
|
||||
spub.VerifyBytes([]byte("message"), sig)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Sign to ASN.1 signature", func(b *testing.B) {
|
||||
_, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = spriv.SignToASN1([]byte("message"))
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Verify from ASN.1 signature", func(b *testing.B) {
|
||||
pub, priv, err := harness.GenerateKeyPair()
|
||||
require.NoError(b, err)
|
||||
|
||||
spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
|
||||
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
|
||||
sig, err := spriv.SignToASN1([]byte("message"))
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
spub.VerifyASN1([]byte("message"), sig)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ const (
|
||||
// TODO
|
||||
PublicKeySize = 33
|
||||
PrivateKeySize = 32
|
||||
SignatureSize = 123456
|
||||
SignatureSize = 64
|
||||
|
||||
MultibaseCode = uint64(0x1200)
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
@@ -89,6 +90,33 @@ func (p *PrivateKey) ToPKCS8PEM() string {
|
||||
}))
|
||||
}
|
||||
|
||||
func (p *PrivateKey) Sign(message []byte) ([]byte, error) {
|
||||
return (*ecdsa.PrivateKey)(p).Sign(rand.Reader, message, nil)
|
||||
/*
|
||||
Note: signatures for the crypto.SigningPrivateKey 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.
|
||||
*/
|
||||
|
||||
func (p *PrivateKey) SignToBytes(message []byte) ([]byte, error) {
|
||||
// Hash the message with SHA-256
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
r, s, err := ecdsa.Sign(rand.Reader, (*ecdsa.PrivateKey)(p), hash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sig := make([]byte, 64)
|
||||
r.FillBytes(sig[:32])
|
||||
s.FillBytes(sig[32:])
|
||||
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
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[:])
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package p256
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
helpers "github.com/INFURA/go-did/crypto/internal"
|
||||
@@ -94,6 +96,30 @@ func (p *PublicKey) ToX509PEM() string {
|
||||
}))
|
||||
}
|
||||
|
||||
func (p *PublicKey) Verify(message, signature []byte) bool {
|
||||
panic("not implemented")
|
||||
/*
|
||||
Note: signatures for the crypto.SigningPrivateKey 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.
|
||||
*/
|
||||
|
||||
func (p *PublicKey) VerifyBytes(message, signature []byte) bool {
|
||||
if len(signature) != SignatureSize {
|
||||
return false
|
||||
}
|
||||
|
||||
// Hash the message with SHA-256
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (p *PublicKey) VerifyASN1(message, signature []byte) bool {
|
||||
// Hash the message with SHA-256
|
||||
hash := sha256.Sum256(message)
|
||||
|
||||
return ecdsa.VerifyASN1((*ecdsa.PublicKey)(p), hash[:], signature)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user