From c11afc24a5af24cfe10510350c6e3343657ec823 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Sun, 17 Sep 2023 16:10:24 +0800 Subject: [PATCH] image-rs: get public key from CDH when verifying cosign image signatures Signed-off-by: Xynnn007 --- image-rs/Cargo.toml | 3 + image-rs/build.rs | 39 ++- image-rs/protos/cosign_pubkey.proto | 15 + .../mechanism/cosign/cosign_pubkey.rs | 308 ++++++++++++++++++ .../mechanism/cosign/cosign_pubkey_ttrpc.rs | 71 ++++ .../src/signature/mechanism/cosign/mod.rs | 13 +- .../signature/mechanism/cosign/public_key.rs | 34 ++ 7 files changed, 468 insertions(+), 15 deletions(-) create mode 100644 image-rs/protos/cosign_pubkey.proto create mode 100644 image-rs/src/signature/mechanism/cosign/cosign_pubkey.rs create mode 100644 image-rs/src/signature/mechanism/cosign/cosign_pubkey_ttrpc.rs create mode 100644 image-rs/src/signature/mechanism/cosign/public_key.rs diff --git a/image-rs/Cargo.toml b/image-rs/Cargo.toml index 70fba9bd6..7814ce82f 100644 --- a/image-rs/Cargo.toml +++ b/image-rs/Cargo.toml @@ -92,6 +92,9 @@ encryption = ["ocicrypt-rs/block-cipher"] encryption-ring = ["ocicrypt-rs/block-cipher-ring", "encryption"] encryption-openssl = ["ocicrypt-rs/block-cipher-openssl", "encryption"] +# Get needed image verification keys from Confidential Data Hub +confidential-data-hub = ["dep:ttrpc", "dep:protobuf"] + keywrap-cmd = ["ocicrypt-rs/keywrap-keyprovider-cmd"] keywrap-grpc = ["ocicrypt-rs/keywrap-keyprovider-grpc", "prost", "tonic", "tonic-build"] keywrap-native = ["ocicrypt-rs/keywrap-keyprovider-native", "attestation_agent"] diff --git a/image-rs/build.rs b/image-rs/build.rs index 2d4bfcac1..235ee3a4e 100644 --- a/image-rs/build.rs +++ b/image-rs/build.rs @@ -10,18 +10,33 @@ fn main() -> Result<()> { tonic_build::compile_protos("./protos/getresource.proto").context("tonic build")?; #[cfg(feature = "ttrpc-codegen")] - ttrpc_codegen::Codegen::new() - .out_dir("./src/resource/kbs/ttrpc_proto") - .input("./protos/getresource.proto") - .include("./protos") - .rust_protobuf() - .customize(ttrpc_codegen::Customize { - async_all: true, - ..Default::default() - }) - .rust_protobuf_customize(ttrpc_codegen::ProtobufCustomize::default().gen_mod_rs(false)) - .run() - .context("ttrpc build")?; + { + ttrpc_codegen::Codegen::new() + .out_dir("./src/resource/kbs/ttrpc_proto") + .input("./protos/getresource.proto") + .include("./protos") + .rust_protobuf() + .customize(ttrpc_codegen::Customize { + async_all: true, + ..Default::default() + }) + .rust_protobuf_customize(ttrpc_codegen::ProtobufCustomize::default().gen_mod_rs(false)) + .run() + .context("ttrpc build")?; + #[cfg(all(feature = "signature-cosign", feature = "confidential-data-hub"))] + ttrpc_codegen::Codegen::new() + .out_dir("./src/signature/mechanism/cosign") + .input("./protos/cosign_pubkey.proto") + .include("./protos") + .rust_protobuf() + .customize(ttrpc_codegen::Customize { + async_all: true, + ..Default::default() + }) + .rust_protobuf_customize(ttrpc_codegen::ProtobufCustomize::default().gen_mod_rs(false)) + .run() + .context("ttrpc build")?; + } Ok(()) } diff --git a/image-rs/protos/cosign_pubkey.proto b/image-rs/protos/cosign_pubkey.proto new file mode 100644 index 000000000..966fde13c --- /dev/null +++ b/image-rs/protos/cosign_pubkey.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package api; + +message GetPublicKeyRequest { + string KeyId = 1; +} + +message GetPublicKeyResponse { + bytes PublicKeyPem = 1; +} + +service GetPublicKeyService { + rpc GetPublicKey(GetPublicKeyRequest) returns (GetPublicKeyResponse) {}; +} diff --git a/image-rs/src/signature/mechanism/cosign/cosign_pubkey.rs b/image-rs/src/signature/mechanism/cosign/cosign_pubkey.rs new file mode 100644 index 000000000..3a36b4476 --- /dev/null +++ b/image-rs/src/signature/mechanism/cosign/cosign_pubkey.rs @@ -0,0 +1,308 @@ +// This file is generated by rust-protobuf 3.2.0. Do not edit +// .proto file is parsed by pure +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_results)] +#![allow(unused_mut)] + +//! Generated file from `cosign_pubkey.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_2_0; + +#[derive(PartialEq,Clone,Default,Debug)] +// @@protoc_insertion_point(message:api.GetPublicKeyRequest) +pub struct GetPublicKeyRequest { + // message fields + // @@protoc_insertion_point(field:api.GetPublicKeyRequest.KeyId) + pub KeyId: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:api.GetPublicKeyRequest.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a GetPublicKeyRequest { + fn default() -> &'a GetPublicKeyRequest { + ::default_instance() + } +} + +impl GetPublicKeyRequest { + pub fn new() -> GetPublicKeyRequest { + ::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::<_, _>( + "KeyId", + |m: &GetPublicKeyRequest| { &m.KeyId }, + |m: &mut GetPublicKeyRequest| { &mut m.KeyId }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "GetPublicKeyRequest", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for GetPublicKeyRequest { + const NAME: &'static str = "GetPublicKeyRequest"; + + 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.KeyId = is.read_string()?; + }, + 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.KeyId.is_empty() { + my_size += ::protobuf::rt::string_size(1, &self.KeyId); + } + 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.KeyId.is_empty() { + os.write_string(1, &self.KeyId)?; + } + 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() -> GetPublicKeyRequest { + GetPublicKeyRequest::new() + } + + fn clear(&mut self) { + self.KeyId.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static GetPublicKeyRequest { + static instance: GetPublicKeyRequest = GetPublicKeyRequest { + KeyId: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for GetPublicKeyRequest { + 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("GetPublicKeyRequest").unwrap()).clone() + } +} + +impl ::std::fmt::Display for GetPublicKeyRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for GetPublicKeyRequest { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +#[derive(PartialEq,Clone,Default,Debug)] +// @@protoc_insertion_point(message:api.GetPublicKeyResponse) +pub struct GetPublicKeyResponse { + // message fields + // @@protoc_insertion_point(field:api.GetPublicKeyResponse.PublicKeyPem) + pub PublicKeyPem: ::std::vec::Vec, + // special fields + // @@protoc_insertion_point(special_field:api.GetPublicKeyResponse.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a GetPublicKeyResponse { + fn default() -> &'a GetPublicKeyResponse { + ::default_instance() + } +} + +impl GetPublicKeyResponse { + pub fn new() -> GetPublicKeyResponse { + ::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::<_, _>( + "PublicKeyPem", + |m: &GetPublicKeyResponse| { &m.PublicKeyPem }, + |m: &mut GetPublicKeyResponse| { &mut m.PublicKeyPem }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "GetPublicKeyResponse", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for GetPublicKeyResponse { + const NAME: &'static str = "GetPublicKeyResponse"; + + 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.PublicKeyPem = 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.PublicKeyPem.is_empty() { + my_size += ::protobuf::rt::bytes_size(1, &self.PublicKeyPem); + } + 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.PublicKeyPem.is_empty() { + os.write_bytes(1, &self.PublicKeyPem)?; + } + 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() -> GetPublicKeyResponse { + GetPublicKeyResponse::new() + } + + fn clear(&mut self) { + self.PublicKeyPem.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static GetPublicKeyResponse { + static instance: GetPublicKeyResponse = GetPublicKeyResponse { + PublicKeyPem: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for GetPublicKeyResponse { + 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("GetPublicKeyResponse").unwrap()).clone() + } +} + +impl ::std::fmt::Display for GetPublicKeyResponse { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for GetPublicKeyResponse { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x13cosign_pubkey.proto\x12\x03api\"+\n\x13GetPublicKeyRequest\x12\x14\ + \n\x05KeyId\x18\x01\x20\x01(\tR\x05KeyId\":\n\x14GetPublicKeyResponse\ + \x12\"\n\x0cPublicKeyPem\x18\x01\x20\x01(\x0cR\x0cPublicKeyPem2Z\n\x13Ge\ + tPublicKeyService\x12C\n\x0cGetPublicKey\x12\x18.api.GetPublicKeyRequest\ + \x1a\x19.api.GetPublicKeyResponseb\x06proto3\ +"; + +/// `FileDescriptorProto` object which was a source for this generated file +fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); + file_descriptor_proto_lazy.get(|| { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() + }) +} + +/// `FileDescriptor` object which allows dynamic access to files +pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { + static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); + static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); + 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(2); + messages.push(GetPublicKeyRequest::generated_message_descriptor_data()); + messages.push(GetPublicKeyResponse::generated_message_descriptor_data()); + let mut enums = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedFileDescriptor::new_generated( + file_descriptor_proto(), + deps, + messages, + enums, + ) + }); + ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) + }) +} diff --git a/image-rs/src/signature/mechanism/cosign/cosign_pubkey_ttrpc.rs b/image-rs/src/signature/mechanism/cosign/cosign_pubkey_ttrpc.rs new file mode 100644 index 000000000..6d6941096 --- /dev/null +++ b/image-rs/src/signature/mechanism/cosign/cosign_pubkey_ttrpc.rs @@ -0,0 +1,71 @@ +// This file is generated by ttrpc-compiler 0.6.1. Do not edit +// @generated + +// https://github.com/Manishearth/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clipto_camel_casepy)] + +#![cfg_attr(rustfmt, rustfmt_skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unsafe_code)] +#![allow(unused_imports)] +#![allow(unused_results)] +use protobuf::{CodedInputStream, CodedOutputStream, Message}; +use std::collections::HashMap; +use std::sync::Arc; +use async_trait::async_trait; + +#[derive(Clone)] +pub struct GetPublicKeyServiceClient { + client: ::ttrpc::r#async::Client, +} + +impl GetPublicKeyServiceClient { + pub fn new(client: ::ttrpc::r#async::Client) -> Self { + GetPublicKeyServiceClient { + client: client, + } + } + + pub async fn get_public_key(&self, ctx: ttrpc::context::Context, req: &super::cosign_pubkey::GetPublicKeyRequest) -> ::ttrpc::Result { + let mut cres = super::cosign_pubkey::GetPublicKeyResponse::new(); + ::ttrpc::async_client_request!(self, ctx, req, "api.GetPublicKeyService", "GetPublicKey", cres); + } +} + +struct GetPublicKeyMethod { + service: Arc>, +} + +#[async_trait] +impl ::ttrpc::r#async::MethodHandler for GetPublicKeyMethod { + async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, req: ::ttrpc::Request) -> ::ttrpc::Result<::ttrpc::Response> { + ::ttrpc::async_request_handler!(self, ctx, req, cosign_pubkey, GetPublicKeyRequest, get_public_key); + } +} + +#[async_trait] +pub trait GetPublicKeyService: Sync { + async fn get_public_key(&self, _ctx: &::ttrpc::r#async::TtrpcContext, _: super::cosign_pubkey::GetPublicKeyRequest) -> ::ttrpc::Result { + Err(::ttrpc::Error::RpcStatus(::ttrpc::get_status(::ttrpc::Code::NOT_FOUND, "/api.GetPublicKeyService/GetPublicKey is not supported".to_string()))) + } +} + +pub fn create_get_public_key_service(service: Arc>) -> HashMap { + let mut ret = HashMap::new(); + let mut methods = HashMap::new(); + let streams = HashMap::new(); + + methods.insert("GetPublicKey".to_string(), + Box::new(GetPublicKeyMethod{service: service.clone()}) as Box); + + ret.insert("api.GetPublicKeyService".to_string(), ::ttrpc::r#async::Service{ methods, streams }); + ret +} diff --git a/image-rs/src/signature/mechanism/cosign/mod.rs b/image-rs/src/signature/mechanism/cosign/mod.rs index eb0b13ff5..d07e6b674 100644 --- a/image-rs/src/signature/mechanism/cosign/mod.rs +++ b/image-rs/src/signature/mechanism/cosign/mod.rs @@ -5,6 +5,15 @@ //! Cosign verification +#[cfg(all(feature = "signature-cosign", feature = "confidential-data-hub"))] +mod cosign_pubkey; + +#[allow(clippy::redundant_field_names)] +#[cfg(all(feature = "signature-cosign", feature = "confidential-data-hub"))] +mod cosign_pubkey_ttrpc; +#[cfg(feature = "signature-cosign")] +mod public_key; + use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use oci_distribution::secrets::RegistryAuth; @@ -16,13 +25,11 @@ use sigstore::{ verification_constraint::{PublicKeyVerifier, VerificationConstraintVec}, verify_constraints, ClientBuilder, CosignCapabilities, }, - crypto::SigningScheme, errors::SigstoreVerifyConstraintsError, registry::{Auth, OciReference}, }; use super::SignScheme; -use crate::resource; use crate::signature::{ image::Image, mechanism::Paths, payload::simple_signing::SigPayload, policy::ref_match::PolicyReqMatchType, @@ -129,7 +136,7 @@ impl CosignParameters { // Get the pubkey let key = match (&self.key_data, &self.key_path) { (None, None) => bail!("Neither keyPath nor keyData is specified."), - (None, Some(key_path)) => resource::get_resource(key_path).await?, + (None, Some(key_path)) => public_key::get_public_key(key_path).await?, (Some(key_data), None) => key_data.as_bytes().to_vec(), (Some(_), Some(_)) => bail!("Both keyPath and keyData are specified."), }; diff --git a/image-rs/src/signature/mechanism/cosign/public_key.rs b/image-rs/src/signature/mechanism/cosign/public_key.rs new file mode 100644 index 000000000..ea6e21312 --- /dev/null +++ b/image-rs/src/signature/mechanism/cosign/public_key.rs @@ -0,0 +1,34 @@ +// Copyright (c) 2023 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::Result; + +pub(super) async fn get_public_key(keyid: &str) -> Result> { + #[cfg(feature = "confidential-data-hub")] + { + use super::{ + cosign_pubkey::GetPublicKeyRequest, cosign_pubkey_ttrpc::GetPublicKeyServiceClient, + }; + const CONFIDENTIAL_DATA_HUB_SOCKET: &str = "unix:///run/confidential-containers/cdh.sock"; + const TTRPC_TIMEOUT: i64 = 50 * 1000 * 1000 * 1000; + + let inner = ttrpc::asynchronous::Client::connect(CONFIDENTIAL_DATA_HUB_SOCKET)?; + let client = GetPublicKeyServiceClient::new(inner); + + let req = GetPublicKeyRequest { + KeyId: keyid.to_string(), + ..Default::default() + }; + let res = client + .get_public_key(ttrpc::context::with_timeout(TTRPC_TIMEOUT), &req) + .await?; + Ok(res.PublicKeyPem) + } + + #[cfg(not(feature = "confidential-data-hub"))] + { + crate::resource::get_resource(keyid).await + } +}