go.chromium.org/luci@v0.0.0-20250314024836-d9a61d0730e6/tokenserver/appengine/impl/certconfig/ca.go (about)

     1  // Copyright 2016 The LUCI Authors.
     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 certconfig
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/x509"
    21  	"encoding/gob"
    22  	"time"
    23  
    24  	"google.golang.org/protobuf/proto"
    25  
    26  	"go.chromium.org/luci/common/retry/transient"
    27  	ds "go.chromium.org/luci/gae/service/datastore"
    28  	"go.chromium.org/luci/server/caching"
    29  
    30  	"go.chromium.org/luci/tokenserver/api/admin/v1"
    31  )
    32  
    33  // CA defines one trusted Certificate Authority (imported from config).
    34  //
    35  // Entity key is CA Common Name (that must match what's is in the certificate).
    36  // Certificate issuer (and the certificate signature) is ignored. Usually, the
    37  // certificates here will be self-signed.
    38  //
    39  // Removed CAs are kept in the datastore, but not actively used.
    40  type CA struct {
    41  	// CN is CA's Common Name.
    42  	CN string `gae:"$id"`
    43  
    44  	// Config is serialized CertificateAuthorityConfig proto message.
    45  	Config []byte `gae:",noindex"`
    46  
    47  	// Cert is a certificate of this CA (in der encoding).
    48  	//
    49  	// It is read from luci-config from path specified in the config.
    50  	Cert []byte `gae:",noindex"`
    51  
    52  	// Removed is true if this CA has been removed from the config.
    53  	Removed bool
    54  
    55  	// Ready is false before this CA's CRL is fetched for the first time.
    56  	Ready bool
    57  
    58  	AddedRev   string `gae:",noindex"` // config rev when this CA appeared
    59  	UpdatedRev string `gae:",noindex"` // config rev when this CA was updated
    60  	RemovedRev string `gae:",noindex"` // config rev when it was removed
    61  
    62  	// ParsedConfig is parsed Config.
    63  	//
    64  	// Populated if CA is fetched through CertChecker.
    65  	ParsedConfig *admin.CertificateAuthorityConfig `gae:"-"`
    66  
    67  	// ParsedCert is parsed Cert.
    68  	//
    69  	// Populated if CA is fetched through CertChecker.
    70  	ParsedCert *x509.Certificate `gae:"-"`
    71  }
    72  
    73  // ParseConfig parses proto message stored in Config.
    74  func (c *CA) ParseConfig() (*admin.CertificateAuthorityConfig, error) {
    75  	msg := &admin.CertificateAuthorityConfig{}
    76  	if err := proto.Unmarshal(c.Config, msg); err != nil {
    77  		return nil, err
    78  	}
    79  	return msg, nil
    80  }
    81  
    82  // ListCAs returns names of all currently active CAs, in no particular order.
    83  func ListCAs(c context.Context) ([]string, error) {
    84  	keys := []*ds.Key{}
    85  	q := ds.NewQuery("CA").Eq("Removed", false).KeysOnly(true)
    86  	if err := ds.GetAll(c, q, &keys); err != nil {
    87  		return nil, transient.Tag.Apply(err)
    88  	}
    89  	names := make([]string, len(keys))
    90  	for i, key := range keys {
    91  		names[i] = key.StringID()
    92  	}
    93  	return names, nil
    94  }
    95  
    96  // CAUniqueIDToCNMap is a singleton entity that stores a mapping between CA's
    97  // unique_id (specified in config) and its Common Name.
    98  //
    99  // It's loaded in memory in full and kept cached there (for 1 min).
   100  // See GetCAByUniqueID below.
   101  type CAUniqueIDToCNMap struct {
   102  	_id int64 `gae:"$id,1"`
   103  
   104  	GobEncodedMap []byte `gae:",noindex"` // gob-encoded map[int64]string
   105  }
   106  
   107  // StoreCAUniqueIDToCNMap overwrites CAUniqueIDToCNMap with new content.
   108  func StoreCAUniqueIDToCNMap(c context.Context, mapping map[int64]string) error {
   109  	buf := bytes.Buffer{}
   110  	enc := gob.NewEncoder(&buf)
   111  	if err := enc.Encode(mapping); err != nil {
   112  		return err
   113  	}
   114  	// Note that in practice 'mapping' is usually very small, so we are not
   115  	// concerned about 1MB entity size limit.
   116  	return transient.Tag.Apply(ds.Put(c, &CAUniqueIDToCNMap{
   117  		GobEncodedMap: buf.Bytes(),
   118  	}))
   119  }
   120  
   121  // LoadCAUniqueIDToCNMap loads CAUniqueIDToCNMap from the datastore.
   122  func LoadCAUniqueIDToCNMap(c context.Context) (map[int64]string, error) {
   123  	ent := CAUniqueIDToCNMap{}
   124  	switch err := ds.Get(c, &ent); {
   125  	case err == ds.ErrNoSuchEntity:
   126  		return nil, nil
   127  	case err != nil:
   128  		return nil, transient.Tag.Apply(err)
   129  	}
   130  	dec := gob.NewDecoder(bytes.NewReader(ent.GobEncodedMap))
   131  	out := map[int64]string{}
   132  	if err := dec.Decode(&out); err != nil {
   133  		return nil, err
   134  	}
   135  	return out, nil
   136  }
   137  
   138  // holds cached result of LoadCAUniqueIDToCNMap().
   139  var mappingCache = caching.RegisterCacheSlot()
   140  
   141  // GetCAByUniqueID returns CN name that corresponds to given unique ID.
   142  //
   143  // It uses cached CAUniqueIDToCNMap for lookups. Returns empty string if there's
   144  // no such CA.
   145  func GetCAByUniqueID(c context.Context, id int64) (string, error) {
   146  	cached, err := mappingCache.Fetch(c, func(any) (any, time.Duration, error) {
   147  		val, err := LoadCAUniqueIDToCNMap(c)
   148  		return val, time.Minute, err
   149  	})
   150  	if err != nil {
   151  		return "", err
   152  	}
   153  	mapping := cached.(map[int64]string)
   154  	return mapping[id], nil
   155  }