-
Notifications
You must be signed in to change notification settings - Fork 4
/
keyless.go
138 lines (116 loc) · 3.06 KB
/
keyless.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
package keyless
import (
"bytes"
"crypto"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func GetCertificate(apiURL string, mTLS ...tls.Certificate) func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
apiURL = strings.TrimSuffix(apiURL, "/")
var client *http.Client
if len(mTLS) == 0 {
client = http.DefaultClient
} else {
client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
IdleConnTimeout: 10 * time.Minute,
TLSClientConfig: &tls.Config{
Certificates: mTLS,
},
},
Timeout: 5 * time.Second,
}
}
return func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
// require SNI
if info.ServerName == "" {
return nil, errors.New("fetching certificate: missing server name")
}
// fetch certificate
res, err := client.Get(apiURL + "/certificate?" + url.QueryEscape(info.ServerName))
if err != nil {
return nil, fmt.Errorf("fetching certificate: %w", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("fetching certificate: %s", res.Status)
}
data, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("fetching certificate: %w", err)
}
// decode certificate
var cert tls.Certificate
for {
var block *pem.Block
block, data = pem.Decode(data)
if block == nil {
break
}
if block.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, block.Bytes)
}
}
if len(cert.Certificate) == 0 {
return nil, errors.New("fetching certificate: no certificates returned")
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("fetching certificate: %w", err)
}
der, err := x509.MarshalPKIXPublicKey(cert.Leaf.PublicKey)
if err != nil {
return nil, fmt.Errorf("fetching certificate: %w", err)
}
hash := sha256.Sum256(der)
cert.PrivateKey = signer{
pub: cert.Leaf.PublicKey,
id: base64.RawURLEncoding.EncodeToString(hash[:]),
api: apiURL,
client: client,
}
if err := info.SupportsCertificate(&cert); err != nil {
return nil, fmt.Errorf("fetching certificate: %w", err)
}
return &cert, nil
}
}
var _ crypto.Signer = signer{}
type signer struct {
pub crypto.PublicKey
id string
api string
client *http.Client
}
func (s signer) Public() crypto.PublicKey {
return s.pub
}
func (s signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
hash := opts.HashFunc().String()
res, err := s.client.Post(
s.api+"/sign?key="+url.QueryEscape(s.id)+"&hash="+url.QueryEscape(hash),
"application/octet-stream", bytes.NewReader(digest))
if err != nil {
return nil, fmt.Errorf("signing digest: %w", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("signing digest: %s", res.Status)
}
data, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("signing digest: %w", err)
}
return data, nil
}