blob: 372443e382676cc704573c2fc0b1de4eaef4c1c9 [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.
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Result;
use http::Uri;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use structopt::StructOpt;
use teaclave_attestation::report::AttestationReport;
use teaclave_crypto::{AesGcm128Key, AesGcm256Key, TeaclaveFile128Key};
const FILE_AUTH_TAG_LENGTH: usize = 16;
type CMac = [u8; FILE_AUTH_TAG_LENGTH];
type KeyVec = Vec<u8>; // Need define a type to use parse derive macro
fn decode_hex(src: &str) -> Result<Vec<u8>, hex::FromHexError> {
hex::decode(src)
}
#[derive(Debug, StructOpt)]
struct EncryptDecryptOpt {
/// Crypto algorithm, supported algorithms are "aes-gcm-128", "aes-gcm-256",
/// "teaclave-file-128".
#[structopt(short, long)]
algorithm: String,
/// Key in the hex format.
#[structopt(short, long, parse(try_from_str = decode_hex))]
key: KeyVec,
/// IV for AES keys in the hex format.
#[structopt(long, parse(try_from_str = decode_hex))]
iv: Option<KeyVec>,
/// Path of input file.
#[structopt(short, long = "input-file")]
input_file: PathBuf,
/// Path of output file.
#[structopt(short, long = "output-file")]
output_file: PathBuf,
/// Flag to print out CMAC.
#[structopt(short = "c", long = "print-cmac")]
print_cmac: bool,
}
#[derive(Debug, StructOpt)]
struct VerifyOpt {
/// Path of enclave info
#[structopt(short, long = "enclave-info")]
enclave_info: PathBuf,
/// Path of signatures
#[structopt(required = true, short, long)]
signatures: Vec<PathBuf>,
/// Path of auditor's public key
#[structopt(required = true, short, long = "public-keys")]
public_keys: Vec<PathBuf>,
}
#[derive(Debug, StructOpt)]
struct AttestOpt {
/// Address of the remote service
#[structopt(short, long)]
address: String,
/// CA cert of attestation service for verifying the attestation report
#[structopt(short = "c", long)]
as_ca_cert: PathBuf,
}
#[derive(Debug, StructOpt)]
enum Command {
/// Encrypt file
#[structopt(name = "encrypt")]
Encrypt(EncryptDecryptOpt),
/// Decrypt file
#[structopt(name = "decrypt")]
Decrypt(EncryptDecryptOpt),
/// Verify signatures of enclave info with auditors' public keys
#[structopt(name = "verify")]
Verify(VerifyOpt),
/// Display the attestation report of remote Teaclave services
#[structopt(name = "attest")]
Attest(AttestOpt),
}
#[derive(Debug, StructOpt)]
#[structopt(name = "teaclave_cli", about = "Teaclave command line tool.")]
struct Opt {
#[structopt(subcommand)]
command: Command,
}
fn decrypt(opt: EncryptDecryptOpt) -> Result<CMac> {
let key = opt.key;
let mut cmac: CMac = [0u8; FILE_AUTH_TAG_LENGTH];
match opt.algorithm.as_str() {
AesGcm128Key::SCHEMA => {
let iv = opt.iv.expect("IV is required.");
let key = AesGcm128Key::new(&key, &iv)?;
let mut content = fs::read(opt.input_file)?;
let res = key.decrypt(&mut content)?;
cmac.copy_from_slice(&res);
fs::write(opt.output_file, content)?;
}
AesGcm256Key::SCHEMA => {
let iv = opt.iv.expect("IV is required.");
let key = AesGcm256Key::new(&key, &iv)?;
let mut content = fs::read(opt.input_file)?;
let res = key.decrypt(&mut content)?;
cmac.copy_from_slice(&res);
fs::write(opt.output_file, content)?;
}
TeaclaveFile128Key::SCHEMA => {
let key = TeaclaveFile128Key::new(&key)?;
let mut output_file = fs::File::create(opt.output_file)?;
let res = key.decrypt(opt.input_file, &mut output_file)?;
cmac.copy_from_slice(&res);
}
_ => bail!("Invalid crypto algorithm"),
}
Ok(cmac)
}
fn encrypt(opt: EncryptDecryptOpt) -> Result<CMac> {
let key = opt.key;
let mut cmac: CMac = [0u8; FILE_AUTH_TAG_LENGTH];
match opt.algorithm.as_str() {
AesGcm128Key::SCHEMA => {
let iv = opt.iv.expect("IV is required.");
let key = AesGcm128Key::new(&key, &iv)?;
let mut content = fs::read(opt.input_file)?;
let res = key.encrypt(&mut content)?;
cmac.copy_from_slice(&res);
fs::write(opt.output_file, content)?;
}
AesGcm256Key::SCHEMA => {
let iv = opt.iv.expect("IV is required.");
let key = AesGcm256Key::new(&key, &iv)?;
let mut content = fs::read(opt.input_file)?;
let res = key.encrypt(&mut content)?;
cmac.copy_from_slice(&res);
fs::write(opt.output_file, content)?;
}
TeaclaveFile128Key::SCHEMA => {
let key = TeaclaveFile128Key::new(&key)?;
let content = fs::File::open(opt.input_file)?;
let res = key.encrypt(opt.output_file, content)?;
cmac.copy_from_slice(&res);
}
_ => bail!("Invalid crypto algorithm"),
}
Ok(cmac)
}
fn verify(opt: VerifyOpt) -> Result<bool> {
let enclave_info = fs::read(opt.enclave_info)?;
let mut public_keys = Vec::new();
let mut signatures = Vec::new();
for p in opt.public_keys {
let content = fs::read(p)?;
let pem = pem::parse(content).expect("Expect a valid PEM file");
public_keys.push(pem.contents);
}
for s in opt.signatures {
signatures.push(fs::read(s)?);
}
Ok(teaclave_types::EnclaveInfo::verify(
&enclave_info,
&public_keys,
&signatures,
))
}
struct TeaclaveServerCertVerifier {
pub root_ca: Vec<u8>,
}
impl TeaclaveServerCertVerifier {
pub fn new(root_ca: &[u8]) -> Self {
Self {
root_ca: root_ca.to_vec(),
}
}
fn display_attestation_report(&self, cert_der: &[u8]) -> bool {
match AttestationReport::from_cert(&cert_der, &self.root_ca) {
Ok(report) => println!("{}", report),
Err(e) => println!("{:?}", e),
}
true
}
}
impl rustls::ServerCertVerifier for TeaclaveServerCertVerifier {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
certs: &[rustls::Certificate],
_hostname: webpki::DNSNameRef,
_ocsp: &[u8],
) -> std::result::Result<rustls::ServerCertVerified, rustls::TLSError> {
// This call automatically verifies certificate signature
if certs.len() != 1 {
return Err(rustls::TLSError::NoCertificatesPresented);
}
if self.display_attestation_report(&certs[0].0) {
Ok(rustls::ServerCertVerified::assertion())
} else {
Err(rustls::TLSError::WebPKIError(
webpki::Error::ExtensionValueInvalid,
))
}
}
}
fn attest(opt: AttestOpt) -> Result<()> {
let uri = opt.address.parse::<Uri>()?;
let hostname = uri.host().ok_or_else(|| anyhow!("Invalid hostname."))?;
let mut stream = std::net::TcpStream::connect(opt.address)?;
let hostname = webpki::DNSNameRef::try_from_ascii_str(hostname)?;
let content = fs::read(opt.as_ca_cert)?;
let pem = pem::parse(content)?;
let verifier = Arc::new(TeaclaveServerCertVerifier::new(&pem.contents));
let mut config = rustls::ClientConfig::new();
config.dangerous().set_certificate_verifier(verifier);
config.versions.clear();
config.enable_sni = false;
config.versions.push(rustls::ProtocolVersion::TLSv1_2);
let rc_config = Arc::new(config);
let mut session = rustls::ClientSession::new(&rc_config, hostname);
let mut tls_stream = rustls::Stream::new(&mut session, &mut stream);
tls_stream.write(&[0]).unwrap();
Ok(())
}
fn main() -> Result<()> {
env_logger::init();
let args = Opt::from_args();
match args.command {
Command::Decrypt(opt) => {
let flag = opt.print_cmac;
let cmac = decrypt(opt)?;
if flag {
let cmac_string = hex::encode(cmac);
println!("{}", cmac_string);
}
}
Command::Encrypt(opt) => {
let flag = opt.print_cmac;
let cmac = encrypt(opt)?;
if flag {
let cmac_string = hex::encode(cmac);
println!("{}", cmac_string);
}
}
Command::Verify(opt) => match verify(opt) {
Ok(false) | Err(_) => bail!("Failed to verify signatures."),
Ok(true) => {
println!("Verify successfully.");
return Ok(());
}
},
Command::Attest(opt) => attest(opt)?,
};
Ok(())
}