add support for P-521
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
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/p521"
|
||||
"github.com/INFURA/go-did/crypto/x25519"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,7 @@ 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) },
|
||||
p521.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p521.PublicKeyFromBytes(b) },
|
||||
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
|
||||
}
|
||||
|
||||
|
||||
32
crypto/p521/key.go
Normal file
32
crypto/p521/key.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package p521
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
// PublicKeyBytesSize is the size, in bytes, of public keys in raw bytes.
|
||||
PublicKeyBytesSize = 67
|
||||
// PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes.
|
||||
PrivateKeyBytesSize = 66
|
||||
// SignatureBytesSize is the size, in bytes, of signatures in raw bytes.
|
||||
SignatureBytesSize = 132
|
||||
|
||||
MultibaseCode = uint64(0x1202)
|
||||
)
|
||||
|
||||
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P521(), 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/p521/key_test.go
Normal file
31
crypto/p521/key_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package p521
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/INFURA/go-did/crypto/_testsuite"
|
||||
)
|
||||
|
||||
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
|
||||
Name: "p521",
|
||||
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/p521/private.go
Normal file
145
crypto/p521/private.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package p521
|
||||
|
||||
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-521 private key size")
|
||||
}
|
||||
|
||||
res := &ecdsa.PrivateKey{
|
||||
D: new(big.Int).SetBytes(b),
|
||||
PublicKey: ecdsa.PublicKey{Curve: elliptic.P521()},
|
||||
}
|
||||
|
||||
// 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 SHA512,
|
||||
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-512
|
||||
hash := sha512.Sum512(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-512
|
||||
hash := sha512.Sum512(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/p521/public.go
Normal file
132
crypto/p521/public.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package p521
|
||||
|
||||
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-521 public key size")
|
||||
}
|
||||
x, y := elliptic.UnmarshalCompressed(elliptic.P521(), b)
|
||||
if x == nil {
|
||||
return nil, fmt.Errorf("invalid P-521 public key")
|
||||
}
|
||||
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
|
||||
func PublicKeyFromXY(x, y *big.Int) (*PublicKey, error) {
|
||||
if !elliptic.P521().IsOnCurve(x, y) {
|
||||
return nil, fmt.Errorf("invalid P-521 public key")
|
||||
}
|
||||
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P521(), 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.P521(), ecdsaPub.X, ecdsaPub.Y)
|
||||
}
|
||||
|
||||
func (p *PublicKey) ToPublicKeyMultibase() string {
|
||||
ecdsaPub := (*ecdsa.PublicKey)(p)
|
||||
bytes := elliptic.MarshalCompressed(elliptic.P521(), 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 SHA512,
|
||||
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-512
|
||||
hash := sha512.Sum512(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-512
|
||||
hash := sha512.Sum512(message)
|
||||
|
||||
return ecdsa.VerifyASN1((*ecdsa.PublicKey)(p), hash[:], signature)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
"github.com/INFURA/go-did/crypto/p256"
|
||||
"github.com/INFURA/go-did/crypto/p384"
|
||||
"github.com/INFURA/go-did/crypto/p521"
|
||||
"github.com/INFURA/go-did/crypto/x25519"
|
||||
"github.com/INFURA/go-did/verifications/ed25519"
|
||||
"github.com/INFURA/go-did/verifications/multikey"
|
||||
@@ -62,7 +63,7 @@ func FromPublicKey(pub crypto.PublicKey) (did.DID, error) {
|
||||
xmsi := xpub.ToPublicKeyMultibase()
|
||||
d.keyAgreement = x25519vm.NewKeyAgreementKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, xmsi), xpub, d)
|
||||
return d, nil
|
||||
case *p256.PublicKey, *p384.PublicKey:
|
||||
case *p256.PublicKey, *p384.PublicKey, *p521.PublicKey:
|
||||
d := DidKey{msi: pub.ToPublicKeyMultibase()}
|
||||
mk := multikey.NewMultiKey(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
|
||||
d.signature = mk
|
||||
|
||||
@@ -57,6 +57,18 @@ func (j jwk) MarshalJSON() ([]byte, error) {
|
||||
X: base64.RawURLEncoding.EncodeToString(pubkey.X.Bytes()),
|
||||
Y: base64.RawURLEncoding.EncodeToString(pubkey.Y.Bytes()),
|
||||
})
|
||||
case *p521.PublicKey:
|
||||
return json.Marshal(struct {
|
||||
Kty string `json:"kty"`
|
||||
Crv string `json:"crv"`
|
||||
X string `json:"x"`
|
||||
Y string `json:"y"`
|
||||
}{
|
||||
Kty: "EC",
|
||||
Crv: "P-521",
|
||||
X: base64.RawURLEncoding.EncodeToString(pubkey.X.Bytes()),
|
||||
Y: base64.RawURLEncoding.EncodeToString(pubkey.Y.Bytes()),
|
||||
})
|
||||
case *x25519.PublicKey:
|
||||
return json.Marshal(struct {
|
||||
Kty string `json:"kty"`
|
||||
@@ -105,6 +117,9 @@ func (j *jwk) UnmarshalJSON(bytes []byte) error {
|
||||
case "P-384":
|
||||
j.pubkey, err = p384.PublicKeyFromXY(x, y)
|
||||
return err
|
||||
case "P-521":
|
||||
j.pubkey, err = p521.PublicKeyFromXY(x, y)
|
||||
return err
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported Curve %s", aux["crv"])
|
||||
|
||||
Reference in New Issue
Block a user