Przeglądaj źródła

openpgp/packet: Parse and serialize ECC key material (RFC 6637).

Support ECDSA signature verification.

R=agl
CC=golang-dev
https://golang.org/cl/13141044
Casey Marshall 12 lat temu
rodzic
commit
da934910d6

+ 3 - 0
openpgp/packet/packet.go

@@ -377,6 +377,9 @@ const (
 	PubKeyAlgoRSASignOnly    PublicKeyAlgorithm = 3
 	PubKeyAlgoElGamal        PublicKeyAlgorithm = 16
 	PubKeyAlgoDSA            PublicKeyAlgorithm = 17
+	// RFC 6637, Section 5.
+	PubKeyAlgoECDH  PublicKeyAlgorithm = 18
+	PubKeyAlgoECDSA PublicKeyAlgorithm = 19
 )
 
 // CanEncrypt returns true if it's possible to encrypt a message to a public

+ 174 - 2
openpgp/packet/public_key.go

@@ -5,11 +5,16 @@
 package packet
 
 import (
+	"bytes"
 	"code.google.com/p/go.crypto/openpgp/elgamal"
 	"code.google.com/p/go.crypto/openpgp/errors"
 	"crypto/dsa"
+	"crypto/ecdsa"
+	"crypto/elliptic"
 	"crypto/rsa"
 	"crypto/sha1"
+	_ "crypto/sha256"
+	_ "crypto/sha512"
 	"encoding/binary"
 	"fmt"
 	"hash"
@@ -19,16 +24,143 @@ import (
 	"time"
 )
 
+var (
+	// NIST curve P-256
+	oidCurveP256 []byte = []byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}
+	// NIST curve P-384
+	oidCurveP384 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x22}
+	// NIST curve P-521
+	oidCurveP521 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x23}
+)
+
+const maxOIDLength = 8
+
+// ecdsaKey stores the algorithm-specific fields for ECDSA keys.
+// as defined in RFC 6637, Section 9.
+type ecdsaKey struct {
+	// oid contains the OID byte sequence identifying the elliptic curve used
+	oid []byte
+	// p contains the elliptic curve point that represents the public key
+	p parsedMPI
+}
+
+// parseOID reads the OID for the curve as defined in RFC 6637, Section 9.
+func parseOID(r io.Reader) (oid []byte, err error) {
+	buf := make([]byte, maxOIDLength)
+	if _, err = readFull(r, buf[:1]); err != nil {
+		return
+	}
+	oidLen := buf[0]
+	if int(oidLen) > len(buf) {
+		err = errors.UnsupportedError("invalid oid length: " + strconv.Itoa(int(oidLen)))
+		return
+	}
+	oid = buf[:oidLen]
+	_, err = readFull(r, oid)
+	return
+}
+
+func (f *ecdsaKey) parse(r io.Reader) (err error) {
+	if f.oid, err = parseOID(r); err != nil {
+		return err
+	}
+	f.p.bytes, f.p.bitLength, err = readMPI(r)
+	return
+}
+
+func (f *ecdsaKey) serialize(w io.Writer) (err error) {
+	buf := make([]byte, maxOIDLength+1)
+	buf[0] = byte(len(f.oid))
+	copy(buf[1:], f.oid)
+	if _, err = w.Write(buf[:len(f.oid)+1]); err != nil {
+		return
+	}
+	return writeMPIs(w, f.p)
+}
+
+func (f *ecdsaKey) newECDSA() (*ecdsa.PublicKey, error) {
+	var c elliptic.Curve
+	if bytes.Equal(f.oid, oidCurveP256) {
+		c = elliptic.P256()
+	} else if bytes.Equal(f.oid, oidCurveP384) {
+		c = elliptic.P384()
+	} else if bytes.Equal(f.oid, oidCurveP521) {
+		c = elliptic.P521()
+	} else {
+		return nil, errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", f.oid))
+	}
+	x, y := elliptic.Unmarshal(c, f.p.bytes)
+	if x == nil {
+		return nil, errors.UnsupportedError("failed to parse EC point")
+	}
+	return &ecdsa.PublicKey{Curve: c, X: x, Y: y}, nil
+}
+
+func (f *ecdsaKey) byteLen() int {
+	return 1 + len(f.oid) + 2 + len(f.p.bytes)
+}
+
+type kdfHashFunction byte
+type kdfAlgorithm byte
+
+// ecdhKdf stores key derivation function parameters
+// used for ECDH encryption. See RFC 6637, Section 9.
+type ecdhKdf struct {
+	KdfHash kdfHashFunction
+	KdfAlgo kdfAlgorithm
+}
+
+func (f *ecdhKdf) parse(r io.Reader) (err error) {
+	buf := make([]byte, 1)
+	if _, err = readFull(r, buf); err != nil {
+		return
+	}
+	kdfLen := int(buf[0])
+	if kdfLen < 3 {
+		return errors.UnsupportedError("Unsupported ECDH KDF length: " + strconv.Itoa(kdfLen))
+	}
+	buf = make([]byte, kdfLen)
+	if _, err = readFull(r, buf); err != nil {
+		return
+	}
+	reserved := int(buf[0])
+	f.KdfHash = kdfHashFunction(buf[1])
+	f.KdfAlgo = kdfAlgorithm(buf[2])
+	if reserved != 0x01 {
+		return errors.UnsupportedError("Unsupported KDF reserved field: " + strconv.Itoa(reserved))
+	}
+	return
+}
+
+func (f *ecdhKdf) serialize(w io.Writer) (err error) {
+	buf := make([]byte, 4)
+	// See RFC 6637, Section 9, Algorithm-Specific Fields for ECDH keys.
+	buf[0] = byte(0x03) // Length of the following fields
+	buf[1] = byte(0x01) // Reserved for future extensions, must be 1 for now
+	buf[2] = byte(f.KdfHash)
+	buf[3] = byte(f.KdfAlgo)
+	_, err = w.Write(buf[:])
+	return
+}
+
+func (f *ecdhKdf) byteLen() int {
+	return 4
+}
+
 // PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
 type PublicKey struct {
 	CreationTime time.Time
 	PubKeyAlgo   PublicKeyAlgorithm
-	PublicKey    interface{} // Either a *rsa.PublicKey or *dsa.PublicKey
+	PublicKey    interface{} // *rsa.PublicKey, *dsa.PublicKey or *ecdsa.PublicKey
 	Fingerprint  [20]byte
 	KeyId        uint64
 	IsSubkey     bool
 
 	n, e, p, q, g, y parsedMPI
+
+	// RFC 6637 fields
+	ec   *ecdsaKey
+	ecdh *ecdhKdf
 }
 
 func fromBig(n *big.Int) parsedMPI {
@@ -87,6 +219,23 @@ func (pk *PublicKey) parse(r io.Reader) (err error) {
 		err = pk.parseDSA(r)
 	case PubKeyAlgoElGamal:
 		err = pk.parseElGamal(r)
+	case PubKeyAlgoECDSA:
+		pk.ec = new(ecdsaKey)
+		if err = pk.ec.parse(r); err != nil {
+			return err
+		}
+		pk.PublicKey, err = pk.ec.newECDSA()
+	case PubKeyAlgoECDH:
+		pk.ec = new(ecdsaKey)
+		if err = pk.ec.parse(r); err != nil {
+			return
+		}
+		pk.ecdh = new(ecdhKdf)
+		if err = pk.ecdh.parse(r); err != nil {
+			return
+		}
+		// The ECDH key is stored in an ecdsa.PublicKey for convenience.
+		pk.PublicKey, err = pk.ec.newECDSA()
 	default:
 		err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
 	}
@@ -206,6 +355,11 @@ func (pk *PublicKey) SerializeSignaturePrefix(h hash.Hash) {
 		pLength += 2 + uint16(len(pk.p.bytes))
 		pLength += 2 + uint16(len(pk.g.bytes))
 		pLength += 2 + uint16(len(pk.y.bytes))
+	case PubKeyAlgoECDSA:
+		pLength += uint16(pk.ec.byteLen())
+	case PubKeyAlgoECDH:
+		pLength += uint16(pk.ec.byteLen())
+		pLength += uint16(pk.ecdh.byteLen())
 	default:
 		panic("unknown public key algorithm")
 	}
@@ -230,6 +384,11 @@ func (pk *PublicKey) Serialize(w io.Writer) (err error) {
 		length += 2 + len(pk.p.bytes)
 		length += 2 + len(pk.g.bytes)
 		length += 2 + len(pk.y.bytes)
+	case PubKeyAlgoECDSA:
+		length += pk.ec.byteLen()
+	case PubKeyAlgoECDH:
+		length += pk.ec.byteLen()
+		length += pk.ecdh.byteLen()
 	default:
 		panic("unknown public key algorithm")
 	}
@@ -269,6 +428,13 @@ func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
 		return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
 	case PubKeyAlgoElGamal:
 		return writeMPIs(w, pk.p, pk.g, pk.y)
+	case PubKeyAlgoECDSA:
+		return pk.ec.serialize(w)
+	case PubKeyAlgoECDH:
+		if err = pk.ec.serialize(w); err != nil {
+			return
+		}
+		return pk.ecdh.serialize(w)
 	}
 	return errors.InvalidArgumentError("bad public-key algorithm")
 }
@@ -315,8 +481,14 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
 			return errors.SignatureError("DSA verification failure")
 		}
 		return nil
+	case PubKeyAlgoECDSA:
+		ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey)
+		if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.bytes), new(big.Int).SetBytes(sig.ECDSASigS.bytes)) {
+			return errors.SignatureError("ECDSA verification failure")
+		}
+		return nil
 	default:
-		panic("shouldn't happen")
+		return errors.SignatureError("Unsupported public key algorithm used in signature")
 	}
 	panic("unreachable")
 }

+ 103 - 0
openpgp/packet/public_key_test.go

@@ -22,6 +22,7 @@ var pubKeyTests = []struct {
 }{
 	{rsaPkDataHex, rsaFingerprintHex, time.Unix(0x4d3c5c10, 0), PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"},
 	{dsaPkDataHex, dsaFingerprintHex, time.Unix(0x4d432f89, 0), PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"},
+	{ecdsaPkDataHex, ecdsaFingerprintHex, time.Unix(0x5071c294, 0), PubKeyAlgoECDSA, 0x43fe956c542ca00b, "43FE956C542CA00B", "542CA00B"},
 }
 
 func TestPublicKeyRead(t *testing.T) {
@@ -90,6 +91,101 @@ func TestPublicKeySerialize(t *testing.T) {
 	}
 }
 
+func TestEcc384Serialize(t *testing.T) {
+	r := readerFromHex(ecc384PubHex)
+	var w bytes.Buffer
+	for i := 0; i < 2; i++ {
+		// Public key
+		p, err := Read(r)
+		if err != nil {
+			t.Error(err)
+		}
+		pubkey := p.(*PublicKey)
+		if !bytes.Equal(pubkey.ec.oid, []byte{0x2b, 0x81, 0x04, 0x00, 0x22}) {
+			t.Errorf("Unexpected pubkey OID: %x", pubkey.ec.oid)
+		}
+		if !bytes.Equal(pubkey.ec.p.bytes[:5], []byte{0x04, 0xf6, 0xb8, 0xc5, 0xac}) {
+			t.Errorf("Unexpected pubkey P[:5]: %x", pubkey.ec.p.bytes)
+		}
+		if pubkey.KeyId != 0x098033880F54719F {
+			t.Errorf("Unexpected pubkey ID: %x", pubkey.KeyId)
+		}
+		err = pubkey.Serialize(&w)
+		if err != nil {
+			t.Error(err)
+		}
+		// User ID
+		p, err = Read(r)
+		if err != nil {
+			t.Error(err)
+		}
+		uid := p.(*UserId)
+		if uid.Id != "ec_dsa_dh_384 <openpgp@brainhub.org>" {
+			t.Error("Unexpected UID:", uid.Id)
+		}
+		err = uid.Serialize(&w)
+		if err != nil {
+			t.Error(err)
+		}
+		// User ID Sig
+		p, err = Read(r)
+		if err != nil {
+			t.Error(err)
+		}
+		uidSig := p.(*Signature)
+		err = pubkey.VerifyUserIdSignature(uid.Id, uidSig)
+		if err != nil {
+			t.Error(err, ": UID")
+		}
+		err = uidSig.Serialize(&w)
+		if err != nil {
+			t.Error(err)
+		}
+		// Subkey
+		p, err = Read(r)
+		if err != nil {
+			t.Error(err)
+		}
+		subkey := p.(*PublicKey)
+		if !bytes.Equal(subkey.ec.oid, []byte{0x2b, 0x81, 0x04, 0x00, 0x22}) {
+			t.Errorf("Unexpected subkey OID: %x", subkey.ec.oid)
+		}
+		if !bytes.Equal(subkey.ec.p.bytes[:5], []byte{0x04, 0x2f, 0xaa, 0x84, 0x02}) {
+			t.Errorf("Unexpected subkey P[:5]: %x", subkey.ec.p.bytes)
+		}
+		if subkey.ecdh.KdfHash != 0x09 {
+			t.Error("Expected KDF hash function SHA384 (0x09), got", subkey.ecdh.KdfHash)
+		}
+		if subkey.ecdh.KdfAlgo != 0x09 {
+			t.Error("Expected KDF symmetric alg AES256 (0x09), got", subkey.ecdh.KdfAlgo)
+		}
+		if subkey.KeyId != 0xAA8B938F9A201946 {
+			t.Errorf("Unexpected subkey ID: %x", subkey.KeyId)
+		}
+		err = subkey.Serialize(&w)
+		if err != nil {
+			t.Error(err)
+		}
+		// Subkey Sig
+		p, err = Read(r)
+		if err != nil {
+			t.Error(err)
+		}
+		subkeySig := p.(*Signature)
+		err = pubkey.VerifyKeySignature(subkey, subkeySig)
+		if err != nil {
+			t.Error(err)
+		}
+		err = subkeySig.Serialize(&w)
+		if err != nil {
+			t.Error(err)
+		}
+		// Now read back what we've written again
+		r = bytes.NewBuffer(w.Bytes())
+		w.Reset()
+	}
+}
+
 const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb"
 
 const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001"
@@ -97,3 +193,10 @@ const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f94
 const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed"
 
 const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0"
+
+const ecdsaFingerprintHex = "9892270b38b8980b05c8d56d43fe956c542ca00b"
+
+const ecdsaPkDataHex = "9893045071c29413052b8104002304230401f4867769cedfa52c325018896245443968e52e51d0c2df8d939949cb5b330f2921711fbee1c9b9dddb95d15cb0255e99badeddda7cc23d9ddcaacbc290969b9f24019375d61c2e4e3b36953a28d8b2bc95f78c3f1d592fb24499be348656a7b17e3963187b4361afe497bc5f9f81213f04069f8e1fb9e6a6290ae295ca1a92b894396cb4"
+
+// Source: https://sites.google.com/site/brainhub/pgpecckeys#TOC-ECC-NIST-P-384-key
+const ecc384PubHex = `99006f044d53059213052b81040022030304f6b8c5aced5b84ef9f4a209db2e4a9dfb70d28cb8c10ecd57674a9fa5a67389942b62d5e51367df4c7bfd3f8e500feecf07ed265a621a8ebbbe53e947ec78c677eba143bd1533c2b350e1c29f82313e1e1108eba063be1e64b10e6950e799c2db42465635f6473615f64685f333834203c6f70656e70677040627261696e6875622e6f72673e8900cb04101309005305024d530592301480000000002000077072656665727265642d656d61696c2d656e636f64696e67407067702e636f6d7067706d696d65040b090807021901051b03000000021602051e010000000415090a08000a0910098033880f54719fca2b0180aa37350968bd5f115afd8ce7bc7b103822152dbff06d0afcda835329510905b98cb469ba208faab87c7412b799e7b633017f58364ea480e8a1a3f253a0c5f22c446e8be9a9fce6210136ee30811abbd49139de28b5bdf8dc36d06ae748579e9ff503b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec9180301090989008404181309000c05024d530592051b0c000000000a0910098033880f54719f80970180eee7a6d8fcee41ee4f9289df17f9bcf9d955dca25c583b94336f3a2b2d4986dc5cf417b8d2dc86f741a9e1a6d236c0e3017d1c76575458a0cfb93ae8a2b274fcc65ceecd7a91eec83656ba13219969f06945b48c56bd04152c3a0553c5f2f4bd1267`

+ 15 - 4
openpgp/packet/signature.go

@@ -30,8 +30,9 @@ type Signature struct {
 	HashTag      [2]byte
 	CreationTime time.Time
 
-	RSASignature     parsedMPI
-	DSASigR, DSASigS parsedMPI
+	RSASignature         parsedMPI
+	DSASigR, DSASigS     parsedMPI
+	ECDSASigR, ECDSASigS parsedMPI
 
 	// rawSubpackets contains the unparsed subpackets, in order.
 	rawSubpackets []outputSubpacket
@@ -71,7 +72,7 @@ func (sig *Signature) parse(r io.Reader) (err error) {
 	sig.SigType = SignatureType(buf[0])
 	sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1])
 	switch sig.PubKeyAlgo {
-	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA:
 	default:
 		err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
 		return
@@ -135,6 +136,11 @@ func (sig *Signature) parse(r io.Reader) (err error) {
 		if err == nil {
 			sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
 		}
+	case PubKeyAlgoECDSA:
+		sig.ECDSASigR.bytes, sig.ECDSASigR.bitLength, err = readMPI(r)
+		if err == nil {
+			sig.ECDSASigS.bytes, sig.ECDSASigS.bitLength, err = readMPI(r)
+		}
 	default:
 		panic("unreachable")
 	}
@@ -499,7 +505,7 @@ func (sig *Signature) Serialize(w io.Writer) (err error) {
 	if len(sig.outSubpackets) == 0 {
 		sig.outSubpackets = sig.rawSubpackets
 	}
-	if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
+	if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil && sig.ECDSASigR.bytes == nil {
 		return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize")
 	}
 
@@ -510,6 +516,9 @@ func (sig *Signature) Serialize(w io.Writer) (err error) {
 	case PubKeyAlgoDSA:
 		sigLength = 2 + len(sig.DSASigR.bytes)
 		sigLength += 2 + len(sig.DSASigS.bytes)
+	case PubKeyAlgoECDSA:
+		sigLength = 2 + len(sig.ECDSASigR.bytes)
+		sigLength += 2 + len(sig.ECDSASigS.bytes)
 	default:
 		panic("impossible")
 	}
@@ -547,6 +556,8 @@ func (sig *Signature) Serialize(w io.Writer) (err error) {
 		err = writeMPIs(w, sig.RSASignature)
 	case PubKeyAlgoDSA:
 		err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
+	case PubKeyAlgoECDSA:
+		err = writeMPIs(w, sig.ECDSASigR, sig.ECDSASigS)
 	default:
 		panic("impossible")
 	}