certdataDiffCCADB/ccadb/ccadb.go (179 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 ccadb
import (
"encoding/csv"
"encoding/json"
"errors"
"io"
"log"
"strings"
"time"
"github.com/mozilla/CCADB-Tools/certdataDiffCCADB/utils"
)
const (
URL = "https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV"
IntermediateReportURL = "https://ccadb.my.salesforce-sites.com/mozilla/PublicAllInterCertsIncTechConsWithPEMCSV"
RootReportURL = "http://ccadb.my.salesforce-sites.com/mozilla/PEMDataForRootCertsWithPEMCSV"
PEMInfo = "PEM Info"
SHA1Fingerprint = "SHA-1 Fingerprint"
SHA256Fingerprint = "SHA-256 Fingerprint"
CertificateID = "Certificate ID"
CertificateIssuerCommonName = "Certificate Issuer Common Name"
CertificateIssuerOrganization = "Certificate Issuer Organization"
CertificateIssuerOrganizationalUnit = "Certificate Issuer Organizational Unit"
PublicKeyAlgorithm = "Public Key Algorithm"
CertificateSerialNumber = "Certificate Serial Number"
SignatureHashAlgorithm = "Signature Hash Algorithm"
CertificateSubjectCommonName = "Certificate Subject Common Name"
CertificateSubjectOrganization = "Certificate Subject Organization"
CertificateSubjectOrganizationUnit = "Certificate Subject Organization Unit"
ValidFromGMT = "Valid From [GMT]"
ValidToGMT = "Valid To [GMT]"
CRLURLs = "CRL URL(s)"
ExtendedKeyUsage = "Extended Key Usage"
TechnicallyConstrained = "Technically Constrained"
CAOwner = "CA Owner"
RootCertificateName = "Root Certificate Name"
Subject = "Subject"
CIO = CertificateIssuerOrganization
CIOU = CertificateIssuerOrganizationalUnit
CN = "Common Name or Certificate Name"
CSN = CertificateSerialNumber
FP = SHA256Fingerprint
PEM = PEMInfo
TB = "Trust Bits"
TimeFMT = "2006 Jan 02"
TrustWeb = "Websites"
TrustEmail = "Email"
)
type Certificate struct {
columnMap map[string]int
row []string
lineNum int
}
func (c *Certificate) Set(key, value string) {
if i, ok := c.columnMap[key]; ok {
c.row[i] = value
} else {
c.row = append(c.row, value)
c.columnMap[key] = len(c.row) - 1
}
}
func (c *Certificate) Get(attr string) (string, bool) {
index, ok := c.columnMap[attr]
if !ok {
return "", false
}
return c.row[index], true
}
func (c *Certificate) Keys() []string {
keys := make([]string, len(c.columnMap))
i := 0
for k, _ := range c.columnMap {
keys[i] = k
i++
}
return keys
}
func (c *Certificate) GetOrPanic(attr string) string {
value, ok := c.Get(attr)
if !ok {
switch {
// Roots are trusted apriori, and thus do not have
// a CRL that can be authoritative about them. As such
// we don't panic on fields that roots don't have.
case attr == CRLURLs, attr == ExtendedKeyUsage, attr == TechnicallyConstrained:
// This is not a local design decision. This is what the CCADB presents
// in a query instead of a null value.
return "(not present)"
}
log.Panicf("Failed to retrieve attribute %v.\n Available attributes are: %v", attr, c.Keys())
}
return value
}
func (c *Certificate) ValidFromGMT() (time.Time, error) {
t, ok := c.Get(ValidFromGMT)
if !ok {
return time.Time{}, errors.New("ValidFromGMT not found.")
}
return time.Parse(TimeFMT, t)
}
func (c *Certificate) ValidToGMT() (time.Time, error) {
t, ok := c.Get(ValidToGMT)
if !ok {
return time.Time{}, errors.New("ValidToGMT not found.")
}
return time.Parse(TimeFMT, t)
}
func (c *Certificate) MarshalJSON() ([]byte, error) {
m := make(map[string]string)
for key, v := range c.columnMap {
m[key] = c.row[v]
}
return json.MarshalIndent(m, "", " ")
}
func NewCertificate(columnMap map[string]int, row []string, lineNum int) *Certificate {
p := row[columnMap["PEM Info"]]
row[columnMap["PEM Info"]] = p[1 : len(p)-1]
return &Certificate{columnMap, row, lineNum}
}
// NewEntry attempts to build a certdataDiffCCADb.Entry from a provided CCADB certificate.
func NewEntry(c *Certificate) *utils.Entry {
var cio string
var ciou string
var cn string
var csn string
var pem string
var fp string
var tb string
var tw bool
var te bool
var ok bool
if cio, ok = c.Get(CIO); !ok {
log.Printf("Failed to find column in CCADB, %v\n", CIO)
}
if ciou, ok = c.Get(CIOU); !ok {
log.Printf("Failed to find column in CCADB, %v\n", CIOU)
}
if cn, ok = c.Get(CN); !ok {
log.Printf("Failed to find column in CCADB, %v\n", CN)
}
if csn, ok = c.Get(CSN); !ok {
log.Printf("Failed to find column in CCADB, %v\n", CSN)
}
if pem, ok = c.Get(PEM); !ok {
log.Printf("Failed to find column in CCADB, %v\n", PEM)
}
if fp, ok = c.Get(FP); !ok {
log.Printf("Failed to find column in CCADB, %v\n", FP)
}
if tb, ok = c.Get(TB); !ok {
log.Printf("Failed to find column in CCADB, %v\n", TB)
}
tw, te = strings.Contains(tb, TrustWeb), strings.Contains(tb, TrustEmail)
return utils.NewEntry(cio, ciou, cn, csn, pem, fp, tw, te,
c.lineNum, "ccadb")
}
func ParseToNormalizedForm(stream io.Reader) ([]*utils.Entry, error) {
records, err := Parse(stream)
if err != nil {
return nil, err
}
entries := make([]*utils.Entry, len(records))
for i, record := range records {
entries[i] = NewEntry(record)
}
return entries, nil
}
func Parse(stream io.Reader) ([]*Certificate, error) {
records := make([]*Certificate, 0)
reader := csv.NewReader(stream)
columnMap := make(map[string]int)
columns, err := reader.Read()
if err != nil {
return records, err
}
// Extract column headers from the first row so we don't have to
// hardcode the column numbers
for index, attr := range columns {
columnMap[attr] = index
}
lineNum := 1
for row, err := reader.Read(); err == nil; row, err = reader.Read() {
lineNum += 1
records = append(records, NewCertificate(columnMap, row, lineNum))
lineNum += strings.Count(strings.Join(row, ""), "\n")
}
return records, nil
}