aws_nitro_enclaves_nsm_api/api/
mod.rs

1// Copyright 2020-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4#![deny(missing_docs)]
5#![allow(clippy::upper_case_acronyms)]
6//! NitroSecurityModule IO
7//! # Overview
8//! This module contains the structure definitions that allows data interchange between
9//! a NitroSecureModule and the client using it. It uses CBOR to encode the data to allow
10//! easy IPC between components.
11
12// BTreeMap preserves ordering, which makes the tests easier to write
13use std::collections::{BTreeMap, BTreeSet};
14use std::io::Error as IoError;
15use std::result;
16
17use serde::{Deserialize, Serialize};
18use serde_bytes::ByteBuf;
19use serde_cbor::error::Error as CborError;
20use serde_cbor::{from_slice, to_vec};
21
22#[derive(Debug)]
23/// Possible error types return from this library.
24pub enum Error {
25    /// An IO error of type `std::io::Error`
26    Io(IoError),
27    /// A CBOR ser/de error of type `serde_cbor::error::Error`.
28    Cbor(CborError),
29}
30
31/// Result type return nsm-io::Error on failure.
32pub type Result<T> = result::Result<T, Error>;
33
34impl From<IoError> for Error {
35    fn from(error: IoError) -> Self {
36        Error::Io(error)
37    }
38}
39
40impl From<CborError> for Error {
41    fn from(error: CborError) -> Self {
42        Error::Cbor(error)
43    }
44}
45
46/// List of error codes that the NSM module can return as part of a Response
47#[repr(C)]
48#[derive(Debug, Serialize, Deserialize)]
49pub enum ErrorCode {
50    /// No errors
51    Success,
52
53    /// Input argument(s) invalid
54    InvalidArgument,
55
56    /// PlatformConfigurationRegister index out of bounds
57    InvalidIndex,
58
59    /// The received response does not correspond to the earlier request
60    InvalidResponse,
61
62    /// PlatformConfigurationRegister is in read-only mode and the operation
63    /// attempted to modify it
64    ReadOnlyIndex,
65
66    /// Given request cannot be fulfilled due to missing capabilities
67    InvalidOperation,
68
69    /// Operation succeeded but provided output buffer is too small
70    BufferTooSmall,
71
72    /// The user-provided input is too large
73    InputTooLarge,
74
75    /// NitroSecureModule cannot fulfill request due to internal errors
76    InternalError,
77}
78
79/// Operations that a NitroSecureModule should implement. Assumes 64K registers will be enough for everyone.
80#[derive(Debug, Serialize, Deserialize)]
81#[non_exhaustive]
82pub enum Request {
83    /// Read data from PlatformConfigurationRegister at `index`
84    DescribePCR {
85        /// index of the PCR to describe
86        index: u16,
87    },
88
89    /// Extend PlatformConfigurationRegister at `index` with `data`
90    ExtendPCR {
91        /// index the PCR to extend
92        index: u16,
93
94        #[serde(with = "serde_bytes")]
95        /// data to extend it with
96        data: Vec<u8>,
97    },
98
99    /// Lock PlatformConfigurationRegister at `index` from further modifications
100    LockPCR {
101        /// index to lock
102        index: u16,
103    },
104
105    /// Lock PlatformConfigurationRegisters at indexes `[0, range)` from further modifications
106    LockPCRs {
107        /// number of PCRs to lock, starting from index 0
108        range: u16,
109    },
110
111    /// Return capabilities and version of the connected NitroSecureModule. Clients are recommended to decode
112    /// major_version and minor_version first, and use an appropriate structure to hold this data, or fail
113    /// if the version is not supported.
114    DescribeNSM,
115
116    /// Requests the NSM to create an AttestationDoc and sign it with it's private key to ensure
117    /// authenticity.
118    Attestation {
119        /// Includes additional user data in the AttestationDoc.
120        user_data: Option<ByteBuf>,
121
122        /// Includes an additional nonce in the AttestationDoc.
123        nonce: Option<ByteBuf>,
124
125        /// Includes a user provided public key in the AttestationDoc.
126        public_key: Option<ByteBuf>,
127    },
128
129    /// Requests entropy from the NSM side.
130    GetRandom,
131}
132
133/// Responses received from a NitroSecureModule as a result of a Request
134#[derive(Debug, Serialize, Deserialize)]
135#[non_exhaustive]
136pub enum Response {
137    /// returns the current PlatformConfigurationRegister state
138    DescribePCR {
139        /// true if the PCR is read-only, false otherwise
140        lock: bool,
141        #[serde(with = "serde_bytes")]
142        /// the current value of the PCR
143        data: Vec<u8>,
144    },
145
146    /// returned if PlatformConfigurationRegister has been successfully extended
147    ExtendPCR {
148        #[serde(with = "serde_bytes")]
149        /// The new value of the PCR after extending the data into the register.
150        data: Vec<u8>,
151    },
152
153    /// returned if PlatformConfigurationRegister has been successfully locked
154    LockPCR,
155
156    /// returned if PlatformConfigurationRegisters have been successfully locked
157    LockPCRs,
158
159    /// returns the runtime configuration of the NitroSecureModule
160    DescribeNSM {
161        /// Breaking API changes are denoted by `major_version`
162        version_major: u16,
163        /// Minor API changes are denoted by `minor_version`. Minor versions should be backwards compatible.
164        version_minor: u16,
165        /// Patch version. These are security and stability updates and do not affect API.
166        version_patch: u16,
167        /// `module_id` is an identifier for a singular NitroSecureModule
168        module_id: String,
169        /// The maximum number of PCRs exposed by the NitroSecureModule.
170        max_pcrs: u16,
171        /// The PCRs that are read-only.
172        locked_pcrs: BTreeSet<u16>,
173        /// The digest of the PCR Bank
174        digest: Digest,
175    },
176
177    /// A response to an Attestation Request containing the CBOR-encoded AttestationDoc and the
178    /// signature generated from the doc by the NitroSecureModule
179    Attestation {
180        /// A signed COSE structure containing a CBOR-encoded AttestationDocument as the payload.
181        #[serde(with = "serde_bytes")]
182        document: Vec<u8>,
183    },
184
185    /// A response containing a number of bytes of entropy.
186    GetRandom {
187        #[serde(with = "serde_bytes")]
188        /// The random bytes.
189        random: Vec<u8>,
190    },
191
192    /// An error has occured, and the NitroSecureModule could not successfully complete the operation
193    Error(ErrorCode),
194}
195
196/// The digest implementation used by a NitroSecureModule
197#[repr(C)]
198#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq)]
199pub enum Digest {
200    /// SHA256
201    SHA256,
202    /// SHA384
203    SHA384,
204    /// SHA512
205    SHA512,
206}
207
208/// An attestation response.  This is also used for sealing
209/// data.
210#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
211pub struct AttestationDoc {
212    /// Issuing NSM ID
213    pub module_id: String,
214
215    /// The digest function used for calculating the register values
216    /// Can be: "SHA256" | "SHA512"
217    pub digest: Digest,
218
219    /// UTC time when document was created expressed as milliseconds since Unix Epoch
220    pub timestamp: u64,
221
222    /// Map of all locked PCRs at the moment the attestation document was generated
223    pub pcrs: BTreeMap<usize, ByteBuf>,
224
225    /// The infrastucture certificate used to sign the document, DER encoded
226    pub certificate: ByteBuf,
227    /// Issuing CA bundle for infrastructure certificate
228    pub cabundle: Vec<ByteBuf>,
229
230    /// An optional DER-encoded key the attestation consumer can use to encrypt data with
231    pub public_key: Option<ByteBuf>,
232
233    /// Additional signed user data, as defined by protocol.
234    pub user_data: Option<ByteBuf>,
235
236    /// An optional cryptographic nonce provided by the attestation consumer as a proof of
237    /// authenticity.
238    pub nonce: Option<ByteBuf>,
239}
240
241impl AttestationDoc {
242    /// Creates a new AttestationDoc.
243    ///
244    /// # Arguments
245    ///
246    /// * module_id: a String representing the name of the NitroSecureModule
247    /// * digest: nsm_io::Digest that describes what the PlatformConfigurationRegisters
248    ///           contain
249    /// * pcrs: BTreeMap containing the index to PCR value
250    /// * certificate: the serialized certificate that will be used to sign this AttestationDoc
251    /// * cabundle: the serialized set of certificates up to the root of trust certificate that
252    ///             emitted `certificate`
253    /// * user_data: optional user definted data included in the AttestationDoc
254    /// * nonce: optional cryptographic nonce that will be included in the AttestationDoc
255    /// * public_key: optional DER-encoded public key that will be included in the AttestationDoc
256    #[allow(clippy::too_many_arguments)]
257    pub fn new(
258        module_id: String,
259        digest: Digest,
260        timestamp: u64,
261        pcrs: BTreeMap<usize, Vec<u8>>,
262        certificate: Vec<u8>,
263        cabundle: Vec<Vec<u8>>,
264        user_data: Option<Vec<u8>>,
265        nonce: Option<Vec<u8>>,
266        public_key: Option<Vec<u8>>,
267    ) -> Self {
268        let mut pcrs_serialized = BTreeMap::new();
269
270        for (i, pcr) in pcrs.into_iter() {
271            let pcr = ByteBuf::from(pcr);
272            pcrs_serialized.insert(i, pcr);
273        }
274
275        let cabundle_serialized = cabundle.into_iter().map(ByteBuf::from).collect();
276
277        AttestationDoc {
278            module_id,
279            digest,
280            timestamp,
281            pcrs: pcrs_serialized,
282            cabundle: cabundle_serialized,
283            certificate: ByteBuf::from(certificate),
284            user_data: user_data.map(ByteBuf::from),
285            nonce: nonce.map(ByteBuf::from),
286            public_key: public_key.map(ByteBuf::from),
287        }
288    }
289
290    /// Helper function that converts an AttestationDoc structure to its CBOR representation
291    pub fn to_binary(&self) -> Vec<u8> {
292        // This should not fail
293        to_vec(self).unwrap()
294    }
295
296    /// Helper function that parses a CBOR representation of an AttestationDoc and creates the
297    /// structure from it, if possible.
298    pub fn from_binary(bin: &[u8]) -> Result<Self> {
299        from_slice(bin).map_err(Error::Cbor)
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn test_attestationdoc_binary_encode() {
309        let mut pcrs = BTreeMap::new();
310        pcrs.insert(1, vec![1, 2, 3]);
311        pcrs.insert(2, vec![4, 5, 6]);
312        pcrs.insert(3, vec![7, 8, 9]);
313
314        let doc1 = AttestationDoc::new(
315            "abcd".to_string(),
316            Digest::SHA256,
317            1234,
318            pcrs,
319            vec![42; 10],
320            vec![],
321            Some(vec![255; 10]),
322            None,
323            None,
324        );
325        let bin1 = doc1.to_binary();
326        let doc2 = AttestationDoc::from_binary(&bin1).unwrap();
327        let bin2 = doc2.to_binary();
328        assert_eq!(doc1, doc2);
329        assert_eq!(bin1, bin2);
330    }
331}