-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add helpers for handling Ed25519 key (un)marshalling
This implements the encoding of Ed25519 keys as defined in https://datatracker.ietf.org/doc/draft-ietf-curdle-pkix-04.
- Loading branch information
Showing
10 changed files
with
3,929 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package derhelpers | ||
|
||
import ( | ||
"crypto" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"errors" | ||
|
||
"golang.org/x/crypto/ed25519" | ||
) | ||
|
||
var errEd25519WrongID = errors.New("incorrect object identifier") | ||
var errEd25519WrongKeyType = errors.New("incorrect key type") | ||
|
||
// ed25519OID is the OID for the Ed25519 signature scheme: see | ||
// https://datatracker.ietf.org/doc/draft-ietf-curdle-pkix-04. | ||
var ed25519OID = asn1.ObjectIdentifier{1, 3, 101, 112} | ||
|
||
// subjectPublicKeyInfo reflects the ASN.1 object defined in the X.509 standard. | ||
// | ||
// This is defined in crypto/x509 as "publicKeyInfo". | ||
type subjectPublicKeyInfo struct { | ||
Algorithm pkix.AlgorithmIdentifier | ||
PublicKey asn1.BitString | ||
} | ||
|
||
// MarshalEd25519PublicKey creates a DER-encoded SubjectPublicKeyInfo for an | ||
// ed25519 public key, as defined in | ||
// https://tools.ietf.org/html/draft-ietf-curdle-pkix-04. This is analagous to | ||
// MarshalPKIXPublicKey in crypto/x509, which doesn't currently support Ed25519. | ||
func MarshalEd25519PublicKey(pk crypto.PublicKey) ([]byte, error) { | ||
pub, ok := pk.(ed25519.PublicKey) | ||
if !ok { | ||
return nil, errEd25519WrongKeyType | ||
} | ||
|
||
spki := subjectPublicKeyInfo{ | ||
Algorithm: pkix.AlgorithmIdentifier{ | ||
Algorithm: ed25519OID, | ||
}, | ||
PublicKey: asn1.BitString{ | ||
BitLength: len(pub) * 8, | ||
Bytes: pub, | ||
}, | ||
} | ||
|
||
return asn1.Marshal(spki) | ||
} | ||
|
||
// ParseEd25519PublicKey returns the Ed25519 public key encoded by the input. | ||
func ParseEd25519PublicKey(der []byte) (crypto.PublicKey, error) { | ||
var spki subjectPublicKeyInfo | ||
if rest, err := asn1.Unmarshal(der, &spki); err != nil { | ||
return nil, err | ||
} else if len(rest) > 0 { | ||
return nil, errors.New("SubjectPublicKeyInfo too long") | ||
} | ||
|
||
if !spki.Algorithm.Algorithm.Equal(ed25519OID) { | ||
return nil, errEd25519WrongID | ||
} | ||
|
||
if spki.PublicKey.BitLength != ed25519.PublicKeySize*8 { | ||
return nil, errors.New("SubjectPublicKeyInfo PublicKey length mismatch") | ||
} | ||
|
||
return ed25519.PublicKey(spki.PublicKey.Bytes), nil | ||
} | ||
|
||
// oneAsymmetricKey reflects the ASN.1 structure for storing private keys in | ||
// https://tools.ietf.org/html/draft-ietf-curdle-pkix-04, excluding the optional | ||
// fields, which we don't use here. | ||
// | ||
// This is identical to pkcs8 in crypto/x509. | ||
type oneAsymmetricKey struct { | ||
Version int | ||
Algorithm pkix.AlgorithmIdentifier | ||
PrivateKey []byte | ||
} | ||
|
||
// curvePrivateKey is the innter type of the PrivateKey field of | ||
// oneAsymmetricKey. | ||
type curvePrivateKey []byte | ||
|
||
// MarshalEd25519PrivateKey returns a DER encdoing of the input private key as | ||
// specified in https://tools.ietf.org/html/draft-ietf-curdle-pkix-04. | ||
func MarshalEd25519PrivateKey(sk crypto.PrivateKey) ([]byte, error) { | ||
priv, ok := sk.(ed25519.PrivateKey) | ||
if !ok { | ||
return nil, errEd25519WrongKeyType | ||
} | ||
|
||
// Marshal the innter CurvePrivateKey. | ||
curvePrivateKey, err := asn1.Marshal(priv.Seed()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Marshal the OneAsymmetricKey. | ||
asym := oneAsymmetricKey{ | ||
Version: 0, | ||
Algorithm: pkix.AlgorithmIdentifier{ | ||
Algorithm: ed25519OID, | ||
}, | ||
PrivateKey: curvePrivateKey, | ||
} | ||
return asn1.Marshal(asym) | ||
} | ||
|
||
// ParseEd25519PrivateKey returns the Ed25519 private key encoded by the input. | ||
func ParseEd25519PrivateKey(der []byte) (crypto.PrivateKey, error) { | ||
asym := new(oneAsymmetricKey) | ||
if rest, err := asn1.Unmarshal(der, asym); err != nil { | ||
return nil, err | ||
} else if len(rest) > 0 { | ||
return nil, errors.New("OneAsymmetricKey too long") | ||
} | ||
|
||
// Check that the key type is correct. | ||
if !asym.Algorithm.Algorithm.Equal(ed25519OID) { | ||
return nil, errEd25519WrongID | ||
} | ||
|
||
// Unmarshal the inner CurvePrivateKey. | ||
seed := new(curvePrivateKey) | ||
if rest, err := asn1.Unmarshal(asym.PrivateKey, seed); err != nil { | ||
return nil, err | ||
} else if len(rest) > 0 { | ||
return nil, errors.New("CurvePrivateKey too long") | ||
} | ||
|
||
return ed25519.NewKeyFromSeed(*seed), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package derhelpers | ||
|
||
import ( | ||
"bytes" | ||
"encoding/pem" | ||
"testing" | ||
|
||
"golang.org/x/crypto/ed25519" | ||
) | ||
|
||
var testPubKey = `-----BEGIN PUBLIC KEY----- | ||
MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= | ||
-----END PUBLIC KEY----- | ||
` | ||
|
||
var testPrivKey = `-----BEGIN PRIVATE KEY----- | ||
MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC | ||
-----END PRIVATE KEY-----` | ||
|
||
func TestParseMarshalEd25519PublicKey(t *testing.T) { | ||
block, rest := pem.Decode([]byte(testPubKey)) | ||
if len(rest) > 0 { | ||
t.Fatal("pem.Decode(); len(rest) > 0, want 0") | ||
} | ||
|
||
pk, err := ParseEd25519PublicKey(block.Bytes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if pkLen := len(pk.(ed25519.PublicKey)); pkLen != 32 { | ||
t.Fatalf("len(pk): got %d: want %d", pkLen, 32) | ||
} | ||
|
||
der, err := MarshalEd25519PublicKey(pk) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if !bytes.Equal(der, block.Bytes) { | ||
t.Errorf("got %d bytes:\n%v \nwant %d bytes:\n%v", | ||
len(der), der, len(block.Bytes), block.Bytes) | ||
} | ||
} | ||
|
||
func TestParseMarshalEd25519PrivateKey(t *testing.T) { | ||
block, rest := pem.Decode([]byte(testPrivKey)) | ||
if len(rest) > 0 { | ||
t.Fatal("pem.Decode(); len(rest) > 0, want 0") | ||
} | ||
|
||
sk, err := ParseEd25519PrivateKey(block.Bytes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if skLen := len(sk.(ed25519.PrivateKey)); skLen != 64 { | ||
t.Fatalf("len(sk): got %d: want %d", skLen, 64) | ||
} | ||
|
||
der, err := MarshalEd25519PrivateKey(sk) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if !bytes.Equal(der, block.Bytes) { | ||
t.Errorf("got %d bytes:\n%v \nwant %d bytes:\n%v", | ||
len(der), der, len(block.Bytes), block.Bytes) | ||
} | ||
} | ||
|
||
func TestKeyPair(t *testing.T) { | ||
block, rest := pem.Decode([]byte(testPrivKey)) | ||
if len(rest) > 0 { | ||
t.Fatal("pem.Decode(); len(rest) > 0, want 0") | ||
} | ||
|
||
sk, err := ParseEd25519PrivateKey(block.Bytes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
block, rest = pem.Decode([]byte(testPubKey)) | ||
if len(rest) > 0 { | ||
t.Fatal("pem.Decode(); len(rest) > 0, want 0") | ||
} | ||
|
||
pub, err := ParseEd25519PublicKey(block.Bytes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
pk := pub.(ed25519.PublicKey) | ||
pk2 := sk.(ed25519.PrivateKey).Public().(ed25519.PublicKey) | ||
if !bytes.Equal(pk, pk2) { | ||
t.Errorf("pk %d bytes:\n%v \nsk.Public() %d bytes:\n%v", | ||
len(pk), pk, len(pk2), pk2) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MC4CAQAwBQYDK2VwBCIEIEHfDC6M85b3uVU5GO82Y5D6Qkx5YehoCe2T1auTbTFN | ||
-----END PRIVATE KEY----- |
Oops, something went wrong.