1package certinfo 2 3import ( 4 "crypto/tls" 5 "crypto/x509" 6 "crypto/x509/pkix" 7 "errors" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "strings" 12 "time" 13 14 "github.com/cloudflare/cfssl/certdb" 15 "github.com/cloudflare/cfssl/helpers" 16) 17 18// Certificate represents a JSON description of an X.509 certificate. 19type Certificate struct { 20 Subject Name `json:"subject,omitempty"` 21 Issuer Name `json:"issuer,omitempty"` 22 SerialNumber string `json:"serial_number,omitempty"` 23 SANs []string `json:"sans,omitempty"` 24 NotBefore time.Time `json:"not_before"` 25 NotAfter time.Time `json:"not_after"` 26 SignatureAlgorithm string `json:"sigalg"` 27 AKI string `json:"authority_key_id"` 28 SKI string `json:"subject_key_id"` 29 RawPEM string `json:"pem"` 30} 31 32// Name represents a JSON description of a PKIX Name 33type Name struct { 34 CommonName string `json:"common_name,omitempty"` 35 SerialNumber string `json:"serial_number,omitempty"` 36 Country string `json:"country,omitempty"` 37 Organization string `json:"organization,omitempty"` 38 OrganizationalUnit string `json:"organizational_unit,omitempty"` 39 Locality string `json:"locality,omitempty"` 40 Province string `json:"province,omitempty"` 41 StreetAddress string `json:"street_address,omitempty"` 42 PostalCode string `json:"postal_code,omitempty"` 43 Names []interface{} `json:"names,omitempty"` 44 //ExtraNames []interface{} `json:"extra_names,omitempty"` 45} 46 47// ParseName parses a new name from a *pkix.Name 48func ParseName(name pkix.Name) Name { 49 n := Name{ 50 CommonName: name.CommonName, 51 SerialNumber: name.SerialNumber, 52 Country: strings.Join(name.Country, ","), 53 Organization: strings.Join(name.Organization, ","), 54 OrganizationalUnit: strings.Join(name.OrganizationalUnit, ","), 55 Locality: strings.Join(name.Locality, ","), 56 Province: strings.Join(name.Province, ","), 57 StreetAddress: strings.Join(name.StreetAddress, ","), 58 PostalCode: strings.Join(name.PostalCode, ","), 59 } 60 61 for i := range name.Names { 62 n.Names = append(n.Names, name.Names[i].Value) 63 } 64 65 // ExtraNames aren't supported in Go 1.4 66 // for i := range name.ExtraNames { 67 // n.ExtraNames = append(n.ExtraNames, name.ExtraNames[i].Value) 68 // } 69 70 return n 71} 72 73func formatKeyID(id []byte) string { 74 var s string 75 76 for i, c := range id { 77 if i > 0 { 78 s += ":" 79 } 80 s += fmt.Sprintf("%02X", c) 81 } 82 83 return s 84} 85 86// ParseCertificate parses an x509 certificate. 87func ParseCertificate(cert *x509.Certificate) *Certificate { 88 c := &Certificate{ 89 RawPEM: string(helpers.EncodeCertificatePEM(cert)), 90 SignatureAlgorithm: helpers.SignatureString(cert.SignatureAlgorithm), 91 NotBefore: cert.NotBefore, 92 NotAfter: cert.NotAfter, 93 Subject: ParseName(cert.Subject), 94 Issuer: ParseName(cert.Issuer), 95 SANs: cert.DNSNames, 96 AKI: formatKeyID(cert.AuthorityKeyId), 97 SKI: formatKeyID(cert.SubjectKeyId), 98 SerialNumber: cert.SerialNumber.String(), 99 } 100 for _, ip := range cert.IPAddresses { 101 c.SANs = append(c.SANs, ip.String()) 102 } 103 return c 104} 105 106// ParseCertificateFile parses x509 certificate file. 107func ParseCertificateFile(certFile string) (*Certificate, error) { 108 certPEM, err := ioutil.ReadFile(certFile) 109 if err != nil { 110 return nil, err 111 } 112 113 return ParseCertificatePEM(certPEM) 114} 115 116// ParseCertificatePEM parses an x509 certificate PEM. 117func ParseCertificatePEM(certPEM []byte) (*Certificate, error) { 118 cert, err := helpers.ParseCertificatePEM(certPEM) 119 if err != nil { 120 return nil, err 121 } 122 123 return ParseCertificate(cert), nil 124} 125 126// ParseCSRPEM uses the helper to parse an x509 CSR PEM. 127func ParseCSRPEM(csrPEM []byte) (*x509.CertificateRequest, error) { 128 csrObject, err := helpers.ParseCSRPEM(csrPEM) 129 if err != nil { 130 return nil, err 131 } 132 133 return csrObject, nil 134} 135 136// ParseCSRFile uses the helper to parse an x509 CSR PEM file. 137func ParseCSRFile(csrFile string) (*x509.CertificateRequest, error) { 138 csrPEM, err := ioutil.ReadFile(csrFile) 139 if err != nil { 140 return nil, err 141 } 142 143 return ParseCSRPEM(csrPEM) 144} 145 146// ParseCertificateDomain parses the certificate served by the given domain. 147func ParseCertificateDomain(domain string) (cert *Certificate, err error) { 148 var host, port string 149 if host, port, err = net.SplitHostPort(domain); err != nil { 150 host = domain 151 port = "443" 152 } 153 154 var conn *tls.Conn 155 conn, err = tls.DialWithDialer(&net.Dialer{Timeout: 10 * time.Second}, "tcp", net.JoinHostPort(host, port), &tls.Config{InsecureSkipVerify: true}) 156 if err != nil { 157 return 158 } 159 defer conn.Close() 160 161 if len(conn.ConnectionState().PeerCertificates) == 0 { 162 return nil, errors.New("received no server certificates") 163 } 164 165 cert = ParseCertificate(conn.ConnectionState().PeerCertificates[0]) 166 return 167} 168 169// ParseSerialNumber parses the serial number and does a lookup in the data 170// storage used for certificates. The authority key is required for the lookup 171// to work and must be passed as a hex string. 172func ParseSerialNumber(serial, aki string, dbAccessor certdb.Accessor) (*Certificate, error) { 173 normalizedAKI := strings.ToLower(aki) 174 normalizedAKI = strings.Replace(normalizedAKI, ":", "", -1) 175 176 certificates, err := dbAccessor.GetCertificate(serial, normalizedAKI) 177 if err != nil { 178 return nil, err 179 } 180 181 if len(certificates) < 1 { 182 return nil, errors.New("no certificate found") 183 } 184 185 if len(certificates) > 1 { 186 return nil, errors.New("more than one certificate found") 187 } 188 189 return ParseCertificatePEM([]byte(certificates[0].PEM)) 190} 191