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, } }