From d69167a2b8dfa7889d8278ff702377c94cab41d5 Mon Sep 17 00:00:00 2001 From: Sarah Harvey Date: Mon, 19 Sep 2016 17:27:22 -0700 Subject: [PATCH] Resolve some comments --- cert_encoder.go | 301 +++++++++++++++++++++++++++++++++++++++++ display.go | 352 ++++++------------------------------------------ main.go | 8 +- verify.go | 40 +++--- 4 files changed, 368 insertions(+), 333 deletions(-) create mode 100644 cert_encoder.go diff --git a/cert_encoder.go b/cert_encoder.go new file mode 100644 index 0000000..da6ec09 --- /dev/null +++ b/cert_encoder.go @@ -0,0 +1,301 @@ +package main + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "net" + "strconv" + "strings" + "time" +) + +var keyUsageStrings = map[x509.KeyUsage]string{ + x509.KeyUsageDigitalSignature: "Digital Signature", + x509.KeyUsageContentCommitment: "Content Commitment", + x509.KeyUsageKeyEncipherment: "Key Encipherment", + x509.KeyUsageDataEncipherment: "Data Encipherment", + x509.KeyUsageKeyAgreement: "Key Agreement", + x509.KeyUsageCertSign: "Cert Sign", + x509.KeyUsageCRLSign: "CRL Sign", + x509.KeyUsageEncipherOnly: "Encipher Only", + x509.KeyUsageDecipherOnly: "Decipher Only", +} + +var extKeyUsageStrings = map[x509.ExtKeyUsage]string{ + x509.ExtKeyUsageAny: "Any", + x509.ExtKeyUsageServerAuth: "Server Auth", + x509.ExtKeyUsageClientAuth: "Client Auth", + x509.ExtKeyUsageCodeSigning: "Code Signing", + x509.ExtKeyUsageEmailProtection: "Email Protection", + x509.ExtKeyUsageIPSECEndSystem: "IPSEC End System", + x509.ExtKeyUsageIPSECTunnel: "IPSEC Tunnel", + x509.ExtKeyUsageIPSECUser: "IPSEC User", + x509.ExtKeyUsageTimeStamping: "Time Stamping", + x509.ExtKeyUsageOCSPSigning: "OCSP Signing", + x509.ExtKeyUsageMicrosoftServerGatedCrypto: "Microsoft ServerGatedCrypto", + x509.ExtKeyUsageNetscapeServerGatedCrypto: "Netscape ServerGatedCrypto", +} + +var algoName = [...]string{ + x509.MD2WithRSA: "MD2-RSA", + x509.MD5WithRSA: "MD5-RSA", + x509.SHA1WithRSA: "SHA1-RSA", + x509.SHA256WithRSA: "SHA256-RSA", + x509.SHA384WithRSA: "SHA384-RSA", + x509.SHA512WithRSA: "SHA512-RSA", + x509.DSAWithSHA1: "DSA-SHA1", + x509.DSAWithSHA256: "DSA-SHA256", + x509.ECDSAWithSHA1: "ECDSA-SHA1", + x509.ECDSAWithSHA256: "ECDSA-SHA256", + x509.ECDSAWithSHA384: "ECDSA-SHA384", + x509.ECDSAWithSHA512: "ECDSA-SHA512", +} + +type basicConstraints struct { + IsCA bool `json:"is_ca"` + MaxPathLen int `json:"pathlen"` +} + +type nameConstraints struct { + Critical bool `json:"critical"` + PermittedDNSDomains []string `json:"permitted_dns_domains,omitempty"` +} + +type simpleCertificate struct { + Alias string `json:"alias,omitempty"` + SerialNumber *big.Int `json:"serial"` + NotBefore time.Time `json:"not_before"` + NotAfter time.Time `json:"not_after"` + SignatureAlgorithm simpleSigAlg `json:"signature_algorithm"` + IsSelfSigned bool `json:"is_self_signed"` + Subject simplePkixName `json:"subject"` + Issuer simplePkixName `json:"issuer"` + BasicConstraints basicConstraints `json:"basic_constraints"` + NameConstraints nameConstraints `json:"name_constraints"` + KeyUsage simpleKeyUsage `json:"key_usage,omitempty"` + ExtKeyUsage []simpleExtKeyUsage `json:"extended_key_usage,omitempty"` + AltDNSNames []string `json:"alternate_dns_names,omitempty"` + AltIPAddresses []net.IP `json:"alternate_ip_addresses,omitempty"` + EmailAddresses []string `json:"email_addresses,omitempty"` + Warnings []string `json:"warnings,omitempty"` +} + +type simplePkixName struct { + Name pkix.Name + KeyId []byte +} + +type simpleKeyUsage x509.KeyUsage +type simpleExtKeyUsage x509.ExtKeyUsage + +type simpleSigAlg x509.SignatureAlgorithm + +type simpleResult struct { + Certificates []simpleCertificate `json:"certificates"` + VerifyResult *simpleVerification `json:"verify_result,omitempty"` +} + +func createSimpleCertificate(c certWithName) simpleCertificate { + out := simpleCertificate{ + Alias: c.name, + SerialNumber: c.cert.SerialNumber, + NotBefore: c.cert.NotBefore, + NotAfter: c.cert.NotAfter, + SignatureAlgorithm: simpleSigAlg(c.cert.SignatureAlgorithm), + IsSelfSigned: isSelfSigned(c.cert), + Subject: simplePkixName{ + Name: c.cert.Subject, + KeyId: c.cert.SubjectKeyId, + }, + Issuer: simplePkixName{ + Name: c.cert.Issuer, + KeyId: c.cert.AuthorityKeyId, + }, + BasicConstraints: basicConstraints{ + IsCA: c.cert.IsCA, + MaxPathLen: c.cert.MaxPathLen, + }, + NameConstraints: nameConstraints{ + Critical: c.cert.PermittedDNSDomainsCritical, + PermittedDNSDomains: c.cert.PermittedDNSDomains, + }, + KeyUsage: simpleKeyUsage(c.cert.KeyUsage), + AltDNSNames: c.cert.DNSNames, + AltIPAddresses: c.cert.IPAddresses, + EmailAddresses: c.cert.EmailAddresses, + Warnings: certWarnings(c.cert), + } + simpleEku := []simpleExtKeyUsage{} + for _, eku := range c.cert.ExtKeyUsage { + simpleEku = append(simpleEku, simpleExtKeyUsage(eku)) + } + out.ExtKeyUsage = simpleEku + + return out +} + +func (c certWithName) MarshalJSON() ([]byte, error) { + out := createSimpleCertificate(c) + return json.Marshal(out) +} + +func (p simplePkixName) MarshalJSON() ([]byte, error) { + out := map[string]interface{}{} + + if p.Name.CommonName != "" { + out["common_name"] = p.Name.CommonName + } + if len(p.Name.Organization) > 0 { + out["organization"] = p.Name.Organization + } + if len(p.Name.OrganizationalUnit) > 0 { + out["organization_unit"] = p.Name.OrganizationalUnit + } + if len(p.Name.Country) > 0 { + out["country"] = p.Name.Country + } + if len(p.Name.Locality) > 0 { + out["locality"] = p.Name.Locality + } + if len(p.KeyId) > 0 { + out["key_id"] = hexify(p.KeyId) + } + + return json.Marshal(out) +} + +func (k simpleKeyUsage) MarshalJSON() ([]byte, error) { + return json.Marshal(keyUsage(k)) +} + +func (e simpleExtKeyUsage) MarshalJSON() ([]byte, error) { + return json.Marshal(extKeyUsage(e)) +} + +func (s simpleSigAlg) MarshalJSON() ([]byte, error) { + return json.Marshal(algString(x509.SignatureAlgorithm(s))) +} + +// hexify returns a colon separated, hexadecimal representation +// of a given byte array. +func hexify(arr []byte) string { + var hexed bytes.Buffer + for i := 0; i < len(arr); i++ { + hexed.WriteString(strings.ToUpper(hex.EncodeToString(arr[i : i+1]))) + if i < len(arr)-1 { + hexed.WriteString(":") + } + } + return hexed.String() +} + +// keyUsage decodes/prints key usage from a certificate. +func keyUsage(sKu simpleKeyUsage) []string { + ku := x509.KeyUsage(sKu) + out := []string{} + for key, value := range keyUsageStrings { + if ku&key > 0 { + out = append(out, value) + } + } + return out +} + +// extKeyUsage decodes/prints extended key usage from a certificate. +func extKeyUsage(sEku simpleExtKeyUsage) string { + eku := x509.ExtKeyUsage(sEku) + val, ok := extKeyUsageStrings[eku] + if ok { + return val + } + return fmt.Sprintf("unknown:%d", eku) +} + +func algString(algo x509.SignatureAlgorithm) string { + if 0 < algo && int(algo) < len(algoName) { + return algoName[algo] + } + return strconv.Itoa(int(algo)) +} + +// decodeKey returns the algorithm and key size for a public key. +func decodeKey(publicKey interface{}) (string, int) { + switch publicKey.(type) { + case *dsa.PublicKey: + return "DSA", publicKey.(*dsa.PublicKey).P.BitLen() + case *ecdsa.PublicKey: + return "ECDSA", publicKey.(*ecdsa.PublicKey).Curve.Params().BitSize + case *rsa.PublicKey: + return "RSA", publicKey.(*rsa.PublicKey).N.BitLen() + default: + return "", 0 + } +} + +// certWarnings prints a list of warnings to show common mistakes in certs. +func certWarnings(cert *x509.Certificate) (warnings []string) { + if cert.SerialNumber.Sign() != 1 { + warnings = append(warnings, "Serial number in cert appears to be zero/negative") + } + + if cert.SerialNumber.BitLen() > 160 { + warnings = append(warnings, "Serial number too long; should be 20 bytes or less") + } + + if (cert.KeyUsage&x509.KeyUsageCertSign != 0) && !cert.IsCA { + warnings = append(warnings, "Key usage 'cert sign' is set, but is not a CA cert") + } + + if (cert.KeyUsage&x509.KeyUsageCertSign == 0) && cert.IsCA { + warnings = append(warnings, "Certificate is a CA cert, but key usage 'cert sign' missing") + } + + if cert.Version < 2 { + warnings = append(warnings, fmt.Sprintf("Certificate is not in X509v3 format (version is %d)", cert.Version+1)) + } + + if len(cert.UnhandledCriticalExtensions) > 0 { + warnings = append(warnings, "Certificate has unhandled critical extensions") + } + + warnings = append(warnings, algWarnings(cert)...) + + return +} + +// algWarnings checks key sizes, signature algorithms. +func algWarnings(cert *x509.Certificate) (warnings []string) { + alg, size := decodeKey(cert.PublicKey) + if (alg == "RSA" || alg == "DSA") && size < 2048 { + warnings = append(warnings, fmt.Sprintf("Size of %s key should be at least 2048 bits", alg)) + } + if alg == "ECDSA" && size < 224 { + warnings = append(warnings, fmt.Sprintf("Size of %s key should be at least 224 bits", alg)) + } + + for _, alg := range badSignatureAlgorithms { + if cert.SignatureAlgorithm == alg { + warnings = append(warnings, fmt.Sprintf("Using %s, which is an outdated signature algorithm", algString(alg))) + } + } + + if alg == "RSA" { + key := cert.PublicKey.(*rsa.PublicKey) + if key.E < 3 { + warnings = append(warnings, "Public key exponent in RSA key is less than 3") + } + if key.N.Sign() != 1 { + warnings = append(warnings, "Public key modulus in RSA key appears to be zero/negative") + } + } + + return +} diff --git a/display.go b/display.go index 1e49f52..8eec832 100644 --- a/display.go +++ b/display.go @@ -17,22 +17,13 @@ package main import ( - "bytes" - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" "crypto/x509" - "encoding/hex" "encoding/pem" "fmt" "os" - "strconv" - "strings" "text/template" "time" - "math/big" - "github.com/fatih/color" ) @@ -40,28 +31,28 @@ var layout = `{{if .Alias}}{{.Alias}} {{end}}Serial: {{.SerialNumber}} Not Before: {{.NotBefore | certStart}} Not After : {{.NotAfter | certEnd}} -Signature : {{.SignatureAlgorithm}} {{if .IsSelfSigned}}(self-signed){{end}} -Subject Info: {{if .Subject.CommonName}} - CommonName: {{.Subject.CommonName}}{{end}} {{if .Subject.Organization}} - Organization: {{.Subject.Organization}} {{end}} {{if .Subject.OrganizationalUnit}} - OrganizationalUnit: {{.Subject.OrganizationalUnit}} {{end}} {{if .Subject.Country}} - Country: {{.Subject.Country}} {{end}} {{if .Subject.Locality}} - Locality: {{.Subject.Locality}} {{end}} -Issuer Info: {{if .Issuer.CommonName}} - CommonName: {{.Issuer.CommonName}} {{end}} {{if .Issuer.Organization}} - Organization: {{.Issuer.Organization}} {{end}} {{if .Issuer.OrganizationalUnit}} - OrganizationalUnit: {{.Issuer.OrganizationalUnit}} {{end}} {{if .Issuer.Country}} - Country: {{.Issuer.Country}} {{end}} {{if .Issuer.Locality}} - Locality: {{.Issuer.Locality}} {{end}} {{if .Subject.KeyId}} -Subject Key ID : {{.Subject.KeyId}} {{end}} {{if .Issuer.KeyId}} -Authority Key ID : {{.Issuer.KeyId}} {{end}} {{if .BasicConstraints}} +Signature : {{.SignatureAlgorithm | highlightAlgorithm}} {{if .IsSelfSigned}}(self-signed){{end}} +Subject Info: {{if .Subject.Name.CommonName}} + CommonName: {{.Subject.Name.CommonName}}{{end}} {{if .Subject.Name.Organization}} + Organization: {{.Subject.Name.Organization}} {{end}} {{if .Subject.Name.OrganizationalUnit}} + OrganizationalUnit: {{.Subject.Name.OrganizationalUnit}} {{end}} {{if .Subject.Name.Country}} + Country: {{.Subject.Name.Country}} {{end}} {{if .Subject.Name.Locality}} + Locality: {{.Subject.Name.Locality}} {{end}} +Issuer Info: {{if .Issuer.Name.CommonName}} + CommonName: {{.Issuer.Name.CommonName}} {{end}} {{if .Issuer.Name.Organization}} + Organization: {{.Issuer.Name.Organization}} {{end}} {{if .Issuer.Name.OrganizationalUnit}} + OrganizationalUnit: {{.Issuer.Name.OrganizationalUnit}} {{end}} {{if .Issuer.Name.Country}} + Country: {{.Issuer.Name.Country}} {{end}} {{if .Issuer.Name.Locality}} + Locality: {{.Issuer.Name.Locality}} {{end}} {{if .Subject.KeyId}} +Subject Key ID : {{.Subject.KeyId | hexify}} {{end}} {{if .Issuer.KeyId}} +Authority Key ID : {{.Issuer.KeyId | hexify}} {{end}} {{if .BasicConstraints}} Basic Constraints: CA:{{.BasicConstraints.IsCA}}{{if ge .BasicConstraints.MaxPathLen 0}}, pathlen:{{.BasicConstraints.MaxPathLen}}{{end}} {{end}} {{if .NameConstraints.PermittedDNSDomains}} Name Constraints {{if .PermittedDNSDomains.Critical}}(critical){{end}}: {{range .NameConstraints.PermittedDNSDomains}} {{.}} {{end}} {{end}} {{if .KeyUsage}} -Key Usage: {{range .KeyUsage}} +Key Usage: {{range .KeyUsage | keyUsage}} {{.}} {{end}} {{end}} {{if .ExtKeyUsage}} Extended Key Usage: {{range .ExtKeyUsage}} - {{.}} {{end}} {{end}} {{if .AltDNSNames}} + {{. | extKeyUsage}} {{end}} {{end}} {{if .AltDNSNames}} Alternate DNS Names: {{range .AltDNSNames}} {{.}} {{end}} {{end}} {{if .AltIPAddresses}} Alternate IP Addresses: {{range .AltIPAddresses}} @@ -69,7 +60,7 @@ Alternate IP Addresses: {{range .AltIPAddresses}} Email Addresses: {{range .EmailAddresses}} {{.}} {{end}} {{end}} {{if .Warnings}} Warnings: {{range .Warnings}} - {{.}} {{end}} {{end}} + {{. | redify}} {{end}} {{end}} ` type certWithName struct { @@ -78,103 +69,7 @@ type certWithName struct { cert *x509.Certificate } -type dn struct { - CommonName string `json:"common_name"` - Organization []string `json:"organization"` - OrganizationalUnit []string `json:"organizational_unit"` - Country []string `json:"country"` - Locality []string `json:"locality"` - KeyId string `json:"key_id,omitempty"` -} - -type basicConstraints struct { - IsCA bool `json:"is_ca"` - MaxPathLen int `json:"pathlen"` -} - -type nameConstraints struct { - Critical bool `json:"critical"` - PermittedDNSDomains []string `json:"permitted_dns_domains"` -} - -type certBlob struct { - Alias string `json:"alias,omitempty"` - SerialNumber *big.Int `json:"serial"` - NotBefore int64 `json:"not_before"` - NotAfter int64 `json:"not_after"` - SignatureAlgorithm string `json:"signature_algorithm"` - IsSelfSigned bool `json:"is_self_signed"` - Subject dn `json:"subject"` - Issuer dn `json:"issuer"` - BasicConstraints basicConstraints `json:"basic_constraints"` - NameConstraints nameConstraints `json:"name_constraints"` - KeyUsage []string `json:"key_usage"` - ExtKeyUsage []string `json:"extended_key_usage"` - AltDNSNames []string `json:"alternate_dns_names,omitempty"` - AltIPAddresses []string `json:"alternate_ip_addresses,omitempty"` - EmailAddresses []string `json:"email_addresses,omitempty"` - Warnings []string `json:"warnings,omitempty"` - original *x509.Certificate -} - -type displayResult struct { - Certificates []certBlob `json:"certificates"` - VerifyResult *vResult `json:"verify_result,omitempty"` -} - -func createDisplayCert(cert certWithName) (dispCert certBlob) { - dispCert = certBlob{ - SerialNumber: cert.cert.SerialNumber, - NotBefore: cert.cert.NotBefore.Unix(), - NotAfter: cert.cert.NotAfter.Unix(), - SignatureAlgorithm: algString(cert.cert.SignatureAlgorithm), - IsSelfSigned: isSelfSigned(cert.cert), - Subject: dn{ - CommonName: cert.cert.Subject.CommonName, - Organization: cert.cert.Subject.Organization, - OrganizationalUnit: cert.cert.Subject.OrganizationalUnit, - Country: cert.cert.Subject.Country, - Locality: cert.cert.Subject.Locality, - KeyId: hexify(cert.cert.SubjectKeyId), - }, - Issuer: dn{ - CommonName: cert.cert.Issuer.CommonName, - Organization: cert.cert.Issuer.Organization, - OrganizationalUnit: cert.cert.Issuer.OrganizationalUnit, - Country: cert.cert.Issuer.Country, - Locality: cert.cert.Issuer.Locality, - KeyId: hexify(cert.cert.AuthorityKeyId), - }, - BasicConstraints: basicConstraints{ - IsCA: cert.cert.IsCA, - MaxPathLen: cert.cert.MaxPathLen, - }, - NameConstraints: nameConstraints{ - Critical: cert.cert.PermittedDNSDomainsCritical, - PermittedDNSDomains: cert.cert.PermittedDNSDomains, - }, - KeyUsage: keyUsage(cert.cert.KeyUsage), - ExtKeyUsage: []string{}, - AltDNSNames: cert.cert.DNSNames, - AltIPAddresses: []string{}, - EmailAddresses: cert.cert.EmailAddresses, - Warnings: certWarnings(cert.cert), - original: cert.cert, - } - if cert.name != "" { - dispCert.Alias = cert.name - } - for _, v := range cert.cert.ExtKeyUsage { - dispCert.ExtKeyUsage = append(dispCert.ExtKeyUsage, extKeyUsage(v)) - } - for _, v := range cert.cert.IPAddresses { - dispCert.AltIPAddresses = append(dispCert.AltIPAddresses, v.String()) - } - - return -} - -func createDisplayCertFromX509(block *pem.Block) certBlob { +func createSimpleCertificateFromX509(block *pem.Block) simpleCertificate { raw, err := x509.ParseCertificate(block.Bytes) if err != nil { fmt.Fprintf(os.Stderr, "error reading cert: %s", err) @@ -189,20 +84,22 @@ func createDisplayCertFromX509(block *pem.Block) certBlob { cert.file = val } - return createDisplayCert(cert) + return createSimpleCertificate(cert) } // displayCert takes in a parsed certificate object // (for jceks certs, blank otherwise), and prints out relevant // information. Start and end dates are colored based on whether or not // the certificate is expired, not expired, or close to expiring. -func displayCert(cert certBlob) { - cert.SignatureAlgorithm = highlightAlgorithm(cert.original.SignatureAlgorithm) - cert.Warnings = fmtCertWarnings(cert.original) - +func displayCert(cert simpleCertificate) { funcMap := template.FuncMap{ - "certStart": certStart, - "certEnd": certEnd, + "certStart": certStart, + "certEnd": certEnd, + "redify": redify, + "highlightAlgorithm": highlightAlgorithm, + "hexify": hexify, + "keyUsage": keyUsage, + "extKeyUsage": extKeyUsage, } t := template.New("Cert template").Funcs(funcMap) t, _ = t.Parse(layout) @@ -215,33 +112,6 @@ var ( red = color.New(color.Bold, color.FgRed) ) -var keyUsageStrings = map[x509.KeyUsage]string{ - x509.KeyUsageDigitalSignature: "Digital Signature", - x509.KeyUsageContentCommitment: "Content Commitment", - x509.KeyUsageKeyEncipherment: "Key Encipherment", - x509.KeyUsageDataEncipherment: "Data Encipherment", - x509.KeyUsageKeyAgreement: "Key Agreement", - x509.KeyUsageCertSign: "Cert Sign", - x509.KeyUsageCRLSign: "CRL Sign", - x509.KeyUsageEncipherOnly: "Encipher Only", - x509.KeyUsageDecipherOnly: "Decipher Only", -} - -var extKeyUsageStrings = map[x509.ExtKeyUsage]string{ - x509.ExtKeyUsageAny: "Any", - x509.ExtKeyUsageServerAuth: "Server Auth", - x509.ExtKeyUsageClientAuth: "Client Auth", - x509.ExtKeyUsageCodeSigning: "Code Signing", - x509.ExtKeyUsageEmailProtection: "Email Protection", - x509.ExtKeyUsageIPSECEndSystem: "IPSEC End System", - x509.ExtKeyUsageIPSECTunnel: "IPSEC Tunnel", - x509.ExtKeyUsageIPSECUser: "IPSEC User", - x509.ExtKeyUsageTimeStamping: "Time Stamping", - x509.ExtKeyUsageOCSPSigning: "OCSP Signing", - x509.ExtKeyUsageMicrosoftServerGatedCrypto: "Microsoft ServerGatedCrypto", - x509.ExtKeyUsageNetscapeServerGatedCrypto: "Netscape ServerGatedCrypto", -} - var algorithmColors = map[x509.SignatureAlgorithm]*color.Color{ x509.MD2WithRSA: red, x509.MD5WithRSA: red, @@ -257,31 +127,10 @@ var algorithmColors = map[x509.SignatureAlgorithm]*color.Color{ x509.ECDSAWithSHA512: green, } -var algoName = [...]string{ - x509.MD2WithRSA: "MD2-RSA", - x509.MD5WithRSA: "MD5-RSA", - x509.SHA1WithRSA: "SHA1-RSA", - x509.SHA256WithRSA: "SHA256-RSA", - x509.SHA384WithRSA: "SHA384-RSA", - x509.SHA512WithRSA: "SHA512-RSA", - x509.DSAWithSHA1: "DSA-SHA1", - x509.DSAWithSHA256: "DSA-SHA256", - x509.ECDSAWithSHA1: "ECDSA-SHA1", - x509.ECDSAWithSHA256: "ECDSA-SHA256", - x509.ECDSAWithSHA384: "ECDSA-SHA384", - x509.ECDSAWithSHA512: "ECDSA-SHA512", -} - -func algString(algo x509.SignatureAlgorithm) string { - if 0 < algo && int(algo) < len(algoName) { - return algoName[algo] - } - return strconv.Itoa(int(algo)) -} - // highlightAlgorithm changes the color of the signing algorithm // based on a set color map, e.g. to make SHA-1 show up red. -func highlightAlgorithm(sig x509.SignatureAlgorithm) string { +func highlightAlgorithm(sigAlg simpleSigAlg) string { + sig := x509.SignatureAlgorithm(sigAlg) color, ok := algorithmColors[sig] if !ok { return algString(sig) @@ -289,26 +138,6 @@ func highlightAlgorithm(sig x509.SignatureAlgorithm) string { return color.SprintFunc()(algString(sig)) } -// keyUsage decodes/prints key usage from a certificate. -func keyUsage(ku x509.KeyUsage) []string { - out := []string{} - for key, value := range keyUsageStrings { - if ku&key > 0 { - out = append(out, value) - } - } - return out -} - -// extKeyUsage decodes/prints extended key usage from a certificate. -func extKeyUsage(eku x509.ExtKeyUsage) string { - val, ok := extKeyUsageStrings[eku] - if ok { - return val - } - return fmt.Sprintf("unknown:%d", eku) -} - // certStart takes a given start time for the validity of // a certificate and returns that time colored properly // based on how close it is to expiry. If it's more than @@ -316,17 +145,16 @@ func extKeyUsage(eku x509.ExtKeyUsage) string { // be green. If it has been less than a day the string will // be yellow. If the certificate is not yet valid, the string // will be red. -func certStart(start int64) string { - startTime := time.Unix(start, 0) +func certStart(start time.Time) string { now := time.Now() day, _ := time.ParseDuration("24h") - threshold := startTime.Add(day) + threshold := start.Add(day) if now.After(threshold) { - return green.SprintfFunc()(startTime.String()) - } else if now.After(startTime) { - return yellow.SprintfFunc()(startTime.String()) + return green.SprintfFunc()(start.String()) + } else if now.After(start) { + return yellow.SprintfFunc()(start.String()) } else { - return red.SprintfFunc()(startTime.String()) + return red.SprintfFunc()(start.String()) } } @@ -337,33 +165,19 @@ func certStart(start int64) string { // green string. If the certificate is less than a month // from expiry it returns a yellow string. If the certificate // is expired it returns a red string. -func certEnd(end int64) string { - endTime := time.Unix(end, 0) +func certEnd(end time.Time) string { now := time.Now() month, _ := time.ParseDuration("720h") threshold := now.Add(month) - if threshold.Before(endTime) { - return green.SprintfFunc()(endTime.String()) - } else if now.Before(endTime) { - return yellow.SprintfFunc()(endTime.String()) + if threshold.Before(end) { + return green.SprintfFunc()(end.String()) + } else if now.Before(end) { + return yellow.SprintfFunc()(end.String()) } else { - return red.SprintfFunc()(endTime.String()) + return red.SprintfFunc()(end.String()) } } -// hexify returns a colon separated, hexadecimal representation -// of a given byte array. -func hexify(arr []byte) string { - var hexed bytes.Buffer - for i := 0; i < len(arr); i++ { - hexed.WriteString(strings.ToUpper(hex.EncodeToString(arr[i : i+1]))) - if i < len(arr)-1 { - hexed.WriteString(":") - } - } - return hexed.String() -} - var badSignatureAlgorithms = []x509.SignatureAlgorithm{ x509.MD2WithRSA, x509.MD5WithRSA, @@ -372,84 +186,6 @@ var badSignatureAlgorithms = []x509.SignatureAlgorithm{ x509.ECDSAWithSHA1, } -func fmtCertWarnings(cert *x509.Certificate) (warnings []string) { - unfmtWarnings := certWarnings(cert) - for _, v := range unfmtWarnings { - warnings = append(warnings, red.SprintfFunc()("%s", v)) - } - return -} - -// certWarnings prints a list of warnings to show common mistakes in certs. -func certWarnings(cert *x509.Certificate) (warnings []string) { - if cert.SerialNumber.Sign() != 1 { - warnings = append(warnings, "Serial number in cert appears to be zero/negative") - } - - if cert.SerialNumber.BitLen() > 160 { - warnings = append(warnings, "Serial number too long; should be 20 bytes or less") - } - - if (cert.KeyUsage&x509.KeyUsageCertSign != 0) && !cert.IsCA { - warnings = append(warnings, "Key usage 'cert sign' is set, but is not a CA cert") - } - - if (cert.KeyUsage&x509.KeyUsageCertSign == 0) && cert.IsCA { - warnings = append(warnings, "Certificate is a CA cert, but key usage 'cert sign' missing") - } - - if cert.Version < 2 { - warnings = append(warnings, fmt.Sprintf("Certificate is not in X509v3 format (version is %d)", cert.Version+1)) - } - - if len(cert.UnhandledCriticalExtensions) > 0 { - warnings = append(warnings, "Certificate has unhandled critical extensions") - } - - warnings = append(warnings, algWarnings(cert)...) - - return -} - -// algWarnings checks key sizes, signature algorithms. -func algWarnings(cert *x509.Certificate) (warnings []string) { - alg, size := decodeKey(cert.PublicKey) - if (alg == "RSA" || alg == "DSA") && size < 2048 { - warnings = append(warnings, fmt.Sprintf("Size of %s key should be at least 2048 bits", alg)) - } - if alg == "ECDSA" && size < 224 { - warnings = append(warnings, fmt.Sprintf("Size of %s key should be at least 224 bits", alg)) - } - - for _, alg := range badSignatureAlgorithms { - if cert.SignatureAlgorithm == alg { - warnings = append(warnings, fmt.Sprintf("Using %s, which is an outdated signature algorithm", algString(alg))) - } - } - - if alg == "RSA" { - key := cert.PublicKey.(*rsa.PublicKey) - if key.E < 3 { - warnings = append(warnings, "Public key exponent in RSA key is less than 3") - } - if key.N.Sign() != 1 { - warnings = append(warnings, "Public key modulus in RSA key appears to be zero/negative") - } - } - - return -} - -// decodeKey returns the algorithm and key size for a public key. -func decodeKey(publicKey interface{}) (string, int) { - switch publicKey.(type) { - case *dsa.PublicKey: - return "DSA", publicKey.(*dsa.PublicKey).P.BitLen() - case *ecdsa.PublicKey: - return "ECDSA", publicKey.(*ecdsa.PublicKey).Curve.Params().BitSize - case *rsa.PublicKey: - return "RSA", publicKey.(*rsa.PublicKey).N.BitLen() - default: - return "", 0 - } +func redify(text string) string { + return red.SprintfFunc()("%s", text) } diff --git a/main.go b/main.go index 4dec990..937cb0a 100644 --- a/main.go +++ b/main.go @@ -85,7 +85,7 @@ var fileExtToFormat = map[string]string{ func main() { app.Version("1.3.0") - result := displayResult{} + result := simpleResult{} switch kingpin.MustParse(app.Parse(os.Args[1:])) { case dump.FullCommand(): // Dump certificate files := inputFiles(*dumpFiles) @@ -104,7 +104,7 @@ func main() { switch block.Type { case "CERTIFICATE": - result.Certificates = append(result.Certificates, createDisplayCertFromX509(block)) + result.Certificates = append(result.Certificates, createSimpleCertificateFromX509(block)) case "PKCS7": certs, err := pkcs7.ExtractCertificates(block.Bytes) if err != nil { @@ -112,7 +112,7 @@ func main() { os.Exit(1) } for _, cert := range certs { - result.Certificates = append(result.Certificates, createDisplayCert(certWithName{cert: cert})) + result.Certificates = append(result.Certificates, createSimpleCertificate(certWithName{cert: cert})) } } }) @@ -143,7 +143,7 @@ func main() { if *connectPem { pem.Encode(os.Stdout, certToPem(cert, nil)) } else { - result.Certificates = append(result.Certificates, createDisplayCert(certWithName{cert: cert})) + result.Certificates = append(result.Certificates, createSimpleCertificate(certWithName{cert: cert})) } } diff --git a/verify.go b/verify.go index b349dbe..aadd3a3 100644 --- a/verify.go +++ b/verify.go @@ -23,20 +23,19 @@ import ( "os" ) -type vCert struct { - Name string `json:"name"` - IsSelfSigned bool `json:"is_self_signed"` - SignatureAlgorithm string `json:"signature_algorithm"` - original *x509.Certificate +type simpleVerifyCert struct { + Name string `json:"name"` + IsSelfSigned bool `json:"is_self_signed"` + SignatureAlgorithm simpleSigAlg `json:"signature_algorithm"` } -type vChain struct { - Certs []vCert `json:"chain"` +type simpleVerifyChain struct { + Certs []simpleVerifyCert `json:"chain"` } -type vResult struct { - Error string `json:"error,omitempty"` - Chains []vChain `json:"chains"` +type simpleVerification struct { + Error string `json:"error,omitempty"` + Chains []simpleVerifyChain `json:"chains"` } func caBundle(caPath string) *x509.CertPool { @@ -55,8 +54,8 @@ func caBundle(caPath string) *x509.CertPool { return bundle } -func verifyChain(certs []*x509.Certificate, dnsName, caPath string) vResult { - result := vResult{} +func verifyChain(certs []*x509.Certificate, dnsName, caPath string) simpleVerification { + result := simpleVerification{} intermediates := x509.NewCertPool() for i := 1; i < len(certs); i++ { @@ -77,17 +76,16 @@ func verifyChain(certs []*x509.Certificate, dnsName, caPath string) vResult { //green.Printf("Server certificates appear to be valid (found %d chains):\n", len(chains)) for _, chain := range chains { - aChain := vChain{} + aChain := simpleVerifyChain{} for _, cert := range chain { - aCert := vCert{} + aCert := simpleVerifyCert{} if cert.Subject.CommonName != "" { aCert.Name = cert.Subject.CommonName } else { aCert.Name = fmt.Sprintf("Serial #%s", cert.SerialNumber.String()) } aCert.IsSelfSigned = isSelfSigned(cert) - aCert.SignatureAlgorithm = algString(cert.SignatureAlgorithm) - aCert.original = cert + aCert.SignatureAlgorithm = simpleSigAlg(cert.SignatureAlgorithm) aChain.Certs = append(aChain.Certs, aCert) } result.Chains = append(result.Chains, aChain) @@ -95,13 +93,13 @@ func verifyChain(certs []*x509.Certificate, dnsName, caPath string) vResult { return result } -func fmtCert(cert vCert) string { +func fmtCert(cert simpleVerifyCert) string { name := cert.Name if cert.IsSelfSigned { name += green.SprintfFunc()(" [self-signed]") } for _, alg := range badSignatureAlgorithms { - if cert.original.SignatureAlgorithm == alg { + if x509.SignatureAlgorithm(cert.SignatureAlgorithm) == alg { name += red.SprintfFunc()(" [%s]", algString(alg)) break } @@ -109,19 +107,19 @@ func fmtCert(cert vCert) string { return name } -func printVerifyResult(result vResult) { +func printVerifyResult(result simpleVerification) { if result.Error != "" { red.Printf("Failed to verify certificate chain:\n") fmt.Printf("\t%s\n", result.Error) return } for i, chain := range result.Chains { - fmt.Printf("[%d] %s", i, fmtCert(chain.Certs[0])) + fmt.Printf("[%d] %s\n", i, fmtCert(chain.Certs[0])) for j, cert := range chain.Certs { if j == 0 { continue } - fmt.Printf("\n\t=> %s", fmtCert(cert)) + fmt.Printf("\t=> %s\n", fmtCert(cert)) } }