Skip to content

Commit

Permalink
CDH: add en/decrypt support for eHSM-KMS
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Duan <[email protected]>
  • Loading branch information
1570005763 committed Dec 1, 2023
1 parent 5df1101 commit d71874f
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 1 deletion.
4 changes: 3 additions & 1 deletion confidential-data-hub/kms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bincode = { workspace = true, optional = true }
chrono = { workspace = true, optional = true }
const_format.workspace = true
crypto = { path = "../../attestation-agent/deps/crypto", optional = true }
ehsm_client = {git = "https://github.com/intel/ehsm", rev = "f84688688e724dfd080c1dc491db3e58415cc5b7", optional = true }
hex = { workspace = true, optional = true }
kbs_protocol = { path = "../../attestation-agent/kbs_protocol", default-features = false, features = ["passport", "aa_token", "openssl"], optional = true }
lazy_static.workspace = true
Expand Down Expand Up @@ -41,8 +42,9 @@ anyhow.workspace = true
tonic-build.workspace = true

[features]
default = ["aliyun", "kbs"]
default = ["aliyun", "kbs", "ehsm"]

aliyun = ["chrono", "hex", "openssl", "prost", "reqwest", "sha2", "tonic"]
kbs = ["kbs_protocol"]
ehsm = ["ehsm_client"]
sev = ["bincode", "crypto", "dep:sev", "prost", "tonic", "uuid", "zeroize"]
4 changes: 4 additions & 0 deletions confidential-data-hub/kms/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub enum Error {
#[error("Kbs client error: {0}")]
KbsClientError(String),

#[cfg(feature = "ehsm")]
#[error("eHSM-KMS client error: {0}")]
EhsmKmsError(String),

#[error("Unsupported provider: {0}")]
UnsupportedProvider(String),
}
116 changes: 116 additions & 0 deletions confidential-data-hub/kms/src/plugins/ehsm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# eHSM-KMS

eHSM-KMS is a SGX-based Key Managment Service (KMS) that provides the near-equivalent hardware protection level of cryptographic functionalities including key generation, management inside the SGX enclave. More information about eHSM-KMS can be found [here](https://github.com/intel/ehsm).

In CDH, we provide the eHSM-KMS client to interact with the eHSM-KMS Server.

## eHSM-KMS Service

For eHSM-KMS client to run, you need to set up an eHSM-KMS service in advance. The following method is only a quick start, and you can find more deployment methods (e.g. with Kubernetes) at webpage of eHSM-KMS.

> Prerequisite: a sgx capable machine
* Install requirement tools
``` shell
sudo apt update

sudo apt install vim autoconf automake build-essential cmake curl debhelper git libcurl4-openssl-dev libprotobuf-dev libssl-dev libtool lsb-release ocaml ocamlbuild protobuf-compiler wget libcurl4 libssl1.1 make g++ fakeroot libelf-dev libncurses-dev flex bison libfdt-dev libncursesw5-dev pkg-config libgtk-3-dev libspice-server-dev libssh-dev python3 python3-pip reprepro unzip libjsoncpp-dev uuid-dev liblog4cplus-1.1-9 liblog4cplus-dev dnsutils
```

* Install SGX SDK
```shell
wget https://download.01.org/intel-sgx/sgx-linux/2.18/as.ld.objdump.r4.tar.gz
tar -zxf as.ld.objdump.r4.tar.gz
sudo cp external/toolset/{current_distr}/* /usr/local/bin
wget https://download.01.org/intel-sgx/sgx-dcap/1.15/linux/distro/ubuntu20.04-server/sgx_linux_x64_sdk_2.18.100.3.bin
#choose to install the sdk into the /opt/intel
chmod a+x ./sgx_linux_x64_sdk_2.18.100.3.bin && sudo ./sgx_linux_x64_sdk_2.18.100.3.bin
source /opt/intel/sgxsdk/environment
```

* Install DCAP required packages
```shell
cd /opt/intel
wget https://download.01.org/intel-sgx/sgx-dcap/1.15/linux/distro/ubuntu20.04-server/sgx_debian_local_repo.tgz
tar xzf sgx_debian_local_repo.tgz
echo 'deb [trusted=yes arch=amd64] file:///opt/intel/sgx_debian_local_repo focal main' | sudo tee /etc/apt/sources.list.d/intel-sgx.list
wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y libsgx-enclave-common-dev libsgx-ae-qe3 libsgx-ae-qve libsgx-urts libsgx-dcap-ql libsgx-dcap-default-qpl libsgx-dcap-quote-verify-dev libsgx-dcap-ql-dev libsgx-dcap-default-qpl-dev libsgx-quote-ex-dev libsgx-uae-service libsgx-ra-network libsgx-ra-uefi
```

* Change PCCS server IP
``` shell
vim /etc/sgx_default_qcnl.conf
```
``` vi
# PCCS server address
PCCS_URL=https://1.2.3.4:8081/sgx/certification/v3/ (your pccs IP)
# To accept insecure HTTPS certificate, set this option to FALSE
USE_SECURE_CERT=FALSE
```

* Either start eHSM-KMS on a single machine without remote attestation.
```
# run eHSM-KMS
./run_with_single.sh
```

* Or build and run eHSM-KMS with docker-compose:
```shell
# Download the current stable release (remove the "-x $http_proxy" if you don't behind the proxy)
sudo curl -x $http_proxy -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose --version
# docker-compose version 1.29.2, build 5becea4c
# Download the ehsm code from github
git clone --recursive https://github.com/intel/ehsm.git ehsm && cd ehsm
vim docker/.env
# Modify the docker/.env configurations
HOST_IP=1.2.3.4 # MUST modify it to your host IP.
PCCS_URL=https://1.2.3.4:8081 # MUST modify it to your pccs server url.
DKEYSERVER_PORT=8888 # (Optional) the default port of dkeyserver, modify it if you want.
KMS_PORT=9000 # (Optional) the default KMS port, modify it if you want.
TAG_VERSION=main # (Optional) the default code base is using the main latest branch, modify it to specific tag if you want.
# start to build and run the docker images (couchdb, dkeyserver, dkeycache, ehsm_kms_service)
cd docker && docker-compose up -d
```

* Enrollment of the APPID and APIKey
```shell
curl -v -k -G "https://<kms_ip>:<port>/ehsm?Action=Enroll"
{"code":200,"message":"successful","result":{"apikey":"xbtXGHwBexb1pgnEz8JZWHLgaSVb1xSk","appid":"56c46c76-60e0-4722-a6ad-408cdd0c62c2"}}
```

* Run the unittest cases
``` shell
cd test
# run the unit testcases
python3 test_kms_with_cli.py --url https://<ip_addr>:<port>
```

Congratulations! eHSM-KMS service should be ready by now.

# eHSM-KMS Client

eHSM-KMS client requires a credential file to run. The file name of the credential file is `credential.{your_app_id}.json`. The credential file need to be placed in `/run/confidential-containers/cdh/kms-credential/ehsm/`. And the structure of the credential file is shown in `ehsm/example_credential/` folder.

To test eHSM-KMS client, run
```bash
cargo test --features ehsm
```
13 changes: 13 additions & 0 deletions confidential-data-hub/kms/src/plugins/ehsm/annotations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) 2023 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0
//

use serde::{Deserialize, Serialize};

/// Serialized [`crate::ProviderSettings`]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EhsmProviderSettings {
pub app_id: String,
pub endpoint: String,
}
226 changes: 226 additions & 0 deletions confidential-data-hub/kms/src/plugins/ehsm/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright (c) 2023 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0
//

use ehsm_client::{api::KMS, client::EHSMClient};

use async_trait::async_trait;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use const_format::concatcp;
use serde_json::Value;
use tokio::fs;

use crate::plugins::_IN_GUEST_DEFAULT_KEY_PATH;
use crate::{Annotations, Decrypter, Encrypter, ProviderSettings};
use crate::{Error, Result};

use super::annotations::EhsmProviderSettings;
use super::credential::Credential;

pub struct EhsmKmsClient {
client: EHSMClient,
}

const EHSM_IN_GUEST_DEFAULT_KEY_PATH: &str = concatcp!(_IN_GUEST_DEFAULT_KEY_PATH, "/ehsm");

impl EhsmKmsClient {
pub fn new(app_id: &str, api_key: &str, endpoint: &str) -> Result<Self> {
Ok(Self {
client: EHSMClient {
base_url: endpoint.to_owned(),
appid: app_id.to_owned(),
apikey: api_key.to_owned(),
},
})
}

/// build client with parameters that have been exported to environment.
pub fn new_from_env() -> Result<Self> {
Ok(Self {
client: EHSMClient::new(),
})
}

/// This new function is used by a in-pod client. The side-effect is to read the
/// [`EHSM_IN_GUEST_DEFAULT_KEY_PATH`] which is the by default path where the credential
/// to access kms is saved.
pub async fn from_provider_settings(provider_settings: &ProviderSettings) -> Result<Self> {
let provider_settings: EhsmProviderSettings =
serde_json::from_value(Value::Object(provider_settings.clone()))
.map_err(|e| Error::EhsmKmsError(format!("parse provider setting failed: {e}")))?;

let credential_path = format!(
"{EHSM_IN_GUEST_DEFAULT_KEY_PATH}/credential_{}.json",
provider_settings.app_id
);

let api_key = {
let cred = fs::read_to_string(credential_path)
.await
.map_err(|e| Error::EhsmKmsError(format!("read credential failed: {e}")))?;
let cred: Credential = serde_json::from_str(&cred)
.map_err(|e| Error::EhsmKmsError(format!("serialize credential failed: {e}")))?;
cred.api_key
};

Self::new(
&provider_settings.app_id,
&api_key,
&provider_settings.endpoint,
)
}

/// Export the [`ProviderSettings`] of the current client. This function is to be used
/// in the encryptor side. The [`ProviderSettings`] will be used to initial a client
/// in the decryptor side.
pub fn export_provider_settings(&self) -> Result<ProviderSettings> {
let provider_settings = EhsmProviderSettings {
app_id: self.client.appid.clone(),
endpoint: self.client.base_url.clone(),
};

let provider_settings = serde_json::to_value(provider_settings)
.map_err(|e| Error::EhsmKmsError(format!("serialize ProviderSettings failed: {e}")))?
.as_object()
.expect("must be an object")
.to_owned();

Ok(provider_settings)
}
}

#[async_trait]
impl Encrypter for EhsmKmsClient {
async fn encrypt(&mut self, data: &[u8], key_id: &str) -> Result<(Vec<u8>, Annotations)> {
let ciphertext = self
.client
.encrypt(key_id, &STANDARD.encode(data), None)
.await
.map_err(|e| Error::EhsmKmsError(format!("EHSM-KMS encrypt failed: {e}")))?;

let annotations = Annotations::new();

Ok((ciphertext.into(), annotations))
}
}

#[async_trait]
impl Decrypter for EhsmKmsClient {
async fn decrypt(
&mut self,
ciphertext: &[u8],
key_id: &str,
_annotations: &Annotations,
) -> Result<Vec<u8>> {
let plaintext_b64 = self
.client
.decrypt(
key_id,
std::str::from_utf8(ciphertext).map_err(|e| {
Error::EhsmKmsError(format!("decrypt &[u8] to &str failed: {e}"))
})?,
None,
)
.await
.map_err(|e| Error::EhsmKmsError(format!("EHSM-KMS decrypt failed: {e}")))?;
let plaintext = STANDARD.decode(plaintext_b64).map_err(|e| {
Error::EhsmKmsError(format!("decode plaintext for decryption failed: {e}"))
})?;

Ok(plaintext)
}
}

impl EhsmKmsClient {
pub async fn create_key(&mut self, key_spec: &str) -> Result<String> {
let origin = "EH_INTERNAL_KEY";
let keyusage = "EH_KEYUSAGE_ENCRYPT_DECRYPT";
let key_id = self
.client
.create_key(key_spec, origin, keyusage)
.await
.map_err(|e| Error::EhsmKmsError(format!("EHSM-KMS create key failed: {e}")))?;

Ok(key_id)
}
}

#[cfg(test)]
mod tests {
use rstest::rstest;
use serde_json::json;

use crate::{plugins::ehsm::client::EhsmKmsClient, Decrypter, Encrypter};

#[ignore]
#[tokio::test]
async fn test_create_key() {
let key_spec = "EH_AES_GCM_256";
let provider_settings = json!({
"app_id": "86f0e9fe-****-a224ddee1233",
"endpoint": "https://172.0.0.1:9000",
});

// init client at user side
let provider_settings = provider_settings.as_object().unwrap().to_owned();
let mut client = EhsmKmsClient::from_provider_settings(&provider_settings)
.await
.unwrap();

// create key
let key_id = client.create_key(key_spec).await;

assert!(key_id.is_ok());
}

#[rstest]
#[ignore]
#[case(b"this is a test plaintext")]
#[ignore]
#[case(b"this is a another test plaintext")]
#[tokio::test]
async fn key_lifetime(#[case] plaintext: &[u8]) {
let key_spec = "EH_AES_GCM_256";
let provider_settings = json!({
"app_id": "86f0e9fe-7f05-4110-9f65-a224ddee1233",
"endpoint": "https://172.16.1.1:9002",
});

// init client at user side
let provider_settings = provider_settings.as_object().unwrap().to_owned();
let mut client = EhsmKmsClient::from_provider_settings(&provider_settings)
.await
.unwrap();

// create key
let key_id = client.create_key(key_spec).await.unwrap();

let mut encryptor = EhsmKmsClient::from_provider_settings(&provider_settings)
.await
.unwrap();

println!("{}", key_id);

// do encryption
let (ciphertext, secret_settings) = encryptor
.encrypt(plaintext, &key_id)
.await
.expect("encrypt");
let provider_settings = encryptor.export_provider_settings().unwrap();

// init decrypter in a guest
let mut decryptor = EhsmKmsClient::from_provider_settings(&provider_settings)
.await
.unwrap();

// do decryption
let decrypted = decryptor
.decrypt(&ciphertext, &key_id, &secret_settings)
.await
.expect("decrypt");

assert_eq!(decrypted, plaintext);
}
}
Loading

0 comments on commit d71874f

Please sign in to comment.