/
attest.go
188 lines (167 loc) · 7.57 KB
/
attest.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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package attest
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/Microsoft/confidential-sidecar-containers/pkg/common"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// CertState contains information about the certificate cache service
// that provides access to the certificate chain required upon attestation
type CertState struct {
CertFetcher CertFetcher `json:"cert_cache"`
Tcbm uint64 `json:"tcbm"`
}
const (
sha256len = 32
)
func (certState *CertState) RefreshCertChain(SNPReport SNPAttestationReport) ([]byte, error) {
logrus.Info("Refreshing CertChain...")
vcekCertChain, thimTcbm, err := certState.CertFetcher.GetCertChain(SNPReport.ChipID, SNPReport.ReportedTCB)
if err != nil {
return nil, errors.Wrap(err, "Refreshing CertChain failed")
}
certState.Tcbm = thimTcbm
return vcekCertChain, nil
}
// Takes bytes and generate report data that MAA expects (SHA256 hash of arbitrary data).
func GenerateMAAReportData(inputBytes []byte) [REPORT_DATA_SIZE]byte {
logrus.Info("Generating MAA Report Data...")
runtimeData := sha256.New()
if inputBytes != nil {
runtimeData.Write(inputBytes)
}
reportData := [REPORT_DATA_SIZE]byte{}
runtimeDataBytes := runtimeData.Sum(nil)
if len(runtimeDataBytes) != sha256len {
panic(fmt.Errorf("length of sha256 hash should be %d bytes, but it is actually %d bytes", sha256len, len(runtimeDataBytes)))
}
if sha256len > REPORT_DATA_SIZE {
panic(fmt.Errorf("generated hash is too large for report data. hash length: %d bytes, report data size: %d", sha256len, REPORT_DATA_SIZE))
}
copy(reportData[:sha256len], runtimeDataBytes)
return reportData
}
// Takes bytes and generate host data that UVM creates at launch of SNP VM (SHA256 hash of arbitrary data).
// It's only useful to create fake attestation report
func GenerateMAAHostData(inputBytes []byte) [HOST_DATA_SIZE]byte {
logrus.Info("Generating MAA Host Data...")
inittimeData := sha256.New()
if inputBytes != nil {
inittimeData.Write(inputBytes)
}
hostData := [HOST_DATA_SIZE]byte{}
inittimeDataBytes := inittimeData.Sum(nil)
if len(inittimeDataBytes) != sha256len {
panic(fmt.Errorf("length of sha256 hash should be %d bytes, but it is actually %d bytes", sha256len, len(inittimeDataBytes)))
}
if sha256len > HOST_DATA_SIZE {
panic(fmt.Errorf("generated hash is too large for host data. hash length: %d bytes, report host size: %d", sha256len, REPORT_DATA_SIZE))
}
copy(hostData[:], inittimeDataBytes)
return hostData
}
// Attest interacts with maa services to fetch an MAA token
// MAA expects four attributes:
// (A) the attestation report signed by the PSP signing key
// (B) a certificate chain that endorses the signing key of the attestation report
// (C) reference information that provides evidence that the UVM image is genuine.
// (D) inittime data: this is the policy blob that has been hashed by the host OS during the utility
//
// VM bringup and has been reported by the PSP in the attestation report as HOST DATA
//
// (E) runtime data: for example it may be a wrapping key blob that has been hashed during the attestation report
//
// retrieval and has been reported by the PSP in the attestation report as REPORT DATA
//
// Note that it uses fake attestation report if it's not running inside SNP VM
func (certState *CertState) Attest(maa common.MAA, runtimeDataBytes []byte, uvmInformation common.UvmInformation) (string, error) {
logrus.Info("Decoding UVM encoded security policy...")
inittimeDataBytes, err := base64.StdEncoding.DecodeString(uvmInformation.EncodedSecurityPolicy)
if err != nil {
return "", errors.Wrap(err, "Decoding policy from Base64 format failed")
}
logrus.Debugf(" inittimeDataBytes: %v", inittimeDataBytes)
// Fetch the attestation report
var reportFetcher AttestationReportFetcher
if IsSNPVM() {
logrus.Info("Running inside SNP VM, using real attestation report fetcher...")
reportFetcher, err = NewAttestationReportFetcher()
if err != nil {
return "", errors.Wrapf(err, "failed to create attestation report fetcher")
}
} else {
logrus.Info("Not running inside SNP VM, using fake attestation report fetcher...")
// Use fake attestation report if it's not running inside SNP VM
hostData := GenerateMAAHostData(inittimeDataBytes)
reportFetcher = UnsafeNewFakeAttestationReportFetcher(hostData)
}
reportData := GenerateMAAReportData(runtimeDataBytes)
logrus.Info("Fetching Attestation Report...")
SNPReportBytes, err := reportFetcher.FetchAttestationReportByte(reportData)
if err != nil {
return "", errors.Wrapf(err, "Failed to retrieve attestation report")
}
// Retrieve the certificate chain using the chip identifier and platform version
// fields of the attestation report
var SNPReport SNPAttestationReport
logrus.Info("Deserializing Attestation Report...")
if err = SNPReport.DeserializeReport(SNPReportBytes); err != nil {
return "", errors.Wrapf(err, "Failed to deserialize attestation report")
}
logrus.Debugf("SNP Report Reported TCB: %d\nCert Chain TCBM Value: %d\n", SNPReport.ReportedTCB, certState.Tcbm)
// At this point check that the TCB of the cert chain matches that reported so we fail early or
// fetch fresh certs by other means.
var vcekCertChain []byte
logrus.Info("Comparing TCB values...")
if SNPReport.ReportedTCB != certState.Tcbm {
// TCB values not the same, try refreshing cert cache first
logrus.Info("TCB values not the same, trying to refresh cert chain...")
vcekCertChain, err = certState.RefreshCertChain(SNPReport)
if err != nil {
return "", err
}
if SNPReport.ReportedTCB != certState.Tcbm {
// TCB values still don't match, try retrieving the SNP report again
logrus.Info("TCB values still don't match, trying to retrieve new attestation report...")
SNPReportBytes, err := reportFetcher.FetchAttestationReportByte(reportData)
if err != nil {
return "", errors.Wrapf(err, "Failed to retrieve new attestation report")
}
if err = SNPReport.DeserializeReport(SNPReportBytes); err != nil {
return "", errors.Wrapf(err, "Failed to deserialize new attestation report")
}
// refresh certs again
logrus.Info("Refreshing cert chain again...")
vcekCertChain, err = certState.RefreshCertChain(SNPReport)
if err != nil {
return "", err
}
// if no match after refreshing certs and attestation report, fail
if SNPReport.ReportedTCB != certState.Tcbm {
return "", errors.New(fmt.Sprintf("SNP reported TCB value: %d doesn't match Certificate TCB value: %d", SNPReport.ReportedTCB, certState.Tcbm))
}
}
} else {
logrus.Info("TCB values match, using cached cert chain...")
certString := uvmInformation.InitialCerts.VcekCert + uvmInformation.InitialCerts.CertificateChain
vcekCertChain = []byte(certString)
}
var uvmReferenceInfoBytes []byte
if len(uvmInformation.EncodedUvmReferenceInfo) > 0 {
uvmReferenceInfoBytes, err = base64.StdEncoding.DecodeString(uvmInformation.EncodedUvmReferenceInfo)
}
if err != nil {
return "", errors.Wrap(err, "Decoding UVM encoded security policy from Base64 format failed")
}
// Retrieve the MAA token required by the request's MAA endpoint
logrus.Info("Retrieving MAA token...")
maaToken, err := maa.Attest(SNPReportBytes, vcekCertChain, inittimeDataBytes, runtimeDataBytes, uvmReferenceInfoBytes)
if err != nil || maaToken == "" {
return "", errors.Wrapf(err, "Retrieving MAA token from MAA endpoint failed")
}
return maaToken, nil
}