From f44e0f15556fef9c2adb9cefac9cbd6bd0aecbe6 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Sat, 11 Apr 2020 09:46:13 +0800 Subject: [PATCH 01/87] first commit --- .gitignore | 2 + Cargo.toml | 24 ++ LICENSE-APACHE | 13 + LICENSE-MIT | 18 + README.MD | Bin 0 -> 138 bytes ca.der | Bin 0 -> 858 bytes clientcert.der | Bin 0 -> 911 bytes clientkey.der | Bin 0 -> 1218 bytes src/lib.rs | 939 +++++++++++++++++++++++++++++++++++++++++++++++++ test.p12 | Bin 0 -> 3462 bytes 10 files changed, 996 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.MD create mode 100644 ca.der create mode 100644 clientcert.der create mode 100644 clientkey.der create mode 100644 src/lib.rs create mode 100644 test.p12 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..fac2b16f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "p12" +version = "0.1.0" +authors = ["hjiayz "] +edition = "2018" +keywords = ["pkcs12", "pkcs"] +description = "pure rust pkcs12 tool" +homepage = "https://github.com/hjiayz/p12" +repository = "https://github.com/hjiayz/p12" +readme = "README.MD" +license = "MIT OR Apache-2.0" + +[dependencies] +lazy_static = "1.4.0" +yasna = { version = "0.3.1" } +ring = "0.16.12" +block-modes = "0.3.3" +block-cipher-trait = "0.6.2" +rc2 = "0.3.0" +des = "0.3.0" + +[dev-dependencies] +hex = "0.4.2" +hex-literal = "0.2.1" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..5972a179 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2020 hjiayz + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..e5a583a3 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,18 @@ +Copyright (c) 2020 hjiayz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN diff --git a/README.MD b/README.MD new file mode 100644 index 0000000000000000000000000000000000000000..9b93c063e9825c527d67fcc048c76568dd28d6be GIT binary patch literal 138 zcmezWPnki1p@6}V!H9vEfeVfc7)lw67*c_9ML4w3Y~|?e6}u!(7gQmyJ`a&7?KQiQ{%xCO_M{6iX=m4IfglW$d}>a znRsMcN86DkrdPAf-&nPOWzUpaG(qb00+#T|1gkH_}E$ZfT zO#WLlu0Q!NEZ*{dkCo!a{reYWq%rJW^|IjFl*6-JYcKK7Db3%awC|_(ue+)`h#bnmBmxX&MusUjpNPKP-oA@(`KuTmMdvK8)z6oH zJz{<~?XTrRzmuG&oz`c*IJV+Y#m4J?o8Oe|`LwLf;AY9csjl^2+pB9eG!|VmJ+`WS z*P$gG)(7`)fBU#kX@;Fd$=1|m)jKT9GJ2kF|Ff_vF*aQz+xhA8$`99`uRUXLy3x&k z3bSONUHZc&z9$Uo6b|=DXyw z@zykjlL4}hi>F=rq-bE;t}S}NV)h=UO6~sD$iv zMpg#qCPsb+pg0#(6C)$T{uMVxoCWo#HO~D1Fi<$fJZQb%(IQ`!gI~nzRVpscKj3s( zZK1*K*5#MHrYW?%I&}5$1K)tXm+Eh9f379ermnxR!KLH#9kX`}nmndKNU@AWz9uNsU0FVzu>R$&URzjpcl->u8@w7cwz-c-absFXhz8RtADVBRgs z$kU;dUh#g5Y^a`~`NqU_1=o-{$?g-Mh3>kl?LVD*pwA!VKQJa-~$GuEI%XT ze-;*ICe{ZAvLHSmix`W@xl1w36${GmcFgfLEZDxEbGg$6J_C7>v@(l?fmnkG-~0cP z({kJVclTWKU9;tZ=awy>8x5pD3Ith%4FsDIQPa*+&%nTl967*r1&ka<26pAUC!TdK z^i!_*<@upx>w&*6wktQxJ?;8@-J$h`I-J5giVFFem5*KL@aA+?ek&yLJN;FF{qHmC zp&t+Y<@1Qz-J^cSOgJ)Y*89Yo%8pyPk9M_1NuIfS(ctyW&1Yt5f09m%?Tt8kdE%7^ zLCX2Qz7vWsi*{bNtE)|qea&cDdPBdZ@4jX6TOV%u64`r){jMbyX?8!nq-V6O?6%el z{#%{t`)igsJYB3_z3#7Ep}Fzir;k3mS}W8hl>FjM_E`K;u4tPw^V{~lKfead>a^Nq zu_$lPnX^CTS8T5KJa8sP`pBH(7hC1h|IhF$n%m4a^;+`zlgasx?-Z-|t~~H}=K46l H1deh5F_v6Q literal 0 HcmV?d00001 diff --git a/clientkey.der b/clientkey.der new file mode 100644 index 0000000000000000000000000000000000000000..d218b2e13ced5575153a33cb8ae987b26e3fb220 GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0Kcf&6hjd& zmVuf7;ZYN0H&U-I#d1v~!Soh?BzV%Fz(Ug|p)lKpr_xH6AcX3|)x+RTP`%QB*th2@ z5{4%)p@2k)^V~M>po8(27PLt!&jn??F&H>UH-%m$E}^SaDTVl)OV*jC8((_f6RkH1 zwmi1}cGExxIm48zOvv(T>#jye9&P+MUj&*#Sf1<78}%ro|D`MvTO-(Zv{eRZ z)4w}bt>*OPqE}jLRlPr9BX@r=7MI0_8wPEfCrp3f6Yd3??>Y?MvjPJF009Dm0RaH7 zY7BIp1&6kZo3=DGjyUw;dT^#m{S6&&7k~PblER;-3AUSMDkbCjc1C?L>EyKCch`%A zkh*SBvjZ@_6ZB>-4U7X7lsQKB7yMsux&>n77WL!s=v_yjjnF<5vKkR#F zA^%IK2X3AkXQ}dgT4&fwhcQ_%xu4w1W4+3iB6m~zJrp2hgERV76wGc~0Lj-Ow`V8O zZrqwxwIG5|c9x%-Dq8K-D*lMfll<3((-YimL7eGR;JYye6nhi<&F*&1Np!xMRC>qB zKDM&w?~i8wz4BH4@{Zpcb5bm-xAR+`VN}5~wz-yNQ!mPHBZ`>OpLAfwOsNIzoOg(m)2FIu3L(jw&3*P49xwiqTyw`n3T9fq?+a?H#GS;=#MCO2jOIE==IY04Iy{aiY@~=vF>6XCr zSq))}59=g1{5d9b1f|LG%(iLS4~fGSRSDzVO2<5!>b9cjk#yg<2+oigLU;e6Fi^7s zfq;0}eF5YA9(joXl1+(jR3Li_{TzNT@&juywBqsNV!knsL`Ewg*(^kQ;&AFQb~l5z zIU9z}dEbP6VHn*Jf}pyn0vd95QlRw-{;SD~ zojsi<)}nF@Tpswj@wgb?^v03zJaQfEEsh3(uoL<61$y=5YC%0ZD}#o-vvL80j4yvg zJ&NqE&jl`D0|^?=xl9D0%YHp2u&Dn#?N9zy3aL8#yiQ~e)&f2`S1HM!fvXMcA1%!Z zc>;lf0L-9+jO(-tBp6iz;QmX@p1_%GHY!Ka>?FnE+u|em&cA80AtN$}E`K*oQ?emip*dW&9un90c^I^AAf+d;kCd literal 0 HcmV?d00001 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..1f271f35 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,939 @@ +//! +//! pure rust pkcs12 tool +//! +//! + +use lazy_static::lazy_static; +use ring::digest::Digest; +use ring::hmac; +use ring::rand::{self, SecureRandom}; +use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; + +fn as_oid(s: &'static [u64]) -> ObjectIdentifier { + ObjectIdentifier::from_slice(s) +} + +lazy_static! { + static ref OID_DATA_CONTENT_TYPE: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 7, 1]); + static ref OID_ENCRYPTED_DATA_CONTENT_TYPE: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 7, 6]); + static ref OID_FRIENDLY_NAME: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 20]); + static ref OID_LOCAL_KEY_ID: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 21]); + static ref OID_CERT_TYPE_X509_CERTIFICATE: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 1]); + static ref OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 3]); + static ref OID_SHA1: ObjectIdentifier = as_oid(&[1, 3, 14, 3, 2, 26]); + static ref OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 6]); + static ref OID_KEY_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 1]); + static ref OID_PKCS8_SHROUDED_KEY_BAG: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 2]); + static ref OID_CERT_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 3]); + static ref OID_CRL_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 4]); + static ref OID_SECRET_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 5]); + static ref OID_SAFE_CONTENTS_BAG: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 6]); + static ref RAND: rand::SystemRandom = rand::SystemRandom::new(); +} + +const ITERATIONS: u64 = 2048; + +fn sha1(bytes: &[u8]) -> Digest { + use ring::digest::*; + digest(&SHA1_FOR_LEGACY_USE_ONLY, bytes) +} + +#[derive(Debug, Clone)] +pub struct EncryptedContentInfo { + content_encryption_algorithm: AlgorithmIdentifier, + encrypted_content: Vec, +} + +impl EncryptedContentInfo { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let content_type = r.next().read_oid()?; + debug_assert_eq!(content_type, *OID_DATA_CONTENT_TYPE); + let content_encryption_algorithm = AlgorithmIdentifier::parse(r.next())?; + let encrypted_content = r + .next() + .read_tagged_implicit(Tag::context(0), |r| r.read_bytes())?; + Ok(EncryptedContentInfo { + content_encryption_algorithm, + encrypted_content, + }) + }) + } + + pub fn data(&self, password: &[u8]) -> Option> { + self.content_encryption_algorithm + .decrypt_pbe(&self.encrypted_content, password) + } + + fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_oid(&OID_DATA_CONTENT_TYPE); + self.content_encryption_algorithm.write(w.next()); + w.next() + .write_tagged_implicit(Tag::context(0), |w| w.write_bytes(&self.encrypted_content)); + }) + } + + pub fn to_der(&self) -> Vec { + yasna::construct_der(|w| self.write(w)) + } + + pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { + let data = yasna::construct_der(|w| { + w.write_sequence_of(|w| { + for sb in safe_bags { + sb.write(w.next()); + } + }) + }); + let salt = rand()?.to_vec(); + let encrypted_content = + pbe_with_sha1_and40_bit_rc2_cbc_encrypt(&data, password, &salt, ITERATIONS)?; + let content_encryption_algorithm = + AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams { + salt: salt, + iterations: ITERATIONS, + }); + Some(EncryptedContentInfo { + content_encryption_algorithm, + encrypted_content, + }) + } +} + +#[derive(Debug, Clone)] +pub struct EncryptedData { + encrypted_content_info: EncryptedContentInfo, +} + +impl EncryptedData { + fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let version = r.next().read_u8()?; + debug_assert_eq!(version, 0); + + let encrypted_content_info = EncryptedContentInfo::parse(r.next())?; + Ok(EncryptedData { + encrypted_content_info, + }) + }) + } + fn data(&self, password: &[u8]) -> Option> { + self.encrypted_content_info.data(password) + } + fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_u8(0); + self.encrypted_content_info.write(w.next()); + }) + } + fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { + let encrypted_content_info = EncryptedContentInfo::from_safe_bags(safe_bags, password)?; + Some(EncryptedData { + encrypted_content_info, + }) + } +} + +#[derive(Debug, Clone)] +pub enum ContentInfo { + Data(Vec), + EncryptedData(EncryptedData), +} + +impl ContentInfo { + fn parse(r: BERReader) -> Result { + Ok(r.read_sequence(|r| { + let content_type = r.next().read_oid()?; + if content_type == *OID_DATA_CONTENT_TYPE { + let data = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; + return Ok(ContentInfo::Data(data)); + } + if content_type == *OID_ENCRYPTED_DATA_CONTENT_TYPE { + let result = r.next().read_tagged(Tag::context(0), |r| { + Ok(ContentInfo::EncryptedData(EncryptedData::parse(r)?)) + }); + return result; + } + println!("undefined context type: {:?}", content_type); + Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + })?) + } + pub fn data(&self, password: &[u8]) -> Option> { + match self { + ContentInfo::Data(data) => Some(data.to_owned()), + ContentInfo::EncryptedData(encrypted) => encrypted.data(password), + } + } + pub fn oid(&self) -> ObjectIdentifier { + match self { + ContentInfo::Data(_) => OID_DATA_CONTENT_TYPE.clone(), + ContentInfo::EncryptedData(_) => OID_ENCRYPTED_DATA_CONTENT_TYPE.clone(), + } + } + pub fn write(&self, w: DERWriter) { + match self { + ContentInfo::Data(data) => w.write_sequence(|w| { + w.next().write_oid(&OID_DATA_CONTENT_TYPE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_bytes(data)) + }), + ContentInfo::EncryptedData(encrypted_data) => w.write_sequence(|w| { + w.next().write_oid(&OID_ENCRYPTED_DATA_CONTENT_TYPE); + w.next() + .write_tagged(Tag::context(0), |w| encrypted_data.write(w)) + }), + } + } + pub fn to_der(&self) -> Vec { + yasna::construct_der(|w| self.write(w)) + } + + pub fn from_der(der: &[u8]) -> Result { + yasna::parse_der(der, Self::parse) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Pkcs12PbeParams { + salt: Vec, + iterations: u64, +} + +impl Pkcs12PbeParams { + fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let salt = r.next().read_bytes()?; + let iterations = r.next().read_u64()?; + Ok(Pkcs12PbeParams { salt, iterations }) + }) + } + fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_bytes(&self.salt); + w.next().write_u64(self.iterations); + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum AlgorithmIdentifier { + Sha1, + PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams), + PbeWithSHAAnd3KeyTripleDESCBC(Pkcs12PbeParams), +} + +impl AlgorithmIdentifier { + fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + + if oid == *OID_SHA1 { + r.next().read_null()?; + return Ok(AlgorithmIdentifier::Sha1); + } + if oid == *OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC { + let param = Pkcs12PbeParams::parse(r.next())?; + return Ok(AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(param)); + } + if oid == *OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC { + let param = Pkcs12PbeParams::parse(r.next())?; + return Ok(AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param)); + } + println!("unknown Algorithm Identifier : {}", oid); + Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + }) + } + fn decrypt_pbe(&self, ciphertext: &[u8], password: &[u8]) -> Option> { + match self { + AlgorithmIdentifier::Sha1 => None, + AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(param) => { + pbe_with_sha1_and40_bit_rc2_cbc(ciphertext, password, ¶m.salt, param.iterations) + } + AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param) => { + pbe_with_sha_and3_key_triple_des_cbc( + ciphertext, + password, + ¶m.salt, + param.iterations, + ) + } + } + } + fn write(&self, w: DERWriter) { + w.write_sequence(|w| match self { + AlgorithmIdentifier::Sha1 => { + w.next().write_oid(&OID_SHA1); + w.next().write_null(); + } + AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(p) => { + w.next().write_oid(&OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC); + p.write(w.next()); + } + AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(p) => { + w.next().write_oid(&OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC); + p.write(w.next()); + } + }) + } +} + +#[derive(Debug)] +pub struct DigestInfo { + digest_algorithm: AlgorithmIdentifier, + digest: Vec, +} + +impl DigestInfo { + fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let digest_algorithm = AlgorithmIdentifier::parse(r.next())?; + let digest = r.next().read_bytes()?; + Ok(DigestInfo { + digest_algorithm, + digest, + }) + }) + } + fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + self.digest_algorithm.write(w.next()); + w.next().write_bytes(&self.digest); + }) + } +} + +#[derive(Debug)] +pub struct MacData { + mac: DigestInfo, + salt: Vec, + iterations: u32, +} + +impl MacData { + fn parse(r: BERReader) -> Result { + Ok(r.read_sequence(|r| { + let mac = DigestInfo::parse(r.next())?; + let salt = r.next().read_bytes()?; + let iterations = r.next().read_u32()?; + Ok(MacData { + mac, + salt, + iterations, + }) + })?) + } + + fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + self.mac.write(w.next()); + w.next().write_bytes(&self.salt); + w.next().write_u32(self.iterations); + }) + } + + fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { + debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); + let key = pkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); + let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); + hmac::verify(&m, data, &self.mac.digest).is_ok() + } + + fn new(data: &[u8], password: &[u8]) -> MacData { + let salt = rand().unwrap(); + let key = pkcs12sha1(password, &salt, ITERATIONS, 3, 20); + let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); + let digest = hmac::sign(&m, data).as_ref().to_owned(); + MacData { + mac: DigestInfo { + digest_algorithm: AlgorithmIdentifier::Sha1, + digest, + }, + salt: salt.to_vec(), + iterations: ITERATIONS as u32, + } + } +} + +fn rand() -> Option<[u8; 8]> { + let mut buf = [0u8; 8]; + let rng = rand::SystemRandom::new(); + rng.fill(&mut buf).ok()?; + Some(buf) +} + +#[derive(Debug)] +pub struct PFX { + version: u8, + auth_safe: ContentInfo, + mac_data: Option, +} + +impl PFX { + pub fn new( + cert_der: &[u8], + key_der: &[u8], + ca_der: Option<&[u8]>, + password: &str, + name: &str, + ) -> Option { + let password = bmp_string(password); + let salt = rand()?.to_vec(); + let encrypted_data = + pbe_with_sha_and3_key_triple_des_cbc_encrypt(key_der, &password, &salt, ITERATIONS)?; + let param = Pkcs12PbeParams { + salt: salt, + iterations: ITERATIONS, + }; + let key_bag_inner = SafeBagKind::Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo { + encryption_algorithm: AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param), + encrypted_data, + }); + let friendly_name = PKCS12Attribute::FriendlyName(name.to_owned()); + let local_key_id = PKCS12Attribute::LocalKeyId(sha1(cert_der).as_ref().to_owned()); + let key_bag = SafeBag { + bag: key_bag_inner, + attributes: vec![friendly_name.clone(), local_key_id.clone()], + }; + let cert_bag_inner = SafeBagKind::CertBag(CertBag { + cert: cert_der.to_owned(), + }); + let cert_bag = SafeBag { + bag: cert_bag_inner, + attributes: vec![friendly_name, local_key_id], + }; + let mut cert_bags = vec![cert_bag]; + if let Some(ca) = ca_der { + cert_bags.push(SafeBag { + bag: SafeBagKind::CertBag(CertBag { + cert: ca.to_owned(), + }), + attributes: vec![], + }); + }; + let contents = yasna::construct_der(|w| { + w.write_sequence_of(|w| { + ContentInfo::EncryptedData( + EncryptedData::from_safe_bags(&cert_bags, &password) + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid)) + .unwrap(), + ) + .write(w.next()); + ContentInfo::Data(yasna::construct_der(|w| { + w.write_sequence_of(|w| { + key_bag.write(w.next()); + }) + })) + .write(w.next()); + }); + }); + let mac_data = MacData::new(&contents, &password); + Some(PFX { + version: 3, + auth_safe: ContentInfo::Data(contents), + mac_data: Some(mac_data), + }) + } + + pub fn parse(bytes: &[u8]) -> Result { + Ok(yasna::parse_der(bytes, |r| { + r.read_sequence(|r| { + let version = r.next().read_u8()?; + let auth_safe = ContentInfo::parse(r.next())?; + let mac_data = r.read_optional(MacData::parse)?; + Ok(PFX { + version, + auth_safe, + mac_data, + }) + }) + })?) + } + + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_u8(self.version); + self.auth_safe.write(w.next()); + if let Some(mac_data) = &self.mac_data { + mac_data.write(w.next()) + } + }) + } + + pub fn to_der(&self) -> Vec { + yasna::construct_der(|w| self.write(w)) + } + pub fn bags(&self, password: &str) -> Result, ASN1Error> { + let password = bmp_string(password); + + let data = self + .auth_safe + .data(&password) + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + + let contents = yasna::parse_der(&data, |r| r.collect_sequence_of(ContentInfo::parse))?; + + let mut result = vec![]; + for content in contents.iter() { + let data = content + .data(&password) + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + + let safe_bags = yasna::parse_der(&data, |r| r.collect_sequence_of(SafeBag::parse))?; + + for safe_bag in safe_bags.iter() { + result.push(safe_bag.to_owned()) + } + } + Ok(result) + } + pub fn cert_bags(&self, password: &str) -> Result>, ASN1Error> { + let mut result = vec![]; + for safe_bag in self.bags(password)? { + if let Some(cert) = safe_bag.bag.get_cert() { + result.push(cert); + } + } + Ok(result) + } + pub fn key_bags(&self, password: &str) -> Result>, ASN1Error> { + let bmp_password = bmp_string(password); + let mut result = vec![]; + for safe_bag in self.bags(password)? { + if let Some(key) = safe_bag.bag.get_key(&bmp_password) { + result.push(key); + } + } + Ok(result) + } + + pub fn verify_mac(&self, password: &str) -> bool { + let bmp_password = bmp_string(password); + if let Some(mac_data) = &self.mac_data { + return match self.auth_safe.data(&bmp_password) { + Some(data) => mac_data.verify_mac(&data, &bmp_password), + None => false, + }; + } + true + } +} + +#[allow(clippy::many_single_char_names)] +fn pkcs12sha1(pass: &[u8], salt: &[u8], iterations: u64, id: u8, size: u64) -> Vec { + const U: u64 = 160 / 8; + const V: u64 = 512 / 8; + let r: u64 = iterations; + let d = [id; V as usize]; + fn get_len(s: usize) -> usize { + let s = s as u64; + (V * ((s + V - 1) / V)) as usize + } + let s = salt.iter().cycle().take(get_len(salt.len())); + let p = pass.iter().cycle().take(get_len(pass.len())); + let mut i: Vec = s.chain(p).cloned().collect(); + let c = (size + U - 1) / U; + let mut a: Vec = vec![]; + for i2 in 1..=c { + let mut ai: Vec = d.iter().chain(i.iter()).cloned().collect(); + + for _ in 0..r { + ai = sha1(&ai).as_ref().to_owned(); + } + + a.append(&mut ai.clone()); + + if i2 < c { + let b: Vec = ai.iter().cycle().take(V as usize).cloned().collect(); + + let b_iter = b.iter().rev().cycle().take(i.len()); + let i_b_iter = i.iter_mut().rev().zip(b_iter); + let mut inc = 1u8; + for (i3, (ii, bi)) in i_b_iter.enumerate() { + if ((i3 as u64) % V) == 0 { + inc = 1; + } + let (ii2, inc2) = ii.overflowing_add(*bi); + let (ii3, inc3) = ii2.overflowing_add(inc); + inc = (inc2 || inc3) as u8; + *ii = ii3; + } + } + } + a.iter().take(size as usize).cloned().collect() +} + +fn pbe_with_sha1_and40_bit_rc2_cbc( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use rc2::Rc2; + type Rc2Cbc = Cbc; + + let dk = pkcs12sha1(password, salt, iterations, 1, 5); + let iv = pkcs12sha1(password, salt, iterations, 2, 8); + + let rc2 = Rc2Cbc::new_var(&dk, &iv).ok()?; + rc2.decrypt_vec(data).ok() +} + +fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use rc2::Rc2; + type Rc2Cbc = Cbc; + + let dk = pkcs12sha1(password, salt, iterations, 1, 5); + let iv = pkcs12sha1(password, salt, iterations, 2, 8); + + let rc2 = Rc2Cbc::new_var(&dk, &iv).ok()?; + Some(rc2.encrypt_vec(data)) +} + +fn pbe_with_sha_and3_key_triple_des_cbc( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use des::TdesEde3; + type TDesCbc = Cbc; + + let dk = pkcs12sha1(password, salt, iterations, 1, 24); + let iv = pkcs12sha1(password, salt, iterations, 2, 8); + + let tdes = TDesCbc::new_var(&dk, &iv).ok()?; + tdes.decrypt_vec(data).ok() +} + +fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use des::TdesEde3; + type TDesCbc = Cbc; + + let dk = pkcs12sha1(password, salt, iterations, 1, 24); + let iv = pkcs12sha1(password, salt, iterations, 2, 8); + + let tdes = TDesCbc::new_var(&dk, &iv).ok()?; + Some(tdes.encrypt_vec(data)) +} + +fn bmp_string(s: &str) -> Vec { + let utf16: Vec = s.encode_utf16().collect(); + + let mut bytes = Vec::with_capacity(utf16.len() * 2 + 2); + for c in utf16 { + bytes.push((c / 256) as u8); + bytes.push((c % 256) as u8); + } + bytes.push(0x00); + bytes.push(0x00); + bytes +} + +#[derive(Debug, Clone)] +pub struct CertBag { + cert: Vec, //x509 only +} + +impl CertBag { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + if oid != *OID_CERT_TYPE_X509_CERTIFICATE { + println!("not x509 cert"); + return Err(ASN1Error::new(ASN1ErrorKind::Invalid)); + }; + let cert = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; + Ok(CertBag { cert }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_oid(&OID_CERT_TYPE_X509_CERTIFICATE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_bytes(&self.cert)) + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct EncryptedPrivateKeyInfo { + encryption_algorithm: AlgorithmIdentifier, + encrypted_data: Vec, +} + +impl EncryptedPrivateKeyInfo { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let encryption_algorithm = AlgorithmIdentifier::parse(r.next())?; + + let encrypted_data = r.next().read_bytes()?; + + Ok(EncryptedPrivateKeyInfo { + encryption_algorithm, + encrypted_data, + }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + self.encryption_algorithm.write(w.next()); + w.next().write_bytes(&self.encrypted_data); + }) + } + pub fn decrypt(&self, password: &[u8]) -> Option> { + self.encryption_algorithm + .decrypt_pbe(&self.encrypted_data, password) + } +} + +#[test] +fn test_encrypted_private_key_info() { + let epki = EncryptedPrivateKeyInfo { + encryption_algorithm: AlgorithmIdentifier::Sha1, + encrypted_data: b"foo".to_vec(), + }; + let der = yasna::construct_der(|w| { + epki.write(w); + }); + let epki2 = yasna::parse_ber(&der, |r| EncryptedPrivateKeyInfo::parse(r)).unwrap(); + assert_eq!(epki2, epki); +} + +#[derive(Debug, Clone)] +pub enum SafeBagKind { + //KeyBag(), + Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo), + CertBag(CertBag), + //CRLBag(), + //SecretBag(), + //SafeContents(Vec), +} + +impl SafeBagKind { + pub fn parse(r: BERReader, oid: ObjectIdentifier) -> Result { + if oid == *OID_CERT_BAG { + return Ok(SafeBagKind::CertBag(CertBag::parse(r)?)); + } + if oid == *OID_PKCS8_SHROUDED_KEY_BAG { + return Ok(SafeBagKind::Pkcs8ShroudedKeyBag( + EncryptedPrivateKeyInfo::parse(r)?, + )); + } + println!("unknown safe bug type : {}", oid); + Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + } + pub fn write(&self, w: DERWriter) { + match self { + SafeBagKind::Pkcs8ShroudedKeyBag(epk) => epk.write(w), + SafeBagKind::CertBag(cb) => cb.write(w), + } + } + pub fn oid(&self) -> ObjectIdentifier { + match self { + SafeBagKind::Pkcs8ShroudedKeyBag(_) => OID_PKCS8_SHROUDED_KEY_BAG.clone(), + SafeBagKind::CertBag(_) => OID_CERT_BAG.clone(), + } + } + + pub fn get_cert(&self) -> Option> { + if let SafeBagKind::CertBag(cb) = self { + return Some(cb.cert.to_owned()); + } + None + } + + pub fn get_key(&self, password: &[u8]) -> Option> { + if let SafeBagKind::Pkcs8ShroudedKeyBag(kb) = self { + return kb.decrypt(password); + } + None + } +} + +#[derive(Debug, Clone)] +pub enum PKCS12Attribute { + FriendlyName(String), + LocalKeyId(Vec), +} + +impl PKCS12Attribute { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + if oid == *OID_FRIENDLY_NAME { + let name = r + .next() + .collect_set_of(|s| s.read_bmp_string())? + .pop() + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + return Ok(PKCS12Attribute::FriendlyName(name)); + } + if oid == *OID_LOCAL_KEY_ID { + let local_key_id = r + .next() + .collect_set_of(|s| s.read_bytes())? + .pop() + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + return Ok(PKCS12Attribute::LocalKeyId(local_key_id)); + } + println!("unknown attribute : {}", oid); + Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| match self { + PKCS12Attribute::FriendlyName(name) => { + w.next().write_oid(&OID_FRIENDLY_NAME); + w.next().write_set_of(|w| { + w.next().write_bmp_string(name); + }) + } + PKCS12Attribute::LocalKeyId(id) => { + w.next().write_oid(&OID_LOCAL_KEY_ID); + w.next().write_set_of(|w| w.next().write_bytes(&id)) + } + }) + } +} +#[derive(Debug, Clone)] +pub struct SafeBag { + bag: SafeBagKind, + attributes: Vec, +} + +impl SafeBag { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + + let bag = r + .next() + .read_tagged(Tag::context(0), |r| SafeBagKind::parse(r, oid))?; + + let attributes = r + .read_optional(|r| r.collect_set_of(PKCS12Attribute::parse))? + .unwrap_or_else(|| vec![]); + + Ok(SafeBag { bag, attributes }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_oid(&self.bag.oid()); + w.next() + .write_tagged(Tag::context(0), |w| self.bag.write(w)); + if !self.attributes.is_empty() { + w.next().write_set_of(|w| { + for attr in &self.attributes { + attr.write(w.next()); + } + }) + } + }) + } + pub fn friendly_name(&self) -> Option { + for attr in self.attributes.iter() { + if let PKCS12Attribute::FriendlyName(name) = attr { + return Some(name.to_owned()); + } + } + None + } + pub fn local_key_id(&self) -> Option> { + for attr in self.attributes.iter() { + if let PKCS12Attribute::LocalKeyId(id) = attr { + return Some(id.to_owned()); + } + } + None + } +} + +#[test] +fn test_create_p12() { + use std::fs::File; + use std::io::{Read, Write}; + let mut cafile = File::open("ca.der").unwrap(); + let mut ca = vec![]; + cafile.read_to_end(&mut ca).unwrap(); + let mut fcert = File::open("clientcert.der").unwrap(); + let mut fkey = File::open("clientkey.der").unwrap(); + let mut cert = vec![]; + fcert.read_to_end(&mut cert).unwrap(); + let mut key = vec![]; + fkey.read_to_end(&mut key).unwrap(); + let p12 = PFX::new(&cert, &key, Some(&ca), "changeit", "look") + .unwrap() + .to_der(); + + let pfx = PFX::parse(&p12).unwrap(); + + let keys = pfx.key_bags("changeit").unwrap(); + assert_eq!(keys[0], key); + + let certs = pfx.cert_bags("changeit").unwrap(); + assert_eq!(certs[0], cert); + assert_eq!(certs[1], ca); + assert!(pfx.verify_mac("changeit")); + + let mut fp12 = File::create("test.p12").unwrap(); + fp12.write_all(&p12).unwrap(); +} + +#[test] +fn test_bmp_string() { + let value = bmp_string("Beavis"); + assert!( + value + == [0x00, 0x42, 0x00, 0x65, 0x00, 0x61, 0x00, 0x76, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00] + ) +} + +#[test] +fn test_pkcs12sha1() { + use hex_literal::hex; + let pass = bmp_string(""); + assert_eq!(pass, vec![0, 0]); + let salt = hex!("9af4702958a8e95c"); + let iterations = 2048; + let id = 1; + let size = 24; + let result = pkcs12sha1(&pass, &salt, iterations, id, size); + let res = hex!("c2294aa6d02930eb5ce9c329eccb9aee1cb136baea746557"); + assert_eq!(result, res); +} + +#[test] +fn test_pkcs12sha1_2() { + use hex_literal::hex; + let pass = bmp_string(""); + assert_eq!(pass, vec![0, 0]); + let salt = hex!("9af4702958a8e95c"); + let iterations = 2048; + let id = 2; + let size = 8; + let result = pkcs12sha1(&pass, &salt, iterations, id, size); + let res = hex!("8e9f8fc7664378bc"); + assert_eq!(result, res); +} diff --git a/test.p12 b/test.p12 new file mode 100644 index 0000000000000000000000000000000000000000..a8a16c3a2a2c932ec8e2f5907452508f13cf140a GIT binary patch literal 3462 zcmY+FXHXM})`mkG2@rya(tDAfP(@lmq<2(0NkHi(m{$y6r_Ic%y;kJJ7>yUKoEdbAT$A?1BHtcb%;j@Dr%|%0^$~! zfROu*rO*WGk$)m+0fD;nH);b?0e+vxe-bJ*2bAXjKhRM@(ez+g`jT}GGP~s+5C{W= z6R2ZJkqR6zO4K)m7>^DlA zb9bGeWlXa&fFqHu z$Z9Q&tNK}6bv~>;H&A-`^-uHrwV!#b{pvDBpP5Z>bk2WlLd9Z?<3+tMhI$wsZaia* zYr=+U8m?hZ3hXXE{DF)=8wBZ7L-Al{5R;-m^e_MiOG9%#9`G8h$ovDc3?im0$Li zh7DZce%bo~)KSlDt=*E9 z`%}CkPsA6Yt$u4*;Via_4|6$lJcO&$GIpp?7w(8~XzSv+{!$g|P~g|i89Ej~h7y_C z{*%)$&rfb`_Tsivx{FzZv*}xJ`nQt1l+qGtJpWwEPs1!n!P`H=$ zRXCtk3FmFWY0r&S)#{AO^vXg`+RM2ctSqL6cM37x3^eHixCyQV=P3+AOZo~WF*~B} zSt&_YN`ANRn=aqf>|?Lh)8e zg4s(&BL!gH*6qsUX|@|4oRmcPf|5%;;}z2-E8ozg8&1RTI7O@*=*4o6-3#AqlTP`t zIUUv1&Uh3lyOw+vFdw<1cEx5q{hiU>FTfwh=khmY)QfU1=}lE0o2YJ|&UR0jgDW*(!@wS$1Pm2fFhese2<=W!p?umV8T(0+GA$aLkcr}hUQi#BU} zBF;2O#dh=pNn2qz`SOcC0|JWUh~Jh=)U_C!j)NBt#{ev&>YN-!X?8>Sr=H-ws~H0t zB1zBt!@bJH7aC>^d8NG+Q@c-=&}L&37vJc9gy(K6I5}UCd?nU_f4z!YSZkntbk67Q zlMT^bdv)`DAl&bJS|#PhUHb6NkGtz*>wINSO=lWCt-`^vhqVkN92wKKx36@UcjP-V z-82+8^?0a);IO>2L~0$!zCsECWuAS?DG#_z;fqUUd!OFPV=gr+2*~vSfgHT!Vyr0! zZUec|mzL6jr^C{)1foP`hY}w_%v!7*Gr;d&(Y$rB2g^hV`mEXvjDMo}YlSg1-;^MP~pf(KK?a-led0@T%rX_LLEM~zwwuT1%66=M^&ketZRgR(WbL^~g$96<~5 z+bLf|51#~>e=*`^A|X{-jg zI5vSYQvxU_zOlJ$Wr8jhB4=mU_8?Es9 z&PPsHm>-Z!)*jfGRE$)v{vgW)>Hrs9Xj=lyA=}5xK`r(iDKtMMc2h?dn+uRLn~Vjp zUQU3MrEFkDj%Ia~IuG=AeHotkM7H2i=JcnXQ1QiS-wqr6NMu)(9#Tyw;iy@u{$648 zX;4T1U>*D03g*!IuniM?_bXn(auJ~Ev(YPhF=Zcr)Mv9KX(yQFhNfp$X6S$L(D>tf zNwoda+#BLJGmEXS->S^v<@@Yn4;6ALa|-sE4LK34>q+4E6VHbL&W0Jr%b4#^WX|-N z=>GCG7gb@Ms_uA%Vbrja@;K6AYSc<%<0tcyYih-$g~U`t#qtfGYsK5z-QHXP3B_w# zeRs`6tzj~z;#NksNB66_k}U6Lm6HUmS*`(i8rt{i#9csu=z|Fm?cZ4A_hO*5|JFDS6|jH+-a`|>+y7&C(BF3Vaam8x zpy99i+wNcjxF0HMo&4<2Ps=EtQ)SIHv75hcin6MdrBl88Rj?0m`oaJYwPRXf&QyWu zm4@fVH5S7O?I?G~5f-HWkZwq~1rNL6~4gM zY6gNdfrw3;iU>I_1N2Iks{GjWlw6k>-^zi+He)-L2Bk+>X^$*AwXvwIe+@q7b0>)w z?b+Oy3I|uPhYfV+D&|q2bbvkgI9&9zCu&zGxx>!$_m?X0*BiKYtKTgnA0$W?O~kkO zs(Cf@y3)AX)}lr@eal{!%X@(XNpzQ(uJbN|3PO8M7W+)qkxb-33 z2ST{54wifFCgu{A!ke9om$v1}Qm*{pI{BGB%4>B-^);kpif|78wWCWlE<&xKcJtt1 z|11TD^)vdP5Re54VS!b9!ykLo4eS17!zd<#xLA5T%=BS~S%{nYn#&}#vJJ|%Gpytv zbDL%LUiyJ<*)1-isoou^s+tU^ptF|Uj=x+NT&<5?t{+nz)jT>cj+ab%QdMw1HJ0bv zprL2Aguf5#*42V7szsWbKSCaGL9b2-^)rrMv~N8YMoFt z&Glm6FnCZRc4OC{@BfQB96DtA6c#DWYtIPqz|+I@HS|t2U8M@MONIKWWKYb# zvB~*AIv+Ny$olaYu|-?)v3T;d{1l(4zeh{K%da{=`GyLE$2vgnsnzj|1GAuP5-pe zAcNI0Fx_3A-4ynD3EK5_A+NwWcuA#iiR!R)(9fpNy!CH?ZVP6+u5?k%;~gBMq;T*F zysiAtKklpAT^zFGVg5?JBija@UO#Qya=ZDGlRFu2zIgpX+x3u!vp=lIuefP}$}M(c zSzQ6&W8^g<7qHDV1KJ-ZF98v&!*cx#8EOmhKA7Gez2L3BvCD_p*|s$t$LaO(8h4S- zpVm8uEuSd!y&Cj}f%~n^Mc3sYihs-T`vIu^1+j_dKux4&DWyQXeIFl3&7YyJ5@cupS zX#RhKYf{(2Or;$68wK##xRi5gAEXEsi-toXV)P&&8w3Dm^0C=FDQDPA egLOtXWEfqv2e5I;f??*cP1! Date: Sat, 11 Apr 2020 09:48:40 +0800 Subject: [PATCH 02/87] first commit --- README.MD | Bin 138 -> 60 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/README.MD b/README.MD index 9b93c063e9825c527d67fcc048c76568dd28d6be..dc699735a6bc6211838618797cab8060a4aea208 100644 GIT binary patch literal 60 zcmY#ZC@?hQ;^HbOElO1=DlINiD9BDOHZ)Qw$Kf@8Z~*`c#}Ntu literal 138 zcmezWPnki1p@6}V!H9vEfeVfc7)lw67*c_9ML Date: Tue, 14 Apr 2020 13:48:59 +0800 Subject: [PATCH 03/87] allow parse not support type --- .gitignore | 1 + Cargo.toml | 2 +- src/lib.rs | 193 ++++++++++++++++++++++++++++++++++------------------- test.p12 | Bin 3462 -> 3462 bytes 4 files changed, 128 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 96ef6c0b..6892182e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +test.p12 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index fac2b16f..81bb0f22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.1.0" +version = "0.1.1" authors = ["hjiayz "] edition = "2018" keywords = ["pkcs12", "pkcs"] diff --git a/src/lib.rs b/src/lib.rs index 1f271f35..59d8142b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! +//! //! pure rust pkcs12 tool //! //! @@ -46,8 +46,8 @@ fn sha1(bytes: &[u8]) -> Digest { #[derive(Debug, Clone)] pub struct EncryptedContentInfo { - content_encryption_algorithm: AlgorithmIdentifier, - encrypted_content: Vec, + pub content_encryption_algorithm: AlgorithmIdentifier, + pub encrypted_content: Vec, } impl EncryptedContentInfo { @@ -71,7 +71,7 @@ impl EncryptedContentInfo { .decrypt_pbe(&self.encrypted_content, password) } - fn write(&self, w: DERWriter) { + pub fn write(&self, w: DERWriter) { w.write_sequence(|w| { w.next().write_oid(&OID_DATA_CONTENT_TYPE); self.content_encryption_algorithm.write(w.next()); @@ -109,11 +109,11 @@ impl EncryptedContentInfo { #[derive(Debug, Clone)] pub struct EncryptedData { - encrypted_content_info: EncryptedContentInfo, + pub encrypted_content_info: EncryptedContentInfo, } impl EncryptedData { - fn parse(r: BERReader) -> Result { + pub fn parse(r: BERReader) -> Result { r.read_sequence(|r| { let version = r.next().read_u8()?; debug_assert_eq!(version, 0); @@ -124,16 +124,16 @@ impl EncryptedData { }) }) } - fn data(&self, password: &[u8]) -> Option> { + pub fn data(&self, password: &[u8]) -> Option> { self.encrypted_content_info.data(password) } - fn write(&self, w: DERWriter) { + pub fn write(&self, w: DERWriter) { w.write_sequence(|w| { w.next().write_u8(0); self.encrypted_content_info.write(w.next()); }) } - fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { + pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { let encrypted_content_info = EncryptedContentInfo::from_safe_bags(safe_bags, password)?; Some(EncryptedData { encrypted_content_info, @@ -141,14 +141,21 @@ impl EncryptedData { } } +#[derive(Debug, Clone)] +pub struct OtherContext { + pub content_type: ObjectIdentifier, + pub content: Vec, +} + #[derive(Debug, Clone)] pub enum ContentInfo { - Data(Vec), - EncryptedData(EncryptedData), + Data(pub Vec), + EncryptedData(pub EncryptedData), + OtherContext(pub OtherContext), } impl ContentInfo { - fn parse(r: BERReader) -> Result { + pub fn parse(r: BERReader) -> Result { Ok(r.read_sequence(|r| { let content_type = r.next().read_oid()?; if content_type == *OID_DATA_CONTENT_TYPE { @@ -161,20 +168,26 @@ impl ContentInfo { }); return result; } - println!("undefined context type: {:?}", content_type); - Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + + let content = r.next().read_tagged(Tag::context(0), |r| r.read_der())?; + Ok(ContentInfo::OtherContext(OtherContext { + content_type, + content, + })) })?) } pub fn data(&self, password: &[u8]) -> Option> { match self { ContentInfo::Data(data) => Some(data.to_owned()), ContentInfo::EncryptedData(encrypted) => encrypted.data(password), + ContentInfo::OtherContext(_) => None, } } pub fn oid(&self) -> ObjectIdentifier { match self { ContentInfo::Data(_) => OID_DATA_CONTENT_TYPE.clone(), ContentInfo::EncryptedData(_) => OID_ENCRYPTED_DATA_CONTENT_TYPE.clone(), + ContentInfo::OtherContext(other) => other.content_type.clone(), } } pub fn write(&self, w: DERWriter) { @@ -189,6 +202,11 @@ impl ContentInfo { w.next() .write_tagged(Tag::context(0), |w| encrypted_data.write(w)) }), + ContentInfo::OtherContext(other) => w.write_sequence(|w| { + w.next().write_oid(&other.content_type); + w.next() + .write_tagged(Tag::context(0), |w| w.write_der(&other.content)) + }), } } pub fn to_der(&self) -> Vec { @@ -202,19 +220,19 @@ impl ContentInfo { #[derive(Debug, Clone, PartialEq)] pub struct Pkcs12PbeParams { - salt: Vec, - iterations: u64, + pub salt: Vec, + pub iterations: u64, } impl Pkcs12PbeParams { - fn parse(r: BERReader) -> Result { + pub fn parse(r: BERReader) -> Result { r.read_sequence(|r| { let salt = r.next().read_bytes()?; let iterations = r.next().read_u64()?; Ok(Pkcs12PbeParams { salt, iterations }) }) } - fn write(&self, w: DERWriter) { + pub fn write(&self, w: DERWriter) { w.write_sequence(|w| { w.next().write_bytes(&self.salt); w.next().write_u64(self.iterations); @@ -222,35 +240,44 @@ impl Pkcs12PbeParams { } } +#[derive(Debug, Clone, PartialEq)] +pub struct OtherAlgorithmIdentifier { + pub algorithm_type: ObjectIdentifier, + pub params: Vec, +} + #[derive(Debug, Clone, PartialEq)] pub enum AlgorithmIdentifier { Sha1, - PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams), - PbeWithSHAAnd3KeyTripleDESCBC(Pkcs12PbeParams), + PbewithSHAAnd40BitRC2CBC(pub Pkcs12PbeParams), + PbeWithSHAAnd3KeyTripleDESCBC(pub Pkcs12PbeParams), + OtherAlg(pub OtherAlgorithmIdentifier), } impl AlgorithmIdentifier { - fn parse(r: BERReader) -> Result { + pub fn parse(r: BERReader) -> Result { r.read_sequence(|r| { - let oid = r.next().read_oid()?; - - if oid == *OID_SHA1 { - r.next().read_null()?; + let algorithm_type = r.next().read_oid()?; + if algorithm_type == *OID_SHA1 { + r.read_optional(|r| r.read_null())?; return Ok(AlgorithmIdentifier::Sha1); } - if oid == *OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC { - let param = Pkcs12PbeParams::parse(r.next())?; - return Ok(AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(param)); + if algorithm_type == *OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC { + let params = Pkcs12PbeParams::parse(r.next())?; + return Ok(AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(params)); } - if oid == *OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC { - let param = Pkcs12PbeParams::parse(r.next())?; - return Ok(AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param)); + if algorithm_type == *OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC { + let params = Pkcs12PbeParams::parse(r.next())?; + return Ok(AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(params)); } - println!("unknown Algorithm Identifier : {}", oid); - Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + let params = r.next().read_der()?; + Ok(AlgorithmIdentifier::OtherAlg(OtherAlgorithmIdentifier { + algorithm_type, + params, + })) }) } - fn decrypt_pbe(&self, ciphertext: &[u8], password: &[u8]) -> Option> { + pub fn decrypt_pbe(&self, ciphertext: &[u8], password: &[u8]) -> Option> { match self { AlgorithmIdentifier::Sha1 => None, AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(param) => { @@ -264,9 +291,10 @@ impl AlgorithmIdentifier { param.iterations, ) } + AlgorithmIdentifier::OtherAlg(_) => None, } } - fn write(&self, w: DERWriter) { + pub fn write(&self, w: DERWriter) { w.write_sequence(|w| match self { AlgorithmIdentifier::Sha1 => { w.next().write_oid(&OID_SHA1); @@ -280,18 +308,23 @@ impl AlgorithmIdentifier { w.next().write_oid(&OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC); p.write(w.next()); } + AlgorithmIdentifier::OtherAlg(other) => { + w.next().write_oid(&other.algorithm_type); + w.next().write_der(&other.params); + } }) } } #[derive(Debug)] pub struct DigestInfo { - digest_algorithm: AlgorithmIdentifier, - digest: Vec, + pub digest_algorithm: AlgorithmIdentifier, + pub digest: Vec, } impl DigestInfo { - fn parse(r: BERReader) -> Result { + pub fn parse(r: BERReader) -> Result { + dbg!(10); r.read_sequence(|r| { let digest_algorithm = AlgorithmIdentifier::parse(r.next())?; let digest = r.next().read_bytes()?; @@ -301,7 +334,7 @@ impl DigestInfo { }) }) } - fn write(&self, w: DERWriter) { + pub fn write(&self, w: DERWriter) { w.write_sequence(|w| { self.digest_algorithm.write(w.next()); w.next().write_bytes(&self.digest); @@ -311,13 +344,13 @@ impl DigestInfo { #[derive(Debug)] pub struct MacData { - mac: DigestInfo, - salt: Vec, - iterations: u32, + pub mac: DigestInfo, + pub salt: Vec, + pub iterations: u32, } impl MacData { - fn parse(r: BERReader) -> Result { + pub fn parse(r: BERReader) -> Result { Ok(r.read_sequence(|r| { let mac = DigestInfo::parse(r.next())?; let salt = r.next().read_bytes()?; @@ -330,7 +363,7 @@ impl MacData { })?) } - fn write(&self, w: DERWriter) { + pub fn write(&self, w: DERWriter) { w.write_sequence(|w| { self.mac.write(w.next()); w.next().write_bytes(&self.salt); @@ -338,14 +371,14 @@ impl MacData { }) } - fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { + pub fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); let key = pkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); hmac::verify(&m, data, &self.mac.digest).is_ok() } - fn new(data: &[u8], password: &[u8]) -> MacData { + pub fn new(data: &[u8], password: &[u8]) -> MacData { let salt = rand().unwrap(); let key = pkcs12sha1(password, &salt, ITERATIONS, 3, 20); let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); @@ -370,9 +403,9 @@ fn rand() -> Option<[u8; 8]> { #[derive(Debug)] pub struct PFX { - version: u8, - auth_safe: ContentInfo, - mac_data: Option, + pub version: u8, + pub auth_safe: ContentInfo, + pub mac_data: Option, } impl PFX { @@ -442,7 +475,7 @@ impl PFX { } pub fn parse(bytes: &[u8]) -> Result { - Ok(yasna::parse_der(bytes, |r| { + yasna::parse_der(bytes, |r| { r.read_sequence(|r| { let version = r.next().read_u8()?; let auth_safe = ContentInfo::parse(r.next())?; @@ -453,7 +486,7 @@ impl PFX { mac_data, }) }) - })?) + }) } pub fn write(&self, w: DERWriter) { @@ -652,7 +685,7 @@ fn bmp_string(s: &str) -> Vec { #[derive(Debug, Clone)] pub struct CertBag { - cert: Vec, //x509 only + pub cert: Vec, //x509 only } impl CertBag { @@ -678,8 +711,8 @@ impl CertBag { #[derive(Debug, Clone, PartialEq)] pub struct EncryptedPrivateKeyInfo { - encryption_algorithm: AlgorithmIdentifier, - encrypted_data: Vec, + pub encryption_algorithm: AlgorithmIdentifier, + pub encrypted_data: Vec, } impl EncryptedPrivateKeyInfo { @@ -720,39 +753,48 @@ fn test_encrypted_private_key_info() { assert_eq!(epki2, epki); } +#[derive(Debug, Clone)] +pub struct OtherBag { + pub bag_id: ObjectIdentifier, + pub bag_value: Vec, +} + #[derive(Debug, Clone)] pub enum SafeBagKind { //KeyBag(), - Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo), - CertBag(CertBag), + Pkcs8ShroudedKeyBag(pub EncryptedPrivateKeyInfo), + CertBag(pub CertBag), //CRLBag(), //SecretBag(), //SafeContents(Vec), + OtherBagKind(pub OtherBag), } impl SafeBagKind { - pub fn parse(r: BERReader, oid: ObjectIdentifier) -> Result { - if oid == *OID_CERT_BAG { + pub fn parse(r: BERReader, bag_id: ObjectIdentifier) -> Result { + if bag_id == *OID_CERT_BAG { return Ok(SafeBagKind::CertBag(CertBag::parse(r)?)); } - if oid == *OID_PKCS8_SHROUDED_KEY_BAG { + if bag_id == *OID_PKCS8_SHROUDED_KEY_BAG { return Ok(SafeBagKind::Pkcs8ShroudedKeyBag( EncryptedPrivateKeyInfo::parse(r)?, )); } - println!("unknown safe bug type : {}", oid); - Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + let bag_value = r.read_der()?; + Ok(SafeBagKind::OtherBagKind(OtherBag { bag_id, bag_value })) } pub fn write(&self, w: DERWriter) { match self { SafeBagKind::Pkcs8ShroudedKeyBag(epk) => epk.write(w), SafeBagKind::CertBag(cb) => cb.write(w), + SafeBagKind::OtherBagKind(other) => w.write_der(&other.bag_value), } } pub fn oid(&self) -> ObjectIdentifier { match self { SafeBagKind::Pkcs8ShroudedKeyBag(_) => OID_PKCS8_SHROUDED_KEY_BAG.clone(), SafeBagKind::CertBag(_) => OID_CERT_BAG.clone(), + SafeBagKind::OtherBagKind(other) => other.bag_id.clone(), } } @@ -771,10 +813,17 @@ impl SafeBagKind { } } +#[derive(Debug, Clone)] +pub struct OtherAttribute { + pub oid: ObjectIdentifier, + pub data: Vec>, +} + #[derive(Debug, Clone)] pub enum PKCS12Attribute { - FriendlyName(String), - LocalKeyId(Vec), + FriendlyName(pub String), + LocalKeyId(pub Vec), + Other(pub OtherAttribute), } impl PKCS12Attribute { @@ -797,8 +846,10 @@ impl PKCS12Attribute { .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; return Ok(PKCS12Attribute::LocalKeyId(local_key_id)); } - println!("unknown attribute : {}", oid); - Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + + let data = r.next().collect_set_of(|s| s.read_der())?; + let other = OtherAttribute { oid, data }; + Ok(PKCS12Attribute::Other(other)) }) } pub fn write(&self, w: DERWriter) { @@ -813,13 +864,21 @@ impl PKCS12Attribute { w.next().write_oid(&OID_LOCAL_KEY_ID); w.next().write_set_of(|w| w.next().write_bytes(&id)) } + PKCS12Attribute::Other(other) => { + w.next().write_oid(&other.oid); + w.next().write_set_of(|w| { + for bytes in other.data.iter() { + w.next().write_der(&bytes); + } + }) + } }) } } #[derive(Debug, Clone)] pub struct SafeBag { - bag: SafeBagKind, - attributes: Vec, + pub bag: SafeBagKind, + pub attributes: Vec, } impl SafeBag { diff --git a/test.p12 b/test.p12 index a8a16c3a2a2c932ec8e2f5907452508f13cf140a..e1764aacd1a0786c87ef065c5b473b1e8bb1cc5d 100644 GIT binary patch delta 3259 zcmV;s3`FyW8-^Q@U4PtqUhX%}a!~>T2mpYB2Z#_~>6xb48h|HexpcNe+$i$(HLM~T zu@HwrcpamFj!}!jZIfj#Md+Ia%2x`#JH1=l__8&4(YKuFPD;0MI;zmNS#Af5#3`8A zd2I}zBGtx}+j;X%MX$9OchV^ld+dUBKZIi;P%-J~k7!*^lYbZV|G7+|!irMLjdQV= z&}heM6B&gm|I6@O=W`h*p_OHEnRKY6JEAvTH1aBv0*jIkI5z?WS@2c4D40}qnpx9d z<;Y&%mgr(Mc+d<(0l@yjB4Y;)NRw&xQeB^z=MGCFc1aEzWO9tOz^The+bMDP@VEF_ z$R}_WYxr%@SAVdzpmT~#%+tfITJkWX6$`24-t7I0Go&a(x1!E|bx2W*lHsCMTqn>| zlFp6cX-jpX;l`=h_#3B4TsKCc_BC49&wDkLuG!7Rbt?+$6ncF2OErf!*@m7@-cP0+ zp!PrQ@eGd(uGoIBh)OT-ScTscE+CIinTw5Wl+F2S-GAnqXT^%5TSubhHVKIz>F(ow zLE{5TsDTRw^x;SE8XpUnh6OAbUsj}mq{$sF+7Qe+5N8mfT>(-5z9wWLB&sPvqgDUC z87Vv1ZI{{rO&uR!z=eHK&wKam8+8)WiyT8`c~lLQ{ri+I5_^M!i8qhXV<|XwE?|eW z0nh0RxqrvU<(apiy$HNCiLww5#ZWy)P5A7^Bc5p`G8bz>fj+<;VvdN=awiy5DP9S? zW4vy5Y-z?Bct}WPzYBe8dR5+piG<0#d^gvA&8=)fe~v2nvp+HEQlnP|1x@f|fXh74 zCgMGft46*VTu7$M|4P4Ibn+Qb<{$yirOu#6s( zcCYtDW^jk4rfvu zmVd*_vqM8vVJ@(t@CTAb3=8BIZE3as7%%`Uwc?smh$3X)z0ee~H;`rzW>mvgv34P+ z=ZAA$%4uLOis7LPxyb^{zzeTO-QDH*2U(p(1{C&zi;bJVhj|JgCCn7lfAcyyex2#K zRB>4B2^i4wLtjNC1vNKx0rXd@nw%(PUw@#c`PNWzNnC-yx_zmyomXc(`dbOV#c=f^ z#6%z`*b^@g1L??N+a)p^*8Sfr^D4}4Y5Po@&OIM{pOp*+zBpe9FUJSClsu?UCuKj{IMJB7GU=rl!Ljqm$6F=`ipYS8GcI+be+s7Nltx^0yF@_pJkG2 zjdvNBYIGLtoGM|E42}MJ`BSQ$mW_U?{dCm%M_GqK6|Ck=#xF1~<4Z(^+dr)_}& zt|Rdga;MF$bojfKAz9TuBxCpEMBa{ofR(W}Oys#+RXLoGT5G zZ7lo}Akmq*+MT)UGeMum%6~QLd)xG6bOVRNx0+CgXd)gJ#9zgJbeNtre+`XzODzKDl=!^-a2q(*zu#(ykQ<@@B;8wCbQDrh?s1g9nZ+28waO@q z%|JcSLB1>c+(dP^3Qc&4+((0))uSw(X(Z$Rd?l+g@A_}H8ogn^&Z|Yekt=`hS_uqA z)_7|%k+`c>MPPTe=6}Din=<*m;@}3vVdFe*nUI(H`3g&GwuiKBD9*;EfD1TRbBCX7 z5ZN3(102bV`=&rsk*-{r&^-2mS)yIaFYfsJ*@Jr%A3$2iitaH@pLv>O9|@Z z>33$}e3jS`YjB&c((uE9f$PDvFYOsXS=t+3eq&hJjR+ou%;Lp3hk(OQS`hDw?+Huf zzIDX9(&osu7`5i3&|W-IfO2E{UGj3TyFY3VbVR>aHQ^4=&VO>S{=%NYAmU2_wDe*I zf8tjpu@xn1lP9`W#W8D4qpg_g7=t?RqV{&N+U>5k`->krtS7`SuJp*2XiSoDqm%Uq zMt{z8P($3)T&4m72ml0v1jw2@s?bVy|B`J@Zuv-xC;ijb1ayZAb)lUZe^oKXN zevmFmCnb-}Q{MJ=s&1A;ASHJlekdzv8d6)reS{s0;ufJhy)Rq|?sWOz7|91lSAX?8 zc(u@qXY&JndVE(e5qwly2np7M(@Yc0IupXj)Sv2|yWFQ?w^9BB+4MEB(iI&Sq07v) z*s-GkBRd>YQs4iDU_)Mf`*k)*-SbBM=O@YX3Ku+T%Den8(j1C%?Q3IN?E*=~Eyr{oGw95pVuK*LMgfda zn?ZgwCbx7eeqE@20~X=XdDb;7H{;;e@tKau^dsh&U&Xl7C>oK&U&s}PQGXeRl;d71 zK;^ygb^?F#Sc@+z;yhl7Qb!85!V_~i@X9JcQs%u#V#vWh2@-jpJV#;#$In|Yt?Gth zXx71?N#cOT#&JGd7g-0v(LlBr%eg_3Pq0%kA);EL4Le3q2xuN;=7a8gl zM-!tlBN=DXP5tw%+a*R-BD^0CplWV*v2c+#XxN}XR5;Wvvshxf{twvpf`2u{jNi!lE8Fz` zY-X;8nb+$Pux$3xWOFOX&61r&jihFUAj48b2qMS*wdUYjeyf05^;8D;Ma5(J_9w`r zAy26LsWDlN!5ItCP;(7!QJAt5PqnVGWl{VkCb2YhbtG8uq<6VvFg-A)*a8KIIVaXC zBB*v{#4F)-0S1bCc7Mfg;kNur64zC+8y345Z~1fXG6Z_FP28h=gT|2wC26SDyHtxBRC)`tVC_Osl+5?o6qZU3rs{#MElZa7|ld` zr^O-YfWYcZfdVopymh_D#92ROT*^WU_#ERKuABB=+i{=HX@9AJ$?v*JFh3H47xsM? zgX(XR8hG|fv-}Bu{D<)U`6^6&c1P#tegKEf@tQW%X9T5f3_@4;@q?@6zpU}x?Rgdf z-^@-3a0-Ge`EO)aZ3U6}0ZS3BdUH<}qD(41ydWof6F^*XVExYdWr9f$Pf4;ppkc(H zMp6|+r0HL90AYl%zUu{G(bwk|?wxE%B-dikmdpuf-Ub{F9Sckk!xkN tb)mqM6Ae)!PQG$mpYUt=^jlcXr#Sva)6+C81PBCt7n3jT7u5m+2mp?$LLdMD delta 3259 zcmV;s3`FyW8-^Q@U4N3`TCWeAxv>HQ2mpYB2Z*|ZI2;J{sPEnjmY=Bou6ew!pMMCwopv(Lr1n--u-753 za6=csBJj}?8}gy5Z5HjpDUnL}F%Bvq)tbuahC+k@*1d^BQ&FglrJ zXj);gfvCx|NamPJv&;MOQ#IY^9X$rsSu;B%iVRBQ2{m$lBh8+zhWK0zATzSgjLT#K zdE+U;i^p0+d|Fg}$$#;v+gEC}y*(1G7yy<#+81EcW%+H>YP?`9T-Hh7n#|jh1YsD{ z5_EN8Wv9#8&9~`rTrSy;j%3eaV54)5rFzAEVrH1#b(`X1dJNo8ihs^HDGZZHM3@UN zi$4rl0)K~Yx{}p1xR4fxKELXA+$HRBQF@I@vki0)34|u#155UTXQsNvaj^|)5IKLW z@>q((MXv&Tr@A7HEXgh3Y_0j{b$jhdTx2P)ZiUQsB{YZBo~3V8`V0MweNwh5;<_0S z2tYg`xpP^w^yb^LR_IN=UAvYa`eNx$0XTd+FMnc{4y>Hg-!wM37U zwUDa3TlzfZ^xm!HY$0;2?e6nGG7q{*s$^w+It@BGe2Y?*I!-tOvgtyr_}CgJY#sEB z34h)@XR{I!QQ%o|e)lJ3&aoXSTC@%;o%m~^zel?JB4M1qt_Nm)FS?ts+7kfXgX2IE zThfxTPE+9m^-ujaCr;`?7RAHDkAYFHBDV98@`Uk=^^V8S%G}>xyLgerK874nW-pYf z9x~`YAX|jPaKOE72vWnZqSpX* zqlt#_;-x`G61Mnz-t*A_7|=l-9HNn3&3$-n1nv`-$wMiPlgNN>s4U*J*-arbZaR>| z`rt59(bWF%4E2DW>NsU){PWXWE3||EKzJ|z#vvJ&?Nw!Yy$&hZ;6Lm^h|2WorV8cGIsmUGJgn*hApeU zJ0gNBS}UnP$s=p3xat1x2$&5eaO7Lb($fmc;}U?>GEZ{MVXr78V&d^CA#ig|paX2( z??*8IV?(oFp(T1RYVCddPz$MJuUsnJiu*}cm175ddk?kS3R_;TA_SaDd(4k3od#k! zpf3}F*ii0-XthS(nNcG&I)8|Z=?@(b*UU7porYa-CY&W#EJBarzOO2;1}>I~R6pV} zXJ2=9R?C9x)!TOne9;DxZ)Xr)^QtNQ>$*9;!3lL$%|Hg+N*}6%qjn<+_+I;^^ z3F)0Rv`J$%MN3XOA7hN`FDNeu1J`Y&(z55S(wk8@_>mb*Vu>0gsDDv{1DuMr!cipi zD9${qZ3RggVEzz(Q+J4zwd&^s$A%mhz@)PPKjCOK=JkCZqoQ$Ltyj_bKfv4h60ZEo4QjLBBZYczV9b%(5G;suzB< zf9FtOYY}dHHGu=z{Lm3fhp5Z-vzr)=M~hOOev+80mP^5GLw}d@rG;SN+@^-o7Z_Y| za^y4f|G%4~Y(3hi7ms0iTiT2SNo|57ax9+(0Q}An(x?|z)R@`FP)Yf5Rw_Li)~>#2 zKN3S(F>Lop6tzZ;8Y9{v+OM!HGA1iNek-3%lSYbMT|(&I115?^S@LO~FXca$)`{cK z>d^159*tO2nkT8cC0ZC203ft#HM@PnJ6=+DwN!Dj7Yfk^!;#pzi=>;SdRa zT@>CIm^}qbr+}V`wWA4wy)SDL&lnF&%}IR2S1)TQARtV;M2}lDcPRZ5F09&doxKTB zxoXHShKb@@&tBqhFNG8%S8;&ZAU~WO!hg-&p1?)Mb%F)ZS`s4}om_%n61i#ZHaysM zr7@%GG)|$EVX#GZ14RN2=v%6RH6Bbb=VDIgkPO7nQg&9JZ`GDB zu8pf<`<6URd}_xJYsq{cS@Gh;Q-6$|hC6_`W~1QhHrg?1Bk;f@7i_7pNYG0|GKZpK zV|1|4;IC&Uy92#edwX=6I1uLi4)z5DSGo;XVL+xMh&O6sYj@{Phk=IQr})kk!Pyrk z9~Tv8)ds7)1{&;^tSGaV&nMqTW5HNpu%$A23&$Umcm zxX|4!eJ+o8tf2kY(Vl6vLrPiTeN_m0xy;}y2ivvq z0R43YpyI+0a$HH$e=i)qO#n>Myd}4ksb)mt@iC_h~64}O7(&VA_tg!&y zQT%EE0J&rQxKgc}A0~?Ogx({GP(VFE&$^KFl zj|8%6o_&V}QGg3bMR-Q~^hH)mh^7#1^nvY&=9BNY2Y?3KPYA3aL8YH;vU(>ASIpNw zyYi>ZZpUy%_@XP`J;xffdc${vXxdOsN**{g+J`$fhid7Xk%N9*R)5xE4=5pE5&&#{ z4hTy_O8P}=z0ia@9zJEAitv`(ZY<*r$2 z&W(hL*Dc^7BwR=sPY{-A)f{k!aBY<^^v0*`XW`k=Al4iVg=@i`Pbdi2~Oyto73{xQOj~m@B3+Cn~Zg$)WrK@qJKF%_F_O{m{ zv#s{(C7WsE!t_>(V`PLeVhPlJTsHQXRX$z*^Ba8#bUVe*>VHhYZuVTfox>uIj=&N( zl%&B&Y4Zvl$bZCgqqg1wYKYY0K{$QHPHxmSb?Ow^1Mkk--~tWg&Nay1mO$Q$a6iTD zN_*+Y(f?mz+q~ss9Qzi4uWKp5_FC*sxzqIB#X}BFcJI5mq82t^EbyFi^#HK>1zUw2 zFVvBmJCF1V{$XG+1Gw6i_a|hH{LC9a)c0S$j8D?^6r>HGMbStvHsjWW=1lGz9B*c+ z_YS#{1UN3Z^ah6})z}+i(kj5c=Z_95v-^pY{S{`uiClpI2I+519CJR%2Kc=QA4xun tZ9{C66Ae)!a9i&BsSxgw2*8E3mQMb?bs8Qt1PE3L=ro~pI-vpr2moS+GY Date: Tue, 14 Apr 2020 13:49:41 +0800 Subject: [PATCH 04/87] allow parse not support type --- test.p12 | Bin 3462 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test.p12 diff --git a/test.p12 b/test.p12 deleted file mode 100644 index e1764aacd1a0786c87ef065c5b473b1e8bb1cc5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3462 zcmY+FcQhLe+lP}#5Uci#+O=9j5glp<6*H(!V-~eX)d)hRcqmG(s#&Vks?pTm1d-C( zeH5+QR26BAug^K(_dV}>?sK2}oa=Ya{pat3 zz&K{*KUe{dqZ|J>qAkMFb^SqYARyq+Y5F$-!eO)w|NDUnNDF5H(=UWO?jT5kW*`tf zAO=VGnAK@Lw|tgAM$(-S+^oGIwf{@GT9mJz6|Wg4SQ?XRmfY?0)J+b#mJcCXF|^-k zx0^d|P!9Xh@_fzMpvC|CYjU%VFI{qa)rB2E91=9!Z~%?Ad(};=5FwToWY`s0D*1xED2IM7QuNolTP1;9RGf{=ByM(X3_?Bopp=4&@t;j_56i) zD9riJf)We8&BE^*_*Y|B<%r(AbimPG%dz#4q(7JUu@Bj*uBj*}2{k&|`^I)3UdqK- z`StIu)8wmVQraygBy5PjS#riwiG{r+**t@kvgC;hDP9_=oH-V#(6>kE79T3xPLD?@ zXV%P)^@YeXY;cB09-@@-DzgbMjQ=(%hZP;FZ~tI=!dNqdt$l1Dzia(ykwZ@SiE(an zicjVk#q0BGo=1ODiJe}_Hx+22(E83wl;#SJ!K+wC$ib4{F24|Cb^_!wuhX5fn6hC( zxjEL+Ypfou#STET^NvfdBI2*5HA^ke+xet#%=l!_0gMENoVp%GnT|&sZ3%?1Pb9;% z-5y#nXP#0s<=7+QaEXW~WEW}G5V^bfM&S55BVlmxTW(8XJAIo{VgoBnzp0w8(eYNl z*b7eyMILX>*jrtK&Z&>dfs(xD(vHwpmp0$f`-9&+QPQ>RD!&=Bp6>EXlgfa`Lk_W_jq1FVd=j}Ctp*+WM`x>Q6&-UJ}_ za}7ms7n+DEG45GO=+vz&)L&0Zis#>Y(J8v8KTNZN)M}H^`He2--Umk}i+pMhm^huw z&|>|Znhk)83Wx<$1(UAO=4bIh3q0#3zSY*Yz{u4V@6lx-nHaxv`*=2;@xlSJP0M-c z$D*!_?PSh+M4I~rcZ=RT^`Rma-|;~XM9;f&NlV3ygkjJ~7h|p7=g;4c>1+y+w48^r z$tn4r_=gNa5~G}x(Lb+W!xpT+vk0)>g7T90wVjaS5M@L#@WASI-g7Ber=oJolxe_i z``FI+QI)j?RvtHh+d(_~{eOw}=?F{CaL8YvSs%j8ODGCVoi57$lo|E)q#EXv)Py1m zvzQX3KqgW`C7w;#L-G z03YdBMBe6|z>a0>eHy*OrX(8H_XmE|`RuuGB20h>vG=GC-VNn@eL=sI72$d{Y|z|O z1^MK7^xH93%UxQb z!)KS)QlZjc&$div*yJ-uBBfV3%4Lp#!(6ef;^&pT3q zk2O?(u7Q-@_X+HAt>e`7r5dpxY=ISHZ-S{)BEK?JJp?SKtEs&DbZ+OP(qIp6Erc zKBs448dvm8itVO&v;1IFX^{nCSp23V#R)G>-^4;r6e z^UD63lIcoB_e&tVvBpJ=chooqV)LSzQ^HVJK;JWu+fMuiRHr%@z3^!Q#pmLm?QDWM z(WW~AnH{*C zEs>RuOubvo}TFR_f1q66#B zdR}x3FQb5s2hOz7%U0s`ToPVS-&^)8cpH|!$=TqIyS`g;7+OEKUDHfW7P?j~*(Xfp_3I@Wy}Ho#r3AlY&gO z7bfk?|FJt52Oi41@tSN9dY<89{k^-C&*<`Q3^*)lisLF=nevwlMD!Qa+kT;ZP z`7>7^^x<*kRQ}iRE?s6kMhU4JTr!!}eIAD3TqcK2L9YBX*b6CxQyVEGcvs`t^?rFV zzPEr?F=}8bic115_+?~=x6j2Tv3E;+xCFhFl$J5ldJf0BQu%yIy3r2&WWlvt=24|p zi3Ftg>-{NE>~aeM$>H)O-Ll$fFT`-#qmkt#%ir${W>4>v3GK-N9`O#FCZ{hc@nqEe z@59j7zR$FUB_0T3rDQ$$(RMvi@q)?A+{HKAoDvEo|wbQ*o1q9HZAcdImohZWOdGRA5@n&S+*2l zdAA6@+37i(2A-Q<#MTq}30P9#wzrGzHt2T$mBHYPSJy@ho#TWFy1>6o^EI(bms)~lu@0}I zXtomlbV8)k_x#B3J@vVI?6cqi#I$2(3 z`KDtcT92W*ha*UJk0_&oUTxQR9_qdYWq3Yu4`A3DMIH>)%B@x zUM5D-=IHSXn>u^xu+o7!MrKi?spRRImrbyjF_2ZSd*3R$GF3cR_l>7c#9HPk1ADrEHuSv+A-ejx0fB3;Q zNOou}qd+I6%>9vYFItCQbnvuk^@}aGD#rGg1?>^C--U80IaDfQ@`_riV3X3#$4E8} zV)il1Y2Ywvs;PG~JCV3luM`|2ZoOCbfZzgGgO|^MAoy#NQ!=8jLf!ggmqLKFN#UXW zzDvy~2JF+8^#a_jynd9R9Yt{XTcd^2$hd)YsDx)FThZ#ZG0vff{*{jL-OID(chffa z4=gjx+GNUi$YQF(GSxahgvvf~*ZlF#07gR{V;$;*JMWlIL`A>IT1?l5VJt{dsx72_ zu+K)_&7P>ma12`!sL4NcoDV1@c~-^@@4mkcS7*oZ97b`+ZTMyIhaDQcJ%M6R@O!5e z8Nz3;170DkO8p30;L_@xh%metXZxT>$6)jt-tA94d1M~&(I8E~=`Ki4}xkaP+V z#r7sV$b`GZP)4mySTdYL!#?2dDT(5SyM4jrc7s|Grms*J&81zo?&J@Muj|-^-2E{9 zoqMO?zP|YMeG8pyL*2EpkY_w2cVve}%Ged^t3M$7Dsh1-7@3}MeximtV9h10r}B93 z3u2(thiv zet*UrF7|K0rN9m5tbkcIiAP})pPVZCU+_xk5i0At0#|^Gz@fAdSr!@)KLh~gH0}tr iE8O!wKCrVMt57{dPEIOa2GfHhd7jE|^L+ZJKK} Date: Tue, 14 Apr 2020 13:51:28 +0800 Subject: [PATCH 05/87] allow parse not support type --- src/lib.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 59d8142b..5ae706fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,9 +149,9 @@ pub struct OtherContext { #[derive(Debug, Clone)] pub enum ContentInfo { - Data(pub Vec), - EncryptedData(pub EncryptedData), - OtherContext(pub OtherContext), + Data(Vec), + EncryptedData(EncryptedData), + OtherContext(OtherContext), } impl ContentInfo { @@ -249,9 +249,9 @@ pub struct OtherAlgorithmIdentifier { #[derive(Debug, Clone, PartialEq)] pub enum AlgorithmIdentifier { Sha1, - PbewithSHAAnd40BitRC2CBC(pub Pkcs12PbeParams), - PbeWithSHAAnd3KeyTripleDESCBC(pub Pkcs12PbeParams), - OtherAlg(pub OtherAlgorithmIdentifier), + PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams), + PbeWithSHAAnd3KeyTripleDESCBC(Pkcs12PbeParams), + OtherAlg(OtherAlgorithmIdentifier), } impl AlgorithmIdentifier { @@ -762,12 +762,12 @@ pub struct OtherBag { #[derive(Debug, Clone)] pub enum SafeBagKind { //KeyBag(), - Pkcs8ShroudedKeyBag(pub EncryptedPrivateKeyInfo), - CertBag(pub CertBag), + Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo), + CertBag(CertBag), //CRLBag(), //SecretBag(), //SafeContents(Vec), - OtherBagKind(pub OtherBag), + OtherBagKind(OtherBag), } impl SafeBagKind { @@ -821,9 +821,9 @@ pub struct OtherAttribute { #[derive(Debug, Clone)] pub enum PKCS12Attribute { - FriendlyName(pub String), - LocalKeyId(pub Vec), - Other(pub OtherAttribute), + FriendlyName(String), + LocalKeyId(Vec), + Other(OtherAttribute), } impl PKCS12Attribute { From 3585bc39e6324e0f0d2f364435e401a0478812af Mon Sep 17 00:00:00 2001 From: hjiayz Date: Tue, 14 Apr 2020 13:58:04 +0800 Subject: [PATCH 06/87] allow parse not support type --- src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5ae706fd..eb7b616b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,7 +243,7 @@ impl Pkcs12PbeParams { #[derive(Debug, Clone, PartialEq)] pub struct OtherAlgorithmIdentifier { pub algorithm_type: ObjectIdentifier, - pub params: Vec, + pub params: Option>, } #[derive(Debug, Clone, PartialEq)] @@ -270,7 +270,7 @@ impl AlgorithmIdentifier { let params = Pkcs12PbeParams::parse(r.next())?; return Ok(AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(params)); } - let params = r.next().read_der()?; + let params = r.read_optional(|r| r.read_der())?; Ok(AlgorithmIdentifier::OtherAlg(OtherAlgorithmIdentifier { algorithm_type, params, @@ -310,7 +310,9 @@ impl AlgorithmIdentifier { } AlgorithmIdentifier::OtherAlg(other) => { w.next().write_oid(&other.algorithm_type); - w.next().write_der(&other.params); + if let Some(der) = &other.params { + w.next().write_der(der); + } } }) } From 4be4e3c18722b970f67efe7c963c3f5f82e1c413 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Tue, 14 Apr 2020 14:01:04 +0800 Subject: [PATCH 07/87] 0.1.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 81bb0f22..4b278578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.1.1" +version = "0.1.2" authors = ["hjiayz "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 55f4eade7f808fef2d05df270e4702f80d666b6b Mon Sep 17 00:00:00 2001 From: hjiayz Date: Thu, 16 Apr 2020 06:33:49 +0800 Subject: [PATCH 08/87] update pbepkcs12 --- src/lib.rs | 77 +++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index eb7b616b..0eedf773 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -375,14 +375,14 @@ impl MacData { pub fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); - let key = pkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); + let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); hmac::verify(&m, data, &self.mac.digest).is_ok() } pub fn new(data: &[u8], password: &[u8]) -> MacData { let salt = rand().unwrap(); - let key = pkcs12sha1(password, &salt, ITERATIONS, 3, 20); + let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); let digest = hmac::sign(&m, data).as_ref().to_owned(); MacData { @@ -560,8 +560,18 @@ impl PFX { } } +#[inline(always)] +fn pbepkcs12sha1core(d: &[u8], i: &[u8], a: &mut Vec, iterations: u64) -> Vec { + let mut ai: Vec = d.iter().chain(i.iter()).cloned().collect(); + for _ in 0..iterations { + ai = sha1(&ai).as_ref().to_owned(); + } + a.append(&mut ai.clone()); + ai +} + #[allow(clippy::many_single_char_names)] -fn pkcs12sha1(pass: &[u8], salt: &[u8], iterations: u64, id: u8, size: u64) -> Vec { +fn pbepkcs12sha1(pass: &[u8], salt: &[u8], iterations: u64, id: u8, size: u64) -> Vec { const U: u64 = 160 / 8; const V: u64 = 512 / 8; let r: u64 = iterations; @@ -575,32 +585,27 @@ fn pkcs12sha1(pass: &[u8], salt: &[u8], iterations: u64, id: u8, size: u64) -> V let mut i: Vec = s.chain(p).cloned().collect(); let c = (size + U - 1) / U; let mut a: Vec = vec![]; - for i2 in 1..=c { - let mut ai: Vec = d.iter().chain(i.iter()).cloned().collect(); - - for _ in 0..r { - ai = sha1(&ai).as_ref().to_owned(); - } + for _ in 1..c { + let ai = pbepkcs12sha1core(&d, &i, &mut a, r); - a.append(&mut ai.clone()); + let b: Vec = ai.iter().cycle().take(V as usize).cloned().collect(); - if i2 < c { - let b: Vec = ai.iter().cycle().take(V as usize).cloned().collect(); - - let b_iter = b.iter().rev().cycle().take(i.len()); - let i_b_iter = i.iter_mut().rev().zip(b_iter); - let mut inc = 1u8; - for (i3, (ii, bi)) in i_b_iter.enumerate() { - if ((i3 as u64) % V) == 0 { - inc = 1; - } - let (ii2, inc2) = ii.overflowing_add(*bi); - let (ii3, inc3) = ii2.overflowing_add(inc); - inc = (inc2 || inc3) as u8; - *ii = ii3; + let b_iter = b.iter().rev().cycle().take(i.len()); + let i_b_iter = i.iter_mut().rev().zip(b_iter); + let mut inc = 1u8; + for (i3, (ii, bi)) in i_b_iter.enumerate() { + if ((i3 as u64) % V) == 0 { + inc = 1; } + let (ii2, inc2) = ii.overflowing_add(*bi); + let (ii3, inc3) = ii2.overflowing_add(inc); + inc = (inc2 || inc3) as u8; + *ii = ii3; } } + + pbepkcs12sha1core(&d, &i, &mut a, r); + a.iter().take(size as usize).cloned().collect() } @@ -614,8 +619,8 @@ fn pbe_with_sha1_and40_bit_rc2_cbc( use rc2::Rc2; type Rc2Cbc = Cbc; - let dk = pkcs12sha1(password, salt, iterations, 1, 5); - let iv = pkcs12sha1(password, salt, iterations, 2, 8); + let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_var(&dk, &iv).ok()?; rc2.decrypt_vec(data).ok() @@ -631,8 +636,8 @@ fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( use rc2::Rc2; type Rc2Cbc = Cbc; - let dk = pkcs12sha1(password, salt, iterations, 1, 5); - let iv = pkcs12sha1(password, salt, iterations, 2, 8); + let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_var(&dk, &iv).ok()?; Some(rc2.encrypt_vec(data)) @@ -648,8 +653,8 @@ fn pbe_with_sha_and3_key_triple_des_cbc( use des::TdesEde3; type TDesCbc = Cbc; - let dk = pkcs12sha1(password, salt, iterations, 1, 24); - let iv = pkcs12sha1(password, salt, iterations, 2, 8); + let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_var(&dk, &iv).ok()?; tdes.decrypt_vec(data).ok() @@ -665,8 +670,8 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( use des::TdesEde3; type TDesCbc = Cbc; - let dk = pkcs12sha1(password, salt, iterations, 1, 24); - let iv = pkcs12sha1(password, salt, iterations, 2, 8); + let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_var(&dk, &iv).ok()?; Some(tdes.encrypt_vec(data)) @@ -972,7 +977,7 @@ fn test_bmp_string() { } #[test] -fn test_pkcs12sha1() { +fn test_pbepkcs12sha1() { use hex_literal::hex; let pass = bmp_string(""); assert_eq!(pass, vec![0, 0]); @@ -980,13 +985,13 @@ fn test_pkcs12sha1() { let iterations = 2048; let id = 1; let size = 24; - let result = pkcs12sha1(&pass, &salt, iterations, id, size); + let result = pbepkcs12sha1(&pass, &salt, iterations, id, size); let res = hex!("c2294aa6d02930eb5ce9c329eccb9aee1cb136baea746557"); assert_eq!(result, res); } #[test] -fn test_pkcs12sha1_2() { +fn test_pbepkcs12sha1_2() { use hex_literal::hex; let pass = bmp_string(""); assert_eq!(pass, vec![0, 0]); @@ -994,7 +999,7 @@ fn test_pkcs12sha1_2() { let iterations = 2048; let id = 2; let size = 8; - let result = pkcs12sha1(&pass, &salt, iterations, id, size); + let result = pbepkcs12sha1(&pass, &salt, iterations, id, size); let res = hex!("8e9f8fc7664378bc"); assert_eq!(result, res); } From 810faa093936fe6b889ab00b4a8ed793e9c89915 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Thu, 16 Apr 2020 07:24:50 +0800 Subject: [PATCH 09/87] update pbepkcs12 --- src/lib.rs | 57 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0eedf773..69adb666 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,8 @@ lazy_static! { static ref OID_LOCAL_KEY_ID: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 21]); static ref OID_CERT_TYPE_X509_CERTIFICATE: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 1]); + static ref OID_CERT_TYPE_SDSI_CERTIFICATE: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 2]); static ref OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 3]); static ref OID_SHA1: ObjectIdentifier = as_oid(&[1, 3, 14, 3, 2, 26]); @@ -436,9 +438,7 @@ impl PFX { bag: key_bag_inner, attributes: vec![friendly_name.clone(), local_key_id.clone()], }; - let cert_bag_inner = SafeBagKind::CertBag(CertBag { - cert: cert_der.to_owned(), - }); + let cert_bag_inner = SafeBagKind::CertBag(CertBag::X509(cert_der.to_owned())); let cert_bag = SafeBag { bag: cert_bag_inner, attributes: vec![friendly_name, local_key_id], @@ -446,9 +446,7 @@ impl PFX { let mut cert_bags = vec![cert_bag]; if let Some(ca) = ca_der { cert_bags.push(SafeBag { - bag: SafeBagKind::CertBag(CertBag { - cert: ca.to_owned(), - }), + bag: SafeBagKind::CertBag(CertBag::X509(ca.to_owned())), attributes: vec![], }); }; @@ -528,10 +526,10 @@ impl PFX { } Ok(result) } - pub fn cert_bags(&self, password: &str) -> Result>, ASN1Error> { + pub fn cert_x509_bags(&self, password: &str) -> Result>, ASN1Error> { let mut result = vec![]; for safe_bag in self.bags(password)? { - if let Some(cert) = safe_bag.bag.get_cert() { + if let Some(cert) = safe_bag.bag.get_x509_cert() { result.push(cert); } } @@ -691,27 +689,40 @@ fn bmp_string(s: &str) -> Vec { } #[derive(Debug, Clone)] -pub struct CertBag { - pub cert: Vec, //x509 only +pub enum CertBag { + X509(Vec), + SDSI(String), } impl CertBag { pub fn parse(r: BERReader) -> Result { r.read_sequence(|r| { let oid = r.next().read_oid()?; - if oid != *OID_CERT_TYPE_X509_CERTIFICATE { - println!("not x509 cert"); - return Err(ASN1Error::new(ASN1ErrorKind::Invalid)); + if oid == *OID_CERT_TYPE_X509_CERTIFICATE { + let x509 = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; + return Ok(CertBag::X509(x509)); }; - let cert = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; - Ok(CertBag { cert }) + if oid == *OID_CERT_TYPE_SDSI_CERTIFICATE { + let sdsi = r + .next() + .read_tagged(Tag::context(0), |r| r.read_ia5_string())?; + return Ok(CertBag::SDSI(sdsi)); + } + Err(ASN1Error::new(ASN1ErrorKind::Invalid)) }) } pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - w.next().write_oid(&OID_CERT_TYPE_X509_CERTIFICATE); - w.next() - .write_tagged(Tag::context(0), |w| w.write_bytes(&self.cert)) + w.write_sequence(|w| match self { + CertBag::X509(x509) => { + w.next().write_oid(&OID_CERT_TYPE_X509_CERTIFICATE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_bytes(x509)); + } + CertBag::SDSI(sdsi) => { + w.next().write_oid(&OID_CERT_TYPE_SDSI_CERTIFICATE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_ia5_string(sdsi)); + } }) } } @@ -805,9 +816,9 @@ impl SafeBagKind { } } - pub fn get_cert(&self) -> Option> { - if let SafeBagKind::CertBag(cb) = self { - return Some(cb.cert.to_owned()); + pub fn get_x509_cert(&self) -> Option> { + if let SafeBagKind::CertBag(CertBag::X509(x509)) = self { + return Some(x509.to_owned()); } None } @@ -958,7 +969,7 @@ fn test_create_p12() { let keys = pfx.key_bags("changeit").unwrap(); assert_eq!(keys[0], key); - let certs = pfx.cert_bags("changeit").unwrap(); + let certs = pfx.cert_x509_bags("changeit").unwrap(); assert_eq!(certs[0], cert); assert_eq!(certs[1], ca); assert!(pfx.verify_mac("changeit")); From 49c79c14dc4bc2cf8c4c6d010fdb3f00b3a64888 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Thu, 16 Apr 2020 07:47:28 +0800 Subject: [PATCH 10/87] sdsi cert --- src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 69adb666..0642c9ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -535,6 +535,15 @@ impl PFX { } Ok(result) } + pub fn cert_sdsi_bags(&self, password: &str) -> Result, ASN1Error> { + let mut result = vec![]; + for safe_bag in self.bags(password)? { + if let Some(cert) = safe_bag.bag.get_sdsi_cert() { + result.push(cert); + } + } + Ok(result) + } pub fn key_bags(&self, password: &str) -> Result>, ASN1Error> { let bmp_password = bmp_string(password); let mut result = vec![]; @@ -823,6 +832,13 @@ impl SafeBagKind { None } + pub fn get_sdsi_cert(&self) -> Option { + if let SafeBagKind::CertBag(CertBag::SDSI(sdsi)) = self { + return Some(sdsi.to_owned()); + } + None + } + pub fn get_key(&self, password: &[u8]) -> Option> { if let SafeBagKind::Pkcs8ShroudedKeyBag(kb) = self { return kb.decrypt(password); From 88a42265f732aab95579df0e0f193a9c51ae2379 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Thu, 16 Apr 2020 13:31:53 +0800 Subject: [PATCH 11/87] 0.1.3 --- Cargo.toml | 2 +- src/lib.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b278578..182058a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.1.2" +version = "0.1.3" authors = ["hjiayz "] edition = "2018" keywords = ["pkcs12", "pkcs"] diff --git a/src/lib.rs b/src/lib.rs index 0642c9ff..244b5ed1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,6 +526,11 @@ impl PFX { } Ok(result) } + //DER-encoded X.509 certificate + pub fn cert_bags(&self, password: &str) -> Result>, ASN1Error> { + self.cert_x509_bags(password) + } + //DER-encoded X.509 certificate pub fn cert_x509_bags(&self, password: &str) -> Result>, ASN1Error> { let mut result = vec![]; for safe_bag in self.bags(password)? { @@ -824,7 +829,6 @@ impl SafeBagKind { SafeBagKind::OtherBagKind(other) => other.bag_id.clone(), } } - pub fn get_x509_cert(&self) -> Option> { if let SafeBagKind::CertBag(CertBag::X509(x509)) = self { return Some(x509.to_owned()); From e2a7503806840d0463a652c6b59259384cb77159 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Fri, 24 Apr 2020 10:31:41 +0800 Subject: [PATCH 12/87] fix --- Cargo.toml | 2 +- src/lib.rs | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 182058a0..608443e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.1.3" +version = "0.1.4" authors = ["hjiayz "] edition = "2018" keywords = ["pkcs12", "pkcs"] diff --git a/src/lib.rs b/src/lib.rs index 244b5ed1..e9ad05d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,7 +328,6 @@ pub struct DigestInfo { impl DigestInfo { pub fn parse(r: BERReader) -> Result { - dbg!(10); r.read_sequence(|r| { let digest_algorithm = AlgorithmIdentifier::parse(r.next())?; let digest = r.next().read_bytes()?; @@ -419,6 +418,19 @@ impl PFX { ca_der: Option<&[u8]>, password: &str, name: &str, + ) -> Option { + let mut cas = vec![]; + if let Some(ca) = ca_der { + cas.push(ca); + } + Self::new_with_cas(cert_der, key_der, &cas, password, name) + } + pub fn new_with_cas( + cert_der: &[u8], + key_der: &[u8], + ca_der_list: &[&[u8]], + password: &str, + name: &str, ) -> Option { let password = bmp_string(password); let salt = rand()?.to_vec(); @@ -444,12 +456,12 @@ impl PFX { attributes: vec![friendly_name, local_key_id], }; let mut cert_bags = vec![cert_bag]; - if let Some(ca) = ca_der { + for ca in ca_der_list { cert_bags.push(SafeBag { - bag: SafeBagKind::CertBag(CertBag::X509(ca.to_owned())), + bag: SafeBagKind::CertBag(CertBag::X509((*ca).to_owned())), attributes: vec![], }); - }; + } let contents = yasna::construct_der(|w| { w.write_sequence_of(|w| { ContentInfo::EncryptedData( From d750eaece49661e675e21a3ff100bd3b6a879578 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 9 Jun 2020 14:34:04 +0200 Subject: [PATCH 13/87] switch to RustCrypto HmacSha1 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 ++ src/lib.rs | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 608443e3..13eaf682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ yasna = { version = "0.3.1" } ring = "0.16.12" block-modes = "0.3.3" block-cipher-trait = "0.6.2" +hmac = "0.7.1" +sha-1 = "0.8.1" rc2 = "0.3.0" des = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs index e9ad05d0..44787909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,14 @@ //! use lazy_static::lazy_static; -use ring::digest::Digest; -use ring::hmac; use ring::rand::{self, SecureRandom}; use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; +use hmac::{Mac, Hmac}; +use sha1::{Sha1, Digest}; + +type HmacSha1 = Hmac; + fn as_oid(s: &'static [u64]) -> ObjectIdentifier { ObjectIdentifier::from_slice(s) } @@ -41,9 +44,10 @@ lazy_static! { const ITERATIONS: u64 = 2048; -fn sha1(bytes: &[u8]) -> Digest { - use ring::digest::*; - digest(&SHA1_FOR_LEGACY_USE_ONLY, bytes) +fn sha1(bytes: &[u8]) -> Vec { + let mut hasher = Sha1::new(); + hasher.input(bytes); + hasher.result().to_vec() } #[derive(Debug, Clone)] @@ -377,15 +381,17 @@ impl MacData { pub fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); - let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); - hmac::verify(&m, data, &self.mac.digest).is_ok() + let mut mac = HmacSha1::new_varkey(&key).unwrap(); + mac.input(data); + mac.verify(&self.mac.digest).is_ok() } pub fn new(data: &[u8], password: &[u8]) -> MacData { let salt = rand().unwrap(); let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); - let m = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, &key); - let digest = hmac::sign(&m, data).as_ref().to_owned(); + let mut mac = HmacSha1::new_varkey(&key).unwrap(); + mac.input(data); + let digest = mac.result().code().to_vec(); MacData { mac: DigestInfo { digest_algorithm: AlgorithmIdentifier::Sha1, @@ -445,7 +451,7 @@ impl PFX { encrypted_data, }); let friendly_name = PKCS12Attribute::FriendlyName(name.to_owned()); - let local_key_id = PKCS12Attribute::LocalKeyId(sha1(cert_der).as_ref().to_owned()); + let local_key_id = PKCS12Attribute::LocalKeyId(sha1(cert_der)); let key_bag = SafeBag { bag: key_bag_inner, attributes: vec![friendly_name.clone(), local_key_id.clone()], @@ -588,7 +594,7 @@ impl PFX { fn pbepkcs12sha1core(d: &[u8], i: &[u8], a: &mut Vec, iterations: u64) -> Vec { let mut ai: Vec = d.iter().chain(i.iter()).cloned().collect(); for _ in 0..iterations { - ai = sha1(&ai).as_ref().to_owned(); + ai = sha1(&ai); } a.append(&mut ai.clone()); ai From 5ef132e3d5165021d6f80eb300ac0cc1c0383335 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 9 Jun 2020 14:52:07 +0200 Subject: [PATCH 14/87] switch to getrandom Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- src/lib.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13eaf682..6aab4f57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ license = "MIT OR Apache-2.0" [dependencies] lazy_static = "1.4.0" yasna = { version = "0.3.1" } -ring = "0.16.12" +getrandom = "0.1.14" block-modes = "0.3.3" block-cipher-trait = "0.6.2" hmac = "0.7.1" diff --git a/src/lib.rs b/src/lib.rs index 44787909..b3df3365 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! use lazy_static::lazy_static; -use ring::rand::{self, SecureRandom}; +use getrandom::getrandom; use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; use hmac::{Mac, Hmac}; @@ -39,7 +39,6 @@ lazy_static! { static ref OID_SECRET_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 5]); static ref OID_SAFE_CONTENTS_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 6]); - static ref RAND: rand::SystemRandom = rand::SystemRandom::new(); } const ITERATIONS: u64 = 2048; @@ -405,9 +404,11 @@ impl MacData { fn rand() -> Option<[u8; 8]> { let mut buf = [0u8; 8]; - let rng = rand::SystemRandom::new(); - rng.fill(&mut buf).ok()?; - Some(buf) + if getrandom(&mut buf).is_ok() { + Some(buf) + } else { + None + } } #[derive(Debug)] From 464e98276a3239d51dac7239b6917830eb24f1ce Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Thu, 11 Jun 2020 16:59:25 +0200 Subject: [PATCH 15/87] update RustCrypto deps Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 12 ++++++------ src/lib.rs | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6aab4f57..9433287f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,12 @@ license = "MIT OR Apache-2.0" lazy_static = "1.4.0" yasna = { version = "0.3.1" } getrandom = "0.1.14" -block-modes = "0.3.3" -block-cipher-trait = "0.6.2" -hmac = "0.7.1" -sha-1 = "0.8.1" -rc2 = "0.3.0" -des = "0.3.0" +block-modes = "0.4.0" +block-cipher-trait = "0.7.0" +hmac = "0.8.0" +sha-1 = "0.9.0" +rc2 = "0.4.0" +des = "0.4.0" [dev-dependencies] hex = "0.4.2" diff --git a/src/lib.rs b/src/lib.rs index b3df3365..476c48f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use lazy_static::lazy_static; use getrandom::getrandom; use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; -use hmac::{Mac, Hmac}; +use hmac::{Mac, Hmac, NewMac}; use sha1::{Sha1, Digest}; type HmacSha1 = Hmac; @@ -45,8 +45,8 @@ const ITERATIONS: u64 = 2048; fn sha1(bytes: &[u8]) -> Vec { let mut hasher = Sha1::new(); - hasher.input(bytes); - hasher.result().to_vec() + hasher.update(bytes); + hasher.finalize().to_vec() } #[derive(Debug, Clone)] @@ -102,7 +102,7 @@ impl EncryptedContentInfo { pbe_with_sha1_and40_bit_rc2_cbc_encrypt(&data, password, &salt, ITERATIONS)?; let content_encryption_algorithm = AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams { - salt: salt, + salt, iterations: ITERATIONS, }); Some(EncryptedContentInfo { @@ -381,7 +381,7 @@ impl MacData { debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); let mut mac = HmacSha1::new_varkey(&key).unwrap(); - mac.input(data); + mac.update(data); mac.verify(&self.mac.digest).is_ok() } @@ -389,8 +389,8 @@ impl MacData { let salt = rand().unwrap(); let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); let mut mac = HmacSha1::new_varkey(&key).unwrap(); - mac.input(data); - let digest = mac.result().code().to_vec(); + mac.update(data); + let digest = mac.finalize().into_bytes().to_vec(); MacData { mac: DigestInfo { digest_algorithm: AlgorithmIdentifier::Sha1, From de4272fda33f819dd817d73a57e4433a919e24e9 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 16 Jun 2020 08:26:44 +0200 Subject: [PATCH 16/87] drop unused block-cipher-trait Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9433287f..43934569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,6 @@ lazy_static = "1.4.0" yasna = { version = "0.3.1" } getrandom = "0.1.14" block-modes = "0.4.0" -block-cipher-trait = "0.7.0" hmac = "0.8.0" sha-1 = "0.9.0" rc2 = "0.4.0" From b5e2e50ce55f250eef035933c13bb20105e232bb Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Fri, 10 Jul 2020 14:21:06 +0200 Subject: [PATCH 17/87] block-modes: update to 0.5.0 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 43934569..3d249d91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ license = "MIT OR Apache-2.0" lazy_static = "1.4.0" yasna = { version = "0.3.1" } getrandom = "0.1.14" -block-modes = "0.4.0" +block-modes = "0.5.0" hmac = "0.8.0" sha-1 = "0.9.0" rc2 = "0.4.0" From 2e8a35acf1448309c26d4079c86557046eb35c69 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Fri, 10 Jul 2020 14:22:37 +0200 Subject: [PATCH 18/87] rustfmt Signed-off-by: Marc-Antoine Perennou --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 476c48f2..07bf9843 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,12 @@ //! //! -use lazy_static::lazy_static; use getrandom::getrandom; +use lazy_static::lazy_static; use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; -use hmac::{Mac, Hmac, NewMac}; -use sha1::{Sha1, Digest}; +use hmac::{Hmac, Mac, NewMac}; +use sha1::{Digest, Sha1}; type HmacSha1 = Hmac; From 017b50e789d05932e49d6085fae59da0c10ae50c Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Mon, 28 Sep 2020 14:39:47 +0200 Subject: [PATCH 19/87] update rustcrypto Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d249d91..3a4f9d33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ license = "MIT OR Apache-2.0" [dependencies] lazy_static = "1.4.0" yasna = { version = "0.3.1" } -getrandom = "0.1.14" -block-modes = "0.5.0" -hmac = "0.8.0" +getrandom = "0.2.0" +block-modes = "0.6.1" +hmac = "0.9.0" sha-1 = "0.9.0" -rc2 = "0.4.0" -des = "0.4.0" +rc2 = "0.5.0" +des = "0.5.0" [dev-dependencies] hex = "0.4.2" -hex-literal = "0.2.1" +hex-literal = "0.3.1" From f48e2231b24581a0e307925c13fd84f068bff635 Mon Sep 17 00:00:00 2001 From: hjiayz Date: Wed, 14 Oct 2020 14:37:17 +0800 Subject: [PATCH 20/87] version 0.2.0 --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3a4f9d33..c8290d31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.1.4" +version = "0.2.0" authors = ["hjiayz "] edition = "2018" keywords = ["pkcs12", "pkcs"] @@ -12,11 +12,11 @@ license = "MIT OR Apache-2.0" [dependencies] lazy_static = "1.4.0" -yasna = { version = "0.3.1" } +yasna = "0.3.2" getrandom = "0.2.0" block-modes = "0.6.1" hmac = "0.9.0" -sha-1 = "0.9.0" +sha-1 = "0.9.1" rc2 = "0.5.0" des = "0.5.0" From 02c5c9719113d96bbfb5ed6824230046f0ea7bca Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Sat, 20 Mar 2021 10:53:55 +0100 Subject: [PATCH 21/87] update crypto dependencies Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c8290d31..6159a23c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,11 @@ license = "MIT OR Apache-2.0" lazy_static = "1.4.0" yasna = "0.3.2" getrandom = "0.2.0" -block-modes = "0.6.1" -hmac = "0.9.0" +block-modes = "0.7.0" +hmac = "0.10.1" sha-1 = "0.9.1" -rc2 = "0.5.0" -des = "0.5.0" +rc2 = "0.6.0" +des = "0.6.0" [dev-dependencies] hex = "0.4.2" From e372089f28041beb7fcd4c63d1161f966e1d4169 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 14 Sep 2021 14:41:29 +0200 Subject: [PATCH 22/87] update crypto dependencies Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 8 ++++---- src/lib.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6159a23c..9e21bbd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,11 @@ license = "MIT OR Apache-2.0" lazy_static = "1.4.0" yasna = "0.3.2" getrandom = "0.2.0" -block-modes = "0.7.0" -hmac = "0.10.1" +block-modes = "0.8.0" +hmac = "0.11.0" sha-1 = "0.9.1" -rc2 = "0.6.0" -des = "0.6.0" +rc2 = "0.7.0" +des = "0.7.0" [dev-dependencies] hex = "0.4.2" diff --git a/src/lib.rs b/src/lib.rs index 07bf9843..592ab73d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -380,7 +380,7 @@ impl MacData { pub fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); - let mut mac = HmacSha1::new_varkey(&key).unwrap(); + let mut mac = HmacSha1::new_from_slice(&key).unwrap(); mac.update(data); mac.verify(&self.mac.digest).is_ok() } @@ -388,7 +388,7 @@ impl MacData { pub fn new(data: &[u8], password: &[u8]) -> MacData { let salt = rand().unwrap(); let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); - let mut mac = HmacSha1::new_varkey(&key).unwrap(); + let mut mac = HmacSha1::new_from_slice(&key).unwrap(); mac.update(data); let digest = mac.finalize().into_bytes().to_vec(); MacData { @@ -653,7 +653,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc( let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - let rc2 = Rc2Cbc::new_var(&dk, &iv).ok()?; + let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; rc2.decrypt_vec(data).ok() } @@ -670,7 +670,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - let rc2 = Rc2Cbc::new_var(&dk, &iv).ok()?; + let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; Some(rc2.encrypt_vec(data)) } @@ -687,7 +687,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc( let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - let tdes = TDesCbc::new_var(&dk, &iv).ok()?; + let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; tdes.decrypt_vec(data).ok() } @@ -704,7 +704,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - let tdes = TDesCbc::new_var(&dk, &iv).ok()?; + let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; Some(tdes.encrypt_vec(data)) } From f30063ccbc3f00d88e342615b03a471975b92129 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 14 Sep 2021 14:42:08 +0200 Subject: [PATCH 23/87] update yasna Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e21bbd6..829d117d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0" [dependencies] lazy_static = "1.4.0" -yasna = "0.3.2" +yasna = "0.4.0" getrandom = "0.2.0" block-modes = "0.8.0" hmac = "0.11.0" From 1c6f6375daee79d533f8c060f7d62a5fbeae4b23 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 20 Oct 2021 16:11:58 +0200 Subject: [PATCH 24/87] rename to pkcs-12 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 10 +++++----- LICENSE-APACHE | 1 + LICENSE-MIT | 1 + README.MD => README.md | 5 +---- 4 files changed, 8 insertions(+), 9 deletions(-) rename README.MD => README.md (83%) diff --git a/Cargo.toml b/Cargo.toml index 829d117d..2fc2d5ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "p12" +name = "pkcs-12" version = "0.2.0" -authors = ["hjiayz "] +authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] description = "pure rust pkcs12 tool" -homepage = "https://github.com/hjiayz/p12" -repository = "https://github.com/hjiayz/p12" -readme = "README.MD" +homepage = "https://github.com/Keruspe/pkcs-12" +repository = "https://github.com/Keruspe/pkcs-12" +readme = "README.md" license = "MIT OR Apache-2.0" [dependencies] diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 5972a179..7eb53864 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -1,3 +1,4 @@ +Copyright (c) 2021 Marc-Antoine Perennou Copyright 2020 hjiayz Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/LICENSE-MIT b/LICENSE-MIT index e5a583a3..a424e020 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,3 +1,4 @@ +Copyright (c) 2021 Marc-Antoine Perennou Copyright (c) 2020 hjiayz Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.MD b/README.md similarity index 83% rename from README.MD rename to README.md index dc699735..e68ccf16 100644 --- a/README.MD +++ b/README.md @@ -1,8 +1,5 @@ -# p12 - +# pkcs-12 pure rust pkcs12 tool - - License: MIT OR Apache-2.0 From 1d3fbd93b7e8dfa80b56e9082ac474a854a24456 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 20 Oct 2021 16:13:31 +0200 Subject: [PATCH 25/87] version 0.3.0 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2fc2d5ea..d0e1d4f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pkcs-12" -version = "0.2.0" +version = "0.3.0" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 5c0018e1e3575fc3f5e89806ee76e5e65c82bf21 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 20 Oct 2021 16:20:22 +0200 Subject: [PATCH 26/87] require yasna with std feature Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d0e1d4f8..8410b815 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ license = "MIT OR Apache-2.0" [dependencies] lazy_static = "1.4.0" -yasna = "0.4.0" getrandom = "0.2.0" block-modes = "0.8.0" hmac = "0.11.0" @@ -20,6 +19,10 @@ sha-1 = "0.9.1" rc2 = "0.7.0" des = "0.7.0" +[dependencies.yasna] +version = "0.4.0" +features = ["std"] + [dev-dependencies] hex = "0.4.2" hex-literal = "0.3.1" From 885484a17d6e2135f8fef3eccc6e664c14af9815 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 20 Oct 2021 16:20:33 +0200 Subject: [PATCH 27/87] version 0.3.1 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8410b815..86a571f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pkcs-12" -version = "0.3.0" +version = "0.3.1" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From dc0fad4a9185178338b95becce72846cab6627ab Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Thu, 21 Oct 2021 10:02:07 +0200 Subject: [PATCH 28/87] add CI Signed-off-by: Marc-Antoine Perennou --- .github/FUNDING.yml | 1 + .github/workflows/build-and-test.yaml | 43 +++++++++++++++++++++++++++ .github/workflows/lint.yaml | 38 +++++++++++++++++++++++ .github/workflows/security.yaml | 17 +++++++++++ 4 files changed, 99 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/build-and-test.yaml create mode 100644 .github/workflows/lint.yaml create mode 100644 .github/workflows/security.yaml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0f5af72e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: Keruspe diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml new file mode 100644 index 00000000..96e5ea06 --- /dev/null +++ b/.github/workflows/build-and-test.yaml @@ -0,0 +1,43 @@ +name: Build and test + +on: + push: + branches: + - master + pull_request: + +jobs: + build_and_test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + rust: [nightly, beta, stable, 1.51.0] + steps: + - uses: actions/checkout@v2 + + - name: Install latest ${{ matrix.rust }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + profile: minimal + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + args: --all --bins --examples --tests --all-features + + - name: Run cargo check (without dev-dependencies to catch missing feature flags) + if: startsWith(matrix.rust, 'nightly') + uses: actions-rs/cargo@v1 + with: + command: check + args: -Z features=dev_dep + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..440ae87b --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,38 @@ +name: Lint + +on: + push: + branches: + - master + pull_request: + +jobs: + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features -- -W clippy::all + + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml new file mode 100644 index 00000000..c4f7947a --- /dev/null +++ b/.github/workflows/security.yaml @@ -0,0 +1,17 @@ +name: Security audit + +on: + push: + branches: + - master + pull_request: + +jobs: + security_audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} From e11d0033c2b13b0af9f692cf9141012a506f394c Mon Sep 17 00:00:00 2001 From: ppc Date: Sun, 24 Oct 2021 12:15:13 +0800 Subject: [PATCH 29/87] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 829d117d..f4ebb76b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.2.0" +version = "0.2.1" authors = ["hjiayz "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 9b6e213d082882c495e21a70a964d56600c91a6f Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 26 Oct 2021 13:19:44 +0200 Subject: [PATCH 30/87] v0.3.2 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e6640d30..197b7775 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.3.1" +version = "0.3.2" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 0888af5628652cb0b3bcf2d81db03fefd94b0c9b Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 26 Oct 2021 13:23:00 +0200 Subject: [PATCH 31/87] fix fork merge Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 197b7775..85403c8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ authors = ["hjiayz ", "Marc-Antoine Perennou Date: Tue, 26 Oct 2021 13:23:22 +0200 Subject: [PATCH 32/87] version 0.3.3 Just fixing metadata Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 85403c8e..839e83d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.3.2" +version = "0.3.3" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 46812d6f31bf5c66c4122ddb93d0fcfefed5fa00 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 15 Dec 2021 10:23:58 +0100 Subject: [PATCH 33/87] update dependencies Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 20 ++++++++++---------- src/lib.rs | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 839e83d0..89fffe6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,18 +11,18 @@ readme = "README.md" license = "MIT OR Apache-2.0" [dependencies] -lazy_static = "1.4.0" -getrandom = "0.2.0" -block-modes = "0.8.0" -hmac = "0.11.0" -sha-1 = "0.9.1" -rc2 = "0.7.0" -des = "0.7.0" +block-modes = "^0.8" +des = "^0.7" +getrandom = "^0.2" +hmac = "^0.12" +lazy_static = "^1.4" +rc2 = "^0.7" +sha-1 = "^0.10" [dependencies.yasna] -version = "0.4.0" +version = "^0.4" features = ["std"] [dev-dependencies] -hex = "0.4.2" -hex-literal = "0.3.1" +hex = "^0.4.2" +hex-literal = "^0.3.1" diff --git a/src/lib.rs b/src/lib.rs index 592ab73d..ce183bb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use getrandom::getrandom; use lazy_static::lazy_static; use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; -use hmac::{Hmac, Mac, NewMac}; +use hmac::{Hmac, Mac}; use sha1::{Digest, Sha1}; type HmacSha1 = Hmac; @@ -382,7 +382,7 @@ impl MacData { let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); let mut mac = HmacSha1::new_from_slice(&key).unwrap(); mac.update(data); - mac.verify(&self.mac.digest).is_ok() + mac.verify_slice(&self.mac.digest).is_ok() } pub fn new(data: &[u8], password: &[u8]) -> MacData { From c05cf02dd80b417384bf67eeeef924dad19f0fa6 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 15 Dec 2021 10:29:13 +0100 Subject: [PATCH 34/87] v0.4.0 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 89fffe6b..ba4dbc17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.3.3" +version = "0.4.0" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 27f3b2e4e4df149084d7bb4cf9a527955b33896d Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Mon, 31 Jan 2022 15:25:49 +0100 Subject: [PATCH 35/87] switch to sha1 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ba4dbc17..d8a35e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ getrandom = "^0.2" hmac = "^0.12" lazy_static = "^1.4" rc2 = "^0.7" -sha-1 = "^0.10" +sha1 = "^0.10" [dependencies.yasna] version = "^0.4" From c8eb0b600184d0216e84ac55a61ca85a90ee55b6 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Mon, 31 Jan 2022 15:25:59 +0100 Subject: [PATCH 36/87] v0.4.1 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d8a35e37..9e05a7c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.4.0" +version = "0.4.1" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From 35102a658a0fad3148fac54a90840f2cae58772f Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 2 Feb 2022 13:29:24 +0100 Subject: [PATCH 37/87] update yasna Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9e05a7c0..419081b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rc2 = "^0.7" sha1 = "^0.10" [dependencies.yasna] -version = "^0.4" +version = "^0.5" features = ["std"] [dev-dependencies] From e5a6535685a050bdfb4e59eee86a5711d2012879 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 2 Feb 2022 13:30:20 +0100 Subject: [PATCH 38/87] v0.5.0 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 419081b2..912ae6a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.4.1" +version = "0.5.0" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2018" keywords = ["pkcs12", "pkcs"] From f83f8bd3d239f6c229dc6f51d5807cdbc40a30b7 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 2 Feb 2022 14:44:06 +0100 Subject: [PATCH 39/87] apply clippy suggestions Signed-off-by: Marc-Antoine Perennou --- src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce183bb4..5df1eb7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,7 +161,7 @@ pub enum ContentInfo { impl ContentInfo { pub fn parse(r: BERReader) -> Result { - Ok(r.read_sequence(|r| { + r.read_sequence(|r| { let content_type = r.next().read_oid()?; if content_type == *OID_DATA_CONTENT_TYPE { let data = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; @@ -179,7 +179,7 @@ impl ContentInfo { content_type, content, })) - })?) + }) } pub fn data(&self, password: &[u8]) -> Option> { match self { @@ -357,7 +357,7 @@ pub struct MacData { impl MacData { pub fn parse(r: BERReader) -> Result { - Ok(r.read_sequence(|r| { + r.read_sequence(|r| { let mac = DigestInfo::parse(r.next())?; let salt = r.next().read_bytes()?; let iterations = r.next().read_u32()?; @@ -366,7 +366,7 @@ impl MacData { salt, iterations, }) - })?) + }) } pub fn write(&self, w: DERWriter) { @@ -444,7 +444,7 @@ impl PFX { let encrypted_data = pbe_with_sha_and3_key_triple_des_cbc_encrypt(key_der, &password, &salt, ITERATIONS)?; let param = Pkcs12PbeParams { - salt: salt, + salt, iterations: ITERATIONS, }; let key_bag_inner = SafeBagKind::Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo { @@ -800,7 +800,7 @@ fn test_encrypted_private_key_info() { let der = yasna::construct_der(|w| { epki.write(w); }); - let epki2 = yasna::parse_ber(&der, |r| EncryptedPrivateKeyInfo::parse(r)).unwrap(); + let epki2 = yasna::parse_ber(&der, EncryptedPrivateKeyInfo::parse).unwrap(); assert_eq!(epki2, epki); } @@ -919,13 +919,13 @@ impl PKCS12Attribute { } PKCS12Attribute::LocalKeyId(id) => { w.next().write_oid(&OID_LOCAL_KEY_ID); - w.next().write_set_of(|w| w.next().write_bytes(&id)) + w.next().write_set_of(|w| w.next().write_bytes(id)) } PKCS12Attribute::Other(other) => { w.next().write_oid(&other.oid); w.next().write_set_of(|w| { for bytes in other.data.iter() { - w.next().write_der(&bytes); + w.next().write_der(bytes); } }) } @@ -949,7 +949,7 @@ impl SafeBag { let attributes = r .read_optional(|r| r.collect_set_of(PKCS12Attribute::parse))? - .unwrap_or_else(|| vec![]); + .unwrap_or_else(Vec::new); Ok(SafeBag { bag, attributes }) }) From f671118e05bf0f4f9e3b1db71a17dec8342e9671 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Thu, 10 Feb 2022 21:06:21 +0100 Subject: [PATCH 40/87] port to cipher 0.4 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 9 +++++--- src/lib.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 912ae6a2..954aefaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,17 @@ readme = "README.md" license = "MIT OR Apache-2.0" [dependencies] -block-modes = "^0.8" -des = "^0.7" +des = "^0.8" getrandom = "^0.2" hmac = "^0.12" lazy_static = "^1.4" -rc2 = "^0.7" +rc2 = "^0.8" sha1 = "^0.10" +[dependencies.cbc] +version = "^0.1" +features = ["block-padding"] + [dependencies.yasna] version = "^0.5" features = ["std"] diff --git a/src/lib.rs b/src/lib.rs index 5df1eb7c..70362466 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -646,15 +646,24 @@ fn pbe_with_sha1_and40_bit_rc2_cbc( salt: &[u8], iterations: u64, ) -> Option> { - use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use cbc::{ + cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}, + Decryptor, + }; use rc2::Rc2; - type Rc2Cbc = Cbc; + type Rc2Cbc = Decryptor; let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - rc2.decrypt_vec(data).ok() + let mut buf = vec![0; data.len()]; + let len = rc2 + .decrypt_padded_b2b_mut::(&data, &mut buf) + .ok()? + .len(); + buf.resize(len, 0); + Some(buf) } fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( @@ -663,15 +672,26 @@ fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( salt: &[u8], iterations: u64, ) -> Option> { - use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use cbc::{ + cipher::{block_padding::Pkcs7, BlockEncryptMut, BlockSizeUser, KeyIvInit}, + Encryptor, + }; use rc2::Rc2; - type Rc2Cbc = Cbc; + type Rc2Cbc = Encryptor; let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - Some(rc2.encrypt_vec(data)) + let mut len = data.len(); + let bs = Rc2::block_size(); + let extra = len % bs; + if extra != 0 { + len += bs - extra; + } + let mut buf = vec![0; len]; + rc2.encrypt_padded_b2b_mut::(data, &mut buf).unwrap(); + Some(buf) } fn pbe_with_sha_and3_key_triple_des_cbc( @@ -680,15 +700,24 @@ fn pbe_with_sha_and3_key_triple_des_cbc( salt: &[u8], iterations: u64, ) -> Option> { - use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use cbc::{ + cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}, + Decryptor, + }; use des::TdesEde3; - type TDesCbc = Cbc; + type TDesCbc = Decryptor; let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - tdes.decrypt_vec(data).ok() + let mut buf = vec![0; data.len()]; + let len = tdes + .decrypt_padded_b2b_mut::(&data, &mut buf) + .ok()? + .len(); + buf.resize(len, 0); + Some(buf) } fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( @@ -697,15 +726,27 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( salt: &[u8], iterations: u64, ) -> Option> { - use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; + use cbc::{ + cipher::{block_padding::Pkcs7, BlockEncryptMut, BlockSizeUser, KeyIvInit}, + Encryptor, + }; use des::TdesEde3; - type TDesCbc = Cbc; + type TDesCbc = Encryptor; let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - Some(tdes.encrypt_vec(data)) + let mut len = data.len(); + let bs = TdesEde3::block_size(); + let extra = len % bs; + if extra != 0 { + len += bs - extra; + } + let mut buf = vec![0; len]; + tdes.encrypt_padded_b2b_mut::(data, &mut buf) + .unwrap(); + Some(buf) } fn bmp_string(s: &str) -> Vec { From 22270e27aca572fa8e99705815119aebc4180ed0 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Fri, 11 Feb 2022 15:46:24 +0100 Subject: [PATCH 41/87] edition 2021 Signed-off-by: Marc-Antoine Perennou --- .github/workflows/build-and-test.yaml | 2 +- Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 96e5ea06..3d6a05c0 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - rust: [nightly, beta, stable, 1.51.0] + rust: [nightly, beta, stable, 1.56.0] steps: - uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 954aefaa..50ef6564 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,14 @@ name = "p12" version = "0.5.0" authors = ["hjiayz ", "Marc-Antoine Perennou "] -edition = "2018" +edition = "2021" keywords = ["pkcs12", "pkcs"] description = "pure rust pkcs12 tool" homepage = "https://github.com/hjiayz/p12" repository = "https://github.com/hjiayz/p12" readme = "README.md" license = "MIT OR Apache-2.0" +rust-version = "1.56.0" [dependencies] des = "^0.8" From e5c1353d8510cb62c645ad9be1b22bd6a2cc0f4a Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Fri, 11 Feb 2022 15:47:10 +0100 Subject: [PATCH 42/87] fix clippy lint Signed-off-by: Marc-Antoine Perennou --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 70362466..1ef5399e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -659,7 +659,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc( let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; let mut buf = vec![0; data.len()]; let len = rc2 - .decrypt_padded_b2b_mut::(&data, &mut buf) + .decrypt_padded_b2b_mut::(data, &mut buf) .ok()? .len(); buf.resize(len, 0); @@ -713,7 +713,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc( let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; let mut buf = vec![0; data.len()]; let len = tdes - .decrypt_padded_b2b_mut::(&data, &mut buf) + .decrypt_padded_b2b_mut::(data, &mut buf) .ok()? .len(); buf.resize(len, 0); From df7e97a3ab1528b81ffb5d9d7ce2fefedf83f051 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Fri, 11 Feb 2022 15:47:27 +0100 Subject: [PATCH 43/87] v0.6.0 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 50ef6564..6f54ae3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.5.0" +version = "0.6.0" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2021" keywords = ["pkcs12", "pkcs"] From 88f34d1f152febda2b7523f18567fac184a39402 Mon Sep 17 00:00:00 2001 From: Patrick Amrein Date: Tue, 15 Feb 2022 15:31:43 +0100 Subject: [PATCH 44/87] ensure we have at least BS data (empty passwords) --- src/lib.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1ef5399e..7bbec853 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -740,7 +740,8 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( let mut len = data.len(); let bs = TdesEde3::block_size(); let extra = len % bs; - if extra != 0 { + if extra != 0 + || len == 0 { len += bs - extra; } let mut buf = vec![0; len]; @@ -1057,6 +1058,32 @@ fn test_create_p12() { let mut fp12 = File::create("test.p12").unwrap(); fp12.write_all(&p12).unwrap(); } +#[test] +fn test_create_p12_without_password() { + use std::fs::File; + use std::io::{Read, Write}; + let mut cafile = File::open("ca.der").unwrap(); + let mut ca = vec![]; + cafile.read_to_end(&mut ca).unwrap(); + let mut fcert = File::open("clientcert.der").unwrap(); + + let mut cert = vec![]; + fcert.read_to_end(&mut cert).unwrap(); + + let p12 = PFX::new(&cert, &[], Some(&ca), "", "look") + .unwrap() + .to_der(); + + let pfx = PFX::parse(&p12).unwrap(); + + let certs = pfx.cert_x509_bags("").unwrap(); + assert_eq!(certs[0], cert); + assert_eq!(certs[1], ca); + assert!(pfx.verify_mac("")); + + let mut fp12 = File::create("test.p12").unwrap(); + fp12.write_all(&p12).unwrap(); +} #[test] fn test_bmp_string() { From 8113ee5c4d8112355fbe6206b4e84d814a0b9f61 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Tue, 15 Feb 2022 15:49:30 +0100 Subject: [PATCH 45/87] v0.6.1 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6f54ae3d..e762ab4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.6.0" +version = "0.6.1" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2021" keywords = ["pkcs12", "pkcs"] diff --git a/src/lib.rs b/src/lib.rs index 7bbec853..e5b2a6bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -740,8 +740,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( let mut len = data.len(); let bs = TdesEde3::block_size(); let extra = len % bs; - if extra != 0 - || len == 0 { + if extra != 0 || len == 0 { len += bs - extra; } let mut buf = vec![0; len]; From ac90e05270b063c05f11dced127f11abdb17d3ff Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 16 Feb 2022 08:10:11 +0100 Subject: [PATCH 46/87] update to cipher 0.4.1 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 4 ++++ src/lib.rs | 41 ++++++----------------------------------- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e762ab4a..8d8a7570 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,10 @@ sha1 = "^0.10" version = "^0.1" features = ["block-padding"] +[dependencies.cipher] +version = "^0.4.1" +features = ["alloc", "block-padding"] + [dependencies.yasna] version = "^0.5" features = ["std"] diff --git a/src/lib.rs b/src/lib.rs index e5b2a6bc..0ee5577f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -657,13 +657,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc( let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - let mut buf = vec![0; data.len()]; - let len = rc2 - .decrypt_padded_b2b_mut::(data, &mut buf) - .ok()? - .len(); - buf.resize(len, 0); - Some(buf) + rc2.decrypt_padded_vec::(data).ok() } fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( @@ -673,7 +667,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( iterations: u64, ) -> Option> { use cbc::{ - cipher::{block_padding::Pkcs7, BlockEncryptMut, BlockSizeUser, KeyIvInit}, + cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}, Encryptor, }; use rc2::Rc2; @@ -683,15 +677,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - let mut len = data.len(); - let bs = Rc2::block_size(); - let extra = len % bs; - if extra != 0 { - len += bs - extra; - } - let mut buf = vec![0; len]; - rc2.encrypt_padded_b2b_mut::(data, &mut buf).unwrap(); - Some(buf) + Some(rc2.encrypt_padded_vec_mut::(data)) } fn pbe_with_sha_and3_key_triple_des_cbc( @@ -711,13 +697,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc( let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - let mut buf = vec![0; data.len()]; - let len = tdes - .decrypt_padded_b2b_mut::(data, &mut buf) - .ok()? - .len(); - buf.resize(len, 0); - Some(buf) + tdes.decrypt_padded_vec::(data).ok() } fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( @@ -727,7 +707,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( iterations: u64, ) -> Option> { use cbc::{ - cipher::{block_padding::Pkcs7, BlockEncryptMut, BlockSizeUser, KeyIvInit}, + cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}, Encryptor, }; use des::TdesEde3; @@ -737,16 +717,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - let mut len = data.len(); - let bs = TdesEde3::block_size(); - let extra = len % bs; - if extra != 0 || len == 0 { - len += bs - extra; - } - let mut buf = vec![0; len]; - tdes.encrypt_padded_b2b_mut::(data, &mut buf) - .unwrap(); - Some(buf) + Some(tdes.encrypt_padded_vec_mut::(data)) } fn bmp_string(s: &str) -> Vec { From 202f08dbf4ff4e3cdddb8a61365ca3b39da9fe1e Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Wed, 16 Feb 2022 08:10:23 +0100 Subject: [PATCH 47/87] v0.6.2 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8d8a7570..b3dcf0ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.6.1" +version = "0.6.2" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2021" keywords = ["pkcs12", "pkcs"] From 45615bc65e5a347b46aa8547675a55d6e5877fbf Mon Sep 17 00:00:00 2001 From: Julius Michaelis Date: Thu, 17 Feb 2022 17:07:56 +0900 Subject: [PATCH 48/87] update to cipher 0.4.2 --- Cargo.toml | 2 +- src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3dcf0ed..a8fee07d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ version = "^0.1" features = ["block-padding"] [dependencies.cipher] -version = "^0.4.1" +version = "^0.4.2" features = ["alloc", "block-padding"] [dependencies.yasna] diff --git a/src/lib.rs b/src/lib.rs index 0ee5577f..7ad065bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -657,7 +657,7 @@ fn pbe_with_sha1_and40_bit_rc2_cbc( let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - rc2.decrypt_padded_vec::(data).ok() + rc2.decrypt_padded_vec_mut::(data).ok() } fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( @@ -697,7 +697,7 @@ fn pbe_with_sha_and3_key_triple_des_cbc( let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - tdes.decrypt_padded_vec::(data).ok() + tdes.decrypt_padded_vec_mut::(data).ok() } fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( From 9983420fd81ba123ee2c284c8e564734de8b5f00 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Fri, 18 Feb 2022 11:14:43 +0100 Subject: [PATCH 49/87] v0.6.3 Signed-off-by: Marc-Antoine Perennou --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a8fee07d..0a972fdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "p12" -version = "0.6.2" +version = "0.6.3" authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2021" keywords = ["pkcs12", "pkcs"] From d6ad9a518a55c383ef12e40cff91bd52be9eef43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 28 Jan 2025 12:31:03 +0100 Subject: [PATCH 50/87] Add TrustStore CRD and controller --- .../helm/secret-operator/templates/roles.yaml | 10 +++ rust/operator-binary/src/backend/dynamic.rs | 7 ++ rust/operator-binary/src/backend/mod.rs | 4 + rust/operator-binary/src/backend/tls/mod.rs | 31 +++++-- rust/operator-binary/src/crd.rs | 17 ++++ rust/operator-binary/src/format/convert.rs | 29 ++++-- rust/operator-binary/src/format/well_known.rs | 30 ++++--- rust/operator-binary/src/main.rs | 24 +++-- .../src/truststore_controller.rs | 90 +++++++++++++++++++ .../kuttl/tls-truststore/00-patch-ns.yaml.j2 | 9 ++ .../kuttl/tls-truststore/01-truststore.yaml | 6 ++ tests/test-definition.yaml | 3 + 12 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 rust/operator-binary/src/truststore_controller.rs create mode 100644 tests/templates/kuttl/tls-truststore/00-patch-ns.yaml.j2 create mode 100644 tests/templates/kuttl/tls-truststore/01-truststore.yaml diff --git a/deploy/helm/secret-operator/templates/roles.yaml b/deploy/helm/secret-operator/templates/roles.yaml index 2ebb1b02..fcbe82d4 100644 --- a/deploy/helm/secret-operator/templates/roles.yaml +++ b/deploy/helm/secret-operator/templates/roles.yaml @@ -55,6 +55,13 @@ rules: - create - patch - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - patch - apiGroups: - "" resources: @@ -95,8 +102,11 @@ rules: - secrets.stackable.tech resources: - secretclasses + - truststores verbs: - get + - watch + - list - apiGroups: - listeners.stackable.tech resources: diff --git a/rust/operator-binary/src/backend/dynamic.rs b/rust/operator-binary/src/backend/dynamic.rs index 8a88359d..0bf35e03 100644 --- a/rust/operator-binary/src/backend/dynamic.rs +++ b/rust/operator-binary/src/backend/dynamic.rs @@ -67,6 +67,13 @@ impl SecretBackend for DynamicAdapter { .map_err(|err| DynError(Box::new(err))) } + async fn get_trust_data(&self) -> Result { + self.0 + .get_trust_data() + .await + .map_err(|err| DynError(Box::new(err))) + } + async fn get_qualified_node_names( &self, selector: &SecretVolumeSelector, diff --git a/rust/operator-binary/src/backend/mod.rs b/rust/operator-binary/src/backend/mod.rs index 7c2cdad6..b8ebe789 100644 --- a/rust/operator-binary/src/backend/mod.rs +++ b/rust/operator-binary/src/backend/mod.rs @@ -274,6 +274,10 @@ pub trait SecretBackend: Debug + Send + Sync { pod_info: pod_info::PodInfo, ) -> Result; + async fn get_trust_data(&self) -> Result { + todo!("temporary blanket impl, remove before merging") + } + /// Try to predict which nodes would be able to provision this secret. /// /// Should return `None` if no constraints apply, `Some(HashSet::new())` is interpreted as "no nodes match the given constraints". diff --git a/rust/operator-binary/src/backend/tls/mod.rs b/rust/operator-binary/src/backend/tls/mod.rs index 4b5c06bd..7b83119c 100644 --- a/rust/operator-binary/src/backend/tls/mod.rs +++ b/rust/operator-binary/src/backend/tls/mod.rs @@ -323,12 +323,16 @@ impl SecretBackend for TlsGenerate { .context(SerializeCertificateSnafu { tpe: CertType::Ca }) }), )?, - certificate_pem: pod_cert - .to_pem() - .context(SerializeCertificateSnafu { tpe: CertType::Pod })?, - key_pem: pod_key - .private_key_to_pem_pkcs8() - .context(SerializeCertificateSnafu { tpe: CertType::Pod })?, + certificate_pem: Some( + pod_cert + .to_pem() + .context(SerializeCertificateSnafu { tpe: CertType::Pod })?, + ), + key_pem: Some( + pod_key + .private_key_to_pem_pkcs8() + .context(SerializeCertificateSnafu { tpe: CertType::Pod })?, + ), }, ))) .expires_after( @@ -336,6 +340,21 @@ impl SecretBackend for TlsGenerate { ), ) } + + async fn get_trust_data(&self) -> Result { + Ok(SecretContents::new(SecretData::WellKnown( + WellKnownSecretData::TlsPem(well_known::TlsPem { + ca_pem: iterator_try_concat_bytes(self.ca_manager.trust_roots().into_iter().map( + |ca| { + ca.to_pem() + .context(SerializeCertificateSnafu { tpe: CertType::Ca }) + }, + ))?, + certificate_pem: None, + key_pem: None, + }), + ))) + } } #[derive(Snafu, Debug)] diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 21809316..359963e6 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -368,6 +368,23 @@ impl Deref for KerberosPrincipal { } } +#[derive(CustomResource, Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[kube( + group = "secrets.stackable.tech", + version = "v1alpha1", + kind = "TrustStore", + namespaced, + crates( + kube_core = "stackable_operator::kube::core", + k8s_openapi = "stackable_operator::k8s_openapi", + schemars = "stackable_operator::schemars" + ) +)] +#[serde(rename_all = "camelCase")] +pub struct TrustStoreSpec { + pub secret_class_name: String, +} + #[cfg(test)] mod test { use super::*; diff --git a/rust/operator-binary/src/format/convert.rs b/rust/operator-binary/src/format/convert.rs index 2679b7d9..e8fe9aef 100644 --- a/rust/operator-binary/src/format/convert.rs +++ b/rust/operator-binary/src/format/convert.rs @@ -54,8 +54,14 @@ pub fn convert_tls_to_pkcs12( p12_password: &str, ) -> Result { use tls_to_pkcs12_error::*; - let cert = X509::from_pem(&pem.certificate_pem).context(LoadCertSnafu)?; - let key = PKey::private_key_from_pem(&pem.key_pem).context(LoadKeySnafu)?; + let cert = pem + .certificate_pem + .map(|cert| X509::from_pem(&cert).context(LoadCertSnafu)) + .transpose()?; + let key = pem + .key_pem + .map(|key| PKey::private_key_from_pem(&key).context(LoadKeySnafu)) + .transpose()?; let mut ca_stack = Stack::::new().context(LoadCaSnafu)?; for ca in split_pem_certificates(&pem.ca_pem) { @@ -66,13 +72,18 @@ pub fn convert_tls_to_pkcs12( Ok(TlsPkcs12 { truststore: pkcs12_truststore(&ca_stack, p12_password)?, - keystore: Pkcs12::builder() - .ca(ca_stack) - .cert(&cert) - .pkey(&key) - .build2(p12_password) - .and_then(|store| store.to_der()) - .context(BuildKeystoreSnafu)?, + keystore: cert + .zip(key) + .map(|(cert, key)| { + Pkcs12::builder() + .ca(ca_stack) + .cert(&cert) + .pkey(&key) + .build2(p12_password) + .and_then(|store| store.to_der()) + .context(BuildKeystoreSnafu) + }) + .transpose()?, }) } diff --git a/rust/operator-binary/src/format/well_known.rs b/rust/operator-binary/src/format/well_known.rs index 0e3c0e0d..dc6d3154 100644 --- a/rust/operator-binary/src/format/well_known.rs +++ b/rust/operator-binary/src/format/well_known.rs @@ -16,14 +16,14 @@ const FILE_KERBEROS_KEYTAB_KRB5_CONF: &str = "krb5.conf"; #[derive(Debug)] pub struct TlsPem { - pub certificate_pem: Vec, - pub key_pem: Vec, + pub certificate_pem: Option>, + pub key_pem: Option>, pub ca_pem: Vec, } #[derive(Debug)] pub struct TlsPkcs12 { - pub keystore: Vec, + pub keystore: Option>, pub truststore: Vec, } @@ -53,19 +53,23 @@ impl WellKnownSecretData { key_pem, ca_pem, }) => [ - (FILE_PEM_CERT_CERT.to_string(), certificate_pem), - (FILE_PEM_CERT_KEY.to_string(), key_pem), - (FILE_PEM_CERT_CA.to_string(), ca_pem), + Some(FILE_PEM_CERT_CERT.to_string()).zip(certificate_pem), + Some(FILE_PEM_CERT_KEY.to_string()).zip(key_pem), + Some((FILE_PEM_CERT_CA.to_string(), ca_pem)), ] - .into(), + .into_iter() + .flatten() + .collect(), WellKnownSecretData::TlsPkcs12(TlsPkcs12 { keystore, truststore, }) => [ - (FILE_PKCS12_CERT_KEYSTORE.to_string(), keystore), - (FILE_PKCS12_CERT_TRUSTSTORE.to_string(), truststore), + Some(FILE_PKCS12_CERT_KEYSTORE.to_string()).zip(keystore), + Some((FILE_PKCS12_CERT_TRUSTSTORE.to_string(), truststore)), ] - .into(), + .into_iter() + .flatten() + .collect(), WellKnownSecretData::Kerberos(Kerberos { keytab, krb5_conf }) => [ (FILE_KERBEROS_KEYTAB_KEYTAB.to_string(), keytab), (FILE_KERBEROS_KEYTAB_KRB5_CONF.to_string(), krb5_conf), @@ -84,13 +88,13 @@ impl WellKnownSecretData { if let Ok(certificate_pem) = take_file(SecretFormat::TlsPem, FILE_PEM_CERT_CERT) { let mut take_file = |file| take_file(SecretFormat::TlsPem, file); Ok(WellKnownSecretData::TlsPem(TlsPem { - certificate_pem, - key_pem: take_file(FILE_PEM_CERT_KEY)?, + certificate_pem: Some(certificate_pem), + key_pem: Some(take_file(FILE_PEM_CERT_KEY)?), ca_pem: take_file(FILE_PEM_CERT_CA)?, })) } else if let Ok(keystore) = take_file(SecretFormat::TlsPkcs12, FILE_PKCS12_CERT_KEYSTORE) { Ok(WellKnownSecretData::TlsPkcs12(TlsPkcs12 { - keystore, + keystore: Some(keystore), truststore: take_file(SecretFormat::TlsPkcs12, FILE_PKCS12_CERT_TRUSTSTORE)?, })) } else if let Ok(keytab) = take_file(SecretFormat::Kerberos, FILE_KERBEROS_KEYTAB_KEYTAB) { diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 6c466b29..63828dc0 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -9,9 +9,10 @@ use grpc::csi::v1::{ controller_server::ControllerServer, identity_server::IdentityServer, node_server::NodeServer, }; use stackable_operator::{ - logging::TracingTarget, utils::cluster_info::KubernetesClusterInfoOpts, CustomResourceExt, + cli::ProductOperatorRun, logging::TracingTarget, namespace::WatchNamespace, + utils::cluster_info::KubernetesClusterInfoOpts, CustomResourceExt, }; -use std::{os::unix::prelude::FileTypeExt, path::PathBuf}; +use std::{os::unix::prelude::FileTypeExt, path::PathBuf, pin::pin}; use tokio::signal::unix::{signal, SignalKind}; use tokio_stream::wrappers::UnixListenerStream; use tonic::transport::Server; @@ -23,6 +24,7 @@ mod csi_server; mod external_crd; mod format; mod grpc; +mod truststore_controller; mod utils; pub const APP_NAME: &str = "secret"; @@ -58,6 +60,11 @@ struct SecretOperatorRun { #[command(flatten)] pub cluster_info_opts: KubernetesClusterInfoOpts, + + // FIXME: Use ProductOperatorRun instead? + /// Provides a specific namespace to watch (instead of watching all namespaces) + #[arg(long, env, default_value = "")] + pub watch_namespace: WatchNamespace, } mod built_info { @@ -70,6 +77,7 @@ async fn main() -> anyhow::Result<()> { match opts.cmd { stackable_operator::cli::Command::Crd => { crd::SecretClass::print_yaml_schema(built_info::PKG_VERSION)?; + crd::TrustStore::print_yaml_schema(built_info::PKG_VERSION)?; } stackable_operator::cli::Command::Run(SecretOperatorRun { csi_endpoint, @@ -77,6 +85,7 @@ async fn main() -> anyhow::Result<()> { tracing_target, privileged, cluster_info_opts, + watch_namespace, }) => { stackable_operator::logging::initialize_logging( "SECRET_PROVISIONER_LOG", @@ -104,7 +113,7 @@ async fn main() -> anyhow::Result<()> { let _ = std::fs::remove_file(&csi_endpoint); } let mut sigterm = signal(SignalKind::terminate())?; - Server::builder() + let csi_server = pin!(Server::builder() .add_service( tonic_reflection::server::Builder::configure() .include_reflection_service(true) @@ -116,7 +125,7 @@ async fn main() -> anyhow::Result<()> { client: client.clone(), })) .add_service(NodeServer::new(SecretProvisionerNode { - client, + client: client.clone(), node_name, privileged, })) @@ -126,8 +135,11 @@ async fn main() -> anyhow::Result<()> { ) .map_ok(TonicUnixStream), sigterm.recv().map(|_| ()), - ) - .await?; + )); + let truststore_controller = + pin!(truststore_controller::start(&client, &watch_namespace)); + // TODO: handle error + futures::future::select(csi_server, truststore_controller).await; } } Ok(()) diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs new file mode 100644 index 00000000..22e62b3a --- /dev/null +++ b/rust/operator-binary/src/truststore_controller.rs @@ -0,0 +1,90 @@ +use std::{sync::Arc, time::Duration}; + +use futures::StreamExt; +use snafu::Snafu; +use stackable_operator::{ + builder::meta::ObjectMetaBuilder, + k8s_openapi::{api::core::v1::ConfigMap, ByteString}, + kube::{ + core::DeserializeGuard, + runtime::{controller, watcher, Controller}, + }, + namespace::WatchNamespace, +}; + +use crate::{ + backend, + crd::{SecretClass, TrustStore}, + format::well_known::CompatibilityOptions, +}; + +pub async fn start(client: &stackable_operator::client::Client, watch_namespace: &WatchNamespace) { + Controller::new( + watch_namespace.get_api::>(client), + watcher::Config::default(), + ) + .run( + reconcile, + error_policy, + Arc::new(Ctx { + client: client.clone(), + }), + ) + .for_each(|x| async move { + println!("{x:?}"); + }) + .await; +} + +#[derive(Debug, Snafu)] +pub enum Error {} +type Result = std::result::Result; + +struct Ctx { + client: stackable_operator::client::Client, +} + +async fn reconcile( + truststore: Arc>, + ctx: Arc, +) -> Result { + let truststore = truststore.0.as_ref().unwrap(); + let secret_class = ctx + .client + .get::(&truststore.spec.secret_class_name, &()) + .await + .unwrap(); + let backend = backend::dynamic::from_class(&ctx.client, secret_class) + .await + .unwrap(); + let trust_data = backend.get_trust_data().await.unwrap(); + let trust_cm = ConfigMap { + metadata: ObjectMetaBuilder::new() + .name_and_namespace(truststore) + .build(), + binary_data: Some( + trust_data + .data + .into_files(None, &CompatibilityOptions::default()) + .unwrap() + .into_iter() + .map(|(k, v)| (k, ByteString(v))) + .collect(), + ), + ..Default::default() + }; + ctx.client + .apply_patch("truststore", &trust_cm, &trust_cm) + .await + .unwrap(); + // TODO: Configure watch instead + Ok(controller::Action::requeue(Duration::from_secs(5))) +} + +fn error_policy( + _obj: Arc>, + _error: &Error, + _ctx: Arc, +) -> controller::Action { + controller::Action::requeue(Duration::from_secs(5)) +} diff --git a/tests/templates/kuttl/tls-truststore/00-patch-ns.yaml.j2 b/tests/templates/kuttl/tls-truststore/00-patch-ns.yaml.j2 new file mode 100644 index 00000000..67185acf --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/00-patch-ns.yaml.j2 @@ -0,0 +1,9 @@ +{% if test_scenario['values']['openshift'] == 'true' %} +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' + timeout: 120 +{% endif %} diff --git a/tests/templates/kuttl/tls-truststore/01-truststore.yaml b/tests/templates/kuttl/tls-truststore/01-truststore.yaml new file mode 100644 index 00000000..eef14fbf --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/01-truststore.yaml @@ -0,0 +1,6 @@ +apiVersion: secrets.stackable.tech/v1alpha1 +kind: TrustStore +metadata: + name: truststore +spec: + secretClassName: tls diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 91cdc540..2da6bf01 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -32,6 +32,9 @@ tests: dimensions: - rsa-key-length - openshift + - name: tls-truststore + dimensions: + - openshift - name: cert-manager-tls dimensions: - openshift From 4c0c1d4fa63f78e04a43c2c777cc9e7dd9e0875d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 29 Jan 2025 14:29:56 +0100 Subject: [PATCH 51/87] Support custom TrustStore formats --- rust/operator-binary/src/crd.rs | 7 ++++++- rust/operator-binary/src/format/well_known.rs | 5 +++-- rust/operator-binary/src/truststore_controller.rs | 2 +- tests/templates/kuttl/tls-truststore/01-truststore.yaml | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 359963e6..589a2942 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -9,8 +9,12 @@ use stackable_operator::{ time::Duration, }; use stackable_secret_operator_crd_utils::SecretReference; +use time::error::Format; -use crate::backend; +use crate::{ + backend, + format::{well_known::CompatibilityOptions, SecretFormat}, +}; /// A [SecretClass](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass) is a cluster-global Kubernetes resource /// that defines a category of secrets that the Secret Operator knows how to provision. @@ -383,6 +387,7 @@ impl Deref for KerberosPrincipal { #[serde(rename_all = "camelCase")] pub struct TrustStoreSpec { pub secret_class_name: String, + pub format: Option, } #[cfg(test)] diff --git a/rust/operator-binary/src/format/well_known.rs b/rust/operator-binary/src/format/well_known.rs index dc6d3154..f28c486a 100644 --- a/rust/operator-binary/src/format/well_known.rs +++ b/rust/operator-binary/src/format/well_known.rs @@ -1,5 +1,6 @@ -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use snafu::{OptionExt, Snafu}; +use stackable_operator::schemars::{self, JsonSchema}; use strum::EnumDiscriminants; use super::{convert, ConvertError, SecretFiles}; @@ -36,7 +37,7 @@ pub struct Kerberos { #[derive(Debug, EnumDiscriminants)] #[strum_discriminants( name(SecretFormat), - derive(Deserialize), + derive(Serialize, Deserialize, JsonSchema), serde(rename_all = "kebab-case") )] pub enum WellKnownSecretData { diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 22e62b3a..11513721 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -65,7 +65,7 @@ async fn reconcile( binary_data: Some( trust_data .data - .into_files(None, &CompatibilityOptions::default()) + .into_files(truststore.spec.format, &CompatibilityOptions::default()) .unwrap() .into_iter() .map(|(k, v)| (k, ByteString(v))) diff --git a/tests/templates/kuttl/tls-truststore/01-truststore.yaml b/tests/templates/kuttl/tls-truststore/01-truststore.yaml index eef14fbf..6a514cf9 100644 --- a/tests/templates/kuttl/tls-truststore/01-truststore.yaml +++ b/tests/templates/kuttl/tls-truststore/01-truststore.yaml @@ -4,3 +4,4 @@ metadata: name: truststore spec: secretClassName: tls + format: tls-pkcs12 From 03ef7b22f0c670807cbf3c9300f4225009b378d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 29 Jan 2025 14:31:23 +0100 Subject: [PATCH 52/87] Fix clippy warnings --- rust/operator-binary/src/crd.rs | 6 +----- rust/operator-binary/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 589a2942..22599f93 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -9,12 +9,8 @@ use stackable_operator::{ time::Duration, }; use stackable_secret_operator_crd_utils::SecretReference; -use time::error::Format; -use crate::{ - backend, - format::{well_known::CompatibilityOptions, SecretFormat}, -}; +use crate::{backend, format::SecretFormat}; /// A [SecretClass](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass) is a cluster-global Kubernetes resource /// that defines a category of secrets that the Secret Operator knows how to provision. diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 63828dc0..9ff6b904 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -9,7 +9,7 @@ use grpc::csi::v1::{ controller_server::ControllerServer, identity_server::IdentityServer, node_server::NodeServer, }; use stackable_operator::{ - cli::ProductOperatorRun, logging::TracingTarget, namespace::WatchNamespace, + logging::TracingTarget, namespace::WatchNamespace, utils::cluster_info::KubernetesClusterInfoOpts, CustomResourceExt, }; use std::{os::unix::prelude::FileTypeExt, path::PathBuf, pin::pin}; From a9085d7ce98edbdc806da54631f7a172fd25cf40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 20 Feb 2025 15:58:24 +0100 Subject: [PATCH 53/87] Explicitly disallow TrustStore for kerberos backend --- rust/operator-binary/src/backend/kerberos_keytab.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/operator-binary/src/backend/kerberos_keytab.rs b/rust/operator-binary/src/backend/kerberos_keytab.rs index 71f48076..f9bb1194 100644 --- a/rust/operator-binary/src/backend/kerberos_keytab.rs +++ b/rust/operator-binary/src/backend/kerberos_keytab.rs @@ -67,6 +67,9 @@ pub enum Error { #[snafu(display("failed to read keytab"))] ReadKeytab { source: std::io::Error }, + + #[snafu(display("the kerberosKeytab backend does not currently support TrustStore exports"))] + TrustExportUnsupported, } impl SecretBackendError for Error { fn grpc_code(&self) -> tonic::Code { @@ -80,6 +83,7 @@ impl SecretBackendError for Error { Error::PodPrincipal { .. } => tonic::Code::FailedPrecondition, Error::ReadKeytab { .. } => tonic::Code::Unavailable, Error::ScopeAddresses { .. } => tonic::Code::Unavailable, + Error::TrustExportUnsupported => tonic::Code::FailedPrecondition, } } } @@ -284,4 +288,8 @@ cluster.local = {realm_name} }), ))) } + + async fn get_trust_data(&self) -> Result { + TrustExportUnsupportedSnafu.fail() + } } From c4e170706430837ee16254a094d1044cd7af2c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 20 Feb 2025 16:48:02 +0100 Subject: [PATCH 54/87] Start sketching out k8s-search support --- .../src/backend/cert_manager.rs | 8 ++++ rust/operator-binary/src/backend/dynamic.rs | 21 ++++++---- .../operator-binary/src/backend/k8s_search.rs | 42 ++++++++++++++++--- .../src/backend/kerberos_keytab.rs | 5 ++- rust/operator-binary/src/backend/mod.rs | 11 +++-- rust/operator-binary/src/backend/tls/mod.rs | 5 ++- rust/operator-binary/src/crd.rs | 7 ++++ .../src/truststore_controller.rs | 7 +++- 8 files changed, 85 insertions(+), 21 deletions(-) diff --git a/rust/operator-binary/src/backend/cert_manager.rs b/rust/operator-binary/src/backend/cert_manager.rs index d5f1d274..40d85858 100644 --- a/rust/operator-binary/src/backend/cert_manager.rs +++ b/rust/operator-binary/src/backend/cert_manager.rs @@ -24,6 +24,7 @@ use super::{ pod_info::{Address, PodInfo, SchedulingPodInfo}, scope::SecretScope, ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents, SecretVolumeSelector, + TrustSelector, }; /// Default lifetime of certs when no annotations are set on the Volume. @@ -173,6 +174,13 @@ impl SecretBackend for CertManager { ))) } + async fn get_trust_data( + &self, + _selector: &TrustSelector, + ) -> Result { + todo!() + } + async fn get_qualified_node_names( &self, selector: &SecretVolumeSelector, diff --git a/rust/operator-binary/src/backend/dynamic.rs b/rust/operator-binary/src/backend/dynamic.rs index 0bf35e03..817e1121 100644 --- a/rust/operator-binary/src/backend/dynamic.rs +++ b/rust/operator-binary/src/backend/dynamic.rs @@ -67,9 +67,12 @@ impl SecretBackend for DynamicAdapter { .map_err(|err| DynError(Box::new(err))) } - async fn get_trust_data(&self) -> Result { + async fn get_trust_data( + &self, + selector: &super::TrustSelector, + ) -> Result { self.0 - .get_trust_data() + .get_trust_data(selector) .await .map_err(|err| DynError(Box::new(err))) } @@ -118,12 +121,14 @@ pub async fn from_class( class: SecretClass, ) -> Result, FromClassError> { Ok(match class.spec.backend { - crd::SecretClassBackend::K8sSearch(crd::K8sSearchBackend { search_namespace }) => { - from(super::K8sSearch { - client: Unloggable(client.clone()), - search_namespace, - }) - } + crd::SecretClassBackend::K8sSearch(crd::K8sSearchBackend { + search_namespace, + truststore_configmap_name, + }) => from(super::K8sSearch { + client: Unloggable(client.clone()), + search_namespace, + truststore_configmap_name, + }), crd::SecretClassBackend::AutoTls(crd::AutoTlsBackend { ca, max_certificate_lifetime, diff --git a/rust/operator-binary/src/backend/k8s_search.rs b/rust/operator-binary/src/backend/k8s_search.rs index f0d9fe78..424213ec 100644 --- a/rust/operator-binary/src/backend/k8s_search.rs +++ b/rust/operator-binary/src/backend/k8s_search.rs @@ -6,7 +6,9 @@ use async_trait::async_trait; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ k8s_openapi::{ - api::core::v1::Secret, apimachinery::pkg::apis::meta::v1::LabelSelector, ByteString, + api::core::v1::{ConfigMap, Secret}, + apimachinery::pkg::apis::meta::v1::LabelSelector, + ByteString, }, kube::api::ListParams, kvp::{LabelError, LabelSelectorExt, Labels}, @@ -17,7 +19,7 @@ use crate::{crd::SearchNamespace, format::SecretData, utils::Unloggable}; use super::{ pod_info::{PodInfo, SchedulingPodInfo}, scope::SecretScope, - SecretBackend, SecretBackendError, SecretContents, SecretVolumeSelector, + SecretBackend, SecretBackendError, SecretContents, SecretVolumeSelector, TrustSelector, }; const LABEL_CLASS: &str = "secrets.stackable.tech/class"; @@ -65,12 +67,13 @@ pub struct K8sSearch { // Not secret per se, but isn't Debug: https://github.com/stackabletech/secret-operator/issues/411 pub client: Unloggable, pub search_namespace: SearchNamespace, + pub truststore_configmap_name: Option, } impl K8sSearch { - fn search_ns_for_pod<'a>(&'a self, selector: &'a SecretVolumeSelector) -> &'a str { + fn search_ns_for_target<'a>(&'a self, target_namespace: &'a str) -> &'a str { match &self.search_namespace { - SearchNamespace::Pod {} => &selector.namespace, + SearchNamespace::Pod {} => target_namespace, SearchNamespace::Name(ns) => ns, } } @@ -90,7 +93,7 @@ impl SecretBackend for K8sSearch { let secret = self .client .list::( - self.search_ns_for_pod(selector), + self.search_ns_for_target(&selector.namespace), &ListParams::default().labels(&label_selector), ) .await @@ -108,6 +111,33 @@ impl SecretBackend for K8sSearch { ))) } + async fn get_trust_data( + &self, + selector: &TrustSelector, + ) -> Result { + let cm = self + .client + .get::( + self.truststore_configmap_name.as_deref().unwrap(), + self.search_ns_for_target(&selector.namespace), + ) + .await + .unwrap(); + Ok(SecretContents::new(SecretData::Unknown( + cm.binary_data + .unwrap_or_default() + .into_iter() + .map(|(k, ByteString(v))| (k, v)) + .chain( + cm.data + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, v.into_bytes())), + ) + .collect(), + ))) + } + async fn get_qualified_node_names( &self, selector: &SecretVolumeSelector, @@ -119,7 +149,7 @@ impl SecretBackend for K8sSearch { Ok(Some( self.client .list::( - self.search_ns_for_pod(selector), + self.search_ns_for_target(&selector.namespace), &ListParams::default().labels(&label_selector), ) .await diff --git a/rust/operator-binary/src/backend/kerberos_keytab.rs b/rust/operator-binary/src/backend/kerberos_keytab.rs index f9bb1194..a3fbf06d 100644 --- a/rust/operator-binary/src/backend/kerberos_keytab.rs +++ b/rust/operator-binary/src/backend/kerberos_keytab.rs @@ -289,7 +289,10 @@ cluster.local = {realm_name} ))) } - async fn get_trust_data(&self) -> Result { + async fn get_trust_data( + &self, + _selector: &super::TrustSelector, + ) -> Result { TrustExportUnsupportedSnafu.fail() } } diff --git a/rust/operator-binary/src/backend/mod.rs b/rust/operator-binary/src/backend/mod.rs index b8ebe789..4eeae760 100644 --- a/rust/operator-binary/src/backend/mod.rs +++ b/rust/operator-binary/src/backend/mod.rs @@ -137,6 +137,12 @@ pub struct SecretVolumeSelector { pub cert_manager_cert_lifetime: Option, } +/// Configuration provided by the [`TrustStore`] selecting what trust data should be provided. +pub struct TrustSelector { + /// The name of the [`TrustStore`]'s `Namespace`. + pub namespace: String, +} + /// Internal parameters of [`SecretVolumeSelector`] managed by secret-operator itself. // These are optional even if they are set unconditionally, because otherwise we will // fail to restore volumes (after Node reboots etc) from before they were added during upgrades. @@ -274,9 +280,8 @@ pub trait SecretBackend: Debug + Send + Sync { pod_info: pod_info::PodInfo, ) -> Result; - async fn get_trust_data(&self) -> Result { - todo!("temporary blanket impl, remove before merging") - } + async fn get_trust_data(&self, selector: &TrustSelector) + -> Result; /// Try to predict which nodes would be able to provision this secret. /// diff --git a/rust/operator-binary/src/backend/tls/mod.rs b/rust/operator-binary/src/backend/tls/mod.rs index 7b83119c..1a20f691 100644 --- a/rust/operator-binary/src/backend/tls/mod.rs +++ b/rust/operator-binary/src/backend/tls/mod.rs @@ -341,7 +341,10 @@ impl SecretBackend for TlsGenerate { ) } - async fn get_trust_data(&self) -> Result { + async fn get_trust_data( + &self, + _selector: &super::TrustSelector, + ) -> Result { Ok(SecretContents::new(SecretData::WellKnown( WellKnownSecretData::TlsPem(well_known::TlsPem { ca_pem: iterator_try_concat_bytes(self.ca_manager.trust_roots().into_iter().map( diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 22599f93..638e1854 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -69,6 +69,13 @@ pub enum SecretClassBackend { pub struct K8sSearchBackend { /// Configures the namespace searched for Secret objects. pub search_namespace: SearchNamespace, + + /// Name of a ConfigMap that contains the information required to validate against this SecretClass. + /// + /// Resolved relative to `search_namespace`. + /// + /// Required to request a TrustStore for this SecretClass. + pub truststore_configmap_name: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 11513721..5c2b6cd0 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -13,7 +13,7 @@ use stackable_operator::{ }; use crate::{ - backend, + backend::{self, TrustSelector}, crd::{SecretClass, TrustStore}, format::well_known::CompatibilityOptions, }; @@ -57,7 +57,10 @@ async fn reconcile( let backend = backend::dynamic::from_class(&ctx.client, secret_class) .await .unwrap(); - let trust_data = backend.get_trust_data().await.unwrap(); + let selector = TrustSelector { + namespace: truststore.metadata.namespace.clone().unwrap(), + }; + let trust_data = backend.get_trust_data(&selector).await.unwrap(); let trust_cm = ConfigMap { metadata: ObjectMetaBuilder::new() .name_and_namespace(truststore) From 9791bee6da021a76ea41785af85719800c8c3434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 10 Mar 2025 13:54:45 +0100 Subject: [PATCH 55/87] Configure watches for the truststore controller --- Cargo.lock | 1 + Cargo.nix | 6 + Cargo.toml | 1 + deploy/helm/secret-operator/crds/crds.yaml | 54 ++++++ .../helm/secret-operator/templates/roles.yaml | 3 + rust/crd-utils/src/lib.rs | 22 ++- rust/operator-binary/Cargo.toml | 1 + rust/operator-binary/src/backend/dynamic.rs | 4 +- .../operator-binary/src/backend/k8s_search.rs | 19 +- rust/operator-binary/src/crd.rs | 20 ++- .../src/truststore_controller.rs | 164 ++++++++++++++++-- 11 files changed, 258 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab812d06..9f9edd87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2902,6 +2902,7 @@ dependencies = [ "clap", "futures 0.3.31", "h2", + "kube-runtime", "libc", "openssl", "p12", diff --git a/Cargo.nix b/Cargo.nix index 655784e4..32f0158b 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -5265,6 +5265,7 @@ rec { features = { "unstable-runtime" = [ "unstable-runtime-subscribe" "unstable-runtime-stream-control" "unstable-runtime-reconcile-on" ]; }; + resolvedDefaultFeatures = [ "unstable-runtime-stream-control" ]; }; "lazy_static" = rec { crateName = "lazy_static"; @@ -9238,6 +9239,11 @@ rec { name = "h2"; packageId = "h2"; } + { + name = "kube-runtime"; + packageId = "kube-runtime"; + features = [ "unstable-runtime-stream-control" ]; + } { name = "libc"; packageId = "libc"; diff --git a/Cargo.toml b/Cargo.toml index 0bd2710e..7803ea7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ byteorder = "1.5" clap = "4.5" futures = { version = "0.3", features = ["compat"] } h2 = "0.4" +kube-runtime = { version = "0.98", features = ["unstable-runtime-stream-control"] } ldap3 = { version = "0.11", default-features = false, features = [ "gssapi", "tls", diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml index e9957745..989dc0d8 100644 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -180,6 +180,15 @@ spec: description: The Secret objects are located in the same namespace as the Pod object. Should be used for Secrets that are provisioned by the application administrator. type: object type: object + trustStoreConfigMapName: + description: |- + Name of a ConfigMap that contains the information required to validate against this SecretClass. + + Resolved relative to `search_namespace`. + + Required to request a TrustStore for this SecretClass. + nullable: true + type: string required: - searchNamespace type: object @@ -308,3 +317,48 @@ spec: served: true storage: true subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: truststores.secrets.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: secrets.stackable.tech + names: + categories: [] + kind: TrustStore + plural: truststores + shortNames: [] + singular: truststore + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for TrustStoreSpec via `CustomResource` + properties: + spec: + properties: + format: + description: Auto-generated discriminant enum variants + enum: + - tls-pem + - tls-pkcs12 + - kerberos + nullable: true + type: string + secretClassName: + type: string + required: + - secretClassName + type: object + required: + - spec + title: TrustStore + type: object + served: true + storage: true + subresources: {} diff --git a/deploy/helm/secret-operator/templates/roles.yaml b/deploy/helm/secret-operator/templates/roles.yaml index fcbe82d4..51eaf131 100644 --- a/deploy/helm/secret-operator/templates/roles.yaml +++ b/deploy/helm/secret-operator/templates/roles.yaml @@ -62,6 +62,9 @@ rules: verbs: - create - patch + - get + - watch + - list - apiGroups: - "" resources: diff --git a/rust/crd-utils/src/lib.rs b/rust/crd-utils/src/lib.rs index cc6b962b..bf5ad557 100644 --- a/rust/crd-utils/src/lib.rs +++ b/rust/crd-utils/src/lib.rs @@ -5,7 +5,10 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; use stackable_operator::{ k8s_openapi::api::core::v1::Secret, - kube::runtime::reflector::ObjectRef, + kube::{ + api::{ObjectMeta, PartialObjectMeta}, + runtime::reflector::ObjectRef, + }, schemars::{self, JsonSchema}, }; @@ -35,3 +38,20 @@ impl From<&SecretReference> for ObjectRef { ObjectRef::::new(&val.name).within(&val.namespace) } } + +impl SecretReference { + fn matches(&self, secret_meta: &ObjectMeta) -> bool { + secret_meta.name.as_deref() == Some(&self.name) + && secret_meta.namespace.as_deref() == Some(&self.namespace) + } +} +impl PartialEq for SecretReference { + fn eq(&self, secret: &Secret) -> bool { + self.matches(&secret.metadata) + } +} +impl PartialEq> for SecretReference { + fn eq(&self, secret: &PartialObjectMeta) -> bool { + self.matches(&secret.metadata) + } +} diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 3058dbe7..161a37b2 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -17,6 +17,7 @@ async-trait.workspace = true clap.workspace = true futures.workspace = true h2.workspace = true +kube-runtime.workspace = true libc.workspace = true openssl.workspace = true p12.workspace = true diff --git a/rust/operator-binary/src/backend/dynamic.rs b/rust/operator-binary/src/backend/dynamic.rs index 817e1121..28e56826 100644 --- a/rust/operator-binary/src/backend/dynamic.rs +++ b/rust/operator-binary/src/backend/dynamic.rs @@ -123,11 +123,11 @@ pub async fn from_class( Ok(match class.spec.backend { crd::SecretClassBackend::K8sSearch(crd::K8sSearchBackend { search_namespace, - truststore_configmap_name, + trust_store_config_map_name, }) => from(super::K8sSearch { client: Unloggable(client.clone()), search_namespace, - truststore_configmap_name, + trust_store_config_map_name, }), crd::SecretClassBackend::AutoTls(crd::AutoTlsBackend { ca, diff --git a/rust/operator-binary/src/backend/k8s_search.rs b/rust/operator-binary/src/backend/k8s_search.rs index 424213ec..ce4d150c 100644 --- a/rust/operator-binary/src/backend/k8s_search.rs +++ b/rust/operator-binary/src/backend/k8s_search.rs @@ -67,16 +67,7 @@ pub struct K8sSearch { // Not secret per se, but isn't Debug: https://github.com/stackabletech/secret-operator/issues/411 pub client: Unloggable, pub search_namespace: SearchNamespace, - pub truststore_configmap_name: Option, -} - -impl K8sSearch { - fn search_ns_for_target<'a>(&'a self, target_namespace: &'a str) -> &'a str { - match &self.search_namespace { - SearchNamespace::Pod {} => target_namespace, - SearchNamespace::Name(ns) => ns, - } - } + pub trust_store_config_map_name: Option, } #[async_trait] @@ -93,7 +84,7 @@ impl SecretBackend for K8sSearch { let secret = self .client .list::( - self.search_ns_for_target(&selector.namespace), + self.search_namespace.resolve(&selector.namespace), &ListParams::default().labels(&label_selector), ) .await @@ -118,8 +109,8 @@ impl SecretBackend for K8sSearch { let cm = self .client .get::( - self.truststore_configmap_name.as_deref().unwrap(), - self.search_ns_for_target(&selector.namespace), + self.trust_store_config_map_name.as_deref().unwrap(), + self.search_namespace.resolve(&selector.namespace), ) .await .unwrap(); @@ -149,7 +140,7 @@ impl SecretBackend for K8sSearch { Ok(Some( self.client .list::( - self.search_ns_for_target(&selector.namespace), + self.search_namespace.resolve(&selector.namespace), &ListParams::default().labels(&label_selector), ) .await diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 638e1854..1cea3b8e 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -75,10 +75,10 @@ pub struct K8sSearchBackend { /// Resolved relative to `search_namespace`. /// /// Required to request a TrustStore for this SecretClass. - pub truststore_configmap_name: Option, + pub trust_store_config_map_name: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, JsonSchema)] #[serde(rename_all = "camelCase")] pub enum SearchNamespace { /// The Secret objects are located in the same namespace as the Pod object. @@ -90,6 +90,22 @@ pub enum SearchNamespace { Name(String), } +impl SearchNamespace { + pub fn resolve<'a>(&'a self, pod_namespace: &'a str) -> &'a str { + match self { + SearchNamespace::Pod {} => pod_namespace, + SearchNamespace::Name(ns) => ns, + } + } + + pub fn can_match_namespace(&self, potential_match_namespace: &str) -> bool { + match self { + SearchNamespace::Pod {} => true, + SearchNamespace::Name(ns) => ns == potential_match_namespace, + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct AutoTlsBackend { diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 5c2b6cd0..548ed5d1 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -1,39 +1,165 @@ -use std::{sync::Arc, time::Duration}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, + time::Duration, +}; use futures::StreamExt; +use kube_runtime::WatchStreamExt as _; use snafu::Snafu; use stackable_operator::{ builder::meta::ObjectMetaBuilder, - k8s_openapi::{api::core::v1::ConfigMap, ByteString}, + k8s_openapi::{ + api::core::v1::{ConfigMap, Secret}, + ByteString, + }, kube::{ + api::PartialObjectMeta, core::DeserializeGuard, - runtime::{controller, watcher, Controller}, + runtime::{ + controller, + reflector::{self, ObjectRef}, + watcher, Controller, + }, + Resource, }, namespace::WatchNamespace, }; use crate::{ backend::{self, TrustSelector}, - crd::{SecretClass, TrustStore}, + crd::{self, SearchNamespace, SecretClass, TrustStore}, format::well_known::CompatibilityOptions, }; pub async fn start(client: &stackable_operator::client::Client, watch_namespace: &WatchNamespace) { - Controller::new( + let (secretclasses, secretclasses_writer) = reflector::store(); + let controller = Controller::new( watch_namespace.get_api::>(client), watcher::Config::default(), - ) - .run( - reconcile, - error_policy, - Arc::new(Ctx { - client: client.clone(), - }), - ) - .for_each(|x| async move { - println!("{x:?}"); - }) - .await; + ); + let truststores = controller.store(); + controller + .watches_stream( + watcher( + client.get_api::>(&()), + watcher::Config::default(), + ) + .reflect(secretclasses_writer) + .touched_objects(), + { + let truststores = truststores.clone(); + move |secretclass| { + truststores + .state() + .into_iter() + .filter(move |ts| { + ts.0.as_ref().is_ok_and(|ts| { + Some(&ts.spec.secret_class_name) == secretclass.meta().name.as_ref() + }) + }) + .map(|ts| ObjectRef::from_obj(&*ts)) + } + }, + ) + // TODO: merge this into the other ConfigMap watch + .owns( + watch_namespace.get_api::>(client), + watcher::Config::default(), + ) + // TODO: refactor... + .watches( + watch_namespace.get_api::>(client), + watcher::Config::default(), + { + let truststores = truststores.clone(); + let secretclasses = secretclasses.clone(); + move |cm| { + let cm_namespace = cm.metadata.namespace.as_deref().unwrap(); + let potentially_matching_secretclasses = secretclasses + .state() + .into_iter() + .filter_map(move |sc| { + sc.0.as_ref().ok().and_then(|sc| match &sc.spec.backend { + crd::SecretClassBackend::K8sSearch(backend) => { + let name_matches = + backend.trust_store_config_map_name == cm.metadata.name; + (name_matches + && backend + .search_namespace + .can_match_namespace(cm_namespace)) + .then(|| { + (ObjectRef::from_obj(sc), backend.search_namespace.clone()) + }) + } + crd::SecretClassBackend::AutoTls(_) => None, + crd::SecretClassBackend::CertManager(_) => None, + crd::SecretClassBackend::KerberosKeytab(_) => None, + }) + }) + .collect::, SearchNamespace>>(); + truststores + .state() + .into_iter() + .filter(move |ts| { + ts.0.as_ref().is_ok_and(|ts| { + let secret_class_ref = + ObjectRef::::new(&ts.spec.secret_class_name); + potentially_matching_secretclasses + .get(&secret_class_ref) + .is_some_and(|secret_class_ns| { + secret_class_ns + .resolve(ts.metadata.namespace.as_deref().unwrap()) + == cm.metadata.namespace.as_deref().unwrap() + }) + }) + }) + .map(|ts| ObjectRef::from_obj(&*ts)) + } + }, + ) + .watches( + watch_namespace.get_api::>(client), + watcher::Config::default(), + move |secret| { + let matching_secretclasses = secretclasses + .state() + .into_iter() + .filter_map(move |sc| { + sc.0.as_ref().ok().and_then(|sc| match &sc.spec.backend { + crd::SecretClassBackend::AutoTls(backend) => { + (backend.ca.secret == secret).then(|| ObjectRef::from_obj(sc)) + } + crd::SecretClassBackend::K8sSearch(_) => None, + crd::SecretClassBackend::CertManager(_) => None, + crd::SecretClassBackend::KerberosKeytab(_) => None, + }) + }) + .collect::>>(); + truststores + .state() + .into_iter() + .filter(move |ts| { + ts.0.as_ref().is_ok_and(|ts| { + let secret_class_ref = + ObjectRef::::new(&ts.spec.secret_class_name); + matching_secretclasses.contains(&secret_class_ref) + }) + }) + .map(|ts| ObjectRef::from_obj(&*ts)) + }, + ) + .run( + reconcile, + error_policy, + Arc::new(Ctx { + client: client.clone(), + }), + ) + .for_each(|x| async move { + println!("{x:?}"); + }) + .await; } #[derive(Debug, Snafu)] @@ -64,6 +190,8 @@ async fn reconcile( let trust_cm = ConfigMap { metadata: ObjectMetaBuilder::new() .name_and_namespace(truststore) + .ownerreference_from_resource(truststore, None, Some(true)) + .unwrap() .build(), binary_data: Some( trust_data @@ -81,7 +209,7 @@ async fn reconcile( .await .unwrap(); // TODO: Configure watch instead - Ok(controller::Action::requeue(Duration::from_secs(5))) + Ok(controller::Action::await_change()) } fn error_policy( From 26b54eb60c11dd192a9ddbee0c3650874b50c13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 10 Mar 2025 20:43:58 +0100 Subject: [PATCH 56/87] Optimistically try to encode truststore data as string --- .../src/truststore_controller.rs | 24 +++++---- rust/operator-binary/src/utils.rs | 53 ++++++++++++++++++- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 548ed5d1..3721f29f 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -30,6 +30,7 @@ use crate::{ backend::{self, TrustSelector}, crd::{self, SearchNamespace, SecretClass, TrustStore}, format::well_known::CompatibilityOptions, + utils::Flattened, }; pub async fn start(client: &stackable_operator::client::Client, watch_namespace: &WatchNamespace) { @@ -187,28 +188,31 @@ async fn reconcile( namespace: truststore.metadata.namespace.clone().unwrap(), }; let trust_data = backend.get_trust_data(&selector).await.unwrap(); + let (Flattened(string_data), Flattened(binary_data)) = trust_data + .data + .into_files(truststore.spec.format, &CompatibilityOptions::default()) + .unwrap() + .into_iter() + // Try to put valid UTF-8 data into `data`, but fall back to `binary_data` otherwise + .map(|(k, v)| match String::from_utf8(v) { + Ok(v) => (Some((k, v)), None), + Err(v) => (None, Some((k, ByteString(v.into_bytes())))), + }) + .collect(); let trust_cm = ConfigMap { metadata: ObjectMetaBuilder::new() .name_and_namespace(truststore) .ownerreference_from_resource(truststore, None, Some(true)) .unwrap() .build(), - binary_data: Some( - trust_data - .data - .into_files(truststore.spec.format, &CompatibilityOptions::default()) - .unwrap() - .into_iter() - .map(|(k, v)| (k, ByteString(v))) - .collect(), - ), + data: Some(string_data), + binary_data: Some(binary_data), ..Default::default() }; ctx.client .apply_patch("truststore", &trust_cm, &trust_cm) .await .unwrap(); - // TODO: Configure watch instead Ok(controller::Action::await_change()) } diff --git a/rust/operator-binary/src/utils.rs b/rust/operator-binary/src/utils.rs index fff6a69c..7211f7d3 100644 --- a/rust/operator-binary/src/utils.rs +++ b/rust/operator-binary/src/utils.rs @@ -201,13 +201,41 @@ impl DerefMut for Unloggable { } } +/// Wrapper type for [`Iterator::collect`] that flattens the incoming [`Iterator`]. +/// +/// This isn't super useful for "regular" collects (just call [`Iterator::flatten`]!), +/// but it can be composed with the [`FromIterator`] impl on [`tuple`]s to partition +/// an incoming iterator while giving each branch a unique type. +#[derive(Default, Debug, PartialEq, Eq)] +pub struct Flattened(pub T); + +impl Extend for Flattened +where + E: IntoIterator, + T: Extend, +{ + fn extend>(&mut self, iter: I2) { + self.0.extend(iter.into_iter().flatten()); + } +} + +impl FromIterator for Flattened +where + I: IntoIterator, + T: FromIterator, +{ + fn from_iter>(iter: I2) -> Self { + Self(iter.into_iter().flatten().collect()) + } +} + #[cfg(test)] mod tests { use futures::StreamExt; use openssl::asn1::Asn1Time; use time::OffsetDateTime; - use crate::utils::{error_full_message, trystream_any, FmtByteSlice}; + use crate::utils::{error_full_message, trystream_any, Flattened, FmtByteSlice}; use super::{asn1time_to_offsetdatetime, iterator_try_concat_bytes}; @@ -296,4 +324,27 @@ mod tests { .unwrap() ); } + + #[test] + fn flattened_collect_single() { + let Flattened(small @ Vec:: { .. }) = [2, 10, 1000, 5, 2000] + .into_iter() + .map(|x| u8::try_from(x).ok()) + .collect(); + assert_eq!(small, vec![2, 10, 5]); + } + + #[test] + fn flattened_collect_split() { + let (Flattened(small @ Vec:: { .. }), Flattened(big @ Vec:: { .. })) = + [2, 10, 1000, 5, 2000] + .into_iter() + .map(|x| match u8::try_from(x) { + Ok(x) => (Some(x), None), + Err(_) => (None, Some(x)), + }) + .collect(); + assert_eq!(small, vec![2, 10, 5]); + assert_eq!(big, vec![1000, 2000]); + } } From b3656dd0a7bb352dc0a94f114951b228318c7bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 12 Mar 2025 14:26:19 +0100 Subject: [PATCH 57/87] Clean up error handling --- .../src/backend/cert_manager.rs | 6 +- .../operator-binary/src/backend/k8s_search.rs | 26 ++++-- rust/operator-binary/src/backend/tls/ca.rs | 3 +- rust/operator-binary/src/backend/tls/mod.rs | 3 +- .../src/truststore_controller.rs | 81 +++++++++++++++---- 5 files changed, 94 insertions(+), 25 deletions(-) diff --git a/rust/operator-binary/src/backend/cert_manager.rs b/rust/operator-binary/src/backend/cert_manager.rs index 40d85858..a68bd588 100644 --- a/rust/operator-binary/src/backend/cert_manager.rs +++ b/rust/operator-binary/src/backend/cert_manager.rs @@ -61,6 +61,9 @@ pub enum Error { source: stackable_operator::client::Error, certificate: ObjectRef, }, + + #[snafu(display("the certManager backend does not currently support TrustStore exports"))] + TrustExportUnsupported, } impl SecretBackendError for Error { @@ -71,6 +74,7 @@ impl SecretBackendError for Error { Error::GetSecret { .. } => tonic::Code::Unavailable, Error::GetCertManagerCertificate { .. } => tonic::Code::Unavailable, Error::ApplyCertManagerCertificate { .. } => tonic::Code::Unavailable, + Error::TrustExportUnsupported => tonic::Code::FailedPrecondition, } } } @@ -178,7 +182,7 @@ impl SecretBackend for CertManager { &self, _selector: &TrustSelector, ) -> Result { - todo!() + TrustExportUnsupportedSnafu.fail() } async fn get_qualified_node_names( diff --git a/rust/operator-binary/src/backend/k8s_search.rs b/rust/operator-binary/src/backend/k8s_search.rs index ce4d150c..8822f1c1 100644 --- a/rust/operator-binary/src/backend/k8s_search.rs +++ b/rust/operator-binary/src/backend/k8s_search.rs @@ -3,6 +3,7 @@ use std::collections::{BTreeMap, HashSet}; use async_trait::async_trait; +use kube_runtime::reflector::ObjectRef; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ k8s_openapi::{ @@ -48,6 +49,15 @@ pub enum Error { #[snafu(display("failed to build label"))] BuildLabel { source: LabelError }, + + #[snafu(display("no trust store ConfigMap is configured for this backend"))] + NoTrustStore, + + #[snafu(display("failed to query for trust store source {configmap}"))] + GetTrustStore { + source: stackable_operator::client::Error, + configmap: ObjectRef, + }, } impl SecretBackendError for Error { @@ -58,6 +68,8 @@ impl SecretBackendError for Error { Error::NoSecret { .. } => tonic::Code::FailedPrecondition, Error::NoListener { .. } => tonic::Code::FailedPrecondition, Error::BuildLabel { .. } => tonic::Code::FailedPrecondition, + Error::NoTrustStore => tonic::Code::FailedPrecondition, + Error::GetTrustStore { .. } => tonic::Code::Internal, } } } @@ -106,14 +118,18 @@ impl SecretBackend for K8sSearch { &self, selector: &TrustSelector, ) -> Result { + let cm_name = self + .trust_store_config_map_name + .as_deref() + .context(NoTrustStoreSnafu)?; + let cm_ns = self.search_namespace.resolve(&selector.namespace); let cm = self .client - .get::( - self.trust_store_config_map_name.as_deref().unwrap(), - self.search_namespace.resolve(&selector.namespace), - ) + .get::(cm_name, cm_ns) .await - .unwrap(); + .with_context(|_| GetTrustStoreSnafu { + configmap: ObjectRef::::new(cm_name).within(cm_ns), + })?; Ok(SecretContents::new(SecretData::Unknown( cm.binary_data .unwrap_or_default() diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 442d0925..7ff1f3bb 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -191,7 +191,8 @@ impl CertificateAuthority { let now = OffsetDateTime::now_utc(); let not_before = now - Duration::from_minutes_unchecked(5); let not_after = now + config.ca_certificate_lifetime; - let conf = Conf::new(ConfMethod::default()).unwrap(); + let conf = + Conf::new(ConfMethod::default()).expect("failed to initialize OpenSSL configuration"); let private_key_length = match config.key_generation { CertificateKeyGeneration::Rsa { length } => length, diff --git a/rust/operator-binary/src/backend/tls/mod.rs b/rust/operator-binary/src/backend/tls/mod.rs index 1a20f691..8277edaf 100644 --- a/rust/operator-binary/src/backend/tls/mod.rs +++ b/rust/operator-binary/src/backend/tls/mod.rs @@ -236,7 +236,8 @@ impl SecretBackend for TlsGenerate { .fail()?; } - let conf = Conf::new(ConfMethod::default()).unwrap(); + let conf = + Conf::new(ConfMethod::default()).expect("failed to initialize OpenSSL configuration"); let pod_key_length = match self.key_generation { CertificateKeyGeneration::Rsa { length } => length, diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 3721f29f..a449242b 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -6,7 +6,7 @@ use std::{ use futures::StreamExt; use kube_runtime::WatchStreamExt as _; -use snafu::Snafu; +use snafu::{OptionExt as _, ResultExt as _, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, k8s_openapi::{ @@ -15,7 +15,7 @@ use stackable_operator::{ }, kube::{ api::PartialObjectMeta, - core::DeserializeGuard, + core::{error_boundary, DeserializeGuard}, runtime::{ controller, reflector::{self, ObjectRef}, @@ -29,7 +29,7 @@ use stackable_operator::{ use crate::{ backend::{self, TrustSelector}, crd::{self, SearchNamespace, SecretClass, TrustStore}, - format::well_known::CompatibilityOptions, + format::{self, well_known::CompatibilityOptions}, utils::Flattened, }; @@ -76,7 +76,7 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: let truststores = truststores.clone(); let secretclasses = secretclasses.clone(); move |cm| { - let cm_namespace = cm.metadata.namespace.as_deref().unwrap(); + let cm_namespace = cm.metadata.namespace.as_deref(); let potentially_matching_secretclasses = secretclasses .state() .into_iter() @@ -88,7 +88,7 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: (name_matches && backend .search_namespace - .can_match_namespace(cm_namespace)) + .can_match_namespace(cm_namespace?)) .then(|| { (ObjectRef::from_obj(sc), backend.search_namespace.clone()) }) @@ -104,14 +104,16 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: .into_iter() .filter(move |ts| { ts.0.as_ref().is_ok_and(|ts| { + let Some(ts_namespace) = ts.metadata.namespace.as_deref() else { + return false; + }; let secret_class_ref = ObjectRef::::new(&ts.spec.secret_class_name); potentially_matching_secretclasses .get(&secret_class_ref) .is_some_and(|secret_class_ns| { - secret_class_ns - .resolve(ts.metadata.namespace.as_deref().unwrap()) - == cm.metadata.namespace.as_deref().unwrap() + Some(secret_class_ns.resolve(ts_namespace)) + == cm.metadata.namespace.as_deref() }) }) }) @@ -164,7 +166,41 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: } #[derive(Debug, Snafu)] -pub enum Error {} +pub enum Error { + #[snafu(display("TrustStore object is invalid"))] + InvalidTrustStore { + source: error_boundary::InvalidObject, + }, + + #[snafu(display("failed to get SecretClass for TrustStore"))] + GetSecretClass { + source: stackable_operator::client::Error, + }, + + #[snafu(display("failed to initialize SecretClass backend"))] + InitBackend { + source: backend::dynamic::FromClassError, + }, + + #[snafu(display("failed to get trust data from backend"))] + BackendGetTrustData { source: backend::dynamic::DynError }, + + #[snafu(display("TrustStore has no associated Namespace"))] + NoTrustStoreNamespace, + + #[snafu(display("failed to convert trust data into desired format"))] + FormatData { source: format::IntoFilesError }, + + #[snafu(display("failed to build owner reference to the TrustStore"))] + BuildOwnerReference { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to apply ConfigMap for the TrustStore"))] + ApplyTrustStoreConfigMap { + source: stackable_operator::client::Error, + }, +} type Result = std::result::Result; struct Ctx { @@ -175,23 +211,34 @@ async fn reconcile( truststore: Arc>, ctx: Arc, ) -> Result { - let truststore = truststore.0.as_ref().unwrap(); + let truststore = truststore + .0 + .as_ref() + .map_err(error_boundary::InvalidObject::clone) + .context(InvalidTrustStoreSnafu)?; let secret_class = ctx .client .get::(&truststore.spec.secret_class_name, &()) .await - .unwrap(); + .context(GetSecretClassSnafu)?; let backend = backend::dynamic::from_class(&ctx.client, secret_class) .await - .unwrap(); + .context(InitBackendSnafu)?; let selector = TrustSelector { - namespace: truststore.metadata.namespace.clone().unwrap(), + namespace: truststore + .metadata + .namespace + .clone() + .context(NoTrustStoreNamespaceSnafu)?, }; - let trust_data = backend.get_trust_data(&selector).await.unwrap(); + let trust_data = backend + .get_trust_data(&selector) + .await + .context(BackendGetTrustDataSnafu)?; let (Flattened(string_data), Flattened(binary_data)) = trust_data .data .into_files(truststore.spec.format, &CompatibilityOptions::default()) - .unwrap() + .context(FormatDataSnafu)? .into_iter() // Try to put valid UTF-8 data into `data`, but fall back to `binary_data` otherwise .map(|(k, v)| match String::from_utf8(v) { @@ -203,7 +250,7 @@ async fn reconcile( metadata: ObjectMetaBuilder::new() .name_and_namespace(truststore) .ownerreference_from_resource(truststore, None, Some(true)) - .unwrap() + .context(BuildOwnerReferenceSnafu)? .build(), data: Some(string_data), binary_data: Some(binary_data), @@ -212,7 +259,7 @@ async fn reconcile( ctx.client .apply_patch("truststore", &trust_cm, &trust_cm) .await - .unwrap(); + .context(ApplyTrustStoreConfigMapSnafu)?; Ok(controller::Action::await_change()) } From b569b050e45c789b611162432da3ab3f981b09b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 12 Mar 2025 15:58:08 +0100 Subject: [PATCH 58/87] Start factoring out truststore reference watch logic --- rust/operator-binary/src/crd.rs | 87 ++++++++++++++++++- .../src/truststore_controller.rs | 70 ++++++++------- 2 files changed, 117 insertions(+), 40 deletions(-) diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 1cea3b8e..5d4acc4f 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -4,7 +4,8 @@ use serde::{Deserialize, Serialize}; use snafu::Snafu; use stackable_operator::{ commons::networking::{HostName, KerberosRealmName}, - kube::CustomResource, + k8s_openapi::api::core::v1::{ConfigMap, Secret}, + kube::{api::PartialObjectMeta, CustomResource}, schemars::{self, schema::Schema, JsonSchema}, time::Duration, }; @@ -64,6 +65,51 @@ pub enum SecretClassBackend { KerberosKeytab(KerberosKeytabBackend), } +impl SecretClassBackend { + // Currently no `refers_to_*` method actually returns more than one element, + // but returning `Iterator` instead of `Option` to ensure that all consumers are ready + // for adding more conditions. + + // The matcher methods are on the CRD type rather than the initialized `Backend` impls + // to avoid having to initialize the backend for each watch event. + + /// Returns the conditions where the backend refers to `config_map`. + pub fn refers_to_config_map( + &self, + config_map: &PartialObjectMeta, + ) -> impl Iterator { + let cm_namespace = config_map.metadata.namespace.as_deref(); + match self { + Self::K8sSearch(backend) => { + let name_matches = backend.trust_store_config_map_name == config_map.metadata.name; + cm_namespace + .filter(|_| name_matches) + .and_then(|cm_ns| backend.search_namespace.matches_namespace(cm_ns)) + } + Self::AutoTls(_) => None, + Self::CertManager(_) => None, + Self::KerberosKeytab(_) => None, + } + .into_iter() + } + + /// Returns the conditions where the backend refers to `secret`. + pub fn refers_to_secret( + &self, + secret: &PartialObjectMeta, + ) -> impl Iterator { + match self { + Self::AutoTls(backend) => { + (backend.ca.secret == *secret).then_some(SearchNamespaceMatchCondition::True) + } + Self::K8sSearch(_) => None, + Self::CertManager(_) => None, + Self::KerberosKeytab(_) => None, + } + .into_iter() + } +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct K8sSearchBackend { @@ -98,10 +144,43 @@ impl SearchNamespace { } } - pub fn can_match_namespace(&self, potential_match_namespace: &str) -> bool { + /// Returns [`Some`] if this `SearchNamespace` could possibly match an object in the namespace + /// `object_namespace`, otherwise [`None`]. + /// + /// This is optimistic, you then need to call [`SearchMatchCondition::matches_pod_namespace`] + /// to evaluate the match for a specific pod's namespace. + pub fn matches_namespace( + &self, + object_namespace: &str, + ) -> Option { + match self { + SearchNamespace::Pod {} => Some(SearchNamespaceMatchCondition::IfPodIsInNamespace { + namespace: object_namespace.to_string(), + }), + SearchNamespace::Name(ns) => { + (ns == object_namespace).then_some(SearchNamespaceMatchCondition::True) + } + } + } +} + +/// A partially evaluated match returned by [`SearchNamespace::matches_namespace`]. +/// Use [`matches_pod_namespace`] to evaluate fully. +#[derive(Debug)] +pub enum SearchNamespaceMatchCondition { + /// The target object matches the search namespace. + True, + + /// The target object only matches the search namespace if mounted into a pod in + /// `namespace`. + IfPodIsInNamespace { namespace: String }, +} + +impl SearchNamespaceMatchCondition { + pub fn matches_pod_namespace(&self, pod_ns: &str) -> bool { match self { - SearchNamespace::Pod {} => true, - SearchNamespace::Name(ns) => ns == potential_match_namespace, + Self::True => true, + Self::IfPodIsInNamespace { namespace } => namespace == pod_ns, } } } diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index a449242b..dba2660d 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -1,8 +1,4 @@ -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, - time::Duration, -}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use futures::StreamExt; use kube_runtime::WatchStreamExt as _; @@ -28,7 +24,7 @@ use stackable_operator::{ use crate::{ backend::{self, TrustSelector}, - crd::{self, SearchNamespace, SecretClass, TrustStore}, + crd::{SearchNamespaceMatchCondition, SecretClass, TrustStore}, format::{self, well_known::CompatibilityOptions}, utils::Flattened, }; @@ -76,29 +72,21 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: let truststores = truststores.clone(); let secretclasses = secretclasses.clone(); move |cm| { - let cm_namespace = cm.metadata.namespace.as_deref(); let potentially_matching_secretclasses = secretclasses .state() .into_iter() .filter_map(move |sc| { - sc.0.as_ref().ok().and_then(|sc| match &sc.spec.backend { - crd::SecretClassBackend::K8sSearch(backend) => { - let name_matches = - backend.trust_store_config_map_name == cm.metadata.name; - (name_matches - && backend - .search_namespace - .can_match_namespace(cm_namespace?)) - .then(|| { - (ObjectRef::from_obj(sc), backend.search_namespace.clone()) - }) - } - crd::SecretClassBackend::AutoTls(_) => None, - crd::SecretClassBackend::CertManager(_) => None, - crd::SecretClassBackend::KerberosKeytab(_) => None, + sc.0.as_ref().ok().and_then(|sc| { + let conditions = sc + .spec + .backend + .refers_to_config_map(&cm) + .collect::>(); + (!conditions.is_empty()) + .then(|| (ObjectRef::from_obj(sc), conditions)) }) }) - .collect::, SearchNamespace>>(); + .collect::, Vec>>(); truststores .state() .into_iter() @@ -111,9 +99,10 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: ObjectRef::::new(&ts.spec.secret_class_name); potentially_matching_secretclasses .get(&secret_class_ref) - .is_some_and(|secret_class_ns| { - Some(secret_class_ns.resolve(ts_namespace)) - == cm.metadata.namespace.as_deref() + .is_some_and(|conds| { + conds + .iter() + .any(|cond| cond.matches_pod_namespace(ts_namespace)) }) }) }) @@ -125,28 +114,37 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: watch_namespace.get_api::>(client), watcher::Config::default(), move |secret| { - let matching_secretclasses = secretclasses + let potentially_matching_secretclasses = secretclasses .state() .into_iter() .filter_map(move |sc| { - sc.0.as_ref().ok().and_then(|sc| match &sc.spec.backend { - crd::SecretClassBackend::AutoTls(backend) => { - (backend.ca.secret == secret).then(|| ObjectRef::from_obj(sc)) - } - crd::SecretClassBackend::K8sSearch(_) => None, - crd::SecretClassBackend::CertManager(_) => None, - crd::SecretClassBackend::KerberosKeytab(_) => None, + sc.0.as_ref().ok().and_then(|sc| { + let conditions = sc + .spec + .backend + .refers_to_secret(&secret) + .collect::>(); + (!conditions.is_empty()).then(|| (ObjectRef::from_obj(sc), conditions)) }) }) - .collect::>>(); + .collect::, Vec>>(); truststores .state() .into_iter() .filter(move |ts| { ts.0.as_ref().is_ok_and(|ts| { + let Some(ts_namespace) = ts.metadata.namespace.as_deref() else { + return false; + }; let secret_class_ref = ObjectRef::::new(&ts.spec.secret_class_name); - matching_secretclasses.contains(&secret_class_ref) + potentially_matching_secretclasses + .get(&secret_class_ref) + .is_some_and(|conds| { + conds + .iter() + .any(|cond| cond.matches_pod_namespace(ts_namespace)) + }) }) }) .map(|ts| ObjectRef::from_obj(&*ts)) From d43274f5496e317cb1189e9b3dcf25a5410dd749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 12 Mar 2025 16:25:41 +0100 Subject: [PATCH 59/87] Factor out TrustStore dependency resolution logic --- .../src/truststore_controller.rs | 135 ++++++++---------- 1 file changed, 57 insertions(+), 78 deletions(-) diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index dba2660d..f58fa282 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -64,91 +64,23 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: watch_namespace.get_api::>(client), watcher::Config::default(), ) - // TODO: refactor... .watches( watch_namespace.get_api::>(client), watcher::Config::default(), - { - let truststores = truststores.clone(); - let secretclasses = secretclasses.clone(); - move |cm| { - let potentially_matching_secretclasses = secretclasses - .state() - .into_iter() - .filter_map(move |sc| { - sc.0.as_ref().ok().and_then(|sc| { - let conditions = sc - .spec - .backend - .refers_to_config_map(&cm) - .collect::>(); - (!conditions.is_empty()) - .then(|| (ObjectRef::from_obj(sc), conditions)) - }) - }) - .collect::, Vec>>(); - truststores - .state() - .into_iter() - .filter(move |ts| { - ts.0.as_ref().is_ok_and(|ts| { - let Some(ts_namespace) = ts.metadata.namespace.as_deref() else { - return false; - }; - let secret_class_ref = - ObjectRef::::new(&ts.spec.secret_class_name); - potentially_matching_secretclasses - .get(&secret_class_ref) - .is_some_and(|conds| { - conds - .iter() - .any(|cond| cond.matches_pod_namespace(ts_namespace)) - }) - }) - }) - .map(|ts| ObjectRef::from_obj(&*ts)) - } - }, + secretclass_dependency_watch_mapper( + truststores.clone(), + secretclasses.clone(), + |secretclass, cm| secretclass.spec.backend.refers_to_config_map(cm), + ), ) .watches( watch_namespace.get_api::>(client), watcher::Config::default(), - move |secret| { - let potentially_matching_secretclasses = secretclasses - .state() - .into_iter() - .filter_map(move |sc| { - sc.0.as_ref().ok().and_then(|sc| { - let conditions = sc - .spec - .backend - .refers_to_secret(&secret) - .collect::>(); - (!conditions.is_empty()).then(|| (ObjectRef::from_obj(sc), conditions)) - }) - }) - .collect::, Vec>>(); - truststores - .state() - .into_iter() - .filter(move |ts| { - ts.0.as_ref().is_ok_and(|ts| { - let Some(ts_namespace) = ts.metadata.namespace.as_deref() else { - return false; - }; - let secret_class_ref = - ObjectRef::::new(&ts.spec.secret_class_name); - potentially_matching_secretclasses - .get(&secret_class_ref) - .is_some_and(|conds| { - conds - .iter() - .any(|cond| cond.matches_pod_namespace(ts_namespace)) - }) - }) - }) - .map(|ts| ObjectRef::from_obj(&*ts)) - }, + secretclass_dependency_watch_mapper( + truststores, + secretclasses, + |secretclass, secret| secretclass.spec.backend.refers_to_secret(secret), + ), ) .run( reconcile, @@ -163,6 +95,53 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: .await; } +/// Resolves modifications to dependencies of [`SecretClass`] objects into +/// a list of affected [`TrustStore`]s. +fn secretclass_dependency_watch_mapper( + truststores: reflector::Store>, + secretclasses: reflector::Store>, + reference_conditions: impl Copy + Fn(&SecretClass, &Dep) -> Conds, +) -> impl Fn(Dep) -> Vec>> +where + Conds: IntoIterator, +{ + move |dep| { + let potentially_matching_secretclasses = secretclasses + .state() + .into_iter() + .filter_map(move |sc| { + sc.0.as_ref().ok().and_then(|sc| { + let conditions = reference_conditions(sc, &dep) + .into_iter() + .collect::>(); + (!conditions.is_empty()).then(|| (ObjectRef::from_obj(sc), conditions)) + }) + }) + .collect::, Vec>>(); + truststores + .state() + .into_iter() + .filter(move |ts| { + ts.0.as_ref().is_ok_and(|ts| { + let Some(ts_namespace) = ts.metadata.namespace.as_deref() else { + return false; + }; + let secret_class_ref = + ObjectRef::::new(&ts.spec.secret_class_name); + potentially_matching_secretclasses + .get(&secret_class_ref) + .is_some_and(|conds| { + conds + .iter() + .any(|cond| cond.matches_pod_namespace(ts_namespace)) + }) + }) + }) + .map(|ts| ObjectRef::from_obj(&*ts)) + .collect() + } +} + #[derive(Debug, Snafu)] pub enum Error { #[snafu(display("TrustStore object is invalid"))] From 57e3727ae7bcf4155b676e3a1a0d51b63284d716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 13 Mar 2025 12:01:34 +0100 Subject: [PATCH 60/87] Log reconciliation results properly --- .../helm/secret-operator/templates/roles.yaml | 7 +++ .../src/truststore_controller.rs | 45 ++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/deploy/helm/secret-operator/templates/roles.yaml b/deploy/helm/secret-operator/templates/roles.yaml index 51eaf131..30fde883 100644 --- a/deploy/helm/secret-operator/templates/roles.yaml +++ b/deploy/helm/secret-operator/templates/roles.yaml @@ -126,6 +126,13 @@ rules: - get - patch - create + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - create + - patch {{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }} - apiGroups: - security.openshift.io diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index f58fa282..2f401f08 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -1,7 +1,10 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; use futures::StreamExt; -use kube_runtime::WatchStreamExt as _; +use kube_runtime::{ + events::{Recorder, Reporter}, + WatchStreamExt as _, +}; use snafu::{OptionExt as _, ResultExt as _, Snafu}; use stackable_operator::{ builder::meta::ObjectMetaBuilder, @@ -19,16 +22,22 @@ use stackable_operator::{ }, Resource, }, + logging::controller::{report_controller_reconciled, ReconcilerError}, namespace::WatchNamespace, }; +use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ backend::{self, TrustSelector}, crd::{SearchNamespaceMatchCondition, SecretClass, TrustStore}, format::{self, well_known::CompatibilityOptions}, utils::Flattened, + OPERATOR_NAME, }; +const CONTROLLER_NAME: &str = "truststore"; +const FULL_CONTROLLER_NAME: &str = "truststore.secrets.stackable.tech"; + pub async fn start(client: &stackable_operator::client::Client, watch_namespace: &WatchNamespace) { let (secretclasses, secretclasses_writer) = reflector::store(); let controller = Controller::new( @@ -36,6 +45,13 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: watcher::Config::default(), ); let truststores = controller.store(); + let event_recorder = Arc::new(Recorder::new( + client.as_kube_client(), + Reporter { + controller: FULL_CONTROLLER_NAME.to_string(), + instance: None, + }, + )); controller .watches_stream( watcher( @@ -89,8 +105,16 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: client: client.clone(), }), ) - .for_each(|x| async move { - println!("{x:?}"); + .for_each_concurrent(16, move |res| { + let event_recorder = event_recorder.clone(); + async move { + report_controller_reconciled( + &event_recorder, + &format!("{CONTROLLER_NAME}.{OPERATOR_NAME}"), + &res, + ) + .await + } }) .await; } @@ -142,7 +166,8 @@ where } } -#[derive(Debug, Snafu)] +#[derive(Debug, Snafu, EnumDiscriminants)] +#[strum_discriminants(derive(IntoStaticStr))] pub enum Error { #[snafu(display("TrustStore object is invalid"))] InvalidTrustStore { @@ -179,6 +204,16 @@ pub enum Error { }, } type Result = std::result::Result; +impl ReconcilerError for Error { + fn category(&self) -> &'static str { + ErrorDiscriminants::from(self).into() + } + + fn secondary_object(&self) -> Option> { + // TODO + None + } +} struct Ctx { client: stackable_operator::client::Client, @@ -234,7 +269,7 @@ async fn reconcile( ..Default::default() }; ctx.client - .apply_patch("truststore", &trust_cm, &trust_cm) + .apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm) .await .context(ApplyTrustStoreConfigMapSnafu)?; Ok(controller::Action::await_change()) From 85b3bfc34a338a0016e52acbc91d0078258eb206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 13 Mar 2025 12:20:13 +0100 Subject: [PATCH 61/87] TrustStore smoke test --- .../kuttl/tls-truststore/01-secretclass.yaml | 5 +++ .../kuttl/tls-truststore/01-truststore.yaml | 7 ---- .../kuttl/tls-truststore/02-assert.yaml | 28 ++++++++++++++ .../kuttl/tls-truststore/02-truststore.yaml | 5 +++ .../kuttl/tls-truststore/03-assert.yaml | 8 ++++ .../kuttl/tls-truststore/secretclass.yaml | 38 +++++++++++++++++++ .../kuttl/tls-truststore/truststore.yaml | 24 ++++++++++++ 7 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 tests/templates/kuttl/tls-truststore/01-secretclass.yaml delete mode 100644 tests/templates/kuttl/tls-truststore/01-truststore.yaml create mode 100644 tests/templates/kuttl/tls-truststore/02-assert.yaml create mode 100644 tests/templates/kuttl/tls-truststore/02-truststore.yaml create mode 100644 tests/templates/kuttl/tls-truststore/03-assert.yaml create mode 100644 tests/templates/kuttl/tls-truststore/secretclass.yaml create mode 100644 tests/templates/kuttl/tls-truststore/truststore.yaml diff --git a/tests/templates/kuttl/tls-truststore/01-secretclass.yaml b/tests/templates/kuttl/tls-truststore/01-secretclass.yaml new file mode 100644 index 00000000..26ebc567 --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/01-secretclass.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: envsubst '$NAMESPACE' < secretclass.yaml | kubectl --namespace=$NAMESPACE apply -f - diff --git a/tests/templates/kuttl/tls-truststore/01-truststore.yaml b/tests/templates/kuttl/tls-truststore/01-truststore.yaml deleted file mode 100644 index 6a514cf9..00000000 --- a/tests/templates/kuttl/tls-truststore/01-truststore.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: secrets.stackable.tech/v1alpha1 -kind: TrustStore -metadata: - name: truststore -spec: - secretClassName: tls - format: tls-pkcs12 diff --git a/tests/templates/kuttl/tls-truststore/02-assert.yaml b/tests/templates/kuttl/tls-truststore/02-assert.yaml new file mode 100644 index 00000000..624f35c6 --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/02-assert.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 5 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: truststore-pem +# data is validated in 03-assert.yaml +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: truststore-pkcs12 +# data is validated in 03-assert.yaml +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: truststore-k8ssearch +data: + foo: bar + # Should be decoded as a valid string + baz: hello +binaryData: + # Should stay binary since it is not legal UTF-8 + actuallyBinary: aWxsZWdhbIB1dGYtOA== diff --git a/tests/templates/kuttl/tls-truststore/02-truststore.yaml b/tests/templates/kuttl/tls-truststore/02-truststore.yaml new file mode 100644 index 00000000..55a2a567 --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/02-truststore.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: envsubst '$NAMESPACE' < truststore.yaml | kubectl --namespace=$NAMESPACE apply -f - diff --git a/tests/templates/kuttl/tls-truststore/03-assert.yaml b/tests/templates/kuttl/tls-truststore/03-assert.yaml new file mode 100644 index 00000000..f81a9795 --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/03-assert.yaml @@ -0,0 +1,8 @@ +# Validate certificates generated by step 02 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 5 +commands: + - script: kubectl --namespace=$NAMESPACE get cm/truststore-pem --output=jsonpath='{.data.ca\.crt}' | openssl x509 -noout + - script: kubectl --namespace=$NAMESPACE get cm/truststore-pkcs12 --output=jsonpath='{.binaryData.truststore\.p12}' | base64 -d | openssl pkcs12 -noout -passin 'pass:' -legacy diff --git a/tests/templates/kuttl/tls-truststore/secretclass.yaml b/tests/templates/kuttl/tls-truststore/secretclass.yaml new file mode 100644 index 00000000..77c29a66 --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/secretclass.yaml @@ -0,0 +1,38 @@ +# $NAMESPACE will be replaced with the namespace of the test case. +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: SecretClass +metadata: + name: tls-$NAMESPACE +spec: + backend: + autoTls: + ca: + secret: + name: secret-provisioner-tls-ca + namespace: $NAMESPACE + autoGenerate: true +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: SecretClass +metadata: + name: k8ssearch-$NAMESPACE +spec: + backend: + k8sSearch: + searchNamespace: + name: $NAMESPACE + trustStoreConfigMapName: truststore-source-k8ssearch +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: truststore-source-k8ssearch +data: + foo: bar +binaryData: + # baz: "hello" + baz: aGVsbG8= + # actuallyBinary: "illegal{0x80}utf-8" (where {0x..} is a raw byte in hex) + # in this case, illegal since a byte starting with 10 is a continuation that must be preceded by a byte starting with 11 or 10 + actuallyBinary: aWxsZWdhbIB1dGYtOA== diff --git a/tests/templates/kuttl/tls-truststore/truststore.yaml b/tests/templates/kuttl/tls-truststore/truststore.yaml new file mode 100644 index 00000000..f0d66b89 --- /dev/null +++ b/tests/templates/kuttl/tls-truststore/truststore.yaml @@ -0,0 +1,24 @@ +# $NAMESPACE will be replaced with the namespace of the test case. +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: TrustStore +metadata: + name: truststore-pem +spec: + secretClassName: tls-$NAMESPACE + format: tls-pem +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: TrustStore +metadata: + name: truststore-pkcs12 +spec: + secretClassName: tls-$NAMESPACE + format: tls-pkcs12 +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: TrustStore +metadata: + name: truststore-k8ssearch +spec: + secretClassName: k8ssearch-$NAMESPACE From 21fe8ed93f9facfbee946f8761db7e6bfbc23144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 13 Mar 2025 13:30:33 +0100 Subject: [PATCH 62/87] Stub out pkcs12 salt for determinism --- Cargo.lock | 16 +- Cargo.nix | 49 +- Cargo.toml | 3 +- rust/operator-binary/src/format/convert.rs | 23 + vendor/p12/.cargo-checksum.json | 1 + vendor/p12/Cargo.toml | 58 ++ vendor/p12/LICENSE-APACHE | 14 + vendor/p12/LICENSE-MIT | 19 + vendor/p12/README.md | 5 + vendor/p12/ca.der | Bin 0 -> 858 bytes vendor/p12/clientcert.der | Bin 0 -> 911 bytes vendor/p12/clientkey.der | Bin 0 -> 1218 bytes vendor/p12/src/lib.rs | 1099 ++++++++++++++++++++ 13 files changed, 1283 insertions(+), 4 deletions(-) create mode 100644 vendor/p12/.cargo-checksum.json create mode 100644 vendor/p12/Cargo.toml create mode 100644 vendor/p12/LICENSE-APACHE create mode 100644 vendor/p12/LICENSE-MIT create mode 100644 vendor/p12/README.md create mode 100644 vendor/p12/ca.der create mode 100644 vendor/p12/clientcert.der create mode 100644 vendor/p12/clientkey.der create mode 100644 vendor/p12/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9f9edd87..c27d3918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1040,6 +1040,18 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hmac" version = "0.12.1" @@ -2056,13 +2068,13 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "p12" version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" dependencies = [ "cbc", "cipher", "des", "getrandom", + "hex", + "hex-literal", "hmac", "lazy_static", "rc2", diff --git a/Cargo.nix b/Cargo.nix index 32f0158b..e36977cd 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -57,6 +57,16 @@ rec { # File a bug if you depend on any for non-debug work! debug = internal.debugCrate { inherit packageId; }; }; + "p12" = rec { + packageId = "p12"; + build = internal.buildRustCrateWithFeatures { + packageId = "p12"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; "stackable-krb5-provision-keytab" = rec { packageId = "stackable-krb5-provision-keytab"; build = internal.buildRustCrateWithFeatures { @@ -3106,6 +3116,33 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "hex" = rec { + crateName = "hex"; + version = "0.4.3"; + edition = "2018"; + sha256 = "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z"; + authors = [ + "KokaKiwi " + ]; + features = { + "default" = [ "std" ]; + "serde" = [ "dep:serde" ]; + "std" = [ "alloc" ]; + }; + resolvedDefaultFeatures = [ "alloc" "default" "std" ]; + }; + "hex-literal" = rec { + crateName = "hex-literal"; + version = "0.3.4"; + edition = "2018"; + sha256 = "1q54yvyy0zls9bdrx15hk6yj304npndy9v4crn1h1vd95sfv5gby"; + procMacro = true; + libName = "hex_literal"; + authors = [ + "RustCrypto Developers" + ]; + + }; "hmac" = rec { crateName = "hmac"; version = "0.12.1"; @@ -6547,7 +6584,7 @@ rec { crateName = "p12"; version = "0.6.3"; edition = "2021"; - sha256 = "094jzl331mj5gg6xcbpanqa1bmj7x7hk3pw4wkkq5zjkvq3371yl"; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./vendor/p12; }; authors = [ "hjiayz " "Marc-Antoine Perennou " @@ -6593,6 +6630,16 @@ rec { features = [ "std" ]; } ]; + devDependencies = [ + { + name = "hex"; + packageId = "hex"; + } + { + name = "hex-literal"; + packageId = "hex-literal"; + } + ]; }; "parking" = rec { diff --git a/Cargo.toml b/Cargo.toml index 7803ea7d..5e1ec74f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,8 @@ ldap3 = { version = "0.11", default-features = false, features = [ libc = "0.2" native-tls = "0.2" openssl = "0.10" -p12 = "0.6" +# p12 = "0.6" +p12 = { path = "vendor/p12" } pin-project = "1.1" pkg-config = "0.3" prost = "0.13" diff --git a/rust/operator-binary/src/format/convert.rs b/rust/operator-binary/src/format/convert.rs index e8fe9aef..7d78a4db 100644 --- a/rust/operator-binary/src/format/convert.rs +++ b/rust/operator-binary/src/format/convert.rs @@ -161,3 +161,26 @@ pub enum TlsToPkcs12Error { #[snafu(display("failed to encrypt data for truststore"))] EncryptDataForTruststore, } + +#[cfg(test)] +mod tests { + use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, x509::X509}; + + use crate::format::convert::pkcs12_truststore; + + #[test] + fn pkcs12_truststore_should_be_deterministic() -> anyhow::Result<()> { + let pkey = PKey::try_from(Rsa::generate(2048)?)?; + let mut x509 = X509::builder()?; + x509.set_pubkey(&pkey)?; + x509.set_version(3 - 1)?; + x509.sign(&pkey, MessageDigest::sha256())?; + let cert = x509.build(); + let password = ""; + assert_eq!( + pkcs12_truststore([cert.as_ref()], password)?, + pkcs12_truststore([cert.as_ref()], password)?, + ); + Ok(()) + } +} diff --git a/vendor/p12/.cargo-checksum.json b/vendor/p12/.cargo-checksum.json new file mode 100644 index 00000000..57f08cf5 --- /dev/null +++ b/vendor/p12/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"8578d9d4628978475b8337848325323f6eced4c91eca621c2a550c7a5fe7f397","LICENSE-APACHE":"ff5aa223e385fce1e6e4ea169f990363b6954fb210d137d254fd3cb3900275a2","LICENSE-MIT":"14639f2a38e774299fe9139c258340af35989cb055f6a2590086321247b7b087","README.md":"eea4ecbab73426b4308d9d92c10c18191788663003f27de80b6c186af726a25b","ca.der":"91a49a25787c70a4055bff6bd011de1692b747707fb32a0736d80976bcb39b91","clientcert.der":"3e4012979bf9dbc0aee810258fe310e80b92cef48ca78b1942fc5427fe57fb5b","clientkey.der":"4e57cbe4e53834a349e709f93bc0cd63243e66c911af6c2ca0e0e07d3e263f8f","src/lib.rs":"a9d7b100a80cd2a90445ae2239e35627b036c5f06b775b088fb22fe9712112cf"},"package":"d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224"} \ No newline at end of file diff --git a/vendor/p12/Cargo.toml b/vendor/p12/Cargo.toml new file mode 100644 index 00000000..327c0e1a --- /dev/null +++ b/vendor/p12/Cargo.toml @@ -0,0 +1,58 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.56.0" +name = "p12" +version = "0.6.3" +authors = ["hjiayz ", "Marc-Antoine Perennou "] +description = "pure rust pkcs12 tool" +homepage = "https://github.com/hjiayz/p12" +readme = "README.md" +keywords = ["pkcs12", "pkcs"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/hjiayz/p12" +resolver = "2" +[dependencies.cbc] +version = "^0.1" +features = ["block-padding"] + +[dependencies.cipher] +version = "^0.4.2" +features = ["alloc", "block-padding"] + +[dependencies.des] +version = "^0.8" + +[dependencies.getrandom] +version = "^0.2" + +[dependencies.hmac] +version = "^0.12" + +[dependencies.lazy_static] +version = "^1.4" + +[dependencies.rc2] +version = "^0.8" + +[dependencies.sha1] +version = "^0.10" + +[dependencies.yasna] +version = "^0.5" +features = ["std"] +[dev-dependencies.hex] +version = "^0.4.2" + +[dev-dependencies.hex-literal] +version = "^0.3.1" diff --git a/vendor/p12/LICENSE-APACHE b/vendor/p12/LICENSE-APACHE new file mode 100644 index 00000000..7eb53864 --- /dev/null +++ b/vendor/p12/LICENSE-APACHE @@ -0,0 +1,14 @@ +Copyright (c) 2021 Marc-Antoine Perennou +Copyright 2020 hjiayz + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/p12/LICENSE-MIT b/vendor/p12/LICENSE-MIT new file mode 100644 index 00000000..a424e020 --- /dev/null +++ b/vendor/p12/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021 Marc-Antoine Perennou +Copyright (c) 2020 hjiayz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN diff --git a/vendor/p12/README.md b/vendor/p12/README.md new file mode 100644 index 00000000..33539f88 --- /dev/null +++ b/vendor/p12/README.md @@ -0,0 +1,5 @@ +# p12 + +pure rust pkcs12 tool + +License: MIT OR Apache-2.0 diff --git a/vendor/p12/ca.der b/vendor/p12/ca.der new file mode 100644 index 0000000000000000000000000000000000000000..93a59375580431f0286a0a5573e71c3a0bb08b74 GIT binary patch literal 858 zcmXqLVh%HCVzOJn%*4pVBoeo7rn4w3Y~|?e6}u!(7gQmyJ`a&7?KQiQ{%xCO_M{6iX=m4IfglW$d}>a znRsMcN86DkrdPAf-&nPOWzUpaG(qb00+#T|1gkH_}E$ZfT zO#WLlu0Q!NEZ*{dkCo!a{reYWq%rJW^|IjFl*6-JYcKK7Db3%awC|_(ue+)`h#bnmBmxX&MusUjpNPKP-oA@(`KuTmMdvK8)z6oH zJz{<~?XTrRzmuG&oz`c*IJV+Y#m4J?o8Oe|`LwLf;AY9csjl^2+pB9eG!|VmJ+`WS z*P$gG)(7`)fBU#kX@;Fd$=1|m)jKT9GJ2kF|Ff_vF*aQz+xhA8$`99`uRUXLy3x&k z3bSONUHZc&z9$Uo6b|=DXyw z@zykjlL4}hi>F=rq-bE;t}S}NV)h=UO6~sD$iv zMpg#qCPsb+pg0#(6C)$T{uMVxoCWo#HO~D1Fi<$fJZQb%(IQ`!gI~nzRVpscKj3s( zZK1*K*5#MHrYW?%I&}5$1K)tXm+Eh9f379ermnxR!KLH#9kX`}nmndKNU@AWz9uNsU0FVzu>R$&URzjpcl->u8@w7cwz-c-absFXhz8RtADVBRgs z$kU;dUh#g5Y^a`~`NqU_1=o-{$?g-Mh3>kl?LVD*pwA!VKQJa-~$GuEI%XT ze-;*ICe{ZAvLHSmix`W@xl1w36${GmcFgfLEZDxEbGg$6J_C7>v@(l?fmnkG-~0cP z({kJVclTWKU9;tZ=awy>8x5pD3Ith%4FsDIQPa*+&%nTl967*r1&ka<26pAUC!TdK z^i!_*<@upx>w&*6wktQxJ?;8@-J$h`I-J5giVFFem5*KL@aA+?ek&yLJN;FF{qHmC zp&t+Y<@1Qz-J^cSOgJ)Y*89Yo%8pyPk9M_1NuIfS(ctyW&1Yt5f09m%?Tt8kdE%7^ zLCX2Qz7vWsi*{bNtE)|qea&cDdPBdZ@4jX6TOV%u64`r){jMbyX?8!nq-V6O?6%el z{#%{t`)igsJYB3_z3#7Ep}Fzir;k3mS}W8hl>FjM_E`K;u4tPw^V{~lKfead>a^Nq zu_$lPnX^CTS8T5KJa8sP`pBH(7hC1h|IhF$n%m4a^;+`zlgasx?-Z-|t~~H}=K46l H1deh5F_v6Q literal 0 HcmV?d00001 diff --git a/vendor/p12/clientkey.der b/vendor/p12/clientkey.der new file mode 100644 index 0000000000000000000000000000000000000000..d218b2e13ced5575153a33cb8ae987b26e3fb220 GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0Kcf&6hjd& zmVuf7;ZYN0H&U-I#d1v~!Soh?BzV%Fz(Ug|p)lKpr_xH6AcX3|)x+RTP`%QB*th2@ z5{4%)p@2k)^V~M>po8(27PLt!&jn??F&H>UH-%m$E}^SaDTVl)OV*jC8((_f6RkH1 zwmi1}cGExxIm48zOvv(T>#jye9&P+MUj&*#Sf1<78}%ro|D`MvTO-(Zv{eRZ z)4w}bt>*OPqE}jLRlPr9BX@r=7MI0_8wPEfCrp3f6Yd3??>Y?MvjPJF009Dm0RaH7 zY7BIp1&6kZo3=DGjyUw;dT^#m{S6&&7k~PblER;-3AUSMDkbCjc1C?L>EyKCch`%A zkh*SBvjZ@_6ZB>-4U7X7lsQKB7yMsux&>n77WL!s=v_yjjnF<5vKkR#F zA^%IK2X3AkXQ}dgT4&fwhcQ_%xu4w1W4+3iB6m~zJrp2hgERV76wGc~0Lj-Ow`V8O zZrqwxwIG5|c9x%-Dq8K-D*lMfll<3((-YimL7eGR;JYye6nhi<&F*&1Np!xMRC>qB zKDM&w?~i8wz4BH4@{Zpcb5bm-xAR+`VN}5~wz-yNQ!mPHBZ`>OpLAfwOsNIzoOg(m)2FIu3L(jw&3*P49xwiqTyw`n3T9fq?+a?H#GS;=#MCO2jOIE==IY04Iy{aiY@~=vF>6XCr zSq))}59=g1{5d9b1f|LG%(iLS4~fGSRSDzVO2<5!>b9cjk#yg<2+oigLU;e6Fi^7s zfq;0}eF5YA9(joXl1+(jR3Li_{TzNT@&juywBqsNV!knsL`Ewg*(^kQ;&AFQb~l5z zIU9z}dEbP6VHn*Jf}pyn0vd95QlRw-{;SD~ zojsi<)}nF@Tpswj@wgb?^v03zJaQfEEsh3(uoL<61$y=5YC%0ZD}#o-vvL80j4yvg zJ&NqE&jl`D0|^?=xl9D0%YHp2u&Dn#?N9zy3aL8#yiQ~e)&f2`S1HM!fvXMcA1%!Z zc>;lf0L-9+jO(-tBp6iz;QmX@p1_%GHY!Ka>?FnE+u|em&cA80AtN$}E`K*oQ?emip*dW&9un90c^I^AAf+d;kCd literal 0 HcmV?d00001 diff --git a/vendor/p12/src/lib.rs b/vendor/p12/src/lib.rs new file mode 100644 index 00000000..6b0c4b0e --- /dev/null +++ b/vendor/p12/src/lib.rs @@ -0,0 +1,1099 @@ +//! +//! pure rust pkcs12 tool +//! +//! + +use getrandom::getrandom; +use lazy_static::lazy_static; +use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; + +use hmac::{Hmac, Mac}; +use sha1::{Digest, Sha1}; + +type HmacSha1 = Hmac; + +fn as_oid(s: &'static [u64]) -> ObjectIdentifier { + ObjectIdentifier::from_slice(s) +} + +lazy_static! { + static ref OID_DATA_CONTENT_TYPE: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 7, 1]); + static ref OID_ENCRYPTED_DATA_CONTENT_TYPE: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 7, 6]); + static ref OID_FRIENDLY_NAME: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 20]); + static ref OID_LOCAL_KEY_ID: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 21]); + static ref OID_CERT_TYPE_X509_CERTIFICATE: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 1]); + static ref OID_CERT_TYPE_SDSI_CERTIFICATE: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 2]); + static ref OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 3]); + static ref OID_SHA1: ObjectIdentifier = as_oid(&[1, 3, 14, 3, 2, 26]); + static ref OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 6]); + static ref OID_KEY_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 1]); + static ref OID_PKCS8_SHROUDED_KEY_BAG: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 2]); + static ref OID_CERT_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 3]); + static ref OID_CRL_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 4]); + static ref OID_SECRET_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 5]); + static ref OID_SAFE_CONTENTS_BAG: ObjectIdentifier = + as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 6]); +} + +const ITERATIONS: u64 = 2048; + +fn sha1(bytes: &[u8]) -> Vec { + let mut hasher = Sha1::new(); + hasher.update(bytes); + hasher.finalize().to_vec() +} + +#[derive(Debug, Clone)] +pub struct EncryptedContentInfo { + pub content_encryption_algorithm: AlgorithmIdentifier, + pub encrypted_content: Vec, +} + +impl EncryptedContentInfo { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let content_type = r.next().read_oid()?; + debug_assert_eq!(content_type, *OID_DATA_CONTENT_TYPE); + let content_encryption_algorithm = AlgorithmIdentifier::parse(r.next())?; + let encrypted_content = r + .next() + .read_tagged_implicit(Tag::context(0), |r| r.read_bytes())?; + Ok(EncryptedContentInfo { + content_encryption_algorithm, + encrypted_content, + }) + }) + } + + pub fn data(&self, password: &[u8]) -> Option> { + self.content_encryption_algorithm + .decrypt_pbe(&self.encrypted_content, password) + } + + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_oid(&OID_DATA_CONTENT_TYPE); + self.content_encryption_algorithm.write(w.next()); + w.next() + .write_tagged_implicit(Tag::context(0), |w| w.write_bytes(&self.encrypted_content)); + }) + } + + pub fn to_der(&self) -> Vec { + yasna::construct_der(|w| self.write(w)) + } + + pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { + let data = yasna::construct_der(|w| { + w.write_sequence_of(|w| { + for sb in safe_bags { + sb.write(w.next()); + } + }) + }); + let salt = rand()?.to_vec(); + let encrypted_content = + pbe_with_sha1_and40_bit_rc2_cbc_encrypt(&data, password, &salt, ITERATIONS)?; + let content_encryption_algorithm = + AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams { + salt, + iterations: ITERATIONS, + }); + Some(EncryptedContentInfo { + content_encryption_algorithm, + encrypted_content, + }) + } +} + +#[derive(Debug, Clone)] +pub struct EncryptedData { + pub encrypted_content_info: EncryptedContentInfo, +} + +impl EncryptedData { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let version = r.next().read_u8()?; + debug_assert_eq!(version, 0); + + let encrypted_content_info = EncryptedContentInfo::parse(r.next())?; + Ok(EncryptedData { + encrypted_content_info, + }) + }) + } + pub fn data(&self, password: &[u8]) -> Option> { + self.encrypted_content_info.data(password) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_u8(0); + self.encrypted_content_info.write(w.next()); + }) + } + pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { + let encrypted_content_info = EncryptedContentInfo::from_safe_bags(safe_bags, password)?; + Some(EncryptedData { + encrypted_content_info, + }) + } +} + +#[derive(Debug, Clone)] +pub struct OtherContext { + pub content_type: ObjectIdentifier, + pub content: Vec, +} + +#[derive(Debug, Clone)] +pub enum ContentInfo { + Data(Vec), + EncryptedData(EncryptedData), + OtherContext(OtherContext), +} + +impl ContentInfo { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let content_type = r.next().read_oid()?; + if content_type == *OID_DATA_CONTENT_TYPE { + let data = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; + return Ok(ContentInfo::Data(data)); + } + if content_type == *OID_ENCRYPTED_DATA_CONTENT_TYPE { + let result = r.next().read_tagged(Tag::context(0), |r| { + Ok(ContentInfo::EncryptedData(EncryptedData::parse(r)?)) + }); + return result; + } + + let content = r.next().read_tagged(Tag::context(0), |r| r.read_der())?; + Ok(ContentInfo::OtherContext(OtherContext { + content_type, + content, + })) + }) + } + pub fn data(&self, password: &[u8]) -> Option> { + match self { + ContentInfo::Data(data) => Some(data.to_owned()), + ContentInfo::EncryptedData(encrypted) => encrypted.data(password), + ContentInfo::OtherContext(_) => None, + } + } + pub fn oid(&self) -> ObjectIdentifier { + match self { + ContentInfo::Data(_) => OID_DATA_CONTENT_TYPE.clone(), + ContentInfo::EncryptedData(_) => OID_ENCRYPTED_DATA_CONTENT_TYPE.clone(), + ContentInfo::OtherContext(other) => other.content_type.clone(), + } + } + pub fn write(&self, w: DERWriter) { + match self { + ContentInfo::Data(data) => w.write_sequence(|w| { + w.next().write_oid(&OID_DATA_CONTENT_TYPE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_bytes(data)) + }), + ContentInfo::EncryptedData(encrypted_data) => w.write_sequence(|w| { + w.next().write_oid(&OID_ENCRYPTED_DATA_CONTENT_TYPE); + w.next() + .write_tagged(Tag::context(0), |w| encrypted_data.write(w)) + }), + ContentInfo::OtherContext(other) => w.write_sequence(|w| { + w.next().write_oid(&other.content_type); + w.next() + .write_tagged(Tag::context(0), |w| w.write_der(&other.content)) + }), + } + } + pub fn to_der(&self) -> Vec { + yasna::construct_der(|w| self.write(w)) + } + + pub fn from_der(der: &[u8]) -> Result { + yasna::parse_der(der, Self::parse) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Pkcs12PbeParams { + pub salt: Vec, + pub iterations: u64, +} + +impl Pkcs12PbeParams { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let salt = r.next().read_bytes()?; + let iterations = r.next().read_u64()?; + Ok(Pkcs12PbeParams { salt, iterations }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_bytes(&self.salt); + w.next().write_u64(self.iterations); + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct OtherAlgorithmIdentifier { + pub algorithm_type: ObjectIdentifier, + pub params: Option>, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum AlgorithmIdentifier { + Sha1, + PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams), + PbeWithSHAAnd3KeyTripleDESCBC(Pkcs12PbeParams), + OtherAlg(OtherAlgorithmIdentifier), +} + +impl AlgorithmIdentifier { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let algorithm_type = r.next().read_oid()?; + if algorithm_type == *OID_SHA1 { + r.read_optional(|r| r.read_null())?; + return Ok(AlgorithmIdentifier::Sha1); + } + if algorithm_type == *OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC { + let params = Pkcs12PbeParams::parse(r.next())?; + return Ok(AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(params)); + } + if algorithm_type == *OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC { + let params = Pkcs12PbeParams::parse(r.next())?; + return Ok(AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(params)); + } + let params = r.read_optional(|r| r.read_der())?; + Ok(AlgorithmIdentifier::OtherAlg(OtherAlgorithmIdentifier { + algorithm_type, + params, + })) + }) + } + pub fn decrypt_pbe(&self, ciphertext: &[u8], password: &[u8]) -> Option> { + match self { + AlgorithmIdentifier::Sha1 => None, + AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(param) => { + pbe_with_sha1_and40_bit_rc2_cbc(ciphertext, password, ¶m.salt, param.iterations) + } + AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param) => { + pbe_with_sha_and3_key_triple_des_cbc( + ciphertext, + password, + ¶m.salt, + param.iterations, + ) + } + AlgorithmIdentifier::OtherAlg(_) => None, + } + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| match self { + AlgorithmIdentifier::Sha1 => { + w.next().write_oid(&OID_SHA1); + w.next().write_null(); + } + AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(p) => { + w.next().write_oid(&OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC); + p.write(w.next()); + } + AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(p) => { + w.next().write_oid(&OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC); + p.write(w.next()); + } + AlgorithmIdentifier::OtherAlg(other) => { + w.next().write_oid(&other.algorithm_type); + if let Some(der) = &other.params { + w.next().write_der(der); + } + } + }) + } +} + +#[derive(Debug)] +pub struct DigestInfo { + pub digest_algorithm: AlgorithmIdentifier, + pub digest: Vec, +} + +impl DigestInfo { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let digest_algorithm = AlgorithmIdentifier::parse(r.next())?; + let digest = r.next().read_bytes()?; + Ok(DigestInfo { + digest_algorithm, + digest, + }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + self.digest_algorithm.write(w.next()); + w.next().write_bytes(&self.digest); + }) + } +} + +#[derive(Debug)] +pub struct MacData { + pub mac: DigestInfo, + pub salt: Vec, + pub iterations: u32, +} + +impl MacData { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let mac = DigestInfo::parse(r.next())?; + let salt = r.next().read_bytes()?; + let iterations = r.next().read_u32()?; + Ok(MacData { + mac, + salt, + iterations, + }) + }) + } + + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + self.mac.write(w.next()); + w.next().write_bytes(&self.salt); + w.next().write_u32(self.iterations); + }) + } + + pub fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { + debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); + let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); + let mut mac = HmacSha1::new_from_slice(&key).unwrap(); + mac.update(data); + mac.verify_slice(&self.mac.digest).is_ok() + } + + pub fn new(data: &[u8], password: &[u8]) -> MacData { + let salt = rand().unwrap(); + let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); + let mut mac = HmacSha1::new_from_slice(&key).unwrap(); + mac.update(data); + let digest = mac.finalize().into_bytes().to_vec(); + MacData { + mac: DigestInfo { + digest_algorithm: AlgorithmIdentifier::Sha1, + digest, + }, + salt: salt.to_vec(), + iterations: ITERATIONS as u32, + } + } +} + +fn rand() -> Option<[u8; 8]> { + let mut buf = [0u8; 8]; + // HACK: use null salt for determinism since we don't care about pkcs#12 encryption anyway + return Some(buf); + if getrandom(&mut buf).is_ok() { + Some(buf) + } else { + None + } +} + +#[derive(Debug)] +pub struct PFX { + pub version: u8, + pub auth_safe: ContentInfo, + pub mac_data: Option, +} + +impl PFX { + pub fn new( + cert_der: &[u8], + key_der: &[u8], + ca_der: Option<&[u8]>, + password: &str, + name: &str, + ) -> Option { + let mut cas = vec![]; + if let Some(ca) = ca_der { + cas.push(ca); + } + Self::new_with_cas(cert_der, key_der, &cas, password, name) + } + pub fn new_with_cas( + cert_der: &[u8], + key_der: &[u8], + ca_der_list: &[&[u8]], + password: &str, + name: &str, + ) -> Option { + let password = bmp_string(password); + let salt = rand()?.to_vec(); + let encrypted_data = + pbe_with_sha_and3_key_triple_des_cbc_encrypt(key_der, &password, &salt, ITERATIONS)?; + let param = Pkcs12PbeParams { + salt, + iterations: ITERATIONS, + }; + let key_bag_inner = SafeBagKind::Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo { + encryption_algorithm: AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param), + encrypted_data, + }); + let friendly_name = PKCS12Attribute::FriendlyName(name.to_owned()); + let local_key_id = PKCS12Attribute::LocalKeyId(sha1(cert_der)); + let key_bag = SafeBag { + bag: key_bag_inner, + attributes: vec![friendly_name.clone(), local_key_id.clone()], + }; + let cert_bag_inner = SafeBagKind::CertBag(CertBag::X509(cert_der.to_owned())); + let cert_bag = SafeBag { + bag: cert_bag_inner, + attributes: vec![friendly_name, local_key_id], + }; + let mut cert_bags = vec![cert_bag]; + for ca in ca_der_list { + cert_bags.push(SafeBag { + bag: SafeBagKind::CertBag(CertBag::X509((*ca).to_owned())), + attributes: vec![], + }); + } + let contents = yasna::construct_der(|w| { + w.write_sequence_of(|w| { + ContentInfo::EncryptedData( + EncryptedData::from_safe_bags(&cert_bags, &password) + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid)) + .unwrap(), + ) + .write(w.next()); + ContentInfo::Data(yasna::construct_der(|w| { + w.write_sequence_of(|w| { + key_bag.write(w.next()); + }) + })) + .write(w.next()); + }); + }); + let mac_data = MacData::new(&contents, &password); + Some(PFX { + version: 3, + auth_safe: ContentInfo::Data(contents), + mac_data: Some(mac_data), + }) + } + + pub fn parse(bytes: &[u8]) -> Result { + yasna::parse_der(bytes, |r| { + r.read_sequence(|r| { + let version = r.next().read_u8()?; + let auth_safe = ContentInfo::parse(r.next())?; + let mac_data = r.read_optional(MacData::parse)?; + Ok(PFX { + version, + auth_safe, + mac_data, + }) + }) + }) + } + + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_u8(self.version); + self.auth_safe.write(w.next()); + if let Some(mac_data) = &self.mac_data { + mac_data.write(w.next()) + } + }) + } + + pub fn to_der(&self) -> Vec { + yasna::construct_der(|w| self.write(w)) + } + pub fn bags(&self, password: &str) -> Result, ASN1Error> { + let password = bmp_string(password); + + let data = self + .auth_safe + .data(&password) + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + + let contents = yasna::parse_der(&data, |r| r.collect_sequence_of(ContentInfo::parse))?; + + let mut result = vec![]; + for content in contents.iter() { + let data = content + .data(&password) + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + + let safe_bags = yasna::parse_der(&data, |r| r.collect_sequence_of(SafeBag::parse))?; + + for safe_bag in safe_bags.iter() { + result.push(safe_bag.to_owned()) + } + } + Ok(result) + } + //DER-encoded X.509 certificate + pub fn cert_bags(&self, password: &str) -> Result>, ASN1Error> { + self.cert_x509_bags(password) + } + //DER-encoded X.509 certificate + pub fn cert_x509_bags(&self, password: &str) -> Result>, ASN1Error> { + let mut result = vec![]; + for safe_bag in self.bags(password)? { + if let Some(cert) = safe_bag.bag.get_x509_cert() { + result.push(cert); + } + } + Ok(result) + } + pub fn cert_sdsi_bags(&self, password: &str) -> Result, ASN1Error> { + let mut result = vec![]; + for safe_bag in self.bags(password)? { + if let Some(cert) = safe_bag.bag.get_sdsi_cert() { + result.push(cert); + } + } + Ok(result) + } + pub fn key_bags(&self, password: &str) -> Result>, ASN1Error> { + let bmp_password = bmp_string(password); + let mut result = vec![]; + for safe_bag in self.bags(password)? { + if let Some(key) = safe_bag.bag.get_key(&bmp_password) { + result.push(key); + } + } + Ok(result) + } + + pub fn verify_mac(&self, password: &str) -> bool { + let bmp_password = bmp_string(password); + if let Some(mac_data) = &self.mac_data { + return match self.auth_safe.data(&bmp_password) { + Some(data) => mac_data.verify_mac(&data, &bmp_password), + None => false, + }; + } + true + } +} + +#[inline(always)] +fn pbepkcs12sha1core(d: &[u8], i: &[u8], a: &mut Vec, iterations: u64) -> Vec { + let mut ai: Vec = d.iter().chain(i.iter()).cloned().collect(); + for _ in 0..iterations { + ai = sha1(&ai); + } + a.append(&mut ai.clone()); + ai +} + +#[allow(clippy::many_single_char_names)] +fn pbepkcs12sha1(pass: &[u8], salt: &[u8], iterations: u64, id: u8, size: u64) -> Vec { + const U: u64 = 160 / 8; + const V: u64 = 512 / 8; + let r: u64 = iterations; + let d = [id; V as usize]; + fn get_len(s: usize) -> usize { + let s = s as u64; + (V * ((s + V - 1) / V)) as usize + } + let s = salt.iter().cycle().take(get_len(salt.len())); + let p = pass.iter().cycle().take(get_len(pass.len())); + let mut i: Vec = s.chain(p).cloned().collect(); + let c = (size + U - 1) / U; + let mut a: Vec = vec![]; + for _ in 1..c { + let ai = pbepkcs12sha1core(&d, &i, &mut a, r); + + let b: Vec = ai.iter().cycle().take(V as usize).cloned().collect(); + + let b_iter = b.iter().rev().cycle().take(i.len()); + let i_b_iter = i.iter_mut().rev().zip(b_iter); + let mut inc = 1u8; + for (i3, (ii, bi)) in i_b_iter.enumerate() { + if ((i3 as u64) % V) == 0 { + inc = 1; + } + let (ii2, inc2) = ii.overflowing_add(*bi); + let (ii3, inc3) = ii2.overflowing_add(inc); + inc = (inc2 || inc3) as u8; + *ii = ii3; + } + } + + pbepkcs12sha1core(&d, &i, &mut a, r); + + a.iter().take(size as usize).cloned().collect() +} + +fn pbe_with_sha1_and40_bit_rc2_cbc( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use cbc::{ + cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}, + Decryptor, + }; + use rc2::Rc2; + type Rc2Cbc = Decryptor; + + let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); + + let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; + rc2.decrypt_padded_vec_mut::(data).ok() +} + +fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use cbc::{ + cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}, + Encryptor, + }; + use rc2::Rc2; + type Rc2Cbc = Encryptor; + + let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); + + let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; + Some(rc2.encrypt_padded_vec_mut::(data)) +} + +fn pbe_with_sha_and3_key_triple_des_cbc( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use cbc::{ + cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}, + Decryptor, + }; + use des::TdesEde3; + type TDesCbc = Decryptor; + + let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); + + let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; + tdes.decrypt_padded_vec_mut::(data).ok() +} + +fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( + data: &[u8], + password: &[u8], + salt: &[u8], + iterations: u64, +) -> Option> { + use cbc::{ + cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}, + Encryptor, + }; + use des::TdesEde3; + type TDesCbc = Encryptor; + + let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); + let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); + + let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; + Some(tdes.encrypt_padded_vec_mut::(data)) +} + +fn bmp_string(s: &str) -> Vec { + let utf16: Vec = s.encode_utf16().collect(); + + let mut bytes = Vec::with_capacity(utf16.len() * 2 + 2); + for c in utf16 { + bytes.push((c / 256) as u8); + bytes.push((c % 256) as u8); + } + bytes.push(0x00); + bytes.push(0x00); + bytes +} + +#[derive(Debug, Clone)] +pub enum CertBag { + X509(Vec), + SDSI(String), +} + +impl CertBag { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + if oid == *OID_CERT_TYPE_X509_CERTIFICATE { + let x509 = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; + return Ok(CertBag::X509(x509)); + }; + if oid == *OID_CERT_TYPE_SDSI_CERTIFICATE { + let sdsi = r + .next() + .read_tagged(Tag::context(0), |r| r.read_ia5_string())?; + return Ok(CertBag::SDSI(sdsi)); + } + Err(ASN1Error::new(ASN1ErrorKind::Invalid)) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| match self { + CertBag::X509(x509) => { + w.next().write_oid(&OID_CERT_TYPE_X509_CERTIFICATE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_bytes(x509)); + } + CertBag::SDSI(sdsi) => { + w.next().write_oid(&OID_CERT_TYPE_SDSI_CERTIFICATE); + w.next() + .write_tagged(Tag::context(0), |w| w.write_ia5_string(sdsi)); + } + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct EncryptedPrivateKeyInfo { + pub encryption_algorithm: AlgorithmIdentifier, + pub encrypted_data: Vec, +} + +impl EncryptedPrivateKeyInfo { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let encryption_algorithm = AlgorithmIdentifier::parse(r.next())?; + + let encrypted_data = r.next().read_bytes()?; + + Ok(EncryptedPrivateKeyInfo { + encryption_algorithm, + encrypted_data, + }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + self.encryption_algorithm.write(w.next()); + w.next().write_bytes(&self.encrypted_data); + }) + } + pub fn decrypt(&self, password: &[u8]) -> Option> { + self.encryption_algorithm + .decrypt_pbe(&self.encrypted_data, password) + } +} + +#[test] +fn test_encrypted_private_key_info() { + let epki = EncryptedPrivateKeyInfo { + encryption_algorithm: AlgorithmIdentifier::Sha1, + encrypted_data: b"foo".to_vec(), + }; + let der = yasna::construct_der(|w| { + epki.write(w); + }); + let epki2 = yasna::parse_ber(&der, EncryptedPrivateKeyInfo::parse).unwrap(); + assert_eq!(epki2, epki); +} + +#[derive(Debug, Clone)] +pub struct OtherBag { + pub bag_id: ObjectIdentifier, + pub bag_value: Vec, +} + +#[derive(Debug, Clone)] +pub enum SafeBagKind { + //KeyBag(), + Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo), + CertBag(CertBag), + //CRLBag(), + //SecretBag(), + //SafeContents(Vec), + OtherBagKind(OtherBag), +} + +impl SafeBagKind { + pub fn parse(r: BERReader, bag_id: ObjectIdentifier) -> Result { + if bag_id == *OID_CERT_BAG { + return Ok(SafeBagKind::CertBag(CertBag::parse(r)?)); + } + if bag_id == *OID_PKCS8_SHROUDED_KEY_BAG { + return Ok(SafeBagKind::Pkcs8ShroudedKeyBag( + EncryptedPrivateKeyInfo::parse(r)?, + )); + } + let bag_value = r.read_der()?; + Ok(SafeBagKind::OtherBagKind(OtherBag { bag_id, bag_value })) + } + pub fn write(&self, w: DERWriter) { + match self { + SafeBagKind::Pkcs8ShroudedKeyBag(epk) => epk.write(w), + SafeBagKind::CertBag(cb) => cb.write(w), + SafeBagKind::OtherBagKind(other) => w.write_der(&other.bag_value), + } + } + pub fn oid(&self) -> ObjectIdentifier { + match self { + SafeBagKind::Pkcs8ShroudedKeyBag(_) => OID_PKCS8_SHROUDED_KEY_BAG.clone(), + SafeBagKind::CertBag(_) => OID_CERT_BAG.clone(), + SafeBagKind::OtherBagKind(other) => other.bag_id.clone(), + } + } + pub fn get_x509_cert(&self) -> Option> { + if let SafeBagKind::CertBag(CertBag::X509(x509)) = self { + return Some(x509.to_owned()); + } + None + } + + pub fn get_sdsi_cert(&self) -> Option { + if let SafeBagKind::CertBag(CertBag::SDSI(sdsi)) = self { + return Some(sdsi.to_owned()); + } + None + } + + pub fn get_key(&self, password: &[u8]) -> Option> { + if let SafeBagKind::Pkcs8ShroudedKeyBag(kb) = self { + return kb.decrypt(password); + } + None + } +} + +#[derive(Debug, Clone)] +pub struct OtherAttribute { + pub oid: ObjectIdentifier, + pub data: Vec>, +} + +#[derive(Debug, Clone)] +pub enum PKCS12Attribute { + FriendlyName(String), + LocalKeyId(Vec), + Other(OtherAttribute), +} + +impl PKCS12Attribute { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + if oid == *OID_FRIENDLY_NAME { + let name = r + .next() + .collect_set_of(|s| s.read_bmp_string())? + .pop() + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + return Ok(PKCS12Attribute::FriendlyName(name)); + } + if oid == *OID_LOCAL_KEY_ID { + let local_key_id = r + .next() + .collect_set_of(|s| s.read_bytes())? + .pop() + .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; + return Ok(PKCS12Attribute::LocalKeyId(local_key_id)); + } + + let data = r.next().collect_set_of(|s| s.read_der())?; + let other = OtherAttribute { oid, data }; + Ok(PKCS12Attribute::Other(other)) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| match self { + PKCS12Attribute::FriendlyName(name) => { + w.next().write_oid(&OID_FRIENDLY_NAME); + w.next().write_set_of(|w| { + w.next().write_bmp_string(name); + }) + } + PKCS12Attribute::LocalKeyId(id) => { + w.next().write_oid(&OID_LOCAL_KEY_ID); + w.next().write_set_of(|w| w.next().write_bytes(id)) + } + PKCS12Attribute::Other(other) => { + w.next().write_oid(&other.oid); + w.next().write_set_of(|w| { + for bytes in other.data.iter() { + w.next().write_der(bytes); + } + }) + } + }) + } +} +#[derive(Debug, Clone)] +pub struct SafeBag { + pub bag: SafeBagKind, + pub attributes: Vec, +} + +impl SafeBag { + pub fn parse(r: BERReader) -> Result { + r.read_sequence(|r| { + let oid = r.next().read_oid()?; + + let bag = r + .next() + .read_tagged(Tag::context(0), |r| SafeBagKind::parse(r, oid))?; + + let attributes = r + .read_optional(|r| r.collect_set_of(PKCS12Attribute::parse))? + .unwrap_or_else(Vec::new); + + Ok(SafeBag { bag, attributes }) + }) + } + pub fn write(&self, w: DERWriter) { + w.write_sequence(|w| { + w.next().write_oid(&self.bag.oid()); + w.next() + .write_tagged(Tag::context(0), |w| self.bag.write(w)); + if !self.attributes.is_empty() { + w.next().write_set_of(|w| { + for attr in &self.attributes { + attr.write(w.next()); + } + }) + } + }) + } + pub fn friendly_name(&self) -> Option { + for attr in self.attributes.iter() { + if let PKCS12Attribute::FriendlyName(name) = attr { + return Some(name.to_owned()); + } + } + None + } + pub fn local_key_id(&self) -> Option> { + for attr in self.attributes.iter() { + if let PKCS12Attribute::LocalKeyId(id) = attr { + return Some(id.to_owned()); + } + } + None + } +} + +#[test] +fn test_create_p12() { + use std::{ + fs::File, + io::{Read, Write}, + }; + let mut cafile = File::open("ca.der").unwrap(); + let mut ca = vec![]; + cafile.read_to_end(&mut ca).unwrap(); + let mut fcert = File::open("clientcert.der").unwrap(); + let mut fkey = File::open("clientkey.der").unwrap(); + let mut cert = vec![]; + fcert.read_to_end(&mut cert).unwrap(); + let mut key = vec![]; + fkey.read_to_end(&mut key).unwrap(); + let p12 = PFX::new(&cert, &key, Some(&ca), "changeit", "look") + .unwrap() + .to_der(); + + let pfx = PFX::parse(&p12).unwrap(); + + let keys = pfx.key_bags("changeit").unwrap(); + assert_eq!(keys[0], key); + + let certs = pfx.cert_x509_bags("changeit").unwrap(); + assert_eq!(certs[0], cert); + assert_eq!(certs[1], ca); + assert!(pfx.verify_mac("changeit")); + + let mut fp12 = File::create("test.p12").unwrap(); + fp12.write_all(&p12).unwrap(); +} +#[test] +fn test_create_p12_without_password() { + use std::{ + fs::File, + io::{Read, Write}, + }; + let mut cafile = File::open("ca.der").unwrap(); + let mut ca = vec![]; + cafile.read_to_end(&mut ca).unwrap(); + let mut fcert = File::open("clientcert.der").unwrap(); + + let mut cert = vec![]; + fcert.read_to_end(&mut cert).unwrap(); + + let p12 = PFX::new(&cert, &[], Some(&ca), "", "look") + .unwrap() + .to_der(); + + let pfx = PFX::parse(&p12).unwrap(); + + let certs = pfx.cert_x509_bags("").unwrap(); + assert_eq!(certs[0], cert); + assert_eq!(certs[1], ca); + assert!(pfx.verify_mac("")); + + let mut fp12 = File::create("test.p12").unwrap(); + fp12.write_all(&p12).unwrap(); +} + +#[test] +fn test_bmp_string() { + let value = bmp_string("Beavis"); + assert!( + value + == [0x00, 0x42, 0x00, 0x65, 0x00, 0x61, 0x00, 0x76, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00] + ) +} + +#[test] +fn test_pbepkcs12sha1() { + use hex_literal::hex; + let pass = bmp_string(""); + assert_eq!(pass, vec![0, 0]); + let salt = hex!("9af4702958a8e95c"); + let iterations = 2048; + let id = 1; + let size = 24; + let result = pbepkcs12sha1(&pass, &salt, iterations, id, size); + let res = hex!("c2294aa6d02930eb5ce9c329eccb9aee1cb136baea746557"); + assert_eq!(result, res); +} + +#[test] +fn test_pbepkcs12sha1_2() { + use hex_literal::hex; + let pass = bmp_string(""); + assert_eq!(pass, vec![0, 0]); + let salt = hex!("9af4702958a8e95c"); + let iterations = 2048; + let id = 2; + let size = 8; + let result = pbepkcs12sha1(&pass, &salt, iterations, id, size); + let res = hex!("8e9f8fc7664378bc"); + assert_eq!(result, res); +} From 75bb19fecb4cc4d1398d4d1a5a11b87c66f6951b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 13 Mar 2025 14:22:32 +0100 Subject: [PATCH 63/87] Move p12 into subtree folder --- Cargo.nix | 2 +- Cargo.toml | 2 - rust/operator-binary/Cargo.toml | 2 +- rust/p12/Cargo.toml | 2 + rust/p12/README.md | 2 + rust/p12/src/lib.rs | 14 +- vendor/p12/.cargo-checksum.json | 1 - vendor/p12/Cargo.toml | 58 -- vendor/p12/LICENSE-APACHE | 14 - vendor/p12/LICENSE-MIT | 19 - vendor/p12/README.md | 5 - vendor/p12/ca.der | Bin 858 -> 0 bytes vendor/p12/clientcert.der | Bin 911 -> 0 bytes vendor/p12/clientkey.der | Bin 1218 -> 0 bytes vendor/p12/src/lib.rs | 1099 ------------------------------- 15 files changed, 16 insertions(+), 1204 deletions(-) delete mode 100644 vendor/p12/.cargo-checksum.json delete mode 100644 vendor/p12/Cargo.toml delete mode 100644 vendor/p12/LICENSE-APACHE delete mode 100644 vendor/p12/LICENSE-MIT delete mode 100644 vendor/p12/README.md delete mode 100644 vendor/p12/ca.der delete mode 100644 vendor/p12/clientcert.der delete mode 100644 vendor/p12/clientkey.der delete mode 100644 vendor/p12/src/lib.rs diff --git a/Cargo.nix b/Cargo.nix index e36977cd..c4b31d0c 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -6584,7 +6584,7 @@ rec { crateName = "p12"; version = "0.6.3"; edition = "2021"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ./vendor/p12; }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/p12; }; authors = [ "hjiayz " "Marc-Antoine Perennou " diff --git a/Cargo.toml b/Cargo.toml index 5e1ec74f..4319a754 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,6 @@ ldap3 = { version = "0.11", default-features = false, features = [ libc = "0.2" native-tls = "0.2" openssl = "0.10" -# p12 = "0.6" -p12 = { path = "vendor/p12" } pin-project = "1.1" pkg-config = "0.3" prost = "0.13" diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 161a37b2..6549daf4 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -11,6 +11,7 @@ publish = false [dependencies] stackable-krb5-provision-keytab = { path = "../krb5-provision-keytab" } stackable-secret-operator-crd-utils = { path = "../crd-utils" } +p12 = { path = "../p12" } anyhow.workspace = true async-trait.workspace = true @@ -20,7 +21,6 @@ h2.workspace = true kube-runtime.workspace = true libc.workspace = true openssl.workspace = true -p12.workspace = true pin-project.workspace = true prost-types.workspace = true prost.workspace = true diff --git a/rust/p12/Cargo.toml b/rust/p12/Cargo.toml index 0a972fdc..ac7936d6 100644 --- a/rust/p12/Cargo.toml +++ b/rust/p12/Cargo.toml @@ -11,6 +11,8 @@ readme = "README.md" license = "MIT OR Apache-2.0" rust-version = "1.56.0" +# Dependencies are tracked inline for now, to minimize divergence from upstream + [dependencies] des = "^0.8" getrandom = "^0.2" diff --git a/rust/p12/README.md b/rust/p12/README.md index 33539f88..eac01c4c 100644 --- a/rust/p12/README.md +++ b/rust/p12/README.md @@ -1,5 +1,7 @@ # p12 +Forked from https://github.com/hjiayz/p12/ + pure rust pkcs12 tool License: MIT OR Apache-2.0 diff --git a/rust/p12/src/lib.rs b/rust/p12/src/lib.rs index 7ad065bc..6b0c4b0e 100644 --- a/rust/p12/src/lib.rs +++ b/rust/p12/src/lib.rs @@ -404,6 +404,8 @@ impl MacData { fn rand() -> Option<[u8; 8]> { let mut buf = [0u8; 8]; + // HACK: use null salt for determinism since we don't care about pkcs#12 encryption anyway + return Some(buf); if getrandom(&mut buf).is_ok() { Some(buf) } else { @@ -1000,8 +1002,10 @@ impl SafeBag { #[test] fn test_create_p12() { - use std::fs::File; - use std::io::{Read, Write}; + use std::{ + fs::File, + io::{Read, Write}, + }; let mut cafile = File::open("ca.der").unwrap(); let mut ca = vec![]; cafile.read_to_end(&mut ca).unwrap(); @@ -1030,8 +1034,10 @@ fn test_create_p12() { } #[test] fn test_create_p12_without_password() { - use std::fs::File; - use std::io::{Read, Write}; + use std::{ + fs::File, + io::{Read, Write}, + }; let mut cafile = File::open("ca.der").unwrap(); let mut ca = vec![]; cafile.read_to_end(&mut ca).unwrap(); diff --git a/vendor/p12/.cargo-checksum.json b/vendor/p12/.cargo-checksum.json deleted file mode 100644 index 57f08cf5..00000000 --- a/vendor/p12/.cargo-checksum.json +++ /dev/null @@ -1 +0,0 @@ -{"files":{"Cargo.toml":"8578d9d4628978475b8337848325323f6eced4c91eca621c2a550c7a5fe7f397","LICENSE-APACHE":"ff5aa223e385fce1e6e4ea169f990363b6954fb210d137d254fd3cb3900275a2","LICENSE-MIT":"14639f2a38e774299fe9139c258340af35989cb055f6a2590086321247b7b087","README.md":"eea4ecbab73426b4308d9d92c10c18191788663003f27de80b6c186af726a25b","ca.der":"91a49a25787c70a4055bff6bd011de1692b747707fb32a0736d80976bcb39b91","clientcert.der":"3e4012979bf9dbc0aee810258fe310e80b92cef48ca78b1942fc5427fe57fb5b","clientkey.der":"4e57cbe4e53834a349e709f93bc0cd63243e66c911af6c2ca0e0e07d3e263f8f","src/lib.rs":"a9d7b100a80cd2a90445ae2239e35627b036c5f06b775b088fb22fe9712112cf"},"package":"d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224"} \ No newline at end of file diff --git a/vendor/p12/Cargo.toml b/vendor/p12/Cargo.toml deleted file mode 100644 index 327c0e1a..00000000 --- a/vendor/p12/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -rust-version = "1.56.0" -name = "p12" -version = "0.6.3" -authors = ["hjiayz ", "Marc-Antoine Perennou "] -description = "pure rust pkcs12 tool" -homepage = "https://github.com/hjiayz/p12" -readme = "README.md" -keywords = ["pkcs12", "pkcs"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/hjiayz/p12" -resolver = "2" -[dependencies.cbc] -version = "^0.1" -features = ["block-padding"] - -[dependencies.cipher] -version = "^0.4.2" -features = ["alloc", "block-padding"] - -[dependencies.des] -version = "^0.8" - -[dependencies.getrandom] -version = "^0.2" - -[dependencies.hmac] -version = "^0.12" - -[dependencies.lazy_static] -version = "^1.4" - -[dependencies.rc2] -version = "^0.8" - -[dependencies.sha1] -version = "^0.10" - -[dependencies.yasna] -version = "^0.5" -features = ["std"] -[dev-dependencies.hex] -version = "^0.4.2" - -[dev-dependencies.hex-literal] -version = "^0.3.1" diff --git a/vendor/p12/LICENSE-APACHE b/vendor/p12/LICENSE-APACHE deleted file mode 100644 index 7eb53864..00000000 --- a/vendor/p12/LICENSE-APACHE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) 2021 Marc-Antoine Perennou -Copyright 2020 hjiayz - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/vendor/p12/LICENSE-MIT b/vendor/p12/LICENSE-MIT deleted file mode 100644 index a424e020..00000000 --- a/vendor/p12/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2021 Marc-Antoine Perennou -Copyright (c) 2020 hjiayz - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN diff --git a/vendor/p12/README.md b/vendor/p12/README.md deleted file mode 100644 index 33539f88..00000000 --- a/vendor/p12/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# p12 - -pure rust pkcs12 tool - -License: MIT OR Apache-2.0 diff --git a/vendor/p12/ca.der b/vendor/p12/ca.der deleted file mode 100644 index 93a59375580431f0286a0a5573e71c3a0bb08b74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 858 zcmXqLVh%HCVzOJn%*4pVBoeo7rn4w3Y~|?e6}u!(7gQmyJ`a&7?KQiQ{%xCO_M{6iX=m4IfglW$d}>a znRsMcN86DkrdPAf-&nPOWzUpaG(qb00+#T|1gkH_}E$ZfT zO#WLlu0Q!NEZ*{dkCo!a{reYWq%rJW^|IjFl*6-JYcKK7Db3%awC|_(ue+)`h#bnmBmxX&MusUjpNPKP-oA@(`KuTmMdvK8)z6oH zJz{<~?XTrRzmuG&oz`c*IJV+Y#m4J?o8Oe|`LwLf;AY9csjl^2+pB9eG!|VmJ+`WS z*P$gG)(7`)fBU#kX@;Fd$=1|m)jKT9GJ2kF|Ff_vF*aQz+xhA8$`99`uRUXLy3x&k z3bSONUHZc&z9$Uo6b|=DXyw z@zykjlL4}hi>F=rq-bE;t}S}NV)h=UO6~sD$iv zMpg#qCPsb+pg0#(6C)$T{uMVxoCWo#HO~D1Fi<$fJZQb%(IQ`!gI~nzRVpscKj3s( zZK1*K*5#MHrYW?%I&}5$1K)tXm+Eh9f379ermnxR!KLH#9kX`}nmndKNU@AWz9uNsU0FVzu>R$&URzjpcl->u8@w7cwz-c-absFXhz8RtADVBRgs z$kU;dUh#g5Y^a`~`NqU_1=o-{$?g-Mh3>kl?LVD*pwA!VKQJa-~$GuEI%XT ze-;*ICe{ZAvLHSmix`W@xl1w36${GmcFgfLEZDxEbGg$6J_C7>v@(l?fmnkG-~0cP z({kJVclTWKU9;tZ=awy>8x5pD3Ith%4FsDIQPa*+&%nTl967*r1&ka<26pAUC!TdK z^i!_*<@upx>w&*6wktQxJ?;8@-J$h`I-J5giVFFem5*KL@aA+?ek&yLJN;FF{qHmC zp&t+Y<@1Qz-J^cSOgJ)Y*89Yo%8pyPk9M_1NuIfS(ctyW&1Yt5f09m%?Tt8kdE%7^ zLCX2Qz7vWsi*{bNtE)|qea&cDdPBdZ@4jX6TOV%u64`r){jMbyX?8!nq-V6O?6%el z{#%{t`)igsJYB3_z3#7Ep}Fzir;k3mS}W8hl>FjM_E`K;u4tPw^V{~lKfead>a^Nq zu_$lPnX^CTS8T5KJa8sP`pBH(7hC1h|IhF$n%m4a^;+`zlgasx?-Z-|t~~H}=K46l H1deh5F_v6Q diff --git a/vendor/p12/clientkey.der b/vendor/p12/clientkey.der deleted file mode 100644 index d218b2e13ced5575153a33cb8ae987b26e3fb220..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0Kcf&6hjd& zmVuf7;ZYN0H&U-I#d1v~!Soh?BzV%Fz(Ug|p)lKpr_xH6AcX3|)x+RTP`%QB*th2@ z5{4%)p@2k)^V~M>po8(27PLt!&jn??F&H>UH-%m$E}^SaDTVl)OV*jC8((_f6RkH1 zwmi1}cGExxIm48zOvv(T>#jye9&P+MUj&*#Sf1<78}%ro|D`MvTO-(Zv{eRZ z)4w}bt>*OPqE}jLRlPr9BX@r=7MI0_8wPEfCrp3f6Yd3??>Y?MvjPJF009Dm0RaH7 zY7BIp1&6kZo3=DGjyUw;dT^#m{S6&&7k~PblER;-3AUSMDkbCjc1C?L>EyKCch`%A zkh*SBvjZ@_6ZB>-4U7X7lsQKB7yMsux&>n77WL!s=v_yjjnF<5vKkR#F zA^%IK2X3AkXQ}dgT4&fwhcQ_%xu4w1W4+3iB6m~zJrp2hgERV76wGc~0Lj-Ow`V8O zZrqwxwIG5|c9x%-Dq8K-D*lMfll<3((-YimL7eGR;JYye6nhi<&F*&1Np!xMRC>qB zKDM&w?~i8wz4BH4@{Zpcb5bm-xAR+`VN}5~wz-yNQ!mPHBZ`>OpLAfwOsNIzoOg(m)2FIu3L(jw&3*P49xwiqTyw`n3T9fq?+a?H#GS;=#MCO2jOIE==IY04Iy{aiY@~=vF>6XCr zSq))}59=g1{5d9b1f|LG%(iLS4~fGSRSDzVO2<5!>b9cjk#yg<2+oigLU;e6Fi^7s zfq;0}eF5YA9(joXl1+(jR3Li_{TzNT@&juywBqsNV!knsL`Ewg*(^kQ;&AFQb~l5z zIU9z}dEbP6VHn*Jf}pyn0vd95QlRw-{;SD~ zojsi<)}nF@Tpswj@wgb?^v03zJaQfEEsh3(uoL<61$y=5YC%0ZD}#o-vvL80j4yvg zJ&NqE&jl`D0|^?=xl9D0%YHp2u&Dn#?N9zy3aL8#yiQ~e)&f2`S1HM!fvXMcA1%!Z zc>;lf0L-9+jO(-tBp6iz;QmX@p1_%GHY!Ka>?FnE+u|em&cA80AtN$}E`K*oQ?emip*dW&9un90c^I^AAf+d;kCd diff --git a/vendor/p12/src/lib.rs b/vendor/p12/src/lib.rs deleted file mode 100644 index 6b0c4b0e..00000000 --- a/vendor/p12/src/lib.rs +++ /dev/null @@ -1,1099 +0,0 @@ -//! -//! pure rust pkcs12 tool -//! -//! - -use getrandom::getrandom; -use lazy_static::lazy_static; -use yasna::{models::ObjectIdentifier, ASN1Error, ASN1ErrorKind, BERReader, DERWriter, Tag}; - -use hmac::{Hmac, Mac}; -use sha1::{Digest, Sha1}; - -type HmacSha1 = Hmac; - -fn as_oid(s: &'static [u64]) -> ObjectIdentifier { - ObjectIdentifier::from_slice(s) -} - -lazy_static! { - static ref OID_DATA_CONTENT_TYPE: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 7, 1]); - static ref OID_ENCRYPTED_DATA_CONTENT_TYPE: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 7, 6]); - static ref OID_FRIENDLY_NAME: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 20]); - static ref OID_LOCAL_KEY_ID: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 9, 21]); - static ref OID_CERT_TYPE_X509_CERTIFICATE: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 1]); - static ref OID_CERT_TYPE_SDSI_CERTIFICATE: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 9, 22, 2]); - static ref OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 3]); - static ref OID_SHA1: ObjectIdentifier = as_oid(&[1, 3, 14, 3, 2, 26]); - static ref OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 12, 1, 6]); - static ref OID_KEY_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 1]); - static ref OID_PKCS8_SHROUDED_KEY_BAG: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 2]); - static ref OID_CERT_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 3]); - static ref OID_CRL_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 4]); - static ref OID_SECRET_BAG: ObjectIdentifier = as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 5]); - static ref OID_SAFE_CONTENTS_BAG: ObjectIdentifier = - as_oid(&[1, 2, 840, 113_549, 1, 12, 10, 1, 6]); -} - -const ITERATIONS: u64 = 2048; - -fn sha1(bytes: &[u8]) -> Vec { - let mut hasher = Sha1::new(); - hasher.update(bytes); - hasher.finalize().to_vec() -} - -#[derive(Debug, Clone)] -pub struct EncryptedContentInfo { - pub content_encryption_algorithm: AlgorithmIdentifier, - pub encrypted_content: Vec, -} - -impl EncryptedContentInfo { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let content_type = r.next().read_oid()?; - debug_assert_eq!(content_type, *OID_DATA_CONTENT_TYPE); - let content_encryption_algorithm = AlgorithmIdentifier::parse(r.next())?; - let encrypted_content = r - .next() - .read_tagged_implicit(Tag::context(0), |r| r.read_bytes())?; - Ok(EncryptedContentInfo { - content_encryption_algorithm, - encrypted_content, - }) - }) - } - - pub fn data(&self, password: &[u8]) -> Option> { - self.content_encryption_algorithm - .decrypt_pbe(&self.encrypted_content, password) - } - - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - w.next().write_oid(&OID_DATA_CONTENT_TYPE); - self.content_encryption_algorithm.write(w.next()); - w.next() - .write_tagged_implicit(Tag::context(0), |w| w.write_bytes(&self.encrypted_content)); - }) - } - - pub fn to_der(&self) -> Vec { - yasna::construct_der(|w| self.write(w)) - } - - pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { - let data = yasna::construct_der(|w| { - w.write_sequence_of(|w| { - for sb in safe_bags { - sb.write(w.next()); - } - }) - }); - let salt = rand()?.to_vec(); - let encrypted_content = - pbe_with_sha1_and40_bit_rc2_cbc_encrypt(&data, password, &salt, ITERATIONS)?; - let content_encryption_algorithm = - AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams { - salt, - iterations: ITERATIONS, - }); - Some(EncryptedContentInfo { - content_encryption_algorithm, - encrypted_content, - }) - } -} - -#[derive(Debug, Clone)] -pub struct EncryptedData { - pub encrypted_content_info: EncryptedContentInfo, -} - -impl EncryptedData { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let version = r.next().read_u8()?; - debug_assert_eq!(version, 0); - - let encrypted_content_info = EncryptedContentInfo::parse(r.next())?; - Ok(EncryptedData { - encrypted_content_info, - }) - }) - } - pub fn data(&self, password: &[u8]) -> Option> { - self.encrypted_content_info.data(password) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - w.next().write_u8(0); - self.encrypted_content_info.write(w.next()); - }) - } - pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { - let encrypted_content_info = EncryptedContentInfo::from_safe_bags(safe_bags, password)?; - Some(EncryptedData { - encrypted_content_info, - }) - } -} - -#[derive(Debug, Clone)] -pub struct OtherContext { - pub content_type: ObjectIdentifier, - pub content: Vec, -} - -#[derive(Debug, Clone)] -pub enum ContentInfo { - Data(Vec), - EncryptedData(EncryptedData), - OtherContext(OtherContext), -} - -impl ContentInfo { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let content_type = r.next().read_oid()?; - if content_type == *OID_DATA_CONTENT_TYPE { - let data = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; - return Ok(ContentInfo::Data(data)); - } - if content_type == *OID_ENCRYPTED_DATA_CONTENT_TYPE { - let result = r.next().read_tagged(Tag::context(0), |r| { - Ok(ContentInfo::EncryptedData(EncryptedData::parse(r)?)) - }); - return result; - } - - let content = r.next().read_tagged(Tag::context(0), |r| r.read_der())?; - Ok(ContentInfo::OtherContext(OtherContext { - content_type, - content, - })) - }) - } - pub fn data(&self, password: &[u8]) -> Option> { - match self { - ContentInfo::Data(data) => Some(data.to_owned()), - ContentInfo::EncryptedData(encrypted) => encrypted.data(password), - ContentInfo::OtherContext(_) => None, - } - } - pub fn oid(&self) -> ObjectIdentifier { - match self { - ContentInfo::Data(_) => OID_DATA_CONTENT_TYPE.clone(), - ContentInfo::EncryptedData(_) => OID_ENCRYPTED_DATA_CONTENT_TYPE.clone(), - ContentInfo::OtherContext(other) => other.content_type.clone(), - } - } - pub fn write(&self, w: DERWriter) { - match self { - ContentInfo::Data(data) => w.write_sequence(|w| { - w.next().write_oid(&OID_DATA_CONTENT_TYPE); - w.next() - .write_tagged(Tag::context(0), |w| w.write_bytes(data)) - }), - ContentInfo::EncryptedData(encrypted_data) => w.write_sequence(|w| { - w.next().write_oid(&OID_ENCRYPTED_DATA_CONTENT_TYPE); - w.next() - .write_tagged(Tag::context(0), |w| encrypted_data.write(w)) - }), - ContentInfo::OtherContext(other) => w.write_sequence(|w| { - w.next().write_oid(&other.content_type); - w.next() - .write_tagged(Tag::context(0), |w| w.write_der(&other.content)) - }), - } - } - pub fn to_der(&self) -> Vec { - yasna::construct_der(|w| self.write(w)) - } - - pub fn from_der(der: &[u8]) -> Result { - yasna::parse_der(der, Self::parse) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Pkcs12PbeParams { - pub salt: Vec, - pub iterations: u64, -} - -impl Pkcs12PbeParams { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let salt = r.next().read_bytes()?; - let iterations = r.next().read_u64()?; - Ok(Pkcs12PbeParams { salt, iterations }) - }) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - w.next().write_bytes(&self.salt); - w.next().write_u64(self.iterations); - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct OtherAlgorithmIdentifier { - pub algorithm_type: ObjectIdentifier, - pub params: Option>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum AlgorithmIdentifier { - Sha1, - PbewithSHAAnd40BitRC2CBC(Pkcs12PbeParams), - PbeWithSHAAnd3KeyTripleDESCBC(Pkcs12PbeParams), - OtherAlg(OtherAlgorithmIdentifier), -} - -impl AlgorithmIdentifier { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let algorithm_type = r.next().read_oid()?; - if algorithm_type == *OID_SHA1 { - r.read_optional(|r| r.read_null())?; - return Ok(AlgorithmIdentifier::Sha1); - } - if algorithm_type == *OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC { - let params = Pkcs12PbeParams::parse(r.next())?; - return Ok(AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(params)); - } - if algorithm_type == *OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC { - let params = Pkcs12PbeParams::parse(r.next())?; - return Ok(AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(params)); - } - let params = r.read_optional(|r| r.read_der())?; - Ok(AlgorithmIdentifier::OtherAlg(OtherAlgorithmIdentifier { - algorithm_type, - params, - })) - }) - } - pub fn decrypt_pbe(&self, ciphertext: &[u8], password: &[u8]) -> Option> { - match self { - AlgorithmIdentifier::Sha1 => None, - AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(param) => { - pbe_with_sha1_and40_bit_rc2_cbc(ciphertext, password, ¶m.salt, param.iterations) - } - AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param) => { - pbe_with_sha_and3_key_triple_des_cbc( - ciphertext, - password, - ¶m.salt, - param.iterations, - ) - } - AlgorithmIdentifier::OtherAlg(_) => None, - } - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| match self { - AlgorithmIdentifier::Sha1 => { - w.next().write_oid(&OID_SHA1); - w.next().write_null(); - } - AlgorithmIdentifier::PbewithSHAAnd40BitRC2CBC(p) => { - w.next().write_oid(&OID_PBE_WITH_SHA1_AND40_BIT_RC2_CBC); - p.write(w.next()); - } - AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(p) => { - w.next().write_oid(&OID_PBE_WITH_SHA_AND3_KEY_TRIPLE_DESCBC); - p.write(w.next()); - } - AlgorithmIdentifier::OtherAlg(other) => { - w.next().write_oid(&other.algorithm_type); - if let Some(der) = &other.params { - w.next().write_der(der); - } - } - }) - } -} - -#[derive(Debug)] -pub struct DigestInfo { - pub digest_algorithm: AlgorithmIdentifier, - pub digest: Vec, -} - -impl DigestInfo { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let digest_algorithm = AlgorithmIdentifier::parse(r.next())?; - let digest = r.next().read_bytes()?; - Ok(DigestInfo { - digest_algorithm, - digest, - }) - }) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - self.digest_algorithm.write(w.next()); - w.next().write_bytes(&self.digest); - }) - } -} - -#[derive(Debug)] -pub struct MacData { - pub mac: DigestInfo, - pub salt: Vec, - pub iterations: u32, -} - -impl MacData { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let mac = DigestInfo::parse(r.next())?; - let salt = r.next().read_bytes()?; - let iterations = r.next().read_u32()?; - Ok(MacData { - mac, - salt, - iterations, - }) - }) - } - - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - self.mac.write(w.next()); - w.next().write_bytes(&self.salt); - w.next().write_u32(self.iterations); - }) - } - - pub fn verify_mac(&self, data: &[u8], password: &[u8]) -> bool { - debug_assert_eq!(self.mac.digest_algorithm, AlgorithmIdentifier::Sha1); - let key = pbepkcs12sha1(password, &self.salt, self.iterations as u64, 3, 20); - let mut mac = HmacSha1::new_from_slice(&key).unwrap(); - mac.update(data); - mac.verify_slice(&self.mac.digest).is_ok() - } - - pub fn new(data: &[u8], password: &[u8]) -> MacData { - let salt = rand().unwrap(); - let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); - let mut mac = HmacSha1::new_from_slice(&key).unwrap(); - mac.update(data); - let digest = mac.finalize().into_bytes().to_vec(); - MacData { - mac: DigestInfo { - digest_algorithm: AlgorithmIdentifier::Sha1, - digest, - }, - salt: salt.to_vec(), - iterations: ITERATIONS as u32, - } - } -} - -fn rand() -> Option<[u8; 8]> { - let mut buf = [0u8; 8]; - // HACK: use null salt for determinism since we don't care about pkcs#12 encryption anyway - return Some(buf); - if getrandom(&mut buf).is_ok() { - Some(buf) - } else { - None - } -} - -#[derive(Debug)] -pub struct PFX { - pub version: u8, - pub auth_safe: ContentInfo, - pub mac_data: Option, -} - -impl PFX { - pub fn new( - cert_der: &[u8], - key_der: &[u8], - ca_der: Option<&[u8]>, - password: &str, - name: &str, - ) -> Option { - let mut cas = vec![]; - if let Some(ca) = ca_der { - cas.push(ca); - } - Self::new_with_cas(cert_der, key_der, &cas, password, name) - } - pub fn new_with_cas( - cert_der: &[u8], - key_der: &[u8], - ca_der_list: &[&[u8]], - password: &str, - name: &str, - ) -> Option { - let password = bmp_string(password); - let salt = rand()?.to_vec(); - let encrypted_data = - pbe_with_sha_and3_key_triple_des_cbc_encrypt(key_der, &password, &salt, ITERATIONS)?; - let param = Pkcs12PbeParams { - salt, - iterations: ITERATIONS, - }; - let key_bag_inner = SafeBagKind::Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo { - encryption_algorithm: AlgorithmIdentifier::PbeWithSHAAnd3KeyTripleDESCBC(param), - encrypted_data, - }); - let friendly_name = PKCS12Attribute::FriendlyName(name.to_owned()); - let local_key_id = PKCS12Attribute::LocalKeyId(sha1(cert_der)); - let key_bag = SafeBag { - bag: key_bag_inner, - attributes: vec![friendly_name.clone(), local_key_id.clone()], - }; - let cert_bag_inner = SafeBagKind::CertBag(CertBag::X509(cert_der.to_owned())); - let cert_bag = SafeBag { - bag: cert_bag_inner, - attributes: vec![friendly_name, local_key_id], - }; - let mut cert_bags = vec![cert_bag]; - for ca in ca_der_list { - cert_bags.push(SafeBag { - bag: SafeBagKind::CertBag(CertBag::X509((*ca).to_owned())), - attributes: vec![], - }); - } - let contents = yasna::construct_der(|w| { - w.write_sequence_of(|w| { - ContentInfo::EncryptedData( - EncryptedData::from_safe_bags(&cert_bags, &password) - .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid)) - .unwrap(), - ) - .write(w.next()); - ContentInfo::Data(yasna::construct_der(|w| { - w.write_sequence_of(|w| { - key_bag.write(w.next()); - }) - })) - .write(w.next()); - }); - }); - let mac_data = MacData::new(&contents, &password); - Some(PFX { - version: 3, - auth_safe: ContentInfo::Data(contents), - mac_data: Some(mac_data), - }) - } - - pub fn parse(bytes: &[u8]) -> Result { - yasna::parse_der(bytes, |r| { - r.read_sequence(|r| { - let version = r.next().read_u8()?; - let auth_safe = ContentInfo::parse(r.next())?; - let mac_data = r.read_optional(MacData::parse)?; - Ok(PFX { - version, - auth_safe, - mac_data, - }) - }) - }) - } - - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - w.next().write_u8(self.version); - self.auth_safe.write(w.next()); - if let Some(mac_data) = &self.mac_data { - mac_data.write(w.next()) - } - }) - } - - pub fn to_der(&self) -> Vec { - yasna::construct_der(|w| self.write(w)) - } - pub fn bags(&self, password: &str) -> Result, ASN1Error> { - let password = bmp_string(password); - - let data = self - .auth_safe - .data(&password) - .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; - - let contents = yasna::parse_der(&data, |r| r.collect_sequence_of(ContentInfo::parse))?; - - let mut result = vec![]; - for content in contents.iter() { - let data = content - .data(&password) - .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; - - let safe_bags = yasna::parse_der(&data, |r| r.collect_sequence_of(SafeBag::parse))?; - - for safe_bag in safe_bags.iter() { - result.push(safe_bag.to_owned()) - } - } - Ok(result) - } - //DER-encoded X.509 certificate - pub fn cert_bags(&self, password: &str) -> Result>, ASN1Error> { - self.cert_x509_bags(password) - } - //DER-encoded X.509 certificate - pub fn cert_x509_bags(&self, password: &str) -> Result>, ASN1Error> { - let mut result = vec![]; - for safe_bag in self.bags(password)? { - if let Some(cert) = safe_bag.bag.get_x509_cert() { - result.push(cert); - } - } - Ok(result) - } - pub fn cert_sdsi_bags(&self, password: &str) -> Result, ASN1Error> { - let mut result = vec![]; - for safe_bag in self.bags(password)? { - if let Some(cert) = safe_bag.bag.get_sdsi_cert() { - result.push(cert); - } - } - Ok(result) - } - pub fn key_bags(&self, password: &str) -> Result>, ASN1Error> { - let bmp_password = bmp_string(password); - let mut result = vec![]; - for safe_bag in self.bags(password)? { - if let Some(key) = safe_bag.bag.get_key(&bmp_password) { - result.push(key); - } - } - Ok(result) - } - - pub fn verify_mac(&self, password: &str) -> bool { - let bmp_password = bmp_string(password); - if let Some(mac_data) = &self.mac_data { - return match self.auth_safe.data(&bmp_password) { - Some(data) => mac_data.verify_mac(&data, &bmp_password), - None => false, - }; - } - true - } -} - -#[inline(always)] -fn pbepkcs12sha1core(d: &[u8], i: &[u8], a: &mut Vec, iterations: u64) -> Vec { - let mut ai: Vec = d.iter().chain(i.iter()).cloned().collect(); - for _ in 0..iterations { - ai = sha1(&ai); - } - a.append(&mut ai.clone()); - ai -} - -#[allow(clippy::many_single_char_names)] -fn pbepkcs12sha1(pass: &[u8], salt: &[u8], iterations: u64, id: u8, size: u64) -> Vec { - const U: u64 = 160 / 8; - const V: u64 = 512 / 8; - let r: u64 = iterations; - let d = [id; V as usize]; - fn get_len(s: usize) -> usize { - let s = s as u64; - (V * ((s + V - 1) / V)) as usize - } - let s = salt.iter().cycle().take(get_len(salt.len())); - let p = pass.iter().cycle().take(get_len(pass.len())); - let mut i: Vec = s.chain(p).cloned().collect(); - let c = (size + U - 1) / U; - let mut a: Vec = vec![]; - for _ in 1..c { - let ai = pbepkcs12sha1core(&d, &i, &mut a, r); - - let b: Vec = ai.iter().cycle().take(V as usize).cloned().collect(); - - let b_iter = b.iter().rev().cycle().take(i.len()); - let i_b_iter = i.iter_mut().rev().zip(b_iter); - let mut inc = 1u8; - for (i3, (ii, bi)) in i_b_iter.enumerate() { - if ((i3 as u64) % V) == 0 { - inc = 1; - } - let (ii2, inc2) = ii.overflowing_add(*bi); - let (ii3, inc3) = ii2.overflowing_add(inc); - inc = (inc2 || inc3) as u8; - *ii = ii3; - } - } - - pbepkcs12sha1core(&d, &i, &mut a, r); - - a.iter().take(size as usize).cloned().collect() -} - -fn pbe_with_sha1_and40_bit_rc2_cbc( - data: &[u8], - password: &[u8], - salt: &[u8], - iterations: u64, -) -> Option> { - use cbc::{ - cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}, - Decryptor, - }; - use rc2::Rc2; - type Rc2Cbc = Decryptor; - - let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); - let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - - let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - rc2.decrypt_padded_vec_mut::(data).ok() -} - -fn pbe_with_sha1_and40_bit_rc2_cbc_encrypt( - data: &[u8], - password: &[u8], - salt: &[u8], - iterations: u64, -) -> Option> { - use cbc::{ - cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}, - Encryptor, - }; - use rc2::Rc2; - type Rc2Cbc = Encryptor; - - let dk = pbepkcs12sha1(password, salt, iterations, 1, 5); - let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - - let rc2 = Rc2Cbc::new_from_slices(&dk, &iv).ok()?; - Some(rc2.encrypt_padded_vec_mut::(data)) -} - -fn pbe_with_sha_and3_key_triple_des_cbc( - data: &[u8], - password: &[u8], - salt: &[u8], - iterations: u64, -) -> Option> { - use cbc::{ - cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}, - Decryptor, - }; - use des::TdesEde3; - type TDesCbc = Decryptor; - - let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); - let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - - let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - tdes.decrypt_padded_vec_mut::(data).ok() -} - -fn pbe_with_sha_and3_key_triple_des_cbc_encrypt( - data: &[u8], - password: &[u8], - salt: &[u8], - iterations: u64, -) -> Option> { - use cbc::{ - cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}, - Encryptor, - }; - use des::TdesEde3; - type TDesCbc = Encryptor; - - let dk = pbepkcs12sha1(password, salt, iterations, 1, 24); - let iv = pbepkcs12sha1(password, salt, iterations, 2, 8); - - let tdes = TDesCbc::new_from_slices(&dk, &iv).ok()?; - Some(tdes.encrypt_padded_vec_mut::(data)) -} - -fn bmp_string(s: &str) -> Vec { - let utf16: Vec = s.encode_utf16().collect(); - - let mut bytes = Vec::with_capacity(utf16.len() * 2 + 2); - for c in utf16 { - bytes.push((c / 256) as u8); - bytes.push((c % 256) as u8); - } - bytes.push(0x00); - bytes.push(0x00); - bytes -} - -#[derive(Debug, Clone)] -pub enum CertBag { - X509(Vec), - SDSI(String), -} - -impl CertBag { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let oid = r.next().read_oid()?; - if oid == *OID_CERT_TYPE_X509_CERTIFICATE { - let x509 = r.next().read_tagged(Tag::context(0), |r| r.read_bytes())?; - return Ok(CertBag::X509(x509)); - }; - if oid == *OID_CERT_TYPE_SDSI_CERTIFICATE { - let sdsi = r - .next() - .read_tagged(Tag::context(0), |r| r.read_ia5_string())?; - return Ok(CertBag::SDSI(sdsi)); - } - Err(ASN1Error::new(ASN1ErrorKind::Invalid)) - }) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| match self { - CertBag::X509(x509) => { - w.next().write_oid(&OID_CERT_TYPE_X509_CERTIFICATE); - w.next() - .write_tagged(Tag::context(0), |w| w.write_bytes(x509)); - } - CertBag::SDSI(sdsi) => { - w.next().write_oid(&OID_CERT_TYPE_SDSI_CERTIFICATE); - w.next() - .write_tagged(Tag::context(0), |w| w.write_ia5_string(sdsi)); - } - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct EncryptedPrivateKeyInfo { - pub encryption_algorithm: AlgorithmIdentifier, - pub encrypted_data: Vec, -} - -impl EncryptedPrivateKeyInfo { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let encryption_algorithm = AlgorithmIdentifier::parse(r.next())?; - - let encrypted_data = r.next().read_bytes()?; - - Ok(EncryptedPrivateKeyInfo { - encryption_algorithm, - encrypted_data, - }) - }) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - self.encryption_algorithm.write(w.next()); - w.next().write_bytes(&self.encrypted_data); - }) - } - pub fn decrypt(&self, password: &[u8]) -> Option> { - self.encryption_algorithm - .decrypt_pbe(&self.encrypted_data, password) - } -} - -#[test] -fn test_encrypted_private_key_info() { - let epki = EncryptedPrivateKeyInfo { - encryption_algorithm: AlgorithmIdentifier::Sha1, - encrypted_data: b"foo".to_vec(), - }; - let der = yasna::construct_der(|w| { - epki.write(w); - }); - let epki2 = yasna::parse_ber(&der, EncryptedPrivateKeyInfo::parse).unwrap(); - assert_eq!(epki2, epki); -} - -#[derive(Debug, Clone)] -pub struct OtherBag { - pub bag_id: ObjectIdentifier, - pub bag_value: Vec, -} - -#[derive(Debug, Clone)] -pub enum SafeBagKind { - //KeyBag(), - Pkcs8ShroudedKeyBag(EncryptedPrivateKeyInfo), - CertBag(CertBag), - //CRLBag(), - //SecretBag(), - //SafeContents(Vec), - OtherBagKind(OtherBag), -} - -impl SafeBagKind { - pub fn parse(r: BERReader, bag_id: ObjectIdentifier) -> Result { - if bag_id == *OID_CERT_BAG { - return Ok(SafeBagKind::CertBag(CertBag::parse(r)?)); - } - if bag_id == *OID_PKCS8_SHROUDED_KEY_BAG { - return Ok(SafeBagKind::Pkcs8ShroudedKeyBag( - EncryptedPrivateKeyInfo::parse(r)?, - )); - } - let bag_value = r.read_der()?; - Ok(SafeBagKind::OtherBagKind(OtherBag { bag_id, bag_value })) - } - pub fn write(&self, w: DERWriter) { - match self { - SafeBagKind::Pkcs8ShroudedKeyBag(epk) => epk.write(w), - SafeBagKind::CertBag(cb) => cb.write(w), - SafeBagKind::OtherBagKind(other) => w.write_der(&other.bag_value), - } - } - pub fn oid(&self) -> ObjectIdentifier { - match self { - SafeBagKind::Pkcs8ShroudedKeyBag(_) => OID_PKCS8_SHROUDED_KEY_BAG.clone(), - SafeBagKind::CertBag(_) => OID_CERT_BAG.clone(), - SafeBagKind::OtherBagKind(other) => other.bag_id.clone(), - } - } - pub fn get_x509_cert(&self) -> Option> { - if let SafeBagKind::CertBag(CertBag::X509(x509)) = self { - return Some(x509.to_owned()); - } - None - } - - pub fn get_sdsi_cert(&self) -> Option { - if let SafeBagKind::CertBag(CertBag::SDSI(sdsi)) = self { - return Some(sdsi.to_owned()); - } - None - } - - pub fn get_key(&self, password: &[u8]) -> Option> { - if let SafeBagKind::Pkcs8ShroudedKeyBag(kb) = self { - return kb.decrypt(password); - } - None - } -} - -#[derive(Debug, Clone)] -pub struct OtherAttribute { - pub oid: ObjectIdentifier, - pub data: Vec>, -} - -#[derive(Debug, Clone)] -pub enum PKCS12Attribute { - FriendlyName(String), - LocalKeyId(Vec), - Other(OtherAttribute), -} - -impl PKCS12Attribute { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let oid = r.next().read_oid()?; - if oid == *OID_FRIENDLY_NAME { - let name = r - .next() - .collect_set_of(|s| s.read_bmp_string())? - .pop() - .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; - return Ok(PKCS12Attribute::FriendlyName(name)); - } - if oid == *OID_LOCAL_KEY_ID { - let local_key_id = r - .next() - .collect_set_of(|s| s.read_bytes())? - .pop() - .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid))?; - return Ok(PKCS12Attribute::LocalKeyId(local_key_id)); - } - - let data = r.next().collect_set_of(|s| s.read_der())?; - let other = OtherAttribute { oid, data }; - Ok(PKCS12Attribute::Other(other)) - }) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| match self { - PKCS12Attribute::FriendlyName(name) => { - w.next().write_oid(&OID_FRIENDLY_NAME); - w.next().write_set_of(|w| { - w.next().write_bmp_string(name); - }) - } - PKCS12Attribute::LocalKeyId(id) => { - w.next().write_oid(&OID_LOCAL_KEY_ID); - w.next().write_set_of(|w| w.next().write_bytes(id)) - } - PKCS12Attribute::Other(other) => { - w.next().write_oid(&other.oid); - w.next().write_set_of(|w| { - for bytes in other.data.iter() { - w.next().write_der(bytes); - } - }) - } - }) - } -} -#[derive(Debug, Clone)] -pub struct SafeBag { - pub bag: SafeBagKind, - pub attributes: Vec, -} - -impl SafeBag { - pub fn parse(r: BERReader) -> Result { - r.read_sequence(|r| { - let oid = r.next().read_oid()?; - - let bag = r - .next() - .read_tagged(Tag::context(0), |r| SafeBagKind::parse(r, oid))?; - - let attributes = r - .read_optional(|r| r.collect_set_of(PKCS12Attribute::parse))? - .unwrap_or_else(Vec::new); - - Ok(SafeBag { bag, attributes }) - }) - } - pub fn write(&self, w: DERWriter) { - w.write_sequence(|w| { - w.next().write_oid(&self.bag.oid()); - w.next() - .write_tagged(Tag::context(0), |w| self.bag.write(w)); - if !self.attributes.is_empty() { - w.next().write_set_of(|w| { - for attr in &self.attributes { - attr.write(w.next()); - } - }) - } - }) - } - pub fn friendly_name(&self) -> Option { - for attr in self.attributes.iter() { - if let PKCS12Attribute::FriendlyName(name) = attr { - return Some(name.to_owned()); - } - } - None - } - pub fn local_key_id(&self) -> Option> { - for attr in self.attributes.iter() { - if let PKCS12Attribute::LocalKeyId(id) = attr { - return Some(id.to_owned()); - } - } - None - } -} - -#[test] -fn test_create_p12() { - use std::{ - fs::File, - io::{Read, Write}, - }; - let mut cafile = File::open("ca.der").unwrap(); - let mut ca = vec![]; - cafile.read_to_end(&mut ca).unwrap(); - let mut fcert = File::open("clientcert.der").unwrap(); - let mut fkey = File::open("clientkey.der").unwrap(); - let mut cert = vec![]; - fcert.read_to_end(&mut cert).unwrap(); - let mut key = vec![]; - fkey.read_to_end(&mut key).unwrap(); - let p12 = PFX::new(&cert, &key, Some(&ca), "changeit", "look") - .unwrap() - .to_der(); - - let pfx = PFX::parse(&p12).unwrap(); - - let keys = pfx.key_bags("changeit").unwrap(); - assert_eq!(keys[0], key); - - let certs = pfx.cert_x509_bags("changeit").unwrap(); - assert_eq!(certs[0], cert); - assert_eq!(certs[1], ca); - assert!(pfx.verify_mac("changeit")); - - let mut fp12 = File::create("test.p12").unwrap(); - fp12.write_all(&p12).unwrap(); -} -#[test] -fn test_create_p12_without_password() { - use std::{ - fs::File, - io::{Read, Write}, - }; - let mut cafile = File::open("ca.der").unwrap(); - let mut ca = vec![]; - cafile.read_to_end(&mut ca).unwrap(); - let mut fcert = File::open("clientcert.der").unwrap(); - - let mut cert = vec![]; - fcert.read_to_end(&mut cert).unwrap(); - - let p12 = PFX::new(&cert, &[], Some(&ca), "", "look") - .unwrap() - .to_der(); - - let pfx = PFX::parse(&p12).unwrap(); - - let certs = pfx.cert_x509_bags("").unwrap(); - assert_eq!(certs[0], cert); - assert_eq!(certs[1], ca); - assert!(pfx.verify_mac("")); - - let mut fp12 = File::create("test.p12").unwrap(); - fp12.write_all(&p12).unwrap(); -} - -#[test] -fn test_bmp_string() { - let value = bmp_string("Beavis"); - assert!( - value - == [0x00, 0x42, 0x00, 0x65, 0x00, 0x61, 0x00, 0x76, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00] - ) -} - -#[test] -fn test_pbepkcs12sha1() { - use hex_literal::hex; - let pass = bmp_string(""); - assert_eq!(pass, vec![0, 0]); - let salt = hex!("9af4702958a8e95c"); - let iterations = 2048; - let id = 1; - let size = 24; - let result = pbepkcs12sha1(&pass, &salt, iterations, id, size); - let res = hex!("c2294aa6d02930eb5ce9c329eccb9aee1cb136baea746557"); - assert_eq!(result, res); -} - -#[test] -fn test_pbepkcs12sha1_2() { - use hex_literal::hex; - let pass = bmp_string(""); - assert_eq!(pass, vec![0, 0]); - let salt = hex!("9af4702958a8e95c"); - let iterations = 2048; - let id = 2; - let size = 8; - let result = pbepkcs12sha1(&pass, &salt, iterations, id, size); - let res = hex!("8e9f8fc7664378bc"); - assert_eq!(result, res); -} From 5326498786bd6addefb19ce9b25f55024a4e66d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 13 Mar 2025 16:51:39 +0100 Subject: [PATCH 64/87] Factor out the RNG provider from p12 instead of always just stubbing it out --- rust/operator-binary/src/format/convert.rs | 24 +++++++-- rust/p12/src/lib.rs | 57 ++++++++++++++-------- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/rust/operator-binary/src/format/convert.rs b/rust/operator-binary/src/format/convert.rs index 7d78a4db..adcb8d50 100644 --- a/rust/operator-binary/src/format/convert.rs +++ b/rust/operator-binary/src/format/convert.rs @@ -109,6 +109,16 @@ fn pkcs12_truststore<'a>( let java_oracle_trusted_key_usage_oid = yasna::models::ObjectIdentifier::from_slice(&[2, 16, 840, 1, 113894, 746875, 1, 1]); + // We don't care about actually encrypting the truststore securely, but if we use a random salt then the pkcs#12 bundle will be different for every write + // (=> TrustStore controller will get stuck reconciling indefinitely.) + // So let's just use a fixed salt instead. + struct DummyRng; + impl p12::Rng for DummyRng { + fn generate_salt(&mut self) -> Option<[u8; 8]> { + Some([0; 8]) + } + } + let mut truststore_bags = Vec::new(); for ca in ca_list { truststore_bags.push(p12::SafeBag { @@ -124,8 +134,12 @@ fn pkcs12_truststore<'a>( } let password_as_bmp_string = bmp_string(p12_password); let encrypted_data = p12::ContentInfo::EncryptedData( - p12::EncryptedData::from_safe_bags(&truststore_bags[..], &password_as_bmp_string) - .context(tls_to_pkcs12_error::EncryptDataForTruststoreSnafu)?, + p12::EncryptedData::from_safe_bags( + &truststore_bags[..], + &password_as_bmp_string, + &mut DummyRng, + ) + .context(tls_to_pkcs12_error::EncryptDataForTruststoreSnafu)?, ); let truststore_data = yasna::construct_der(|w| { w.write_sequence_of(|w| { @@ -134,7 +148,11 @@ fn pkcs12_truststore<'a>( }); Ok(p12::PFX { version: 3, - mac_data: Some(p12::MacData::new(&truststore_data, &password_as_bmp_string)), + mac_data: Some(p12::MacData::new( + &truststore_data, + &password_as_bmp_string, + &mut DummyRng, + )), auth_safe: p12::ContentInfo::Data(truststore_data), } .to_der()) diff --git a/rust/p12/src/lib.rs b/rust/p12/src/lib.rs index 6b0c4b0e..d03313dc 100644 --- a/rust/p12/src/lib.rs +++ b/rust/p12/src/lib.rs @@ -89,7 +89,11 @@ impl EncryptedContentInfo { yasna::construct_der(|w| self.write(w)) } - pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { + pub fn from_safe_bags( + safe_bags: &[SafeBag], + password: &[u8], + rng: &mut impl Rng, + ) -> Option { let data = yasna::construct_der(|w| { w.write_sequence_of(|w| { for sb in safe_bags { @@ -97,7 +101,7 @@ impl EncryptedContentInfo { } }) }); - let salt = rand()?.to_vec(); + let salt = rng.generate_salt()?.to_vec(); let encrypted_content = pbe_with_sha1_and40_bit_rc2_cbc_encrypt(&data, password, &salt, ITERATIONS)?; let content_encryption_algorithm = @@ -138,8 +142,13 @@ impl EncryptedData { self.encrypted_content_info.write(w.next()); }) } - pub fn from_safe_bags(safe_bags: &[SafeBag], password: &[u8]) -> Option { - let encrypted_content_info = EncryptedContentInfo::from_safe_bags(safe_bags, password)?; + pub fn from_safe_bags( + safe_bags: &[SafeBag], + password: &[u8], + rng: &mut impl Rng, + ) -> Option { + let encrypted_content_info = + EncryptedContentInfo::from_safe_bags(safe_bags, password, rng)?; Some(EncryptedData { encrypted_content_info, }) @@ -385,8 +394,8 @@ impl MacData { mac.verify_slice(&self.mac.digest).is_ok() } - pub fn new(data: &[u8], password: &[u8]) -> MacData { - let salt = rand().unwrap(); + pub fn new(data: &[u8], password: &[u8], rng: &mut impl Rng) -> MacData { + let salt = rng.generate_salt().unwrap(); let key = pbepkcs12sha1(password, &salt, ITERATIONS, 3, 20); let mut mac = HmacSha1::new_from_slice(&key).unwrap(); mac.update(data); @@ -402,14 +411,20 @@ impl MacData { } } -fn rand() -> Option<[u8; 8]> { - let mut buf = [0u8; 8]; - // HACK: use null salt for determinism since we don't care about pkcs#12 encryption anyway - return Some(buf); - if getrandom(&mut buf).is_ok() { - Some(buf) - } else { - None +/// Random number generator +pub trait Rng { + fn generate_salt(&mut self) -> Option<[u8; 8]>; +} + +pub struct SystemRng; +impl Rng for SystemRng { + fn generate_salt(&mut self) -> Option<[u8; 8]> { + let mut buf = [0u8; 8]; + if getrandom(&mut buf).is_ok() { + Some(buf) + } else { + None + } } } @@ -427,12 +442,13 @@ impl PFX { ca_der: Option<&[u8]>, password: &str, name: &str, + rng: &mut impl Rng, ) -> Option { let mut cas = vec![]; if let Some(ca) = ca_der { cas.push(ca); } - Self::new_with_cas(cert_der, key_der, &cas, password, name) + Self::new_with_cas(cert_der, key_der, &cas, password, name, rng) } pub fn new_with_cas( cert_der: &[u8], @@ -440,9 +456,10 @@ impl PFX { ca_der_list: &[&[u8]], password: &str, name: &str, + rng: &mut impl Rng, ) -> Option { let password = bmp_string(password); - let salt = rand()?.to_vec(); + let salt = rng.generate_salt()?.to_vec(); let encrypted_data = pbe_with_sha_and3_key_triple_des_cbc_encrypt(key_der, &password, &salt, ITERATIONS)?; let param = Pkcs12PbeParams { @@ -474,7 +491,7 @@ impl PFX { let contents = yasna::construct_der(|w| { w.write_sequence_of(|w| { ContentInfo::EncryptedData( - EncryptedData::from_safe_bags(&cert_bags, &password) + EncryptedData::from_safe_bags(&cert_bags, &password, rng) .ok_or_else(|| ASN1Error::new(ASN1ErrorKind::Invalid)) .unwrap(), ) @@ -487,7 +504,7 @@ impl PFX { .write(w.next()); }); }); - let mac_data = MacData::new(&contents, &password); + let mac_data = MacData::new(&contents, &password, rng); Some(PFX { version: 3, auth_safe: ContentInfo::Data(contents), @@ -1015,7 +1032,7 @@ fn test_create_p12() { fcert.read_to_end(&mut cert).unwrap(); let mut key = vec![]; fkey.read_to_end(&mut key).unwrap(); - let p12 = PFX::new(&cert, &key, Some(&ca), "changeit", "look") + let p12 = PFX::new(&cert, &key, Some(&ca), "changeit", "look", &mut SystemRng) .unwrap() .to_der(); @@ -1046,7 +1063,7 @@ fn test_create_p12_without_password() { let mut cert = vec![]; fcert.read_to_end(&mut cert).unwrap(); - let p12 = PFX::new(&cert, &[], Some(&ca), "", "look") + let p12 = PFX::new(&cert, &[], Some(&ca), "", "look", &mut SystemRng) .unwrap() .to_der(); From f284c9b1358dffbec72e98ef41399520d4fd42ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 14 Mar 2025 12:27:32 +0100 Subject: [PATCH 65/87] Handle CSI startup errors (again) --- rust/operator-binary/src/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 9ff6b904..9fee8a3e 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -137,9 +137,11 @@ async fn main() -> anyhow::Result<()> { sigterm.recv().map(|_| ()), )); let truststore_controller = - pin!(truststore_controller::start(&client, &watch_namespace)); - // TODO: handle error - futures::future::select(csi_server, truststore_controller).await; + pin!(truststore_controller::start(&client, &watch_namespace).map(Ok)); + futures::future::select(csi_server, truststore_controller) + .await + .factor_first() + .0?; } } Ok(()) From 5092971ad28d5a125d5541e6d7bdf9859356d107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 14 Mar 2025 15:01:34 +0100 Subject: [PATCH 66/87] Docs --- .../examples/secretclass-tls.yaml | 1 + .../examples/truststore-tls.yaml | 8 +++++++ .../secret-operator/pages/secretclass.adoc | 17 +++++++++++--- .../secret-operator/pages/truststore.adoc | 23 +++++++++++++++++++ .../modules/secret-operator/partials/nav.adoc | 1 + 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 docs/modules/secret-operator/examples/truststore-tls.yaml create mode 100644 docs/modules/secret-operator/pages/truststore.adoc diff --git a/docs/modules/secret-operator/examples/secretclass-tls.yaml b/docs/modules/secret-operator/examples/secretclass-tls.yaml index 66db033d..a325ede3 100644 --- a/docs/modules/secret-operator/examples/secretclass-tls.yaml +++ b/docs/modules/secret-operator/examples/secretclass-tls.yaml @@ -17,3 +17,4 @@ spec: pod: {} # or... name: my-namespace + trustStoreConfigMapName: tls-ca # <4> diff --git a/docs/modules/secret-operator/examples/truststore-tls.yaml b/docs/modules/secret-operator/examples/truststore-tls.yaml new file mode 100644 index 00000000..b8239d8e --- /dev/null +++ b/docs/modules/secret-operator/examples/truststore-tls.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: TrustStore +metadata: + name: truststore-pem # <1> +spec: + secretClassName: tls # <2> + format: tls-pem # <3> diff --git a/docs/modules/secret-operator/pages/secretclass.adoc b/docs/modules/secret-operator/pages/secretclass.adoc index 617ddcce..e7c00e61 100644 --- a/docs/modules/secret-operator/pages/secretclass.adoc +++ b/docs/modules/secret-operator/pages/secretclass.adoc @@ -17,6 +17,7 @@ include::example$secretclass-tls.yaml[] <1> Backends are mutually exclusive, only one may be used by each SecretClass <2> Configures and selects the xref:#backend-autotls[] backend <3> Configures and selects the xref:#backend-k8ssearch[] backend +<4> Provides a trust root to be requested by xref:truststore.adoc[] [#backend] == Backend @@ -28,6 +29,8 @@ Each SecretClass is a associated with a single backend, which dictates the mecha *Format*: xref:#format-tls-pem[] +*TrustStore*: Yes + Issues a TLS certificate signed by the Secret Operator. The certificate authority can be provided by the administrator, or managed automatically by the Secret Operator. @@ -132,6 +135,8 @@ spec: *Format*: xref:#format-tls-pem[] +*TrustStore*: No + Injects a TLS certificate issued by {cert-manager}[Cert-Manager]. WARNING: This backend is experimental, and subject to change. @@ -195,6 +200,8 @@ spec: *Format*: xref:#format-kerberos[] +*TrustStore*: No + Creates a Kerberos keytab file for a selected realm. The Kerberos KDC and administrator credentials must be provided by the administrator. IMPORTANT: Only MIT Kerberos (krb5) and Active Directory are currently supported. @@ -350,6 +357,8 @@ spec: *Format*: Free-form +*TrustStore*: If configured + This backend can be used to mount `Secret` across namespaces into pods. The `Secret` object is selected based on two things: 1. The xref:scope.adoc[scopes] specified on the `Volume` using the attribute `secrets.stackable.tech/scope`. @@ -430,14 +439,16 @@ spec: pod: {} # or... name: my-namespace + trustStoreConfigMapName: tls-ca # <4> ---- `k8sSearch`:: Declares that the `k8sSearch` backend is used. -`k8sSearch.searchNamespace`:: Configures the namespace searched for `Secret` objects. -`k8sSearch.searchNamespace.pod`:: The `Secret` objects are located in the same namespace as the `Pod` object. Should be used +`k8sSearch.searchNamespace`:: Configures the namespace searched for Secrets. +`k8sSearch.searchNamespace.pod`:: The Secret objects are located in the same namespace as the Pod. Should be used for secrets that are provisioned by the application administrator. -`k8sSearch.searchNamespace.name`:: The `Secret` objects are located in a single global namespace. Should be used for secrets +`k8sSearch.searchNamespace.name`:: The Secrets are located in a single global namespace. Should be used for secrets that are provisioned by the cluster administrator. +`k8sSearch.trustStoreConfigMapName`:: ConfigMap used to provision xref:truststore.adoc[]. ==== Format diff --git a/docs/modules/secret-operator/pages/truststore.adoc b/docs/modules/secret-operator/pages/truststore.adoc new file mode 100644 index 00000000..cd79e5b0 --- /dev/null +++ b/docs/modules/secret-operator/pages/truststore.adoc @@ -0,0 +1,23 @@ += TrustStore +:description: A TrustStore in Kubernetes retrieves the trust anchors from a SecretClass. + +A _TrustStore_ is a Kubernetes resource that can be used to request the trust anchor information (such as the TLS certificate authorities) from a xref:secretclass.adoc[]. + +This can be used to access a protected service from other services that do not require their own certificates (or from clients running outside of Kubernetes). + +A TrustStore looks like this: + +[source,yaml] +---- +include::example$truststore-tls.yaml[] +---- +<1> Also used to name the created ConfigMap +<2> The name of the xref:secretclass.adoc[] +<3> The requested xref:secretclass.adoc#format[format] + +This will create a ConfigMap named `truststore-pem` containing a `ca.crt` with the trust root certificates. +It can then either be mounted into a Pod or retrieved and used from outside of Kubernetes. + +NOTE: Make sure to have a procedure for updating the retrieved certificates. The Secret Operator will automatically rotate + the xref:secretclass.adoc#backend-autotls[autoTls] certificate authority as needed, but all trust roots will require + some form of update occasionally. diff --git a/docs/modules/secret-operator/partials/nav.adoc b/docs/modules/secret-operator/partials/nav.adoc index 7ff84ca7..a776810e 100644 --- a/docs/modules/secret-operator/partials/nav.adoc +++ b/docs/modules/secret-operator/partials/nav.adoc @@ -6,6 +6,7 @@ ** xref:secret-operator:secretclass.adoc[] ** xref:secret-operator:scope.adoc[] ** xref:secret-operator:volume.adoc[] +** xref:secret-operator:truststore.adoc[] * Guides ** xref:secret-operator:cert-manager.adoc[] * xref:secret-operator:security.adoc[] From eac63385e0b5d0a0fd278e2b1ecf15533e386231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 14 Mar 2025 15:04:15 +0100 Subject: [PATCH 67/87] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48bc752f..95e8e36b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. - Made RSA key length configurable for certificates issued by cert-manager ([#528]). - Kerberos principal backends now also provision principals for IP address, not just DNS hostnames ([#552]). +- Added TrustStore CRD for requesting CA certificate information ([#557]). ### Fixed @@ -25,6 +26,7 @@ All notable changes to this project will be documented in this file. [#536]: https://github.com/stackabletech/secret-operator/pull/536 [#548]: https://github.com/stackabletech/secret-operator/pull/548 [#552]: https://github.com/stackabletech/secret-operator/pull/552 +[#557]: https://github.com/stackabletech/secret-operator/pull/557 ## [24.11.0] - 2024-11-18 From 055c73245442bab6be8891e6dae718598074dcf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 17 Mar 2025 15:53:16 +0100 Subject: [PATCH 68/87] Minor cleanup --- .../operator-binary/src/backend/k8s_search.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rust/operator-binary/src/backend/k8s_search.rs b/rust/operator-binary/src/backend/k8s_search.rs index 8822f1c1..3ebb6a27 100644 --- a/rust/operator-binary/src/backend/k8s_search.rs +++ b/rust/operator-binary/src/backend/k8s_search.rs @@ -130,18 +130,18 @@ impl SecretBackend for K8sSearch { .with_context(|_| GetTrustStoreSnafu { configmap: ObjectRef::::new(cm_name).within(cm_ns), })?; + let binary_data = cm + .binary_data + .unwrap_or_default() + .into_iter() + .map(|(k, ByteString(v))| (k, v)); + let str_data = cm + .data + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, v.into_bytes())); Ok(SecretContents::new(SecretData::Unknown( - cm.binary_data - .unwrap_or_default() - .into_iter() - .map(|(k, ByteString(v))| (k, v)) - .chain( - cm.data - .unwrap_or_default() - .into_iter() - .map(|(k, v)| (k, v.into_bytes())), - ) - .collect(), + binary_data.chain(str_data).collect(), ))) } From 8cd87e407f0af02f5a0a84ad46c5ddd67eba070a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 17 Mar 2025 16:03:28 +0100 Subject: [PATCH 69/87] Use ProductOperatorRun --- rust/operator-binary/src/main.rs | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 9fee8a3e..a9632fd7 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -8,10 +8,7 @@ use futures::{FutureExt, TryStreamExt}; use grpc::csi::v1::{ controller_server::ControllerServer, identity_server::IdentityServer, node_server::NodeServer, }; -use stackable_operator::{ - logging::TracingTarget, namespace::WatchNamespace, - utils::cluster_info::KubernetesClusterInfoOpts, CustomResourceExt, -}; +use stackable_operator::{cli::ProductOperatorRun, CustomResourceExt}; use std::{os::unix::prelude::FileTypeExt, path::PathBuf, pin::pin}; use tokio::signal::unix::{signal, SignalKind}; use tokio_stream::wrappers::UnixListenerStream; @@ -54,17 +51,8 @@ struct SecretOperatorRun { #[clap(long, env)] privileged: bool, - /// Tracing log collector system - #[arg(long, env, default_value_t, value_enum)] - pub tracing_target: TracingTarget, - - #[command(flatten)] - pub cluster_info_opts: KubernetesClusterInfoOpts, - - // FIXME: Use ProductOperatorRun instead? - /// Provides a specific namespace to watch (instead of watching all namespaces) - #[arg(long, env, default_value = "")] - pub watch_namespace: WatchNamespace, + #[clap(flatten)] + common: ProductOperatorRun, } mod built_info { @@ -82,10 +70,14 @@ async fn main() -> anyhow::Result<()> { stackable_operator::cli::Command::Run(SecretOperatorRun { csi_endpoint, node_name, - tracing_target, privileged, - cluster_info_opts, - watch_namespace, + common: + ProductOperatorRun { + product_config: _, + watch_namespace, + tracing_target, + cluster_info_opts, + }, }) => { stackable_operator::logging::initialize_logging( "SECRET_PROVISIONER_LOG", From 26a60f43b1cc951f24c14652276784fd89476cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:00:39 +0100 Subject: [PATCH 70/87] Fixed an import that got missing in the merge conflict --- rust/operator-binary/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 7dd457bf..18ce5a30 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -1,4 +1,4 @@ -use std::{os::unix::prelude::FileTypeExt, path::PathBuf}; +use std::{os::unix::prelude::FileTypeExt, path::PathBuf, pin::pin}; use anyhow::Context; use clap::{crate_description, crate_version, Parser}; From 1181174d8f840f1a1eb0506a4abb01f4ccea23ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:24:22 +0100 Subject: [PATCH 71/87] Add CRD docs for TrustStore --- deploy/helm/secret-operator/crds/crds.yaml | 7 ++++++- rust/operator-binary/src/crd.rs | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml index 989dc0d8..c1e3558b 100644 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -341,9 +341,13 @@ spec: description: Auto-generated derived type for TrustStoreSpec via `CustomResource` properties: spec: + description: |- + A [TrustStore](https://docs.stackable.tech/home/nightly/secret-operator/truststore) requests information about how to validate secrets issued by a [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass). + + The requested information is written to a ConfigMap with the same name as the TrustStore. properties: format: - description: Auto-generated discriminant enum variants + description: The [format](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#format) that the data should be converted into. enum: - tls-pem - tls-pkcs12 @@ -351,6 +355,7 @@ spec: nullable: true type: string secretClassName: + description: The name of the SecretClass that the request concerns. type: string required: - secretClassName diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 607ab0f0..f409969a 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -475,6 +475,10 @@ impl Deref for KerberosPrincipal { } } +/// A [TrustStore](DOCS_BASE_URL_PLACEHOLDER/secret-operator/truststore) requests information about how to +/// validate secrets issued by a [SecretClass](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass). +/// +/// The requested information is written to a ConfigMap with the same name as the TrustStore. #[derive(CustomResource, Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[kube( group = "secrets.stackable.tech", @@ -489,7 +493,10 @@ impl Deref for KerberosPrincipal { )] #[serde(rename_all = "camelCase")] pub struct TrustStoreSpec { + /// The name of the SecretClass that the request concerns. pub secret_class_name: String, + + /// The [format](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass#format) that the data should be converted into. pub format: Option, } From ecd2fd15e43fac86cf31355a40688f0938ed8d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:27:45 +0100 Subject: [PATCH 72/87] Fix pre-commit errors --- rust/p12/.github/FUNDING.yml | 1 + rust/p12/.github/workflows/build-and-test.yaml | 7 ++++--- rust/p12/.github/workflows/lint.yaml | 1 + rust/p12/.github/workflows/security.yaml | 1 + rust/p12/.gitignore | 2 +- rust/p12/README.md | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/rust/p12/.github/FUNDING.yml b/rust/p12/.github/FUNDING.yml index 0f5af72e..4f2b4e4c 100644 --- a/rust/p12/.github/FUNDING.yml +++ b/rust/p12/.github/FUNDING.yml @@ -1 +1,2 @@ +--- github: Keruspe diff --git a/rust/p12/.github/workflows/build-and-test.yaml b/rust/p12/.github/workflows/build-and-test.yaml index 3d6a05c0..cddb0575 100644 --- a/rust/p12/.github/workflows/build-and-test.yaml +++ b/rust/p12/.github/workflows/build-and-test.yaml @@ -1,3 +1,4 @@ +--- name: Build and test on: @@ -20,9 +21,9 @@ jobs: - name: Install latest ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.rust }} - profile: minimal - override: true + toolchain: ${{ matrix.rust }} + profile: minimal + override: true - name: Run cargo check uses: actions-rs/cargo@v1 diff --git a/rust/p12/.github/workflows/lint.yaml b/rust/p12/.github/workflows/lint.yaml index 440ae87b..3ccbedbf 100644 --- a/rust/p12/.github/workflows/lint.yaml +++ b/rust/p12/.github/workflows/lint.yaml @@ -1,3 +1,4 @@ +--- name: Lint on: diff --git a/rust/p12/.github/workflows/security.yaml b/rust/p12/.github/workflows/security.yaml index c4f7947a..e8e5b74f 100644 --- a/rust/p12/.github/workflows/security.yaml +++ b/rust/p12/.github/workflows/security.yaml @@ -1,3 +1,4 @@ +--- name: Security audit on: diff --git a/rust/p12/.gitignore b/rust/p12/.gitignore index 6892182e..aaf79aa6 100644 --- a/rust/p12/.gitignore +++ b/rust/p12/.gitignore @@ -1,3 +1,3 @@ /target Cargo.lock -test.p12 \ No newline at end of file +test.p12 diff --git a/rust/p12/README.md b/rust/p12/README.md index eac01c4c..6a3d1319 100644 --- a/rust/p12/README.md +++ b/rust/p12/README.md @@ -1,6 +1,6 @@ # p12 -Forked from https://github.com/hjiayz/p12/ +Forked from pure rust pkcs12 tool From fd3ab95d0ad2671b949299de124da8e1739ad9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:29:51 +0100 Subject: [PATCH 73/87] Drop unused hex dependency --- Cargo.lock | 7 ------- Cargo.nix | 19 ------------------- rust/p12/Cargo.toml | 1 - 3 files changed, 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5bec204..bf38aa0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1052,12 +1052,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hex-literal" version = "0.3.4" @@ -2110,7 +2104,6 @@ dependencies = [ "cipher", "des", "getrandom 0.2.15", - "hex", "hex-literal", "hmac", "lazy_static", diff --git a/Cargo.nix b/Cargo.nix index 926d41dc..ba8b3c44 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -3211,21 +3211,6 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; - "hex" = rec { - crateName = "hex"; - version = "0.4.3"; - edition = "2018"; - sha256 = "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z"; - authors = [ - "KokaKiwi " - ]; - features = { - "default" = [ "std" ]; - "serde" = [ "dep:serde" ]; - "std" = [ "alloc" ]; - }; - resolvedDefaultFeatures = [ "alloc" "default" "std" ]; - }; "hex-literal" = rec { crateName = "hex-literal"; version = "0.3.4"; @@ -6810,10 +6795,6 @@ rec { } ]; devDependencies = [ - { - name = "hex"; - packageId = "hex"; - } { name = "hex-literal"; packageId = "hex-literal"; diff --git a/rust/p12/Cargo.toml b/rust/p12/Cargo.toml index ac7936d6..f5273a87 100644 --- a/rust/p12/Cargo.toml +++ b/rust/p12/Cargo.toml @@ -34,5 +34,4 @@ version = "^0.5" features = ["std"] [dev-dependencies] -hex = "^0.4.2" hex-literal = "^0.3.1" From 42d423a7da877465ccd41ee53666a68b57c53ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:31:48 +0100 Subject: [PATCH 74/87] Update Ring for rustsec advisory --- Cargo.lock | 4 ++-- Cargo.nix | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf38aa0e..54c89b47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,9 +2485,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", diff --git a/Cargo.nix b/Cargo.nix index ba8b3c44..f91d4a7f 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -7928,10 +7928,10 @@ rec { }; "ring" = rec { crateName = "ring"; - version = "0.17.11"; + version = "0.17.14"; edition = "2021"; - links = "ring_core_0_17_11_"; - sha256 = "0wzyhdbf71ndd14kkpyj2a6nvczvli2mndzv2al7r26k4yp4jlys"; + links = "ring_core_0_17_14_"; + sha256 = "1dw32gv19ccq4hsx3ribhpdzri1vnrlcfqb2vj41xn4l49n9ws54"; dependencies = [ { name = "cfg-if"; From 70a809c39abb877d75c1647a9370a546ef1438aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 04:37:13 +0100 Subject: [PATCH 75/87] Ignore RUSTSEC-2025-0012 for now --- deny.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/deny.toml b/deny.toml index 2c0138d0..b256f323 100644 --- a/deny.toml +++ b/deny.toml @@ -29,6 +29,15 @@ ignore = [ # # TODO: Remove after https://github.com/kube-rs/kube/pull/1652 is merged "RUSTSEC-2024-0384", + + # https://rustsec.org/advisories/RUSTSEC-2025-0012 + # "backoff" is unmainted. + # + # Upstream (kube) has switched to backon in 0.99.0, and an upgrade is scheduled on our end. In the meantime, + # this is a very low-severity problem. + # + # TODO: Remove after upgrading to kube 0.99. + "RUSTSEC-2025-0012", ] [bans] From 0762b445eddc2f535922235aca890ab068c8945c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 10:20:34 +0100 Subject: [PATCH 76/87] Fix broken doclinks --- rust/operator-binary/src/backend/mod.rs | 2 ++ rust/operator-binary/src/crd.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/operator-binary/src/backend/mod.rs b/rust/operator-binary/src/backend/mod.rs index be9e2ca1..e81dbaad 100644 --- a/rust/operator-binary/src/backend/mod.rs +++ b/rust/operator-binary/src/backend/mod.rs @@ -25,6 +25,8 @@ use stackable_operator::{ pub use tls::TlsGenerate; use self::pod_info::SchedulingPodInfo; +#[cfg(doc)] +use crate::crd::TrustStore; use crate::format::{SecretData, SecretFormat}; /// Configuration provided by the `Volume` selecting what secret data should be provided diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index f409969a..733d19c5 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -147,7 +147,7 @@ impl SearchNamespace { /// Returns [`Some`] if this `SearchNamespace` could possibly match an object in the namespace /// `object_namespace`, otherwise [`None`]. /// - /// This is optimistic, you then need to call [`SearchMatchCondition::matches_pod_namespace`] + /// This is optimistic, you then need to call [`SearchNamespaceMatchCondition::matches_pod_namespace`] /// to evaluate the match for a specific pod's namespace. pub fn matches_namespace( &self, @@ -165,7 +165,7 @@ impl SearchNamespace { } /// A partially evaluated match returned by [`SearchNamespace::matches_namespace`]. -/// Use [`matches_pod_namespace`] to evaluate fully. +/// Use [`Self::matches_pod_namespace`] to evaluate fully. #[derive(Debug)] pub enum SearchNamespaceMatchCondition { /// The target object matches the search namespace. From cb9873a73cf303111b7bc5444df5449aded11c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Tue, 18 Mar 2025 10:37:33 +0100 Subject: [PATCH 77/87] Switch p12 to use the workspace version (to unbreak our PR build script) --- Cargo.lock | 2 +- Cargo.nix | 2 +- rust/p12/Cargo.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54c89b47..52c53dbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2098,7 +2098,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "p12" -version = "0.6.3" +version = "0.0.0-dev" dependencies = [ "cbc", "cipher", diff --git a/Cargo.nix b/Cargo.nix index f91d4a7f..1be13496 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -6746,7 +6746,7 @@ rec { }; "p12" = rec { crateName = "p12"; - version = "0.6.3"; + version = "0.0.0-dev"; edition = "2021"; src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/p12; }; authors = [ diff --git a/rust/p12/Cargo.toml b/rust/p12/Cargo.toml index f5273a87..21706521 100644 --- a/rust/p12/Cargo.toml +++ b/rust/p12/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "p12" -version = "0.6.3" +version.workspace = true authors = ["hjiayz ", "Marc-Antoine Perennou "] edition = "2021" keywords = ["pkcs12", "pkcs"] -description = "pure rust pkcs12 tool" +description = "pure rust pkcs12 tool (Stackable fork)" homepage = "https://github.com/hjiayz/p12" repository = "https://github.com/hjiayz/p12" readme = "README.md" From 676965971300a05e84b325ef2b834f3bb734c6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 10 Apr 2025 12:27:22 +0200 Subject: [PATCH 78/87] Switch truststore.adoc to line-per-sentence --- docs/modules/secret-operator/pages/truststore.adoc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/modules/secret-operator/pages/truststore.adoc b/docs/modules/secret-operator/pages/truststore.adoc index cd79e5b0..0250b5f5 100644 --- a/docs/modules/secret-operator/pages/truststore.adoc +++ b/docs/modules/secret-operator/pages/truststore.adoc @@ -18,6 +18,5 @@ include::example$truststore-tls.yaml[] This will create a ConfigMap named `truststore-pem` containing a `ca.crt` with the trust root certificates. It can then either be mounted into a Pod or retrieved and used from outside of Kubernetes. -NOTE: Make sure to have a procedure for updating the retrieved certificates. The Secret Operator will automatically rotate - the xref:secretclass.adoc#backend-autotls[autoTls] certificate authority as needed, but all trust roots will require - some form of update occasionally. +NOTE: Make sure to have a procedure for updating the retrieved certificates. + The Secret Operator will automatically rotate the xref:secretclass.adoc#backend-autotls[autoTls] certificate authority as needed, but all trust roots will require some form of update occasionally. From b969a7613b8daedf9e0aa823730a4a4fba137933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 10 Apr 2025 14:11:31 +0200 Subject: [PATCH 79/87] Normalize FULL_CONTROLLER_NAME --- Cargo.lock | 1 + Cargo.nix | 4 ++++ rust/operator-binary/Cargo.toml | 1 + rust/operator-binary/src/truststore_controller.rs | 10 +++------- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52c53dbd..7fac0e4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2974,6 +2974,7 @@ dependencies = [ "async-trait", "built", "clap", + "const_format", "futures 0.3.31", "h2", "kube-runtime", diff --git a/Cargo.nix b/Cargo.nix index 1be13496..165c0d1b 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -9529,6 +9529,10 @@ rec { name = "clap"; packageId = "clap"; } + { + name = "const_format"; + packageId = "const_format"; + } { name = "futures"; packageId = "futures 0.3.31"; diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 6549daf4..ef0e3ea3 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -41,6 +41,7 @@ tracing.workspace = true uuid.workspace = true yasna.workspace = true rand.workspace = true +const_format = "0.2.34" [dev-dependencies] serde_yaml.workspace = true diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 2f401f08..b09ba4a8 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; +use const_format::concatcp; use futures::StreamExt; use kube_runtime::{ events::{Recorder, Reporter}, @@ -36,7 +37,7 @@ use crate::{ }; const CONTROLLER_NAME: &str = "truststore"; -const FULL_CONTROLLER_NAME: &str = "truststore.secrets.stackable.tech"; +const FULL_CONTROLLER_NAME: &str = concatcp!(CONTROLLER_NAME, ".", OPERATOR_NAME); pub async fn start(client: &stackable_operator::client::Client, watch_namespace: &WatchNamespace) { let (secretclasses, secretclasses_writer) = reflector::store(); @@ -108,12 +109,7 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: .for_each_concurrent(16, move |res| { let event_recorder = event_recorder.clone(); async move { - report_controller_reconciled( - &event_recorder, - &format!("{CONTROLLER_NAME}.{OPERATOR_NAME}"), - &res, - ) - .await + report_controller_reconciled(&event_recorder, FULL_CONTROLLER_NAME, &res).await } }) .await; From dfe6427e773bc72c2e2ae533a0060d074ac35792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Thu, 10 Apr 2025 16:34:49 +0200 Subject: [PATCH 80/87] Propagate `secondary_object` information through backends This is currently only used by the TrustStore controller, but would also be a step towards exposing the same info for the CSI endpoints in the future. --- .../src/backend/cert_manager.rs | 15 +++ rust/operator-binary/src/backend/dynamic.rs | 18 +++ .../operator-binary/src/backend/k8s_search.rs | 12 ++ .../src/backend/kerberos_keytab.rs | 25 +++- rust/operator-binary/src/backend/mod.rs | 44 +++++-- rust/operator-binary/src/backend/pod_info.rs | 118 ++++++++++-------- rust/operator-binary/src/backend/tls/ca.rs | 46 +++++-- rust/operator-binary/src/backend/tls/mod.rs | 17 +++ .../src/truststore_controller.rs | 49 ++++++-- 9 files changed, 260 insertions(+), 84 deletions(-) diff --git a/rust/operator-binary/src/backend/cert_manager.rs b/rust/operator-binary/src/backend/cert_manager.rs index 613a0a1b..b8b07d4c 100644 --- a/rust/operator-binary/src/backend/cert_manager.rs +++ b/rust/operator-binary/src/backend/cert_manager.rs @@ -76,6 +76,21 @@ impl SecretBackendError for Error { Error::TrustExportUnsupported => tonic::Code::FailedPrecondition, } } + + fn secondary_object(&self) -> Option> { + match self { + Error::NoPvcName => None, + Error::ScopeAddresses { source, .. } => source.secondary_object(), + Error::GetSecret { secret, .. } => Some(secret.clone().erase()), + Error::ApplyCertManagerCertificate { certificate, .. } => { + Some(certificate.clone().erase()) + } + Error::GetCertManagerCertificate { certificate, .. } => { + Some(certificate.clone().erase()) + } + Error::TrustExportUnsupported => None, + } + } } #[derive(Debug)] diff --git a/rust/operator-binary/src/backend/dynamic.rs b/rust/operator-binary/src/backend/dynamic.rs index ac0b74d8..06cf7e20 100644 --- a/rust/operator-binary/src/backend/dynamic.rs +++ b/rust/operator-binary/src/backend/dynamic.rs @@ -41,6 +41,9 @@ impl SecretBackendError for DynError { fn grpc_code(&self) -> tonic::Code { self.0.grpc_code() } + fn secondary_object(&self) -> Option> { + self.0.secondary_object() + } } pub struct DynamicAdapter(B); @@ -113,6 +116,12 @@ impl SecretBackendError for FromClassError { FromClassError::KerberosKeytab { source } => source.grpc_code(), } } + fn secondary_object(&self) -> Option> { + match self { + FromClassError::Tls { source } => source.secondary_object(), + FromClassError::KerberosKeytab { source } => source.secondary_object(), + } + } } pub async fn from_class( @@ -188,6 +197,15 @@ impl SecretBackendError for FromSelectorError { FromSelectorError::FromClass { source, .. } => source.grpc_code(), } } + + fn secondary_object(&self) -> Option> { + match self { + FromSelectorError::GetSecretClass { class, .. } => Some(class.clone().erase()), + FromSelectorError::FromClass { source, class } => source + .secondary_object() + .or_else(|| Some(class.clone().erase())), + } + } } pub async fn from_selector( diff --git a/rust/operator-binary/src/backend/k8s_search.rs b/rust/operator-binary/src/backend/k8s_search.rs index d53fdd64..68c900ea 100644 --- a/rust/operator-binary/src/backend/k8s_search.rs +++ b/rust/operator-binary/src/backend/k8s_search.rs @@ -71,6 +71,18 @@ impl SecretBackendError for Error { Error::GetTrustStore { .. } => tonic::Code::Internal, } } + + fn secondary_object(&self) -> Option> { + match self { + Error::SecretSelector { .. } => None, + Error::SecretQuery { .. } => None, + Error::NoSecret { .. } => None, + Error::NoListener { .. } => None, + Error::BuildLabel { .. } => None, + Error::NoTrustStore => None, + Error::GetTrustStore { configmap, .. } => Some(configmap.clone().erase()), + } + } } #[derive(Debug)] diff --git a/rust/operator-binary/src/backend/kerberos_keytab.rs b/rust/operator-binary/src/backend/kerberos_keytab.rs index 53f81ba4..2d537733 100644 --- a/rust/operator-binary/src/backend/kerberos_keytab.rs +++ b/rust/operator-binary/src/backend/kerberos_keytab.rs @@ -64,8 +64,8 @@ pub enum Error { #[snafu(display("generated invalid Kerberos principal for pod"))] PodPrincipal { source: InvalidKerberosPrincipal }, - #[snafu(display("failed to read keytab"))] - ReadKeytab { source: std::io::Error }, + #[snafu(display("failed to read the provisioned keytab"))] + ReadProvisionedKeytab { source: std::io::Error }, #[snafu(display("the kerberosKeytab backend does not currently support TrustStore exports"))] TrustExportUnsupported, @@ -80,11 +80,26 @@ impl SecretBackendError for Error { Error::WriteAdminKeytab { .. } => tonic::Code::Unavailable, Error::ProvisionKeytab { .. } => tonic::Code::Unavailable, Error::PodPrincipal { .. } => tonic::Code::FailedPrecondition, - Error::ReadKeytab { .. } => tonic::Code::Unavailable, + Error::ReadProvisionedKeytab { .. } => tonic::Code::Unavailable, Error::ScopeAddresses { .. } => tonic::Code::Unavailable, Error::TrustExportUnsupported => tonic::Code::FailedPrecondition, } } + + fn secondary_object(&self) -> Option> { + match self { + Error::ScopeAddresses { source, .. } => source.secondary_object(), + Error::LoadAdminKeytab { secret, .. } => Some(secret.clone().erase()), + Error::NoAdminKeytabKeyInSecret { secret } => Some(secret.clone().erase()), + Error::TempSetup { .. } => None, + Error::WriteConfig { .. } => None, + Error::WriteAdminKeytab { .. } => None, + Error::ProvisionKeytab { .. } => None, + Error::PodPrincipal { .. } => None, + Error::ReadProvisionedKeytab { .. } => None, + Error::TrustExportUnsupported => None, + } + } } #[derive(Debug)] @@ -275,11 +290,11 @@ cluster.local = {realm_name} let mut keytab_data = Vec::new(); let mut keytab_file = File::open(keytab_file_path) .await - .context(ReadKeytabSnafu)?; + .context(ReadProvisionedKeytabSnafu)?; keytab_file .read_to_end(&mut keytab_data) .await - .context(ReadKeytabSnafu)?; + .context(ReadProvisionedKeytabSnafu)?; Ok(SecretContents::new(SecretData::WellKnown( WellKnownSecretData::Kerberos(well_known::Kerberos { keytab: keytab_data, diff --git a/rust/operator-binary/src/backend/mod.rs b/rust/operator-binary/src/backend/mod.rs index e81dbaad..40ebf71b 100644 --- a/rust/operator-binary/src/backend/mod.rs +++ b/rust/operator-binary/src/backend/mod.rs @@ -14,12 +14,15 @@ use async_trait::async_trait; pub use cert_manager::CertManager; pub use k8s_search::K8sSearch; pub use kerberos_keytab::KerberosKeytab; +use kube_runtime::reflector::ObjectRef; use pod_info::Address; use scope::SecretScope; use serde::{de::Unexpected, Deserialize, Deserializer, Serialize}; use snafu::{OptionExt, Snafu}; use stackable_operator::{ + commons::listener::PodListeners, k8s_openapi::chrono::{DateTime, FixedOffset}, + kube::api::DynamicObject, time::Duration, }; pub use tls::TlsGenerate; @@ -175,8 +178,25 @@ fn default_cert_jitter_factor() -> f64 { #[derive(Snafu, Debug)] #[snafu(module)] pub enum ScopeAddressesError { - #[snafu(display("no addresses found for listener {listener}"))] - NoListenerAddresses { listener: String }, + #[snafu(display( + "listener addresses were not fetched (this is likely a bug in secret-operator)" + ))] + ListenerAddressesNotFetched, + + #[snafu(display("no addresses found for listener {listener} on {pod_listeners}"))] + NoListenerAddresses { + listener: String, + pod_listeners: ObjectRef, + }, +} + +impl ScopeAddressesError { + pub fn secondary_object(&self) -> Option> { + match self { + Self::ListenerAddressesNotFetched => None, + Self::NoListenerAddresses { pod_listeners, .. } => Some(pod_listeners.clone().erase()), + } + } } impl SecretVolumeSelector { @@ -212,11 +232,17 @@ impl SecretVolumeSelector { scope::SecretScope::Service { name } => vec![Address::Dns(format!( "{name}.{namespace}.svc.{cluster_domain}", ))], - scope::SecretScope::ListenerVolume { name } => pod_info - .listener_addresses - .get(name) - .context(NoListenerAddressesSnafu { listener: name })? - .to_vec(), + scope::SecretScope::ListenerVolume { name } => match &pod_info.listener_addresses { + Some(listener_addresses) => listener_addresses + .by_listener_volume_name + .get(name) + .with_context(|| NoListenerAddressesSnafu { + listener: name, + pod_listeners: listener_addresses.source.clone(), + })? + .to_vec(), + None => return ListenerAddressesNotFetchedSnafu.fail(), + }, }) } @@ -301,10 +327,14 @@ pub trait SecretBackend: Debug + Send + Sync { pub trait SecretBackendError: std::error::Error + Send + Sync + 'static { fn grpc_code(&self) -> tonic::Code; + fn secondary_object(&self) -> Option>; } impl SecretBackendError for Infallible { fn grpc_code(&self) -> tonic::Code { match *self {} } + fn secondary_object(&self) -> Option> { + match *self {} + } } diff --git a/rust/operator-binary/src/backend/pod_info.rs b/rust/operator-binary/src/backend/pod_info.rs index 8969e2ba..a9caa0d3 100644 --- a/rust/operator-binary/src/backend/pod_info.rs +++ b/rust/operator-binary/src/backend/pod_info.rs @@ -6,6 +6,7 @@ use std::{ }; use futures::{StreamExt, TryStreamExt}; +use kube_runtime::reflector::Lookup; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ commons::{ @@ -109,7 +110,7 @@ pub struct PodInfo { pub service_name: Option, pub node_name: String, pub node_ips: Vec, - pub listener_addresses: HashMap>, + pub listener_addresses: Option, pub kubernetes_cluster_domain: DomainName, pub scheduling: SchedulingPodInfo, } @@ -134,10 +135,10 @@ impl PodInfo { })?; let scheduling = SchedulingPodInfo::from_pod(client, &pod, scopes).await?; let listener_addresses = if !scheduling.volume_listener_names.is_empty() { - pod_listener_addresses(client, &pod, &scheduling, scopes).await? + Some(ListenerAddresses::fetch_for_pod(client, &pod, &scheduling, scopes).await?) } else { // We don't care about the listener addresses if there is no listener scope, so we can save the API call - HashMap::new() + None }; Ok(Self { // This will generally be empty, since Kubernetes assigns pod IPs *after* CSI plugins are successful @@ -335,55 +336,66 @@ async fn listener_pvc_is_node_scoped( Ok(listener_class.spec.service_type == ServiceType::NodePort) } -async fn pod_listener_addresses( - client: &stackable_operator::client::Client, - pod: &Pod, - pod_info: &SchedulingPodInfo, - scopes: &[SecretScope], -) -> Result>, FromPodError> { - use from_pod_error::*; - let pod_listeners_name = format!( - "pod-{}", - pod.metadata.uid.as_deref().context(NoPodUidSnafu)? - ); - let listeners = client - .get::(&pod_listeners_name, &pod_info.namespace) - .await - .context(GetPodListenersSnafu { - pod_listeners: ObjectRef::::new(&pod_listeners_name) - .within(&pod_info.namespace), - pod: ObjectRef::from_obj(pod), - })?; - let listeners_ref = ObjectRef::from_obj(&listeners); - scopes - .iter() - .filter_map(|scope| match scope { - SecretScope::ListenerVolume { name } => Some(name), - _ => None, - }) - .map(|listener| { - let addresses = listeners - .spec - .listeners - .get(listener) - .and_then(|ingresses| ingresses.ingress_addresses.as_ref()) - .context(NoPodListenerAddressesSnafu { - pod_listeners: listeners_ref.clone(), - listener, - })?; - Ok(( - listener.clone(), - addresses - .iter() - .map(|ingr| { - (ingr.address_type, &*ingr.address).try_into().context( - IllegalAddressSnafu { - address: &ingr.address, - }, - ) - }) - .collect::, FromPodError>>()?, - )) +#[derive(Debug)] +pub struct ListenerAddresses { + pub source: ObjectRef, + pub by_listener_volume_name: HashMap>, +} + +impl ListenerAddresses { + async fn fetch_for_pod( + client: &stackable_operator::client::Client, + pod: &Pod, + pod_info: &SchedulingPodInfo, + scopes: &[SecretScope], + ) -> Result { + use from_pod_error::*; + let pod_listeners_name = format!( + "pod-{}", + pod.metadata.uid.as_deref().context(NoPodUidSnafu)? + ); + let pod_listeners = client + .get::(&pod_listeners_name, &pod_info.namespace) + .await + .context(GetPodListenersSnafu { + pod_listeners: ObjectRef::::new(&pod_listeners_name) + .within(&pod_info.namespace), + pod: ObjectRef::from_obj(pod), + })?; + let listeners_ref = ObjectRef::from_obj(&pod_listeners); + Ok(ListenerAddresses { + source: pod_listeners.to_object_ref(()), + by_listener_volume_name: scopes + .iter() + .filter_map(|scope| match scope { + SecretScope::ListenerVolume { name } => Some(name), + _ => None, + }) + .map(|listener| { + let addresses = pod_listeners + .spec + .listeners + .get(listener) + .and_then(|ingresses| ingresses.ingress_addresses.as_ref()) + .context(NoPodListenerAddressesSnafu { + pod_listeners: listeners_ref.clone(), + listener, + })?; + Ok(( + listener.clone(), + addresses + .iter() + .map(|ingr| { + (ingr.address_type, &*ingr.address).try_into().context( + IllegalAddressSnafu { + address: &ingr.address, + }, + ) + }) + .collect::, FromPodError>>()?, + )) + }) + .collect::, FromPodError>>()?, }) - .collect::, FromPodError>>() + } } diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 7ff1f3bb..78b78ad5 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -2,6 +2,7 @@ use std::{collections::BTreeMap, fmt::Display}; +use kube_runtime::reflector::Lookup; use openssl::{ asn1::{Asn1Integer, Asn1Time}, bn::{BigNum, MsbOption}, @@ -116,13 +117,31 @@ impl SecretBackendError for Error { Error::SaveRequestedButForbidden { .. } => tonic::Code::FailedPrecondition, } } + + fn secondary_object(&self) -> Option> { + match self { + Error::GenerateKey { .. } => None, + Error::FindCa { secret, .. } => Some(secret.clone().erase()), + Error::CaNotFoundAndGenDisabled { secret } => Some(secret.clone().erase()), + Error::MissingCertificate { secret, .. } => Some(secret.clone().erase()), + Error::LoadCertificate { secret, .. } => Some(secret.clone().erase()), + Error::ParseLifetime { secret, .. } => Some(secret.clone().erase()), + Error::BuildCertificate { .. } => None, + Error::SerializeCertificate { .. } => None, + Error::SaveCaCertificate { secret, .. } => Some(secret.clone().erase()), + Error::SaveRequestedButForbidden => None, + } + } } #[derive(Debug, Snafu)] #[snafu(module)] pub enum GetCaError { - #[snafu(display("No CA will live until at least {cutoff}"))] - NoCaLivesLongEnough { cutoff: OffsetDateTime }, + #[snafu(display("no CA in {secret} will live until at least {cutoff}"))] + NoCaLivesLongEnough { + cutoff: OffsetDateTime, + secret: ObjectRef, + }, } impl SecretBackendError for GetCaError { @@ -131,6 +150,12 @@ impl SecretBackendError for GetCaError { GetCaError::NoCaLivesLongEnough { .. } => tonic::Code::FailedPrecondition, } } + + fn secondary_object(&self) -> Option> { + match self { + GetCaError::NoCaLivesLongEnough { secret, .. } => Some(secret.clone().erase()), + } + } } #[derive(Debug)] @@ -293,6 +318,7 @@ impl CertificateAuthority { /// Manages multiple [`CertificateAuthorities`](`CertificateAuthority`), rotating them as needed. #[derive(Debug)] pub struct Manager { + source_secret: ObjectRef, certificate_authorities: Vec, } @@ -304,7 +330,7 @@ impl Manager { ) -> Result { // Use entry API rather than apply so that we crash and retry on conflicts (to avoid creating spurious certs that we throw away immediately) let secrets_api = &client.get_api::(&secret_ref.namespace); - let ca_secret = secrets_api + let mut ca_secret = secrets_api .entry(&secret_ref.name) .await .with_context(|_| FindCaSnafu { secret: secret_ref })?; @@ -405,8 +431,8 @@ impl Manager { info!(secret = %secret_ref, "CA has been modified, saving"); // Sort CAs by age to avoid spurious writes certificate_authorities.sort_by_key(|ca| ca.not_after); - let mut ca_secret = ca_secret.or_insert(Secret::default); - ca_secret.get_mut().data = Some( + let mut occupied_ca_secret = ca_secret.or_insert(Secret::default); + occupied_ca_secret.get_mut().data = Some( certificate_authorities .iter() .enumerate() @@ -434,16 +460,21 @@ impl Manager { }) .collect::>()?, ); - ca_secret + occupied_ca_secret .commit(&PostParams::default()) .await .context(SaveCaCertificateSnafu { secret: secret_ref })?; + ca_secret = Entry::Occupied(occupied_ca_secret); } else { return SaveRequestedButForbiddenSnafu.fail(); } } Ok(Self { certificate_authorities, + source_secret: ca_secret + .get() + .map(|secret| secret.to_object_ref(())) + .unwrap_or_else(|| secret_ref.into()), }) } @@ -458,8 +489,9 @@ impl Manager { .filter(|ca| ca.not_after > valid_until_at_least) // pick the oldest valid CA, since it will be trusted by the most peers .min_by_key(|ca| ca.not_after) - .context(NoCaLivesLongEnoughSnafu { + .with_context(|| NoCaLivesLongEnoughSnafu { cutoff: valid_until_at_least, + secret: self.source_secret.clone(), }) } diff --git a/rust/operator-binary/src/backend/tls/mod.rs b/rust/operator-binary/src/backend/tls/mod.rs index 8f725acc..609ee4a7 100644 --- a/rust/operator-binary/src/backend/tls/mod.rs +++ b/rust/operator-binary/src/backend/tls/mod.rs @@ -125,6 +125,23 @@ impl SecretBackendError for Error { Error::JitterOutOfRange { .. } => tonic::Code::InvalidArgument, } } + + fn secondary_object( + &self, + ) -> Option> + { + match self { + Error::ScopeAddresses { source, .. } => source.secondary_object(), + Error::GenerateKey { .. } => None, + Error::LoadCa { source } => source.secondary_object(), + Error::PickCa { source } => source.secondary_object(), + Error::BuildCertificate { .. } => None, + Error::SerializeCertificate { .. } => None, + Error::InvalidCertLifetime { .. } => None, + Error::TooShortCertLifetimeRequiresTimeTravel { .. } => None, + Error::JitterOutOfRange { .. } => None, + } + } } #[derive(Debug)] diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index b09ba4a8..24a1f7a6 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -4,6 +4,7 @@ use const_format::concatcp; use futures::StreamExt; use kube_runtime::{ events::{Recorder, Reporter}, + reflector::Lookup, WatchStreamExt as _, }; use snafu::{OptionExt as _, ResultExt as _, Snafu}; @@ -29,7 +30,7 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - backend::{self, TrustSelector}, + backend::{self, SecretBackendError, TrustSelector}, crd::{SearchNamespaceMatchCondition, SecretClass, TrustStore}, format::{self, well_known::CompatibilityOptions}, utils::Flattened, @@ -170,14 +171,16 @@ pub enum Error { source: error_boundary::InvalidObject, }, - #[snafu(display("failed to get SecretClass for TrustStore"))] + #[snafu(display("failed to get {secret_class} for TrustStore"))] GetSecretClass { source: stackable_operator::client::Error, + secret_class: ObjectRef, }, - #[snafu(display("failed to initialize SecretClass backend"))] + #[snafu(display("failed to initialize SecretClass backend for {secret_class}"))] InitBackend { source: backend::dynamic::FromClassError, + secret_class: ObjectRef, }, #[snafu(display("failed to get trust data from backend"))] @@ -187,16 +190,20 @@ pub enum Error { NoTrustStoreNamespace, #[snafu(display("failed to convert trust data into desired format"))] - FormatData { source: format::IntoFilesError }, + FormatData { + source: format::IntoFilesError, + secret_class: ObjectRef, + }, #[snafu(display("failed to build owner reference to the TrustStore"))] BuildOwnerReference { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("failed to apply ConfigMap for the TrustStore"))] + #[snafu(display("failed to apply target {config_map} for the TrustStore"))] ApplyTrustStoreConfigMap { source: stackable_operator::client::Error, + config_map: ObjectRef, }, } type Result = std::result::Result; @@ -206,8 +213,16 @@ impl ReconcilerError for Error { } fn secondary_object(&self) -> Option> { - // TODO - None + match self { + Error::InvalidTrustStore { .. } => None, + Error::GetSecretClass { secret_class, .. } => Some(secret_class.clone().erase()), + Error::InitBackend { secret_class, .. } => Some(secret_class.clone().erase()), + Error::BackendGetTrustData { source } => source.secondary_object(), + Error::NoTrustStoreNamespace => None, + Error::FormatData { secret_class, .. } => Some(secret_class.clone().erase()), + Error::BuildOwnerReference { .. } => None, + Error::ApplyTrustStoreConfigMap { config_map, .. } => Some(config_map.clone().erase()), + } } } @@ -224,14 +239,20 @@ async fn reconcile( .as_ref() .map_err(error_boundary::InvalidObject::clone) .context(InvalidTrustStoreSnafu)?; + let secret_class_name = &truststore.spec.secret_class_name; let secret_class = ctx .client - .get::(&truststore.spec.secret_class_name, &()) + .get::(secret_class_name, &()) .await - .context(GetSecretClassSnafu)?; + .context(GetSecretClassSnafu { + secret_class: ObjectRef::::new(secret_class_name), + })?; + let secret_class_ref = secret_class.to_object_ref(()); let backend = backend::dynamic::from_class(&ctx.client, secret_class) .await - .context(InitBackendSnafu)?; + .with_context(|_| InitBackendSnafu { + secret_class: secret_class_ref.clone(), + })?; let selector = TrustSelector { namespace: truststore .metadata @@ -246,7 +267,9 @@ async fn reconcile( let (Flattened(string_data), Flattened(binary_data)) = trust_data .data .into_files(truststore.spec.format, &CompatibilityOptions::default()) - .context(FormatDataSnafu)? + .context(FormatDataSnafu { + secret_class: secret_class_ref, + })? .into_iter() // Try to put valid UTF-8 data into `data`, but fall back to `binary_data` otherwise .map(|(k, v)| match String::from_utf8(v) { @@ -267,7 +290,9 @@ async fn reconcile( ctx.client .apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm) .await - .context(ApplyTrustStoreConfigMapSnafu)?; + .context(ApplyTrustStoreConfigMapSnafu { + config_map: &trust_cm, + })?; Ok(controller::Action::await_change()) } From ca0326f8b21ec3b8ed94cb4e637e82b83df0e638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 11 Apr 2025 17:02:47 +0200 Subject: [PATCH 81/87] Clarify error messages when loading CAs --- rust/operator-binary/src/backend/tls/ca.rs | 52 +++++++++++----------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 74427e4d..c943ed78 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -59,16 +59,16 @@ pub enum Error { #[snafu(display("failed to generate certificate key"))] GenerateKey { source: openssl::error::ErrorStack }, - #[snafu(display("failed to load {config_map}"))] - FindConfigMap { + #[snafu(display("failed to load CA from {secret}"))] + FindCertificateAuthority { source: kube::Error, - config_map: ObjectRef, + secret: ObjectRef, }, - #[snafu(display("failed to load {secret}"))] - FindSecret { - source: kube::Error, - secret: ObjectRef, + #[snafu(display("failed to load extra trust root from {object}"))] + FindExtraTrustRoot { + source: stackable_operator::client::Error, + object: ObjectRef, }, #[snafu(display("CA {secret} does not exist, and autoGenerate is false"))] @@ -124,8 +124,8 @@ impl SecretBackendError for Error { match self { Error::GenerateKey { .. } => tonic::Code::Internal, Error::MissingCertificate { .. } => tonic::Code::FailedPrecondition, - Error::FindConfigMap { .. } => tonic::Code::Unavailable, - Error::FindSecret { .. } => tonic::Code::Unavailable, + Error::FindCertificateAuthority { .. } => tonic::Code::Unavailable, + Error::FindExtraTrustRoot { .. } => tonic::Code::Unavailable, Error::CaNotFoundAndGenDisabled { .. } => tonic::Code::FailedPrecondition, Error::LoadCertificate { .. } => tonic::Code::FailedPrecondition, Error::UnsupportedCertificateFormat { .. } => tonic::Code::InvalidArgument, @@ -140,12 +140,12 @@ impl SecretBackendError for Error { fn secondary_object(&self) -> Option> { match self { Error::GenerateKey { .. } => None, - Error::FindConfigMap { config_map, .. } => Some(config_map.clone().erase()), - Error::FindSecret { secret, .. } => Some(secret.clone().erase()), + Error::FindCertificateAuthority { secret, .. } => Some(secret.clone().erase()), + Error::FindExtraTrustRoot { object, .. } => Some(object.clone()), Error::CaNotFoundAndGenDisabled { secret } => Some(secret.clone().erase()), Error::MissingCertificate { secret, .. } => Some(secret.clone().erase()), - Error::LoadCertificate { object, .. } => Some(object.clone().erase()), - Error::UnsupportedCertificateFormat { object, .. } => Some(object.clone().erase()), + Error::LoadCertificate { object, .. } => Some(object.clone()), + Error::UnsupportedCertificateFormat { object, .. } => Some(object.clone()), Error::ParseLifetime { secret, .. } => Some(secret.clone().erase()), Error::BuildCertificate { .. } => None, Error::SerializeCertificate { .. } => None, @@ -356,7 +356,7 @@ impl Manager { let mut ca_secret = secrets_api .entry(&secret_ref.name) .await - .with_context(|_| FindSecretSnafu { secret: secret_ref })?; + .with_context(|_| FindCertificateAuthoritySnafu { secret: secret_ref })?; let mut update_ca_secret = false; let mut certificate_authorities = match &ca_secret { Entry::Occupied(ca_secret) => { @@ -497,10 +497,10 @@ impl Manager { for entry in additional_trust_roots { let certs = match entry { AdditionalTrustRoot::ConfigMap(config_map) => { - Self::read_certificates_from_config_map(client, config_map).await? + Self::read_extra_trust_roots_from_config_map(client, config_map).await? } AdditionalTrustRoot::Secret(secret) => { - Self::read_certificates_from_secret(client, secret).await? + Self::read_extra_trust_roots_from_secret(client, secret).await? } }; additional_trusted_certificates.extend(certs); @@ -520,18 +520,17 @@ impl Manager { /// /// The keys are assumed to be filenames and their extensions denote the expected format of the /// certificate. - async fn read_certificates_from_config_map( + async fn read_extra_trust_roots_from_config_map( client: &stackable_operator::client::Client, config_map_ref: &ConfigMapReference, ) -> Result> { let mut certificates = vec![]; - let config_map_api = &client.get_api::(&config_map_ref.namespace); - let config_map = config_map_api - .get(&config_map_ref.name) + let config_map = client + .get::(&config_map_ref.name, &config_map_ref.namespace) .await - .with_context(|_| FindConfigMapSnafu { - config_map: config_map_ref, + .context(FindExtraTrustRootSnafu { + object: config_map_ref, })?; let config_map_data = config_map.data.unwrap_or_default(); @@ -562,17 +561,16 @@ impl Manager { /// /// The keys are assumed to be filenames and their extensions denote the expected format of the /// certificate. - async fn read_certificates_from_secret( + async fn read_extra_trust_roots_from_secret( client: &stackable_operator::client::Client, secret_ref: &SecretReference, ) -> Result> { let mut certificates = vec![]; - let secrets_api = &client.get_api::(&secret_ref.namespace); - let secret = secrets_api - .get(&secret_ref.name) + let secret = client + .get::(&secret_ref.name, &secret_ref.namespace) .await - .with_context(|_| FindSecretSnafu { secret: secret_ref })?; + .context(FindExtraTrustRootSnafu { object: secret_ref })?; let secret_data = secret.data.unwrap_or_default(); for (key, ByteString(value)) in &secret_data { From a90f8ccdbd71f58f05d10ed099b0ac8f7f6b43e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 11 Apr 2025 17:33:14 +0200 Subject: [PATCH 82/87] Only erase extra trust root refs lazily --- rust/operator-binary/src/backend/tls/ca.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index c943ed78..cf951ad0 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -544,7 +544,7 @@ impl Manager { .map(|(key, ByteString(value))| (key, value.as_ref())), ); for (key, value) in data { - let certs = Self::deserialize_certificate(key, value, config_map_ref.into())?; + let certs = Self::deserialize_certificate(key, value, config_map_ref)?; info!( ?certs, %config_map_ref, @@ -574,7 +574,7 @@ impl Manager { let secret_data = secret.data.unwrap_or_default(); for (key, ByteString(value)) in &secret_data { - let certs = Self::deserialize_certificate(key, value, secret_ref.into())?; + let certs = Self::deserialize_certificate(key, value, secret_ref)?; info!( ?certs, %secret_ref, @@ -592,7 +592,7 @@ impl Manager { fn deserialize_certificate( key: &str, value: &[u8], - object_ref: ObjectRef, + object_ref: impl Into>, ) -> Result> { let extension = Path::new(key).extension().and_then(OsStr::to_str); From 8b3645f478a17ed35ff613c835fdfc0cbb5832e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Fri, 11 Apr 2025 17:41:45 +0200 Subject: [PATCH 83/87] rustfmt --- .../src/active_directory.rs | 9 +- rust/operator-binary/src/crd.rs | 84 +++++++++---------- .../src/truststore_controller.rs | 11 +-- 3 files changed, 46 insertions(+), 58 deletions(-) diff --git a/rust/krb5-provision-keytab/src/active_directory.rs b/rust/krb5-provision-keytab/src/active_directory.rs index f30de6b7..442c038a 100644 --- a/rust/krb5-provision-keytab/src/active_directory.rs +++ b/rust/krb5-provision-keytab/src/active_directory.rs @@ -399,12 +399,9 @@ async fn get_user_kvno( // Perform search with KVNO attribute let (search_results, _) = ldap - .search( - distinguished_name, - Scope::Base, - "(objectClass=user)", - vec!["msDS-KeyVersionNumber"], - ) + .search(distinguished_name, Scope::Base, "(objectClass=user)", vec![ + "msDS-KeyVersionNumber", + ]) .await .context(SearchLdapSnafu)? .success() diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index eeeb4cac..c613040c 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -551,26 +551,23 @@ mod test { let deserializer = serde_yaml::Deserializer::from_str(input); let secret_class: SecretClass = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - assert_eq!( - secret_class.spec, - SecretClassSpec { - backend: crate::crd::SecretClassBackend::AutoTls(AutoTlsBackend { - ca: crate::crd::AutoTlsCa { - secret: SecretReference { - name: "secret-provisioner-tls-ca".to_string(), - namespace: "default".to_string(), - }, - auto_generate: false, - ca_certificate_lifetime: DEFAULT_CA_CERT_LIFETIME, - key_generation: CertificateKeyGeneration::Rsa { - length: CertificateKeyGeneration::RSA_KEY_LENGTH_3072 - } + assert_eq!(secret_class.spec, SecretClassSpec { + backend: crate::crd::SecretClassBackend::AutoTls(AutoTlsBackend { + ca: crate::crd::AutoTlsCa { + secret: SecretReference { + name: "secret-provisioner-tls-ca".to_string(), + namespace: "default".to_string(), }, - additional_trust_roots: vec![], - max_certificate_lifetime: DEFAULT_MAX_CERT_LIFETIME, - }) - } - ); + auto_generate: false, + ca_certificate_lifetime: DEFAULT_CA_CERT_LIFETIME, + key_generation: CertificateKeyGeneration::Rsa { + length: CertificateKeyGeneration::RSA_KEY_LENGTH_3072 + } + }, + additional_trust_roots: vec![], + max_certificate_lifetime: DEFAULT_MAX_CERT_LIFETIME, + }) + }); let input: &str = r#" apiVersion: secrets.stackable.tech/v1alpha1 @@ -598,32 +595,29 @@ mod test { let deserializer = serde_yaml::Deserializer::from_str(input); let secret_class: SecretClass = serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - assert_eq!( - secret_class.spec, - SecretClassSpec { - backend: crate::crd::SecretClassBackend::AutoTls(AutoTlsBackend { - ca: crate::crd::AutoTlsCa { - secret: SecretReference { - name: "secret-provisioner-tls-ca".to_string(), - namespace: "default".to_string(), - }, - auto_generate: true, - ca_certificate_lifetime: Duration::from_days_unchecked(100), - key_generation: CertificateKeyGeneration::default() + assert_eq!(secret_class.spec, SecretClassSpec { + backend: crate::crd::SecretClassBackend::AutoTls(AutoTlsBackend { + ca: crate::crd::AutoTlsCa { + secret: SecretReference { + name: "secret-provisioner-tls-ca".to_string(), + namespace: "default".to_string(), }, - additional_trust_roots: vec![ - AdditionalTrustRoot::ConfigMap(ConfigMapReference { - name: "tls-root-ca-config-map".to_string(), - namespace: "default".to_string(), - }), - AdditionalTrustRoot::Secret(SecretReference { - name: "tls-root-ca-secret".to_string(), - namespace: "default".to_string(), - }) - ], - max_certificate_lifetime: Duration::from_days_unchecked(31), - }) - } - ); + auto_generate: true, + ca_certificate_lifetime: Duration::from_days_unchecked(100), + key_generation: CertificateKeyGeneration::default() + }, + additional_trust_roots: vec![ + AdditionalTrustRoot::ConfigMap(ConfigMapReference { + name: "tls-root-ca-config-map".to_string(), + namespace: "default".to_string(), + }), + AdditionalTrustRoot::Secret(SecretReference { + name: "tls-root-ca-secret".to_string(), + namespace: "default".to_string(), + }) + ], + max_certificate_lifetime: Duration::from_days_unchecked(31), + }) + }); } } diff --git a/rust/operator-binary/src/truststore_controller.rs b/rust/operator-binary/src/truststore_controller.rs index 89ff079f..e817d86e 100644 --- a/rust/operator-binary/src/truststore_controller.rs +++ b/rust/operator-binary/src/truststore_controller.rs @@ -50,13 +50,10 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace: watcher::Config::default(), ); let truststores = controller.store(); - let event_recorder = Arc::new(Recorder::new( - client.as_kube_client(), - Reporter { - controller: FULL_CONTROLLER_NAME.to_string(), - instance: None, - }, - )); + let event_recorder = Arc::new(Recorder::new(client.as_kube_client(), Reporter { + controller: FULL_CONTROLLER_NAME.to_string(), + instance: None, + })); controller .watches_stream( watcher( From 5d4bd01e612b9a9394e57fe4d0d4b659a26f58b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 14 Apr 2025 10:30:05 +0200 Subject: [PATCH 84/87] Update docs/modules/secret-operator/pages/secretclass.adoc Co-authored-by: Siegfried Weber --- docs/modules/secret-operator/pages/secretclass.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/secret-operator/pages/secretclass.adoc b/docs/modules/secret-operator/pages/secretclass.adoc index 5eee53d9..fb6b658e 100644 --- a/docs/modules/secret-operator/pages/secretclass.adoc +++ b/docs/modules/secret-operator/pages/secretclass.adoc @@ -453,7 +453,7 @@ spec: pod: {} # or... name: my-namespace - trustStoreConfigMapName: tls-ca # <4> + trustStoreConfigMapName: tls-ca ---- `k8sSearch`:: Declares that the `k8sSearch` backend is used. From c1932bebe519960561c81cda44ee682137712535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 14 Apr 2025 10:32:11 +0200 Subject: [PATCH 85/87] Move const_format to workspace cargo.toml --- Cargo.toml | 1 + rust/operator-binary/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 466fb9cc..28ff51b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ bindgen = "0.71" built = { version = "0.7", features = ["chrono", "git2"] } byteorder = "1.5" clap = "4.5" +const_format = "0.2.34" futures = { version = "0.3", features = ["compat"] } h2 = "0.4" kube-runtime = { version = "0.99", features = ["unstable-runtime-stream-control"] } diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index ef0e3ea3..3988919d 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -41,7 +41,7 @@ tracing.workspace = true uuid.workspace = true yasna.workspace = true rand.workspace = true -const_format = "0.2.34" +const_format.workspace = true [dev-dependencies] serde_yaml.workspace = true From 71c318def14e44f2057e2d571feb327678712d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Mon, 14 Apr 2025 10:46:48 +0200 Subject: [PATCH 86/87] Update yanked dependencies (crossbeam-channel, tokio, openssl) --- Cargo.lock | 16 ++++++++-------- Cargo.nix | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bd062d2..627b79ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,9 +611,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -2040,9 +2040,9 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags", "cfg-if", @@ -2072,9 +2072,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", @@ -3439,9 +3439,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.nix b/Cargo.nix index 501df4ca..0471b52a 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -2039,9 +2039,9 @@ rec { }; "crossbeam-channel" = rec { crateName = "crossbeam-channel"; - version = "0.5.14"; + version = "0.5.15"; edition = "2021"; - sha256 = "0wa41qybq5w8s70anb472myh4fid4aw6v65vws6wn528w9l6vfh6"; + sha256 = "1cicd9ins0fkpfgvz9vhz3m9rpkh6n8d3437c3wnfsdkd3wgif42"; libName = "crossbeam_channel"; dependencies = [ { @@ -6614,9 +6614,9 @@ rec { }; "openssl" = rec { crateName = "openssl"; - version = "0.10.71"; + version = "0.10.72"; edition = "2021"; - sha256 = "1kgvk6wi57bacn6b5z6b57vkyd2j85s6vyxhvj7jbkcqd861652y"; + sha256 = "1np54pm6hw512rmfjv3kc54h8yvf51mdlm8a8cc33xx1b1yympzy"; authors = [ "Steven Fackler " ]; @@ -6652,6 +6652,7 @@ rec { } ]; features = { + "aws-lc" = [ "ffi/aws-lc" ]; "bindgen" = [ "ffi/bindgen" ]; "unstable_boringssl" = [ "ffi/unstable_boringssl" ]; "vendored" = [ "ffi/vendored" ]; @@ -6695,10 +6696,10 @@ rec { }; "openssl-sys" = rec { crateName = "openssl-sys"; - version = "0.9.106"; + version = "0.9.107"; edition = "2021"; links = "openssl"; - sha256 = "1pbwfy5x8znchsbqf7rnkdbdhw1fis5hpx3940y9xhqwh6lixdlb"; + sha256 = "01yydv8yaagdnapvair8b6rggf225lwb854h99s9qx44rnd9g242"; build = "build/main.rs"; libName = "openssl_sys"; authors = [ @@ -6726,6 +6727,7 @@ rec { } ]; features = { + "aws-lc" = [ "dep:aws-lc-sys" ]; "bindgen" = [ "dep:bindgen" ]; "bssl-sys" = [ "dep:bssl-sys" ]; "openssl-src" = [ "dep:openssl-src" ]; @@ -11307,9 +11309,9 @@ rec { }; "tokio" = rec { crateName = "tokio"; - version = "1.43.0"; + version = "1.44.2"; edition = "2021"; - sha256 = "17pdm49ihlhfw3rpxix3kdh2ppl1yv7nwp1kxazi5r1xz97zlq9x"; + sha256 = "0j4w3qvlcqzgbxlnap0czvspqj6x461vyk1sbqcf97g4rci8if76"; authors = [ "Tokio Contributors " ]; From 136033176c9514f28d3ceda49c47b2428a465052 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Wed, 7 May 2025 10:22:38 +0200 Subject: [PATCH 87/87] Apply template --- .github/workflows/pr_pre-commit.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr_pre-commit.yaml b/.github/workflows/pr_pre-commit.yaml index 8a31b68c..34e5766b 100644 --- a/.github/workflows/pr_pre-commit.yaml +++ b/.github/workflows/pr_pre-commit.yaml @@ -6,6 +6,7 @@ on: env: CARGO_TERM_COLOR: always + NIX_PKG_MANAGER_VERSION: "2.28.3" RUST_TOOLCHAIN_VERSION: "nightly-2025-01-15" HADOLINT_VERSION: "v2.12.0" PYTHON_VERSION: "3.12" @@ -29,3 +30,5 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} rust: ${{ env.RUST_TOOLCHAIN_VERSION }} hadolint: ${{ env.HADOLINT_VERSION }} + nix: ${{ env.NIX_PKG_MANAGER_VERSION }} + nix-github-token: ${{ secrets.GITHUB_TOKEN }}