srctree

Gregory Mullen parent dd31f73a 8d3deabe
y u no support named ecdhe openssl

src/cipher.zig added: 192, removed: 62, total 130
@@ -1,5 +1,6 @@
const std = @import("std");
const ConnCtx = @import("context.zig");
const Version = @import("protocol.zig");
 
pub const X25519 = std.crypto.dh.X25519;
 
@@ -88,11 +89,14 @@ pub const Suites = enum(u16) {
TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A,
TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D,
TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018,
// Planned to implement
 
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAC,
TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAB,
TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAE,
 
/// TODO
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC014,
 
///Current Supported
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8,
@@ -122,6 +126,14 @@ pub const Suites = enum(u16) {
 
pub const AnyAES = struct {
material: Material(AES(CBC), Sha256),
 
block: union(enum) {
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 .{
@@ -129,7 +141,65 @@ pub const AnyAES = struct {
};
}
 
fn packNamed(_: EllipticCurve, _: []u8) !usize {}
fn packEncryptedPremasterSecret(aes: AnyAES, buffer: []u8) !usize {
var l_buffer: [1024]u8 = undefined;
var fba = fixedBufferStream(l_buffer[0..]);
const w = fba.writer().any();
 
try w.writeByte(Version.Current.major);
try w.writeByte(Version.Current.minor);
 
const random: [46]u8 = [_]u8{0} ** 46;
try w.writeAll(&random);
switch (aes.block) {
.cbc => try CBC.decrypt(aes.material.cli_key, l_buffer[0..48], buffer),
}
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);
//const w = fba.writer().any();
return try aes.packClientECDHE(buffer);
//return try aes.packEncryptedPremasterSecret(buffer);
//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(CBC), Sha256) {
var aes = &ctx.cipher.suite.aes;
 
@@ -193,16 +263,30 @@ pub const AnyAES = struct {
ctx.cipher.suite.aes.material = try buildKeyMaterial(ctx);
}
 
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;
ctx.cipher.suite.aes.srv_dh = undefined;
const peer_key = &ctx.cipher.suite.aes.srv_dh.?.public_key;
try r.readNoEof(peer_key);
 
// TODO verify signature
 
ctx.cipher.suite.aes.material = try buildKeyMaterial(ctx);
}
 
pub fn unpackKeyExchange(buffer: []const u8, ctx: *ConnCtx) !void {
if (ctx.cipher.suite != .aes) unreachable;
//var fba = fixedBufferStream(buffer);
//const r = fba.reader().any();
var fba = fixedBufferStream(buffer);
const r = fba.reader().any();
 
//const curve_type = try CurveType.fromByte(try r.readByte());
//switch (curve_type) {
// .named_curve => try unpackNamed(buffer[1..], ctx),
// else => return error.UnsupportedCurve,
//}
const curve_type = try CurveType.fromByte(try r.readByte());
switch (curve_type) {
.named_curve => try unpackNamed(buffer[1..], ctx),
else => return error.UnsupportedCurve,
}
return unpackCBC(buffer, ctx);
}
};
@@ -210,6 +294,27 @@ pub const AnyAES = struct {
pub const CBC = struct {
pub const key_length = 32;
pub const nonce_length = 0;
 
pub fn decrypt(key: [32]u8, cipher: []const u8, clear: []u8) !void {
if (cipher.len % 16 != 0) return error.InvalidCipherLength;
if (clear.len < cipher.len) return error.InvalidClearLength;
var ctx = std.crypto.core.aes.Aes256.initDec(key);
const blocks = cipher.len / 16;
var i: usize = 0;
while (i < blocks) : (i += 1) {
ctx.decrypt(
clear[i * 16 ..][0..16],
cipher[i * 16 ..][0..16],
);
}
}
 
pub fn encrypt(key: []const u8, cipher: []const u8, clear: []u8) !void {
_ = key;
_ = clear;
_ = cipher;
comptime unreachable;
}
};
 
//pub const AESType = enum {
@@ -223,40 +328,39 @@ pub fn AES(comptime T: type) type {
pub const nonce_length = T.nonce_length;
};
}
pub const Curves = union(CurveType) {
invalid: void,
explicit_prime: ExplicitPrime,
explicit_char2: ExplicitChar2,
named_curve: NamedCurve,
};
 
pub const CurveType = enum(u8) {
invalid = 0,
explicit_prime = 1,
explicit_char2 = 2,
named_curve = 3,
 
pub fn fromByte(t: u8) !CurveType {
return switch (t) {
inline 0...3 => |i| @enumFromInt(i),
else => return error.InvalidECCCurveType,
};
}
};
 
pub const ExplicitPrime = struct {};
pub const ExplicitChar2 = struct {};
pub const NamedCurve = struct {};
 
pub const EllipticCurve = struct {
curve: Curves = .{ .invalid = {} },
 
srv_dh: ?X25519.KeyPair = undefined,
cli_dh: ?X25519.KeyPair = null,
srv_dh: ?X25519.KeyPair = null,
 
material: Material(ChaCha20, Sha256) = undefined,
 
pub const Curves = union(CurveType) {
invalid: void,
explicit_prime: ExplicitPrime,
explicit_char2: ExplicitChar2,
named_curve: NamedCurve,
};
 
pub const CurveType = enum(u8) {
invalid = 0,
explicit_prime = 1,
explicit_char2 = 2,
named_curve = 3,
 
pub fn fromByte(t: u8) !CurveType {
return switch (t) {
inline 0...3 => |i| @enumFromInt(i),
else => return error.InvalidECCCurveType,
};
}
};
 
pub const ExplicitPrime = struct {};
pub const ExplicitChar2 = struct {};
pub const NamedCurve = struct {};
 
/// srv is copied, and will not zero any arguments
pub fn init(srv: [X25519.public_length]u8) !EllipticCurve {
return .{
 
src/handshake.zig added: 192, removed: 62, total 130
@@ -123,6 +123,7 @@ 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,
};
}
@@ -133,18 +134,25 @@ pub const Finished = struct {
var fba = fixedBufferStream(buffer);
var w = fba.writer().any();
 
var sha = std.crypto.auth.hmac.sha2.HmacSha256.init(&ctx.cipher.suite.ecc.material.master);
const master = switch (ctx.cipher.suite) {
.ecc => |ecc| ecc.material.master,
.aes => |aes| aes.material.master,
else => unreachable,
};
 
var sha = std.crypto.auth.hmac.sha2.HmacSha256.init(&master);
sha.update("client finished");
sha.update(ctx.handshake_record.items);
var verify: [32]u8 = undefined;
sha.final(&verify);
 
sha = std.crypto.auth.hmac.sha2.HmacSha256.init(&ctx.cipher.suite.ecc.material.master);
sha = std.crypto.auth.hmac.sha2.HmacSha256.init(&master);
sha.update(&verify);
sha.update("client finished");
sha.update(ctx.handshake_record.items);
sha.final(&verify);
 
// TODO do I need to zero this struct?
defer ctx.handshake_record.deinit();
 
try w.writeAll(&verify);
@@ -193,6 +201,7 @@ pub const ServerHello = struct {
.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
=> {
ctx.cipher.suite = .{ .aes = undefined };
},
@@ -229,7 +238,10 @@ pub const ServerKeyExchange = struct {
ctx.cipher.suite.ecc.cli_dh = try Cipher.X25519.KeyPair.create(null);
try Cipher.EllipticCurve.unpackKeyExchange(buffer, ctx);
},
.aes => try Cipher.AnyAES.unpackKeyExchange(buffer, ctx),
.aes => {
ctx.cipher.suite.aes.cli_dh = try Cipher.X25519.KeyPair.create(null);
try Cipher.AnyAES.unpackKeyExchange(buffer, ctx);
},
else => unreachable,
}
return .{
@@ -380,21 +392,28 @@ pub const Handshake = struct {
 
try w.writeByte(@intFromEnum(hs.msg_type));
try w.writeInt(u24, @truncate(len), std.builtin.Endian.big);
if (hs.body == .client_hello)
try ctx.handshake_record.appendSlice(buffer[0 .. len + 4]);
return len + 4;
}
 
pub fn unpack(buffer: []const u8, sess: *ConnCtx) !Handshake {
pub fn unpack(buffer: []const u8, ctx: *ConnCtx) !Handshake {
const hs_type = try Type.fromByte(buffer[0]);
const hsbuf = buffer[4..];
const len = std.mem.readInt(u24, buffer[1..4], .big);
 
// TODO choose real assert length
std.debug.assert(len < 1024);
try ctx.handshake_record.appendSlice(buffer[0 .. len + 4]);
const hsbuf = buffer[4..][0..len];
return .{
.msg_type = hs_type,
.body = switch (hs_type) {
.client_hello => .{ .client_hello = try ClientHello.unpack(hsbuf, sess) },
.server_hello => .{ .server_hello = try ServerHello.unpack(hsbuf, sess) },
.certificate => .{ .certificate = try Certificate.unpack(hsbuf, sess) },
.server_key_exchange => .{ .server_key_exchange = try ServerKeyExchange.unpack(hsbuf, sess) },
.certificate_request => .{ .certificate_request = try CertificateRequest.unpack(hsbuf, sess) },
.server_hello_done => .{ .server_hello_done = try ServerHelloDone.unpack(hsbuf, sess) },
.client_hello => .{ .client_hello = try ClientHello.unpack(hsbuf, ctx) },
.server_hello => .{ .server_hello = try ServerHello.unpack(hsbuf, ctx) },
.certificate => .{ .certificate = try Certificate.unpack(hsbuf, ctx) },
.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) },
else => unreachable,
},
};
 
src/protocol.zig added: 192, removed: 62, total 130
@@ -12,3 +12,5 @@ pub const TLSv1_3: Version = .{
.major = 3,
.minor = 4,
};
 
pub const Current = TLSv1_2;
 
src/root.zig added: 192, removed: 62, total 130
@@ -76,14 +76,21 @@ const TLSRecord = struct {
// dst.* = iv ^ seq;
const encrypted_body = buffer[5..];
 
std.crypto.aead.chacha_poly.ChaCha20Poly1305.encrypt(
encrypted_body[0..len],
encrypted_body[len..][0..16],
clear_buffer[0..len],
&empty,
ctx.cipher.suite.ecc.material.cli_iv,
ctx.cipher.suite.ecc.material.cli_key,
);
switch (ctx.cipher.suite) {
.ecc => {
std.crypto.aead.chacha_poly.ChaCha20Poly1305.encrypt(
encrypted_body[0..len],
encrypted_body[len..][0..16],
clear_buffer[0..len],
&empty,
ctx.cipher.suite.ecc.material.cli_iv,
ctx.cipher.suite.ecc.material.cli_key,
);
},
.aes => |_| {},
else => unreachable,
}
 
//print("biv {any}\n", .{buffer[5..][0..12]});
//print("encrypted {any}\n", .{encrypted_body[0..len]});
//print("tag {any}\n", .{encrypted_body[len .. len + 16]});
@@ -162,6 +169,7 @@ test "Handshake ClientHello" {
 
const len = try record.pack(&buffer, &ctx);
_ = len;
defer ctx.handshake_record.deinit();
}
 
fn startHandshakeCustomSuites(conn: std.net.Stream, suites: []const Cipher.Suites) !ConnCtx {
@@ -209,8 +217,6 @@ 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 ..];
 
@@ -255,8 +261,7 @@ fn completeClient(conn: std.net.Stream, ctx: *ConnCtx) !void {
};
 
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]);
//try std.testing.expectEqual(42, cke_len);
if (false) print("CKE: {any}\n", .{buffer[0..cke_len]});
const ckeout = try conn.write(buffer[0..cke_len]);
if (false) print("cke delivered, {}\n", .{ckeout});