Skip to content

Commit

Permalink
Merge #429
Browse files Browse the repository at this point in the history
429: sgxs crate changes to enable offline signing r=[Pagten] a=arai-fortanix

This change adds new interfaces to the `sgxs` crate to make it possible to sign enclaves separately from creating the sigstructs. This is useful, for example, for performing signing via an HSM.

The first two commits in this sequence come from this pull request from ravenac95: #327. I did not include the changes to the command-line sgx-sign tool from that pull request. I think we can have a separate discussion about whether we want to support that model. The library changes should be less controversial.

I also included a change to expose the hash bytes from an EnclaveHash object. That comes from #341 from trevor-crypto.

This change should be backward-compatible with old code using this crate. New code that wants to use the new `cat_sign()` method and is using a custom key implementation will need to provide the new `SgxRsaPubOps()` trait for calculating the Q1 and Q2 values from a signature, instead of doing this during signing.

Co-authored-by: Reuven V. Gonzales <reuven@oasislabs.com>
Co-authored-by: Daniel Arai <daniel@fortanix.com>
  • Loading branch information
3 people committed Jan 3, 2023
2 parents 84ef00f + 16a87b6 commit 9c4589c
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 37 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions intel-sgx/sgxs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Version 0.7.4 - 2022-12-21

## New Features
- Refactored the signing APIs to permit signatures to be generated separately (for example using an HSM).
The way to use this new functionality is to construct a Sigstruct as usual and call
`sigstruct.unsigned_hash()` to produce the hash that must be signed externally. Once the signature is
available, reconstruct the sigstruct and call `sigstruct.cat_sign()` with the signature to get the signed
and populated Sigstruct.
- If you are using a custom key implementation, you will need to implement the new `SgxRsaPubOps()`
trait for your key. This trait must provide a `calculate_q1_q2()` method that calculates the q1 and q2
values for a given signature. The q1 and q2 calculation is the same as for the existing
`sign_sha256_pkcs1v1_5_with_q1_q2()` method, but the `calculate_q1_q2()` method takes the signature
as a parameter instead of creating the signature.


2 changes: 1 addition & 1 deletion intel-sgx/sgxs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sgxs"
version = "0.7.3"
version = "0.7.4"
authors = ["Fortanix, Inc."]
license = "MPL-2.0"
description = """
Expand Down
85 changes: 57 additions & 28 deletions intel-sgx/sgxs/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,72 @@ pub trait SgxRsaOps {
hash: H,
) -> Result<(), Self::Error>;

/// Retrieve the public key in little-endian format
/// Retrieve the public key exponent in little-endian format
fn e(&self) -> Vec<u8>;

/// Retrieve the modulus in little-endian format
fn n(&self) -> Vec<u8>;
}

pub trait SgxRsaPubOps {
type Error: ::std::error::Error;

/// Given a signature, compute
/// - `q1 = s^2 / n`
/// - `q2 = (s^3 - q1*s*n) / n`
/// where `/` is integer division.
///
/// Returns `(q1, q2)` in little-endian format.
///
/// ### Panics
/// May panic if the input length is not 32, or if the key does not contain
/// the private component.
fn calculate_q1_q2(&self, s: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Self::Error>;
}

#[cfg(feature = "crypto-openssl")]
mod openssl {
use super::*;

use foreign_types::ForeignTypeRef;
use openssl::bn::{BigNum, BigNumContext};
use openssl::bn::{BigNum, BigNumRef, BigNumContext};
use openssl::error::ErrorStack as SslError;
use openssl::hash::{Hasher, MessageDigest};
use openssl::nid::Nid;
use openssl::pkey::{HasPublic, Private, Public};
use openssl::rsa::RsaRef;
use openssl_sys as ffi;

pub fn calculate_q1_q2(n: &BigNumRef, s_slice: &[u8]) -> Result<(Vec<u8>, Vec<u8>), SslError> {
// Compute Q1 and Q2
let mut s_2 = BigNum::new()?;
let mut s_3 = BigNum::new()?;
let mut q1 = BigNum::new()?;
let mut tmp1 = BigNum::new()?;
let mut tmp2 = BigNum::new()?;
let mut tmp3 = BigNum::new()?;
let mut q2 = BigNum::new()?;

let mut ctx = BigNumContext::new()?;
let s = BigNum::from_slice(s_slice)?;
s_2.sqr(&s, &mut ctx)?;
q1.checked_div(&s_2, &n, &mut ctx)?;

s_3.checked_mul(&s_2, &s, &mut ctx)?;
tmp1.checked_mul(&q1, &s, &mut ctx)?;
tmp2.checked_mul(&tmp1, &n, &mut ctx)?;
tmp3.checked_sub(&s_3, &tmp2)?;
q2.checked_div(&tmp3, &n, &mut ctx)?;
let mut q1 = q1.to_vec();
let mut q2 = q2.to_vec();

// Return in little-endian format
q1.reverse();
q2.reverse();
Ok((q1, q2))
}


impl SgxHashOps for Hasher {
fn new() -> Self {
Hasher::new(MessageDigest::sha256()).expect("failed to create openssl hasher")
Expand Down Expand Up @@ -137,32 +183,7 @@ mod openssl {
}
};

// Compute Q1 and Q2
let mut s_2 = BigNum::new()?;
let mut s_3 = BigNum::new()?;
let mut q1 = BigNum::new()?;
let mut tmp1 = BigNum::new()?;
let mut tmp2 = BigNum::new()?;
let mut tmp3 = BigNum::new()?;
let mut q2 = BigNum::new()?;

let mut ctx = BigNumContext::new()?;
let s = BigNum::from_slice(&s_vec)?;
let n = self.n();
s_2.sqr(&s, &mut ctx)?;
q1.checked_div(&s_2, &n, &mut ctx)?;

s_3.checked_mul(&s_2, &s, &mut ctx)?;
tmp1.checked_mul(&q1, &s, &mut ctx)?;
tmp2.checked_mul(&tmp1, &n, &mut ctx)?;
tmp3.checked_sub(&s_3, &tmp2)?;
q2.checked_div(&tmp3, &n, &mut ctx)?;
let mut q1 = q1.to_vec();
let mut q2 = q2.to_vec();

// Return in little-endian format
q1.reverse();
q2.reverse();
let (q1, q2) = calculate_q1_q2(self.n(), &s_vec)?;
s_vec.reverse();
Ok((s_vec, q1, q2))
}
Expand Down Expand Up @@ -208,6 +229,14 @@ mod openssl {
v
}
}

impl<T: HasPublic> SgxRsaPubOps for RsaRef<T> {
type Error = SslError;

fn calculate_q1_q2(&self, s: &[u8]) -> Result<(Vec<u8>, Vec<u8>), Self::Error> {
calculate_q1_q2(self.n(), s)
}
}
}

#[cfg(feature = "sha2")]
Expand Down
56 changes: 49 additions & 7 deletions intel-sgx/sgxs/src/sigstruct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use time;

use abi::{self, SIGSTRUCT_HEADER1, SIGSTRUCT_HEADER2};
pub use abi::{Attributes, AttributesFlags, Miscselect, Sigstruct};
use crypto::{Hash, SgxHashOps, SgxRsaOps};
use crypto::{Hash, SgxHashOps, SgxRsaOps, SgxRsaPubOps};
use sgxs::{copy_measured, SgxsRead};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
Expand All @@ -24,6 +24,10 @@ impl EnclaveHash {
EnclaveHash { hash }
}

pub fn hash(&self) -> Hash {
self.hash
}

pub fn from_stream<R: SgxsRead, H: SgxHashOps>(stream: &mut R) -> Result<Self, Error> {
struct WriteToHasher<H> {
hasher: H,
Expand Down Expand Up @@ -105,13 +109,14 @@ impl Signer {
hasher.finish()
}

/// # Panics
///
/// Panics if key is not 3072 bits. Panics if the public exponent of key is not 3.
pub fn sign<K: SgxRsaOps, H: SgxHashOps>(self, key: &K) -> Result<Sigstruct, K::Error> {
Self::check_key(key);
pub fn unsigned_hash<H: SgxHashOps>(&self) -> Hash {
let sig = Self::unsigned_sig(self);

Self::sighash::<H>(&sig)
}

let mut sig = Sigstruct {
pub fn unsigned_sig(&self) -> Sigstruct {
let sig = Sigstruct {
header: SIGSTRUCT_HEADER1,
vendor: 0,
date: self.date,
Expand All @@ -135,6 +140,17 @@ impl Signer {
q2: [0; 384],
};

sig
}

/// # Panics
///
/// Panics if key is not 3072 bits. Panics if the public exponent of key is not 3.
pub fn sign<K: SgxRsaOps, H: SgxHashOps>(self, key: &K) -> Result<Sigstruct, K::Error> {
Self::check_key(key);

let mut sig = Self::unsigned_sig(&self);

let (s, q1, q2) = key.sign_sha256_pkcs1v1_5_with_q1_q2(Self::sighash::<H>(&sig))?;
let n = key.n();

Expand All @@ -147,6 +163,32 @@ impl Signer {
Ok(sig)
}

/// Adds a signature from raw bytes. This is used to add a signature
/// generated in an out-of-band process outside of sgxs-tools.
pub fn cat_sign<K: SgxRsaPubOps + SgxRsaOps>(
&self,
key: &K,
mut s_vec: Vec<u8>,
) -> Result<Sigstruct, <K as SgxRsaPubOps>::Error> {
Self::check_key(key);

let mut sig = Self::unsigned_sig(&self);
let (q1, q2) = key.calculate_q1_q2(&s_vec)?;

// The signature is read in as big-endian. It must be little-endian for
// the sigstruct.
s_vec.reverse();

let n = key.n();

(&mut sig.modulus[..]).write_all(&n).unwrap();
(&mut sig.signature[..]).write_all(&s_vec).unwrap();
(&mut sig.q1[..]).write_all(&q1).unwrap();
(&mut sig.q2[..]).write_all(&q2).unwrap();

Ok(sig)
}

pub fn date(&mut self, year: u16, month: u8, day: u8) -> &mut Self {
// could be faster with manual BCD conversion
self.date = u32::from_str_radix(&format!("{:04}{:02}{:02}", year, month, day), 16).unwrap();
Expand Down

0 comments on commit 9c4589c

Please sign in to comment.