diff --git a/helpers.go b/helpers.go deleted file mode 100644 index 4b5f474..0000000 --- a/helpers.go +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright (C) 2016 Opsmate, Inc. -// -// 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/. -// -// This software is distributed WITHOUT A WARRANTY OF ANY KIND. -// See the Mozilla Public License for details. - -package certspotter - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "math/big" - "os" - "os/exec" - "strconv" - "strings" - "time" - - "software.sslmate.com/src/certspotter/ct" -) - -func ReadSTHFile(path string) (*ct.SignedTreeHead, error) { - content, err := ioutil.ReadFile(path) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - var sth ct.SignedTreeHead - if err := json.Unmarshal(content, &sth); err != nil { - return nil, err - } - - return &sth, nil -} - -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) -} - -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 "" - } -} - -func sha256sum(data []byte) []byte { - sum := sha256.Sum256(data) - return sum[:] -} - -func sha256hex(data []byte) string { - return hex.EncodeToString(sha256sum(data)) -} - -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 - Identifiers *Identifiers - IdentifiersParseError error - Filename string - Bygone bool -} - -type CertInfo struct { - TBS *TBSCertificate - - Subject RDNSequence - SubjectParseError error - Issuer RDNSequence - IssuerParseError error - SANs []SubjectAltName - SANsParseError error - SerialNumber *big.Int - SerialNumberParseError error - Validity *CertValidity - ValidityParseError error - IsCA *bool - IsCAParseError error -} - -func MakeCertInfoFromTBS(tbs *TBSCertificate) *CertInfo { - info := &CertInfo{TBS: tbs} - - info.Subject, info.SubjectParseError = tbs.ParseSubject() - info.Issuer, info.IssuerParseError = tbs.ParseIssuer() - info.SANs, info.SANsParseError = tbs.ParseSubjectAltNames() - info.SerialNumber, info.SerialNumberParseError = tbs.ParseSerialNumber() - info.Validity, info.ValidityParseError = tbs.ParseValidity() - info.IsCA, info.IsCAParseError = tbs.ParseBasicConstraints() - - return info -} - -func MakeCertInfoFromRawTBS(tbsBytes []byte) (*CertInfo, error) { - tbs, err := ParseTBSCertificate(tbsBytes) - if err != nil { - return nil, err - } - return MakeCertInfoFromTBS(tbs), nil -} - -func MakeCertInfoFromRawCert(certBytes []byte) (*CertInfo, error) { - cert, err := ParseCertificate(certBytes) - if err != nil { - return nil, err - } - return MakeCertInfoFromRawTBS(cert.GetRawTBSCertificate()) -} - -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)") - } -} - -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 - } -} - -func (info *CertInfo) PubkeyHash() string { - return sha256hex(info.TBS.GetRawPublicKey()) -} - -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.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()) - } - - // TODO: include SANs in environment - - return env -} - -func (info *EntryInfo) HasParseErrors() bool { - return info.ParseError != nil || - info.IdentifiersParseError != nil || - info.CertInfo.SubjectParseError != nil || - info.CertInfo.IssuerParseError != nil || - info.CertInfo.SANsParseError != nil || - info.CertInfo.SerialNumberParseError != nil || - info.CertInfo.ValidityParseError != nil || - info.CertInfo.IsCAParseError != nil -} - -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 { - return "precert" - } else { - return "cert" - } -} - -func (info *EntryInfo) typeFriendlyString() string { - if info.IsPrecert { - return "Pre-certificate" - } else { - return "Certificate" - } -} - -func yesnoString(value bool) string { - if value { - return "yes" - } else { - return "no" - } -} - -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), - } - - if info.Filename != "" { - env = append(env, "CERT_FILENAME="+info.Filename) - } - if info.ParseError != nil { - env = append(env, "PARSE_ERROR="+info.ParseError.Error()) - } else if info.CertInfo != nil { - certEnv := info.CertInfo.Environ() - env = append(env, certEnv...) - } - if info.IdentifiersParseError != nil { - env = append(env, "IDENTIFIERS_PARSE_ERROR="+info.IdentifiersParseError.Error()) - } else if info.Identifiers != nil { - env = append(env, "DNS_NAMES="+info.Identifiers.dnsNamesString(",")) - env = append(env, "IP_ADDRESSES="+info.Identifiers.ipAddrsString(",")) - } - - 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.IdentifiersParseError != nil { - writeField(out, "Identifiers", nil, info.IdentifiersParseError) - } else if info.Identifiers != nil { - for _, dnsName := range info.Identifiers.DNSNames { - writeField(out, "DNS Name", dnsName, nil) - } - for _, ipaddr := range info.Identifiers.IPAddrs { - writeField(out, "IP Address", ipaddr, nil) - } - } - if info.ParseError != nil { - writeField(out, "Parse Error", "*** "+info.ParseError.Error()+" ***", nil) - } else if info.CertInfo != nil { - writeField(out, "Pubkey", info.CertInfo.PubkeyHash(), nil) - writeField(out, "Issuer", info.CertInfo.Issuer, info.CertInfo.IssuerParseError) - writeField(out, "Not Before", info.CertInfo.NotBefore(), info.CertInfo.ValidityParseError) - writeField(out, "Not After", info.CertInfo.NotAfter(), info.CertInfo.ValidityParseError) - if info.Bygone { - writeField(out, "BygoneSSL", "True", info.CertInfo.ValidityParseError) - } - } - writeField(out, "Log Entry", fmt.Sprintf("%d @ %s (%s)", info.Entry.Index, info.LogUri, info.typeFriendlyString()), nil) - writeField(out, "crt.sh", "https://crt.sh/?sha256="+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...) - stderrBuffer := bytes.Buffer{} - cmd.Stderr = &stderrBuffer - if err := cmd.Run(); err != nil { - if _, isExitError := err.(*exec.ExitError); isExitError { - return fmt.Errorf("Script failed: %s: %s", command, strings.TrimSpace(stderrBuffer.String())) - } else { - return fmt.Errorf("Failed to execute script: %s: %s", command, err) - } - } - return nil -} - -func MatchesWildcard(dnsName string, pattern string) bool { - for len(pattern) > 0 { - if pattern[0] == '*' { - if len(dnsName) > 0 && dnsName[0] != '.' && MatchesWildcard(dnsName[1:], pattern) { - return true - } - pattern = pattern[1:] - } else { - if len(dnsName) == 0 || pattern[0] != dnsName[0] { - return false - } - pattern = pattern[1:] - dnsName = dnsName[1:] - } - } - return len(dnsName) == 0 -}