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:
Roland Bracewell Shoemaker 2020-04-23 11:51:59 -07:00 committed by GitHub
parent d2ae471026
commit 70ff4d9347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1116 additions and 141 deletions

431
cmd/bad-key-revoker/main.go Normal file
View File

@ -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()
}
}
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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)

View File

@ -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() {

View File

@ -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;

View File

@ -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")
}

View File

@ -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
}

View File

@ -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.

View File

@ -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 {

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}
}

View File

@ -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": [
{

View File

@ -25,7 +25,8 @@
},
"features": {
"StoreIssuerInfo": true,
"StoreKeyHashes": true
"StoreKeyHashes": true,
"StoreRevokerInfo": true
}
},

View File

@ -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}}

View File

@ -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-----

View File

@ -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-----

View File

@ -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

View File

@ -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")
}

View File

@ -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';

View File

@ -0,0 +1 @@
badkeyrevoker@tcp(boulder-mysql:3306)/boulder_sa_integration

View File

@ -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