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