...

Source file src/github.com/google/certificate-transparency-go/trillian/ctfe/config.go

Documentation: github.com/google/certificate-transparency-go/trillian/ctfe

     1  // Copyright 2016 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ctfe
    16  
    17  import (
    18  	"crypto"
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"time"
    23  
    24  	ct "github.com/google/certificate-transparency-go"
    25  	"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
    26  	"github.com/google/certificate-transparency-go/x509"
    27  	"google.golang.org/protobuf/encoding/prototext"
    28  	"google.golang.org/protobuf/proto"
    29  	"k8s.io/klog/v2"
    30  )
    31  
    32  // ValidatedLogConfig represents the LogConfig with the information that has
    33  // been successfully parsed as a result of validating it.
    34  type ValidatedLogConfig struct {
    35  	Config        *configpb.LogConfig
    36  	PubKey        crypto.PublicKey
    37  	PrivKey       proto.Message
    38  	KeyUsages     []x509.ExtKeyUsage
    39  	NotAfterStart *time.Time
    40  	NotAfterLimit *time.Time
    41  	FrozenSTH     *ct.SignedTreeHead
    42  }
    43  
    44  // LogConfigFromFile creates a slice of LogConfig options from the given
    45  // filename, which should contain text or binary-encoded protobuf configuration
    46  // data.
    47  func LogConfigFromFile(filename string) ([]*configpb.LogConfig, error) {
    48  	cfgBytes, err := os.ReadFile(filename)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	var cfg configpb.LogConfigSet
    54  	if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil {
    55  		if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil {
    56  			return nil, fmt.Errorf("failed to parse LogConfigSet from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr)
    57  		}
    58  	}
    59  
    60  	if len(cfg.Config) == 0 {
    61  		return nil, errors.New("empty log config found")
    62  	}
    63  	return cfg.Config, nil
    64  }
    65  
    66  // ToMultiLogConfig creates a multi backend config proto from the data
    67  // loaded from a single-backend configuration file. All the log configs
    68  // reference a default backend spec as provided.
    69  func ToMultiLogConfig(cfg []*configpb.LogConfig, beSpec string) *configpb.LogMultiConfig {
    70  	defaultBackend := &configpb.LogBackend{Name: "default", BackendSpec: beSpec}
    71  	for _, c := range cfg {
    72  		c.LogBackendName = defaultBackend.Name
    73  	}
    74  	return &configpb.LogMultiConfig{
    75  		LogConfigs: &configpb.LogConfigSet{Config: cfg},
    76  		Backends:   &configpb.LogBackendSet{Backend: []*configpb.LogBackend{defaultBackend}},
    77  	}
    78  }
    79  
    80  // MultiLogConfigFromFile creates a LogMultiConfig proto from the given
    81  // filename, which should contain text or binary-encoded protobuf configuration data.
    82  // Does not do full validation of the config but checks that it is non empty.
    83  func MultiLogConfigFromFile(filename string) (*configpb.LogMultiConfig, error) {
    84  	cfgBytes, err := os.ReadFile(filename)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	var cfg configpb.LogMultiConfig
    90  	if txtErr := prototext.Unmarshal(cfgBytes, &cfg); txtErr != nil {
    91  		if binErr := proto.Unmarshal(cfgBytes, &cfg); binErr != nil {
    92  			return nil, fmt.Errorf("failed to parse LogMultiConfig from %q as text protobuf (%v) or binary protobuf (%v)", filename, txtErr, binErr)
    93  		}
    94  	}
    95  
    96  	if len(cfg.LogConfigs.GetConfig()) == 0 || len(cfg.Backends.GetBackend()) == 0 {
    97  		return nil, errors.New("config is missing backends and/or log configs")
    98  	}
    99  	return &cfg, nil
   100  }
   101  
   102  // ValidateLogConfig checks that a single log config is valid. In particular:
   103  //   - A mirror log has a valid public key and no private key.
   104  //   - A non-mirror log has a private, and optionally a public key (both valid).
   105  //   - Each of NotBeforeStart and NotBeforeLimit, if set, is a valid timestamp
   106  //     proto. If both are set then NotBeforeStart <= NotBeforeLimit.
   107  //   - Merge delays (if present) are correct.
   108  //   - Frozen STH (if present) is correct and signed by the provided public key.
   109  //
   110  // Returns the validated structures (useful to avoid double validation).
   111  func ValidateLogConfig(cfg *configpb.LogConfig) (*ValidatedLogConfig, error) {
   112  	if cfg.LogId == 0 {
   113  		return nil, errors.New("empty log ID")
   114  	}
   115  
   116  	vCfg := ValidatedLogConfig{Config: cfg}
   117  
   118  	// Validate the public key.
   119  	if pubKey := cfg.PublicKey; pubKey != nil {
   120  		var err error
   121  		if vCfg.PubKey, err = x509.ParsePKIXPublicKey(pubKey.Der); err != nil {
   122  			return nil, fmt.Errorf("x509.ParsePKIXPublicKey: %w", err)
   123  		}
   124  	} else if cfg.IsMirror {
   125  		return nil, errors.New("empty public key for mirror")
   126  	} else if cfg.FrozenSth != nil {
   127  		return nil, errors.New("empty public key for frozen STH")
   128  	}
   129  
   130  	// Validate the private key.
   131  	if !cfg.IsMirror {
   132  		if cfg.PrivateKey == nil {
   133  			return nil, errors.New("empty private key")
   134  		}
   135  		privKey, err := cfg.PrivateKey.UnmarshalNew()
   136  		if err != nil {
   137  			return nil, fmt.Errorf("invalid private key: %v", err)
   138  		}
   139  		vCfg.PrivKey = privKey
   140  	} else if cfg.PrivateKey != nil {
   141  		return nil, errors.New("unnecessary private key for mirror")
   142  	}
   143  
   144  	if cfg.RejectExpired && cfg.RejectUnexpired {
   145  		return nil, errors.New("rejecting all certificates")
   146  	}
   147  
   148  	// Validate the extended key usages list.
   149  	if len(cfg.ExtKeyUsages) > 0 {
   150  		for _, kuStr := range cfg.ExtKeyUsages {
   151  			if ku, ok := stringToKeyUsage[kuStr]; ok {
   152  				// If "Any" is specified, then we can ignore the entire list and
   153  				// just disable EKU checking.
   154  				if ku == x509.ExtKeyUsageAny {
   155  					klog.Infof("%s: Found ExtKeyUsageAny, allowing all EKUs", cfg.Prefix)
   156  					vCfg.KeyUsages = nil
   157  					break
   158  				}
   159  				vCfg.KeyUsages = append(vCfg.KeyUsages, ku)
   160  			} else {
   161  				return nil, fmt.Errorf("unknown extended key usage: %s", kuStr)
   162  			}
   163  		}
   164  	}
   165  
   166  	// Validate the time interval.
   167  	start, limit := cfg.NotAfterStart, cfg.NotAfterLimit
   168  	if start != nil {
   169  		vCfg.NotAfterStart = &time.Time{}
   170  		if err := start.CheckValid(); err != nil {
   171  			return nil, fmt.Errorf("invalid start timestamp: %v", err)
   172  		}
   173  		*vCfg.NotAfterStart = start.AsTime()
   174  	}
   175  	if limit != nil {
   176  		vCfg.NotAfterLimit = &time.Time{}
   177  		if err := limit.CheckValid(); err != nil {
   178  			return nil, fmt.Errorf("invalid limit timestamp: %v", err)
   179  		}
   180  		*vCfg.NotAfterLimit = limit.AsTime()
   181  	}
   182  	if start != nil && limit != nil && (*vCfg.NotAfterLimit).Before(*vCfg.NotAfterStart) {
   183  		return nil, errors.New("limit before start")
   184  	}
   185  
   186  	switch {
   187  	case cfg.MaxMergeDelaySec < 0:
   188  		return nil, errors.New("negative maximum merge delay")
   189  	case cfg.ExpectedMergeDelaySec < 0:
   190  		return nil, errors.New("negative expected merge delay")
   191  	case cfg.ExpectedMergeDelaySec > cfg.MaxMergeDelaySec:
   192  		return nil, errors.New("expected merge delay exceeds MMD")
   193  	}
   194  
   195  	if sth := cfg.FrozenSth; sth != nil {
   196  		verifier, err := ct.NewSignatureVerifier(vCfg.PubKey)
   197  		if err != nil {
   198  			return nil, fmt.Errorf("failed to create signature verifier: %v", err)
   199  		}
   200  		if vCfg.FrozenSTH, err = (&ct.GetSTHResponse{
   201  			TreeSize:          uint64(sth.TreeSize),
   202  			Timestamp:         uint64(sth.Timestamp),
   203  			SHA256RootHash:    sth.Sha256RootHash,
   204  			TreeHeadSignature: sth.TreeHeadSignature,
   205  		}).ToSignedTreeHead(); err != nil {
   206  			return nil, fmt.Errorf("invalid frozen STH: %v", err)
   207  		}
   208  		if err := verifier.VerifySTHSignature(*vCfg.FrozenSTH); err != nil {
   209  			return nil, fmt.Errorf("signature verification failed: %v", err)
   210  		}
   211  	}
   212  
   213  	return &vCfg, nil
   214  }
   215  
   216  // LogBackendMap is a map from log backend names to LogBackend objects.
   217  type LogBackendMap = map[string]*configpb.LogBackend
   218  
   219  // BuildLogBackendMap returns a map from log backend names to the corresponding
   220  // LogBackend objects. It returns an error unless all backends have unique
   221  // non-empty names and specifications.
   222  func BuildLogBackendMap(lbs *configpb.LogBackendSet) (LogBackendMap, error) {
   223  	lbm := make(LogBackendMap)
   224  	specs := make(map[string]bool)
   225  	for _, be := range lbs.Backend {
   226  		if len(be.Name) == 0 {
   227  			return nil, fmt.Errorf("empty backend name: %v", be)
   228  		}
   229  		if len(be.BackendSpec) == 0 {
   230  			return nil, fmt.Errorf("empty backend spec: %v", be)
   231  		}
   232  		if _, ok := lbm[be.Name]; ok {
   233  			return nil, fmt.Errorf("duplicate backend name: %v", be)
   234  		}
   235  		if ok := specs[be.BackendSpec]; ok {
   236  			return nil, fmt.Errorf("duplicate backend spec: %v", be)
   237  		}
   238  		lbm[be.Name] = be
   239  		specs[be.BackendSpec] = true
   240  	}
   241  	return lbm, nil
   242  }
   243  
   244  func validateConfigs(cfg []*configpb.LogConfig) error {
   245  	// Check that logs have no duplicate or empty prefixes. Apply other LogConfig
   246  	// specific checks.
   247  	logNameMap := make(map[string]bool)
   248  	for _, logCfg := range cfg {
   249  		if _, err := ValidateLogConfig(logCfg); err != nil {
   250  			return fmt.Errorf("log config: %v: %v", err, logCfg)
   251  		}
   252  		if len(logCfg.Prefix) == 0 {
   253  			return fmt.Errorf("log config: empty prefix: %v", logCfg)
   254  		}
   255  		if logNameMap[logCfg.Prefix] {
   256  			return fmt.Errorf("log config: duplicate prefix: %s: %v", logCfg.Prefix, logCfg)
   257  		}
   258  		logNameMap[logCfg.Prefix] = true
   259  	}
   260  
   261  	return nil
   262  }
   263  
   264  // ValidateLogConfigs checks that a config is valid for use with a single log
   265  // server. The rules applied are:
   266  //
   267  // 1. All log configs must be valid (see ValidateLogConfig).
   268  // 2. The prefixes of configured logs must all be distinct and must not be
   269  // empty.
   270  // 3. The set of tree IDs must be distinct.
   271  func ValidateLogConfigs(cfg []*configpb.LogConfig) error {
   272  	if err := validateConfigs(cfg); err != nil {
   273  		return err
   274  	}
   275  
   276  	// Check that logs have no duplicate tree IDs.
   277  	treeIDs := make(map[int64]bool)
   278  	for _, logCfg := range cfg {
   279  		if treeIDs[logCfg.LogId] {
   280  			return fmt.Errorf("log config: dup tree id: %d for: %v", logCfg.LogId, logCfg)
   281  		}
   282  		treeIDs[logCfg.LogId] = true
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  // ValidateLogMultiConfig checks that a config is valid for use with multiple
   289  // backend log servers. The rules applied are the same as ValidateLogConfigs, as
   290  // well as these additional rules:
   291  //
   292  // 1. The backend set must define a set of log backends with distinct
   293  // (non empty) names and non empty backend specs.
   294  // 2. The backend specs must all be distinct.
   295  // 3. The log configs must all specify a log backend and each must be one of
   296  // those defined in the backend set.
   297  //
   298  // Also, another difference is that the tree IDs need only to be distinct per
   299  // backend.
   300  //
   301  // TODO(pavelkalinnikov): Replace the returned map with a fully fledged
   302  // ValidatedLogMultiConfig that contains a ValidatedLogConfig for each log.
   303  func ValidateLogMultiConfig(cfg *configpb.LogMultiConfig) (LogBackendMap, error) {
   304  	backendMap, err := BuildLogBackendMap(cfg.Backends)
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	if err := validateConfigs(cfg.GetLogConfigs().GetConfig()); err != nil {
   310  		return nil, err
   311  	}
   312  
   313  	// Check that logs all reference a defined backend.
   314  	logIDMap := make(map[string]bool)
   315  	for _, logCfg := range cfg.LogConfigs.Config {
   316  		if _, ok := backendMap[logCfg.LogBackendName]; !ok {
   317  			return nil, fmt.Errorf("log config: references undefined backend: %s: %v", logCfg.LogBackendName, logCfg)
   318  		}
   319  		logIDKey := fmt.Sprintf("%s-%d", logCfg.LogBackendName, logCfg.LogId)
   320  		if ok := logIDMap[logIDKey]; ok {
   321  			return nil, fmt.Errorf("log config: dup tree id: %d for: %v", logCfg.LogId, logCfg)
   322  		}
   323  		logIDMap[logIDKey] = true
   324  	}
   325  
   326  	return backendMap, nil
   327  }
   328  
   329  var stringToKeyUsage = map[string]x509.ExtKeyUsage{
   330  	"Any":                        x509.ExtKeyUsageAny,
   331  	"ServerAuth":                 x509.ExtKeyUsageServerAuth,
   332  	"ClientAuth":                 x509.ExtKeyUsageClientAuth,
   333  	"CodeSigning":                x509.ExtKeyUsageCodeSigning,
   334  	"EmailProtection":            x509.ExtKeyUsageEmailProtection,
   335  	"IPSECEndSystem":             x509.ExtKeyUsageIPSECEndSystem,
   336  	"IPSECTunnel":                x509.ExtKeyUsageIPSECTunnel,
   337  	"IPSECUser":                  x509.ExtKeyUsageIPSECUser,
   338  	"TimeStamping":               x509.ExtKeyUsageTimeStamping,
   339  	"OCSPSigning":                x509.ExtKeyUsageOCSPSigning,
   340  	"MicrosoftServerGatedCrypto": x509.ExtKeyUsageMicrosoftServerGatedCrypto,
   341  	"NetscapeServerGatedCrypto":  x509.ExtKeyUsageNetscapeServerGatedCrypto,
   342  }
   343  

View as plain text