1 //! Name tests
2 
3 use const_oid::ObjectIdentifier;
4 use der::asn1::{Ia5StringRef, OctetStringRef, PrintableStringRef, SetOfVec, Utf8StringRef};
5 use der::{Any, Decode, Encode, Tag, Tagged};
6 use hex_literal::hex;
7 use x509_cert::attr::AttributeTypeAndValue;
8 use x509_cert::name::{Name, RdnSequence, RelativeDistinguishedName};
9 
10 #[test]
decode_name()11 fn decode_name() {
12     // 134  64:     SEQUENCE {
13     // 136  11:       SET {
14     // 138   9:         SEQUENCE {
15     // 140   3:           OBJECT IDENTIFIER countryName (2 5 4 6)
16     // 145   2:           PrintableString 'US'
17     //        :           }
18     //        :         }
19     // 149  31:       SET {
20     // 151  29:         SEQUENCE {
21     // 153   3:           OBJECT IDENTIFIER organizationName (2 5 4 10)
22     // 158  22:           PrintableString 'Test Certificates 2011'
23     //        :           }
24     //        :         }
25     // 182  16:       SET {
26     // 184  14:         SEQUENCE {
27     // 186   3:           OBJECT IDENTIFIER commonName (2 5 4 3)
28     // 191   7:           PrintableString 'Good CA'
29     //        :           }
30     //        :         }
31     //        :       }
32     let rdn1 =
33         Name::from_der(&hex!("3040310B3009060355040613025553311F301D060355040A1316546573742043657274696669636174657320323031313110300E06035504031307476F6F64204341")[..]);
34     let rdn1a = rdn1.unwrap();
35 
36     let mut counter = 0;
37     let i = rdn1a.0.iter();
38     for rdn in i {
39         let i1 = rdn.0.iter();
40         for atav in i1 {
41             if 0 == counter {
42                 assert_eq!(atav.oid.to_string(), "2.5.4.6");
43                 assert_eq!(
44                     PrintableStringRef::try_from(&atav.value)
45                         .unwrap()
46                         .to_string(),
47                     "US"
48                 );
49             } else if 1 == counter {
50                 assert_eq!(atav.oid.to_string(), "2.5.4.10");
51                 assert_eq!(
52                     PrintableStringRef::try_from(&atav.value)
53                         .unwrap()
54                         .to_string(),
55                     "Test Certificates 2011"
56                 );
57             } else if 2 == counter {
58                 assert_eq!(atav.oid.to_string(), "2.5.4.3");
59                 assert_eq!(
60                     PrintableStringRef::try_from(&atav.value)
61                         .unwrap()
62                         .to_string(),
63                     "Good CA"
64                 );
65             }
66             counter += 1;
67         }
68     }
69 
70     #[cfg(feature = "std")]
71     {
72         // https://datatracker.ietf.org/doc/html/rfc4514.html#section-2.1
73         // If the RDNSequence is an empty sequence, the result is the empty or
74         // zero-length string.
75         // Otherwise, the output consists of the string encodings of each
76         // RelativeDistinguishedName in the RDNSequence (according to Section 2.2),
77         // starting with the last element of the sequence and moving backwards
78         // toward the first.
79         // The encodings of adjoining RelativeDistinguishedNames are separated by
80         // a comma (',' U+002C) character.
81         let name = rdn1a.to_string();
82         assert_eq!(name, "CN=Good CA,O=Test Certificates 2011,C=US");
83 
84         // https://github.com/RustCrypto/formats/issues/1121
85         let rdn1 = Name::from_der(&hex!("3081c0310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f676c65204c4c43311e301c06035504030c154f51464176444e4457732e676f6f676c652e636f6d31243022060355040b0c1b6d616e6167656d656e743a64732e67726f75702e3338393131313131293027060a0992268993f22c6401010c196964656e746974793a64732e67726f75702e33383931313131")[..]);
86         let rdn1a = rdn1.unwrap();
87         let name = rdn1a.to_string();
88         assert_eq!(name, "UID=identity:ds.group.3891111,OU=management:ds.group.3891111,CN=OQFAvDNDWs.google.com,O=Google LLC,L=Mountain View,ST=California,C=US");
89     }
90 }
91 
92 #[test]
decode_rdn()93 fn decode_rdn() {
94     //  0  11: SET {
95     //   2   9:   SEQUENCE {
96     //   4   3:     OBJECT IDENTIFIER countryName (2 5 4 6)
97     //   9   2:     PrintableString 'US'
98     //        :     }
99     //        :   }
100     let rdn1 =
101         RelativeDistinguishedName::from_der(&hex!("310B3009060355040613025553")[..]).unwrap();
102     let i = rdn1.0.iter();
103     for atav in i {
104         let oid = atav.oid;
105         assert_eq!(oid.to_string(), "2.5.4.6");
106         let value = &atav.value;
107         assert_eq!(value.tag(), Tag::PrintableString);
108         let ps = PrintableStringRef::try_from(value).unwrap();
109         assert_eq!(ps.to_string(), "US");
110     }
111 
112     //  0  31: SET {
113     //   2  17:   SEQUENCE {
114     //   4   3:     OBJECT IDENTIFIER commonName (2 5 4 3)
115     //   9  10:     UTF8String 'JOHN SMITH'
116     //        :     }
117     //  21  10:   SEQUENCE {
118     //  23   3:     OBJECT IDENTIFIER organizationName (2 5 4 10)
119     //  28   3:     UTF8String '123'
120     //        :     }
121     //        :   }
122 
123     // reordered the encoding so second element above appears first so parsing can succeed
124     let rdn2a = RelativeDistinguishedName::from_der(
125         &hex!("311F300A060355040A0C03313233301106035504030C0A4A4F484E20534D495448")[..],
126     )
127     .unwrap();
128     let mut i = rdn2a.0.iter();
129     let atav1a = i.next().unwrap();
130     let oid2 = atav1a.oid;
131     assert_eq!(oid2.to_string(), "2.5.4.10");
132     let value2 = &atav1a.value;
133     assert_eq!(value2.tag(), Tag::Utf8String);
134     let utf8b = Utf8StringRef::try_from(value2).unwrap();
135     assert_eq!(utf8b.to_string(), "123");
136 
137     let atav2a = i.next().unwrap();
138     let oid1 = atav2a.oid;
139     assert_eq!(oid1.to_string(), "2.5.4.3");
140     let value1 = &atav2a.value;
141     assert_eq!(value1.tag(), Tag::Utf8String);
142     let utf8a = Utf8StringRef::try_from(value1).unwrap();
143     assert_eq!(utf8a.to_string(), "JOHN SMITH");
144 
145     let mut from_scratch = RelativeDistinguishedName::default();
146     assert!(from_scratch.0.insert(atav1a.clone()).is_ok());
147     assert!(from_scratch.0.insert(atav2a.clone()).is_ok());
148     let reencoded = from_scratch.to_der().unwrap();
149     assert_eq!(
150         reencoded,
151         &hex!("311F300A060355040A0C03313233301106035504030C0A4A4F484E20534D495448")
152     );
153 
154     let mut from_scratch2 = RelativeDistinguishedName::default();
155     assert!(from_scratch2.0.insert_ordered(atav2a.clone()).is_ok());
156     // fails when caller adds items not in DER lexicographical order
157     assert!(from_scratch2.0.insert_ordered(atav1a.clone()).is_err());
158 
159     // allow out-of-order RDNs (see: RustCrypto/formats#625)
160     assert!(RelativeDistinguishedName::from_der(
161         &hex!("311F301106035504030C0A4A4F484E20534D495448300A060355040A0C03313233")[..],
162     )
163     .is_ok());
164 }
165 
166 // #[test]
167 // fn encode_atav() {
168 //     //  0  11: SET {
169 //     //   2   9:   SEQUENCE {
170 //     //   4   3:     OBJECT IDENTIFIER countryName (2 5 4 6)
171 //     //   9   2:     PrintableString 'US'
172 //     //        :     }
173 //     //        :   }
174 //     let rdn1 =
175 //         RelativeDistinguishedName::from_der(&hex!("310B3009060355040613025553")[..]).unwrap();
176 //
177 //     // Re-encode and compare to reference
178 //     let b1 = rdn1.to_vec().unwrap();
179 //     assert_eq!(b1, &hex!("310B3009060355040613025553")[..]);
180 //     let mut i = rdn1.iter();
181 //     let atav1 = i.next().unwrap();
182 //
183 //     //  0  31: SET {
184 //     //   2  17:   SEQUENCE {
185 //     //   4   3:     OBJECT IDENTIFIER commonName (2 5 4 3)
186 //     //   9  10:     UTF8String 'JOHN SMITH'
187 //     //        :     }
188 //     //  21  10:   SEQUENCE {
189 //     //  23   3:     OBJECT IDENTIFIER organizationName (2 5 4 10)
190 //     //  28   3:     UTF8String '123'
191 //     //        :     }
192 //     //        :   }
193 //     let rdn2 = RelativeDistinguishedName::from_der(
194 //         &hex!("311F301106035504030C0A4A4F484E20534D495448300A060355040A0C03313233")[..],
195 //     )
196 //     .unwrap();
197 //
198 //     // Re-encode and compare to reference
199 //     let b1 = rdn2.to_vec().unwrap();
200 //     assert_eq!(
201 //         b1,
202 //         &hex!("311F301106035504030C0A4A4F484E20534D495448300A060355040A0C03313233")[..]
203 //     );
204 //
205 //     let mut i = rdn2.iter();
206 //     let atav2 = i.next().unwrap();
207 //
208 //     // Create new AttributeTypeAndValue with OID from second item above and value from first
209 //     let atav3: AttributeTypeAndValue = AttributeTypeAndValue {
210 //         oid: atav2.oid,
211 //         value: atav1.value,
212 //     };
213 //     let b3 = atav3.to_vec().unwrap();
214 //     assert_eq!(b3, &hex!("3009060355040313025553")[..]);
215 // }
216 
217 /// Tests RdnSequence string serialization and deserialization
218 #[test]
rdns_serde()219 fn rdns_serde() {
220     #[allow(clippy::type_complexity)]
221     let values: &[(&[&str], &str, &[&[AttributeTypeAndValue]])] = &[
222         (
223             &[
224                 "CN=foo,SN=bar,C=baz+L=bat",
225                 "commonName=foo,sn=bar,COUNTRYNAME=baz+l=bat",
226             ],
227             "CN=foo,SN=bar,C=baz+L=bat",
228             &[
229                 &[
230                     AttributeTypeAndValue {
231                         oid: const_oid::db::rfc4519::C,
232                         value: Any::from(PrintableStringRef::new("baz").unwrap()),
233                     },
234                     AttributeTypeAndValue {
235                         oid: const_oid::db::rfc4519::L,
236                         value: Any::from(Utf8StringRef::new("bat").unwrap()),
237                     },
238                 ],
239                 &[AttributeTypeAndValue {
240                     oid: const_oid::db::rfc4519::SN,
241                     value: Any::from(Utf8StringRef::new("bar").unwrap()),
242                 }],
243                 &[AttributeTypeAndValue {
244                     oid: const_oid::db::rfc4519::CN,
245                     value: Any::from(Utf8StringRef::new("foo").unwrap()),
246                 }],
247             ],
248         ),
249         (
250             &["UID=jsmith,DC=example,DC=net"],
251             "UID=jsmith,DC=example,DC=net",
252             &[
253                 &[AttributeTypeAndValue {
254                     oid: const_oid::db::rfc4519::DC,
255                     value: Any::from(Ia5StringRef::new("net").unwrap()),
256                 }],
257                 &[AttributeTypeAndValue {
258                     oid: const_oid::db::rfc4519::DC,
259                     value: Any::from(Ia5StringRef::new("example").unwrap()),
260                 }],
261                 &[AttributeTypeAndValue {
262                     oid: const_oid::db::rfc4519::UID,
263                     value: Any::from(Utf8StringRef::new("jsmith").unwrap()),
264                 }],
265             ],
266         ),
267         (
268             &["OU=Sales+CN=J.  Smith,DC=example,DC=net"],
269             "OU=Sales+CN=J.  Smith,DC=example,DC=net",
270             &[
271                 &[AttributeTypeAndValue {
272                     oid: const_oid::db::rfc4519::DC,
273                     value: Any::from(Ia5StringRef::new("net").unwrap()),
274                 }],
275                 &[AttributeTypeAndValue {
276                     oid: const_oid::db::rfc4519::DC,
277                     value: Any::from(Ia5StringRef::new("example").unwrap()),
278                 }],
279                 &[
280                     AttributeTypeAndValue {
281                         oid: const_oid::db::rfc4519::OU,
282                         value: Any::from(Utf8StringRef::new("Sales").unwrap()),
283                     },
284                     AttributeTypeAndValue {
285                         oid: const_oid::db::rfc4519::CN,
286                         value: Any::from(Utf8StringRef::new("J.  Smith").unwrap()),
287                     },
288                 ],
289             ],
290         ),
291         (
292             &["CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net"],
293             "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
294             &[
295                 &[AttributeTypeAndValue {
296                     oid: const_oid::db::rfc4519::DC,
297                     value: Any::from(Ia5StringRef::new("net").unwrap()),
298                 }],
299                 &[AttributeTypeAndValue {
300                     oid: const_oid::db::rfc4519::DC,
301                     value: Any::from(Ia5StringRef::new("example").unwrap()),
302                 }],
303                 &[AttributeTypeAndValue {
304                     oid: const_oid::db::rfc4519::CN,
305                     value: Any::from(Utf8StringRef::new(r#"James "Jim" Smith, III"#).unwrap()),
306                 }],
307             ],
308         ),
309         (
310             &["CN=Before\\0dAfter,DC=example,DC=net"],
311             "CN=Before\\0dAfter,DC=example,DC=net",
312             &[
313                 &[AttributeTypeAndValue {
314                     oid: const_oid::db::rfc4519::DC,
315                     value: Any::from(Ia5StringRef::new("net").unwrap()),
316                 }],
317                 &[AttributeTypeAndValue {
318                     oid: const_oid::db::rfc4519::DC,
319                     value: Any::from(Ia5StringRef::new("example").unwrap()),
320                 }],
321                 &[AttributeTypeAndValue {
322                     oid: const_oid::db::rfc4519::CN,
323                     value: Any::from(Utf8StringRef::new("Before\rAfter").unwrap()),
324                 }],
325             ],
326         ),
327         (
328             &["1.3.6.1.4.1.1466.0=#04024869"],
329             "1.3.6.1.4.1.1466.0=#04024869",
330             &[&[AttributeTypeAndValue {
331                 oid: ObjectIdentifier::new("1.3.6.1.4.1.1466.0").unwrap(),
332                 value: Any::from(OctetStringRef::new(&[b'H', b'i']).unwrap()),
333             }]],
334         ),
335     ];
336 
337     for (inputs, output, rdns) in values {
338         let mut brdns = RdnSequence::default();
339         for rdn in rdns.iter() {
340             let sofv = SetOfVec::try_from(rdn.to_vec()).unwrap();
341             brdns.0.push(RelativeDistinguishedName::from(sofv));
342         }
343 
344         // Check that serialization matches the expected output.
345         eprintln!("output: {}", output);
346         assert_eq!(*output, format!("{}", brdns));
347 
348         // Check that all inputs deserializize as expected.
349         for input in inputs.iter() {
350             eprintln!("input: {}", input);
351 
352             let der = input
353                 .parse::<RdnSequence>()
354                 .and_then(|rdn| rdn.to_der())
355                 .unwrap();
356 
357             let rdns = RdnSequence::from_der(&der).unwrap();
358 
359             for (l, r) in brdns.0.iter().zip(rdns.0.iter()) {
360                 for (ll, rr) in l.0.iter().zip(r.0.iter()) {
361                     assert_eq!(ll, rr);
362                 }
363 
364                 assert_eq!(l, r);
365             }
366 
367             assert_eq!(brdns, rdns);
368         }
369     }
370 }
371