sev/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! The `sev` crate provides an implementation of the [AMD Secure Encrypted
4//! Virtualization (SEV)][SEV] APIs and the [SEV Secure Nested Paging
5//! Firmware (SNP)][SNP] ABIs.
6//!
7//! [SEV]: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/programmer-references/55766_SEV-KM_API_Specification.pdf
8//! [SNP]: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
9//!
10//! ## SEV APIs
11//!
12//! The linux kernel exposes two technically distinct AMD SEV APIs:
13//!
14//! 1. An API for managing the SEV platform itself
15//! 2. An API for managing SEV-enabled KVM virtual machines
16//!
17//! This crate implements both of those APIs and offers them to client.
18//! code through a flexible and type-safe high-level interface.
19//!
20//! ## SNP ABIs
21//!
22//! Like SEV, the linux kernel exposes another two different AMD SEV-SNP ABIs:
23//!
24//! 1. An ABI for managing the SEV-SNP platform itself
25//! 2. An ABI for managing SEV-SNP enabled KVM virtual machines
26//!
27//! These new ABIs work only for **SEV-SNP** enabled hosts and guests.
28//!
29//! This crate implements APIs for both SEV and SEV-SNP management.
30//!
31//! ## SEV and SEV-SNP enablement
32//!
33//! By default, both the SEV and SEV-SNP libraries are compiled.
34//! Because many modules provide support to both legacy SEV and SEV-SNP, they have been split into individual sub-modules `sev.rs` and `snp.rs`, isolating generation specific behavior.
35//! If desired, you may opt to exclude either of the sub-modules by disabling its feature in your project's `Cargo.toml`  
36//!
37//! For example, to include the SEV APIs only:  
38//! `sev = { version = "1.2.1", default-features = false, features = ["sev"] }`  
39//!  
40//! To include the SEV-SNP APIs only:  
41//! `sev = { version = "1.2.1", default-features = false, features = ["snp"] }`  
42//!
43//! ## Platform Management
44//!
45//! Refer to the [firmware](crate::firmware) module for more information.
46//!
47//! ## Guest Management
48//!
49//! Refer to the [launch](crate::launch) module for more information.
50//!
51//! ## Cryptographic Verification
52//!
53//! To enable the cryptographic verification of certificate chains and
54//! attestation reports, either the `openssl` or `crypto_nossl` feature
55//! has to be enabled manually. With `openssl`, OpenSSL is used for the
56//! verification. With `crypto_nossl`, OpenSSL is _not_ used for the
57//! verification and instead pure-Rust libraries (e.g., `p384`, `rsa`,
58//! etc.) are used. `openssl` and `crypto_nossl` are mutually exclusive,
59//! and enabling both at the same time leads to a compiler error.
60//!
61//! ## Remarks
62//!
63//! Note that the linux kernel provides access to these APIs through a set
64//! of `ioctl`s that are meant to be called on device nodes (`/dev/kvm` and
65//! `/dev/sev`, to be specific). As a result, these `ioctl`s form the substrate
66//! of the `sev` crate. Binaries that result from consumers of this crate are
67//! expected to run as a process with the necessary privileges to interact
68//! with the device nodes.
69//!
70//! ## Using the C API
71//!
72//! Projects in C can take advantage of the C API for the SEV [launch] ioctls.
73//! To install the C API, users can use `cargo-c` with the features they would
74//! like to produce and install a `pkg-config` file, a static library, a dynamic
75//! library, and a C header:
76//!
77//! `cargo cinstall --prefix=/usr --libdir=/usr/lib64`
78//!
79//! [firmware]: ./src/firmware/
80//! [launch]: ./src/launch/
81
82#![deny(clippy::all)]
83#![deny(missing_docs)]
84#![allow(unknown_lints)]
85#![allow(clippy::identity_op)]
86#![allow(clippy::unreadable_literal)]
87
88#[cfg(all(feature = "openssl", feature = "crypto_nossl"))]
89compile_error!(
90    "feature \"openssl\" and feature \"crypto_nossl\" cannot be enabled at the same time"
91);
92
93/// SEV and SEV-SNP certificates interface.
94pub mod certs;
95
96pub mod firmware;
97#[cfg(target_os = "linux")]
98pub mod launch;
99#[cfg(all(
100    any(feature = "sev", feature = "snp"),
101    feature = "openssl",
102    target_os = "linux"
103))]
104pub mod measurement;
105#[cfg(all(target_os = "linux", feature = "openssl", feature = "sev"))]
106pub mod session;
107mod util;
108pub mod vmsa;
109
110/// Error module.
111pub mod error;
112
113/// Module for Encoding and Decoding types.
114pub mod parser;
115
116use crate::parser::Decoder;
117
118#[cfg(all(feature = "sev", feature = "dangerous_hw_tests"))]
119pub use util::cached_chain;
120
121#[cfg(all(feature = "openssl", feature = "sev"))]
122use certs::sev::sev;
123
124#[cfg(feature = "sev")]
125use certs::sev::ca::{Certificate, Chain as CertSevCaChain};
126
127#[cfg(all(
128    not(feature = "sev"),
129    feature = "snp",
130    any(feature = "openssl", feature = "crypto_nossl")
131))]
132use certs::snp::ca::Chain as CertSnpCaChain;
133
134#[cfg(feature = "sev")]
135use certs::sev::builtin as SevBuiltin;
136
137#[cfg(all(
138    not(feature = "sev"),
139    feature = "snp",
140    any(feature = "openssl", feature = "crypto_nossl")
141))]
142use certs::snp::builtin as SnpBuiltin;
143
144#[cfg(any(feature = "sev", feature = "snp"))]
145use std::convert::TryFrom;
146
147use std::io::{Read, Write};
148
149/// A representation for EPYC generational product lines.
150///
151/// Implements type conversion traits to determine which generation
152/// a given SEV certificate chain corresponds to. This is helpful for
153/// automatically detecting what platform code is running on, as one
154/// can simply export the SEV certificate chain and attempt to produce
155/// a `Generation` from it with the [TryFrom](
156/// https://doc.rust-lang.org/std/convert/trait.TryFrom.html) trait.
157///
158/// ## Example
159///
160/// ```no_run
161/// # #[cfg(features = "openssl")]
162/// # {
163///
164/// // NOTE: The conversion traits require the `sev` crate to have the
165/// // `openssl` feature enabled.
166///
167/// use std::convert::TryFrom;
168/// use sev::certs::sev::Usage;
169/// use sev::firmware::host::types::Firmware;
170/// use sev::Generation;
171///
172/// let mut firmware = Firmware::open().expect("failed to open /dev/sev");
173///
174/// let chain = firmware.pdh_cert_export()
175///     .expect("unable to export SEV certificates");
176///
177/// let id = firmware.get_identifier().expect("error fetching identifier");
178///
179/// // NOTE: Requesting a signed CEK from AMD's KDS has been omitted for
180/// // brevity.
181///
182/// let generation = Generation::try_from(&chain).expect("not a SEV/ES chain");
183/// match generation {
184///     Generation::Naples => println!("Naples"),
185///     Generation::Rome => println!("Rome"),
186/// }
187/// # }
188/// ```
189#[derive(Copy, Clone)]
190pub enum Generation {
191    /// First generation EPYC (SEV).
192    #[cfg(feature = "sev")]
193    Naples,
194
195    /// Second generation EPYC (SEV, SEV-ES).
196    #[cfg(feature = "sev")]
197    Rome,
198
199    /// Third generation EPYC (SEV, SEV-ES, SEV-SNP).
200    #[cfg(any(feature = "sev", feature = "snp"))]
201    Milan,
202
203    /// Fourth generation EPYC (SEV, SEV-ES, SEV-SNP).
204    #[cfg(any(feature = "sev", feature = "snp"))]
205    Genoa,
206
207    /// Fifth generation EPYC (SEV, SEV-ES, SEV-SNP).
208    #[cfg(any(feature = "sev", feature = "snp"))]
209    Turin,
210}
211
212#[cfg(feature = "snp")]
213impl TryFrom<&[u8]> for Generation {
214    type Error = std::io::Error;
215
216    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
217        if bytes.len() != 4 {
218            return Err(std::io::Error::new(
219                std::io::ErrorKind::InvalidData,
220                "invalid length of bytes representing cpuid",
221            ));
222        }
223
224        let base_model = (bytes[0] & 0xF0) >> 4;
225        let base_family = bytes[1] & 0x0F;
226
227        let ext_model = bytes[2] & 0x0F;
228
229        let ext_family = {
230            let low = (bytes[2] & 0xF0) >> 4;
231            let high = (bytes[3] & 0x0F) << 4;
232
233            low | high
234        };
235
236        let family = base_family + ext_family;
237        let model = (ext_model << 4) | base_model;
238
239        Self::identify_cpu(family, model)
240    }
241}
242
243/// Type alias for the CPU family
244#[cfg(feature = "snp")]
245pub type CpuFamily = u8;
246
247/// Type alias for the CPU model
248#[cfg(feature = "snp")]
249pub type CpuModel = u8;
250
251#[cfg(feature = "snp")]
252impl TryFrom<(CpuFamily, CpuModel)> for Generation {
253    type Error = std::io::Error;
254
255    fn try_from(val: (CpuFamily, CpuModel)) -> Result<Self, Self::Error> {
256        Self::identify_cpu(val.0, val.1)
257    }
258}
259
260#[cfg(feature = "snp")]
261impl Generation {
262    /// Identify the SEV generation based on the CPU family and model.
263    pub fn identify_cpu(family: u8, model: u8) -> Result<Self, std::io::Error> {
264        match family {
265            0x19 => match model {
266                0x0..=0xF => Ok(Self::Milan),
267                0x10..=0x1F | 0xA0..=0xAF => Ok(Self::Genoa),
268                _ => Err(std::io::Error::new(
269                    std::io::ErrorKind::InvalidData,
270                    "processor is not of know SEV-SNP model.",
271                )),
272            },
273            0x1A => match model {
274                0x0..=0x11 => Ok(Self::Turin),
275                _ => Err(std::io::Error::new(
276                    std::io::ErrorKind::InvalidData,
277                    "processor is not of know SEV-SNP model.",
278                )),
279            },
280            _ => Err(std::io::Error::new(
281                std::io::ErrorKind::InvalidData,
282                "processor is not of know SEV-SNP generation.",
283            )),
284        }
285    }
286
287    /// Identify the EPYC processor generation based on the CPUID instruction.
288    #[cfg(feature = "snp")]
289    pub fn identify_host_generation() -> Result<Self, std::io::Error> {
290        use std::convert::TryInto;
291
292        #[cfg(target_arch = "x86_64")]
293        return unsafe { std::arch::x86_64::__cpuid(0x8000_0001) }
294            .eax
295            .to_le_bytes()
296            .as_slice()
297            .try_into();
298
299        #[cfg(not(target_arch = "x86_64"))]
300        Err(std::io::Error::other(
301            "Cannot get EPYC generation on non-x86 platform",
302        ))
303    }
304}
305
306#[cfg(feature = "sev")]
307impl From<Generation> for CertSevCaChain {
308    fn from(generation: Generation) -> CertSevCaChain {
309        let (ark, ask) = match generation {
310            #[cfg(feature = "sev")]
311            Generation::Naples => (SevBuiltin::naples::ARK, SevBuiltin::naples::ASK),
312            #[cfg(feature = "sev")]
313            Generation::Rome => (SevBuiltin::rome::ARK, SevBuiltin::rome::ASK),
314            #[cfg(any(feature = "sev", feature = "snp"))]
315            Generation::Milan => (SevBuiltin::milan::ARK, SevBuiltin::milan::ASK),
316            #[cfg(any(feature = "sev", feature = "snp"))]
317            Generation::Genoa => (SevBuiltin::genoa::ARK, SevBuiltin::genoa::ASK),
318            #[cfg(any(feature = "sev", feature = "snp"))]
319            Generation::Turin => (SevBuiltin::turin::ARK, SevBuiltin::turin::ASK),
320        };
321
322        CertSevCaChain {
323            ask: Certificate::decode(&mut &*ask, ()).unwrap(),
324            ark: Certificate::decode(&mut &*ark, ()).unwrap(),
325        }
326    }
327}
328
329#[cfg(all(
330    not(feature = "sev"),
331    feature = "snp",
332    any(feature = "openssl", feature = "crypto_nossl")
333))]
334impl From<Generation> for CertSnpCaChain {
335    fn from(gen: Generation) -> CertSnpCaChain {
336        let (ark, ask) = match gen {
337            Generation::Milan => (
338                SnpBuiltin::milan::ark().unwrap(),
339                SnpBuiltin::milan::ask().unwrap(),
340            ),
341            Generation::Genoa => (
342                SnpBuiltin::genoa::ark().unwrap(),
343                SnpBuiltin::genoa::ask().unwrap(),
344            ),
345            Generation::Turin => (
346                SnpBuiltin::turin::ark().unwrap(),
347                SnpBuiltin::turin::ask().unwrap(),
348            ),
349        };
350
351        CertSnpCaChain { ark, ask }
352    }
353}
354
355#[cfg(all(feature = "sev", feature = "openssl"))]
356impl TryFrom<&sev::Chain> for Generation {
357    type Error = ();
358
359    fn try_from(schain: &sev::Chain) -> Result<Self, Self::Error> {
360        use crate::certs::sev::Verifiable;
361
362        let naples: CertSevCaChain = Generation::Naples.into();
363        let rome: CertSevCaChain = Generation::Rome.into();
364        let milan: CertSevCaChain = Generation::Milan.into();
365        let genoa: CertSevCaChain = Generation::Genoa.into();
366        let turin: CertSevCaChain = Generation::Turin.into();
367
368        Ok(if (&naples.ask, &schain.cek).verify().is_ok() {
369            Generation::Naples
370        } else if (&rome.ask, &schain.cek).verify().is_ok() {
371            Generation::Rome
372        } else if (&milan.ask, &schain.cek).verify().is_ok() {
373            Generation::Milan
374        } else if (&genoa.ask, &schain.cek).verify().is_ok() {
375            Generation::Genoa
376        } else if (&turin.ask, &schain.cek).verify().is_ok() {
377            Generation::Turin
378        } else {
379            return Err(());
380        })
381    }
382}
383
384#[cfg(any(feature = "sev", feature = "snp"))]
385impl TryFrom<String> for Generation {
386    type Error = ();
387
388    fn try_from(val: String) -> Result<Self, Self::Error> {
389        match &val.to_lowercase()[..] {
390            #[cfg(feature = "sev")]
391            "naples" => Ok(Self::Naples),
392
393            #[cfg(feature = "sev")]
394            "rome" => Ok(Self::Rome),
395
396            #[cfg(any(feature = "sev", feature = "snp"))]
397            "milan" => Ok(Self::Milan),
398
399            #[cfg(any(feature = "sev", feature = "snp"))]
400            "genoa" => Ok(Self::Genoa),
401
402            #[cfg(any(feature = "sev", feature = "snp"))]
403            "bergamo" => Ok(Self::Genoa),
404
405            #[cfg(any(feature = "sev", feature = "snp"))]
406            "siena" => Ok(Self::Genoa),
407
408            #[cfg(any(feature = "sev", feature = "snp"))]
409            "turin" => Ok(Self::Turin),
410
411            _ => Err(()),
412        }
413    }
414}
415
416#[cfg(any(feature = "sev", feature = "snp"))]
417impl Generation {
418    /// Create a title-cased string identifying the SEV generation.
419    pub fn titlecase(&self) -> String {
420        match self {
421            #[cfg(feature = "sev")]
422            Self::Naples => "Naples".to_string(),
423
424            #[cfg(feature = "sev")]
425            Self::Rome => "Rome".to_string(),
426
427            #[cfg(any(feature = "sev", feature = "snp"))]
428            Self::Milan => "Milan".to_string(),
429
430            #[cfg(any(feature = "sev", feature = "snp"))]
431            Self::Genoa => "Genoa".to_string(),
432
433            #[cfg(any(feature = "sev", feature = "snp"))]
434            Self::Turin => "Turin".to_string(),
435        }
436    }
437}