Browse Source

crypto/ssh: Support turning a crypto.Signer into an ssh.Signer

This adds a NewSignerFromSigner to crypto/ssh which takes a
crypto.Signer and turns it into an ssh.Signer, helpful if, e.g., your
crypto.Signer is backed by some sort of hardware device.

The interfaces are very similar - the biggest differences are that a
crypto.Signer accepts hashed data, while an ssh.Signer does not, and
some differences in encoding for DSA and ECDSA signatures.

This also adjusts NewSignerFromKey to use NewSignerFromSigner where
possible, dropping the rsaPrivateKey and ecdsaPrivateKey types in
favor of wrappedSigner. (However, because *dsa.PrivateKey is not a
crypto.Signer, we still have to keep dsaPrivateKey)

Change-Id: Ia2e20ece9c9d3844b4e5a64c1a7d997178ec8781
Reviewed-on: https://go-review.googlesource.com/10953
Reviewed-by: Adam Langley <agl@golang.org>
Evan Broder 10 years ago
parent
commit
e74b0352e5
2 changed files with 89 additions and 71 deletions
  1. 85 67
      ssh/keys.go
  2. 4 4
      ssh/keys_test.go

+ 85 - 67
ssh/keys.go

@@ -267,28 +267,6 @@ func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
 	return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
 }
 
-type rsaPrivateKey struct {
-	*rsa.PrivateKey
-}
-
-func (r *rsaPrivateKey) PublicKey() PublicKey {
-	return (*rsaPublicKey)(&r.PrivateKey.PublicKey)
-}
-
-func (r *rsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
-	h := crypto.SHA1.New()
-	h.Write(data)
-	digest := h.Sum(nil)
-	blob, err := rsa.SignPKCS1v15(rand, r.PrivateKey, crypto.SHA1, digest)
-	if err != nil {
-		return nil, err
-	}
-	return &Signature{
-		Format: r.PublicKey().Type(),
-		Blob:   blob,
-	}, nil
-}
-
 type dsaPublicKey dsa.PublicKey
 
 func (r *dsaPublicKey) Type() string {
@@ -496,72 +474,112 @@ func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
 	return errors.New("ssh: signature did not verify")
 }
 
-type ecdsaPrivateKey struct {
-	*ecdsa.PrivateKey
+// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
+// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
+// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
+func NewSignerFromKey(key interface{}) (Signer, error) {
+	switch key := key.(type) {
+	case crypto.Signer:
+		return NewSignerFromSigner(key)
+	case *dsa.PrivateKey:
+		return &dsaPrivateKey{key}, nil
+	default:
+		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
+	}
 }
 
-func (k *ecdsaPrivateKey) PublicKey() PublicKey {
-	return (*ecdsaPublicKey)(&k.PrivateKey.PublicKey)
+type wrappedSigner struct {
+	signer crypto.Signer
+	pubKey PublicKey
 }
 
-func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
-	h := ecHash(k.PrivateKey.PublicKey.Curve).New()
-	h.Write(data)
-	digest := h.Sum(nil)
-	r, s, err := ecdsa.Sign(rand, k.PrivateKey, digest)
+// NewSignerFromSigner takes any crypto.Signer implementation and
+// returns a corresponding Signer interface. This can be used, for
+// example, with keys kept in hardware modules.
+func NewSignerFromSigner(signer crypto.Signer) (Signer, error) {
+	pubKey, err := NewPublicKey(signer.Public())
 	if err != nil {
 		return nil, err
 	}
 
-	sig := make([]byte, intLength(r)+intLength(s))
-	rest := marshalInt(sig, r)
-	marshalInt(rest, s)
-	return &Signature{
-		Format: k.PublicKey().Type(),
-		Blob:   sig,
-	}, nil
+	return &wrappedSigner{signer, pubKey}, nil
 }
 
-// NewSignerFromKey takes a pointer to rsa, dsa or ecdsa PrivateKey
-// returns a corresponding Signer instance. EC keys should use P256,
-// P384 or P521.
-func NewSignerFromKey(k interface{}) (Signer, error) {
-	var sshKey Signer
-	switch t := k.(type) {
-	case *rsa.PrivateKey:
-		sshKey = &rsaPrivateKey{t}
-	case *dsa.PrivateKey:
-		sshKey = &dsaPrivateKey{t}
-	case *ecdsa.PrivateKey:
-		if !supportedEllipticCurve(t.Curve) {
-			return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
-		}
+func (s *wrappedSigner) PublicKey() PublicKey {
+	return s.pubKey
+}
 
-		sshKey = &ecdsaPrivateKey{t}
+func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
+	var hashFunc crypto.Hash
+
+	switch key := s.pubKey.(type) {
+	case *rsaPublicKey, *dsaPublicKey:
+		hashFunc = crypto.SHA1
+	case *ecdsaPublicKey:
+		hashFunc = ecHash(key.Curve)
 	default:
-		return nil, fmt.Errorf("ssh: unsupported key type %T", k)
+		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
+	}
+
+	h := hashFunc.New()
+	h.Write(data)
+	digest := h.Sum(nil)
+
+	signature, err := s.signer.Sign(rand, digest, hashFunc)
+	if err != nil {
+		return nil, err
 	}
-	return sshKey, nil
+
+	// crypto.Signer.Sign is expected to return an ASN.1-encoded signature
+	// for ECDSA and DSA, but that's not the encoding expected by SSH, so
+	// re-encode.
+	switch s.pubKey.(type) {
+	case *ecdsaPublicKey, *dsaPublicKey:
+		type asn1Signature struct {
+			R, S *big.Int
+		}
+		asn1Sig := new(asn1Signature)
+		_, err := asn1.Unmarshal(signature, asn1Sig)
+		if err != nil {
+			return nil, err
+		}
+
+		switch s.pubKey.(type) {
+		case *ecdsaPublicKey:
+			signature = Marshal(asn1Sig)
+
+		case *dsaPublicKey:
+			signature = make([]byte, 40)
+			r := asn1Sig.R.Bytes()
+			s := asn1Sig.S.Bytes()
+			copy(signature[20-len(r):20], r)
+			copy(signature[40-len(s):40], s)
+		}
+	}
+
+	return &Signature{
+		Format: s.pubKey.Type(),
+		Blob:   signature,
+	}, nil
 }
 
-// NewPublicKey takes a pointer to rsa, dsa or ecdsa PublicKey
-// and returns a corresponding ssh PublicKey instance. EC keys should use P256, P384 or P521.
-func NewPublicKey(k interface{}) (PublicKey, error) {
-	var sshKey PublicKey
-	switch t := k.(type) {
+// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or
+// any other crypto.Signer and returns a corresponding Signer instance. ECDSA
+// keys must use P-256, P-384 or P-521.
+func NewPublicKey(key interface{}) (PublicKey, error) {
+	switch key := key.(type) {
 	case *rsa.PublicKey:
-		sshKey = (*rsaPublicKey)(t)
+		return (*rsaPublicKey)(key), nil
 	case *ecdsa.PublicKey:
-		if !supportedEllipticCurve(t.Curve) {
-			return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
+		if !supportedEllipticCurve(key.Curve) {
+			return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.")
 		}
-		sshKey = (*ecdsaPublicKey)(t)
+		return (*ecdsaPublicKey)(key), nil
 	case *dsa.PublicKey:
-		sshKey = (*dsaPublicKey)(t)
+		return (*dsaPublicKey)(key), nil
 	default:
-		return nil, fmt.Errorf("ssh: unsupported key type %T", k)
+		return nil, fmt.Errorf("ssh: unsupported key type %T", key)
 	}
-	return sshKey, nil
 }
 
 // ParsePrivateKey returns a Signer from a PEM encoded private key. It supports

+ 4 - 4
ssh/keys_test.go

@@ -57,12 +57,12 @@ func TestUnsupportedCurves(t *testing.T) {
 		t.Fatalf("GenerateKey: %v", err)
 	}
 
-	if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P256") {
-		t.Fatalf("NewPrivateKey should not succeed with P224, got: %v", err)
+	if _, err = NewSignerFromKey(raw); err == nil || !strings.Contains(err.Error(), "only P-256") {
+		t.Fatalf("NewPrivateKey should not succeed with P-224, got: %v", err)
 	}
 
-	if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P256") {
-		t.Fatalf("NewPublicKey should not succeed with P224, got: %v", err)
+	if _, err = NewPublicKey(&raw.PublicKey); err == nil || !strings.Contains(err.Error(), "only P-256") {
+		t.Fatalf("NewPublicKey should not succeed with P-224, got: %v", err)
 	}
 }