diff --git a/cmd/gcp/main.go b/cmd/gcp/main.go index 8c7437bf..7f4d5593 100644 --- a/cmd/gcp/main.go +++ b/cmd/gcp/main.go @@ -81,19 +81,30 @@ func main() { if err != nil { klog.Exitf("Can't create secret manager signer: %v", err) } - cpSigner, err := sctfe.NewCpSigner(signer, *origin, timeSource) + + chainValidationConfig := sctfe.ChainValidationConfig{ + RootsPEMFile: *rootsPemFile, + RejectExpired: *rejectExpired, + RejectUnexpired: *rejectUnexpired, + ExtKeyUsages: *extKeyUsages, + RejectExtensions: *rejectExtensions, + NotAfterStart: notAfterStart.t, + NotAfterLimit: notAfterLimit.t, + } + + vCfg, err := sctfe.ValidateLogConfig(chainValidationConfig, *origin, signer) if err != nil { - klog.Exitf("Failed to create checkpoint signer: %v", err) + klog.Exitf("Invalid log config: %v", err) } - storage, err := newGCPStorage(ctx, cpSigner) + cpSigner, err := sctfe.NewCpSigner(signer, vCfg.Origin, timeSource) if err != nil { - klog.Exitf("Failed to initiate storage backend: %v", err) + klog.Exitf("Failed to create checkpoint signer: %v", err) } - vCfg, err := sctfe.ValidateLogConfig(*origin, *rootsPemFile, *rejectExpired, *rejectUnexpired, *extKeyUsages, *rejectExtensions, notAfterStart.t, notAfterLimit.t, signer) + storage, err := newGCPStorage(ctx, cpSigner) if err != nil { - klog.Exitf("Invalid config: %v", err) + klog.Exitf("Failed to initialize storage backend: %v", err) } opts := sctfe.HandlerOptions{ diff --git a/config.go b/config.go index 00007f64..3ceaaaa2 100644 --- a/config.go +++ b/config.go @@ -29,6 +29,38 @@ import ( "k8s.io/klog/v2" ) +// ChainValidationConfig contains parameters to configure chain validation. +type ChainValidationConfig struct { + // RootsPEMFile is the path to the file containing root certificates that + // are acceptable to the log. The certs are served through get-roots + // endpoint. + RootsPEMFile string + // RejectExpired controls if 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 + // RejectUnexpired controls if the SCTFE rejects certificates that are + // either currently valid or not yet valid. + // TODO(phboneff): evaluate whether we need to keep this one. + RejectUnexpired bool + // ExtKeyUsages lists Extended Key Usage values that newly submitted + // certificates MUST contain. By default all are accepted. The + // values specified must be ones known to the x509 package, comma separated. + ExtKeyUsages string + // RejectExtensions lists X.509 extension OIDs that newly submitted + // certificates MUST NOT contain. Empty by default. Values must be + // specificed in dotted string form (e.g. "2.3.4.5"). + RejectExtensions string + // 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 +} + // ValidatedLogConfig represents the LogConfig with the information that has // been successfully parsed as a result of validating it. type ValidatedLogConfig struct { @@ -49,17 +81,18 @@ type ValidatedLogConfig struct { // - Merge delays (if present) are correct. // // Returns the validated structures (useful to avoid double validation). -func ValidateLogConfig(origin string, rootsPemFile string, rejectExpired bool, rejectUnexpired bool, extKeyUsages string, rejectExtensions string, notAfterStart *time.Time, notAfterLimit *time.Time, signer crypto.Signer) (*ValidatedLogConfig, error) { +// TODO(phboneff): change the name of this function. +func ValidateLogConfig(cfg ChainValidationConfig, origin string, signer crypto.Signer) (*ValidatedLogConfig, error) { if origin == "" { return nil, errors.New("empty origin") } // Load the trusted roots. - if rootsPemFile == "" { + if cfg.RootsPEMFile == "" { return nil, errors.New("empty rootsPemFile") } roots := x509util.NewPEMCertPool() - if err := roots.AppendCertsFromPEMFile(rootsPemFile); err != nil { + if err := roots.AppendCertsFromPEMFile(cfg.RootsPEMFile); err != nil { return nil, fmt.Errorf("failed to read trusted roots: %v", err) } @@ -73,27 +106,27 @@ func ValidateLogConfig(origin string, rootsPemFile string, rejectExpired bool, r return nil, fmt.Errorf("unsupported key type: %v", keyType) } - if rejectExpired && rejectUnexpired { + if cfg.RejectExpired && cfg.RejectUnexpired { return nil, errors.New("configuration would reject all certificates") } // Validate the time interval. - if notAfterStart != nil && notAfterLimit != nil && (notAfterLimit).Before(*notAfterStart) { - return nil, fmt.Errorf("'Not After' limit %q before start %q", notAfterLimit.Format(time.RFC3339), notAfterStart.Format(time.RFC3339)) + if cfg.NotAfterStart != nil && cfg.NotAfterLimit != nil && (cfg.NotAfterLimit).Before(*cfg.NotAfterStart) { + return nil, fmt.Errorf("'Not After' limit %q before start %q", cfg.NotAfterLimit.Format(time.RFC3339), cfg.NotAfterStart.Format(time.RFC3339)) } validationOpts := CertValidationOpts{ trustedRoots: roots, - rejectExpired: rejectExpired, - rejectUnexpired: rejectUnexpired, - notAfterStart: notAfterStart, - notAfterLimit: notAfterLimit, + rejectExpired: cfg.RejectExpired, + rejectUnexpired: cfg.RejectUnexpired, + notAfterStart: cfg.NotAfterStart, + notAfterLimit: cfg.NotAfterLimit, } // Filter which extended key usages are allowed. lExtKeyUsages := []string{} - if extKeyUsages != "" { - lExtKeyUsages = strings.Split(extKeyUsages, ",") + if cfg.ExtKeyUsages != "" { + lExtKeyUsages = strings.Split(cfg.ExtKeyUsages, ",") } // Validate the extended key usages list. for _, kuStr := range lExtKeyUsages { @@ -112,8 +145,8 @@ func ValidateLogConfig(origin string, rootsPemFile string, rejectExpired bool, r } // Filter which extensions are rejected. var err error - if rejectExtensions != "" { - lRejectExtensions := strings.Split(rejectExtensions, ",") + if cfg.RejectExtensions != "" { + lRejectExtensions := strings.Split(cfg.RejectExtensions, ",") validationOpts.rejectExtIds, err = parseOIDs(lRejectExtensions) if err != nil { return nil, fmt.Errorf("failed to parse RejectExtensions: %v", err) diff --git a/config_test.go b/config_test.go index fff94d9e..1c79c20d 100644 --- a/config_test.go +++ b/config_test.go @@ -33,20 +33,14 @@ func TestValidateLogConfig(t *testing.T) { t200 := time.Unix(200, 0) for _, tc := range []struct { - desc string - origin string - projectID string - bucket string - spannerDB string - wantErr string - rootsPemFile string - rejectExpired bool - rejectUnexpired bool - extKeyUsages string - rejectExtensions string - notAfterStart *time.Time - notAfterLimit *time.Time - signer crypto.Signer + desc string + origin string + projectID string + bucket string + spannerDB string + wantErr string + cvcfg ChainValidationConfig + signer crypto.Signer }{ { desc: "empty-origin", @@ -65,146 +59,169 @@ func TestValidateLogConfig(t *testing.T) { signer: signer, }, { - desc: "missing-root-cert", - wantErr: "failed to read trusted roots", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/bogus.cert", - signer: signer, + desc: "missing-root-cert", + wantErr: "failed to read trusted roots", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/bogus.cert", + }, + signer: signer, }, { - desc: "rejecting-all", - wantErr: "configuration would reject all certificates", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - rejectExpired: true, - rejectUnexpired: true, - signer: signer, + desc: "rejecting-all", + wantErr: "configuration would reject all certificates", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + RejectExpired: true, + RejectUnexpired: true}, + signer: signer, }, { - 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-1", + wantErr: "unknown extended key usage", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + ExtKeyUsages: "wrong_usage"}, + signer: signer, }, { - 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-2", + wantErr: "unknown extended key usage", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + ExtKeyUsages: "ClientAuth,ServerAuth,TimeStomping", + }, + signer: signer, }, { - 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: "unknown-ext-key-usage-3", + wantErr: "unknown extended key usage", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + ExtKeyUsages: "Any ", + }, + signer: signer, }, { - desc: "unknown-reject-ext", - wantErr: "failed to parse RejectExtensions", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - rejectExtensions: "1.2.3.4,one.banana.two.bananas", - signer: signer, + desc: "unknown-reject-ext", + wantErr: "failed to parse RejectExtensions", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + RejectExtensions: "1.2.3.4,one.banana.two.bananas", + }, + signer: signer, }, { - desc: "limit-before-start", - wantErr: "before start", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - notAfterStart: &t200, - notAfterLimit: &t100, - signer: signer, + wantErr: "before start", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + NotAfterStart: &t200, + NotAfterLimit: &t100, + }, + signer: signer, }, { - desc: "ok", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - signer: signer, + desc: "ok", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + }, + signer: signer, }, { - 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-ext-key-usages", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + ExtKeyUsages: "ServerAuth,ClientAuth,OCSPSigning", + }, + signer: signer, }, { - desc: "ok-reject-ext", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - rejectExtensions: "1.2.3.4,5.6.7.8", - signer: signer, + desc: "ok-reject-ext", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + RejectExtensions: "1.2.3.4,5.6.7.8", + }, + signer: signer, }, { - desc: "ok-start-timestamp", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - notAfterStart: &t100, - signer: signer, + desc: "ok-start-timestamp", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + NotAfterStart: &t100, + }, + signer: signer, }, { - desc: "ok-limit-timestamp", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - notAfterStart: &t200, - signer: signer, + desc: "ok-limit-timestamp", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + NotAfterStart: &t200, + }, + signer: signer, }, { - desc: "ok-range-timestamp", - origin: "testlog", - projectID: "project", - bucket: "bucket", - spannerDB: "spanner", - rootsPemFile: "./testdata/fake-ca.cert", - notAfterStart: &t100, - notAfterLimit: &t200, - signer: signer, + desc: "ok-range-timestamp", + origin: "testlog", + projectID: "project", + bucket: "bucket", + spannerDB: "spanner", + cvcfg: ChainValidationConfig{ + RootsPEMFile: "./testdata/fake-ca.cert", + NotAfterStart: &t100, + NotAfterLimit: &t200, + }, + signer: signer, }, } { t.Run(tc.desc, func(t *testing.T) { - vc, err := ValidateLogConfig(tc.origin, tc.rootsPemFile, tc.rejectExpired, tc.rejectUnexpired, tc.extKeyUsages, tc.rejectExtensions, tc.notAfterStart, tc.notAfterLimit, signer) + vc, err := ValidateLogConfig(tc.cvcfg, tc.origin, signer) if len(tc.wantErr) == 0 && err != nil { t.Errorf("ValidateLogConfig()=%v, want nil", err) }