-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmod.rs
More file actions
361 lines (327 loc) · 11.8 KB
/
Copy pathmod.rs
File metadata and controls
361 lines (327 loc) · 11.8 KB
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#[cfg(feature = "azure")]
pub mod azure;
pub mod dcap;
pub mod measurements;
pub mod tcb_info;
use measurements::MultiMeasurements;
use parity_scale_codec::{Decode, Encode};
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display, Formatter},
time::{SystemTime, UNIX_EPOCH},
};
use thiserror::Error;
use crate::attestation::{dcap::DcapVerificationError, measurements::MeasurementPolicy};
/// This is the type sent over the channel to provide an attestation
#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
pub struct AttestationExchangeMessage {
/// What CVM platform is used (including none)
pub attestation_type: AttestationType,
/// The attestation evidence as bytes - in the case of DCAP this is a quote
pub attestation: Vec<u8>,
}
impl AttestationExchangeMessage {
/// Create an empty attestation payload for the case that we are running in a non-confidential
/// environment
pub fn without_attestation() -> Self {
Self {
attestation_type: AttestationType::None,
attestation: Vec::new(),
}
}
}
/// Type of attestaion used
/// Only supported (or soon-to-be supported) types are given
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AttestationType {
/// No attestion
None,
/// Forwards the attestaion to a remote service (for testing purposes)
Dummy,
/// TDX on Google Cloud Platform
GcpTdx,
/// TDX on Azure, with MAA
AzureTdx,
/// TDX on Qemu (no cloud platform)
QemuTdx,
/// DCAP TDX
DcapTdx,
}
impl AttestationType {
/// Matches the names used by Constellation aTLS
pub fn as_str(&self) -> &'static str {
match self {
AttestationType::None => "none",
AttestationType::Dummy => "dummy",
AttestationType::AzureTdx => "azure-tdx",
AttestationType::QemuTdx => "qemu-tdx",
AttestationType::GcpTdx => "gcp-tdx",
AttestationType::DcapTdx => "dcap-tdx",
}
}
}
/// SCALE encode (used over the wire)
impl Encode for AttestationType {
fn encode(&self) -> Vec<u8> {
self.as_str().encode()
}
}
/// SCALE decode
impl Decode for AttestationType {
fn decode<I: parity_scale_codec::Input>(
input: &mut I,
) -> Result<Self, parity_scale_codec::Error> {
let s: String = String::decode(input)?;
serde_json::from_str(&format!("\"{s}\"")).map_err(|_| "Failed to decode enum".into())
}
}
impl Display for AttestationType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// Can generate a local attestation based on attestation type
#[derive(Clone)]
pub struct AttestationGenerator {
pub attestation_type: AttestationType,
dummy_dcap_url: Option<String>,
}
impl AttestationGenerator {
pub fn new(
attestation_type: AttestationType,
dummy_dcap_url: Option<String>,
) -> Result<Self, AttestationError> {
match attestation_type {
AttestationType::Dummy => Self::new_dummy(dummy_dcap_url),
_ => Self::new_not_dummy(attestation_type),
}
}
pub fn with_no_attestation() -> Self {
Self {
attestation_type: AttestationType::None,
dummy_dcap_url: None,
}
}
pub fn new_not_dummy(attestation_type: AttestationType) -> Result<Self, AttestationError> {
if attestation_type == AttestationType::Dummy {
return Err(AttestationError::DummyUrl);
}
Ok(Self {
attestation_type,
dummy_dcap_url: None,
})
}
pub fn new_dummy(dummy_dcap_url: Option<String>) -> Result<Self, AttestationError> {
match dummy_dcap_url {
Some(url) => {
let url = if url.starts_with("http://") || url.starts_with("https://") {
url.to_string()
} else {
format!("http://{}", url.trim_start_matches("http://"))
};
let url = url.strip_suffix('/').unwrap_or(&url).to_string();
Ok(Self {
attestation_type: AttestationType::Dummy,
dummy_dcap_url: Some(url),
})
}
None => Err(AttestationError::DummyUrl),
}
}
/// Generate an attestation exchange message
pub async fn generate_attestation(
&self,
input_data: [u8; 64],
) -> Result<AttestationExchangeMessage, AttestationError> {
Ok(AttestationExchangeMessage {
attestation_type: self.attestation_type,
attestation: self.generate_attestation_bytes(input_data).await?,
})
}
/// Generate attestation evidence bytes based on attestation type
async fn generate_attestation_bytes(
&self,
input_data: [u8; 64],
) -> Result<Vec<u8>, AttestationError> {
match self.attestation_type {
AttestationType::None => Ok(Vec::new()),
AttestationType::AzureTdx => {
#[cfg(feature = "azure")]
{
Ok(azure::create_azure_attestation(input_data).await?)
}
#[cfg(not(feature = "azure"))]
{
tracing::error!("Attempted to generate an azure attestation but the `azure` feature not enabled");
Err(AttestationError::AttestationTypeNotSupported)
}
}
AttestationType::Dummy => self.generate_dummy_attestation(input_data).await,
_ => dcap::create_dcap_attestation(input_data).await,
}
}
async fn generate_dummy_attestation(
&self,
input_data: [u8; 64],
) -> Result<Vec<u8>, AttestationError> {
let url = format!(
"{}/attest/{}",
self.dummy_dcap_url
.clone()
.ok_or(AttestationError::DummyUrl)?,
hex::encode(input_data)
);
Ok(reqwest::get(url)
.await
.map_err(|err| AttestationError::DummyServer(err.to_string()))?
.bytes()
.await
.map_err(|err| AttestationError::DummyServer(err.to_string()))?
.to_vec())
}
}
/// Allows remote attestations to be verified
#[derive(Clone, Debug)]
pub struct AttestationVerifier {
/// The measurement policy with accepted values and attestation types
pub measurement_policy: MeasurementPolicy,
/// If this is empty, anything will be accepted - but measurements are always injected into HTTP
/// headers, so that they can be verified upstream
/// A PCCS service to use - defaults to Intel PCS
pub pccs_url: Option<String>,
/// Whether to log quotes to a file
pub log_dcap_quote: bool,
}
impl AttestationVerifier {
/// Create an [AttestationVerifier] which will allow no remote attestation
pub fn expect_none() -> Self {
Self {
measurement_policy: MeasurementPolicy::expect_none(),
pccs_url: None,
log_dcap_quote: false,
}
}
/// Expect mock measurements used in tests
#[cfg(test)]
pub fn mock() -> Self {
Self {
measurement_policy: MeasurementPolicy::mock(),
pccs_url: None,
log_dcap_quote: false,
}
}
/// Verify an attestation, and ensure the measurements match one of our accepted measurements
pub async fn verify_attestation(
&self,
attestation_exchange_message: AttestationExchangeMessage,
expected_input_data: [u8; 64],
) -> Result<Option<MultiMeasurements>, AttestationError> {
let attestation_type = attestation_exchange_message.attestation_type;
tracing::debug!("Verifing {attestation_type} attestation");
if self.log_dcap_quote {
log_attestation(&attestation_exchange_message).await;
}
let measurements = match attestation_type {
AttestationType::None => {
if self.has_remote_attestion() {
return Err(AttestationError::AttestationTypeNotAccepted);
}
if attestation_exchange_message.attestation.is_empty() {
return Ok(None);
} else {
return Err(AttestationError::AttestationGivenWhenNoneExpected);
}
}
AttestationType::AzureTdx => {
#[cfg(feature = "azure")]
{
azure::verify_azure_attestation(
attestation_exchange_message.attestation,
expected_input_data,
self.pccs_url.clone(),
)
.await?
}
#[cfg(not(feature = "azure"))]
{
return Err(AttestationError::AttestationTypeNotSupported);
}
}
AttestationType::Dummy => {
// Dummy assumes dummy DCAP
dcap::verify_dcap_attestation(
attestation_exchange_message.attestation,
expected_input_data,
self.pccs_url.clone(),
)
.await?
}
_ => {
if cfg!(test) {
dcap::mock_verify_dcap(
attestation_exchange_message.attestation,
expected_input_data,
)?
} else {
dcap::verify_dcap_attestation(
attestation_exchange_message.attestation,
expected_input_data,
self.pccs_url.clone(),
)
.await?
}
}
};
// Do a measurement / attestation type policy check
self.measurement_policy.check_measurement(&measurements)?;
tracing::debug!("Verification successful");
Ok(Some(measurements))
}
/// Whether we allow no remote attestation
pub fn has_remote_attestion(&self) -> bool {
self.measurement_policy.has_remote_attestion()
}
}
/// Write attestation data to a log file
async fn log_attestation(attestation: &AttestationExchangeMessage) {
if attestation.attestation_type != AttestationType::None {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_nanos();
let filename = format!("quotes/{}-{}", attestation.attestation_type, timestamp);
if let Err(err) = tokio::fs::write(&filename, attestation.attestation.clone()).await {
tracing::warn!("Failed to write {filename}: {err}");
}
}
}
/// An error when generating or verifying an attestation
#[derive(Error, Debug)]
pub enum AttestationError {
#[error("Certificate chain is empty")]
NoCertificate,
#[error("X509 parse: {0}")]
X509Parse(#[from] x509_parser::asn1_rs::Err<x509_parser::error::X509Error>),
#[error("X509: {0}")]
X509(#[from] x509_parser::error::X509Error),
#[error("Configuration mismatch - expected no remote attestation")]
AttestationGivenWhenNoneExpected,
#[error("Configfs-tsm quote generation: {0}")]
QuoteGeneration(#[from] configfs_tsm::QuoteGenerationError),
#[error("DCAP verification: {0}")]
DcapVerification(#[from] DcapVerificationError),
#[error("Attestation type not supported")]
AttestationTypeNotSupported,
#[error("Attestation type not accepted")]
AttestationTypeNotAccepted,
#[error("Measurements not accepted")]
MeasurementsNotAccepted,
#[cfg(feature = "azure")]
#[error("MAA: {0}")]
Maa(#[from] azure::MaaError),
#[error("Dummy attestation type requires dummy service URL")]
DummyUrl,
#[error("Dummy server: {0}")]
DummyServer(String),
}