Skip to content

Instantly share code, notes, and snippets.

@rolandshoemaker
Last active August 29, 2015 14:19
Show Gist options
  • Save rolandshoemaker/32793e5ae5ddc844435d to your computer and use it in GitHub Desktop.
Save rolandshoemaker/32793e5ae5ddc844435d to your computer and use it in GitHub Desktop.
package main
import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"database/sql"
"encoding/json"
"fmt"
"runtime"
"time"
_ "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/core"
"github.com/letsencrypt/boulder/jose"
"github.com/letsencrypt/boulder/mail"
)
var maxWorkers int = 10000
var expirationMessagesSent int = 0
var ocspSignings int = 0
type worker struct {
Mailer mail.Mailer
Sql *sql.DB
}
// probably more... formal...
var expirationMsg string = `Hi!
Your certificate for domains %v are set to expire at %s. You may want to,
you know, renew them or something?
BYE :)
`
func (w *worker) checkExpiry(digest string, cert *x509.Certificate) bool {
// check if expiration time is < time.Now + a week
if cert.NotAfter.Before(time.Now().Add(time.Hour * 24 * 7)) {
// figure out where to send this, format expiration template
// and send it to the person who should get it
var jsonWebKey jose.JsonWebKey
var ok bool
switch cert.PublicKeyAlgorithm {
case x509.RSA:
jsonWebKey.KeyType = jose.KeyTypeRSA
jsonWebKey.Rsa, ok = cert.PublicKey.(*rsa.PublicKey)
if !ok {
}
case x509.ECDSA:
jsonWebKey.KeyType = jose.KeyTypeEC
jsonWebKey.Ec = cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
}
}
jsonWebKey.ComputeThumbprint()
rows, err := w.Sql.Query("SELECT value FROM registrations WHERE thumbprint = ?;", jsonWebKey.Thumbprint)
if err != nil {
// this shouldn't happen :<
}
defer rows.Close()
var emails []string
for rows.Next() {
var jsonReg []byte
if err = rows.Scan(&jsonReg); err != nil {
// soooomething
}
var reg core.Registration
err = json.Unmarshal(jsonReg, &reg)
if err != nil {
// also this shouldn't...
}
for _, contact := range reg.Contact {
if contact.Scheme == "mailto" {
emails = append(emails, contact.Opaque) // ???
}
}
}
// send email to contacts
err = w.Mailer.SendMail(emails, fmt.Sprintf(expirationMsg, cert.DNSNames, cert.NotAfter))
if err != nil {
// this can probably happen...
return false
}
return true
}
return false
}
func (w *worker) signOCSP(digest string, cert *x509.Certificate) bool {
// I'm not really sure what this entails...
return false
}
func (w *worker) processCert(digest string, certBytes []byte) {
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
// should NEVER happen since CA should've checked that the certificate it
// got was... you know... valid
return
}
var expirySent bool
var ocspSigned bool
var existingRecord bool
err = w.Sql.QueryRow("SELECT expirySent, ocspSigned FROM eo_checks WHERE digest = ?;", digest).Scan(&expirySent, &ocspSigned)
switch {
case err == sql.ErrNoRows:
expirySent = false
ocspSigned = false
existingRecord = false
case err != nil:
// something bad happend
default:
existingRecord = true
}
if expirySent && ocspSigned {
return
}
// check expiration
if !expirySent {
expirySent = w.checkExpiry(digest, cert)
}
// do OCSP-y stuff
if !ocspSigned {
ocspSigned = w.signOCSP(digest, cert) // idk is this will need the digest
}
// update / create record for cert in eo_checks
if existingRecord {
_, err = w.Sql.Exec("UPDATE eo_checks SET (expirySent, ocspSigned) VALUES (?, ?) WHERE digest = ?;", expirySent, ocspSigned, digest)
} else {
_, err = w.Sql.Exec("INSERT INTO eo_checks (digest, expirySent, ocspSigned) VALUES (?, ?, ?);", digest, expirySent, ocspSigned)
}
if err != nil {
// something bad time...
}
// finished
return
}
func setupTables(Sql *sql.DB) error {
tx, err := Sql.Begin()
if err != nil {
return err
}
// Create our supr useful table to track stuff
_, err = tx.Exec("CREATE TABLE eo_checks (digest TEXT, expirySent BOOL, ocspSigned BOOL);")
if err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
func main() {
var w worker
var err error
// should grab this from boulder conf really...
w.Mailer = mail.NewMailer("localhost", "25", "cert-guy@example.com", "password")
w.Sql, err = sql.Open("mysql", "root:7r0gd0r!@tcp(192.168.125.2:3306)/boulder")
if err != nil {
fmt.Println(err)
return
}
// check if eo_checks table exists
var tableExists bool
err = w.Sql.QueryRow("SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_schema = 'boulder' AND table_name = 'eo_checks');").Scan(&tableExists)
if err != nil {
fmt.Println(err)
return
}
if !tableExists {
// it doesn't! :<
err = setupTables(w.Sql)
if err != nil {
fmt.Println(err)
return
}
}
// reverse so we can get the OCSP signings out of the way first
rows, err := w.Sql.Query("SELECT digest, value FROM certificates ORDER BY sequence DESC;")
if err != nil {
//
fmt.Println(err)
return
} else {
defer rows.Close()
for rows.Next() {
var digest string
var certBytes []byte
if err := rows.Scan(&digest, &certBytes); err != nil {
//
} else {
// pass off to processCert in goroutine if there isn't already the max number of workers
// (since i've used NumGoroutine instead of some fixed counter this may be off by a few
// but that shouldn't really matter if the max is high enough...)
if runtime.NumGoroutine() >= maxWorkers {
for {
if runtime.NumGoroutine() < maxWorkers {
break
}
}
}
go w.processCert(digest, certBytes)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment