blob: 4cc735eef2cd52c02f39b314ddc9d5eee2cdd5d7 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// 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.
package certconfig
import (
"bytes"
"context"
"crypto/x509"
"encoding/gob"
"time"
"google.golang.org/protobuf/proto"
"go.chromium.org/luci/common/retry/transient"
ds "go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/luci/server/caching"
"go.chromium.org/luci/tokenserver/api/admin/v1"
)
// CA defines one trusted Certificate Authority (imported from config).
//
// Entity key is CA Common Name (that must match what's is in the certificate).
// Certificate issuer (and the certificate signature) is ignored. Usually, the
// certificates here will be self-signed.
//
// Removed CAs are kept in the datastore, but not actively used.
type CA struct {
// CN is CA's Common Name.
CN string `gae:"$id"`
// Config is serialized CertificateAuthorityConfig proto message.
Config []byte `gae:",noindex"`
// Cert is a certificate of this CA (in der encoding).
//
// It is read from luci-config from path specified in the config.
Cert []byte `gae:",noindex"`
// Removed is true if this CA has been removed from the config.
Removed bool
// Ready is false before this CA's CRL is fetched for the first time.
Ready bool
AddedRev string `gae:",noindex"` // config rev when this CA appeared
UpdatedRev string `gae:",noindex"` // config rev when this CA was updated
RemovedRev string `gae:",noindex"` // config rev when it was removed
// ParsedConfig is parsed Config.
//
// Populated if CA is fetched through CertChecker.
ParsedConfig *admin.CertificateAuthorityConfig `gae:"-"`
// ParsedCert is parsed Cert.
//
// Populated if CA is fetched through CertChecker.
ParsedCert *x509.Certificate `gae:"-"`
}
// ParseConfig parses proto message stored in Config.
func (c *CA) ParseConfig() (*admin.CertificateAuthorityConfig, error) {
msg := &admin.CertificateAuthorityConfig{}
if err := proto.Unmarshal(c.Config, msg); err != nil {
return nil, err
}
return msg, nil
}
// ListCAs returns names of all currently active CAs, in no particular order.
func ListCAs(c context.Context) ([]string, error) {
keys := []*ds.Key{}
q := ds.NewQuery("CA").Eq("Removed", false).KeysOnly(true)
if err := ds.GetAll(c, q, &keys); err != nil {
return nil, transient.Tag.Apply(err)
}
names := make([]string, len(keys))
for i, key := range keys {
names[i] = key.StringID()
}
return names, nil
}
// CAUniqueIDToCNMap is a singleton entity that stores a mapping between CA's
// unique_id (specified in config) and its Common Name.
//
// It's loaded in memory in full and kept cached there (for 1 min).
// See GetCAByUniqueID below.
type CAUniqueIDToCNMap struct {
_id int64 `gae:"$id,1"`
GobEncodedMap []byte `gae:",noindex"` // gob-encoded map[int64]string
}
// StoreCAUniqueIDToCNMap overwrites CAUniqueIDToCNMap with new content.
func StoreCAUniqueIDToCNMap(c context.Context, mapping map[int64]string) error {
buf := bytes.Buffer{}
enc := gob.NewEncoder(&buf)
if err := enc.Encode(mapping); err != nil {
return err
}
// Note that in practice 'mapping' is usually very small, so we are not
// concerned about 1MB entity size limit.
return transient.Tag.Apply(ds.Put(c, &CAUniqueIDToCNMap{
GobEncodedMap: buf.Bytes(),
}))
}
// LoadCAUniqueIDToCNMap loads CAUniqueIDToCNMap from the datastore.
func LoadCAUniqueIDToCNMap(c context.Context) (map[int64]string, error) {
ent := CAUniqueIDToCNMap{}
switch err := ds.Get(c, &ent); {
case err == ds.ErrNoSuchEntity:
return nil, nil
case err != nil:
return nil, transient.Tag.Apply(err)
}
dec := gob.NewDecoder(bytes.NewReader(ent.GobEncodedMap))
out := map[int64]string{}
if err := dec.Decode(&out); err != nil {
return nil, err
}
return out, nil
}
// holds cached result of LoadCAUniqueIDToCNMap().
var mappingCache = caching.RegisterCacheSlot()
// GetCAByUniqueID returns CN name that corresponds to given unique ID.
//
// It uses cached CAUniqueIDToCNMap for lookups. Returns empty string if there's
// no such CA.
func GetCAByUniqueID(c context.Context, id int64) (string, error) {
cached, err := mappingCache.Fetch(c, func(any) (any, time.Duration, error) {
val, err := LoadCAUniqueIDToCNMap(c)
return val, time.Minute, err
})
if err != nil {
return "", err
}
mapping := cached.(map[int64]string)
return mapping[id], nil
}