├── .gitignore ├── README.md ├── build.zig ├── examples └── server.zig └── src ├── Server.zig ├── cert ├── asn1.zig ├── cert.zig └── pem.zig ├── ciphers.zig ├── crypto ├── crypto.zig └── ecdsa.zig ├── encryption.zig ├── feilich.zig ├── handshake.zig └── tls.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-out 2 | zig-cache 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Feilich 2 | 3 | Feilich is a TLS 1.3 implementation in [zig](https://ziglang.org). Zig has a great async model, allowing users to write concurrent programs. 4 | However, most TLS libraries are written in C. Unfortunately, most of those libraries do not work well (or at all) with Zig's async. 5 | Zig also produces small binaries as well has remarkable freestanding support. This, together with explicit allocators, allows us 6 | to produce a library that also works for kernels and embedded devices. For those reasons, and the fact I've been wanting to 7 | learn more about how TLS works, have made me decide to write this library. 8 | 9 | The initial goal is to implement the server side of TLS 1.3 to make it work with my Gemini library [lemon_pie](https://github.com/Luukdegram/lemon_pie). 10 | This is a great usecase as Gemini's specification requires the usage of TLS. As the usage of TLS 1.3 is not yet very widespread and many 11 | libraries not offering full support for it yet, I do very much want to implement the client side of TLS 1.3 also. 12 | 13 | TLS 1.2 is not a goal as we already have a great TLS 1.2 library for zig called [iguanaTLS](https://github.com/alexnask/igunaTLS). 14 | Although it currently only supports client side, we could perhaps PR server support in the future. 15 | 16 | ## Project status 17 | 18 | The project is currently on-hold until I have more free time to work on this besides the self-hosted compiler for the Zig programming language. 19 | I'd like to bring this project to the point where it's fully operational for a single cipher suite. From there on, it should be fairly straight 20 | forward to implement more suites as well as other crypto algorithms supported by TLS 1.3. 21 | I expect to be able to continue and get it to that state Q3/Q4 of this year. Until then, this project will be stale. 22 | 23 | ## Should I use this? 24 | 25 | Maybe? I have no prior experience with TLS, nor am I some crypto expert. TLS contains some known [implementation pitfalls](https://datatracker.ietf.org/doc/html/rfc8446#appendix-C.3), 26 | and can be quite complex to implement correctly. For those reasons alone I cannot recommend to use this library outside experimental, hobby usage. 27 | It would be great to bring this library to a state where others and I could recommend its usage, tho I'm not sure that is possible with my lack 28 | of knowledge and experience in this area. 29 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard release options allow the person running `zig build` to select 5 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 6 | const mode = b.standardReleaseOptions(); 7 | 8 | const lib = b.addStaticLibrary("feilich", "src/feilich.zig"); 9 | lib.setBuildMode(mode); 10 | lib.install(); 11 | 12 | var main_tests = b.addTest("src/feilich.zig"); 13 | main_tests.setBuildMode(mode); 14 | 15 | const test_step = b.step("test", "Run library tests"); 16 | test_step.dependOn(&main_tests.step); 17 | 18 | const example = b.addExecutable("server", "examples/server.zig"); 19 | example.setBuildMode(mode); 20 | example.addPackagePath("feilich", "src/feilich.zig"); 21 | 22 | const example_run_step = example.run(); 23 | example_run_step.step.dependOn(&example.step); 24 | 25 | const example_cmd_step = b.step("example-server", "Sets up an example tls 1.3 http server"); 26 | example_cmd_step.dependOn(&example_run_step.step); 27 | } 28 | -------------------------------------------------------------------------------- /examples/server.zig: -------------------------------------------------------------------------------- 1 | const tls = @import("feilich"); 2 | const std = @import("std"); 3 | const net = std.net; 4 | 5 | pub fn main() !void { 6 | var server = net.StreamServer.init(.{ .reuse_address = true }); 7 | defer server.close(); 8 | try server.listen(net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080)); 9 | 10 | const tls_server = tls.Server.init(std.heap.page_allocator, "", ""); 11 | 12 | while (true) { 13 | const connection = try server.accept(); 14 | const stream = connection.stream; 15 | 16 | tls_server.connect(stream.reader(), stream.writer()) catch |err| { 17 | std.log.debug("Error: {s}\n", .{@errorName(err)}); 18 | return err; 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Server.zig: -------------------------------------------------------------------------------- 1 | //! Handles the connection between the server (this) 2 | //! and its peer (client). Initially, it performs a handshake, 3 | //! which if succesful will send all data encrypted to the client. 4 | const Server = @This(); 5 | 6 | const std = @import("std"); 7 | const tls = @import("tls.zig"); 8 | const ciphers = @import("ciphers.zig"); 9 | const handshake = @import("handshake.zig"); 10 | const encryption = @import("encryption.zig"); 11 | const mem = std.mem; 12 | const Allocator = mem.Allocator; 13 | const crypto = std.crypto; 14 | const Sha256 = crypto.hash.sha2.Sha256; 15 | const HkdfSha256 = crypto.kdf.hkdf.HkdfSha256; 16 | const Curve25519 = crypto.ecc.Curve25519; 17 | 18 | private_key: []const u8, 19 | public_key: []const u8, 20 | gpa: Allocator, 21 | 22 | const Error = error{ 23 | /// We expected a certain message from the client, 24 | /// but instead received a different one. 25 | UnexpectedMessage, 26 | /// The client does not support TLS 1.3 27 | UnsupportedVersion, 28 | /// When the named groups supported by the client, 29 | /// or part of the given key_share are not supported by 30 | /// the server. 31 | UnsupportedNamedGroup, 32 | /// None of the cipher suites provided by the client are 33 | /// currently supported by the server. 34 | UnsupportedCipherSuite, 35 | /// The signate algorithms provided by the client 36 | /// are not supported by the server. 37 | UnsupportedSignatureAlgorithm, 38 | /// The client has sent a record whose length exceeds 2^14-1 bytes 39 | IllegalLength, 40 | /// Client has sent an unexpected record type, or closed it with a 41 | /// different record type than it was opened with. 42 | UnexpectedRecordType, 43 | /// Host ran out of memory 44 | OutOfMemory, 45 | /// Peer has closed the connection with the host. 46 | EndOfStream, 47 | } || crypto.errors.IdentityElementError || tls.Alert.Error; 48 | 49 | /// Initializes a new `Server` instance for a given public/private key pair. 50 | pub fn init(gpa: Allocator, private_key: []const u8, public_key: []const u8) Server { 51 | return .{ .gpa = gpa, .private_key = private_key, .public_key = public_key }; 52 | } 53 | 54 | /// Connects the server with a new client and performs its handshake. 55 | /// After succesfull handshake, a new reader and writer are returned which 56 | /// automatically decrypt, and encrypt the data before reading/writing. 57 | pub fn connect( 58 | self: Server, 59 | /// Reader 'interface' to the client's connection 60 | reader: anytype, 61 | /// Writer 'interface' to the client's connection 62 | writer: anytype, 63 | ) ConnectError(@TypeOf(reader), @TypeOf(writer))!void { 64 | var hasher = Sha256.init(.{}); 65 | var handshake_reader = handshake.handshakeReader(reader, &hasher); 66 | var handshake_writer = handshake.handshakeWriter(writer, &hasher); 67 | 68 | var client_key_share: tls.KeyShare = undefined; 69 | var server_key_share: tls.KeyShare = undefined; 70 | var signature: tls.SignatureAlgorithm = undefined; 71 | var server_exchange: tls.KeyExchange = undefined; 72 | var cipher_suite: tls.CipherSuite = undefined; 73 | 74 | const record = try tls.Record.readFrom(reader); 75 | try ensureLength(record.len, writer); 76 | if (record.record_type != .handshake) { 77 | try writeAlert(.fatal, .unexpected_message, writer); 78 | return error.UnexpectedRecordType; 79 | } 80 | 81 | // A client requested to connect with the server, 82 | // verify a client hello message. 83 | // 84 | // We're using a while loop here as we may send a HelloRetryRequest 85 | // in which the client will send a new helloClient. 86 | // When a succesful hello reply was sent, we continue the regular path. 87 | while (true) { 88 | const hello_result = try handshake_reader.decode(); 89 | switch (hello_result) { 90 | .client_hello => |client_result| { 91 | cipher_suite = for (client_result.cipher_suites) |suite| { 92 | if (ciphers.isSupported(suite)) break suite; 93 | } else { 94 | try writeAlert(.fatal, .handshake_failure, writer); 95 | return error.UnsupportedCipherSuite; 96 | }; 97 | 98 | var version_verified = false; 99 | var chosen_signature: ?tls.SignatureAlgorithm = null; 100 | var chosen_group: ?tls.NamedGroup = null; 101 | var key_share: ?tls.KeyShare = null; 102 | 103 | var it = tls.Extension.Iterator.init(client_result.extensions); 104 | loop: while (true) { 105 | it_loop: while (it.next(self.gpa)) |maybe_extension| { 106 | const extension = maybe_extension orelse break :loop; // reached end of iterator so break out of outer loop 107 | switch (extension) { 108 | .supported_versions => |versions| for (versions) |version| { 109 | // Check for TLS 1.3, when found continue 110 | // else we return an error. 111 | if (version == 0x0304) { 112 | version_verified = true; 113 | continue :it_loop; 114 | } 115 | } else return error.UnsupportedVersion, 116 | .supported_groups => |groups| for (groups) |group| { 117 | if (tls.supported_named_groups.isSupported(group)) { 118 | chosen_group = group; 119 | continue :it_loop; 120 | } 121 | }, 122 | .signature_algorithms => |algs| for (algs) |alg| { 123 | if (tls.supported_signature_algorithms.isSupported(alg)) { 124 | chosen_signature = alg; 125 | continue :it_loop; 126 | } 127 | }, 128 | .key_share => |keys| { 129 | defer self.gpa.free(keys); 130 | for (keys) |key| { 131 | if (tls.supported_named_groups.isSupported(key.named_group)) { 132 | key_share = .{ 133 | .named_group = key.named_group, 134 | .key_exchange = key.key_exchange, 135 | }; 136 | continue :it_loop; 137 | } 138 | } 139 | }, 140 | else => {}, 141 | } 142 | } else |err| switch (err) { 143 | error.UnsupportedExtension => { 144 | // try writeAlert(.warning, .unsupported_extension, writer); 145 | // unsupported extensions are a warning, we do not need to support 146 | // them all. Simply continue the loop when we find one. 147 | continue :loop; 148 | }, 149 | else => |e| return e, 150 | } 151 | } 152 | 153 | if (!version_verified) { 154 | try writeAlert(.fatal, .protocol_version, writer); 155 | return error.UnsupportedVersion; 156 | } 157 | 158 | client_key_share = key_share orelse { 159 | try writeAlert(.fatal, .handshake_failure, writer); 160 | return error.UnsupportedNamedGroup; 161 | }; 162 | 163 | signature = chosen_signature orelse { 164 | try writeAlert(.fatal, .handshake_failure, writer); 165 | return error.UnsupportedSignatureAlgorithm; 166 | }; 167 | 168 | server_key_share = blk: { 169 | const group = chosen_group orelse { 170 | try writeAlert(.fatal, .handshake_failure, writer); 171 | return error.UnsupportedNamedGroup; 172 | }; 173 | 174 | server_exchange = try tls.KeyExchange.fromCurve(tls.curves.x25519); 175 | break :blk tls.KeyShare{ 176 | .named_group = group, 177 | .key_exchange = server_exchange.public_key, 178 | }; 179 | }; 180 | 181 | const random_seed = blk: { 182 | // we do not provide TLS downgrading and therefore do not have to set the 183 | // last 8 bytes to specific values as noted in section 4.1.3 184 | // https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.3 185 | var seed: [32]u8 = undefined; 186 | std.crypto.random.bytes(&seed); 187 | break :blk seed; 188 | }; 189 | 190 | // hash and write the server hello 191 | try handshake_writer.serverHello( 192 | .server_hello, 193 | client_result.session_id, 194 | cipher_suite, 195 | server_key_share, 196 | random_seed, 197 | ); 198 | 199 | try writer.writeAll(&.{ 200 | 0x14, 0x03, 0x03, 0x00, 0x01, 0x01, 201 | }); 202 | 203 | // We sent our hello server, meaning we can continue 204 | // the regular path. 205 | break; 206 | }, 207 | // else => return error.UnexpectedMessage, 208 | } 209 | } 210 | 211 | // generate handshake key, which is constructed by multiplying 212 | // the client's public key with the server's private key using the negotiated 213 | // named group. 214 | const curve = std.crypto.ecc.Curve25519.fromBytes(client_key_share.key_exchange); 215 | const shared_key = try curve.clampedMul(server_exchange.private_key); 216 | 217 | // Calculate the handshake keys 218 | // Since we do not yet support PSK resumation, 219 | // we first build an early secret which we use to 220 | // expand our keys later on. 221 | var empty_hash: [32]u8 = undefined; 222 | const early_secret = HkdfSha256.extract("", &[_]u8{0} ** 32); 223 | Sha256.hash("", &empty_hash, .{}); 224 | const derived_secret = tls.hkdfExpandLabel(early_secret, "derived", &empty_hash, 32); 225 | const handshake_secret = HkdfSha256.extract(&derived_secret, &shared_key.toBytes()); 226 | 227 | const current_hash: [32]u8 = blk: { 228 | var temp_hasher = hasher; 229 | var buf: [32]u8 = undefined; 230 | temp_hasher.final(&buf); 231 | break :blk buf; 232 | }; 233 | 234 | // secrets to generate our keys 235 | const client_secret = tls.hkdfExpandLabel(handshake_secret, "c hs traffic", ¤t_hash, 32); 236 | const server_secret = tls.hkdfExpandLabel(handshake_secret, "s hs traffic", ¤t_hash, 32); 237 | 238 | var key_storage: ciphers.KeyStorage = .{}; 239 | inline for (ciphers.supported) |cipher| { 240 | if (cipher.suite == cipher_suite) { 241 | // keys used to encrypt and decrypt client/server data. 242 | const client_handshake_key = tls.hkdfExpandLabel(client_secret, "key", "", cipher.key_length); 243 | const server_handshake_key = tls.hkdfExpandLabel(server_secret, "key", "", cipher.key_length); 244 | 245 | // nonces for data decryption/encryption from/to client/server. 246 | const client_handshake_iv = tls.hkdfExpandLabel(client_secret, "iv", "", cipher.nonce_length); 247 | const server_handshake_iv = tls.hkdfExpandLabel(server_secret, "iv", "", cipher.nonce_length); 248 | 249 | key_storage.setServerKey(cipher, server_handshake_key); 250 | key_storage.setClientKey(cipher, client_handshake_key); 251 | key_storage.setServerIv(cipher, server_handshake_iv); 252 | key_storage.setClientIv(cipher, client_handshake_iv); 253 | 254 | // -- Write the encrypted message that wraps multiple handshake headers -- // 255 | try handshake_writer.handshakeFinish(cipher, &key_storage, self.public_key, server_secret, 0); 256 | } 257 | } 258 | 259 | // The encryption read -and writer that we will return back to 260 | // the user at the end of the handshake. 261 | var encryption_readwriter = encryption.encryptedReadWriter( 262 | reader, 263 | writer, 264 | cipher_suite, 265 | key_storage, 266 | ); 267 | const decryption_reader = encryption_readwriter.reader(); 268 | const encryption_writer = encryption_readwriter.writer(); 269 | 270 | // -- Expect client response with encrypted data --// 271 | var decrypted_data: [1 << 14]u8 = undefined; 272 | var read_len = try decryption_reader.read(&decrypted_data); 273 | while (encryption_readwriter.reader_state == .reading) { 274 | read_len += try decryption_reader.read(decrypted_data[read_len..]); 275 | } else if (read_len == 0) return error.EndOfStream; 276 | 277 | const hs_msg = handshake.HandshakeHeader.fromBytes(decrypted_data[0..4].*); 278 | if (hs_msg.handshake_type != .finished) { 279 | const alert = tls.Alert.init(.unexpected_message, .fatal); 280 | try alert.writeTo(encryption_writer); 281 | return error.UnexpectedMessage; 282 | } 283 | const finished_key = tls.hkdfExpandLabel(client_secret, "finished", "", 32); 284 | const finished_hash = blk: { 285 | var tmp_hash = hasher; 286 | var buf: [32]u8 = undefined; 287 | tmp_hash.final(&buf); 288 | break :blk buf; 289 | }; 290 | 291 | var verify_data: [32]u8 = undefined; 292 | crypto.auth.hmac.sha2.HmacSha256.create(&verify_data, &finished_key, &finished_hash); 293 | if (!std.mem.eql(u8, &verify_data, decrypted_data[4..][0..32])) { 294 | const alert = tls.Alert.init(.decrypt_error, .fatal); 295 | try alert.writeTo(encryption_writer); 296 | return error.UnexpectedMessage; 297 | } 298 | 299 | var ping: [4]u8 = undefined; 300 | try decryption_reader.readNoEof(&ping); 301 | std.debug.print("Ping: {s}\n", .{&ping}); 302 | if (!std.mem.eql(u8, &ping, "ping")) { 303 | const alert = tls.Alert.init(.decrypt_error, .fatal); 304 | try alert.writeTo(encryption_writer); 305 | return error.UnexpectedMessage; 306 | } 307 | } 308 | 309 | /// Constructs an alert record and writes it to the client's connection. 310 | /// When an alert is fatal, it is illegal to write any more data to the `writer` 311 | /// as the connection will be closed by both server and client. 312 | fn writeAlert(severity: tls.Alert.Severity, tag: tls.Alert.Tag, writer: anytype) @TypeOf(writer).Error!void { 313 | const record = tls.Record.init(.alert, 2); // 2 bytes for level and description. 314 | try record.writeTo(writer); 315 | try writer.writeAll(&.{ severity.int(), tag.int() }); 316 | switch (severity) { 317 | .warning => std.log.warn("{s}", .{@tagName(tag)}), 318 | .fatal => std.log.err("{s}", .{@tagName(tag)}), 319 | } 320 | } 321 | 322 | /// Tests if given `length` surpasses the max record length of 2^14 323 | fn ensureLength(length: usize, writer: anytype) (@TypeOf(writer).Error || error{IllegalLength})!void { 324 | if (length > 1 << 14) { 325 | try writeAlert(.fatal, .record_overflow, writer); 326 | return error.IllegalLength; 327 | } 328 | } 329 | 330 | // --- logical tests --- // 331 | 332 | test "Shared key generation" { 333 | const client_public_key: [32]u8 = .{ 334 | 0x35, 0x80, 0x72, 0xd6, 0x36, 0x58, 0x80, 0xd1, 335 | 0xae, 0xea, 0x32, 0x9a, 0xdf, 0x91, 0x21, 0x38, 336 | 0x38, 0x51, 0xed, 0x21, 0xa2, 0x8e, 0x3b, 0x75, 337 | 0xe9, 0x65, 0xd0, 0xd2, 0xcd, 0x16, 0x62, 0x54, 338 | }; 339 | const server_private_key: [32]u8 = .{ 340 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 341 | 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 342 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 343 | 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 344 | }; 345 | const curve = Curve25519.fromBytes(client_public_key); 346 | const shared = try curve.clampedMul(server_private_key); 347 | 348 | try std.testing.expectEqualSlices(u8, &.{ 349 | 0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 350 | 0xcf, 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 351 | 0xba, 0xad, 0x26, 0x97, 0xe2, 0x9f, 0x1f, 352 | 0x92, 0x0d, 0xcc, 0x77, 0xc8, 0xa0, 0xa0, 353 | 0x88, 0x44, 0x76, 0x24, 354 | }, &shared.toBytes()); 355 | } 356 | 357 | /// Returns the error set calling `connect` 358 | pub fn ConnectError(comptime ReaderType: type, comptime WriterType: type) type { 359 | return handshake.ReadWriteError(ReaderType, WriterType) || 360 | encryption.EncryptedReadWriter(ReaderType, WriterType).Error || 361 | Server.Error; 362 | } 363 | 364 | // Uses example data from https://tls13.ulfheim.net/ to verify 365 | // its output 366 | test "Handshake keys calculation" { 367 | const hello_hash: [32]u8 = [_]u8{ 368 | 0xda, 0x75, 0xce, 0x11, 0x39, 0xac, 0x80, 0xda, 369 | 0xe4, 0x04, 0x4d, 0xa9, 0x32, 0x35, 0x0c, 0xf6, 370 | 0x5c, 0x97, 0xcc, 0xc9, 0xe3, 0x3f, 0x1e, 0x6f, 371 | 0x7d, 0x2d, 0x4b, 0x18, 0xb7, 0x36, 0xff, 0xd5, 372 | }; 373 | const shared_secret: [32]u8 = [_]u8{ 374 | 0xdf, 0x4a, 0x29, 0x1b, 0xaa, 0x1e, 0xb7, 0xcf, 375 | 0xa6, 0x93, 0x4b, 0x29, 0xb4, 0x74, 0xba, 0xad, 376 | 0x26, 0x97, 0xe2, 0x9f, 0x1f, 0x92, 0x0d, 0xcc, 377 | 0x77, 0xc8, 0xa0, 0xa0, 0x88, 0x44, 0x76, 0x24, 378 | }; 379 | const early_secret = HkdfSha256.extract(&.{}, &[_]u8{0} ** 32); 380 | var empty_hash: [32]u8 = undefined; 381 | Sha256.hash("", &empty_hash, .{}); 382 | const derived_secret = tls.hkdfExpandLabel(early_secret, "derived", &empty_hash, 32); 383 | try std.testing.expectEqualSlices(u8, &.{ 384 | 0x6f, 0x26, 0x15, 0xa1, 0x08, 0xc7, 0x02, 385 | 0xc5, 0x67, 0x8f, 0x54, 0xfc, 0x9d, 0xba, 386 | 0xb6, 0x97, 0x16, 0xc0, 0x76, 0x18, 0x9c, 387 | 0x48, 0x25, 0x0c, 0xeb, 0xea, 0xc3, 0x57, 388 | 0x6c, 0x36, 0x11, 0xba, 389 | }, &derived_secret); 390 | 391 | const handshake_secret = HkdfSha256.extract(&derived_secret, &shared_secret); 392 | try std.testing.expectEqualSlices(u8, &.{ 393 | 0xfb, 0x9f, 0xc8, 0x06, 0x89, 0xb3, 0xa5, 0xd0, 394 | 0x2c, 0x33, 0x24, 0x3b, 0xf6, 0x9a, 0x1b, 0x1b, 395 | 0x20, 0x70, 0x55, 0x88, 0xa7, 0x94, 0x30, 0x4a, 396 | 0x6e, 0x71, 0x20, 0x15, 0x5e, 0xdf, 0x14, 0x9a, 397 | }, &handshake_secret); 398 | 399 | const client_secret = tls.hkdfExpandLabel(handshake_secret, "c hs traffic", &hello_hash, 32); 400 | const server_secret = tls.hkdfExpandLabel(handshake_secret, "s hs traffic", &hello_hash, 32); 401 | 402 | try std.testing.expectEqualSlices(u8, &.{ 403 | 0xff, 0x0e, 0x5b, 0x96, 0x52, 0x91, 0xc6, 0x08, 404 | 0xc1, 0xe8, 0xcd, 0x26, 0x7e, 0xef, 0xc0, 0xaf, 405 | 0xcc, 0x5e, 0x98, 0xa2, 0x78, 0x63, 0x73, 0xf0, 406 | 0xdb, 0x47, 0xb0, 0x47, 0x86, 0xd7, 0x2a, 0xea, 407 | }, &client_secret); 408 | try std.testing.expectEqualSlices(u8, &.{ 409 | 0xa2, 0x06, 0x72, 0x65, 0xe7, 0xf0, 0x65, 0x2a, 410 | 0x92, 0x3d, 0x5d, 0x72, 0xab, 0x04, 0x67, 0xc4, 411 | 0x61, 0x32, 0xee, 0xb9, 0x68, 0xb6, 0xa3, 0x2d, 412 | 0x31, 0x1c, 0x80, 0x58, 0x68, 0x54, 0x88, 0x14, 413 | }, &server_secret); 414 | 415 | const client_handshake_key = tls.hkdfExpandLabel(client_secret, "key", "", 16); 416 | const server_handshake_key = tls.hkdfExpandLabel(server_secret, "key", "", 16); 417 | 418 | try std.testing.expectEqualSlices(u8, &.{ 419 | 0x71, 0x54, 0xf3, 0x14, 0xe6, 0xbe, 0x7d, 0xc0, 420 | 0x08, 0xdf, 0x2c, 0x83, 0x2b, 0xaa, 0x1d, 0x39, 421 | }, &client_handshake_key); 422 | try std.testing.expectEqualSlices(u8, &.{ 423 | 0x84, 0x47, 0x80, 0xa7, 0xac, 0xad, 0x9f, 0x98, 424 | 0x0f, 0xa2, 0x5c, 0x11, 0x4e, 0x43, 0x40, 0x2a, 425 | }, &server_handshake_key); 426 | 427 | var temp: [32]u8 = undefined; 428 | std.mem.copy(u8, &temp, &client_secret); 429 | const client_handshake_iv = tls.hkdfExpandLabel(temp, "iv", "", 12); 430 | 431 | try std.testing.expectEqualSlices(u8, &.{ 432 | 0x71, 0xab, 0xc2, 0xca, 0xe4, 0xc6, 0x99, 0xd4, 0x7c, 0x60, 0x02, 0x68, 433 | }, &client_handshake_iv); 434 | 435 | std.mem.copy(u8, &temp, &server_secret); 436 | const server_handshake_iv = tls.hkdfExpandLabel(temp, "iv", "", 12); 437 | 438 | try std.testing.expectEqualSlices(u8, &.{ 439 | 0x4c, 0x04, 0x2d, 0xdc, 0x12, 0x0a, 0x38, 0xd1, 0x41, 0x7f, 0xc8, 0x15, 440 | }, &server_handshake_iv); 441 | } 442 | 443 | test "Encrypt initial wrapper" { 444 | const server_handshake_key: [16]u8 = .{ 445 | 0x84, 0x47, 0x80, 0xa7, 0xac, 0xad, 0x9f, 0x98, 446 | 0x0f, 0xa2, 0x5c, 0x11, 0x4e, 0x43, 0x40, 0x2a, 447 | }; 448 | 449 | const server_iv: [12]u8 = .{ 450 | 0x4c, 0x04, 0x2d, 0xdc, 0x12, 0x0a, 0x38, 0xd1, 0x41, 0x7f, 0xc8, 0x15, 451 | }; 452 | 453 | const encrypted_extensions = [_]u8{ 454 | 0x08, 0x00, 0x00, 0x02, 455 | 0x00, 0x00, 456 | }; 457 | 458 | const certificate_bytes = [_]u8{ 459 | 0x0b, 0x00, 0x03, 0x2e, 0x00, 0x00, 0x03, 0x2a, 0x00, 0x03, 0x25, 0x30, 0x82, 0x03, 0x21, 0x30, 460 | 0x82, 0x02, 0x09, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x15, 0x5a, 0x92, 0xad, 0xc2, 0x04, 461 | 0x8f, 0x90, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 462 | 0x00, 0x30, 0x22, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 463 | 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x45, 0x78, 0x61, 0x6d, 0x70, 464 | 0x6c, 0x65, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x31, 0x30, 0x30, 0x35, 0x30, 465 | 0x31, 0x33, 0x38, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x30, 0x30, 0x35, 0x30, 0x31, 466 | 0x33, 0x38, 0x31, 0x37, 0x5a, 0x30, 0x2b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 467 | 0x13, 0x02, 0x55, 0x53, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x13, 0x65, 468 | 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 0x2e, 0x6e, 469 | 0x65, 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 470 | 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 471 | 0x01, 0x01, 0x00, 0xc4, 0x80, 0x36, 0x06, 0xba, 0xe7, 0x47, 0x6b, 0x08, 0x94, 0x04, 0xec, 0xa7, 472 | 0xb6, 0x91, 0x04, 0x3f, 0xf7, 0x92, 0xbc, 0x19, 0xee, 0xfb, 0x7d, 0x74, 0xd7, 0xa8, 0x0d, 0x00, 473 | 0x1e, 0x7b, 0x4b, 0x3a, 0x4a, 0xe6, 0x0f, 0xe8, 0xc0, 0x71, 0xfc, 0x73, 0xe7, 0x02, 0x4c, 0x0d, 474 | 0xbc, 0xf4, 0xbd, 0xd1, 0x1d, 0x39, 0x6b, 0xba, 0x70, 0x46, 0x4a, 0x13, 0xe9, 0x4a, 0xf8, 0x3d, 475 | 0xf3, 0xe1, 0x09, 0x59, 0x54, 0x7b, 0xc9, 0x55, 0xfb, 0x41, 0x2d, 0xa3, 0x76, 0x52, 0x11, 0xe1, 476 | 0xf3, 0xdc, 0x77, 0x6c, 0xaa, 0x53, 0x37, 0x6e, 0xca, 0x3a, 0xec, 0xbe, 0xc3, 0xaa, 0xb7, 0x3b, 477 | 0x31, 0xd5, 0x6c, 0xb6, 0x52, 0x9c, 0x80, 0x98, 0xbc, 0xc9, 0xe0, 0x28, 0x18, 0xe2, 0x0b, 0xf7, 478 | 0xf8, 0xa0, 0x3a, 0xfd, 0x17, 0x04, 0x50, 0x9e, 0xce, 0x79, 0xbd, 0x9f, 0x39, 0xf1, 0xea, 0x69, 479 | 0xec, 0x47, 0x97, 0x2e, 0x83, 0x0f, 0xb5, 0xca, 0x95, 0xde, 0x95, 0xa1, 0xe6, 0x04, 0x22, 0xd5, 480 | 0xee, 0xbe, 0x52, 0x79, 0x54, 0xa1, 0xe7, 0xbf, 0x8a, 0x86, 0xf6, 0x46, 0x6d, 0x0d, 0x9f, 0x16, 481 | 0x95, 0x1a, 0x4c, 0xf7, 0xa0, 0x46, 0x92, 0x59, 0x5c, 0x13, 0x52, 0xf2, 0x54, 0x9e, 0x5a, 0xfb, 482 | 0x4e, 0xbf, 0xd7, 0x7a, 0x37, 0x95, 0x01, 0x44, 0xe4, 0xc0, 0x26, 0x87, 0x4c, 0x65, 0x3e, 0x40, 483 | 0x7d, 0x7d, 0x23, 0x07, 0x44, 0x01, 0xf4, 0x84, 0xff, 0xd0, 0x8f, 0x7a, 0x1f, 0xa0, 0x52, 0x10, 484 | 0xd1, 0xf4, 0xf0, 0xd5, 0xce, 0x79, 0x70, 0x29, 0x32, 0xe2, 0xca, 0xbe, 0x70, 0x1f, 0xdf, 0xad, 485 | 0x6b, 0x4b, 0xb7, 0x11, 0x01, 0xf4, 0x4b, 0xad, 0x66, 0x6a, 0x11, 0x13, 0x0f, 0xe2, 0xee, 0x82, 486 | 0x9e, 0x4d, 0x02, 0x9d, 0xc9, 0x1c, 0xdd, 0x67, 0x16, 0xdb, 0xb9, 0x06, 0x18, 0x86, 0xed, 0xc1, 487 | 0xba, 0x94, 0x21, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x52, 0x30, 0x50, 0x30, 0x0e, 0x06, 0x03, 488 | 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x1d, 0x06, 0x03, 489 | 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 490 | 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1f, 0x06, 0x03, 0x55, 491 | 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x89, 0x4f, 0xde, 0x5b, 0xcc, 0x69, 0xe2, 0x52, 492 | 0xcf, 0x3e, 0xa3, 0x00, 0xdf, 0xb1, 0x97, 0xb8, 0x1d, 0xe1, 0xc1, 0x46, 0x30, 0x0d, 0x06, 0x09, 493 | 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 494 | 0x59, 0x16, 0x45, 0xa6, 0x9a, 0x2e, 0x37, 0x79, 0xe4, 0xf6, 0xdd, 0x27, 0x1a, 0xba, 0x1c, 0x0b, 495 | 0xfd, 0x6c, 0xd7, 0x55, 0x99, 0xb5, 0xe7, 0xc3, 0x6e, 0x53, 0x3e, 0xff, 0x36, 0x59, 0x08, 0x43, 496 | 0x24, 0xc9, 0xe7, 0xa5, 0x04, 0x07, 0x9d, 0x39, 0xe0, 0xd4, 0x29, 0x87, 0xff, 0xe3, 0xeb, 0xdd, 497 | 0x09, 0xc1, 0xcf, 0x1d, 0x91, 0x44, 0x55, 0x87, 0x0b, 0x57, 0x1d, 0xd1, 0x9b, 0xdf, 0x1d, 0x24, 498 | 0xf8, 0xbb, 0x9a, 0x11, 0xfe, 0x80, 0xfd, 0x59, 0x2b, 0xa0, 0x39, 0x8c, 0xde, 0x11, 0xe2, 0x65, 499 | 0x1e, 0x61, 0x8c, 0xe5, 0x98, 0xfa, 0x96, 0xe5, 0x37, 0x2e, 0xef, 0x3d, 0x24, 0x8a, 0xfd, 0xe1, 500 | 0x74, 0x63, 0xeb, 0xbf, 0xab, 0xb8, 0xe4, 0xd1, 0xab, 0x50, 0x2a, 0x54, 0xec, 0x00, 0x64, 0xe9, 501 | 0x2f, 0x78, 0x19, 0x66, 0x0d, 0x3f, 0x27, 0xcf, 0x20, 0x9e, 0x66, 0x7f, 0xce, 0x5a, 0xe2, 0xe4, 502 | 0xac, 0x99, 0xc7, 0xc9, 0x38, 0x18, 0xf8, 0xb2, 0x51, 0x07, 0x22, 0xdf, 0xed, 0x97, 0xf3, 0x2e, 503 | 0x3e, 0x93, 0x49, 0xd4, 0xc6, 0x6c, 0x9e, 0xa6, 0x39, 0x6d, 0x74, 0x44, 0x62, 0xa0, 0x6b, 0x42, 504 | 0xc6, 0xd5, 0xba, 0x68, 0x8e, 0xac, 0x3a, 0x01, 0x7b, 0xdd, 0xfc, 0x8e, 0x2c, 0xfc, 0xad, 0x27, 505 | 0xcb, 0x69, 0xd3, 0xcc, 0xdc, 0xa2, 0x80, 0x41, 0x44, 0x65, 0xd3, 0xae, 0x34, 0x8c, 0xe0, 0xf3, 506 | 0x4a, 0xb2, 0xfb, 0x9c, 0x61, 0x83, 0x71, 0x31, 0x2b, 0x19, 0x10, 0x41, 0x64, 0x1c, 0x23, 0x7f, 507 | 0x11, 0xa5, 0xd6, 0x5c, 0x84, 0x4f, 0x04, 0x04, 0x84, 0x99, 0x38, 0x71, 0x2b, 0x95, 0x9e, 0xd6, 508 | 0x85, 0xbc, 0x5c, 0x5d, 0xd6, 0x45, 0xed, 0x19, 0x90, 0x94, 0x73, 0x40, 0x29, 0x26, 0xdc, 0xb4, 509 | 0x0e, 0x34, 0x69, 0xa1, 0x59, 0x41, 0xe8, 0xe2, 0xcc, 0xa8, 0x4b, 0xb6, 0x08, 0x46, 0x36, 0xa0, 510 | 0x00, 0x00, 511 | }; 512 | 513 | const server_certificate_verify = [_]u8{ 514 | 0x0f, 0x00, 0x01, 0x04, 0x08, 0x04, 0x01, 0x00, 0x17, 0xfe, 0xb5, 0x33, 0xca, 0x6d, 0x00, 0x7d, 515 | 0x00, 0x58, 0x25, 0x79, 0x68, 0x42, 0x4b, 0xbc, 0x3a, 0xa6, 0x90, 0x9e, 0x9d, 0x49, 0x55, 0x75, 516 | 0x76, 0xa5, 0x20, 0xe0, 0x4a, 0x5e, 0xf0, 0x5f, 0x0e, 0x86, 0xd2, 0x4f, 0xf4, 0x3f, 0x8e, 0xb8, 517 | 0x61, 0xee, 0xf5, 0x95, 0x22, 0x8d, 0x70, 0x32, 0xaa, 0x36, 0x0f, 0x71, 0x4e, 0x66, 0x74, 0x13, 518 | 0x92, 0x6e, 0xf4, 0xf8, 0xb5, 0x80, 0x3b, 0x69, 0xe3, 0x55, 0x19, 0xe3, 0xb2, 0x3f, 0x43, 0x73, 519 | 0xdf, 0xac, 0x67, 0x87, 0x06, 0x6d, 0xcb, 0x47, 0x56, 0xb5, 0x45, 0x60, 0xe0, 0x88, 0x6e, 0x9b, 520 | 0x96, 0x2c, 0x4a, 0xd2, 0x8d, 0xab, 0x26, 0xba, 0xd1, 0xab, 0xc2, 0x59, 0x16, 0xb0, 0x9a, 0xf2, 521 | 0x86, 0x53, 0x7f, 0x68, 0x4f, 0x80, 0x8a, 0xef, 0xee, 0x73, 0x04, 0x6c, 0xb7, 0xdf, 0x0a, 0x84, 522 | 0xfb, 0xb5, 0x96, 0x7a, 0xca, 0x13, 0x1f, 0x4b, 0x1c, 0xf3, 0x89, 0x79, 0x94, 0x03, 0xa3, 0x0c, 523 | 0x02, 0xd2, 0x9c, 0xbd, 0xad, 0xb7, 0x25, 0x12, 0xdb, 0x9c, 0xec, 0x2e, 0x5e, 0x1d, 0x00, 0xe5, 524 | 0x0c, 0xaf, 0xcf, 0x6f, 0x21, 0x09, 0x1e, 0xbc, 0x4f, 0x25, 0x3c, 0x5e, 0xab, 0x01, 0xa6, 0x79, 525 | 0xba, 0xea, 0xbe, 0xed, 0xb9, 0xc9, 0x61, 0x8f, 0x66, 0x00, 0x6b, 0x82, 0x44, 0xd6, 0x62, 0x2a, 526 | 0xaa, 0x56, 0x88, 0x7c, 0xcf, 0xc6, 0x6a, 0x0f, 0x38, 0x51, 0xdf, 0xa1, 0x3a, 0x78, 0xcf, 0xf7, 527 | 0x99, 0x1e, 0x03, 0xcb, 0x2c, 0x3a, 0x0e, 0xd8, 0x7d, 0x73, 0x67, 0x36, 0x2e, 0xb7, 0x80, 0x5b, 528 | 0x00, 0xb2, 0x52, 0x4f, 0xf2, 0x98, 0xa4, 0xda, 0x48, 0x7c, 0xac, 0xde, 0xaf, 0x8a, 0x23, 0x36, 529 | 0xc5, 0x63, 0x1b, 0x3e, 0xfa, 0x93, 0x5b, 0xb4, 0x11, 0xe7, 0x53, 0xca, 0x13, 0xb0, 0x15, 0xfe, 530 | 0xc7, 0xe4, 0xa7, 0x30, 0xf1, 0x36, 0x9f, 0x9e, 531 | }; 532 | 533 | const handshake_finished = [_]u8{ 534 | 0x14, 0x00, 0x00, 0x20, 0xea, 0x6e, 0xe1, 0x76, 0xdc, 0xcc, 0x4a, 0xf1, 0x85, 0x9e, 0x9e, 0x4e, 535 | 0x93, 0xf7, 0x97, 0xea, 0xc9, 0xa7, 0x8c, 0xe4, 0x39, 0x30, 0x1e, 0x35, 0x27, 0x5a, 0xd4, 0x3f, 536 | 0x3c, 0xdd, 0xbd, 0xe3, 537 | }; 538 | 539 | const auth_tag_verify: [16]u8 = .{ 540 | 0xe0, 0x8b, 0x0e, 0x45, 0x5a, 0x35, 0x0a, 0xe5, 0x4d, 0x76, 0x34, 0x9a, 0xa6, 0x8c, 0x71, 0xae, 541 | }; 542 | var auth_tag: [16]u8 = undefined; 543 | 544 | const message = encrypted_extensions ++ certificate_bytes ++ server_certificate_verify ++ handshake_finished ++ [_]u8{0x16}; 545 | var buf: [message.len]u8 = undefined; 546 | crypto.aead.aes_gcm.Aes128Gcm.encrypt(&buf, &auth_tag, &message, &.{ 547 | // record header 548 | 0x17, 0x03, 0x03, 0x04, 0x75, 549 | }, server_iv, server_handshake_key); 550 | 551 | try std.testing.expectEqualSlices(u8, &.{ 552 | 0xda, 0x1e, 0xc2, 0xd7, 0xbd, 0xa8, 0xeb, 0xf7, 0x3e, 0xdd, 0x50, 0x10, 0xfb, 0xa8, 0x08, 0x9f, 553 | 0xd4, 0x26, 0xb0, 0xea, 0x1e, 0xa4, 0xd8, 0x8d, 0x07, 0x4f, 0xfe, 0xa8, 0xa9, 0x87, 0x3a, 0xf5, 554 | 0xf5, 0x02, 0x26, 0x1e, 0x34, 0xb1, 0x56, 0x33, 0x43, 0xe9, 0xbe, 0xb6, 0x13, 0x2e, 0x7e, 0x83, 555 | 0x6d, 0x65, 0xdb, 0x6d, 0xcf, 0x00, 0xbc, 0x40, 0x19, 0x35, 0xae, 0x36, 0x9c, 0x44, 0x0d, 0x67, 556 | 0xaf, 0x71, 0x9e, 0xc0, 0x3b, 0x98, 0x4c, 0x45, 0x21, 0xb9, 0x05, 0xd5, 0x8b, 0xa2, 0x19, 0x7c, 557 | 0x45, 0xc4, 0xf7, 0x73, 0xbd, 0x9d, 0xd1, 0x21, 0xb4, 0xd2, 0xd4, 0xe6, 0xad, 0xff, 0xfa, 0x27, 558 | 0xc2, 0xa8, 0x1a, 0x99, 0xa8, 0xef, 0xe8, 0x56, 0xc3, 0x5e, 0xe0, 0x8b, 0x71, 0xb3, 0xe4, 0x41, 559 | 0xbb, 0xec, 0xaa, 0x65, 0xfe, 0x72, 0x08, 0x15, 0xca, 0xb5, 0x8d, 0xb3, 0xef, 0xa8, 0xd1, 0xe5, 560 | 0xb7, 0x1c, 0x58, 0xe8, 0xd1, 0xfd, 0xb6, 0xb2, 0x1b, 0xfc, 0x66, 0xa9, 0x86, 0x5f, 0x85, 0x2c, 561 | 0x1b, 0x4b, 0x64, 0x0e, 0x94, 0xbd, 0x90, 0x84, 0x69, 0xe7, 0x15, 0x1f, 0x9b, 0xbc, 0xa3, 0xce, 562 | 0x53, 0x22, 0x4a, 0x27, 0x06, 0x2c, 0xeb, 0x24, 0x0a, 0x10, 0x5b, 0xd3, 0x13, 0x2d, 0xc1, 0x85, 563 | 0x44, 0x47, 0x77, 0x94, 0xc3, 0x73, 0xbc, 0x0f, 0xb5, 0xa2, 0x67, 0x88, 0x5c, 0x85, 0x7d, 0x4c, 564 | 0xcb, 0x4d, 0x31, 0x74, 0x2b, 0x7a, 0x29, 0x62, 0x40, 0x29, 0xfd, 0x05, 0x94, 0x0d, 0xe3, 0xf9, 565 | 0xf9, 0xb6, 0xe0, 0xa9, 0xa2, 0x37, 0x67, 0x2b, 0xc6, 0x24, 0xba, 0x28, 0x93, 0xa2, 0x17, 0x09, 566 | 0x83, 0x3c, 0x52, 0x76, 0xd4, 0x13, 0x63, 0x1b, 0xdd, 0xe6, 0xae, 0x70, 0x08, 0xc6, 0x97, 0xa8, 567 | 0xef, 0x42, 0x8a, 0x79, 0xdb, 0xf6, 0xe8, 0xbb, 0xeb, 0x47, 0xc4, 0xe4, 0x08, 0xef, 0x65, 0x6d, 568 | 0x9d, 0xc1, 0x9b, 0x8b, 0x5d, 0x49, 0xbc, 0x09, 0x1e, 0x21, 0x77, 0x35, 0x75, 0x94, 0xc8, 0xac, 569 | 0xd4, 0x1c, 0x10, 0x1c, 0x77, 0x50, 0xcb, 0x11, 0xb5, 0xbe, 0x6a, 0x19, 0x4b, 0x8f, 0x87, 0x70, 570 | 0x88, 0xc9, 0x82, 0x8e, 0x35, 0x07, 0xda, 0xda, 0x17, 0xbb, 0x14, 0xbb, 0x2c, 0x73, 0x89, 0x03, 571 | 0xc7, 0xaa, 0xb4, 0x0c, 0x54, 0x5c, 0x46, 0xaa, 0x53, 0x82, 0x3b, 0x12, 0x01, 0x81, 0xa1, 0x6c, 572 | 0xe9, 0x28, 0x76, 0x28, 0x8c, 0x4a, 0xcd, 0x81, 0x5b, 0x23, 0x3d, 0x96, 0xbb, 0x57, 0x2b, 0x16, 573 | 0x2e, 0xc1, 0xb9, 0xd7, 0x12, 0xf2, 0xc3, 0x96, 0x6c, 0xaa, 0xc9, 0xcf, 0x17, 0x4f, 0x3a, 0xed, 574 | 0xfe, 0xc4, 0xd1, 0x9f, 0xf9, 0xa8, 0x7f, 0x8e, 0x21, 0xe8, 0xe1, 0xa9, 0x78, 0x9b, 0x49, 0x0b, 575 | 0xa0, 0x5f, 0x1d, 0xeb, 0xd2, 0x17, 0x32, 0xfb, 0x2e, 0x15, 0xa0, 0x17, 0xc4, 0x75, 0xc4, 0xfd, 576 | 0x00, 0xbe, 0x04, 0x21, 0x86, 0xdc, 0x29, 0xe6, 0x8b, 0xb7, 0xec, 0xe1, 0x92, 0x43, 0x8f, 0x3b, 577 | 0x0c, 0x5e, 0xf8, 0xe4, 0xa5, 0x35, 0x83, 0xa0, 0x19, 0x43, 0xcf, 0x84, 0xbb, 0xa5, 0x84, 0x21, 578 | 0x73, 0xa6, 0xb3, 0xa7, 0x28, 0x95, 0x66, 0x68, 0x7c, 0x30, 0x18, 0xf7, 0x64, 0xab, 0x18, 0x10, 579 | 0x31, 0x69, 0x91, 0x93, 0x28, 0x71, 0x3c, 0x3b, 0xd4, 0x63, 0xd3, 0x39, 0x8a, 0x1f, 0xeb, 0x8e, 580 | 0x68, 0xe4, 0x4c, 0xfe, 0x48, 0x2f, 0x72, 0x84, 0x7f, 0x46, 0xc8, 0x0e, 0x6c, 0xc7, 0xf6, 0xcc, 581 | 0xf1, 0x79, 0xf4, 0x82, 0xc8, 0x88, 0x59, 0x4e, 0x76, 0x27, 0x66, 0x53, 0xb4, 0x83, 0x98, 0xa2, 582 | 0x6c, 0x7c, 0x9e, 0x42, 0x0c, 0xb6, 0xc1, 0xd3, 0xbc, 0x76, 0x46, 0xf3, 0x3b, 0xb8, 0x32, 0xbf, 583 | 0xba, 0x98, 0x48, 0x9c, 0xad, 0xfb, 0xd5, 0x5d, 0xd8, 0xb2, 0xc5, 0x76, 0x87, 0xa4, 0x7a, 0xcb, 584 | 0xa4, 0xab, 0x39, 0x01, 0x52, 0xd8, 0xfb, 0xb3, 0xf2, 0x03, 0x27, 0xd8, 0x24, 0xb2, 0x84, 0xd2, 585 | 0x88, 0xfb, 0x01, 0x52, 0xe4, 0x9f, 0xc4, 0x46, 0x78, 0xae, 0xd4, 0xd3, 0xf0, 0x85, 0xb7, 0xc5, 586 | 0x5d, 0xe7, 0x7b, 0xd4, 0x5a, 0xf8, 0x12, 0xfc, 0x37, 0x94, 0x4a, 0xd2, 0x45, 0x4f, 0x99, 0xfb, 587 | 0xb3, 0x4a, 0x58, 0x3b, 0xf1, 0x6b, 0x67, 0x65, 0x9e, 0x6f, 0x21, 0x6d, 0x34, 0xb1, 0xd7, 0x9b, 588 | 0x1b, 0x4d, 0xec, 0xc0, 0x98, 0xa4, 0x42, 0x07, 0xe1, 0xc5, 0xfe, 0xeb, 0x6c, 0xe3, 0x0a, 0xcc, 589 | 0x2c, 0xf7, 0xe2, 0xb1, 0x34, 0x49, 0x0b, 0x44, 0x27, 0x44, 0x77, 0x2d, 0x18, 0x4e, 0x59, 0x03, 590 | 0x8a, 0xa5, 0x17, 0xa9, 0x71, 0x54, 0x18, 0x1e, 0x4d, 0xfd, 0x94, 0xfe, 0x72, 0xa5, 0xa4, 0xca, 591 | 0x2e, 0x7e, 0x22, 0xbc, 0xe7, 0x33, 0xd0, 0x3e, 0x7d, 0x93, 0x19, 0x71, 0x0b, 0xef, 0xbc, 0x30, 592 | 0xd7, 0x82, 0x6b, 0x72, 0x85, 0x19, 0xba, 0x74, 0x69, 0x0e, 0x4f, 0x90, 0x65, 0x87, 0xa0, 0x38, 593 | 0x28, 0x95, 0xb9, 0x0d, 0x82, 0xed, 0x3e, 0x35, 0x7f, 0xaf, 0x8e, 0x59, 0xac, 0xa8, 0x5f, 0xd2, 594 | 0x06, 0x3a, 0xb5, 0x92, 0xd8, 0x3d, 0x24, 0x5a, 0x91, 0x9e, 0xa5, 0x3c, 0x50, 0x1b, 0x9a, 0xcc, 595 | 0xd2, 0xa1, 0xed, 0x95, 0x1f, 0x43, 0xc0, 0x49, 0xab, 0x9d, 0x25, 0xc7, 0xf1, 0xb7, 0x0a, 0xe4, 596 | 0xf9, 0x42, 0xed, 0xb1, 0xf3, 0x11, 0xf7, 0x41, 0x78, 0x33, 0x06, 0x22, 0x45, 0xb4, 0x29, 0xd4, 597 | 0xf0, 0x13, 0xae, 0x90, 0x19, 0xff, 0x52, 0x04, 0x4c, 0x97, 0xc7, 0x3b, 0x88, 0x82, 0xcf, 0x03, 598 | 0x95, 0x5c, 0x73, 0x9f, 0x87, 0x4a, 0x02, 0x96, 0x37, 0xc0, 0xf0, 0x60, 0x71, 0x00, 0xe3, 0x07, 599 | 0x0f, 0x40, 0x8d, 0x08, 0x2a, 0xa7, 0xa2, 0xab, 0xf1, 0x3e, 0x73, 0xbd, 0x1e, 0x25, 0x2c, 0x22, 600 | 0x8a, 0xba, 0x7a, 0x9c, 0x1f, 0x07, 0x5b, 0xc4, 0x39, 0x57, 0x1b, 0x35, 0x93, 0x2f, 0x5c, 0x91, 601 | 0x2c, 0xb0, 0xb3, 0x8d, 0xa1, 0xc9, 0x5e, 0x64, 0xfc, 0xf9, 0xbf, 0xec, 0x0b, 0x9b, 0x0d, 0xd8, 602 | 0xf0, 0x42, 0xfd, 0xf0, 0x5e, 0x50, 0x58, 0x29, 0x9e, 0x96, 0xe4, 0x18, 0x50, 0x74, 0x91, 0x9d, 603 | 0x90, 0xb7, 0xb3, 0xb0, 0xa9, 0x7e, 0x22, 0x42, 0xca, 0x08, 0xcd, 0x99, 0xc9, 0xec, 0xb1, 0x2f, 604 | 0xc4, 0x9a, 0xdb, 0x2b, 0x25, 0x72, 0x40, 0xcc, 0x38, 0x78, 0x02, 0xf0, 0x0e, 0x0e, 0x49, 0x95, 605 | 0x26, 0x63, 0xea, 0x27, 0x84, 0x08, 0x70, 0x9b, 0xce, 0x5b, 0x36, 0x3c, 0x03, 0x60, 0x93, 0xd7, 606 | 0xa0, 0x5d, 0x44, 0x0c, 0x9e, 0x7a, 0x7a, 0xbb, 0x3d, 0x71, 0xeb, 0xb4, 0xd1, 0x0b, 0xfc, 0x77, 607 | 0x81, 0xbc, 0xd6, 0x6f, 0x79, 0x32, 0x2c, 0x18, 0x26, 0x2d, 0xfc, 0x2d, 0xcc, 0xf3, 0xe5, 0xf1, 608 | 0xea, 0x98, 0xbe, 0xa3, 0xca, 0xae, 0x8a, 0x83, 0x70, 0x63, 0x12, 0x76, 0x44, 0x23, 0xa6, 0x92, 609 | 0xae, 0x0c, 0x1e, 0x2e, 0x23, 0xb0, 0x16, 0x86, 0x5f, 0xfb, 0x12, 0x5b, 0x22, 0x38, 0x57, 0x54, 610 | 0x7a, 0xc7, 0xe2, 0x46, 0x84, 0x33, 0xb5, 0x26, 0x98, 0x43, 0xab, 0xba, 0xbb, 0xe9, 0xf6, 0xf4, 611 | 0x38, 0xd7, 0xe3, 0x87, 0xe3, 0x61, 0x7a, 0x21, 0x9f, 0x62, 0x54, 0x0e, 0x73, 0x43, 0xe1, 0xbb, 612 | 0xf4, 0x93, 0x55, 0xfb, 0x5a, 0x19, 0x38, 0x04, 0x84, 0x39, 0xcb, 0xa5, 0xce, 0xe8, 0x19, 0x19, 613 | 0x9b, 0x2b, 0x5c, 0x39, 0xfd, 0x35, 0x1a, 0xa2, 0x74, 0x53, 0x6a, 0xad, 0xb6, 0x82, 0xb5, 0x78, 614 | 0x94, 0x3f, 0x0c, 0xcf, 0x48, 0xe4, 0xec, 0x7d, 0xdc, 0x93, 0x8e, 0x2f, 0xd0, 0x1a, 0xcf, 0xaa, 615 | 0x1e, 0x72, 0x17, 0xf7, 0xb3, 0x89, 0x28, 0x5c, 0x0d, 0xfd, 0x31, 0xa1, 0x54, 0x5e, 0xd3, 0xa8, 616 | 0x5f, 0xac, 0x8e, 0xb9, 0xda, 0xb6, 0xee, 0x82, 0x6a, 0xf9, 0x0f, 0x9e, 0x1e, 0xe5, 0xd5, 0x55, 617 | 0xdd, 0x1c, 0x05, 0xae, 0xc0, 0x77, 0xf7, 0xc8, 0x03, 0xcb, 0xc2, 0xf1, 0xcf, 0x98, 0x39, 0x3f, 618 | 0x0f, 0x37, 0x83, 0x8f, 0xfe, 0xa3, 0x72, 0xff, 0x70, 0x88, 0x86, 0xb0, 0x59, 0x34, 0xe1, 0xa6, 619 | 0x45, 0x12, 0xde, 0x14, 0x46, 0x08, 0x86, 0x4a, 0x88, 0xa5, 0xc3, 0xa1, 0x73, 0xfd, 0xcf, 0xdf, 620 | 0x57, 0x25, 0xda, 0x91, 0x6e, 0xd5, 0x07, 0xe4, 0xca, 0xec, 0x87, 0x87, 0xbe, 0xfb, 0x91, 0xe3, 621 | 0xec, 0x9b, 0x22, 0x2f, 0xa0, 0x9f, 0x37, 0x4b, 0xd9, 0x68, 0x81, 0xac, 0x2d, 0xdd, 0x1f, 0x88, 622 | 0x5d, 0x42, 0xea, 0x58, 0x4c, 623 | }, &buf); 624 | 625 | try std.testing.expectEqualSlices(u8, &auth_tag_verify, &auth_tag); 626 | } 627 | -------------------------------------------------------------------------------- /src/cert/asn1.zig: -------------------------------------------------------------------------------- 1 | //! Decoder for the ASN.1 DER encoding rules. 2 | //! DER encoding contains a tag, length and value for each element. 3 | //! Note: This means it only supports DER encoding rules, not BER and CER 4 | //! as the only use case is for X.509 support. 5 | 6 | const std = @import("std"); 7 | const mem = std.mem; 8 | const Allocator = mem.Allocator; 9 | const BigInt = std.math.big.int.Const; 10 | const testing = std.testing; 11 | 12 | /// A subset of ASN.1 tag types as specified by the spec. 13 | /// We only define the tags that are required for PEM certificates. 14 | /// Read more at: 15 | /// https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 16 | pub const Tag = enum(u8) { 17 | integer = 0x02, 18 | bit_string = 0x03, 19 | octet_string = 0x04, 20 | @"null" = 0x05, 21 | object_identifier = 0x06, 22 | utf8_string = 0x0C, 23 | printable_string = 0x13, 24 | ia5_string = 0x16, 25 | utc_time = 0x17, 26 | sequence = 0x30, 27 | set = 0x31, 28 | // context = 255, // Used by decoder, but is not a valid Tag. 29 | _, 30 | }; 31 | 32 | /// Represents the value of an element that was encoded using ASN.1 DER encoding rules. 33 | pub const Value = union(Tag) { 34 | integer: BigInt, // can represent integers up to 126 bytes. 35 | bit_string: BitString, 36 | octet_string: []const u8, 37 | object_identifier: struct { 38 | data: [16]u32, 39 | len: u5, 40 | }, 41 | utf8_string: []const u8, 42 | printable_string: []const u8, 43 | ia5_string: []const u8, 44 | utc_time: []const u8, 45 | sequence: []const Value, 46 | set: []const Value, 47 | @"null", 48 | 49 | /// Frees any memory that was allocated while constructed 50 | /// a given `Value` 51 | pub fn deinit(self: Value, gpa: Allocator) void { 52 | switch (self) { 53 | .integer => |int| gpa.free(int.limbs), 54 | .sequence => |seq| for (seq) |val| { 55 | val.deinit(gpa); 56 | } else gpa.free(seq), 57 | .set => |set| for (set) |val| { 58 | val.deinit(gpa); 59 | } else gpa.free(set), 60 | else => {}, 61 | } 62 | } 63 | }; 64 | 65 | /// Represents a string which may contain unused bits 66 | pub const BitString = struct { 67 | /// Contains the string as well as the unused bits 68 | data: []const u8, 69 | /// The total amount of bits of the string 70 | bit_length: u8, 71 | }; 72 | 73 | /// Kind is a union representing each individual element 74 | /// within a schema as to what is expected from the encoded 75 | /// data and how to interpret the data. This allows us 76 | /// to successfully decode asn.1 data that requires information 77 | /// from outside what's available within the data itself. 78 | pub const Kind = union(enum) { 79 | /// A regular element that should not be ignored, 80 | /// is mandatory, but does not require special casing. 81 | tag: Tag, 82 | /// The element could be an optional or context specific. 83 | /// Will decode the element according to the given `Tag`, 84 | /// if the id matches the upper bits of the tag byte. 85 | /// When the id does not match, the element will be ignored. 86 | context_specific: struct { 87 | optional: bool = false, 88 | id: u8, 89 | tag: Tag, 90 | }, 91 | /// Allows the user how to decode the choice 92 | choice: fn (decoder: *Decoder, id: u8) Decoder.Error!Value, 93 | /// When the element can be ignored 94 | none, 95 | }; 96 | 97 | /// Represents a series of asn.1 elements, where each 98 | /// `Kind` describes how to decode the next element. 99 | /// This is required in cases where the data is ambigious, 100 | /// such as context specific or optional data and the data describing 101 | /// the Tag of the element is omitted. 102 | pub const Schema = []const Kind; 103 | 104 | /// Decodes ans.1 binary data into Zig types 105 | pub const Decoder = struct { 106 | /// Internal index into the data. 107 | /// Should not be tempered with outside the Decoder. 108 | index: usize, 109 | /// The asn.1 binary data that is being decoded by the current instance. 110 | data: []const u8, 111 | /// Allocator is used to allocate memory for limbs (as we must swap them due to endianness), 112 | /// as well as allocate a Value for sets and sequences. 113 | /// Memory can be freed easily by calling `deinit` on a `Value`. 114 | gpa: Allocator, 115 | /// A `Schema` is used to provide a list of ordered elements that tell 116 | /// the decoder how to decode each individual element, allowing the decoder 117 | /// to handle context-specific elements. 118 | schema: ?Schema, 119 | /// The index into `schema` to determine which element is being decoded. 120 | /// The field is not used when `schema` is `null`. 121 | schema_index: usize, 122 | 123 | pub const Error = error{ 124 | /// Tag found is either not supported or incorrect 125 | InvalidTag, 126 | OutOfMemory, 127 | /// Index has reached the end of `data`'s size 128 | EndOfData, 129 | /// The length could not be decoded and is malformed 130 | InvalidLength, 131 | /// The encoded data contains context specific data, 132 | /// meaning we cannot infer the Tag without knowing the context. 133 | /// Use `usingSchema` to define a context and tell the decoder 134 | /// how to decode the given data. 135 | ContextSpecific, 136 | }; 137 | 138 | const DecodeOptions = union(enum) { 139 | no_schema, 140 | with_schema: Schema, 141 | }; 142 | 143 | /// Initializes a new decoder instance for the given binary data. 144 | /// Allows the caller to provide a `Schema` which represents the 145 | /// layout of the encoded data and tells the decoder how it must be decoded. 146 | /// 147 | /// Provide `.no_schema` when the data is 'simple' and requires no context-specific handling. 148 | pub fn init(gpa: Allocator, data: []const u8, options: DecodeOptions) Decoder { 149 | return .{ 150 | .index = 0, 151 | .data = data, 152 | .gpa = gpa, 153 | .schema = if (options == .no_schema) null else options.with_schema, 154 | .schema_index = 0, 155 | }; 156 | } 157 | 158 | /// Decodes the data, interpreting it as the given `Tag`. 159 | /// The decoder still verifies if the given tag is a valid tag according 160 | /// to the found tag-byte. 161 | pub fn decodeTag(self: *Decoder, tag: Tag) Error!Value { 162 | return try self.decodeMaybeTag(tag); 163 | } 164 | 165 | /// Decodes from the current index into `data` and returns 166 | /// a `Value`, representing a type based on the tag found 167 | /// in the encoded data. 168 | pub fn decode(self: *Decoder) Error!Value { 169 | return self.decodeMaybeTag(null); 170 | } 171 | 172 | fn decodeMaybeTag(self: *Decoder, maybe_tag: ?Tag) Error!Value { 173 | const tag_byte = try self.nextByte(); 174 | const tag = std.meta.intToEnum(Tag, tag_byte) catch blk: { 175 | if (tag_byte & 0x80 == 0x80) { 176 | if (maybe_tag) |wanted_tag| break :blk wanted_tag; 177 | 178 | if (self.element()) |kind| { 179 | switch (kind) { 180 | .context_specific => |ctx| { 181 | const id = @truncate(u3, tag_byte); 182 | if (id == ctx.id) { 183 | break :blk ctx.tag; 184 | } else if (ctx.optional) { 185 | self.maybeAdvanceElement(); 186 | self.index -= 1; 187 | return @as(Value, .@"null"); 188 | } 189 | return error.InvalidTag; 190 | }, 191 | .choice => |callback| { 192 | // decrease index to make the tag byte available again to the callback 193 | self.index -= 1; 194 | return callback(self, @truncate(u3, tag_byte)); 195 | }, 196 | else => return error.InvalidTag, 197 | } 198 | } 199 | return error.ContextSpecific; 200 | } 201 | return error.InvalidTag; 202 | }; 203 | self.maybeAdvanceElement(); 204 | 205 | return switch (tag) { 206 | .bit_string => Value{ .bit_string = try self.decodeBitString() }, 207 | .integer => Value{ .integer = try self.decodeInt() }, 208 | .octet_string, .ia5_string, .utf8_string, .printable_string => { 209 | const string_value = try self.decodeString(); 210 | return @as(Value, switch (tag) { 211 | .octet_string => .{ .octet_string = string_value }, 212 | .ia5_string => .{ .ia5_string = string_value }, 213 | .utf8_string => .{ .utf8_string = string_value }, 214 | .printable_string => .{ .printable_string = string_value }, 215 | .utc_time => .{ .utc_time = string_value }, 216 | else => unreachable, 217 | }); 218 | }, 219 | .object_identifier => try self.decodeObjectIdentifier(), 220 | .sequence, .set => try self.decodeSequence(tag), 221 | .@"null" => try self.decodeNull(), 222 | else => unreachable, 223 | }; 224 | } 225 | 226 | /// Reads the next byte from the data and increments the index. 227 | /// Returns error.EndOfData if reached the end of the data. 228 | fn nextByte(self: *Decoder) error{EndOfData}!u8 { 229 | if (self.index >= self.data.len) return error.EndOfData; 230 | defer self.index += 1; 231 | return self.data[self.index]; 232 | } 233 | 234 | /// Returns the current element `Kind`. 235 | /// Will return `null` when no schema was set, or no element 236 | /// is present at the current index. 237 | /// 238 | /// Note: This does no bound-checking to verify the index advances 239 | /// past the length of elements. However, `element()` does verify this. 240 | fn element(self: *Decoder) ?Kind { 241 | if (self.schema) |schema| { 242 | if (self.schema_index >= schema.len) return null; 243 | return schema[self.schema_index]; 244 | } 245 | return null; 246 | } 247 | 248 | /// When a `Schema` was set on the `Decoder`, this will 249 | /// advance the index, allowing us to retrieve the next element's `Kind` 250 | fn maybeAdvanceElement(self: *Decoder) void { 251 | self.schema_index += @boolToInt(self.schema != null); 252 | } 253 | 254 | /// Returns the length of the current element being decoded. 255 | fn findLength(self: *Decoder) error{InvalidLength}!usize { 256 | const first_byte = self.data[self.index]; 257 | self.index += 1; 258 | if (first_byte & 0x80 == 0) { 259 | return first_byte; // single byte length 260 | } 261 | 262 | // The first 7 bits of the first byte contain the total 263 | // amount of bytes that represent the length. 264 | const byte_length = @truncate(u7, first_byte); 265 | if (byte_length > self.data[self.index..].len) return error.InvalidLength; 266 | if (byte_length > @sizeOf(usize)) @panic("TODO: Implement lengths larger than @sizeOf(usize))"); 267 | const length = mem.readIntBig(usize, self.data[self.index..][0..@sizeOf(usize)]); 268 | self.index += byte_length; 269 | return length; 270 | } 271 | 272 | /// Decodes the data into a `BitString` 273 | pub fn decodeBitString(self: *Decoder) !BitString { 274 | const tag_byte = try self.nextByte(); 275 | if (tag_byte != @enumToInt(Tag.bit_string)) { 276 | return error.InvalidTag; 277 | } 278 | const length = try self.findLength(); 279 | const extra_bits = try self.nextByte(); 280 | const total_bit_length = (length - 1) * 8 - extra_bits; 281 | const string_length = std.math.divCeil(usize, total_bit_length, 8) catch unreachable; 282 | const string = self.data[self.index..][0..string_length]; 283 | self.index += length; 284 | return BitString{ .data = string, .bit_length = @intCast(u8, total_bit_length) }; 285 | } 286 | 287 | /// Decodes the data into the given string tag. 288 | /// If the expected data contains a bit string, use `decodeBitString`. 289 | pub fn decodeString(self: *Decoder) ![]const u8 { 290 | const length = try self.findLength(); 291 | defer self.index += length; 292 | return self.data[self.index..][0..length]; 293 | } 294 | 295 | /// Decodes an integer value, while allocating the memory for the limbs 296 | /// as we must ensure the endianness is correct. 297 | /// BigInt expects LE bytes, whereas certificates are BE. 298 | pub fn decodeInt(self: *Decoder) !BigInt { 299 | const length = try self.findLength(); 300 | const byte = try self.nextByte(); 301 | const is_positive = byte == 0x00 and length > 1; 302 | const actual_length = length - @boolToInt(is_positive); 303 | 304 | const limb_count = std.math.divCeil(usize, actual_length, @sizeOf(usize)) catch unreachable; 305 | const limbs = try self.gpa.alloc(usize, limb_count); 306 | errdefer self.gpa.free(limbs); 307 | mem.set(usize, limbs, 0); 308 | 309 | const limb_bytes = @ptrCast([*]u8, limbs.ptr)[0..actual_length]; 310 | if (is_positive) { 311 | mem.copy(u8, limb_bytes, self.data[self.index..][0..actual_length]); 312 | } else { 313 | mem.copy(u8, limb_bytes[1..], self.data[self.index - 1 ..][1..actual_length]); 314 | limb_bytes[0] = byte & ~@as(u8, 0x80); 315 | } 316 | 317 | mem.reverse(u8, limb_bytes); 318 | self.index += length - 1; 319 | return BigInt{ 320 | .limbs = limbs, 321 | .positive = is_positive or byte & 0x80 == 0x00, 322 | }; 323 | } 324 | 325 | /// Decodes data into an object identifier 326 | pub fn decodeObjectIdentifier(self: *Decoder) !Value { 327 | const length = try self.findLength(); 328 | const initial_byte = try self.nextByte(); 329 | var identifier = Value{ .object_identifier = .{ .data = undefined, .len = 0 } }; 330 | identifier.object_identifier.data[0] = initial_byte / 40; 331 | identifier.object_identifier.data[1] = initial_byte % 40; 332 | 333 | var out_idx: u5 = 2; 334 | var index: usize = 0; 335 | while (index < length - 1) { 336 | var current: u32 = 0; 337 | var current_byte = try self.nextByte(); 338 | index += 1; 339 | while (current_byte & 0x80 == 0x80) : (index += 1) { 340 | current *= 128; 341 | current += @as(u32, current_byte & ~@as(u8, 0x80)) * 128; 342 | current_byte = try self.nextByte(); 343 | } else { 344 | current += current_byte; 345 | } 346 | identifier.object_identifier.data[out_idx] = current; 347 | out_idx += 1; 348 | } 349 | identifier.object_identifier.len = out_idx; 350 | self.index += length; 351 | return identifier; 352 | } 353 | 354 | /// Decodes either a sequence of/set of into a `Value` 355 | /// Allocates memory for the list of `Value`. 356 | pub fn decodeSequence(self: *Decoder, tag: Tag) !Value { 357 | const length = try self.findLength(); 358 | var value_list = std.ArrayList(Value).init(self.gpa); 359 | errdefer for (value_list.items) |val| { 360 | val.deinit(self.gpa); 361 | } else value_list.deinit(); 362 | 363 | while (self.index < length) { 364 | const value = try self.decode(); 365 | errdefer value.deinit(self.gpa); 366 | try value_list.append(value); 367 | } 368 | 369 | const final_list = value_list.toOwnedSlice(); 370 | return switch (tag) { 371 | .sequence => Value{ .sequence = final_list }, 372 | .set => Value{ .sequence = final_list }, 373 | else => unreachable, 374 | }; 375 | } 376 | 377 | /// Decodes the data into a `@"null"` `Value` 378 | /// and verifies the length is '0'. 379 | pub fn decodeNull(self: *Decoder) !Value { 380 | const length = try self.findLength(); 381 | if (length != 0) return error.InvalidLength; 382 | return .@"null"; 383 | } 384 | }; 385 | 386 | test "Decode int" { 387 | const bytes: []const u8 = &.{ 0x02, 0x09, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; 388 | var decoder = Decoder.init(testing.allocator, bytes, .no_schema); 389 | const value = try decoder.decode(); 390 | defer value.deinit(testing.allocator); 391 | try testing.expectEqual(@as(u64, (1 << 63) + 1), try value.integer.to(u64)); 392 | } 393 | 394 | test "Octet string" { 395 | const bytes: []const u8 = &.{ 0x04, 0x04, 0x03, 0x02, 0x06, 0xA0 }; 396 | var decoder = Decoder.init(testing.allocator, bytes, .no_schema); 397 | const value = try decoder.decode(); 398 | defer value.deinit(testing.allocator); 399 | try testing.expectEqualSlices(u8, bytes[2..], value.octet_string); 400 | } 401 | 402 | test "Object identifier" { 403 | const bytes: []const u8 = &.{ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b }; 404 | var decoder = Decoder.init(testing.allocator, bytes, .no_schema); 405 | const value = try decoder.decode(); 406 | defer value.deinit(testing.allocator); 407 | try testing.expectEqual(@as(u5, 7), value.object_identifier.len); 408 | try testing.expectEqualSlices(u32, &.{ 409 | 1, 2, 840, 113549, 1, 1, 11, 410 | }, value.object_identifier.data[0..value.object_identifier.len]); 411 | } 412 | 413 | test "Printable string" { 414 | const bytes: []const u8 = &.{ 0x13, 0x02, 0x68, 0x69 }; 415 | var decoder = Decoder.init(testing.allocator, bytes, .no_schema); 416 | const value = try decoder.decode(); 417 | defer value.deinit(testing.allocator); 418 | try testing.expectEqualStrings("hi", value.printable_string); 419 | } 420 | 421 | test "Utf8 string" { 422 | const bytes: []const u8 = &.{ 0x0c, 0x04, 0xf0, 0x9f, 0x98, 0x8e }; 423 | var decoder = Decoder.init(testing.allocator, bytes, .no_schema); 424 | const value = try decoder.decode(); 425 | defer value.deinit(testing.allocator); 426 | try testing.expectEqualStrings("😎", value.utf8_string); 427 | } 428 | 429 | test "Sequence of" { 430 | const bytes: []const u8 = &.{ 0x30, 0x09, 0x02, 0x01, 0x07, 0x02, 0x01, 0x08, 0x02, 0x01, 0x09 }; 431 | var decoder = Decoder.init(testing.allocator, bytes, .no_schema); 432 | const value = try decoder.decode(); 433 | defer value.deinit(testing.allocator); 434 | 435 | const expected: []const u8 = &.{ 7, 8, 9 }; 436 | try testing.expectEqual(expected.len, value.sequence.len); 437 | for (expected) |expected_value, index| { 438 | try testing.expectEqual(expected_value, try value.sequence[index].integer.to(u8)); 439 | } 440 | } 441 | 442 | test "Choice" { 443 | const bytes: []const u8 = &.{ 0x82, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d }; 444 | 445 | const callback = struct { 446 | fn decode(decoder: *Decoder, id: u8) Decoder.Error!Value { 447 | if (id != 2) return error.InvalidTag; 448 | return try decoder.decodeTag(.ia5_string); 449 | } 450 | }.decode; 451 | 452 | var decoder = Decoder.init( 453 | testing.allocator, 454 | bytes, 455 | .{ 456 | .with_schema = &.{ 457 | .{ 458 | .choice = callback, 459 | }, 460 | }, 461 | }, 462 | ); 463 | const value = try decoder.decode(); 464 | defer value.deinit(testing.allocator); 465 | 466 | try testing.expectEqualStrings("example.com", value.ia5_string); 467 | } 468 | 469 | test "Optional" { 470 | const bytes: []const u8 = &.{ 0x30, 0x03, 0x80, 0x01, 0x09 }; 471 | 472 | var decoder = Decoder.init(testing.allocator, bytes, .{ .with_schema = &.{ 473 | .{ .tag = .sequence }, .{ .context_specific = .{ .id = 0x0, .tag = .integer } }, 474 | } }); 475 | const value = try decoder.decode(); 476 | defer value.deinit(testing.allocator); 477 | 478 | try testing.expectEqual(@as(u8, 9), try value.sequence[0].integer.to(u8)); 479 | } 480 | 481 | test "Optional with null" { 482 | const bytes: []const u8 = &.{ 0x30, 0x03, 0x81, 0x01, 0x09 }; 483 | 484 | var decoder = Decoder.init(testing.allocator, bytes, .{ 485 | .with_schema = &.{ 486 | .{ .tag = .sequence }, 487 | .{ .context_specific = .{ .optional = true, .id = 0x0, .tag = .integer } }, 488 | .{ .context_specific = .{ .optional = true, .id = 0x1, .tag = .integer } }, 489 | }, 490 | }); 491 | const value = try decoder.decode(); 492 | defer value.deinit(testing.allocator); 493 | 494 | try testing.expect(value.sequence.len == 2); 495 | try testing.expect(value.sequence[0] == .@"null"); 496 | try testing.expectEqual(@as(u8, 9), try value.sequence[1].integer.to(u8)); 497 | } 498 | -------------------------------------------------------------------------------- /src/cert/cert.zig: -------------------------------------------------------------------------------- 1 | pub const asn = @import("asn1.zig"); 2 | pub const pem = @import("pem.zig"); 3 | 4 | test { 5 | _ = asn; 6 | _ = pem; 7 | } 8 | -------------------------------------------------------------------------------- /src/cert/pem.zig: -------------------------------------------------------------------------------- 1 | //! Implements PEM decoding according to https://datatracker.ietf.org/doc/html/rfc7468 2 | 3 | const std = @import("std"); 4 | const mem = std.mem; 5 | const Allocator = mem.Allocator; 6 | 7 | /// Convenience type that contains the `AsnType` 8 | /// and the base64 decoded content of the file. 9 | pub const Pem = struct { 10 | asn_type: AsnType, 11 | /// Decoded content of a PEM file 12 | content: []const u8, 13 | 14 | /// Frees any memory that was allocated during Pem decoding. 15 | /// Must provide the same `Allocator` that was given to the decoder. 16 | pub fn deinit(self: *Pem, gpa: Allocator) void { 17 | gpa.free(self.content); 18 | self.* = undefined; 19 | } 20 | }; 21 | 22 | /// All ASN.1 types 23 | pub const AsnType = enum { 24 | certificate, 25 | certificate_list, 26 | certificate_request, 27 | content_info, 28 | private_key_info, 29 | encrypted_private_key_info, 30 | attribute_certificate, 31 | subject_public_key_info, 32 | 33 | /// From a given label string, returns a corresponding `AsnType`. 34 | /// Will return null when label does not provide any match. 35 | pub fn fromLabel(label: []const u8) ?AsnType { 36 | if (mem.eql(u8, label, "CERTIFICATE")) return .certificate; 37 | if (mem.eql(u8, label, "X509 XRL")) return .certificate_list; 38 | if (mem.eql(u8, label, "CERTIFICATE REQUEST")) return .certificate_request; 39 | if (mem.eql(u8, label, "PKCS7")) return .content_info; 40 | if (mem.eql(u8, label, "CMS")) return .content_info; 41 | if (mem.eql(u8, label, "PRIVATE KEY")) return .private_key_info; 42 | if (mem.eql(u8, label, "ENCRYPTED PRIVATE KEY")) return .encrypted_private_key_info; 43 | if (mem.eql(u8, label, "ATTRIBUTE CERTIFICATE")) return .attribute_certificate; 44 | if (mem.eql(u8, label, "PUBLIC KEY")) return .subject_public_key_info; 45 | 46 | return null; 47 | } 48 | }; 49 | 50 | /// Error set containing all possible errors when decoding a PEM file. 51 | pub const DecodeError = error{ 52 | /// Given PEM file is missing '-----BEGIN' 53 | MissingBegin, 54 | /// Given PEM file is missing '-----END' 55 | MissingEnd, 56 | /// -----BEGIN section is missing closing '-----' 57 | InvalidBegin, 58 | /// -----END section is missing closing '-----' 59 | InvalidEnd, 60 | /// Unknown or missing ASN.1 type 61 | InvalidAsnType, 62 | /// The ASN.1 type found in BEGIN and END are not matching 63 | MismatchingAsnType, 64 | /// A character was expected during encoding, but was either missing 65 | /// or a different character was found. 66 | MalformedFile, 67 | /// Tried to allocate memory during decoding, but no memory was available 68 | OutOfMemory, 69 | }; 70 | 71 | /// Decodes given bytes into a `Pem` instance. 72 | /// Memory is owned by caller and can be freed upon calling `deinit` on 73 | /// the returned instance. 74 | pub fn decode(gpa: Allocator, data: []const u8) (DecodeError || std.base64.Error)!Pem { 75 | var maybe_asn_type: ?AsnType = null; 76 | const begin_offset = if (mem.indexOf(u8, data, "-----BEGIN ")) |offset| blk: { 77 | const end_offset = mem.indexOfPos(u8, data, offset + 11, "-----") orelse return error.InvalidBegin; 78 | maybe_asn_type = AsnType.fromLabel(data[offset + 11 .. end_offset]); 79 | if (data[end_offset + 5] == '\n') break :blk end_offset + 6; 80 | if (data[end_offset + 5] == '\r') break :blk end_offset + 7; 81 | return error.MalformedFile; 82 | } else return error.MissingBegin; 83 | var end_offset = mem.indexOf(u8, data, "-----END ") orelse return error.MissingEnd; 84 | const asn_type = maybe_asn_type orelse return error.InvalidAsnType; 85 | 86 | // verify end 87 | { 88 | const start = end_offset + 9; 89 | const end = mem.indexOfPos(u8, data, start, "-----") orelse return error.InvalidEnd; 90 | const end_asn = AsnType.fromLabel(data[start..end]) orelse return error.InvalidAsnType; 91 | if (asn_type != end_asn) return error.MismatchingAsnType; 92 | } 93 | 94 | end_offset -= @boolToInt(data[end_offset - 1] == '\n'); 95 | end_offset -= @boolToInt(data[end_offset - 1] == '\r'); 96 | const content_to_decode = data[begin_offset..end_offset]; 97 | const len = try std.base64.standard.Decoder.calcSizeForSlice(content_to_decode); 98 | const decoded_data = try gpa.alloc(u8, len); 99 | errdefer gpa.free(decoded_data); 100 | try std.base64.standard.Decoder.decode(decoded_data, content_to_decode); 101 | return Pem{ 102 | .asn_type = asn_type, 103 | .content = decoded_data, 104 | }; 105 | } 106 | 107 | /// Given a file path, will attempt to decode its content into a `Pem` instance. 108 | /// Memory is owned by the caller. 109 | pub fn fromFile(gpa: Allocator, file_path: []const u8) (DecodeError || std.base64.Error)!Pem { 110 | const file = try std.fs.cwd().openFile(file_path, .{}); 111 | defer file.close(); 112 | 113 | const file_length = (try file.stat()).size; 114 | const content = try file.readToEndAlloc(gpa, file_length); 115 | defer gpa.free(content); 116 | 117 | return decode(gpa, content); 118 | } 119 | 120 | test "ASN.1 type CERTIFICATE" { 121 | const bytes = 122 | "-----BEGIN CERTIFICATE-----\n" ++ 123 | "MIIBmTCCAUegAwIBAgIBKjAJBgUrDgMCHQUAMBMxETAPBgNVBAMTCEF0bGFudGlz" ++ 124 | "MB4XDTEyMDcwOTAzMTAzOFoXDTEzMDcwOTAzMTAzN1owEzERMA8GA1UEAxMIQXRs" ++ 125 | "YW50aXMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAu+BXo+miabDIHHx+yquqzqNh" ++ 126 | "Ryn/XtkJIIHVcYtHvIX+S1x5ErgMoHehycpoxbErZmVR4GCq1S2diNmRFZCRtQID" ++ 127 | "AQABo4GJMIGGMAwGA1UdEwEB/wQCMAAwIAYDVR0EAQH/BBYwFDAOMAwGCisGAQQB" ++ 128 | "gjcCARUDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzA1BgNVHQEE" ++ 129 | "LjAsgBA0jOnSSuIHYmnVryHAdywMoRUwEzERMA8GA1UEAxMIQXRsYW50aXOCASow" ++ 130 | "CQYFKw4DAh0FAANBAKi6HRBaNEL5R0n56nvfclQNaXiDT174uf+lojzA4lhVInc0" ++ 131 | "ILwpnZ1izL4MlI9eCSHhVQBHEp2uQdXJB+d5Byg=" ++ 132 | "-----END CERTIFICATE-----"; 133 | 134 | var pem = try decode(std.testing.allocator, bytes); 135 | defer pem.deinit(std.testing.allocator); 136 | 137 | try std.testing.expectEqual(AsnType.certificate, pem.asn_type); 138 | } 139 | 140 | test "ASN.1 type CERTIFICATE + Explanatory Text" { 141 | const bytes = 142 | "Subject: CN=Atlantis\n" ++ 143 | "Issuer: CN=Atlantis\n" ++ 144 | "Validity: from 7/9/2012 3:10:38 AM UTC to 7/9/2013 3:10:37 AM UTC\n" ++ 145 | "-----BEGIN CERTIFICATE-----\n" ++ 146 | "MIIBmTCCAUegAwIBAgIBKjAJBgUrDgMCHQUAMBMxETAPBgNVBAMTCEF0bGFudGlz" ++ 147 | "MB4XDTEyMDcwOTAzMTAzOFoXDTEzMDcwOTAzMTAzN1owEzERMA8GA1UEAxMIQXRs" ++ 148 | "YW50aXMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAu+BXo+miabDIHHx+yquqzqNh" ++ 149 | "Ryn/XtkJIIHVcYtHvIX+S1x5ErgMoHehycpoxbErZmVR4GCq1S2diNmRFZCRtQID" ++ 150 | "AQABo4GJMIGGMAwGA1UdEwEB/wQCMAAwIAYDVR0EAQH/BBYwFDAOMAwGCisGAQQB" ++ 151 | "gjcCARUDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDAzA1BgNVHQEE" ++ 152 | "LjAsgBA0jOnSSuIHYmnVryHAdywMoRUwEzERMA8GA1UEAxMIQXRsYW50aXOCASow" ++ 153 | "CQYFKw4DAh0FAANBAKi6HRBaNEL5R0n56nvfclQNaXiDT174uf+lojzA4lhVInc0" ++ 154 | "ILwpnZ1izL4MlI9eCSHhVQBHEp2uQdXJB+d5Byg=" ++ 155 | "-----END CERTIFICATE-----"; 156 | 157 | var pem = try decode(std.testing.allocator, bytes); 158 | defer pem.deinit(std.testing.allocator); 159 | 160 | try std.testing.expectEqual(AsnType.certificate, pem.asn_type); 161 | } 162 | -------------------------------------------------------------------------------- /src/ciphers.zig: -------------------------------------------------------------------------------- 1 | //! Contains imlementations of cipher suites, required (or supported) 2 | //! for TLS 1.3 3 | //! All implementations are done, with 'streaming' support, required 4 | //! to be able to read in parts from a connection as users may not always 5 | //! provide a buffer large enough that entails the entire payload. 6 | 7 | const std = @import("std"); 8 | const tls = @import("tls.zig"); 9 | const mem = std.mem; 10 | const crypto = std.crypto; 11 | const modes = crypto.core.modes; 12 | const Ghash = crypto.onetimeauth.Ghash; 13 | 14 | fn ReturnType(comptime T: type) type { 15 | return @typeInfo(T).Fn.return_type.?; 16 | } 17 | 18 | /// Supported cipher suites 19 | pub const supported = [_]type{ 20 | Aes128, 21 | }; 22 | 23 | /// Type for our key data 24 | pub const KeyStorage = KeyData(&supported); 25 | 26 | /// Returns the type of a cipher based on a given `tls.CipherSuite`. 27 | /// 28 | /// It is illegal to provide a suite that is not part of the supported 29 | /// cipher suites found in `supported`. 30 | pub fn TypeFromSuite(comptime suite: tls.CipherSuite) type { 31 | return for (supported) |cipher| { 32 | if (cipher.suite == suite) { 33 | break cipher.Context; 34 | } 35 | } else unreachable; // given `suite` is not supported. 36 | } 37 | 38 | /// Checks if a given `tls.CipherSuite` is supported and implemented. 39 | pub fn isSupported(suite: tls.CipherSuite) bool { 40 | return inline for (supported) |cipher| { 41 | if (cipher.suite == suite) break true; 42 | } else false; 43 | } 44 | 45 | pub const Aes128 = struct { 46 | const Aes128Gcm = crypto.aead.aes_gcm.Aes128Gcm; 47 | const StdAes128 = crypto.core.aes.Aes128; 48 | 49 | pub const suite: tls.CipherSuite = .tls_aes_128_gcm_sha256; 50 | pub const tag_length = Aes128Gcm.tag_length; 51 | pub const nonce_length = Aes128Gcm.nonce_length; 52 | pub const key_length = Aes128Gcm.key_length; 53 | 54 | pub const Context = struct { 55 | ctx: ReturnType(@TypeOf(StdAes128.initEnc)), 56 | mac: crypto.onetimeauth.Ghash, 57 | t: u128, 58 | j: u128, 59 | }; 60 | 61 | /// Creates a new `Context` for the Aes128Gcm cipher, setting the initiual values 62 | /// using the keys provided and the current `sequence. 63 | pub fn init(key_data: *KeyStorage, sequence: u64, ad: []const u8) Context { 64 | const ctx = StdAes128.initEnc(key_data.clientKey(Aes128).*); 65 | var h: [16]u8 = undefined; 66 | ctx.encrypt(&h, &[_]u8{0} ** 16); 67 | 68 | var iv_copy = key_data.clientIv(Aes128).*; 69 | xorIv(&iv_copy, sequence); 70 | 71 | var t: [16]u8 = undefined; 72 | var j: [16]u8 = undefined; 73 | mem.copy(u8, j[0..nonce_length], &iv_copy); 74 | mem.writeIntBig(u32, j[nonce_length..][0..4], 1); 75 | ctx.encrypt(&t, &j); 76 | 77 | var mac = Ghash.init(&h); 78 | mac.update(ad); 79 | mac.pad(); 80 | 81 | mem.writeIntBig(u32, j[nonce_length..][0..4], 2); 82 | return .{ 83 | .ctx = ctx, 84 | .mac = mac, 85 | .t = mem.readIntBig(u128, &t), 86 | .j = mem.readIntBig(u128, &j), 87 | }; 88 | } 89 | 90 | /// Decrypts a partial message, writing the decrypted data to `out`. 91 | /// Also writes the read length to `idx`. 92 | /// NOTE: This does not validate the data. 93 | /// Ensure all data is correct by calling `verify` once all data is received. 94 | pub fn decryptPartial(context: *Context, out: []u8, data: []const u8, idx: *usize) void { 95 | context.mac.update(data); 96 | ctr(@TypeOf(context.ctx), context.ctx, out, data, &context.j, idx, .Big); 97 | } 98 | 99 | /// Verifies that all decrypted data til this point was valid. 100 | pub fn verify(context: *Context, auth_tag: [tag_length]u8, message_length: usize) !void { 101 | context.mac.pad(); 102 | var final_block: [16]u8 = undefined; 103 | mem.writeIntBig(u64, final_block[0..8], 5 * 8); // RecordHeader is always 5 bytes. 104 | mem.writeIntBig(u64, final_block[8..16], message_length * 8); // message length we have decrypted til this point. 105 | context.mac.update(&final_block); 106 | var computed_tag: [Ghash.mac_length]u8 = undefined; 107 | context.mac.final(&computed_tag); 108 | var t: [16]u8 = undefined; 109 | mem.writeIntBig(u128, &t, context.t); 110 | for (t) |x, i| { 111 | computed_tag[i] ^= x; 112 | } 113 | 114 | var acc: u8 = 0; 115 | for (computed_tag) |_, i| { 116 | acc |= (computed_tag[i] ^ auth_tag[i]); 117 | } 118 | if (acc != 0) { 119 | return error.AuthenticationFailed; 120 | } 121 | } 122 | 123 | /// Encrypts the given `data` and writes it to `out`. Asserts `out` has the same length as `data`. 124 | /// An authentication tag is written to `tag`, allowing a peer to verify the data. 125 | /// The given `sequence` will be xor'd with the server IV, stored in given `KeyStorage`. 126 | pub fn encrypt( 127 | /// Contains all keys of the client and server, to encrypt/decrypt the data 128 | key_storage: *KeyStorage, 129 | /// The buffer that will have the encrypted data written to 130 | out: []u8, 131 | /// The message to be encrypted 132 | data: []const u8, 133 | /// Additional data to encrypt with the message, in TLS this is the record header 134 | ad: []const u8, 135 | /// The sequence of the total amount of encrypted data we've transmitted. 136 | /// This will be xor'd with the server nonce. 137 | sequence: u64, 138 | /// The authentication tag created during the encryption of the data. 139 | /// Can be used by the peer to verify the data. 140 | tag: *[tag_length]u8, 141 | ) void { 142 | var iv = key_storage.serverIv(Aes128).*; 143 | xorIv(&iv, sequence); 144 | 145 | Aes128Gcm.encrypt(out, tag, data, ad, iv, key_storage.serverKey(Aes128).*); 146 | } 147 | 148 | /// xor's the sequence with a key_iv. 149 | fn xorIv(iv: *[nonce_length]u8, sequence: u64) void { 150 | var i: u5 = 0; 151 | while (i < 8) : (i += 1) { 152 | iv[nonce_length - 1 - i] ^= @intCast(u8, (sequence >> (i *% 8)) & 0xFF); 153 | } 154 | } 155 | }; 156 | 157 | /// Counter mode. 158 | /// 159 | /// This mode creates a key stream by encrypting an incrementing counter using a block cipher, and adding it to the source material. 160 | /// 161 | /// Important: the counter mode doesn't provide authenticated encryption: the ciphertext can be trivially modified without this being detected. 162 | /// As a result, applications should generally never use it directly, but only in a construction that includes a MAC. 163 | /// 164 | /// Original from: https://github.com/alexnask/iguanaTLS/blob/master/src/crypto.zig#L159 165 | pub fn ctr( 166 | comptime BlockCipher: anytype, 167 | block_cipher: BlockCipher, 168 | dst: []u8, 169 | src: []const u8, 170 | counterInt: *u128, 171 | idx: *usize, 172 | endian: std.builtin.Endian, 173 | ) void { 174 | std.debug.assert(dst.len >= src.len); 175 | const block_length = BlockCipher.block_length; 176 | var cur_idx: usize = 0; 177 | 178 | const offset = idx.* % block_length; 179 | if (offset != 0) { 180 | const part_len = std.math.min(block_length - offset, src.len); 181 | 182 | var counter: [BlockCipher.block_length]u8 = undefined; 183 | mem.writeInt(u128, &counter, counterInt.*, endian); 184 | var pad = [_]u8{0} ** block_length; 185 | mem.copy(u8, pad[offset..], src[0..part_len]); 186 | block_cipher.xor(&pad, &pad, counter); 187 | mem.copy(u8, dst[0..part_len], pad[offset..][0..part_len]); 188 | cur_idx += part_len; 189 | idx.* += part_len; 190 | if (idx.* % block_length == 0) 191 | counterInt.* += 1; 192 | } 193 | 194 | const start_idx = cur_idx; 195 | const remaining = src.len - cur_idx; 196 | cur_idx = 0; 197 | 198 | const parallel_count = BlockCipher.block.parallel.optimal_parallel_blocks; 199 | const wide_block_length = parallel_count * 16; 200 | if (remaining >= wide_block_length) { 201 | var counters: [parallel_count * 16]u8 = undefined; 202 | while (cur_idx + wide_block_length <= remaining) : (cur_idx += wide_block_length) { 203 | comptime var j = 0; 204 | inline while (j < parallel_count) : (j += 1) { 205 | mem.writeInt(u128, counters[j * 16 .. j * 16 + 16], counterInt.*, endian); 206 | counterInt.* +%= 1; 207 | } 208 | block_cipher.xorWide( 209 | parallel_count, 210 | dst[start_idx..][cur_idx..][0..wide_block_length], 211 | src[start_idx..][cur_idx..][0..wide_block_length], 212 | counters, 213 | ); 214 | idx.* += wide_block_length; 215 | } 216 | } 217 | while (cur_idx + block_length <= remaining) : (cur_idx += block_length) { 218 | var counter: [BlockCipher.block_length]u8 = undefined; 219 | mem.writeInt(u128, &counter, counterInt.*, endian); 220 | counterInt.* +%= 1; 221 | block_cipher.xor( 222 | dst[start_idx..][cur_idx..][0..block_length], 223 | src[start_idx..][cur_idx..][0..block_length], 224 | counter, 225 | ); 226 | idx.* += block_length; 227 | } 228 | if (cur_idx < remaining) { 229 | std.debug.assert(idx.* % block_length == 0); 230 | var counter: [BlockCipher.block_length]u8 = undefined; 231 | mem.writeInt(u128, &counter, counterInt.*, endian); 232 | 233 | var pad = [_]u8{0} ** block_length; 234 | mem.copy(u8, &pad, src[start_idx..][cur_idx..]); 235 | block_cipher.xor(&pad, &pad, counter); 236 | mem.copy(u8, dst[start_idx..][cur_idx..], pad[0 .. remaining - cur_idx]); 237 | 238 | idx.* += remaining - cur_idx; 239 | if (idx.* % block_length == 0) 240 | counterInt.* +%= 1; 241 | } 242 | } 243 | 244 | test "Aes128 - single message" { 245 | var key_data = KeyStorage{}; 246 | const key: [Aes128.key_length]u8 = [_]u8{0x69} ** Aes128.key_length; 247 | key_data.setClientKey(Aes128, key); 248 | const nonce: [Aes128.nonce_length]u8 = [_]u8{0x42} ** Aes128.nonce_length; 249 | key_data.setClientIv(Aes128, nonce); 250 | const m = "Test with message only"; 251 | const record: tls.Record = .{ .record_type = .application_data, .len = m.len }; 252 | var c: [m.len]u8 = undefined; 253 | var m2: [m.len]u8 = undefined; 254 | var tag: [Aes128.tag_length]u8 = undefined; 255 | 256 | crypto.aead.aes_gcm.Aes128Gcm.encrypt(&c, &tag, m, &record.toBytes(), nonce, key); 257 | var state = Aes128.init(&key_data, 0, &record.toBytes()); 258 | var idx: usize = 0; 259 | Aes128.decryptPartial(&state, &m2, &c, &idx); 260 | try Aes128.verify(&state, tag, m.len); 261 | try std.testing.expectEqualSlices(u8, m[0..], m2[0..]); 262 | try std.testing.expectEqual(m.len, idx); 263 | } 264 | 265 | test "Aes128 - multiple messages" { 266 | var key_data = KeyStorage{}; 267 | const key: [Aes128.key_length]u8 = [_]u8{0x69} ** Aes128.key_length; 268 | key_data.setClientKey(Aes128, key); 269 | const nonce: [Aes128.nonce_length]u8 = [_]u8{0x42} ** Aes128.nonce_length; 270 | key_data.setClientIv(Aes128, nonce); 271 | const m = "Test with message only"; 272 | const half_length = m.len / 2; 273 | var idx: usize = 0; 274 | const record: tls.Record = .{ .record_type = .application_data, .len = m.len }; 275 | var c: [m.len]u8 = undefined; 276 | var m2: [m.len]u8 = undefined; 277 | var tag: [Aes128.tag_length]u8 = undefined; 278 | 279 | crypto.aead.aes_gcm.Aes128Gcm.encrypt(&c, &tag, m, &record.toBytes(), nonce, key); 280 | var state = Aes128.init(&key_data, 0, &record.toBytes()); 281 | Aes128.decryptPartial(&state, m2[0..half_length], c[0..half_length], &idx); 282 | try std.testing.expectEqual(half_length, idx); 283 | Aes128.decryptPartial(&state, m2[half_length..], c[half_length..], &idx); 284 | try std.testing.expectEqual(m.len, idx); 285 | try Aes128.verify(&state, tag, m.len); 286 | try std.testing.expectEqualSlices(u8, m[0..], m2[0..]); 287 | } 288 | 289 | /// Manages storage of key data, generic over a slice 290 | /// of cipher types. Allowing us to store and read key data 291 | /// in correct lengths. 292 | pub fn KeyData(comptime ciphers: []const type) type { 293 | comptime var max_length: usize = 0; 294 | inline for (ciphers) |cipher| { 295 | var total = cipher.nonce_length + cipher.key_length; 296 | total *= 2; 297 | if (total > max_length) { 298 | max_length = total; 299 | } 300 | } 301 | 302 | return struct { 303 | const Self = @This(); 304 | 305 | data: [max_length]u8 = undefined, 306 | 307 | /// Returns the server IV array based on a given cipher type 308 | pub fn serverIv(self: *Self, comptime cipher: type) *[cipher.nonce_length]u8 { 309 | const start_index = cipher.key_length * 2; 310 | return self.data[start_index..][0..cipher.nonce_length]; 311 | } 312 | 313 | /// Returns the client IV array based on a given cipher type 314 | pub fn clientIv(self: *Self, comptime cipher: type) *[cipher.nonce_length]u8 { 315 | const start_index = (cipher.key_length * 2) + cipher.nonce_length; 316 | return self.data[start_index..][0..cipher.nonce_length]; 317 | } 318 | 319 | /// Returns the server secret key of a given cipher type 320 | pub fn serverKey(self: *Self, comptime cipher: type) *[cipher.key_length]u8 { 321 | return self.data[0..cipher.key_length]; 322 | } 323 | 324 | /// Returns the client secret key of a given cipher type 325 | pub fn clientKey(self: *Self, comptime cipher: type) *[cipher.key_length]u8 { 326 | return self.data[cipher.key_length..][0..cipher.key_length]; 327 | } 328 | 329 | /// Sets the server IV 330 | pub fn setServerIv(self: *Self, comptime cipher: type, data: [cipher.nonce_length]u8) void { 331 | const start_index = cipher.key_length * 2; 332 | self.data[start_index..][0..cipher.nonce_length].* = data; 333 | } 334 | 335 | /// Sets the client IV 336 | pub fn setClientIv(self: *Self, comptime cipher: type, data: [cipher.nonce_length]u8) void { 337 | const start_index = (cipher.key_length * 2) + cipher.nonce_length; 338 | self.data[start_index..][0..cipher.nonce_length].* = data; 339 | } 340 | 341 | /// Sets the client IV 342 | pub fn setServerKey(self: *Self, comptime cipher: type, data: [cipher.key_length]u8) void { 343 | self.data[0..cipher.key_length].* = data; 344 | } 345 | 346 | /// Sets the client IV 347 | pub fn setClientKey(self: *Self, comptime cipher: type, data: [cipher.key_length]u8) void { 348 | self.data[cipher.key_length..][0..cipher.key_length].* = data; 349 | } 350 | }; 351 | } 352 | -------------------------------------------------------------------------------- /src/crypto/crypto.zig: -------------------------------------------------------------------------------- 1 | test { 2 | _ = @import("ecdsa.zig"); 3 | } 4 | -------------------------------------------------------------------------------- /src/crypto/ecdsa.zig: -------------------------------------------------------------------------------- 1 | //! Elliptic Curve Digital Signature Algorithm (ECDSA) as specified 2 | //! in [FIPS 186-4] (Digital Signature Standard). 3 | //! https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf 4 | //! 5 | //! Also known as secp256r1 under SEC2. 6 | //! https://www.secg.org/sec2-v2.pdf (page 9) 7 | //! 8 | //! NOTE: This implementation uses the P256 Curve and is not interchangable. 9 | 10 | const std = @import("std"); 11 | const crypto = std.crypto; 12 | const mem = std.mem; 13 | 14 | /// The underlying elliptic curve. 15 | pub const Curve = crypto.ecc.P256; 16 | /// The length in bytes for the seed. 17 | pub const seed_length = 32; 18 | 19 | const Scalar = Curve.scalar.Scalar; 20 | const Sha256 = crypto.hash.sha2.Sha256; 21 | 22 | pub const KeyPair = struct { 23 | /// Private key component. 24 | d: Scalar, 25 | /// Public key belonging to this KeyPair 26 | public_key: PublicKey, 27 | 28 | pub const PublicKey = struct { 29 | /// Public key component x-coordinate. 30 | x: Curve.scalar.CompressedScalar, 31 | /// Public key component y-coordinate. 32 | y: Curve.scalar.CompressedScalar, 33 | 34 | /// Verifies if the given public keys are equivalent. 35 | pub fn eql(self: PublicKey, other: PublicKey) bool { 36 | return std.mem.eql(u8, &self.x, &other.x) and 37 | std.mem.eql(u8, &self.y, &other.y); 38 | } 39 | }; 40 | 41 | /// Creates a new key pair using a provided seed or else 42 | /// generates a new seed and uses that instead. 43 | pub fn init(maybe_seed: ?[seed_length]u8) !KeyPair { 44 | const seed = maybe_seed orelse blk: { 45 | var random_seed: [seed_length]u8 = undefined; 46 | crypto.random.bytes(&random_seed); 47 | break :blk random_seed; 48 | }; 49 | 50 | const q = try Curve.basePoint.mul(seed, .Little); 51 | const affine = q.affineCoordinates(); 52 | 53 | return KeyPair{ 54 | .d = try Scalar.fromBytes(seed, .Little), 55 | .public_key = .{ 56 | .x = affine.x.toBytes(.Big), 57 | .y = affine.y.toBytes(.Big), 58 | }, 59 | }; 60 | } 61 | 62 | /// Verifies the given keypairs are equal 63 | pub fn eql(self: KeyPair, other: KeyPair) bool { 64 | return self.d.equivalent(other.d) and self.public_key.eql(other); 65 | } 66 | }; 67 | 68 | /// Represents the signature of a message, that was signed using the private key 69 | /// of ECDSA using the P256-curve. 70 | pub const Signature = struct { 71 | /// The r-component of a signature. 72 | r_serialized: [32]u8, 73 | /// The s-component of a signature. 74 | s_serialized: [32]u8, 75 | 76 | /// Initializes a new Signature from serialized r and s values. 77 | /// 78 | /// User must ensure to input them as Big endian bytes 79 | /// 80 | /// Returns `error.NonCanonical` when the provided parameters are not canonical 81 | pub fn init(r_value: [32]u8, s_value: [32]u8) error{NonCanonical}!Signature { 82 | try Curve.scalar.rejectNonCanonical(r_value, .Big); 83 | try Curve.scalar.rejectNonCanonical(s_value, .Big); 84 | return .{ .r_serialized = r_value, .s_serialized = s_value }; 85 | } 86 | 87 | /// Returns the `r` as a `Scalar` 88 | pub fn r(self: Signature) Scalar { 89 | return Scalar.fromBytes(self.r_serialized, .Big) catch unreachable; 90 | } 91 | 92 | /// Returns the `s` as a `Scalar` 93 | pub fn s(self: Signature) Scalar { 94 | return Scalar.fromBytes(self.s_serialized, .Big) catch unreachable; 95 | } 96 | 97 | /// Initializes a `Signature` from given `Scalar` 'r' & 's' 98 | pub fn fromScalars(r_scalar: Scalar, s_scalar: Scalar) Signature { 99 | return .{ .r_serialized = r_scalar.toBytes(.Big), .s_serialized = s_scalar.toBytes(.Big) }; 100 | } 101 | }; 102 | 103 | /// Possible errors when attempting to sign a message 104 | pub const SignError = error{ 105 | NonCanonical, 106 | IdentityElement, 107 | /// The provided scalar `k` is invalid 108 | InvalidEphemeralScalar, 109 | /// Computed 's' over 'r' and 'z' is 0. 110 | CouldNotCompute, 111 | }; 112 | 113 | /// Signs a message, using the public key of the given `key_pair`. 114 | /// Uses Sha256 to create the digest for the input `msg`. 115 | /// 116 | /// Verification is done by using the inverse of scalar 'k' 117 | pub fn sign(key_pair: KeyPair, k: Scalar, msg: []const u8) SignError!Signature { 118 | var digest: [Sha256.digest_length]u8 = undefined; 119 | Sha256.hash(msg, &digest, .{}); 120 | const z = try Scalar.fromBytes(digest, .Big); 121 | 122 | const k_inverse = k.invert(); 123 | 124 | if (k_inverse.isZero()) { 125 | return error.InvalidEphemeralScalar; 126 | } 127 | 128 | const q = try Curve.basePoint.mul(k.toBytes(.Little), .Little); 129 | const x = q.affineCoordinates().x; 130 | const r = try Scalar.fromBytes(x.toBytes(.Little), .Little); 131 | const s = key_pair.d.mul(r).add(z).mul(k_inverse); 132 | 133 | if (s.isZero()) { 134 | return error.CouldNotCompute; 135 | } 136 | 137 | return Signature.fromScalars(r, s); 138 | } 139 | 140 | /// Possible errors when verifying an error 141 | pub const VerifyError = error{ 142 | NonCanonical, 143 | InvalidEncoding, 144 | IdentityElement, 145 | /// The signature could not be verified using the public key, 146 | /// meaning it's an invalid signature. 147 | InvalidSignature, 148 | }; 149 | 150 | /// Verifies a signature of the hash using a given `public_key` 151 | pub fn verify(public_key: KeyPair.PublicKey, msg: []const u8, signature: Signature) VerifyError!void { 152 | var digest: [Sha256.digest_length]u8 = undefined; 153 | Sha256.hash(msg, &digest, .{}); 154 | const z = try Scalar.fromBytes(digest, .Big); 155 | 156 | const r = signature.r(); 157 | const s = signature.s(); 158 | const s_inv = s.invert(); 159 | const u_1 = z.mul(s_inv); 160 | const u_2 = r.mul(s_inv); 161 | 162 | const lhs = try Curve.basePoint.mulPublic(u_1.toBytes(.Little), .Little); 163 | const rhs = try (try Curve.fromSerializedAffineCoordinates( 164 | public_key.x, 165 | public_key.y, 166 | .Big, 167 | )).mulPublic( 168 | u_2.toBytes(.Little), 169 | .Little, 170 | ); 171 | const x = lhs.add(rhs).affineCoordinates().x; 172 | 173 | if (!(try Scalar.fromBytes(x.toBytes(.Little), .Little)).equivalent(r)) { 174 | return error.InvalidSignature; 175 | } 176 | } 177 | 178 | test "KeyPair - eql" { 179 | var key_pair = try KeyPair.init(null); 180 | var public_key = (&key_pair.public_key).*; // ensure a copy 181 | 182 | try std.testing.expect(key_pair.public_key.eql(public_key)); 183 | } 184 | 185 | test "verify" { 186 | const msg = [_]u8{ 187 | 0xe1, 0x13, 0x0a, 0xf6, 0xa3, 0x8c, 0xcb, 0x41, 0x2a, 0x9c, 0x8d, 0x13, 0xe1, 0x5d, 0xbf, 0xc9, 188 | 0xe6, 0x9a, 0x16, 0x38, 0x5a, 0xf3, 0xc3, 0xf1, 0xe5, 0xda, 0x95, 0x4f, 0xd5, 0xe7, 0xc4, 0x5f, 189 | 0xd7, 0x5e, 0x2b, 0x8c, 0x36, 0x69, 0x92, 0x28, 0xe9, 0x28, 0x40, 0xc0, 0x56, 0x2f, 0xbf, 0x37, 190 | 0x72, 0xf0, 0x7e, 0x17, 0xf1, 0xad, 0xd5, 0x65, 0x88, 0xdd, 0x45, 0xf7, 0x45, 0x0e, 0x12, 0x17, 191 | 0xad, 0x23, 0x99, 0x22, 0xdd, 0x9c, 0x32, 0x69, 0x5d, 0xc7, 0x1f, 0xf2, 0x42, 0x4c, 0xa0, 0xde, 192 | 0xc1, 0x32, 0x1a, 0xa4, 0x70, 0x64, 0xa0, 0x44, 0xb7, 0xfe, 0x3c, 0x2b, 0x97, 0xd0, 0x3c, 0xe4, 193 | 0x70, 0xa5, 0x92, 0x30, 0x4c, 0x5e, 0xf2, 0x1e, 0xed, 0x9f, 0x93, 0xda, 0x56, 0xbb, 0x23, 0x2d, 194 | 0x1e, 0xeb, 0x00, 0x35, 0xf9, 0xbf, 0x0d, 0xfa, 0xfd, 0xcc, 0x46, 0x06, 0x27, 0x2b, 0x20, 0xa3, 195 | }; 196 | 197 | const Qx = [_]u8{ 198 | 0xe4, 0x24, 0xdc, 0x61, 0xd4, 0xbb, 0x3c, 0xb7, 0xef, 0x43, 0x44, 0xa7, 0xf8, 0x95, 0x7a, 0x0c, 199 | 0x51, 0x34, 0xe1, 0x6f, 0x7a, 0x67, 0xc0, 0x74, 0xf8, 0x2e, 0x6e, 0x12, 0xf4, 0x9a, 0xbf, 0x3c, 200 | }; 201 | const Qy = [_]u8{ 202 | 0x97, 0x0e, 0xed, 0x7a, 0xa2, 0xbc, 0x48, 0x65, 0x15, 0x45, 0x94, 0x9d, 0xe1, 0xdd, 0xda, 0xf0, 203 | 0x12, 0x7e, 0x59, 0x65, 0xac, 0x85, 0xd1, 0x24, 0x3d, 0x6f, 0x60, 0xe7, 0xdf, 0xae, 0xe9, 0x27, 204 | }; 205 | 206 | const R = [_]u8{ 207 | 0xbf, 0x96, 0xb9, 0x9a, 0xa4, 0x9c, 0x70, 0x5c, 0x91, 0x0b, 0xe3, 0x31, 0x42, 0x01, 0x7c, 0x64, 208 | 0x2f, 0xf5, 0x40, 0xc7, 0x63, 0x49, 0xb9, 0xda, 0xb7, 0x2f, 0x98, 0x1f, 0xd9, 0x34, 0x7f, 0x4f, 209 | }; 210 | const S = [_]u8{ 211 | 0x17, 0xc5, 0x50, 0x95, 0x81, 0x90, 0x89, 0xc2, 0xe0, 0x3b, 0x9c, 0xd4, 0x15, 0xab, 0xdf, 0x12, 212 | 0x44, 0x4e, 0x32, 0x30, 0x75, 0xd9, 0x8f, 0x31, 0x92, 0x0b, 0x9e, 0x0f, 0x57, 0xec, 0x87, 0x1c, 213 | }; 214 | 215 | try verify( 216 | .{ .x = Qx, .y = Qy }, 217 | &msg, 218 | .{ .r_serialized = R, .s_serialized = S }, 219 | ); 220 | } 221 | 222 | test "Sign" { 223 | const msg = [_]u8{ 224 | 0x59, 0x05, 0x23, 0x88, 0x77, 0xc7, 0x74, 0x21, 0xf7, 0x3e, 0x43, 0xee, 0x3d, 0xa6, 0xf2, 0xd9, 225 | 0xe2, 0xcc, 0xad, 0x5f, 0xc9, 0x42, 0xdc, 0xec, 0x0c, 0xbd, 0x25, 0x48, 0x29, 0x35, 0xfa, 0xaf, 226 | 0x41, 0x69, 0x83, 0xfe, 0x16, 0x5b, 0x1a, 0x04, 0x5e, 0xe2, 0xbc, 0xd2, 0xe6, 0xdc, 0xa3, 0xbd, 227 | 0xf4, 0x6c, 0x43, 0x10, 0xa7, 0x46, 0x1f, 0x9a, 0x37, 0x96, 0x0c, 0xa6, 0x72, 0xd3, 0xfe, 0xb5, 228 | 0x47, 0x3e, 0x25, 0x36, 0x05, 0xfb, 0x1d, 0xdf, 0xd2, 0x80, 0x65, 0xb5, 0x3c, 0xb5, 0x85, 0x8a, 229 | 0x8a, 0xd2, 0x81, 0x75, 0xbf, 0x9b, 0xd3, 0x86, 0xa5, 0xe4, 0x71, 0xea, 0x7a, 0x65, 0xc1, 0x7c, 230 | 0xc9, 0x34, 0xa9, 0xd7, 0x91, 0xe9, 0x14, 0x91, 0xeb, 0x37, 0x54, 0xd0, 0x37, 0x99, 0x79, 0x0f, 231 | 0xe2, 0xd3, 0x08, 0xd1, 0x61, 0x46, 0xd5, 0xc9, 0xb0, 0xd0, 0xde, 0xbd, 0x97, 0xd7, 0x9c, 0xe8, 232 | }; 233 | 234 | const d = [_]u8{ 235 | 0x51, 0x9b, 0x42, 0x3d, 0x71, 0x5f, 0x8b, 0x58, 0x1f, 0x4f, 0xa8, 0xee, 0x59, 0xf4, 0x77, 0x1a, 236 | 0x5b, 0x44, 0xc8, 0x13, 0x0b, 0x4e, 0x3e, 0xac, 0xca, 0x54, 0xa5, 0x6d, 0xda, 0x72, 0xb4, 0x64, 237 | }; 238 | 239 | const Qx = [_]u8{ 240 | 0x1c, 0xcb, 0xe9, 0x1c, 0x07, 0x5f, 0xc7, 0xf4, 0xf0, 0x33, 0xbf, 0xa2, 0x48, 0xdb, 0x8f, 0xcc, 241 | 0xd3, 0x56, 0x5d, 0xe9, 0x4b, 0xbf, 0xb1, 0x2f, 0x3c, 0x59, 0xff, 0x46, 0xc2, 0x71, 0xbf, 0x83, 242 | }; 243 | const Qy = [_]u8{ 244 | 0xce, 0x40, 0x14, 0xc6, 0x88, 0x11, 0xf9, 0xa2, 0x1a, 0x1f, 0xdb, 0x2c, 0x0e, 0x61, 0x13, 0xe0, 245 | 0x6d, 0xb7, 0xca, 0x93, 0xb7, 0x40, 0x4e, 0x78, 0xdc, 0x7c, 0xcd, 0x5c, 0xa8, 0x9a, 0x4c, 0xa9, 246 | }; 247 | const k = [_]u8{ 248 | 0x94, 0xa1, 0xbb, 0xb1, 0x4b, 0x90, 0x6a, 0x61, 0xa2, 0x80, 0xf2, 0x45, 0xf9, 0xe9, 0x3c, 0x7f, 249 | 0x3b, 0x4a, 0x62, 0x47, 0x82, 0x4f, 0x5d, 0x33, 0xb9, 0x67, 0x07, 0x87, 0x64, 0x2a, 0x68, 0xde, 250 | }; 251 | const R = [_]u8{ 252 | 0xf3, 0xac, 0x80, 0x61, 0xb5, 0x14, 0x79, 0x5b, 0x88, 0x43, 0xe3, 0xd6, 0x62, 0x95, 0x27, 0xed, 253 | 0x2a, 0xfd, 0x6b, 0x1f, 0x6a, 0x55, 0x5a, 0x7a, 0xca, 0xbb, 0x5e, 0x6f, 0x79, 0xc8, 0xc2, 0xac, 254 | }; 255 | const S = [_]u8{ 256 | 0x8b, 0xf7, 0x78, 0x19, 0xca, 0x05, 0xa6, 0xb2, 0x78, 0x6c, 0x76, 0x26, 0x2b, 0xf7, 0x37, 0x1c, 257 | 0xef, 0x97, 0xb2, 0x18, 0xe9, 0x6f, 0x17, 0x5a, 0x3c, 0xcd, 0xda, 0x2a, 0xcc, 0x05, 0x89, 0x03, 258 | }; 259 | 260 | const key_pair: KeyPair = .{ 261 | .d = try Scalar.fromBytes(d, .Big), 262 | .public_key = .{ .x = Qx, .y = Qy }, 263 | }; 264 | 265 | const k_scalar = try Scalar.fromBytes(k, .Big); 266 | const signature = try sign(key_pair, k_scalar, &msg); 267 | 268 | try std.testing.expectEqualSlices(u8, &R, &signature.r_serialized); 269 | try std.testing.expectEqualSlices(u8, &S, &signature.s_serialized); 270 | } 271 | -------------------------------------------------------------------------------- /src/encryption.zig: -------------------------------------------------------------------------------- 1 | //! Contains a reader and writer that will encrypt 2 | //! or decrypt tls records before sending to the peer 3 | //! or providing it to the caller. Meaning callers will get 4 | //! access to actual data from the peer, without having to deal with 5 | //! decryption based on what was agreed upon during handshake. 6 | 7 | const std = @import("std"); 8 | const tls = @import("tls.zig"); 9 | const ciphers = @import("ciphers.zig"); 10 | const io = std.io; 11 | const math = std.math; 12 | const assert = std.debug.assert; 13 | 14 | /// Initializes a new generic `EncryptedReadWriter` using a given reader and writer and 15 | /// the key storage data that belongs to the given cipher suite. 16 | pub fn encryptedReadWriter( 17 | /// Internal reader that is preferably directly from the source. 18 | reader: anytype, 19 | /// Internal writer that is preferably directly from the source. 20 | writer: anytype, 21 | /// The cipher suite for which we want to use to encrypt and decrypt the data with. 22 | cipher_suite: tls.CipherSuite, 23 | /// The storage of all key data that will be used by the cipher suite to encrypt 24 | /// and decrypt the data. 25 | key_storage: ciphers.KeyStorage, 26 | ) EncryptedReadWriter(@TypeOf(reader), @TypeOf(writer)) { 27 | return EncryptedReadWriter(@TypeOf(reader), @TypeOf(writer)).init( 28 | reader, 29 | writer, 30 | key_storage, 31 | cipher_suite, 32 | ); 33 | } 34 | 35 | /// A generic wrapper over a given `ReaderType` and `WriterType`. 36 | /// This will decrypt any data is receives before providing to the caller, 37 | /// as well as encrypt data before writing it to the internal writer of `WriterType`. 38 | pub fn EncryptedReadWriter(comptime ReaderType: type, comptime WriterType: type) type { 39 | return struct { 40 | const Self = @This(); 41 | 42 | /// Writer we write to after encrypting the user's data. 43 | inner_writer: WriterType, 44 | /// Reader we read from containing the encrypted data, and decrypt 45 | /// before passing the data to the user. 46 | inner_reader: ReaderType, 47 | /// Cipher used to encrypt and decrypt our data. 48 | cipher: tls.CipherSuite, 49 | /// The state of the reader, to ensure we decrypt data correctly as the user 50 | /// may provide a buffer that is not large enough to read the entire payload message, 51 | /// before we can decrypt it. 52 | reader_state: State = .start, 53 | /// Storage of our server and client keys, accessible in a generic way where a given 54 | /// cipher type is used to construct the lengths correctly. 55 | key_storage: ciphers.KeyStorage, 56 | /// Sequences of encrypted data we have received from the client. This will be xor'd 57 | /// with the client nonce when decrypting the data. 58 | client_seq: u64 = 0, 59 | /// Sequences of data we have encrypted and set to the client. This will be xor'd 60 | /// with the server nonce when encrypting the data. 61 | server_seq: u64 = 1, 62 | 63 | const State = union(enum) { 64 | start: void, 65 | reading: struct { 66 | length: u16, 67 | index: usize, 68 | context: Context(&ciphers.supported), 69 | }, 70 | }; 71 | 72 | /// Represents the type of the reader that will be returned when `reader()` is called. 73 | pub const Reader = io.Reader(*Self, ReadError, read); 74 | /// Represents the type of the writer that will be returned when `writer()` is called. 75 | pub const Writer = io.Writer(*Self, WriteError, write); 76 | 77 | /// Error set containing the possible errors when performing reads. 78 | pub const ReadError = ReaderType.Error || 79 | WriterType.Error || tls.Alert.Error || 80 | error{ AuthenticationFailed, EndOfStream }; 81 | /// Error set containing the possible errors when performing writes. 82 | pub const WriteError = WriterType.Error; 83 | /// Merged error set of both `ReadError` and `WriteError` 84 | pub const Error = ReadError || WriteError; 85 | 86 | /// Initializes a new `EncryptedReadWriter` generic, from a given 87 | /// `ReaderType` and `WriterType`. This construct allows a user to read decrypted 88 | /// data and send encrypted data. 89 | /// The `KeyStorage` must be filled with all key data to ensure the keys can be accessed 90 | /// when data is being decrypted or encrypted. 91 | pub fn init(parent_reader: ReaderType, parent_writer: WriterType, key_storage: ciphers.KeyStorage, cipher: tls.CipherSuite) Self { 92 | return .{ 93 | .inner_writer = parent_writer, 94 | .inner_reader = parent_reader, 95 | .key_storage = key_storage, 96 | .cipher = cipher, 97 | }; 98 | } 99 | 100 | /// Returns a generic `Reader` 101 | pub fn reader(self: *Self) Reader { 102 | return .{ .context = self }; 103 | } 104 | 105 | /// Returns a generic `Writer` 106 | pub fn writer(self: *Self) Writer { 107 | return .{ .context = self }; 108 | } 109 | 110 | /// Returns a pointer to the context of the currently used cipher. 111 | /// 112 | /// User must ensure `reader_state` is `reading`. 113 | /// User must ensure given `suite` is a supported cipher suite as found 114 | /// in `tls.supported`. 115 | inline fn context(self: *Self, comptime suite: tls.CipherSuite) *ciphers.TypeFromSuite(suite) { 116 | return inline for (ciphers.supported) |cipher| { 117 | if (cipher.suite == suite) { 118 | break &@field(self.reader_state.reading.context, @tagName(suite)); 119 | } 120 | } else unreachable; // User must provide supported cipher suite 121 | } 122 | 123 | /// Returns the index of the current context as a mutable pointer. 124 | /// 125 | /// NOTE: User must ensure current state is `reader_state` 126 | inline fn index(self: *Self) *usize { 127 | return &self.reader_state.reading.index; 128 | } 129 | 130 | /// Returns the length of the current record. 131 | /// 132 | /// NOTE: User must ensure current state is `reader_state` 133 | inline fn recordLength(self: Self) u16 { 134 | return self.reader_state.reading.length; 135 | } 136 | 137 | /// performs a single read, attempts to decrypt the data if required 138 | /// and returns the length that was read from the connection. 139 | pub fn read(self: *Self, buf: []u8) ReadError!usize { 140 | if (self.reader_state == .start) { 141 | const record_header = try tls.Record.readFrom(self.inner_reader); 142 | std.debug.print("Record: {}\n", .{record_header}); 143 | 144 | if (record_header.record_type != .application_data and 145 | record_header.record_type != .alert) 146 | { 147 | if (record_header.record_type == .change_cipher_spec) { 148 | const b = try self.inner_reader.readByte(); 149 | assert(b == 0x01); 150 | return self.read(buf); 151 | } 152 | const alert = tls.Alert.init(.unexpected_message, .fatal); 153 | try alert.writeTo(self.writer()); 154 | return error.UnexpectedMessage; 155 | } 156 | 157 | inline for (ciphers.supported) |cipher| { 158 | if (cipher.suite == self.cipher) { 159 | self.reader_state = .{ 160 | .reading = .{ 161 | .length = record_header.len - 16, // minus auth tag 162 | .index = 0, 163 | .context = @unionInit( 164 | Context(&ciphers.supported), 165 | @tagName(cipher.suite), 166 | cipher.init( 167 | &self.key_storage, 168 | self.client_seq, 169 | &record_header.toBytes(), 170 | ), 171 | ), 172 | }, 173 | }; 174 | } 175 | } 176 | 177 | if (record_header.record_type == .alert) { 178 | var encrypted: [2]u8 = undefined; 179 | var auth_tag: [16]u8 = undefined; 180 | try self.inner_reader.readNoEof(&encrypted); 181 | try self.inner_reader.readNoEof(&auth_tag); 182 | 183 | var alert_buf: [2]u8 = undefined; 184 | inline for (ciphers.supported) |cipher| { 185 | if (cipher.suite == self.cipher) { 186 | cipher.decryptPartial( 187 | self.context(cipher.suite), 188 | &alert_buf, 189 | &encrypted, 190 | self.index(), 191 | ); 192 | 193 | assert(self.index().* == self.recordLength()); 194 | try cipher.verify( 195 | self.context(cipher.suite), 196 | auth_tag, 197 | self.recordLength(), 198 | ); 199 | } 200 | } 201 | self.reader_state = .start; 202 | self.client_seq += 1; 203 | 204 | const alert = tls.Alert.fromBytes(encrypted); 205 | if (alert.tag == .close_notify) { 206 | return error.EndOfStream; 207 | } 208 | return alert.toError(); 209 | } 210 | 211 | // decrypt in max sizes of 1024 bytes 212 | // TODO: Check if we increase this. 213 | // The reason we do this is because we need double buffers, 214 | // one to store encrypted data, and the user provided buffer 215 | // to write the decrypted data to. 216 | // This will never read more than record length. 217 | const max_length = math.min(math.min(self.recordLength(), 1024), buf.len); 218 | var encrypted: [1024]u8 = undefined; 219 | 220 | const read_len = try self.inner_reader.read(encrypted[0..max_length]); 221 | inline for (ciphers.supported) |cipher| { 222 | if (cipher.suite == self.cipher) { 223 | cipher.decryptPartial( 224 | self.context(cipher.suite), 225 | buf[0..read_len], 226 | encrypted[0..read_len], 227 | self.index(), 228 | ); 229 | 230 | // If we read all data, verify its authentication 231 | if (self.index().* == self.recordLength()) { 232 | const auth_tag = try self.readAuthTag(); 233 | try cipher.verify( 234 | self.context(cipher.suite), 235 | auth_tag, 236 | self.recordLength(), 237 | ); 238 | 239 | self.client_seq += 1; 240 | self.reader_state = .start; 241 | } 242 | } 243 | } 244 | return read_len; 245 | } 246 | 247 | // state is `reading` 248 | const state = &self.reader_state.reading; 249 | const max_len = math.min(math.min(buf.len, 1024), state.length - state.index); 250 | 251 | var encrypted: [1024]u8 = undefined; 252 | const read_len = try self.inner_reader.read(encrypted[0..max_len]); 253 | 254 | inline for (ciphers.supported) |cipher| { 255 | if (cipher.suite == self.cipher) { 256 | cipher.decryptPartial( 257 | self.context(cipher.suite), 258 | buf[0..read_len], 259 | encrypted[0..read_len], 260 | &state.index, 261 | ); 262 | 263 | if (state.index == state.length) { 264 | const auth_tag = try self.readAuthTag(); 265 | try cipher.verify( 266 | self.context(cipher.suite), 267 | auth_tag, 268 | state.length, 269 | ); 270 | 271 | self.client_seq += 1; 272 | self.reader_state = .start; 273 | } 274 | } 275 | } 276 | return read_len; 277 | } 278 | 279 | /// Writes encrypted data to the underlying writer. 280 | /// 281 | /// NOTE: For each write, it will create a new application data record 282 | /// with its content encrypted using the current cipher. 283 | /// Currently, it's limited to writing 4096 bytes at a time. 284 | /// To save writes, it's recommended to wrap the writer into a BufferedWriter 285 | /// 286 | /// TODO: In the future, perhaps we can keep writing data until all 287 | /// contents of `data` has been encrypted. 288 | /// This will save us many writes. 289 | pub fn write(self: *Self, data: []const u8) WriteError!usize { 290 | if (data.len == 0) return 0; 291 | std.debug.assert(data.len < 1 << 14); // max record size is 2^14-1 292 | 293 | var buf: [4096]u8 = undefined; 294 | var tag: [16]u8 = undefined; 295 | const max_len = math.min(4096, data.len); 296 | 297 | const record = tls.Record.init(.application_data, @intCast(u16, max_len + 16)); // tag must be counted as well 298 | inline for (ciphers.supported) |cipher| { 299 | if (cipher.suite == self.cipher) { 300 | cipher.encrypt( 301 | &self.key_storage, 302 | buf[0..max_len], 303 | data[0..max_len], 304 | &record.toBytes(), 305 | self.server_seq, 306 | &tag, 307 | ); 308 | } 309 | } 310 | self.server_seq += 1; 311 | 312 | try record.writeTo(self.inner_writer); 313 | try self.inner_writer.writeAll(buf[0..max_len]); 314 | try self.inner_writer.writeAll(&tag); 315 | return max_len; 316 | } 317 | 318 | /// Reads an authentication tag from the inner reader. 319 | /// Returns `EndOfStream` if stream is not long enough. 320 | fn readAuthTag(self: *Self) ReadError![16]u8 { 321 | var buf: [16]u8 = undefined; 322 | try self.inner_reader.readNoEof(&buf); 323 | return buf; 324 | } 325 | }; 326 | } 327 | 328 | /// Creates a union where each tag is a type corresponding 329 | /// to a given list of ciphers. 330 | fn Context(comptime cipher_suites: []const type) type { 331 | var fields: [cipher_suites.len]std.builtin.TypeInfo.UnionField = undefined; 332 | for (cipher_suites) |cipher, index| { 333 | fields[index] = .{ 334 | .name = @tagName(cipher.suite), 335 | .field_type = cipher.Context, 336 | .alignment = @alignOf(cipher.Context), 337 | }; 338 | } 339 | 340 | return @Type(.{ 341 | .Union = .{ 342 | .layout = .Extern, 343 | .tag_type = null, 344 | .fields = &fields, 345 | .decls = &.{}, 346 | }, 347 | }); 348 | } 349 | -------------------------------------------------------------------------------- /src/feilich.zig: -------------------------------------------------------------------------------- 1 | pub const Server = @import("Server.zig"); 2 | 3 | test { 4 | _ = @import("handshake.zig"); 5 | _ = @import("Server.zig"); 6 | _ = @import("crypto/crypto.zig"); 7 | _ = @import("ciphers.zig"); 8 | _ = @import("cert/cert.zig"); 9 | } 10 | -------------------------------------------------------------------------------- /src/handshake.zig: -------------------------------------------------------------------------------- 1 | //! Contains the data and logic to perform 2 | //! a TLS 1.3 handshake 3 | //! This does however not contain the logic to generate 4 | //! and verify any of the handshake keys required to sign and 5 | //! verify the messages. 6 | 7 | const std = @import("std"); 8 | const tls = @import("tls.zig"); 9 | const ciphers = @import("ciphers.zig"); 10 | const mem = std.mem; 11 | const crypto = std.crypto; 12 | const Sha256 = crypto.hash.sha2.Sha256; 13 | const HmacSha256 = crypto.auth.hmac.sha2.HmacSha256; 14 | const ecdsa = @import("crypto/ecdsa.zig"); 15 | 16 | /// Represents the possible handshake types 17 | pub const HandshakeType = enum(u8) { 18 | client_hello = 1, 19 | server_hello = 2, 20 | new_session_ticket = 4, 21 | end_of_early_data = 5, 22 | encrypted_extensions = 8, 23 | certificate = 11, 24 | certificate_request = 13, 25 | certificate_verify = 15, 26 | finished = 20, 27 | key_update = 24, 28 | message_hash = 254, 29 | 30 | pub fn int(self: HandshakeType) u8 { 31 | return @enumToInt(self); 32 | } 33 | }; 34 | 35 | /// Handshake-specific header record type 36 | pub const HandshakeHeader = struct { 37 | handshake_type: HandshakeType, 38 | length: u24, 39 | 40 | /// Converts the header into bytes 41 | pub fn toBytes(self: HandshakeHeader) [4]u8 { 42 | var buf: [4]u8 = undefined; 43 | buf[0] = self.handshake_type.int(); 44 | mem.writeIntBig(16, buf[1..4], self.length); 45 | return buf; 46 | } 47 | 48 | /// Constructs a HandshakeHeader from an array of bytes 49 | pub fn fromBytes(bytes: [4]u8) HandshakeHeader { 50 | return .{ 51 | .handshake_type = @intToEnum(HandshakeType, bytes[0]), 52 | .length = mem.readIntBig(u24, bytes[1..4]), 53 | }; 54 | } 55 | }; 56 | 57 | pub const ReadError = error{ 58 | /// Reached end of stream, perhaps the client disconnected. 59 | EndOfStream, 60 | }; 61 | 62 | /// Builds an error type representing both a `HandshakeReader`'s `Error` 63 | /// and a `HandshakeWriter`'s `Error` depending on a given `reader` and `writer`. 64 | pub fn ReadWriteError(comptime ReaderType: type, comptime WriterType: type) type { 65 | const ReaderError = HandshakeReader(ReaderType).Error; 66 | const WriterError = HandshakeWriter(WriterType).Error; 67 | return ReaderError || WriterError; 68 | } 69 | 70 | /// Initializes a new reader that decodes and performs a handshake 71 | pub fn handshakeReader(reader: anytype, hasher: *Sha256) HandshakeReader(@TypeOf(reader)) { 72 | return HandshakeReader(@TypeOf(reader)).init(reader, hasher); 73 | } 74 | 75 | /// Generic handshake reader that will perform a handshake and decode all 76 | /// handshake types 77 | pub fn HandshakeReader(comptime ReaderType: type) type { 78 | return struct { 79 | const Self = @This(); 80 | /// HashReader that will read from the stream 81 | /// and then hash its contents. 82 | reader: HashReader(ReaderType), 83 | 84 | pub const Error = ReadError || ReaderType.Error; 85 | 86 | const ClientHelloResult = struct { 87 | legacy_version: u16, 88 | session_id: [32]u8, 89 | random: [32]u8, 90 | cipher_suites: []const tls.CipherSuite, 91 | /// Represents the extensions as raw bytes 92 | /// Utilize ExtensionIterator to iterate over. 93 | extensions: []u8, 94 | }; 95 | 96 | const Result = union(enum) { 97 | client_hello: ClientHelloResult, 98 | }; 99 | 100 | /// Initializes a new instance of `HandshakeReader` of a given reader that must be of 101 | /// `ReaderType`. 102 | pub fn init(reader: ReaderType, hasher: *Sha256) Self { 103 | return .{ .reader = HashReader(ReaderType).init(reader, hasher) }; 104 | } 105 | 106 | /// Starts reading from the reader and will try to perform a handshake. 107 | pub fn decode(self: *Self) Error!Result { 108 | var reader = self.reader.reader(); 109 | const handshake_type = try reader.readByte(); 110 | const remaining_length = try reader.readIntBig(u24); 111 | 112 | switch (@intToEnum(HandshakeType, handshake_type)) { 113 | .client_hello => return Result{ .client_hello = try self.decodeClientHello(remaining_length) }, 114 | else => @panic("TODO"), 115 | } 116 | } 117 | 118 | /// Decodes a 'client hello' message received from the client. 119 | /// This means the Record header and handshake header have already been read 120 | /// and the first data to read will be the protocol version. 121 | pub fn decodeClientHello(self: *Self, message_length: usize) Error!ClientHelloResult { 122 | var result: ClientHelloResult = undefined; 123 | 124 | // maximum length of an entire record (record header + message) 125 | var buf: [1 << 14]u8 = undefined; 126 | try self.reader.reader().readNoEof(buf[0..message_length]); 127 | const content = buf[0..message_length]; 128 | result.legacy_version = mem.readIntBig(u16, content[0..2]); 129 | // current index into `contents` 130 | var index: usize = 2; 131 | 132 | std.mem.copy(u8, &result.random, content[index..][0..32]); 133 | index += 32; // random 134 | 135 | // TLS version 1.3 ignores session_id 136 | // but we will return it to echo it in the server hello. 137 | const session_len = content[index]; 138 | index += 1; 139 | result.session_id = [_]u8{0} ** 32; 140 | if (session_len != 0) { 141 | std.mem.copy(u8, &result.session_id, content[index..][0..session_len]); 142 | index += session_len; 143 | } 144 | 145 | const cipher_suites_len = mem.readIntBig(u16, content[index..][0..2]); 146 | index += 2; 147 | 148 | const cipher_suites = blk: { 149 | const cipher_bytes = content[index..][0..cipher_suites_len]; 150 | index += cipher_suites_len; 151 | break :blk tls.bytesToTypedSlice(tls.CipherSuite, cipher_bytes); 152 | }; 153 | result.cipher_suites = cipher_suites; 154 | 155 | // TLS version 1.3 ignores compression as well 156 | const compression_methods_len = content[index]; 157 | index += compression_methods_len + 1; 158 | 159 | const extensions_length = mem.readIntBig(u16, content[index..][0..2]); 160 | index += 2; 161 | result.extensions = content[index..][0..extensions_length]; 162 | index += extensions_length; 163 | 164 | std.debug.assert(index == message_length); 165 | 166 | return result; 167 | } 168 | }; 169 | } 170 | 171 | /// Initializes a new `HandshakeWriter`, deducing the type of a given 172 | /// instance of a `writer`. The handshake writer will construct all 173 | /// required messages for a succesful handshake. 174 | pub fn handshakeWriter(writer: anytype, hasher: *Sha256) HandshakeWriter(@TypeOf(writer)) { 175 | return HandshakeWriter(@TypeOf(writer)).init(writer, hasher); 176 | } 177 | 178 | /// Creates a new HandshakeWriter using a given writer type. 179 | /// The handshakewriter builds all messages required to construct a succesful handshake. 180 | pub fn HandshakeWriter(comptime WriterType: type) type { 181 | return struct { 182 | const Self = @This(); 183 | 184 | writer: WriterType, 185 | hasher: *Sha256, 186 | 187 | const Error = WriterType.Error; 188 | 189 | /// Initializes a new `HandshakeWriter` by wrapping the given `writer` into 190 | /// a `HashWriter` and setting up the hasher. 191 | pub fn init(writer: WriterType, hasher: *Sha256) Self { 192 | return .{ .writer = writer, .hasher = hasher }; 193 | } 194 | 195 | /// Constructs and sends a 'Server Hello' message to the client. 196 | /// This must be called, after a succesful 'Client Hello' message was received. 197 | pub fn serverHello( 198 | self: *Self, 199 | /// Determines if the server hello is a regular server hello, 200 | /// or a Hello Retry Request. As the messages share the same format, 201 | /// they're combined for simplicity, but the random that is generated 202 | /// will be different. 203 | kind: enum { server_hello, retry_request }, 204 | // Legacy session_id to emit. 205 | // In TLS 1.3 we can simply echo client's session id. 206 | session_id: [32]u8, 207 | // The cipher_suite we support as a server and that was provided 208 | // by the client. 209 | cipher_suite: tls.CipherSuite, 210 | /// The `KeyShare` that was generated, based 211 | /// on the client's Key Share. 212 | key_share: tls.KeyShare, 213 | /// Randomly generated bytes. 214 | /// Bytes will be overwritten if `kind` is `retry_request`. 215 | random: [32]u8, 216 | ) Error!void { 217 | var builder = RecordBuilder.init(); 218 | builder.startMessage(.server_hello); 219 | const writer = builder.writer(); 220 | 221 | // Means TLS 1.2, this is legacy and actual version is sent through extensions 222 | writer.writeIntBig(u16, 0x303) catch unreachable; 223 | 224 | const server_random = switch (kind) { 225 | .server_hello => random, 226 | .retry_request => blk: { 227 | // When sending a hello retry request, the random must always be the 228 | // SHA-256 of "HelloRetryRequest" 229 | var buf: [32]u8 = undefined; 230 | Sha256.hash("HelloRetryRequest", &buf, .{}); 231 | break :blk buf; 232 | }, 233 | }; 234 | 235 | writer.writeAll(&server_random) catch unreachable; 236 | 237 | // session_id is legacy and no longer used. In TLS 1.3 we 238 | // can just 'echo' client's session id. 239 | writer.writeByte(0x20) catch unreachable; // length of session id (32); 240 | writer.writeAll(&session_id) catch unreachable; 241 | 242 | // cipher suite 243 | writer.writeIntBig(u16, cipher_suite.int()) catch unreachable; 244 | 245 | // Compression methods, which is no longer allowed for TLS 1.3 so assign "null" 246 | writer.writeByte(0x00) catch unreachable; 247 | 248 | // write the extension length key_share's length + 6 for supported versions 249 | writer.writeIntBig(u16, key_share.byteLen() + 6) catch unreachable; 250 | 251 | // Extension -- Key Share 252 | // TODO: When sending a retry, we should only send the named_group we want. 253 | try key_share.writeTo(writer); 254 | 255 | // Extension -- Supported versions 256 | const supported_versions = &[_]u8{ 257 | // Extension type 258 | 0x0, 0x2b, 259 | // byte length remaining (2) 260 | 0x0, 0x02, 261 | // actual version (TLS 1.3) 262 | 0x03, 0x04, 263 | }; 264 | writer.writeAll(supported_versions) catch unreachable; 265 | 266 | builder.endMessage(self.hasher); 267 | try builder.writeRecord(.handshake, self.writer); 268 | } 269 | 270 | /// Sends the remaining messages required to finish the handshake. 271 | /// Wraps all messages and encrypts them, using the provided Cipher. 272 | pub fn handshakeFinish( 273 | self: *Self, 274 | comptime Cipher: type, 275 | key_storage: *ciphers.KeyStorage, 276 | certificate: []const u8, 277 | secret: [32]u8, 278 | sequence: u64, 279 | ) !void { 280 | var builder = RecordBuilder.init(); 281 | const builder_writer = builder.writer(); 282 | // encrypted extensions 283 | builder.startMessage(.encrypted_extensions); 284 | builder_writer.writeAll(&.{ 0x00, 0x00 }) catch unreachable; 285 | builder.endMessage(self.hasher); 286 | 287 | // Certificate 288 | builder.startMessage(.certificate); 289 | builder_writer.writeByte(0x00) catch unreachable; // request context 290 | // Full length of all certificates 291 | // For now, only support a single one 292 | // 5 extra bytes as we write the length of the first 293 | // certificate once more, and the certificate extensions. 294 | builder_writer.writeIntBig(u24, @intCast(u24, certificate.len + 5)) catch unreachable; 295 | builder_writer.writeIntBig(u24, @intCast(u24, certificate.len)) catch unreachable; 296 | builder_writer.writeAll(certificate) catch unreachable; 297 | builder_writer.writeAll(&.{ 0x00, 0x00 }) catch unreachable; // no extensions 298 | builder.endMessage(self.hasher); 299 | 300 | // Certificate verify 301 | builder.startMessage(.certificate_verify); 302 | builder_writer.writeIntBig(u16, Cipher.suite.int()) catch unreachable; 303 | builder_writer.writeIntBig(u16, 64) catch unreachable; // signature length 304 | 305 | // the message we will sign, containing 306 | // 0x20 (x64), 34 bytes for "TLS 1.3, server CertificateVerify", 307 | // and 32 bytes for the hash of the handshake 308 | var sig_msg = [_]u8{0} ** (64 + 34 + 32); 309 | sig_msg[0..64].* = [_]u8{0x20} ** 64; 310 | std.mem.copy(u8, sig_msg[64..], "TLS 1.3, server CertificateVerify"); 311 | { 312 | var hash_copy = self.hasher; 313 | hash_copy.final(sig_msg[64 + 34 ..][0..32]); 314 | } 315 | // TODO: Get public key from certificate so we can 316 | // sign our message and input the bytes 317 | 318 | builder.endMessage(self.hasher); 319 | 320 | // handshake finished type 321 | builder.startMessage(.finished); 322 | const verify_data: [32]u8 = blk: { 323 | const finished_key = tls.hkdfExpandLabel(secret, "finished", "", 32); 324 | // copy hasher 325 | const finished_hash: [32]u8 = hsh: { 326 | var temp_hasher = self.hasher.*; 327 | var buf: [32]u8 = undefined; 328 | // add the data between server hello and cert verify 329 | // Will not include the `finished` message. 330 | temp_hasher.final(&buf); 331 | break :hsh buf; 332 | }; 333 | 334 | var out: [32]u8 = undefined; 335 | HmacSha256.create(&out, &finished_key, &finished_hash); 336 | break :blk out; 337 | }; 338 | builder_writer.writeAll(&verify_data) catch unreachable; 339 | builder.endMessage(self.hasher); 340 | 341 | try builder.writeRecordEncrypted( 342 | .handshake, 343 | self.writer, 344 | Cipher, 345 | key_storage, 346 | sequence, 347 | ); 348 | } 349 | }; 350 | } 351 | 352 | /// Constructs a new record to be sent to the client 353 | /// for the handshake process. Contains an internal 354 | /// buffer so we can calculate the total length required 355 | /// to parse the entire content. 356 | const RecordBuilder = struct { 357 | buffer: [1 << 14]u8, 358 | index: u14, 359 | state: union(enum) { 360 | start: u14, 361 | end: void, 362 | }, 363 | 364 | /// No errors can occur when writing to the internal 365 | /// buffer as it's safety checked during release-safe and debug modes. 366 | /// 367 | /// Any panics are caused by a developer of the library, not by user-code. 368 | const Error = error{}; 369 | 370 | const CipherData = struct { 371 | cipher: type, 372 | key_storage: *ciphers.KeyStorage, 373 | sequence: u64, 374 | }; 375 | 376 | pub fn init() RecordBuilder { 377 | return .{ .buffer = undefined, .index = 0, .state = .end }; 378 | } 379 | 380 | /// Initializes a new Handshake message of type `HandshakeType`. 381 | /// It sets an inner state and saves the location where the result length 382 | /// will be written to. 383 | pub fn startMessage(self: *RecordBuilder, rec: HandshakeType) void { 384 | std.debug.assert(self.state == .end); 385 | self.buffer[self.index] = rec.int(); 386 | self.state = .{ .start = self.index + 1 }; 387 | self.index += 4; // 3 bytes for the length we will write later 388 | } 389 | 390 | /// Ends the current Handshake message (NOT the total record), updates the state 391 | /// and writes the written length to the handshake record. 392 | pub fn endMessage(self: *RecordBuilder, hasher: *Sha256) void { 393 | std.debug.assert(self.state == .start); 394 | defer self.state = .end; 395 | const idx = self.state.start; 396 | const len = self.index - idx - 3; // 3 bytes for writing the index 397 | std.mem.writeIntBig(u24, self.buffer[idx..][0..3], len); 398 | hasher.update(self.buffer[idx - 1 ..][0..len]); 399 | } 400 | 401 | /// Writes to the internal buffer, asserting a handshake record was set. 402 | /// Updates the internal index on each write and returns the length that 403 | /// was written to the internal buffer. 404 | fn write(self: *RecordBuilder, bytes: []const u8) Error!usize { 405 | std.debug.assert(bytes.len != 0); // empty slice given. 406 | std.debug.assert(self.state == .start); // it's illegal to write random data without creating a message type first. 407 | mem.copy(u8, self.buffer[self.index..], bytes); 408 | self.index += @intCast(u14, bytes.len); 409 | return bytes.len; 410 | } 411 | 412 | /// Initializes a `std.io.Writer` that allows writing data to a handshake record 413 | /// without requiring any allocations. 414 | /// 415 | /// It is illegal to write to this without calling `startRecord` first. 416 | pub fn writer(self: *RecordBuilder) std.io.Writer(*RecordBuilder, Error, write) { 417 | return .{ .context = self }; 418 | } 419 | 420 | /// Writes a new Record to a given writer using a given `tag` of `tls.Record.RecordType`. 421 | /// Writes the total length written to this buffer as part of the Record. 422 | /// 423 | /// Asserts a started handshake record was ended before calling this. 424 | /// 425 | /// This does not reset the internal buffer. For that, use `reset()`. 426 | pub fn writeRecord(self: RecordBuilder, tag: tls.Record.RecordType, any_writer: anytype) @TypeOf(any_writer).Error!void { 427 | std.debug.assert(self.state == .end); 428 | 429 | const data_len = @intCast(u16, self.length()); 430 | var record = tls.Record.init(tag, data_len); 431 | try record.writeTo(any_writer); 432 | try any_writer.writeAll(self.toSlice()); 433 | } 434 | 435 | /// Writes a new Record to a given writer using a given `tag` of `tls.Record.RecordType`. 436 | /// Writes the total length written to this buffer as part of the Record. 437 | /// 438 | /// Asserts a started handshake record was ended before calling this. 439 | /// 440 | /// This does not reset the internal buffer. For that, use `reset()`. 441 | pub fn writeRecordEncrypted( 442 | self: *RecordBuilder, 443 | tag: tls.Record.RecordType, 444 | any_writer: anytype, 445 | comptime Cipher: type, 446 | key_storage: *ciphers.KeyStorage, 447 | sequence: u64, 448 | ) @TypeOf(any_writer).Error!void { 449 | std.debug.assert(self.state == .end); 450 | 451 | // Write the actual tag 452 | self.buffer[self.index] = tag.int(); 453 | self.index += 1; 454 | 455 | const data_len = @intCast(u16, self.length()); 456 | var record = tls.Record.init(.application_data, data_len + 16); // include auth tag 457 | 458 | var auth_tag: [16]u8 = undefined; 459 | var buf: [1 << 14]u8 = undefined; 460 | Cipher.encrypt( 461 | key_storage, 462 | buf[0..data_len], 463 | self.toSlice(), 464 | &record.toBytes(), 465 | sequence, 466 | &auth_tag, 467 | ); 468 | 469 | try record.writeTo(any_writer); 470 | try any_writer.writeAll(buf[0..data_len]); 471 | try any_writer.writeAll(&auth_tag); 472 | } 473 | 474 | /// Resets the internal buffer's index to 0 so we can build a new record. 475 | /// 476 | /// Asserts no handshake record is being written currently. 477 | pub fn reset(self: *RecordBuilder) void { 478 | std.debug.assert(self.state == .end); // resetting during a record write is not allowed. 479 | self.index = 0; 480 | } 481 | 482 | /// Returns a slice of the current internal buffer. 483 | /// 484 | /// When we are still writing to a handshake message, this will return a 485 | /// slice until the start of that record as it assumes the slice is needed 486 | /// unside the record. 487 | pub fn toSlice(self: RecordBuilder) []const u8 { 488 | return switch (self.state) { 489 | .start => |idx| self.buffer[0..idx], 490 | .end => self.buffer[0..self.index], 491 | }; 492 | } 493 | 494 | /// Returns the length of the currently written data 495 | pub fn length(self: RecordBuilder) usize { 496 | return self.index; 497 | } 498 | }; 499 | 500 | /// Constructs a reader that hashes each read's content 501 | /// NOTE: It reads raw data, not hashed data. 502 | fn HashReader(comptime ReaderType: type) type { 503 | return struct { 504 | const Self = @This(); 505 | hash: *Sha256, 506 | any_reader: ReaderType, 507 | 508 | const Error = ReaderType.Error; 509 | 510 | pub fn init(any_reader: ReaderType, hash: *Sha256) Self { 511 | return .{ .any_reader = any_reader, .hash = hash }; 512 | } 513 | 514 | pub fn read(self: *Self, buf: []u8) Error!usize { 515 | const len = try self.any_reader.read(buf); 516 | if (len != 0) { 517 | self.hash.update(buf[0..len]); 518 | } 519 | return len; 520 | } 521 | 522 | pub fn reader(self: *Self) std.io.Reader(*Self, Error, read) { 523 | return .{ .context = self }; 524 | } 525 | }; 526 | } 527 | 528 | test "Client Hello" { 529 | // Client hello bytes taken from: 530 | // https://tls13.ulfheim.net/ 531 | 532 | // zig fmt: off 533 | var data = [_]u8{ 534 | // Handshake header 535 | 0x01, 0x00, 0x00, 0xc6, 536 | // client version 537 | 0x03, 0x03, 538 | // random 539 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 540 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 541 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 542 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 543 | // Session id 544 | 0x20, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 545 | 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 546 | 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 547 | 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 548 | 0xff, 549 | // Cipher suites 550 | 0x00, 0x06, 0x13, 0x01, 551 | 0x13, 0x02, 0x13, 0x03, 552 | // Compression methods 553 | 0x01, 0x00, 554 | // Extension length 555 | 0x00, 0x77, 556 | // Extension - Server name 557 | 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 558 | 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 559 | 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 560 | 0x2e, 0x6e, 0x65, 0x74, 561 | // Extension - Support groups 562 | 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 563 | 0x00, 0x17, 0x00, 0x18, 564 | // Extension - Signature Algorithms 565 | 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 566 | 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 567 | 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 568 | // Extensions - Key Share 569 | 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 570 | 0x00, 0x20, 0x35, 0x80, 0x72, 0xd6, 0x36, 0x58, 571 | 0x80, 0xd1, 0xae, 0xea, 0x32, 0x9a, 0xdf, 0x91, 572 | 0x21, 0x38, 0x38, 0x51, 0xed, 0x21, 0xa2, 0x8e, 573 | 0x3b, 0x75, 0xe9, 0x65, 0xd0, 0xd2, 0xcd, 0x16, 574 | 0x62, 0x54, 575 | // Extension - PSK Key Exchange modes 576 | 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 577 | // Extension - Supported versions 578 | 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, 579 | }; 580 | // zig fmt: on 581 | 582 | var fb_reader = std.io.fixedBufferStream(&data).reader(); 583 | var hasher = Sha256.init(.{}); 584 | var hs_reader = handshakeReader(fb_reader, &hasher); 585 | const result = try hs_reader.decode(); 586 | const client_hello = result.client_hello; 587 | 588 | try std.testing.expectEqual(@as(u16, 0x0303), client_hello.legacy_version); 589 | 590 | // check random 591 | try std.testing.expectEqualSlices(u8, &.{ 592 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 593 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 594 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 595 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 596 | }, &client_hello.random); 597 | 598 | // check session id 599 | try std.testing.expectEqualSlices(u8, &.{ 600 | 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 601 | 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 602 | 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 603 | 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 604 | 0xfc, 0xfd, 0xfe, 0xff, 605 | }, &client_hello.session_id); 606 | 607 | var cipher_bytes = [_]u8{ 0x13, 0x01, 0x13, 0x02, 0x13, 0x03 }; 608 | const ciphers_slice = tls.bytesToTypedSlice(tls.CipherSuite, &cipher_bytes); 609 | try std.testing.expectEqualSlices(tls.CipherSuite, ciphers_slice, client_hello.cipher_suites); 610 | } 611 | 612 | test "Server Hello" { 613 | var buf: [2048]u8 = undefined; 614 | var fb = std.io.fixedBufferStream(&buf); 615 | const writer = fb.writer(); 616 | var hasher = Sha256.init(.{}); 617 | var hs_writer = handshakeWriter(writer, &hasher); 618 | const session_id: [32]u8 = .{ 619 | 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 620 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 621 | }; 622 | 623 | const random: [32]u8 = .{ 624 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 625 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 626 | }; 627 | 628 | const key_share = tls.KeyShare{ 629 | .key_exchange = .{ 630 | 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 631 | 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15, 632 | }, 633 | .named_group = .x25519, 634 | }; 635 | 636 | try hs_writer.serverHello( 637 | .server_hello, 638 | session_id, 639 | .tls_aes_128_gcm_sha256, 640 | key_share, 641 | random, 642 | ); 643 | 644 | // zig fmt: off 645 | const expected = &[_]u8{ 646 | // record header 647 | 0x16, 0x03, 0x03, 0x00, 0x7a, 648 | // handshake header 649 | 0x02, 0x00, 0x00, 0x76, 650 | // server version 651 | 0x03, 0x03, 652 | } 653 | // server random 654 | ++ random ++ 655 | // session id length (32 bytes) 656 | &[_]u8{0x20} ++ 657 | // session id 658 | session_id ++ &[_]u8{ 659 | //cipher suite 660 | 0x13, 0x1, 661 | // compression method 662 | 0x00, 663 | // extension length 664 | 0x00, 0x2e, 665 | // extension key_share 666 | 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 0xd3, 0xf9, 0x6d, 0x5b, 667 | 0x1b, 0x2a, 0xf9, 0x10, 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15, 668 | // supported versions 669 | 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04 670 | }; 671 | // zig fmt: on 672 | 673 | try std.testing.expectEqualSlices(u8, expected, fb.getWritten()); 674 | } 675 | 676 | test "RecordBuilder" { 677 | const finished_bytes = [_]u8{ 678 | 0xea, 0x6e, 0xe1, 0x76, 0xdc, 0xcc, 0x4a, 0xf1, 0x85, 0x9e, 0x9e, 0x4e, 0x93, 0xf7, 0x97, 0xea, 679 | 0xc9, 0xa7, 0x8c, 0xe4, 0x39, 0x30, 0x1e, 0x35, 0x27, 0x5a, 0xd4, 0x3f, 0x3c, 0xdd, 0xbd, 0xe3, 680 | }; 681 | var buf: [1 << 14]u8 = undefined; 682 | var stream = std.io.fixedBufferStream(&buf); 683 | const writer = stream.writer(); 684 | var builder = RecordBuilder.init(); 685 | var hasher = Sha256.init(.{}); 686 | builder.startMessage(.finished); 687 | builder.writer().writeAll(&finished_bytes) catch unreachable; 688 | builder.endMessage(&hasher); 689 | try builder.writeRecord(.application_data, writer); 690 | 691 | // zig fmt: off 692 | try std.testing.expectEqualSlices(u8, &([_]u8{ 693 | // application data 694 | 0x17, 695 | // Tls version 696 | 0x03, 0x03, 697 | // length 698 | 0x00, 0x24, 699 | // finished handshake header type 700 | 0x14, 701 | // handshake header length 702 | 0x00, 0x00, 0x20, 703 | // actual finished bytes 704 | } ++ finished_bytes), 705 | stream.getWritten()); 706 | // zig fmt: on 707 | } 708 | -------------------------------------------------------------------------------- /src/tls.zig: -------------------------------------------------------------------------------- 1 | //! Contains data constructs related to the TLS protocol. 2 | const std = @import("std"); 3 | const mem = std.mem; 4 | const Allocator = mem.Allocator; 5 | const crypto = std.crypto; 6 | const HkdfSha256 = crypto.kdf.hkdf.HkdfSha256; 7 | 8 | /// Target cpu's endianness. Use this to check if byte swapping is required. 9 | const target_endianness = @import("builtin").cpu.arch.endian(); 10 | 11 | /// Record header. TLS sessions are broken into the sending 12 | /// and receiving of records, which are blocks of data with a type, 13 | /// protocol version and a length. 14 | pub const Record = extern struct { 15 | /// The type of record we're receiving or sending 16 | record_type: RecordType, 17 | /// The (legacy) protocol version. 18 | /// This is *always* 0x0303 (TLS 1.2) even for TLS 1.3 19 | /// as the supported versions are part of an extension in TLS 1.3, 20 | /// rather than the `Record` header. 21 | protocol_version: u16 = 0x0303, 22 | /// The length of the bytes that are left for reading. 23 | /// The length MUST not exceed 2^14 bytes. 24 | len: u16, 25 | 26 | /// Supported record types by TLS 1.3 27 | pub const RecordType = enum(u8) { 28 | change_cipher_spec = 20, 29 | alert = 21, 30 | handshake = 22, 31 | application_data = 23, 32 | 33 | pub fn int(self: RecordType) u8 { 34 | return @enumToInt(self); 35 | } 36 | }; 37 | 38 | /// Initializes a new `Record` that always has its `protocol_version` set to 0x0303. 39 | pub fn init(record_type: RecordType, len: u16) Record { 40 | return .{ .record_type = record_type, .len = len }; 41 | } 42 | 43 | /// Writes a `Record` to a given `writer`. 44 | pub fn writeTo(self: Record, writer: anytype) !void { 45 | try writer.writeByte(@enumToInt(self.record_type)); 46 | try writer.writeIntBig(u16, self.protocol_version); 47 | try writer.writeIntBig(u16, self.len); 48 | } 49 | 50 | /// Reads from a given `reader` to initialize a new `Record`. 51 | /// It's up to the user to verify correctness of the data (such as protocol version). 52 | pub fn readFrom(reader: anytype) !Record { 53 | return Record{ 54 | .record_type = @intToEnum(RecordType, try reader.readByte()), 55 | .protocol_version = try reader.readIntBig(u16), 56 | .len = try reader.readIntBig(u16), 57 | }; 58 | } 59 | 60 | /// Represents a `Record` as an array 61 | pub fn toBytes(self: Record) [5]u8 { 62 | var bytes: [5]u8 = undefined; 63 | bytes[0] = @enumToInt(self.record_type); 64 | mem.writeIntBig(u16, bytes[1..3], self.protocol_version); 65 | mem.writeIntBig(u16, bytes[3..5], self.len); 66 | return bytes; 67 | } 68 | }; 69 | 70 | pub const Alert = struct { 71 | severity: Severity, 72 | tag: Tag, 73 | 74 | /// Initializes a new `Alert` of a given type `Tag` with its severity set 75 | /// to `severity`. 76 | pub fn init(tag: Tag, severity: Severity) Alert { 77 | return .{ .tag = tag, .severity = severity }; 78 | } 79 | 80 | /// Reads an alert from a given `reader` 81 | pub fn readFrom(reader: anytype) (@TypeOf(reader).Error || error{EndOfStream})!Alert { 82 | return Alert{ 83 | .severity = @intToEnum(Severity, try reader.readByte()), 84 | .tag = @intToEnum(Tag, try reader.readByte()), 85 | }; 86 | } 87 | 88 | /// Initializes a new `Alert` from a given byte array. 89 | pub fn fromBytes(bytes: [2]u8) Alert { 90 | return Alert{ 91 | .severity = @intToEnum(Severity, bytes[0]), 92 | .tag = @intToEnum(Tag, bytes[1]), 93 | }; 94 | } 95 | 96 | /// Writes the Alert to a given `writer` stream. 97 | pub fn writeTo(self: Alert, writer: anytype) @TypeOf(writer).Error!void { 98 | try writer.writeByte(self.severity.int()); 99 | try writer.writeByte(self.tag.int()); 100 | } 101 | 102 | /// Returns the `Tag` of an `Alert` as an `Error`. Can be used 103 | /// inside functions that receive an alert as response from the peer. 104 | pub fn toError(self: Alert) Error { 105 | return switch (self.tag) { 106 | .close_notify => error.CloseNotify, 107 | .unexpected_message => error.UnexpectedMessage, 108 | .bad_record_mac => error.BadRecordMac, 109 | .record_overflow => error.RecordOverflow, 110 | .handshake_failure => error.HandshakeFailure, 111 | .bad_certificate => error.BadCertificate, 112 | .unsupported_certificate => error.UnsupportedCertificate, 113 | .certificate_revoked => error.CertificateRevoked, 114 | .certificate_expired => error.CertificateExpired, 115 | .certificate_unknown => error.CertificateUnknown, 116 | .illegal_parameter => error.IllegalParameter, 117 | .unknown_ca => error.UnknownCA, 118 | .access_denied => error.AccessDenied, 119 | .decode_error => error.DecodeError, 120 | .decrypt_error => error.DecryptError, 121 | .protocol_version => error.ProtocolVersion, 122 | .insufficient_security => error.InsufficientSecurity, 123 | .internal_error => error.InternalError, 124 | .inappropriate_fallback => error.InappropiateFallback, 125 | .user_canceled => error.UserCanceled, 126 | .missing_extension => error.MissingExtension, 127 | .unsupported_extension => error.UnsupportedExtension, 128 | .unrecognized_name => error.UnrecognizedName, 129 | .bad_certificate_status_response => error.BadCertificateStatusResponse, 130 | .unknown_psk_identity => error.UnknownPskIdentity, 131 | .certificate_required => error.CertificateRequired, 132 | .no_application_protocol => error.NoApplicationProtocol, 133 | }; 134 | } 135 | 136 | /// Types of alerts we can emit or receive 137 | /// Known as AlertDescription by TLS 138 | pub const Tag = enum(u8) { 139 | close_notify = 0, 140 | unexpected_message = 10, 141 | bad_record_mac = 20, 142 | record_overflow = 22, 143 | handshake_failure = 40, 144 | bad_certificate = 42, 145 | unsupported_certificate = 43, 146 | certificate_revoked = 44, 147 | certificate_expired = 45, 148 | certificate_unknown = 46, 149 | illegal_parameter = 47, 150 | unknown_ca = 48, 151 | access_denied = 49, 152 | decode_error = 50, 153 | decrypt_error = 51, 154 | protocol_version = 70, 155 | insufficient_security = 71, 156 | internal_error = 80, 157 | inappropriate_fallback = 86, 158 | user_canceled = 90, 159 | missing_extension = 109, 160 | unsupported_extension = 110, 161 | unrecognized_name = 112, 162 | bad_certificate_status_response = 113, 163 | unknown_psk_identity = 115, 164 | certificate_required = 116, 165 | no_application_protocol = 120, 166 | 167 | pub fn int(self: Tag) u8 { 168 | return @enumToInt(self); 169 | } 170 | }; 171 | 172 | /// Represents the severity of the alert. 173 | /// When the level is `fatal`, no more data must be read 174 | /// or written to the connection. 175 | pub const Severity = enum(u8) { 176 | warning = 1, 177 | fatal = 2, 178 | 179 | pub fn int(self: Severity) u8 { 180 | return @enumToInt(self); 181 | } 182 | }; 183 | 184 | /// All alert errors/warnings specified by RFC 8446 185 | pub const Error = error{ 186 | CloseNotify, 187 | UnexpectedMessage, 188 | BadRecordMac, 189 | RecordOverflow, 190 | HandshakeFailure, 191 | BadCertificate, 192 | UnsupportedCertificate, 193 | CertificateRevoked, 194 | CertificateExpired, 195 | CertificateUnknown, 196 | IllegalParameter, 197 | UnknownCA, 198 | AccessDenied, 199 | DecodeError, 200 | DecryptError, 201 | ProtocolVersion, 202 | InsufficientSecurity, 203 | InternalError, 204 | InappropiateFallback, 205 | UserCanceled, 206 | MissingExtension, 207 | UnsupportedExtension, 208 | UnrecognizedName, 209 | BadCertificateStatusResponse, 210 | UnknownPskIdentity, 211 | CertificateRequired, 212 | NoApplicationProtocol, 213 | }; 214 | }; 215 | 216 | /// Represents the key exchange that is supported by the client or server 217 | /// Prior to TLS 1.3 this was called 'elliptic_curves' and only contained elliptic curve groups. 218 | pub const NamedGroup = enum(u16) { 219 | secp256r1 = 0x0017, 220 | secp384r1 = 0x0018, 221 | secp521r1 = 0x0019, 222 | x25519 = 0x001D, 223 | x448 = 0x001E, 224 | ffdhe2048 = 0x0100, 225 | ffdhe3072 = 0x0101, 226 | ffdhe4096 = 0x0102, 227 | ffdhe_private_use_start = 0x01FC, 228 | ffdhe_private_use_end = 0x01FF, 229 | ecdhe_private_use_start = 0xFE00, 230 | ecdhe_private_use_end = 0xFEFF, 231 | /// reserved and unsupported values 232 | /// as they're part of earlier TLS version. 233 | _, 234 | 235 | pub fn int(self: NamedGroup) u16 { 236 | return @enumToInt(self); 237 | } 238 | }; 239 | 240 | /// Provides a list of supported `NamedGroup` by this library 241 | /// that have been implemented. Meaning this may contain only 242 | /// a subset of all groups that TLS 1.3 may support. 243 | pub const supported_named_groups = struct { 244 | pub const set: []const NamedGroup = &.{ 245 | .x25519, 246 | }; 247 | 248 | /// Verifies if a given `NamedGroup` is supported by this library. 249 | /// Returns false if the group isn't implemented/supported yet. 250 | pub fn isSupported(group: NamedGroup) bool { 251 | return for (set) |g| { 252 | if (g == group) break true; 253 | } else false; 254 | } 255 | }; 256 | 257 | pub const SignatureAlgorithm = enum(u16) { 258 | // RSASSA-PKCS1-v1_5 algorithms 259 | rsa_pkcs1_sha256 = 0x0401, 260 | rsa_pkcs1_sha384 = 0x0501, 261 | rsa_pkcs1_sha512 = 0x0601, 262 | 263 | // ECDSA algorithms 264 | ecdsa_secp256r1_sha256 = 0x0403, 265 | ecdsa_secp384r1_sha384 = 0x0503, 266 | ecdsa_secp521r1_sha512 = 0x0603, 267 | 268 | // RSASSA-PSS algorithms with public key OID rsaEncryption 269 | rsa_pss_rsae_sha256 = 0x0804, 270 | rsa_pss_rsae_sha384 = 0x0805, 271 | rsa_pss_rsae_sha512 = 0x0806, 272 | 273 | // EdDSA algorithms 274 | ed25519 = 0x0807, 275 | ed448 = 0x0808, 276 | 277 | // RSASSA-PSS algorithms with public key OID RSASSA-PSS 278 | rsa_pss_pss_sha256 = 0x0809, 279 | rsa_pss_pss_sha384 = 0x080a, 280 | rsa_pss_pss_sha512 = 0x080b, 281 | 282 | // Legacy algorithms 283 | rsa_pkcs1_sha1 = 0x0201, 284 | ecdsa_sha1 = 0x0203, 285 | 286 | /// Reserved Code Points 287 | _, 288 | 289 | pub fn int(self: SignatureAlgorithm) u16 { 290 | return @enumToInt(self); 291 | } 292 | }; 293 | 294 | /// Table of supported `SignatureAlgorithm` of this library 295 | /// This may only include a subset of the enum values found in 296 | /// `SignatureAlgorithm` itself. 297 | pub const supported_signature_algorithms = struct { 298 | pub const set: []const SignatureAlgorithm = &.{ 299 | .ecdsa_secp256r1_sha256, 300 | }; 301 | 302 | /// Checks if a given `SignatureAlgorithm` is supported by the library. 303 | pub fn isSupported(signature: SignatureAlgorithm) bool { 304 | return for (set) |alg| { 305 | if (alg == signature) break true; 306 | } else false; 307 | } 308 | }; 309 | 310 | /// Supported cipher suites by TLS 1.3 311 | /// https://datatracker.ietf.org/doc/html/rfc8446#appendix-B.4 312 | pub const CipherSuite = enum(u16) { 313 | tls_aes_128_gcm_sha256 = 0x1301, 314 | tls_aes_256_gcm_sha384 = 0x1302, 315 | tls_chacha20_poly1305_sha256 = 0x1303, 316 | tls_aes_128_ccm_sha256 = 0x1304, 317 | tls_aes_128_ccm_8_sha256 = 0x1305, 318 | /// Unsupported, legacy suites 319 | _, 320 | 321 | pub fn int(self: CipherSuite) u16 { 322 | return @enumToInt(self); 323 | } 324 | }; 325 | 326 | /// Pre-shared Key Exchange Modes as described in section 4.2.9 327 | /// https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.9 328 | pub const PskKeyExchangeMode = enum(u8) { 329 | psk_ke = 0, 330 | psk_dhe_ke = 1, 331 | }; 332 | 333 | /// Reads big endian bytes into a typed slice of `T`. 334 | /// Converts each element's bytes to target cpu's endianness. 335 | /// 336 | /// Note: Given type `T` must be representable as an integer type, 337 | /// meaning if an Enum type is given, it must be tagged. 338 | pub fn bytesToTypedSlice(comptime T: type, bytes: anytype) []const T { 339 | const IntType = switch (@typeInfo(T)) { 340 | .Enum => |info| info.tag_type, 341 | .Int => T, 342 | else => @compileLog("Given type " ++ @typeName(T) ++ " is not representable as an Integer type."), 343 | }; 344 | 345 | var slice = std.mem.bytesAsSlice(IntType, bytes); 346 | if (target_endianness == .Little) for (slice) |*element| { 347 | element.* = @byteSwap(IntType, element.*); 348 | }; 349 | return @bitCast([]const T, slice); 350 | } 351 | 352 | /// Keyshare represents the key exchange used to generate 353 | /// its public key, and the actual public key. 354 | pub const KeyShare = struct { 355 | /// The key exchange (i.e. curve25519) 356 | named_group: NamedGroup, 357 | /// The public key of the client 358 | key_exchange: [32]u8, 359 | 360 | /// Returns the total bytes it will write to a TLS connection 361 | /// during a handshake 362 | pub fn byteLen(self: KeyShare) u16 { 363 | return self.key_exchange.len + 8; 364 | } 365 | 366 | /// Ensures the correct bytes are written to the writer 367 | /// based on a given `KeyShare`. 368 | pub fn writeTo(self: KeyShare, writer: anytype) !void { 369 | try writer.writeIntBig(u16, Extension.Tag.key_share.int()); 370 | try writer.writeIntBig(u16, 0x0024); // length (36 bytes) 371 | try writer.writeIntBig(u16, self.named_group.int()); 372 | try writer.writeIntBig(u16, 0x20); // public key length (32 bytes) 373 | try writer.writeAll(&self.key_exchange); 374 | } 375 | }; 376 | 377 | /// Extension define what the client requests for extended functionality from servers. 378 | /// Note that some extensions are required for TLS 1.3 itself, 379 | /// while others are optional. 380 | pub const Extension = union(Tag) { 381 | /// The supported TLS versions of the client. 382 | supported_versions: []const u16, 383 | /// The PSK key exchange modes the client supports 384 | psk_key_exchange_modes: []const PskKeyExchangeMode, 385 | /// List of public keys and the key exchange required for them. 386 | key_share: []const KeyShare, 387 | /// A list of signature algorithms the client supports 388 | signature_algorithms: []const SignatureAlgorithm, 389 | /// The groups of curve types the client supports 390 | supported_groups: []const NamedGroup, 391 | /// Hostname of the server the client wants to connect to 392 | /// TLS uses this to determine which server certificate to use, 393 | /// rather than requiring multiple servers. 394 | server_name: []const u8, 395 | 396 | // TODO: Implement the other types. Currently, 397 | // they're just void types. 398 | max_gragment_length, 399 | status_request, 400 | use_srtp, 401 | heartbeat, 402 | application_layer_protocol_negotation, 403 | signed_certificate_timestamp, 404 | client_certificate_type, 405 | server_certificate_type, 406 | pre_shared_key, 407 | early_data, 408 | cookie, 409 | certificate_authorities, 410 | oid_filters, 411 | post_handshake_auth, 412 | signature_algorithms_cert, 413 | 414 | /// All extensions that are compatible with TLS 1.3 415 | /// Some may be specified as an external rfc. 416 | pub const Tag = enum(u16) { 417 | server_name = 0, 418 | max_gragment_length = 1, 419 | status_request = 5, 420 | supported_groups = 10, 421 | signature_algorithms = 13, 422 | use_srtp = 14, 423 | heartbeat = 15, 424 | application_layer_protocol_negotation = 16, 425 | signed_certificate_timestamp = 18, 426 | client_certificate_type = 19, 427 | server_certificate_type = 20, 428 | pre_shared_key = 41, 429 | early_data = 42, 430 | supported_versions = 43, 431 | cookie = 44, 432 | psk_key_exchange_modes = 45, 433 | certificate_authorities = 47, 434 | oid_filters = 48, 435 | post_handshake_auth = 49, 436 | signature_algorithms_cert = 50, 437 | key_share = 51, 438 | /// Unsupported 'legacy' extensions 439 | _, 440 | 441 | pub fn int(self: Tag) u16 { 442 | return @enumToInt(self); 443 | } 444 | }; 445 | 446 | /// Constructs extensions as they are parsed. 447 | /// Allowing to to reduce the need for allocations. 448 | pub const Iterator = struct { 449 | /// Mutable slice as we may require 450 | /// to byteswap elements to ensure correct endianness 451 | data: []u8, 452 | /// Current index into `data` 453 | index: usize, 454 | 455 | /// Initializes a new instance of `Iterator` with given data. 456 | pub fn init(data: []u8) Iterator { 457 | return .{ .data = data, .index = 0 }; 458 | } 459 | 460 | /// Sets `index` to '0', allowing to re-iterate over the extensions present in `data`. 461 | pub fn reset(self: *Iterator) void { 462 | self.index = 0; 463 | } 464 | 465 | /// Parses the next extension, returning `null` when all extensions have been parsed. 466 | /// Will return `UnsupportedExtension` when an extension is not supported by TLS 1.3, 467 | /// or simply isn't implemented yet. 468 | pub fn next(self: *Iterator, gpa: Allocator) error{ OutOfMemory, UnsupportedExtension }!?Extension { 469 | if (self.index >= self.data.len) return null; 470 | 471 | const tag_byte = mem.readIntBig(u16, self.data[self.index..][0..2]); 472 | self.index += 2; 473 | const extension_length = mem.readIntBig(u16, self.data[self.index..][0..2]); 474 | self.index += 2; 475 | const extension_data = self.data[self.index .. self.index + extension_length]; 476 | self.index += extension_data.len; 477 | 478 | switch (@intToEnum(Extension.Tag, tag_byte)) { 479 | .supported_versions => return Extension{ .supported_versions = bytesToTypedSlice(u16, extension_data[1..]) }, 480 | .psk_key_exchange_modes => return Extension{ .psk_key_exchange_modes = @bitCast( 481 | []const PskKeyExchangeMode, 482 | extension_data[1..], 483 | ) }, 484 | .key_share => { 485 | const len = mem.readIntBig(u16, extension_data[0..2]); 486 | var keys = std.ArrayList(KeyShare).init(gpa); 487 | defer keys.deinit(); 488 | 489 | var i: usize = 0; 490 | while (i < len) { 491 | // allocate memory for a new key 492 | const key = try keys.addOne(); 493 | 494 | // get the slice for current data 495 | const data = extension_data[i + 2 ..]; 496 | 497 | // read named_group and the amount of bytes of public key 498 | const named_group = mem.readIntBig(u16, data[0..2]); 499 | const key_len = mem.readIntBig(u16, data[2..4]); 500 | _ = key_len; 501 | 502 | // update pointer's value 503 | key.* = .{ 504 | .named_group = @intToEnum(NamedGroup, named_group), 505 | .key_exchange = data[4..][0..32].*, 506 | }; 507 | i += key_len + 4; 508 | } 509 | 510 | return Extension{ .key_share = keys.toOwnedSlice() }; 511 | }, 512 | // For TLS 1.3 only 1 hostname can be provided which is always 513 | // of type DNS hostname. This means the hostname is the remaining 514 | // bytes after the 5th element. 515 | .server_name => return Extension{ .server_name = extension_data[5..] }, 516 | .supported_groups => return Extension{ .supported_groups = bytesToTypedSlice( 517 | NamedGroup, 518 | extension_data[2..], 519 | ) }, 520 | .signature_algorithms => return Extension{ .signature_algorithms = bytesToTypedSlice( 521 | SignatureAlgorithm, 522 | extension_data[2..], 523 | ) }, 524 | else => return error.UnsupportedExtension, 525 | } 526 | } 527 | }; 528 | }; 529 | 530 | test "Extension iterator" { 531 | var extension_bytes = [_]u8{ 532 | // Extension - Server name 533 | 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 534 | 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 535 | 0x2e, 0x75, 0x6c, 0x66, 0x68, 0x65, 0x69, 0x6d, 536 | 0x2e, 0x6e, 0x65, 0x74, 537 | // Extension - Support groups 538 | 0x00, 0x0a, 0x00, 0x08, 539 | 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 540 | // Extension - Signature Algorithms 541 | 0x00, 0x0d, 0x00, 0x14, 0x00, 0x12, 0x04, 0x03, 542 | 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 543 | 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 544 | // Extensions - Key Share 545 | 0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 546 | 0x00, 0x20, 0x35, 0x80, 0x72, 0xd6, 0x36, 0x58, 547 | 0x80, 0xd1, 0xae, 0xea, 0x32, 0x9a, 0xdf, 0x91, 548 | 0x21, 0x38, 0x38, 0x51, 0xed, 0x21, 0xa2, 0x8e, 549 | 0x3b, 0x75, 0xe9, 0x65, 0xd0, 0xd2, 0xcd, 0x16, 550 | 0x62, 0x54, 551 | // Extension - PSK Key Exchange modes 552 | 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 553 | // Extension - Supported versions 554 | 0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, 555 | }; 556 | 557 | var it = Extension.Iterator{ 558 | .data = &extension_bytes, 559 | .index = 0, 560 | }; 561 | 562 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 563 | defer arena.deinit(); 564 | while (try it.next(arena.allocator())) |ext| { 565 | switch (ext) { 566 | .server_name => |name| try std.testing.expectEqualStrings("example.ulfheim.net", name), 567 | .supported_groups => |groups| try std.testing.expectEqualSlices( 568 | NamedGroup, 569 | &.{ .x25519, .secp256r1, .secp384r1 }, 570 | groups, 571 | ), 572 | .signature_algorithms => |algs| try std.testing.expectEqualSlices( 573 | SignatureAlgorithm, 574 | &.{ 575 | .ecdsa_secp256r1_sha256, .rsa_pss_rsae_sha256, .rsa_pkcs1_sha256, 576 | .ecdsa_secp384r1_sha384, .rsa_pss_rsae_sha384, .rsa_pkcs1_sha384, 577 | .rsa_pss_rsae_sha512, .rsa_pkcs1_sha512, .rsa_pkcs1_sha1, 578 | }, 579 | algs, 580 | ), 581 | .psk_key_exchange_modes => |modes| try std.testing.expectEqualSlices( 582 | PskKeyExchangeMode, 583 | &.{.psk_dhe_ke}, 584 | modes, 585 | ), 586 | else => {}, //TODO: Implement all extensions 587 | } 588 | } 589 | } 590 | 591 | /// Represents a private and public key for either the server 592 | /// or a client. 593 | pub const KeyExchange = struct { 594 | private_key: [32]u8, 595 | public_key: [32]u8, 596 | 597 | /// Generates a new private/public key pair using the given curve. 598 | pub fn fromCurve(curve: *Curve) Curve.Error!KeyExchange { 599 | var exchange: KeyExchange = undefined; 600 | crypto.random.bytes(&exchange.private_key); 601 | try curve.generateKey(exchange.private_key, &exchange.public_key); 602 | return exchange; 603 | } 604 | }; 605 | 606 | /// Curve allows us to generate a public key for a given 607 | /// private key, using a generation function provided 608 | /// by an implementation. 609 | pub const Curve = struct { 610 | /// Error which can occur when generating the public key 611 | pub const Error = crypto.errors.IdentityElementError; 612 | genFn: fn (*Curve, [32]u8, *[32]u8) Error!void, 613 | 614 | /// Generates a new public key from a given private key. 615 | /// Writes the output of the curve function to `public_key_out`. 616 | pub fn generateKey(self: *Curve, private_key: [32]u8, public_key_out: *[32]u8) Error!void { 617 | try self.genFn(self, private_key, public_key_out); 618 | } 619 | }; 620 | 621 | /// Namespace of implemented curves, that can be used 622 | /// to generate keys. 623 | pub const curves = struct { 624 | const _x25519 = struct { 625 | var state = Curve{ .genFn = gen }; 626 | 627 | fn gen(curve: *Curve, private_key: [32]u8, public_key_out: *[32]u8) !void { 628 | _ = curve; 629 | public_key_out.* = try crypto.dh.X25519.recoverPublicKey(private_key); 630 | } 631 | }; 632 | /// Provides a x25519 elliptic curve to construct a private/public key-pair. 633 | pub const x25519 = &_x25519.state; 634 | 635 | /// From a given `NamedGroup` returns a `Curve`, used to generate 636 | /// a KeyExchange containing a public and private key component. 637 | pub fn fromNamedGroup(group: NamedGroup) *Curve { 638 | return switch (group) { 639 | .x25519 => x25519, 640 | else => @panic("TODO: Implement more curves"), 641 | }; 642 | } 643 | }; 644 | 645 | test "x25519 curve" { 646 | const x_curve = curves.x25519; 647 | const private_key = [_]u8{ 648 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 649 | 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 650 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 651 | 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 652 | }; 653 | 654 | const expected_public_key = [_]u8{ 655 | 0x9f, 0xd7, 0xad, 0x6d, 0xcf, 0xf4, 0x29, 0x8d, 656 | 0xd3, 0xf9, 0x6d, 0x5b, 0x1b, 0x2a, 0xf9, 0x10, 657 | 0xa0, 0x53, 0x5b, 0x14, 0x88, 0xd7, 0xf8, 0xfa, 658 | 0xbb, 0x34, 0x9a, 0x98, 0x28, 0x80, 0xb6, 0x15, 659 | }; 660 | 661 | var public_key: [32]u8 = undefined; 662 | try x_curve.generateKey(private_key, &public_key); 663 | 664 | try std.testing.expectEqualSlices(u8, &expected_public_key, &public_key); 665 | 666 | const exchange = try KeyExchange.fromCurve(x_curve); 667 | var test_key: [32]u8 = undefined; 668 | try x_curve.generateKey(exchange.private_key, &test_key); 669 | try std.testing.expectEqualSlices(u8, &exchange.public_key, &test_key); 670 | } 671 | 672 | /// Uses hkdf's expand to generate a derived key. 673 | /// Constructs a hkdf context by generating a hkdf-label 674 | /// which consists of `length`, the label "tls13 " ++ `label` and the given 675 | /// `context`. 676 | pub fn hkdfExpandLabel( 677 | secret: [32]u8, 678 | comptime label: []const u8, 679 | context: []const u8, 680 | comptime length: u16, 681 | ) [length]u8 { 682 | std.debug.assert(label.len <= 255 and label.len > 0); 683 | std.debug.assert(context.len <= 255); 684 | const full_label = "tls13 " ++ label; 685 | 686 | // length, label, context 687 | var buf: [2 + 255 + 255]u8 = undefined; 688 | std.mem.writeIntBig(u16, buf[0..2], length); 689 | buf[2] = full_label.len; 690 | std.mem.copy(u8, buf[3..], full_label); 691 | buf[3 + full_label.len] = @intCast(u8, context.len); 692 | std.mem.copy(u8, buf[4 + full_label.len ..], context); 693 | const actual_context = buf[0 .. 4 + full_label.len + context.len]; 694 | 695 | var out: [32]u8 = undefined; 696 | HkdfSha256.expand(&out, actual_context, secret); 697 | return out[0..length].*; 698 | } 699 | 700 | test "hkdfExpandLabel" { 701 | const early_secret = HkdfSha256.extract(&.{}, &[_]u8{0} ** 32); 702 | var empty_hash: [32]u8 = undefined; 703 | std.crypto.hash.sha2.Sha256.hash("", &empty_hash, .{}); 704 | const derived_secret = hkdfExpandLabel(early_secret, "derived", &empty_hash, 32); 705 | try std.testing.expectEqualSlices(u8, &.{ 706 | 0x6f, 0x26, 0x15, 0xa1, 0x08, 0xc7, 0x02, 707 | 0xc5, 0x67, 0x8f, 0x54, 0xfc, 0x9d, 0xba, 708 | 0xb6, 0x97, 0x16, 0xc0, 0x76, 0x18, 0x9c, 709 | 0x48, 0x25, 0x0c, 0xeb, 0xea, 0xc3, 0x57, 710 | 0x6c, 0x36, 0x11, 0xba, 711 | }, &derived_secret); 712 | } 713 | 714 | test "runtime cipher suite" { 715 | // var suite_we_want: CipherSuite = .tls_aes_128_gcm_sha256; 716 | // inline for (supported_cipher_suites.set) |supported| { 717 | // if (supported == suite_we_want) { 718 | // const CipherType = ciphers.TypeFromSuite(supported); 719 | // var selected_cipher = CipherType.default; 720 | // const cipher: *Cipher = selected_cipher.cipher(); 721 | // try std.testing.expectEqual(CipherSuite.tls_aes_128_gcm_sha256, cipher.cipher_suite); 722 | // } 723 | // } 724 | } 725 | --------------------------------------------------------------------------------