File: bundle.go

package info (click to toggle)
golang-github-cloudflare-cfssl 1.2.0%2Bgit20160825.89.7fb22c8-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 4,916 kB
  • ctags: 2,827
  • sloc: sh: 146; sql: 62; python: 11; makefile: 8
file content (184 lines) | stat: -rw-r--r-- 4,999 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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
	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,
		"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++
	}
}