Skip to content

Instantly share code, notes, and snippets.

@maraino
Last active October 17, 2024 16:17
Show Gist options
  • Save maraino/4dcb64cb051b17ef6d421892cb4e55a8 to your computer and use it in GitHub Desktop.
Save maraino/4dcb64cb051b17ef6d421892cb4e55a8 to your computer and use it in GitHub Desktop.
Create CRL index.txt
#!/bin/sh
set -e
# prepare copy directory
mkdir -p /crl/db/
# clean leftovers
rm -f /crl/db/*
# make a copy of badger db
cp -pr /data/db /crl/
# extract index.txt
go run listcerts.go /crl/db
package main
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"crypto/x509"
"encoding/json"
"encoding/pem"
"github.com/dgraph-io/badger"
"github.com/pkg/errors"
"strings"
"time"
)
// main
func main() {
db, err := badger.Open(badger.DefaultOptions(os.Args[1]).WithLogger(nil))
if err != nil {
panic(err)
}
defer db.Close()
writeIndex(db, []byte("x509_certs"), "/dev/stdout")
}
func writeIndex(db *badger.DB, prefix []byte, filename string) {
txn := db.NewTransaction(false)
defer txn.Discard()
opts := badger.DefaultIteratorOptions
prefix, err := badgerEncode(prefix)
if err != nil {
panic(err)
}
iter := txn.NewIterator(opts)
defer iter.Close()
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
panic(err)
}
defer f.Close()
for iter.Seek(prefix); iter.ValidForPrefix(prefix); iter.Next() {
item := iter.Item()
var valCopy []byte
valCopy, err = item.ValueCopy(nil)
if err != nil {
panic(err)
}
if len(strings.TrimSpace(string(valCopy))) == 0 {
// Item is empty
continue
}
// read data to object
b, err := json.Marshal(valCopy)
if err != nil {
panic(err)
}
// make cert-data from db decodable pem
// json contains ""
r := strings.ReplaceAll(string(b), "\"", "")
base64 := fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----", r)
block, _ := pem.Decode([]byte(base64))
if block == nil {
panic("failed to parse certificate PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
// ocsp responder needs this in HEX
serial := fmt.Sprintf("%X", cert.SerialNumber)
// step needs int to revoke by serialnumber
//serial := cert.SerialNumber.String()
if err != nil {
panic("failed to parse certificate: " + err.Error())
}
var revokedAt string
flag := "X"
var obj2 *badger.Item
// see if cert is revoked
obj2, err = GetRevoked(db, []byte("revoked_x509_certs"), []byte(cert.SerialNumber.String()))
if err != nil {
// we skip errors on revoke (like not found)
} else {
// we have found a revoked cert
var valCopy2 []byte
valCopy2, err = obj2.ValueCopy(nil)
if err != nil {
panic(err)
}
if len(strings.TrimSpace(string(valCopy2))) > 0 {
var obj RevokeOptions
if err := json.Unmarshal(valCopy2, &obj); err != nil {
panic(err)
}
// time.RFC3339 contains - : and T
// index.txt wants yymmddHHMMSSZ (Z = Zulu = UTC)
revokedAt = fmt.Sprintf(obj.RevokedAt.UTC().Format(time.RFC3339))
revokedAt = strings.ReplaceAll(string(revokedAt), ":", "")
revokedAt = strings.ReplaceAll(string(revokedAt), "-", "")
revokedAt = strings.ReplaceAll(string(revokedAt), "T", "")
// remove 20 from start
revokedAt = revokedAt[2:]
flag = "R"
}
}
// time.RFC3339 contains - : and T
// index.txt wants yymmddHHMMSSZ (Z = Zulu = UTC)
notafter := fmt.Sprintf(cert.NotAfter.UTC().Format(time.RFC3339))
notafter = strings.ReplaceAll(string(notafter), ":", "")
notafter = strings.ReplaceAll(string(notafter), "-", "")
notafter = strings.ReplaceAll(string(notafter), "T", "")
// remove 20 from start
notafter = notafter[2:]
today := time.Now()
if revokedAt == "" {
// Cert is still valid?
if today.Before(cert.NotAfter) {
flag = "V"
} else {
flag = "E"
}
}
// 0) Entry type. May be "V" (valid), "R" (revoked) or "E" (expired).
// Note that an expired may have the type "V" because the type has
// not been updated. 'openssl ca updatedb' does such an update.
// 1) Expiration datetime.
// 2) Revokation datetime. This is set for any entry of the type "R".
// 3) Serial number.
// 4) File name of the certificate. This doesn't seem to be used,
// ever, so it's always "unknown".
// 5) Certificate subject name.
fmt.Fprintf(f, "%s\t%s\t%s\t%032s\t%s\t%s\n", flag, notafter, revokedAt, serial, "unknown", cert.Subject)
}
}
type RevokeOptions struct {
Serial string
Reason string
ReasonCode int
PassiveOnly bool
RevokedAt time.Time
MTLS bool
}
func GetRevoked(db *badger.DB, prefix []byte, key []byte) (item *badger.Item, rerr error) {
badgerKey, _ := toBadgerKey(prefix, key)
txn := db.NewTransaction(false)
defer txn.Discard()
opts := badger.DefaultIteratorOptions
_ = opts
item, err2 := txn.Get(badgerKey)
if err2 != nil {
return nil, err2
}
return item, nil
}
// badgerEncode encodes a byte slice into a section of a BadgerKey.
// See documentation for toBadgerKey.
func badgerEncode(val []byte) ([]byte, error) {
l := len(val)
switch {
case l == 0:
return nil, errors.Errorf("input cannot be empty")
case l > 65535:
return nil, errors.Errorf("length of input cannot be greater than 65535")
default:
lb := new(bytes.Buffer)
if err := binary.Write(lb, binary.LittleEndian, uint16(l)); err != nil {
return nil, errors.Wrap(err, "error doing binary Write")
}
return append(lb.Bytes(), val...), nil
}
}
func toBadgerKey(bucket, key []byte) ([]byte, error) {
first, err := badgerEncode(bucket)
if err != nil {
return nil, err
}
second, err := badgerEncode(key)
if err != nil {
return nil, err
}
return append(first, second...), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment