em_app/
lib.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#![allow(unexpected_cfgs)] // fortanixvme platform hasn't been upstreamed yet, but used in cfg
7pub extern crate serde_derive;
8
9pub mod mbedtls_hyper;
10
11pub mod utils;
12
13use em_node_agent_client::{models, Api, Client};
14use mbedtls::hash;
15use pkix::types::Name;
16use rustc_serialize::hex::FromHex;
17use std::borrow::Cow;
18use uuid::Uuid;
19
20mod platform;
21
22pub mod csr;
23pub use csr::*;
24
25pub mod error;
26pub use error::*;
27use yasna::models::TaggedDerValue;
28use yasna::tags::TAG_UTF8STRING;
29use sdkms::api_model::Blob;
30
31type Result<T> = std::result::Result<T, Error>;
32
33/// Result of the certificate issuance operation.
34pub struct FortanixEmCertificate {
35    // Signed fortanix certificate with attestation extension.
36    pub attestation_certificate_der: Option<Vec<u8>>,
37
38    // Node agent certificate.
39    pub node_certificate_der: Option<Vec<u8>>,
40
41    // Response
42    pub certificate_response: models::IssueCertificateResponse,
43}
44
45pub fn common_name_to_subject(common_name: &str) -> Name {
46    vec![(
47        pkix::oid::commonName.clone(),
48        TaggedDerValue::from_tag_and_bytes(TAG_UTF8STRING, common_name.as_bytes().to_vec()),
49    )]
50    .into()
51}
52
53pub fn get_certificate_status(url: &str, task_id: Uuid) -> Result<models::IssueCertificateResponse> {
54    let client = Client::try_new_http(url).map_err(|e| Error::NodeAgentClient(Box::new(e)))?;
55    client.get_issue_certificate_response(task_id).map_err(|e| Error::NodeAgentClient(Box::new(e)))
56}
57
58pub fn get_fortanix_em_certificate_subject(
59    url: &str,
60    subject: &Name,
61    signer: &mut dyn CsrSigner,
62) -> Result<FortanixEmCertificate> {
63    get_certificate_subject(url, subject, signer, None, None)
64}
65
66pub fn get_fortanix_em_certificate(
67    url: &str,
68    common_name: &str,
69    signer: &mut dyn CsrSigner,
70) -> Result<FortanixEmCertificate> {
71    get_certificate(url, common_name, signer, None, None)
72}
73
74
75pub fn get_certificate_subject(
76    url: &str,
77    subject: &Name,
78    signer: &mut dyn CsrSigner,
79    alt_names: Option<Vec<Cow<str>>>,
80    config_id: Option<&str>,
81) -> Result<FortanixEmCertificate> {
82    let pub_key = signer.get_public_key_der()?;
83    let user_data = get_user_data(&pub_key, config_id)?;
84
85    let (attestation_certificate_der, node_certificate_der, csr_pem) =
86        platform::get_remote_attestation_parameters_subject(
87            signer, url, subject, &user_data, alt_names,
88        )?;
89
90    let certificate_response = request_issue_certificate(url, csr_pem)?;
91
92    Ok(FortanixEmCertificate {
93        attestation_certificate_der,
94        node_certificate_der,
95        certificate_response,
96    })
97}
98
99pub fn get_certificate(
100    url: &str,
101    common_name: &str,
102    signer: &mut dyn CsrSigner,
103    alt_names: Option<Vec<Cow<str>>>,
104    config_id: Option<&str>,
105) -> Result<FortanixEmCertificate> {
106    let subject = common_name_to_subject(common_name);
107    get_certificate_subject(url, &subject, signer, alt_names, config_id)
108}
109
110pub fn get_remote_attestation_csr_subject(
111    url: &str,
112    subject: &Name,
113    signer: &mut dyn CsrSigner,
114    alt_names: Option<Vec<Cow<str>>>,
115    config_id: Option<&str>,
116) -> Result<String> {
117    let pub_key = signer.get_public_key_der()?;
118    let user_data = get_user_data(&pub_key, config_id)?;
119    let (_, _, csr_pem) = platform::get_remote_attestation_parameters_subject(
120        signer, url, subject, &user_data, alt_names,
121    )?;
122    Ok(csr_pem)
123}
124
125pub fn get_remote_attestation_csr(
126    url: &str,
127    common_name: &str,
128    signer: &mut dyn CsrSigner,
129    alt_names: Option<Vec<Cow<str>>>,
130    config_id: Option<&str>,
131) -> Result<String> {
132    let subject = common_name_to_subject(common_name);
133    get_remote_attestation_csr_subject(url, &subject, signer, alt_names, config_id)
134}
135
136pub fn request_issue_certificate(url: &str, csr_pem: String) -> Result<models::IssueCertificateResponse> {
137    let client = Client::try_new_http(url).map_err(|e| Error::NodeAgentClient(Box::new(e)))?;
138    let request = models::IssueCertificateRequest { csr: Some(csr_pem) };
139    client.issue_certificate(request).map_err(|e| Error::NodeAgentClient(Box::new(e)))
140}
141
142/// Computes application configuration hash from raw string
143pub fn compute_app_config_hash(app_config: &str, hash_type: hash::Type) -> Result<Blob> {
144    let mut digest = vec![0; 32];
145    hash::Md::hash(hash_type, app_config.as_bytes(), &mut digest).map_err(|e| Error::TargetReportHash(Box::new(e)))?;
146
147    Ok(Blob::from(digest.to_vec()))
148}
149
150fn get_user_data(pub_key: &Vec<u8>, config_id: Option<&str>) -> Result<[u8;64]> {
151    let mut data=[0u8;64];
152    hash::Md::hash(hash::Type::Sha256, &pub_key, &mut data).map_err(|e| Error::TargetReportHash(Box::new(e)))?;
153
154    if let Some(id) = config_id {
155        let id = id.from_hex().map_err(|e| Error::ConfigIdIssue(format!("Failed decoding config ID: {}", e)))?;
156        if id.len() != 32 {
157            return Err(Error::ConfigIdIssue(format!("config ID is invalid, length: {}, expected length: 32", id.len())));
158        }
159
160        let mut payload=[0u8;65];
161        payload[0] = 1;
162        payload[1..33].copy_from_slice(&data[0..32]);
163        payload[33..65].copy_from_slice(&id[0..32]);
164
165        // The payload is formed as follows in case of workflow report.
166
167        // First 32 bytes is a Sha256 of (Version + public key sha256 + config-id)
168        hash::Md::hash(hash::Type::Sha256, &payload, &mut data[0..32]).map_err(|e| Error::TargetReportHash(Box::new(e)))?;
169
170        // Second 32 bytes part is the actual config-id.
171        data[32..64].copy_from_slice(&id[0..32]);
172    }
173    // if non-workflow report then first 32 bytes is the hash of the public key, second 32 bytes are all 0.
174
175    Ok(data)
176}