@@ -11,10 +11,15 @@ use deno_core::convert::Uint8Array;
1111use deno_core:: op2;
1212use deno_core:: unsync:: spawn_blocking;
1313use deno_error:: JsErrorBox ;
14+ use digest:: Digest ;
15+ use digest:: FixedOutputReset ;
1416use ed25519_dalek:: pkcs8:: BitStringRef ;
1517use elliptic_curve:: JwkEcKey ;
18+ use hmac:: Hmac ;
19+ use hmac:: Mac ;
1620use num_bigint:: BigInt ;
1721use num_traits:: FromPrimitive as _;
22+ use p12:: AlgorithmIdentifier as Pkcs12AlgorithmIdentifier ;
1823use p12:: PFX as Pkcs12 ;
1924use pkcs8:: DecodePrivateKey as _;
2025use pkcs8:: Document ;
@@ -4160,6 +4165,16 @@ pub enum PfxValidationError {
41604165 MacVerifyFailure ,
41614166}
41624167
4168+ // Cap on the iteration count for the PKCS#12 MAC PBKDF, since an attacker
4169+ // who controls the PFX bytes also controls this field (`mac_data.iterations`
4170+ // is a u32, up to ~4 billion, which would tie up CPU for tens of seconds).
4171+ // Matches the limit Mozilla NSS uses for the same KDF; well above any
4172+ // realistic legitimate value — OpenSSL defaults to 2048 on creation, and
4173+ // hardened producers rarely go beyond ~100k. OpenSSL itself doesn't cap
4174+ // here, but Node trusts the caller's PFX; in Deno the PFX often comes
4175+ // from untrusted input (e.g. server config), so capping is defensive.
4176+ const PFX_MAC_ITERATIONS_CAP : u64 = 600_000 ;
4177+
41634178#[ op2]
41644179pub fn op_node_validate_pfx (
41654180 #[ buffer] pfx : & [ u8 ] ,
@@ -4168,12 +4183,249 @@ pub fn op_node_validate_pfx(
41684183 let parsed =
41694184 Pkcs12 :: parse ( pfx) . map_err ( |_| PfxValidationError :: NotEnoughData ) ?;
41704185 let password = passphrase. as_deref ( ) . unwrap_or ( "" ) ;
4171- if !parsed. verify_mac ( password) {
4186+ let bmp_password = bmp_string ( password) ;
4187+ // If no MAC is present, the file is considered valid.
4188+ let Some ( mac_data) = & parsed. mac_data else {
4189+ return Ok ( ( ) ) ;
4190+ } ;
4191+ let iterations = u64:: from ( mac_data. iterations ) ;
4192+ if iterations > PFX_MAC_ITERATIONS_CAP {
4193+ return Err ( PfxValidationError :: MacVerifyFailure ) ;
4194+ }
4195+ let data = parsed
4196+ . auth_safe
4197+ . data ( & bmp_password)
4198+ . ok_or ( PfxValidationError :: MacVerifyFailure ) ?;
4199+ let ok = verify_pkcs12_mac (
4200+ & mac_data. mac . digest_algorithm ,
4201+ & mac_data. mac . digest ,
4202+ & mac_data. salt ,
4203+ iterations,
4204+ & data,
4205+ & bmp_password,
4206+ )
4207+ . ok_or ( PfxValidationError :: MacVerifyFailure ) ?;
4208+ if !ok {
41724209 return Err ( PfxValidationError :: MacVerifyFailure ) ;
41734210 }
41744211 Ok ( ( ) )
41754212}
41764213
4214+ // Convert a UTF-8 password to the PKCS#12 BMPString form (RFC 7292
4215+ // section B.1): each character is encoded as UTF-16BE, followed by a
4216+ // U+0000 terminator.
4217+ fn bmp_string ( s : & str ) -> Vec < u8 > {
4218+ let utf16: Vec < u16 > = s. encode_utf16 ( ) . collect ( ) ;
4219+ let mut bytes = Vec :: with_capacity ( utf16. len ( ) * 2 + 2 ) ;
4220+ for c in utf16 {
4221+ bytes. extend_from_slice ( & c. to_be_bytes ( ) ) ;
4222+ }
4223+ bytes. extend_from_slice ( & [ 0x00 , 0x00 ] ) ;
4224+ bytes
4225+ }
4226+
4227+ // PKCS#12 password-based key derivation, generic over a Digest. Implements
4228+ // the algorithm from RFC 7292 Appendix B.2:
4229+ // https://www.rfc-editor.org/rfc/rfc7292#appendix-B.2
4230+ //
4231+ // `v` is the hash function's block size in bytes (64 for SHA-1, SHA-224,
4232+ // SHA-256; 128 for SHA-384, SHA-512, SHA-512/224, SHA-512/256). `id` is
4233+ // the diversifier (1 = encryption key, 2 = IV, 3 = MAC key). `size` is
4234+ // the number of output bytes desired.
4235+ fn pkcs12_pbkdf < D : Digest + FixedOutputReset > (
4236+ pass : & [ u8 ] ,
4237+ salt : & [ u8 ] ,
4238+ iterations : u64 ,
4239+ id : u8 ,
4240+ size : usize ,
4241+ v : usize ,
4242+ ) -> Vec < u8 > {
4243+ // u = hash output size in bytes.
4244+ let u = <D as Digest >:: output_size ( ) ;
4245+ // Step 1: D = id repeated v bytes.
4246+ let d = vec ! [ id; v] ;
4247+ // Steps 2 & 3: S and P are the salt / password padded to a multiple of
4248+ // v bytes by cyclic repetition. Empty inputs contribute an empty string.
4249+ let s: Vec < u8 > = if salt. is_empty ( ) {
4250+ Vec :: new ( )
4251+ } else {
4252+ salt
4253+ . iter ( )
4254+ . cycle ( )
4255+ . take ( v * salt. len ( ) . div_ceil ( v) )
4256+ . copied ( )
4257+ . collect ( )
4258+ } ;
4259+ let p: Vec < u8 > = if pass. is_empty ( ) {
4260+ Vec :: new ( )
4261+ } else {
4262+ pass
4263+ . iter ( )
4264+ . cycle ( )
4265+ . take ( v * pass. len ( ) . div_ceil ( v) )
4266+ . copied ( )
4267+ . collect ( )
4268+ } ;
4269+ // Step 4: I = S || P.
4270+ let mut i: Vec < u8 > = Vec :: with_capacity ( s. len ( ) + p. len ( ) ) ;
4271+ i. extend_from_slice ( & s) ;
4272+ i. extend_from_slice ( & p) ;
4273+ // Step 5: c = ceil(size / u).
4274+ let c = size. div_ceil ( u) ;
4275+ let mut out: Vec < u8 > = Vec :: with_capacity ( c * u) ;
4276+ let mut hasher = D :: new ( ) ;
4277+ for _ in 0 ..c {
4278+ // Step 6a: Ai = H^iterations(D || I).
4279+ Digest :: update ( & mut hasher, & d) ;
4280+ Digest :: update ( & mut hasher, & i) ;
4281+ let mut ai = hasher. finalize_reset ( ) . to_vec ( ) ;
4282+ for _ in 1 ..iterations {
4283+ Digest :: update ( & mut hasher, & ai) ;
4284+ ai = hasher. finalize_reset ( ) . to_vec ( ) ;
4285+ }
4286+ // Step 7 (partial): A = A || Ai.
4287+ out. extend_from_slice ( & ai) ;
4288+ if i. is_empty ( ) {
4289+ continue ;
4290+ }
4291+ // Step 6b: B = Ai repeated cyclically to v bytes.
4292+ let b: Vec < u8 > = ai. iter ( ) . cycle ( ) . take ( v) . copied ( ) . collect ( ) ;
4293+ // Step 6c: treating I as v-byte blocks, set each block to
4294+ // (block + B + 1) mod 2^(8v). Big-endian add with carry.
4295+ for chunk in i. chunks_mut ( v) {
4296+ let mut carry: u16 = 1 ;
4297+ for j in ( 0 ..v) . rev ( ) {
4298+ let sum = chunk[ j] as u16 + b[ j] as u16 + carry;
4299+ chunk[ j] = ( sum & 0xff ) as u8 ;
4300+ carry = sum >> 8 ;
4301+ }
4302+ }
4303+ }
4304+ // Step 8: take the first `size` bytes of A.
4305+ out. truncate ( size) ;
4306+ out
4307+ }
4308+
4309+ fn pkcs12_hmac < D > ( key : & [ u8 ] , data : & [ u8 ] ) -> Vec < u8 >
4310+ where
4311+ D : Digest
4312+ + digest:: core_api:: CoreProxy
4313+ + FixedOutputReset
4314+ + digest:: core_api:: BlockSizeUser ,
4315+ <D as digest:: core_api:: CoreProxy >:: Core : digest:: core_api:: BufferKindUser <
4316+ BufferKind = digest:: block_buffer:: Eager ,
4317+ > + digest:: core_api:: FixedOutputCore
4318+ + digest:: HashMarker
4319+ + Default
4320+ + Clone ,
4321+ <<D as digest:: core_api:: CoreProxy >:: Core as digest:: core_api:: BlockSizeUser >:: BlockSize : digest:: typenum:: IsLess < digest:: typenum:: U256 > ,
4322+ digest:: typenum:: Le < <<D as digest:: core_api:: CoreProxy >:: Core as digest:: core_api:: BlockSizeUser >:: BlockSize , digest:: typenum:: U256 > : digest:: typenum:: NonZero ,
4323+ {
4324+ let mut mac = <Hmac < D > as Mac >:: new_from_slice ( key) . unwrap ( ) ;
4325+ Mac :: update ( & mut mac, data) ;
4326+ mac. finalize ( ) . into_bytes ( ) . to_vec ( )
4327+ }
4328+
4329+ // OID identifiers in components.
4330+ const OID_SHA1 : & [ u64 ] = & [ 1 , 3 , 14 , 3 , 2 , 26 ] ;
4331+ const OID_SHA224 : & [ u64 ] = & [ 2 , 16 , 840 , 1 , 101 , 3 , 4 , 2 , 4 ] ;
4332+ const OID_SHA256 : & [ u64 ] = & [ 2 , 16 , 840 , 1 , 101 , 3 , 4 , 2 , 1 ] ;
4333+ const OID_SHA384 : & [ u64 ] = & [ 2 , 16 , 840 , 1 , 101 , 3 , 4 , 2 , 2 ] ;
4334+ const OID_SHA512 : & [ u64 ] = & [ 2 , 16 , 840 , 1 , 101 , 3 , 4 , 2 , 3 ] ;
4335+ const OID_SHA512_224 : & [ u64 ] = & [ 2 , 16 , 840 , 1 , 101 , 3 , 4 , 2 , 5 ] ;
4336+ const OID_SHA512_256 : & [ u64 ] = & [ 2 , 16 , 840 , 1 , 101 , 3 , 4 , 2 , 6 ] ;
4337+
4338+ enum Pkcs12MacAlgorithm {
4339+ Sha1 ,
4340+ Sha224 ,
4341+ Sha256 ,
4342+ Sha384 ,
4343+ Sha512 ,
4344+ Sha512_224 ,
4345+ Sha512_256 ,
4346+ }
4347+
4348+ fn pkcs12_mac_algorithm (
4349+ algorithm : & Pkcs12AlgorithmIdentifier ,
4350+ ) -> Option < Pkcs12MacAlgorithm > {
4351+ let oid = match algorithm {
4352+ Pkcs12AlgorithmIdentifier :: Sha1 => return Some ( Pkcs12MacAlgorithm :: Sha1 ) ,
4353+ Pkcs12AlgorithmIdentifier :: OtherAlg ( other) => {
4354+ other. algorithm_type . components ( ) . as_slice ( )
4355+ }
4356+ _ => return None ,
4357+ } ;
4358+ if oid == OID_SHA1 {
4359+ Some ( Pkcs12MacAlgorithm :: Sha1 )
4360+ } else if oid == OID_SHA224 {
4361+ Some ( Pkcs12MacAlgorithm :: Sha224 )
4362+ } else if oid == OID_SHA256 {
4363+ Some ( Pkcs12MacAlgorithm :: Sha256 )
4364+ } else if oid == OID_SHA384 {
4365+ Some ( Pkcs12MacAlgorithm :: Sha384 )
4366+ } else if oid == OID_SHA512 {
4367+ Some ( Pkcs12MacAlgorithm :: Sha512 )
4368+ } else if oid == OID_SHA512_224 {
4369+ Some ( Pkcs12MacAlgorithm :: Sha512_224 )
4370+ } else if oid == OID_SHA512_256 {
4371+ Some ( Pkcs12MacAlgorithm :: Sha512_256 )
4372+ } else {
4373+ None
4374+ }
4375+ }
4376+
4377+ fn verify_pkcs12_mac (
4378+ algorithm : & Pkcs12AlgorithmIdentifier ,
4379+ expected : & [ u8 ] ,
4380+ salt : & [ u8 ] ,
4381+ iterations : u64 ,
4382+ data : & [ u8 ] ,
4383+ password : & [ u8 ] ,
4384+ ) -> Option < bool > {
4385+ let mac_alg = pkcs12_mac_algorithm ( algorithm) ?;
4386+ let computed = match mac_alg {
4387+ Pkcs12MacAlgorithm :: Sha1 => {
4388+ let key =
4389+ pkcs12_pbkdf :: < sha1:: Sha1 > ( password, salt, iterations, 3 , 20 , 64 ) ;
4390+ pkcs12_hmac :: < sha1:: Sha1 > ( & key, data)
4391+ }
4392+ Pkcs12MacAlgorithm :: Sha224 => {
4393+ let key =
4394+ pkcs12_pbkdf :: < sha2:: Sha224 > ( password, salt, iterations, 3 , 28 , 64 ) ;
4395+ pkcs12_hmac :: < sha2:: Sha224 > ( & key, data)
4396+ }
4397+ Pkcs12MacAlgorithm :: Sha256 => {
4398+ let key =
4399+ pkcs12_pbkdf :: < sha2:: Sha256 > ( password, salt, iterations, 3 , 32 , 64 ) ;
4400+ pkcs12_hmac :: < sha2:: Sha256 > ( & key, data)
4401+ }
4402+ Pkcs12MacAlgorithm :: Sha384 => {
4403+ let key =
4404+ pkcs12_pbkdf :: < sha2:: Sha384 > ( password, salt, iterations, 3 , 48 , 128 ) ;
4405+ pkcs12_hmac :: < sha2:: Sha384 > ( & key, data)
4406+ }
4407+ Pkcs12MacAlgorithm :: Sha512 => {
4408+ let key =
4409+ pkcs12_pbkdf :: < sha2:: Sha512 > ( password, salt, iterations, 3 , 64 , 128 ) ;
4410+ pkcs12_hmac :: < sha2:: Sha512 > ( & key, data)
4411+ }
4412+ Pkcs12MacAlgorithm :: Sha512_224 => {
4413+ let key = pkcs12_pbkdf :: < sha2:: Sha512_224 > (
4414+ password, salt, iterations, 3 , 28 , 128 ,
4415+ ) ;
4416+ pkcs12_hmac :: < sha2:: Sha512_224 > ( & key, data)
4417+ }
4418+ Pkcs12MacAlgorithm :: Sha512_256 => {
4419+ let key = pkcs12_pbkdf :: < sha2:: Sha512_256 > (
4420+ password, salt, iterations, 3 , 32 , 128 ,
4421+ ) ;
4422+ pkcs12_hmac :: < sha2:: Sha512_256 > ( & key, data)
4423+ }
4424+ } ;
4425+ use subtle:: ConstantTimeEq ;
4426+ Some ( bool:: from ( computed. ct_eq ( expected) ) )
4427+ }
4428+
41774429#[ derive( Debug , thiserror:: Error , deno_error:: JsError ) ]
41784430pub enum CrlValidationError {
41794431 #[ class( generic) ]
@@ -4199,3 +4451,86 @@ pub fn op_node_validate_crl(
41994451 }
42004452 Ok ( ( ) )
42014453}
4454+
4455+ #[ cfg( test) ]
4456+ mod tests {
4457+ use super :: * ;
4458+
4459+ // Cross-check our generic implementation of the PKCS#12 PBKDF
4460+ // (RFC 7292 Appendix B.2) against test vectors computed with an
4461+ // independent reference implementation.
4462+
4463+ // SHA-1 vector lifted from the `p12` crate's own test suite, which
4464+ // matches output produced by Bouncy Castle.
4465+ #[ test]
4466+ fn pkcs12_pbkdf_sha1 ( ) {
4467+ let pass = bmp_string ( "" ) ;
4468+ assert_eq ! ( pass, vec![ 0 , 0 ] ) ;
4469+ let salt: [ u8 ; 8 ] = [ 0x9a , 0xf4 , 0x70 , 0x29 , 0x58 , 0xa8 , 0xe9 , 0x5c ] ;
4470+ let got = pkcs12_pbkdf :: < sha1:: Sha1 > ( & pass, & salt, 2048 , 1 , 24 , 64 ) ;
4471+ let expected: [ u8 ; 24 ] = [
4472+ 0xc2 , 0x29 , 0x4a , 0xa6 , 0xd0 , 0x29 , 0x30 , 0xeb , 0x5c , 0xe9 , 0xc3 , 0x29 ,
4473+ 0xec , 0xcb , 0x9a , 0xee , 0x1c , 0xb1 , 0x36 , 0xba , 0xea , 0x74 , 0x65 , 0x57 ,
4474+ ] ;
4475+ assert_eq ! ( got, expected) ;
4476+ }
4477+
4478+ // SHA-256 / SHA-384 / SHA-512 vectors were generated by porting the
4479+ // RFC 7292 B.2 pseudocode to Python and feeding the same inputs.
4480+ #[ test]
4481+ fn pkcs12_pbkdf_sha256 ( ) {
4482+ let pass = bmp_string ( "secret" ) ;
4483+ let salt: [ u8 ; 8 ] = [ 0x01 , 0x23 , 0x45 , 0x67 , 0x89 , 0xab , 0xcd , 0xef ] ;
4484+ let got = pkcs12_pbkdf :: < sha2:: Sha256 > ( & pass, & salt, 1000 , 3 , 32 , 64 ) ;
4485+ let expected: [ u8 ; 32 ] = [
4486+ 0x7f , 0xe1 , 0x91 , 0x75 , 0x7d , 0xca , 0xf1 , 0xed , 0x6a , 0x29 , 0x77 , 0xb9 ,
4487+ 0xb9 , 0x15 , 0x3f , 0x60 , 0x82 , 0xaf , 0x0b , 0xda , 0xfd , 0x09 , 0x35 , 0x2d ,
4488+ 0xcd , 0xaa , 0x96 , 0x7f , 0x57 , 0x17 , 0x82 , 0xb0 ,
4489+ ] ;
4490+ assert_eq ! ( got, expected) ;
4491+ }
4492+
4493+ #[ test]
4494+ fn pkcs12_pbkdf_sha384 ( ) {
4495+ let pass = bmp_string ( "secret" ) ;
4496+ let salt: [ u8 ; 8 ] = [ 0x01 , 0x23 , 0x45 , 0x67 , 0x89 , 0xab , 0xcd , 0xef ] ;
4497+ let got = pkcs12_pbkdf :: < sha2:: Sha384 > ( & pass, & salt, 1000 , 3 , 48 , 128 ) ;
4498+ let expected: [ u8 ; 48 ] = [
4499+ 0x6a , 0x59 , 0x71 , 0x72 , 0x05 , 0x22 , 0x76 , 0x31 , 0x21 , 0xf4 , 0x9a , 0x1d ,
4500+ 0x5c , 0x04 , 0x10 , 0xa1 , 0xdb , 0x42 , 0x0b , 0xe4 , 0x96 , 0x6d , 0xc5 , 0x2f ,
4501+ 0x51 , 0x91 , 0x9d , 0x91 , 0x15 , 0x2d , 0x60 , 0x2d , 0x31 , 0x1c , 0x4c , 0xb0 ,
4502+ 0x8d , 0x99 , 0x83 , 0xad , 0xaf , 0x68 , 0xff , 0x5d , 0xdd , 0x69 , 0x0b , 0x87 ,
4503+ ] ;
4504+ assert_eq ! ( got, expected) ;
4505+ }
4506+
4507+ #[ test]
4508+ fn pkcs12_pbkdf_sha512 ( ) {
4509+ let pass = bmp_string ( "secret" ) ;
4510+ let salt: [ u8 ; 8 ] = [ 0x01 , 0x23 , 0x45 , 0x67 , 0x89 , 0xab , 0xcd , 0xef ] ;
4511+ let got = pkcs12_pbkdf :: < sha2:: Sha512 > ( & pass, & salt, 1000 , 3 , 64 , 128 ) ;
4512+ let expected: [ u8 ; 64 ] = [
4513+ 0x3e , 0x5c , 0x5d , 0xb5 , 0xf5 , 0xe3 , 0xd0 , 0xc2 , 0x23 , 0x2e , 0xbf , 0xbb ,
4514+ 0x86 , 0x08 , 0x23 , 0x7a , 0x2b , 0x0a , 0xf6 , 0x00 , 0x30 , 0xe6 , 0xa0 , 0x08 ,
4515+ 0x2a , 0xbe , 0x7c , 0x19 , 0x3f , 0x52 , 0x5e , 0x98 , 0x97 , 0x6f , 0xb2 , 0xbb ,
4516+ 0x1c , 0x88 , 0xc3 , 0xc6 , 0xf8 , 0xb5 , 0x64 , 0x91 , 0x74 , 0x3f , 0xc5 , 0x04 ,
4517+ 0xfb , 0x3d , 0xd9 , 0x10 , 0xf4 , 0xf9 , 0x5e , 0xf6 , 0x99 , 0xc2 , 0x48 , 0x15 ,
4518+ 0xf1 , 0x3e , 0xc1 , 0x74 ,
4519+ ] ;
4520+ assert_eq ! ( got, expected) ;
4521+ }
4522+
4523+ #[ test]
4524+ fn bmp_string_basic ( ) {
4525+ assert_eq ! ( bmp_string( "" ) , vec![ 0x00 , 0x00 ] ) ;
4526+ // "Beavis" — every code unit becomes two big-endian bytes, then a
4527+ // U+0000 terminator.
4528+ assert_eq ! (
4529+ bmp_string( "Beavis" ) ,
4530+ vec![
4531+ 0x00 , 0x42 , 0x00 , 0x65 , 0x00 , 0x61 , 0x00 , 0x76 , 0x00 , 0x69 , 0x00 , 0x73 ,
4532+ 0x00 , 0x00 ,
4533+ ] ,
4534+ ) ;
4535+ }
4536+ }
0 commit comments