| ... | ... | @@ -79,6 +79,12 @@ pub const Parsed = struct { |
| 79 | 79 | pub_key_algo: AlgorithmCategory, |
| 80 | 80 | pub_key_slice: Slice, |
| 81 | 81 | message_slice: Slice, |
| 82 | validity: Validity, |
| 83 | |
| 84 | pub const Validity = struct { |
| 85 | not_before: u64, |
| 86 | not_after: u64, |
| 87 | }; |
| 82 | 88 | |
| 83 | 89 | pub const Slice = der.Element.Slice; |
| 84 | 90 | |
| ... | ... | @@ -110,6 +116,8 @@ pub const Parsed = struct { |
| 110 | 116 | return p.slice(p.message_slice); |
| 111 | 117 | } |
| 112 | 118 | |
| 119 | /// This function checks the time validity for the subject only. Checking |
| 120 | /// the issuer's time validity is out of scope. |
| 113 | 121 | pub fn verify(parsed_subject: Parsed, parsed_issuer: Parsed) !void { |
| 114 | 122 | // Check that the subject's issuer name matches the issuer's |
| 115 | 123 | // subject name. |
| ... | ... | @@ -117,8 +125,11 @@ pub const Parsed = struct { |
| 117 | 125 | return error.CertificateIssuerMismatch; |
| 118 | 126 | } |
| 119 | 127 | |
| 120 | | // TODO check the time validity for the subject |
| 121 | | // TODO check the time validity for the issuer |
| 128 | const now_sec = std.time.timestamp(); |
| 129 | if (now_sec < parsed_subject.validity.not_before) |
| 130 | return error.CertificateNotYetValid; |
| 131 | if (now_sec > parsed_subject.validity.not_after) |
| 132 | return error.CertificateExpired; |
| 122 | 133 | |
| 123 | 134 | switch (parsed_subject.signature_algorithm) { |
| 124 | 135 | inline .sha1WithRSAEncryption, |
| ... | ... | @@ -157,6 +168,10 @@ pub fn parse(cert: Certificate) !Parsed { |
| 157 | 168 | const tbs_signature = try der.parseElement(cert_bytes, serial_number.slice.end); |
| 158 | 169 | const issuer = try der.parseElement(cert_bytes, tbs_signature.slice.end); |
| 159 | 170 | const validity = try der.parseElement(cert_bytes, issuer.slice.end); |
| 171 | const not_before = try der.parseElement(cert_bytes, validity.slice.start); |
| 172 | const not_before_utc = try parseTime(cert, not_before); |
| 173 | const not_after = try der.parseElement(cert_bytes, not_before.slice.end); |
| 174 | const not_after_utc = try parseTime(cert, not_after); |
| 160 | 175 | const subject = try der.parseElement(cert_bytes, validity.slice.end); |
| 161 | 176 | |
| 162 | 177 | const pub_key_info = try der.parseElement(cert_bytes, subject.slice.end); |
| ... | ... | @@ -198,6 +213,10 @@ pub fn parse(cert: Certificate) !Parsed { |
| 198 | 213 | .message_slice = .{ .start = certificate.slice.start, .end = tbs_certificate.slice.end }, |
| 199 | 214 | .pub_key_algo = pub_key_algo, |
| 200 | 215 | .pub_key_slice = pub_key, |
| 216 | .validity = .{ |
| 217 | .not_before = not_before_utc, |
| 218 | .not_after = not_after_utc, |
| 219 | }, |
| 201 | 220 | }; |
| 202 | 221 | } |
| 203 | 222 | |
| ... | ... | @@ -208,7 +227,7 @@ pub fn verify(subject: Certificate, issuer: Certificate) !void { |
| 208 | 227 | } |
| 209 | 228 | |
| 210 | 229 | pub fn contents(cert: Certificate, elem: der.Element) []const u8 { |
| 211 | | return cert.buffer[elem.start..elem.end]; |
| 230 | return cert.buffer[elem.slice.start..elem.slice.end]; |
| 212 | 231 | } |
| 213 | 232 | |
| 214 | 233 | pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice { |
| ... | ... | @@ -217,6 +236,133 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice { |
| 217 | 236 | return .{ .start = elem.slice.start + 1, .end = elem.slice.end }; |
| 218 | 237 | } |
| 219 | 238 | |
| 239 | /// Returns number of seconds since epoch. |
| 240 | pub fn parseTime(cert: Certificate, elem: der.Element) !u64 { |
| 241 | const bytes = cert.contents(elem); |
| 242 | switch (elem.identifier.tag) { |
| 243 | .utc_time => { |
| 244 | // Example: "YYMMDD000000Z" |
| 245 | if (bytes.len != 13) |
| 246 | return error.CertificateTimeInvalid; |
| 247 | if (bytes[12] != 'Z') |
| 248 | return error.CertificateTimeInvalid; |
| 249 | |
| 250 | return Date.toSeconds(.{ |
| 251 | .year = @as(u16, 2000) + try parseTimeDigits(bytes[0..2].*, 0, 99), |
| 252 | .month = try parseTimeDigits(bytes[2..4].*, 1, 12), |
| 253 | .day = try parseTimeDigits(bytes[4..6].*, 1, 31), |
| 254 | .hour = try parseTimeDigits(bytes[6..8].*, 0, 23), |
| 255 | .minute = try parseTimeDigits(bytes[8..10].*, 0, 59), |
| 256 | .second = try parseTimeDigits(bytes[10..12].*, 0, 59), |
| 257 | }); |
| 258 | }, |
| 259 | .generalized_time => { |
| 260 | // Examples: |
| 261 | // "19920521000000Z" |
| 262 | // "19920622123421Z" |
| 263 | // "19920722132100.3Z" |
| 264 | if (bytes.len < 15) |
| 265 | return error.CertificateTimeInvalid; |
| 266 | return Date.toSeconds(.{ |
| 267 | .year = try parseYear4(bytes[0..4]), |
| 268 | .month = try parseTimeDigits(bytes[4..6].*, 1, 12), |
| 269 | .day = try parseTimeDigits(bytes[6..8].*, 1, 31), |
| 270 | .hour = try parseTimeDigits(bytes[8..10].*, 0, 23), |
| 271 | .minute = try parseTimeDigits(bytes[10..12].*, 0, 59), |
| 272 | .second = try parseTimeDigits(bytes[12..14].*, 0, 59), |
| 273 | }); |
| 274 | }, |
| 275 | else => return error.CertificateFieldHasWrongDataType, |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | const Date = struct { |
| 280 | /// example: 1999 |
| 281 | year: u16, |
| 282 | /// range: 1 to 12 |
| 283 | month: u8, |
| 284 | /// range: 1 to 31 |
| 285 | day: u8, |
| 286 | /// range: 0 to 59 |
| 287 | hour: u8, |
| 288 | /// range: 0 to 59 |
| 289 | minute: u8, |
| 290 | /// range: 0 to 59 |
| 291 | second: u8, |
| 292 | |
| 293 | /// Convert to number of seconds since epoch. |
| 294 | pub fn toSeconds(date: Date) u64 { |
| 295 | var sec: u64 = 0; |
| 296 | |
| 297 | { |
| 298 | var year: u16 = 1970; |
| 299 | while (year < date.year) : (year += 1) { |
| 300 | const days: u64 = std.time.epoch.getDaysInYear(year); |
| 301 | sec += days * std.time.epoch.secs_per_day; |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | { |
| 306 | const is_leap = std.time.epoch.isLeapYear(date.year); |
| 307 | var month: u4 = 1; |
| 308 | while (month < date.month) : (month += 1) { |
| 309 | const days: u64 = std.time.epoch.getDaysInMonth( |
| 310 | @intToEnum(std.time.epoch.YearLeapKind, @boolToInt(is_leap)), |
| 311 | @intToEnum(std.time.epoch.Month, month), |
| 312 | ); |
| 313 | sec += days * std.time.epoch.secs_per_day; |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | sec += (date.day - 1) * @as(u64, std.time.epoch.secs_per_day); |
| 318 | sec += date.hour * @as(u64, 60 * 60); |
| 319 | sec += date.minute * @as(u64, 60); |
| 320 | sec += date.second; |
| 321 | |
| 322 | return sec; |
| 323 | } |
| 324 | }; |
| 325 | |
| 326 | pub fn parseTimeDigits(nn: @Vector(2, u8), min: u8, max: u8) !u8 { |
| 327 | const zero: @Vector(2, u8) = .{ '0', '0' }; |
| 328 | const mm: @Vector(2, u8) = .{ 10, 1 }; |
| 329 | const result = @reduce(.Add, (nn -% zero) *% mm); |
| 330 | if (result < min) return error.CertificateTimeInvalid; |
| 331 | if (result > max) return error.CertificateTimeInvalid; |
| 332 | return result; |
| 333 | } |
| 334 | |
| 335 | test parseTimeDigits { |
| 336 | const expectEqual = std.testing.expectEqual; |
| 337 | try expectEqual(@as(u8, 0), try parseTimeDigits("00".*, 0, 99)); |
| 338 | try expectEqual(@as(u8, 99), try parseTimeDigits("99".*, 0, 99)); |
| 339 | try expectEqual(@as(u8, 42), try parseTimeDigits("42".*, 0, 99)); |
| 340 | |
| 341 | const expectError = std.testing.expectError; |
| 342 | try expectError(error.CertificateTimeInvalid, parseTimeDigits("13".*, 1, 12)); |
| 343 | try expectError(error.CertificateTimeInvalid, parseTimeDigits("00".*, 1, 12)); |
| 344 | } |
| 345 | |
| 346 | pub fn parseYear4(text: *const [4]u8) !u16 { |
| 347 | const nnnn: @Vector(4, u16) = .{ text[0], text[1], text[2], text[3] }; |
| 348 | const zero: @Vector(4, u16) = .{ '0', '0', '0', '0' }; |
| 349 | const mmmm: @Vector(4, u16) = .{ 1000, 100, 10, 1 }; |
| 350 | const result = @reduce(.Add, (nnnn -% zero) *% mmmm); |
| 351 | if (result > 9999) return error.CertificateTimeInvalid; |
| 352 | return result; |
| 353 | } |
| 354 | |
| 355 | test parseYear4 { |
| 356 | const expectEqual = std.testing.expectEqual; |
| 357 | try expectEqual(@as(u16, 0), try parseYear4("0000")); |
| 358 | try expectEqual(@as(u16, 9999), try parseYear4("9999")); |
| 359 | try expectEqual(@as(u16, 1988), try parseYear4("1988")); |
| 360 | |
| 361 | const expectError = std.testing.expectError; |
| 362 | try expectError(error.CertificateTimeInvalid, parseYear4("999b")); |
| 363 | try expectError(error.CertificateTimeInvalid, parseYear4("crap")); |
| 364 | } |
| 365 | |
| 220 | 366 | pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm { |
| 221 | 367 | if (element.identifier.tag != .object_identifier) |
| 222 | 368 | return error.CertificateFieldHasWrongDataType; |
| ... | ... | @@ -241,7 +387,13 @@ pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute { |
| 241 | 387 | return error.CertificateHasUnrecognizedAlgorithm; |
| 242 | 388 | } |
| 243 | 389 | |
| 244 | | fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: AlgorithmCategory, pub_key: []const u8) !void { |
| 390 | fn verifyRsa( |
| 391 | comptime Hash: type, |
| 392 | message: []const u8, |
| 393 | sig: []const u8, |
| 394 | pub_key_algo: AlgorithmCategory, |
| 395 | pub_key: []const u8, |
| 396 | ) !void { |
| 245 | 397 | if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch; |
| 246 | 398 | const pub_key_seq = try der.parseElement(pub_key, 0); |
| 247 | 399 | if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType; |
| ... | ... | @@ -328,6 +480,10 @@ const mem = std.mem; |
| 328 | 480 | const der = std.crypto.der; |
| 329 | 481 | const Certificate = @This(); |
| 330 | 482 | |
| 483 | test { |
| 484 | _ = Bundle; |
| 485 | } |
| 486 | |
| 331 | 487 | /// TODO: replace this with Frank's upcoming RSA implementation. the verify |
| 332 | 488 | /// function won't have the possibility of failure - it will either identify a |
| 333 | 489 | /// valid signature or an invalid signature. |