Skip to content

Commit

Permalink
MINOR: add more information fields to ssl_certificate
Browse files Browse the repository at this point in the history
New fields: Algorithm, AuthorityKeyID, Serial, Sha1FingerPrint,
Sha256FingerPrint, Subject, SubjectAlternativeNames, SubjectKeyID
  • Loading branch information
aiharos authored and oliwer committed Dec 12, 2023
1 parent 523b5fa commit b9d3318
Show file tree
Hide file tree
Showing 13 changed files with 586 additions and 92 deletions.
24 changes: 24 additions & 0 deletions models/ssl_certificate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions models/ssl_certificate_compare.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions models/ssl_certificate_compare_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions specification/build/haproxy_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8825,6 +8825,10 @@ definitions:
ssl_certificate:
description: A file containing one or more SSL/TLS certificates and keys
properties:
algorithm:
type: string
authority_key_id:
type: string
description:
type: string
domains:
Expand Down Expand Up @@ -8853,13 +8857,25 @@ definitions:
type: string
x-go-custom-tag: gorm:"type:timestamp with time zone"
x-nullable: true
serial:
type: string
sha1_finger_print:
type: string
sha256_finger_print:
type: string
size:
description: File size in bytes.
readOnly: true
type: integer
x-nullable: true
storage_name:
type: string
subject:
type: string
subject_alternative_names:
type: string
subject_key_id:
type: string
title: SSL File
type: object
ssl_certificates:
Expand Down
24 changes: 20 additions & 4 deletions specification/models/storage/ssl_certificate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,43 @@ ssl_certificate:
readOnly: true
x-nullable: true
description: File size in bytes.
not_after:
serial:
type: string
not_before:
type: string
format: date-time
readOnly: true
x-nullable: true
x-go-custom-tag: gorm:"type:timestamp with time zone"
not_before:
not_after:
type: string
format: date-time
readOnly: true
x-nullable: true
x-go-custom-tag: gorm:"type:timestamp with time zone"
issuers:
algorithm:
type: string
sha1_finger_print:
type: string
sha256_finger_print:
type: string
domains:
type: string
readOnly: true
x-omitempty: true
domains:
issuers:
type: string
readOnly: true
x-omitempty: true
ip_addresses:
type: string
readOnly: true
x-omitempty: true
authority_key_id:
type: string
subject:
type: string
subject_alternative_names:
type: string
subject_key_id:
type: string
90 changes: 83 additions & 7 deletions storage/cert-info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,42 @@
package storage

import (
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
"time"
)

// Information about stored certificates to be returned by the API.
type CertificatesInfo struct {
NotAfter, NotBefore *time.Time
DNS, IPs, Issuers string
NotAfter, NotBefore *time.Time
DNS, IPs, Issuers string
AuthorityKeyID string
SubjectKeyID string
Serial string
Algorithm string
Sha1FingerPrint string
Sha256FingerPrint string
Subject string
SubjectAlternativeNames string
}

// Private struct to store unique info about multiple certificates.
type certsInfo struct {
NotAfter, NotBefore time.Time
DNS, IPs, Issuers map[string]struct{}
NotAfter, NotBefore time.Time
DNS, IPs, Issuers map[string]struct{}
AuthorityKeyID string
SubjectKeyID string
Serial string
Algorithm string
Sha1FingerPrint string
Sha256FingerPrint string
Subject string
SubjectAlternativeNames []string
Certs []*x509.Certificate
}

func newCertsInfo() *certsInfo {
Expand Down Expand Up @@ -59,6 +79,22 @@ func ParseCertificatesInfo(bundle []byte) (*CertificatesInfo, error) {
bundle = rest
}

crt, err := findLeafCertificate(ci.Certs)
if err == nil {
// Format the keys as OpenSSL does: hex digits in uppercase, colon-separated
ci.AuthorityKeyID = formatFingerprint(crt.AuthorityKeyId)
ci.SubjectKeyID = formatFingerprint(crt.SubjectKeyId)
ci.Serial = crt.SerialNumber.String()
ci.Algorithm = crt.SignatureAlgorithm.String()
// Format the fingerprint as OpenSSL does: hex digits in uppercase, colon-separated
fingerPrint := sha1.Sum(crt.Raw) //nolint:gosec
ci.Sha1FingerPrint = formatFingerprint(fingerPrint[:])
fingerPrint256 := sha256.Sum256(crt.Raw)
ci.Sha256FingerPrint = formatFingerprint(fingerPrint256[:])
ci.Subject = crt.Subject.CommonName
ci.SubjectAlternativeNames = crt.DNSNames
}

return ci.toCertificatesInfo(), nil
}

Expand All @@ -70,6 +106,8 @@ func (ci *certsInfo) parseCertificate(der []byte) error {
return err
}

ci.Certs = append(ci.Certs, crt)

// Only keep the earliest expiration date.
if ci.NotAfter.IsZero() || crt.NotAfter.Before(ci.NotAfter) {
ci.NotAfter = crt.NotAfter
Expand All @@ -95,12 +133,50 @@ func (ci *certsInfo) parseCertificate(der []byte) error {
return nil
}

// formatFingerprint formats a byte array as: hex digits in uppercase, colon-separated
func formatFingerprint(fingerprint []byte) string {
parts := make([]string, len(fingerprint))
for i, b := range fingerprint {
parts[i] = fmt.Sprintf("%02X", b)
}
return strings.Join(parts, ":")
}

// findLeafCertificate returns the first leaf certificate in the chain.
func findLeafCertificate(certs []*x509.Certificate) (*x509.Certificate, error) {
if len(certs) == 0 {
return nil, fmt.Errorf("empty certificate chain")
}
// Create a map to check if a certificate is someone else's issuer
isIssuer := make(map[string]bool)
for _, cert := range certs {
isIssuer[cert.Issuer.String()] = true
}

// Find the starting certificate (a certificate whose issuer is not in the list)
for _, cert := range certs {
if !cert.IsCA && cert.Subject.CommonName != "" && !isIssuer[cert.Subject.String()] {
return cert, nil
}
}

return nil, fmt.Errorf("no leaf certificate found")
}

// Transform a dirty *certsInfo into a clean *CertificatesInfo for the API.
func (ci *certsInfo) toCertificatesInfo() *CertificatesInfo {
csi := &CertificatesInfo{
DNS: strings.Join(mapKeys(ci.DNS), ", "),
IPs: strings.Join(mapKeys(ci.IPs), ", "),
Issuers: strings.Join(mapKeys(ci.Issuers), ", "),
DNS: strings.Join(mapKeys(ci.DNS), ", "),
IPs: strings.Join(mapKeys(ci.IPs), ", "),
Issuers: strings.Join(mapKeys(ci.Issuers), ", "),
AuthorityKeyID: ci.AuthorityKeyID,
SubjectKeyID: ci.SubjectKeyID,
Serial: ci.Serial,
Algorithm: ci.Algorithm,
Sha1FingerPrint: ci.Sha1FingerPrint,
Sha256FingerPrint: ci.Sha256FingerPrint,
Subject: ci.Subject,
SubjectAlternativeNames: strings.Join(ci.SubjectAlternativeNames, ", "),
}
if !ci.NotAfter.IsZero() {
csi.NotAfter = &ci.NotAfter
Expand Down
Loading

0 comments on commit b9d3318

Please sign in to comment.