1 use crate::{oids, AlgorithmIdentifier, Extension, Extensions, Name, SubjectPublicKeyInfo};
2 use picky_asn1::tag::Tag;
3 use picky_asn1::wrapper::{Asn1SetOf, BitStringAsn1, ObjectIdentifierAsn1};
4 use serde::{de, ser, Deserialize, Serialize};
5 
6 /// [RFC 2986 #4](https://tools.ietf.org/html/rfc2986#section-4)
7 ///
8 /// ```not_rust
9 /// CertificationRequestInfo ::= SEQUENCE {
10 ///      version       INTEGER { v1(0) } (v1,...),
11 ///      subject       Name,
12 ///      subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
13 ///      attributes    [0] Attributes{{ CRIAttributes }}
14 /// }
15 /// ```
16 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
17 pub struct CertificationRequestInfo {
18     pub version: u8,
19     pub subject: Name,
20     pub subject_public_key_info: SubjectPublicKeyInfo,
21     pub attributes: Attributes,
22 }
23 
24 impl CertificationRequestInfo {
new(subject: Name, subject_public_key_info: SubjectPublicKeyInfo) -> Self25     pub fn new(subject: Name, subject_public_key_info: SubjectPublicKeyInfo) -> Self {
26         // It shall be 0 for this version of the standard.
27         Self {
28             version: 0,
29             subject,
30             subject_public_key_info,
31             attributes: Attributes(Vec::new()),
32         }
33     }
34 
with_attribute(mut self, attribute: Attribute) -> Self35     pub fn with_attribute(mut self, attribute: Attribute) -> Self {
36         self.attributes.0.push(attribute);
37         self
38     }
39 
add_attribute(&mut self, attribute: Attribute)40     pub fn add_attribute(&mut self, attribute: Attribute) {
41         self.attributes.0.push(attribute);
42     }
43 }
44 
45 // FIXME: this type is a hack to workaround [this issue](https://github.com/Devolutions/picky-rs/pull/78#issuecomment-789904165).
46 // Further refactorings are required to clean this up (proper support for IMPLICIT / EXPLICIT tags, etc)
47 #[derive(Clone, Debug, PartialEq)]
48 pub struct Attributes(pub Vec<Attribute>);
49 
50 impl ser::Serialize for Attributes {
serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error> where S: ser::Serializer,51     fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
52     where
53         S: ser::Serializer,
54     {
55         let mut raw_der = picky_asn1_der::to_vec(&self.0).unwrap_or_default();
56         raw_der[0] = Tag::APP_0.number();
57         picky_asn1_der::Asn1RawDer(raw_der).serialize(serializer)
58     }
59 }
60 
61 impl<'de> de::Deserialize<'de> for Attributes {
deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error> where D: de::Deserializer<'de>,62     fn deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error>
63     where
64         D: de::Deserializer<'de>,
65     {
66         let mut raw_der = picky_asn1_der::Asn1RawDer::deserialize(deserializer)?.0;
67         raw_der[0] = Tag::SEQUENCE.number();
68         let vec = picky_asn1_der::from_bytes(&raw_der).unwrap_or_default();
69         Ok(Attributes(vec))
70     }
71 }
72 
73 /// [RFC 2985 page 15 and 16](https://tools.ietf.org/html/rfc2985#page-15)
74 ///
75 /// Accepted attribute types are `challengePassword` and `extensionRequest`
76 ///
77 #[derive(Clone, Debug, PartialEq)]
78 pub enum AttributeValue {
79     /// `extensionRequest`
80     Extensions(Asn1SetOf<Extensions>), // the set will always have 1 element in this variant
81     // TODO: support for challenge password
82     // ChallengePassword(Asn1SetOf<ChallengePassword>))
83     Custom(picky_asn1_der::Asn1RawDer), // fallback
84 }
85 
86 #[derive(Clone, Debug, PartialEq)]
87 pub struct Attribute {
88     pub ty: ObjectIdentifierAsn1,
89     pub value: AttributeValue,
90 }
91 
92 impl Attribute {
new_extension_request(extensions: Vec<Extension>) -> Self93     pub fn new_extension_request(extensions: Vec<Extension>) -> Self {
94         Self {
95             ty: oids::extension_request().into(),
96             value: AttributeValue::Extensions(Asn1SetOf(vec![Extensions(extensions)])),
97         }
98     }
99 }
100 
101 impl ser::Serialize for Attribute {
serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error> where S: ser::Serializer,102     fn serialize<S>(&self, serializer: S) -> Result<<S as ser::Serializer>::Ok, <S as ser::Serializer>::Error>
103     where
104         S: ser::Serializer,
105     {
106         use ser::SerializeSeq;
107         let mut seq = serializer.serialize_seq(Some(2))?;
108         seq.serialize_element(&self.ty)?;
109         match &self.value {
110             AttributeValue::Extensions(extensions) => seq.serialize_element(extensions)?,
111             AttributeValue::Custom(der) => seq.serialize_element(der)?,
112         }
113         seq.end()
114     }
115 }
116 
117 impl<'de> de::Deserialize<'de> for Attribute {
deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error> where D: de::Deserializer<'de>,118     fn deserialize<D>(deserializer: D) -> Result<Self, <D as de::Deserializer<'de>>::Error>
119     where
120         D: de::Deserializer<'de>,
121     {
122         use std::fmt;
123 
124         struct Visitor;
125 
126         impl<'de> de::Visitor<'de> for Visitor {
127             type Value = Attribute;
128 
129             fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
130                 formatter.write_str("a valid DER-encoded attribute")
131             }
132 
133             fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
134             where
135                 A: de::SeqAccess<'de>,
136             {
137                 let ty: ObjectIdentifierAsn1 = seq_next_element!(seq, Attribute, "type oid");
138 
139                 let value = match Into::<String>::into(&ty.0).as_str() {
140                     oids::EXTENSION_REQ => {
141                         AttributeValue::Extensions(seq_next_element!(seq, Attribute, "at extension request"))
142                     }
143                     _ => AttributeValue::Custom(seq_next_element!(seq, Attribute, "at custom value")),
144                 };
145 
146                 Ok(Attribute { ty, value })
147             }
148         }
149 
150         deserializer.deserialize_seq(Visitor)
151     }
152 }
153 
154 /// [RFC 2986 #4](https://tools.ietf.org/html/rfc2986#section-4)
155 ///
156 /// ```not_rust
157 /// CertificationRequest ::= SEQUENCE {
158 ///      certificationRequestInfo CertificationRequestInfo,
159 ///      signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
160 ///      signature          BIT STRING
161 /// }
162 /// ```
163 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
164 pub struct CertificationRequest {
165     pub certification_request_info: CertificationRequestInfo,
166     pub signature_algorithm: AlgorithmIdentifier,
167     pub signature: BitStringAsn1,
168 }
169 
170 #[cfg(test)]
171 mod tests {
172     use super::*;
173     use crate::name::*;
174     use crate::{DirectoryName, GeneralName};
175     use picky_asn1::bit_string::BitString;
176     use picky_asn1::restricted_string::{IA5String, PrintableString, Utf8String};
177     use picky_asn1::wrapper::IntegerAsn1;
178     use std::str::FromStr;
179 
180     #[test]
basic_csr()181     fn basic_csr() {
182         let encoded = base64::decode(
183             "MIICYjCCAUoCAQAwHTEbMBkGA1UEAxMSdGVzdC5jb250b3NvLmxvY2FsMIIBIjAN\
184             BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAym0At2TvEqP0mYVLJzGVpNXjugu/\
185             kBpuKvXt/Vax4Bxnj3YzHTCpwkyZPytUC6zJ+q+uGh0e7gYQsYHJKjgoKEsS6gQ4\
186             ZM3D/AQy0zqPUT0ruSKDWKK4f2d/2ijDs5R2LHj7DtNZBanCXU16Qp1O28su0QZK\
187             OYbXzsJSpHp80dhqD6JUxXlSZzlVBp28CC9ryrE6w+kOQ38TZ1/mBJPsfmDeKBpm\
188             3FRrfHtWt43eok/T6FhCLIzsqyCZ0UCQqkcLr+TfoftJe2nOHQ1sfk4keJ9iwA/f\
189             hYv5rqUB3RUztSIhExwtYDwd+YovenhsL4sW/kjR29RTLUFPPXAelG9XPwIDAQAB\
190             oAAwDQYJKoZIhvcNAQELBQADggEBAKrCf4sFDBFZQ6CPYdaxe3InMp7KFaueMIB8\
191             /YK73rJ+JGB6fQfltCCkToTE1y0Q3UqTlqHmaqdoh0KMWue6jCFvBat4/TUqUG7W\
192             tRLDP67eMulolcIzLqwTjR38DVJvnwrd2pey43q3UHBjlStxT/gI4ysQHn4qrzHB\
193             6OK9O6ypqTtwXxnm3TJF9dctLwvbh7NZSaamSlxI0/ajKZOP9k1KZEOPtaiiMPe2\
194             yr+QvwY2ov66MRG5PPRZELQWBaPZOuFwmCsFOLXJMpvhoAgklBCFZmiQMgApGIC1\
195             FIDgjm2ZhQQIRMnTsAV6f7BclRTaUkc0sPl17YB9GfNfOm1oL7o=",
196         )
197         .expect("invalid base64");
198 
199         let certification_request_info = CertificationRequestInfo::new(
200             DirectoryName::new_common_name(PrintableString::from_str("test.contoso.local").unwrap()).into(),
201             SubjectPublicKeyInfo::new_rsa_key(
202                 IntegerAsn1::from(encoded[74..331].to_vec()),
203                 IntegerAsn1::from(encoded[333..336].to_vec()),
204             ),
205         );
206 
207         check_serde!(certification_request_info: CertificationRequestInfo in encoded[4..338]);
208 
209         let csr = CertificationRequest {
210             certification_request_info,
211             signature_algorithm: AlgorithmIdentifier::new_sha256_with_rsa_encryption(),
212             signature: BitString::with_bytes(&encoded[358..614]).into(),
213         };
214 
215         check_serde!(csr: CertificationRequest in encoded);
216     }
217 
218     #[test]
csr_with_extensions_attribute()219     fn csr_with_extensions_attribute() {
220         let encoded = base64::decode(
221             "MIICjDCCAXQCAQAwIDELMAkGA1UEBhMCWFgxETAPBgNVBAMMCHNvbWV0ZXN0MIIB\
222             IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNELh212N4optYS7pqbtvjyv\
223             +t4fQjX/pwB88BUCEjBgh+DJ49EBPQg9oObADTcBi3EeXu4M5y6f/dzIhovayJ/y\
224             9j7Cj0Bw+VY+eRXywkVG/DqaiKG2mIQW+fho7/jhazhpeIxCzObPTwiQK7i96Vjq\
225             9S+o4QQejE2SYLOhQ4/cgUaT7JBm4yab7cvhFjKYjVmoP6ioIcHb9Cmv25Lttuvk\
226             n64bDiPKz6BkutRpbMipQjSA8xKEgjgFG/nxBynA8PXnZIunhTNyhXrqRoAe6SXn\
227             ZLZLmwOkeU5WTewVVTXlmqaZTPwtb/9EjjoRnO3+Ulb5zT5wPULc79xuY16kzwID\
228             AQABoCcwJQYJKoZIhvcNAQkOMRgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJ\
229             KoZIhvcNAQELBQADggEBAIm9lOhZG3XY4CNJ5b18Qu/OfFi+T0tgxt4bTqINQ1Iz\
230             SQFrsnheBrzmasfFliz10N96cOmNka1UpWqK7N5/TfkJHX3zKYRpc2jEkrFun48B\
231             3+bOJJPH48zmTGxBgU7iiorpaVt3CpgXNswhU3fpcT5gLy8Ys7DXC39Nn1lW0Lko\
232             cd6xK4oIJyoeiXyVBdn68gtPY6xjFxta67nyj39sSGhATxrDgxtLHEH2+HStywr0\
233             4/osg9vP/OH5iFYOiEimK6ErYNg8rM1A/OTe5p8emA6y3o5dHG8lKYwevyUXMSLv\
234             38CNeh0MS2KmyHz2085HlIIAXIu2xAUyWLsQik+eV6M=",
235         )
236         .expect("invalid base64");
237 
238         let extensions = vec![Extension::new_subject_alt_name(vec![GeneralName::DNSName(
239             IA5String::from_string("localhost".into()).unwrap().into(),
240         )])
241         .into_non_critical()];
242 
243         let mut dn = DirectoryName::new();
244         dn.add_attr(NameAttr::CountryName, PrintableString::from_str("XX").unwrap());
245         dn.add_attr(NameAttr::CommonName, Utf8String::from_str("sometest").unwrap());
246 
247         let certification_request_info = CertificationRequestInfo::new(
248             dn.into(),
249             SubjectPublicKeyInfo::new_rsa_key(
250                 IntegerAsn1::from(encoded[77..334].to_vec()),
251                 IntegerAsn1::from(encoded[336..339].to_vec()),
252             ),
253         )
254         .with_attribute(Attribute::new_extension_request(extensions));
255 
256         check_serde!(certification_request_info: CertificationRequestInfo in encoded[4..380]);
257 
258         let csr = CertificationRequest {
259             certification_request_info,
260             signature_algorithm: AlgorithmIdentifier::new_sha256_with_rsa_encryption(),
261             signature: BitString::with_bytes(&encoded[400..656]).into(),
262         };
263 
264         check_serde!(csr: CertificationRequest in encoded);
265     }
266 }
267