certspotter/helpers.go

409 lines
11 KiB
Go
Raw Normal View History

2016-02-05 03:45:37 +01:00
package ctwatch
import (
"fmt"
"time"
"os"
"os/exec"
"bytes"
"io"
"io/ioutil"
"math/big"
"path/filepath"
"strconv"
"strings"
"crypto/sha256"
"encoding/hex"
"encoding/pem"
"encoding/json"
2016-02-05 03:45:37 +01:00
"src.agwa.name/ctwatch/ct"
2016-02-05 03:45:37 +01:00
)
2016-02-18 19:09:33 +01:00
func ReadSTHFile (path string) (*ct.SignedTreeHead, error) {
2016-02-05 03:45:37 +01:00
content, err := ioutil.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
2016-02-05 03:45:37 +01:00
}
return nil, err
2016-02-05 03:45:37 +01:00
}
var sth ct.SignedTreeHead
if err := json.Unmarshal(content, &sth); err != nil {
return nil, err
2016-02-05 03:45:37 +01:00
}
return &sth, nil
2016-02-05 03:45:37 +01:00
}
2016-02-18 19:09:33 +01:00
func WriteSTHFile (path string, sth *ct.SignedTreeHead) error {
sthJson, err := json.MarshalIndent(sth, "", "\t")
if err != nil {
return err
}
sthJson = append(sthJson, byte('\n'))
return ioutil.WriteFile(path, sthJson, 0666)
2016-02-05 03:45:37 +01:00
}
func WriteProofFile (path string, proof ct.ConsistencyProof) error {
proofJson, err := json.MarshalIndent(proof, "", "\t")
if err != nil {
return err
}
proofJson = append(proofJson, byte('\n'))
return ioutil.WriteFile(path, proofJson, 0666)
}
func IsPrecert (entry *ct.LogEntry) bool {
return entry.Leaf.TimestampedEntry.EntryType == ct.PrecertLogEntryType
}
func GetFullChain (entry *ct.LogEntry) [][]byte {
certs := make([][]byte, 0, len(entry.Chain) + 1)
if entry.Leaf.TimestampedEntry.EntryType == ct.X509LogEntryType {
certs = append(certs, entry.Leaf.TimestampedEntry.X509Entry)
}
for _, cert := range entry.Chain {
certs = append(certs, cert)
}
return certs
}
func formatSerialNumber (serial *big.Int) string {
if serial != nil {
return fmt.Sprintf("%x", serial)
} else {
return ""
2016-02-05 03:45:37 +01:00
}
}
func sha256sum (data []byte) []byte {
sum := sha256.Sum256(data)
return sum[:]
}
func sha256hex (data []byte) string {
return hex.EncodeToString(sha256sum(data))
2016-02-05 03:45:37 +01:00
}
type EntryInfo struct {
LogUri string
Entry *ct.LogEntry
IsPrecert bool
FullChain [][]byte // first entry is logged X509 cert or pre-cert
CertInfo *CertInfo
ParseError error // set iff CertInfo is nil
Filename string
2016-02-05 03:45:37 +01:00
}
type CertInfo struct {
TBS *TBSCertificate
CommonNames []string
CommonNamesParseError error
DNSNames []string
DNSNamesParseError error
Subject RDNSequence
SubjectParseError error
Issuer RDNSequence
IssuerParseError error
SerialNumber *big.Int
SerialNumberParseError error
Validity *CertValidity
ValidityParseError error
IsCA *bool
IsCAParseError error
}
2016-02-05 03:45:37 +01:00
func MakeCertInfoFromTBS (tbs *TBSCertificate) *CertInfo {
info := &CertInfo{TBS: tbs}
2016-02-05 03:45:37 +01:00
info.CommonNames, info.CommonNamesParseError = tbs.ParseCommonNames()
info.DNSNames, info.DNSNamesParseError = tbs.ParseDNSNames()
info.Subject, info.SubjectParseError = tbs.ParseSubject()
info.Issuer, info.IssuerParseError = tbs.ParseIssuer()
info.SerialNumber, info.SerialNumberParseError = tbs.ParseSerialNumber()
info.Validity, info.ValidityParseError = tbs.ParseValidity()
info.IsCA, info.IsCAParseError = tbs.ParseBasicConstraints()
2016-02-05 03:45:37 +01:00
return info
2016-02-05 03:45:37 +01:00
}
func MakeCertInfoFromRawTBS (tbsBytes []byte) (*CertInfo, error) {
tbs, err := ParseTBSCertificate(tbsBytes)
if err != nil {
return nil, err
2016-02-05 03:45:37 +01:00
}
return MakeCertInfoFromTBS(tbs), nil
2016-02-05 03:45:37 +01:00
}
func MakeCertInfoFromRawCert (certBytes []byte) (*CertInfo, error) {
cert, err := ParseCertificate(certBytes)
if err != nil {
return nil, err
}
return MakeCertInfoFromRawTBS(cert.GetRawTBSCertificate())
2016-02-05 03:45:37 +01:00
}
2016-03-18 00:34:53 +01:00
func MakeCertInfoFromLogEntry (entry *ct.LogEntry) (*CertInfo, error) {
switch entry.Leaf.TimestampedEntry.EntryType {
case ct.X509LogEntryType:
return MakeCertInfoFromRawCert(entry.Leaf.TimestampedEntry.X509Entry)
case ct.PrecertLogEntryType:
return MakeCertInfoFromRawTBS(entry.Leaf.TimestampedEntry.PrecertEntry.TBSCertificate)
default:
return nil, fmt.Errorf("MakeCertInfoFromCTEntry: unknown CT entry type (neither X509 nor precert)")
2016-02-05 03:45:37 +01:00
}
}
func (info *CertInfo) commonNamesString () string {
if info.CommonNamesParseError == nil {
return strings.Join(info.CommonNames, ", ")
} else {
return ""
}
}
func (info *CertInfo) dnsNamesString () string {
if info.DNSNamesParseError == nil {
return strings.Join(info.DNSNames, ", ")
} else {
return ""
}
}
func (info *CertInfo) NotBefore () *time.Time {
if info.ValidityParseError == nil {
return &info.Validity.NotBefore
} else {
return nil
}
}
func (info *CertInfo) NotAfter () *time.Time {
if info.ValidityParseError == nil {
return &info.Validity.NotAfter
} else {
return nil
}
2016-02-05 03:45:37 +01:00
}
func (info *CertInfo) PubkeyHash () string {
return sha256hex(info.TBS.GetRawPublicKey())
2016-02-05 03:45:37 +01:00
}
func (info *CertInfo) PubkeyHashBytes () []byte {
return sha256sum(info.TBS.GetRawPublicKey())
}
func (info *CertInfo) Environ () []string {
env := make([]string, 0, 10)
env = append(env, "PUBKEY_HASH=" + info.PubkeyHash())
if info.CommonNamesParseError != nil {
env = append(env, "COMMON_NAMES_PARSE_ERROR=" + info.CommonNamesParseError.Error())
} else {
env = append(env, "COMMON_NAMES=" + strings.Join(info.CommonNames, ","))
}
if info.DNSNamesParseError != nil {
env = append(env, "DNS_NAMES_PARSE_ERROR=" + info.DNSNamesParseError.Error())
} else {
env = append(env, "DNS_NAMES=" + strings.Join(info.DNSNames, ","))
}
if info.SerialNumberParseError != nil {
env = append(env, "SERIAL_PARSE_ERROR=" + info.SerialNumberParseError.Error())
} else {
env = append(env, "SERIAL=" + formatSerialNumber(info.SerialNumber))
}
if info.ValidityParseError != nil {
env = append(env, "VALIDITY_PARSE_ERROR=" + info.ValidityParseError.Error())
} else {
env = append(env, "NOT_BEFORE=" + info.Validity.NotBefore.String())
env = append(env, "NOT_BEFORE_UNIXTIME=" + strconv.FormatInt(info.Validity.NotBefore.Unix(), 10))
env = append(env, "NOT_AFTER=" + info.Validity.NotAfter.String())
env = append(env, "NOT_AFTER_UNIXTIME=" + strconv.FormatInt(info.Validity.NotAfter.Unix(), 10))
}
if info.SubjectParseError != nil {
env = append(env, "SUBJECT_PARSE_ERROR=" + info.SubjectParseError.Error())
} else {
env = append(env, "SUBJECT_DN=" + info.Subject.String())
}
if info.IssuerParseError != nil {
env = append(env, "ISSUER_PARSE_ERROR=" + info.IssuerParseError.Error())
} else {
env = append(env, "ISSUER_DN=" + info.Issuer.String())
}
return env
}
2016-03-24 04:18:12 +01:00
func (info *EntryInfo) HasParseErrors () bool {
return info.ParseError != nil ||
info.CertInfo.CommonNamesParseError != nil ||
2016-03-24 04:18:12 +01:00
info.CertInfo.DNSNamesParseError != nil ||
info.CertInfo.SubjectParseError != nil ||
info.CertInfo.IssuerParseError != nil ||
info.CertInfo.SerialNumberParseError != nil ||
info.CertInfo.ValidityParseError != nil ||
info.CertInfo.IsCAParseError != nil
2016-03-24 04:18:12 +01:00
}
func (info *EntryInfo) Fingerprint () string {
if len(info.FullChain) > 0 {
return sha256hex(info.FullChain[0])
} else {
return ""
}
}
func (info *EntryInfo) FingerprintBytes () []byte {
if len(info.FullChain) > 0 {
return sha256sum(info.FullChain[0])
} else {
return []byte{}
}
}
func (info *EntryInfo) typeString () string {
if info.IsPrecert {
2016-02-05 03:45:37 +01:00
return "precert"
} else {
return "cert"
}
}
func (info *EntryInfo) typeFriendlyString () string {
if info.IsPrecert {
2016-02-05 03:45:37 +01:00
return "Pre-certificate"
} else {
return "Certificate"
}
}
func yesnoString (value bool) string {
if value {
return "yes"
} else {
return "no"
}
2016-02-05 03:45:37 +01:00
}
func (info *EntryInfo) Environ () []string {
env := []string{
"FINGERPRINT=" + info.Fingerprint(),
"CERT_TYPE=" + info.typeString(),
"CERT_PARSEABLE=" + yesnoString(info.ParseError == nil),
"LOG_URI=" + info.LogUri,
"ENTRY_INDEX=" + strconv.FormatInt(info.Entry.Index, 10),
}
2016-02-05 03:45:37 +01:00
if info.Filename != "" {
env = append(env, "CERT_FILENAME=" + info.Filename)
}
if info.ParseError == nil {
certEnv := info.CertInfo.Environ()
env = append(env, certEnv...)
} else {
env = append(env, "PARSE_ERROR=" + info.ParseError.Error())
}
return env
}
func writeField (out io.Writer, name string, value interface{}, err error) {
if err == nil {
fmt.Fprintf(out, "\t%13s = %s\n", name, value)
} else {
fmt.Fprintf(out, "\t%13s = *** UNKNOWN (%s) ***\n", name, err)
}
}
func (info *EntryInfo) Write (out io.Writer) {
fingerprint := info.Fingerprint()
fmt.Fprintf(out, "%s:\n", fingerprint)
if info.ParseError != nil {
writeField(out, "Parse Error", "*** " + info.ParseError.Error() + " ***", nil)
} else {
writeField(out, "Common Name", info.CertInfo.commonNamesString(), info.CertInfo.CommonNamesParseError)
writeField(out, "DNS Names", info.CertInfo.dnsNamesString(), info.CertInfo.DNSNamesParseError)
writeField(out, "Pubkey", info.CertInfo.PubkeyHash(), nil)
writeField(out, "Subject", info.CertInfo.Subject, info.CertInfo.SubjectParseError)
writeField(out, "Issuer", info.CertInfo.Issuer, info.CertInfo.IssuerParseError)
writeField(out, "Serial", info.CertInfo.SerialNumber, info.CertInfo.SerialNumberParseError)
writeField(out, "Not Before", info.CertInfo.NotBefore(), info.CertInfo.ValidityParseError)
writeField(out, "Not After", info.CertInfo.NotAfter(), info.CertInfo.ValidityParseError)
}
writeField(out, "Type", info.typeFriendlyString(), nil)
writeField(out, "Log Entry", fmt.Sprintf("%d @ %s", info.Entry.Index, info.LogUri), nil)
writeField(out, "crt.sh", "https://crt.sh/?q=" + fingerprint, nil)
if info.Filename != "" {
writeField(out, "Filename", info.Filename, nil)
}
}
func (info *EntryInfo) InvokeHookScript (command string) error {
cmd := exec.Command(command)
cmd.Env = os.Environ()
infoEnv := info.Environ()
cmd.Env = append(cmd.Env, infoEnv...)
2016-02-05 03:45:37 +01:00
stderrBuffer := bytes.Buffer{}
cmd.Stderr = &stderrBuffer
if err := cmd.Run(); err != nil {
if _, isExitError := err.(*exec.ExitError); isExitError {
fmt.Errorf("Script failed: %s: %s", command, strings.TrimSpace(stderrBuffer.String()))
} else {
fmt.Errorf("Failed to execute script: %s: %s", command, err)
}
}
return nil
}
func WriteCertRepository (repoPath string, isPrecert bool, certs [][]byte) (bool, string, error) {
if len(certs) == 0 {
return false, "", fmt.Errorf("Cannot write an empty certificate chain")
}
fingerprint := sha256hex(certs[0])
2016-02-05 03:45:37 +01:00
prefixPath := filepath.Join(repoPath, fingerprint[0:2])
var filenameSuffix string
if isPrecert {
2016-02-05 03:45:37 +01:00
filenameSuffix = ".precert.pem"
} else {
2016-02-05 03:45:37 +01:00
filenameSuffix = ".cert.pem"
}
if err := os.Mkdir(prefixPath, 0777); err != nil && !os.IsExist(err) {
return false, "", fmt.Errorf("Failed to create prefix directory %s: %s", prefixPath, err)
2016-02-05 03:45:37 +01:00
}
path := filepath.Join(prefixPath, fingerprint + filenameSuffix)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
if os.IsExist(err) {
return true, path, nil
2016-02-05 03:45:37 +01:00
} else {
return false, path, fmt.Errorf("Failed to open %s for writing: %s", path, err)
2016-02-05 03:45:37 +01:00
}
}
for _, cert := range certs {
if err := pem.Encode(file, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
2016-02-05 03:45:37 +01:00
file.Close()
return false, path, fmt.Errorf("Error writing to %s: %s", path, err)
2016-02-05 03:45:37 +01:00
}
}
if err := file.Close(); err != nil {
return false, path, fmt.Errorf("Error writing to %s: %s", path, err)
2016-02-05 03:45:37 +01:00
}
return false, path, nil
2016-02-05 03:45:37 +01:00
}