@@ -81,6 +81,43 @@ pub const NamedCurve = enum {
81
81
});
82
82
};
83
83
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
+
84
121
pub const Parsed = struct {
85
122
certificate : Certificate ,
86
123
issuer_slice : Slice ,
@@ -91,6 +128,7 @@ pub const Parsed = struct {
91
128
pub_key_algo : PubKeyAlgo ,
92
129
pub_key_slice : Slice ,
93
130
message_slice : Slice ,
131
+ subject_alt_name_slice : Slice ,
94
132
validity : Validity ,
95
133
96
134
pub const PubKeyAlgo = union (AlgorithmCategory ) {
@@ -137,6 +175,10 @@ pub const Parsed = struct {
137
175
return p .slice (p .message_slice );
138
176
}
139
177
178
+ pub fn subjectAltName (p : Parsed ) []const u8 {
179
+ return p .slice (p .subject_alt_name_slice );
180
+ }
181
+
140
182
pub const VerifyError = error {
141
183
CertificateIssuerMismatch ,
142
184
CertificateNotYetValid ,
@@ -152,8 +194,10 @@ pub const Parsed = struct {
152
194
CertificateSignatureNamedCurveUnsupported ,
153
195
};
154
196
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.
157
201
pub fn verify (parsed_subject : Parsed , parsed_issuer : Parsed ) VerifyError ! void {
158
202
// Check that the subject's issuer name matches the issuer's
159
203
// subject name.
@@ -194,6 +238,62 @@ pub const Parsed = struct {
194
238
),
195
239
}
196
240
}
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
+ }
197
297
};
198
298
199
299
pub fn parse (cert : Certificate ) ! Parsed {
@@ -268,6 +368,39 @@ pub fn parse(cert: Certificate) !Parsed {
268
368
const sig_elem = try der .Element .parse (cert_bytes , sig_algo .slice .end );
269
369
const signature = try parseBitString (cert , sig_elem );
270
370
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
+
271
404
return .{
272
405
.certificate = cert ,
273
406
.common_name_slice = common_name ,
@@ -282,6 +415,7 @@ pub fn parse(cert: Certificate) !Parsed {
282
415
.not_before = not_before_utc ,
283
416
.not_after = not_after_utc ,
284
417
},
418
+ .subject_alt_name_slice = subject_alt_name_slice ,
285
419
};
286
420
}
287
421
@@ -444,6 +578,10 @@ pub fn parseNamedCurve(bytes: []const u8, element: der.Element) !NamedCurve {
444
578
return parseEnum (NamedCurve , bytes , element );
445
579
}
446
580
581
+ pub fn parseExtensionId (bytes : []const u8 , element : der.Element ) ! ExtensionId {
582
+ return parseEnum (ExtensionId , bytes , element );
583
+ }
584
+
447
585
fn parseEnum (comptime E : type , bytes : []const u8 , element : der.Element ) ! E {
448
586
if (element .identifier .tag != .object_identifier )
449
587
return error .CertificateFieldHasWrongDataType ;
@@ -604,6 +742,7 @@ pub const der = struct {
604
742
boolean = 1 ,
605
743
integer = 2 ,
606
744
bitstring = 3 ,
745
+ octetstring = 4 ,
607
746
null = 5 ,
608
747
object_identifier = 6 ,
609
748
sequence = 16 ,
0 commit comments