Last active
August 29, 2015 14:19
-
-
Save rolandshoemaker/32793e5ae5ddc844435d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ®) | |
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