Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ hyperv = ["tss-esapi"]
clap = { version = "4.5", features = [ "derive" ] }
env_logger = "0.10.0"
anyhow = "1.0.69"
sev = { version = "6.2.1", default-features = false, features = ['openssl','snp']}
sev = { version = "6.3.0", default-features = false, features = ['openssl','snp']}
nix = "^0.23"
serde = { version = "1.0", features = ["derive"] }
bincode = "^1.2.1"
Expand Down
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,7 @@ snpguest fetch <SUBCOMMAND>

### 5. `key`

Creates the derived key based on input parameters and stores it. `$KEY_PATH` is the path to store the derived key. `$ROOT_KEY_SELECT` is the root key from which to derive the key (either "vcek" or "vmrk"). The `--guest_field_select` option specifies which Guest Field Select bits to enable as a 6-digit binary string. Each of the 6 bits from *right to left* correspond to Guest Policy, Image ID, Family ID, Measurement, SVN and TCB Version respectively. For each bit, 0 denotes off, and 1 denotes on. The `--guest_svn` option specifies the guest SVN to mix into the key, and the `--tcb_version` option specifies the TCB version to mix into the derived key. The `--vmpl` option specifies the VMPL level the Guest is running on and defaults to 1.

Creates the derived key based on input parameters and stores it. `$KEY_PATH` is the path to store the derived key. `$ROOT_KEY_SELECT` is the root key from which to derive the key (either "vcek" or "vmrk"). The `--guest_field_select` option specifies which Guest Field Select bits to enable as a binary string of length 6 or 7. Each of the bits from *right to left* correspond to Guest Policy, Image ID, Family ID, Measurement, SVN and TCB Version, Launch Mitigation Vector, respectively. For each bit, 0 denotes off, and 1 denotes on. The `--guest_svn` option specifies the guest SVN to mix into the key, the `--tcb_version` option specifies the TCB version to mix into the derived key, and the `--launch_mit_vector` option specifies the launch mitigation vector value to mix into the derived key. The `--vmpl` option specifies the VMPL level the Guest is running on and defaults to 1.

**Usage**
```bash
Expand All @@ -177,20 +176,23 @@ snpguest key $KEY_PATH $ROOT_KEY_SELECT [-v, --vmpl] [-g, --guest_field_select]
| `$KEY_PATH` | The path to store the derived key. | required |
| `$ROOT_KEY_SELECT` | is the root key from which to derive the key (either "vcek" or "vmrk"). | required |
| `-v, --vmpl $VMPL` | option specifies the VMPL level the Guest is running on. | 1 |
| `-g, --guest_field_select $GFS` | option specifies which Guest Field Select bits to enable as a 6-digit binary string. For each bit, 0 denotes off, and 1 denotes on. | |
| `-g, --guest_field_select $GFS` | option specifies which Guest Field Select bits to enable as a binary string (length 6 or 7). For each bit, 0 denotes off, and 1 denotes on. | all 0s |
| `-s, --guest_svn $GSVN` | option specifies the guest SVN to mix into the key. | — |
| `-t, --tcb_version $TCBV` | option specifies the TCB version to mix into the derived key. | — |
| `-l, --launch_mit_vector $LMV` | option specifies the launch mitigation vector value to mix into the derived key (only available for `MSG_KEY_REQ` version ≥ 2 ). | — |

**Guest Field Select**

| Bit | Description |
| :-- | :-- |
| 0 (LSB) | Guest Policy |
| 1 | Image ID |
| 2 | Family ID |
| 3 | Measurement |
| 4 | SVN |
| 5 (MSB) | TCB Version |
| Bit | Field | Note |
| :-- | :-- | :-- |
| 0 (LSB) | Guest Policy | |
| 1 | Image ID | |
| 2 | Family ID | |
| 3 | Measurement | |
| 4 | SVN | |
| 5 | TCB Version | |
| 6 (MSB) | Launch MIT Vector | Set to 0 if not specified; supported for `MSG_KEY_REQ` version ≥ 2. |
| (7–63) | (Reserved) | Currently not supported. |

For example, `--guest_field_select 110001` denotes
```
Expand All @@ -199,7 +201,8 @@ Image ID:Off (0),
Family ID: Off (0),
Measurement: Off (0),
SVN:On (1),
TCB Version:On (1).
TCB Version:On (1),
Launch MIT Vector: Off (none).
```

**Example**
Expand Down
40 changes: 30 additions & 10 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pub struct KeyArgs {
#[arg(short, long, value_name = "vmpl", default_value = "1")]
pub vmpl: Option<u32>,

/// Specify which Guest Field Select bits to enable. It is a 6 digit binary string. For each bit, 0 denotes off and 1 denotes on.
/// The least significant (rightmost) bit is Guest Policy followed by Image ID, Family ID, Measurement, SVN, TCB Version which is the most significant (leftmost) bit.
#[arg(short, long = "guest_field_select", value_name = "######")]
/// Specify which Guest Field Select bits to enable. It is a 6 or 7 digit binary string. For each bit, 0 denotes off and 1 denotes on.
/// The least significant (rightmost) bit is Guest Policy followed by Image ID, Family ID, Measurement, SVN, TCB Version, and Launch Mitigation Vector which is the most significant (leftmost) bit.
#[arg(short, long = "guest_field_select", value_name = "#######")]
pub gfs: Option<String>,

/// Specify the guest SVN to mix into the key. Must not exceed the guest SVN provided at launch in the ID block.
Expand All @@ -32,6 +32,10 @@ pub struct KeyArgs {
/// Specify the TCB version to mix into the derived key. Must not exceed CommittedTcb.
#[arg(short, long = "tcb_version")]
pub tcbv: Option<u64>,

/// Specify the launch mitigation vector to mix into the derived key.
#[arg(short, long = "launch_mit_vector")]
pub lmv: Option<u64>,
}

pub fn get_derived_key(args: KeyArgs) -> Result<()> {
Expand All @@ -54,12 +58,18 @@ pub fn get_derived_key(args: KeyArgs) -> Result<()> {

let gfs = match args.gfs {
Some(gfs) => {
let value: u64 = u64::from_str_radix(gfs.as_str(), 2).unwrap();
if value <= 63 {
value
} else {
return Err(anyhow::anyhow!("Invalid Guest Field Select option."));
if gfs.len() != 6 && gfs.len() != 7 {
return Err(anyhow::anyhow!(
"Invalid Guest Field Select option. Must be 6 or 7 digits."
));
}
if gfs.chars().any(|c| c != '0' && c != '1') {
return Err(anyhow::anyhow!(
"Invalid Guest Field Select option. Must be a binary string."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we perhaps want to make this input hex? Seems like that would be more reasonable.

Copy link
Contributor Author

@hyperfinitism hyperfinitism Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree — specifying this as a fixed-length hex string (16 characters for 64 bits) could reduce confusion compared to the current 6–7 bit binary format.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I support the idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we switch the --guest-field-select argument to hex, should we also unify other inputs such as --launch-mit-vector and --tcbv, which are currently parsed as decimal, into hex as well?

Alternatively, we could provide a parser like:

fn parse_u64_multi_format(s: &str) -> Result<u64, ParseIntError> {
    if let Some(hex) = s.strip_prefix("0x") {
        u64::from_str_radix(hex, 16)
    } else if let Some(bin) = s.strip_prefix("0b") {
        u64::from_str_radix(bin, 2)
    } else {
        u64::from_str_radix(s, 10)
    }
}

and then use it as follows:

#[arg(short, long = "guest_field_select", value_parser = parse_u64_multi_format)]
pub gfs: Option<u64>,

This would allow us to support multiple formats. (Though requiring a 0x prefix for hex values might be slightly inconvenient.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about this, and I like the idea of supporting multiple formats. There isn’t really a single obvious way to provide this information.

The reason GuestFieldSelect was made a binary string is that each field is represented by a binary number—1 meaning enabled and 0 meaning disabled. Meanwhile, the TCB is also a u64 field, but it has a completely different meaning since you’re providing values of the TCB, where each value can be expressed as a decimal. Then, the MIT VECTOR is a vector of values, so hex makes the most sense there.

If you could extend the parser so we can see what that would look like, we could then implement the same approach in other functionality like this:
#117

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! I'm thinking of implementing this as a separate module (e.g., src/clparser/*) so that it can be reused easily across different subcommands. Using it from each subcommand should be straightforward.

For this PR, since its primary purpose is to add support for the new ABI version, I would suggest keeping (inheriting) the current implementation (--guest_field_select as binary), and then opening a follow-up PR to introduce the multi-format parser. Do you think that separation makes sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, maybe we will cut a minor release when this PR is merged and then make bigger changes when 7.0.0 and the new parser implementation is merged.

));
}
let value: u64 = u64::from_str_radix(gfs.as_str(), 2).unwrap();
value
}
None => 0,
};
Expand All @@ -68,10 +78,20 @@ pub fn get_derived_key(args: KeyArgs) -> Result<()> {

let tcbv: u64 = args.tcbv.unwrap_or(0);

let request = DerivedKey::new(root_key_select, GuestFieldSelect(gfs), vmpl, gsvn, tcbv);
let request = DerivedKey::new(
root_key_select,
GuestFieldSelect(gfs),
vmpl,
gsvn,
tcbv,
args.lmv,
);

let msg_ver = if args.lmv.is_some() { 2 } else { 1 };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The logic for msg_ver may need clarification for future maintainers.

Please document the dependency between msg_ver and lmv, or ensure updates to lmv semantics are consistently reflected in this logic.

Suggested change
let msg_ver = if args.lmv.is_some() { 2 } else { 1 };
// The message version (msg_ver) is determined by the presence of the 'lmv' argument.
// If 'lmv' is Some, we use version 2; otherwise, we use version 1.
// IMPORTANT: If the semantics of 'lmv' change, ensure this logic is updated accordingly.
let msg_ver = if args.lmv.is_some() { 2 } else { 1 };


let mut sev_fw = Firmware::open().context("failed to open SEV firmware device.")?;
let derived_key: [u8; 32] = sev_fw
.get_derived_key(None, request)
.get_derived_key(Some(msg_ver), request)
.context("Failed to request derived key")?;

// Create derived key path
Expand Down
Loading