/
tls.go
182 lines (170 loc) · 5.74 KB
/
tls.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Package dane provides functionalities to use DNS-based Authentication of Named Entities
// aka DANE in standard go tls connections
package dane
import (
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/hex"
"fmt"
"github.com/miekg/dns"
"log"
"net"
)
// TLSA Certificate Usages Registry
const (
pkixTA = 0 // Certificate Authority Constraint
pkixEE = 1 // Service Certificate Constraint
daneTA = 2 // Trust Anchor Assertion
daneEE = 3 // Domain Issued Certificate
)
// TLSA Selectors
const (
certSelector = 0 // Full certificate
spkiSelector = 1 // SubjectPublicKeyInfo
)
// TLSA Matching Types
const (
fullMatch = 0 // No hash used
sha2_256 = 1 // 256 bit hash by SHA2
sha2_512 = 2 // 512 bit hash by SHA2
)
// hashCert create a hash from cert to verify it against a TLSA record
func hashCert(cert *x509.Certificate, selector uint8, hash uint8) (string, error) {
var input []byte
switch selector {
case certSelector:
input = cert.Raw
case spkiSelector:
input = cert.RawSubjectPublicKeyInfo
default:
return "", fmt.Errorf("invalid TLSA selector: %d", selector)
}
var output []byte
switch hash {
case fullMatch:
output = input
case sha2_256:
tmp := sha256.Sum256(input)
output = tmp[:]
case sha2_512:
tmp := sha512.Sum512(input)
output = tmp[:]
default:
return "", fmt.Errorf("unknown TLSA matching type: %d", hash)
}
return hex.EncodeToString(output), nil
}
// VerifyPeerCertificate is a custom tls validator which uses TLSA records
// to verify provided certificates.
// InsecureSkipVerify in tls.Config has to be set to true.
// "network" and "addr" from DialTLS or DialTLSContext are needed
// to find the matching TLSA record
func VerifyPeerCertificate(network string, addr string, rawCerts [][]byte, roots *x509.CertPool) error {
certs := make([]*x509.Certificate, len(rawCerts))
for i, asn1Data := range rawCerts {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return fmt.Errorf("failed to parse server certificate: %s", err.Error())
}
certs[i] = cert
}
// FIXME: use correct port, network
host, port, err := net.SplitHostPort(addr)
if err != nil {
return err
}
if err := certs[0].VerifyHostname(host); err != nil {
return err
}
tlsaRecords, err := resolver.GetTLSA(network, dns.Fqdn(host), port)
if err != nil {
return err
}
for _, tlsa := range tlsaRecords {
switch tlsa.Usage {
case pkixTA:
/*
tlsa certificate MUST be found in any of the PKIX certification paths
for the end entity certificate given by the server in TLS.
The presented certificate MUST pass PKIX certification path
validation, and a CA certificate that matches the TLSA record MUST
be included as part of a valid certification path
*/
var opts x509.VerifyOptions
opts.Roots = roots
opts.Intermediates = x509.NewCertPool()
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
chains, err := certs[0].Verify(opts)
if err != nil {
log.Printf("chain verification failed: %s\n", err.Error())
continue
}
for _, chain := range chains {
for _, cert := range chain[1:] {
hash, err := hashCert(cert, tlsa.Selector, tlsa.MatchingType)
if err != nil {
log.Printf("hash failed: %s\n", err.Error())
continue
}
if hash == tlsa.Certificate {
return nil
}
}
}
case pkixEE:
/*
The target certificate MUST pass PKIX certification path validation and MUST
match the TLSA record.
*/
var opts x509.VerifyOptions
opts.Roots = roots
_, err := certs[0].Verify(opts)
if err != nil {
continue
}
hash, err := hashCert(certs[0], tlsa.Selector, tlsa.MatchingType)
if err != nil {
continue
}
if hash == tlsa.Certificate {
return nil
}
case daneTA:
/*
The target certificate MUST pass PKIX certification path validation, with any
certificate matching the TLSA record considered to be a trust
anchor for this certification path validation.
*/
var opts x509.VerifyOptions
opts.Roots = roots
for _, cert := range certs[1:] {
hash, err := hashCert(certs[0], tlsa.Selector, tlsa.MatchingType)
if err == nil && hash == tlsa.Certificate {
opts.Roots.AddCert(cert)
}
}
_, err := certs[0].Verify(opts)
if err == nil {
return nil
}
case daneEE:
/*
The target certificate MUST match the TLSA record.
PKIX validation is not tested for certificate usage 3.
*/
hash, err := hashCert(certs[0], tlsa.Selector, tlsa.MatchingType)
if err != nil {
continue
}
if hash == tlsa.Certificate {
return nil
}
default:
log.Printf("invalid tlsa usage: %d\n", tlsa.Usage)
}
}
return fmt.Errorf("no valid certification found")
}