| ... | @@ -15,7 +15,7 @@ pub const Key = struct { | ... | @@ -15,7 +15,7 @@ pub const Key = struct { |
| 15 | | 15 | |
| 16 | /// The returned bytes become invalid after calling any of the rescan functions | 16 | /// The returned bytes become invalid after calling any of the rescan functions |
| 17 | /// or add functions. | 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 | const Adapter = struct { | 19 | const Adapter = struct { |
| 20 | cb: CertificateBundle, | 20 | cb: CertificateBundle, |
| 21 | | 21 | |
| ... | @@ -29,8 +29,7 @@ pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 { | ... | @@ -29,8 +29,7 @@ pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 { |
| 29 | return mem.eql(u8, a, b); | 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; | 32 | return cb.map.getAdapted(subject_name, Adapter{ .cb = cb }); |
| 33 | return cb.bytes.items[index..]; | | |
| 34 | } | 33 | } |
| 35 | | 34 | |
| 36 | pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void { | 35 | pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void { |
| ... | @@ -105,7 +104,7 @@ pub fn addCertsFromFile( | ... | @@ -105,7 +104,7 @@ pub fn addCertsFromFile( |
| 105 | const decoded_start = @intCast(u32, cb.bytes.items.len); | 104 | const decoded_start = @intCast(u32, cb.bytes.items.len); |
| 106 | const dest_buf = cb.bytes.allocatedSlice()[decoded_start..]; | 105 | const dest_buf = cb.bytes.allocatedSlice()[decoded_start..]; |
| 107 | cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert); | 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 | const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb }); | 108 | const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb }); |
| 110 | if (gop.found_existing) { | 109 | if (gop.found_existing) { |
| 111 | cb.bytes.items.len = decoded_start; | 110 | cb.bytes.items.len = decoded_start; |
| ... | @@ -115,16 +114,12 @@ pub fn addCertsFromFile( | ... | @@ -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 | const bytes = cb.bytes.items; | 118 | const bytes = cb.bytes.items; |
| 120 | const certificate = try Der.parseElement(bytes, bytes_index); | 119 | const certificate = try Der.parseElement(bytes, bytes_index); |
| 121 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); | 120 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); |
| 122 | const version = try Der.parseElement(bytes, tbs_certificate.start); | 121 | const version = try Der.parseElement(bytes, tbs_certificate.start); |
| 123 | if (@bitCast(u8, version.identifier) != 0xa0 or | 122 | try checkVersion(bytes, version); |
| 124 | !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02")) | | |
| 125 | { | | |
| 126 | return error.UnsupportedCertificateVersion; | | |
| 127 | } | | |
| 128 | | 123 | |
| 129 | const serial_number = try Der.parseElement(bytes, version.end); | 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,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 | const builtin = @import("builtin"); | 304 | const builtin = @import("builtin"); |
| 148 | const std = @import("../std.zig"); | 305 | const std = @import("../std.zig"); |
| 149 | const fs = std.fs; | 306 | const fs = std.fs; |
| 150 | const mem = std.mem; | 307 | const mem = std.mem; |
| | 308 | const crypto = std.crypto; |
| 151 | const Allocator = std.mem.Allocator; | 309 | const Allocator = std.mem.Allocator; |
| 152 | const Der = std.crypto.Der; | 310 | const Der = std.crypto.Der; |
| 153 | const CertificateBundle = @This(); | 311 | const CertificateBundle = @This(); |
| ... | @@ -177,3 +335,138 @@ test { | ... | @@ -177,3 +335,138 @@ test { |
| 177 | | 335 | |
| 178 | try bundle.rescan(std.testing.allocator); | 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 | }; |