This repository has been archived by the owner on Sep 21, 2023. It is now read-only.
/
cert.go
159 lines (139 loc) · 4.07 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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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
}