1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
//! X.509 distinguished names (a small, common subset of RDNSequence).
use alloc::string::String;
use alloc::vec::Vec;
use super::{Error, oid};
use crate::der::{Reader, encode_sequence, encode_string, encode_tlv, oid_tlv, parse_oid, tag};
/// A distinguished name with the most common attributes. Encodes/decodes as an
/// X.501 `RDNSequence` (one single-valued RDN per present attribute).
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DistinguishedName {
/// `countryName` (C).
pub country: Option<String>,
/// `organizationName` (O).
pub organization: Option<String>,
/// `organizationalUnitName` (OU).
pub organizational_unit: Option<String>,
/// `commonName` (CN).
pub common_name: Option<String>,
}
impl DistinguishedName {
/// An empty name.
pub fn new() -> Self {
Self::default()
}
/// A name with only a common name set.
pub fn common_name(cn: &str) -> Self {
DistinguishedName {
common_name: Some(String::from(cn)),
..Self::default()
}
}
/// Builder setter for the organization.
pub fn with_organization(mut self, o: &str) -> Self {
self.organization = Some(String::from(o));
self
}
/// Builder setter for the country.
pub fn with_country(mut self, c: &str) -> Self {
self.country = Some(String::from(c));
self
}
/// Encodes the name as a DER `RDNSequence` (`SEQUENCE OF RelativeDistinguishedName`).
pub(crate) fn to_der(&self) -> Vec<u8> {
let mut rdns = Vec::new();
// Conventional ordering: C, O, OU, CN.
if let Some(c) = &self.country {
rdns.extend_from_slice(&rdn(oid::COUNTRY, tag::PRINTABLE_STRING, c));
}
if let Some(o) = &self.organization {
rdns.extend_from_slice(&rdn(oid::ORGANIZATION, tag::UTF8_STRING, o));
}
if let Some(ou) = &self.organizational_unit {
rdns.extend_from_slice(&rdn(oid::ORGANIZATIONAL_UNIT, tag::UTF8_STRING, ou));
}
if let Some(cn) = &self.common_name {
rdns.extend_from_slice(&rdn(oid::COMMON_NAME, tag::UTF8_STRING, cn));
}
encode_sequence(&rdns)
}
/// Reads one `Name` (`RDNSequence`) from `reader`.
pub(crate) fn decode(reader: &mut Reader) -> Result<Self, Error> {
let mut dn = DistinguishedName::default();
let mut seq = reader.read_sequence()?;
while !seq.is_empty() {
// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF
// AttributeTypeAndValue. Multi-valued RDNs are rare but legal —
// parse every AttributeTypeAndValue in the SET rather than
// silently dropping trailing ones (which would make two
// differently-named certificates render identically). An empty
// SET violates the SIZE (1..MAX) constraint and is rejected.
let set = seq.read_tlv(tag::SET)?;
let mut set_reader = Reader::new(set);
if set_reader.is_empty() {
return Err(Error::Malformed);
}
while !set_reader.is_empty() {
let mut atv = set_reader.read_sequence()?;
let oid_body = atv.read_oid()?;
let (_, value) = atv.read_any()?;
// Strict DER: an AttributeTypeAndValue is exactly
// `SEQUENCE { type, value }` — trailing bytes are rejected.
atv.finish()?;
let s: String = core::str::from_utf8(value)
.map_err(|_| Error::Malformed)?
.into();
// Reject embedded NUL and other control characters in
// attribute values. They have no legitimate place in a
// printable name and enable display spoofing or log injection
// when the decoded DN is later rendered. The byte-exact
// issuer/subject comparison used for chain building works on
// raw TLV bytes elsewhere and is unaffected by this check.
if s.chars().any(|c| c.is_control()) {
return Err(Error::Malformed);
}
let arcs = parse_oid(oid_body)?;
let arcs = arcs.as_slice();
if arcs == oid::COMMON_NAME {
dn.common_name = Some(s);
} else if arcs == oid::ORGANIZATION {
dn.organization = Some(s);
} else if arcs == oid::ORGANIZATIONAL_UNIT {
dn.organizational_unit = Some(s);
} else if arcs == oid::COUNTRY {
dn.country = Some(s);
}
// Unknown attributes are ignored.
}
set_reader.finish()?;
}
Ok(dn)
}
}
/// Encodes a single-attribute RDN: `SET { SEQUENCE { type OID, value } }`.
fn rdn(attr_oid: &[u64], value_tag: u8, value: &str) -> Vec<u8> {
let atv = encode_sequence(&[oid_tlv(attr_oid), encode_string(value_tag, value)].concat());
encode_tlv(tag::SET, &atv)
}
#[cfg(test)]
mod tests {
use super::*;
/// Builds a one-attribute `Name` (RDNSequence) DER whose single
/// commonName carries `value` as a UTF8String body (verbatim bytes, so a
/// NUL or control char survives into the encoding).
fn name_with_cn(value: &[u8]) -> Vec<u8> {
// commonName OID 2.5.4.3.
let mut atv = alloc::vec![0x06u8, 0x03, 0x55, 0x04, 0x03];
// value: UTF8String (0x0c) wrapping the raw bytes.
atv.push(0x0c);
atv.push(value.len() as u8);
atv.extend_from_slice(value);
let atv = encode_sequence(&atv); // AttributeTypeAndValue SEQUENCE
let set = encode_tlv(tag::SET, &atv); // RelativeDistinguishedName SET
encode_sequence(&set) // Name SEQUENCE OF RDN
}
#[test]
fn decode_accepts_clean_common_name() {
let der = name_with_cn(b"example.com");
let mut r = Reader::new(&der);
let dn = DistinguishedName::decode(&mut r).unwrap();
assert_eq!(dn.common_name.as_deref(), Some("example.com"));
}
/// Encodes one AttributeTypeAndValue SEQUENCE with `arcs` as the type and
/// a UTF8String `value`.
fn atv(arcs: &[u64], value: &str) -> Vec<u8> {
encode_sequence(&[oid_tlv(arcs), encode_string(tag::UTF8_STRING, value)].concat())
}
#[test]
fn decode_parses_multi_valued_rdn() {
// One SET carrying two AttributeTypeAndValues (CN + O). Both must be
// surfaced — dropping the trailing one would let two distinct names
// render identically.
let set = encode_tlv(
tag::SET,
&[atv(oid::COMMON_NAME, "leaf"), atv(oid::ORGANIZATION, "org")].concat(),
);
let der = encode_sequence(&set);
let mut r = Reader::new(&der);
let dn = DistinguishedName::decode(&mut r).unwrap();
assert_eq!(dn.common_name.as_deref(), Some("leaf"));
assert_eq!(dn.organization.as_deref(), Some("org"));
}
#[test]
fn decode_rejects_empty_rdn_set() {
// RelativeDistinguishedName ::= SET SIZE (1..MAX): an empty SET is
// malformed.
let set = encode_tlv(tag::SET, &[]);
let der = encode_sequence(&set);
let mut r = Reader::new(&der);
assert!(DistinguishedName::decode(&mut r).is_err());
}
#[test]
fn decode_rejects_trailing_bytes_inside_atv() {
// An AttributeTypeAndValue with trailing garbage after the value must
// be rejected, not silently accepted.
let mut inner = [oid_tlv(oid::COMMON_NAME), encode_string(0x0c, "x")].concat();
inner.push(0x00); // trailing junk inside the ATV SEQUENCE
let set = encode_tlv(tag::SET, &encode_sequence(&inner));
let der = encode_sequence(&set);
let mut r = Reader::new(&der);
assert!(DistinguishedName::decode(&mut r).is_err());
}
#[test]
fn decode_rejects_control_chars_in_value() {
for bad in [
b"evil\x00name".as_slice(),
b"line1\nline2".as_slice(),
b"tab\there".as_slice(),
] {
let der = name_with_cn(bad);
let mut r = Reader::new(&der);
assert!(
DistinguishedName::decode(&mut r).is_err(),
"should reject {bad:?}"
);
}
}
}