forked from coreos/etcd-ca
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cert.go
145 lines (126 loc) · 3.49 KB
/
cert.go
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
package pkix
import (
"bytes"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"time"
)
const (
certificatePEMBlockType = "CERTIFICATE"
)
type Certificate struct {
// derBytes is always set for valid Certificate
derBytes []byte
crt *x509.Certificate
}
// NewCertificateFromDER inits Certificate from DER-format bytes
func NewCertificateFromDER(derBytes []byte) *Certificate {
return &Certificate{derBytes: derBytes}
}
// NewCertificateFromPEM inits Certificate from PEM-format bytes
// data should contain at most one certificate
func NewCertificateFromPEM(data []byte) (c *Certificate, err error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
err = errors.New("cannot find the next PEM formatted block")
return
}
if pemBlock.Type != certificatePEMBlockType || len(pemBlock.Headers) != 0 {
err = errors.New("unmatched type or headers")
return
}
c = &Certificate{derBytes: pemBlock.Bytes}
return
}
// build crt field if needed
func (c *Certificate) buildX509Certificate() error {
if c.crt != nil {
return nil
}
crts, err := x509.ParseCertificates(c.derBytes)
if err != nil {
return err
}
if len(crts) != 1 {
return errors.New("unsupported multiple certificates in a block")
}
c.crt = crts[0]
return nil
}
// GetRawCertificate returns a copy of this certificate as an x509.Certificate
func (c *Certificate) GetRawCertificate() (*x509.Certificate, error) {
if err := c.buildX509Certificate(); err != nil {
return nil, err
}
return c.crt, nil
}
// GetExpirationDuration gets time duration before expiration
func (c *Certificate) GetExpirationDuration() time.Duration {
if err := c.buildX509Certificate(); err != nil {
return time.Unix(0, 0).Sub(time.Now())
}
return c.crt.NotAfter.Sub(time.Now())
}
// CheckAuthority checks the authority of certificate against itself.
// It only ensures that certificate is self-explanatory, and
// cannot promise the validity and security.
func (c *Certificate) CheckAuthority() error {
if err := c.buildX509Certificate(); err != nil {
return err
}
return c.crt.CheckSignatureFrom(c.crt)
}
// VerifyHost verifies the host certificate using host name.
// Only certificate of authority could call this function successfully.
// Current implementation allows one CA and direct hosts only,
// so the organization is always this:
// CA
// host1 host2 host3
func (c *Certificate) VerifyHost(hostCert *Certificate, name string) error {
if err := c.CheckAuthority(); err != nil {
return err
}
roots := x509.NewCertPool()
roots.AddCert(c.crt)
verifyOpts := x509.VerifyOptions{
DNSName: "",
// no intermediates are allowed for now
Intermediates: nil,
Roots: roots,
// if zero, the current time is used
CurrentTime: time.Now(),
// An empty list means ExtKeyUsageServerAuth.
KeyUsages: nil,
}
rawHostCrt, err := hostCert.GetRawCertificate()
if err != nil {
return err
}
units := rawHostCrt.Subject.OrganizationalUnit
if len(units) != 1 || units[0] != name {
return fmt.Errorf("unmatched hostname between %v and %v", units, name)
}
chains, err := rawHostCrt.Verify(verifyOpts)
if err != nil {
return err
}
if len(chains) != 1 {
return errors.New("internal error: verify chain number != 1")
}
return nil
}
// Export returns PEM-format bytes
func (c *Certificate) Export() ([]byte, error) {
pemBlock := &pem.Block{
Type: certificatePEMBlockType,
Headers: nil,
Bytes: c.derBytes,
}
buf := new(bytes.Buffer)
if err := pem.Encode(buf, pemBlock); err != nil {
return nil, err
}
return buf.Bytes(), nil
}