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 }