srctree

Gregory Mullen parent f0f525a8 31b9c95b
wip commit

src/cipher.zig added: 133, removed: 102, total 31
@@ -26,7 +26,11 @@ suite: union(enum) {
ecc: EllipticCurve,
aes: AnyAES,
} = .{ .invalid = {} },
key_xhg: KeyExchange = .{},
key_xchg: union(enum) {
invalid: void,
ecdh: ECDH,
dh: void,
} = .{ .invalid = {} },
sequence: u72 = 0, // u72 is chacha20 specific :/
 
pub fn encrypt(c: *Cipher, clear_text: []const u8, cipher_text: []u8) !usize {
@@ -88,21 +92,76 @@ pub fn encrypt(c: *Cipher, clear_text: []const u8, cipher_text: []u8) !usize {
comptime unreachable;
}
 
pub fn decrypt(c: *Cipher, cipher_text: []const u8, clear: []u8) ![]const u8 {
_ = cipher_text;
pub fn decrypt(c: *Cipher, cipher_text_: []const u8, clear_txt: []u8) !usize {
var cipher_txt = cipher_text_;
switch (c.suite) {
.ecc => |ecc| {
_ = ecc;
unreachable;
},
.aes => |aes| {
_ = aes;
var aes_ctx = std.crypto.core.aes.Aes256.initDec(aes.material.srv_key);
var fba = fixedBufferStream(clear_txt);
var w = fba.writer().any();
 
var xord: [16]u8 = cipher_txt[0..16].*;
cipher_txt = cipher_txt[16..];
std.debug.assert(cipher_txt.len % 16 == 0);
for (0..(cipher_txt.len - 16) / 16) |_| {
var xclear: [16]u8 = undefined;
aes_ctx.decrypt(xclear[0..], cipher_txt[0..16]);
cipher_txt = cipher_txt[16..];
var clear: [16]u8 = undefined;
for (clear[0..], xclear[0..], xord[0..]) |*cl, xc, xr| cl.* = xc ^ xr;
@memcpy(xord[0..16], cipher_txt[0..16]);
try w.writeAll(clear[0..]);
}
 
// TODO compute and verify mac
//var len: usize = clear_text.len;
//{
// const mac_out: *[48]u8 = l_clear[len..][0..48];
// var mac = HmacSha384.init(&aes.material.cli_mac);
// mac.update(std.mem.asBytes(&nativeToBig(u64, @truncate(c.sequence))));
// mac.update(&[_]u8{ 22, 3, 3 });
// mac.update(std.mem.asBytes(&nativeToBig(u16, @truncate(len))));
// mac.update(clear_text);
// mac.final(mac_out);
// len += 48;
//}
 
return try fba.getPos() - 48;
},
else => unreachable,
}
return clear;
return clear_txt;
}
 
pub const KeyExchange = struct {};
pub const ECDH = struct {
curve: union(enum) {
x25519: struct {
cli_dh: ?X25519.KeyPair = null,
srv_dh: ?X25519.KeyPair = null,
},
} = .{},
 
pub fn packClient(ecdh: ECDH, buffer: []u8) !usize {
var fba = fixedBufferStream(buffer);
const w = fba.writer().any();
 
switch (ecdh.curve) {
.x25519 => |x| {
try w.writeByte(@truncate(x.cli_dh.?.public_key.len));
try w.writeAll(&x.cli_dh.?.public_key);
return 1 + x.cli_dh.?.public_key.len;
},
}
}
 
//pub fn packKeyExchange(dh: ECDH, buffer: []u8) !usize { }
 
//pub fn pack(dh: ECDH, buffer: []u8) !usize { }
};
 
pub fn Material(comptime ENC: anytype, comptime HMAC: anytype) type {
return struct {
@@ -115,9 +174,6 @@ pub fn Material(comptime ENC: anytype, comptime HMAC: anytype) type {
 
pub const PRF = HmacSha384;
 
srv_pub_key: [32]u8 = undefined,
 
premaster: [X25519.shared_length]u8 = [_]u8{0} ** X25519.shared_length,
master: [48]u8 = undefined,
cli_mac: [MacLength]u8,
srv_mac: [MacLength]u8,
@@ -254,9 +310,6 @@ pub const AnyAES = struct {
cbc: CBC,
},
 
cli_dh: ?X25519.KeyPair = null,
srv_dh: ?X25519.KeyPair = null,
 
/// srv is copied, and will not zero any arguments
pub fn init(srv: [X25519.public_length]u8) !EllipticCurve {
return .{
@@ -280,32 +333,6 @@ pub const AnyAES = struct {
return 48;
}
 
fn packClientECDHE(aes: AnyAES, buffer: []u8) !usize {
var fba = fixedBufferStream(buffer);
const w = fba.writer().any();
 
try w.writeByte(@truncate(aes.cli_dh.?.public_key.len));
try w.writeAll(&aes.cli_dh.?.public_key);
return 1 + aes.cli_dh.?.public_key.len;
}
 
fn packClientDHE(aes: AnyAES, buffer: []u8) !usize {
var fba = fixedBufferStream(buffer);
const w = fba.writer().any();
try w.writeByte(1); // PublicValueEncoding.explicit
_ = aes;
// struct {
// select (PublicValueEncoding) {
// case implicit: struct { };
// case explicit: opaque dh_Yc<1..2^16-1>;
// } dh_public;
// } ClientDiffieHellmanPublic;
 
// dh_Yc
// The client's Diffie-Hellman public value (Yc).
return 0;
}
 
/// TODO move this into the CBC struct
fn packCBC(aes: AnyAES, buffer: []u8) !usize {
//var fba = fixedBufferStream(buffer);
@@ -315,19 +342,11 @@ pub const AnyAES = struct {
//w.writeAll(encrypted_premaster_secret);
}
 
pub fn packKeyExchange(aes: AnyAES, buffer: []u8) !usize {
return switch (aes.block) {
.cbc => aes.packCBC(buffer),
// LOL sorry future me!
//else => return error.NotImplemented,
};
}
 
fn buildKeyMaterial(ctx: *ConnCtx) !Material(AES(256, CBC), HmacSha384) {
var aes = &ctx.cipher.suite.aes;
 
const our_seckey = ctx.cipher.suite.aes.cli_dh.?.secret_key;
const peer_key = &ctx.cipher.suite.aes.srv_dh.?.public_key;
const our_seckey = ctx.cipher.key_xchg.ecdh.curve.x25519.cli_dh.?.secret_key;
const peer_key = &ctx.cipher.key_xchg.ecdh.curve.x25519.srv_dh.?.public_key;
const premaster = try X25519.scalarmult(our_seckey, peer_key.*);
 
const seed = "master secret" ++ ctx.cli_random.? ++ ctx.srv_random.?;
@@ -349,7 +368,7 @@ pub const AnyAES = struct {
//var fba = fixedBufferStream(buffer);
//const r = fba.reader().any();
////const name = try r.readInt(u16, .big);
////ctx.cipher.suite.aes.srv_dh = undefined;
////ctx.cipher.key_xhg.group.x25519.srv_dh = undefined;
//const peer_key = &ctx.cipher.suite.aes.material.srv_pub_key;
//try r.readNoEof(peer_key);
 
@@ -364,11 +383,11 @@ pub const AnyAES = struct {
if (name != 0x001d) return error.UnknownCurveName;
const key_len = try r.readByte();
std.debug.assert(key_len == 32);
ctx.cipher.suite.aes.srv_dh = undefined;
try r.readNoEof(&ctx.cipher.suite.aes.srv_dh.?.public_key);
ctx.cipher.key_xchg.ecdh.curve.x25519.srv_dh = undefined;
try r.readNoEof(&ctx.cipher.key_xchg.ecdh.curve.x25519.srv_dh.?.public_key);
 
// TODO verify signature
ctx.cipher.suite.aes.cli_dh = try Cipher.X25519.KeyPair.create(null);
ctx.cipher.key_xchg.ecdh.curve.x25519.cli_dh = try Cipher.X25519.KeyPair.create(null);
ctx.cipher.suite.aes.material = try buildKeyMaterial(ctx);
}
 
@@ -457,11 +476,9 @@ pub const ECC = struct {
};
 
pub const EllipticCurve = struct {
curve: Curves = .{ .invalid = {} },
 
cli_dh: ?X25519.KeyPair = null,
srv_dh: ?X25519.KeyPair = null,
 
curve: enum {
named_curve,
},
material: Material(ChaCha20, Sha256) = undefined,
 
/// srv is copied, and will not zero any arguments
@@ -500,28 +517,26 @@ pub const EllipticCurve = struct {
pub fn packKeyExchange(ecc: EllipticCurve, buffer: []u8) !usize {
return switch (ecc.curve) {
.named_curve => try ecc.packNamed(buffer),
// LOL sorry future me!
else => return try ecc.packNamed(buffer),
};
}
 
fn buildKeyMaterial(ctx: *ConnCtx) !Material(ChaCha20, Sha256) {
var material: Material(ChaCha20, Sha256) = ctx.cipher.suite.ecc.material;
 
const our_seckey = ctx.cipher.suite.ecc.cli_dh.?.secret_key;
const peer_key = &ctx.cipher.suite.ecc.srv_dh.?.public_key;
material.premaster = try X25519.scalarmult(our_seckey, peer_key.*);
const our_seckey = ctx.cipher.key_xchg.ecdh.curve.x25519.cli_dh.?.secret_key;
const peer_key = &ctx.cipher.key_xchg.ecdh.curve.x25519.srv_dh.?.public_key;
const premaster = try X25519.scalarmult(our_seckey, peer_key.*);
 
const seed = "master secret" ++ ctx.cli_random.? ++ ctx.srv_random.?;
 
var pre_left: [32]u8 = undefined;
var pre_right: [32]u8 = undefined;
Sha256.create(&pre_left, seed, &material.premaster);
Sha256.create(&pre_right, &pre_left, &material.premaster);
Sha256.create(&pre_left, seed, &premaster);
Sha256.create(&pre_right, &pre_left, &premaster);
var left: [32]u8 = undefined;
var right: [32]u8 = undefined;
Sha256.create(&left, pre_left ++ seed, &material.premaster);
Sha256.create(&right, pre_right ++ seed, &material.premaster);
Sha256.create(&left, pre_left ++ seed, &premaster);
Sha256.create(&right, pre_right ++ seed, &premaster);
 
material.master = left ++ right[0..16].*;
 
@@ -568,13 +583,13 @@ pub const EllipticCurve = struct {
if (name != 0x001d) return error.UnknownCurveName;
const key_len = try r.readByte();
std.debug.assert(key_len == 32);
ctx.cipher.suite.ecc.srv_dh = undefined;
const peer_key = &ctx.cipher.suite.ecc.srv_dh.?.public_key;
ctx.cipher.key_xchg.ecdh.curve.x25519.srv_dh = undefined;
const peer_key = &ctx.cipher.key_xchg.ecdh.curve.x25519.srv_dh.?.public_key;
try r.readNoEof(peer_key);
 
// TODO verify signature
 
ctx.cipher.suite.ecc.cli_dh = try Cipher.X25519.KeyPair.create(null);
ctx.cipher.key_xchg.ecdh.curve.x25519.cli_dh = try Cipher.X25519.KeyPair.create(null);
ctx.cipher.suite.ecc.material = try buildKeyMaterial(ctx);
}
 
 
src/extensions.zig added: 133, removed: 102, total 31
@@ -2,9 +2,6 @@ const Extensions = @This();
const std = @import("std");
const fixedBufferStream = std.io.fixedBufferStream;
 
pub const Flavor = union(enum) {
sni: ServerNameIndicator,
};
 
pub const Extension = struct {
ctx: *anyopaque,
@@ -20,21 +17,15 @@ pub const Extension = struct {
};
 
/// Server Name Indicator
pub const ServerNameIndicator = struct {
pub fn pack(_: ServerNameIndicator, buffer: []u8) !usize {
_ = buffer;
//@memcpy(buffer[0..18], &[_]u8{ 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x13, 108, 111, 99, 97, 108, 104, 111, 115, 116 });
pub const ServerNameIndicator = Extension{
.pack_fn = &.{
@memcpy(buffer[0..18], &[_]u8{ 0x00, 0x00, 0x00, 0x18, 0x00, 0x16, 0x00, 0x00, 0x13, 108, 111, 99, 97, 108, 104, 111, 115, 116 });
return 0;
}
 
pub fn entension(sni: *ServerNameIndicator) Extension {
return .{
.ctx = sni,
.pack_fn = pack,
};
}
},
.ctx = &.{}
};
 
 
/// Signed certificate timestamp
pub const SCT = struct {
pub fn pack(_: *anyopaque, buffer: []u8) usize {
 
src/handshake.zig added: 133, removed: 102, total 31
@@ -25,7 +25,7 @@ const HelloRequest = struct {};
 
/// Client Section
pub const ClientHello = struct {
version: Protocol.Version = Protocol.TLSv1_2,
version: Protocol.Version = Protocol.TLSv1_3,
random: Random,
session_id: SessionID,
ciphers: []const Cipher.Suites = &SupportedSuiteList,
@@ -39,7 +39,6 @@ pub const ClientHello = struct {
 
pub const SupportedSuiteList = [_]Cipher.Suites{
.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
};
 
pub const length = @sizeOf(ClientHello);
@@ -107,10 +106,12 @@ pub const ClientKeyExchange = struct {
}
 
pub fn pack(cke: ClientKeyExchange, buffer: []u8) !usize {
return switch (cke.cipher.suite) {
.ecc => |ecc| ecc.packKeyExchange(buffer),
.aes => |aes| aes.packKeyExchange(buffer),
else => return error.NotImplemented,
return switch (cke.cipher.key_xchg) {
.ecdh => |ecdh| ecdh.packClient(buffer),
.dh => unreachable,
.invalid => unreachable,
//.aes => |aes| aes.packKeyExchange(buffer),
//else => return error.NotImplemented,
};
}
};
@@ -137,6 +138,27 @@ pub const Finished = struct {
try w.writeAll(verified[0..12]);
return 12;
}
 
pub fn unpack(buffer: [12]u8, ctx: *const ConnCtx) !Finished {
const master = switch (ctx.cipher.suite) {
.ecc => |ecc| ecc.material.master,
.aes => |aes| aes.material.master,
else => unreachable,
};
var hash: [48]u8 = undefined;
std.crypto.hash.sha2.Sha384.hash(ctx.handshake_record.items, hash[0..], .{});
 
const seed = "server finished" ++ hash;
var a1: [48]u8 = undefined;
HmacSha384.create(&a1, seed, &master);
var verified: [48]u8 = undefined;
HmacSha384.create(&verified, a1 ++ seed, &master);
 
if (!std.crypto.utils.timingSafeEql([12]u8, buffer, verified[0..12].*))
return error.HandshakeRecordInvalid;
 
return .{};
}
};
 
/// Server Section
@@ -156,6 +178,7 @@ pub const ServerHello = struct {
.minor = try r.readByte(),
};
std.debug.assert(version.major == 3 and version.minor == 3);
print("version {}\n", .{version});
 
ctx.srv_random = undefined;
try r.readNoEof(&ctx.srv_random.?);
@@ -204,7 +227,7 @@ pub const ServerKeyExchange = struct {
buffer: []const u8,
 
pub fn pack(_: ServerKeyExchange, _: []u8) !usize {
return 0;
unreachable;
}
 
/// Will modify sess with supplied
@@ -375,7 +398,7 @@ pub const Handshake = struct {
 
// TODO choose real assert length
std.debug.assert(len < 1024);
try ctx.handshake_record.appendSlice(buffer[0 .. len + 4]);
defer ctx.handshake_record.appendSlice(buffer[0 .. len + 4]) catch {};
const hsbuf = buffer[4..][0..len];
return .{
.msg_type = hs_type,
@@ -386,6 +409,7 @@ pub const Handshake = struct {
.server_key_exchange => .{ .server_key_exchange = try ServerKeyExchange.unpack(hsbuf, ctx) },
.certificate_request => .{ .certificate_request = try CertificateRequest.unpack(hsbuf, ctx) },
.server_hello_done => .{ .server_hello_done = try ServerHelloDone.unpack(hsbuf, ctx) },
.finished => .{ .finished = try Finished.unpack(hsbuf[0..12].*, ctx) },
else => unreachable,
},
};
 
src/root.zig added: 133, removed: 102, total 31
@@ -76,8 +76,7 @@ pub const TLSRecord = struct {
}
 
pub fn decryptFragment(cipher: []const u8, clear: []u8, ctx: *ConnCtx) ![]const u8 {
if (true) unreachable;
return try ctx.cipher.decrypt(cipher, clear);
return clear[0..try ctx.cipher.decrypt(cipher, clear)];
}
 
pub fn unpackFragment(buffer: []const u8, ctx: *ConnCtx) !TLSRecord {
@@ -94,7 +93,7 @@ pub const TLSRecord = struct {
var decrypted: [0x1000]u8 = undefined;
if (length > buffer[5..].len) return error.IncompleteFragment;
const fragbuf: []const u8 = if (ctx.session_encrypted)
try decryptFragment(buffer[5..][0..length], decrypted[0..length], ctx)
try decryptFragment(buffer[5..][0..length], decrypted[0..], ctx)
else
buffer[5..][0..length];
 
@@ -103,7 +102,9 @@ pub const TLSRecord = struct {
.length = length + 5, // Record header size
.kind = switch (fragtype) {
.change_cipher_spec => .{
.change_cipher_spec = if (fragbuf[0] != 1) return error.InvalidCCSPacket else {},
.change_cipher_spec = if (fragbuf[0] != 1) return error.InvalidCCSPacket else {
ctx.session_encrypted = true;
},
},
.alert => .{ .alert = try Alert.unpack(fragbuf) },
.handshake => .{
@@ -280,12 +281,12 @@ fn completeClient(conn: std.net.Stream, ctx: *ConnCtx) !void {
const num2 = try conn.read(&r_buf);
if (false) print("sin: {any}\n", .{r_buf[0..num2]});
const sin2 = try TLSRecord.unpack(r_buf[0..num2], ctx);
if (true) print("server thing {}\n", .{sin2});
if (false) print("server thing {}\n", .{sin2});
 
if (num2 > sin2.length) {
const n_buf = r_buf[sin2.length..];
const sin3 = try TLSRecord.unpack(n_buf, ctx);
if (true) print("server thing {}\n", .{sin3});
if (false) print("server thing {}\n", .{sin3});
} else {
//const num3 = try conn.read(&r_buf);
}