123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- package bundler
- import (
- "bytes"
- "crypto/ecdsa"
- "crypto/rsa"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/json"
- "encoding/pem"
- "errors"
- "fmt"
- "time"
- "github.com/cloudflare/cfssl/helpers"
- )
- // A Bundle contains a certificate and its trust chain. It is intended
- // to store the most widely applicable chain, with shortness an
- // explicit goal.
- type Bundle struct {
- Chain []*x509.Certificate
- Cert *x509.Certificate
- Root *x509.Certificate
- Key interface{}
- Issuer *pkix.Name
- Subject *pkix.Name
- Expires time.Time
- LeafExpires time.Time
- Hostnames []string
- Status *BundleStatus
- }
- // BundleStatus is designated for various status reporting.
- type BundleStatus struct {
- // A flag on whether a new bundle is generated
- IsRebundled bool `json:"rebundled"`
- // A list of SKIs of expiring certificates
- ExpiringSKIs []string `json:"expiring_SKIs"`
- // A list of untrusted root store names
- Untrusted []string `json:"untrusted_root_stores"`
- // A list of human readable warning messages based on the bundle status.
- Messages []string `json:"messages"`
- // A status code consists of binary flags
- Code int `json:"code"`
- }
- type chain []*x509.Certificate
- func (c chain) MarshalJSON() ([]byte, error) {
- var buf bytes.Buffer
- for _, cert := range c {
- buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
- }
- ret := bytes.TrimSpace(buf.Bytes())
- return json.Marshal(string(ret))
- }
- // PemBlockToString turns a pem.Block into the string encoded form.
- func PemBlockToString(block *pem.Block) string {
- if block.Bytes == nil || block.Type == "" {
- return ""
- }
- return string(bytes.TrimSpace(pem.EncodeToMemory(block)))
- }
- var typeToName = map[int]string{
- 3: "CommonName",
- 5: "SerialNumber",
- 6: "Country",
- 7: "Locality",
- 8: "Province",
- 9: "StreetAddress",
- 10: "Organization",
- 11: "OrganizationalUnit",
- 17: "PostalCode",
- }
- type names []pkix.AttributeTypeAndValue
- func (n names) MarshalJSON() ([]byte, error) {
- var buf bytes.Buffer
- for _, name := range n {
- buf.WriteString(fmt.Sprintf("/%s=%s", typeToName[name.Type[3]], name.Value))
- }
- return json.Marshal(buf.String())
- }
- // MarshalJSON serialises the bundle to JSON. The resulting JSON
- // structure contains the bundle (as a sequence of PEM-encoded
- // certificates), the certificate, the private key, the size of they
- // key, the issuer(s), the subject name(s), the expiration, the
- // hostname(s), the OCSP server, and the signature on the certificate.
- func (b *Bundle) MarshalJSON() ([]byte, error) {
- if b == nil || b.Cert == nil {
- return nil, errors.New("no certificate in bundle")
- }
- var keyBytes, rootBytes []byte
- var keyLength int
- var keyType, keyString string
- keyLength = helpers.KeyLength(b.Cert.PublicKey)
- switch b.Cert.PublicKeyAlgorithm {
- case x509.ECDSA:
- keyType = fmt.Sprintf("%d-bit ECDSA", keyLength)
- case x509.RSA:
- keyType = fmt.Sprintf("%d-bit RSA", keyLength)
- case x509.DSA:
- keyType = "DSA"
- default:
- keyType = "Unknown"
- }
- switch key := b.Key.(type) {
- case *rsa.PrivateKey:
- keyBytes = x509.MarshalPKCS1PrivateKey(key)
- keyString = PemBlockToString(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
- case *ecdsa.PrivateKey:
- keyBytes, _ = x509.MarshalECPrivateKey(key)
- keyString = PemBlockToString(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes})
- case fmt.Stringer:
- keyString = key.String()
- }
- if len(b.Hostnames) == 0 {
- b.buildHostnames()
- }
- var ocspSupport = false
- if b.Cert.OCSPServer != nil {
- ocspSupport = true
- }
- var crlSupport = false
- if b.Cert.CRLDistributionPoints != nil {
- crlSupport = true
- }
- if b.Root != nil {
- rootBytes = b.Root.Raw
- }
- return json.Marshal(map[string]interface{}{
- "bundle": chain(b.Chain),
- "root": PemBlockToString(&pem.Block{Type: "CERTIFICATE", Bytes: rootBytes}),
- "crt": PemBlockToString(&pem.Block{Type: "CERTIFICATE", Bytes: b.Cert.Raw}),
- "key": keyString,
- "key_type": keyType,
- "key_size": keyLength,
- "issuer": names(b.Issuer.Names),
- "subject": names(b.Subject.Names),
- "expires": b.Expires,
- "leaf_expires": b.LeafExpires,
- "hostnames": b.Hostnames,
- "ocsp_support": ocspSupport,
- "crl_support": crlSupport,
- "ocsp": b.Cert.OCSPServer,
- "signature": helpers.SignatureString(b.Cert.SignatureAlgorithm),
- "status": b.Status,
- })
- }
- // buildHostnames sets bundle.Hostnames by the x509 cert's subject CN and DNS names
- // Since the subject CN may overlap with one of the DNS names, it needs to handle
- // the duplication by a set.
- func (b *Bundle) buildHostnames() {
- if b.Cert == nil {
- return
- }
- // hset keeps a set of unique hostnames.
- hset := make(map[string]bool)
- // insert CN into hset
- if b.Cert.Subject.CommonName != "" {
- hset[b.Cert.Subject.CommonName] = true
- }
- // insert all DNS names into hset
- for _, h := range b.Cert.DNSNames {
- hset[h] = true
- }
- // convert hset to an array of hostnames
- b.Hostnames = make([]string, len(hset))
- i := 0
- for h := range hset {
- b.Hostnames[i] = h
- i++
- }
- }
|