Add bad-key-revoker daemon (#4788)
Adds a daemon which monitors the new blockedKeys table and checks for any unexpired, unrevoked certificates that are associated with the added SPKI hashes and revokes them, notifying the user that issued the certificates. Fixes #4772.
This commit is contained in:
parent
d2ae471026
commit
70ff4d9347
|
|
@ -0,0 +1,431 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
netmail "net/mail"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
"github.com/letsencrypt/boulder/db"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/mail"
|
||||
rapb "github.com/letsencrypt/boulder/ra/proto"
|
||||
"github.com/letsencrypt/boulder/revocation"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var keysProcessed = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "bad_keys_processed",
|
||||
Help: "A counter of blockedKeys rows processed labelled by processing state",
|
||||
}, []string{"state"})
|
||||
var certsRevoked = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "bad_keys_certs_revoked",
|
||||
Help: "A counter of certificates associated with rows in blockedKeys that have been revoked",
|
||||
})
|
||||
var mailErrors = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "bad_keys_mail_errors",
|
||||
Help: "A counter of email send errors",
|
||||
})
|
||||
|
||||
// revoker is an interface used to reduce the scope of a RA gRPC client
|
||||
// to only the single method we need to use, this makes testing significantly
|
||||
// simpler
|
||||
type revoker interface {
|
||||
AdministrativelyRevokeCertificate(ctx context.Context, in *rapb.AdministrativelyRevokeCertificateRequest, opts ...grpc.CallOption) (*corepb.Empty, error)
|
||||
}
|
||||
|
||||
type badKeyRevoker struct {
|
||||
dbMap *db.WrappedMap
|
||||
maxRevocations int
|
||||
serialBatchSize int
|
||||
raClient revoker
|
||||
mailer mail.Mailer
|
||||
emailSubject string
|
||||
emailTemplate *template.Template
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// uncheckedBlockedKey represents a row in the blockedKeys table
|
||||
type uncheckedBlockedKey struct {
|
||||
KeyHash []byte
|
||||
RevokedBy int64
|
||||
}
|
||||
|
||||
func (bkr *badKeyRevoker) selectUncheckedKey() (uncheckedBlockedKey, error) {
|
||||
var row uncheckedBlockedKey
|
||||
err := bkr.dbMap.SelectOne(
|
||||
&row,
|
||||
"SELECT keyHash, revokedBy FROM blockedKeys WHERE extantCertificatesChecked = false",
|
||||
)
|
||||
return row, err
|
||||
}
|
||||
|
||||
// unrevokedCertificate represents a yet to be revoked certificate
|
||||
type unrevokedCertificate struct {
|
||||
ID int
|
||||
Serial string
|
||||
DER []byte
|
||||
RegistrationID int64
|
||||
}
|
||||
|
||||
// findUnrevoked looks for all unexpired, currently valid certificates which have a specific SPKI hash,
|
||||
// by looking first at the keyHashToSerial table and then the certificateStatus and certificates tables.
|
||||
// If the number of certificates it finds is larger than bkr.maxRevocations it'll error out.
|
||||
func (bkr *badKeyRevoker) findUnrevoked(unchecked uncheckedBlockedKey) ([]unrevokedCertificate, error) {
|
||||
var unrevokedCerts []unrevokedCertificate
|
||||
initialID := 0
|
||||
for {
|
||||
var batch []struct {
|
||||
ID int
|
||||
CertSerial string
|
||||
}
|
||||
_, err := bkr.dbMap.Select(
|
||||
&batch,
|
||||
"SELECT id, certserial FROM keyHashToSerial WHERE keyHash = ? AND id > ? ORDER BY id LIMIT ?",
|
||||
unchecked.KeyHash,
|
||||
initialID,
|
||||
bkr.serialBatchSize,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(batch) == 0 {
|
||||
break
|
||||
}
|
||||
initialID = batch[len(batch)-1].ID
|
||||
for _, serial := range batch {
|
||||
var unrevokedCert unrevokedCertificate
|
||||
err = bkr.dbMap.SelectOne(
|
||||
&unrevokedCert,
|
||||
`SELECT cs.id, cs.serial, c.registrationID, c.der
|
||||
FROM certificateStatus AS cs
|
||||
JOIN certificates AS c
|
||||
ON cs.serial = c.serial
|
||||
WHERE cs.serial = ? AND cs.isExpired = false AND cs.status != ?`,
|
||||
serial.CertSerial,
|
||||
string(core.StatusRevoked),
|
||||
)
|
||||
if err != nil {
|
||||
if db.IsNoRows(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
unrevokedCerts = append(unrevokedCerts, unrevokedCert)
|
||||
}
|
||||
}
|
||||
if len(unrevokedCerts) > bkr.maxRevocations {
|
||||
return nil, fmt.Errorf("too many certificates to revoke associated with %x: got %d, max %d", unchecked.KeyHash, len(unrevokedCerts), bkr.maxRevocations)
|
||||
}
|
||||
return unrevokedCerts, nil
|
||||
}
|
||||
|
||||
// markRowChecked updates a row in the blockedKeys table to mark a keyHash
|
||||
// as having been checked for extant unrevoked certificates.
|
||||
func (bkr *badKeyRevoker) markRowChecked(unchecked uncheckedBlockedKey) error {
|
||||
_, err := bkr.dbMap.Exec("UPDATE blockedKeys SET extantCertificatesChecked = true WHERE keyHash = ?", unchecked.KeyHash)
|
||||
return err
|
||||
}
|
||||
|
||||
// resolveContacts builds a map of id -> email addresses
|
||||
func (bkr *badKeyRevoker) resolveContacts(ids []int64) (map[int64][]string, error) {
|
||||
idToEmail := map[int64][]string{}
|
||||
for _, id := range ids {
|
||||
var emails struct {
|
||||
Contact []string
|
||||
}
|
||||
err := bkr.dbMap.SelectOne(&emails, "SELECT contact FROM registrations WHERE id = ?", id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(emails.Contact) != 0 {
|
||||
for _, email := range emails.Contact {
|
||||
idToEmail[id] = append(idToEmail[id], strings.TrimPrefix(email, "mailto:"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return idToEmail, nil
|
||||
}
|
||||
|
||||
var maxSerials = 100
|
||||
|
||||
// sendMessage sends a single email to the provided address with the revoked
|
||||
// serials
|
||||
func (bkr *badKeyRevoker) sendMessage(addr string, serials []string) error {
|
||||
err := bkr.mailer.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = bkr.mailer.Close()
|
||||
}()
|
||||
mutSerials := make([]string, len(serials))
|
||||
copy(mutSerials, serials)
|
||||
if len(mutSerials) > maxSerials {
|
||||
more := len(mutSerials) - maxSerials
|
||||
mutSerials = mutSerials[:maxSerials]
|
||||
mutSerials = append(mutSerials, fmt.Sprintf("and %d more certificates.", more))
|
||||
}
|
||||
message := bytes.NewBuffer(nil)
|
||||
err = bkr.emailTemplate.Execute(message, mutSerials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bkr.mailer.SendMail([]string{addr}, bkr.emailSubject, message.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var keyCompromiseCode = int64(revocation.KeyCompromise)
|
||||
var revokerName = "bad-key-revoker"
|
||||
|
||||
// revokeCerts revokes all the certificates associated with a particular key hash and sends
|
||||
// emails to the users that issued the certificates. Emails are not sent to the user which
|
||||
// requested revocation of the original certificate which marked the key as compromised.
|
||||
func (bkr *badKeyRevoker) revokeCerts(revokerEmails []string, emailToCerts map[string][]unrevokedCertificate) error {
|
||||
revokerEmailsMap := map[string]bool{}
|
||||
for _, email := range revokerEmails {
|
||||
revokerEmailsMap[email] = true
|
||||
}
|
||||
alreadyRevoked := map[int]bool{}
|
||||
for email, certs := range emailToCerts {
|
||||
var revokedSerials []string
|
||||
for _, cert := range certs {
|
||||
revokedSerials = append(revokedSerials, cert.Serial)
|
||||
if alreadyRevoked[cert.ID] {
|
||||
continue
|
||||
}
|
||||
_, err := bkr.raClient.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
|
||||
Cert: cert.DER,
|
||||
Code: &keyCompromiseCode,
|
||||
AdminName: &revokerName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
certsRevoked.Inc()
|
||||
alreadyRevoked[cert.ID] = true
|
||||
}
|
||||
// don't send emails to the person who revoked the certificate
|
||||
if revokerEmailsMap[email] || email == "" {
|
||||
continue
|
||||
}
|
||||
err := bkr.sendMessage(email, revokedSerials)
|
||||
if err != nil {
|
||||
mailErrors.Inc()
|
||||
bkr.logger.Errf("failed to send message to %q: %s", email, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// invoke processes a single key in the blockedKeys table and returns whether
|
||||
// there were any rows to process or not.
|
||||
func (bkr *badKeyRevoker) invoke() (bool, error) {
|
||||
// select a row to process
|
||||
unchecked, err := bkr.selectUncheckedKey()
|
||||
if err != nil {
|
||||
if db.IsNoRows(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// select all unrevoked, unexpired serials associated with the blocked key hash
|
||||
unrevokedCerts, err := bkr.findUnrevoked(unchecked)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(unrevokedCerts) == 0 {
|
||||
// mark row as checked
|
||||
err = bkr.markRowChecked(unchecked)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// build a map of registration ID -> certificates, and collect a
|
||||
// list of unique registration IDs
|
||||
ownedBy := map[int64][]unrevokedCertificate{}
|
||||
var ids []int64
|
||||
for _, cert := range unrevokedCerts {
|
||||
if ownedBy[cert.RegistrationID] == nil {
|
||||
ids = append(ids, cert.RegistrationID)
|
||||
}
|
||||
ownedBy[cert.RegistrationID] = append(ownedBy[cert.RegistrationID], cert)
|
||||
}
|
||||
// get contact addresses for the list of IDs
|
||||
idToEmails, err := bkr.resolveContacts(ids)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// build a map of email -> certificates, this de-duplicates accounts with
|
||||
// the same email addresses
|
||||
emailsToCerts := map[string][]unrevokedCertificate{}
|
||||
for id, emails := range idToEmails {
|
||||
for _, email := range emails {
|
||||
emailsToCerts[email] = append(emailsToCerts[email], ownedBy[id]...)
|
||||
}
|
||||
}
|
||||
|
||||
// revoke each certificate and send emails to their owners
|
||||
err = bkr.revokeCerts(idToEmails[unchecked.RevokedBy], emailsToCerts)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// mark the key as checked
|
||||
err = bkr.markRowChecked(unchecked)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var config struct {
|
||||
BadKeyRevoker struct {
|
||||
cmd.DBConfig
|
||||
DebugAddr string
|
||||
|
||||
TLS cmd.TLSConfig
|
||||
RAService *cmd.GRPCClientConfig
|
||||
|
||||
// MaximumRevocations specifies the maximum number of certificates associated with
|
||||
// a key hash that bad-key-revoker will attempt to revoke. If the number of certificates
|
||||
// is higher than MaximumRevocations bad-key-revoker will error out and refuse to
|
||||
// progress until this is addressed.
|
||||
MaximumRevocations int
|
||||
// FindCertificatesBatchSize specifies the maximum number of serials to select from the
|
||||
// keyHashToSerial table at once
|
||||
FindCertificatesBatchSize int
|
||||
|
||||
// Interval specifies how long bad-key-revoker should sleep between attempting to find
|
||||
// blockedKeys rows to process when there is no work to do
|
||||
Interval cmd.ConfigDuration
|
||||
|
||||
Mailer struct {
|
||||
cmd.SMTPConfig
|
||||
// Path to a file containing a list of trusted root certificates for use
|
||||
// during the SMTP connection (as opposed to the gRPC connections).
|
||||
SMTPTrustedRootFile string
|
||||
|
||||
From string
|
||||
EmailSubject string
|
||||
EmailTemplate string
|
||||
}
|
||||
}
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
}
|
||||
configPath := flag.String("config", "", "File path to the configuration file for this service")
|
||||
flag.Parse()
|
||||
|
||||
if *configPath == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
err := cmd.ReadConfigFile(*configPath, &config)
|
||||
cmd.FailOnError(err, "Failed reading config file")
|
||||
|
||||
scope, logger := cmd.StatsAndLogging(config.Syslog, config.BadKeyRevoker.DebugAddr)
|
||||
clk := cmd.Clock()
|
||||
|
||||
scope.MustRegister(keysProcessed)
|
||||
scope.MustRegister(certsRevoked)
|
||||
scope.MustRegister(mailErrors)
|
||||
|
||||
dbURL, err := config.BadKeyRevoker.DBConfig.URL()
|
||||
cmd.FailOnError(err, "Couldn't load DB URL")
|
||||
dbMap, err := sa.NewDbMap(dbURL, config.BadKeyRevoker.DBConfig.MaxDBConns)
|
||||
cmd.FailOnError(err, "Could not connect to database")
|
||||
sa.SetSQLDebug(dbMap, logger)
|
||||
sa.InitDBMetrics(dbMap, scope)
|
||||
|
||||
tlsConfig, err := config.BadKeyRevoker.TLS.Load()
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
|
||||
clientMetrics := bgrpc.NewClientMetrics(scope)
|
||||
conn, err := bgrpc.ClientSetup(config.BadKeyRevoker.RAService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
|
||||
rac := rapb.NewRegistrationAuthorityClient(conn)
|
||||
|
||||
var smtpRoots *x509.CertPool
|
||||
if config.BadKeyRevoker.Mailer.SMTPTrustedRootFile != "" {
|
||||
pem, err := ioutil.ReadFile(config.BadKeyRevoker.Mailer.SMTPTrustedRootFile)
|
||||
cmd.FailOnError(err, "Loading trusted roots file")
|
||||
smtpRoots = x509.NewCertPool()
|
||||
if !smtpRoots.AppendCertsFromPEM(pem) {
|
||||
cmd.FailOnError(nil, "Failed to parse root certs PEM")
|
||||
}
|
||||
}
|
||||
|
||||
fromAddress, err := netmail.ParseAddress(config.BadKeyRevoker.Mailer.From)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Could not parse from address: %s", config.BadKeyRevoker.Mailer.From))
|
||||
|
||||
smtpPassword, err := config.BadKeyRevoker.Mailer.PasswordConfig.Pass()
|
||||
cmd.FailOnError(err, "Failed to load SMTP password")
|
||||
mailClient := mail.New(
|
||||
config.BadKeyRevoker.Mailer.Server,
|
||||
config.BadKeyRevoker.Mailer.Port,
|
||||
config.BadKeyRevoker.Mailer.Username,
|
||||
smtpPassword,
|
||||
smtpRoots,
|
||||
*fromAddress,
|
||||
logger,
|
||||
scope,
|
||||
1*time.Second, // reconnection base backoff
|
||||
5*60*time.Second, // reconnection maximum backoff
|
||||
)
|
||||
|
||||
if config.BadKeyRevoker.Mailer.EmailSubject == "" {
|
||||
cmd.Fail("BadKeyRevoker.Mailer.EmailSubject must be populated")
|
||||
}
|
||||
templateBytes, err := ioutil.ReadFile(config.BadKeyRevoker.Mailer.EmailTemplate)
|
||||
cmd.FailOnError(err, fmt.Sprintf("failed to read email template %q: %s", config.BadKeyRevoker.Mailer.EmailTemplate, err))
|
||||
emailTemplate, err := template.New("email").Parse(string(templateBytes))
|
||||
cmd.FailOnError(err, fmt.Sprintf("failed to parse email template %q: %s", config.BadKeyRevoker.Mailer.EmailTemplate, err))
|
||||
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
maxRevocations: config.BadKeyRevoker.MaximumRevocations,
|
||||
serialBatchSize: config.BadKeyRevoker.FindCertificatesBatchSize,
|
||||
raClient: rac,
|
||||
mailer: mailClient,
|
||||
emailSubject: config.BadKeyRevoker.Mailer.EmailSubject,
|
||||
emailTemplate: emailTemplate,
|
||||
logger: logger,
|
||||
}
|
||||
for {
|
||||
noWork, err := bkr.invoke()
|
||||
if err != nil {
|
||||
keysProcessed.WithLabelValues("error").Inc()
|
||||
logger.Errf("failed to process blockedKeys row: %s", err)
|
||||
continue
|
||||
}
|
||||
if noWork {
|
||||
time.Sleep(config.BadKeyRevoker.Interval.Duration)
|
||||
} else {
|
||||
keysProcessed.WithLabelValues("success").Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
"github.com/letsencrypt/boulder/db"
|
||||
"github.com/letsencrypt/boulder/mocks"
|
||||
rapb "github.com/letsencrypt/boulder/ra/proto"
|
||||
"github.com/letsencrypt/boulder/sa"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"github.com/letsencrypt/boulder/test/vars"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if !strings.HasSuffix(os.Getenv("BOULDER_CONFIG_DIR"), "config-next") {
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func randHash(t *testing.T) []byte {
|
||||
t.Helper()
|
||||
h := make([]byte, 32)
|
||||
_, err := rand.Read(h)
|
||||
test.AssertNotError(t, err, "failed to read rand")
|
||||
return h
|
||||
}
|
||||
|
||||
func insertBlockedRow(t *testing.T, dbMap *db.WrappedMap, hash []byte, by int64, checked bool) {
|
||||
t.Helper()
|
||||
_, err := dbMap.Exec(`INSERT INTO blockedKeys
|
||||
(keyHash, added, source, revokedBy, extantCertificatesChecked)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?)`,
|
||||
hash,
|
||||
time.Now(),
|
||||
1,
|
||||
by,
|
||||
checked,
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to add test row")
|
||||
}
|
||||
|
||||
func TestSelectUncheckedRows(t *testing.T) {
|
||||
dbMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
test.AssertNotError(t, err, "failed setting up db client")
|
||||
defer test.ResetSATestDatabase(t)()
|
||||
|
||||
bkr := &badKeyRevoker{dbMap: dbMap}
|
||||
|
||||
hashA, hashB := randHash(t), randHash(t)
|
||||
insertBlockedRow(t, dbMap, hashA, 1, true)
|
||||
row, err := bkr.selectUncheckedKey()
|
||||
test.AssertError(t, err, "selectUncheckedKey didn't fail with no rows to process")
|
||||
test.Assert(t, db.IsNoRows(err), "returned error is not sql.ErrNoRows")
|
||||
insertBlockedRow(t, dbMap, hashB, 1, false)
|
||||
row, err = bkr.selectUncheckedKey()
|
||||
test.AssertNotError(t, err, "selectUncheckKey failed")
|
||||
test.AssertByteEquals(t, row.KeyHash, hashB)
|
||||
test.AssertEquals(t, row.RevokedBy, int64(1))
|
||||
}
|
||||
|
||||
func insertRegistration(t *testing.T, dbMap *db.WrappedMap, addrs ...string) int64 {
|
||||
t.Helper()
|
||||
jwkHash := make([]byte, 2)
|
||||
_, err := rand.Read(jwkHash)
|
||||
test.AssertNotError(t, err, "failed to read rand")
|
||||
contactStr := "[]"
|
||||
if len(addrs) > 0 {
|
||||
contacts := []string{}
|
||||
for _, addr := range addrs {
|
||||
contacts = append(contacts, fmt.Sprintf(`"mailto:%s"`, addr))
|
||||
}
|
||||
contactStr = fmt.Sprintf("[%s]", strings.Join(contacts, ","))
|
||||
}
|
||||
res, err := dbMap.Exec(
|
||||
"INSERT INTO registrations (jwk, jwk_sha256, contact, agreement, initialIP, createdAt, status, LockCol) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
[]byte{},
|
||||
fmt.Sprintf("%x", jwkHash),
|
||||
contactStr,
|
||||
"yes",
|
||||
[]byte{},
|
||||
time.Now(),
|
||||
string(core.StatusValid),
|
||||
0,
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to insert test registrations row")
|
||||
regID, err := res.LastInsertId()
|
||||
test.AssertNotError(t, err, "failed to get registration ID")
|
||||
return regID
|
||||
}
|
||||
|
||||
func insertCert(t *testing.T, dbMap *db.WrappedMap, keyHash []byte, serial string, regID int64, expired bool, revoked bool) {
|
||||
t.Helper()
|
||||
_, err := dbMap.Exec(
|
||||
"INSERT INTO keyHashToSerial (keyHash, certNotAfter, certSerial) VALUES (?, ?, ?)",
|
||||
keyHash,
|
||||
time.Now(),
|
||||
serial,
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
|
||||
|
||||
status := string(core.StatusValid)
|
||||
if revoked {
|
||||
status = string(core.StatusRevoked)
|
||||
}
|
||||
_, err = dbMap.Exec(
|
||||
"INSERT INTO certificateStatus (serial, status, isExpired, ocspLAstUpdated, revokedDate, revokedReason, lastExpirationNagSent) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
serial,
|
||||
status,
|
||||
expired,
|
||||
time.Now(),
|
||||
time.Time{},
|
||||
0,
|
||||
time.Time{},
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to insert test certificateStatus row")
|
||||
|
||||
_, err = dbMap.Exec(
|
||||
"INSERT INTO certificates (serial, registrationID, der, digest, issued, expires) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
serial,
|
||||
regID,
|
||||
[]byte{1, 2, 3},
|
||||
[]byte{},
|
||||
time.Now(),
|
||||
time.Now(),
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to insert test certificates row")
|
||||
}
|
||||
|
||||
func TestFindUnrevoked(t *testing.T) {
|
||||
dbMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
test.AssertNotError(t, err, "failed setting up db client")
|
||||
defer test.ResetSATestDatabase(t)()
|
||||
|
||||
regID := insertRegistration(t, dbMap, "")
|
||||
|
||||
bkr := &badKeyRevoker{dbMap: dbMap, serialBatchSize: 1, maxRevocations: 10}
|
||||
|
||||
hashA := randHash(t)
|
||||
// insert valid, unexpired
|
||||
insertCert(t, dbMap, hashA, "ff", regID, false, false)
|
||||
// insert valid, expired
|
||||
insertCert(t, dbMap, hashA, "ee", regID, true, false)
|
||||
// insert revoked
|
||||
insertCert(t, dbMap, hashA, "dd", regID, false, true)
|
||||
|
||||
rows, err := bkr.findUnrevoked(uncheckedBlockedKey{KeyHash: hashA})
|
||||
test.AssertNotError(t, err, "findUnrevoked failed")
|
||||
test.AssertEquals(t, len(rows), 1)
|
||||
test.AssertEquals(t, rows[0].Serial, "ff")
|
||||
test.AssertEquals(t, rows[0].RegistrationID, int64(1))
|
||||
test.AssertByteEquals(t, rows[0].DER, []byte{1, 2, 3})
|
||||
|
||||
bkr.maxRevocations = 0
|
||||
_, err = bkr.findUnrevoked(uncheckedBlockedKey{KeyHash: hashA})
|
||||
test.AssertError(t, err, "findUnrevoked didn't fail with 0 maxRevocations")
|
||||
test.AssertEquals(t, err.Error(), fmt.Sprintf("too many certificates to revoke associated with %x: got 1, max 0", hashA))
|
||||
}
|
||||
|
||||
func TestResolveContacts(t *testing.T) {
|
||||
dbMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
test.AssertNotError(t, err, "failed setting up db client")
|
||||
defer test.ResetSATestDatabase(t)()
|
||||
|
||||
bkr := &badKeyRevoker{dbMap: dbMap}
|
||||
|
||||
regIDA := insertRegistration(t, dbMap, "")
|
||||
regIDB := insertRegistration(t, dbMap, "example.com", "example-2.com")
|
||||
regIDC := insertRegistration(t, dbMap, "example.com")
|
||||
regIDD := insertRegistration(t, dbMap, "example-2.com")
|
||||
|
||||
idToEmail, err := bkr.resolveContacts([]int64{regIDA, regIDB, regIDC, regIDD})
|
||||
test.AssertNotError(t, err, "resolveContacts failed")
|
||||
test.AssertDeepEquals(t, idToEmail, map[int64][]string{
|
||||
regIDA: {""},
|
||||
regIDB: {"example.com", "example-2.com"},
|
||||
regIDC: {"example.com"},
|
||||
regIDD: {"example-2.com"},
|
||||
})
|
||||
}
|
||||
|
||||
var testTemplate = template.Must(template.New("testing").Parse("{{range .}}{{.}}\n{{end}}"))
|
||||
|
||||
func TestSendMessage(t *testing.T) {
|
||||
mm := &mocks.Mailer{}
|
||||
bkr := &badKeyRevoker{mailer: mm, emailSubject: "testing", emailTemplate: testTemplate}
|
||||
|
||||
maxSerials = 2
|
||||
err := bkr.sendMessage("example.com", []string{"a", "b", "c"})
|
||||
test.AssertNotError(t, err, "sendMessages failed")
|
||||
test.AssertEquals(t, len(mm.Messages), 1)
|
||||
test.AssertEquals(t, mm.Messages[0].To, "example.com")
|
||||
test.AssertEquals(t, mm.Messages[0].Subject, bkr.emailSubject)
|
||||
test.AssertEquals(t, mm.Messages[0].Body, "a\nb\nand 1 more certificates.\n")
|
||||
|
||||
}
|
||||
|
||||
type mockRevoker struct {
|
||||
revoked int
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (mr *mockRevoker) AdministrativelyRevokeCertificate(ctx context.Context, in *rapb.AdministrativelyRevokeCertificateRequest, opts ...grpc.CallOption) (*corepb.Empty, error) {
|
||||
mr.mu.Lock()
|
||||
defer mr.mu.Unlock()
|
||||
mr.revoked++
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestRevokeCerts(t *testing.T) {
|
||||
dbMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
test.AssertNotError(t, err, "failed setting up db client")
|
||||
defer test.ResetSATestDatabase(t)()
|
||||
|
||||
mm := &mocks.Mailer{}
|
||||
mr := &mockRevoker{}
|
||||
bkr := &badKeyRevoker{dbMap: dbMap, raClient: mr, mailer: mm, emailSubject: "testing", emailTemplate: testTemplate}
|
||||
|
||||
err = bkr.revokeCerts([]string{"revoker@example.com", "revoker-b@example.com"}, map[string][]unrevokedCertificate{
|
||||
"revoker@example.com": {{ID: 0, Serial: "ff"}},
|
||||
"revoker-b@example.com": {{ID: 0, Serial: "ff"}},
|
||||
"other@example.com": {{ID: 1, Serial: "ee"}},
|
||||
})
|
||||
test.AssertNotError(t, err, "revokeCerts failed")
|
||||
test.AssertEquals(t, len(mm.Messages), 1)
|
||||
test.AssertEquals(t, mm.Messages[0].To, "other@example.com")
|
||||
test.AssertEquals(t, mm.Messages[0].Subject, bkr.emailSubject)
|
||||
test.AssertEquals(t, mm.Messages[0].Body, "ee\n")
|
||||
}
|
||||
|
||||
func TestInvoke(t *testing.T) {
|
||||
dbMap, err := sa.NewDbMap(vars.DBConnSAFullPerms, 0)
|
||||
test.AssertNotError(t, err, "failed setting up db client")
|
||||
defer test.ResetSATestDatabase(t)()
|
||||
|
||||
mm := &mocks.Mailer{}
|
||||
mr := &mockRevoker{}
|
||||
bkr := &badKeyRevoker{dbMap: dbMap, maxRevocations: 10, serialBatchSize: 1, raClient: mr, mailer: mm, emailSubject: "testing", emailTemplate: testTemplate}
|
||||
|
||||
// populate DB with all the test data
|
||||
regIDA := insertRegistration(t, dbMap, "example.com")
|
||||
regIDB := insertRegistration(t, dbMap, "example.com")
|
||||
regIDC := insertRegistration(t, dbMap, "other.example.com", "uno.example.com")
|
||||
regIDD := insertRegistration(t, dbMap, "")
|
||||
hashA := randHash(t)
|
||||
insertBlockedRow(t, dbMap, hashA, regIDC, false)
|
||||
insertCert(t, dbMap, hashA, "ff", regIDA, false, false)
|
||||
insertCert(t, dbMap, hashA, "ee", regIDB, false, false)
|
||||
insertCert(t, dbMap, hashA, "dd", regIDC, false, false)
|
||||
insertCert(t, dbMap, hashA, "cc", regIDD, false, false)
|
||||
|
||||
noWork, err := bkr.invoke()
|
||||
test.AssertNotError(t, err, "invoke failed")
|
||||
test.AssertEquals(t, noWork, false)
|
||||
test.AssertEquals(t, mr.revoked, 4)
|
||||
test.AssertEquals(t, len(mm.Messages), 1)
|
||||
test.AssertEquals(t, mm.Messages[0].To, "example.com")
|
||||
|
||||
var checked struct {
|
||||
ExtantCertificatesChecked bool
|
||||
}
|
||||
err = dbMap.SelectOne(&checked, "SELECT extantCertificatesChecked FROM blockedKeys WHERE keyHash = ?", hashA)
|
||||
test.AssertNotError(t, err, "failed to select row from blockedKeys")
|
||||
test.AssertEquals(t, checked.ExtantCertificatesChecked, true)
|
||||
|
||||
// add a row with no associated valid certificates
|
||||
hashB := randHash(t)
|
||||
insertBlockedRow(t, dbMap, hashB, regIDC, false)
|
||||
insertCert(t, dbMap, hashB, "bb", regIDA, true, true)
|
||||
|
||||
noWork, err = bkr.invoke()
|
||||
test.AssertNotError(t, err, "invoke failed")
|
||||
test.AssertEquals(t, noWork, false)
|
||||
|
||||
checked.ExtantCertificatesChecked = false
|
||||
err = dbMap.SelectOne(&checked, "SELECT extantCertificatesChecked FROM blockedKeys WHERE keyHash = ?", hashB)
|
||||
test.AssertNotError(t, err, "failed to select row from blockedKeys")
|
||||
test.AssertEquals(t, checked.ExtantCertificatesChecked, true)
|
||||
|
||||
noWork, err = bkr.invoke()
|
||||
test.AssertNotError(t, err, "invoke failed")
|
||||
test.AssertEquals(t, noWork, true)
|
||||
}
|
||||
|
|
@ -27,11 +27,12 @@ func _() {
|
|||
_ = x[StoreIssuerInfo-16]
|
||||
_ = x[StoreKeyHashes-17]
|
||||
_ = x[BlockedKeyTable-18]
|
||||
_ = x[StoreRevokerInfo-19]
|
||||
}
|
||||
|
||||
const _FeatureFlag_name = "unusedWriteIssuedNamesPrecertHeadNonceStatusOKRemoveWFE2AccountIDCheckRenewalFirstParallelCheckFailedValidationDeleteUnusedChallengesCAAValidationMethodsCAAAccountURIEnforceMultiVAMultiVAFullResultsMandatoryPOSTAsGETAllowV1RegistrationV1DisableNewValidationsPrecertificateRevocationStripDefaultSchemePortStoreIssuerInfoStoreKeyHashesBlockedKeyTable"
|
||||
const _FeatureFlag_name = "unusedWriteIssuedNamesPrecertHeadNonceStatusOKRemoveWFE2AccountIDCheckRenewalFirstParallelCheckFailedValidationDeleteUnusedChallengesCAAValidationMethodsCAAAccountURIEnforceMultiVAMultiVAFullResultsMandatoryPOSTAsGETAllowV1RegistrationV1DisableNewValidationsPrecertificateRevocationStripDefaultSchemePortStoreIssuerInfoStoreKeyHashesBlockedKeyTableStoreRevokerInfo"
|
||||
|
||||
var _FeatureFlag_index = [...]uint16{0, 6, 29, 46, 65, 82, 111, 133, 153, 166, 180, 198, 216, 235, 258, 282, 304, 319, 333, 348}
|
||||
var _FeatureFlag_index = [...]uint16{0, 6, 29, 46, 65, 82, 111, 133, 153, 166, 180, 198, 216, 235, 258, 282, 304, 319, 333, 348, 364}
|
||||
|
||||
func (i FeatureFlag) String() string {
|
||||
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ const (
|
|||
// BlockedKeyTable enables storage, and checking, of the blockedKeys table in addition
|
||||
// to the blocked key list
|
||||
BlockedKeyTable
|
||||
// StoreRevokerInfo enables storage of the revoker and a bool indicating if the row
|
||||
// was checked for extant unrevoked certificates in the blockedKeys table. It should
|
||||
// only be enabled if BlockedKeyTable is also enabled.
|
||||
StoreRevokerInfo
|
||||
)
|
||||
|
||||
// List of features and their default value, protected by fMu
|
||||
|
|
@ -75,6 +79,7 @@ var features = map[FeatureFlag]bool{
|
|||
WriteIssuedNamesPrecert: false,
|
||||
StoreKeyHashes: false,
|
||||
BlockedKeyTable: false,
|
||||
StoreRevokerInfo: false,
|
||||
}
|
||||
|
||||
var fMu = new(sync.RWMutex)
|
||||
|
|
|
|||
9
ra/ra.go
9
ra/ra.go
|
|
@ -1687,7 +1687,7 @@ func revokeEvent(state, serial, cn string, names []string, revocationCode revoca
|
|||
|
||||
// revokeCertificate generates a revoked OCSP response for the given certificate, stores
|
||||
// the revocation information, and purges OCSP request URLs from Akamai.
|
||||
func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, cert x509.Certificate, code revocation.Reason, source string, comment string) error {
|
||||
func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, cert x509.Certificate, code revocation.Reason, revokedBy int64, source string, comment string) error {
|
||||
status := string(core.OCSPStatusRevoked)
|
||||
reason := int32(code)
|
||||
revokedAt := ra.clk.Now().UnixNano()
|
||||
|
|
@ -1726,6 +1726,9 @@ func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, cert
|
|||
if comment != "" {
|
||||
req.Comment = &comment
|
||||
}
|
||||
if features.Enabled(features.StoreRevokerInfo) && revokedBy != 0 {
|
||||
req.RevokedBy = &revokedBy
|
||||
}
|
||||
if _, err = ra.SA.AddBlockedKey(ctx, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -1745,7 +1748,7 @@ func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, cert
|
|||
// RevokeCertificateWithReg terminates trust in the certificate provided.
|
||||
func (ra *RegistrationAuthorityImpl) RevokeCertificateWithReg(ctx context.Context, cert x509.Certificate, revocationCode revocation.Reason, regID int64) error {
|
||||
serialString := core.SerialToString(cert.SerialNumber)
|
||||
err := ra.revokeCertificate(ctx, cert, revocationCode, "API", "")
|
||||
err := ra.revokeCertificate(ctx, cert, revocationCode, regID, "API", "")
|
||||
|
||||
state := "Failure"
|
||||
defer func() {
|
||||
|
|
@ -1777,7 +1780,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte
|
|||
serialString := core.SerialToString(cert.SerialNumber)
|
||||
// TODO(#4774): allow setting the comment via the RPC, format should be:
|
||||
// "revoked by %s: %s", user, comment
|
||||
err := ra.revokeCertificate(ctx, cert, revocationCode, "admin-revoker", fmt.Sprintf("revoked by %s", user))
|
||||
err := ra.revokeCertificate(ctx, cert, revocationCode, 0, "admin-revoker", fmt.Sprintf("revoked by %s", user))
|
||||
|
||||
state := "Failure"
|
||||
defer func() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
-- +goose Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
ALTER TABLE blockedKeys ADD `revokedBy` BIGINT(20) DEFAULT 0;
|
||||
ALTER TABLE blockedKeys ADD `extantCertificatesChecked` BOOLEAN DEFAULT FALSE;
|
||||
CREATE INDEX `extantCertificatesChecked_idx` ON blockedKeys (`extantCertificatesChecked`);
|
||||
|
||||
-- +goose Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
ALTER TABLE blockedKeys DROP `revokedBy`;
|
||||
ALTER TABLE blockedKeys DROP `extantCertificatesChecked`;
|
||||
DROP INDEX `extantCertificatesChecked_idx` ON blockedKeys;
|
||||
|
|
@ -140,5 +140,4 @@ func initTables(dbMap *gorp.DbMap) {
|
|||
dbMap.AddTableWithName(recordedSerialModel{}, "serials").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(precertificateModel{}, "precertificates").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(keyHashModel{}, "keyHashToSerial").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(blockedKeyModel{}, "blockedKeys").SetKeys(true, "ID")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -622,11 +622,3 @@ var stringToSourceInt = map[string]int{
|
|||
"API": 1,
|
||||
"admin-revoker": 2,
|
||||
}
|
||||
|
||||
type blockedKeyModel struct {
|
||||
ID int64
|
||||
KeyHash []byte
|
||||
Added time.Time
|
||||
Source int
|
||||
Comment *string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1809,6 +1809,7 @@ type AddBlockedKeyRequest struct {
|
|||
Added *int64 `protobuf:"varint,2,opt,name=added" json:"added,omitempty"`
|
||||
Source *string `protobuf:"bytes,3,opt,name=source" json:"source,omitempty"`
|
||||
Comment *string `protobuf:"bytes,4,opt,name=comment" json:"comment,omitempty"`
|
||||
RevokedBy *int64 `protobuf:"varint,5,opt,name=revokedBy" json:"revokedBy,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
|
@ -1867,6 +1868,13 @@ func (m *AddBlockedKeyRequest) GetComment() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *AddBlockedKeyRequest) GetRevokedBy() int64 {
|
||||
if m != nil && m.RevokedBy != nil {
|
||||
return *m.RevokedBy
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type KeyBlockedRequest struct {
|
||||
KeyHash []byte `protobuf:"bytes,1,opt,name=keyHash" json:"keyHash,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
|
|
@ -1950,122 +1958,123 @@ func init() {
|
|||
func init() { proto.RegisterFile("sa/proto/sa.proto", fileDescriptor_099fb35e782a48a6) }
|
||||
|
||||
var fileDescriptor_099fb35e782a48a6 = []byte{
|
||||
// 1830 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xeb, 0x72, 0x1b, 0xb7,
|
||||
0x15, 0xe6, 0xc5, 0x94, 0xc9, 0xa3, 0x2b, 0x61, 0x99, 0xdd, 0xd0, 0xb2, 0x4d, 0x23, 0x8e, 0x47,
|
||||
0x99, 0x4e, 0x15, 0x67, 0x9b, 0x49, 0x32, 0xa3, 0xd6, 0x89, 0x14, 0xca, 0xb2, 0x62, 0x47, 0x66,
|
||||
0x96, 0xb5, 0xda, 0xe9, 0xf4, 0xcf, 0x86, 0x8b, 0xd0, 0x5b, 0x53, 0xbb, 0x0c, 0x00, 0x4a, 0xa6,
|
||||
0x7e, 0x77, 0xa6, 0x79, 0x82, 0x4e, 0x7f, 0xf6, 0x39, 0xfa, 0x12, 0x7d, 0xa5, 0x0e, 0x0e, 0xb0,
|
||||
0x57, 0xee, 0x52, 0xd5, 0xb4, 0xd3, 0x7f, 0x7b, 0x0e, 0xce, 0x0d, 0xc0, 0xb9, 0x7c, 0x58, 0x68,
|
||||
0x0b, 0xf7, 0x93, 0x29, 0x0f, 0x65, 0xf8, 0x89, 0x70, 0xf7, 0xf0, 0x83, 0xd4, 0x84, 0xdb, 0xbd,
|
||||
0x3b, 0x0a, 0x39, 0x33, 0x0b, 0xea, 0x53, 0x2f, 0xd1, 0x1e, 0x6c, 0x38, 0x6c, 0xec, 0x0b, 0xc9,
|
||||
0x5d, 0xe9, 0x87, 0xc1, 0x49, 0x9f, 0x6c, 0x40, 0xcd, 0xf7, 0xac, 0x6a, 0xaf, 0xba, 0x5b, 0x77,
|
||||
0x6a, 0xbe, 0x47, 0x1f, 0x00, 0x7c, 0x3b, 0x7c, 0x7d, 0xfa, 0x7b, 0xf6, 0xc3, 0x4b, 0x36, 0x27,
|
||||
0x5b, 0x50, 0xff, 0xf3, 0xe5, 0x3b, 0x5c, 0x5e, 0x73, 0xd4, 0x27, 0x7d, 0x04, 0x9b, 0x07, 0x33,
|
||||
0xf9, 0x36, 0xe4, 0xfe, 0xd5, 0xa2, 0x89, 0x16, 0x9a, 0xf8, 0x67, 0x15, 0x1e, 0x1c, 0x33, 0x39,
|
||||
0x60, 0x81, 0xe7, 0x07, 0xe3, 0x8c, 0xb4, 0xc3, 0x7e, 0x9a, 0x31, 0x21, 0xc9, 0x13, 0xd8, 0xe0,
|
||||
0x99, 0x38, 0x4c, 0x04, 0x39, 0xae, 0x92, 0xf3, 0x3d, 0x16, 0x48, 0xff, 0x47, 0x9f, 0xf1, 0xdf,
|
||||
0xcd, 0xa7, 0xcc, 0xaa, 0xa1, 0x9b, 0x1c, 0x97, 0xec, 0xc2, 0x66, 0xc2, 0x39, 0x73, 0x27, 0x33,
|
||||
0x66, 0xd5, 0x51, 0x30, 0xcf, 0x26, 0x0f, 0x00, 0x2e, 0xdc, 0x89, 0xef, 0xbd, 0x09, 0xa4, 0x3f,
|
||||
0xb1, 0x6e, 0xa1, 0xd7, 0x14, 0x87, 0x0a, 0xb8, 0x7f, 0xcc, 0xe4, 0x99, 0x62, 0x64, 0x22, 0x17,
|
||||
0x37, 0x0d, 0xdd, 0x82, 0xdb, 0x5e, 0x78, 0xee, 0xfa, 0x81, 0xb0, 0x6a, 0xbd, 0xfa, 0x6e, 0xcb,
|
||||
0x89, 0x48, 0x75, 0xa8, 0x41, 0x78, 0x89, 0x01, 0xd6, 0x1d, 0xf5, 0x49, 0xff, 0x51, 0x85, 0x3b,
|
||||
0x05, 0x2e, 0xc9, 0x97, 0xd0, 0xc0, 0xd0, 0xac, 0x6a, 0xaf, 0xbe, 0xbb, 0x6a, 0xd3, 0x3d, 0xe1,
|
||||
0xee, 0x15, 0xc8, 0xed, 0x7d, 0xe7, 0x4e, 0x8f, 0x26, 0xec, 0x9c, 0x05, 0xd2, 0xd1, 0x0a, 0xdd,
|
||||
0xd7, 0x00, 0x09, 0x93, 0x74, 0x60, 0x45, 0x3b, 0x37, 0xb7, 0x64, 0x28, 0xf2, 0x31, 0x34, 0xdc,
|
||||
0x99, 0x7c, 0x7b, 0x85, 0xa7, 0xba, 0x6a, 0xdf, 0xd9, 0xc3, 0x54, 0xc9, 0xde, 0x98, 0x96, 0xa0,
|
||||
0xff, 0xaa, 0x41, 0xfb, 0x1b, 0xc6, 0xd5, 0x51, 0x8e, 0x5c, 0xc9, 0x86, 0xd2, 0x95, 0x33, 0xa1,
|
||||
0x0c, 0x0b, 0xc6, 0x7d, 0x77, 0x12, 0x19, 0xd6, 0x14, 0xf2, 0x51, 0xc2, 0x5c, 0x83, 0xa1, 0xd4,
|
||||
0x3d, 0x85, 0x23, 0x31, 0x7d, 0xe5, 0x0a, 0xf9, 0x66, 0xea, 0xb9, 0x92, 0x79, 0xe6, 0x0a, 0xf2,
|
||||
0x6c, 0xd2, 0x83, 0x55, 0xce, 0x2e, 0xc2, 0x77, 0xcc, 0xeb, 0xbb, 0x92, 0x59, 0x0d, 0x94, 0x4a,
|
||||
0xb3, 0xc8, 0x63, 0x58, 0x37, 0xa4, 0xc3, 0x5c, 0x11, 0x06, 0xd6, 0x0a, 0xca, 0x64, 0x99, 0xe4,
|
||||
0x33, 0xb8, 0x3b, 0x71, 0x85, 0x3c, 0x7a, 0x3f, 0xf5, 0xf5, 0xd5, 0x9c, 0xba, 0xe3, 0x21, 0x0b,
|
||||
0xa4, 0x75, 0x1b, 0xa5, 0x8b, 0x17, 0x09, 0x85, 0x35, 0x15, 0x90, 0xc3, 0xc4, 0x34, 0x0c, 0x04,
|
||||
0xb3, 0x9a, 0x58, 0x00, 0x19, 0x1e, 0xe9, 0x42, 0x33, 0x08, 0xe5, 0xc1, 0x8f, 0x92, 0x71, 0xab,
|
||||
0x85, 0xc6, 0x62, 0x9a, 0xec, 0x40, 0xcb, 0x17, 0x68, 0x96, 0x79, 0x16, 0xf4, 0xaa, 0xbb, 0x4d,
|
||||
0x27, 0x61, 0x7c, 0x7b, 0xab, 0x59, 0xdb, 0xaa, 0xd3, 0x1e, 0xac, 0x0c, 0x93, 0xd3, 0x2a, 0x38,
|
||||
0x45, 0xba, 0x0f, 0x0d, 0xc7, 0x0d, 0xc6, 0xe8, 0x8a, 0xb9, 0x7c, 0xe2, 0x33, 0x21, 0x4d, 0xb6,
|
||||
0xc5, 0xb4, 0x52, 0x9e, 0xb8, 0x52, 0xad, 0xd4, 0x70, 0xc5, 0x50, 0xf4, 0x3e, 0x34, 0xbe, 0x09,
|
||||
0x67, 0x81, 0x24, 0xdb, 0xd0, 0x18, 0xa9, 0x0f, 0xa3, 0xa9, 0x09, 0xfa, 0x07, 0x78, 0x88, 0xcb,
|
||||
0xa9, 0x3b, 0x15, 0x87, 0xf3, 0x53, 0xf7, 0x9c, 0xc5, 0x99, 0xfe, 0x10, 0x1a, 0x5c, 0xb9, 0x47,
|
||||
0xc5, 0x55, 0xbb, 0xa5, 0xb2, 0x0f, 0xe3, 0x71, 0x34, 0x5f, 0x59, 0x0e, 0x94, 0x82, 0x49, 0x70,
|
||||
0x4d, 0xd0, 0xbf, 0x56, 0x61, 0x0d, 0x4d, 0x1b, 0x73, 0xe4, 0x2b, 0x58, 0x1b, 0xa5, 0x68, 0x93,
|
||||
0xcc, 0xf7, 0x94, 0xb9, 0xb4, 0x5c, 0x3a, 0x8b, 0x33, 0x0a, 0xdd, 0xcf, 0x33, 0xc9, 0x4c, 0xe0,
|
||||
0x96, 0x72, 0x64, 0xce, 0x0a, 0xbf, 0x93, 0x3d, 0xd6, 0xd2, 0x7b, 0x1c, 0xc0, 0x7d, 0x74, 0x90,
|
||||
0x6e, 0x79, 0xe2, 0x70, 0x7e, 0x32, 0x88, 0x76, 0xa8, 0x3a, 0xd7, 0xd4, 0x74, 0xb7, 0x9a, 0x3f,
|
||||
0x4d, 0x76, 0x5c, 0x2b, 0xde, 0x31, 0xfd, 0xb9, 0x0a, 0x8f, 0xd0, 0xe4, 0x49, 0x70, 0xf1, 0xdf,
|
||||
0xb7, 0x88, 0x2e, 0x34, 0xdf, 0x86, 0x42, 0xe2, 0x6e, 0x74, 0x5f, 0x8b, 0xe9, 0x24, 0x94, 0x7a,
|
||||
0x49, 0x28, 0x43, 0x20, 0x18, 0xc9, 0x6b, 0xee, 0x31, 0x1e, 0xbb, 0xde, 0x81, 0x96, 0x3b, 0xc2,
|
||||
0xdd, 0xc7, 0x5e, 0x13, 0xc6, 0xf5, 0xfb, 0x7b, 0x01, 0xdb, 0x68, 0xf4, 0xf9, 0xf7, 0xfd, 0xd3,
|
||||
0x21, 0x93, 0xb1, 0xd9, 0x0e, 0xac, 0x5c, 0xfa, 0x81, 0x17, 0x5e, 0x1a, 0x9b, 0x86, 0x2a, 0x6f,
|
||||
0x72, 0xf4, 0x29, 0x6c, 0x1b, 0x23, 0x47, 0xef, 0x7d, 0x91, 0x58, 0x4a, 0x69, 0x54, 0xb3, 0x1a,
|
||||
0x03, 0xe8, 0x0d, 0x38, 0xbb, 0xf0, 0xc3, 0x99, 0x48, 0x25, 0x65, 0x56, 0xbb, 0xac, 0x91, 0x6d,
|
||||
0x43, 0x83, 0xb3, 0xf1, 0x49, 0x3f, 0xba, 0x7f, 0x24, 0x54, 0x85, 0x69, 0x75, 0xa5, 0xc7, 0xf0,
|
||||
0x0b, 0xf5, 0x9a, 0x8e, 0xa1, 0xa8, 0x84, 0xad, 0x03, 0xcf, 0xd3, 0x65, 0x18, 0xf9, 0x88, 0x6d,
|
||||
0x55, 0x53, 0xb6, 0x52, 0x35, 0x5a, 0xcb, 0x74, 0x3a, 0x0b, 0x6e, 0x8f, 0x38, 0xc3, 0x4e, 0xa6,
|
||||
0x1b, 0x7a, 0x44, 0xaa, 0x15, 0x86, 0x05, 0x2f, 0x4c, 0x8f, 0x8b, 0x48, 0x55, 0x21, 0x77, 0x0f,
|
||||
0x3c, 0x2f, 0xb5, 0xcb, 0xc8, 0xf7, 0x16, 0xd4, 0x3d, 0xc6, 0xa3, 0x79, 0xeb, 0x31, 0x5e, 0xbc,
|
||||
0x33, 0x55, 0x03, 0xaa, 0x17, 0xa1, 0xcb, 0x35, 0x07, 0xbf, 0x55, 0x84, 0xbe, 0x10, 0xb3, 0xb8,
|
||||
0xa5, 0x1a, 0x4a, 0x65, 0x19, 0x7e, 0xf1, 0x93, 0xbe, 0x69, 0xa3, 0x31, 0x4d, 0x9f, 0x42, 0x27,
|
||||
0x1f, 0x88, 0xe9, 0x6e, 0xea, 0xa4, 0xfd, 0x71, 0xd4, 0x70, 0xd4, 0x49, 0x23, 0x45, 0x07, 0xb0,
|
||||
0x86, 0x19, 0x97, 0x2e, 0xa1, 0x14, 0x7e, 0x20, 0x4f, 0xe1, 0xce, 0x4c, 0xb0, 0x33, 0x3b, 0x5b,
|
||||
0x19, 0x18, 0x7d, 0xd3, 0x29, 0x5a, 0xa2, 0xaf, 0x80, 0x46, 0x13, 0x17, 0x2d, 0x17, 0xd7, 0x54,
|
||||
0xde, 0x4f, 0x07, 0x56, 0xdc, 0xd1, 0x48, 0xc6, 0x07, 0x63, 0x28, 0x3a, 0x87, 0x5f, 0x1c, 0x33,
|
||||
0x5d, 0x14, 0xcf, 0x43, 0x9e, 0xe9, 0x67, 0x89, 0x4a, 0x35, 0xad, 0x52, 0xdc, 0xc6, 0xca, 0x36,
|
||||
0x52, 0x2f, 0xdf, 0xc8, 0xdf, 0xab, 0x60, 0x1d, 0x33, 0xf9, 0x7f, 0x83, 0x0d, 0x6a, 0x9a, 0x72,
|
||||
0xf6, 0xd3, 0xcc, 0xe7, 0x26, 0x96, 0x2b, 0x9d, 0x69, 0x4d, 0x27, 0xcf, 0xa6, 0x7f, 0xab, 0xc2,
|
||||
0x46, 0x0e, 0x5b, 0xfc, 0x3a, 0x9a, 0xfd, 0xba, 0x1d, 0xdf, 0x57, 0xbd, 0x60, 0x09, 0xac, 0x40,
|
||||
0xd9, 0xff, 0x3d, 0xac, 0x78, 0x05, 0x0f, 0x0f, 0x3c, 0xaf, 0x08, 0x2a, 0xc6, 0x27, 0xf7, 0x71,
|
||||
0x36, 0xd0, 0x65, 0xd6, 0x1e, 0xc3, 0x56, 0x0e, 0x9c, 0xe2, 0xb1, 0xf9, 0x5e, 0xd4, 0x6c, 0xd4,
|
||||
0x27, 0xa5, 0x0b, 0x52, 0xf6, 0x02, 0x0c, 0xfe, 0x08, 0xda, 0x19, 0x19, 0x3b, 0x67, 0xaa, 0xae,
|
||||
0x4d, 0x5d, 0x81, 0xe5, 0x20, 0xdc, 0x28, 0xa8, 0xe5, 0x25, 0xd8, 0x88, 0x6b, 0xc0, 0x62, 0x32,
|
||||
0x57, 0x53, 0xaa, 0xa6, 0x15, 0xf4, 0x31, 0x17, 0x8c, 0xdf, 0xaa, 0x76, 0x79, 0x84, 0x41, 0x6e,
|
||||
0x61, 0xad, 0xc7, 0x34, 0xfd, 0x4b, 0x0d, 0x76, 0x9e, 0xfb, 0x81, 0x3b, 0xf1, 0xaf, 0x58, 0x21,
|
||||
0xc8, 0x2e, 0x28, 0x19, 0x03, 0xca, 0x6a, 0x19, 0x50, 0x96, 0x6a, 0x54, 0xf5, 0x4c, 0xa3, 0xc2,
|
||||
0x69, 0x22, 0x25, 0x3b, 0x9f, 0x46, 0x40, 0xad, 0xe5, 0x24, 0x0c, 0xd2, 0x87, 0x36, 0x0e, 0x41,
|
||||
0xe3, 0x74, 0x14, 0x72, 0x4f, 0x58, 0x0d, 0xbc, 0xa4, 0x8e, 0xbe, 0xa4, 0xb3, 0xdc, 0xb2, 0xb3,
|
||||
0xa8, 0x40, 0x9e, 0xc1, 0x66, 0xc2, 0x3c, 0xe2, 0x3c, 0xe4, 0x08, 0xe4, 0x56, 0xed, 0x6d, 0x6d,
|
||||
0x63, 0xc0, 0xc3, 0x1f, 0x26, 0xec, 0xbc, 0xcf, 0xa4, 0xeb, 0x4f, 0x84, 0x93, 0x17, 0xa6, 0xef,
|
||||
0x61, 0xfb, 0xc0, 0xf3, 0x0e, 0x27, 0xe1, 0xe8, 0x1d, 0xf3, 0x5e, 0xb2, 0x79, 0x6a, 0xd0, 0xbc,
|
||||
0x63, 0xf3, 0x17, 0xae, 0x78, 0x6b, 0xda, 0x69, 0x44, 0xaa, 0x7a, 0x77, 0x3d, 0x8f, 0x79, 0x51,
|
||||
0x4b, 0x45, 0x02, 0x4f, 0x27, 0x9c, 0xf1, 0x11, 0x8b, 0x21, 0x2b, 0x52, 0xd8, 0xe0, 0xc3, 0x73,
|
||||
0x95, 0xef, 0xe6, 0x04, 0x22, 0x92, 0xfe, 0x0a, 0xda, 0x2f, 0xd9, 0xdc, 0x78, 0xbe, 0xd6, 0xad,
|
||||
0xfd, 0xf3, 0x1d, 0xd8, 0x1a, 0xca, 0x90, 0xbb, 0xe3, 0xe8, 0xba, 0xe4, 0x9c, 0xec, 0xc3, 0xe6,
|
||||
0x31, 0xcb, 0x00, 0x14, 0x42, 0x70, 0x2a, 0x67, 0xba, 0x42, 0x97, 0xe8, 0xb3, 0x48, 0x73, 0x69,
|
||||
0x85, 0xfc, 0x06, 0xb6, 0x73, 0xca, 0x87, 0x73, 0xf5, 0x6a, 0xdb, 0x50, 0x16, 0x92, 0x57, 0x5c,
|
||||
0x89, 0xf6, 0xa7, 0xb0, 0x71, 0xcc, 0xd2, 0xf8, 0x8f, 0x80, 0xd2, 0xd3, 0xc3, 0xb0, 0xdb, 0xd6,
|
||||
0x3a, 0xa9, 0x65, 0x5a, 0x21, 0x9f, 0x41, 0x5b, 0x3d, 0xec, 0x38, 0x1b, 0xdd, 0x44, 0x6b, 0x1f,
|
||||
0xc3, 0x5c, 0x7c, 0x3c, 0xa4, 0x15, 0xef, 0x22, 0x1a, 0xcc, 0x8b, 0xd0, 0x0a, 0x19, 0x82, 0x55,
|
||||
0x86, 0x53, 0xc9, 0x87, 0x31, 0x84, 0x2c, 0x47, 0xb1, 0xdd, 0xad, 0x3c, 0xce, 0xa4, 0x15, 0xf2,
|
||||
0x02, 0x3a, 0xc5, 0xc0, 0x90, 0x3c, 0x8a, 0xa5, 0xcb, 0x40, 0x63, 0xb7, 0x15, 0x8b, 0xd0, 0x0a,
|
||||
0xf9, 0x0e, 0xee, 0x95, 0x48, 0x23, 0x42, 0xbe, 0xa9, 0x39, 0x1b, 0x56, 0x53, 0xa0, 0x8e, 0x74,
|
||||
0xe2, 0xb5, 0x0c, 0xca, 0xcb, 0xea, 0x7c, 0x0e, 0xeb, 0x19, 0xcc, 0x46, 0xac, 0x78, 0x35, 0x07,
|
||||
0xe3, 0xb2, 0x7a, 0x5f, 0xc0, 0x7a, 0x06, 0xa1, 0x69, 0xbd, 0x22, 0xd0, 0xd6, 0xc5, 0x9b, 0xd2,
|
||||
0x2c, 0x5a, 0x21, 0xaf, 0xe1, 0x83, 0x52, 0xa0, 0x46, 0x1e, 0x2b, 0xd1, 0xeb, 0x70, 0x5c, 0xce,
|
||||
0xe0, 0xd7, 0x98, 0x56, 0xd9, 0x7e, 0x4b, 0xb6, 0x17, 0x06, 0xd2, 0x49, 0xdf, 0xee, 0x16, 0x75,
|
||||
0x7f, 0xbc, 0x50, 0xb2, 0x30, 0x79, 0x6d, 0xb2, 0xa3, 0x4c, 0x94, 0x4d, 0xe4, 0x2e, 0x59, 0x9c,
|
||||
0x78, 0xb4, 0x42, 0xde, 0xe0, 0x0c, 0x2f, 0x1a, 0x48, 0x36, 0xa1, 0xc6, 0xde, 0x92, 0x3f, 0x1b,
|
||||
0x65, 0x01, 0x3e, 0x33, 0x79, 0x52, 0x38, 0xe9, 0xec, 0xc2, 0x9a, 0xcf, 0x5c, 0xd6, 0x9f, 0x60,
|
||||
0x67, 0x09, 0x48, 0xb2, 0xc9, 0x13, 0x13, 0xda, 0x35, 0x30, 0xaa, 0x64, 0xd3, 0xdf, 0x9b, 0xe8,
|
||||
0x0a, 0x5f, 0x35, 0x36, 0xf9, 0x28, 0x8e, 0x64, 0xd9, 0xb3, 0x27, 0x1b, 0xb0, 0x83, 0x38, 0xec,
|
||||
0xac, 0xc8, 0xdc, 0xa3, 0x74, 0xac, 0x37, 0x09, 0xf3, 0x53, 0x80, 0xa4, 0xe1, 0x12, 0x6c, 0x19,
|
||||
0x0b, 0x0d, 0x38, 0x97, 0x5a, 0xfb, 0xb0, 0x79, 0xca, 0x2e, 0x73, 0xfd, 0x75, 0xa1, 0x1b, 0x96,
|
||||
0x74, 0xc8, 0x2f, 0x80, 0xe8, 0xdf, 0x11, 0xd7, 0xea, 0xaf, 0x6a, 0xde, 0xd1, 0xf9, 0x54, 0xce,
|
||||
0x69, 0x85, 0x9c, 0xc0, 0x46, 0x16, 0x56, 0x93, 0x0f, 0x70, 0x43, 0x45, 0x98, 0xbf, 0xdb, 0x2d,
|
||||
0x5a, 0x32, 0x33, 0xbe, 0x42, 0x7e, 0x0b, 0x6d, 0x05, 0x90, 0xb2, 0x2d, 0x77, 0x89, 0xb5, 0x5c,
|
||||
0x24, 0x4f, 0xa1, 0x15, 0x3f, 0x70, 0x4c, 0x49, 0xe5, 0xde, 0x3b, 0x79, 0x8d, 0x7d, 0xe8, 0xf4,
|
||||
0x99, 0x3b, 0x92, 0xfe, 0xc5, 0xe2, 0xc6, 0x17, 0x93, 0x34, 0xa7, 0xfc, 0x04, 0x9a, 0xa7, 0xec,
|
||||
0x12, 0xf3, 0x8f, 0x98, 0x25, 0x24, 0xba, 0x69, 0x02, 0xc3, 0x22, 0x43, 0x83, 0xd2, 0x07, 0x3c,
|
||||
0x1c, 0x31, 0x21, 0xfc, 0x60, 0x5c, 0xa8, 0x11, 0x59, 0xfe, 0x25, 0xac, 0x47, 0x1a, 0x38, 0xf7,
|
||||
0xaf, 0x13, 0x8e, 0x90, 0x51, 0x79, 0x2c, 0x89, 0x70, 0x33, 0x7a, 0x31, 0x10, 0x1c, 0x16, 0xe9,
|
||||
0xf7, 0x4d, 0x3e, 0xf0, 0x67, 0xb0, 0x95, 0x7f, 0x5e, 0x90, 0x7b, 0x26, 0x9f, 0x8b, 0x1e, 0x1d,
|
||||
0x79, 0xfd, 0xaf, 0xa1, 0xbd, 0x00, 0x18, 0x75, 0x9f, 0x2a, 0xc3, 0x91, 0xf9, 0x70, 0x1d, 0x20,
|
||||
0xa7, 0xec, 0x32, 0x5f, 0x53, 0x1f, 0x9a, 0xab, 0x5d, 0x86, 0xa4, 0xf5, 0x90, 0x5d, 0x80, 0xb5,
|
||||
0x98, 0xaf, 0x9d, 0x42, 0x24, 0x69, 0x93, 0x1e, 0xce, 0x84, 0x25, 0x28, 0x33, 0x1f, 0xde, 0x57,
|
||||
0x60, 0x25, 0xe9, 0xf3, 0x1f, 0xb5, 0xf4, 0x9c, 0x81, 0x5d, 0x58, 0xd3, 0xf9, 0x69, 0x06, 0x4a,
|
||||
0x1a, 0x25, 0x64, 0x6b, 0xfb, 0x4b, 0x58, 0xcf, 0x20, 0x3f, 0x3d, 0xc0, 0x8a, 0xc0, 0x60, 0xce,
|
||||
0xc7, 0xe1, 0xed, 0x3f, 0x36, 0xf0, 0x7f, 0xf8, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x26, 0xa2,
|
||||
0x44, 0x9d, 0x3e, 0x17, 0x00, 0x00,
|
||||
// 1842 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x6d, 0x73, 0x1b, 0xb7,
|
||||
0x11, 0xe6, 0x8b, 0x29, 0x93, 0xab, 0x57, 0xc2, 0x32, 0x7b, 0xa1, 0x65, 0x9b, 0x46, 0x1c, 0x8f,
|
||||
0x32, 0x9d, 0x2a, 0xce, 0x35, 0x93, 0x64, 0x46, 0xad, 0x13, 0x29, 0x94, 0x65, 0xc5, 0x8e, 0xcc,
|
||||
0x1c, 0x6b, 0xb5, 0xd3, 0xe9, 0x97, 0x0b, 0x0f, 0xa1, 0xaf, 0xa6, 0xee, 0x18, 0x00, 0x94, 0x42,
|
||||
0x7d, 0xee, 0x4c, 0xfd, 0x0b, 0x3a, 0xfd, 0xd8, 0xdf, 0xd1, 0x3f, 0xd1, 0xbf, 0xd4, 0xc1, 0x02,
|
||||
0xf7, 0xca, 0x3b, 0xaa, 0x9a, 0x76, 0xfa, 0xed, 0x76, 0x81, 0x5d, 0x2c, 0x80, 0xdd, 0x67, 0x1f,
|
||||
0x1c, 0xb4, 0x85, 0xfb, 0xc9, 0x94, 0x87, 0x32, 0xfc, 0x44, 0xb8, 0x7b, 0xf8, 0x41, 0x6a, 0xc2,
|
||||
0xed, 0xde, 0x1d, 0x85, 0x9c, 0x99, 0x01, 0xf5, 0xa9, 0x87, 0x68, 0x0f, 0x36, 0x1c, 0x36, 0xf6,
|
||||
0x85, 0xe4, 0xae, 0xf4, 0xc3, 0xe0, 0xa4, 0x4f, 0x36, 0xa0, 0xe6, 0x7b, 0x56, 0xb5, 0x57, 0xdd,
|
||||
0xad, 0x3b, 0x35, 0xdf, 0xa3, 0x0f, 0x00, 0xbe, 0x1d, 0xbe, 0x3e, 0xfd, 0x3d, 0xfb, 0xe1, 0x25,
|
||||
0x9b, 0x93, 0x2d, 0xa8, 0xff, 0xf9, 0xf2, 0x1d, 0x0e, 0xaf, 0x39, 0xea, 0x93, 0x3e, 0x82, 0xcd,
|
||||
0x83, 0x99, 0x7c, 0x1b, 0x72, 0xff, 0x6a, 0xd1, 0x45, 0x0b, 0x5d, 0xfc, 0xb3, 0x0a, 0x0f, 0x8e,
|
||||
0x99, 0x1c, 0xb0, 0xc0, 0xf3, 0x83, 0x71, 0x66, 0xb6, 0xc3, 0x7e, 0x9a, 0x31, 0x21, 0xc9, 0x13,
|
||||
0xd8, 0xe0, 0x99, 0x38, 0x4c, 0x04, 0x39, 0xad, 0x9a, 0xe7, 0x7b, 0x2c, 0x90, 0xfe, 0x8f, 0x3e,
|
||||
0xe3, 0xbf, 0x9b, 0x4f, 0x99, 0x55, 0xc3, 0x65, 0x72, 0x5a, 0xb2, 0x0b, 0x9b, 0x89, 0xe6, 0xcc,
|
||||
0x9d, 0xcc, 0x98, 0x55, 0xc7, 0x89, 0x79, 0x35, 0x79, 0x00, 0x70, 0xe1, 0x4e, 0x7c, 0xef, 0x4d,
|
||||
0x20, 0xfd, 0x89, 0x75, 0x0b, 0x57, 0x4d, 0x69, 0xa8, 0x80, 0xfb, 0xc7, 0x4c, 0x9e, 0x29, 0x45,
|
||||
0x26, 0x72, 0x71, 0xd3, 0xd0, 0x2d, 0xb8, 0xed, 0x85, 0xe7, 0xae, 0x1f, 0x08, 0xab, 0xd6, 0xab,
|
||||
0xef, 0xb6, 0x9c, 0x48, 0x54, 0x87, 0x1a, 0x84, 0x97, 0x18, 0x60, 0xdd, 0x51, 0x9f, 0xf4, 0x1f,
|
||||
0x55, 0xb8, 0x53, 0xb0, 0x24, 0xf9, 0x12, 0x1a, 0x18, 0x9a, 0x55, 0xed, 0xd5, 0x77, 0x57, 0x6d,
|
||||
0xba, 0x27, 0xdc, 0xbd, 0x82, 0x79, 0x7b, 0xdf, 0xb9, 0xd3, 0xa3, 0x09, 0x3b, 0x67, 0x81, 0x74,
|
||||
0xb4, 0x41, 0xf7, 0x35, 0x40, 0xa2, 0x24, 0x1d, 0x58, 0xd1, 0x8b, 0x9b, 0x5b, 0x32, 0x12, 0xf9,
|
||||
0x18, 0x1a, 0xee, 0x4c, 0xbe, 0xbd, 0xc2, 0x53, 0x5d, 0xb5, 0xef, 0xec, 0x61, 0xaa, 0x64, 0x6f,
|
||||
0x4c, 0xcf, 0xa0, 0xff, 0xaa, 0x41, 0xfb, 0x1b, 0xc6, 0xd5, 0x51, 0x8e, 0x5c, 0xc9, 0x86, 0xd2,
|
||||
0x95, 0x33, 0xa1, 0x1c, 0x0b, 0xc6, 0x7d, 0x77, 0x12, 0x39, 0xd6, 0x12, 0xea, 0x71, 0x86, 0xb9,
|
||||
0x06, 0x23, 0xa9, 0x7b, 0x0a, 0x47, 0x62, 0xfa, 0xca, 0x15, 0xf2, 0xcd, 0xd4, 0x73, 0x25, 0xf3,
|
||||
0xcc, 0x15, 0xe4, 0xd5, 0xa4, 0x07, 0xab, 0x9c, 0x5d, 0x84, 0xef, 0x98, 0xd7, 0x77, 0x25, 0xb3,
|
||||
0x1a, 0x38, 0x2b, 0xad, 0x22, 0x8f, 0x61, 0xdd, 0x88, 0x0e, 0x73, 0x45, 0x18, 0x58, 0x2b, 0x38,
|
||||
0x27, 0xab, 0x24, 0x9f, 0xc1, 0xdd, 0x89, 0x2b, 0xe4, 0xd1, 0xcf, 0x53, 0x5f, 0x5f, 0xcd, 0xa9,
|
||||
0x3b, 0x1e, 0xb2, 0x40, 0x5a, 0xb7, 0x71, 0x76, 0xf1, 0x20, 0xa1, 0xb0, 0xa6, 0x02, 0x72, 0x98,
|
||||
0x98, 0x86, 0x81, 0x60, 0x56, 0x13, 0x0b, 0x20, 0xa3, 0x23, 0x5d, 0x68, 0x06, 0xa1, 0x3c, 0xf8,
|
||||
0x51, 0x32, 0x6e, 0xb5, 0xd0, 0x59, 0x2c, 0x93, 0x1d, 0x68, 0xf9, 0x02, 0xdd, 0x32, 0xcf, 0x82,
|
||||
0x5e, 0x75, 0xb7, 0xe9, 0x24, 0x8a, 0x6f, 0x6f, 0x35, 0x6b, 0x5b, 0x75, 0xda, 0x83, 0x95, 0x61,
|
||||
0x72, 0x5a, 0x05, 0xa7, 0x48, 0xf7, 0xa1, 0xe1, 0xb8, 0xc1, 0x18, 0x97, 0x62, 0x2e, 0x9f, 0xf8,
|
||||
0x4c, 0x48, 0x93, 0x6d, 0xb1, 0xac, 0x8c, 0x27, 0xae, 0x54, 0x23, 0x35, 0x1c, 0x31, 0x12, 0xbd,
|
||||
0x0f, 0x8d, 0x6f, 0xc2, 0x59, 0x20, 0xc9, 0x36, 0x34, 0x46, 0xea, 0xc3, 0x58, 0x6a, 0x81, 0xfe,
|
||||
0x01, 0x1e, 0xe2, 0x70, 0xea, 0x4e, 0xc5, 0xe1, 0xfc, 0xd4, 0x3d, 0x67, 0x71, 0xa6, 0x3f, 0x84,
|
||||
0x06, 0x57, 0xcb, 0xa3, 0xe1, 0xaa, 0xdd, 0x52, 0xd9, 0x87, 0xf1, 0x38, 0x5a, 0xaf, 0x3c, 0x07,
|
||||
0xca, 0xc0, 0x24, 0xb8, 0x16, 0xe8, 0x5f, 0xab, 0xb0, 0x86, 0xae, 0x8d, 0x3b, 0xf2, 0x15, 0xac,
|
||||
0x8d, 0x52, 0xb2, 0x49, 0xe6, 0x7b, 0xca, 0x5d, 0x7a, 0x5e, 0x3a, 0x8b, 0x33, 0x06, 0xdd, 0xcf,
|
||||
0x33, 0xc9, 0x4c, 0xe0, 0x96, 0x5a, 0xc8, 0x9c, 0x15, 0x7e, 0x27, 0x7b, 0xac, 0xa5, 0xf7, 0x38,
|
||||
0x80, 0xfb, 0xb8, 0x40, 0x1a, 0xf2, 0xc4, 0xe1, 0xfc, 0x64, 0x10, 0xed, 0x50, 0x21, 0xd7, 0xd4,
|
||||
0xa0, 0x5b, 0xcd, 0x9f, 0x26, 0x3b, 0xae, 0x15, 0xef, 0x98, 0xbe, 0xaf, 0xc2, 0x23, 0x74, 0x79,
|
||||
0x12, 0x5c, 0xfc, 0xf7, 0x10, 0xd1, 0x85, 0xe6, 0xdb, 0x50, 0x48, 0xdc, 0x8d, 0xc6, 0xb5, 0x58,
|
||||
0x4e, 0x42, 0xa9, 0x97, 0x84, 0x32, 0x04, 0x82, 0x91, 0xbc, 0xe6, 0x1e, 0xe3, 0xf1, 0xd2, 0x3b,
|
||||
0xd0, 0x72, 0x47, 0xb8, 0xfb, 0x78, 0xd5, 0x44, 0x71, 0xfd, 0xfe, 0x5e, 0xc0, 0x36, 0x3a, 0x7d,
|
||||
0xfe, 0x7d, 0xff, 0x74, 0xc8, 0x64, 0xec, 0xb6, 0x03, 0x2b, 0x97, 0x7e, 0xe0, 0x85, 0x97, 0xc6,
|
||||
0xa7, 0x91, 0xca, 0x41, 0x8e, 0x3e, 0x85, 0x6d, 0xe3, 0xe4, 0xe8, 0x67, 0x5f, 0x24, 0x9e, 0x52,
|
||||
0x16, 0xd5, 0xac, 0xc5, 0x00, 0x7a, 0x03, 0xce, 0x2e, 0xfc, 0x70, 0x26, 0x52, 0x49, 0x99, 0xb5,
|
||||
0x2e, 0x03, 0xb2, 0x6d, 0x68, 0x70, 0x36, 0x3e, 0xe9, 0x47, 0xf7, 0x8f, 0x82, 0xaa, 0x30, 0x6d,
|
||||
0xae, 0xec, 0x18, 0x7e, 0xa1, 0x5d, 0xd3, 0x31, 0x12, 0x95, 0xb0, 0x75, 0xe0, 0x79, 0xba, 0x0c,
|
||||
0xa3, 0x35, 0x62, 0x5f, 0xd5, 0x94, 0xaf, 0x54, 0x8d, 0xd6, 0x32, 0x48, 0x67, 0xc1, 0xed, 0x11,
|
||||
0x67, 0x88, 0x64, 0x1a, 0xd0, 0x23, 0x51, 0x8d, 0x30, 0x2c, 0x78, 0x61, 0x30, 0x2e, 0x12, 0x55,
|
||||
0x85, 0xdc, 0x3d, 0xf0, 0xbc, 0xd4, 0x2e, 0xa3, 0xb5, 0xb7, 0xa0, 0xee, 0x31, 0x1e, 0xf5, 0x5b,
|
||||
0x8f, 0xf1, 0xe2, 0x9d, 0xa9, 0x1a, 0x50, 0x58, 0x84, 0x4b, 0xae, 0x39, 0xf8, 0xad, 0x22, 0xf4,
|
||||
0x85, 0x98, 0xc5, 0x90, 0x6a, 0x24, 0x95, 0x65, 0xf8, 0xc5, 0x4f, 0xfa, 0x06, 0x46, 0x63, 0x99,
|
||||
0x3e, 0x85, 0x4e, 0x3e, 0x10, 0x83, 0x6e, 0xea, 0xa4, 0xfd, 0x71, 0x04, 0x38, 0xea, 0xa4, 0x51,
|
||||
0xa2, 0x03, 0x58, 0xc3, 0x8c, 0x4b, 0x97, 0x50, 0x8a, 0x3f, 0x90, 0xa7, 0x70, 0x67, 0x26, 0xd8,
|
||||
0x99, 0x9d, 0xad, 0x0c, 0x8c, 0xbe, 0xe9, 0x14, 0x0d, 0xd1, 0x57, 0x40, 0xa3, 0x8e, 0x8b, 0x9e,
|
||||
0x8b, 0x6b, 0x2a, 0xbf, 0x4e, 0x07, 0x56, 0xdc, 0xd1, 0x48, 0xc6, 0x07, 0x63, 0x24, 0x3a, 0x87,
|
||||
0x5f, 0x1c, 0x33, 0x5d, 0x14, 0xcf, 0x43, 0x9e, 0xc1, 0xb3, 0xc4, 0xa4, 0x9a, 0x36, 0x29, 0x86,
|
||||
0xb1, 0xb2, 0x8d, 0xd4, 0xcb, 0x37, 0xf2, 0xf7, 0x2a, 0x58, 0xc7, 0x4c, 0xfe, 0xdf, 0x68, 0x83,
|
||||
0xea, 0xa6, 0x9c, 0xfd, 0x34, 0xf3, 0xb9, 0x89, 0xe5, 0x4a, 0x67, 0x5a, 0xd3, 0xc9, 0xab, 0xe9,
|
||||
0xdf, 0xaa, 0xb0, 0x91, 0xe3, 0x16, 0xbf, 0x8e, 0x7a, 0xbf, 0x86, 0xe3, 0xfb, 0x0a, 0x0b, 0x96,
|
||||
0xd0, 0x0a, 0x9c, 0xfb, 0xbf, 0xa7, 0x15, 0xaf, 0xe0, 0xe1, 0x81, 0xe7, 0x15, 0x51, 0xc5, 0xf8,
|
||||
0xe4, 0x3e, 0xce, 0x06, 0xba, 0xcc, 0xdb, 0x63, 0xd8, 0xca, 0x91, 0x53, 0x3c, 0x36, 0xdf, 0x8b,
|
||||
0xc0, 0x46, 0x7d, 0x52, 0xba, 0x30, 0xcb, 0x5e, 0xa0, 0xc1, 0x1f, 0x41, 0x3b, 0x33, 0xc7, 0xce,
|
||||
0xb9, 0xaa, 0x6b, 0x57, 0x57, 0x60, 0x39, 0x48, 0x37, 0x0a, 0x6a, 0x79, 0x09, 0x37, 0xe2, 0x9a,
|
||||
0xb0, 0x98, 0xcc, 0xd5, 0x92, 0xaa, 0x69, 0x45, 0x7d, 0xcc, 0x05, 0xe3, 0xb7, 0xaa, 0x5d, 0x1e,
|
||||
0x71, 0x90, 0x5b, 0x58, 0xeb, 0xb1, 0x4c, 0xff, 0x52, 0x83, 0x9d, 0xe7, 0x7e, 0xe0, 0x4e, 0xfc,
|
||||
0x2b, 0x56, 0x48, 0xb2, 0x0b, 0x4a, 0xc6, 0x90, 0xb2, 0x5a, 0x86, 0x94, 0xa5, 0x80, 0xaa, 0x9e,
|
||||
0x01, 0x2a, 0xec, 0x26, 0x52, 0xb2, 0xf3, 0x69, 0x44, 0xd4, 0x5a, 0x4e, 0xa2, 0x20, 0x7d, 0x68,
|
||||
0x63, 0x13, 0x34, 0x8b, 0x8e, 0x42, 0xee, 0x09, 0xab, 0x81, 0x97, 0xd4, 0xd1, 0x97, 0x74, 0x96,
|
||||
0x1b, 0x76, 0x16, 0x0d, 0xc8, 0x33, 0xd8, 0x4c, 0x94, 0x47, 0x9c, 0x87, 0x1c, 0x89, 0xdc, 0xaa,
|
||||
0xbd, 0xad, 0x7d, 0x0c, 0x78, 0xf8, 0xc3, 0x84, 0x9d, 0xf7, 0x99, 0x74, 0xfd, 0x89, 0x70, 0xf2,
|
||||
0x93, 0x55, 0x6a, 0x6f, 0x1f, 0x78, 0xde, 0xe1, 0x24, 0x1c, 0xbd, 0x63, 0xde, 0x4b, 0x36, 0x4f,
|
||||
0x75, 0x9a, 0x77, 0x6c, 0xfe, 0xc2, 0x15, 0x6f, 0x0d, 0x9e, 0x46, 0xa2, 0x2a, 0x78, 0xd7, 0xf3,
|
||||
0x98, 0x17, 0x61, 0x2a, 0x0a, 0x78, 0x3c, 0xe1, 0x8c, 0x8f, 0x58, 0xcc, 0x59, 0x51, 0x42, 0x84,
|
||||
0x0f, 0xcf, 0x55, 0xc2, 0x9b, 0x23, 0x88, 0x44, 0x75, 0x3c, 0x86, 0x6c, 0x1e, 0xce, 0x0d, 0xb4,
|
||||
0x26, 0x0a, 0xfa, 0x2b, 0x68, 0xbf, 0x64, 0x73, 0x13, 0xd7, 0xb5, 0x41, 0xd9, 0xef, 0xef, 0xc0,
|
||||
0xd6, 0x50, 0x86, 0xdc, 0x1d, 0x47, 0xb7, 0x29, 0xe7, 0x64, 0x1f, 0x36, 0x8f, 0x59, 0x86, 0xbf,
|
||||
0x10, 0x82, 0x4d, 0x3b, 0x03, 0x1a, 0x5d, 0xa2, 0x8f, 0x2a, 0xad, 0xa5, 0x15, 0xf2, 0x1b, 0xd8,
|
||||
0xce, 0x19, 0x1f, 0xce, 0xd5, 0xa3, 0x6e, 0x43, 0x79, 0x48, 0x1e, 0x79, 0x25, 0xd6, 0x9f, 0xc2,
|
||||
0xc6, 0x31, 0x4b, 0xd3, 0x43, 0x02, 0xca, 0x4e, 0xf7, 0xca, 0x6e, 0x5b, 0xdb, 0xa4, 0x86, 0x69,
|
||||
0x85, 0x7c, 0x06, 0x6d, 0xf5, 0xee, 0xe3, 0x6c, 0x74, 0x13, 0xab, 0x7d, 0x0c, 0x73, 0xf1, 0x6d,
|
||||
0x91, 0x36, 0xbc, 0x8b, 0x64, 0x31, 0x3f, 0x85, 0x56, 0xc8, 0x10, 0xac, 0x32, 0x1a, 0x4b, 0x3e,
|
||||
0x8c, 0x19, 0x66, 0x39, 0xc9, 0xed, 0x6e, 0xe5, 0x69, 0x28, 0xad, 0x90, 0x17, 0xd0, 0x29, 0xe6,
|
||||
0x8d, 0xe4, 0x51, 0x3c, 0xbb, 0x8c, 0x53, 0x76, 0x5b, 0xf1, 0x14, 0x5a, 0x21, 0xdf, 0xc1, 0xbd,
|
||||
0x92, 0xd9, 0x48, 0xa0, 0x6f, 0xea, 0xce, 0x86, 0xd5, 0x14, 0xe7, 0x23, 0x9d, 0x78, 0x2c, 0x43,
|
||||
0x02, 0xb3, 0x36, 0x9f, 0xc3, 0x7a, 0x86, 0xd2, 0x11, 0x2b, 0x1e, 0xcd, 0xb1, 0xbc, 0xac, 0xdd,
|
||||
0x17, 0xb0, 0x9e, 0x21, 0x70, 0xda, 0xae, 0x88, 0xd3, 0x75, 0xf1, 0xa6, 0xb4, 0x8a, 0x56, 0xc8,
|
||||
0x6b, 0xf8, 0xa0, 0x94, 0xc7, 0x91, 0xc7, 0x6a, 0xea, 0x75, 0x34, 0x2f, 0xe7, 0xf0, 0x6b, 0x4c,
|
||||
0xab, 0x2c, 0x1c, 0x93, 0xed, 0x85, 0x7e, 0x75, 0xd2, 0xb7, 0xbb, 0x45, 0xcd, 0x01, 0x2f, 0x94,
|
||||
0x2c, 0x34, 0x66, 0x9b, 0xec, 0x28, 0x17, 0x65, 0x0d, 0xbb, 0x4b, 0x16, 0x1b, 0x22, 0xad, 0x90,
|
||||
0x37, 0xd8, 0xe2, 0x8b, 0xfa, 0x95, 0x4d, 0xa8, 0xf1, 0xb7, 0xe4, 0xc7, 0x47, 0x59, 0x80, 0xcf,
|
||||
0x4c, 0x9e, 0x14, 0x36, 0x42, 0xbb, 0xb0, 0xe6, 0x33, 0x97, 0xf5, 0x27, 0xd8, 0x59, 0xc2, 0xa1,
|
||||
0x6c, 0xf2, 0xc4, 0x84, 0x76, 0x0d, 0xcb, 0x2a, 0xd9, 0xf4, 0xf7, 0x26, 0xba, 0xc2, 0x47, 0x8f,
|
||||
0x4d, 0x3e, 0x8a, 0x23, 0x59, 0xf6, 0x2a, 0xca, 0x06, 0xec, 0x20, 0x4d, 0x3b, 0x2b, 0x72, 0xf7,
|
||||
0x28, 0x1d, 0xeb, 0x4d, 0xc2, 0xfc, 0x14, 0x20, 0x01, 0x5c, 0x82, 0x90, 0xb1, 0x00, 0xc0, 0xb9,
|
||||
0xd4, 0xda, 0x87, 0xcd, 0x53, 0x76, 0x99, 0xc3, 0xd7, 0x05, 0x34, 0x2c, 0x41, 0xc8, 0x2f, 0x80,
|
||||
0xe8, 0xbf, 0x15, 0xd7, 0xda, 0xaf, 0x6a, 0xdd, 0xd1, 0xf9, 0x54, 0xce, 0x69, 0x85, 0x9c, 0xc0,
|
||||
0x46, 0x96, 0x75, 0x93, 0x0f, 0x70, 0x43, 0x45, 0x4f, 0x82, 0x6e, 0xb7, 0x68, 0xc8, 0x50, 0x80,
|
||||
0x0a, 0xf9, 0x2d, 0xb4, 0x15, 0x7f, 0xca, 0x42, 0xee, 0x12, 0x6f, 0xb9, 0x48, 0x9e, 0x42, 0x2b,
|
||||
0x7e, 0xff, 0x98, 0x92, 0xca, 0x3d, 0x87, 0xf2, 0x16, 0xfb, 0xd0, 0xe9, 0x33, 0x77, 0x24, 0xfd,
|
||||
0x8b, 0xc5, 0x8d, 0x2f, 0x26, 0x69, 0xce, 0xf8, 0x09, 0x34, 0x4f, 0xd9, 0x25, 0xe6, 0x1f, 0x31,
|
||||
0x43, 0x28, 0x74, 0xd3, 0x02, 0x86, 0x45, 0x86, 0x86, 0xc4, 0x0f, 0x78, 0x38, 0x62, 0x42, 0xf8,
|
||||
0xc1, 0xb8, 0xd0, 0x22, 0xf2, 0xfc, 0x4b, 0x58, 0x8f, 0x2c, 0x90, 0x16, 0x5c, 0x37, 0x39, 0x22,
|
||||
0x4e, 0xe5, 0xb1, 0x24, 0x93, 0x9b, 0xd1, 0x83, 0x82, 0x60, 0xb3, 0x48, 0x3f, 0x7f, 0xf2, 0x81,
|
||||
0x3f, 0x83, 0xad, 0xfc, 0xeb, 0x83, 0xdc, 0x33, 0xf9, 0x5c, 0xf4, 0x26, 0xc9, 0xdb, 0x7f, 0x0d,
|
||||
0xed, 0x05, 0x3e, 0xa9, 0x71, 0xaa, 0x8c, 0x66, 0xe6, 0xc3, 0x75, 0x80, 0x9c, 0xb2, 0xcb, 0x7c,
|
||||
0x4d, 0x7d, 0x68, 0xae, 0x76, 0x19, 0xd1, 0xd6, 0x4d, 0x76, 0x81, 0xf5, 0x62, 0xbe, 0x76, 0x0a,
|
||||
0x89, 0xa6, 0x4d, 0x7a, 0xd8, 0x13, 0x96, 0x90, 0xd0, 0x7c, 0x78, 0x5f, 0x81, 0x95, 0xa4, 0xcf,
|
||||
0x7f, 0x04, 0xe9, 0x39, 0x07, 0xbb, 0xb0, 0xa6, 0xf3, 0xd3, 0x34, 0x94, 0x34, 0x4b, 0xc8, 0xd6,
|
||||
0xf6, 0x97, 0xb0, 0x9e, 0xe1, 0x85, 0xba, 0x81, 0x15, 0x51, 0xc5, 0xdc, 0x1a, 0x87, 0xb7, 0xff,
|
||||
0xd8, 0xc0, 0xdf, 0xe5, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x70, 0x4b, 0x22, 0xaf, 0x5d, 0x17,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@ message AddBlockedKeyRequest {
|
|||
optional int64 added = 2; // Unix timestamp (nanoseconds)
|
||||
optional string source = 3;
|
||||
optional string comment = 4;
|
||||
optional int64 revokedBy = 5;
|
||||
}
|
||||
|
||||
message KeyBlockedRequest {
|
||||
|
|
|
|||
28
sa/sa.go
28
sa/sa.go
|
|
@ -21,6 +21,7 @@ import (
|
|||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
"github.com/letsencrypt/boulder/db"
|
||||
berrors "github.com/letsencrypt/boulder/errors"
|
||||
"github.com/letsencrypt/boulder/features"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
"github.com/letsencrypt/boulder/identifier"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
|
|
@ -1772,6 +1773,8 @@ func addKeyHash(db db.Inserter, cert *x509.Certificate) error {
|
|||
return db.Insert(khm)
|
||||
}
|
||||
|
||||
var blockedKeysColumns = "keyHash, added, source, comment"
|
||||
|
||||
// AddBlockedKey adds a key hash to the blockedKeys table
|
||||
func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.AddBlockedKeyRequest) (*corepb.Empty, error) {
|
||||
if req == nil || req.KeyHash == nil || req.Added == nil || req.Source == nil {
|
||||
|
|
@ -1781,12 +1784,22 @@ func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.Add
|
|||
if !ok {
|
||||
return nil, errors.New("unknown source")
|
||||
}
|
||||
err := ssa.dbMap.Insert(&blockedKeyModel{
|
||||
KeyHash: req.KeyHash,
|
||||
Added: time.Unix(0, *req.Added),
|
||||
Source: sourceInt,
|
||||
Comment: req.Comment,
|
||||
})
|
||||
cols, qs := blockedKeysColumns, "?, ?, ?, ?"
|
||||
vals := []interface{}{
|
||||
req.KeyHash,
|
||||
time.Unix(0, *req.Added),
|
||||
sourceInt,
|
||||
req.Comment,
|
||||
}
|
||||
if features.Enabled(features.StoreRevokerInfo) && req.RevokedBy != nil {
|
||||
cols += ", revokedBy"
|
||||
qs += ", ?"
|
||||
vals = append(vals, *req.RevokedBy)
|
||||
}
|
||||
_, err := ssa.dbMap.Exec(
|
||||
fmt.Sprintf("INSERT INTO blockedKeys (%s) VALUES (%s)", cols, qs),
|
||||
vals...,
|
||||
)
|
||||
if err != nil {
|
||||
if db.IsDuplicate(err) {
|
||||
// Ignore duplicate inserts so multiple certs with the same key can
|
||||
|
|
@ -1804,7 +1817,8 @@ func (ssa *SQLStorageAuthority) KeyBlocked(ctx context.Context, req *sapb.KeyBlo
|
|||
return nil, errIncompleteRequest
|
||||
}
|
||||
exists := false
|
||||
if err := ssa.dbMap.SelectOne(&blockedKeyModel{}, `SELECT * FROM blockedKeys WHERE keyHash = ?`, req.KeyHash); err != nil {
|
||||
var id int64
|
||||
if err := ssa.dbMap.SelectOne(&id, `SELECT ID FROM blockedKeys WHERE keyHash = ?`, req.KeyHash); err != nil {
|
||||
if db.IsNoRows(err) {
|
||||
return &sapb.Exists{Exists: &exists}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2321,3 +2321,33 @@ func TestAddBlockedKeyUnknownSource(t *testing.T) {
|
|||
test.AssertError(t, err, "AddBlockedKey didn't fail with unknown source")
|
||||
test.AssertEquals(t, err.Error(), "unknown source")
|
||||
}
|
||||
|
||||
func TestBlockedKeyRevokedBy(t *testing.T) {
|
||||
if !strings.HasSuffix(os.Getenv("BOULDER_CONFIG_DIR"), "config-next") {
|
||||
return
|
||||
}
|
||||
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
err := features.Set(map[string]bool{"StoreRevokerInfo": true})
|
||||
test.AssertNotError(t, err, "failed to set features")
|
||||
defer features.Reset()
|
||||
|
||||
added := int64(0)
|
||||
source := "API"
|
||||
_, err = sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
||||
KeyHash: []byte{1},
|
||||
Added: &added,
|
||||
Source: &source,
|
||||
})
|
||||
test.AssertNotError(t, err, "AddBlockedKey failed")
|
||||
revoker := int64(1)
|
||||
_, err = sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
||||
KeyHash: []byte{2},
|
||||
Added: &added,
|
||||
Source: &source,
|
||||
RevokedBy: &revoker,
|
||||
})
|
||||
test.AssertNotError(t, err, "AddBlockedKey failed")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"BadKeyRevoker": {
|
||||
"dbConnectFile": "test/secrets/badkeyrevoker_dburl",
|
||||
"maxDBConns": 10,
|
||||
"debugAddr": ":8020",
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/bad-key-revoker.boulder/cert.pem",
|
||||
"keyFile": "test/grpc-creds/bad-key-revoker.boulder/key.pem"
|
||||
},
|
||||
"raService": {
|
||||
"serverAddress": "ra.boulder:9094",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"mailer": {
|
||||
"server": "localhost",
|
||||
"port": "9380",
|
||||
"username": "cert-master@example.com",
|
||||
"from": "bad key revoker <test@example.com>",
|
||||
"passwordFile": "test/secrets/smtp_password",
|
||||
"SMTPTrustedRootFile": "test/mail-test-srv/minica.pem",
|
||||
"emailSubject": "Certificates you've issued have been revoked due to key compromise",
|
||||
"emailTemplate": "test/example-bad-key-revoker-template"
|
||||
},
|
||||
"maximumRevocations": 15,
|
||||
"findCertificatesBatchSize": 10,
|
||||
"interval": "1s"
|
||||
},
|
||||
"syslog": {
|
||||
"stdoutlevel": 6,
|
||||
"sysloglevel": 4
|
||||
}
|
||||
}
|
||||
|
|
@ -42,11 +42,13 @@
|
|||
"address": ":9094",
|
||||
"clientNames": [
|
||||
"wfe.boulder",
|
||||
"admin-revoker.boulder"
|
||||
"admin-revoker.boulder",
|
||||
"bad-key-revoker.boulder"
|
||||
]
|
||||
},
|
||||
"features": {
|
||||
"BlockedKeyTable": true
|
||||
"BlockedKeyTable": true,
|
||||
"StoreRevokerInfo": true
|
||||
},
|
||||
"CTLogGroups2": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@
|
|||
},
|
||||
"features": {
|
||||
"StoreIssuerInfo": true,
|
||||
"StoreKeyHashes": true
|
||||
"StoreKeyHashes": true,
|
||||
"StoreRevokerInfo": true
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
Hello,
|
||||
|
||||
The public key associated with certificates which you have issued has been marked as compromised. As such we are required to revoke any certificates which contain this public key.
|
||||
|
||||
The following currently unexpired certificates that you've issued contain this public key and have been revoked:
|
||||
{{range . -}}
|
||||
{{.}}
|
||||
{{end}}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDKTCCAhGgAwIBAgIIIyhasHOijBIwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgM2I4YjJjMCAXDTIwMDQyMTAyNTI0MFoYDzIxMTAw
|
||||
NDIxMDM1MjQwWjAiMSAwHgYDVQQDExdiYWQta2V5LXJldm9rZXIuYm91bGRlcjCC
|
||||
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMK7gvPqYoNE2A2/TUe/LHzw
|
||||
6ya992e0nPEdUsbw8T1dhSujK6JfGMEHTIwWRDpai8MCgBcMDI3nzF1lYKaKWrdW
|
||||
uNa9fztF0S/cOeyoEi7+bBQsyK4rmEHzbfw5z7NaeEBXi83T6NZdc0lcXAX6rex7
|
||||
hZ8mjEWD4ATmYZR82enfxZWSLEoz0AkRSGtMnjlbXdKhrwaDImgKqlKK0LOea/+2
|
||||
i857s2ilgdjWQ63eGGXjp1jIJe8wbgrYBGDyXdFKN1M6PAAa971LEbJJjdJgXKSY
|
||||
KAfo9oHYhm3ehX/r9PNcTNBYLzOnLhe7DFcJlfFVcu7qrK35L6EEb4K2Ndqun0cC
|
||||
AwEAAaNjMGEwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
||||
BgEFBQcDAjAMBgNVHRMBAf8EAjAAMCIGA1UdEQQbMBmCF2JhZC1rZXktcmV2b2tl
|
||||
ci5ib3VsZGVyMA0GCSqGSIb3DQEBCwUAA4IBAQBwmkDXtY2nArUzO50btsq8jxA4
|
||||
/rGHeuaSP9WraNyClV659W0FHHT+YzMRpC4cLHVY+KLNeMjyUtpcdQnFZ7Z53nmK
|
||||
Hls11FMGZqXJ5cox440FQG3qiWQdYEoz2lblZW2uhHny7gD2T8cEt99nfFFcpfQc
|
||||
+ZFOF3L36ssTar0aJOfb2qcbZ60CvRTcY6COqRqENEooyEAv4mo04LCtx2OZhIB+
|
||||
AvJHs+4vPG0KKdiY+PmqytmoNtexZaVm3+D7ibVbZXrnjK7n6Ieh6nB92gvZ/eAW
|
||||
qAShiiI6k9wFWDiSVyVHQ/Pu/O3jnuXWA687tUtY6yjegTgK2pcC9jCZgXYA
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAwruC8+pig0TYDb9NR78sfPDrJr33Z7Sc8R1SxvDxPV2FK6Mr
|
||||
ol8YwQdMjBZEOlqLwwKAFwwMjefMXWVgpopat1a41r1/O0XRL9w57KgSLv5sFCzI
|
||||
riuYQfNt/DnPs1p4QFeLzdPo1l1zSVxcBfqt7HuFnyaMRYPgBOZhlHzZ6d/FlZIs
|
||||
SjPQCRFIa0yeOVtd0qGvBoMiaAqqUorQs55r/7aLznuzaKWB2NZDrd4YZeOnWMgl
|
||||
7zBuCtgEYPJd0Uo3Uzo8ABr3vUsRskmN0mBcpJgoB+j2gdiGbd6Ff+v081xM0Fgv
|
||||
M6cuF7sMVwmV8VVy7uqsrfkvoQRvgrY12q6fRwIDAQABAoIBAQCNU17/vMxQLoeK
|
||||
uprQhjs4VfSjglzqw9be2oQ346eA/L1oZRyG0/N4K97vED3mB87E8ayajWETH/Ze
|
||||
lfOmCmU6B9NP7elH0Cy4SmEzkurXdkhj//iJBxSSUKQy2JYXuYHqWF8bOz8RTHMd
|
||||
+8zBfiP5q8/XKDfHP6U2iSiqhk30f/CF24H1/LPO5LhpM79+H78cNsEhV7Jb/p1R
|
||||
3Svh69jxmqUrx/PuP1aJru65+IqxNwy9g6n1F0UJLdKcFXbr0/UMJr1YNO14DVOH
|
||||
NZkK9mk0vAbGPbpMWm9ZAY068Crm1Y+ckiMDoaql+KeM0KLMnYfr+DyEwFcdioON
|
||||
twg/HUSRAoGBAM1zVfd4+Lfqugf90aXGWoR5qcg/I+KaBwfHQ0r2KkobWtNHhhhM
|
||||
h9Yq+KTQyHgnQcPgw50P1OF8u6aX9OLjO+JcX7Di7kKa4H1UaHTvK7MSSyUQMyd/
|
||||
pcQQNIFGALED2oYvdLi1QLAaDtEl5z2hLfDMPar667/sp1+TJ2tGUpWvAoGBAPKl
|
||||
FF9k8KhZ41zsEm+6VCfRGlHVOiUgTKqIxMEr2ifAPro1pdX7ESGFF4xluA+ujHTS
|
||||
NRNNVWmZje3rTrn83mtEoSuhsnBVCGyWn+ONCvhMUYZOUIxARNAuEZUGSh0eQAG5
|
||||
zgF3hgEUYnVH7AXsohfwdTBvLpedfZey1MRr3Y3pAoGBALRFbHwuAIdYhg13EJrW
|
||||
NhyhqHFVvcYaguq3VHuVDjxiTkqvKqFtnY81u2Da9dxADfuy39GTz6ZfTUR7d1wS
|
||||
KTyQ80IBjTCSN0KhatqX9g81kQwfb9NLtQcZdQithPPNvtQZFeDw4abj5nZsPMAe
|
||||
CnKMs9uwOmX4YFCDjYYaeWJvAoGAI3U+MeaFSITCNe0FkLAw5hSnfPfk5FIBAha0
|
||||
ceofmhl80SdP0aI70aMqWsjuidQfEF87hFOTvLfExtTRD1rFgfVofADIG6RBc+Ta
|
||||
/py40qoMa8z79lLZ+3YP+bAOmoy2G8p0MUCvI29AKBVXh1IaKddouKg2rc9E8Csg
|
||||
7oc4vCkCgYBKJiWIkqMqULozEAsbOcnKb03hXYqljvtS0sVzgxx7I1C9hk2+Lkd7
|
||||
ObCp+JqaGsth01P9EFgRhCBkwQVOVqtWaWwcV4v2b5NG3GYJ9Pp99/iLjBLE6N81
|
||||
a50Y6niW/q4EXQLgurki0ZHTrKC7qo9fZOL2VUdnDyGAIsMimnTNtg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -10,7 +10,8 @@ command -v minica >/dev/null 2>&1 || {
|
|||
}
|
||||
|
||||
for HOSTNAME in admin-revoker.boulder expiration-mailer.boulder \
|
||||
ocsp-updater.boulder orphan-finder.boulder wfe.boulder akamai-purger.boulder nonce.boulder ; do
|
||||
ocsp-updater.boulder orphan-finder.boulder wfe.boulder akamai-purger.boulder nonce.boulder \
|
||||
bad-key-revoker.boulder ; do
|
||||
minica -domains ${HOSTNAME}
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
ocsp_helper "github.com/letsencrypt/boulder/test/ocsp/helper"
|
||||
|
|
@ -167,3 +170,80 @@ func TestRevokeWithKeyCompromise(t *testing.T) {
|
|||
test.AssertError(t, err, "NewAccount didn't fail with a blacklisted key")
|
||||
test.AssertEquals(t, err.Error(), `acme: error code 400 "urn:ietf:params:acme:error:badPublicKey": public key is forbidden`)
|
||||
}
|
||||
|
||||
func TestBadKeyRevoker(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !strings.HasSuffix(os.Getenv("BOULDER_CONFIG_DIR"), "config-next") {
|
||||
return
|
||||
}
|
||||
|
||||
os.Setenv("DIRECTORY", "http://boulder:4001/directory")
|
||||
cA, err := makeClient("mailto:bad-key-revoker-revoker@letsencrypt.org", "mailto:bad-key-revoker-revoker-2@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "creating acme client")
|
||||
cB, err := makeClient("mailto:bad-key-revoker-revoker-2@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "creating acme client")
|
||||
cC, err := makeClient("mailto:bad-key-revoker-revokee@letsencrypt.org", "mailto:bad-key-revoker-revokee-2@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "creating acme client")
|
||||
cD, err := makeClient("mailto:bad-key-revoker-revokee-2@letsencrypt.org", "mailto:bad-key-revoker-revokee@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "creating acme client")
|
||||
cE, err := makeClient()
|
||||
test.AssertNotError(t, err, "creating acme client")
|
||||
|
||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
test.AssertNotError(t, err, "failed to generate cert key")
|
||||
|
||||
badCert, err := authAndIssue(cA, certKey, []string{random_domain()})
|
||||
test.AssertNotError(t, err, "authAndIssue failed")
|
||||
|
||||
certs := []*x509.Certificate{}
|
||||
for _, c := range []*client{cA, cB, cC, cD, cE} {
|
||||
for i := 0; i < 2; i++ {
|
||||
cert, err := authAndIssue(c, certKey, []string{random_domain()})
|
||||
test.AssertNotError(t, err, "authAndIssue failed")
|
||||
certs = append(certs, cert.certs[0])
|
||||
}
|
||||
}
|
||||
|
||||
err = cA.RevokeCertificate(
|
||||
cA.Account,
|
||||
badCert.certs[0],
|
||||
cA.Account.PrivateKey,
|
||||
ocsp.KeyCompromise,
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to revoke certificate")
|
||||
_, err = ocsp_helper.ReqDER(badCert.certs[0].Raw, ocsp.Revoked)
|
||||
test.AssertNotError(t, err, "ReqDER failed")
|
||||
|
||||
for _, cert := range certs {
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err = ocsp_helper.ReqDER(cert.Raw, ocsp.Revoked)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if i == 5 {
|
||||
t.Fatal("timed out waiting for revoked OCSP status")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
countResp, err := http.Get("http://boulder:9381/count?to=bad-key-revoker-revokee@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "mail-test-srv GET /count failed")
|
||||
defer func() { _ = countResp.Body.Close() }()
|
||||
body, err := ioutil.ReadAll(countResp.Body)
|
||||
test.AssertNotError(t, err, "failed to read body")
|
||||
test.AssertEquals(t, string(body), "1\n")
|
||||
otherCountResp, err := http.Get("http://boulder:9381/count?to=bad-key-revoker-revokee-2@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "mail-test-srv GET /count failed")
|
||||
defer func() { _ = otherCountResp.Body.Close() }()
|
||||
body, err = ioutil.ReadAll(otherCountResp.Body)
|
||||
test.AssertNotError(t, err, "failed to read body")
|
||||
test.AssertEquals(t, string(body), "1\n")
|
||||
|
||||
zeroCountResp, err := http.Get("http://boulder:9381/count?to=bad-key-revoker-revoker@letsencrypt.org")
|
||||
test.AssertNotError(t, err, "mail-test-srv GET /count failed")
|
||||
defer func() { _ = zeroCountResp.Body.Close() }()
|
||||
body, err = ioutil.ReadAll(zeroCountResp.Body)
|
||||
test.AssertNotError(t, err, "failed to read body")
|
||||
test.AssertEquals(t, string(body), "0\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ CREATE USER IF NOT EXISTS 'ocsp_update'@'localhost';
|
|||
CREATE USER IF NOT EXISTS 'test_setup'@'localhost';
|
||||
CREATE USER IF NOT EXISTS 'purger'@'localhost';
|
||||
CREATE USER IF NOT EXISTS 'janitor'@'localhost';
|
||||
CREATE USER IF NOT EXISTS 'badkeyrevoker'@'localhost';
|
||||
|
||||
-- Storage Authority
|
||||
GRANT SELECT,INSERT ON certificates TO 'sa'@'localhost';
|
||||
|
|
@ -67,5 +68,12 @@ GRANT SELECT,DELETE ON requestedNames TO 'janitor'@'localhost';
|
|||
GRANT SELECT,DELETE ON orderFqdnSets TO 'janitor'@'localhost';
|
||||
GRANT SELECT,DELETE ON orderToAuthz2 TO 'janitor'@'localhost';
|
||||
|
||||
-- Bad Key Revoker
|
||||
GRANT SELECT,UPDATE ON blockedKeys TO 'badkeyrevoker'@'localhost';
|
||||
GRANT SELECT ON keyHashToSerial TO 'badkeyrevoker'@'localhost';
|
||||
GRANT SELECT ON certificateStatus TO 'badkeyrevoker'@'localhost';
|
||||
GRANT SELECT ON certificates TO 'badkeyrevoker'@'localhost';
|
||||
GRANT SELECT ON registrations TO 'badkeyrevoker'@'localhost';
|
||||
|
||||
-- Test setup and teardown
|
||||
GRANT ALL PRIVILEGES ON * to 'test_setup'@'localhost';
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
badkeyrevoker@tcp(boulder-mysql:3306)/boulder_sa_integration
|
||||
|
|
@ -63,6 +63,7 @@ def start(race_detection, fakeclock):
|
|||
progs.extend([
|
||||
[8011, './bin/boulder-remoteva --config %s' % os.path.join(config_dir, "va-remote-a.json")],
|
||||
[8012, './bin/boulder-remoteva --config %s' % os.path.join(config_dir, "va-remote-b.json")],
|
||||
[8020, './bin/bad-key-revoker --config %s' % os.path.join(config_dir, "bad-key-revoker.json")],
|
||||
])
|
||||
progs.extend([
|
||||
[53, './bin/sd-test-srv --listen :53'], # Service discovery DNS server
|
||||
|
|
|
|||
Loading…
Reference in New Issue