func WriteCerts(out io.Writer, certs CertList, matcher CertMatcher) {
	for _, cert := range certs {
		if !matcher(cert) {
			log.Printf("Skipping certificate %s", cert.Label)
			continue
		}
		io.WriteString(out, "\n")
		io.WriteString(out, "# Issuer: "+cert.Issuer+"\n")
		io.WriteString(out, "# Subject: "+cert.Subject+"\n")
		io.WriteString(out, "# Label: "+cert.Label+"\n")
		io.WriteString(out, "# Serial: "+cert.Serial+"\n")
		io.WriteString(out, "# MD5 Fingerprint: "+cert.MD5Fingerprint+"\n")
		io.WriteString(out, "# SHA1 Fingerprint: "+cert.SHA1Fingerprint+"\n")
		io.WriteString(out, "# SHA256 Fingerprint: "+cert.SHA256Fingerprint+"\n")
		pem.Encode(out, cert.PEMBlock)
	}
}
WriteCerts writes certificates out if they match a specific filter criteria.
package certs
import (
	"bufio"
	"bytes"
	"crypto"
	_ "crypto/md5"
	"crypto/sha1"
	_ "crypto/sha256"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"flag"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)
type Object struct {
	attrs        map[string]Attribute
	startingLine int  
}
type Attribute struct {
	attrType string
	value    []byte
}
type Certificate struct {
	Issuer            string
	Subject           string
	Label             string
	Serial            string
	MD5Fingerprint    string
	SHA1Fingerprint   string
	SHA256Fingerprint string
	PEMBlock          *pem.Block
}
type CertList []*Certificate
type CertMatcher func(*Certificate) bool
var (
	ignoreList map[string]string
	includedUntrustedFlag = flag.Bool("include-untrusted", false, "If set, untrusted certificates will also be included in the output")
	ignoreListFilename    = flag.String("ignore-list", "", "File containing a list of certificates to ignore")
)
func parseIgnoreList(ignoreListFile io.Reader) {
	in := bufio.NewReader(ignoreListFile)
	var lineNo int
	for line, eof := getLine(in, &lineNo); !eof; line, eof = getLine(in, &lineNo) {
		if split := strings.SplitN(line, "#", 2); len(split) == 2 {
			ignoreList[strings.TrimSpace(split[0])] = strings.TrimSpace(split[1])
		} else {
			ignoreList[line] = ""
		}
	}
}
func ParseInput(inFile io.Reader) (license, cvsId string, objects []*Object) {
	in := bufio.NewReader(inFile)
	var lineNo int
	for line, eof := getLine(in, &lineNo); !eof; line, eof = getLine(in, &lineNo) {
		if strings.Contains(line, "This Source Code") {
			license += line
			license += "\n"
			break
		}
	}
	if len(license) == 0 {
		log.Fatalf("Read whole input and failed to find beginning of license")
	}
	for line, eof := getLine(in, &lineNo); !eof; line, eof = getLine(in, &lineNo) {
		if strings.Contains(line, "CVS_ID") || len(line) == 0 {
			break
		}
		license += line
		license += "\n"
	}
	var currentObject *Object
	var beginData bool
	for line, eof := getLine(in, &lineNo); !eof; line, eof = getLine(in, &lineNo) {
		if len(line) == 0 || line[0] == '#' {
			continue
		}
		if strings.HasPrefix(line, "CVS_ID ") {
			cvsId = line[7:]
			continue
		}
		if line == "BEGINDATA" {
			beginData = true
			continue
		}
		words := strings.Fields(line)
		var value []byte
		if len(words) == 2 && words[1] == "MULTILINE_OCTAL" {
			startingLine := lineNo
			var ok bool
			value, ok = readMultilineOctal(in, &lineNo)
			if !ok {
				log.Fatalf("Failed to read octal value starting at line %d", startingLine)
			}
		} else if len(words) < 3 {
			log.Fatalf("Expected three or more values on line %d, but found %d", lineNo, len(words))
		} else {
			value = []byte(strings.Join(words[2:], " "))
		}
		if words[0] == "CKA_CLASS" {
			if currentObject != nil {
				objects = append(objects, currentObject)
			}
			currentObject = new(Object)
			currentObject.attrs = make(map[string]Attribute)
			currentObject.startingLine = lineNo
		}
		if currentObject == nil {
			log.Fatalf("Found attribute on line %d which appears to be outside of an object", lineNo)
		}
		currentObject.attrs[words[0]] = Attribute{
			attrType: words[1],
			value:    value,
		}
	}
	if !beginData {
		log.Fatalf("Read whole input and failed to find BEGINDATA")
	}
	if currentObject != nil {
		objects = append(objects, currentObject)
	}
	return
}
func OutputAllLabels(certs CertList) (labels []string) {
	for _, cert := range certs {
		escapedLabel := unescapeLabel(strings.Trim(cert.Label, "\""))
		labels = append(labels, escapedLabel)
	}
	return
}
func OutputTrustedCerts(objects []*Object) (parsedCerts CertList) {
	certs := filterObjectsByClass(objects, "CKO_CERTIFICATE")
	trusts := filterObjectsByClass(objects, "CKO_NSS_TRUST")
	parsedCerts = make(CertList, 0)
	for _, cert := range certs {
		derBytes := cert.attrs["CKA_VALUE"].value
		hash := sha1.New()
		hash.Write(derBytes)
		digest := hash.Sum(nil)
		label := string(cert.attrs["CKA_LABEL"].value)
		x509, err := x509.ParseCertificate(derBytes)
		if err != nil {
			log.Printf("Failed to parse certificate starting on line %d: %s", cert.startingLine, err)
			continue
		}
		var trust *Object
		for _, possibleTrust := range trusts {
			if bytes.Equal(digest, possibleTrust.attrs["CKA_CERT_SHA1_HASH"].value) {
				trust = possibleTrust
				break
			}
		}
		if trust == nil {
			log.Fatalf("No trust found for certificate object starting on line %d (sha1: %x)", cert.startingLine, digest)
		}
		trustType := trust.attrs["CKA_TRUST_SERVER_AUTH"].value
		if len(trustType) == 0 {
			log.Fatalf("No CKA_TRUST_SERVER_AUTH found in trust starting at line %d", trust.startingLine)
		}
		var trusted bool
		switch string(trustType) {
		case "CKT_NSS_NOT_TRUSTED":
			trusted = false
		case "CKT_NSS_TRUSTED_DELEGATOR":
			trusted = true
		case "CKT_NSS_TRUST_UNKNOWN", "CKT_NSS_MUST_VERIFY_TRUST":
			trusted = false
		default:
			log.Fatalf("Unknown trust value '%s' found for trust record starting on line %d", trustType, trust.startingLine)
		}
		if !trusted {
			continue
		}
		distrustAfter := cert.attrs["CKA_NSS_SERVER_DISTRUST_AFTER"]
		if distrustAfter.attrType == "CK_BBOOL" {
			if string(distrustAfter.value) != "CK_FALSE" {
				log.Fatalf("Unknown distrust after value '%s' found for certificate on line %d", distrustAfter.value, cert.startingLine)
			}
		} else if distrustAfter.attrType == "MULTILINE_OCTAL" {
			layout := "060102150405Z"
			t, err := time.Parse(layout, string(distrustAfter.value))
			if err != nil {
				log.Fatalf("Unable to parse distrust after value '%s' for certificate on line %d, error %s", distrustAfter.value, cert.startingLine, err)
			}
			if t.After(time.Now()) {
				continue
			}
		} else if distrustAfter.attrType == "" {
			log.Printf("Certificate on line %d missing distrustAfter field", cert.startingLine)
		} else {
			log.Fatalf("Unknown type '%s' for distrustAfter for certificate on line %d", distrustAfter.attrType, cert.startingLine)
		}
		block := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}
		parsedCert := &Certificate{
			nameToString(x509.Issuer),
			nameToString(x509.Subject),
			label,
			x509.SerialNumber.String(),
			fingerprintString(crypto.MD5, x509.Raw),
			fingerprintString(crypto.SHA1, x509.Raw),
			fingerprintString(crypto.SHA256, x509.Raw),
			block,
		}
		parsedCerts = append(parsedCerts, parsedCert)
	}
	return
}
func WhitelistMatcher(whitelist map[string]interface{}) CertMatcher {
	return func(c *Certificate) bool {
		escapedLabel := unescapeLabel(strings.Trim(c.Label, "\""))
		if _, present := whitelist[escapedLabel]; present {
			return true
		} else {
			return false
		}
	}
}
func BlacklistMatcher(blacklist map[string]interface{}) CertMatcher {
	return func(c *Certificate) bool {
		escapedLabel := unescapeLabel(strings.Trim(c.Label, "\""))
		if _, present := blacklist[escapedLabel]; present {
			return false
		} else {
			return true
		}
	}
}
func SubstringWhitelistMatcher(whitelist []string) CertMatcher {
	internal_whitelist := make([]string, len(whitelist))
	for i, val := range whitelist {
		internal_whitelist[i] = strings.ToLower(val)
	}
	return func(c *Certificate) bool {
		escapedLabel := unescapeLabel(strings.Trim(c.Label, "\""))
		normalisedLabel := strings.ToLower(escapedLabel)
		for _, label := range internal_whitelist {
			if strings.Contains(normalisedLabel, label) {
				return true
			}
		}
		return false
	}
}
func SubstringBlacklistMatcher(blacklist []string) CertMatcher {
	internal_blacklist := make([]string, len(blacklist))
	for i, val := range blacklist {
		internal_blacklist[i] = strings.ToLower(val)
	}
	return func(c *Certificate) bool {
		escapedLabel := unescapeLabel(strings.Trim(c.Label, "\""))
		normalisedLabel := strings.ToLower(escapedLabel)
		for _, label := range internal_blacklist {
			if strings.Contains(normalisedLabel, label) {
				return false
			}
		}
		return true
	}
}