diff --git a/Cargo.toml b/Cargo.toml index 791349c46..266d4910e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "attestation-agent/test-binaries", "confidential-data-hub/hub", "confidential-data-hub/kms", + "confidential-data-hub/image", "confidential-data-hub/secret", "image-rs", "ocicrypt-rs", diff --git a/attestation-agent/coco_keyprovider/Cargo.toml b/attestation-agent/coco_keyprovider/Cargo.toml index 82f2917ed..4a6a2d2cd 100644 --- a/attestation-agent/coco_keyprovider/Cargo.toml +++ b/attestation-agent/coco_keyprovider/Cargo.toml @@ -8,22 +8,29 @@ edition = "2021" [dependencies] aes-gcm.workspace = true anyhow.workspace = true +async-trait.workspace = true base64.workspace = true clap = { workspace = true, features = ["derive"] } +config = "0.13.3" +crypto = { path = "../deps/crypto", default-features = false, optional = true } ctr.workspace = true env_logger = "0.10.0" futures = "0.3.5" +image = { path = "../../confidential-data-hub/image", default-features = false } jwt-simple = "0.11.4" +kms = { path = "../../confidential-data-hub/kms", default-features = false } log.workspace = true prost.workspace = true rand.workspace = true reqwest.workspace = true +resource_uri = { path = "../deps/resource_uri", optional = true } serde.workspace = true serde_json.workspace = true strum.workspace = true tokio = { workspace = true, features = ["fs", "rt-multi-thread"] } tonic.workspace = true uuid = { workspace = true, features = ["fast-rng", "v4"] } +zeroize.workspace = true [build-dependencies] shadow-rs = "0.23.0" @@ -33,3 +40,7 @@ tonic-build.workspace = true rstest.workspace = true [features] +default = ["kbs"] + +kbs = ["resource_uri", "crypto"] +aliyun = ["kms/aliyun"] diff --git a/attestation-agent/coco_keyprovider/src/config.rs b/attestation-agent/coco_keyprovider/src/config.rs new file mode 100644 index 000000000..064c322a0 --- /dev/null +++ b/attestation-agent/coco_keyprovider/src/config.rs @@ -0,0 +1,49 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::net::SocketAddr; + +use anyhow::anyhow; +use clap::Parser; +use serde::Deserialize; + +const DEFAULT_SOCKET: &str = "127.0.0.1:50000"; + +/// Contains all configurable CoCoKeyprovider properties. +#[derive(Clone, Debug, Deserialize)] +pub struct CoCoKeyproviderConfig { + /// KBS provider configurations + #[cfg(feature = "kbs")] + pub kbs_config: crate::plugins::kbs::Config, + + /// Socket addresses (IP:port) to listen on, e.g. 127.0.0.1:50000. + pub socket: SocketAddr, +} + +impl TryFrom<&str> for CoCoKeyproviderConfig { + type Error = anyhow::Error; + + /// Load `Config` from a configuration file. Supported formats are all formats supported by the + /// `config` crate. See [`Config`] for schema information. + fn try_from(config_path: &str) -> Result { + let c = config::Config::builder() + .set_default("sockets", vec![DEFAULT_SOCKET])? + .add_source(config::File::with_name(config_path)) + .build()?; + + c.try_deserialize() + .map_err(|e| anyhow!("invalid config: {}", e.to_string())) + } +} + +/// KBS command-line arguments. +#[derive(Debug, Parser)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// Path to a CoCoKeyprovider config file. Supported formats: TOML, YAML, JSON and possibly other formats + /// supported by the `config` crate. + #[arg(short, long)] + pub config_file: String, +} diff --git a/attestation-agent/coco_keyprovider/src/enc_mods/crypto.rs b/attestation-agent/coco_keyprovider/src/enc_mods/crypto.rs deleted file mode 100644 index cbf484475..000000000 --- a/attestation-agent/coco_keyprovider/src/enc_mods/crypto.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2023 Alibaba Cloud -// -// SPDX-License-Identifier: Apache-2.0 -// - -use aes_gcm::{aead::Aead, aes::Aes256, Aes256Gcm, Key, Nonce}; -use anyhow::*; -use strum::EnumString; - -/// Only for sample -pub const HARDCODED_KEY: &[u8] = &[ - 217, 155, 119, 5, 176, 186, 122, 22, 130, 149, 179, 163, 54, 114, 112, 176, 221, 155, 55, 27, - 245, 20, 202, 139, 155, 167, 240, 163, 55, 17, 218, 234, -]; - -#[derive(Default, EnumString)] -pub enum Algorithm { - #[default] - A256GCM, - A256CTR, -} - -impl ToString for Algorithm { - fn to_string(&self) -> String { - match self { - Algorithm::A256GCM => "A256GCM".into(), - Algorithm::A256CTR => "A256CTR".into(), - } - } -} - -pub fn encrypt(data: &[u8], key: &[u8], iv: &[u8], algorithm: &Algorithm) -> Result> { - match algorithm { - Algorithm::A256GCM => { - use aes_gcm::KeyInit; - let encryption_key = Key::::from_slice(key); - let cipher = Aes256Gcm::new(encryption_key); - let nonce = Nonce::from_slice(iv); - cipher - .encrypt(nonce, data.as_ref()) - .map_err(|e| anyhow!("Decrypt failed: {:?}", e)) - } - Algorithm::A256CTR => { - use ctr::cipher::{KeyIvInit, StreamCipher}; - let mut buf = data.to_vec(); - let mut cipher = ctr::Ctr128BE::::new(key.into(), iv.into()); - cipher.apply_keystream(&mut buf); - Ok(buf) - } - } -} diff --git a/attestation-agent/coco_keyprovider/src/enc_mods/kbs.rs b/attestation-agent/coco_keyprovider/src/enc_mods/kbs.rs deleted file mode 100644 index a5a1cb00e..000000000 --- a/attestation-agent/coco_keyprovider/src/enc_mods/kbs.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023 Alibaba Cloud -// -// SPDX-License-Identifier: Apache-2.0 -// - -use anyhow::*; -use jwt_simple::prelude::{Claims, Duration, Ed25519KeyPair, EdDSAKeyPairLike}; -use log::debug; -use reqwest::Url; - -const KBS_URL_PATH_PREFIX: &str = "kbs/v0/resource"; - -/// Register the given key with kid into the kbs. This request will be authorized with a -/// JWT token, which will be signed by the private_key. -pub(crate) async fn register_kek( - private_key: &Ed25519KeyPair, - kbs_addr: &Url, - key: Vec, - kid: &str, -) -> Result<()> { - let kid = kid.strip_prefix('/').unwrap_or(kid); - let claims = Claims::create(Duration::from_hours(2)); - let token = private_key.sign(claims)?; - debug!("sign claims."); - - let client = reqwest::Client::new(); - let mut resource_url = kbs_addr.clone(); - - let path = format!("{KBS_URL_PATH_PREFIX}/{kid}"); - - resource_url.set_path(&path); - - debug!("register KEK into {}", resource_url); - let _ = client - .post(resource_url) - .header("Content-Type", "application/octet-stream") - .bearer_auth(token) - .body(key) - .send() - .await?; - - Ok(()) -} diff --git a/attestation-agent/coco_keyprovider/src/enc_mods/mod.rs b/attestation-agent/coco_keyprovider/src/enc_mods/mod.rs deleted file mode 100644 index 607140891..000000000 --- a/attestation-agent/coco_keyprovider/src/enc_mods/mod.rs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) 2023 Alibaba Cloud -// -// SPDX-License-Identifier: Apache-2.0 -// - -use std::collections::HashMap; - -use anyhow::*; -use base64::Engine; -use jwt_simple::prelude::Ed25519KeyPair; -use log::{debug, info}; -use rand::RngCore; -use reqwest::Url; -use serde::{Deserialize, Serialize}; -use tokio::fs; - -use self::{crypto::Algorithm, kbs::register_kek}; - -mod crypto; -mod kbs; - -/// `AnnotationPacket` is what a encrypted image layer's -/// `org.opencontainers.image.enc.keys.provider.attestation-agent` -/// annotation should contain when it is encrypted by CoCo's -/// encryption modules. Please refer to issue -/// -#[derive(Serialize, Deserialize)] -pub struct AnnotationPacket { - // Key ID to manage multiple keys - pub kid: String, - // Encrypted key to unwrap (base64-encoded) - pub wrapped_data: String, - // Initialisation vector (base64-encoded) - pub iv: String, - // Wrap type to specify encryption algorithm and mode - pub wrap_type: String, -} - -struct InputParams { - /// Whether this image is encrypted by sample key provider. - /// By default `false`. - /// If `sample` is enabled, the `keypath` field will be ignored. - sample: bool, - - /// Specify the KEK of this image. keyid field will be - /// included in AnnotationPacket. For example: - /// `kbs:///default/key/test-tag` - keyid: Option, - - /// Specify the KEK to encrypted the image in local - /// filesystem. This key will be read from fs and then - /// used to encrypt the image. This key's length must - /// be 32 bytes - keypath: Option, - - /// Encryption algorithm, included in the `wrap_type` - /// field of AnnotationPacket. Can be - /// - `A256GCM`: aes 256 gcm (default) - /// - `A256CTR`: aes 256 ctr - algorithm: Algorithm, -} - -const HARD_CODED_KEYID: &str = "kbs:///default/test-key/1"; - -/// When a KEK is randomly generated, a new kid will be generated -/// with this prefix. -const DEFAULT_KEY_REPO_PATH: &str = "/default/image-kek"; - -const KBS_RESOURCE_URL_PREFIX: &str = "kbs://"; - -fn parse_input_params(input: &str) -> Result { - let map: HashMap<&str, &str> = input - .split("::") - .collect::>() - .iter() - .filter_map(|field| field.split_once('=')) - .map(|(k, v)| (k, v)) - .collect(); - debug!("Get new request: {:?}", map); - let sample = map - .get("sample") - .map(|sa| sa.parse::().unwrap_or(false)) - .unwrap_or(false); - let keyid = map.get("keyid").map(|id| id.to_string()); - let keypath = map.get("keypath").map(|p| p.to_string()); - let algorithm = map - .get("keypath") - .map(|alg| (*alg).try_into().unwrap_or_default()) - .unwrap_or_default(); - Ok(InputParams { - sample, - keyid, - keypath, - algorithm, - }) -} - -/// This function will generate (key, iv, keyid) for given `InputParams` -async fn generate_key_parameters(input_params: &InputParams) -> Result<(Vec, Vec, String)> { - let sample_flag = input_params.sample; - match sample_flag { - // sample keyprovider will use hard coded key and iv - true => { - info!("Use sample keyprovider (HARDCODED KEY and IV)"); - Ok(( - crypto::HARDCODED_KEY.to_vec(), - [0; 12].to_vec(), - HARD_CODED_KEYID.into(), - )) - } - // use input key and randomly generated iv - false => match &input_params.keypath { - Some(kpath) => { - debug!("use given key from: {kpath}"); - let key = fs::read(kpath).await.context("read Key file failed")?; - let mut iv = [0; 12]; - rand::rngs::OsRng.fill_bytes(&mut iv); - let kid = match &input_params.keyid { - Some(kid) => kid.to_string(), - None => { - debug!("no kid input, generate a random kid"); - let tag = uuid::Uuid::new_v4().to_string(); - format!("{DEFAULT_KEY_REPO_PATH}/{tag}") - } - }; - - Ok((key.to_vec(), iv.to_vec(), kid)) - } - None => { - debug!("no key input, generate a random key"); - - let mut iv = [0; 12]; - rand::rngs::OsRng.fill_bytes(&mut iv); - - let mut key = [0; 32]; - rand::rngs::OsRng.fill_bytes(&mut key); - - let kid = match &input_params.keyid { - Some(kid) => kid.to_string(), - None => { - debug!("no kid input, generate a random kid"); - let tag = uuid::Uuid::new_v4().to_string(); - format!("{DEFAULT_KEY_REPO_PATH}/{tag}") - } - }; - Ok((key.to_vec(), iv.to_vec(), kid)) - } - }, - } -} - -/// Normalize the given keyid into (kbs addr, key path), s.t. -/// converting `kbs://...` or `../..` to `(, //)`. -fn normalize_path(keyid: &str) -> Result<(String, String)> { - debug!("normalize key id {keyid}"); - let path = keyid.strip_prefix(KBS_RESOURCE_URL_PREFIX).unwrap_or(keyid); - let values: Vec<&str> = path.split('/').collect(); - if values.len() == 4 { - Ok(( - values[0].to_string(), - format!("{}/{}/{}", values[1], values[2], values[3]), - )) - } else { - bail!( - "Resource path {keyid} must follow one of the following formats: - 'kbs://///' - 'kbs://///' - '///' - '///' - " - ) - } -} - -/// The input params vector should only have one element. -/// The format of the element is in the following format: -/// ```plaintext -/// =::=::... -/// ``` -/// -/// That is, a set of key-value pairs separated by double colons. -/// Now the supported key-value pairs are -/// | Key | Value | Usage | -/// |-----------|--------------------------------------|--------------------------------------------------------------------------------------------------| -/// | sample | `true` or `false` | Whether this image is encrypted by sample key provider. By default `false` | -/// | keyid | a KBS Resource URI, s.t. `kbs://..` | Specify the KEK of this image. keyid field will be included in AnnotationPacket | -/// | keypath | path to the KEK, e.g. `/home/key` | Specify the KEK to encrypted the image in local filesystem | -/// | algorithm | `A256GCM` or `A256CTR` | Encryption algorithm, included in the `wrap_type` field of AnnotationPacket. By default `A256GCM`| -pub async fn enc_optsdata_gen_anno( - kbs_parameter: (&Option, &Option), - optsdata: &[u8], - params: Vec, -) -> Result { - let input_params = parse_input_params(¶ms[0])?; - let (key, iv, kid) = generate_key_parameters(&input_params) - .await - .context("generating key params")?; - - let (kbs_addr, k_path) = normalize_path(&kid)?; - - let algorithm = input_params.algorithm; - let encrypt_optsdata = crypto::encrypt(optsdata, &key, &iv, &algorithm) - .map_err(|e| anyhow!("Encrypt failed: {:?}", e))?; - - if let (Some(addr), Some(private_key)) = kbs_parameter { - if !input_params.sample { - // We do not register KEK for sample kbc - register_kek(private_key, addr, key, &k_path) - .await - .context("register KEK failed")?; - info!("register KEK succeeded."); - } - } - - let engine = base64::engine::general_purpose::STANDARD; - let annotation = AnnotationPacket { - kid: format!("{KBS_RESOURCE_URL_PREFIX}{kbs_addr}/{k_path}"), - wrapped_data: engine.encode(encrypt_optsdata), - iv: engine.encode(iv), - wrap_type: algorithm.to_string(), - }; - - serde_json::to_string(&annotation).map_err(|_| anyhow!("Serialize annotation failed")) -} - -#[cfg(test)] -mod tests { - use rstest::rstest; - - #[rstest] - #[case("kbs://a/b/c/d", ("a", "b/c/d"))] - #[case("kbs:///b/c/d", ("", "b/c/d"))] - #[case("a/b/c/d", ("a", "b/c/d"))] - #[case("/b/c/d", ("", "b/c/d"))] - fn test_normalize_keypath(#[case] input: &str, #[case] expected: (&str, &str)) { - let res = crate::enc_mods::normalize_path(input).expect("normalize failed"); - assert_eq!(res.0, expected.0); - assert_eq!(res.1, expected.1); - } -} diff --git a/attestation-agent/coco_keyprovider/src/encrypt.rs b/attestation-agent/coco_keyprovider/src/encrypt.rs new file mode 100644 index 000000000..c90285d7b --- /dev/null +++ b/attestation-agent/coco_keyprovider/src/encrypt.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::collections::HashMap; + +use anyhow::*; +use log::{debug, info}; + +use crate::plugins::init_image_encrypter; + +struct InputParams { + /// Specify the KEK of this image. keyid field will be + /// included in AnnotationPacket. For example: + /// `kbs:///default/key/test-tag` + keyid: Option, + + /// Specify the KEK to encrypted the image in local + /// filesystem. This key will be read from fs and then + /// used to encrypt the image. This key's length must + /// be 32 bytes + keypath: Option, + + /// The KMS type to encrypt the image + encrypter: String, +} + +fn parse_input_params(input: &str) -> Result { + let map: HashMap<&str, &str> = input + .split("::") + .collect::>() + .iter() + .filter_map(|field| field.split_once('=')) + .map(|(k, v)| (k, v)) + .collect(); + debug!("Get new request: {:?}", map); + + let keyid = map.get("keyid").map(|id| id.to_string()); + let keypath = map.get("keypath").map(|p| p.to_string()); + let encrypter = map.get("encrypter").unwrap_or(&"kbs").to_string(); + + Ok(InputParams { + keyid, + keypath, + encrypter, + }) +} + +/// The input params vector should only have one element. +/// The format of the element is in the following format: +/// ```plaintext +/// =::=::... +/// ``` +/// +/// That is, a set of key-value pairs separated by double colons. +/// Now the supported key-value pairs are +/// | Key | Value | Usage | +/// |-----------|--------------------------------------|--------------------------------------------------------------------------------------------------| +/// | keyid | a KBS Resource URI, s.t. `kbs://..` | Specify the KEK of this image. keyid field will be included in AnnotationPacket | +/// | keypath | path to the KEK, e.g. `/home/key` | Specify the KEK to encrypted the image in local filesystem | +/// | encrypter | KMS plugin name, e.g. `kbs`, `aliyun`| Specify the KMS plugin to encrypt the image | +pub async fn enc_optsdata_gen_anno(optsdata: &[u8], params: Vec) -> Result { + let input_params = parse_input_params(¶ms[0])?; + let mut encrypter = init_image_encrypter(&input_params.encrypter).await?; + + info!("using {} to encrypt image...", input_params.encrypter); + + let kek = match input_params.keypath { + Some(path) => Some(tokio::fs::read(path).await?), + None => None, + }; + + let annotation_packet = encrypter + .encrypt_lek(optsdata, input_params.keyid, kek) + .await?; + + debug!("encryption succeed. AnnotationPacket: {annotation_packet:?}"); + serde_json::to_string(&annotation_packet) + .map_err(|_| anyhow!("Serialize annotation_packet failed")) +} diff --git a/attestation-agent/coco_keyprovider/src/grpc/mod.rs b/attestation-agent/coco_keyprovider/src/grpc/mod.rs index b68624cff..b66c9e9e7 100644 --- a/attestation-agent/coco_keyprovider/src/grpc/mod.rs +++ b/attestation-agent/coco_keyprovider/src/grpc/mod.rs @@ -3,21 +3,19 @@ // SPDX-License-Identifier: Apache-2.0 // -use crate::enc_mods; use anyhow::*; use base64::Engine; -use jwt_simple::prelude::Ed25519KeyPair; use log::*; -use reqwest::Url; -use std::net::SocketAddr; -use std::path::PathBuf; -use tokio::fs; +use std::env; use tonic::{transport::Server, Request, Response, Status}; use key_provider::key_provider_service_server::{KeyProviderService, KeyProviderServiceServer}; use key_provider::{KeyProviderKeyWrapProtocolInput, KeyProviderKeyWrapProtocolOutput}; use protocol::keyprovider_structs::*; +use crate::config::CoCoKeyproviderConfig; +use crate::encrypt::enc_optsdata_gen_anno; + pub mod protocol; pub mod key_provider { #![allow(unknown_lints)] @@ -26,22 +24,11 @@ pub mod key_provider { tonic::include_proto!("keyprovider"); } -pub struct KeyProvider { - auth_private_key: Option, - kbs: Option, -} +pub struct KeyProvider; impl KeyProvider { - pub fn new(auth_private_key: Option, kbs: Option) -> Result { - let kbs = match kbs { - Some(addr) => addr.parse().ok(), - None => None, - }; - - Ok(Self { - auth_private_key, - kbs, - }) + pub fn new() -> Result { + Ok(Self) } } @@ -93,8 +80,7 @@ impl KeyProviderService for KeyProvider { }) .collect(); - let annotation: String = enc_mods::enc_optsdata_gen_anno( - (&self.kbs, &self.auth_private_key), + let annotation: String = enc_optsdata_gen_anno( &engine .decode(optsdata) .map_err(|_| Status::aborted("base64 decode"))?, @@ -137,28 +123,21 @@ impl KeyProviderService for KeyProvider { } } -pub async fn start_service( - socket: SocketAddr, - auth_private_key: Option, - kbs: Option, -) -> Result<()> { - let auth_private_key = match auth_private_key { - Some(key_path) => { - let pem = fs::read_to_string(key_path) - .await - .context("open auth private key")?; - - Some(Ed25519KeyPair::from_pem(&pem)?) - } - None => None, - }; +pub async fn start_service(config: CoCoKeyproviderConfig) -> Result<()> { + #[cfg(feature = "kbs")] + { + use crate::plugins::kbs::{KBS_ADDR_ENV_KEY, KBS_PRIVATE_KEY_PATH_ENV_KEY}; + + env::set_var( + KBS_PRIVATE_KEY_PATH_ENV_KEY, + config.kbs_config.private_key_path, + ); + env::set_var(KBS_ADDR_ENV_KEY, config.kbs_config.kbs_addr); + } Server::builder() - .add_service(KeyProviderServiceServer::new(KeyProvider::new( - auth_private_key, - kbs, - )?)) - .serve(socket) + .add_service(KeyProviderServiceServer::new(KeyProvider::new()?)) + .serve(config.socket) .await?; Ok(()) } diff --git a/attestation-agent/coco_keyprovider/src/main.rs b/attestation-agent/coco_keyprovider/src/main.rs index 67911032d..38acf2506 100644 --- a/attestation-agent/coco_keyprovider/src/main.rs +++ b/attestation-agent/coco_keyprovider/src/main.rs @@ -4,33 +4,15 @@ // use anyhow::*; -use clap::{arg, command, Parser}; +use clap::Parser; use log::*; -use std::{net::SocketAddr, path::PathBuf}; -pub mod enc_mods; -pub mod grpc; +use crate::config::{Cli, CoCoKeyproviderConfig}; -#[derive(Debug, Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - /// Socket address (IP:port) to listen to, e.g. 127.0.0.1:50000. - #[arg(required = true, short, long)] - socket: SocketAddr, - - /// Private key used to authenticate the resource registration endpoint token (JWT) - /// to Key Broker Service. This key can sign legal JWTs. If both `kbs` - /// and this field are given, the automatic registration will be - /// enabled. - #[arg(short, long)] - auth_private_key: Option, - - /// Address of Key Broker Service. If both `auth_private_key` and - /// this field are specified, the keys generated to encrypt an image - /// will be automatically registered into the KBS. - #[arg(long)] - kbs: Option, -} +mod config; +pub mod encrypt; +pub mod grpc; +mod plugins; #[tokio::main] async fn main() -> Result<()> { @@ -39,16 +21,11 @@ async fn main() -> Result<()> { let cli = Cli::parse(); debug!("starting keyprovider gRPC service..."); - info!("listening to socket addr: {:?}", cli.socket); + let config = CoCoKeyproviderConfig::try_from(&cli.config_file[..])?; - if cli.auth_private_key.is_some() && cli.kbs.is_some() { - info!( - "The encryption key will be registered to kbs: {:?}", - cli.kbs - ); - } + info!("listening to socket addr: {}", config.socket); - grpc::start_service(cli.socket, cli.auth_private_key, cli.kbs).await?; + grpc::start_service(config).await?; Ok(()) } diff --git a/attestation-agent/coco_keyprovider/src/plugins/kbs.rs b/attestation-agent/coco_keyprovider/src/plugins/kbs.rs new file mode 100644 index 000000000..2b4195e2c --- /dev/null +++ b/attestation-agent/coco_keyprovider/src/plugins/kbs.rs @@ -0,0 +1,184 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::env; + +use anyhow::{anyhow, bail, Result}; +use async_trait::async_trait; +use base64::{engine::general_purpose::STANDARD, Engine}; +use image::{annotation_packet::v2::DEFAULT_VERSION, AnnotationPacket}; +use jwt_simple::prelude::{Claims, Duration, Ed25519KeyPair, EdDSAKeyPairLike}; +use kms::{Annotations, ProviderSettings}; +use log::debug; +use rand::RngCore; +use reqwest::Url; +use resource_uri::ResourceUri; +use serde::Deserialize; +use zeroize::Zeroizing; + +use super::ImageEncrypter; + +const KBS_URL_PATH_PREFIX: &str = "kbs/v0/resource"; +pub const KBS_ADDR_ENV_KEY: &str = "KBS_ADDR"; +pub const KBS_PRIVATE_KEY_PATH_ENV_KEY: &str = "KBS_PRIVATE_KEY_PATH"; + +#[derive(Clone, Debug, Deserialize)] +pub struct Config { + pub kbs_addr: String, + pub private_key_path: String, +} + +pub struct Client { + kbs_addr: Url, + private_key: Ed25519KeyPair, +} + +#[async_trait] +impl ImageEncrypter for Client { + async fn encrypt_lek( + &mut self, + lek: &[u8], + kek_id: Option, + kek: Option>, + ) -> Result { + let (kek, iv, kid) = Self::generate_key_parameters(kek_id, kek)?; + let wrapped_data = crypto::encrypt( + Zeroizing::new(kek.clone()), + lek.to_vec(), + iv.clone(), + crypto::WrapType::Aes256Gcm, + )?; + let annotation_packet = AnnotationPacket { + version: DEFAULT_VERSION.into(), + kid: kid.whole_uri(), + wrapped_data: STANDARD.encode(wrapped_data), + provider: "kbs".into(), + iv: Some(STANDARD.encode(iv)), + wrap_type: Some(crypto::WrapType::Aes256Gcm.as_ref().to_string()), + provider_settings: ProviderSettings::default(), + annotations: Annotations::default(), + }; + + self.register_kek(kek, &kid.resource_path()).await?; + Ok(annotation_packet) + } +} + +impl Client { + pub async fn new() -> Result { + let kbs_addr = env::var(KBS_ADDR_ENV_KEY)?; + let kbs_addr = Url::try_from(&kbs_addr[..])?; + let private_key_path = env::var(KBS_PRIVATE_KEY_PATH_ENV_KEY)?; + + let private_key = tokio::fs::read_to_string(private_key_path).await?; + let private_key = Ed25519KeyPair::from_pem(&private_key)?; + + Ok(Self { + kbs_addr, + private_key, + }) + } + + /// This function will generate (key, iv, keyid) for given `InputParams` + fn generate_key_parameters( + kek_id: Option, + kek: Option>, + ) -> Result<(Vec, Vec, ResourceUri)> { + let kek_id = match kek_id { + Some(id) => { + let id = Self::normalize_path(&id)?; + ResourceUri::try_from(&id[..]) + .map_err(|_| anyhow!("parse kek_id {id} to ResourceUri failed"))? + } + None => { + debug!("no kid input, generate a random kid"); + let tag = uuid::Uuid::new_v4().to_string(); + ResourceUri { + kbs_addr: "".into(), + repository: "default".into(), + r#type: "image-kek".into(), + tag, + } + } + }; + + let kek = match kek { + Some(k) => k.to_vec(), + None => { + debug!("no key input, generate a random key"); + + let mut key = [0; 32]; + rand::rngs::OsRng.fill_bytes(&mut key); + + key.to_vec() + } + }; + + let mut iv = [0; 12]; + rand::rngs::OsRng.fill_bytes(&mut iv); + + Ok((kek, iv.to_vec(), kek_id)) + } + + async fn register_kek(&self, key: Vec, key_path: &str) -> Result<()> { + let kid = key_path.strip_prefix('/').unwrap_or(key_path); + let claims = Claims::create(Duration::from_hours(2)); + let token = self.private_key.sign(claims)?; + debug!("sign claims."); + + let client = reqwest::Client::new(); + let mut resource_url = self.kbs_addr.clone(); + + let path = format!("{KBS_URL_PATH_PREFIX}/{kid}"); + + resource_url.set_path(&path); + + debug!("register KEK into {}", resource_url); + let _ = client + .post(resource_url) + .header("Content-Type", "application/octet-stream") + .bearer_auth(token) + .body(key) + .send() + .await?; + + Ok(()) + } + + /// Normalize the given keyid into [`ResourceUri`], s.t. + /// converting `kbs://...` or `../..` to `kbs://///)`. + fn normalize_path(keyid: &str) -> Result { + debug!("normalize key id {keyid}"); + let path = keyid.strip_prefix("kbs://").unwrap_or(keyid); + let values: Vec<&str> = path.split('/').collect(); + if values.len() == 4 { + Ok(format!("kbs:///{}/{}/{}", values[1], values[2], values[3])) + } else { + bail!( + "Resource path {keyid} must follow one of the following formats: + 'kbs://///' + 'kbs://///' + '///' + '///' + " + ) + } + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + #[rstest] + #[case("kbs://a/b/c/d", "kbs:///b/c/d")] + #[case("kbs:///b/c/d", "kbs:///b/c/d")] + #[case("a/b/c/d", "kbs:///b/c/d")] + #[case("/b/c/d", "kbs:///b/c/d")] + fn test_normalize_keypath(#[case] input: &str, #[case] expected: &str) { + let res = super::Client::normalize_path(input).expect("normalize failed"); + assert_eq!(res, expected); + } +} diff --git a/attestation-agent/coco_keyprovider/src/plugins/mod.rs b/attestation-agent/coco_keyprovider/src/plugins/mod.rs new file mode 100644 index 000000000..e230e7ed6 --- /dev/null +++ b/attestation-agent/coco_keyprovider/src/plugins/mod.rs @@ -0,0 +1,29 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod kbs; + +use anyhow::{bail, Result}; +use image::AnnotationPacket; +use tonic::async_trait; + +#[async_trait] +pub trait ImageEncrypter: Send + Sync { + async fn encrypt_lek( + &mut self, + lek: &[u8], + kek_id: Option, + kek: Option>, + ) -> Result; +} + +pub async fn init_image_encrypter(encryptor_name: &str) -> Result> { + let encryptor = match encryptor_name { + "kbs" => Box::new(kbs::Client::new().await?) as Box, + other => bail!("unsupported encrypter {other}"), + }; + + Ok(encryptor) +} diff --git a/attestation-agent/deps/resource_uri/src/lib.rs b/attestation-agent/deps/resource_uri/src/lib.rs index 2366b176a..d12646691 100644 --- a/attestation-agent/deps/resource_uri/src/lib.rs +++ b/attestation-agent/deps/resource_uri/src/lib.rs @@ -16,7 +16,7 @@ const RESOURCE_ID_ERROR_INFO: &str = const SCHEME: &str = "kbs"; /// Resource Id document -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ResourceUri { pub kbs_addr: String, pub repository: String, diff --git a/confidential-data-hub/hub/Cargo.toml b/confidential-data-hub/hub/Cargo.toml index 3d2c28937..5eed01edb 100644 --- a/confidential-data-hub/hub/Cargo.toml +++ b/confidential-data-hub/hub/Cargo.toml @@ -15,11 +15,13 @@ anyhow = { workspace = true, optional = true } async-trait.workspace = true base64.workspace = true clap = { workspace = true, features = [ "derive" ], optional = true } +image = { path = "../image", default-features = false } kms = { path = "../kms", default-features = false } lazy_static.workspace = true log.workspace = true protobuf = { workspace = true, optional = true } secret.path = "../secret" +serde = { workspace = true, optional = true } serde_json.workspace = true sev = { path = "../../attestation-agent/deps/sev", optional = true } thiserror.workspace = true @@ -33,12 +35,12 @@ ttrpc-codegen = { workspace = true, optional = true } default = ["kbs"] # support aliyun stacks (KMS, ..) -aliyun = ["secret/aliyun"] +aliyun = ["image/aliyun", "secret/aliyun"] # support coco-KBS to provide confidential resources -kbs = ["kms/kbs", "secret/kbs"] +kbs = ["image/kbs", "kms/kbs", "secret/kbs"] # support sev to provide confidential resources -sev = ["kms/sev", "dep:sev", "secret/sev"] +sev = ["image/sev", "kms/sev", "dep:sev", "secret/sev"] -bin = ["anyhow", "clap", "protobuf", "tokio/signal", "ttrpc", "ttrpc-codegen"] +bin = ["anyhow", "clap", "protobuf", "serde", "tokio/signal", "ttrpc", "ttrpc-codegen"] diff --git a/confidential-data-hub/hub/protos/api.proto b/confidential-data-hub/hub/protos/api.proto index cba138287..97a3c6266 100644 --- a/confidential-data-hub/hub/protos/api.proto +++ b/confidential-data-hub/hub/protos/api.proto @@ -18,6 +18,14 @@ message GetResourceResponse { bytes Resource = 1; } +message KeyProviderKeyWrapProtocolInput { + bytes KeyProviderKeyWrapProtocolInput = 1; +} + +message KeyProviderKeyWrapProtocolOutput { + bytes KeyProviderKeyWrapProtocolOutput = 1; +} + service SealedSecretService { rpc UnsealSecret(UnsealSecretInput) returns (UnsealSecretOutput) {}; } @@ -25,3 +33,7 @@ service SealedSecretService { service GetResourceService { rpc GetResource(GetResourceRequest) returns (GetResourceResponse) {}; } + +service KeyProviderService { + rpc UnWrapKey(KeyProviderKeyWrapProtocolInput) returns (KeyProviderKeyWrapProtocolOutput) {}; +} diff --git a/confidential-data-hub/hub/src/bin/confidential-data-hub/api.rs b/confidential-data-hub/hub/src/bin/confidential-data-hub/api.rs index a15d51523..a58d8c2eb 100644 --- a/confidential-data-hub/hub/src/bin/confidential-data-hub/api.rs +++ b/confidential-data-hub/hub/src/bin/confidential-data-hub/api.rs @@ -513,16 +513,265 @@ impl ::protobuf::reflect::ProtobufValue for GetResourceResponse { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +#[derive(PartialEq,Clone,Default,Debug)] +// @@protoc_insertion_point(message:api.KeyProviderKeyWrapProtocolInput) +pub struct KeyProviderKeyWrapProtocolInput { + // message fields + // @@protoc_insertion_point(field:api.KeyProviderKeyWrapProtocolInput.KeyProviderKeyWrapProtocolInput) + pub KeyProviderKeyWrapProtocolInput: ::std::vec::Vec, + // special fields + // @@protoc_insertion_point(special_field:api.KeyProviderKeyWrapProtocolInput.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a KeyProviderKeyWrapProtocolInput { + fn default() -> &'a KeyProviderKeyWrapProtocolInput { + ::default_instance() + } +} + +impl KeyProviderKeyWrapProtocolInput { + pub fn new() -> KeyProviderKeyWrapProtocolInput { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "KeyProviderKeyWrapProtocolInput", + |m: &KeyProviderKeyWrapProtocolInput| { &m.KeyProviderKeyWrapProtocolInput }, + |m: &mut KeyProviderKeyWrapProtocolInput| { &mut m.KeyProviderKeyWrapProtocolInput }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "KeyProviderKeyWrapProtocolInput", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for KeyProviderKeyWrapProtocolInput { + const NAME: &'static str = "KeyProviderKeyWrapProtocolInput"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.KeyProviderKeyWrapProtocolInput = is.read_bytes()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.KeyProviderKeyWrapProtocolInput.is_empty() { + my_size += ::protobuf::rt::bytes_size(1, &self.KeyProviderKeyWrapProtocolInput); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.KeyProviderKeyWrapProtocolInput.is_empty() { + os.write_bytes(1, &self.KeyProviderKeyWrapProtocolInput)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> KeyProviderKeyWrapProtocolInput { + KeyProviderKeyWrapProtocolInput::new() + } + + fn clear(&mut self) { + self.KeyProviderKeyWrapProtocolInput.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static KeyProviderKeyWrapProtocolInput { + static instance: KeyProviderKeyWrapProtocolInput = KeyProviderKeyWrapProtocolInput { + KeyProviderKeyWrapProtocolInput: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for KeyProviderKeyWrapProtocolInput { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("KeyProviderKeyWrapProtocolInput").unwrap()).clone() + } +} + +impl ::std::fmt::Display for KeyProviderKeyWrapProtocolInput { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for KeyProviderKeyWrapProtocolInput { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +#[derive(PartialEq,Clone,Default,Debug)] +// @@protoc_insertion_point(message:api.KeyProviderKeyWrapProtocolOutput) +pub struct KeyProviderKeyWrapProtocolOutput { + // message fields + // @@protoc_insertion_point(field:api.KeyProviderKeyWrapProtocolOutput.KeyProviderKeyWrapProtocolOutput) + pub KeyProviderKeyWrapProtocolOutput: ::std::vec::Vec, + // special fields + // @@protoc_insertion_point(special_field:api.KeyProviderKeyWrapProtocolOutput.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a KeyProviderKeyWrapProtocolOutput { + fn default() -> &'a KeyProviderKeyWrapProtocolOutput { + ::default_instance() + } +} + +impl KeyProviderKeyWrapProtocolOutput { + pub fn new() -> KeyProviderKeyWrapProtocolOutput { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "KeyProviderKeyWrapProtocolOutput", + |m: &KeyProviderKeyWrapProtocolOutput| { &m.KeyProviderKeyWrapProtocolOutput }, + |m: &mut KeyProviderKeyWrapProtocolOutput| { &mut m.KeyProviderKeyWrapProtocolOutput }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "KeyProviderKeyWrapProtocolOutput", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for KeyProviderKeyWrapProtocolOutput { + const NAME: &'static str = "KeyProviderKeyWrapProtocolOutput"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.KeyProviderKeyWrapProtocolOutput = is.read_bytes()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.KeyProviderKeyWrapProtocolOutput.is_empty() { + my_size += ::protobuf::rt::bytes_size(1, &self.KeyProviderKeyWrapProtocolOutput); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.KeyProviderKeyWrapProtocolOutput.is_empty() { + os.write_bytes(1, &self.KeyProviderKeyWrapProtocolOutput)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> KeyProviderKeyWrapProtocolOutput { + KeyProviderKeyWrapProtocolOutput::new() + } + + fn clear(&mut self) { + self.KeyProviderKeyWrapProtocolOutput.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static KeyProviderKeyWrapProtocolOutput { + static instance: KeyProviderKeyWrapProtocolOutput = KeyProviderKeyWrapProtocolOutput { + KeyProviderKeyWrapProtocolOutput: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for KeyProviderKeyWrapProtocolOutput { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("KeyProviderKeyWrapProtocolOutput").unwrap()).clone() + } +} + +impl ::std::fmt::Display for KeyProviderKeyWrapProtocolOutput { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for KeyProviderKeyWrapProtocolOutput { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + static file_descriptor_proto_data: &'static [u8] = b"\ \n\tapi.proto\x12\x03api\"+\n\x11UnsealSecretInput\x12\x16\n\x06secret\ \x18\x01\x20\x01(\x0cR\x06secret\"2\n\x12UnsealSecretOutput\x12\x1c\n\tp\ laintext\x18\x01\x20\x01(\x0cR\tplaintext\"8\n\x12GetResourceRequest\x12\ \"\n\x0cResourcePath\x18\x01\x20\x01(\tR\x0cResourcePath\"1\n\x13GetReso\ - urceResponse\x12\x1a\n\x08Resource\x18\x01\x20\x01(\x0cR\x08Resource2V\n\ + urceResponse\x12\x1a\n\x08Resource\x18\x01\x20\x01(\x0cR\x08Resource\"k\ + \n\x1fKeyProviderKeyWrapProtocolInput\x12H\n\x1fKeyProviderKeyWrapProtoc\ + olInput\x18\x01\x20\x01(\x0cR\x1fKeyProviderKeyWrapProtocolInput\"n\n\ + \x20KeyProviderKeyWrapProtocolOutput\x12J\n\x20KeyProviderKeyWrapProtoco\ + lOutput\x18\x01\x20\x01(\x0cR\x20KeyProviderKeyWrapProtocolOutput2V\n\ \x13SealedSecretService\x12?\n\x0cUnsealSecret\x12\x16.api.UnsealSecretI\ nput\x1a\x17.api.UnsealSecretOutput2V\n\x12GetResourceService\x12@\n\x0b\ GetResource\x12\x17.api.GetResourceRequest\x1a\x18.api.GetResourceRespon\ - seb\x06proto3\ + se2n\n\x12KeyProviderService\x12X\n\tUnWrapKey\x12$.api.KeyProviderKeyWr\ + apProtocolInput\x1a%.api.KeyProviderKeyWrapProtocolOutputb\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -540,11 +789,13 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { file_descriptor.get(|| { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { let mut deps = ::std::vec::Vec::with_capacity(0); - let mut messages = ::std::vec::Vec::with_capacity(4); + let mut messages = ::std::vec::Vec::with_capacity(6); messages.push(UnsealSecretInput::generated_message_descriptor_data()); messages.push(UnsealSecretOutput::generated_message_descriptor_data()); messages.push(GetResourceRequest::generated_message_descriptor_data()); messages.push(GetResourceResponse::generated_message_descriptor_data()); + messages.push(KeyProviderKeyWrapProtocolInput::generated_message_descriptor_data()); + messages.push(KeyProviderKeyWrapProtocolOutput::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(0); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( file_descriptor_proto(), diff --git a/confidential-data-hub/hub/src/bin/confidential-data-hub/api_ttrpc.rs b/confidential-data-hub/hub/src/bin/confidential-data-hub/api_ttrpc.rs index 95f1e5b2b..a86192fa2 100644 --- a/confidential-data-hub/hub/src/bin/confidential-data-hub/api_ttrpc.rs +++ b/confidential-data-hub/hub/src/bin/confidential-data-hub/api_ttrpc.rs @@ -117,3 +117,51 @@ pub fn create_get_resource_service(service: Arc Self { + KeyProviderServiceClient { + client, + } + } + + pub async fn un_wrap_key(&self, ctx: ttrpc::context::Context, req: &super::api::KeyProviderKeyWrapProtocolInput) -> ::ttrpc::Result { + let mut cres = super::api::KeyProviderKeyWrapProtocolOutput::new(); + ::ttrpc::async_client_request!(self, ctx, req, "api.KeyProviderService", "UnWrapKey", cres); + } +} + +struct UnWrapKeyMethod { + service: Arc>, +} + +#[async_trait] +impl ::ttrpc::r#async::MethodHandler for UnWrapKeyMethod { + async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, req: ::ttrpc::Request) -> ::ttrpc::Result<::ttrpc::Response> { + ::ttrpc::async_request_handler!(self, ctx, req, api, KeyProviderKeyWrapProtocolInput, un_wrap_key); + } +} + +#[async_trait] +pub trait KeyProviderService: Sync { + async fn un_wrap_key(&self, _ctx: &::ttrpc::r#async::TtrpcContext, _: super::api::KeyProviderKeyWrapProtocolInput) -> ::ttrpc::Result { + Err(::ttrpc::Error::RpcStatus(::ttrpc::get_status(::ttrpc::Code::NOT_FOUND, "/api.KeyProviderService/UnWrapKey is not supported".to_string()))) + } +} + +pub fn create_key_provider_service(service: Arc>) -> HashMap { + let mut ret = HashMap::new(); + let mut methods = HashMap::new(); + let streams = HashMap::new(); + + methods.insert("UnWrapKey".to_string(), + Box::new(UnWrapKeyMethod{service: service.clone()}) as Box); + + ret.insert("api.KeyProviderService".to_string(), ::ttrpc::r#async::Service{ methods, streams }); + ret +} diff --git a/confidential-data-hub/hub/src/bin/confidential-data-hub/main.rs b/confidential-data-hub/hub/src/bin/confidential-data-hub/main.rs index 1702ee48c..2737c9da3 100644 --- a/confidential-data-hub/hub/src/bin/confidential-data-hub/main.rs +++ b/confidential-data-hub/hub/src/bin/confidential-data-hub/main.rs @@ -6,7 +6,9 @@ use std::{path::Path, sync::Arc}; use anyhow::{Context, Result}; -use api_ttrpc::create_sealed_secret_service; +use api_ttrpc::{ + create_get_resource_service, create_key_provider_service, create_sealed_secret_service, +}; use clap::Parser; use log::info; use server::Server; @@ -16,8 +18,6 @@ use tokio::{ }; use ttrpc::r#async::Server as TtrpcServer; -use crate::api_ttrpc::create_get_resource_service; - mod api; mod api_ttrpc; mod server; @@ -57,11 +57,13 @@ async fn main() -> Result<()> { let sealed_secret_service = ttrpc_service!(create_sealed_secret_service); let get_resource_service = ttrpc_service!(create_get_resource_service); + let key_provider_service = ttrpc_service!(create_key_provider_service); let mut server = TtrpcServer::new() .bind(&cli.socket) .context("cannot bind cdh ttrpc service")? .register_service(sealed_secret_service) - .register_service(get_resource_service); + .register_service(get_resource_service) + .register_service(key_provider_service); server.start().await?; diff --git a/confidential-data-hub/hub/src/bin/confidential-data-hub/server.rs b/confidential-data-hub/hub/src/bin/confidential-data-hub/server.rs deleted file mode 100644 index 336df1ecf..000000000 --- a/confidential-data-hub/hub/src/bin/confidential-data-hub/server.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2023 Alibaba Cloud -// -// SPDX-License-Identifier: Apache-2.0 -// - -use std::sync::Arc; - -use anyhow::Result; -use async_trait::async_trait; -use confidential_data_hub::{hub::Hub, DataHub}; -use lazy_static::lazy_static; -use log::debug; -use tokio::sync::RwLock; -use ttrpc::{asynchronous::TtrpcContext, Code, Error, Status}; - -use crate::{ - api::{GetResourceRequest, GetResourceResponse, UnsealSecretInput, UnsealSecretOutput}, - api_ttrpc::{GetResourceService, SealedSecretService}, -}; - -lazy_static! { - static ref HUB: Arc>> = Arc::new(RwLock::new(None)); -} - -pub struct Server; - -impl Server { - async fn init() -> Result<()> { - let mut writer = HUB.write().await; - if writer.is_none() { - let hub = Hub::new().await?; - *writer = Some(hub); - } - - Ok(()) - } - - pub async fn new() -> Result { - Self::init().await?; - Ok(Self) - } -} - -#[async_trait] -impl SealedSecretService for Server { - async fn unseal_secret( - &self, - _ctx: &TtrpcContext, - input: UnsealSecretInput, - ) -> ::ttrpc::Result { - debug!("get new UnsealSecret request"); - let reader = HUB.read().await; - let reader = reader.as_ref().expect("must be initialized"); - let plaintext = reader.unseal_secret(input.secret).await.map_err(|e| { - let mut status = Status::new(); - status.set_code(Code::INTERNAL); - status.set_message(format!("[CDH] [ERROR]: Unseal Secret failed: {e}")); - Error::RpcStatus(status) - })?; - - let mut reply = UnsealSecretOutput::new(); - reply.plaintext = plaintext; - debug!("send back plaintext of the sealed secret"); - Ok(reply) - } -} - -#[async_trait] -impl GetResourceService for Server { - async fn get_resource( - &self, - _ctx: &TtrpcContext, - req: GetResourceRequest, - ) -> ::ttrpc::Result { - debug!("get new GetResource request"); - let reader = HUB.read().await; - let reader = reader.as_ref().expect("must be initialized"); - let resource = reader.get_resource(req.ResourcePath).await.map_err(|e| { - let mut status = Status::new(); - status.set_code(Code::INTERNAL); - status.set_message(format!("[CDH] [ERROR]: Get Resource failed: {e}")); - Error::RpcStatus(status) - })?; - - let mut reply = GetResourceResponse::new(); - reply.Resource = resource; - debug!("send back the resource"); - Ok(reply) - } -} diff --git a/confidential-data-hub/hub/src/bin/confidential-data-hub/server/message.rs b/confidential-data-hub/hub/src/bin/confidential-data-hub/server/message.rs new file mode 100644 index 000000000..196450ea0 --- /dev/null +++ b/confidential-data-hub/hub/src/bin/confidential-data-hub/server/message.rs @@ -0,0 +1,89 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::*; +use base64::Engine; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::str; +use std::vec::Vec; + +const ANNOTATION_KEY_NAME: &str = "attestation-agent"; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)] +pub struct KeyProviderInput { + // Operation is either "keywrap" or "keyunwrap" + // attestation-agent can only handle the case of "keyunwrap" + op: String, + // For attestation-agent, keywrapparams should be empty. + pub keywrapparams: KeyWrapParams, + pub keyunwrapparams: KeyUnwrapParams, +} + +impl KeyProviderInput { + pub fn get_annotation(&self) -> Result> { + let annotation_base64 = self + .keyunwrapparams + .dc + .as_ref() + .and_then(|dc| dc.parameters.get(ANNOTATION_KEY_NAME)) + .and_then(|paras| paras.get(0)) + .ok_or_else(|| anyhow!("Illegal UnwrapKey request: no AnnotationPacket given."))?; + + let engine = base64::engine::general_purpose::STANDARD; + let annotation = engine.decode(annotation_base64)?; + Ok(annotation) + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)] +pub struct KeyWrapParams { + // For attestation-agent, ec is null + pub ec: Option, + // For attestation-agent, optsdata is null + pub optsdata: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub struct Ec { + #[serde(rename = "Parameters")] + pub parameters: HashMap>, + #[serde(rename = "DecryptConfig")] + pub decrypt_config: Dc, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)] +pub struct KeyUnwrapParams { + pub dc: Option, + pub annotation: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +pub struct Dc { + // Name is expected to be "attestation-agent". + // Values are expected to be base-64 encoded. + #[serde(rename = "Parameters")] + pub parameters: HashMap>, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KeyWrapOutput { + pub keywrapresults: KeyWrapResults, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KeyWrapResults { + pub annotation: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KeyUnwrapOutput { + pub keyunwrapresults: KeyUnwrapResults, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct KeyUnwrapResults { + pub optsdata: Vec, +} diff --git a/confidential-data-hub/hub/src/bin/confidential-data-hub/server/mod.rs b/confidential-data-hub/hub/src/bin/confidential-data-hub/server/mod.rs new file mode 100644 index 000000000..4565ca9de --- /dev/null +++ b/confidential-data-hub/hub/src/bin/confidential-data-hub/server/mod.rs @@ -0,0 +1,153 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::sync::Arc; + +use anyhow::Result; +use async_trait::async_trait; +use confidential_data_hub::{hub::Hub, DataHub}; +use lazy_static::lazy_static; +use log::debug; +use tokio::sync::RwLock; +use ttrpc::{asynchronous::TtrpcContext, Code, Error, Status}; + +use crate::{ + api::{ + GetResourceRequest, GetResourceResponse, KeyProviderKeyWrapProtocolInput, + KeyProviderKeyWrapProtocolOutput, UnsealSecretInput, UnsealSecretOutput, + }, + api_ttrpc::{GetResourceService, KeyProviderService, SealedSecretService}, + server::message::{KeyProviderInput, KeyUnwrapOutput, KeyUnwrapResults}, +}; + +lazy_static! { + static ref HUB: Arc>> = Arc::new(RwLock::new(None)); +} + +mod message; + +pub struct Server; + +impl Server { + async fn init() -> Result<()> { + let mut writer = HUB.write().await; + if writer.is_none() { + let hub = Hub::new().await?; + *writer = Some(hub); + } + + Ok(()) + } + + pub async fn new() -> Result { + Self::init().await?; + Ok(Self) + } +} + +#[async_trait] +impl SealedSecretService for Server { + async fn unseal_secret( + &self, + _ctx: &TtrpcContext, + input: UnsealSecretInput, + ) -> ::ttrpc::Result { + debug!("get new UnsealSecret request"); + let reader = HUB.read().await; + let reader = reader.as_ref().expect("must be initialized"); + let plaintext = reader.unseal_secret(input.secret).await.map_err(|e| { + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message(format!("[CDH] [ERROR]: Unseal Secret failed: {e}")); + Error::RpcStatus(status) + })?; + + let mut reply = UnsealSecretOutput::new(); + reply.plaintext = plaintext; + debug!("send back plaintext of the sealed secret"); + Ok(reply) + } +} + +#[async_trait] +impl GetResourceService for Server { + async fn get_resource( + &self, + _ctx: &TtrpcContext, + req: GetResourceRequest, + ) -> ::ttrpc::Result { + debug!("get new GetResource request"); + let reader = HUB.read().await; + let reader = reader.as_ref().expect("must be initialized"); + let resource = reader.get_resource(req.ResourcePath).await.map_err(|e| { + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message(format!("[CDH] [ERROR]: Get Resource failed: {e}")); + Error::RpcStatus(status) + })?; + + let mut reply = GetResourceResponse::new(); + reply.Resource = resource; + debug!("send back the resource"); + Ok(reply) + } +} + +#[async_trait] +impl KeyProviderService for Server { + async fn un_wrap_key( + &self, + _ctx: &TtrpcContext, + req: KeyProviderKeyWrapProtocolInput, + ) -> ::ttrpc::Result { + debug!("get new UnWrapKey request"); + let reader = HUB.read().await; + let reader = reader.as_ref().expect("must be initialized"); + let key_provider_input: KeyProviderInput = + serde_json::from_slice(&req.KeyProviderKeyWrapProtocolInput[..]).map_err(|e| { + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message(format!("[ERROR] UnwrapKey Parse request failed: {e}")); + Error::RpcStatus(status) + })?; + + let annotation_packet = key_provider_input.get_annotation().map_err(|e| { + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message(format!("[ERROR] UnwrapKey Parse request failed: {e}")); + Error::RpcStatus(status) + })?; + + debug!("Call CDH to Unwrap Key..."); + let decrypted_optsdata = reader.unwrap_key(&annotation_packet).await.map_err(|e| { + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message(format!("[CDH] [ERROR]: UnwrapKey failed: {e}")); + Error::RpcStatus(status) + })?; + + let mut reply = KeyProviderKeyWrapProtocolOutput::new(); + + // Construct output structure and serialize it as the return value of gRPC + let output_struct = KeyUnwrapOutput { + keyunwrapresults: KeyUnwrapResults { + optsdata: decrypted_optsdata, + }, + }; + + let lek = serde_json::to_vec(&output_struct).map_err(|e| { + let mut status = Status::new(); + status.set_code(Code::INTERNAL); + status.set_message(format!( + "[CDH] [ERROR]: UnwrapKey serialize response failed: {e}" + )); + Error::RpcStatus(status) + })?; + + reply.KeyProviderKeyWrapProtocolOutput = lek; + debug!("send back the resource"); + Ok(reply) + } +} diff --git a/confidential-data-hub/hub/src/error.rs b/confidential-data-hub/hub/src/error.rs index f2c06c58e..15169480d 100644 --- a/confidential-data-hub/hub/src/error.rs +++ b/confidential-data-hub/hub/src/error.rs @@ -12,6 +12,9 @@ pub enum Error { #[error("get resource failed: {0}")] GetResource(String), + #[error("decrypt image (unwrap key) failed: {0}")] + ImageDecryption(String), + #[error("init Hub failed: {0}")] InitializationFailed(String), diff --git a/confidential-data-hub/hub/src/hub.rs b/confidential-data-hub/hub/src/hub.rs index 61ad8c0e2..704fc75fb 100644 --- a/confidential-data-hub/hub/src/hub.rs +++ b/confidential-data-hub/hub/src/hub.rs @@ -5,6 +5,7 @@ use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD, Engine}; +use image::AnnotationPacket; use kms::{Annotations, ProviderSettings}; use secret::secret::Secret; @@ -49,8 +50,14 @@ impl DataHub for Hub { Ok(res) } - async fn unwrap_key(&self, _annotation: &[u8]) -> Result> { - todo!() + async fn unwrap_key(&self, annotation_packet: &[u8]) -> Result> { + let annotation_packet: AnnotationPacket = serde_json::from_slice(annotation_packet) + .map_err(|e| Error::ImageDecryption(format!("illegal AnnotationPacket format: {e}")))?; + let lek = annotation_packet + .unwrap_key() + .await + .map_err(|e| Error::ImageDecryption(format!("unwrap key failed: {e}")))?; + Ok(lek) } async fn get_resource(&self, uri: String) -> Result> { diff --git a/confidential-data-hub/image/Cargo.toml b/confidential-data-hub/image/Cargo.toml new file mode 100644 index 000000000..8f7b19459 --- /dev/null +++ b/confidential-data-hub/image/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "image" +version = "0.1.0" +authors = ["The Confidential Container Authors"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64.workspace = true +crypto.path = "../../attestation-agent/deps/crypto" +kms = { path = "../kms", default-features = false } +resource_uri = { path = "../../attestation-agent/deps/resource_uri", optional = true } +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true + +[dev-dependencies] +assert-json-diff.workspace = true +rstest.workspace = true + +[features] +default = [] + +# legacy AnnotationPacket format, s.t. legacy encrypted image format relies on `kbs` feature +kbs = ["kms/kbs", "resource_uri"] +aliyun = ["kms/aliyun"] +sev = ["kms/sev"] diff --git a/confidential-data-hub/image/src/annotation_packet/mod.rs b/confidential-data-hub/image/src/annotation_packet/mod.rs new file mode 100644 index 000000000..de5317ad1 --- /dev/null +++ b/confidential-data-hub/image/src/annotation_packet/mod.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(feature = "kbs")] +pub mod v1; +pub mod v2; + +pub use v2::AnnotationPacketV2 as AnnotationPacket; + +#[cfg(test)] +mod tests { + use super::AnnotationPacket; + + #[test] + fn compatiblity_with_old_packets() { + let v1_raw = include_str!("../../test/v1.json"); + let _: AnnotationPacket = serde_json::from_str(v1_raw).expect("unable to parse V1 with V2"); + } +} diff --git a/confidential-data-hub/image/src/annotation_packet/v1.rs b/confidential-data-hub/image/src/annotation_packet/v1.rs new file mode 100644 index 000000000..69bbecd13 --- /dev/null +++ b/confidential-data-hub/image/src/annotation_packet/v1.rs @@ -0,0 +1,61 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use base64::{engine::general_purpose::STANDARD, Engine}; +use crypto::WrapType; +use kms::{plugins::VaultProvider, Annotations, ProviderSettings}; +use serde::{Deserialize, Serialize}; + +use resource_uri::ResourceUri; + +use crate::{Error, Result}; + +/// `AnnotationPacket` is what a encrypted image layer's +/// `org.opencontainers.image.enc.keys.provider.attestation-agent` +/// annotation should contain when it is encrypted by CoCo's +/// encryption modules. Please refer to issue +/// +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct AnnotationPacket { + // Key ID to manage multiple keys + pub kid: ResourceUri, + // Encrypted key to unwrap (base64-encoded) + pub wrapped_data: String, + // Initialisation vector (base64-encoded) + pub iv: String, + // Wrap type to specify encryption algorithm and mode + pub wrap_type: String, +} + +impl AnnotationPacket { + pub(crate) async fn unwrap_key(&self) -> Result> { + let wrap_type = WrapType::try_from(&self.wrap_type[..]) + .map_err(|e| Error::UnwrapAnnotationV1Failed(format!("parse WrapType failed: {e}")))?; + let mut kbs_client = + kms::new_getter(VaultProvider::Kbs.as_ref(), ProviderSettings::default()) + .await + .map_err(|e| Error::UnwrapAnnotationV1Failed(format!("create kbc failed: {e}")))?; + let name = self.kid.whole_uri(); + let kek = kbs_client + .get_secret(&name, &Annotations::default()) + .await + .map_err(|e| Error::UnwrapAnnotationV1Failed(format!("get KEK failed: {e}")))?; + + let lek = crypto::decrypt( + kek.into(), + STANDARD.decode(&self.wrapped_data).map_err(|e| { + Error::UnwrapAnnotationV1Failed(format!("base64 decode `wrapped_data` failed: {e}")) + })?, + STANDARD.decode(&self.iv).map_err(|e| { + Error::UnwrapAnnotationV1Failed(format!("base64 decode `iv` failed: {e}")) + })?, + wrap_type, + ) + .map_err(|e| { + Error::UnwrapAnnotationV1Failed(format!("decrypt LEK using KEK failed: {e}")) + })?; + Ok(lek) + } +} diff --git a/confidential-data-hub/image/src/annotation_packet/v2.rs b/confidential-data-hub/image/src/annotation_packet/v2.rs new file mode 100644 index 000000000..2942c9612 --- /dev/null +++ b/confidential-data-hub/image/src/annotation_packet/v2.rs @@ -0,0 +1,140 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! This is a new version of [`AnnotationPacket`] which is compatible with +//! the previous version. + +use base64::{engine::general_purpose::STANDARD, Engine}; +use kms::{Annotations, ProviderSettings}; +use serde::{Deserialize, Serialize}; +use serde_json::Map; + +use crate::{Error, Result}; + +pub const DEFAULT_VERSION: &str = "0.1.0"; + +/// New version format of AnnotationPacket +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct AnnotationPacketV2 { + /// Version of the AnnotationPacket + #[serde(default = "default_version")] + pub version: String, + + /// Key ID to manage multiple keys. If provider is `kbs`, this field + /// should be a [`ResourceUri`] + pub kid: String, + + /// Encrypted key to unwrap (base64-encoded) + pub wrapped_data: String, + + /// The way to decrypt this LEK, s.t. provider of the KEK. + #[serde(default = "default_provider")] + pub provider: String, + + /// Initialisation vector (base64-encoded). Only used when + /// provider is `"kbs"` + #[serde(skip_serializing_if = "Option::is_none")] + pub iv: Option, + + /// Wrap type to specify encryption algorithm and mode. Only used when + /// provider is `"kbs"` + #[serde(skip_serializing_if = "Option::is_none")] + pub wrap_type: Option, + + /// extra information to create a client + #[serde(default = "Map::default")] + pub provider_settings: ProviderSettings, + + /// KMS specific fields to locate the Key inside KMS + #[serde(default = "Map::default")] + pub annotations: Annotations, +} + +fn default_version() -> String { + DEFAULT_VERSION.to_string() +} + +fn default_provider() -> String { + "kbs".to_string() +} + +#[cfg(feature = "kbs")] +impl TryInto for AnnotationPacketV2 { + type Error = Error; + + fn try_into(self) -> std::result::Result { + if self.version != DEFAULT_VERSION { + return Err(Error::ConvertAnnotationPacketFailed(format!( + "`version` must be {DEFAULT_VERSION}." + ))); + } + + if self.provider != "kbs" { + return Err(Error::ConvertAnnotationPacketFailed(String::from( + "Provider must be `kbs`.", + ))); + } + + if self.wrap_type.is_none() { + return Err(Error::ConvertAnnotationPacketFailed(String::from( + "no `WrapType` given.", + ))); + } + + if self.iv.is_none() { + return Err(Error::ConvertAnnotationPacketFailed(String::from( + "no `iv` given.", + ))); + } + + let kid = resource_uri::ResourceUri::try_from(&self.kid[..]).map_err(|e| { + Error::ConvertAnnotationPacketFailed(format!("illegal ResourceUri in `kid` field: {e}")) + })?; + + let annotation_packet = super::v1::AnnotationPacket { + kid, + wrapped_data: self.wrapped_data, + iv: self.iv.expect("must have `iv`"), + wrap_type: self.wrap_type.expect("must have `wrap_type`"), + }; + + Ok(annotation_packet) + } +} + +impl AnnotationPacketV2 { + pub async fn unwrap_key(&self) -> Result> { + let lek = match &self.provider[..] { + #[cfg(feature = "kbs")] + "kbs" => { + let anno_v1: super::v1::AnnotationPacket = self.clone().try_into()?; + anno_v1.unwrap_key().await? + } + kms => { + let mut kms_client = kms::new_decryptor(kms, self.provider_settings.clone()) + .await + .map_err(|e| { + Error::UnwrapAnnotationV2Failed(format!("create KMS client failed: {e}")) + })?; + + kms_client + .decrypt( + &STANDARD.decode(&self.wrapped_data).map_err(|e| { + Error::UnwrapAnnotationV1Failed(format!( + "base64 decode `wrapped_data` failed: {e}" + )) + })?, + &self.kid, + &self.annotations, + ) + .await + .map_err(|e| { + Error::UnwrapAnnotationV2Failed(format!("KMS decryption failed: {e}")) + })? + } + }; + Ok(lek) + } +} diff --git a/confidential-data-hub/image/src/error.rs b/confidential-data-hub/image/src/error.rs new file mode 100644 index 000000000..bb4316bc6 --- /dev/null +++ b/confidential-data-hub/image/src/error.rs @@ -0,0 +1,20 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Convert AnnotationPacket failed: {0}")] + ConvertAnnotationPacketFailed(String), + + #[error("unwrap key failed (Annotation V1): {0}")] + UnwrapAnnotationV1Failed(String), + + #[error("unwrap key failed (Annotation V2): {0}")] + UnwrapAnnotationV2Failed(String), +} diff --git a/confidential-data-hub/image/src/lib.rs b/confidential-data-hub/image/src/lib.rs new file mode 100644 index 000000000..1bd4c23e5 --- /dev/null +++ b/confidential-data-hub/image/src/lib.rs @@ -0,0 +1,10 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod annotation_packet; +pub mod error; + +pub use annotation_packet::AnnotationPacket; +pub use error::*; diff --git a/confidential-data-hub/image/test/v1.json b/confidential-data-hub/image/test/v1.json new file mode 100644 index 000000000..0a5cbed71 --- /dev/null +++ b/confidential-data-hub/image/test/v1.json @@ -0,0 +1,6 @@ +{ + "kid": "kbs:///kbs:///default/key/1", + "wrapped_data": "HRCsBgH/ueLcWlUqP8j8BTREOjQXSNQvjPejbSTQt8Nu4pve07TmkSqRzu2yR7l9WbbZ4O5PChsnjuNLzu6cHvOqVSSCu3aIDYVs8VUPJSO434G+K5sAvsCSJK454VMQEY9zW8y45BrYUYV5jEKSsKvR1iqFkpzcdwI8tjGTpRMlu1gGQ6mA1jgVZKgnK8hcihmgCu24oKPmdd0pMKfiFDbzmsnRw4CS+eRb1Dp3vgLD6hCMz5BV7abaq7VMWP7SKJtJJbpYzqlKiI5i0r1qO8U=", + "iv": "M02S5rumY5JybMLQ", + "wrap_type": "A256GCM" +} \ No newline at end of file diff --git a/confidential-data-hub/kms/src/plugins/mod.rs b/confidential-data-hub/kms/src/plugins/mod.rs index defba9b11..57995263b 100644 --- a/confidential-data-hub/kms/src/plugins/mod.rs +++ b/confidential-data-hub/kms/src/plugins/mod.rs @@ -50,6 +50,7 @@ pub async fn new_getter( let provider = VaultProvider::try_from(provider_name) .map_err(|_| Error::UnsupportedProvider(provider_name.to_string()))?; match provider { + #[cfg(feature = "kbs")] VaultProvider::Kbs => Ok(Box::new(kbs::KbcClient::new().await?) as Box), } }