srctree

Gregory Mullen parent c92ca7df cc008969
handshake completes, some key generation, no working encryption

src/cipher.zig added: 184, removed: 65, total 119
@@ -6,12 +6,35 @@ pub const X25519 = std.crypto.dh.X25519;
const print = std.debug.print;
const fixedBufferStream = std.io.fixedBufferStream;
 
//const cipherT = std.crypto.tls.ApplicationCipherT;
const hscipherT = std.crypto.tls.HandshakeCipherT;
const ChaCha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;
const chacha_t = hscipherT(ChaCha20Poly1305, std.crypto.hash.sha2.Sha256);
const hkdfExpandLabel = std.crypto.tls.hkdfExpandLabel;
 
const ChaCha20 = ChaCha20Poly1305;
const Sha256 = std.crypto.auth.hmac.sha2.HmacSha256;
 
pub const Cipher = @This();
 
suite: union(enum) {
invalid: void,
ecc: EllipticCurve,
} = .{ .invalid = {} },
sequence: u72 = 0, // this is chacha20 specific :/
 
pub fn Material(comptime enc: anytype, comptime hmac: anytype) type {
return struct {
premaster: [X25519.shared_length]u8 = [_]u8{0} ** X25519.shared_length,
master: [48]u8 = undefined,
cli_mac: [hmac.mac_length]u8,
srv_mac: [hmac.mac_length]u8,
cli_key: [enc.key_length]u8,
srv_key: [enc.key_length]u8,
cli_iv: [enc.nonce_length]u8,
srv_iv: [enc.nonce_length]u8,
};
}
 
pub const UnsupportedSuites = enum(u16) {
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013,
@@ -67,9 +90,10 @@ pub const Suites = enum(u16) {
pub const EllipticCurve = struct {
curve: Curves = .{ .invalid = {} },
 
srv_material: ?X25519.KeyPair = undefined,
cli_material: ?X25519.KeyPair = null,
premaster: [X25519.shared_length]u8 = [_]u8{0} ** X25519.shared_length,
srv_dh: ?X25519.KeyPair = undefined,
cli_dh: ?X25519.KeyPair = null,
 
material: Material(ChaCha20, Sha256) = undefined,
 
pub const Curves = union(CurveType) {
invalid: void,
@@ -137,21 +161,74 @@ pub const EllipticCurve = struct {
};
}
 
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 seed = "master secret" ++ ctx.our_random ++ ctx.peer_random.?;
//var left = std.crypto.auth.hmac.sha2.HmacSha256.init(ctx.cipher.suite.ecc.premaster);
 
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);
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);
 
material.master = left ++ right[0..16].*;
 
{
const key_seed = "key expansion" ++ ctx.peer_random.? ++ ctx.our_random;
var first: [32]u8 = undefined;
Sha256.create(&first, key_seed, &material.master);
var second: [32]u8 = undefined;
Sha256.create(&second, &first, &material.master);
var third: [32]u8 = undefined;
Sha256.create(&third, &second, &material.master);
var forth: [32]u8 = undefined;
Sha256.create(&forth, &third, &material.master);
var fifth: [32]u8 = undefined;
Sha256.create(&fifth, &forth, &material.master);
 
var p_first: [32]u8 = undefined;
Sha256.create(&p_first, first ++ key_seed, &material.master);
var p_second: [32]u8 = undefined;
Sha256.create(&p_second, second ++ key_seed, &material.master);
var p_third: [32]u8 = undefined;
Sha256.create(&p_third, third ++ key_seed, &material.master);
var p_forth: [32]u8 = undefined;
Sha256.create(&p_forth, forth ++ key_seed, &material.master);
var p_fifth: [32]u8 = undefined;
Sha256.create(&p_fifth, fifth ++ key_seed, &material.master);
const final = p_first ++ p_second ++ p_third ++ p_forth ++ p_fifth;
 
material.cli_mac = final[0..][0..32].*;
material.cli_mac = final[32..][0..32].*;
material.cli_key = final[64..][0..32].*;
material.srv_key = final[96..][0..32].*;
material.srv_iv = final[128..][0..12].*;
material.srv_iv = final[140..][0..12].*;
}
return material;
}
 
fn unpackNamed(buffer: []const u8, ctx: *ConnCtx) !void {
var fba = fixedBufferStream(buffer);
const r = fba.reader().any();
const name = try r.readInt(u16, .big);
if (name != 0x001d) return error.UnknownCurveName;
if (ctx.cipher.suite.ecc.cli_material == null) unreachable;
const our_seckey = ctx.cipher.suite.ecc.cli_material.?.secret_key;
ctx.cipher.suite.ecc.srv_material = undefined;
const peer_key = &ctx.cipher.suite.ecc.srv_material.?.public_key;
ctx.cipher.suite.ecc.srv_dh = undefined;
const peer_key = &ctx.cipher.suite.ecc.srv_dh.?.public_key;
try r.readNoEof(peer_key);
 
ctx.cipher.suite.ecc.premaster = try X25519.scalarmult(our_seckey, peer_key.*);
 
// TODO verify signature
 
ctx.cipher.suite.ecc.material = try buildKeyMaterial(ctx);
}
 
pub fn unpackKeyExchange(buffer: []const u8, ctx: *ConnCtx) !void {
 
src/context.zig added: 184, removed: 65, total 119
@@ -14,11 +14,28 @@ const SessionID = root.SessionID;
pub const ConnCtx = @This();
 
cipher: Cipher = .{},
our_random: [32]u8 = [_]u8{0} ** 32,
peer_random: [32]u8 = [_]u8{0} ** 32,
our_random: [32]u8,
peer_random: ?[32]u8 = null,
session_id: ?SessionID = null,
entity: ConnectionEnd = .{},
prf_algorithm: PRFAlgorithm = .{},
mac_algorithm: MACAlgorithm = .{},
/// Compressed encryption is a mistake...
compression_algorithm: CompressionMethod = null,
// I hate this protocol
handshake_record: std.ArrayList(u8),
 
pub fn initClient(a: std.mem.Allocator) ConnCtx {
var rand: [32]u8 = undefined;
var csprng = std.Random.ChaCha.init([_]u8{0} ** 32);
csprng.fill(&rand);
 
return .{
.our_random = rand,
.handshake_record = std.ArrayList(u8).init(a),
};
}
 
pub fn initServer() ConnCtx {
return .{};
}
 
src/handshake.zig added: 184, removed: 65, total 119
@@ -59,13 +59,12 @@ pub const ClientHello = struct {
 
pub const length = @sizeOf(ClientHello);
 
pub fn init(rand: Random) ClientHello {
pub fn init(ctx: ConnCtx) ClientHello {
var hello = ClientHello{
.random = rand,
.random = ctx.our_random,
.session_id = [_]u8{0} ** 32,
};
 
//csprng.fill(&hello.random.random_bytes);
csprng.fill(&hello.session_id);
return hello;
}
@@ -130,18 +129,25 @@ pub const ClientKeyExchange = struct {
};
 
pub const Finished = struct {
pub fn pack(_: Finished, buffer: []u8) !usize {
pub fn pack(buffer: []u8, ctx: *const ConnCtx) !usize {
var fba = fixedBufferStream(buffer);
var w = fba.writer().any();
 
const iv = [0x10]u8{
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
};
try w.writeAll(&iv);
const encrypted = [_]u8{0} ** 0x30;
var sha = std.crypto.auth.hmac.sha2.HmacSha256.init(&ctx.cipher.suite.ecc.material.master);
sha.update("client finished");
sha.update(ctx.handshake_record.items);
var verify: [32]u8 = undefined;
sha.final(&verify);
 
try w.writeAll(&encrypted);
sha = std.crypto.auth.hmac.sha2.HmacSha256.init(&ctx.cipher.suite.ecc.material.master);
sha.update(&verify);
sha.update("client finished");
sha.update(ctx.handshake_record.items);
sha.final(&verify);
 
defer ctx.handshake_record.deinit();
 
try w.writeAll(&verify);
return 0x40;
}
};
@@ -154,7 +160,7 @@ pub const ServerHello = struct {
compression: Compression,
extensions: []const Extension,
 
pub fn unpack(buffer: []const u8, ctx: *ConnCtx) !ServerHello {
pub fn unpack(buffer: []const u8, ctx: *ConnCtx) !void {
print("buffer:: {any}\n", .{buffer});
var fba = fixedBufferStream(buffer);
const r = fba.reader().any();
@@ -163,8 +169,10 @@ pub const ServerHello = struct {
.major = try r.readByte(),
.minor = try r.readByte(),
};
var random: [32]u8 = undefined;
try r.readNoEof(&random);
std.debug.assert(version.major == 3 and version.minor == 3);
 
ctx.peer_random = undefined;
try r.readNoEof(&ctx.peer_random.?);
 
const session_size = try r.readByte();
var session_id: [32]u8 = [_]u8{0} ** 32;
@@ -195,13 +203,6 @@ pub const ServerHello = struct {
error.EndOfStream => if (false) print("SrvHelo no extensions\n", .{}),
else => return err,
}
 
return .{
.version = version,
.random = random,
.compression = .null,
.extensions = &[0]Extension{},
};
}
};
 
@@ -320,7 +321,7 @@ fn handshakeFromHeader(kind: Type) type {
const Handshakes = union(Type) {
hello_request: void,
client_hello: ClientHello,
server_hello: ServerHello,
server_hello: void,
certificate: Certificate,
server_key_exchange: ServerKeyExchange,
certificate_request: CertificateRequest,
@@ -354,13 +355,13 @@ pub const Handshake = struct {
};
}
 
pub fn pack(hs: Handshake, buffer: []u8) !usize {
pub fn pack(hs: Handshake, buffer: []u8, ctx: *ConnCtx) !usize {
var fba = fixedBufferStream(buffer);
var w = fba.writer().any();
const len = switch (hs.body) {
.client_hello => |ch| try ch.pack(buffer[4..]),
.client_key_exchange => |cke| try cke.pack(buffer[4..]),
.finished => |fin| try fin.pack(buffer[4..]),
.finished => try Finished.pack(buffer[4..], ctx),
else => unreachable,
};
std.debug.assert(len < std.math.maxInt(u24));
 
src/root.zig added: 184, removed: 65, total 119
@@ -3,6 +3,7 @@ const net = std.net;
const testing = std.testing;
const print = std.debug.print;
const fixedBufferStream = std.io.fixedBufferStream;
const asBytes = std.mem.asBytes;
 
const TESTING_IP = "127.0.0.1";
const TESTING_PORT = 4433;
@@ -42,13 +43,16 @@ const TLSRecord = struct {
application_data: []const u8,
},
 
pub fn packFragment(record: TLSRecord, buffer: []u8) !usize {
var fba = fixedBufferStream(buffer);
const len = switch (record.kind) {
.handshake => |ch| try ch.pack(buffer[5..]),
.change_cipher_spec => ChangeCipherSpec.pack(buffer[5..]),
pub fn packFragment(record: TLSRecord, buffer: []u8, ctx: *ConnCtx) !usize {
return switch (record.kind) {
.handshake => |ch| try ch.pack(buffer, ctx),
.change_cipher_spec => ChangeCipherSpec.pack(buffer),
else => unreachable,
};
}
 
fn packHeader(record: TLSRecord, buffer: []u8, len: usize) !usize {
var fba = fixedBufferStream(buffer);
var w = fba.writer().any();
try w.writeByte(@intFromEnum(record.kind));
try w.writeByte(record.version.major);
@@ -57,8 +61,29 @@ const TLSRecord = struct {
return len + 5;
}
 
pub fn pack(record: TLSRecord, buffer: []u8) !usize {
return record.packFragment(buffer);
pub fn pack(record: TLSRecord, buffer: []u8, ctx: *ConnCtx) !usize {
const len = try record.packFragment(buffer[5..], ctx);
return record.packHeader(buffer, len);
}
 
pub fn encrypt(record: TLSRecord, buffer: []u8, ctx: *ConnCtx) !usize {
var clear_buffer: [0x1000]u8 = undefined;
const empty: [0]u8 = undefined;
var iv: [12]u8 = buffer[5..][0..12].*;
for (&iv, ctx.cipher.suite.ecc.material.cli_iv, asBytes(&ctx.cipher.sequence)[4..]) |*dst, src, seq|
dst.* = src ^ seq;
const encrypted_body = buffer[5 + 12 ..];
const len = try record.packFragment(&clear_buffer, ctx);
 
std.crypto.aead.chacha_poly.ChaCha20Poly1305.encrypt(
encrypted_body[0..len],
encrypted_body[len..][0..16],
clear_buffer[0..len],
&empty,
iv,
ctx.cipher.suite.ecc.material.cli_key,
);
return try record.packHeader(buffer, len + 16);
}
 
pub fn unpackFragment(buffer: []const u8, sess: *ConnCtx) !TLSRecord {
@@ -121,39 +146,35 @@ pub const ChangeCipherSpec = struct {
 
test "Handshake ClientHello" {
var buffer = [_]u8{0} ** 0x400;
var csprng = std.Random.ChaCha.init([_]u8{0} ** 32);
var rand = [_]u8{0} ** 32;
csprng.fill(&rand);
const client_hello = Handshake.ClientHello.init(rand);
 
var ctx = ConnCtx.initClient(std.testing.allocator);
const client_hello = Handshake.ClientHello.init(ctx);
const record = TLSRecord{
.kind = .{
.handshake = try Handshake.Handshake.wrap(client_hello),
},
};
 
const len = try record.pack(&buffer);
const len = try record.pack(&buffer, &ctx);
_ = len;
}
 
fn startHandshake(conn: std.net.Stream) !ConnCtx {
var buffer = [_]u8{0} ** 0x1000;
var csprng = std.Random.ChaCha.init([_]u8{0} ** 32);
var rand = [_]u8{0} ** 32;
csprng.fill(&rand);
const client_hello = Handshake.ClientHello.init(rand);
var ctx = ConnCtx.initClient(std.testing.allocator);
const client_hello = Handshake.ClientHello.init(ctx);
const record = TLSRecord{
.kind = .{
.handshake = try Handshake.Handshake.wrap(client_hello),
},
};
 
const len = try record.pack(&buffer);
const len = try record.pack(&buffer, &ctx);
const dout = try conn.write(buffer[0..len]);
try ctx.handshake_record.appendSlice(buffer[5 .. len - 5]);
if (false) print("data count {}\n", .{dout});
if (false) print("data out {any}\n", .{buffer[0..len]});
return .{
.our_random = client_hello.random,
};
return ctx;
}
 
/// Forgive me, I'm tired
@@ -174,6 +195,9 @@ fn buildServer(data: []const u8, ctx: *ConnCtx) !void {
if (false) print("server block\n{any}\n", .{next_block});
const tlsr = try TLSRecord.unpack(next_block, ctx);
if (false) print("mock {}\n", .{tlsr.length});
if (next_block.len > 6)
try ctx.handshake_record.appendSlice(next_block[5..][0..tlsr.length]);
 
next_block = next_block[tlsr.length + 5 ..];
 
switch (tlsr.kind) {
@@ -182,12 +206,11 @@ fn buildServer(data: []const u8, ctx: *ConnCtx) !void {
switch (hs.body) {
.server_hello => |hello| {
print("server hello {}\n", .{@TypeOf(hello)});
print("srv selected suite {any}\n", .{hello.cipher});
print("test selected suite {any}\n", .{.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256});
print("srv selected suite {any}\n", .{ctx.cipher});
if (ctx.cipher.suite != .ecc) {
return error.UnexpectedCipherSuite;
}
ctx.cipher.suite.ecc.cli_material = try Cipher.X25519.KeyPair.create(null);
ctx.cipher.suite.ecc.cli_dh = try Cipher.X25519.KeyPair.create(null);
},
.certificate => |cert| {
print("server cert {}\n", .{@TypeOf(cert)});
@@ -218,8 +241,9 @@ fn completeClient(conn: std.net.Stream, ctx: *ConnCtx) !void {
},
};
 
const cke_len = try cke_record.pack(&buffer);
const cke_len = try cke_record.pack(&buffer, ctx);
try std.testing.expectEqual(42, cke_len);
try ctx.handshake_record.appendSlice(buffer[5 .. cke_len - 5]);
print("CKE: {any}\n", .{buffer[0..cke_len]});
const ckeout = try conn.write(buffer[0..cke_len]);
if (true) print("cke delivered, {}\n", .{ckeout});
@@ -235,7 +259,7 @@ fn completeClient(conn: std.net.Stream, ctx: *ConnCtx) !void {
const ccs_record = TLSRecord{
.kind = .{ .change_cipher_spec = {} },
};
const ccs_len = try ccs_record.pack(&buffer);
const ccs_len = try ccs_record.pack(&buffer, ctx);
const ccsout = try conn.write(buffer[0..ccs_len]);
try std.testing.expectEqual(6, ccsout);
 
@@ -246,7 +270,7 @@ fn completeClient(conn: std.net.Stream, ctx: *ConnCtx) !void {
.handshake = try Handshake.Handshake.wrap(fin),
},
};
const fin_len = try fin_record.pack(&buffer);
const fin_len = try fin_record.encrypt(&buffer, ctx);
print("fin: {any}\n", .{buffer[0..fin_len]});
const finout = try conn.write(buffer[0..fin_len]);
if (true) print("fin delivered, {}\n", .{finout});