فهرست منبع

x/crypto/openpgp: support crypto.Signer private keys

This adds support for crypto.Signer-based RSA and ECDSA private keys.
This enables using opaque signing keys as explained in the documentation
for crypto.Signer.

The support is in the form of a new NewSignerPrivateKey function which
makes a PrivateKey from a crypto.Signer.

Fixes golang/go#15841.

Change-Id: Ice2ec2793a9f5409a5bfd4e5e49d919e14ede1e0
Reviewed-on: https://go-review.googlesource.com/23802
Run-TryBot: Adam Langley <agl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Adam Langley <agl@golang.org>
Samuele Pedroni 9 سال پیش
والد
کامیت
00c1479577
3فایلهای تغییر یافته به همراه191 افزوده شده و 4 حذف شده
  1. 19 1
      openpgp/packet/private_key.go
  2. 144 0
      openpgp/packet/private_key_test.go
  3. 28 3
      openpgp/packet/signature.go

+ 19 - 1
openpgp/packet/private_key.go

@@ -6,6 +6,7 @@ package packet
 
 import (
 	"bytes"
+	"crypto"
 	"crypto/cipher"
 	"crypto/dsa"
 	"crypto/ecdsa"
@@ -30,7 +31,7 @@ type PrivateKey struct {
 	encryptedData []byte
 	cipher        CipherFunction
 	s2k           func(out, in []byte)
-	PrivateKey    interface{} // An *rsa.PrivateKey or *dsa.PrivateKey.
+	PrivateKey    interface{} // An *{rsa|dsa|ecdsa}.PrivateKey or a crypto.Signer.
 	sha1Checksum  bool
 	iv            []byte
 }
@@ -63,6 +64,23 @@ func NewECDSAPrivateKey(currentTime time.Time, priv *ecdsa.PrivateKey) *PrivateK
 	return pk
 }
 
+// NewSignerPrivateKey creates a sign-only PrivateKey from a crypto.Signer that
+// implements RSA or ECDSA.
+func NewSignerPrivateKey(currentTime time.Time, signer crypto.Signer) *PrivateKey {
+	pk := new(PrivateKey)
+	switch pubkey := signer.Public().(type) {
+	case rsa.PublicKey:
+		pk.PublicKey = *NewRSAPublicKey(currentTime, &pubkey)
+		pk.PubKeyAlgo = PubKeyAlgoRSASignOnly
+	case ecdsa.PublicKey:
+		pk.PublicKey = *NewECDSAPublicKey(currentTime, &pubkey)
+	default:
+		panic("openpgp: unknown crypto.Signer type in NewSignerPrivateKey")
+	}
+	pk.PrivateKey = signer
+	return pk
+}
+
 func (pk *PrivateKey) parse(r io.Reader) (err error) {
 	err = (&pk.PublicKey).parse(r)
 	if err != nil {

+ 144 - 0
openpgp/packet/private_key_test.go

@@ -10,7 +10,11 @@ import (
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/hex"
 	"hash"
+	"io"
 	"testing"
 	"time"
 )
@@ -70,6 +74,50 @@ func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) {
 	return h, nil
 }
 
+func TestRSAPrivateKey(t *testing.T) {
+	privKeyDER, _ := hex.DecodeString(pkcs1PrivKeyHex)
+	rsaPriv, err := x509.ParsePKCS1PrivateKey(privKeyDER)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var buf bytes.Buffer
+	if err := NewRSAPrivateKey(time.Now(), rsaPriv).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: PubKeyAlgoRSA,
+		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 TestECDSAPrivateKey(t *testing.T) {
 	ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	if err != nil {
@@ -113,6 +161,98 @@ func TestECDSAPrivateKey(t *testing.T) {
 	}
 }
 
+type rsaSigner struct {
+	priv *rsa.PrivateKey
+}
+
+func (s *rsaSigner) Public() crypto.PublicKey {
+	return s.priv.PublicKey
+}
+
+func (s *rsaSigner) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
+	return s.priv.Sign(rand, msg, opts)
+}
+
+func TestRSASignerPrivateKey(t *testing.T) {
+	rsaPriv, err := rsa.GenerateKey(rand.Reader, 1024)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	priv := NewSignerPrivateKey(time.Now(), &rsaSigner{rsaPriv})
+
+	if priv.PubKeyAlgo != PubKeyAlgoRSASignOnly {
+		t.Fatal("NewSignerPrivateKey should have made a sign-only RSA private key")
+	}
+
+	sig := &Signature{
+		PubKeyAlgo: PubKeyAlgoRSASignOnly,
+		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)
+	}
+}
+
+type ecdsaSigner struct {
+	priv *ecdsa.PrivateKey
+}
+
+func (s *ecdsaSigner) Public() crypto.PublicKey {
+	return s.priv.PublicKey
+}
+
+func (s *ecdsaSigner) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
+	return s.priv.Sign(rand, msg, opts)
+}
+
+func TestECDSASignerPrivateKey(t *testing.T) {
+	ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	priv := NewSignerPrivateKey(time.Now(), &ecdsaSigner{ecdsaPriv})
+
+	if priv.PubKeyAlgo != PubKeyAlgoECDSA {
+		t.Fatal("NewSignerPrivateKey should have made a ECSDA 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"))
@@ -124,3 +264,7 @@ const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5
 // Generated by `gpg --export-secret-keys` followed by a manual extraction of
 // the ElGamal subkey from the packets.
 const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc"
+
+// pkcs1PrivKeyHex is a PKCS#1, RSA private key.
+// Generated by `openssl genrsa 1024 | openssl rsa -outform DER  | xxd -p`
+const pkcs1PrivKeyHex = "3082025d02010002818100e98edfa1c3b35884a54d0b36a6a603b0290fa85e49e30fa23fc94fef9c6790bc4849928607aa48d809da326fb42a969d06ad756b98b9c1a90f5d4a2b6d0ac05953c97f4da3120164a21a679793ce181c906dc01d235cc085ddcdf6ea06c389b6ab8885dfd685959e693138856a68a7e5db263337ff82a088d583a897cf2d59e9020301000102818100b6d5c9eb70b02d5369b3ee5b520a14490b5bde8a317d36f7e4c74b7460141311d1e5067735f8f01d6f5908b2b96fbd881f7a1ab9a84d82753e39e19e2d36856be960d05ac9ef8e8782ea1b6d65aee28fdfe1d61451e8cff0adfe84322f12cf455028b581cf60eb9e0e140ba5d21aeba6c2634d7c65318b9a665fc01c3191ca21024100fa5e818da3705b0fa33278bb28d4b6f6050388af2d4b75ec9375dd91ccf2e7d7068086a8b82a8f6282e4fbbdb8a7f2622eb97295249d87acea7f5f816f54d347024100eecf9406d7dc49cdfb95ab1eff4064de84c7a30f64b2798936a0d2018ba9eb52e4b636f82e96c49cc63b80b675e91e40d1b2e4017d4b9adaf33ab3d9cf1c214f024100c173704ace742c082323066226a4655226819a85304c542b9dacbeacbf5d1881ee863485fcf6f59f3a604f9b42289282067447f2b13dfeed3eab7851fc81e0550240741fc41f3fc002b382eed8730e33c5d8de40256e4accee846667f536832f711ab1d4590e7db91a8a116ac5bff3be13d3f9243ff2e976662aa9b395d907f8e9c9024046a5696c9ef882363e06c9fa4e2f5b580906452befba03f4a99d0f873697ef1f851d2226ca7934b30b7c3e80cb634a67172bbbf4781735fe3e09263e2dd723e7"

+ 28 - 3
openpgp/packet/signature.go

@@ -9,10 +9,11 @@ import (
 	"crypto"
 	"crypto/dsa"
 	"crypto/ecdsa"
-	"crypto/rsa"
+	"encoding/asn1"
 	"encoding/binary"
 	"hash"
 	"io"
+	"math/big"
 	"strconv"
 	"time"
 
@@ -516,7 +517,8 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
 
 	switch priv.PubKeyAlgo {
 	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
-		sig.RSASignature.bytes, err = rsa.SignPKCS1v15(config.Random(), priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
+		// supports both *rsa.PrivateKey and crypto.Signer
+		sig.RSASignature.bytes, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, sig.Hash)
 		sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
 	case PubKeyAlgoDSA:
 		dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
@@ -534,7 +536,17 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
 			sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
 		}
 	case PubKeyAlgoECDSA:
-		r, s, err := ecdsa.Sign(config.Random(), priv.PrivateKey.(*ecdsa.PrivateKey), digest)
+		var r, s *big.Int
+		if pk, ok := priv.PrivateKey.(*ecdsa.PrivateKey); ok {
+			// direct support, avoid asn1 wrapping/unwrapping
+			r, s, err = ecdsa.Sign(config.Random(), pk, digest)
+		} else {
+			var b []byte
+			b, err = priv.PrivateKey.(crypto.Signer).Sign(config.Random(), digest, nil)
+			if err == nil {
+				r, s, err = unwrapECDSASig(b)
+			}
+		}
 		if err == nil {
 			sig.ECDSASigR = fromBig(r)
 			sig.ECDSASigS = fromBig(s)
@@ -546,6 +558,19 @@ func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err e
 	return
 }
 
+// unwrapECDSASig parses the two integer components of an ASN.1-encoded ECDSA
+// signature.
+func unwrapECDSASig(b []byte) (r, s *big.Int, err error) {
+	var ecsdaSig struct {
+		R, S *big.Int
+	}
+	_, err = asn1.Unmarshal(b, &ecsdaSig)
+	if err != nil {
+		return
+	}
+	return ecsdaSig.R, ecsdaSig.S, nil
+}
+
 // SignUserId computes a signature from priv, asserting that pub is a valid
 // key for the identity id.  On success, the signature is stored in sig. Call
 // Serialize to write it out.