diff --git a/client/verifier_test.go b/client/verifier_test.go index 778364c206..7e14dd4322 100644 --- a/client/verifier_test.go +++ b/client/verifier_test.go @@ -19,19 +19,19 @@ import ( "github.com/google/trillian" tcrypto "github.com/google/trillian/crypto" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/merkle/rfc6962" "github.com/google/trillian/testonly" ) func TestVerifyRootErrors(t *testing.T) { // Test setup - key, err := keys.NewFromPrivatePEM(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) + key, err := pem.UnmarshalPrivateKey(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) if err != nil { t.Fatalf("Failed to open test key, err=%v", err) } signer := tcrypto.NewSHA256Signer(key) - pk, err := keys.NewFromPublicPEM(testonly.DemoPublicKey) + pk, err := pem.UnmarshalPublicKey(testonly.DemoPublicKey) if err != nil { t.Fatalf("Failed to load public key, err=%v", err) } diff --git a/cmd/createtree/main.go b/cmd/createtree/main.go index 3521168af3..299eb79727 100644 --- a/cmd/createtree/main.go +++ b/cmd/createtree/main.go @@ -41,7 +41,8 @@ import ( "github.com/golang/protobuf/ptypes/any" "github.com/google/trillian" "github.com/google/trillian/cmd" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/crypto/sigpb" "github.com/letsencrypt/pkcs11key" @@ -180,16 +181,16 @@ func newPK(keyFormat string) (*any.Any, error) { if *pemKeyPath == "" { return nil, errors.New("empty pem_key_path") } - pemSigner, err := keys.NewFromPrivatePEMFile( + pemSigner, err := pem.ReadPrivateKeyFile( *pemKeyPath, *pemKeyPassword) if err != nil { return nil, err } - der, err := keys.MarshalPrivateKey(pemSigner) + keyDER, err := der.MarshalPrivateKey(pemSigner) if err != nil { return nil, err } - return ptypes.MarshalAny(&keyspb.PrivateKey{Der: der}) + return ptypes.MarshalAny(&keyspb.PrivateKey{Der: keyDER}) case "PKCS11ConfigFile": if *pkcs11ConfigPath == "" { return nil, errors.New("empty PKCS11 config file path") diff --git a/cmd/createtree/pem_test.go b/cmd/createtree/pem_test.go index 606b0ce2b5..401a4eff99 100644 --- a/cmd/createtree/pem_test.go +++ b/cmd/createtree/pem_test.go @@ -17,7 +17,8 @@ package main import ( "testing" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/keyspb" ) @@ -64,12 +65,12 @@ func TestWithPEMKeyFile(t *testing.T) { func TestWithPrivateKey(t *testing.T) { pemPath, pemPassword := "../../testdata/log-rpc-server.privkey.pem", "towel" - key, err := keys.NewFromPrivatePEMFile(pemPath, pemPassword) + key, err := pem.ReadPrivateKeyFile(pemPath, pemPassword) if err != nil { t.Fatalf("Error reading test private key file: %v", err) } - keyDER, err := keys.MarshalPrivateKey(key) + keyDER, err := der.MarshalPrivateKey(key) if err != nil { t.Fatalf("Error marshaling test private key to DER: %v", err) } diff --git a/crypto/keys/default_signer_factory.go b/crypto/keys/default_signer_factory.go index e2c71d0e1e..6a7d0cb2d7 100644 --- a/crypto/keys/default_signer_factory.go +++ b/crypto/keys/default_signer_factory.go @@ -21,9 +21,25 @@ import ( "sync" "github.com/golang/protobuf/proto" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" + "github.com/google/trillian/crypto/keys/pkcs11" "github.com/google/trillian/crypto/keyspb" ) +// SignerFactory gets and creates cryptographic signers. +// This may be done by loading a key from a file, interfacing with a HSM, or +// sending requests to a remote key management service, to give a few examples. +type SignerFactory interface { + // NewSigner uses the information in the provided protobuf message to obtain and return a crypto.Signer. + NewSigner(context.Context, proto.Message) (crypto.Signer, error) + + // Generate creates a new private key based on a key specification. + // It returns a proto that describes how to access that key. + // This proto can be passed to NewSigner() to get a crypto.Signer. + Generate(context.Context, *keyspb.Specification) (proto.Message, error) +} + // DefaultSignerFactory produces a crypto.Signer from a protobuf message describing a key. // It can also generate new private keys. // It implements keys.SignerFactory. @@ -37,11 +53,11 @@ type DefaultSignerFactory struct { func (f *DefaultSignerFactory) NewSigner(ctx context.Context, pb proto.Message) (crypto.Signer, error) { switch privateKey := pb.(type) { case *keyspb.PEMKeyFile: - return NewFromPrivatePEMFile(privateKey.GetPath(), privateKey.GetPassword()) + return pem.ReadPrivateKeyFile(privateKey.GetPath(), privateKey.GetPassword()) case *keyspb.PrivateKey: - return NewFromPrivateDER(privateKey.GetDer()) + return der.UnmarshalPrivateKey(privateKey.GetDer()) case *keyspb.PKCS11Config: - return NewFromPKCS11Config(f.pkcs11Module, privateKey) + return pkcs11.FromConfig(f.pkcs11Module, privateKey) } return nil, fmt.Errorf("unsupported private key protobuf type: %T", pb) @@ -55,12 +71,12 @@ func (f *DefaultSignerFactory) Generate(ctx context.Context, spec *keyspb.Specif return nil, fmt.Errorf("error generating key: %v", err) } - der, err := MarshalPrivateKey(key) + keyDER, err := der.MarshalPrivateKey(key) if err != nil { return nil, fmt.Errorf("error marshaling private key as DER: %v", err) } - return &keyspb.PrivateKey{Der: der}, nil + return &keyspb.PrivateKey{Der: keyDER}, nil } // SetPKCS11Module sets the PKCS#11 module used to load PKCS#11 keys. diff --git a/crypto/keys/default_signer_factory_test.go b/crypto/keys/default_signer_factory_test.go index 408a9b3192..51c0b1f6e9 100644 --- a/crypto/keys/default_signer_factory_test.go +++ b/crypto/keys/default_signer_factory_test.go @@ -17,16 +17,18 @@ package keys import ( "testing" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/keyspb" ) func TestDefaultSignerFactory(t *testing.T) { - key, err := NewFromPrivatePEMFile("../../testdata/log-rpc-server.privkey.pem", "towel") + key, err := pem.ReadPrivateKeyFile("../../testdata/log-rpc-server.privkey.pem", "towel") if err != nil { t.Fatalf("Failed to load private key: %v", err) } - keyDER, err := MarshalPrivateKey(key) + keyDER, err := der.MarshalPrivateKey(key) if err != nil { t.Fatalf("Failed to marshal private key to DER: %v", err) } diff --git a/crypto/keys/der/der.go b/crypto/keys/der/der.go new file mode 100644 index 0000000000..6e36061b94 --- /dev/null +++ b/crypto/keys/der/der.go @@ -0,0 +1,78 @@ +// Copyright 2017 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 der + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "fmt" + + "github.com/google/trillian/crypto/keyspb" +) + +// FromProto takes a PrivateKey protobuf message and returns the private key contained within. +func FromProto(pb *keyspb.PrivateKey) (crypto.Signer, error) { + return UnmarshalPrivateKey(pb.GetDer()) +} + +// UnmarshalPrivateKey reads a DER-encoded private key. +func UnmarshalPrivateKey(keyDER []byte) (crypto.Signer, error) { + key1, err1 := x509.ParseECPrivateKey(keyDER) + if err1 == nil { + return key1, nil + } + + key2, err2 := x509.ParsePKCS8PrivateKey(keyDER) + if err2 == nil { + switch key2 := key2.(type) { + case *ecdsa.PrivateKey: + return key2, nil + case *rsa.PrivateKey: + return key2, nil + } + return nil, fmt.Errorf("der: unsupported private key type: %T", key2) + } + + key3, err3 := x509.ParsePKCS1PrivateKey(keyDER) + if err3 == nil { + return key3, nil + } + + return nil, fmt.Errorf("der: could not parse private key as SEC1 (%v), PKCS8 (%v) or PKCS1 (%v)", err1, err2, err3) +} + +// UnmarshalPublicKey reads a DER-encoded public key. +func UnmarshalPublicKey(keyDER []byte) (crypto.PublicKey, error) { + key, err := x509.ParsePKIXPublicKey(keyDER) + if err != nil { + return nil, fmt.Errorf("der: could not parse public key as PKIX (%v)", err) + } + + return key, nil +} + +// MarshalPrivateKey serializes an RSA or ECDSA private key as DER. +func MarshalPrivateKey(key crypto.Signer) ([]byte, error) { + switch key := key.(type) { + case *ecdsa.PrivateKey: + return x509.MarshalECPrivateKey(key) + case *rsa.PrivateKey: + return x509.MarshalPKCS1PrivateKey(key), nil + } + + return nil, fmt.Errorf("der: unsupported key type: %T", key) +} diff --git a/crypto/keys/doc.go b/crypto/keys/doc.go new file mode 100644 index 0000000000..b6901cf06a --- /dev/null +++ b/crypto/keys/doc.go @@ -0,0 +1,16 @@ +// Copyright 2017 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 keys provides access to public and private keys for signing and verification of signatures. +package keys diff --git a/crypto/keys/generate.go b/crypto/keys/generate.go new file mode 100644 index 0000000000..ed5f49020f --- /dev/null +++ b/crypto/keys/generate.go @@ -0,0 +1,76 @@ +// Copyright 2017 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 keys + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "fmt" + + "github.com/google/trillian/crypto/keyspb" +) + +const ( + // DefaultRsaKeySizeInBits is the size of an RSA key generated by this package, in bits, if not overridden. + DefaultRsaKeySizeInBits = 2048 + + // MinRsaKeySizeInBits is the smallest RSA key that this package will generate. + MinRsaKeySizeInBits = 2048 +) + +// NewFromSpec generates a new private key based on a key specification. +// If an RSA key is specified, the key size must be at least MinRsaKeySizeInBits. +func NewFromSpec(spec *keyspb.Specification) (crypto.Signer, error) { + switch params := spec.GetParams().(type) { + case *keyspb.Specification_EcdsaParams: + curve := ECDSACurveFromParams(params.EcdsaParams) + if curve == nil { + return nil, fmt.Errorf("unsupported ECDSA curve: %s", params.EcdsaParams.GetCurve()) + } + + return ecdsa.GenerateKey(curve, rand.Reader) + case *keyspb.Specification_RsaParams: + bits := int(params.RsaParams.GetBits()) + if bits == 0 { + bits = DefaultRsaKeySizeInBits + } + if bits < MinRsaKeySizeInBits { + return nil, fmt.Errorf("minimum RSA key size is %v bits, got %v bits", MinRsaKeySizeInBits, bits) + } + + return rsa.GenerateKey(rand.Reader, bits) + default: + return nil, fmt.Errorf("unsupported keygen params type: %T", params) + } +} + +// ECDSACurveFromParams returns the curve specified by the given parameters. +// Returns nil if the curve is not supported. +func ECDSACurveFromParams(params *keyspb.Specification_ECDSA) elliptic.Curve { + switch params.GetCurve() { + case keyspb.Specification_ECDSA_DEFAULT_CURVE: + return elliptic.P256() + case keyspb.Specification_ECDSA_P256: + return elliptic.P256() + case keyspb.Specification_ECDSA_P384: + return elliptic.P384() + case keyspb.Specification_ECDSA_P521: + return elliptic.P521() + } + return nil +} diff --git a/crypto/keys/generate_test.go b/crypto/keys/generate_test.go new file mode 100644 index 0000000000..2193470ed7 --- /dev/null +++ b/crypto/keys/generate_test.go @@ -0,0 +1,107 @@ +// Copyright 2017 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 keys_test + +import ( + "testing" + + . "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/testonly" + "github.com/google/trillian/crypto/keyspb" +) + +func TestNewFromSpec(t *testing.T) { + for _, test := range []struct { + desc string + keySpec *keyspb.Specification + wantErr bool + }{ + { + desc: "ECDSA with default params", + keySpec: &keyspb.Specification{ + Params: &keyspb.Specification_EcdsaParams{}, + }, + }, + { + desc: "ECDSA with explicit params", + keySpec: &keyspb.Specification{ + Params: &keyspb.Specification_EcdsaParams{ + EcdsaParams: &keyspb.Specification_ECDSA{ + Curve: keyspb.Specification_ECDSA_P521, + }, + }, + }, + }, + { + desc: "RSA with default params", + keySpec: &keyspb.Specification{ + Params: &keyspb.Specification_RsaParams{}, + }, + }, + { + desc: "RSA with explicit params", + keySpec: &keyspb.Specification{ + Params: &keyspb.Specification_RsaParams{ + RsaParams: &keyspb.Specification_RSA{ + Bits: 4096, + }, + }, + }, + }, + { + desc: "RSA with negative key size", + keySpec: &keyspb.Specification{ + Params: &keyspb.Specification_RsaParams{ + RsaParams: &keyspb.Specification_RSA{ + Bits: -4096, + }, + }, + }, + wantErr: true, + }, + { + desc: "RSA with insufficient key size", + keySpec: &keyspb.Specification{ + Params: &keyspb.Specification_RsaParams{ + RsaParams: &keyspb.Specification_RSA{ + Bits: MinRsaKeySizeInBits - 1, + }, + }, + }, + wantErr: true, + }, + { + desc: "No params", + keySpec: &keyspb.Specification{}, + wantErr: true, + }, + { + desc: "Nil KeySpec", + wantErr: true, + }, + } { + key, err := NewFromSpec(test.keySpec) + if gotErr := err != nil; gotErr != test.wantErr { + t.Errorf("%v: NewFromSpec() = (_, %v), want err? %v", test.desc, err, test.wantErr) + continue + } else if gotErr { + continue + } + + if err := testonly.CheckKeyMatchesSpec(key, test.keySpec); err != nil { + t.Errorf("%v: CheckKeyMatchesSpec(): %v", test.desc, err) + } + } +} diff --git a/crypto/keys/pem/pem.go b/crypto/keys/pem/pem.go new file mode 100644 index 0000000000..012373226a --- /dev/null +++ b/crypto/keys/pem/pem.go @@ -0,0 +1,98 @@ +// Copyright 2017 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 pem + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keyspb" +) + +// FromProto takes a PEMKeyFile protobuf message and loads the private key it specifies. +func FromProto(pb *keyspb.PEMKeyFile) (crypto.Signer, error) { + return ReadPrivateKeyFile(pb.GetPath(), pb.GetPassword()) +} + +// ReadPrivateKeyFile reads a PEM-encoded private key from a file. +// The key must be protected by a password. +func ReadPrivateKeyFile(file, password string) (crypto.Signer, error) { + if password == "" { + return nil, fmt.Errorf("pemfile: empty password for file %q", file) + } + + keyPEM, err := ioutil.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("pemfile: error reading file %q: %v", file, err) + } + + k, err := UnmarshalPrivateKey(string(keyPEM), password) + if err != nil { + return nil, fmt.Errorf("pemfile: error decoding private key from file %q: %v", file, err) + } + + return k, nil +} + +// ReadPublicKeyFile reads a PEM-encoded public key from a file. +func ReadPublicKeyFile(file string) (crypto.PublicKey, error) { + keyPEM, err := ioutil.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("pemfile: error reading %q: %v", file, err) + } + + return UnmarshalPublicKey(string(keyPEM)) +} + +// UnmarshalPrivateKey reads a PEM-encoded private key from a string. +// The key may be protected by a password. +func UnmarshalPrivateKey(keyPEM, password string) (crypto.Signer, error) { + block, rest := pem.Decode([]byte(keyPEM)) + if block == nil { + return nil, errors.New("pemfile: invalid private key PEM") + } + if len(rest) > 0 { + return nil, errors.New("pemfile: extra data found after first PEM block") + } + + keyDER := block.Bytes + if password != "" { + pwdDer, err := x509.DecryptPEMBlock(block, []byte(password)) + if err != nil { + return nil, fmt.Errorf("pemfile: failed to decrypt: %v", err) + } + keyDER = pwdDer + } + + return der.UnmarshalPrivateKey(keyDER) +} + +// UnmarshalPublicKey reads a PEM-encoded public key from a string. +func UnmarshalPublicKey(keyPEM string) (crypto.PublicKey, error) { + block, rest := pem.Decode([]byte(keyPEM)) + if block == nil { + return nil, errors.New("pemfile: invalid public key PEM") + } + if len(rest) > 0 { + return nil, errors.New("pemfile: extra data found after first PEM block") + } + + return der.UnmarshalPublicKey(block.Bytes) +} diff --git a/crypto/keys/keys_test.go b/crypto/keys/pem/pem_test.go similarity index 55% rename from crypto/keys/keys_test.go rename to crypto/keys/pem/pem_test.go index 4a25fb8146..dd6ceb1697 100644 --- a/crypto/keys/keys_test.go +++ b/crypto/keys/pem/pem_test.go @@ -12,17 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package keys +package pem_test import ( "crypto" - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" "testing" - "github.com/google/trillian/crypto/keyspb" - "github.com/google/trillian/crypto/sigpb" + . "github.com/google/trillian/crypto/keys/pem" + ktestonly "github.com/google/trillian/crypto/keys/testonly" "github.com/google/trillian/testonly" ) @@ -57,56 +54,53 @@ NHcCAQEEIHG5m/q2sUSa4P8pRZgYt3K0ESFSKp1qp15VjJhpLle4oAoGCCqGSM49AwEHoUQDQgAEvuyn ) func TestLoadPrivateKeyAndSign(t *testing.T) { - hasher := crypto.SHA256 - digest := []byte("\x2c\xf2\x4d\xba\x5f\xb0\xa3\x0e\x26\xe8\x3b\x2a\xc5\xb9\xe2\x9e\x1b\x16\x1e\x5c\x1f\xa7\x42\x5e\x73\x04\x33\x62\x93\x8b\x98\x24") - tests := []struct { - name string + desc string keyPEM string keyPath string keyPass string wantLoadErr bool }{ { - name: "ECDSA with password", + desc: "ECDSA with password", keyPEM: testonly.DemoPrivateKey, keyPass: testonly.DemoPrivateKeyPass, }, { - name: "ECDSA from file with password", - keyPath: "../../testdata/log-rpc-server.privkey.pem", + desc: "ECDSA from file with password", + keyPath: "../../../testdata/log-rpc-server.privkey.pem", keyPass: "towel", }, { - name: "Non-existent file", + desc: "Non-existent file", keyPath: "non-existent.pem", wantLoadErr: true, }, { - name: "ECDSA with wrong password", + desc: "ECDSA with wrong password", keyPEM: testonly.DemoPrivateKey, keyPass: testonly.DemoPrivateKeyPass + "foo", wantLoadErr: true, }, { - name: "ECDSA", + desc: "ECDSA", keyPEM: ecdsaPrivateKey, }, { - name: "RSA", + desc: "RSA", keyPEM: rsaPrivateKey, }, { - name: "ECDSA with leading junk", + desc: "ECDSA with leading junk", keyPEM: "foobar\n" + ecdsaPrivateKey, }, { - name: "ECDSA with trailing junk", + desc: "ECDSA with trailing junk", keyPEM: ecdsaPrivateKey + "\nfoobar", wantLoadErr: true, }, { - name: "Corrupt ECDSA", + desc: "Corrupt ECDSA", keyPEM: corruptEcdsaPrivateKey, wantLoadErr: true, }, @@ -117,179 +111,33 @@ func TestLoadPrivateKeyAndSign(t *testing.T) { var err error switch { case test.keyPEM != "": - k, err = NewFromPrivatePEM(test.keyPEM, test.keyPass) + k, err = UnmarshalPrivateKey(test.keyPEM, test.keyPass) switch gotErr := err != nil; { case gotErr != test.wantLoadErr: - t.Errorf("%v: NewFromPrivatePEM() = (%v, %v), want err? %v", test.name, k, err, test.wantLoadErr) + t.Errorf("%v: UnmarshalPrivateKey() = (%v, %v), want err? %v", test.desc, k, err, test.wantLoadErr) continue case gotErr: continue } case test.keyPath != "": - k, err = NewFromPrivatePEMFile(test.keyPath, test.keyPass) + k, err = ReadPrivateKeyFile(test.keyPath, test.keyPass) switch gotErr := err != nil; { case gotErr != test.wantLoadErr: - t.Errorf("%v: NewFromPrivatePEMFile() = (%v, %v), want err? %v", test.name, k, err, test.wantLoadErr) + t.Errorf("%v: ReadPrivateKeyFile() = (%v, %v), want err? %v", test.desc, k, err, test.wantLoadErr) continue case gotErr: continue } default: - t.Errorf("%v: No PEM or file path set in test definition", test.name) - continue - } - - signature, err := k.Sign(rand.Reader, digest, hasher) - if err != nil { - t.Errorf("%v: failed to sign: %v", test.name, err) + t.Errorf("%v: No PEM or file path set in test definition", test.desc) continue } - // Do a round trip by verifying the signature using the public key. - if err := verify(k.Public(), digest, signature, hasher, hasher); err != nil { - t.Errorf("%v: %v", test.name, err) - } - - } -} - -func TestSignatureAlgorithm(t *testing.T) { - tests := []struct { - name string - keyPEM string - want sigpb.DigitallySigned_SignatureAlgorithm - }{ - { - name: "ECDSA", - keyPEM: ecdsaPublicKey, - want: sigpb.DigitallySigned_ECDSA, - }, - { - name: "RSA", - keyPEM: rsaPublicKey, - want: sigpb.DigitallySigned_RSA, - }, - { - name: "DSA", - keyPEM: dsaPublicKey, - want: sigpb.DigitallySigned_ANONYMOUS, - }, - } - - for _, test := range tests { - key, err := NewFromPublicPEM(test.keyPEM) - if err != nil { - t.Errorf("%v: Failed to load key: %v", test.name, err) - continue - } - - if got := SignatureAlgorithm(key); got != test.want { - t.Errorf("%v: SignatureAlgorithm(%v) = %v, want %v", test.name, key, got, test.want) - } - } -} - -func TestGenerateKey(t *testing.T) { - for _, test := range []struct { - name string - keygen *keyspb.Specification - wantErr bool - }{ - { - name: "ECDSA with default params", - keygen: &keyspb.Specification{ - Params: &keyspb.Specification_EcdsaParams{}, - }, - }, - { - name: "ECDSA with explicit params", - keygen: &keyspb.Specification{ - Params: &keyspb.Specification_EcdsaParams{ - EcdsaParams: &keyspb.Specification_ECDSA{ - Curve: keyspb.Specification_ECDSA_P521, - }, - }, - }, - }, - { - name: "RSA with default params", - keygen: &keyspb.Specification{ - Params: &keyspb.Specification_RsaParams{}, - }, - }, - { - name: "RSA with explicit params", - keygen: &keyspb.Specification{ - Params: &keyspb.Specification_RsaParams{ - RsaParams: &keyspb.Specification_RSA{ - Bits: 4096, - }, - }, - }, - }, - { - name: "RSA with negative key size", - keygen: &keyspb.Specification{ - Params: &keyspb.Specification_RsaParams{ - RsaParams: &keyspb.Specification_RSA{ - Bits: -4096, - }, - }, - }, - wantErr: true, - }, - { - name: "RSA with insufficient key size", - keygen: &keyspb.Specification{ - Params: &keyspb.Specification_RsaParams{ - RsaParams: &keyspb.Specification_RSA{ - Bits: MinRsaKeySizeInBits - 1, - }, - }, - }, - wantErr: true, - }, - { - name: "No params", - keygen: &keyspb.Specification{}, - wantErr: true, - }, - } { - key, err := NewFromSpec(test.keygen) - if gotErr := err != nil; gotErr != test.wantErr { - t.Errorf("%v: NewFromSpecification() = (_, %v), want err? %v", test.name, err, test.wantErr) - continue - } else if gotErr { - continue - } - - switch params := test.keygen.Params.(type) { - case *keyspb.Specification_EcdsaParams: - switch key := key.(type) { - case *ecdsa.PrivateKey: - wantCurve := curveFromParams(params.EcdsaParams) - if wantCurve.Params().Name != key.Params().Name { - t.Errorf("%v: NewFromSpecification() => ECDSA key on %v curve, want %v curve", test.name, key.Params().Name, wantCurve.Params().Name) - } - default: - t.Errorf("%v: NewFromSpecification() = (%T, nil), want *ecdsa.PrivateKey", test.name, key) - } - case *keyspb.Specification_RsaParams: - switch key := key.(type) { - case *rsa.PrivateKey: - wantBits := defaultRsaKeySizeInBits - if params.RsaParams.GetBits() != 0 { - wantBits = int(params.RsaParams.GetBits()) - } - - if got, want := key.N.BitLen(), wantBits; got != want { - t.Errorf("%v: NewFromSpecification() => %v-bit RSA key, want %v-bit", test.name, got, want) - } - default: - t.Errorf("%v: NewFromSpecification() = (%T, nil), want *rsa.PrivateKey", test.name, key) - } + // Check the key by creating a signature and verifying it. + if err := ktestonly.SignAndVerify(k, k.Public()); err != nil { + t.Errorf("%v: SignAndVerify() = %q, want nil", test.desc, err) } } } diff --git a/crypto/keys/pkcs11/doc.go b/crypto/keys/pkcs11/doc.go new file mode 100644 index 0000000000..0189ac9fd2 --- /dev/null +++ b/crypto/keys/pkcs11/doc.go @@ -0,0 +1,16 @@ +// Copyright 2017 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 pkcs11 provides access to private keys using a PKCS#11 interface. +package pkcs11 diff --git a/crypto/keys/pkcs11/pkcs11.go b/crypto/keys/pkcs11/pkcs11.go new file mode 100644 index 0000000000..cffd8da03a --- /dev/null +++ b/crypto/keys/pkcs11/pkcs11.go @@ -0,0 +1,40 @@ +// Copyright 2017 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 pkcs11 + +import ( + "crypto" + "errors" + "fmt" + + "github.com/google/trillian/crypto/keys/pem" + "github.com/google/trillian/crypto/keyspb" + "github.com/letsencrypt/pkcs11key" +) + +// FromConfig returns a crypto.Signer that uses a PKCS#11 interface. +func FromConfig(modulePath string, config *keyspb.PKCS11Config) (crypto.Signer, error) { + if modulePath == "" { + return nil, errors.New("pkcs11: No module path") + } + + pubKeyPEM := config.GetPublicKey() + pubKey, err := pem.UnmarshalPublicKey(pubKeyPEM) + if err != nil { + return nil, fmt.Errorf("pkcs11: error loading public key from %q: %v", pubKeyPEM, err) + } + + return pkcs11key.New(modulePath, config.GetTokenLabel(), config.GetPin(), pubKey) +} diff --git a/crypto/keys/pkcs11/pkcs11_test.go b/crypto/keys/pkcs11/pkcs11_test.go new file mode 100644 index 0000000000..89c5fed264 --- /dev/null +++ b/crypto/keys/pkcs11/pkcs11_test.go @@ -0,0 +1,22 @@ +// Copyright 2017 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 pkcs11 + +import "testing" + +func TestPkcs11(t *testing.T) { + // PKCS11Config support is tested by integration/log_integration.sh (when $WITH_PKCS11 == "true"). + t.Skip("Only integration testing is implemented for PKCS#11") +} diff --git a/crypto/keys/private_keys.go b/crypto/keys/private_keys.go deleted file mode 100644 index c20c4e83ce..0000000000 --- a/crypto/keys/private_keys.go +++ /dev/null @@ -1,186 +0,0 @@ -// 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 keys - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io/ioutil" - - "github.com/golang/protobuf/proto" - "github.com/google/trillian/crypto/keyspb" - "github.com/letsencrypt/pkcs11key" -) - -// The size of an RSA key generated by this library, in bits, if not overridden. -const defaultRsaKeySizeInBits = 2048 - -// MinRsaKeySizeInBits is the smallest RSA key that this package will generate. -const MinRsaKeySizeInBits = 2048 - -// SignerFactory gets and creates cryptographic signers. -// This may be done by loading a key from a file, interfacing with a HSM, or -// sending requests to a remote key management service, to give a few examples. -type SignerFactory interface { - // NewSigner uses the information in the provided protobuf message to obtain and return a crypto.Signer. - NewSigner(context.Context, proto.Message) (crypto.Signer, error) - - // Generate creates a new private key based on a key specification. - // It returns a proto that describes how to access that key. - // This proto can be passed to NewSigner() to get a crypto.Signer. - Generate(context.Context, *keyspb.Specification) (proto.Message, error) -} - -// NewFromPrivatePEMFile reads a PEM-encoded private key from a file. -// The key must be protected by a password. -func NewFromPrivatePEMFile(keyFile, keyPassword string) (crypto.Signer, error) { - if keyPassword == "" { - return nil, fmt.Errorf("empty password for PEM key file %q", keyFile) - } - pemData, err := ioutil.ReadFile(keyFile) - if err != nil { - return nil, fmt.Errorf("failed to read private key from file %q: %v", keyFile, err) - } - - k, err := NewFromPrivatePEM(string(pemData), keyPassword) - if err != nil { - return nil, fmt.Errorf("failed to decode private key from file %q: %v", keyFile, err) - } - - return k, nil -} - -// NewFromPrivatePEM reads a PEM-encoded private key from a string. -// The key may be protected by a password. -func NewFromPrivatePEM(pemEncodedKey, password string) (crypto.Signer, error) { - block, rest := pem.Decode([]byte(pemEncodedKey)) - if block == nil { - return nil, errors.New("invalid PEM") - } - if len(rest) > 0 { - return nil, errors.New("extra data found after PEM decoding") - } - - der := block.Bytes - if password != "" { - pwdDer, err := x509.DecryptPEMBlock(block, []byte(password)) - if err != nil { - return nil, fmt.Errorf("failed to decrypt PEM: %v", err) - } - der = pwdDer - } - - return NewFromPrivateDER(der) -} - -// NewFromPrivateDER reads a DER-encoded private key. -func NewFromPrivateDER(der []byte) (crypto.Signer, error) { - key1, err1 := x509.ParseECPrivateKey(der) - if err1 == nil { - return key1, nil - } - - key2, err2 := x509.ParsePKCS8PrivateKey(der) - if err2 == nil { - switch key2 := key2.(type) { - case *ecdsa.PrivateKey: - return key2, nil - case *rsa.PrivateKey: - return key2, nil - } - return nil, fmt.Errorf("unsupported private key type: %T", key2) - } - - key3, err3 := x509.ParsePKCS1PrivateKey(der) - if err3 == nil { - return key3, nil - } - - return nil, fmt.Errorf("could not parse DER private key as SEC1 (%v), PKCS8 (%v), or PKCS1 (%v)", err1, err2, err3) -} - -// NewFromSpec generates a new private key based on a key specification. -// If an RSA key is specified, the key size must be at least MinRsaKeySizeInBits. -func NewFromSpec(spec *keyspb.Specification) (crypto.Signer, error) { - switch params := spec.GetParams().(type) { - case *keyspb.Specification_EcdsaParams: - curve := curveFromParams(params.EcdsaParams) - if curve == nil { - return nil, fmt.Errorf("unsupported ECDSA curve: %s", params.EcdsaParams.GetCurve()) - } - - return ecdsa.GenerateKey(curve, rand.Reader) - case *keyspb.Specification_RsaParams: - bits := int(params.RsaParams.GetBits()) - if bits == 0 { - bits = defaultRsaKeySizeInBits - } - if bits < MinRsaKeySizeInBits { - return nil, fmt.Errorf("minimum RSA key size is %v bits, got %v bits", MinRsaKeySizeInBits, bits) - } - - return rsa.GenerateKey(rand.Reader, bits) - default: - return nil, fmt.Errorf("unsupported keygen params type: %T", params) - } -} - -// curveFromParams returns the curve specified by the given parameters. -// Returns nil if the curve is not supported. -func curveFromParams(params *keyspb.Specification_ECDSA) elliptic.Curve { - switch params.GetCurve() { - case keyspb.Specification_ECDSA_DEFAULT_CURVE: - return elliptic.P256() - case keyspb.Specification_ECDSA_P256: - return elliptic.P256() - case keyspb.Specification_ECDSA_P384: - return elliptic.P384() - case keyspb.Specification_ECDSA_P521: - return elliptic.P521() - } - return nil -} - -// MarshalPrivateKey serializes an RSA or ECDSA private key as DER. -func MarshalPrivateKey(key crypto.Signer) ([]byte, error) { - switch key := key.(type) { - case *ecdsa.PrivateKey: - return x509.MarshalECPrivateKey(key) - case *rsa.PrivateKey: - return x509.MarshalPKCS1PrivateKey(key), nil - } - - return nil, fmt.Errorf("unsupported key type: %T", key) -} - -// NewFromPKCS11Config returns a crypto.Signer that uses a PKCS#11 interface. -func NewFromPKCS11Config(modulePath string, config *keyspb.PKCS11Config) (crypto.Signer, error) { - if modulePath == "" { - return nil, errors.New("No PKCS#11 module path set, cannot create signer") - } - pubKey, err := NewFromPublicPEM(config.GetPublicKey()) - if err != nil { - return nil, fmt.Errorf("failed to load public key from %q: %s", config.GetPublicKey(), err) - } - return pkcs11key.New(modulePath, config.GetTokenLabel(), config.GetPin(), pubKey) -} diff --git a/crypto/keys/public_keys.go b/crypto/keys/public_keys.go deleted file mode 100644 index f70fecad8b..0000000000 --- a/crypto/keys/public_keys.go +++ /dev/null @@ -1,74 +0,0 @@ -// 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 keys provides access to public and private keys for signing and verification of signatures. -package keys - -import ( - "crypto" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io/ioutil" - - "github.com/google/trillian/crypto/sigpb" -) - -// NewFromPublicPEMFile reads a PEM-encoded public key from a file. -func NewFromPublicPEMFile(keyFile string) (crypto.PublicKey, error) { - pemData, err := ioutil.ReadFile(keyFile) - if err != nil { - return nil, fmt.Errorf("failed to read: %s. %v", keyFile, err) - } - return NewFromPublicPEM(string(pemData)) -} - -// NewFromPublicPEM reads a PEM-encoded public key from a string. -func NewFromPublicPEM(pemEncodedKey string) (crypto.PublicKey, error) { - publicBlock, rest := pem.Decode([]byte(pemEncodedKey)) - if publicBlock == nil { - return nil, errors.New("could not decode PEM for public key") - } - if len(rest) > 0 { - return nil, errors.New("extra data found after PEM key decoded") - } - - return NewFromPublicDER(publicBlock.Bytes) -} - -// NewFromPublicDER reads a DER-encoded public key. -func NewFromPublicDER(der []byte) (crypto.PublicKey, error) { - key, err := x509.ParsePKIXPublicKey(der) - if err != nil { - return nil, fmt.Errorf("unable to parse public key: %v", err) - } - - return key, nil -} - -// SignatureAlgorithm returns the algorithm used for this public key. -// Only ECDSA and RSA keys are supported. Other key types will return sigpb.DigitallySigned_ANONYMOUS. -func SignatureAlgorithm(k crypto.PublicKey) sigpb.DigitallySigned_SignatureAlgorithm { - switch k.(type) { - case *ecdsa.PublicKey: - return sigpb.DigitallySigned_ECDSA - case *rsa.PublicKey: - return sigpb.DigitallySigned_RSA - } - - return sigpb.DigitallySigned_ANONYMOUS -} diff --git a/crypto/keys/testonly/keys.go b/crypto/keys/testonly/keys.go index 08223538e9..79d266273e 100644 --- a/crypto/keys/testonly/keys.go +++ b/crypto/keys/testonly/keys.go @@ -15,37 +15,136 @@ package testonly import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" "crypto/x509" + "encoding/asn1" + "errors" + "fmt" + "math/big" "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" + "github.com/google/trillian/crypto/keyspb" ) // MustMarshalPublicPEMToDER reads a PEM-encoded public key and returns it in DER encoding. // If an error occurs, it panics. -func MustMarshalPublicPEMToDER(pem string) []byte { - key, err := keys.NewFromPublicPEM(pem) +func MustMarshalPublicPEMToDER(keyPEM string) []byte { + key, err := pem.UnmarshalPublicKey(keyPEM) if err != nil { panic(err) } - der, err := x509.MarshalPKIXPublicKey(key) + keyDER, err := x509.MarshalPKIXPublicKey(key) if err != nil { panic(err) } - return der + return keyDER } // MustMarshalPrivatePEMToDER decrypts a PEM-encoded private key and returns it in DER encoding. // If an error occurs, it panics. -func MustMarshalPrivatePEMToDER(pem, password string) []byte { - key, err := keys.NewFromPrivatePEM(pem, password) +func MustMarshalPrivatePEMToDER(keyPEM, password string) []byte { + key, err := pem.UnmarshalPrivateKey(keyPEM, password) if err != nil { panic(err) } - der, err := keys.MarshalPrivateKey(key) + keyDER, err := der.MarshalPrivateKey(key) if err != nil { panic(err) } - return der + return keyDER +} + +// SignAndVerify exercises a signer by using it to generate a signature, and +// then verifies that this signature is correct. +func SignAndVerify(signer crypto.Signer, pubKey crypto.PublicKey) error { + hasher := crypto.SHA256 + digest := sha256.Sum256([]byte("test")) + signature, err := signer.Sign(rand.Reader, digest[:], hasher) + if err != nil { + return err + } + + switch pubKey := pubKey.(type) { + case *ecdsa.PublicKey: + return verifyECDSA(pubKey, digest[:], signature) + case *rsa.PublicKey: + return verifyRSA(pubKey, digest[:], signature, hasher, hasher) + default: + return fmt.Errorf("unknown public key type: %T", pubKey) + } +} + +func verifyECDSA(pubKey *ecdsa.PublicKey, digest, sig []byte) error { + var ecdsaSig struct { + R, S *big.Int + } + + rest, err := asn1.Unmarshal(sig, &ecdsaSig) + if err != nil { + return err + } + if len(rest) != 0 { + return fmt.Errorf("ECDSA signature %v bytes longer than expected", len(rest)) + } + + if !ecdsa.Verify(pubKey, digest, ecdsaSig.R, ecdsaSig.S) { + return errors.New("ECDSA signature failed verification") + } + return nil +} + +func verifyRSA(pubKey *rsa.PublicKey, digest, sig []byte, hasher crypto.Hash, opts crypto.SignerOpts) error { + if pssOpts, ok := opts.(*rsa.PSSOptions); ok { + return rsa.VerifyPSS(pubKey, hasher, digest, sig, pssOpts) + } + return rsa.VerifyPKCS1v15(pubKey, hasher, digest, sig) +} + +// CheckKeyMatchesSpec verifies that the key conforms to the specification. +// If it does not, an error is returned. +func CheckKeyMatchesSpec(key crypto.PrivateKey, spec *keyspb.Specification) error { + switch params := spec.Params.(type) { + case *keyspb.Specification_EcdsaParams: + if key, ok := key.(*ecdsa.PrivateKey); ok { + return checkEcdsaKeyMatchesParams(key, params.EcdsaParams) + } + return fmt.Errorf("%T, want *ecdsa.PrivateKey", key) + case *keyspb.Specification_RsaParams: + if key, ok := key.(*rsa.PrivateKey); ok { + return checkRsaKeyMatchesParams(key, params.RsaParams) + } + return fmt.Errorf("%T, want *rsa.PrivateKey", key) + } + + return fmt.Errorf("%T is not a supported keyspb.Specification.Params type", spec.Params) +} + +func checkEcdsaKeyMatchesParams(key *ecdsa.PrivateKey, params *keyspb.Specification_ECDSA) error { + wantCurve := keys.ECDSACurveFromParams(params) + if wantCurve.Params().Name != key.Params().Name { + return fmt.Errorf("ECDSA key on %v curve, want %v curve", key.Params().Name, wantCurve.Params().Name) + } + + return nil +} + +func checkRsaKeyMatchesParams(key *rsa.PrivateKey, params *keyspb.Specification_RSA) error { + wantBits := keys.DefaultRsaKeySizeInBits + if params.GetBits() != 0 { + wantBits = int(params.GetBits()) + } + + if got, want := key.N.BitLen(), wantBits; got != want { + return fmt.Errorf("%v-bit RSA key, want %v-bit", got, want) + } + + return nil } diff --git a/crypto/signatures.go b/crypto/signatures.go new file mode 100644 index 0000000000..9d3b7d156f --- /dev/null +++ b/crypto/signatures.go @@ -0,0 +1,36 @@ +// 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 ( + gocrypto "crypto" + "crypto/ecdsa" + "crypto/rsa" + + "github.com/google/trillian/crypto/sigpb" +) + +// SignatureAlgorithm returns the algorithm used for this public key. +// Only ECDSA and RSA keys are supported. Other key types will return sigpb.DigitallySigned_ANONYMOUS. +func SignatureAlgorithm(k gocrypto.PublicKey) sigpb.DigitallySigned_SignatureAlgorithm { + switch k.(type) { + case *ecdsa.PublicKey: + return sigpb.DigitallySigned_ECDSA + case *rsa.PublicKey: + return sigpb.DigitallySigned_RSA + } + + return sigpb.DigitallySigned_ANONYMOUS +} diff --git a/crypto/signatures_test.go b/crypto/signatures_test.go new file mode 100644 index 0000000000..43c01d3aae --- /dev/null +++ b/crypto/signatures_test.go @@ -0,0 +1,75 @@ +// 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/crypto/keys/pem" + "github.com/google/trillian/crypto/sigpb" +) + +const ( + ecdsaPublicKey = ` +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvuynpVdR+5xSNaVBb//1fqO6Nb/nC+WvRQ4bALzy4G+QbByvO1Qpm2eUzTdDUnsLN5hp3pIXYAmtjvjY1fFZEg== +-----END PUBLIC KEY-----` + + rsaPublicKey = ` +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMB4reLZhs+2ReYX01nZpqLBQ9uhcZvBmzH54RsZDTb5khw+luSXKbLKXxdbQfrsxURbeVdugDNnV897VI43znuiKJ19Y/XS3N5Z7Q97/GOxOxGFObP0DovCAPblxAMaQBb+U9jkVt/4bHcNIOTZl/lXgX+yp58lH5uPfDwav/hVNg7QkAW3BxQZ5wiLTTZUILoTMjax4R24pULlg/Wt/rT4bDj8rxUgYR60MuO93jdBtNGwmzdCYyk4cEmrPEgCueRC6jFafUzlLjvuX89ES9n98LxX+gBANA7RpVPkJd0kfWFHO1JRUEJr++WjU3x4la2Xs4tUNX4QBSJP4XEOXwIDAQAB +-----END PUBLIC KEY-----` + + dsaPublicKey = ` +-----BEGIN PUBLIC KEY----- +MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQDgLI6pXvqpcOY33lzeZrjUBHxphiz0I9VKF9vGpWymNfBptQ75bpQFe16jBjaOGwDImASHTp53XskQJLOXC4bZxoRUHsm8bHQVZHQhYgxn8ZDQX/40zOR1d73y1TXSiULo6rDKVlM+fFcm33tGv+ZOdfaIhW17c5jvDAy6UWqQakasvL+kfiejIDGHjLVFWwX0vLCG+pAomgO6snQHGcPhDO9uxEYPd9on7YTgBrpa2IcXk5jFeY8xOxMnMwoBojRvH97+ivdBR1yW8f+4FAGg5o1eFV5ZqoUAF8GO3BBEwluMGNeT7gMgl4PO8N8xBxJulHd3tLW5qkW0cBPwkbzzAiEAvdYeMPamsFAyd7s07dt78wxXyHGrwVl2AcQBo0QTATkCggEASH9Rp+EjNkL7uCqGJ78P4tjJM+2+xaEhZpJ/kTzq6DtdFhu5Rov6lN5NnZKPSUNYr9Vkmu88ru0iND1N37z0rJpImksXKxCv0AwBkwtqCwf9jjkTrZiGRzP8xf789wK+uG7Uud20ml9QzXKr9Af9WrRx3DtCq44PBaIlhPvpZS9znCZsuUZqYZFW3/oD4EhwPgVLSWeulh1t33ku3mYQwVS8ZTdJGPyFRoD1dcQ4EchR4ce0u0nTXlqErWhfnmb9msF6dFCV0Mx5yrqxkEHbJ/vZgB4zAdOke7XiJsWqIok/7IJpJuVOvkY9NHgBdlq3xU180+pEo2NrGm4pbrGm1wOCAQUAAoIBAAGbucHEfgtcu++OQQjYqneukv4zqcP/PCJTP+GuXen6SH25V2ZlHC88lG6qdZVBPWZidAb9BSoUQpW7BzauKRqH7rKOsIeqvEPCiWBKA781Zi5HAWGhC4INJJx54Q66F54DkGlTRVFkXlGpAIudhfAIG//MyO9TIsLSgRyqjKWVm+/XhWDIT5iMJZZ/IgmbICueaa7go8poHuTTyUDPHPIeL5d9Aru7qD4JtX+UVy6GYKhWx/guv+A7zyJ8d1kMLsmUAro80DLPDoais2I8YPpbu+xTSLLswIYddDdwg3P8mMAGzuWY/ZLumwpRr/fbI+t2Sm9KKGNGkGGIKAg43cs= +-----END PUBLIC KEY-----` +) + +func TestSignatureAlgorithm(t *testing.T) { + tests := []struct { + name string + keyPEM string + want sigpb.DigitallySigned_SignatureAlgorithm + }{ + { + name: "ECDSA", + keyPEM: ecdsaPublicKey, + want: sigpb.DigitallySigned_ECDSA, + }, + { + name: "RSA", + keyPEM: rsaPublicKey, + want: sigpb.DigitallySigned_RSA, + }, + { + name: "DSA", + keyPEM: dsaPublicKey, + want: sigpb.DigitallySigned_ANONYMOUS, + }, + } + + for _, test := range tests { + key, err := pem.UnmarshalPublicKey(test.keyPEM) + if err != nil { + t.Errorf("%v: Failed to load key: %v", test.name, err) + continue + } + + if got := SignatureAlgorithm(key); got != test.want { + t.Errorf("%v: SignatureAlgorithm(%v) = %v, want %v", test.name, key, got, test.want) + } + } +} diff --git a/crypto/signer.go b/crypto/signer.go index 264e983696..cc4cddcc8d 100644 --- a/crypto/signer.go +++ b/crypto/signer.go @@ -21,7 +21,6 @@ import ( "encoding/json" "github.com/benlaurie/objecthash/go/objecthash" - "github.com/google/trillian/crypto/keys" "github.com/google/trillian/crypto/sigpb" ) @@ -61,7 +60,7 @@ func (s *Signer) Sign(data []byte) (*sigpb.DigitallySigned, error) { } return &sigpb.DigitallySigned{ - SignatureAlgorithm: keys.SignatureAlgorithm(s.Public()), + SignatureAlgorithm: SignatureAlgorithm(s.Public()), HashAlgorithm: sigpbHashLookup[s.Hash], Signature: sig, }, nil diff --git a/crypto/signer_test.go b/crypto/signer_test.go index fb9d6c4117..a07a93f519 100644 --- a/crypto/signer_test.go +++ b/crypto/signer_test.go @@ -21,7 +21,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/trillian" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/sigpb" "github.com/google/trillian/testonly" ) @@ -43,13 +43,13 @@ func (i usesSHA256Hasher) String() string { } func TestSigner(t *testing.T) { - key, err := keys.NewFromPrivatePEM(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) + key, err := pem.UnmarshalPrivateKey(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) if err != nil { t.Fatalf("Failed to open test key, err=%v", err) } signer := NewSHA256Signer(key) - pk, err := keys.NewFromPublicPEM(testonly.DemoPublicKey) + pk, err := pem.UnmarshalPublicKey(testonly.DemoPublicKey) if err != nil { t.Fatalf("Failed to load public key, err=%v", err) } @@ -83,7 +83,7 @@ func TestSignerFails(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - key, err := keys.NewFromPrivatePEM(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) + key, err := pem.UnmarshalPrivateKey(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) if err != nil { t.Fatalf("Failed to load private key: %v", err) } @@ -100,7 +100,7 @@ func TestSignLogRootSignerFails(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - key, err := keys.NewFromPrivatePEM(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) + key, err := pem.UnmarshalPrivateKey(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) if err != nil { t.Fatalf("Failed to load public key: %v", err) } diff --git a/crypto/verifier.go b/crypto/verifier.go index 8d7e1187de..24bbf65057 100644 --- a/crypto/verifier.go +++ b/crypto/verifier.go @@ -25,7 +25,6 @@ import ( "math/big" "github.com/benlaurie/objecthash/go/objecthash" - "github.com/google/trillian/crypto/keys" "github.com/google/trillian/crypto/sigpb" ) @@ -54,7 +53,7 @@ func Verify(pub crypto.PublicKey, data []byte, sig *sigpb.DigitallySigned) error return errors.New("signature is nil") } - if got, want := sig.SignatureAlgorithm, keys.SignatureAlgorithm(pub); got != want { + if got, want := sig.SignatureAlgorithm, SignatureAlgorithm(pub); got != want { return fmt.Errorf("signature algorithm does not match public key, got:%v, want:%v", got, want) } diff --git a/crypto/verifier_test.go b/crypto/verifier_test.go index 93c71eddbb..dbd9ef0280 100644 --- a/crypto/verifier_test.go +++ b/crypto/verifier_test.go @@ -18,7 +18,7 @@ import ( "testing" "github.com/google/trillian" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/sigpb" "github.com/google/trillian/testonly" ) @@ -59,7 +59,7 @@ func TestSignVerify(t *testing.T) { }, } { - key, err := keys.NewFromPrivatePEM(test.pem, test.password) + key, err := pem.UnmarshalPrivateKey(test.pem, test.password) if err != nil { t.Errorf("%s: LoadPrivateKey(_, %q)=%v, want nil", test.name, test.password, err) continue @@ -84,7 +84,7 @@ func TestSignVerify(t *testing.T) { } func TestSignVerifyObject(t *testing.T) { - key, err := keys.NewFromPrivatePEM(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) + key, err := pem.UnmarshalPrivateKey(testonly.DemoPrivateKey, testonly.DemoPrivateKeyPass) if err != nil { t.Fatalf("Failed to open test key, err=%v", err) } diff --git a/integration/maptest/map_test.go b/integration/maptest/map_test.go index b99e3ad44f..41af958541 100644 --- a/integration/maptest/map_test.go +++ b/integration/maptest/map_test.go @@ -23,7 +23,7 @@ import ( "testing" "github.com/google/trillian" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/der" "github.com/google/trillian/merkle" "github.com/google/trillian/merkle/hashers" "github.com/google/trillian/testonly" @@ -165,9 +165,9 @@ func TestLeafHistory(t *testing.T) { t.Errorf("%v: newTreeWithHasher(%v): %v", tc.desc, hashStrategy, err) continue } - pubKey, err := keys.NewFromPublicDER(tree.GetPublicKey().GetDer()) + pubKey, err := der.UnmarshalPublicKey(tree.GetPublicKey().GetDer()) if err != nil { - t.Errorf("%v: NewFromPublicDER(%v): %v", tc.desc, hashStrategy, err) + t.Errorf("%v: UnmarshalPublicKey(%v): %v", tc.desc, hashStrategy, err) continue } @@ -249,9 +249,9 @@ func TestInclusion(t *testing.T) { t.Errorf("%v: newTreeWithHasher(%v): %v", tc.desc, hashStrategy, err) continue } - pubKey, err := keys.NewFromPublicDER(tree.GetPublicKey().GetDer()) + pubKey, err := der.UnmarshalPublicKey(tree.GetPublicKey().GetDer()) if err != nil { - t.Errorf("%v: NewFromPublicDER(%v): %v", tc.desc, hashStrategy, err) + t.Errorf("%v: UnmarshalPublicKey(%v): %v", tc.desc, hashStrategy, err) continue } @@ -332,7 +332,7 @@ func TestInclusionBatch(t *testing.T) { func RunMapBatchTest(ctx context.Context, env *integration.MapEnv, tree *trillian.Tree, batchSize, numBatches int) error { // Parse variables from tree - pubKey, err := keys.NewFromPublicDER(tree.GetPublicKey().GetDer()) + pubKey, err := der.UnmarshalPublicKey(tree.GetPublicKey().GetDer()) if err != nil { return err } diff --git a/log/sequencer_test.go b/log/sequencer_test.go index 674ede149e..8d11ca99a2 100644 --- a/log/sequencer_test.go +++ b/log/sequencer_test.go @@ -26,7 +26,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/trillian" "github.com/google/trillian/crypto" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/sigpb" "github.com/google/trillian/merkle/rfc6962" "github.com/google/trillian/quota" @@ -179,12 +179,12 @@ func fakeTime() time.Time { } func newSignerWithFixedSig(sig *sigpb.DigitallySigned) (gocrypto.Signer, error) { - key, err := keys.NewFromPublicPEM(testonly.DemoPublicKey) + key, err := pem.UnmarshalPublicKey(testonly.DemoPublicKey) if err != nil { return nil, err } - if got, want := sig.GetSignatureAlgorithm(), keys.SignatureAlgorithm(key); got != want { + if got, want := sig.GetSignatureAlgorithm(), crypto.SignatureAlgorithm(key); got != want { return nil, fmt.Errorf("signature algorithm (%v) does not match key (%v)", got, want) } @@ -192,7 +192,7 @@ func newSignerWithFixedSig(sig *sigpb.DigitallySigned) (gocrypto.Signer, error) } func newSignerWithErr(signErr error) (gocrypto.Signer, error) { - key, err := keys.NewFromPublicPEM(testonly.DemoPublicKey) + key, err := pem.UnmarshalPublicKey(testonly.DemoPublicKey) if err != nil { return nil, err } diff --git a/server/admin/admin_server.go b/server/admin/admin_server.go index 07251af885..7993af6eac 100644 --- a/server/admin/admin_server.go +++ b/server/admin/admin_server.go @@ -22,7 +22,7 @@ import ( "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/empty" "github.com/google/trillian" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/extension" "github.com/google/trillian/merkle/hashers" @@ -133,7 +133,7 @@ func (s *Server) CreateTree(ctx context.Context, request *trillian.CreateTreeReq return nil, status.Errorf(codes.InvalidArgument, "failed to create signer for tree: %v", err.Error()) } - if treeSigAlgo, keySigAlgo := tree.GetSignatureAlgorithm(), keys.SignatureAlgorithm(signer.Public()); treeSigAlgo != keySigAlgo { + if treeSigAlgo, keySigAlgo := tree.GetSignatureAlgorithm(), crypto.SignatureAlgorithm(signer.Public()); treeSigAlgo != keySigAlgo { return nil, status.Errorf(codes.InvalidArgument, "tree.signature_algorithm = %v, but SignatureAlgorithm(tree.private_key) = %v", treeSigAlgo, keySigAlgo) } diff --git a/server/admin/admin_server_test.go b/server/admin/admin_server_test.go index 8b70cf7740..47dbda70c0 100644 --- a/server/admin/admin_server_test.go +++ b/server/admin/admin_server_test.go @@ -30,6 +30,7 @@ import ( "github.com/golang/protobuf/ptypes/any" "github.com/google/trillian" "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/der" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/crypto/sigpb" "github.com/google/trillian/extension" @@ -439,7 +440,7 @@ func TestServer_CreateTree(t *testing.T) { continue } - keyDER, err := keys.MarshalPrivateKey(privateKey) + keyDER, err := der.MarshalPrivateKey(privateKey) if err != nil { t.Errorf("%v: failed to marshal test private key as DER: %v", test.desc, err) continue diff --git a/server/sequencer_manager_test.go b/server/sequencer_manager_test.go index 3c194524af..41f81300f6 100644 --- a/server/sequencer_manager_test.go +++ b/server/sequencer_manager_test.go @@ -24,7 +24,9 @@ import ( "github.com/golang/mock/gomock" "github.com/golang/protobuf/ptypes" "github.com/google/trillian" + tcrypto "github.com/google/trillian/crypto" "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/sigpb" "github.com/google/trillian/extension" "github.com/google/trillian/merkle/rfc6962" @@ -84,12 +86,12 @@ const writeRev = int64(24) // newSignerWithFixedSig returns a fake signer that always returns the specified signature. func newSignerWithFixedSig(sig *sigpb.DigitallySigned) (crypto.Signer, error) { - key, err := keys.NewFromPublicPEM(testonly.DemoPublicKey) + key, err := pem.UnmarshalPublicKey(testonly.DemoPublicKey) if err != nil { return nil, err } - if keys.SignatureAlgorithm(key) != sig.GetSignatureAlgorithm() { + if tcrypto.SignatureAlgorithm(key) != sig.GetSignatureAlgorithm() { return nil, errors.New("signature algorithm does not match demo public key") } diff --git a/storage/tools/dump_tree/dumplib/dumplib.go b/storage/tools/dump_tree/dumplib/dumplib.go index e7ff375a0c..1e30be6162 100644 --- a/storage/tools/dump_tree/dumplib/dumplib.go +++ b/storage/tools/dump_tree/dumplib/dumplib.go @@ -29,13 +29,14 @@ import ( "strings" "time" - "github.com/gogo/protobuf/proto" "github.com/golang/glog" + "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/any" "github.com/google/trillian" tc "github.com/google/trillian/crypto" - "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/der" + "github.com/google/trillian/crypto/keys/pem" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/crypto/sigpb" "github.com/google/trillian/log" @@ -143,11 +144,11 @@ func sequence(treeID int64, seq *log.Sequencer, count, batchSize int) { } func getPrivateKey(pemPath, pemPassword string) (*any.Any, crypto.Signer) { - pemSigner, err := keys.NewFromPrivatePEM(pemPath, pemPassword) + pemSigner, err := pem.UnmarshalPrivateKey(pemPath, pemPassword) if err != nil { - glog.Fatalf("NewFromPrivatePEM(): %v", err) + glog.Fatalf("UnmarshalPrivateKey(): %v", err) } - pemDer, err := keys.MarshalPrivateKey(pemSigner) + pemDer, err := der.MarshalPrivateKey(pemSigner) if err != nil { glog.Fatalf("MarshalPrivateKey(): %v", err) } @@ -159,17 +160,17 @@ func getPrivateKey(pemPath, pemPassword string) (*any.Any, crypto.Signer) { return anyPrivKey, pemSigner } -func getPublicKey(pem string) []byte { - key, err := keys.NewFromPublicPEM(pem) +func getPublicKey(keyPEM string) []byte { + key, err := pem.UnmarshalPublicKey(keyPEM) if err != nil { panic(err) } - der, err := x509.MarshalPKIXPublicKey(key) + keyDER, err := x509.MarshalPKIXPublicKey(key) if err != nil { panic(err) } - return der + return keyDER } func createTree(as storage.AdminStorage) (*trillian.Tree, crypto.Signer) { diff --git a/testonly/integration/logenv.go b/testonly/integration/logenv.go index f211956d48..9098966d9e 100644 --- a/testonly/integration/logenv.go +++ b/testonly/integration/logenv.go @@ -29,6 +29,7 @@ import ( "github.com/golang/protobuf/ptypes" "github.com/google/trillian" "github.com/google/trillian/crypto/keys" + "github.com/google/trillian/crypto/keys/pem" ktestonly "github.com/google/trillian/crypto/keys/testonly" "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/extension" @@ -145,7 +146,7 @@ func NewLogEnvWithRegistry(ctx context.Context, numSequencers int, testID string return nil, err } - publicKey, err := keys.NewFromPublicPEM(publicKey) + publicKey, err := pem.UnmarshalPublicKey(publicKey) if err != nil { cancel() return nil, err diff --git a/trees/trees.go b/trees/trees.go index ca5244a3d0..65792edf1c 100644 --- a/trees/trees.go +++ b/trees/trees.go @@ -127,7 +127,7 @@ func Signer(ctx context.Context, sf keys.SignerFactory, tree *trillian.Tree) (*t return nil, err } - if keys.SignatureAlgorithm(signer.Public()) != tree.SignatureAlgorithm { + if tcrypto.SignatureAlgorithm(signer.Public()) != tree.SignatureAlgorithm { return nil, fmt.Errorf("%s signature not supported by signer of type %T", tree.SignatureAlgorithm, signer) }