Skip to content

Commit 00f3baa

Browse files
committed
security: zeroize pem encoded key
`PemEncodedKey` is now zeroized and no longer contains ASN.1 parsed data. Because `Vec<simple_asn1::ASN1Block>` owns the underlying bytes and does not support zeroization, the full ASN.1 parsing has been replaced by a custom partial ASN.1 implementation. This parsing is performed only to classify the key type during construction and to extract the key bytes on access. This change also allows the removal of the `simple_asn1` dependency.
1 parent 991e89a commit 00f3baa

2 files changed

Lines changed: 174 additions & 88 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ signature = { version = "2.2.0", features = ["std"] }
3030

3131
# For PEM decoding
3232
pem = { version = "3", optional = true }
33-
simple_asn1 = { version = "0.6", optional = true }
3433

3534
# "aws_lc_rs" feature
3635
aws-lc-rs = { version = "1.15.0", optional = true }
@@ -43,6 +42,7 @@ p384 = { version = "0.13.0", optional = true, features = ["ecdsa"] }
4342
rand = { version = "0.8.5", optional = true, features = ["std"], default-features = false }
4443
rsa = { version = "0.9.6", optional = true }
4544
sha2 = { version = "0.10.7", optional = true, features = ["oid"] }
45+
zeroize = { version = "1.8.2", features = ["derive"] }
4646

4747
[target.'cfg(target_arch = "wasm32")'.dependencies]
4848
js-sys = "0.3"
@@ -65,7 +65,7 @@ criterion = { version = "0.8", default-features = false }
6565

6666
[features]
6767
default = ["use_pem"]
68-
use_pem = ["pem", "simple_asn1"]
68+
use_pem = ["pem"]
6969
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
7070
aws_lc_rs = ["aws-lc-rs"]
7171

src/pem/decoder.rs

Lines changed: 172 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use zeroize::{Zeroize, ZeroizeOnDrop};
2+
13
use crate::errors::{ErrorKind, Result};
24

35
/// Supported PEM files for EC and RSA Public and Private Keys
@@ -40,11 +42,12 @@ enum Classification {
4042
/// Documentation about these formats is at
4143
/// PKCS#1: https://tools.ietf.org/html/rfc8017
4244
/// PKCS#8: https://tools.ietf.org/html/rfc5958
43-
#[derive(Debug)]
45+
#[derive(Debug, ZeroizeOnDrop, Zeroize)]
4446
pub(crate) struct PemEncodedKey {
4547
content: Vec<u8>,
46-
asn1: Vec<simple_asn1::ASN1Block>,
48+
#[zeroize(skip)]
4749
pem_type: PemType,
50+
#[zeroize(skip)]
4851
standard: Standard,
4952
}
5053

@@ -53,22 +56,15 @@ impl PemEncodedKey {
5356
pub fn new(input: &[u8]) -> Result<PemEncodedKey> {
5457
match pem::parse(input) {
5558
Ok(content) => {
56-
let asn1_content = match simple_asn1::from_der(content.contents()) {
57-
Ok(asn1) => asn1,
58-
Err(_) => return Err(ErrorKind::InvalidKeyFormat.into()),
59-
};
60-
6159
match content.tag() {
6260
// This handles a PKCS#1 RSA Private key
6361
"RSA PRIVATE KEY" => Ok(PemEncodedKey {
6462
content: content.into_contents(),
65-
asn1: asn1_content,
6663
pem_type: PemType::RsaPrivate,
6764
standard: Standard::Pkcs1,
6865
}),
6966
"RSA PUBLIC KEY" => Ok(PemEncodedKey {
7067
content: content.into_contents(),
71-
asn1: asn1_content,
7268
pem_type: PemType::RsaPublic,
7369
standard: Standard::Pkcs1,
7470
}),
@@ -79,41 +75,22 @@ impl PemEncodedKey {
7975

8076
// This handles PKCS#8 certificates and public & private keys
8177
tag @ "PRIVATE KEY" | tag @ "PUBLIC KEY" | tag @ "CERTIFICATE" => {
82-
match classify_pem(&asn1_content) {
83-
Some(c) => {
84-
let is_private = tag == "PRIVATE KEY";
85-
let pem_type = match c {
86-
Classification::Ec => {
87-
if is_private {
88-
PemType::EcPrivate
89-
} else {
90-
PemType::EcPublic
91-
}
92-
}
93-
Classification::Ed => {
94-
if is_private {
95-
PemType::EdPrivate
96-
} else {
97-
PemType::EdPublic
98-
}
99-
}
100-
Classification::Rsa => {
101-
if is_private {
102-
PemType::RsaPrivate
103-
} else {
104-
PemType::RsaPublic
105-
}
106-
}
107-
};
108-
Ok(PemEncodedKey {
109-
content: content.into_contents(),
110-
asn1: asn1_content,
111-
pem_type,
112-
standard: Standard::Pkcs8,
113-
})
114-
}
115-
None => Err(ErrorKind::InvalidKeyFormat.into()),
116-
}
78+
let is_private = tag == "PRIVATE KEY";
79+
let pem_type = match classify_der(content.contents())
80+
.ok_or(ErrorKind::InvalidKeyFormat)?
81+
{
82+
Classification::Ec if is_private => PemType::EcPrivate,
83+
Classification::Ec => PemType::EcPublic,
84+
Classification::Ed if is_private => PemType::EdPrivate,
85+
Classification::Ed => PemType::EdPublic,
86+
Classification::Rsa if is_private => PemType::RsaPrivate,
87+
Classification::Rsa => PemType::RsaPublic,
88+
};
89+
Ok(PemEncodedKey {
90+
content: content.into_contents(),
91+
pem_type,
92+
standard: Standard::Pkcs8,
93+
})
11794
}
11895

11996
// Unknown/unsupported type
@@ -140,7 +117,8 @@ impl PemEncodedKey {
140117
match self.standard {
141118
Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()),
142119
Standard::Pkcs8 => match self.pem_type {
143-
PemType::EcPublic => extract_first_bitstring(&self.asn1),
120+
PemType::EcPublic => extract_first_bitstring_der(&self.content)
121+
.ok_or_else(|| ErrorKind::InvalidKeyFormat.into()),
144122
_ => Err(ErrorKind::InvalidKeyFormat.into()),
145123
},
146124
}
@@ -162,7 +140,8 @@ impl PemEncodedKey {
162140
match self.standard {
163141
Standard::Pkcs1 => Err(ErrorKind::InvalidKeyFormat.into()),
164142
Standard::Pkcs8 => match self.pem_type {
165-
PemType::EdPublic => extract_first_bitstring(&self.asn1),
143+
PemType::EdPublic => extract_first_bitstring_der(&self.content)
144+
.ok_or_else(|| ErrorKind::InvalidKeyFormat.into()),
166145
_ => Err(ErrorKind::InvalidKeyFormat.into()),
167146
},
168147
}
@@ -173,68 +152,175 @@ impl PemEncodedKey {
173152
match self.standard {
174153
Standard::Pkcs1 => Ok(self.content.as_slice()),
175154
Standard::Pkcs8 => match self.pem_type {
176-
PemType::RsaPrivate => extract_first_bitstring(&self.asn1),
177-
PemType::RsaPublic => extract_first_bitstring(&self.asn1),
155+
PemType::RsaPrivate | PemType::RsaPublic => {
156+
extract_first_bitstring_der(&self.content)
157+
.ok_or_else(|| ErrorKind::InvalidKeyFormat.into())
158+
}
178159
_ => Err(ErrorKind::InvalidKeyFormat.into()),
179160
},
180161
}
181162
}
182163
}
183164

165+
const TAG_BIT_STRING: u8 = 0x03;
166+
const TAG_OCTET_STRING: u8 = 0x04;
167+
const TAG_OID: u8 = 0x06;
168+
const TAG_SEQUENCE: u8 = 0x30;
169+
184170
// This really just finds and returns the first bitstring or octet string
185171
// Which is the x coordinate for EC public keys
186172
// And the DER contents of an RSA key
187173
// Though PKCS#11 keys shouldn't have anything else.
188174
// It will get confusing with certificates.
189-
fn extract_first_bitstring(asn1: &[simple_asn1::ASN1Block]) -> Result<&[u8]> {
190-
for asn1_entry in asn1.iter() {
191-
match asn1_entry {
192-
simple_asn1::ASN1Block::Sequence(_, entries) => {
193-
if let Ok(result) = extract_first_bitstring(entries) {
194-
return Ok(result);
175+
fn extract_first_bitstring_der(bytes: &[u8]) -> Option<&[u8]> {
176+
let mut stack = vec![bytes];
177+
178+
while let Some(bytes) = stack.pop() {
179+
let Some((tag, value, rest)) = read_tlv(bytes) else {
180+
continue; // Skip invalid TLV
181+
};
182+
183+
if !rest.is_empty() {
184+
stack.push(rest);
185+
}
186+
187+
match tag {
188+
TAG_BIT_STRING => {
189+
if value.is_empty() {
190+
return None; // Missing padding length
191+
} else if value[0] != 0 {
192+
return None; // Padding length must be zero for cryptographic keys
195193
}
194+
return Some(&value[1..]);
196195
}
197-
simple_asn1::ASN1Block::BitString(_, _, value) => {
198-
return Ok(value.as_ref());
199-
}
200-
simple_asn1::ASN1Block::OctetString(_, value) => {
201-
return Ok(value.as_ref());
196+
TAG_OCTET_STRING => return Some(value),
197+
TAG_SEQUENCE => {
198+
stack.push(value);
202199
}
203-
_ => (),
200+
_ => {}
204201
}
205202
}
206203

207-
Err(ErrorKind::InvalidEcdsaKey.into())
204+
None
208205
}
209206

210207
/// Find whether this is EC, RSA, or Ed
211-
fn classify_pem(asn1: &[simple_asn1::ASN1Block]) -> Option<Classification> {
212-
// These should be constant but the macro requires
213-
// #![feature(const_vec_new)]
214-
let ec_public_key_oid = simple_asn1::oid!(1, 2, 840, 10_045, 2, 1);
215-
let rsa_public_key_oid = simple_asn1::oid!(1, 2, 840, 113_549, 1, 1, 1);
216-
let ed25519_oid = simple_asn1::oid!(1, 3, 101, 112);
217-
218-
for asn1_entry in asn1.iter() {
219-
match asn1_entry {
220-
simple_asn1::ASN1Block::Sequence(_, entries) => {
221-
if let Some(classification) = classify_pem(entries) {
222-
return Some(classification);
223-
}
224-
}
225-
simple_asn1::ASN1Block::ObjectIdentifier(_, oid) => {
226-
if oid == ec_public_key_oid {
227-
return Some(Classification::Ec);
228-
}
229-
if oid == rsa_public_key_oid {
230-
return Some(Classification::Rsa);
231-
}
232-
if oid == ed25519_oid {
233-
return Some(Classification::Ed);
234-
}
208+
fn classify_der(bytes: &[u8]) -> Option<Classification> {
209+
const EC_PUBLIC_KEY_OID: &[u8] = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01]; // 1.2.840.10045.2.1
210+
const RSA_PUBLIC_KEY_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01]; // 1.2.840.113549.1.1.1
211+
const ED25519_OID: &[u8] = &[0x2B, 0x65, 0x70]; // 1.3.101.112
212+
213+
let mut stack = vec![bytes];
214+
215+
while let Some(bytes) = stack.pop() {
216+
let Some((tag, value, rest)) = read_tlv(bytes) else {
217+
continue; // Skip invalid TLV
218+
};
219+
220+
if !rest.is_empty() {
221+
stack.push(rest);
222+
}
223+
224+
if tag == TAG_OID {
225+
match value {
226+
EC_PUBLIC_KEY_OID => return Some(Classification::Ec),
227+
RSA_PUBLIC_KEY_OID => return Some(Classification::Rsa),
228+
ED25519_OID => return Some(Classification::Ed),
229+
_ => {}
235230
}
236-
_ => {}
231+
} else if tag == TAG_SEQUENCE {
232+
stack.push(value);
237233
}
238234
}
235+
239236
None
240237
}
238+
239+
/// Returns `Some((tag, value, rest))` or `None` if the TLV is invalid.
240+
fn read_tlv(mut bytes: &[u8]) -> Option<(u8, &[u8], &[u8])> {
241+
if bytes.len() < 2 {
242+
return None;
243+
}
244+
245+
let tag = bytes[0];
246+
let len = bytes[1];
247+
bytes = &bytes[2..];
248+
249+
let len = if len < 0x80 {
250+
len as usize
251+
} else {
252+
let len_len = (len & 0x7f) as usize;
253+
if len_len == 0 {
254+
return None; // Indefinite length
255+
} else if size_of::<usize>() < len_len {
256+
return None; // Too long; prevents usize overflow
257+
} else if bytes.len() < len_len {
258+
return None; // Not enough bytes
259+
}
260+
let len_bytes = &bytes[..len_len];
261+
bytes = &bytes[len_len..];
262+
len_bytes.iter().fold(0, |acc, &x| acc * 256 + x as usize)
263+
};
264+
265+
if bytes.len() < len {
266+
return None; // Not enough bytes
267+
}
268+
269+
let (value, rest) = bytes.split_at(len);
270+
Some((tag, value, rest))
271+
}
272+
273+
#[cfg(test)]
274+
mod tests {
275+
use super::*;
276+
277+
#[test]
278+
fn classify_ec_key() {
279+
let pem = pem::parse(include_bytes!("../../tests/ecdsa/public_ecdsa_key.pem")).unwrap();
280+
assert_eq!(classify_der(pem.contents()), Some(Classification::Ec));
281+
}
282+
283+
#[test]
284+
fn classify_rsa_key() {
285+
let pem = pem::parse(include_bytes!("../../tests/rsa/public_rsa_key_pkcs8.pem")).unwrap();
286+
assert_eq!(classify_der(pem.contents()), Some(Classification::Rsa));
287+
}
288+
289+
#[test]
290+
fn classify_ed25519_key() {
291+
let pem = pem::parse(include_bytes!("../../tests/eddsa/public_ed25519_key.pem")).unwrap();
292+
assert_eq!(classify_der(pem.contents()), Some(Classification::Ed));
293+
}
294+
295+
#[test]
296+
fn ec_public_key_extraction() {
297+
let key =
298+
PemEncodedKey::new(include_bytes!("../../tests/ecdsa/public_ecdsa_key.pem")).unwrap();
299+
let bytes = key.as_ec_public_key().unwrap();
300+
assert_eq!(bytes[0], 0x04); // uncompressed point
301+
assert_eq!(bytes.len(), 65); // 1 + 32 + 32 for P-256
302+
}
303+
304+
#[test]
305+
fn ed_public_key_extraction() {
306+
let key =
307+
PemEncodedKey::new(include_bytes!("../../tests/eddsa/public_ed25519_key.pem")).unwrap();
308+
let bytes = key.as_ed_public_key().unwrap();
309+
assert_eq!(bytes.len(), 32);
310+
}
311+
312+
#[test]
313+
fn rsa_pkcs8_key_extraction() {
314+
let key =
315+
PemEncodedKey::new(include_bytes!("../../tests/rsa/public_rsa_key_pkcs8.pem")).unwrap();
316+
let bytes = key.as_rsa_key().unwrap();
317+
assert_eq!(bytes[0], 0x30); // SEQUENCE
318+
}
319+
#[test]
320+
fn rsa_pkcs1_key() {
321+
let key = PemEncodedKey::new(include_bytes!("../../tests/rsa/private_rsa_key_pkcs1.pem"))
322+
.unwrap();
323+
let bytes = key.as_rsa_key().unwrap();
324+
assert_eq!(bytes[0], 0x30); // SEQUENCE
325+
}
326+
}

0 commit comments

Comments
 (0)