/
sgx.go
289 lines (250 loc) · 8.15 KB
/
sgx.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package node
import (
"encoding/binary"
"fmt"
"time"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/crypto/tuplehash"
"github.com/oasisprotocol/oasis-core/go/common/sgx"
"github.com/oasisprotocol/oasis-core/go/common/sgx/ias"
"github.com/oasisprotocol/oasis-core/go/common/sgx/quote"
)
const (
// LatestSGXConstraintsVersion is the latest SGX constraints structure version that should be
// used for all new descriptors.
LatestSGXConstraintsVersion = 1
)
var emptyFeatures TEEFeatures
// SGXConstraints are the Intel SGX TEE constraints.
type SGXConstraints struct {
cbor.Versioned
// Enclaves is the allowed MRENCLAVE/MRSIGNER pairs.
Enclaves []sgx.EnclaveIdentity `json:"enclaves,omitempty"`
// Policy is the quote policy.
Policy *quote.Policy `json:"policy,omitempty"`
// MaxAttestationAge is the maximum attestation age (in blocks).
MaxAttestationAge uint64 `json:"max_attestation_age,omitempty"`
}
// sgxConstraintsV0 are the version 0 Intel SGX TEE constraints which only supports IAS.
type sgxConstraintsV0 struct {
Enclaves []sgx.EnclaveIdentity `json:"enclaves,omitempty"`
AllowedQuoteStatuses []ias.ISVEnclaveQuoteStatus `json:"allowed_quote_statuses,omitempty"`
}
// UnmarshalCBOR is a custom deserializer that handles different structure versions.
func (sc *SGXConstraints) UnmarshalCBOR(data []byte) error {
// Determine Entity structure version.
v, err := cbor.GetVersion(data)
if err != nil {
v = 0 // Previous SGXConstraints structures were not versioned.
}
switch v {
case 0:
// Old version only supported the IAS-related constraints.
var scv0 sgxConstraintsV0
if err = cbor.Unmarshal(data, &scv0); err != nil {
return err
}
// Convert into new format.
sc.Versioned = cbor.NewVersioned(0)
sc.Enclaves = scv0.Enclaves
sc.Policy = "e.Policy{
IAS: &ias.QuotePolicy{
AllowedQuoteStatuses: scv0.AllowedQuoteStatuses,
},
}
return nil
case 1:
// New version, call the default unmarshaler.
type scv1 SGXConstraints
return cbor.Unmarshal(data, (*scv1)(sc))
default:
return fmt.Errorf("invalid SGX constraints version: %d", v)
}
}
// MarshalCBOR is a custom serializer that handles different structure versions.
func (sc *SGXConstraints) MarshalCBOR() ([]byte, error) {
switch sc.V {
case 0:
// Old version only supported the IAS-related constraints.
scv0 := sgxConstraintsV0{
Enclaves: sc.Enclaves,
}
if sc.Policy != nil && sc.Policy.IAS != nil {
scv0.AllowedQuoteStatuses = sc.Policy.IAS.AllowedQuoteStatuses
}
return cbor.Marshal(scv0), nil
default:
type scv1 SGXConstraints
return cbor.Marshal((*scv1)(sc)), nil
}
}
// ValidateBasic performs basic structure validity checks.
func (sc *SGXConstraints) ValidateBasic(cfg *TEEFeatures) error {
if cfg == nil {
cfg = &emptyFeatures
}
// Before the PCS feature only v0 of SGX constraints is supported.
if !cfg.SGX.PCS && sc.V != 0 {
return fmt.Errorf("unsupported SGX constraints version: %d", sc.V)
}
// Sanity check version (should never fail as deserialization already checks this).
if sc.V > LatestSGXConstraintsVersion {
return fmt.Errorf("unsupported SGX constraints version: %d", sc.V)
}
return nil
}
// ContainsEnclave returns true iff the allowed enclave list in SGX constraints contain the given
// enclave identity.
func (sc *SGXConstraints) ContainsEnclave(eid sgx.EnclaveIdentity) bool {
for _, e := range sc.Enclaves {
if eid == e {
return true
}
}
return false
}
const (
// LatestSGXAttestationVersion is the latest SGX attestation structure version that should be
// used for all new descriptors.
LatestSGXAttestationVersion = 1
)
// SGXAttestation is an Intel SGX remote attestation.
type SGXAttestation struct {
cbor.Versioned
// Quote is an Intel SGX quote.
Quote quote.Quote `json:"quote"`
// Height is the runtime's view of the consensus layer height at the time of attestation.
Height uint64 `json:"height"`
// Signature is the signature of the attestation by the enclave (RAK).
Signature signature.RawSignature `json:"signature"`
}
// UnmarshalCBOR is a custom deserializer that handles different structure versions.
func (sa *SGXAttestation) UnmarshalCBOR(data []byte) error {
// Determine Entity structure version.
v, err := cbor.GetVersion(data)
if err != nil {
v = 0 // Previous SGXAttestation structures were not versioned.
}
switch v {
case 0:
// Old version only supported the IAS attestation.
var sav0 ias.AVRBundle
if err = cbor.Unmarshal(data, &sav0); err != nil {
return err
}
// Convert into new format.
sa.Versioned = cbor.NewVersioned(0)
sa.Quote = quote.Quote{
IAS: &sav0,
}
return nil
case 1:
// New version, call the default unmarshaler.
type sav1 SGXAttestation
return cbor.Unmarshal(data, (*sav1)(sa))
default:
return fmt.Errorf("invalid SGX attestation version: %d", v)
}
}
// MarshalCBOR is a custom serializer that handles different structure versions.
func (sa *SGXAttestation) MarshalCBOR() ([]byte, error) {
switch sa.V {
case 0:
// Old version only supported the IAS attestation.
return cbor.Marshal(sa.Quote.IAS), nil
default:
type sav1 SGXAttestation
return cbor.Marshal((*sav1)(sa)), nil
}
}
// ValidateBasic performs basic structure validity checks.
func (sa *SGXAttestation) ValidateBasic(cfg *TEEFeatures) error {
if cfg == nil {
cfg = &emptyFeatures
}
// Before the PCS feature only v0 of SGX attestation is supported.
if !cfg.SGX.PCS && sa.V != 0 {
return fmt.Errorf("unsupported SGX attestation version: %d", sa.V)
}
// Sanity check version (should never fail as deserialization already checks this).
if sa.V > LatestSGXAttestationVersion {
return fmt.Errorf("unsupported SGX attestation version: %d", sa.V)
}
return nil
}
// Verify verifies the SGX attestation.
func (sa *SGXAttestation) Verify(
cfg *TEEFeatures,
ts time.Time,
height uint64,
sc *SGXConstraints,
rak signature.PublicKey,
nodeID signature.PublicKey,
) error {
if cfg == nil {
cfg = &emptyFeatures
}
// Use defaults from consensus parameters.
cfg.SGX.ApplyDefaultConstraints(sc)
// Verify the quote.
verifiedQuote, err := sa.Quote.Verify(sc.Policy, ts)
if err != nil {
return err
}
// Ensure that the MRENCLAVE/MRSIGNER match what is specified
// in the TEE-specific constraints field.
if !sc.ContainsEnclave(verifiedQuote.Identity) {
return ErrBadEnclaveIdentity
}
// Ensure that the report data includes the hash of the node's RAK.
var reportDataRAKHash hash.Hash
_ = reportDataRAKHash.UnmarshalBinary(verifiedQuote.ReportData[:hash.Size])
rakHash := HashRAK(rak)
if !rakHash.Equal(&reportDataRAKHash) {
return ErrRAKHashMismatch
}
// The last 32 bytes of the quote ReportData are deliberately
// ignored.
if cfg.SGX.SignedAttestations {
// In case the signed attestation feature is enabled, verify the signature.
return sa.verifyAttestationSignature(sc, rak, verifiedQuote.ReportData, nodeID, height)
}
return nil
}
func (sa *SGXAttestation) verifyAttestationSignature(
sc *SGXConstraints,
rak signature.PublicKey,
reportData []byte,
nodeID signature.PublicKey,
height uint64,
) error {
h := HashAttestation(reportData, nodeID, sa.Height)
if !rak.Verify(AttestationSignatureContext, h, sa.Signature[:]) {
return ErrInvalidAttestationSignature
}
// Check height is relatively recent and not from the future.
if sa.Height > height {
return ErrAttestationFromFuture
}
if height-sa.Height > sc.MaxAttestationAge {
return ErrAttestationNotFresh
}
return nil
}
// HashAttestation hashes the required data that needs to be signed by RAK producing the attestation
// signature. The hash is computed as follows:
//
// TupleHash[AttestationSignatureContext](reportData, nodeID, height)
//
func HashAttestation(reportData []byte, nodeID signature.PublicKey, height uint64) []byte {
h := tuplehash.New256(32, []byte(AttestationSignatureContext))
_, _ = h.Write(reportData)
rawNodeID, _ := nodeID.MarshalBinary()
_, _ = h.Write(rawNodeID)
var rawHeight [8]byte
binary.LittleEndian.PutUint64(rawHeight[:], height)
_, _ = h.Write(rawHeight[:])
return h.Sum(nil)
}