boulder/cmd/admin-revoker/main.go

250 lines
6.8 KiB
Go

// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sort"
"strconv"
"strings"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/codegangsta/cli"
// Load both drivers to allow configuring either
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
_ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/mattn/go-sqlite3"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc"
"github.com/letsencrypt/boulder/sa"
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
)
var reasons = map[int]string{
0: "unspecified",
1: "keyCompromise",
2: "cACompromise",
3: "affiliationChanged",
4: "superseded",
5: "cessationOfOperation",
6: "certificateHold",
// 7 is unused
8: "removeFromCRL", // needed?
9: "privilegeWithdrawn",
10: "aAcompromise",
}
func loadConfig(c *cli.Context) (config cmd.Config, err error) {
configFileName := c.GlobalString("config")
configJSON, err := ioutil.ReadFile(configFileName)
if err != nil {
return
}
err = json.Unmarshal(configJSON, &config)
return
}
func setupContext(context *cli.Context) (rpc.CertificateAuthorityClient, *blog.AuditLogger, *gorp.DbMap) {
c, err := loadConfig(context)
cmd.FailOnError(err, "Failed to load Boulder configuration")
stats, err := statsd.NewClient(c.Statsd.Server, c.Statsd.Prefix)
cmd.FailOnError(err, "Couldn't connect to statsd")
auditlogger, err := blog.Dial(c.Syslog.Network, c.Syslog.Server, c.Syslog.Tag, stats)
cmd.FailOnError(err, "Could not connect to Syslog")
blog.SetAuditLogger(auditlogger)
ch, err := rpc.AmqpChannel(c)
cmd.FailOnError(err, "Could not connect to AMQP")
caRPC, err := rpc.NewAmqpRPCClient("revoker->CA", c.AMQP.CA.Server, ch)
cmd.FailOnError(err, "Unable to create RPC client")
cac, err := rpc.NewCertificateAuthorityClient(caRPC)
cmd.FailOnError(err, "Unable to create CA client")
dbMap, err := sa.NewDbMap(c.Revoker.DBDriver, c.Revoker.DBConnect)
cmd.FailOnError(err, "Couldn't setup database connection")
return cac, auditlogger, dbMap
}
func addDeniedNames(tx *gorp.Transaction, names []string) (err error) {
sort.Strings(names)
deniedCSR := &core.DeniedCSR{Names: strings.ToLower(strings.Join(names, ","))}
err = tx.Insert(deniedCSR)
return
}
func revokeBySerial(serial string, reasonCode int, deny bool, cac rpc.CertificateAuthorityClient, auditlogger *blog.AuditLogger, tx *gorp.Transaction) (err error) {
if reasonCode < 0 || reasonCode == 7 || reasonCode > 10 {
panic(fmt.Sprintf("Invalid reason code: %d", reasonCode))
}
certObj, err := tx.Get(core.Certificate{}, serial)
if err != nil {
return
}
certificate, ok := certObj.(*core.Certificate)
if !ok {
err = fmt.Errorf("Cast failure")
return
}
if deny {
// Retrieve DNS names associated with serial
var cert *x509.Certificate
cert, err = x509.ParseCertificate(certificate.DER)
if err != nil {
return
}
err = addDeniedNames(tx, append(cert.DNSNames, cert.Subject.CommonName))
if err != nil {
return
}
}
err = cac.RevokeCertificate(certificate.Serial, reasonCode)
if err != nil {
return
}
auditlogger.Info(fmt.Sprintf("Revoked certificate %s with reason '%s'", serial, reasons[reasonCode]))
return
}
func revokeByReg(regID int, reasonCode int, deny bool, cac rpc.CertificateAuthorityClient, auditlogger *blog.AuditLogger, tx *gorp.Transaction) (err error) {
_, err = tx.Get(core.Registration{}, regID)
if err != nil {
return
}
var certs []core.Certificate
_, err = tx.Select(&certs, "SELECT serial FROM certificates WHERE registrationID = :regID", map[string]interface{}{"regID": regID})
if err != nil {
return
}
for _, cert := range certs {
err = revokeBySerial(cert.Serial, reasonCode, deny, cac, auditlogger, tx)
if err != nil {
return
}
}
return
}
var version = "0.0.1"
func main() {
app := cli.NewApp()
app.Name = "admin-revoker"
app.Version = version
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config",
Value: "config.json",
EnvVar: "BOULDER_CONFIG",
Usage: "Path to Boulder JSON configuration file",
},
cli.BoolFlag{
Name: "deny",
Usage: "Add certificate DNS names to the denied list",
},
}
app.Commands = []cli.Command{
{
Name: "serial-revoke",
Usage: "Revoke a single certificate by the hex serial number",
Action: func(c *cli.Context) {
// 1: serial, 2: reasonCode (3: deny flag)
serial := c.Args().First()
reasonCode, err := strconv.Atoi(c.Args().Get(1))
cmd.FailOnError(err, "Reason code argument must be a integer")
deny := c.GlobalBool("deny")
cac, auditlogger, dbMap := setupContext(c)
tx, err := dbMap.Begin()
if err != nil {
tx.Rollback()
}
cmd.FailOnError(err, "Couldn't begin transaction")
err = revokeBySerial(serial, reasonCode, deny, cac, auditlogger, tx)
if err != nil {
tx.Rollback()
}
cmd.FailOnError(err, "Couldn't revoke certificate")
err = tx.Commit()
cmd.FailOnError(err, "Couldn't cleanly close transaction")
},
},
{
Name: "reg-revoke",
Usage: "Revoke all certificates associated with a registration ID",
Action: func(c *cli.Context) {
// 1: registration ID, 2: reasonCode (3: deny flag)
regID, err := strconv.Atoi(c.Args().First())
cmd.FailOnError(err, "Registration ID argument must be a integer")
reasonCode, err := strconv.Atoi(c.Args().Get(1))
cmd.FailOnError(err, "Reason code argument must be a integer")
deny := c.GlobalBool("deny")
cac, auditlogger, dbMap := setupContext(c)
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
defer auditlogger.AuditPanic()
tx, err := dbMap.Begin()
if err != nil {
tx.Rollback()
}
cmd.FailOnError(err, "Couldn't begin transaction")
err = revokeByReg(regID, reasonCode, deny, cac, auditlogger, tx)
if err != nil {
tx.Rollback()
}
cmd.FailOnError(err, "Couldn't revoke certificate")
err = tx.Commit()
cmd.FailOnError(err, "Couldn't cleanly close transaction")
},
},
{
Name: "list-reasons",
Usage: "List all revocation reason codes",
Action: func(c *cli.Context) {
var codes []int
for k := range reasons {
codes = append(codes, k)
}
sort.Ints(codes)
fmt.Printf("Revocation reason codes\n-----------------------\n\n")
for _, k := range codes {
fmt.Printf("%d: %s\n", k, reasons[k])
}
},
},
}
err := app.Run(os.Args)
cmd.FailOnError(err, "Failed to run application")
}