-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathclient.go
135 lines (115 loc) · 3.41 KB
/
client.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
package client
import (
"bytes"
"fmt"
"net/http"
"github.com/tinfoilsh/verifier/attestation"
"github.com/tinfoilsh/verifier/github"
"github.com/tinfoilsh/verifier/sigstore"
)
// GroundTruth represents the "known good" verified of the enclave
type GroundTruth struct {
CertFingerprint []byte
Digest string
Measurement string
}
type SecureClient struct {
enclave, repo string
groundTruth *GroundTruth
}
func NewSecureClient(enclave, repo string) *SecureClient {
return &SecureClient{
enclave: enclave,
repo: repo,
}
}
// GroundTruth returns the last verified enclave state
func (s *SecureClient) GroundTruth() *GroundTruth {
return s.groundTruth
}
// Verify fetches the latest verification information from GitHub and Sigstore and stores the ground truth results in the client
func (s *SecureClient) Verify() (*GroundTruth, error) {
digest, err := github.FetchLatestDigest(s.repo)
if err != nil {
return nil, fmt.Errorf("failed to fetch latest release: %v", err)
}
sigstoreBundle, err := github.FetchAttestationBundle(s.repo, digest)
if err != nil {
return nil, fmt.Errorf("failed to fetch attestation bundle: %v", err)
}
trustRootJSON, err := sigstore.FetchTrustRoot()
if err != nil {
return nil, fmt.Errorf("failed to fetch trust root: %v", err)
}
codeMeasurements, err := sigstore.VerifyAttestation(trustRootJSON, sigstoreBundle, digest, s.repo)
if err != nil {
return nil, fmt.Errorf("failed to verify attested measurements: %v", err)
}
enclaveAttestation, err := attestation.Fetch(s.enclave)
if err != nil {
return nil, fmt.Errorf("failed to fetch enclave measurements: %v", err)
}
verification, err := enclaveAttestation.Verify()
if err != nil {
return nil, fmt.Errorf("failed to verify enclave measurements: %v", err)
}
err = codeMeasurements.Equals(verification.Measurement)
if err == nil {
s.groundTruth = &GroundTruth{
CertFingerprint: verification.CertFP,
Digest: digest,
Measurement: codeMeasurements.Fingerprint(),
}
}
return s.groundTruth, err
}
// HTTPClient returns an HTTP client that only accepts TLS connections to the verified enclave
func (s *SecureClient) HTTPClient() (*http.Client, error) {
if s.groundTruth == nil {
_, err := s.Verify()
if err != nil {
return nil, fmt.Errorf("failed to verify enclave: %v", err)
}
}
return &http.Client{
Transport: &TLSBoundRoundTripper{s.groundTruth.CertFingerprint},
}, nil
}
func (s *SecureClient) makeRequest(req *http.Request) (*Response, error) {
httpClient, err := s.HTTPClient()
if err != nil {
return nil, err
}
// If URL doesn't start with anything, assume it's a relative path and set the base URL
if req.URL.Host == "" {
req.URL.Scheme = "https"
req.URL.Host = s.enclave
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
return toResponse(resp)
}
// Post makes an HTTP POST request
func (s *SecureClient) Post(url string, headers map[string]string, body []byte) (*Response, error) {
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header.Set(k, v)
}
return s.makeRequest(req)
}
// Get makes an HTTP GET request
func (s *SecureClient) Get(url string, headers map[string]string) (*Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header.Set(k, v)
}
return s.makeRequest(req)
}