blob: f2c1684afad13dd2ac7fdb270b6aa163a200418a [file]
// Copyright 2016 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package certconfig
import (
"bytes"
"crypto/x509"
"encoding/gob"
"time"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
ds "github.com/luci/gae/service/datastore"
"github.com/luci/luci-go/common/clock"
"github.com/luci/luci-go/common/data/caching/lazyslot"
"github.com/luci/luci-go/common/data/caching/proccache"
"github.com/luci/luci-go/common/errors"
"github.com/luci/luci-go/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, errors.WrapTransient(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 errors.WrapTransient(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, errors.WrapTransient(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
}
// 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) {
mapper, err := proccache.GetOrMake(c, mapperCacheKey(0), func() (interface{}, time.Duration, error) {
return makeIDToCNmapper(), 0, nil
})
if err != nil {
return "", err
}
return mapper.(*idToCNmapper).getCAByUniqueID(c, id)
}
type mapperCacheKey int
// idToCNmapper is stored in proccache, it does "unique ID -> CN name" mapping.
//
// It holds cached copy of CAUniqueIDToCNMap, periodically refreshing it.
type idToCNmapper struct {
mapping lazyslot.Slot
}
func makeIDToCNmapper() *idToCNmapper {
return &idToCNmapper{
mapping: lazyslot.Slot{
Fetcher: func(c context.Context, _ lazyslot.Value) (lazyslot.Value, error) {
val, err := LoadCAUniqueIDToCNMap(c)
return lazyslot.Value{
Value: val,
Expiration: clock.Now(c).Add(time.Minute),
}, err
},
},
}
}
func (m *idToCNmapper) getCAByUniqueID(c context.Context, id int64) (string, error) {
val, err := m.mapping.Get(c)
if err != nil {
return "", err
}
mapping := val.Value.(map[int64]string)
return mapping[id], nil
}