add support for P-384

This commit is contained in:
Michael Muré
2025-06-25 15:53:29 +02:00
parent 2809127a08
commit 5230212c86
13 changed files with 406 additions and 49 deletions

View File

@@ -0,0 +1,32 @@
package allkeys
import (
"fmt"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/ed25519"
helpers "github.com/INFURA/go-did/crypto/internal"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/p384"
"github.com/INFURA/go-did/crypto/x25519"
)
var decoders = map[uint64]func(b []byte) (crypto.PublicKey, error){
ed25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return ed25519.PublicKeyFromBytes(b) },
p256.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p256.PublicKeyFromBytes(b) },
p384.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p384.PublicKeyFromBytes(b) },
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its PublicKeyMultibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (crypto.PublicKey, error) {
code, pubBytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, fmt.Errorf("invalid publicKeyMultibase: %w", err)
}
decoder, ok := decoders[code]
if !ok {
return nil, fmt.Errorf("unsupported publicKeyMultibase code: %d", code)
}
return decoder(pubBytes)
}

View File

@@ -10,7 +10,7 @@ import (
"golang.org/x/crypto/cryptobyte"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_helpers"
"github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.SigningPublicKey = &PublicKey{}

View File

@@ -106,9 +106,9 @@ func (p *PrivateKey) SignToBytes(message []byte) ([]byte, error) {
return nil, err
}
sig := make([]byte, 64)
r.FillBytes(sig[:32])
s.FillBytes(sig[32:])
sig := make([]byte, SignatureBytesSize)
r.FillBytes(sig[:SignatureBytesSize/2])
s.FillBytes(sig[SignatureBytesSize/2:])
return sig, nil
}

View File

@@ -10,7 +10,7 @@ import (
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/_helpers"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.SigningPublicKey = (*PublicKey)(nil)
@@ -118,8 +118,8 @@ func (p *PublicKey) VerifyBytes(message, signature []byte) bool {
// Hash the message with SHA-256
hash := sha256.Sum256(message)
r := new(big.Int).SetBytes(signature[:32])
s := new(big.Int).SetBytes(signature[32:])
r := new(big.Int).SetBytes(signature[:SignatureBytesSize/2])
s := new(big.Int).SetBytes(signature[SignatureBytesSize/2:])
return ecdsa.Verify((*ecdsa.PublicKey)(p), hash[:], r, s)
}

32
crypto/p384/key.go Normal file
View File

@@ -0,0 +1,32 @@
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
)
const (
// PublicKeyBytesSize is the size, in bytes, of public keys in raw bytes.
PublicKeyBytesSize = 49
// PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes.
PrivateKeyBytesSize = 48
// SignatureBytesSize is the size, in bytes, of signatures in raw bytes.
SignatureBytesSize = 96
MultibaseCode = uint64(0x1201)
)
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, nil, err
}
pub := priv.Public().(*ecdsa.PublicKey)
return (*PublicKey)(pub), (*PrivateKey)(priv), nil
}
const (
pemPubBlockType = "PUBLIC KEY"
pemPrivBlockType = "PRIVATE KEY"
)

31
crypto/p384/key_test.go Normal file
View File

@@ -0,0 +1,31 @@
package p384
import (
"testing"
"github.com/INFURA/go-did/crypto/_testsuite"
)
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "p384",
GenerateKeyPair: GenerateKeyPair,
PublicKeyFromBytes: PublicKeyFromBytes,
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromBytes: PrivateKeyFromBytes,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
PublicKeyBytesSize: PublicKeyBytesSize,
PrivateKeyBytesSize: PrivateKeyBytesSize,
SignatureBytesSize: SignatureBytesSize,
}
func TestSuite(t *testing.T) {
testsuite.TestSuite(t, harness)
}
func BenchmarkSuite(b *testing.B) {
testsuite.BenchSuite(b, harness)
}

145
crypto/p384/private.go Normal file
View File

@@ -0,0 +1,145 @@
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
)
var _ crypto.SigningPrivateKey = (*PrivateKey)(nil)
var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil)
type PrivateKey ecdsa.PrivateKey
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
if len(b) != PrivateKeyBytesSize {
return nil, fmt.Errorf("invalid P-384 private key size")
}
res := &ecdsa.PrivateKey{
D: new(big.Int).SetBytes(b),
PublicKey: ecdsa.PublicKey{Curve: elliptic.P384()},
}
// recompute the public key
res.PublicKey.X, res.PublicKey.Y = res.PublicKey.Curve.ScalarBaseMult(b)
return (*PrivateKey)(res), nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
priv, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
ecdsaPriv := priv.(*ecdsa.PrivateKey)
return (*PrivateKey)(ecdsaPriv), nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPrivBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PrivateKeyFromPKCS8DER(block.Bytes)
}
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return (*ecdsa.PrivateKey)(p).Equal((*ecdsa.PrivateKey)(other))
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
ecdhPub := (*ecdsa.PrivateKey)(p).Public().(*ecdsa.PublicKey)
return (*PublicKey)(ecdhPub)
}
func (p *PrivateKey) ToBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [PrivateKeyBytesSize]byte
((*ecdsa.PrivateKey)(p)).D.FillBytes(buf[:])
return buf[:]
}
func (p *PrivateKey) ToPKCS8DER() []byte {
res, _ := x509.MarshalPKCS8PrivateKey((*ecdsa.PrivateKey)(p))
return res
}
func (p *PrivateKey) ToPKCS8PEM() string {
der := p.ToPKCS8DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPrivBlockType,
Bytes: der,
}))
}
/*
Note: signatures for the crypto.SigningPrivateKey interface assumes SHA384,
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-384
hash := sha512.Sum384(message)
r, s, err := ecdsa.Sign(rand.Reader, (*ecdsa.PrivateKey)(p), hash[:])
if err != nil {
return nil, err
}
sig := make([]byte, SignatureBytesSize)
r.FillBytes(sig[:SignatureBytesSize/2])
s.FillBytes(sig[SignatureBytesSize/2:])
return sig, nil
}
func (p *PrivateKey) SignToASN1(message []byte) ([]byte, error) {
// Hash the message with SHA-384
hash := sha512.Sum384(message)
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")
}

132
crypto/p384/public.go Normal file
View File

@@ -0,0 +1,132 @@
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.SigningPublicKey = (*PublicKey)(nil)
type PublicKey ecdsa.PublicKey
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if len(b) != PublicKeyBytesSize {
return nil, fmt.Errorf("invalid P-384 public key size")
}
x, y := elliptic.UnmarshalCompressed(elliptic.P384(), b)
if x == nil {
return nil, fmt.Errorf("invalid P-384 public key")
}
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P384(), X: x, Y: y}), nil
}
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
func PublicKeyFromXY(x, y *big.Int) (*PublicKey, error) {
if !elliptic.P384().IsOnCurve(x, y) {
return nil, fmt.Errorf("invalid P-384 public key")
}
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P384(), X: x, Y: y}), nil
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
return PublicKeyFromBytes(bytes)
}
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(bytes)
if err != nil {
return nil, err
}
ecdsaPub := pub.(*ecdsa.PublicKey)
return (*PublicKey)(ecdsaPub), nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPubBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return (*ecdsa.PublicKey)(p).Equal((*ecdsa.PublicKey)(other))
}
return false
}
func (p *PublicKey) ToBytes() []byte {
ecdsaPub := (*ecdsa.PublicKey)(p)
return elliptic.MarshalCompressed(elliptic.P384(), ecdsaPub.X, ecdsaPub.Y)
}
func (p *PublicKey) ToPublicKeyMultibase() string {
ecdsaPub := (*ecdsa.PublicKey)(p)
bytes := elliptic.MarshalCompressed(elliptic.P384(), ecdsaPub.X, ecdsaPub.Y)
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
}
func (p *PublicKey) ToX509DER() []byte {
res, _ := x509.MarshalPKIXPublicKey((*ecdsa.PublicKey)(p))
return res
}
func (p *PublicKey) ToX509PEM() string {
der := p.ToX509DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPubBlockType,
Bytes: der,
}))
}
/*
Note: signatures for the crypto.SigningPrivateKey interface assumes SHA384,
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) != SignatureBytesSize {
return false
}
// Hash the message with SHA-384
hash := sha512.Sum384(message)
r := new(big.Int).SetBytes(signature[:SignatureBytesSize/2])
s := new(big.Int).SetBytes(signature[SignatureBytesSize/2:])
return ecdsa.Verify((*ecdsa.PublicKey)(p), hash[:], r, s)
}
func (p *PublicKey) VerifyASN1(message, signature []byte) bool {
// Hash the message with SHA-384
hash := sha512.Sum384(message)
return ecdsa.VerifyASN1((*ecdsa.PublicKey)(p), hash[:], signature)
}

View File

@@ -8,8 +8,8 @@ import (
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/_helpers"
"github.com/INFURA/go-did/crypto/ed25519"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.PublicKey = (*PublicKey)(nil)