diff --git a/.gitignore b/.gitignore index 5458442822..16c9563282 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /loglb /protoc *.swp +*.swo diff --git a/crypto/key_manager.go b/crypto/key_manager.go index f75dd96ac0..b8d9b9133e 100644 --- a/crypto/key_manager.go +++ b/crypto/key_manager.go @@ -80,9 +80,13 @@ func (k *PEMKeyManager) LoadPrivateKey(pemEncodedKey, password string) error { return errors.New("extra data found after PEM decoding") } - der, err := x509.DecryptPEMBlock(block, []byte(password)) - if err != nil { - return err + der := block.Bytes + if password != "" { + pwdDer, err := x509.DecryptPEMBlock(block, []byte(password)) + if err != nil { + return err + } + der = pwdDer } key, algo, err := parsePrivateKey(der) @@ -92,6 +96,11 @@ func (k *PEMKeyManager) LoadPrivateKey(pemEncodedKey, password string) error { k.serverPrivateKey = key k.signatureAlgorithm = algo + signer, err := k.Signer() + if err != nil { + return err + } + k.serverPublicKey = signer.Public() return nil } diff --git a/crypto/verifier.go b/crypto/verifier.go new file mode 100644 index 0000000000..bececef2ca --- /dev/null +++ b/crypto/verifier.go @@ -0,0 +1,84 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crypto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "encoding/asn1" + "errors" + "fmt" + "math/big" + + "github.com/google/trillian" +) + +// ErrVerify occurs whenever signature verification fails. +var ErrVerify = errors.New("signature verification failed") + +// Verify cryptographically verifies the output of Signer. +func Verify(pub crypto.PublicKey, data []byte, sig trillian.DigitallySigned) error { + sigAlgo := sig.SignatureAlgorithm + + // Recompute digest + hasher, err := NewHasher(sig.HashAlgorithm) + if err != nil { + return err + } + digest := hasher.Digest(data) + + // Verify signature algo type + switch key := pub.(type) { + case *ecdsa.PublicKey: + if sigAlgo != trillian.SignatureAlgorithm_ECDSA { + return fmt.Errorf("signature algorithm does not match public key") + } + return verifyECDSA(key, digest, sig.Signature) + case *rsa.PublicKey: + if sigAlgo != trillian.SignatureAlgorithm_RSA { + return fmt.Errorf("signature algorithm does not match public key") + } + return verifyRSA(key, digest, sig.Signature, hasher.Hash, hasher) + default: + return fmt.Errorf("unknown private key type: %T", key) + } +} + +func verifyRSA(pub *rsa.PublicKey, hashed, sig []byte, hasher crypto.Hash, opts crypto.SignerOpts) error { + if pssOpts, ok := opts.(*rsa.PSSOptions); ok { + return rsa.VerifyPSS(pub, hasher, hashed, sig, pssOpts) + } + return rsa.VerifyPKCS1v15(pub, hasher, hashed, sig) +} + +func verifyECDSA(pub *ecdsa.PublicKey, hashed, sig []byte) error { + var ecdsaSig struct { + R, S *big.Int + } + rest, err := asn1.Unmarshal(sig, &ecdsaSig) + if err != nil { + return ErrVerify + } + if len(rest) != 0 { + return ErrVerify + } + + if !ecdsa.Verify(pub, hashed, ecdsaSig.R, ecdsaSig.S) { + return ErrVerify + } + return nil + +} diff --git a/crypto/verifier_test.go b/crypto/verifier_test.go new file mode 100644 index 0000000000..d70f06f284 --- /dev/null +++ b/crypto/verifier_test.go @@ -0,0 +1,78 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crypto + +import ( + "testing" + + "github.com/google/trillian" + "github.com/google/trillian/testonly" +) + +const ( + // openssl ecparam -name prime256v1 -genkey -out p256-key.pem + privPEM = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIGbhE2+z8d5lHzb0gmkS78d86gm5gHUtXCpXveFbK3pcoAoGCCqGSM49 +AwEHoUQDQgAEUxX42oxJ5voiNfbjoz8UgsGqh1bD1NXK9m8VivPmQSoYUdVFgNav +csFaQhohkiCEthY51Ga6Xa+ggn+eTZtf9Q== +-----END EC PRIVATE KEY-----` +) + +func TestSignVerify(t *testing.T) { + for _, test := range []struct { + PEM string + password string + HashAlgo trillian.HashAlgorithm + SigAlgo trillian.SignatureAlgorithm + }{ + {privPEM, "", trillian.HashAlgorithm_SHA256, trillian.SignatureAlgorithm_ECDSA}, + {testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass, + trillian.HashAlgorithm_SHA256, trillian.SignatureAlgorithm_ECDSA}, + } { + + km := NewPEMKeyManager() + if err := km.LoadPrivateKey(test.PEM, test.password); err != nil { + t.Errorf("LoadPrivateKey(_, %v)=%v, want nil", test.password, err) + continue + } + kmsigner, err := km.Signer() + if err != nil { + t.Errorf("Signer()=(_,%v), want (_,nil)", err) + continue + } + hasher, err := NewHasher(test.HashAlgo) + if err != nil { + t.Errorf("NewHasher(%v)=(_,%v), want (_,nil)", test.HashAlgo, err) + continue + } + signer := NewSigner(hasher, test.SigAlgo, kmsigner) + + // Sign and Verify. + msg := []byte("foo") + signed, err := signer.Sign(msg) + if err != nil { + t.Errorf("Sign()=(_,%v), want (_,nil)", err) + continue + } + pub, err := km.GetPublicKey() + if err != nil { + t.Errorf("GetPublicKey()=(_,%v), want (_,nil)", err) + continue + } + if err := Verify(pub, msg, signed); err != nil { + t.Errorf("Verify(,,)=%v, want nil", err) + } + } +}