Skip to content
This repository has been archived by the owner on Feb 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4 from mozilla-services/signdataandhashes
Browse files Browse the repository at this point in the history
Expose Hash, Sign, VerifySignature and VerifyHashSignature
  • Loading branch information
jvehent authored Jun 22, 2018
2 parents a2fdb2d + 838b2b0 commit debf6b0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 84 deletions.
94 changes: 57 additions & 37 deletions sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"crypto/sha512"
"encoding/asn1"
"encoding/base64"
"fmt"
"hash"
"io"
"math"
"math/big"
)
Expand All @@ -34,7 +36,7 @@ const (
// PrepareSignature adds a new signature header to a MAR file
// but does not sign yet. You have to call FinalizeSignature
// to actually sign the MAR file.
func (file *File) PrepareSignature(key crypto.PrivateKey, pubkey crypto.PublicKey) {
func (file *File) PrepareSignature(key crypto.PrivateKey, pubkey crypto.PublicKey) error {
var sig Signature
switch pubkey.(type) {
case *rsa.PublicKey:
Expand All @@ -46,15 +48,15 @@ func (file *File) PrepareSignature(key crypto.PrivateKey, pubkey crypto.PublicKe
case *ecdsa.PublicKey:
sig.AlgorithmID, sig.Size = getEcdsaInfo(pubkey.(*ecdsa.PublicKey).Params().Name)
if sig.AlgorithmID == 0 || sig.Size == 0 {
panic("unsupported key type")
return fmt.Errorf("invalid ecdsa algorithm id %d size %d", sig.AlgorithmID, sig.Size)
}
default:
panic("unsupported key type")
return fmt.Errorf("unsupported key type %T", pubkey)
}
sig.privateKey = key
file.Signatures = append(file.Signatures, sig)
file.SignaturesHeader.NumSignatures++
return
return nil
}

// FinalizeSignatures calculates RSA signatures on a MAR file
Expand All @@ -64,44 +66,19 @@ func (file *File) FinalizeSignatures() error {
if err != nil {
return err
}
if len(file.Signatures) == 0 {
return fmt.Errorf("there are no signatures to finalize")
}
for i := range file.Signatures {
// hash the signature block using the appropriate algorithm
var md hash.Hash
h := crypto.SHA384
switch file.Signatures[i].AlgorithmID {
case SigAlgRsaPkcs1Sha1:
md = sha1.New()
h = crypto.SHA1
case SigAlgEcdsaP256Sha256:
md = sha256.New()
h = crypto.SHA256
case SigAlgRsaPkcs1Sha384, SigAlgEcdsaP384Sha384:
md = sha512.New384()
}
md.Write(signableBlock)

// call the signer interface of the private key to sign the hash
sigData, err := file.Signatures[i].privateKey.(crypto.Signer).Sign(
rand.Reader, md.Sum(nil), h)
hashed, hashAlg, err := Hash(signableBlock, file.Signatures[i].AlgorithmID)
if err != nil {
return err
}

// write the signature into the mar file
switch file.Signatures[i].privateKey.(type) {
case *ecdsa.PrivateKey:
// when using an ecdsa key, the Sign() interface returns an ASN.1 encoded signature
// which we need to parse and convert to its R||S form
sigData, err := convertAsn1EcdsaToRS(sigData, int(file.Signatures[i].Size))
if err != nil {
return err
}
file.Signatures[i].Data = append(file.Signatures[i].Data, sigData...)
case *rsa.PrivateKey:
// when using an rsa key, the Sign() interface returns a PKCS1 v1.5 signature that
// we directly insert into the MAR file.
file.Signatures[i].Data = append(file.Signatures[i].Data, sigData...)
sigData, err := Sign(file.Signatures[i].privateKey, rand.Reader, hashed, hashAlg)
if err != nil {
return err
}
file.Signatures[i].Data = append(file.Signatures[i].Data, sigData...)
}
return nil
}
Expand All @@ -112,6 +89,49 @@ func (file *File) MarshalForSignature() ([]byte, error) {
return file.Marshal()
}

// Hash takes an input and a signature algorithm and returns its hashed value
func Hash(input []byte, sigalg uint32) (output []byte, h crypto.Hash, err error) {
// hash the signature block using the appropriate algorithm
var md hash.Hash
switch sigalg {
case SigAlgRsaPkcs1Sha1:
md = sha1.New()
h = crypto.SHA1
case SigAlgEcdsaP256Sha256:
md = sha256.New()
h = crypto.SHA256
case SigAlgRsaPkcs1Sha384, SigAlgEcdsaP384Sha384:
md = sha512.New384()
h = crypto.SHA384
default:
return nil, h, fmt.Errorf("unsupported signature algorithm")
}
md.Write(input)
return md.Sum(nil), h, nil
}

// Sign signs digest with the private key, possibly using entropy from rand
func Sign(key crypto.PrivateKey, rand io.Reader, digest []byte, h crypto.Hash) (sigData []byte, err error) {
if _, ok := key.(crypto.Signer); !ok {
return nil, fmt.Errorf("private key of type %T does not implement the Signer interface", key)
}
// call the signer interface of the private key to sign the hash
sigData, err = key.(crypto.Signer).Sign(rand, digest, h)
if err != nil {
return nil, err
}
switch key.(type) {
case *ecdsa.PrivateKey:
// when using an ecdsa key, the Sign() interface returns an ASN.1 encoded signature
// which we need to parse and convert to its R||S form
_, size := getEcdsaInfo(key.(*ecdsa.PrivateKey).Params().Name)
return convertAsn1EcdsaToRS(sigData, int(size))
case *rsa.PrivateKey:
return sigData, nil
}
return nil, fmt.Errorf("unsupported key type %T", key)
}

type ecdsaSignature struct {
R, S *big.Int
}
Expand Down
83 changes: 36 additions & 47 deletions verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,44 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"hash"
"math/big"
)

// VerifySignature takes a signed block, a signature, an algorithm id and a public key and returns
// nil if the signature verifies, or an error if it does not
func VerifySignature(input []byte, signature []byte, sigalg uint32, key crypto.PublicKey) error {
digest, hashAlg, err := Hash(input, sigalg)
if err != nil {
return err
}
return VerifyHashSignature(signature, digest, hashAlg, key)
}

// VerifyHashSignature takes a signature, the digest of a signed MAR block, a hash algorithm and a public
// key and returns nil if a valid signature is found, or an error if it isn't
func VerifyHashSignature(signature []byte, digest []byte, hashAlg crypto.Hash, key crypto.PublicKey) error {
switch key.(type) {
case *rsa.PublicKey:
err := rsa.VerifyPKCS1v15(key.(*rsa.PublicKey), hashAlg, digest, signature)
if err == nil {
return nil
}
case *ecdsa.PublicKey:
r, s := new(big.Int), new(big.Int)
r.SetBytes(signature[:len(signature)/2])
s.SetBytes(signature[len(signature)/2:])
if ecdsa.Verify(key.(*ecdsa.PublicKey), digest, r, s) {
return nil
}
default:
return fmt.Errorf("unknown public key type %T", key)
}
return fmt.Errorf("invalid signature")
}

// VerifySignature attempts to verify signatures in the MAR file using
// the provided public key until one of them passes. A valid signature
// is indicated by returning a nil error.
Expand All @@ -23,49 +51,10 @@ func (file *File) VerifySignature(key crypto.PublicKey) error {
return err
}
for _, sig := range file.Signatures {
switch key.(type) {
case *rsa.PublicKey:
switch sig.AlgorithmID {
case SigAlgRsaPkcs1Sha1:
hashed := sha1.Sum(signedBlock)
err = rsa.VerifyPKCS1v15(key.(*rsa.PublicKey), crypto.SHA1, hashed[:], sig.Data)
if err == nil {
// signature is valid, return
debugPrint("found valid %s signature\n", sig.Algorithm)
return nil
}
case SigAlgRsaPkcs1Sha384:
hashed := sha512.Sum384(signedBlock)
err = rsa.VerifyPKCS1v15(key.(*rsa.PublicKey), crypto.SHA384, hashed[:], sig.Data)
if err == nil {
debugPrint("found valid %s signature\n", sig.Algorithm)
return nil
}
default:
// ignore other signature types that may be using a non-rsa key
continue
}
case *ecdsa.PublicKey:
var md hash.Hash
switch sig.AlgorithmID {
case SigAlgEcdsaP256Sha256:
md = sha256.New()
case SigAlgEcdsaP384Sha384:
md = sha512.New384()
default:
// ignore other signature types that may be using a non-rsa key
continue
}
md.Write(signedBlock)
r, s := new(big.Int), new(big.Int)
r.SetBytes(sig.Data[:len(sig.Data)/2])
s.SetBytes(sig.Data[len(sig.Data)/2:])
if ecdsa.Verify(key.(*ecdsa.PublicKey), md.Sum(nil), r, s) {
debugPrint("found valid %s signature\n", sig.Algorithm)
return nil
}
default:
return fmt.Errorf("unknown public key type")
err = VerifySignature(signedBlock, sig.Data, sig.AlgorithmID, key)
if err == nil {
debugPrint("found valid %s signature\n", sig.Algorithm)
return nil
}
}
return fmt.Errorf("no valid signature found")
Expand Down
22 changes: 22 additions & 0 deletions verify_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mar

import (
"crypto/dsa"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"log"
Expand Down Expand Up @@ -38,3 +40,23 @@ func TestFirefoxKeys(t *testing.T) {
t.Fatal("expected signature from 'unit_test' key but didn't get one")
}
}

func TestBadKey(t *testing.T) {
var priv dsa.PrivateKey
params := &priv.Parameters
err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160)
if err != nil {
t.Fatal(err)
}
err = dsa.GenerateKey(&priv, rand.Reader)
if err != nil {
t.Fatal(err)
}
err = VerifySignature([]byte("0000"), []byte("0000"), SigAlgEcdsaP384Sha384, priv.PublicKey)
if err == nil {
t.Fatal("expected to fail with invalid dsa key type but succeeded")
}
if err.Error() != "unknown public key type dsa.PublicKey" {
t.Fatalf("expect to fail with invalid dsa key type but failed with: %v", err)
}
}

0 comments on commit debf6b0

Please sign in to comment.