mirror of
https://github.com/square/certigo.git
synced 2026-10-21 06:45:42 +00:00
This commit adds an explicit `golangci-lint` configuration that simply matches the default linters previously in use, and then fixes all current linter findings. Aside from tweaking error message strings, this commit introduces one externally visible behavior change: PEM data generated from PKCS#12 files now correctly identifies private key blocks as `RSA PRIVATE KEY` or `EC PRIVATE KEY`, whereas previously it generated invalid `PRIVATE KEY` blocks with non-PKCS#8 data. This commit lays the groundwork for more aggressive linting in the future, and identifies some additional issues via TODOs.
417 lines
13 KiB
Go
417 lines
13 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/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)
|
|
|
|
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))
|
|
}
|
|
|
|
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) (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 {
|
|
l := lintRegistry.ByName(lintName)
|
|
lints = append(lints, fmt.Sprintf("%s: [%s] %s", strings.ToUpper(lintResult.Status.String()),
|
|
l.Source, l.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
|
|
}
|