From e0c3e08bd5fb8ee9c30aed7de271e2a25db5d531 Mon Sep 17 00:00:00 2001 From: Carl Wallace Date: Wed, 21 Aug 2024 12:33:09 -0400 Subject: [PATCH] Add support for KEMRecipientInfo as defined in RFC9629 (#1485) --- cms/src/kemri.rs | 65 +++++++++++++ cms/src/lib.rs | 1 + ....22554.5.6.1_ML-KEM-512-ipd_kemri_auth.der | Bin 0 -> 1061 bytes ...-512-ipd_kemri_id-alg-hkdf-with-sha256.der | Bin 0 -> 1046 bytes ...1.22554.5.6.1_ML-KEM-512-ipd_kemri_ukm.der | Bin 0 -> 1082 bytes cms/tests/kemri.rs | 91 ++++++++++++++++++ 6 files changed, 157 insertions(+) create mode 100644 cms/src/kemri.rs create mode 100644 cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_auth.der create mode 100644 cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_id-alg-hkdf-with-sha256.der create mode 100644 cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_ukm.der create mode 100644 cms/tests/kemri.rs diff --git a/cms/src/kemri.rs b/cms/src/kemri.rs new file mode 100644 index 000000000..721da6756 --- /dev/null +++ b/cms/src/kemri.rs @@ -0,0 +1,65 @@ +//! KEMRecipientInfo-related types + +use crate::{ + content_info::CmsVersion, + enveloped_data::{EncryptedKey, RecipientIdentifier, UserKeyingMaterial}, +}; +use const_oid::ObjectIdentifier; +use der::{asn1::OctetString, Sequence}; +use spki::AlgorithmIdentifierOwned; + +/// From [RFC9629 Section 3] +/// ```text +/// id-ori OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) +/// rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) 13 } +/// +/// id-ori-kem OBJECT IDENTIFIER ::= { id-ori 3 } +/// ``` +/// [RFC9629 Section 3]: https://datatracker.ietf.org/doc/html/rfc9629#section-3 +pub const ID_ORI_KEM: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.16.13.3"); + +/// The `KEMRecipientInfo` type is defined in [RFC9629 Section 3] +/// ```text +/// KEMRecipientInfo ::= SEQUENCE { +/// version CMSVersion, -- always set to 0 +/// rid RecipientIdentifier, +/// kem KEMAlgorithmIdentifier, +/// kemct OCTET STRING, +/// kdf KeyDerivationAlgorithmIdentifier, +/// kekLength INTEGER (1..65535), +/// ukm [0] EXPLICIT UserKeyingMaterial OPTIONAL, +/// wrap KeyEncryptionAlgorithmIdentifier, +/// encryptedKey EncryptedKey } +/// ``` +/// [RFC9629 Section 3]: https://datatracker.ietf.org/doc/html/rfc9629#section-3 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct KemRecipientInfo { + pub version: CmsVersion, + pub rid: RecipientIdentifier, + pub kem: AlgorithmIdentifierOwned, + pub kem_ct: OctetString, + pub kdf: AlgorithmIdentifierOwned, + pub kek_length: u16, + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub ukm: Option, + pub wrap: AlgorithmIdentifierOwned, + pub encrypted_key: EncryptedKey, +} + +/// The `CMSORIforKEMOtherInfo` type is defined in [RFC9629 Section 5] +/// ```text +/// CMSORIforKEMOtherInfo ::= SEQUENCE { +/// wrap KeyEncryptionAlgorithmIdentifier, +/// kekLength INTEGER (1..65535), +/// ukm [0] EXPLICIT UserKeyingMaterial OPTIONAL } +/// ``` +/// [RFC9629 Section 5]: https://datatracker.ietf.org/doc/html/rfc9629#section-5 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct CmsOriForKemOtherInfo { + pub wrap: AlgorithmIdentifierOwned, + pub kek_length: u16, + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub ukm: Option, +} diff --git a/cms/src/lib.rs b/cms/src/lib.rs index 335aa9f25..d409d5b27 100644 --- a/cms/src/lib.rs +++ b/cms/src/lib.rs @@ -37,6 +37,7 @@ pub mod content_info; pub mod digested_data; pub mod encrypted_data; pub mod enveloped_data; +pub mod kemri; pub mod revocation; pub mod signed_data; pub mod timestamped_data; diff --git a/cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_auth.der b/cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_auth.der new file mode 100644 index 0000000000000000000000000000000000000000..81cf5e7299785aee2f8ed6f9b9faa27e23f3e5c3 GIT binary patch literal 1061 zcmV+=1ls#Bf&?K33o3?4hW8Bt2@nAnpn?PtFoFaO0s#Oqf&+-8f&+vG3o3?4hW8Bt z2@nkfFoFYf0s#Pk6tR09Sb!HpynEt;cS;wFb`lHsW<}7m602-kVn!HvyeLp+Ba8>UYb-2nxv0A< zAMx4qgF7S0bAuVHByu5UGE>cwp;FI5YO)u@kLf$HHiM|<{GhGcTJ7HZz+IGYYRpd? zT(Dr*_(|#Bd{<5bmII-Fm; ze=K-ECEXEW?KbW-Gfm*Kwp@Hu-sL?oOXyuZ^5@x5Io7xlBWdNrJhlaDys8}!eDN5z z>gRJ0n2dRpOWC31wgm5&cJZ@AKM03j7D@8Hnt3*vQE{J#;-2`TX)|wg9}PX?diVp( zR@>bcM;c18y-v6z>V;DuJBA1sRbNqi+q11qX5Gf3gj;89E5(vsb_-f}f8H|`JoS>D z`{J|*z{nbXP}3Fj&?Q=Tv&$7EL)@oGN>$Z@)l(exi*DPgs|LyyY%6T~sx1kaPUPat zbZM>Ete#EJOpmvD%m(K&>2*_KaSh*5WUc>JTQLl^$xiQF0q}iP?%q<~jE(Hpm_ve* zDGsTBdJb^ery%gGOhN)7ABQ@=4FcHrB9RDz8cOEOQpyp@#D)Pgz-OP;H5NioJQ6ZC zr;4!dr5RvF%EGP5_9J|gaa#6Mb^c{3LC=i>MSxT=z@$#vL;g~aix^$6S!gvm0M1+i zMf6jVsqx6D+S~e;UHSPfb$xcxnWFPX79hs>ALB|GbFbf6=V1`HmWdj5P z6#@YuFbf6=V1`HmWdj5OEd(fnr6*+qEx;V9GkcEsrG32Or8%+F9456ZVMRm>ck-3o zZx}ChD}-Y(X$A=@hDe6@4FLxMFdha8V1`HmWdj5OE-(=U3|Nei=6^d-!e>!cH39(; zfIJ*bwhf(-FRV|kP6``e34uY$!OL*&a;|ZW$$P fbo0-Mucm0?4Auk?4jlex<2`Yw2hW8Bt2Lqsj1OqUF1OEa612KXFill-Ah6W2NhDe6@4FL%d z4FfQO19buc0Du&+dmUJSDatb-5-`OK=U-Rms>v2G4F(G<1_1;Cfv_3{1_1U0Ae?Lh$soNh#4U5Boh(Zswqd;O);J}V;^1qZAxHKY+w(pt<2 zMB3U+SoI+B@*>0eM;FgoA|hxe|163V?S4;gwKrx%4|o^X8HX+KI;$%AywJNDIkg|b zmUjxIv8p^syDRy-s`&fW%s;O};FD_uFg#akMdTS;{0elDiuF2+%2QvbM8+@R-fj)n^} z5d{#$uQ}$IB!K>#n>6*_)NUsJ;6Eco_~dB4?2i4Ro!iFW1W8B+(`>r`1mT8qoExqn z)@M#gVoX6mktFiAEW-IwCl8C+hekxsbiH%k92f=UH ze|c9EKr4EJ(vd95AFM)Vj|b|6Am18s#?{>0(kICX5cvEtR8mic65fEwjt7kIFVljC z`J&kHE7EI273HpH#N>j3y!fOs=m)s&)s)?qP?#IZXBCn6|53kxJJFANtTWMN#7j}B zaP8d`>M8!@AZiEGqP~!z?_sDxev< zvO$u1v~Td#C?WpaVMrZJoIhsL4vP`7U~i3G<0H&1h6%ihQ&~V7sLWfqA8|GYQ_Ww0 z1_{O%Q8`wSIP^O<9;+2$fscEhgG2Hwx?63SU{x#8wIL)MQeZF*1`8^NNQU4&TBQdA;c-{~2-N82|tP literal 0 HcmV?d00001 diff --git a/cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_ukm.der b/cms/tests/examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_ukm.der new file mode 100644 index 0000000000000000000000000000000000000000..cb906458372466ddc5f2f75073bd98f15364d8db GIT binary patch literal 1082 zcmV-A1jYL>f&?}O2`Yw2hW8Bt2Lqsj1Sc?p1S0|g12KXFuB3tkss;-xhDe6@4FL%d z4FfQO1DOH=0Du&+dmUJSDatb-5-`OK=U-Rms>v2G4F(G<1_1;Cfv_3{1_1znYGKJb!twk(s3km3^<35Mz~0F`s?Uf2>nt)iHye*91BkRtISJ;-bh( zs*aJoM?GlJZHn)k{t>-#*;IvkdpM>X!uW=y#a*}8fbAOgq;E^mWj35Q{Z>1R&V;$I zN^zWsqP`o{D$MyrwEl9{AkG~4x6mVFB;6SdH>TfFu^GStD2~F{ltIu0>GW{Xoh^jF zUFAB>)Lrm4CP4lg43@OYMV;EBVkD+fee6@==XU zK^*kAPzzsP%5ZDCKKF)U(qTEZ8cS=Z4+V!7=gsnbhfAXz-tpc&mEbI#7^xpAl@NKX z1HSTmAVV+a5&n67u9xP#)^8ja>C_OYNDGa@F3_vE$lCbcL!lgsRfYmwl*mI}A+DEk zmzoUzZ!&Lpko@_rJ6wjTIFyy@pYzt7HGOA@RVpWKl}kBWS4NN5OMd+U1AMkKw8IK&cwCOoTW(y|H$S1`j~Ve-_B zYSj)vt>K9=*z=*4a++zTKL*O9PWaOxjY6Y@s_Kqm#xf~(IH`xL$31IXO7My89}W(8 z9YrHO?7zzZmYgEdI=={iNjSm8v~|{#3BWU52nZ;{_I{aX4OYQ5zdr0 za5IG^l&5tT3$8d)vPlQprQ3`>-PLl)hc7a>6aUOOkF?NC{r)fu1_@w>NC9O71OgQT z0U)3x1R_*uX>%ZHb0BkXZDk-;b7gWMOJ#X!Zf77(VRU73X<=*%Fbf6=V1`HmWdj5O zEd(gJ^(6aQ%GW{xC$avf9bXmozR*B;t9m=_)d9A$71pXjd)fm7$BJw)Yz7G`hDe6@ z4FLxMFdYU7V1`HmWdj5ODg+Qg#4S1j8iyIx{Y=BxcpeFWK*UQz37@0eK^jjo?)(n| zO|goM-sIWnJzImfbO`nJ)*q@r!Y4(>90I&Ma~$6xYZim|x(T`lP)N_SuyuVjX%{l= Ar2qf` literal 0 HcmV?d00001 diff --git a/cms/tests/kemri.rs b/cms/tests/kemri.rs new file mode 100644 index 000000000..e1d5e4031 --- /dev/null +++ b/cms/tests/kemri.rs @@ -0,0 +1,91 @@ +use cms::authenveloped_data::AuthEnvelopedData; +use cms::content_info::{CmsVersion, ContentInfo}; +use cms::enveloped_data::{EnvelopedData, RecipientIdentifier, RecipientInfo}; +use cms::kemri::ID_ORI_KEM; +use const_oid::ObjectIdentifier; +use der::{Decode, Encode}; +use hex_literal::hex; + +#[test] +fn kemri_auth_enveloped_data() { + let data = include_bytes!("examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_auth.der"); + let ci = ContentInfo::from_der(data).unwrap(); + let aed = AuthEnvelopedData::from_der(&ci.content.to_der().unwrap()).unwrap(); + for ri in aed.recip_infos.0.iter() { + if let RecipientInfo::Ori(ori) = ri { + let ori_value = ori.ori_value.to_der().unwrap(); + assert_eq!(ori.ori_type, ID_ORI_KEM); + let kemri = cms::kemri::KemRecipientInfo::from_der(&ori_value).unwrap(); + let reenc = kemri.to_der().unwrap(); + assert_eq!(reenc, ori_value); + } else { + panic!("Unexpected recipient info type"); + } + } +} + +#[test] +fn kemri_enveloped_data() { + let data = include_bytes!( + "examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_id-alg-hkdf-with-sha256.der" + ); + let ci = ContentInfo::from_der(data).unwrap(); + let ed = EnvelopedData::from_der(&ci.content.to_der().unwrap()).unwrap(); + for ri in ed.recip_infos.0.iter() { + if let RecipientInfo::Ori(ori) = ri { + let ori_value = ori.ori_value.to_der().unwrap(); + assert_eq!(ori.ori_type, ID_ORI_KEM); + let kemri = cms::kemri::KemRecipientInfo::from_der(&ori_value).unwrap(); + let reenc = kemri.to_der().unwrap(); + assert_eq!(reenc, ori_value); + } else { + panic!("Unexpected recipient info type"); + } + } +} + +#[test] +fn kemri_enveloped_data_ukm() { + let data = include_bytes!("examples/1.3.6.1.4.1.22554.5.6.1_ML-KEM-512-ipd_kemri_ukm.der"); + let ci = ContentInfo::from_der(data).unwrap(); + let ed = EnvelopedData::from_der(&ci.content.to_der().unwrap()).unwrap(); + for ri in ed.recip_infos.0.iter() { + if let RecipientInfo::Ori(ori) = ri { + let ori_value = ori.ori_value.to_der().unwrap(); + assert_eq!(ori.ori_type, ID_ORI_KEM); + let kemri = cms::kemri::KemRecipientInfo::from_der(&ori_value).unwrap(); + + assert_eq!(kemri.version, CmsVersion::V0); + + if let RecipientIdentifier::SubjectKeyIdentifier(skid) = &kemri.rid { + let rid = hex!("B17B1D588029CA33201230C50CE75F57E6AAC916"); + assert_eq!(skid.0.as_bytes(), rid); + } else { + panic!("Unexpected recipient identifier type"); + } + + pub const ML_KEM_512_IPD: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.6.1.4.1.22554.5.6.1"); + assert_eq!(kemri.kem.oid, ML_KEM_512_IPD); + + pub const ID_KMAC128: ObjectIdentifier = + ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.21"); + assert_eq!(kemri.kdf.oid, ID_KMAC128); + + assert_eq!( + kemri.ukm.clone().unwrap().as_bytes(), + "This is some User Keying Material\n".as_bytes() + ); + + let enc_key = hex!( + "B9F524FB59CAD7420127B1FEA61D5F15F5BED04078AB7A3BEDD501B6B215D6AA417BDA0303C78A6C" + ); + assert_eq!(kemri.encrypted_key.as_bytes(), enc_key); + + let reenc = kemri.to_der().unwrap(); + assert_eq!(reenc, ori_value); + } else { + panic!("Unexpected recipient info type"); + } + } +}