@@ -81,6 +81,43 @@ pub const NamedCurve = enum {
8181 });
8282};
8383
84+ pub const ExtensionId = enum {
85+ subject_key_identifier ,
86+ key_usage ,
87+ private_key_usage_period ,
88+ subject_alt_name ,
89+ issuer_alt_name ,
90+ basic_constraints ,
91+ crl_number ,
92+ certificate_policies ,
93+ authority_key_identifier ,
94+
95+ pub const map = std .ComptimeStringMap (ExtensionId , .{
96+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x0E }, .subject_key_identifier },
97+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x0F }, .key_usage },
98+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x10 }, .private_key_usage_period },
99+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x11 }, .subject_alt_name },
100+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x12 }, .issuer_alt_name },
101+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x13 }, .basic_constraints },
102+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x14 }, .crl_number },
103+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x20 }, .certificate_policies },
104+ .{ &[_ ]u8 { 0x55 , 0x1D , 0x23 }, .authority_key_identifier },
105+ });
106+ };
107+
108+ pub const GeneralNameTag = enum (u5 ) {
109+ otherName = 0 ,
110+ rfc822Name = 1 ,
111+ dNSName = 2 ,
112+ x400Address = 3 ,
113+ directoryName = 4 ,
114+ ediPartyName = 5 ,
115+ uniformResourceIdentifier = 6 ,
116+ iPAddress = 7 ,
117+ registeredID = 8 ,
118+ _ ,
119+ };
120+
84121pub const Parsed = struct {
85122 certificate : Certificate ,
86123 issuer_slice : Slice ,
@@ -91,6 +128,7 @@ pub const Parsed = struct {
91128 pub_key_algo : PubKeyAlgo ,
92129 pub_key_slice : Slice ,
93130 message_slice : Slice ,
131+ subject_alt_name_slice : Slice ,
94132 validity : Validity ,
95133
96134 pub const PubKeyAlgo = union (AlgorithmCategory ) {
@@ -137,6 +175,10 @@ pub const Parsed = struct {
137175 return p .slice (p .message_slice );
138176 }
139177
178+ pub fn subjectAltName (p : Parsed ) []const u8 {
179+ return p .slice (p .subject_alt_name_slice );
180+ }
181+
140182 pub const VerifyError = error {
141183 CertificateIssuerMismatch ,
142184 CertificateNotYetValid ,
@@ -152,8 +194,10 @@ pub const Parsed = struct {
152194 CertificateSignatureNamedCurveUnsupported ,
153195 };
154196
155- /// This function checks the time validity for the subject only. Checking
156- /// the issuer's time validity is out of scope.
197+ /// This function verifies:
198+ /// * That the subject's issuer is indeed the provided issuer.
199+ /// * The time validity of the subject.
200+ /// * The signature.
157201 pub fn verify (parsed_subject : Parsed , parsed_issuer : Parsed ) VerifyError ! void {
158202 // Check that the subject's issuer name matches the issuer's
159203 // subject name.
@@ -194,6 +238,62 @@ pub const Parsed = struct {
194238 ),
195239 }
196240 }
241+
242+ pub const VerifyHostNameError = error {
243+ CertificateHostMismatch ,
244+ CertificateFieldHasInvalidLength ,
245+ };
246+
247+ pub fn verifyHostName (parsed_subject : Parsed , host_name : []const u8 ) VerifyHostNameError ! void {
248+ // If the Subject Alternative Names extension is present, this is
249+ // what to check. Otherwise, only the common name is checked.
250+ const subject_alt_name = parsed_subject .subjectAltName ();
251+ if (subject_alt_name .len == 0 ) {
252+ if (checkHostName (host_name , parsed_subject .commonName ())) {
253+ return ;
254+ } else {
255+ return error .CertificateHostMismatch ;
256+ }
257+ }
258+
259+ const general_names = try der .Element .parse (subject_alt_name , 0 );
260+ var name_i = general_names .slice .start ;
261+ while (name_i < general_names .slice .end ) {
262+ const general_name = try der .Element .parse (subject_alt_name , name_i );
263+ name_i = general_name .slice .end ;
264+ switch (@intToEnum (GeneralNameTag , @enumToInt (general_name .identifier .tag ))) {
265+ .dNSName = > {
266+ const dns_name = subject_alt_name [general_name .slice .start .. general_name .slice .end ];
267+ if (checkHostName (host_name , dns_name )) return ;
268+ },
269+ else = > {},
270+ }
271+ }
272+
273+ return error .CertificateHostMismatch ;
274+ }
275+
276+ fn checkHostName (host_name : []const u8 , dns_name : []const u8 ) bool {
277+ if (mem .eql (u8 , dns_name , host_name )) {
278+ return true ; // exact match
279+ }
280+
281+ if (mem .startsWith (u8 , dns_name , "*." )) {
282+ // wildcard certificate, matches any subdomain
283+ // TODO: I think wildcards are not supposed to match any prefix but
284+ // only match exactly one subdomain.
285+ if (mem .endsWith (u8 , host_name , dns_name [1.. ])) {
286+ // The host_name has a subdomain, but the important part matches.
287+ return true ;
288+ }
289+ if (mem .eql (u8 , dns_name [2.. ], host_name )) {
290+ // The host_name has no subdomain and matches exactly.
291+ return true ;
292+ }
293+ }
294+
295+ return false ;
296+ }
197297};
198298
199299pub fn parse (cert : Certificate ) ! Parsed {
@@ -268,6 +368,39 @@ pub fn parse(cert: Certificate) !Parsed {
268368 const sig_elem = try der .Element .parse (cert_bytes , sig_algo .slice .end );
269369 const signature = try parseBitString (cert , sig_elem );
270370
371+ // Extensions
372+ var subject_alt_name_slice = der .Element .Slice .empty ;
373+ ext : {
374+ if (pub_key_info .slice .end >= tbs_certificate .slice .end )
375+ break :ext ;
376+
377+ const outer_extensions = try der .Element .parse (cert_bytes , pub_key_info .slice .end );
378+ if (outer_extensions .identifier .tag != .bitstring )
379+ break :ext ;
380+
381+ const extensions = try der .Element .parse (cert_bytes , outer_extensions .slice .start );
382+
383+ var ext_i = extensions .slice .start ;
384+ while (ext_i < extensions .slice .end ) {
385+ const extension = try der .Element .parse (cert_bytes , ext_i );
386+ ext_i = extension .slice .end ;
387+ const oid_elem = try der .Element .parse (cert_bytes , extension .slice .start );
388+ const ext_id = parseExtensionId (cert_bytes , oid_elem ) catch | err | switch (err ) {
389+ error .CertificateHasUnrecognizedObjectId = > continue ,
390+ else = > | e | return e ,
391+ };
392+ const critical_elem = try der .Element .parse (cert_bytes , oid_elem .slice .end );
393+ const ext_bytes_elem = if (critical_elem .identifier .tag != .boolean )
394+ critical_elem
395+ else
396+ try der .Element .parse (cert_bytes , critical_elem .slice .end );
397+ switch (ext_id ) {
398+ .subject_alt_name = > subject_alt_name_slice = ext_bytes_elem .slice ,
399+ else = > continue ,
400+ }
401+ }
402+ }
403+
271404 return .{
272405 .certificate = cert ,
273406 .common_name_slice = common_name ,
@@ -282,6 +415,7 @@ pub fn parse(cert: Certificate) !Parsed {
282415 .not_before = not_before_utc ,
283416 .not_after = not_after_utc ,
284417 },
418+ .subject_alt_name_slice = subject_alt_name_slice ,
285419 };
286420}
287421
@@ -444,6 +578,10 @@ pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve {
444578 return parseEnum (NamedCurve , bytes , element );
445579}
446580
581+ pub fn parseExtensionId (bytes : []const u8 , element : der.Element ) ! ExtensionId {
582+ return parseEnum (ExtensionId , bytes , element );
583+ }
584+
447585fn parseEnum (comptime E : type , bytes : []const u8 , element : der.Element ) ! E {
448586 if (element .identifier .tag != .object_identifier )
449587 return error .CertificateFieldHasWrongDataType ;
@@ -604,6 +742,7 @@ pub const der = struct {
604742 boolean = 1 ,
605743 integer = 2 ,
606744 bitstring = 3 ,
745+ octetstring = 4 ,
607746 null = 5 ,
608747 object_identifier = 6 ,
609748 sequence = 16 ,
0 commit comments