Skip to content

Commit 700ba36

Browse files
committed
kernel/attest: Decrypt secret from attestation proxy
On a successful attestation, the driver will receive an encrypted secret from the attestation server. Decrypt the secret using an ECDH handshake (with the server's public key, which is also supplied) and return it. Signed-off-by: Tyler Fanelli <tfanelli@redhat.com>
1 parent 198307b commit 700ba36

File tree

7 files changed

+189
-33
lines changed

7 files changed

+189
-33
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ release = { path = "release" }
5757
virtio-drivers = { path = "virtio-drivers" }
5858

5959
# crates.io
60+
aes = "0.8.4"
6061
aes-gcm = { version = "0.10.3", default-features = false }
6162
arbitrary = "1.3.0"
6263
base64 = { version = "0.22.1", default-features = false }

aproxy/src/backend/kbs.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,33 @@ impl AttestationProtocol for KbsProtocol {
129129
let resp: Response = serde_json::from_str(&text)
130130
.context("unable to convert KBS /resource response to KBS Response object")?;
131131

132+
let pub_key = {
133+
let val = serde_json::from_slice(&resp.encrypted_key).unwrap();
134+
let Value::Object(map) = val else {
135+
panic!();
136+
};
137+
138+
let x = map.get("x_b64url").unwrap();
139+
let Value::String(x) = x else {
140+
panic!();
141+
};
142+
143+
let y = map.get("y_b64url").unwrap();
144+
let Value::String(y) = y else {
145+
panic!();
146+
};
147+
148+
AttestationKey::EC {
149+
crv: "EC384".to_string(),
150+
x_b64url: x.to_string(),
151+
y_b64url: y.to_string(),
152+
}
153+
};
154+
132155
Ok(AttestationResponse {
133156
success: true,
134157
secret: Some(resp.ciphertext),
135-
pub_key: Some(resp.encrypted_key),
158+
pub_key: Some(pub_key),
136159
})
137160
}
138161
}

kernel/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ libaproxy = { workspace = true, optional = true }
2525
elf.workspace = true
2626
syscall.workspace = true
2727

28+
aes = { workspace = true, optional = true }
2829
aes-gcm = { workspace = true, features = ["aes", "alloc"] }
2930
base64 = { workspace = true, optional = true, features = ["alloc"] }
3031
bitfield-struct.workspace = true
@@ -69,7 +70,7 @@ verus_stub = { workspace = true }
6970
test.workspace = true
7071

7172
[features]
72-
attest = ["dep:base64", "dep:cocoon-tpm-crypto", "dep:cocoon-tpm-tpm2-interface", "dep:cocoon-tpm-utils-common", "dep:kbs-types", "dep:libaproxy", "dep:serde", "dep:serde_json"]
73+
attest = ["dep:aes", "dep:base64", "dep:cocoon-tpm-crypto", "dep:cocoon-tpm-tpm2-interface", "dep:cocoon-tpm-utils-common", "dep:kbs-types", "dep:libaproxy", "dep:serde", "dep:serde_json"]
7374

7475
default = []
7576
enable-gdb = ["dep:gdbstub", "dep:gdbstub_arch"]

kernel/src/attest.rs

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@ use crate::{
1313
io::{Read, Write, DEFAULT_IO_DRIVER},
1414
serial::SerialPort,
1515
};
16+
use aes::{cipher::BlockDecrypt, Aes256};
17+
use aes_gcm::KeyInit;
1618
use alloc::{string::ToString, vec, vec::Vec};
1719
use base64::{prelude::*, Engine};
1820
use cocoon_tpm_crypto::{
19-
ecc,
21+
ecc::{curve::Curve, ecdh::ecdh_c_1e_1s_cdh_party_v_key_gen, EccKey},
2022
rng::{self, HashDrbg, RngCore as _, X86RdSeedRng},
2123
CryptoError, EmptyCryptoIoSlices,
2224
};
23-
use cocoon_tpm_tpm2_interface::{
24-
self as tpm2_interface, Tpm2bEccParameter, TpmEccCurve, TpmiAlgHash, TpmsEccPoint,
25-
};
25+
use cocoon_tpm_tpm2_interface::{self as tpm2_interface, TpmEccCurve, TpmiAlgHash, TpmsEccPoint};
2626
use cocoon_tpm_utils_common::{
2727
alloc::try_alloc_zeroizing_vec,
2828
io_slices::{self, IoSlicesIterCommon as _},
2929
};
30+
use core::cmp::min;
3031
use kbs_types::Tee;
3132
use libaproxy::*;
3233
use serde::Serialize;
@@ -35,12 +36,12 @@ use zerocopy::{FromBytes, IntoBytes};
3536

3637
/// The attestation driver that communicates with the proxy via some communication channel (serial
3738
/// port, virtio-vsock, etc...).
38-
#[derive(Debug)]
39+
#[allow(missing_debug_implementations)]
3940
pub struct AttestationDriver<'a> {
4041
sp: SerialPort<'a>,
4142
tee: Tee,
42-
pub_key: TpmsEccPoint<'static>,
43-
_priv_key: Tpm2bEccParameter<'static>,
43+
ecc: EccKey,
44+
curve: Curve,
4445
}
4546

4647
impl TryFrom<Tee> for AttestationDriver<'_> {
@@ -57,13 +58,14 @@ impl TryFrom<Tee> for AttestationDriver<'_> {
5758
_ => return Err(AttestationError::UnsupportedTee.into()),
5859
}
5960

60-
let key = sc_key_generate(TpmEccCurve::NistP384).map_err(AttestationError::KeyGen)?;
61+
let curve = Curve::new(TpmEccCurve::NistP384).map_err(AttestationError::Crypto)?;
62+
let ecc = sc_key_generate(&curve).map_err(AttestationError::Crypto)?;
6163

6264
Ok(Self {
6365
sp,
6466
tee,
65-
pub_key: key.0,
66-
_priv_key: key.1,
67+
ecc,
68+
curve,
6769
})
6870
}
6971
}
@@ -73,7 +75,8 @@ impl AttestationDriver<'_> {
7375
pub fn attest(&mut self) -> Result<Vec<u8>, SvsmError> {
7476
let negotiation = self.negotiation()?;
7577

76-
Ok(self.attestation(negotiation)?)
78+
self.attestation(negotiation)
79+
.map_err(SvsmError::TeeAttestation)
7780
}
7881

7982
/// Send a negotiation request to the proxy. Proxy should reply with Negotiation parameters
@@ -95,15 +98,19 @@ impl AttestationDriver<'_> {
9598
/// containing the status (success/fail) and an optional secret returned from the server upon
9699
/// successful attestation.
97100
fn attestation(&mut self, n: NegotiationResponse) -> Result<Vec<u8>, AttestationError> {
98-
let evidence = evidence(&self.tee, hash(n, &self.pub_key)?)?;
101+
let pub_key = self
102+
.ecc
103+
.pub_key()
104+
.to_tpms_ecc_point(&self.curve.curve_ops().map_err(AttestationError::Crypto)?)
105+
.map_err(AttestationError::Crypto)?;
106+
107+
let evidence = evidence(&self.tee, hash(n, &pub_key)?)?;
99108

100109
let req = AttestationRequest {
101110
evidence: BASE64_URL_SAFE.encode(evidence),
102-
key: AttestationKey::EC {
103-
crv: "EC384".to_string(),
104-
x_b64url: BASE64_URL_SAFE.encode(&*self.pub_key.x.buffer),
105-
y_b64url: BASE64_URL_SAFE.encode(&*self.pub_key.y.buffer),
106-
},
111+
key: (TpmEccCurve::NistP384, &pub_key)
112+
.try_into()
113+
.map_err(|_| AttestationError::AttestationDeserialize)?,
107114
};
108115

109116
self.write(req)?;
@@ -112,7 +119,49 @@ impl AttestationDriver<'_> {
112119
let response: AttestationResponse = serde_json::from_slice(&payload)
113120
.map_err(|_| AttestationError::AttestationDeserialize)?;
114121

115-
todo!();
122+
if !response.success {
123+
return Err(AttestationError::Failed);
124+
}
125+
126+
let Some(ak) = response.pub_key else {
127+
return Err(AttestationError::PublicKeyMissing)?;
128+
};
129+
130+
let pub_key: TpmsEccPoint<'static> = ak.try_into().map_err(AttestationError::Crypto)?;
131+
132+
let Some(ciphertext) = response.secret else {
133+
return Err(AttestationError::SecretMissing);
134+
};
135+
136+
self.decrypt(ciphertext, pub_key)
137+
}
138+
139+
/// Decrypt a secret from the attestation server with the TEE private key.
140+
fn decrypt(
141+
&self,
142+
ciphertext: Vec<u8>,
143+
pub_key: TpmsEccPoint<'static>,
144+
) -> Result<Vec<u8>, AttestationError> {
145+
let shared_secret =
146+
ecdh_c_1e_1s_cdh_party_v_key_gen(TpmiAlgHash::Sha256, "", &self.ecc, &pub_key)
147+
.map_err(AttestationError::Crypto)?;
148+
149+
let aes = Aes256::new_from_slice(&shared_secret[..])
150+
.map_err(|_| AttestationError::AesGenerate)?;
151+
// Decrypt each 16-byte block of the ciphertext with the symmetric key.
152+
let mut ptr = 0;
153+
let len = ciphertext.len();
154+
let mut vec: Vec<u8> = Vec::new();
155+
while ptr < len {
156+
let remain = min(16, len - ptr);
157+
let mut arr: [u8; 16] = [0u8; 16];
158+
arr[..remain].copy_from_slice(&ciphertext[ptr..ptr + remain]);
159+
aes.decrypt_block((&mut arr).into());
160+
vec.append(&mut arr.to_vec());
161+
ptr += remain;
162+
}
163+
164+
Ok(vec)
116165
}
117166

118167
/// Read attestation data from the serial port.
@@ -154,8 +203,12 @@ impl AttestationDriver<'_> {
154203
/// Possible errors when attesting TEE evidence.
155204
#[derive(Clone, Copy, Debug)]
156205
pub enum AttestationError {
206+
/// Error generating AES key.
207+
AesGenerate,
157208
/// Error deserializing the attestation response from JSON bytes.
158209
AttestationDeserialize,
210+
/// Guest has failed attestation.
211+
Failed,
159212
/// Error deserializing the negotiation response from JSON bytes.
160213
NegotiationDeserialize,
161214
/// Error serializing the negotiation request to JSON bytes.
@@ -164,10 +217,16 @@ pub enum AttestationError {
164217
ProxyRead,
165218
/// Error writing over the attestation proxy transport channel.
166219
ProxyWrite,
220+
/// Attestation successful, but no public key found.
221+
PublicKeyMissing,
167222
/// Unsupported TEE architecture.
168223
UnsupportedTee,
169224
/// Unable to generate secure channel key.
170-
KeyGen(CryptoError),
225+
Crypto(CryptoError),
226+
/// Attestation successful, but unable to decrypt secret.
227+
SecretDecrypt,
228+
/// Attestation successful, but no secret found.
229+
SecretMissing,
171230
/// Unable to fetch SEV-SNP attestation report.
172231
SnpGetReport,
173232
}
@@ -180,9 +239,7 @@ impl From<AttestationError> for SvsmError {
180239

181240
/// Generate a key used to establish a secure channel between the confidential guest and
182241
/// attestation server.
183-
fn sc_key_generate(
184-
curve_id: TpmEccCurve,
185-
) -> Result<(TpmsEccPoint<'static>, Tpm2bEccParameter<'static>), CryptoError> {
242+
fn sc_key_generate(curve: &Curve) -> Result<EccKey, CryptoError> {
186243
let mut rng = {
187244
let mut rdseed = X86RdSeedRng::instantiate().map_err(|_| CryptoError::RngFailure)?;
188245
let mut hash_drbg_entropy =
@@ -202,15 +259,9 @@ fn sc_key_generate(
202259
)
203260
}?;
204261

205-
let curve = ecc::curve::Curve::new(curve_id)?;
206262
let curve_ops = curve.curve_ops()?;
207-
let ecc_key = ecc::EccKey::generate(&curve_ops, &mut rng, None)?;
208-
209-
let (pub_key, priv_key) = ecc_key.into_tpms(&curve_ops)?;
210-
211-
let priv_key = priv_key.ok_or(CryptoError::Internal)?;
212263

213-
Ok((pub_key, priv_key))
264+
EccKey::generate(&curve_ops, &mut rng, None)
214265
}
215266

216267
/// Hash negotiation parameters and fetch TEE evidence.

libaproxy/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7+
base64 = { workspace = true, features = ["alloc"] }
8+
cocoon-tpm-crypto = { workspace = true, features = [
9+
"enable_arch_math_asm", "zeroize",
10+
# Enable x86 rdseed based rng.
11+
"enable_x86_64_rdseed",
12+
# At least one of block cipher, mode and hash is needed,
13+
# otherwise compilation will fail due to empty enums.
14+
"aes", "cfb", "sha256", "sha384", "sha512",
15+
"ecc", "ecdh", "ecdsa",
16+
"ecc_nist_p224", "ecc_nist_p256",
17+
"ecc_nist_p384", "ecc_nist_p521",
18+
]}
19+
cocoon-tpm-tpm2-interface.workspace = true
720
kbs-types = { workspace = true, features = ["alloc"] }
821
serde.workspace = true
922

libaproxy/src/attestation.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
// Author: Tyler Fanelli <tfanelli@redhat.com>
77

88
extern crate alloc;
9-
use alloc::{string::String, vec::Vec};
9+
use alloc::{
10+
string::{String, ToString},
11+
vec::Vec,
12+
};
13+
use base64::prelude::*;
14+
use cocoon_tpm_crypto::CryptoError;
15+
use cocoon_tpm_tpm2_interface::{Tpm2bEccParameter, TpmBuffer, TpmEccCurve, TpmsEccPoint};
16+
use core::convert::TryFrom;
1017
use serde::{Deserialize, Serialize};
1118

1219
/// The format of the public key that is used to encrypt secrets sent to SVSM upon successful
@@ -23,6 +30,62 @@ pub enum AttestationKey {
2330
},
2431
}
2532

33+
impl TryFrom<(TpmEccCurve, &TpmsEccPoint<'static>)> for AttestationKey {
34+
type Error = CryptoError;
35+
36+
fn try_from(args: (TpmEccCurve, &TpmsEccPoint<'static>)) -> Result<Self, Self::Error> {
37+
let x_b64url = BASE64_URL_SAFE.encode(&*args.1.x.buffer);
38+
let y_b64url = BASE64_URL_SAFE.encode(&*args.1.y.buffer);
39+
40+
let crv = match args.0 {
41+
TpmEccCurve::NistP224 => "EC224",
42+
TpmEccCurve::NistP256 => "EC256",
43+
TpmEccCurve::NistP384 => "EC384",
44+
TpmEccCurve::NistP521 => "EC521",
45+
_ => return Err(CryptoError::InvalidParams),
46+
}
47+
.to_string();
48+
49+
Ok(AttestationKey::EC {
50+
crv,
51+
x_b64url,
52+
y_b64url,
53+
})
54+
}
55+
}
56+
57+
impl TryFrom<AttestationKey> for TpmsEccPoint<'static> {
58+
type Error = CryptoError;
59+
60+
fn try_from(arg: AttestationKey) -> Result<Self, CryptoError> {
61+
match arg {
62+
AttestationKey::EC {
63+
crv: _,
64+
x_b64url,
65+
y_b64url,
66+
} => {
67+
let x_buf = BASE64_URL_SAFE
68+
.decode(&x_b64url)
69+
.map_err(|_| CryptoError::InvalidParams)?;
70+
let y_buf = BASE64_URL_SAFE
71+
.decode(&y_b64url)
72+
.map_err(|_| CryptoError::InvalidParams)?;
73+
74+
let point = TpmsEccPoint {
75+
x: Tpm2bEccParameter {
76+
buffer: TpmBuffer::Owned(x_buf),
77+
},
78+
y: Tpm2bEccParameter {
79+
buffer: TpmBuffer::Owned(y_buf),
80+
},
81+
};
82+
83+
Ok(point)
84+
}
85+
}
86+
}
87+
}
88+
2689
/// The attestation request payload sent to the proxy from SVSM.
2790
#[derive(Serialize, Deserialize, Debug)]
2891
pub struct AttestationRequest {
@@ -41,5 +104,5 @@ pub struct AttestationResponse {
41104
/// Secret encrypted with the key generated by SVSM
42105
pub secret: Option<Vec<u8>>,
43106
/// Server's public key used for symmetric encryption/decryption.
44-
pub pub_key: Option<Vec<u8>>,
107+
pub pub_key: Option<AttestationKey>,
45108
}

0 commit comments

Comments
 (0)