Skip to content

Commit

Permalink
Merge pull request #58 from namreg/revoke-command
Browse files Browse the repository at this point in the history
Add revoke command
  • Loading branch information
mcpherrinm authored Oct 3, 2018
2 parents 5000d32 + 6388ade commit e27060a
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 0 deletions.
1 change: 1 addition & 0 deletions certstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func main() {
cmd.NewInitCommand(),
cmd.NewCertRequestCommand(),
cmd.NewSignCommand(),
cmd.NewRevokeCommand(),
}
app.Before = func(c *cli.Context) error {
cmd.InitDepot(c.String("depot-path"))
Expand Down
125 changes: 125 additions & 0 deletions cmd/revoke.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package cmd

import (
"crypto/rand"
"crypto/x509"
x509pkix "crypto/x509/pkix"
"errors"
"fmt"
"os"
"strings"
"time"

"github.com/codegangsta/cli"
"github.com/square/certstrap/depot"
"github.com/square/certstrap/pkix"
)

type revokeCommand struct {
ca, cn string
}

// NewRevokeCommand revokes the given certificate by adding it to the CA's CRL.
func NewRevokeCommand() cli.Command {
return cli.Command{
Name: "revoke",
Usage: "Revoke certificate",
Description: "Add certificate to the CA's CRL.",
Flags: []cli.Flag{
cli.StringFlag{"CN", "", "Certificate's CN to revoke", ""},
cli.StringFlag{"CA", "", "CA's name to revoke cert", ""},
},
Action: new(revokeCommand).run,
}
}

func (c *revokeCommand) checkErr(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

func (c *revokeCommand) parseArgs(ctx *cli.Context) error {
if ctx.String("CA") == "" {
return errors.New("CA name must be provided")
}
c.ca = strings.Replace(ctx.String("CA"), " ", "_", -1)

if ctx.String("CN") == "" {
return errors.New("CN name must be provided")
}
c.cn = strings.Replace(ctx.String("CN"), " ", "_", -1)

return nil
}

func (c *revokeCommand) run(ctx *cli.Context) {
c.checkErr(c.parseArgs(ctx))

caCert, err := c.CAx509Certificate()
c.checkErr(err)

cnCert, err := c.CNx509Certificate()
c.checkErr(err)

revoked, err := c.revokedCertificates()
c.checkErr(err)

revoked = append(revoked, x509pkix.RevokedCertificate{
SerialNumber: cnCert.SerialNumber,
RevocationTime: time.Now(),
})

err = c.saveRevokedCertificates(caCert, revoked)
c.checkErr(err)
}

func (c *revokeCommand) CAx509Certificate() (*x509.Certificate, error) {
cert, err := depot.GetCertificate(d, c.ca)
if err != nil {
return nil, err
}
return cert.GetRawCertificate()
}

func (c *revokeCommand) CNx509Certificate() (*x509.Certificate, error) {
cert, err := depot.GetCertificate(d, c.cn)
if err != nil {
return nil, err
}
return cert.GetRawCertificate()
}

func (c *revokeCommand) revokedCertificates() ([]x509pkix.RevokedCertificate, error) {
list, err := depot.GetCertificateRevocationList(d, c.ca)
if err != nil {
return nil, err
}

certList, err := x509.ParseDERCRL(list.DERBytes())
if err != nil {
return nil, err
}

return certList.TBSCertList.RevokedCertificates, nil
}

func (c *revokeCommand) saveRevokedCertificates(cert *x509.Certificate, list []x509pkix.RevokedCertificate) error {
priv, err := depot.GetPrivateKey(d, c.ca)
if err != nil {
return fmt.Errorf("could not get %q private key: %v", c.ca, err)
}

crlBytes, err := cert.CreateCRL(rand.Reader, priv.Private, list, time.Now(), time.Now())
if err != nil {
return fmt.Errorf("could not create CRL: %v", err)
}
if err := d.Delete(depot.CrlTag(c.ca)); err != nil {
return fmt.Errorf("could not delete CRL: %v", err)
}
if err = depot.PutCertificateRevocationList(d, c.ca, pkix.NewCertificateRevocationListFromDER(crlBytes)); err != nil {
return fmt.Errorf("could not put revokation list: %v", err)
}
return nil
}
121 changes: 121 additions & 0 deletions cmd/revoke_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cmd

import (
"crypto/x509"
"flag"
"io/ioutil"
"os"
"testing"
"time"

"github.com/codegangsta/cli"
"github.com/square/certstrap/depot"
"github.com/square/certstrap/pkix"
)

const (
caName = "ca"
cnName = "cn"
)

func TestRevokeCmd(t *testing.T) {
tmp, err := ioutil.TempDir("", "certstrap-revoke")
if err != nil {
t.Fatalf("could not create tmp dir: %v", err)
}
defer os.RemoveAll(tmp)

d, err = depot.NewFileDepot(tmp)
if err != nil {
t.Fatalf("could not create file depot: %v", err)
}

setupCA(t, d)
setupCN(t, d)

fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.String("CA", "", "")
fs.String("CN", "", "")
fs.Parse([]string{"-CA", "ca", "-CN", "cn"})

new(revokeCommand).run(cli.NewContext(nil, fs, nil))

list, err := depot.GetCertificateRevocationList(d, caName)
if err != nil {
t.Fatalf("could not get crl: %v", err)
}

certList, err := x509.ParseDERCRL(list.DERBytes())
if err != nil {
t.Fatalf("could not parse crl: %v", err)
}

if len(certList.TBSCertList.RevokedCertificates) != 1 {
t.Fatalf("unexpected number of revoked certs: want = 1, got = %d", len(certList.TBSCertList.RevokedCertificates))
}

cnCert, _ := depot.GetCertificate(d, cnName)
cnX509, _ := cnCert.GetRawCertificate()

if cnX509.SerialNumber.Cmp(certList.TBSCertList.RevokedCertificates[0].SerialNumber) != 0 {
t.Fatalf("certificates serial numbers are not equal")
}
}

func setupCA(t *testing.T, dt depot.Depot) {
// create private key
key, err := pkix.CreateRSAKey(2048)
if err != nil {
t.Fatalf("could not create RSA key: %v", err)
}
if err = depot.PutPrivateKey(dt, caName, key); err != nil {
t.Fatalf("could not put private key: %v", err)
}

// create certificate authority
caCert, err := pkix.CreateCertificateAuthority(key, caName, time.Now().Add(1*time.Minute), "", "", "", "", caName)
if err != nil {
t.Fatalf("could not create authority cert: %v", err)
}
if err = depot.PutCertificate(dt, caName, caCert); err != nil {
t.Fatalf("could not put certificate: %v", err)
}

// create an empty certificate revocation list
crl, err := pkix.CreateCertificateRevocationList(key, caCert, time.Now().Add(1*time.Minute))
if err != nil {
t.Fatalf("could not create crl: %v", err)
}
if err = depot.PutCertificateRevocationList(dt, caName, crl); err != nil {
t.Fatalf("could not put crl: %v", err)
}
}

func setupCN(t *testing.T, dt depot.Depot) {
// create private key
key, err := pkix.CreateRSAKey(2048)
if err != nil {
t.Fatalf("could not create RSA key: %v", err)
}
if err = depot.PutPrivateKey(dt, cnName, key); err != nil {
t.Fatalf("could not put private key: %v", err)
}

csr, err := pkix.CreateCertificateSigningRequest(key, cnName, nil, []string{"example.com"}, "", "", "", "", cnName)
if err != nil {
t.Fatalf("could not create csr: %v", err)
}

caCert, err := depot.GetCertificate(dt, caName)
if err != nil {
t.Fatalf("could not get cert: %v", err)
}

cnCert, err := pkix.CreateCertificateHost(caCert, key, csr, time.Now().Add(1*time.Hour))
if err != nil {
t.Fatalf("could not create cert host: %v", err)
}
if err = depot.PutCertificate(dt, "cn", cnCert); err != nil {
t.Fatalf("could not put cert: %v", err)
}
}
9 changes: 9 additions & 0 deletions depot/pkix.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,12 @@ func PutCertificateRevocationList(d Depot, name string, crl *pkix.CertificateRev
}
return d.Put(CrlTag(name), b)
}

//GetCertificateRevocationList gets a CRL file for a given name and ca in the depot.
func GetCertificateRevocationList(d Depot, name string) (*pkix.CertificateRevocationList, error) {
b, err := d.Get(CrlTag(name))
if err != nil {
return nil, err
}
return pkix.NewCertificateRevocationListFromPEM(b)
}
5 changes: 5 additions & 0 deletions pkix/crl.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ type CertificateRevocationList struct {
derBytes []byte
}

//DERBytes returns DER-formatted bytes of the CRL.
func (c *CertificateRevocationList) DERBytes() []byte {
return c.derBytes
}

// NewCertificateRevocationListFromDER inits CertificateRevocationList from DER-format bytes
func NewCertificateRevocationListFromDER(derBytes []byte) *CertificateRevocationList {
return &CertificateRevocationList{derBytes: derBytes}
Expand Down

0 comments on commit e27060a

Please sign in to comment.