blob: b8dc3de698ec886a09c4bdfaf721447f4e13d3b9 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License..
#![allow(non_snake_case)]
extern crate base64;
extern crate base64_url;
extern crate jsonwebkey;
extern crate jsonwebtoken;
extern crate libloading;
extern crate serde;
extern crate serde_json;
extern crate sgx_types;
extern crate sgx_urts;
extern crate sha2;
extern crate x509_certificate;
use std::convert::{TryFrom, TryInto};
use base64::{engine::general_purpose, Engine as _};
use jsonwebkey::{JsonWebKey, Key, PublicExponent, RsaPublic};
use jsonwebtoken as jwt;
use serde::{Deserialize, Serialize};
use sgx_types::*;
use sgx_urts::SgxEnclave;
use sha2::{Digest, Sha256};
use x509_certificate::{X509Certificate, X509CertificateError};
static ENCLAVE_FILE: &'static str = "enclave.signed.so";
const ATTESTATION_PROVIDER_URL: &'static str = "https://sharedeus.eus.attest.azure.net";
const SGX_ATTESTATION_URI: &'static str = "/attest/SgxEnclave?api-version=2022-08-01";
extern "C" {
fn enclave_create_report(
eid: sgx_enclave_id_t,
retval: *mut i32,
p_qe3_target: &sgx_target_info_t,
p_report_data: &sgx_report_data_t,
p_report: *mut sgx_report_t,
) -> sgx_status_t;
}
fn init_enclave() -> SgxResult<SgxEnclave> {
let mut launch_token: sgx_launch_token_t = [0; 1024];
let mut launch_token_updated: i32 = 0;
// call sgx_create_enclave to initialize an enclave instance
// Debug Support: set 2nd parameter to 1
let debug = 0;
let mut misc_attr = sgx_misc_attribute_t {
secs_attr: sgx_attributes_t { flags: 0, xfrm: 0 },
misc_select: 0,
};
SgxEnclave::create(
ENCLAVE_FILE,
debug,
&mut launch_token,
&mut launch_token_updated,
&mut misc_attr,
)
}
// Re-invent App/utility.cpp
// int generate_quote(uint8_t **quote_buffer, uint32_t& quote_size)
fn generate_quote(runtime_data: &[u8]) -> Option<Vec<u8>> {
let mut ti: sgx_target_info_t = sgx_target_info_t::default();
println!("Step1: Call sgx_qe_get_target_info:");
//println!("sgx_qe_get_target_info = {:p}", sgx_qe_get_target_info as * const _);
let qe3_ret = unsafe { sgx_qe_get_target_info(&mut ti as *mut _) };
if qe3_ret != sgx_quote3_error_t::SGX_QL_SUCCESS {
println!("Error in sgx_qe_get_target_info. {:?}\n", qe3_ret);
return None;
}
//println!("target_info.mr_enclave = {:?}", ti.mr_enclave.m);
//println!("target_info.config_id = {:02x}", ti.config_id.iter().format(" "));
println!("succeed!\nStep2: Call create_app_report:");
let app_report: sgx_report_t = if let Some(r) = create_app_enclave_report(&ti, &runtime_data) {
println!("succeed! \nStep3: Call sgx_qe_get_quote_size:");
r
} else {
println!("\nCall to create_app_report() failed\n");
return None;
};
println!(
"app_report.body.mr_enclave = {:x?}",
app_report.body.mr_enclave.m
);
println!(
"app_report.body.mr_signer = {:x?}",
app_report.body.mr_signer.m
);
// println!(
// "app_report.body.misc_select = {:08x}",
// app_report.body.misc_select
// );
let mut quote_size: u32 = 0;
let qe3_ret = unsafe { sgx_qe_get_quote_size(&mut quote_size as _) };
if qe3_ret != sgx_quote3_error_t::SGX_QL_SUCCESS {
println!("Error in sgx_qe_get_quote_size . {:?}\n", qe3_ret);
return None;
}
println!("succeed!");
let mut quote_vec: Vec<u8> = vec![0; quote_size as usize];
println!("\nStep4: Call sgx_qe_get_quote:");
let qe3_ret =
unsafe { sgx_qe_get_quote(&app_report as _, quote_size, quote_vec.as_mut_ptr() as _) };
if qe3_ret != sgx_quote3_error_t::SGX_QL_SUCCESS {
println!("Error in sgx_qe_get_quote. {:?}\n", qe3_ret);
return None;
}
println!("succeed!");
Some(quote_vec)
}
fn create_app_enclave_report(
qe_ti: &sgx_target_info_t,
runtime_data: &[u8],
) -> Option<sgx_report_t> {
let enclave = if let Ok(r) = init_enclave() {
r
} else {
return None;
};
let mut retval = 0;
let mut ret_report: sgx_report_t = sgx_report_t::default();
let mut report_data = sgx_report_data_t::default();
let mut hasher = Sha256::new();
hasher.update(runtime_data);
let res = hasher.finalize();
report_data.d[..32].copy_from_slice(&res);
let result = unsafe {
enclave_create_report(
enclave.geteid(),
&mut retval,
qe_ti,
&report_data,
&mut ret_report as *mut sgx_report_t,
)
};
match result {
sgx_status_t::SGX_SUCCESS => {}
_ => {
println!("[-] ECALL Enclave Failed {}!", result.as_str());
return None;
}
}
enclave.destroy();
Some(ret_report)
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct AzureSgxAttestationRequest {
quote: String,
runtime_data: SgxRuntimeData,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct SgxRuntimeData {
data: String,
data_type: String,
}
impl SgxRuntimeData {
fn new_binary(data: &[u8]) -> Self {
Self {
data: base64_url::encode(data),
data_type: "Binary".to_string(),
}
}
}
#[derive(Deserialize)]
struct JwtResponse {
pub token: String,
}
#[derive(Deserialize)]
struct RawJsonWebKey {
pub x5c: Vec<String>,
pub kid: String,
pub kty: String,
// pub alg: String,
}
#[derive(Deserialize)]
struct RawJsonWebKeySet {
pub keys: Vec<RawJsonWebKey>,
}
#[derive(Debug)]
struct JsonWebKeySet {
pub keys: Vec<JsonWebKey>,
}
impl TryFrom<RawJsonWebKeySet> for JsonWebKeySet {
type Error = X509CertificateError;
// This method only works for RS256 algorithm using by Azure Attestation
fn try_from(raw_set: RawJsonWebKeySet) -> Result<Self, X509CertificateError> {
let mut keys = Vec::new();
for key in raw_set.keys {
if key.kty != "RSA" {
return Err(X509CertificateError::UnknownKeyAlgorithm(key.kty));
}
let raw_cert = general_purpose::STANDARD
.decode(key.x5c[0].clone())
.unwrap();
let x509 = X509Certificate::from_der(&raw_cert)?;
let pubkey = x509.rsa_public_key_data()?;
let rsa_pub = RsaPublic {
e: PublicExponent,
n: pubkey.modulus.as_slice().into(),
};
let rsa_key = Key::RSA {
public: rsa_pub,
private: None,
};
let mut jwk = JsonWebKey::new(rsa_key);
jwk.key_id = Some(key.kid);
keys.push(jwk);
}
Ok(JsonWebKeySet { keys })
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct TokenClaims {
pub x_ms_sgx_is_debuggable: bool,
pub x_ms_sgx_mrenclave: String,
pub x_ms_sgx_mrsigner: String,
// pub x_ms_sgx_product_id: u64,
pub x_ms_sgx_svn: u64,
pub x_ms_sgx_ehd: String,
}
/// Validate JsonWebToken with JsonWebKeySet,
/// only works for RS256 algorithm and token from default attestation provider.
fn validate_json_web_token(jwt: String, jwks: JsonWebKeySet) -> jwt::errors::Result<TokenClaims> {
let header = jwt::decode_header(&jwt)?;
if header.kid.is_none() {
return Err(jwt::errors::Error::from(
jwt::errors::ErrorKind::InvalidToken,
));
}
// find the corresponding key
let mut idx: Option<usize> = None;
for (i, key) in jwks.keys.iter().enumerate() {
if key.key_id.is_some() {
if key.key_id == header.kid {
idx = Some(i);
}
}
}
if idx.is_none() {
// cannot find corresponding pubkey
return Err(jwt::errors::Error::from(
jwt::errors::ErrorKind::InvalidRsaKey,
));
}
let pem = jwks.keys[idx.unwrap()].key.try_to_pem().unwrap();
// println!("\n{pem}");
let key = jwt::DecodingKey::from_rsa_pem(pem.as_bytes())?;
// prepare validation
let algo = jwt::Algorithm::RS256;
let mut validation = jwt::Validation::new(algo);
validation.validate_exp = false;
validation.iss = Some(ATTESTATION_PROVIDER_URL.to_string());
// decode JWT with the public key
Ok(jwt::decode::<TokenClaims>(&jwt, &key, &validation)?.claims)
}
fn main() {
// The runtime data shall be generated by the enclave in the runtime and this static byte array is just for demo purpose.
let runtime_data = b"This is some runtime data";
// generate a quote using runtime data
let quote = generate_quote(runtime_data).unwrap();
// This part could be a SGX OCALL and request for a JWT from Azure Attestation
let attest_request = AzureSgxAttestationRequest {
quote: base64_url::encode(&quote),
runtime_data: SgxRuntimeData::new_binary(runtime_data),
};
// request for azure attestation
let client = reqwest::blocking::Client::new();
let res = client
.post(format!(
"{}{}",
ATTESTATION_PROVIDER_URL, SGX_ATTESTATION_URI
))
.json(&attest_request)
.send()
.unwrap();
let jwt = res.json::<JwtResponse>().unwrap().token;
// The below code could be your client-side code to validate the JWT
// get public key from azure attestation
let res = client
.get(format!("{}/certs", ATTESTATION_PROVIDER_URL))
.send()
.unwrap();
let resp_text = res.text().unwrap();
// println!("{resp_text}");
let raw_key_set: RawJsonWebKeySet = serde_json::from_str(&resp_text).unwrap();
let jwk_set: JsonWebKeySet = raw_key_set.try_into().unwrap();
let claims = validate_json_web_token(jwt, jwk_set).unwrap();
println!(
"Verified SGX debuggable status: {}",
claims.x_ms_sgx_is_debuggable
);
println!(
"Verified SGX enclave measurement: {}",
claims.x_ms_sgx_mrenclave
);
println!(
"Verified SGX signer measurement: {}",
claims.x_ms_sgx_mrsigner
);
println!("Verified SGX SGX SVN: {}", claims.x_ms_sgx_svn);
println!(
"Verified SGX runtime data: {}",
std::str::from_utf8(&base64_url::decode(&claims.x_ms_sgx_ehd).unwrap()).unwrap()
);
}
#[cfg(test)]
mod tests {
use super::*;
const RAW_KEY_SET: &str = r#"{
"keys": [
{
"x5c": [
"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RU52Y25CdmNtRjBhVzl1TVJRd0VnWURWUVFIREF0VFlXNTBZU0JEYkdGeVlURUxNQWtHQTFVRQpDQXdDUTBFeEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJek1Ea3hPVEV6TkRVMU1Gb1hEVE13TURreE9URXpORFUxCk1Gb3djREVpTUNBR0ExVUVBd3daU1c1MFpXd2dVMGRZSUZCRFN5QkRaWEowYVdacFkyRjBaVEVhTUJnR0ExVUUKQ2d3UlNXNTBaV3dnUTI5eWNHOXlZWFJwYjI0eEZEQVNCZ05WQkFjTUMxTmhiblJoSUVOc1lYSmhNUXN3Q1FZRApWUVFJREFKRFFURUxNQWtHQTFVRUJoTUNWVk13V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRGCkVtTmd0c1VmcWVHR2FvNmQrZVp6ZTFaQXEyWGlyRWVaZkY1VHFhTGhQREFXTWlOVWxZcUZHUmV1aXhjeUV0L0EKbk9ORjFPanBLWHYzUHFtZWRQa01vNElDcURDQ0FxUXdId1lEVlIwakJCZ3dGb0FVME9pcTJuWFgrUzVKRjVnOApleFJsME5YeVdVMHdiQVlEVlIwZkJHVXdZekJob0YrZ1hZWmJhSFIwY0hNNkx5OWhjR2t1ZEhKMWMzUmxaSE5sCmNuWnBZMlZ6TG1sdWRHVnNMbU52YlM5elozZ3ZZMlZ5ZEdsbWFXTmhkR2x2Ymk5Mk15OXdZMnRqY213L1kyRTkKY0hKdlkyVnpjMjl5Sm1WdVkyOWthVzVuUFdSbGNqQWRCZ05WSFE0RUZnUVV1dWJNV0xtZjhzN1llMTE2TjIvTgplaWZtRFU4d0RnWURWUjBQQVFIL0JBUURBZ2JBTUF3R0ExVWRFd0VCL3dRQ01BQXdnZ0hVQmdrcWhraUcrRTBCCkRRRUVnZ0hGTUlJQndUQWVCZ29xaGtpRytFMEJEUUVCQkJESzdoOHlrZmFQNHFhNmtBZVhyOWVCTUlJQlpBWUsKS29aSWh2aE5BUTBCQWpDQ0FWUXdFQVlMS29aSWh2aE5BUTBCQWdFQ0FSVXdFQVlMS29aSWh2aE5BUTBCQWdJQwpBUlV3RUFZTEtvWklodmhOQVEwQkFnTUNBUUl3RUFZTEtvWklodmhOQVEwQkFnUUNBUVF3RUFZTEtvWklodmhOCkFRMEJBZ1VDQVFFd0VRWUxLb1pJaHZoTkFRMEJBZ1lDQWdDQU1CQUdDeXFHU0liNFRRRU5BUUlIQWdFT01CQUcKQ3lxR1NJYjRUUUVOQVFJSUFnRUFNQkFHQ3lxR1NJYjRUUUVOQVFJSkFnRUFNQkFHQ3lxR1NJYjRUUUVOQVFJSwpBZ0VBTUJBR0N5cUdTSWI0VFFFTkFRSUxBZ0VBTUJBR0N5cUdTSWI0VFFFTkFRSU1BZ0VBTUJBR0N5cUdTSWI0ClRRRU5BUUlOQWdFQU1CQUdDeXFHU0liNFRRRU5BUUlPQWdFQU1CQUdDeXFHU0liNFRRRU5BUUlQQWdFQU1CQUcKQ3lxR1NJYjRUUUVOQVFJUUFnRUFNQkFHQ3lxR1NJYjRUUUVOQVFJUkFnRU5NQjhHQ3lxR1NJYjRUUUVOQVFJUwpCQkFWRlFJRUFZQU9BQUFBQUFBQUFBQUFNQkFHQ2lxR1NJYjRUUUVOQVFNRUFnQUFNQlFHQ2lxR1NJYjRUUUVOCkFRUUVCZ0NRYnRVQUFEQVBCZ29xaGtpRytFMEJEUUVGQ2dFQU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lBb00KMFMvOU5IbmpjQnB4bE43d0ZSSDB4N0VGWnNOKzI4aDlQdTkvRkJsWEFpRUE1Sm1XOFhERVBmY3pDUnJqR0VDMQpxbFA5akVyQnJFYWUyZno1TDdqWXl5ND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ21EQ0NBajZnQXdJQkFnSVZBTkRvcXRwMTEva3VTUmVZUEhzVVpkRFY4bGxOTUFvR0NDcUdTTTQ5QkFNQwpNR2d4R2pBWUJnTlZCQU1NRVVsdWRHVnNJRk5IV0NCU2IyOTBJRU5CTVJvd0dBWURWUVFLREJGSmJuUmxiQ0JECmIzSndiM0poZEdsdmJqRVVNQklHQTFVRUJ3d0xVMkZ1ZEdFZ1EyeGhjbUV4Q3pBSkJnTlZCQWdNQWtOQk1Rc3cKQ1FZRFZRUUdFd0pWVXpBZUZ3MHhPREExTWpFeE1EVXdNVEJhRncwek16QTFNakV4TURVd01UQmFNSEV4SXpBaApCZ05WQkFNTUdrbHVkR1ZzSUZOSFdDQlFRMHNnVUhKdlkyVnpjMjl5SUVOQk1Sb3dHQVlEVlFRS0RCRkpiblJsCmJDQkRiM0p3YjNKaGRHbHZiakVVTUJJR0ExVUVCd3dMVTJGdWRHRWdRMnhoY21FeEN6QUpCZ05WQkFnTUFrTkIKTVFzd0NRWURWUVFHRXdKVlV6QlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJMOXErTk1wMklPZwp0ZGwxYmsvdVdaNStUR1FtOGFDaTh6NzhmcytmS0NRM2QrdUR6WG5WVEFUMlpoRENpZnlJdUp3dk4zd05CcDlpCkhCU1NNSk1KckJPamdic3dnYmd3SHdZRFZSMGpCQmd3Rm9BVUltVU0xbHFkTkluemc3U1ZVcjlRR3prbkJxd3cKVWdZRFZSMGZCRXN3U1RCSG9FV2dRNFpCYUhSMGNITTZMeTlqWlhKMGFXWnBZMkYwWlhNdWRISjFjM1JsWkhObApjblpwWTJWekxtbHVkR1ZzTG1OdmJTOUpiblJsYkZOSFdGSnZiM1JEUVM1a1pYSXdIUVlEVlIwT0JCWUVGTkRvCnF0cDExL2t1U1JlWVBIc1VaZERWOGxsTk1BNEdBMVVkRHdFQi93UUVBd0lCQmpBU0JnTlZIUk1CQWY4RUNEQUcKQVFIL0FnRUFNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUUNKZ1RidFZxT3laMW0zanFpQVhNNlFZYTZyNXNXUwo0eS9HN3k4dUlKR3hkd0lnUnFQdkJTS3p6UWFnQkxRcTVzNUE3MHBkb2lhUko4ei8wdUR6NE5nVjkxaz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ2p6Q0NBalNnQXdJQkFnSVVJbVVNMWxxZE5JbnpnN1NWVXI5UUd6a25CcXd3Q2dZSUtvWkl6ajBFQXdJdwphREVhTUJnR0ExVUVBd3dSU1c1MFpXd2dVMGRZSUZKdmIzUWdRMEV4R2pBWUJnTlZCQW9NRVVsdWRHVnNJRU52CmNuQnZjbUYwYVc5dU1SUXdFZ1lEVlFRSERBdFRZVzUwWVNCRGJHRnlZVEVMTUFrR0ExVUVDQXdDUTBFeEN6QUoKQmdOVkJBWVRBbFZUTUI0WERURTRNRFV5TVRFd05EVXhNRm9YRFRRNU1USXpNVEl6TlRrMU9Wb3dhREVhTUJnRwpBMVVFQXd3UlNXNTBaV3dnVTBkWUlGSnZiM1FnUTBFeEdqQVlCZ05WQkFvTUVVbHVkR1ZzSUVOdmNuQnZjbUYwCmFXOXVNUlF3RWdZRFZRUUhEQXRUWVc1MFlTQkRiR0Z5WVRFTE1Ba0dBMVVFQ0F3Q1EwRXhDekFKQmdOVkJBWVQKQWxWVE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUM2bkV3TURJWVpPai9pUFdzQ3phRUtpNwoxT2lPU0xSRmhXR2pibkJWSmZWbmtZNHUzSWprRFlZTDBNeE80bXFzeVlqbEJhbFRWWXhGUDJzSkJLNXpsS09CCnV6Q0J1REFmQmdOVkhTTUVHREFXZ0JRaVpReldXcDAwaWZPRHRKVlN2MUFiT1NjR3JEQlNCZ05WSFI4RVN6QkoKTUVlZ1JhQkRoa0ZvZEhSd2N6b3ZMMk5sY25ScFptbGpZWFJsY3k1MGNuVnpkR1ZrYzJWeWRtbGpaWE11YVc1MApaV3d1WTI5dEwwbHVkR1ZzVTBkWVVtOXZkRU5CTG1SbGNqQWRCZ05WSFE0RUZnUVVJbVVNMWxxZE5JbnpnN1NWClVyOVFHemtuQnF3d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFFd0NnWUkKS29aSXpqMEVBd0lEU1FBd1JnSWhBT1cvNVFrUitTOUNpU0RjTm9vd0x1UFJMc1dHZi9ZaTdHU1g5NEJnd1R3ZwpBaUVBNEowbHJIb01zK1hvNW8vc1g2TzlRV3hIUkF2WlVHT2RSUTdjdnFSWGFxST0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoAMA0GCSqGSIb3DQEBCwUAA4IBAQCPIgeaN4IQA+MH6x4t7uqHLJ52vHVJ0m/DuLJ1DnekUrpRI44WbofC1hKmGPBcqV55sEzoyUH1WWAge3Lg0EBqogKddVmS04rE1He9QMVptiL9Bg1ahfZUjEdKYHriDzPo3KYs43nkXMRMFGWAdAuAdRoVVh6+g66M+iJ16KXxAQT5v6I+OAjwoivzEv6+6MpBt57/tm71iu2CyR5MEsOkEW6deHsKIEnZz0v4fydQfajVu49myXFsd6NFNbyk3Voira/OYuY0T8+eyfZMs5zmGY/waEEgr7U8igAxllV3/FCquZ/b86IRQ4VH7phYQ1oVbLvAem2huFV5LJuqzsfh"
],
"kid": "rFl9xM+g7TvX63y0iseZtIn20MD5SYAnGblKFasau8I=",
"kty": "RSA"
},
{
"x5c": [
"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"
],
"kid": "dSsaF5uUxZO_LRycmQ4KJu3ctMc",
"kty": "RSA"
},
{
"x5c": [
"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R0ExVUVBd3daU1c1MFpXd2dVMGRZSUZCRFN5QkRaWEowYVdacFkyRjBaVEVhTUJnR0ExVUUKQ2d3UlNXNTBaV3dnUTI5eWNHOXlZWFJwYjI0eEZEQVNCZ05WQkFjTUMxTmhiblJoSUVOc1lYSmhNUXN3Q1FZRApWUVFJREFKRFFURUxNQWtHQTFVRUJoTUNWVk13V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRGCkVtTmd0c1VmcWVHR2FvNmQrZVp6ZTFaQXEyWGlyRWVaZkY1VHFhTGhQREFXTWlOVWxZcUZHUmV1aXhjeUV0L0EKbk9ORjFPanBLWHYzUHFtZWRQa01vNElDcURDQ0FxUXdId1lEVlIwakJCZ3dGb0FVME9pcTJuWFgrUzVKRjVnOApleFJsME5YeVdVMHdiQVlEVlIwZkJHVXdZekJob0YrZ1hZWmJhSFIwY0hNNkx5OWhjR2t1ZEhKMWMzUmxaSE5sCmNuWnBZMlZ6TG1sdWRHVnNMbU52YlM5elozZ3ZZMlZ5ZEdsbWFXTmhkR2x2Ymk5Mk15OXdZMnRqY213L1kyRTkKY0hKdlkyVnpjMjl5Sm1WdVkyOWthVzVuUFdSbGNqQWRCZ05WSFE0RUZnUVV1dWJNV0xtZjhzN1llMTE2TjIvTgplaWZtRFU4d0RnWURWUjBQQVFIL0JBUURBZ2JBTUF3R0ExVWRFd0VCL3dRQ01BQXdnZ0hVQmdrcWhraUcrRTBCCkRRRUVnZ0hGTUlJQndUQWVCZ29xaGtpRytFMEJEUUVCQkJESzdoOHlrZmFQNHFhNmtBZVhyOWVCTUlJQlpBWUsKS29aSWh2aE5BUTBCQWpDQ0FWUXdFQVlMS29aSWh2aE5BUTBCQWdFQ0FSVXdFQVlMS29aSWh2aE5BUTBCQWdJQwpBUlV3RUFZTEtvWklodmhOQVEwQkFnTUNBUUl3RUFZTEtvWklodmhOQVEwQkFnUUNBUVF3RUFZTEtvWklodmhOCkFRMEJBZ1VDQVFFd0VRWUxLb1pJaHZoTkFRMEJBZ1lDQWdDQU1CQUdDeXFHU0liNFRRRU5BUUlIQWdFT01CQUcKQ3lxR1NJYjRUUUVOQVFJSUFnRUFNQkFHQ3lxR1NJYjRUUUVOQVFJSkFnRUFNQkFHQ3lxR1NJYjRUUUVOQVFJSwpBZ0VBTUJBR0N5cUdTSWI0VFFFTkFRSUxBZ0VBTUJBR0N5cUdTSWI0VFFFTkFRSU1BZ0VBTUJBR0N5cUdTSWI0ClRRRU5BUUlOQWdFQU1CQUdDeXFHU0liNFRRRU5BUUlPQWdFQU1CQUdDeXFHU0liNFRRRU5BUUlQQWdFQU1CQUcKQ3lxR1NJYjRUUUVOQVFJUUFnRUFNQkFHQ3lxR1NJYjRUUUVOQVFJUkFnRU5NQjhHQ3lxR1NJYjRUUUVOQVFJUwpCQkFWRlFJRUFZQU9BQUFBQUFBQUFBQUFNQkFHQ2lxR1NJYjRUUUVOQVFNRUFnQUFNQlFHQ2lxR1NJYjRUUUVOCkFRUUVCZ0NRYnRVQUFEQVBCZ29xaGtpRytFMEJEUUVGQ2dFQU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lBb00KMFMvOU5IbmpjQnB4bE43d0ZSSDB4N0VGWnNOKzI4aDlQdTkvRkJsWEFpRUE1Sm1XOFhERVBmY3pDUnJqR0VDMQpxbFA5akVyQnJFYWUyZno1TDdqWXl5ND0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ21EQ0NBajZnQXdJQkFnSVZBTkRvcXRwMTEva3VTUmVZUEhzVVpkRFY4bGxOTUFvR0NDcUdTTTQ5QkFNQwpNR2d4R2pBWUJnTlZCQU1NRVVsdWRHVnNJRk5IV0NCU2IyOTBJRU5CTVJvd0dBWURWUVFLREJGSmJuUmxiQ0JECmIzSndiM0poZEdsdmJqRVVNQklHQTFVRUJ3d0xVMkZ1ZEdFZ1EyeGhjbUV4Q3pBSkJnTlZCQWdNQWtOQk1Rc3cKQ1FZRFZRUUdFd0pWVXpBZUZ3MHhPREExTWpFeE1EVXdNVEJhRncwek16QTFNakV4TURVd01UQmFNSEV4SXpBaApCZ05WQkFNTUdrbHVkR1ZzSUZOSFdDQlFRMHNnVUhKdlkyVnpjMjl5SUVOQk1Sb3dHQVlEVlFRS0RCRkpiblJsCmJDQkRiM0p3YjNKaGRHbHZiakVVTUJJR0ExVUVCd3dMVTJGdWRHRWdRMnhoY21FeEN6QUpCZ05WQkFnTUFrTkIKTVFzd0NRWURWUVFHRXdKVlV6QlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJMOXErTk1wMklPZwp0ZGwxYmsvdVdaNStUR1FtOGFDaTh6NzhmcytmS0NRM2QrdUR6WG5WVEFUMlpoRENpZnlJdUp3dk4zd05CcDlpCkhCU1NNSk1KckJPamdic3dnYmd3SHdZRFZSMGpCQmd3Rm9BVUltVU0xbHFkTkluemc3U1ZVcjlRR3prbkJxd3cKVWdZRFZSMGZCRXN3U1RCSG9FV2dRNFpCYUhSMGNITTZMeTlqWlhKMGFXWnBZMkYwWlhNdWRISjFjM1JsWkhObApjblpwWTJWekxtbHVkR1ZzTG1OdmJTOUpiblJsYkZOSFdGSnZiM1JEUVM1a1pYSXdIUVlEVlIwT0JCWUVGTkRvCnF0cDExL2t1U1JlWVBIc1VaZERWOGxsTk1BNEdBMVVkRHdFQi93UUVBd0lCQmpBU0JnTlZIUk1CQWY4RUNEQUcKQVFIL0FnRUFNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUUNKZ1RidFZxT3laMW0zanFpQVhNNlFZYTZyNXNXUwo0eS9HN3k4dUlKR3hkd0lnUnFQdkJTS3p6UWFnQkxRcTVzNUE3MHBkb2lhUko4ei8wdUR6NE5nVjkxaz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJQ2p6Q0NBalNnQXdJQkFnSVVJbVVNMWxxZE5JbnpnN1NWVXI5UUd6a25CcXd3Q2dZSUtvWkl6ajBFQXdJdwphREVhTUJnR0ExVUVBd3dSU1c1MFpXd2dVMGRZSUZKdmIzUWdRMEV4R2pBWUJnTlZCQW9NRVVsdWRHVnNJRU52CmNuQnZjbUYwYVc5dU1SUXdFZ1lEVlFRSERBdFRZVzUwWVNCRGJHRnlZVEVMTUFrR0ExVUVDQXdDUTBFeEN6QUoKQmdOVkJBWVRBbFZUTUI0WERURTRNRFV5TVRFd05EVXhNRm9YRFRRNU1USXpNVEl6TlRrMU9Wb3dhREVhTUJnRwpBMVVFQXd3UlNXNTBaV3dnVTBkWUlGSnZiM1FnUTBFeEdqQVlCZ05WQkFvTUVVbHVkR1ZzSUVOdmNuQnZjbUYwCmFXOXVNUlF3RWdZRFZRUUhEQXRUWVc1MFlTQkRiR0Z5WVRFTE1Ba0dBMVVFQ0F3Q1EwRXhDekFKQmdOVkJBWVQKQWxWVE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUM2bkV3TURJWVpPai9pUFdzQ3phRUtpNwoxT2lPU0xSRmhXR2pibkJWSmZWbmtZNHUzSWprRFlZTDBNeE80bXFzeVlqbEJhbFRWWXhGUDJzSkJLNXpsS09CCnV6Q0J1REFmQmdOVkhTTUVHREFXZ0JRaVpReldXcDAwaWZPRHRKVlN2MUFiT1NjR3JEQlNCZ05WSFI4RVN6QkoKTUVlZ1JhQkRoa0ZvZEhSd2N6b3ZMMk5sY25ScFptbGpZWFJsY3k1MGNuVnpkR1ZrYzJWeWRtbGpaWE11YVc1MApaV3d1WTI5dEwwbHVkR1ZzVTBkWVVtOXZkRU5CTG1SbGNqQWRCZ05WSFE0RUZnUVVJbVVNMWxxZE5JbnpnN1NWClVyOVFHemtuQnF3d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFFd0NnWUkKS29aSXpqMEVBd0lEU1FBd1JnSWhBT1cvNVFrUitTOUNpU0RjTm9vd0x1UFJMc1dHZi9ZaTdHU1g5NEJnd1R3ZwpBaUVBNEowbHJIb01zK1hvNW8vc1g2TzlRV3hIUkF2WlVHT2RSUTdjdnFSWGFxST0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQoAMA0GCSqGSIb3DQEBCwUAA4GBAJXP4REbBXOUekVsrULSnN1cemM4ZhNNDZngCUrFjRmQ7A1gOt8+QwEH7as/764CWNgTvaporuQzYxr8zTFuQeRfLoyvRuUG1Y46Z+lfJ88H5L3AeAuK6emeaTzYE3klChaNnQOorXFJI5CDgBxfeSH9QNKH/C86aBAlSk+axiLK"
],
"kid": "lH/VQ9TL53cXpeFt3Vf9tiOv+rkWwV+5PbiNzgWcySQ=",
"kty": "RSA"
}
]
}"#;
// The sample JWT from the Azure attestation server.
// Format: ${header}.${body}.${signature}
const RAW_TOKEN: &str = "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vc2hhcmVkZXVzLmV1cy5hdHRlc3QuYXp1cmUubmV0L2NlcnRzIiwia2lkIjoickZsOXhNK2c3VHZYNjN5MGlzZVp0SW4yME1ENVNZQW5HYmxLRmFzYXU4ST0iLCJ0eXAiOiJKV1QifQ\
.eyJhYXMtZWhkIjoiVkdocGN5QnBjeUJ6YjIxbElISjFiblJwYldVZ1pHRjBZUSIsImV4cCI6MTY5NTc2NjU0NywiaWF0IjoxNjk1NzM3NzQ3LCJpcy1kZWJ1Z2dhYmxlIjpmYWxzZSwiaXNzIjoiaHR0cHM6Ly9zaGFyZWRldXMuZXVzLmF0dGVzdC5henVyZS5uZXQiLCJqdGkiOiJmM2Q3NDU2ZjIwOGVhNzc5MTkxY2U0ZGVkMDY2YWI3ZmUyY2I3NTVhY2Y1MDYzOThiMzIzOGVmMjY3ZjgzZDlmIiwibWFhLWF0dGVzdGF0aW9uY29sbGF0ZXJhbCI6eyJxZWlkY2VydHNoYXNoIjoiYTY0ZDY0OTE5ODUwN2Q4YjU3ZTMzZjYzYWIyNjY4MzhmNDNmMzI3YmQ0YWFjYzc4NTEwYjY5NzZlZDA0NmUxMCIsInFlaWRjcmxoYXNoIjoiMTMxMTNlZWQ1NTEyZTBmMTcwYjVhY2RkNjkwM2VkNTcxYmU0MGFjOGJkMTVlNzhhYzYwZmI3YWZiOTE2YjFiYiIsInFlaWRoYXNoIjoiNzcwMWY2NDcwMGI3ZjUwNWQ3YjRiN2E5M2U0NWQ1Y2RlOGNmYzg2NWI2MGYxZGQ0OWVjYmVlOTc5MGMzMzcyZSIsInF1b3RlaGFzaCI6Ijg5ZWUxMWE4ODNhMDgwYWFiNmUyNjI2MmMxMDUwMzk4YjY3NWVkYzI0YWMzNGUyMzcwNDg1MWM0NjUzNzBmMTAiLCJ0Y2JpbmZvY2VydHNoYXNoIjoiYTY0ZDY0OTE5ODUwN2Q4YjU3ZTMzZjYzYWIyNjY4MzhmNDNmMzI3YmQ0YWFjYzc4NTEwYjY5NzZlZDA0NmUxMCIsInRjYmluZm9jcmxoYXNoIjoiMTMxMTNlZWQ1NTEyZTBmMTcwYjVhY2RkNjkwM2VkNTcxYmU0MGFjOGJkMTVlNzhhYzYwZmI3YWZiOTE2YjFiYiIsInRjYmluZm9oYXNoIjoiODJkMTA5ZmIzMDhmMjRhOTBlNDM5MzZlYTllMTJiNTViMDUyNTAyMjFmZGEyMjk0Zjc0YWI1ODE3ZTcxYmVhNCJ9LCJtYWEtZWhkIjoiVkdocGN5QnBjeUJ6YjIxbElISjFiblJwYldVZ1pHRjBZUSIsIm5iZiI6MTY5NTczNzc0NywicHJvZHVjdC1pZCI6MSwic2d4LW1yZW5jbGF2ZSI6ImY1NjczNWFhNDI1NjM2MjdhODMyZTBjN2JhOTkxMTM4MmViNjhhZmVkNzU4MzBiM2Y5NzI2NmYzZTY3YmRjOTkiLCJzZ3gtbXJzaWduZXIiOiJhNTk1YzZjNTgwNWRhMGM5YzRjYjkyMDMzNGQzNTRhZWFlZTIyMDdlNDc5ZGZmNjc5ZDVmMzYwMzc1ZjU1N2RkIiwic3ZuIjoxLCJ0ZWUiOiJzZ3giLCJ4LW1zLWF0dGVzdGF0aW9uLXR5cGUiOiJzZ3giLCJ4LW1zLXBvbGljeSI6eyJpcy1kZWJ1Z2dhYmxlIjpmYWxzZSwicHJvZHVjdC1pZCI6MSwic2d4LW1yZW5jbGF2ZSI6ImY1NjczNWFhNDI1NjM2MjdhODMyZTBjN2JhOTkxMTM4MmViNjhhZmVkNzU4MzBiM2Y5NzI2NmYzZTY3YmRjOTkiLCJzZ3gtbXJzaWduZXIiOiJhNTk1YzZjNTgwNWRhMGM5YzRjYjkyMDMzNGQzNTRhZWFlZTIyMDdlNDc5ZGZmNjc5ZDVmMzYwMzc1ZjU1N2RkIiwic3ZuIjoxLCJ0ZWUiOiJzZ3gifSwieC1tcy1wb2xpY3ktaGFzaCI6Ik93RXZwU1ZFV0E1ZWlzQ0VuY0J0OE5TWkZMWURSS29MYW9PTlByWmdvZVkiLCJ4LW1zLXNneC1jb2xsYXRlcmFsIjp7InFlaWRjZXJ0c2hhc2giOiJhNjRkNjQ5MTk4NTA3ZDhiNTdlMzNmNjNhYjI2NjgzOGY0M2YzMjdiZDRhYWNjNzg1MTBiNjk3NmVkMDQ2ZTEwIiwicWVpZGNybGhhc2giOiIxMzExM2VlZDU1MTJlMGYxNzBiNWFjZGQ2OTAzZWQ1NzFiZTQwYWM4YmQxNWU3OGFjNjBmYjdhZmI5MTZiMWJiIiwicWVpZGhhc2giOiI3NzAxZjY0NzAwYjdmNTA1ZDdiNGI3YTkzZTQ1ZDVjZGU4Y2ZjODY1YjYwZjFkZDQ5ZWNiZWU5NzkwYzMzNzJlIiwicXVvdGVoYXNoIjoiODllZTExYTg4M2EwODBhYWI2ZTI2MjYyYzEwNTAzOThiNjc1ZWRjMjRhYzM0ZTIzNzA0ODUxYzQ2NTM3MGYxMCIsInRjYmluZm9jZXJ0c2hhc2giOiJhNjRkNjQ5MTk4NTA3ZDhiNTdlMzNmNjNhYjI2NjgzOGY0M2YzMjdiZDRhYWNjNzg1MTBiNjk3NmVkMDQ2ZTEwIiwidGNiaW5mb2NybGhhc2giOiIxMzExM2VlZDU1MTJlMGYxNzBiNWFjZGQ2OTAzZWQ1NzFiZTQwYWM4YmQxNWU3OGFjNjBmYjdhZmI5MTZiMWJiIiwidGNiaW5mb2hhc2giOiI4MmQxMDlmYjMwOGYyNGE5MGU0MzkzNmVhOWUxMmI1NWIwNTI1MDIyMWZkYTIyOTRmNzRhYjU4MTdlNzFiZWE0In0sIngtbXMtc2d4LWVoZCI6IlZHaHBjeUJwY3lCemIyMWxJSEoxYm5ScGJXVWdaR0YwWVEiLCJ4LW1zLXNneC1pcy1kZWJ1Z2dhYmxlIjpmYWxzZSwieC1tcy1zZ3gtbXJlbmNsYXZlIjoiZjU2NzM1YWE0MjU2MzYyN2E4MzJlMGM3YmE5OTExMzgyZWI2OGFmZWQ3NTgzMGIzZjk3MjY2ZjNlNjdiZGM5OSIsIngtbXMtc2d4LW1yc2lnbmVyIjoiYTU5NWM2YzU4MDVkYTBjOWM0Y2I5MjAzMzRkMzU0YWVhZWUyMjA3ZTQ3OWRmZjY3OWQ1ZjM2MDM3NWY1NTdkZCIsIngtbXMtc2d4LXByb2R1Y3QtaWQiOjEsIngtbXMtc2d4LXJlcG9ydC1kYXRhIjoiOTRiYTQ0ZjM5OWI5YzRhZGM4MzBkNzhjNjdmNDkxNGNiYmMzYTM4MzhmNzk2ZDJlNzY2NjU5NDc1NGMwNjdkOTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJ4LW1zLXNneC1zdm4iOjEsIngtbXMtc2d4LXRjYmlkZW50aWZpZXIiOiIxMCIsIngtbXMtdmVyIjoiMS4wIn0\
.QJyc2Ka98fiy6r_FDbfzjgV3TCTFmODe-32FiGSiAyCz_ZO5Bmw9XnQI2Rzs-Yrq6b4bDV4WlMRmJePRXzI1i2cR3xtWhnJKQjTz_EYp63OfH8SsiWci_BQpTnzoiAbUi5EdrbYz3CXQtThTy_XHyYmJVEY8qLZ0dzSO4QmBxz6q8BfcEp7fhwuKzibetQlJ3zdz-TwIK0l0WbZ1jBG93oXPnQy9KhDAyDX533DvYjDjAE3FPnjV5cMZfjmcLVxTL6DROEIlZtm_yn5zJSWlQBrFRDxoYxoYtQlEeaOn-klKZj4ECJF498mACo5fYW20UhXv5ZZNdEMYVNb4dEVf-w";
#[test]
fn raw_key_conversion() {
let raw_set: RawJsonWebKeySet = serde_json::from_str(&RAW_KEY_SET).unwrap();
let jwk_set: JsonWebKeySet = raw_set.try_into().unwrap();
assert_eq!(jwk_set.keys.len(), 3);
}
#[test]
fn token_validation() {
let raw_set: RawJsonWebKeySet = serde_json::from_str(&RAW_KEY_SET).unwrap();
let jwks: JsonWebKeySet = raw_set.try_into().unwrap();
let claims = validate_json_web_token(RAW_TOKEN.to_string(), jwks).unwrap();
assert!(!claims.x_ms_sgx_is_debuggable);
assert_eq!(
claims.x_ms_sgx_mrenclave,
"f56735aa42563627a832e0c7ba9911382eb68afed75830b3f97266f3e67bdc99"
);
assert_eq!(
claims.x_ms_sgx_mrsigner,
"a595c6c5805da0c9c4cb920334d354aeaee2207e479dff679d5f360375f557dd"
);
assert_eq!(
base64_url::decode(&claims.x_ms_sgx_ehd).unwrap(),
b"This is some runtime data",
)
}
}