Skip to content

Commit cbbbc1d

Browse files
frankdavidIDX GitHub Automation
andauthored
feat: Implement SEV-based key derivation (#6112)
The SEV-based key derivation uses the key coming from the SEV Secure Processor to derive keys for various purposes. We use HKDF to derive the keys. The resulting derived keys are encoded with base64 so that they can be entered manually if necessary. --------- Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
1 parent af7da00 commit cbbbc1d

File tree

8 files changed

+155
-0
lines changed

8 files changed

+155
-0
lines changed

Cargo.lock

Lines changed: 3 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
@@ -571,6 +571,7 @@ getrandom = { version = "0.2", features = ["custom"] }
571571
goldenfile = "1.8.0"
572572
gpt = "4.1"
573573
hex = { version = "0.4.3", features = ["serde"] }
574+
hkdf = "^0.12"
574575
http = "1.3.1"
575576
http-body = "1.0.1"
576577
http-body-util = "0.1.3"

rs/ic_os/sev/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ package(default_visibility = ["//rs:ic-os-pkg"])
55
DEPENDENCIES = [
66
# Keep sorted.
77
"@crate_index//:anyhow",
8+
"@crate_index//:base64",
89
"@crate_index//:der",
10+
"@crate_index//:hkdf",
911
"@crate_index//:mockall",
1012
"@crate_index//:reqwest",
1113
"@crate_index//:sev",
14+
"@crate_index//:sha2",
1215
"@crate_index//:tempfile",
1316
]
1417

rs/ic_os/sev/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ edition = "2021"
44

55
[dependencies]
66
anyhow = { workspace = true }
7+
base64 = { workspace = true }
78
der = { workspace = true }
9+
hkdf = { workspace = true }
810
mockall = { workspace = true }
911
reqwest = { workspace = true }
1012
sev = { workspace = true }
13+
sha2 = { workspace = true }
1114
tempfile = { workspace = true }
1215

1316
[dev-dependencies]

rs/ic_os/sev/src/guest/firmware.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use sev::error::UserApiError;
2+
use sev::firmware::guest::DerivedKey;
3+
#[cfg(target_os = "linux")]
4+
use sev::firmware::guest::Firmware;
5+
6+
#[mockall::automock]
7+
pub trait SevGuestFirmware: Sync + Send {
8+
fn get_derived_key(
9+
&mut self,
10+
message_version: Option<u32>,
11+
derived_key_request: DerivedKey,
12+
) -> Result<[u8; 32], UserApiError>;
13+
}
14+
15+
#[cfg(target_os = "linux")]
16+
impl SevGuestFirmware for Firmware {
17+
fn get_derived_key(
18+
&mut self,
19+
message_version: Option<u32>,
20+
derived_key_request: DerivedKey,
21+
) -> Result<[u8; 32], UserApiError> {
22+
self.get_derived_key(message_version, derived_key_request)
23+
}
24+
}

rs/ic_os/sev/src/guest/key_deriver.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use crate::guest::firmware::SevGuestFirmware;
2+
use anyhow::Context;
3+
use anyhow::Result;
4+
use hkdf::SimpleHkdf;
5+
use sev::firmware::guest::{DerivedKey, GuestFieldSelect};
6+
use sha2::Sha256;
7+
use std::os::unix::ffi::OsStrExt;
8+
use std::path::Path;
9+
10+
/// A key derivation provider that uses the SEV firmware to derive keys.
11+
pub struct SevKeyDeriver {
12+
sev_firmware: Box<dyn SevGuestFirmware>,
13+
}
14+
15+
impl SevKeyDeriver {
16+
pub fn new() -> Result<Self> {
17+
#[cfg(not(target_os = "linux"))]
18+
{
19+
anyhow::bail!("SEV key derivation is only supported on Linux");
20+
}
21+
22+
#[cfg(target_os = "linux")]
23+
Ok(Self {
24+
sev_firmware: Box::new(
25+
sev::firmware::guest::Firmware::open().context("Could not open SEV firmware")?,
26+
),
27+
})
28+
}
29+
30+
pub fn new_for_test(sev_firmware: Box<dyn SevGuestFirmware>) -> Self {
31+
Self { sev_firmware }
32+
}
33+
34+
/// Derives a key for the given `Key` variant using the SEV firmware.
35+
/// The key is in base64 format (useful e.g., if the key must be entered manually).
36+
pub fn derive_key(&mut self, key: Key) -> Result<String> {
37+
let mut field_select = GuestFieldSelect::default();
38+
field_select.set_measurement(true);
39+
40+
let derived_key = self
41+
.sev_firmware
42+
.get_derived_key(Some(1), DerivedKey::new(false, field_select, 0, 0, 0))
43+
.context("Failed to get derived key from SEV firmware")?;
44+
45+
let mut output = vec![0; 32];
46+
// Should not be InvalidLength, since we hardcoded 32 bytes for the derived key length
47+
SimpleHkdf::<Sha256>::new(/*salt=*/ None, &derived_key)
48+
.expand_multi_info(key.as_info().as_slice(), &mut output)
49+
.unwrap();
50+
51+
Ok(base64::encode(&output))
52+
}
53+
}
54+
55+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56+
pub enum Key<'a> {
57+
DiskEncryptionKey { device_path: &'a Path },
58+
}
59+
60+
impl Key<'_> {
61+
fn as_info(&self) -> [&[u8]; 2] {
62+
// change to Vec once necessary
63+
match self {
64+
Key::DiskEncryptionKey { device_path } => [
65+
b"ic-disk-encryption-key",
66+
device_path.as_os_str().as_bytes(),
67+
],
68+
}
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use super::*;
75+
use crate::guest::firmware::MockSevGuestFirmware;
76+
use std::collections::HashSet;
77+
78+
#[test]
79+
fn test_derives_key() {
80+
let mut mock_sev_guest_firmware = MockSevGuestFirmware::new();
81+
mock_sev_guest_firmware
82+
.expect_get_derived_key()
83+
.returning(|_, _| Ok([42; 32]));
84+
let mut key_provider = SevKeyDeriver::new_for_test(Box::new(mock_sev_guest_firmware));
85+
86+
assert_eq!(
87+
key_provider
88+
.derive_key(Key::DiskEncryptionKey {
89+
device_path: Path::new("/dev/vda8")
90+
})
91+
.unwrap(),
92+
// This value does not have any particular meaning, but it should not change
93+
// unless the key derivation algorithm changes.
94+
"f0ap27QRjRgVHqeQBK8RO0GYHnSxYRLsgpJ8Ad3j/r8="
95+
);
96+
}
97+
98+
#[test]
99+
fn test_derives_unique_keys() {
100+
let mut mock_sev_guest_firmware = MockSevGuestFirmware::new();
101+
mock_sev_guest_firmware
102+
.expect_get_derived_key()
103+
.returning(|_, _| Ok([42; 32]));
104+
let mut key_provider = SevKeyDeriver::new_for_test(Box::new(mock_sev_guest_firmware));
105+
106+
let all_keys = (1..=10)
107+
.map(|i| {
108+
key_provider
109+
.derive_key(Key::DiskEncryptionKey {
110+
device_path: Path::new(&format!("/dev/vda{}", i)),
111+
})
112+
.unwrap()
113+
})
114+
.collect::<HashSet<String>>();
115+
116+
assert_eq!(all_keys.len(), 10, "Keys should be unique");
117+
}
118+
}

rs/ic_os/sev/src/guest/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod firmware;
2+
pub mod key_deriver;

rs/ic_os/sev/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod guest;
12
pub mod host;

0 commit comments

Comments
 (0)