mirror of
https://github.com/square/certigo.git
synced 2024-04-21 12:32:40 +00:00
Co-authored-by: John Wood <jwood@squareup.com> Co-authored-by: Sven Wick <sven.wick@gmx.de>
434 lines
14 KiB
Go
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
|
|
}
|