284 lines
6.2 KiB
Go
284 lines
6.2 KiB
Go
|
package inspect
|
||
|
|
||
|
import (
|
||
|
"crypto/rsa"
|
||
|
"crypto/x509"
|
||
|
"errors"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// DERCertificate attempts to parse a DER-form X.509 certificate, and returns
|
||
|
// information about it.
|
||
|
func DERCertificateInfo(loc string, der []byte) Info {
|
||
|
c, err := x509.ParseCertificate(der)
|
||
|
if err != nil {
|
||
|
return &BadInfo{
|
||
|
Typ: TypeX509Certificate,
|
||
|
Loc: loc,
|
||
|
Underlying: err,
|
||
|
}
|
||
|
}
|
||
|
return CertificateInfo(loc, c)
|
||
|
}
|
||
|
|
||
|
// CertificateInfo returns structured information about the given certificate.
|
||
|
// It can return BadInfo if the certificate is not for an RSA key. Note that
|
||
|
// the returned Info structure holds a reference to the passed certificate.
|
||
|
func CertificateInfo(loc string, cert *x509.Certificate) Info {
|
||
|
pubkey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||
|
if !ok {
|
||
|
return &BadInfo{
|
||
|
Typ: TypeX509Certificate,
|
||
|
Loc: loc,
|
||
|
Underlying: errors.New("certificate is not for an RSA key"),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
c := &Certificate{
|
||
|
Loc: loc,
|
||
|
Key: PublicKeyInfo(loc, pubkey),
|
||
|
Cert: cert,
|
||
|
}
|
||
|
|
||
|
if (cert.KeyUsage & x509.KeyUsageDigitalSignature) != 0 {
|
||
|
c.ValidUses = append(c.ValidUses, "DigitalSignature")
|
||
|
}
|
||
|
if (cert.KeyUsage & x509.KeyUsageCertSign) != 0 {
|
||
|
c.ValidUses = append(c.ValidUses, "CertSign")
|
||
|
}
|
||
|
if (cert.KeyUsage & x509.KeyUsageCRLSign) != 0 {
|
||
|
c.ValidUses = append(c.ValidUses, "CRLSign")
|
||
|
}
|
||
|
for _, use := range cert.ExtKeyUsage {
|
||
|
switch use {
|
||
|
case x509.ExtKeyUsageAny:
|
||
|
c.ValidUses = append(c.ValidUses, "any")
|
||
|
case x509.ExtKeyUsageServerAuth:
|
||
|
c.ValidUses = append(c.ValidUses, "ServerAuth")
|
||
|
case x509.ExtKeyUsageClientAuth:
|
||
|
c.ValidUses = append(c.ValidUses, "ClientAuth")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := cert.CheckSignatureFrom(cert); err == nil {
|
||
|
c.IsSelfSigned = true
|
||
|
}
|
||
|
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type Certificate struct {
|
||
|
// Loc is the location the certificate was encountered.
|
||
|
Loc string
|
||
|
|
||
|
// Key holds information about the public key of the certificate.
|
||
|
Key *PublicKey
|
||
|
|
||
|
// Cert is the original certificate.
|
||
|
Cert *x509.Certificate
|
||
|
|
||
|
// The following section contains any computed values that are not
|
||
|
// immediately obtainable from the certificate.
|
||
|
|
||
|
// ValidUses is a set of named certificate uses.
|
||
|
ValidUses []string
|
||
|
|
||
|
// IsSelfSigned reports whether the certificate is self-signed.
|
||
|
IsSelfSigned bool
|
||
|
}
|
||
|
|
||
|
// Type indicates this is an X.509 certificate.
|
||
|
func (c *Certificate) Type() Type {
|
||
|
return TypeX509Certificate
|
||
|
}
|
||
|
|
||
|
// Location returns the location at which the certificate was encountered.
|
||
|
func (c *Certificate) Location() string {
|
||
|
return c.Loc
|
||
|
}
|
||
|
|
||
|
// Info returns structured information about the X.509 Certificate.
|
||
|
func (c *Certificate) Info() []Section {
|
||
|
var s []Section
|
||
|
s = append(s, c.UsageSection())
|
||
|
s = append(s, c.ValiditySection())
|
||
|
s = append(s, c.SubjectSection())
|
||
|
s = append(s, c.IssuerSection())
|
||
|
if c.Cert.IsCA {
|
||
|
s = append(s, c.CASection())
|
||
|
}
|
||
|
s = append(s, c.Key.PublicKeyInfoSection())
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// UsageSection returns information regarding the key usage bits.
|
||
|
func (c *Certificate) UsageSection() Section {
|
||
|
return Section{
|
||
|
Title: "Certificate usage",
|
||
|
Fields: []Field{
|
||
|
Field{
|
||
|
Key: "Is CA",
|
||
|
Value: c.Cert.IsCA,
|
||
|
},
|
||
|
Field{
|
||
|
Key: "Self-signed",
|
||
|
Value: c.IsSelfSigned,
|
||
|
},
|
||
|
Field{
|
||
|
Key: "Valid uses",
|
||
|
Value: strings.Join(c.ValidUses, " | "),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ValiditySection returns information regarding the validity of the certificate.
|
||
|
func (c *Certificate) ValiditySection() Section {
|
||
|
return Section{
|
||
|
Title: "Validity",
|
||
|
Fields: []Field{
|
||
|
Field{
|
||
|
Key: "Valid from",
|
||
|
Value: c.Cert.NotBefore,
|
||
|
},
|
||
|
Field{
|
||
|
Key: "Valid until",
|
||
|
Value: c.Cert.NotAfter,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SubjectSection returns information about the Subject of the certificate.
|
||
|
func (c *Certificate) SubjectSection() Section {
|
||
|
f := []Field{
|
||
|
Field{
|
||
|
Key: "Description",
|
||
|
Value: c.Cert.Subject.CommonName,
|
||
|
},
|
||
|
}
|
||
|
f = appendX509DNField(f, c.Cert.Subject)
|
||
|
if len(c.Cert.DNSNames) > 0 {
|
||
|
f = append(f, Field{
|
||
|
Key: "Hostnames",
|
||
|
Value: c.Cert.DNSNames,
|
||
|
})
|
||
|
}
|
||
|
if len(c.Cert.IPAddresses) > 0 {
|
||
|
var ips []string
|
||
|
for _, ip := range c.Cert.IPAddresses {
|
||
|
ips = append(ips, ip.String())
|
||
|
}
|
||
|
f = append(f, Field{
|
||
|
Key: "IP addresses",
|
||
|
Value: ips,
|
||
|
})
|
||
|
}
|
||
|
if len(c.Cert.SubjectKeyId) > 0 {
|
||
|
f = append(f, Field{
|
||
|
Key: "Key ID",
|
||
|
Value: FormatHexBytes(c.Cert.SubjectKeyId),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return Section{
|
||
|
Title: "Subject",
|
||
|
Fields: f,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// IssuerSection returns information about the Issuer of the certificate.
|
||
|
func (c *Certificate) IssuerSection() Section {
|
||
|
f := []Field{
|
||
|
Field{
|
||
|
Key: "Serial",
|
||
|
Value: FormatHexBytes(c.Cert.SerialNumber.Bytes()),
|
||
|
},
|
||
|
}
|
||
|
if c.Cert.Issuer.CommonName != "" {
|
||
|
f = append(f, Field{
|
||
|
Key: "Description",
|
||
|
Value: c.Cert.Issuer.CommonName,
|
||
|
})
|
||
|
}
|
||
|
f = appendX509DNField(f, c.Cert.Issuer)
|
||
|
if len(c.Cert.AuthorityKeyId) > 0 {
|
||
|
f = append(f, Field{
|
||
|
Key: "Key ID",
|
||
|
Value: FormatHexBytes(c.Cert.AuthorityKeyId),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return Section{
|
||
|
Title: "Issuer",
|
||
|
Fields: f,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CASection returns information about a CA certificate.
|
||
|
func (c *Certificate) CASection() Section {
|
||
|
var f []Field
|
||
|
|
||
|
switch {
|
||
|
case c.Cert.MaxPathLen < 0,
|
||
|
c.Cert.MaxPathLen == 0 && !c.Cert.MaxPathLenZero:
|
||
|
f = append(f, Field{
|
||
|
Key: "Max path len",
|
||
|
Value: "unlimited",
|
||
|
})
|
||
|
default:
|
||
|
f = append(f, Field{
|
||
|
Key: "Max path len",
|
||
|
Value: c.Cert.MaxPathLen,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if len(c.Cert.PermittedDNSDomains) > 0 {
|
||
|
f = append(f, Field{
|
||
|
Key: "Permitted DNS names",
|
||
|
Value: c.Cert.PermittedDNSDomains,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if len(c.Cert.ExcludedDNSDomains) > 0 {
|
||
|
f = append(f, Field{
|
||
|
Key: "Excluded DNS names",
|
||
|
Value: c.Cert.ExcludedDNSDomains,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if len(c.Cert.PermittedIPRanges) > 0 {
|
||
|
var ips []string
|
||
|
for _, ip := range c.Cert.PermittedIPRanges {
|
||
|
ips = append(ips, ip.String())
|
||
|
}
|
||
|
f = append(f, Field{
|
||
|
Key: "Permitted IP addresses",
|
||
|
Value: ips,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if len(c.Cert.ExcludedIPRanges) > 0 {
|
||
|
var ips []string
|
||
|
for _, ip := range c.Cert.ExcludedIPRanges {
|
||
|
ips = append(ips, ip.String())
|
||
|
}
|
||
|
f = append(f, Field{
|
||
|
Key: "Excluded IP addresses",
|
||
|
Value: ips,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if len(c.Cert.CRLDistributionPoints) > 0 {
|
||
|
f = append(f, Field{
|
||
|
Key: "CRL distribution points",
|
||
|
Value: c.Cert.CRLDistributionPoints,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return Section{
|
||
|
Title: "Certificate authority",
|
||
|
Fields: f,
|
||
|
}
|
||
|
}
|