| ... | ... | @@ -15,7 +15,7 @@ pub const Key = struct { |
| 15 | 15 | |
| 16 | 16 | /// The returned bytes become invalid after calling any of the rescan functions |
| 17 | 17 | /// or add functions. |
| 18 | | pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 { |
| 18 | pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 { |
| 19 | 19 | const Adapter = struct { |
| 20 | 20 | cb: CertificateBundle, |
| 21 | 21 | |
| ... | ... | @@ -29,8 +29,7 @@ pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 { |
| 29 | 29 | return mem.eql(u8, a, b); |
| 30 | 30 | } |
| 31 | 31 | }; |
| 32 | | const index = cb.map.getAdapted(subject_name, Adapter{ .cb = cb }) orelse return null; |
| 33 | | return cb.bytes.items[index..]; |
| 32 | return cb.map.getAdapted(subject_name, Adapter{ .cb = cb }); |
| 34 | 33 | } |
| 35 | 34 | |
| 36 | 35 | pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void { |
| ... | ... | @@ -105,7 +104,7 @@ pub fn addCertsFromFile( |
| 105 | 104 | const decoded_start = @intCast(u32, cb.bytes.items.len); |
| 106 | 105 | const dest_buf = cb.bytes.allocatedSlice()[decoded_start..]; |
| 107 | 106 | cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert); |
| 108 | | const k = try key(cb, decoded_start); |
| 107 | const k = try cb.key(decoded_start); |
| 109 | 108 | const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb }); |
| 110 | 109 | if (gop.found_existing) { |
| 111 | 110 | cb.bytes.items.len = decoded_start; |
| ... | ... | @@ -115,16 +114,12 @@ pub fn addCertsFromFile( |
| 115 | 114 | } |
| 116 | 115 | } |
| 117 | 116 | |
| 118 | | pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key { |
| 117 | pub fn key(cb: CertificateBundle, bytes_index: u32) !Key { |
| 119 | 118 | const bytes = cb.bytes.items; |
| 120 | 119 | const certificate = try Der.parseElement(bytes, bytes_index); |
| 121 | 120 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); |
| 122 | 121 | const version = try Der.parseElement(bytes, tbs_certificate.start); |
| 123 | | if (@bitCast(u8, version.identifier) != 0xa0 or |
| 124 | | !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02")) |
| 125 | | { |
| 126 | | return error.UnsupportedCertificateVersion; |
| 127 | | } |
| 122 | try checkVersion(bytes, version); |
| 128 | 123 | |
| 129 | 124 | const serial_number = try Der.parseElement(bytes, version.end); |
| 130 | 125 | |
| ... | ... | @@ -144,10 +139,173 @@ pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key { |
| 144 | 139 | }; |
| 145 | 140 | } |
| 146 | 141 | |
| 142 | pub const Certificate = struct { |
| 143 | buffer: []const u8, |
| 144 | index: u32, |
| 145 | |
| 146 | pub fn verify(subject: Certificate, issuer: Certificate) !void { |
| 147 | const subject_certificate = try Der.parseElement(subject.buffer, subject.index); |
| 148 | const subject_tbs_certificate = try Der.parseElement(subject.buffer, subject_certificate.start); |
| 149 | const subject_version = try Der.parseElement(subject.buffer, subject_tbs_certificate.start); |
| 150 | try checkVersion(subject.buffer, subject_version); |
| 151 | const subject_serial_number = try Der.parseElement(subject.buffer, subject_version.end); |
| 152 | // RFC 5280, section 4.1.2.3: |
| 153 | // "This field MUST contain the same algorithm identifier as |
| 154 | // the signatureAlgorithm field in the sequence Certificate." |
| 155 | const subject_signature = try Der.parseElement(subject.buffer, subject_serial_number.end); |
| 156 | const subject_issuer = try Der.parseElement(subject.buffer, subject_signature.end); |
| 157 | const subject_validity = try Der.parseElement(subject.buffer, subject_issuer.end); |
| 158 | //const subject_name = try Der.parseElement(subject.buffer, subject_validity.end); |
| 159 | |
| 160 | const subject_sig_algo = try Der.parseElement(subject.buffer, subject_tbs_certificate.end); |
| 161 | const subject_algo_elem = try Der.parseElement(subject.buffer, subject_sig_algo.start); |
| 162 | const subject_algo = try Der.parseObjectId(subject.buffer, subject_algo_elem); |
| 163 | const subject_sig_elem = try Der.parseElement(subject.buffer, subject_sig_algo.end); |
| 164 | const subject_sig = try parseBitString(subject, subject_sig_elem); |
| 165 | |
| 166 | const issuer_certificate = try Der.parseElement(issuer.buffer, issuer.index); |
| 167 | const issuer_tbs_certificate = try Der.parseElement(issuer.buffer, issuer_certificate.start); |
| 168 | const issuer_version = try Der.parseElement(issuer.buffer, issuer_tbs_certificate.start); |
| 169 | try checkVersion(issuer.buffer, issuer_version); |
| 170 | const issuer_serial_number = try Der.parseElement(issuer.buffer, issuer_version.end); |
| 171 | // RFC 5280, section 4.1.2.3: |
| 172 | // "This field MUST contain the same algorithm identifier as |
| 173 | // the signatureAlgorithm field in the sequence Certificate." |
| 174 | const issuer_signature = try Der.parseElement(issuer.buffer, issuer_serial_number.end); |
| 175 | const issuer_issuer = try Der.parseElement(issuer.buffer, issuer_signature.end); |
| 176 | const issuer_validity = try Der.parseElement(issuer.buffer, issuer_issuer.end); |
| 177 | const issuer_name = try Der.parseElement(issuer.buffer, issuer_validity.end); |
| 178 | const issuer_pub_key_info = try Der.parseElement(issuer.buffer, issuer_name.end); |
| 179 | const issuer_pub_key_signature_algorithm = try Der.parseElement(issuer.buffer, issuer_pub_key_info.start); |
| 180 | const issuer_pub_key_algo_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.start); |
| 181 | const issuer_pub_key_algo = try Der.parseObjectId(issuer.buffer, issuer_pub_key_algo_elem); |
| 182 | const issuer_pub_key_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.end); |
| 183 | const issuer_pub_key = try parseBitString(issuer, issuer_pub_key_elem); |
| 184 | |
| 185 | // Check that the subject's issuer name matches the issuer's subject |
| 186 | // name. |
| 187 | if (!mem.eql(u8, subject.contents(subject_issuer), issuer.contents(issuer_name))) { |
| 188 | return error.CertificateIssuerMismatch; |
| 189 | } |
| 190 | |
| 191 | // TODO check the time validity for the subject |
| 192 | _ = subject_validity; |
| 193 | // TODO check the time validity for the issuer |
| 194 | |
| 195 | const message = subject.buffer[subject_certificate.start..subject_tbs_certificate.end]; |
| 196 | //std.debug.print("issuer algo: {any} subject algo: {any}\n", .{ issuer_pub_key_algo, subject_algo }); |
| 197 | switch (subject_algo) { |
| 198 | // zig fmt: off |
| 199 | .sha1WithRSAEncryption => return verifyRsa(crypto.hash.Sha1, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 200 | .sha224WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha224, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 201 | .sha256WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha256, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 202 | .sha384WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha384, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 203 | .sha512WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha512, message, subject_sig, issuer_pub_key_algo, issuer_pub_key), |
| 204 | // zig fmt: on |
| 205 | else => { |
| 206 | std.debug.print("unhandled algorithm: {any}\n", .{subject_algo}); |
| 207 | return error.UnsupportedCertificateSignatureAlgorithm; |
| 208 | }, |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | pub fn contents(cert: Certificate, elem: Der.Element) []const u8 { |
| 213 | return cert.buffer[elem.start..elem.end]; |
| 214 | } |
| 215 | |
| 216 | pub fn parseBitString(cert: Certificate, elem: Der.Element) ![]const u8 { |
| 217 | if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType; |
| 218 | if (cert.buffer[elem.start] != 0) return error.CertificateHasInvalidBitString; |
| 219 | return cert.buffer[elem.start + 1 .. elem.end]; |
| 220 | } |
| 221 | |
| 222 | fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: Der.Oid, pub_key: []const u8) !void { |
| 223 | if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch; |
| 224 | const pub_key_seq = try Der.parseElement(pub_key, 0); |
| 225 | if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType; |
| 226 | const modulus_elem = try Der.parseElement(pub_key, pub_key_seq.start); |
| 227 | if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType; |
| 228 | const exponent_elem = try Der.parseElement(pub_key, modulus_elem.end); |
| 229 | if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType; |
| 230 | // Skip over meaningless zeroes in the modulus. |
| 231 | const modulus_raw = pub_key[modulus_elem.start..modulus_elem.end]; |
| 232 | const modulus_offset = for (modulus_raw) |byte, i| { |
| 233 | if (byte != 0) break i; |
| 234 | } else modulus_raw.len; |
| 235 | const modulus = modulus_raw[modulus_offset..]; |
| 236 | const exponent = pub_key[exponent_elem.start..exponent_elem.end]; |
| 237 | if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid; |
| 238 | if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength; |
| 239 | |
| 240 | const hash_der = switch (Hash) { |
| 241 | crypto.hash.Sha1 => [_]u8{ |
| 242 | 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, |
| 243 | 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, |
| 244 | }, |
| 245 | crypto.hash.sha2.Sha224 => [_]u8{ |
| 246 | 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, |
| 247 | 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, |
| 248 | 0x00, 0x04, 0x1c, |
| 249 | }, |
| 250 | crypto.hash.sha2.Sha256 => [_]u8{ |
| 251 | 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, |
| 252 | 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, |
| 253 | 0x00, 0x04, 0x20, |
| 254 | }, |
| 255 | crypto.hash.sha2.Sha384 => [_]u8{ |
| 256 | 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, |
| 257 | 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, |
| 258 | 0x00, 0x04, 0x30, |
| 259 | }, |
| 260 | crypto.hash.sha2.Sha512 => [_]u8{ |
| 261 | 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, |
| 262 | 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, |
| 263 | 0x00, 0x04, 0x40, |
| 264 | }, |
| 265 | else => @compileError("unreachable"), |
| 266 | }; |
| 267 | |
| 268 | var msg_hashed: [Hash.digest_length]u8 = undefined; |
| 269 | Hash.hash(message, &msg_hashed, .{}); |
| 270 | |
| 271 | switch (modulus.len) { |
| 272 | inline 128, 256, 512 => |modulus_len| { |
| 273 | const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3; |
| 274 | const em: [modulus_len]u8 = |
| 275 | [2]u8{ 0, 1 } ++ |
| 276 | ([1]u8{0xff} ** ps_len) ++ |
| 277 | [1]u8{0} ++ |
| 278 | hash_der ++ |
| 279 | msg_hashed; |
| 280 | |
| 281 | const public_key = try rsa.PublicKey.fromBytes(exponent, modulus, rsa.poop); |
| 282 | const em_dec = try rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key, rsa.poop); |
| 283 | |
| 284 | if (!mem.eql(u8, &em, &em_dec)) { |
| 285 | try std.testing.expectEqualSlices(u8, &em, &em_dec); |
| 286 | return error.CertificateSignatureInvalid; |
| 287 | } |
| 288 | }, |
| 289 | else => { |
| 290 | return error.CertificateSignatureUnsupportedBitCount; |
| 291 | }, |
| 292 | } |
| 293 | } |
| 294 | }; |
| 295 | |
| 296 | fn checkVersion(bytes: []const u8, version: Der.Element) !void { |
| 297 | if (@bitCast(u8, version.identifier) != 0xa0 or |
| 298 | !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02")) |
| 299 | { |
| 300 | return error.UnsupportedCertificateVersion; |
| 301 | } |
| 302 | } |
| 303 | |
| 147 | 304 | const builtin = @import("builtin"); |
| 148 | 305 | const std = @import("../std.zig"); |
| 149 | 306 | const fs = std.fs; |
| 150 | 307 | const mem = std.mem; |
| 308 | const crypto = std.crypto; |
| 151 | 309 | const Allocator = std.mem.Allocator; |
| 152 | 310 | const Der = std.crypto.Der; |
| 153 | 311 | const CertificateBundle = @This(); |
| ... | ... | @@ -177,3 +335,138 @@ test { |
| 177 | 335 | |
| 178 | 336 | try bundle.rescan(std.testing.allocator); |
| 179 | 337 | } |
| 338 | |
| 339 | /// TODO: replace this with Frank's upcoming RSA implementation. the verify |
| 340 | /// function won't have the possibility of failure - it will either identify a |
| 341 | /// valid signature or an invalid signature. |
| 342 | /// This code is borrowed from https://github.com/shiguredo/tls13-zig |
| 343 | /// which is licensed under the Apache License Version 2.0, January 2004 |
| 344 | /// http://www.apache.org/licenses/ |
| 345 | /// The code has been modified. |
| 346 | const rsa = struct { |
| 347 | const BigInt = std.math.big.int.Managed; |
| 348 | |
| 349 | const PublicKey = struct { |
| 350 | n: BigInt, |
| 351 | e: BigInt, |
| 352 | |
| 353 | pub fn deinit(self: *PublicKey) void { |
| 354 | self.n.deinit(); |
| 355 | self.e.deinit(); |
| 356 | } |
| 357 | |
| 358 | pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8, allocator: std.mem.Allocator) !PublicKey { |
| 359 | var _n = try BigInt.init(allocator); |
| 360 | errdefer _n.deinit(); |
| 361 | try setBytes(&_n, modulus_bytes, allocator); |
| 362 | |
| 363 | var _e = try BigInt.init(allocator); |
| 364 | errdefer _e.deinit(); |
| 365 | try setBytes(&_e, pub_bytes, allocator); |
| 366 | |
| 367 | return .{ |
| 368 | .n = _n, |
| 369 | .e = _e, |
| 370 | }; |
| 371 | } |
| 372 | }; |
| 373 | |
| 374 | fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey, allocator: std.mem.Allocator) ![modulus_len]u8 { |
| 375 | var m = try BigInt.init(allocator); |
| 376 | defer m.deinit(); |
| 377 | |
| 378 | try setBytes(&m, &msg, allocator); |
| 379 | |
| 380 | if (m.order(public_key.n) != .lt) { |
| 381 | return error.MessageTooLong; |
| 382 | } |
| 383 | |
| 384 | var e = try BigInt.init(allocator); |
| 385 | defer e.deinit(); |
| 386 | |
| 387 | try pow_montgomery(&e, &m, &public_key.e, &public_key.n, allocator); |
| 388 | |
| 389 | var res: [modulus_len]u8 = undefined; |
| 390 | |
| 391 | try toBytes(&res, &e, allocator); |
| 392 | |
| 393 | return res; |
| 394 | } |
| 395 | |
| 396 | fn setBytes(r: *BigInt, bytes: []const u8, allcator: std.mem.Allocator) !void { |
| 397 | try r.set(0); |
| 398 | var tmp = try BigInt.init(allcator); |
| 399 | defer tmp.deinit(); |
| 400 | for (bytes) |b| { |
| 401 | try r.shiftLeft(r, 8); |
| 402 | try tmp.set(b); |
| 403 | try r.add(r, &tmp); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | fn pow_montgomery(r: *BigInt, a: *const BigInt, x: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void { |
| 408 | var bin_raw: [512]u8 = undefined; |
| 409 | try toBytes(&bin_raw, x, allocator); |
| 410 | |
| 411 | var i: usize = 0; |
| 412 | while (bin_raw[i] == 0x00) : (i += 1) {} |
| 413 | const bin = bin_raw[i..]; |
| 414 | |
| 415 | try r.set(1); |
| 416 | var r1 = try BigInt.init(allocator); |
| 417 | defer r1.deinit(); |
| 418 | try BigInt.copy(&r1, a.toConst()); |
| 419 | i = 0; |
| 420 | while (i < bin.len * 8) : (i += 1) { |
| 421 | if (((bin[i / 8] >> @intCast(u3, (7 - (i % 8)))) & 0x1) == 0) { |
| 422 | try BigInt.mul(&r1, r, &r1); |
| 423 | try mod(&r1, &r1, n, allocator); |
| 424 | try BigInt.sqr(r, r); |
| 425 | try mod(r, r, n, allocator); |
| 426 | } else { |
| 427 | try BigInt.mul(r, r, &r1); |
| 428 | try mod(r, r, n, allocator); |
| 429 | try BigInt.sqr(&r1, &r1); |
| 430 | try mod(&r1, &r1, n, allocator); |
| 431 | } |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | fn toBytes(out: []u8, a: *const BigInt, allocator: std.mem.Allocator) !void { |
| 436 | const Error = error{ |
| 437 | BufferTooSmall, |
| 438 | }; |
| 439 | |
| 440 | var mask = try BigInt.initSet(allocator, 0xFF); |
| 441 | defer mask.deinit(); |
| 442 | var tmp = try BigInt.init(allocator); |
| 443 | defer tmp.deinit(); |
| 444 | |
| 445 | var a_copy = try BigInt.init(allocator); |
| 446 | defer a_copy.deinit(); |
| 447 | try a_copy.copy(a.toConst()); |
| 448 | |
| 449 | // Encoding into big-endian bytes |
| 450 | var i: usize = 0; |
| 451 | while (i < out.len) : (i += 1) { |
| 452 | try tmp.bitAnd(&a_copy, &mask); |
| 453 | const b = try tmp.to(u8); |
| 454 | out[out.len - i - 1] = b; |
| 455 | try a_copy.shiftRight(&a_copy, 8); |
| 456 | } |
| 457 | |
| 458 | if (!a_copy.eqZero()) { |
| 459 | return Error.BufferTooSmall; |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | fn mod(rem: *BigInt, a: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void { |
| 464 | var q = try BigInt.init(allocator); |
| 465 | defer q.deinit(); |
| 466 | |
| 467 | try BigInt.divFloor(&q, rem, a, n); |
| 468 | } |
| 469 | |
| 470 | // TODO: flush the toilet |
| 471 | const poop = std.heap.page_allocator; |
| 472 | }; |