diff --git a/personalities/sctfe/config.go b/personalities/sctfe/config.go index d6ca175e..8e524c5b 100644 --- a/personalities/sctfe/config.go +++ b/personalities/sctfe/config.go @@ -18,53 +18,42 @@ import ( "crypto" "errors" "fmt" - "os" "strings" "time" "github.com/google/certificate-transparency-go/x509" - "github.com/google/trillian/crypto/keyspb" - "github.com/transparency-dev/trillian-tessera/personalities/sctfe/configpb" - "google.golang.org/protobuf/encoding/prototext" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" "k8s.io/klog/v2" ) // ValidatedLogConfig represents the LogConfig with the information that has // been successfully parsed as a result of validating it. type ValidatedLogConfig struct { - Config *LogConfig - PubKey crypto.PublicKey - PrivKey proto.Message + // Origin identifies the log. It will be used in its checkpoint, and + // is also its submission prefix, as per https://c2sp.org/static-ct-api. + Origin string + // Used to sign the checkpoint and SCTs. + // TODO(phboneff): check that this is RSA or ECDSA only. + Signer crypto.Signer // If set, ExtKeyUsages will restrict the set of such usages that the // server will accept. By default all are accepted. The values specified // must be ones known to the x509 package. - KeyUsages []x509.ExtKeyUsage + KeyUsages []x509.ExtKeyUsage + // NotAfterStart defines the start of the range of acceptable NotAfter + // values, inclusive. + // Leaving this unset implies no lower bound to the range. NotAfterStart *time.Time + // NotAfterLimit defines the end of the range of acceptable NotAfter values, + // exclusive. + // Leaving this unset implies no upper bound to the range. NotAfterLimit *time.Time -} - -// TODO(phboneff): inline this in ValidatedLogConfig and probably inline things further -// TODO(phboneff): edit comments -type LogConfig struct { - // origin identifies the log. It will be used in its checkpoint, and - // is also its submission prefix, as per https://c2sp.org/static-ct-api - Origin string // Path to the file containing root certificates that are acceptable to the // log. The certs are served through get-roots endpoint. RootsPemFile string - // The private key used for signing Checkpoints or SCTs. - PrivateKey *anypb.Any - // The public key matching the above private key (if both are present). - // It can be specified for the convenience of test tools, but it not used - // by the server. - PublicKey *keyspb.PublicKey - // If reject_expired is true then the certificate validity period will be + // If RejectExpired is true then the certificate validity period will be // checked against the current time during the validation of submissions. // This will cause expired certificates to be rejected. RejectExpired bool - // If reject_unexpired is true then CTFE rejects certificates that are either + // If RejectUnexpired is true then CTFE rejects certificates that are either // currently valid or not yet valid. RejectUnexpired bool // A list of X.509 extension OIDs, in dotted string form (e.g. "2.3.4.5") @@ -72,25 +61,6 @@ type LogConfig struct { RejectExtensions []string } -// LogConfigFromFile creates a LogConfig options from the given -// filename, which should contain text or binary-encoded protobuf configuration -// data. -func LogConfigFromFile(filename string) (*configpb.LogConfig, error) { - cfgBytes, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - - var cfg configpb.LogConfig - if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil { - if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil { - return nil, fmt.Errorf("failed to parse LogConfig from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr) - } - } - - return &cfg, nil -} - // ValidateLogConfig checks that a single log config is valid. In particular: // - A log has a private, and optionally a public key (both valid). // - Each of NotBeforeStart and NotBeforeLimit, if set, is a valid timestamp @@ -98,7 +68,7 @@ func LogConfigFromFile(filename string) (*configpb.LogConfig, error) { // - Merge delays (if present) are correct. // // Returns the validated structures (useful to avoid double validation). -func ValidateLogConfig(cfg *configpb.LogConfig, origin string, projectID string, bucket string, spannerDB string, rootsPemFile string, rejectExpired bool, rejectUnexpired bool, extKeyUsages string, rejectExtensions string, notAfterStart *time.Time, notAfterLimit *time.Time) (*ValidatedLogConfig, error) { +func ValidateLogConfig(origin string, projectID string, bucket string, spannerDB string, rootsPemFile string, rejectExpired bool, rejectUnexpired bool, extKeyUsages string, rejectExtensions string, notAfterStart *time.Time, notAfterLimit *time.Time, signer crypto.Signer) (*ValidatedLogConfig, error) { if origin == "" { return nil, errors.New("empty origin") } @@ -116,6 +86,10 @@ func ValidateLogConfig(cfg *configpb.LogConfig, origin string, projectID string, return nil, errors.New("empty spannerDB") } + if rootsPemFile == "" { + return nil, errors.New("empty rootsPemFile") + } + lExtKeyUsages := []string{} lRejectExtensions := []string{} if extKeyUsages != "" { @@ -126,36 +100,15 @@ func ValidateLogConfig(cfg *configpb.LogConfig, origin string, projectID string, } vCfg := ValidatedLogConfig{ - Config: &LogConfig{ - Origin: origin, - RootsPemFile: rootsPemFile, - PrivateKey: cfg.PrivateKey, - PublicKey: cfg.PublicKey, - RejectExpired: rejectExpired, - RejectUnexpired: rejectUnexpired, - RejectExtensions: lRejectExtensions, - }, - NotAfterStart: notAfterStart, - NotAfterLimit: notAfterLimit, - } - - // Validate the public key. - if pubKey := cfg.PublicKey; pubKey != nil { - var err error - if vCfg.PubKey, err = x509.ParsePKIXPublicKey(pubKey.Der); err != nil { - return nil, fmt.Errorf("x509.ParsePKIXPublicKey: %w", err) - } - } - - // Validate the private key. - if cfg.PrivateKey == nil { - return nil, errors.New("empty private key") - } - privKey, err := cfg.PrivateKey.UnmarshalNew() - if err != nil { - return nil, fmt.Errorf("invalid private key: %v", err) + Origin: origin, + RootsPemFile: rootsPemFile, + RejectExpired: rejectExpired, + RejectUnexpired: rejectUnexpired, + RejectExtensions: lRejectExtensions, + NotAfterStart: notAfterStart, + NotAfterLimit: notAfterLimit, + Signer: signer, } - vCfg.PrivKey = privKey if rejectExpired && rejectUnexpired { return nil, errors.New("rejecting all certificates") diff --git a/personalities/sctfe/config_test.go b/personalities/sctfe/config_test.go index bd6373dd..042e1547 100644 --- a/personalities/sctfe/config_test.go +++ b/personalities/sctfe/config_test.go @@ -15,240 +15,191 @@ package sctfe import ( - "fmt" + "crypto" "strings" "testing" "time" - "github.com/google/trillian/crypto/keyspb" - "github.com/transparency-dev/trillian-tessera/personalities/sctfe/configpb" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" + "github.com/google/trillian/crypto/keys/pem" ) -func mustMarshalAny(pb proto.Message) *anypb.Any { - ret, err := anypb.New(pb) +func TestValidateLogConfig(t *testing.T) { + signer, err := pem.ReadPrivateKeyFile("./testdata/ct-http-server.privkey.pem", "dirk") if err != nil { - panic(fmt.Sprintf("MarshalAny failed: %v", err)) + t.Fatalf("Can't open key: %v", err) } - return ret -} - -func TestValidateLogConfig(t *testing.T) { - privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "../testdata/ct-http-server.privkey.pem", Password: "dirk"}) t100 := time.Unix(100, 0) t200 := time.Unix(200, 0) for _, tc := range []struct { desc string - cfg *configpb.LogConfig origin string projectID string bucket string spannerDB string wantErr string + rootsPemFile string rejectExpired bool rejectUnexpired bool extKeyUsages string notAfterStart *time.Time notAfterLimit *time.Time + signer crypto.Signer }{ { desc: "empty-origin", wantErr: "empty origin", - cfg: &configpb.LogConfig{}, projectID: "project", bucket: "bucket", spannerDB: "spanner", }, { - desc: "empty-private-key", - wantErr: "empty private key", - cfg: &configpb.LogConfig{}, + desc: "empty-projectID", + wantErr: "empty projectID", origin: "testlog", - projectID: "project", + projectID: "", bucket: "bucket", spannerDB: "spanner", + signer: signer, }, { - desc: "invalid-private-key", - wantErr: "invalid private key", - cfg: &configpb.LogConfig{ - PrivateKey: &anypb.Any{}, - }, + desc: "empty-bucket", + wantErr: "empty bucket", origin: "testlog", projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - }, - { - desc: "empty-projectID", - wantErr: "empty projectID", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, - origin: "testlog", - projectID: "", - bucket: "bucket", + bucket: "", spannerDB: "spanner", + signer: signer, }, { - desc: "empty-bucket", - wantErr: "empty bucket", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "empty-spannerDB", + wantErr: "empty spannerDB", origin: "testlog", projectID: "project", - bucket: "", - spannerDB: "spanner", + bucket: "bucket", + spannerDB: "", + signer: signer, }, { - desc: "empty-spannerDB", - wantErr: "empty spannerDB", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "empty-rootsPemFile", + wantErr: "empty rootsPemFile", origin: "testlog", projectID: "project", bucket: "bucket", - spannerDB: "", + spannerDB: "spanner", + signer: signer, }, { - desc: "rejecting-all", - wantErr: "rejecting all certificates", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "rejecting-all", + wantErr: "rejecting all certificates", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", rejectExpired: true, rejectUnexpired: true, + signer: signer, }, { - desc: "unknown-ext-key-usage-1", - wantErr: "unknown extended key usage", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "unknown-ext-key-usage-1", + wantErr: "unknown extended key usage", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", extKeyUsages: "wrong_usage", + signer: signer, }, { - desc: "unknown-ext-key-usage-2", - wantErr: "unknown extended key usage", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "unknown-ext-key-usage-2", + wantErr: "unknown extended key usage", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", extKeyUsages: "ClientAuth,ServerAuth,TimeStomping", + signer: signer, }, { - desc: "unknown-ext-key-usage-3", - wantErr: "unknown extended key usage", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "unknown-ext-key-usage-3", + wantErr: "unknown extended key usage", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", extKeyUsages: "Any ", + signer: signer, }, { - desc: "limit-before-start", - wantErr: "limit before start", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "limit-before-start", + wantErr: "limit before start", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", notAfterStart: &t200, notAfterLimit: &t100, + signer: signer, }, { - desc: "ok", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - }, - { - // Note: Substituting an arbitrary proto.Message as a PrivateKey will not - // fail the validation because the actual key loading happens at runtime. - // TODO(pavelkalinnikov): Decouple key protos validation and loading, and - // make this test fail. - desc: "ok-not-a-key", - cfg: &configpb.LogConfig{ - PrivateKey: mustMarshalAny(&configpb.LogConfig{}), - }, - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", + desc: "ok", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", + signer: signer, }, { - desc: "ok-ext-key-usages", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "ok-ext-key-usages", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", extKeyUsages: "ServerAuth,ClientAuth,OCSPSigning", + signer: signer, }, { - desc: "ok-start-timestamp", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "ok-start-timestamp", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", notAfterStart: &t100, + signer: signer, }, { - desc: "ok-limit-timestamp", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "ok-limit-timestamp", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", notAfterStart: &t200, + signer: signer, }, { - desc: "ok-range-timestamp", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "ok-range-timestamp", origin: "testlog", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/fake-ca.cert", notAfterStart: &t100, notAfterLimit: &t200, + signer: signer, }, } { t.Run(tc.desc, func(t *testing.T) { - vc, err := ValidateLogConfig(tc.cfg, tc.origin, tc.projectID, tc.bucket, tc.spannerDB, "", tc.rejectExpired, tc.rejectUnexpired, tc.extKeyUsages, "", tc.notAfterStart, tc.notAfterLimit) + vc, err := ValidateLogConfig(tc.origin, tc.projectID, tc.bucket, tc.spannerDB, tc.rootsPemFile, tc.rejectExpired, tc.rejectUnexpired, tc.extKeyUsages, "", tc.notAfterStart, tc.notAfterLimit, signer) if len(tc.wantErr) == 0 && err != nil { t.Errorf("ValidateLogConfig()=%v, want nil", err) } diff --git a/personalities/sctfe/configpb/config.pb.go b/personalities/sctfe/configpb/config.pb.go deleted file mode 100644 index 6bc0e09e..00000000 --- a/personalities/sctfe/configpb/config.pb.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2017 Google LLC. 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. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.27.3 -// source: configpb/config.proto - -package configpb - -import ( - keyspb "github.com/google/trillian/crypto/keyspb" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - anypb "google.golang.org/protobuf/types/known/anypb" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// LogConfig describes the configuration options for a log instance. -// -// NEXT_ID: 15 -type LogConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The private key used for signing Checkpoints or SCTs. - PrivateKey *anypb.Any `protobuf:"bytes,3,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"` - // The public key matching the above private key (if both are present). - // It can be specified for the convenience of test tools, but it not used - // by the server. - PublicKey *keyspb.PublicKey `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` -} - -func (x *LogConfig) Reset() { - *x = LogConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_configpb_config_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *LogConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LogConfig) ProtoMessage() {} - -func (x *LogConfig) ProtoReflect() protoreflect.Message { - mi := &file_configpb_config_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LogConfig.ProtoReflect.Descriptor instead. -func (*LogConfig) Descriptor() ([]byte, []int) { - return file_configpb_config_proto_rawDescGZIP(), []int{0} -} - -func (x *LogConfig) GetPrivateKey() *anypb.Any { - if x != nil { - return x.PrivateKey - } - return nil -} - -func (x *LogConfig) GetPublicKey() *keyspb.PublicKey { - if x != nil { - return x.PublicKey - } - return nil -} - -var File_configpb_config_proto protoreflect.FileDescriptor - -var file_configpb_config_proto_rawDesc = []byte{ - 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, - 0x62, 0x1a, 0x1a, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, - 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, - 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x74, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, - 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x0a, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x62, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x42, 0x4b, - 0x5a, 0x49, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x74, 0x72, - 0x69, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x2d, 0x74, 0x65, 0x73, 0x73, 0x65, 0x72, 0x61, 0x2f, 0x70, - 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2f, 0x73, 0x63, 0x74, - 0x66, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_configpb_config_proto_rawDescOnce sync.Once - file_configpb_config_proto_rawDescData = file_configpb_config_proto_rawDesc -) - -func file_configpb_config_proto_rawDescGZIP() []byte { - file_configpb_config_proto_rawDescOnce.Do(func() { - file_configpb_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_configpb_config_proto_rawDescData) - }) - return file_configpb_config_proto_rawDescData -} - -var file_configpb_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_configpb_config_proto_goTypes = []any{ - (*LogConfig)(nil), // 0: configpb.LogConfig - (*anypb.Any)(nil), // 1: google.protobuf.Any - (*keyspb.PublicKey)(nil), // 2: keyspb.PublicKey -} -var file_configpb_config_proto_depIdxs = []int32{ - 1, // 0: configpb.LogConfig.private_key:type_name -> google.protobuf.Any - 2, // 1: configpb.LogConfig.public_key:type_name -> keyspb.PublicKey - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name -} - -func init() { file_configpb_config_proto_init() } -func file_configpb_config_proto_init() { - if File_configpb_config_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_configpb_config_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*LogConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_configpb_config_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_configpb_config_proto_goTypes, - DependencyIndexes: file_configpb_config_proto_depIdxs, - MessageInfos: file_configpb_config_proto_msgTypes, - }.Build() - File_configpb_config_proto = out.File - file_configpb_config_proto_rawDesc = nil - file_configpb_config_proto_goTypes = nil - file_configpb_config_proto_depIdxs = nil -} diff --git a/personalities/sctfe/configpb/config.proto b/personalities/sctfe/configpb/config.proto deleted file mode 100644 index 51cd0cc5..00000000 --- a/personalities/sctfe/configpb/config.proto +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2017 Google LLC. 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. - -syntax = "proto3"; - -option go_package = "github.com/transparency-dev/trillian-tessera/personalities/sctfe/configpb"; - -package configpb; - -import "crypto/keyspb/keyspb.proto"; -import "google/protobuf/any.proto"; - -// LogConfig describes the configuration options for a log instance. -// -// NEXT_ID: 15 -message LogConfig { - // The private key used for signing Checkpoints or SCTs. - google.protobuf.Any private_key = 3; - // The public key matching the above private key (if both are present). - // It can be specified for the convenience of test tools, but it not used - // by the server. - keyspb.PublicKey public_key = 4; -} diff --git a/personalities/sctfe/ct_server_gcp/main.go b/personalities/sctfe/ct_server_gcp/main.go index 819c0819..9265716e 100644 --- a/personalities/sctfe/ct_server_gcp/main.go +++ b/personalities/sctfe/ct_server_gcp/main.go @@ -17,7 +17,6 @@ package main import ( "context" - "crypto" "crypto/tls" "flag" "fmt" @@ -29,11 +28,7 @@ import ( "syscall" "time" - "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/keys/pkcs11" - "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/monitoring/opencensus" "github.com/google/trillian/monitoring/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -45,7 +40,6 @@ import ( gcpSCTFE "github.com/transparency-dev/trillian-tessera/personalities/sctfe/storage/gcp" gcpTessera "github.com/transparency-dev/trillian-tessera/storage/gcp" "golang.org/x/mod/sumdb/note" - "google.golang.org/protobuf/proto" "k8s.io/klog/v2" ) @@ -59,17 +53,16 @@ var ( notAfterStart timestampFlag notAfterLimit timestampFlag - httpEndpoint = flag.String("http_endpoint", "localhost:6962", "Endpoint for HTTP (host:port).") - tlsCert = flag.String("tls_certificate", "", "Path to server TLS certificate.") - tlsKey = flag.String("tls_key", "", "Path to server TLS private key.") - metricsEndpoint = flag.String("metrics_endpoint", "", "Endpoint for serving metrics; if left empty, metrics will be visible on --http_endpoint.") + httpEndpoint = flag.String("http_endpoint", "localhost:6962", "Endpoint for HTTP (host:port).") + tlsCert = flag.String("tls_certificate", "", "Path to server TLS certificate.") + tlsKey = flag.String("tls_key", "", "Path to server TLS private key.") + metricsEndpoint = flag.String("metrics_endpoint", "", "Endpoint for serving metrics; if left empty, metrics will be visible on --http_endpoint.") + // TODO(phboneff): delete / rename rpcDeadline = flag.Duration("rpc_deadline", time.Second*10, "Deadline for backend RPC requests.") - logConfig = flag.String("log_config", "", "File holding log config in text proto format.") maskInternalErrors = flag.Bool("mask_internal_errors", false, "Don't return error strings with Internal Server Error HTTP responses.") tracing = flag.Bool("tracing", false, "If true opencensus Stackdriver tracing will be enabled. See https://opencensus.io/.") tracingProjectID = flag.String("tracing_project_id", "", "project ID to pass to stackdriver. Can be empty for GCP, consult docs for other platforms.") tracingPercent = flag.Int("tracing_percent", 0, "Percent of requests to be traced. Zero is a special case to use the DefaultSampler.") - pkcs11ModulePath = flag.String("pkcs11_module_path", "", "Path to the PKCS#11 module to use for keys that use the PKCS#11 interface.") dedupPath = flag.String("dedup_path", "", "Path to the deduplication database.") origin = flag.String("origin", "", "Origin of the log, for checkpoints and the monitoring prefix.") projectID = flag.String("project_id", "", "GCP ProjectID.") @@ -80,6 +73,8 @@ var ( rejectUnexpired = flag.Bool("reject_unexpired", false, "If true then CTFE rejects certificates that are either currently valid or not yet valid.") extKeyUsages = flag.String("ext_key_usages", "", "If set, will restrict the set of such usages that the server will accept. By default all are accepted. The values specified must be ones known to the x509 package.") rejectExtensions = flag.String("reject_extension", "", "A list of X.509 extension OIDs, in dotted string form (e.g. '2.3.4.5') which, if present, should cause submissions to be rejected.") + privKey = flag.String("private_key", "", "Path to a private key .der file. Used to sign checkpoints and SCTs.") + privKeyPass = flag.String("password", "", "private_key password.") ) // nolint:staticcheck @@ -88,21 +83,13 @@ func main() { flag.Parse() ctx := context.Background() - keys.RegisterHandler(&keyspb.PEMKeyFile{}, pem.FromProto) - keys.RegisterHandler(&keyspb.PrivateKey{}, der.FromProto) - keys.RegisterHandler(&keyspb.PKCS11Config{}, func(ctx context.Context, pb proto.Message) (crypto.Signer, error) { - if cfg, ok := pb.(*keyspb.PKCS11Config); ok { - return pkcs11.FromConfig(*pkcs11ModulePath, cfg) - } - return nil, fmt.Errorf("pkcs11: got %T, want *keyspb.PKCS11Config", pb) - }) - - cfg, err := sctfe.LogConfigFromFile(*logConfig) + // TODO(phboneff): move to something else, like KMS + signer, err := pem.ReadPrivateKeyFile(*privKey, *privKeyPass) if err != nil { - klog.Exitf("Failed to read config: %v", err) + klog.Exitf("Can't open key: %v", err) } - vCfg, err := sctfe.ValidateLogConfig(cfg, *origin, *projectID, *bucket, *spannerDB, *rootsPemFile, *rejectExpired, *rejectUnexpired, *extKeyUsages, *rejectExtensions, notAfterStart.t, notAfterLimit.t) + vCfg, err := sctfe.ValidateLogConfig(*origin, *projectID, *bucket, *spannerDB, *rootsPemFile, *rejectExpired, *rejectUnexpired, *extKeyUsages, *rejectExtensions, notAfterStart.t, notAfterLimit.t, signer) if err != nil { klog.Exitf("Invalid config: %v", err) } diff --git a/personalities/sctfe/handlers.go b/personalities/sctfe/handlers.go index 648aad2f..f776c17b 100644 --- a/personalities/sctfe/handlers.go +++ b/personalities/sctfe/handlers.go @@ -216,8 +216,7 @@ func newLogInfo( timeSource TimeSource, storage Storage, ) *logInfo { - vCfg := instanceOpts.Validated - cfg := vCfg.Config + cfg := instanceOpts.Validated li := &logInfo{ LogOrigin: cfg.Origin, diff --git a/personalities/sctfe/handlers_test.go b/personalities/sctfe/handlers_test.go index c1d92d0a..3bb0873c 100644 --- a/personalities/sctfe/handlers_test.go +++ b/personalities/sctfe/handlers_test.go @@ -75,9 +75,8 @@ func setupTest(t *testing.T, pemRoots []string, signer crypto.Signer) handlerTes rejectExpired: false, } - cfg := &LogConfig{Origin: "example.com"} - vCfg := &ValidatedLogConfig{Config: cfg} - iOpts := InstanceOptions{Validated: vCfg, Deadline: time.Millisecond * 500, MetricFactory: monitoring.InertMetricFactory{}, RequestLog: new(DefaultRequestLog)} + cfg := &ValidatedLogConfig{Origin: "example.com"} + iOpts := InstanceOptions{Validated: cfg, Deadline: time.Millisecond * 500, MetricFactory: monitoring.InertMetricFactory{}, RequestLog: new(DefaultRequestLog)} info.li = newLogInfo(iOpts, vOpts, signer, fakeTimeSource, info.storage) for _, pemRoot := range pemRoots { diff --git a/personalities/sctfe/instance.go b/personalities/sctfe/instance.go index f7a43404..1a7d89d5 100644 --- a/personalities/sctfe/instance.go +++ b/personalities/sctfe/instance.go @@ -17,8 +17,6 @@ package sctfe import ( "context" "crypto" - "crypto/ecdsa" - "errors" "fmt" "strconv" "strings" @@ -26,7 +24,6 @@ import ( "github.com/google/certificate-transparency-go/asn1" "github.com/google/certificate-transparency-go/x509util" - "github.com/google/trillian/crypto/keys" "github.com/google/trillian/monitoring" "golang.org/x/mod/sumdb/note" ) @@ -69,23 +66,7 @@ func (i *Instance) GetPublicKey() crypto.PublicKey { // configuration, and returns an object containing a set of handlers for this // log, and an STH getter. func SetUpInstance(ctx context.Context, opts InstanceOptions) (*Instance, error) { - logInfo, err := setUpLogInfo(ctx, opts) - if err != nil { - return nil, err - } - handlers := logInfo.Handlers(opts.Validated.Config.Origin) - return &Instance{Handlers: handlers, li: logInfo}, nil -} - -func setUpLogInfo(ctx context.Context, opts InstanceOptions) (*logInfo, error) { - vCfg := opts.Validated - cfg := vCfg.Config - - // TODO(phboneff): move to ValidateLogConfig - // Check config validity. - if len(cfg.RootsPemFile) == 0 { - return nil, errors.New("need to specify RootsPemFile") - } + cfg := opts.Validated // Load the trusted roots. roots := x509util.NewPEMCertPool() @@ -93,44 +74,26 @@ func setUpLogInfo(ctx context.Context, opts InstanceOptions) (*logInfo, error) { return nil, fmt.Errorf("failed to read trusted roots: %v", err) } - var signer crypto.Signer - var err error - if signer, err = keys.NewSigner(ctx, vCfg.PrivKey); err != nil { - return nil, fmt.Errorf("failed to load private key: %v", err) - } - - // TODO(phboneff): are pub keys actually used? If not, remove - // If a public key has been configured for a log, check that it is consistent with the private key. - if vCfg.PubKey != nil { - switch pub := vCfg.PubKey.(type) { - case *ecdsa.PublicKey: - if !pub.Equal(signer.Public()) { - return nil, errors.New("public key is not consistent with private key") - } - default: - return nil, errors.New("failed to verify consistency of public key with private key") - } - } - validationOpts := CertValidationOpts{ trustedRoots: roots, rejectExpired: cfg.RejectExpired, rejectUnexpired: cfg.RejectUnexpired, - notAfterStart: vCfg.NotAfterStart, - notAfterLimit: vCfg.NotAfterLimit, - extKeyUsages: vCfg.KeyUsages, + notAfterStart: cfg.NotAfterStart, + notAfterLimit: cfg.NotAfterLimit, + extKeyUsages: cfg.KeyUsages, } + var err error validationOpts.rejectExtIds, err = parseOIDs(cfg.RejectExtensions) if err != nil { return nil, fmt.Errorf("failed to parse RejectExtensions: %v", err) } - logID, err := GetCTLogID(signer.Public()) + logID, err := GetCTLogID(cfg.Signer.Public()) if err != nil { return nil, fmt.Errorf("failed to get logID for signing: %v", err) } timeSource := new(SystemTimeSource) - ctSigner := NewCpSigner(signer, vCfg.Config.Origin, logID, timeSource) + ctSigner := NewCpSigner(cfg.Signer, cfg.Origin, logID, timeSource) if opts.CreateStorage == nil { return nil, fmt.Errorf("failed to initiate storage backend: nil createStorage") @@ -140,8 +103,10 @@ func setUpLogInfo(ctx context.Context, opts InstanceOptions) (*logInfo, error) { return nil, fmt.Errorf("failed to initiate storage backend: %v", err) } - logInfo := newLogInfo(opts, validationOpts, signer, timeSource, storage) - return logInfo, nil + logInfo := newLogInfo(opts, validationOpts, cfg.Signer, timeSource, storage) + + handlers := logInfo.Handlers(opts.Validated.Origin) + return &Instance{Handlers: handlers, li: logInfo}, nil } func parseOIDs(oids []string) ([]asn1.ObjectIdentifier, error) { diff --git a/personalities/sctfe/instance_test.go b/personalities/sctfe/instance_test.go index ad439fc4..ae35c392 100644 --- a/personalities/sctfe/instance_test.go +++ b/personalities/sctfe/instance_test.go @@ -16,6 +16,7 @@ package sctfe import ( "context" + "crypto" "errors" "fmt" "net/http/httptest" @@ -24,19 +25,11 @@ import ( "time" ct "github.com/google/certificate-transparency-go" - "github.com/google/trillian/crypto/keys" "github.com/google/trillian/crypto/keys/pem" - "github.com/google/trillian/crypto/keyspb" "github.com/google/trillian/monitoring" - "github.com/transparency-dev/trillian-tessera/personalities/sctfe/configpb" "golang.org/x/mod/sumdb/note" - "google.golang.org/protobuf/types/known/anypb" ) -func init() { - keys.RegisterHandler(&keyspb.PEMKeyFile{}, pem.FromProto) -} - func fakeCTStorage(_ context.Context, _ note.Signer) (*CTStorage, error) { return &CTStorage{}, nil } @@ -44,13 +37,13 @@ func fakeCTStorage(_ context.Context, _ note.Signer) (*CTStorage, error) { func TestSetUpInstance(t *testing.T) { ctx := context.Background() - privKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "./testdata/ct-http-server.privkey.pem", Password: "dirk"}) - missingPrivKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "./testdata/bogus.privkey.pem", Password: "dirk"}) - wrongPassPrivKey := mustMarshalAny(&keyspb.PEMKeyFile{Path: "./testdata/ct-http-server.privkey.pem", Password: "dirkly"}) + signer, err := pem.ReadPrivateKeyFile("./testdata/ct-http-server.privkey.pem", "dirk") + if err != nil { + t.Fatalf("Can't open key: %v", err) + } var tests = []struct { desc string - cfg *configpb.LogConfig origin string projectID string bucket string @@ -58,116 +51,77 @@ func TestSetUpInstance(t *testing.T) { rootsPemFile string extKeyUsages string rejectExtensions string + signer crypto.Signer ctStorage func(context.Context, note.Signer) (*CTStorage, error) wantErr string }{ { - desc: "valid", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "valid", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", ctStorage: fakeCTStorage, + signer: signer, }, { - desc: "no-roots", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, - origin: "log", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - ctStorage: fakeCTStorage, - wantErr: "specify RootsPemFile", - }, - { - desc: "missing-root-cert", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "no-roots", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", + rootsPemFile: "./testdata/nofile", ctStorage: fakeCTStorage, - rootsPemFile: "./testdata/bogus.cert", wantErr: "failed to read trusted roots", + signer: signer, }, { - desc: "missing-privkey", - cfg: &configpb.LogConfig{ - PrivateKey: missingPrivKey, - }, + desc: "missing-root-cert", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - ctStorage: fakeCTStorage, - wantErr: "failed to load private key", - }, - { - desc: "privkey-wrong-password", - cfg: &configpb.LogConfig{ - PrivateKey: wrongPassPrivKey, - }, - origin: "log", - projectID: "projeot", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", ctStorage: fakeCTStorage, - wantErr: "failed to load private key", + rootsPemFile: "./testdata/bogus.cert", + signer: signer, + wantErr: "failed to read trusted roots", }, { - desc: "valid-ekus-1", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "valid-ekus-1", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", extKeyUsages: "Any", + signer: signer, ctStorage: fakeCTStorage, }, { - desc: "valid-ekus-2", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "valid-ekus-2", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", extKeyUsages: "Any,ServerAuth,TimeStamping", + signer: signer, ctStorage: fakeCTStorage, }, { - desc: "valid-reject-ext", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "valid-reject-ext", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", rejectExtensions: "1.2.3.4,5.6.7.8", + signer: signer, ctStorage: fakeCTStorage, }, { - desc: "invalid-reject-ext", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "invalid-reject-ext", origin: "log", projectID: "project", bucket: "bucket", @@ -175,30 +129,27 @@ func TestSetUpInstance(t *testing.T) { ctStorage: fakeCTStorage, rootsPemFile: "./testdata/fake-ca.cert", rejectExtensions: "1.2.3.4,one.banana.two.bananas", + signer: signer, wantErr: "one", }, { - desc: "missing-create-storage", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "missing-create-storage", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", + signer: signer, wantErr: "failed to initiate storage backend", }, { - desc: "failing-create-storage", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "failing-create-storage", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", + signer: signer, ctStorage: func(_ context.Context, _ note.Signer) (*CTStorage, error) { return nil, fmt.Errorf("I failed") }, @@ -208,7 +159,7 @@ func TestSetUpInstance(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - vCfg, err := ValidateLogConfig(test.cfg, test.origin, test.projectID, test.bucket, test.spannerDB, test.rootsPemFile, false, false, test.extKeyUsages, test.rejectExtensions, nil, nil) + vCfg, err := ValidateLogConfig(test.origin, test.projectID, test.bucket, test.spannerDB, test.rootsPemFile, false, false, test.extKeyUsages, test.rejectExtensions, nil, nil, signer) if err != nil { t.Fatalf("ValidateLogConfig(): %v", err) } @@ -246,13 +197,13 @@ func TestSetUpInstanceSetsValidationOpts(t *testing.T) { start := time.Unix(10000, 0) limit := time.Unix(12000, 0) - privKey, err := anypb.New(&keyspb.PEMKeyFile{Path: "./testdata/ct-http-server.privkey.pem", Password: "dirk"}) + signer, err := pem.ReadPrivateKeyFile("./testdata/ct-http-server.privkey.pem", "dirk") if err != nil { - t.Fatalf("Could not marshal private key proto: %v", err) + t.Fatalf(fmt.Sprintf("Can't open key: %v", err)) } + var tests = []struct { desc string - cfg *configpb.LogConfig origin string projectID string bucket string @@ -260,23 +211,19 @@ func TestSetUpInstanceSetsValidationOpts(t *testing.T) { rootsPemFile string notAfterStart *time.Time notAfterLimit *time.Time + signer crypto.Signer }{ { - desc: "no validation opts", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "no validation opts", origin: "log", projectID: "project", bucket: "bucket", spannerDB: "spanner", rootsPemFile: "./testdata/fake-ca.cert", + signer: signer, }, { - desc: "notAfterStart only", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "notAfterStart only", origin: "log", projectID: "project", bucket: "bucket", @@ -285,10 +232,7 @@ func TestSetUpInstanceSetsValidationOpts(t *testing.T) { notAfterStart: &start, }, { - desc: "notAfter range", - cfg: &configpb.LogConfig{ - PrivateKey: privKey, - }, + desc: "notAfter range", origin: "log", projectID: "project", bucket: "bucket", @@ -296,12 +240,13 @@ func TestSetUpInstanceSetsValidationOpts(t *testing.T) { rootsPemFile: "./testdata/fake-ca.cert", notAfterStart: &start, notAfterLimit: &limit, + signer: signer, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - vCfg, err := ValidateLogConfig(test.cfg, test.origin, test.projectID, test.bucket, test.spannerDB, test.rootsPemFile, false, false, "", "", test.notAfterStart, test.notAfterLimit) + vCfg, err := ValidateLogConfig(test.origin, test.projectID, test.bucket, test.spannerDB, test.rootsPemFile, false, false, "", "", test.notAfterStart, test.notAfterLimit, signer) if err != nil { t.Fatalf("ValidateLogConfig(): %v", err) }