Selaa lähdekoodia

openpgp: ECDSA key and signature support

Add support for creating and signing with ECDSA OpenPGP keys

Change-Id: I6d530325ab801ba800187b88e8cfea079cfa41ed
Reviewed-on: https://go-review.googlesource.com/20608
Run-TryBot: Adam Langley <agl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jacob H. Haven <jacob@jhaven.me>
Reviewed-by: Adam Langley <agl@golang.org>
Jacob H. Haven 9 vuotta sitten
vanhempi
commit
6025851c7c

+ 39 - 3
openpgp/packet/private_key.go

@@ -8,16 +8,18 @@ import (
 	"bytes"
 	"crypto/cipher"
 	"crypto/dsa"
+	"crypto/ecdsa"
 	"crypto/rsa"
 	"crypto/sha1"
-	"golang.org/x/crypto/openpgp/elgamal"
-	"golang.org/x/crypto/openpgp/errors"
-	"golang.org/x/crypto/openpgp/s2k"
 	"io"
 	"io/ioutil"
 	"math/big"
 	"strconv"
 	"time"
+
+	"golang.org/x/crypto/openpgp/elgamal"
+	"golang.org/x/crypto/openpgp/errors"
+	"golang.org/x/crypto/openpgp/s2k"
 )
 
 // PrivateKey represents a possibly encrypted private key. See RFC 4880,
@@ -54,6 +56,13 @@ func NewElGamalPrivateKey(currentTime time.Time, priv *elgamal.PrivateKey) *Priv
 	return pk
 }
 
+func NewECDSAPrivateKey(currentTime time.Time, priv *ecdsa.PrivateKey) *PrivateKey {
+	pk := new(PrivateKey)
+	pk.PublicKey = *NewECDSAPublicKey(currentTime, &priv.PublicKey)
+	pk.PrivateKey = priv
+	return pk
+}
+
 func (pk *PrivateKey) parse(r io.Reader) (err error) {
 	err = (&pk.PublicKey).parse(r)
 	if err != nil {
@@ -139,6 +148,8 @@ func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
 		err = serializeDSAPrivateKey(privateKeyBuf, priv)
 	case *elgamal.PrivateKey:
 		err = serializeElGamalPrivateKey(privateKeyBuf, priv)
+	case *ecdsa.PrivateKey:
+		err = serializeECDSAPrivateKey(privateKeyBuf, priv)
 	default:
 		err = errors.InvalidArgumentError("unknown private key type")
 	}
@@ -198,6 +209,10 @@ func serializeElGamalPrivateKey(w io.Writer, priv *elgamal.PrivateKey) error {
 	return writeBig(w, priv.X)
 }
 
+func serializeECDSAPrivateKey(w io.Writer, priv *ecdsa.PrivateKey) error {
+	return writeBig(w, priv.D)
+}
+
 // Decrypt decrypts an encrypted private key using a passphrase.
 func (pk *PrivateKey) Decrypt(passphrase []byte) error {
 	if !pk.Encrypted {
@@ -249,6 +264,8 @@ func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
 		return pk.parseDSAPrivateKey(data)
 	case PubKeyAlgoElGamal:
 		return pk.parseElGamalPrivateKey(data)
+	case PubKeyAlgoECDSA:
+		return pk.parseECDSAPrivateKey(data)
 	}
 	panic("impossible")
 }
@@ -324,3 +341,22 @@ func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
 
 	return nil
 }
+
+func (pk *PrivateKey) parseECDSAPrivateKey(data []byte) (err error) {
+	ecdsaPub := pk.PublicKey.PublicKey.(*ecdsa.PublicKey)
+
+	buf := bytes.NewBuffer(data)
+	d, _, err := readMPI(buf)
+	if err != nil {
+		return
+	}
+
+	pk.PrivateKey = &ecdsa.PrivateKey{
+		PublicKey: *ecdsaPub,
+		D:         new(big.Int).SetBytes(d),
+	}
+	pk.Encrypted = false
+	pk.encryptedData = nil
+
+	return nil
+}

+ 57 - 0
openpgp/packet/private_key_test.go

@@ -5,6 +5,12 @@
 package packet
 
 import (
+	"bytes"
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"hash"
 	"testing"
 	"time"
 )
@@ -56,6 +62,57 @@ func TestPrivateKeyRead(t *testing.T) {
 	}
 }
 
+func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) {
+	h := hashFunc.New()
+	if _, err := h.Write(msg); err != nil {
+		return nil, err
+	}
+	return h, nil
+}
+
+func TestECDSAPrivateKey(t *testing.T) {
+	ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var buf bytes.Buffer
+	if err := NewECDSAPrivateKey(time.Now(), ecdsaPriv).Serialize(&buf); err != nil {
+		t.Fatal(err)
+	}
+
+	p, err := Read(&buf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	priv, ok := p.(*PrivateKey)
+	if !ok {
+		t.Fatal("didn't parse private key")
+	}
+
+	sig := &Signature{
+		PubKeyAlgo: PubKeyAlgoECDSA,
+		Hash:       crypto.SHA256,
+	}
+	msg := []byte("Hello World!")
+
+	h, err := populateHash(sig.Hash, msg)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := sig.Sign(h, priv, nil); err != nil {
+		t.Fatal(err)
+	}
+
+	if h, err = populateHash(sig.Hash, msg); err != nil {
+		t.Fatal(err)
+	}
+	if err := priv.VerifySignature(h, sig); err != nil {
+		t.Fatal(err)
+	}
+}
+
 func TestIssue11505(t *testing.T) {
 	// parsing a rsa private key with p or q == 1 used to panic due to a divide by zero
 	_, _ = Read(readerFromHex("9c3004303030300100000011303030000000000000010130303030303030303030303030303030303030303030303030303030303030303030303030303030303030"))

+ 26 - 0
openpgp/packet/public_key.go

@@ -224,6 +224,32 @@ func NewElGamalPublicKey(creationTime time.Time, pub *elgamal.PublicKey) *Public
 	return pk
 }
 
+func NewECDSAPublicKey(creationTime time.Time, pub *ecdsa.PublicKey) *PublicKey {
+	pk := &PublicKey{
+		CreationTime: creationTime,
+		PubKeyAlgo:   PubKeyAlgoECDSA,
+		PublicKey:    pub,
+		ec:           new(ecdsaKey),
+	}
+
+	switch pub.Curve {
+	case elliptic.P256():
+		pk.ec.oid = oidCurveP256
+	case elliptic.P384():
+		pk.ec.oid = oidCurveP384
+	case elliptic.P521():
+		pk.ec.oid = oidCurveP521
+	default:
+		panic("unknown elliptic curve")
+	}
+
+	pk.ec.p.bytes = elliptic.Marshal(pub.Curve, pub.X, pub.Y)
+	pk.ec.p.bitLength = uint16(8 * len(pk.ec.p.bytes))
+
+	pk.setFingerPrintAndKeyId()
+	return pk
+}
+
 func (pk *PublicKey) parse(r io.Reader) (err error) {
 	// RFC 4880, section 5.5.2
 	var buf [6]byte

+ 7 - 0
openpgp/packet/signature.go

@@ -8,6 +8,7 @@ import (
 	"bytes"
 	"crypto"
 	"crypto/dsa"
+	"crypto/ecdsa"
 	"crypto/rsa"
 	"encoding/binary"
 	"hash"
@@ -532,6 +533,12 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
 			sig.DSASigS.bytes = s.Bytes()
 			sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
 		}
+	case PubKeyAlgoECDSA:
+		r, s, err := ecdsa.Sign(config.Random(), priv.PrivateKey.(*ecdsa.PrivateKey), digest)
+		if err == nil {
+			sig.ECDSASigR = fromBig(r)
+			sig.ECDSASigS = fromBig(s)
+		}
 	default:
 		err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
 	}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 19 - 0
openpgp/read_test.go


+ 14 - 0
openpgp/write_test.go

@@ -50,6 +50,20 @@ func TestSignDetachedDSA(t *testing.T) {
 	testDetachedSignature(t, kring, out, signedInput, "check", testKey3KeyId)
 }
 
+func TestSignDetachedP256(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(p256TestKeyPrivateHex))
+	kring[0].PrivateKey.Decrypt([]byte("passphrase"))
+
+	out := bytes.NewBuffer(nil)
+	message := bytes.NewBufferString(signedInput)
+	err := DetachSign(out, kring[0], message, nil)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testDetachedSignature(t, kring, out, signedInput, "check", testKeyP256KeyId)
+}
+
 func TestNewEntity(t *testing.T) {
 	if testing.Short() {
 		return

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä