Files
eea7ad61c3 Add zlint support (#286)
Co-authored-by: John Wood <jwood@squareup.com>
Co-authored-by: Sven Wick <sven.wick@gmx.de>
2022-08-01 12:01:38 -06:00

434 lines
14 KiB
Go

/*-
* Copyright 2016 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package lib
import (
"bytes"
"crypto/dsa"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"log"
"net"
"sort"
"strconv"
"strings"
"sync"
"time"
zx509 "github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
)
var keyUsages = []x509.KeyUsage{
x509.KeyUsageDigitalSignature,
x509.KeyUsageContentCommitment,
x509.KeyUsageKeyEncipherment,
x509.KeyUsageDataEncipherment,
x509.KeyUsageKeyAgreement,
x509.KeyUsageCertSign,
x509.KeyUsageCRLSign,
x509.KeyUsageEncipherOnly,
x509.KeyUsageDecipherOnly,
}
var signatureSchemeStrings = map[tls.SignatureScheme]string{
// As per RFC 5246 (TLS v1.2), the handshake contains a set of
// SignatureAndHashAlgorithm values which is a tuple of a hash
// and a signature algorithm each. Go takes these values and
// maps them into a tls.SignatureScheme value, where the upper
// 8 bits are hash and lower 8 bits are the signature.
//
// TLS v1.3 changes this to use the signature_algorithms extension,
// see draft-ietf-tls-tls13-18 section 4.2.3. These are 16-bit
// values that explicitly specify a signature algorithm.
//
// cf. RFC 5246, Section A.4.1
// cf. draft-ietf-tls-tls13-18, Section 4.2.3
// cf. RFC 5758, Section 2
//
// See also ssl/ssl_locl.h in OpenSSL (grep for TLSEXT_SIGALG).
//
// Common values:
// --
// Signatures:
// 0x0 anonymous
// 0x1 RSA
// 0x2 DSA
// 0x3 ECDSA
// --
// Hashes:
// 0x0 none
// 0x1 MD-5
// 0x2 SHA-1
// 0x3 SHA-224
// 0x4 SHA-256
// 0x5 SHA-384
// 0x6 SHA-512
// --
// TLS v1.3:
// 0x0807 ED25519
// 0x0808 ED448
// 0xFE00-0xFFFF Private use
// --
tls.PKCS1WithSHA1: "RSA-PKCS1 with SHA1",
tls.PKCS1WithSHA256: "RSA-PKCS1 with SHA256",
tls.PKCS1WithSHA384: "RSA-PKCS1 with SHA384",
tls.PKCS1WithSHA512: "RSA-PKCS1 with SHA512",
tls.PSSWithSHA256: "RSA-PSS with SHA256",
tls.PSSWithSHA384: "RSA-PSS with SHA384",
tls.PSSWithSHA512: "RSA-PSS with SHA512",
tls.ECDSAWithP256AndSHA256: "ECDSA with P256 and SHA256",
tls.ECDSAWithP384AndSHA384: "ECDSA with P384 and SHA384",
tls.ECDSAWithP521AndSHA512: "ECDSA with P521 and SHA512",
// Not from stdlib
// Defined in TLS 1.3 draft
0x807: "ED25519",
0x808: "ED448",
// Not in stdlib: server sent {sha1,ecdsa} or {sha224,ecdsa}.
0x203: "ECDSA with SHA1",
0x303: "ECDSA with SHA224",
// Unused (?) but theorically possible combos (per RFC 5246)
0x101: "RSA-PKCS1 with MD5",
0x301: "RSA-PKCS1 with SHA224",
0x102: "DSA with MD5",
0x202: "DSA with SHA1",
0x302: "DSA with SHA224",
0x402: "DSA with SHA256",
0x502: "DSA with SHA384",
0x602: "DSA with SHA512",
// Funky stuff supported by OpenSSL
0xeeee: "GOST 34.10-2012 (256)",
0xefef: "GOST 34.10-2012 (512)",
0xeded: "GOST 34.10-2001",
}
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",
x509.PureEd25519: "ED25519",
}
type basicConstraints struct {
IsCA bool `json:"is_ca"`
MaxPathLen *int `json:"pathlen,omitempty"`
}
type nameConstraints struct {
Critical bool `json:"critical,omitempty"`
PermittedDNSDomains []string `json:"permitted_dns_domains,omitempty"`
ExcludedDNSDomains []string `json:"excluded_dns_domains,omitempty"`
PermittedIPRanges []*net.IPNet `json:"permitted_ip_ranges,omitempty"`
ExcludedIPRanges []*net.IPNet `json:"excluded_ip_ranges,omitempty"`
PermittedEmailAddresses []string `json:"permitted_email_addresses,omitempty"`
ExcludedEmailAddresses []string `json:"excluded_email_addresses,omitempty"`
PermittedURIDomains []string `json:"permitted_uri_domains,omitempty"`
ExcludedURIDomains []string `json:"excluded_uri_domains,omitempty"`
}
// simpleCertificate is a JSON-representable certificate metadata holder.
type simpleCertificate struct {
Alias string `json:"alias,omitempty"`
SerialNumber string `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,omitempty"`
NameConstraints *nameConstraints `json:"name_constraints,omitempty"`
OCSPServer []string `json:"ocsp_server,omitempty"`
IssuingCertificateURL []string `json:"issuing_certificate,omitempty"`
KeyUsage simpleKeyUsage `json:"key_usage,omitempty"`
ExtKeyUsage []simpleExtKeyUsage `json:"extended_key_usage,omitempty"`
AltDNSNames []string `json:"dns_names,omitempty"`
AltIPAddresses []net.IP `json:"ip_addresses,omitempty"`
URINames []string `json:"uri_names,omitempty"`
EmailAddresses []string `json:"email_addresses,omitempty"`
SCTList []*simpleSCT `json:"sct_list,omitempty"`
Warnings []string `json:"lints,omitempty"`
PEM string `json:"pem,omitempty"`
// Internal fields for text display. Set - to skip serialize.
Width int `json:"-"`
}
type simpleSCT struct {
Version uint64 `json:"version"`
LogOperator string `json:"log_operator,omitempty"`
LogURL string `json:"log_url,omitempty"`
LogID []byte `json:"log_id"`
Timestamp time.Time `json:"timestamp"`
SignatureAlgorithm simpleSigAlg `json:"signature_algorithm"`
}
type simplePKIXName struct {
Name pkix.Name
KeyID []byte
}
type simpleKeyUsage x509.KeyUsage
type simpleExtKeyUsage x509.ExtKeyUsage
type simpleSigAlg x509.SignatureAlgorithm
func createSimpleCertificate(name string, cert *x509.Certificate) simpleCertificate {
out := simpleCertificate{
Alias: name,
SerialNumber: cert.SerialNumber.String(),
NotBefore: cert.NotBefore,
NotAfter: cert.NotAfter,
SignatureAlgorithm: simpleSigAlg(cert.SignatureAlgorithm),
IsSelfSigned: IsSelfSigned(cert),
Subject: simplePKIXName{
Name: cert.Subject,
KeyID: cert.SubjectKeyId,
},
Issuer: simplePKIXName{
Name: cert.Issuer,
KeyID: cert.AuthorityKeyId,
},
KeyUsage: simpleKeyUsage(cert.KeyUsage),
OCSPServer: cert.OCSPServer,
IssuingCertificateURL: cert.IssuingCertificateURL,
AltDNSNames: cert.DNSNames,
AltIPAddresses: cert.IPAddresses,
EmailAddresses: cert.EmailAddresses,
SCTList: parseSCTList(cert),
PEM: string(pem.EncodeToMemory(EncodeX509ToPEM(cert, nil))),
}
for _, uri := range cert.URIs {
out.URINames = append(out.URINames, uri.String())
}
out.Warnings = certLints(cert, out.URINames)
if cert.BasicConstraintsValid {
out.BasicConstraints = &basicConstraints{
IsCA: cert.IsCA,
}
if cert.MaxPathLen > 0 || cert.MaxPathLenZero {
out.BasicConstraints.MaxPathLen = &cert.MaxPathLen
}
}
if len(cert.PermittedDNSDomains) > 0 || len(cert.ExcludedDNSDomains) > 0 ||
len(cert.PermittedIPRanges) > 0 || len(cert.ExcludedIPRanges) > 0 ||
len(cert.PermittedEmailAddresses) > 0 || len(cert.ExcludedEmailAddresses) > 0 ||
len(cert.PermittedURIDomains) > 0 || len(cert.ExcludedURIDomains) > 0 {
out.NameConstraints = &nameConstraints{
Critical: cert.PermittedDNSDomainsCritical,
PermittedDNSDomains: cert.PermittedDNSDomains,
ExcludedDNSDomains: cert.ExcludedDNSDomains,
PermittedIPRanges: cert.PermittedIPRanges,
ExcludedIPRanges: cert.ExcludedIPRanges,
PermittedEmailAddresses: cert.PermittedEmailAddresses,
ExcludedEmailAddresses: cert.ExcludedEmailAddresses,
PermittedURIDomains: cert.PermittedURIDomains,
ExcludedURIDomains: cert.ExcludedURIDomains,
}
}
simpleEku := []simpleExtKeyUsage{}
for _, eku := range cert.ExtKeyUsage {
simpleEku = append(simpleEku, simpleExtKeyUsage(eku))
}
out.ExtKeyUsage = simpleEku
return out
}
func (p simplePKIXName) MarshalJSON() ([]byte, error) {
out := map[string]interface{}{}
for _, rdn := range p.Name.Names {
oid := describeOid(rdn.Type)
if prev, ok := out[oid.Slug]; oid.Multiple && ok {
l := prev.([]interface{})
out[oid.Slug] = append(l, rdn.Value)
} else if oid.Multiple {
out[oid.Slug] = []interface{}{rdn.Value}
} else {
out[oid.Slug] = rdn.Value
}
}
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 := range keyUsages {
if ku&key > 0 {
out = append(out, keyUsageStrings[key])
}
}
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 pk := publicKey.(type) {
case *dsa.PublicKey:
return "DSA", pk.P.BitLen()
case *ecdsa.PublicKey:
return "ECDSA", pk.Curve.Params().BitSize
case *rsa.PublicKey:
return "RSA", pk.N.BitLen()
default:
return "", 0
}
}
var lintRegistryOnce sync.Once
var lintRegistry lint.Registry
// certLints prints a list of lints to show common mistakes in certs.
func certLints(cert *x509.Certificate, uriNames []string) (lints []string) {
parsed, err := zx509.ParseCertificate(cert.Raw)
if err != nil {
lints = append(lints, fmt.Sprintf("Failed to parse certificate: %v", err))
return
}
lintRegistryOnce.Do(func() {
registry, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
IncludeSources: []lint.LintSource{lint.RFC5280, lint.Community},
})
if err != nil {
log.Fatalf("Failed to filter lint registry: %v", err)
}
lintRegistry = registry
})
zLints := zlint.LintCertificateEx(parsed, lintRegistry)
for lintName, lintResult := range zLints.Results {
if lintResult.Status >= lint.Warn {
lint := lintRegistry.ByName(lintName)
lints = append(lints, fmt.Sprintf("%s: [%s] %s", strings.ToUpper(lintResult.Status.String()),
lint.Source, lint.Description))
}
}
sort.Strings(lints)
return
}
// IsSelfSigned returns true iff the given certificate has a valid self-signature.
func IsSelfSigned(cert *x509.Certificate) bool {
return cert.CheckSignatureFrom(cert) == nil
}