diff --git a/.travis.yml b/.travis.yml
index df435f0408dab4513e65af5d6dc9170aa825c8a3..38fa5799c48cf33071fcf64b6790aaa8803340d2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,4 +26,4 @@ script:
   - go list ./... |grep -v vendor/ |xargs go test
   - gofmt -d $(find -type f -name '*.go' -not -path './vendor/*')
   - go list ./... |grep -v vendor/ |xargs go vet
-  - go list ./... |grep -v vendor/ |xargs -L1 golint
+  - go list ./... |grep -v vendor/ |xargs -L1 golint -set_exit_status
diff --git a/cmd/cashierd/main.go b/cmd/cashierd/main.go
index 2a649c278be6871b8467df7f101f505fe2409a3b..8295c74ba50c35e742a7664faf088f5e9d1d3aeb 100644
--- a/cmd/cashierd/main.go
+++ b/cmd/cashierd/main.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"bytes"
 	"crypto/rand"
 	"encoding/hex"
 	"encoding/json"
@@ -211,17 +210,16 @@ func rootHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, er
 }
 
 func listRevokedCertsHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, error) {
-	var out bytes.Buffer
 	revoked, err := a.certstore.GetRevoked()
 	if err != nil {
 		return http.StatusInternalServerError, err
 	}
-	for _, c := range revoked {
-		out.WriteString(c.Raw)
-		out.WriteString("\n")
+	rl, err := a.sshKeySigner.GenerateRevocationList(revoked)
+	if err != nil {
+		return http.StatusInternalServerError, err
 	}
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.Write(out.Bytes())
+	w.Header().Set("Content-Type", "application/octet-stream")
+	w.Write(rl)
 	return http.StatusOK, nil
 }
 
diff --git a/server/signer/signer.go b/server/signer/signer.go
index 8169c11b86dc8501e4bb555d3686dec4b5fbf259..0bff1c33bbf51c129855c07b94de583c7a51c923 100644
--- a/server/signer/signer.go
+++ b/server/signer/signer.go
@@ -13,6 +13,8 @@ import (
 
 	"github.com/nsheridan/cashier/lib"
 	"github.com/nsheridan/cashier/server/config"
+	"github.com/nsheridan/cashier/server/store"
+	"github.com/stripe/krl"
 	"golang.org/x/crypto/ssh"
 )
 
@@ -51,6 +53,22 @@ func (s *KeySigner) SignUserKey(req *lib.SignRequest) (*ssh.Certificate, error)
 	return cert, nil
 }
 
+// GenerateRevocationList returns an SSH key revocation list (KRL).
+func (s *KeySigner) GenerateRevocationList(certs []*store.CertRecord) ([]byte, error) {
+	revoked := &krl.KRLCertificateSection{
+		CA: s.ca.PublicKey(),
+	}
+	ids := krl.KRLCertificateKeyID{}
+	for _, c := range certs {
+		ids = append(ids, c.KeyID)
+	}
+	revoked.Sections = append(revoked.Sections, &ids)
+	k := &krl.KRL{
+		Sections: []krl.KRLSection{revoked},
+	}
+	return k.Marshal(rand.Reader)
+}
+
 func makeperms(perms []string) map[string]string {
 	if len(perms) > 0 {
 		m := make(map[string]string)
diff --git a/server/signer/signer_test.go b/server/signer/signer_test.go
index a80e64ae5411524dd66ce701ad1b9a2433ab8925..9c76f4bdb6381ad7e2e8863380013d6ca6e89b58 100644
--- a/server/signer/signer_test.go
+++ b/server/signer/signer_test.go
@@ -7,7 +7,9 @@ import (
 	"time"
 
 	"github.com/nsheridan/cashier/lib"
+	"github.com/nsheridan/cashier/server/store"
 	"github.com/nsheridan/cashier/testdata"
+	"github.com/stripe/krl"
 
 	"golang.org/x/crypto/ssh"
 )
@@ -49,3 +51,32 @@ func TestCert(t *testing.T) {
 		t.Fatalf("Invalid validity, expected %d, got %d", r.ValidUntil, cert.ValidBefore)
 	}
 }
+
+func TestRevocationList(t *testing.T) {
+	r := &lib.SignRequest{
+		Key:        string(testdata.Pub),
+		Principal:  "revoked",
+		ValidUntil: time.Now().Add(1 * time.Hour),
+	}
+	cert1, _ := signer.SignUserKey(r)
+	r.Principal = "ok"
+	cert2, _ := signer.SignUserKey(r)
+	var rec []*store.CertRecord
+	rec = append(rec, &store.CertRecord{
+		KeyID: cert1.KeyId,
+	})
+	rl, err := signer.GenerateRevocationList(rec)
+	if err != nil {
+		t.Error(err)
+	}
+	k, err := krl.ParseKRL(rl)
+	if err != nil {
+		t.Error(err)
+	}
+	if !k.IsRevoked(cert1) {
+		t.Errorf("expected cert %s to be revoked", cert1.KeyId)
+	}
+	if k.IsRevoked(cert2) {
+		t.Errorf("cert %s should not be revoked", cert2.KeyId)
+	}
+}
diff --git a/templates/certs.go b/templates/certs.go
index 2fde4fd3cc12622ce74accad6804e575d2a13a5d..877a0b3c657229d914e8a85d356f3cf3f318a020 100644
--- a/templates/certs.go
+++ b/templates/certs.go
@@ -1,6 +1,6 @@
 package templates
 
-// Token is the page users see when authenticated.
+// Certs lists all unexpired issued certificates.
 const Certs = `<html>
   <head>
     <title>Certs</title>
diff --git a/vendor/github.com/stripe/krl/LICENSE b/vendor/github.com/stripe/krl/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f3241a29c1359b818139604b71b25a65e3730dc7
--- /dev/null
+++ b/vendor/github.com/stripe/krl/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015, 2016 Stripe, Inc.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/stripe/krl/README.md b/vendor/github.com/stripe/krl/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..6402e635c06b8ee8269d2b265b77a137a3986a6a
--- /dev/null
+++ b/vendor/github.com/stripe/krl/README.md
@@ -0,0 +1,7 @@
+krl
+===
+
+[![GoDoc](https://godoc.org/github.com/stripe/krl?status.svg)](https://godoc.org/github.com/stripe/krl)
+
+Package krl provides functionality for reading and writing SSH Key Revocation
+Lists (KRLs).
diff --git a/vendor/github.com/stripe/krl/format.go b/vendor/github.com/stripe/krl/format.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a4b8226f160833f7511bb78c387f5c4a46d28da
--- /dev/null
+++ b/vendor/github.com/stripe/krl/format.go
@@ -0,0 +1,163 @@
+package krl
+
+import "math/big"
+
+// We (unfortunately) make extensive use of x/crypto/ssh.Unmarshal's "rest"
+// parameter here. The KRL specification makes extensive use of sections placed
+// back-to-back, and there's no other way to get x/crypto/ssh.Unmarshal to emit
+// the portion of the input that has not yet been parsed.
+
+const krlMagic = 0x5353484b524c0a00
+
+/*
+#define KRL_MAGIC		0x5353484b524c0a00ULL  /* "SSHKRL\n\0" * /
+#define KRL_FORMAT_VERSION	1
+
+	uint64	KRL_MAGIC
+	uint32	KRL_FORMAT_VERSION
+	uint64	krl_version
+	uint64	generated_date
+	uint64	flags
+	string	reserved
+	string	comment
+*/
+type krlHeader struct {
+	KRLMagic         uint64
+	KRLFormatVersion uint32
+	KRLVersion       uint64
+	GeneratedDate    uint64
+	Flags            uint64
+	Reserved         []byte
+	Comment          string
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	byte	section_type
+	string	section_data
+
+#define KRL_SECTION_CERTIFICATES		1
+#define KRL_SECTION_EXPLICIT_KEY		2
+#define KRL_SECTION_FINGERPRINT_SHA1		3
+#define KRL_SECTION_SIGNATURE			4
+*/
+type krlSection struct {
+	SectionType byte
+	SectionData []byte
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	string ca_key
+	string reserved
+*/
+type krlCertificateSectionHeader struct {
+	CAKey    []byte
+	Reserved []byte
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	byte	cert_section_type
+	string	cert_section_data
+
+#define KRL_SECTION_CERT_SERIAL_LIST	0x20
+#define KRL_SECTION_CERT_SERIAL_RANGE	0x21
+#define KRL_SECTION_CERT_SERIAL_BITMAP	0x22
+#define KRL_SECTION_CERT_KEY_ID		0x23
+*/
+type krlCertificateSection struct {
+	CertSectionType byte
+	CertSectionData []byte
+
+	Rest []byte `ssh:"rest"`
+}
+
+const (
+	krlSectionCertSerialList   = 0x20
+	krlSectionCertSerialRange  = 0x21
+	krlSectionCertSerialBitmap = 0x22
+	krlSectionCertKeyId        = 0x23
+)
+
+/*
+	uint64	revoked_cert_serial
+	uint64	...
+*/
+type krlSerialList struct {
+	RevokedCertSerial uint64
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	uint64	serial_min
+	uint64	serial_max
+*/
+type krlSerialRange struct {
+	SerialMin uint64
+	SerialMax uint64
+}
+
+/*
+	uint64	serial_offset
+	mpint	revoked_keys_bitmap
+*/
+type krlSerialBitmap struct {
+	SerialOffset      uint64
+	RevokedKeysBitmap *big.Int
+}
+
+/*
+	string	key_id[0]
+	...
+*/
+type krlKeyID struct {
+	KeyID string
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	string	public_key_blob[0]
+	....
+*/
+type krlExplicitKey struct {
+	PublicKeyBlob []byte
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	string	public_key_hash[0]
+	....
+*/
+type krlFingerprintSHA1 struct {
+	PublicKeyHash []byte
+
+	Rest []byte `ssh:"rest"`
+}
+
+/*
+	byte	KRL_SECTION_SIGNATURE
+	string	signature_key
+	string	signature
+
+We split this struct into two parts: krlSignatureHeader is included in the
+signature, and so the inverse of its "Rest" key is the data coverd by the
+signature.
+*/
+type krlSignatureHeader struct {
+	SignatureKey []byte `sshtype:"4"`
+
+	Rest []byte `ssh:"rest"`
+}
+
+type krlSignature struct {
+	Signature []byte
+
+	Rest []byte `ssh:"rest"`
+}
diff --git a/vendor/github.com/stripe/krl/krl.go b/vendor/github.com/stripe/krl/krl.go
new file mode 100644
index 0000000000000000000000000000000000000000..e3457f8ccfad2f87737876d132148a82492219a4
--- /dev/null
+++ b/vendor/github.com/stripe/krl/krl.go
@@ -0,0 +1,372 @@
+// Package krl provides functionality for reading and writing SSH Key Revocation
+// Lists (KRLs).
+//
+// References:
+// 	https://raw.githubusercontent.com/openssh/openssh-portable/master/PROTOCOL.krl
+package krl
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"io"
+	"math/big"
+	"sort"
+	"time"
+
+	"golang.org/x/crypto/ssh"
+)
+
+// KRL, or Key Revocation List, is a list of revoked keys, certificates, and
+// identities, possibly signed by some authority. The zero value of KRL is
+// appropriate for use, and represents an empty list.
+type KRL struct {
+	// Version is a number that increases every time the KRL is modified.
+	// When marshaling a KRL, if Version is zero GeneratedDate will be used
+	// instead.
+	Version uint64
+	// GeneratedDate is the Unix timestamp the KRL was generated at. When
+	// marshaling a KRL, if GeneratedDate is zero the current Unix timestamp
+	// will be used instead.
+	GeneratedDate uint64
+	// Comment is an optional comment for the KRL.
+	Comment string
+	// Sections is a list of public key and certificate selectors that this
+	// KRL applies to.
+	Sections []KRLSection
+	// SigningKeys is set by ParseKRL and Marshal to the list of Signers
+	// that signed (or which claimed to sign) the KRL in the order they
+	// appeared (i.e., innermost-first).
+	SigningKeys []ssh.PublicKey
+}
+
+/*
+KRLSection describes a section of a KRL, which selects certain certificates and
+keys for revocation. The concrete types KRLCertificateSection,
+KRLExplicitKeySection, and KRLFingerprintSection satisfy this interface, and
+correspond to the three types of KRL sections currently defined.
+*/
+type KRLSection interface {
+	isRevoked(key ssh.PublicKey) bool
+	marshal() []byte
+}
+
+/*
+KRLCertificateSection revokes SSH certificates by certificate authority and
+either serial numbers or key ids.
+*/
+type KRLCertificateSection struct {
+	// CA is the certificate authority whose keys are being revoked by this
+	// section. If CA is nil, this section applies to keys signed by any
+	// certificate authority.
+	CA ssh.PublicKey
+	// Sections is a list of certificate selectors.
+	Sections []KRLCertificateSubsection
+}
+
+func (k *KRLCertificateSection) isRevoked(key ssh.PublicKey) bool {
+	cert, ok := key.(*ssh.Certificate)
+	if !ok {
+		return false
+	}
+
+	if k.CA != nil {
+		sk := cert.SignatureKey.Marshal()
+		ca := k.CA.Marshal()
+		if !bytes.Equal(sk, ca) {
+			return false
+		}
+	}
+
+	for _, section := range k.Sections {
+		if section.isRevoked(cert) {
+			return true
+		}
+	}
+	return false
+}
+
+func (k *KRLCertificateSection) marshal() []byte {
+	var buf bytes.Buffer
+	var ca []byte
+	if k.CA != nil {
+		ca = k.CA.Marshal()
+	}
+	buf.Write(ssh.Marshal(krlCertificateSectionHeader{CAKey: ca}))
+	headerLen := buf.Len()
+	for _, section := range k.Sections {
+		buf.Write(section.marshal())
+	}
+	// All subsections were empty; we should be empty too.
+	if buf.Len() == headerLen {
+		return nil
+	}
+	return ssh.Marshal(krlSection{
+		SectionType: 1,
+		SectionData: buf.Bytes(),
+	})
+}
+
+/*
+KRLCertificateSubsection describes a subsection of a KRL certificate selection,
+and selects certain certificates for revocation. The concrete types
+KRLCertificateSerialList, KRLCertificateSerialRange, KRLCertificateSerialBitmap,
+and KRLCertificateSerialBitmap satisfy this interface, and correspond to the
+four subsections currently defined.
+*/
+type KRLCertificateSubsection interface {
+	isRevoked(cert *ssh.Certificate) bool
+	marshal() []byte
+}
+
+// KRLCertificateSerialList revokes certificates by listing their serial
+// numbers.
+type KRLCertificateSerialList []uint64
+
+func (k *KRLCertificateSerialList) isRevoked(cert *ssh.Certificate) bool {
+	for _, serial := range *k {
+		if serial == cert.Serial {
+			return true
+		}
+	}
+	return false
+}
+
+func (k *KRLCertificateSerialList) marshal() []byte {
+	if len(*k) == 0 {
+		return nil
+	}
+
+	var buf bytes.Buffer
+	for _, serial := range *k {
+		buf.Write(ssh.Marshal(krlSerialList{
+			RevokedCertSerial: serial,
+		}))
+	}
+	return ssh.Marshal(krlCertificateSection{
+		CertSectionType: krlSectionCertSerialList,
+		CertSectionData: buf.Bytes(),
+	})
+}
+
+// KRLCertificateSerialRange revokes all certificates with serial numbers in the
+// range between Min and Max, inclusive.
+type KRLCertificateSerialRange struct {
+	Min, Max uint64
+}
+
+func (k *KRLCertificateSerialRange) isRevoked(cert *ssh.Certificate) bool {
+	return k.Min <= cert.Serial && cert.Serial <= k.Max
+}
+
+func (k *KRLCertificateSerialRange) marshal() []byte {
+	return ssh.Marshal(krlCertificateSection{
+		CertSectionType: krlSectionCertSerialRange,
+		CertSectionData: ssh.Marshal(krlSerialRange{
+			SerialMin: k.Min,
+			SerialMax: k.Max,
+		}),
+	})
+}
+
+// KRLCertificateSerialBitmap revokes certificates densely using a bitmap. If
+// bit N of the bitmap is set, the certificate with serial Offset + N is
+// revoked.
+type KRLCertificateSerialBitmap struct {
+	Offset uint64
+	Bitmap *big.Int
+}
+
+func (k *KRLCertificateSerialBitmap) isRevoked(cert *ssh.Certificate) bool {
+	if cert.Serial < k.Offset {
+		return false
+	}
+	if cert.Serial-k.Offset > krlMaxBitmapSize {
+		return false
+	}
+	return k.Bitmap.Bit(int(cert.Serial-k.Offset)) == 1
+}
+
+func (k *KRLCertificateSerialBitmap) marshal() []byte {
+	return ssh.Marshal(krlCertificateSection{
+		CertSectionType: krlSectionCertSerialBitmap,
+		CertSectionData: ssh.Marshal(krlSerialBitmap{
+			SerialOffset:      k.Offset,
+			RevokedKeysBitmap: k.Bitmap,
+		}),
+	})
+}
+
+// KRLCertificateKeyID revokes certificates by listing key ids. This may be
+// useful in revoking all certificates associated with a particular identity,
+// for instance hosts or users.
+type KRLCertificateKeyID []string
+
+func (k *KRLCertificateKeyID) isRevoked(cert *ssh.Certificate) bool {
+	for _, id := range *k {
+		if id == cert.KeyId {
+			return true
+		}
+	}
+	return false
+}
+
+func (k *KRLCertificateKeyID) marshal() []byte {
+	if len(*k) == 0 {
+		return nil
+	}
+
+	var buf bytes.Buffer
+	for _, id := range *k {
+		buf.Write(ssh.Marshal(krlKeyID{
+			KeyID: id,
+		}))
+	}
+	return ssh.Marshal(krlCertificateSection{
+		CertSectionType: krlSectionCertKeyId,
+		CertSectionData: buf.Bytes(),
+	})
+}
+
+// ssh.PublicKey objects might be certificates, which have a different wire
+// format.
+func marshalPubkey(key ssh.PublicKey) []byte {
+	switch v := key.(type) {
+	case *ssh.Certificate:
+		return marshalPubkey(v.Key)
+	default:
+		return key.Marshal()
+	}
+}
+
+// KRLExplicitKeySection revokes keys by explicitly listing them.
+type KRLExplicitKeySection []ssh.PublicKey
+
+func (k *KRLExplicitKeySection) isRevoked(key ssh.PublicKey) bool {
+	kbuf := marshalPubkey(key)
+	for _, key := range *k {
+		if bytes.Equal(kbuf, marshalPubkey(key)) {
+			return true
+		}
+	}
+	return false
+}
+
+func (k *KRLExplicitKeySection) marshal() []byte {
+	if len(*k) == 0 {
+		return nil
+	}
+
+	var buf bytes.Buffer
+	for _, key := range *k {
+		buf.Write(ssh.Marshal(krlExplicitKey{
+			PublicKeyBlob: marshalPubkey(key),
+		}))
+	}
+	return ssh.Marshal(krlSection{
+		SectionType: 2,
+		SectionData: buf.Bytes(),
+	})
+}
+
+// KRLFingerprintSection revokes keys by their SHA1 fingerprints. It is
+// semantically equivalent to--but is more space efficient than--
+// KRLExplicitKeySection.
+type KRLFingerprintSection [][sha1.Size]byte
+
+func (k *KRLFingerprintSection) isRevoked(key ssh.PublicKey) bool {
+	sha := sha1.Sum(marshalPubkey(key))
+	for _, hash := range *k {
+		if hash == sha {
+			return true
+		}
+	}
+	return false
+}
+
+type bigEndian [][sha1.Size]byte
+
+func (b bigEndian) Len() int {
+	return len(b)
+}
+func (b bigEndian) Less(i, j int) bool {
+	return bytes.Compare(b[i][:], b[j][:]) == -1
+}
+func (b bigEndian) Swap(i, j int) {
+	b[i], b[j] = b[j], b[i]
+}
+
+func (k *KRLFingerprintSection) marshal() []byte {
+	if len(*k) == 0 {
+		return nil
+	}
+
+	// For some reason SSH insists that keys revoked by fingerprint must be
+	// sorted as if they were big-endian integers (i.e., lexicographically).
+	be := make(bigEndian, len(*k))
+	for i, hash := range *k {
+		be[i] = hash
+	}
+	sort.Sort(be)
+
+	var buf bytes.Buffer
+	for _, hash := range be {
+		buf.Write(ssh.Marshal(krlFingerprintSHA1{
+			PublicKeyHash: hash[:],
+		}))
+	}
+	return ssh.Marshal(krlSection{
+		SectionType: 3,
+		SectionData: buf.Bytes(),
+	})
+}
+
+// Marshal serializes the KRL and optionally signs it with one or more authority
+// keys.
+func (k *KRL) Marshal(rand io.Reader, keys ...ssh.Signer) ([]byte, error) {
+	if k.GeneratedDate == 0 {
+		k.GeneratedDate = uint64(time.Now().Unix())
+	}
+	if k.Version == 0 {
+		k.Version = k.GeneratedDate
+	}
+	k.SigningKeys = nil
+
+	var buf bytes.Buffer
+	buf.Write(ssh.Marshal(krlHeader{
+		KRLMagic:         krlMagic,
+		KRLFormatVersion: 1,
+		KRLVersion:       k.Version,
+		GeneratedDate:    k.GeneratedDate,
+		Comment:          k.Comment,
+	}))
+
+	for _, section := range k.Sections {
+		buf.Write(section.marshal())
+	}
+
+	for _, key := range keys {
+		buf.Write(ssh.Marshal(krlSignatureHeader{
+			SignatureKey: key.PublicKey().Marshal(),
+		}))
+		sig, err := key.Sign(rand, buf.Bytes())
+		if err != nil {
+			return nil, err
+		}
+		buf.Write(ssh.Marshal(krlSignature{
+			Signature: ssh.Marshal(sig),
+		}))
+		k.SigningKeys = append(k.SigningKeys, key.PublicKey())
+	}
+
+	return buf.Bytes(), nil
+}
+
+// IsRevoked returns true if the given key has been revoked by this KRL.
+func (k *KRL) IsRevoked(key ssh.PublicKey) bool {
+	for _, section := range k.Sections {
+		if section.isRevoked(key) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/vendor/github.com/stripe/krl/parse.go b/vendor/github.com/stripe/krl/parse.go
new file mode 100644
index 0000000000000000000000000000000000000000..75f25510b91dac566de58619ef97e484423e09fe
--- /dev/null
+++ b/vendor/github.com/stripe/krl/parse.go
@@ -0,0 +1,237 @@
+package krl
+
+import (
+	"crypto/sha1"
+	"fmt"
+
+	"golang.org/x/crypto/ssh"
+)
+
+// Hundreds of millions, or 32MB of bitmap
+const krlMaxBitmapSize = 0x10000000
+
+// KRLSigningErrors is a slice of error messages which correspond one-to-one
+// with KRL.SigningKeys.
+type KRLSigningErrors []error
+
+func (k KRLSigningErrors) Error() string {
+	return fmt.Sprintf("krl: bad signatures: %v", []error(k))
+}
+
+func (k KRLSigningErrors) err() error {
+	for _, err := range k {
+		if err != nil {
+			return k
+		}
+	}
+	return nil
+}
+
+// ParseKRL parses a KRL. If the KRL was signed by one or more authorities,
+// those signatures will be checked, and any verification errors will be
+// returned.
+func ParseKRL(in []byte) (*KRL, error) {
+	orig := in
+
+	var header krlHeader
+	if err := ssh.Unmarshal(in, &header); err != nil {
+		return nil, fmt.Errorf("krl: while parsing header: %v", err)
+	}
+	if header.KRLMagic != krlMagic {
+		return nil, fmt.Errorf("krl: bad magic value %x", header.KRLMagic)
+	}
+	if header.KRLFormatVersion != 1 {
+		return nil, fmt.Errorf("krl: bad format version %v", header.KRLFormatVersion)
+	}
+	krl := &KRL{
+		Version:       header.KRLVersion,
+		GeneratedDate: header.GeneratedDate,
+		Comment:       header.Comment,
+	}
+	in = header.Rest
+
+	for len(in) > 0 && in[0] != 4 { // // KRL_SECTION_SIGNATURE
+		var sdata krlSection
+		if err := ssh.Unmarshal(in, &sdata); err != nil {
+			return nil, fmt.Errorf("krl: malformed section: %v", err)
+		}
+		in = sdata.Rest
+
+		var err error
+		var section KRLSection
+		switch sdata.SectionType {
+		case 1: // KRL_SECTION_CERTIFICATES
+			section, err = parseCertificateSection(sdata.SectionData)
+		case 2: // KRL_SECTION_EXPLICIT_KEY
+			section, err = parseExplicitKeySection(sdata.SectionData)
+		case 3: // KRL_SECTION_FINGERPRINT_SHA1
+			section, err = parseFingerprintSection(sdata.SectionData)
+		default:
+			return nil, fmt.Errorf("krl: unexpected section type %d", sdata.SectionType)
+		}
+		if err != nil {
+			return nil, err
+		}
+		krl.Sections = append(krl.Sections, section)
+	}
+
+	var signingErrors KRLSigningErrors
+	for len(in) > 0 {
+		var sigHeader krlSignatureHeader
+		if err := ssh.Unmarshal(in, &sigHeader); err != nil {
+			return nil, fmt.Errorf("krl: malfored signature header: %v", err)
+		}
+		in = sigHeader.Rest
+
+		key, err := ssh.ParsePublicKey(sigHeader.SignatureKey)
+		if err != nil {
+			return nil, fmt.Errorf("krl: malformed signing key: %v", err)
+		}
+
+		var sig krlSignature
+		if err := ssh.Unmarshal(in, &sig); err != nil {
+			return nil, fmt.Errorf("krl: malfored signature wrapper: %v", err)
+		}
+		in = sig.Rest
+
+		sshsig := new(ssh.Signature)
+		if err := ssh.Unmarshal(sig.Signature, sshsig); err != nil {
+			return nil, fmt.Errorf("krl: malformed signature: %v", err)
+		}
+
+		// The entire KRL up until immediately after the signature
+		// header is signed.
+		data := orig[:len(orig)-len(sigHeader.Rest)]
+
+		krl.SigningKeys = append(krl.SigningKeys, key)
+		signingErrors = append(signingErrors, key.Verify(data, sshsig))
+	}
+
+	return krl, signingErrors.err()
+}
+
+func parseCertificateSection(in []byte) (*KRLCertificateSection, error) {
+	var header krlCertificateSectionHeader
+	if err := ssh.Unmarshal(in, &header); err != nil {
+		return nil, fmt.Errorf("krl: while parsing certificate section header: %v", err)
+	}
+	ca, err := ssh.ParsePublicKey(header.CAKey)
+	if err != nil {
+		return nil, fmt.Errorf("krl: while parsing CA key: %v", err)
+	}
+	k := &KRLCertificateSection{CA: ca}
+	in = header.Rest
+	for len(in) > 0 {
+		var section krlCertificateSection
+		if err := ssh.Unmarshal(in, &section); err != nil {
+			return nil, fmt.Errorf("krl: malformed certificate section: %v", err)
+		}
+		in = section.Rest
+		var err error
+		var subsection KRLCertificateSubsection
+		switch section.CertSectionType {
+		case krlSectionCertSerialList:
+			subsection, err = parseCertSerialList(section.CertSectionData)
+		case krlSectionCertSerialRange:
+			subsection, err = parseCertSerialRange(section.CertSectionData)
+		case krlSectionCertSerialBitmap:
+			subsection, err = parseCertSerialBitmap(section.CertSectionData)
+		case krlSectionCertKeyId:
+			subsection, err = parseCertKeyID(section.CertSectionData)
+		default:
+			return nil, fmt.Errorf("krl: unexpected cert section type %x", in[0])
+		}
+		if err != nil {
+			return nil, err
+		}
+		k.Sections = append(k.Sections, subsection)
+	}
+
+	return k, nil
+}
+
+func parseCertSerialList(in []byte) (*KRLCertificateSerialList, error) {
+	s := &KRLCertificateSerialList{}
+	for len(in) > 0 {
+		var list krlSerialList
+		if err := ssh.Unmarshal(in, &list); err != nil {
+			return nil, fmt.Errorf("krl: while parsing serial in list: %v", err)
+		}
+		in = list.Rest
+		*s = append(*s, list.RevokedCertSerial)
+	}
+	return s, nil
+}
+
+func parseCertSerialRange(in []byte) (*KRLCertificateSerialRange, error) {
+	var s krlSerialRange
+	if err := ssh.Unmarshal(in, &s); err != nil {
+		return nil, fmt.Errorf("krl: while parsing serial range: %v", err)
+	}
+	return &KRLCertificateSerialRange{
+		Min: s.SerialMin,
+		Max: s.SerialMax,
+	}, nil
+}
+
+func parseCertSerialBitmap(in []byte) (*KRLCertificateSerialBitmap, error) {
+	var s krlSerialBitmap
+	if err := ssh.Unmarshal(in, &s); err != nil {
+		return nil, fmt.Errorf("krl: while parsing serial bitmap: %v", err)
+	}
+	if bl := s.RevokedKeysBitmap.BitLen(); bl > krlMaxBitmapSize {
+		return nil, fmt.Errorf("krl: serial bitmap too wide: %v", bl)
+	}
+	return &KRLCertificateSerialBitmap{
+		Offset: s.SerialOffset,
+		Bitmap: s.RevokedKeysBitmap,
+	}, nil
+}
+
+func parseCertKeyID(in []byte) (*KRLCertificateKeyID, error) {
+	s := &KRLCertificateKeyID{}
+	for len(in) > 0 {
+		var list krlKeyID
+		if err := ssh.Unmarshal(in, &list); err != nil {
+			return nil, fmt.Errorf("krl: while parsing key id in list: %v", err)
+		}
+		in = list.Rest
+		*s = append(*s, list.KeyID)
+	}
+	return s, nil
+}
+
+func parseExplicitKeySection(in []byte) (*KRLExplicitKeySection, error) {
+	s := &KRLExplicitKeySection{}
+	for len(in) > 0 {
+		var list krlExplicitKey
+		if err := ssh.Unmarshal(in, &list); err != nil {
+			return nil, fmt.Errorf("krl: while parsing explicit key in list: %v", err)
+		}
+		in = list.Rest
+		key, err := ssh.ParsePublicKey(list.PublicKeyBlob)
+		if err != nil {
+			return nil, fmt.Errorf("krl: while parsing explicit key: %v", err)
+		}
+		*s = append(*s, key)
+	}
+	return s, nil
+}
+
+func parseFingerprintSection(in []byte) (*KRLFingerprintSection, error) {
+	s := &KRLFingerprintSection{}
+	for len(in) > 0 {
+		var list krlFingerprintSHA1
+		if err := ssh.Unmarshal(in, &list); err != nil {
+			return nil, fmt.Errorf("krl: while parsing fingerprint in list: %v", err)
+		}
+		in = list.Rest
+		if len(list.PublicKeyHash) != sha1.Size {
+			return nil, fmt.Errorf("krl: key fingerprint wrong length for SHA1: %x", list.PublicKeyHash)
+		}
+		var sha [sha1.Size]byte
+		copy(sha[:], list.PublicKeyHash)
+		*s = append(*s, sha)
+	}
+	return s, nil
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 4e3177342cdeaacf72b208c3b1beb8082732ca06..a687a53a87ba86d925d6478e411193fe4e0658d1 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -376,6 +376,12 @@
 			"revision": "d77da356e56a7428ad25149ca77381849a6a5232",
 			"revisionTime": "2016-06-15T09:26:46Z"
 		},
+		{
+			"checksumSHA1": "Mqb+Cn1S2ZN/nOfn8FDaAiXTdhU=",
+			"path": "github.com/stripe/krl",
+			"revision": "fd565ec6f8e236d6b63b953f3aecb2f2cca80605",
+			"revisionTime": "2016-07-21T22:16:07Z"
+		},
 		{
 			"checksumSHA1": "BS9oue0y6JjMzz3spKlMTVmxZxo=",
 			"path": "go4.org/wkfs",