entryMaker/main.go (138 lines of code) (raw):
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package main
import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"flag"
"fmt"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509/pkix"
"github.com/mozilla/OneCRL-Tools/entryMaker/oneCRL"
"github.com/mozilla/OneCRL-Tools/entryMaker/util"
"io/ioutil"
"time"
)
func check(e error) {
if e != nil {
panic(e)
}
}
// certificate-transparency-go/x509 is too strict for our purposes.
// re-purpose it to be less strict.
type certificate struct {
Raw asn1.RawContent
TBSCertificate tbsCertificate
SignatureAlgorithm pkix.AlgorithmIdentifier
SignatureValue asn1.BitString
}
type tbsCertificate struct {
Raw asn1.RawContent
Version int `asn1:"optional,explicit,default:0,tag:0"`
SerialNumber asn1.RawValue
SignatureAlgorithm pkix.AlgorithmIdentifier
Issuer asn1.RawValue
Validity validity
Subject asn1.RawValue
PublicKey publicKeyInfo
UniqueId asn1.BitString `asn1:"optional,tag:1"`
SubjectUniqueId asn1.BitString `asn1:"optional,tag:2"`
Extensions []asn1.RawValue `asn1:"optional,explicit,tag:3"`
}
type validity struct {
NotBefore, NotAfter time.Time
}
type publicKeyInfo struct {
Raw asn1.RawContent
Algorithm pkix.AlgorithmIdentifier
PublicKey asn1.BitString
}
func parseCertificate(asn1Data []byte) (*certificate, error) {
var cert certificate
rest, err := asn1.UnmarshalWithParams(asn1Data, &cert, "lax")
if err != nil {
return nil, err
}
if len(rest) > 0 {
return nil, asn1.SyntaxError{Msg: "trailing data"}
}
return &cert, nil
}
func main() {
certPtr := flag.String("cert", "", "a DER or PEM encoded certificate file")
exceptionsPtr := flag.String("exceptions", "", "A JSON document containing exceptional additions")
revocationTypePtr := flag.String("type", "issuer-serial", "What type of revocation you want (options: issuer-serial, subject-pubkey)")
checkValidityPtr := flag.String("checkvalidity", "no", "should validity be checked? (yes / no)")
whoPtr := flag.String("who", "", "email of revoking party")
whyPtr := flag.String("why", "", "reason for revocation")
namePtr := flag.String("name", "", "name of this revocation")
bugPtr := flag.String("bug", "", "bug URL for this revocation")
overwritePtr := flag.Bool("overwrite", false, "Overwrite the exceptions file?")
flag.Parse()
// Optionally load the exceptions list to make the addition to
exceptionsList := new(oneCRL.Records)
if len(*exceptionsPtr) != 0 {
if err := util.LoadExceptions(*exceptionsPtr, &oneCRL.Records{}, exceptionsList); nil != err {
panic(err)
}
}
var certData []byte
if nil != certPtr && len(*certPtr) > 0 {
// Get the cert from the args
var err error
certData, err = ioutil.ReadFile(*certPtr)
check(err)
}
var record oneCRL.Record
if len(certData) > 0 {
// Maybe it's PEM; try to parse as PEM, if that fails, just use the bytes
// We only care about the first block for now
block, _ := pem.Decode(certData)
if nil == block {
panic(errors.New("There was a problem decoding the certificate"))
}
certData = block.Bytes
cert, err := parseCertificate(certData)
check(err)
// Check to see if the cert is still valid (if it's not, we don't want
// an entry
if "yes" == *checkValidityPtr {
if time.Now().After(cert.TBSCertificate.Validity.NotAfter) {
panic(errors.New(fmt.Sprintf("Cert is no longer valid (NotAfter %v)", cert.TBSCertificate.Validity.NotAfter)))
}
}
switch *revocationTypePtr {
case "issuer-serial":
issuerString := base64.StdEncoding.EncodeToString(cert.TBSCertificate.Issuer.FullBytes)
serialString := base64.StdEncoding.EncodeToString(cert.TBSCertificate.SerialNumber.Bytes)
record = oneCRL.Record{IssuerName: issuerString, SerialNumber: serialString}
case "subject-pubkey":
subjectString := base64.StdEncoding.EncodeToString(cert.TBSCertificate.Subject.FullBytes)
if pubKeyData, err := x509.MarshalPKIXPublicKey(cert.TBSCertificate.PublicKey); err == nil {
hash := sha256.Sum256(pubKeyData)
base64EncodedHash := base64.StdEncoding.EncodeToString(hash[:])
record = oneCRL.Record{Subject: subjectString, PubKeyHash: base64EncodedHash}
}
default:
panic("Unexpected revocation type")
}
}
// Set the values on the new record
record.Enabled = true
record.Details.Who = *whoPtr
record.Details.Why = *whyPtr
record.Details.Name = *namePtr
record.Details.Bug = *bugPtr
record.Details.Created = ""
var toPrint interface{}
if len(exceptionsList.Data) > 0 {
exceptionsList.Data = append(exceptionsList.Data, record)
toPrint = exceptionsList
} else {
toPrint = record
}
if *overwritePtr && len(exceptionsList.Data) > 0 {
if err := util.StoreExceptions(*exceptionsPtr, exceptionsList); nil != err {
panic(err)
}
} else {
formattedJson, err := json.MarshalIndent(toPrint, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("%s\n", formattedJson)
}
}