-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CDH: add en/decrypt support for eHSM-KMS #359
Merged
Xynnn007
merged 1 commit into
confidential-containers:main
from
1570005763:feat-cdh-kms-ehsm
Dec 4, 2023
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as mentioned |
||
}); | ||
|
||
// 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); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About the way to read credential. Currently we follow the steps:
/run/..
/run/..
@fitzthum once mentioned that this will expose the plaintext of credentials to the guest and suggest that the credentials be kept in the memory of the CDH. I think it is a good way, and the steps could be
Which I'd like to put another PR and let's talk more about that then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad to hear that. eHSM-KMS itself supports reading the credential from environment variables, which will make the transition easy to implement.