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}