certinfo.go 5.6 KB

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