Merge pull request #21872 from jacobly0/tlsv1.2
Some checks failed
ci / x86_64-linux-debug (push) Has been cancelled
ci / x86_64-linux-release (push) Has been cancelled
ci / aarch64-linux-debug (push) Has been cancelled
ci / aarch64-linux-release (push) Has been cancelled
ci / x86_64-macos-release (push) Has been cancelled
ci / aarch64-macos-debug (push) Has been cancelled
ci / aarch64-macos-release (push) Has been cancelled
ci / x86_64-windows-debug (push) Has been cancelled
ci / x86_64-windows-release (push) Has been cancelled
ci / aarch64-windows (push) Has been cancelled

std.crypto.tls: implement TLSv1.2
This commit is contained in:
Jacob Young 2024-11-08 02:01:52 -05:00 committed by GitHub
commit e5f5229fd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1808 additions and 935 deletions

View File

@ -151,7 +151,9 @@ pub const Ed25519 = struct {
a: Curve,
expected_r: Curve,
fn init(sig: Signature, public_key: PublicKey) (NonCanonicalError || EncodingError || IdentityElementError)!Verifier {
pub const InitError = NonCanonicalError || EncodingError || IdentityElementError;
fn init(sig: Signature, public_key: PublicKey) InitError!Verifier {
const r = sig.r;
const s = sig.s;
try Curve.scalar.rejectNonCanonical(s);
@ -173,8 +175,11 @@ pub const Ed25519 = struct {
self.h.update(msg);
}
pub const VerifyError = WeakPublicKeyError || IdentityElementError ||
SignatureVerificationError;
/// Verify that the signature is valid for the entire message.
pub fn verify(self: *Verifier) (SignatureVerificationError || WeakPublicKeyError || IdentityElementError)!void {
pub fn verify(self: *Verifier) VerifyError!void {
var hram64: [Sha512.digest_length]u8 = undefined;
self.h.final(&hram64);
const hram = Curve.scalar.reduce64(hram64);
@ -197,10 +202,10 @@ pub const Ed25519 = struct {
s: CompressedScalar,
/// Return the raw signature (r, s) in little-endian format.
pub fn toBytes(self: Signature) [encoded_length]u8 {
pub fn toBytes(sig: Signature) [encoded_length]u8 {
var bytes: [encoded_length]u8 = undefined;
bytes[0..Curve.encoded_length].* = self.r;
bytes[Curve.encoded_length..].* = self.s;
bytes[0..Curve.encoded_length].* = sig.r;
bytes[Curve.encoded_length..].* = sig.s;
return bytes;
}
@ -214,17 +219,19 @@ pub const Ed25519 = struct {
}
/// Create a Verifier for incremental verification of a signature.
pub fn verifier(self: Signature, public_key: PublicKey) (NonCanonicalError || EncodingError || IdentityElementError)!Verifier {
return Verifier.init(self, public_key);
pub fn verifier(sig: Signature, public_key: PublicKey) Verifier.InitError!Verifier {
return Verifier.init(sig, public_key);
}
pub const VerifyError = Verifier.InitError || Verifier.VerifyError;
/// Verify the signature against a message and public key.
/// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
/// or SignatureVerificationError if the signature is invalid for the given message and key.
pub fn verify(self: Signature, msg: []const u8, public_key: PublicKey) (IdentityElementError || NonCanonicalError || SignatureVerificationError || EncodingError || WeakPublicKeyError)!void {
var st = try Verifier.init(self, public_key);
pub fn verify(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {
var st = try sig.verifier(public_key);
st.update(msg);
return st.verify();
try st.verify();
}
};

View File

@ -20,18 +20,18 @@ pub const Algorithm = enum {
curveEd25519,
pub const map = std.StaticStringMap(Algorithm).initComptime(.{
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption },
.{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
.{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
.{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
.{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption },
.{ &.{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
});
pub fn Hash(comptime algorithm: Algorithm) type {
@ -49,13 +49,15 @@ pub const Algorithm = enum {
pub const AlgorithmCategory = enum {
rsaEncryption,
rsassa_pss,
X9_62_id_ecPublicKey,
curveEd25519,
pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
.{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A }, .rsassa_pss },
.{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
.{ &.{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
});
};
@ -74,18 +76,18 @@ pub const Attribute = enum {
domainComponent,
pub const map = std.StaticStringMap(Attribute).initComptime(.{
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
.{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
.{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
.{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
.{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
.{ &[_]u8{ 0x55, 0x04, 0x09 }, .streetAddress },
.{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
.{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
.{ &[_]u8{ 0x55, 0x04, 0x11 }, .postalCode },
.{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
.{ &[_]u8{ 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 }, .domainComponent },
.{ &.{ 0x55, 0x04, 0x03 }, .commonName },
.{ &.{ 0x55, 0x04, 0x05 }, .serialNumber },
.{ &.{ 0x55, 0x04, 0x06 }, .countryName },
.{ &.{ 0x55, 0x04, 0x07 }, .localityName },
.{ &.{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
.{ &.{ 0x55, 0x04, 0x09 }, .streetAddress },
.{ &.{ 0x55, 0x04, 0x0A }, .organizationName },
.{ &.{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
.{ &.{ 0x55, 0x04, 0x11 }, .postalCode },
.{ &.{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
.{ &.{ 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 }, .domainComponent },
});
};
@ -95,9 +97,9 @@ pub const NamedCurve = enum {
X9_62_prime256v1,
pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
.{ &.{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
.{ &.{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
.{ &.{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
});
pub fn Curve(comptime curve: NamedCurve) type {
@ -131,28 +133,28 @@ pub const ExtensionId = enum {
netscape_comment,
pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
.{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
.{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
.{ &[_]u8{ 0x55, 0x1D, 0x0E }, .subject_key_identifier },
.{ &[_]u8{ 0x55, 0x1D, 0x0F }, .key_usage },
.{ &[_]u8{ 0x55, 0x1D, 0x0A }, .basic_constraints },
.{ &[_]u8{ 0x55, 0x1D, 0x10 }, .private_key_usage_period },
.{ &[_]u8{ 0x55, 0x1D, 0x11 }, .subject_alt_name },
.{ &[_]u8{ 0x55, 0x1D, 0x12 }, .issuer_alt_name },
.{ &[_]u8{ 0x55, 0x1D, 0x13 }, .basic_constraints },
.{ &[_]u8{ 0x55, 0x1D, 0x14 }, .crl_number },
.{ &[_]u8{ 0x55, 0x1D, 0x1F }, .crl_distribution_points },
.{ &[_]u8{ 0x55, 0x1D, 0x20 }, .certificate_policies },
.{ &[_]u8{ 0x55, 0x1D, 0x23 }, .authority_key_identifier },
.{ &[_]u8{ 0x55, 0x1D, 0x25 }, .ext_key_usage },
.{ &[_]u8{ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01 }, .msCertsrvCAVersion },
.{ &[_]u8{ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }, .info_access },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF6, 0x7D, 0x07, 0x41, 0x00 }, .entrustVersInfo },
.{ &[_]u8{ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02 }, .enroll_certtype },
.{ &[_]u8{ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c }, .pe_logotype },
.{ &[_]u8{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01 }, .netscape_cert_type },
.{ &[_]u8{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d }, .netscape_comment },
.{ &.{ 0x55, 0x04, 0x03 }, .commonName },
.{ &.{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
.{ &.{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
.{ &.{ 0x55, 0x1D, 0x0E }, .subject_key_identifier },
.{ &.{ 0x55, 0x1D, 0x0F }, .key_usage },
.{ &.{ 0x55, 0x1D, 0x0A }, .basic_constraints },
.{ &.{ 0x55, 0x1D, 0x10 }, .private_key_usage_period },
.{ &.{ 0x55, 0x1D, 0x11 }, .subject_alt_name },
.{ &.{ 0x55, 0x1D, 0x12 }, .issuer_alt_name },
.{ &.{ 0x55, 0x1D, 0x13 }, .basic_constraints },
.{ &.{ 0x55, 0x1D, 0x14 }, .crl_number },
.{ &.{ 0x55, 0x1D, 0x1F }, .crl_distribution_points },
.{ &.{ 0x55, 0x1D, 0x20 }, .certificate_policies },
.{ &.{ 0x55, 0x1D, 0x23 }, .authority_key_identifier },
.{ &.{ 0x55, 0x1D, 0x25 }, .ext_key_usage },
.{ &.{ 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01 }, .msCertsrvCAVersion },
.{ &.{ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }, .info_access },
.{ &.{ 0x2A, 0x86, 0x48, 0x86, 0xF6, 0x7D, 0x07, 0x41, 0x00 }, .entrustVersInfo },
.{ &.{ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02 }, .enroll_certtype },
.{ &.{ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c }, .pe_logotype },
.{ &.{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01 }, .netscape_cert_type },
.{ &.{ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d }, .netscape_comment },
});
};
@ -185,6 +187,7 @@ pub const Parsed = struct {
pub const PubKeyAlgo = union(AlgorithmCategory) {
rsaEncryption: void,
rsassa_pss: void,
X9_62_id_ecPublicKey: NamedCurve,
curveEd25519: void,
};
@ -386,7 +389,7 @@ test "Parsed.checkHostName" {
try expectEqual(true, Parsed.checkHostName("bar.ziglang.org", "*.Ziglang.ORG"));
}
pub const ParseError = der.Element.ParseElementError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError;
pub const ParseError = der.Element.ParseError || ParseVersionError || ParseTimeError || ParseEnumError || ParseBitStringError;
pub fn parse(cert: Certificate) ParseError!Parsed {
const cert_bytes = cert.buffer;
@ -413,13 +416,9 @@ pub fn parse(cert: Certificate) ParseError!Parsed {
const pub_key_info = try der.Element.parse(cert_bytes, subject.slice.end);
const pub_key_signature_algorithm = try der.Element.parse(cert_bytes, pub_key_info.slice.start);
const pub_key_algo_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.start);
const pub_key_algo_tag = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);
var pub_key_algo: Parsed.PubKeyAlgo = undefined;
switch (pub_key_algo_tag) {
.rsaEncryption => {
pub_key_algo = .{ .rsaEncryption = {} };
},
.X9_62_id_ecPublicKey => {
const pub_key_algo: Parsed.PubKeyAlgo = switch (try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem)) {
inline else => |tag| @unionInit(Parsed.PubKeyAlgo, @tagName(tag), {}),
.X9_62_id_ecPublicKey => pub_key_algo: {
// RFC 5480 Section 2.1.1.1 Named Curve
// ECParameters ::= CHOICE {
// namedCurve OBJECT IDENTIFIER
@ -428,12 +427,9 @@ pub fn parse(cert: Certificate) ParseError!Parsed {
// }
const params_elem = try der.Element.parse(cert_bytes, pub_key_algo_elem.slice.end);
const named_curve = try parseNamedCurve(cert_bytes, params_elem);
pub_key_algo = .{ .X9_62_id_ecPublicKey = named_curve };
break :pub_key_algo .{ .X9_62_id_ecPublicKey = named_curve };
},
.curveEd25519 => {
pub_key_algo = .{ .curveEd25519 = {} };
},
}
};
const pub_key_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.end);
const pub_key = try parseBitString(cert, pub_key_elem);
@ -731,7 +727,7 @@ pub fn parseVersion(bytes: []const u8, version_elem: der.Element) ParseVersionEr
fn verifyRsa(
comptime Hash: type,
message: []const u8,
msg: []const u8,
sig: []const u8,
pub_key_algo: Parsed.PubKeyAlgo,
pub_key: []const u8,
@ -743,59 +739,14 @@ fn verifyRsa(
if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid;
if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength;
const hash_der = switch (Hash) {
crypto.hash.Sha1 => [_]u8{
0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
},
crypto.hash.sha2.Sha224 => [_]u8{
0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
0x00, 0x04, 0x1c,
},
crypto.hash.sha2.Sha256 => [_]u8{
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
},
crypto.hash.sha2.Sha384 => [_]u8{
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30,
},
crypto.hash.sha2.Sha512 => [_]u8{
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40,
},
else => @compileError("unreachable"),
};
var msg_hashed: [Hash.digest_length]u8 = undefined;
Hash.hash(message, &msg_hashed, .{});
switch (modulus.len) {
inline 128, 256, 384, 512 => |modulus_len| {
const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3;
const em: [modulus_len]u8 =
[2]u8{ 0, 1 } ++
([1]u8{0xff} ** ps_len) ++
[1]u8{0} ++
hash_der ++
msg_hashed;
const public_key = rsa.PublicKey.fromBytes(exponent, modulus) catch return error.CertificateSignatureInvalid;
const em_dec = rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key) catch |err| switch (err) {
error.MessageTooLong => unreachable,
};
if (!mem.eql(u8, &em, &em_dec)) {
const public_key = rsa.PublicKey.fromBytes(exponent, modulus) catch
return error.CertificateSignatureInvalid;
rsa.PKCS1v1_5Signature.verify(modulus_len, sig[0..modulus_len].*, msg, public_key, Hash) catch
return error.CertificateSignatureInvalid;
}
},
else => {
return error.CertificateSignatureUnsupportedBitCount;
},
else => return error.CertificateSignatureUnsupportedBitCount,
}
}
@ -908,9 +859,9 @@ pub const der = struct {
pub const empty: Slice = .{ .start = 0, .end = 0 };
};
pub const ParseElementError = error{CertificateFieldHasInvalidLength};
pub const ParseError = error{CertificateFieldHasInvalidLength};
pub fn parse(bytes: []const u8, index: u32) ParseElementError!Element {
pub fn parse(bytes: []const u8, index: u32) Element.ParseError!Element {
var i = index;
const identifier = @as(Identifier, @bitCast(bytes[i]));
i += 1;
@ -958,21 +909,41 @@ pub const rsa = struct {
const Modulus = std.crypto.ff.Modulus(max_modulus_bits);
const Fe = Modulus.Fe;
/// RFC 3447 8.1 RSASSA-PSS
pub const PSSSignature = struct {
pub fn fromBytes(comptime modulus_len: usize, msg: []const u8) [modulus_len]u8 {
var result = [1]u8{0} ** modulus_len;
std.mem.copyForwards(u8, &result, msg);
var result: [modulus_len]u8 = undefined;
@memcpy(result[0..msg.len], msg);
@memset(result[msg.len..], 0);
return result;
}
pub fn verify(comptime modulus_len: usize, sig: [modulus_len]u8, msg: []const u8, public_key: PublicKey, comptime Hash: type) !void {
pub const VerifyError = EncryptError || error{InvalidSignature};
pub fn verify(
comptime modulus_len: usize,
sig: [modulus_len]u8,
msg: []const u8,
public_key: PublicKey,
comptime Hash: type,
) VerifyError!void {
try concatVerify(modulus_len, sig, &.{msg}, public_key, Hash);
}
pub fn concatVerify(
comptime modulus_len: usize,
sig: [modulus_len]u8,
msg: []const []const u8,
public_key: PublicKey,
comptime Hash: type,
) VerifyError!void {
const mod_bits = public_key.n.bits();
const em_dec = try encrypt(modulus_len, sig, public_key);
EMSA_PSS_VERIFY(msg, &em_dec, mod_bits - 1, Hash.digest_length, Hash) catch unreachable;
try EMSA_PSS_VERIFY(msg, &em_dec, mod_bits - 1, Hash.digest_length, Hash);
}
fn EMSA_PSS_VERIFY(msg: []const u8, em: []const u8, emBit: usize, sLen: usize, comptime Hash: type) !void {
fn EMSA_PSS_VERIFY(msg: []const []const u8, em: []const u8, emBit: usize, sLen: usize, comptime Hash: type) VerifyError!void {
// 1. If the length of M is greater than the input limitation for
// the hash function (2^61 - 1 octets for SHA-1), output
// "inconsistent" and stop.
@ -986,7 +957,11 @@ pub const rsa = struct {
// 2. Let mHash = Hash(M), an octet string of length hLen.
var mHash: [Hash.digest_length]u8 = undefined;
Hash.hash(msg, &mHash, .{});
{
var hasher: Hash = .init(.{});
for (msg) |part| hasher.update(part);
hasher.final(&mHash);
}
// 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop.
if (emLen < Hash.digest_length + sLen + 2) {
@ -1082,25 +1057,14 @@ pub const rsa = struct {
}
fn MGF1(comptime Hash: type, out: []u8, seed: *const [Hash.digest_length]u8, len: usize) ![]u8 {
var counter: usize = 0;
var counter: u32 = 0;
var idx: usize = 0;
var c: [4]u8 = undefined;
var hash: [Hash.digest_length + c.len]u8 = undefined;
@memcpy(hash[0..Hash.digest_length], seed);
var hashed: [Hash.digest_length]u8 = undefined;
var hash = seed.* ++ @as([4]u8, undefined);
while (idx < len) {
c[0] = @as(u8, @intCast((counter >> 24) & 0xFF));
c[1] = @as(u8, @intCast((counter >> 16) & 0xFF));
c[2] = @as(u8, @intCast((counter >> 8) & 0xFF));
c[3] = @as(u8, @intCast(counter & 0xFF));
std.mem.copyForwards(u8, hash[seed.len..], &c);
Hash.hash(&hash, &hashed, .{});
std.mem.copyForwards(u8, out[idx..], &hashed);
idx += hashed.len;
std.mem.writeInt(u32, hash[seed.len..][0..4], counter, .big);
Hash.hash(&hash, out[idx..][0..Hash.digest_length], .{});
idx += Hash.digest_length;
counter += 1;
}
@ -1108,11 +1072,128 @@ pub const rsa = struct {
}
};
/// RFC 3447 8.2 RSASSA-PKCS1-v1_5
pub const PKCS1v1_5Signature = struct {
pub fn fromBytes(comptime modulus_len: usize, msg: []const u8) [modulus_len]u8 {
var result: [modulus_len]u8 = undefined;
@memcpy(result[0..msg.len], msg);
@memset(result[msg.len..], 0);
return result;
}
pub const VerifyError = EncryptError || error{InvalidSignature};
pub fn verify(
comptime modulus_len: usize,
sig: [modulus_len]u8,
msg: []const u8,
public_key: PublicKey,
comptime Hash: type,
) VerifyError!void {
try concatVerify(modulus_len, sig, &.{msg}, public_key, Hash);
}
pub fn concatVerify(
comptime modulus_len: usize,
sig: [modulus_len]u8,
msg: []const []const u8,
public_key: PublicKey,
comptime Hash: type,
) VerifyError!void {
const em_dec = try encrypt(modulus_len, sig, public_key);
const em = try EMSA_PKCS1_V1_5_ENCODE(msg, modulus_len, Hash);
if (!std.mem.eql(u8, &em_dec, &em)) return error.InvalidSignature;
}
fn EMSA_PKCS1_V1_5_ENCODE(msg: []const []const u8, comptime emLen: usize, comptime Hash: type) VerifyError![emLen]u8 {
comptime var em_index = emLen;
var em: [emLen]u8 = undefined;
// 1. Apply the hash function to the message M to produce a hash value
// H:
//
// H = Hash(M).
//
// If the hash function outputs "message too long," output "message
// too long" and stop.
var hasher: Hash = .init(.{});
for (msg) |part| hasher.update(part);
em_index -= Hash.digest_length;
hasher.final(em[em_index..]);
// 2. Encode the algorithm ID for the hash function and the hash value
// into an ASN.1 value of type DigestInfo (see Appendix A.2.4) with
// the Distinguished Encoding Rules (DER), where the type DigestInfo
// has the syntax
//
// DigestInfo ::= SEQUENCE {
// digestAlgorithm AlgorithmIdentifier,
// digest OCTET STRING
// }
//
// The first field identifies the hash function and the second
// contains the hash value. Let T be the DER encoding of the
// DigestInfo value (see the notes below) and let tLen be the length
// in octets of T.
const hash_der: []const u8 = &switch (Hash) {
crypto.hash.Sha1 => .{
0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
},
crypto.hash.sha2.Sha224 => .{
0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
0x00, 0x04, 0x1c,
},
crypto.hash.sha2.Sha256 => .{
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20,
},
crypto.hash.sha2.Sha384 => .{
0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30,
},
crypto.hash.sha2.Sha512 => .{
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40,
},
else => @compileError("unreachable"),
};
em_index -= hash_der.len;
@memcpy(em[em_index..][0..hash_der.len], hash_der);
// 3. If emLen < tLen + 11, output "intended encoded message length too
// short" and stop.
// 4. Generate an octet string PS consisting of emLen - tLen - 3 octets
// with hexadecimal value 0xff. The length of PS will be at least 8
// octets.
em_index -= 1;
@memset(em[2..em_index], 0xff);
// 5. Concatenate PS, the DER encoding T, and other padding to form the
// encoded message EM as
//
// EM = 0x00 || 0x01 || PS || 0x00 || T.
em[em_index] = 0x00;
em[1] = 0x01;
em[0] = 0x00;
// 6. Output EM.
return em;
}
};
pub const PublicKey = struct {
n: Modulus,
e: Fe,
pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8) !PublicKey {
pub const FromBytesError = error{CertificatePublicKeyInvalid};
pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8) FromBytesError!PublicKey {
// Reject modulus below 512 bits.
// 512-bit RSA was factored in 1999, so this limit barely means anything,
// but establish some limit now to ratchet in what we can.
@ -1137,7 +1218,9 @@ pub const rsa = struct {
};
}
pub fn parseDer(pub_key: []const u8) !struct { modulus: []const u8, exponent: []const u8 } {
pub const ParseDerError = der.Element.ParseError || error{CertificateFieldHasWrongDataType};
pub fn parseDer(pub_key: []const u8) ParseDerError!struct { modulus: []const u8, exponent: []const u8 } {
const pub_key_seq = try der.Element.parse(pub_key, 0);
if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
const modulus_elem = try der.Element.parse(pub_key, pub_key_seq.slice.start);
@ -1156,7 +1239,9 @@ pub const rsa = struct {
}
};
fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey) ![modulus_len]u8 {
const EncryptError = error{MessageTooLong};
fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey) EncryptError![modulus_len]u8 {
const m = Fe.fromBytes(public_key.n, &msg, .big) catch return error.MessageTooLong;
const e = public_key.n.powPublic(m, public_key.e) catch unreachable;
var res: [modulus_len]u8 = undefined;

View File

@ -91,24 +91,26 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
s: Curve.scalar.CompressedScalar,
/// Create a Verifier for incremental verification of a signature.
pub fn verifier(self: Signature, public_key: PublicKey) (NonCanonicalError || EncodingError || IdentityElementError)!Verifier {
return Verifier.init(self, public_key);
pub fn verifier(sig: Signature, public_key: PublicKey) Verifier.InitError!Verifier {
return Verifier.init(sig, public_key);
}
pub const VerifyError = Verifier.InitError || Verifier.VerifyError;
/// Verify the signature against a message and public key.
/// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
/// or SignatureVerificationError if the signature is invalid for the given message and key.
pub fn verify(self: Signature, msg: []const u8, public_key: PublicKey) (IdentityElementError || NonCanonicalError || SignatureVerificationError)!void {
var st = try Verifier.init(self, public_key);
pub fn verify(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {
var st = try sig.verifier(public_key);
st.update(msg);
return st.verify();
try st.verify();
}
/// Return the raw signature (r, s) in big-endian format.
pub fn toBytes(self: Signature) [encoded_length]u8 {
pub fn toBytes(sig: Signature) [encoded_length]u8 {
var bytes: [encoded_length]u8 = undefined;
@memcpy(bytes[0 .. encoded_length / 2], &self.r);
@memcpy(bytes[encoded_length / 2 ..], &self.s);
@memcpy(bytes[0 .. encoded_length / 2], &sig.r);
@memcpy(bytes[encoded_length / 2 ..], &sig.s);
return bytes;
}
@ -124,23 +126,23 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
/// Encode the signature using the DER format.
/// The maximum length of the DER encoding is der_encoded_length_max.
/// The function returns a slice, that can be shorter than der_encoded_length_max.
pub fn toDer(self: Signature, buf: *[der_encoded_length_max]u8) []u8 {
pub fn toDer(sig: Signature, buf: *[der_encoded_length_max]u8) []u8 {
var fb = io.fixedBufferStream(buf);
const w = fb.writer();
const r_len = @as(u8, @intCast(self.r.len + (self.r[0] >> 7)));
const s_len = @as(u8, @intCast(self.s.len + (self.s[0] >> 7)));
const r_len = @as(u8, @intCast(sig.r.len + (sig.r[0] >> 7)));
const s_len = @as(u8, @intCast(sig.s.len + (sig.s[0] >> 7)));
const seq_len = @as(u8, @intCast(2 + r_len + 2 + s_len));
w.writeAll(&[_]u8{ 0x30, seq_len }) catch unreachable;
w.writeAll(&[_]u8{ 0x02, r_len }) catch unreachable;
if (self.r[0] >> 7 != 0) {
if (sig.r[0] >> 7 != 0) {
w.writeByte(0x00) catch unreachable;
}
w.writeAll(&self.r) catch unreachable;
w.writeAll(&sig.r) catch unreachable;
w.writeAll(&[_]u8{ 0x02, s_len }) catch unreachable;
if (self.s[0] >> 7 != 0) {
if (sig.s[0] >> 7 != 0) {
w.writeByte(0x00) catch unreachable;
}
w.writeAll(&self.s) catch unreachable;
w.writeAll(&sig.s) catch unreachable;
return fb.getWritten();
}
@ -236,7 +238,9 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
s: Curve.scalar.Scalar,
public_key: PublicKey,
fn init(sig: Signature, public_key: PublicKey) (IdentityElementError || NonCanonicalError)!Verifier {
pub const InitError = IdentityElementError || NonCanonicalError;
fn init(sig: Signature, public_key: PublicKey) InitError!Verifier {
const r = try Curve.scalar.Scalar.fromBytes(sig.r, .big);
const s = try Curve.scalar.Scalar.fromBytes(sig.s, .big);
if (r.isZero() or s.isZero()) return error.IdentityElement;
@ -254,8 +258,11 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
self.h.update(data);
}
pub const VerifyError = IdentityElementError || NonCanonicalError ||
SignatureVerificationError;
/// Verify that the signature is valid for the entire message.
pub fn verify(self: *Verifier) (IdentityElementError || NonCanonicalError || SignatureVerificationError)!void {
pub fn verify(self: *Verifier) VerifyError!void {
const ht = Curve.scalar.encoded_length;
const h_len = @max(Hash.digest_length, ht);
var h: [h_len]u8 = [_]u8{0} ** h_len;

View File

@ -54,6 +54,8 @@ pub const close_notify_alert = [_]u8{
};
pub const ProtocolVersion = enum(u16) {
tls_1_0 = 0x0301,
tls_1_1 = 0x0302,
tls_1_2 = 0x0303,
tls_1_3 = 0x0304,
_,
@ -69,14 +71,18 @@ pub const ContentType = enum(u8) {
};
pub const HandshakeType = enum(u8) {
hello_request = 0,
client_hello = 1,
server_hello = 2,
new_session_ticket = 4,
end_of_early_data = 5,
encrypted_extensions = 8,
certificate = 11,
server_key_exchange = 12,
certificate_request = 13,
server_hello_done = 14,
certificate_verify = 15,
client_key_exchange = 16,
finished = 20,
key_update = 24,
message_hash = 254,
@ -198,36 +204,36 @@ pub const AlertDescription = enum(u8) {
_,
pub fn toError(alert: AlertDescription) Error!void {
return switch (alert) {
switch (alert) {
.close_notify => {}, // not an error
.unexpected_message => error.TlsAlertUnexpectedMessage,
.bad_record_mac => error.TlsAlertBadRecordMac,
.record_overflow => error.TlsAlertRecordOverflow,
.handshake_failure => error.TlsAlertHandshakeFailure,
.bad_certificate => error.TlsAlertBadCertificate,
.unsupported_certificate => error.TlsAlertUnsupportedCertificate,
.certificate_revoked => error.TlsAlertCertificateRevoked,
.certificate_expired => error.TlsAlertCertificateExpired,
.certificate_unknown => error.TlsAlertCertificateUnknown,
.illegal_parameter => error.TlsAlertIllegalParameter,
.unknown_ca => error.TlsAlertUnknownCa,
.access_denied => error.TlsAlertAccessDenied,
.decode_error => error.TlsAlertDecodeError,
.decrypt_error => error.TlsAlertDecryptError,
.protocol_version => error.TlsAlertProtocolVersion,
.insufficient_security => error.TlsAlertInsufficientSecurity,
.internal_error => error.TlsAlertInternalError,
.inappropriate_fallback => error.TlsAlertInappropriateFallback,
.unexpected_message => return error.TlsAlertUnexpectedMessage,
.bad_record_mac => return error.TlsAlertBadRecordMac,
.record_overflow => return error.TlsAlertRecordOverflow,
.handshake_failure => return error.TlsAlertHandshakeFailure,
.bad_certificate => return error.TlsAlertBadCertificate,
.unsupported_certificate => return error.TlsAlertUnsupportedCertificate,
.certificate_revoked => return error.TlsAlertCertificateRevoked,
.certificate_expired => return error.TlsAlertCertificateExpired,
.certificate_unknown => return error.TlsAlertCertificateUnknown,
.illegal_parameter => return error.TlsAlertIllegalParameter,
.unknown_ca => return error.TlsAlertUnknownCa,
.access_denied => return error.TlsAlertAccessDenied,
.decode_error => return error.TlsAlertDecodeError,
.decrypt_error => return error.TlsAlertDecryptError,
.protocol_version => return error.TlsAlertProtocolVersion,
.insufficient_security => return error.TlsAlertInsufficientSecurity,
.internal_error => return error.TlsAlertInternalError,
.inappropriate_fallback => return error.TlsAlertInappropriateFallback,
.user_canceled => {}, // not an error
.missing_extension => error.TlsAlertMissingExtension,
.unsupported_extension => error.TlsAlertUnsupportedExtension,
.unrecognized_name => error.TlsAlertUnrecognizedName,
.bad_certificate_status_response => error.TlsAlertBadCertificateStatusResponse,
.unknown_psk_identity => error.TlsAlertUnknownPskIdentity,
.certificate_required => error.TlsAlertCertificateRequired,
.no_application_protocol => error.TlsAlertNoApplicationProtocol,
_ => error.TlsAlertUnknown,
};
.missing_extension => return error.TlsAlertMissingExtension,
.unsupported_extension => return error.TlsAlertUnsupportedExtension,
.unrecognized_name => return error.TlsAlertUnrecognizedName,
.bad_certificate_status_response => return error.TlsAlertBadCertificateStatusResponse,
.unknown_psk_identity => return error.TlsAlertUnknownPskIdentity,
.certificate_required => return error.TlsAlertCertificateRequired,
.no_application_protocol => return error.TlsAlertNoApplicationProtocol,
_ => return error.TlsAlertUnknown,
}
}
};
@ -260,6 +266,17 @@ pub const SignatureScheme = enum(u16) {
rsa_pkcs1_sha1 = 0x0201,
ecdsa_sha1 = 0x0203,
ecdsa_brainpoolP256r1tls13_sha256 = 0x081a,
ecdsa_brainpoolP384r1tls13_sha384 = 0x081b,
ecdsa_brainpoolP512r1tls13_sha512 = 0x081c,
rsa_sha224 = 0x0301,
dsa_sha224 = 0x0302,
ecdsa_sha224 = 0x0303,
dsa_sha256 = 0x0402,
dsa_sha384 = 0x0502,
dsa_sha512 = 0x0602,
_,
};
@ -285,7 +302,27 @@ pub const NamedGroup = enum(u16) {
_,
};
pub const PskKeyExchangeMode = enum(u8) {
psk_ke = 0,
psk_dhe_ke = 1,
_,
};
pub const CipherSuite = enum(u16) {
RSA_WITH_AES_128_CBC_SHA = 0x002F,
DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033,
RSA_WITH_AES_256_CBC_SHA = 0x0035,
DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039,
RSA_WITH_AES_128_CBC_SHA256 = 0x003C,
RSA_WITH_AES_256_CBC_SHA256 = 0x003D,
DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067,
DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B,
RSA_WITH_AES_128_GCM_SHA256 = 0x009C,
RSA_WITH_AES_256_GCM_SHA384 = 0x009D,
DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E,
DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F,
EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF,
AES_128_GCM_SHA256 = 0x1301,
AES_256_GCM_SHA384 = 0x1302,
CHACHA20_POLY1305_SHA256 = 0x1303,
@ -293,6 +330,102 @@ pub const CipherSuite = enum(u16) {
AES_128_CCM_8_SHA256 = 0x1305,
AEGIS_256_SHA512 = 0x1306,
AEGIS_128L_SHA256 = 0x1307,
ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009,
ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A,
ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013,
ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014,
ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023,
ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024,
ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027,
ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028,
ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B,
ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C,
ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F,
ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030,
ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8,
ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9,
DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAA,
_,
pub const With = enum {
AES_128_CBC_SHA,
AES_256_CBC_SHA,
AES_128_CBC_SHA256,
AES_256_CBC_SHA256,
AES_256_CBC_SHA384,
AES_128_GCM_SHA256,
AES_256_GCM_SHA384,
CHACHA20_POLY1305_SHA256,
AES_128_CCM_SHA256,
AES_128_CCM_8_SHA256,
AEGIS_256_SHA512,
AEGIS_128L_SHA256,
};
pub fn with(cipher_suite: CipherSuite) With {
return switch (cipher_suite) {
.RSA_WITH_AES_128_CBC_SHA,
.DHE_RSA_WITH_AES_128_CBC_SHA,
.ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
.ECDHE_RSA_WITH_AES_128_CBC_SHA,
=> .AES_128_CBC_SHA,
.RSA_WITH_AES_256_CBC_SHA,
.DHE_RSA_WITH_AES_256_CBC_SHA,
.ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
.ECDHE_RSA_WITH_AES_256_CBC_SHA,
=> .AES_256_CBC_SHA,
.RSA_WITH_AES_128_CBC_SHA256,
.DHE_RSA_WITH_AES_128_CBC_SHA256,
.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
.ECDHE_RSA_WITH_AES_128_CBC_SHA256,
=> .AES_128_CBC_SHA256,
.RSA_WITH_AES_256_CBC_SHA256,
.DHE_RSA_WITH_AES_256_CBC_SHA256,
=> .AES_256_CBC_SHA256,
.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
.ECDHE_RSA_WITH_AES_256_CBC_SHA384,
=> .AES_256_CBC_SHA384,
.RSA_WITH_AES_128_GCM_SHA256,
.DHE_RSA_WITH_AES_128_GCM_SHA256,
.AES_128_GCM_SHA256,
.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
.ECDHE_RSA_WITH_AES_128_GCM_SHA256,
=> .AES_128_GCM_SHA256,
.RSA_WITH_AES_256_GCM_SHA384,
.DHE_RSA_WITH_AES_256_GCM_SHA384,
.AES_256_GCM_SHA384,
.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
.ECDHE_RSA_WITH_AES_256_GCM_SHA384,
=> .AES_256_GCM_SHA384,
.CHACHA20_POLY1305_SHA256,
.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
=> .CHACHA20_POLY1305_SHA256,
.AES_128_CCM_SHA256 => .AES_128_CCM_SHA256,
.AES_128_CCM_8_SHA256 => .AES_128_CCM_8_SHA256,
.AEGIS_256_SHA512 => .AEGIS_256_SHA512,
.AEGIS_128L_SHA256 => .AEGIS_128L_SHA256,
.EMPTY_RENEGOTIATION_INFO_SCSV => unreachable,
_ => unreachable,
};
}
};
pub const CompressionMethod = enum(u8) {
null = 0,
_,
};
@ -308,58 +441,114 @@ pub const KeyUpdateRequest = enum(u8) {
_,
};
pub fn HandshakeCipherT(comptime AeadType: type, comptime HashType: type) type {
return struct {
pub const AEAD = AeadType;
pub const Hash = HashType;
pub const Hmac = crypto.auth.hmac.Hmac(Hash);
pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
pub const ChangeCipherSpecType = enum(u8) {
change_cipher_spec = 1,
_,
};
handshake_secret: [Hkdf.prk_length]u8,
master_secret: [Hkdf.prk_length]u8,
client_handshake_key: [AEAD.key_length]u8,
server_handshake_key: [AEAD.key_length]u8,
client_finished_key: [Hmac.key_length]u8,
server_finished_key: [Hmac.key_length]u8,
client_handshake_iv: [AEAD.nonce_length]u8,
server_handshake_iv: [AEAD.nonce_length]u8,
transcript_hash: Hash,
pub fn HandshakeCipherT(comptime AeadType: type, comptime HashType: type, comptime explicit_iv_length: comptime_int) type {
return struct {
pub const A = ApplicationCipherT(AeadType, HashType, explicit_iv_length);
transcript_hash: A.Hash,
version: union {
tls_1_2: struct {
expected_server_verify_data: [A.verify_data_length]u8,
app_cipher: A.Tls_1_2,
},
tls_1_3: struct {
handshake_secret: [A.Hkdf.prk_length]u8,
master_secret: [A.Hkdf.prk_length]u8,
client_handshake_key: [A.AEAD.key_length]u8,
server_handshake_key: [A.AEAD.key_length]u8,
client_finished_key: [A.Hmac.key_length]u8,
server_finished_key: [A.Hmac.key_length]u8,
client_handshake_iv: [A.AEAD.nonce_length]u8,
server_handshake_iv: [A.AEAD.nonce_length]u8,
},
},
};
}
pub const HandshakeCipher = union(enum) {
AES_128_GCM_SHA256: HandshakeCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
AES_256_GCM_SHA384: HandshakeCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
CHACHA20_POLY1305_SHA256: HandshakeCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
AEGIS_256_SHA512: HandshakeCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha512),
AEGIS_128L_SHA256: HandshakeCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
AES_128_GCM_SHA256: HandshakeCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256, 8),
AES_256_GCM_SHA384: HandshakeCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384, 8),
CHACHA20_POLY1305_SHA256: HandshakeCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256, 0),
AEGIS_256_SHA512: HandshakeCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha512, 0),
AEGIS_128L_SHA256: HandshakeCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256, 0),
};
pub fn ApplicationCipherT(comptime AeadType: type, comptime HashType: type) type {
return struct {
pub fn ApplicationCipherT(comptime AeadType: type, comptime HashType: type, comptime explicit_iv_length: comptime_int) type {
return union {
pub const AEAD = AeadType;
pub const Hash = HashType;
pub const Hmac = crypto.auth.hmac.Hmac(Hash);
pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
client_secret: [Hash.digest_length]u8,
server_secret: [Hash.digest_length]u8,
client_key: [AEAD.key_length]u8,
server_key: [AEAD.key_length]u8,
client_iv: [AEAD.nonce_length]u8,
server_iv: [AEAD.nonce_length]u8,
pub const enc_key_length = AEAD.key_length;
pub const fixed_iv_length = AEAD.nonce_length - explicit_iv_length;
pub const record_iv_length = explicit_iv_length;
pub const mac_length = AEAD.tag_length;
pub const mac_key_length = Hmac.key_length_min;
pub const verify_data_length = 12;
tls_1_2: Tls_1_2,
tls_1_3: Tls_1_3,
pub const Tls_1_2 = extern struct {
client_write_MAC_key: [mac_key_length]u8,
server_write_MAC_key: [mac_key_length]u8,
client_write_key: [enc_key_length]u8,
server_write_key: [enc_key_length]u8,
client_write_IV: [fixed_iv_length]u8,
server_write_IV: [fixed_iv_length]u8,
// non-standard entropy
client_salt: [record_iv_length]u8,
};
pub const Tls_1_3 = struct {
client_secret: [Hash.digest_length]u8,
server_secret: [Hash.digest_length]u8,
client_key: [AEAD.key_length]u8,
server_key: [AEAD.key_length]u8,
client_iv: [AEAD.nonce_length]u8,
server_iv: [AEAD.nonce_length]u8,
};
};
}
/// Encryption parameters for application traffic.
pub const ApplicationCipher = union(enum) {
AES_128_GCM_SHA256: ApplicationCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
AES_256_GCM_SHA384: ApplicationCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
CHACHA20_POLY1305_SHA256: ApplicationCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
AEGIS_256_SHA512: ApplicationCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha512),
AEGIS_128L_SHA256: ApplicationCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
AES_128_GCM_SHA256: ApplicationCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256, 8),
AES_256_GCM_SHA384: ApplicationCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384, 8),
CHACHA20_POLY1305_SHA256: ApplicationCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256, 0),
AEGIS_256_SHA512: ApplicationCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha512, 0),
AEGIS_128L_SHA256: ApplicationCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256, 0),
};
pub fn hmacExpandLabel(
comptime Hmac: type,
secret: []const u8,
label_then_seed: []const []const u8,
comptime len: usize,
) [len]u8 {
const initial_hmac: Hmac = .init(secret);
var a: [Hmac.mac_length]u8 = undefined;
var result: [std.mem.alignForwardAnyAlign(usize, len, Hmac.mac_length)]u8 = undefined;
var index: usize = 0;
while (index < result.len) : (index += Hmac.mac_length) {
var a_hmac = initial_hmac;
if (index > 0) a_hmac.update(&a) else for (label_then_seed) |part| a_hmac.update(part);
a_hmac.final(&a);
var result_hmac = initial_hmac;
result_hmac.update(&a);
for (label_then_seed) |part| result_hmac.update(part);
result_hmac.final(result[index..][0..Hmac.mac_length]);
}
return result[0..len].*;
}
pub fn hkdfExpandLabel(
comptime Hkdf: type,
key: [Hkdf.prk_length]u8,
@ -399,38 +588,39 @@ pub fn hmac(comptime Hmac: type, message: []const u8, key: [Hmac.key_length]u8)
return result;
}
pub inline fn extension(comptime et: ExtensionType, bytes: anytype) [2 + 2 + bytes.len]u8 {
return int2(@intFromEnum(et)) ++ array(1, bytes);
pub inline fn extension(et: ExtensionType, bytes: anytype) [2 + 2 + bytes.len]u8 {
return int(u16, @intFromEnum(et)) ++ array(u16, u8, bytes);
}
pub inline fn array(comptime elem_size: comptime_int, bytes: anytype) [2 + bytes.len]u8 {
comptime assert(bytes.len % elem_size == 0);
return int2(bytes.len) ++ bytes;
}
pub inline fn enum_array(comptime E: type, comptime tags: []const E) [2 + @sizeOf(E) * tags.len]u8 {
assert(@sizeOf(E) == 2);
var result: [tags.len * 2]u8 = undefined;
for (tags, 0..) |elem, i| {
result[i * 2] = @as(u8, @truncate(@intFromEnum(elem) >> 8));
result[i * 2 + 1] = @as(u8, @truncate(@intFromEnum(elem)));
pub inline fn array(
comptime Len: type,
comptime Elem: type,
elems: anytype,
) [@divExact(@bitSizeOf(Len), 8) + @divExact(@bitSizeOf(Elem), 8) * elems.len]u8 {
const len_size = @divExact(@bitSizeOf(Len), 8);
const elem_size = @divExact(@bitSizeOf(Elem), 8);
var arr: [len_size + elem_size * elems.len]u8 = undefined;
std.mem.writeInt(Len, arr[0..len_size], @intCast(elem_size * elems.len), .big);
const ElemInt = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(Elem) } });
for (0.., @as([elems.len]Elem, elems)) |index, elem| {
std.mem.writeInt(
ElemInt,
arr[len_size + elem_size * index ..][0..elem_size],
switch (@typeInfo(Elem)) {
.int => @as(Elem, elem),
.@"enum" => @intFromEnum(@as(Elem, elem)),
else => @bitCast(@as(Elem, elem)),
},
.big,
);
}
return array(2, result);
return arr;
}
pub inline fn int2(x: u16) [2]u8 {
return .{
@as(u8, @truncate(x >> 8)),
@as(u8, @truncate(x)),
};
}
pub inline fn int3(x: u24) [3]u8 {
return .{
@as(u8, @truncate(x >> 16)),
@as(u8, @truncate(x >> 8)),
@as(u8, @truncate(x)),
};
pub inline fn int(comptime Int: type, val: Int) [@divExact(@bitSizeOf(Int), 8)]u8 {
var arr: [@divExact(@bitSizeOf(Int), 8)]u8 = undefined;
std.mem.writeInt(Int, &arr, val, .big);
return arr;
}
/// An abstraction to ensure that protocol-parsing code does not perform an
@ -512,9 +702,8 @@ pub const Decoder = struct {
else => @compileError("unsupported int type: " ++ @typeName(T)),
},
.@"enum" => |info| {
const int = d.decode(info.tag_type);
if (info.is_exhaustive) @compileError("exhaustive enum cannot be used");
return @as(T, @enumFromInt(int));
return @enumFromInt(d.decode(info.tag_type));
},
else => @compileError("unsupported type: " ++ @typeName(T)),
}

File diff suppressed because it is too large Load Diff

View File

@ -388,6 +388,7 @@ pub const Connection = struct {
// try to cleanly close the TLS connection, for any server that cares.
_ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};
if (conn.tls_client.ssl_key_log) |key_log| key_log.file.close();
allocator.destroy(conn.tls_client);
}
@ -566,7 +567,7 @@ pub const Response = struct {
.reason = undefined,
.version = undefined,
.keep_alive = false,
.parser = proto.HeadersParser.init(&header_buffer),
.parser = .init(&header_buffer),
};
@memcpy(header_buffer[0..response_bytes.len], response_bytes);
@ -610,7 +611,7 @@ pub const Response = struct {
}
pub fn iterateHeaders(r: Response) http.HeaderIterator {
return http.HeaderIterator.init(r.parser.get());
return .init(r.parser.get());
}
test iterateHeaders {
@ -628,7 +629,7 @@ pub const Response = struct {
.reason = undefined,
.version = undefined,
.keep_alive = false,
.parser = proto.HeadersParser.init(&header_buffer),
.parser = .init(&header_buffer),
};
@memcpy(header_buffer[0..response_bytes.len], response_bytes);
@ -771,7 +772,7 @@ pub const Request = struct {
req.client.connection_pool.release(req.client.allocator, req.connection.?);
req.connection = null;
var server_header = std.heap.FixedBufferAllocator.init(req.response.parser.header_bytes_buffer);
var server_header: std.heap.FixedBufferAllocator = .init(req.response.parser.header_bytes_buffer);
defer req.response.parser.header_bytes_buffer = server_header.buffer[server_header.end_index..];
const protocol, const valid_uri = try validateUri(uri, server_header.allocator());
@ -1354,7 +1355,27 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
errdefer client.allocator.destroy(conn.data.tls_client);
conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
const ssl_key_log_file: ?std.fs.File = if (std.options.http_enable_ssl_key_log_file) ssl_key_log_file: {
const ssl_key_log_path = std.process.getEnvVarOwned(client.allocator, "SSLKEYLOGFILE") catch |err| switch (err) {
error.EnvironmentVariableNotFound, error.InvalidWtf8 => break :ssl_key_log_file null,
error.OutOfMemory => return error.OutOfMemory,
};
defer client.allocator.free(ssl_key_log_path);
break :ssl_key_log_file std.fs.cwd().createFile(ssl_key_log_path, .{
.truncate = false,
.mode = switch (builtin.os.tag) {
.windows, .wasi => 0,
else => 0o600,
},
}) catch null;
} else null;
errdefer if (ssl_key_log_file) |key_log_file| key_log_file.close();
conn.data.tls_client.* = std.crypto.tls.Client.init(stream, .{
.host = .{ .explicit = host },
.ca = .{ .bundle = client.ca_bundle },
.ssl_key_log_file = ssl_key_log_file,
}) catch return error.TlsInitializationFailed;
// This is appropriate for HTTPS because the HTTP headers contain
// the content length which is used to detect truncation attacks.
conn.data.tls_client.allow_truncation_attacks = true;
@ -1620,7 +1641,7 @@ pub fn open(
}
}
var server_header = std.heap.FixedBufferAllocator.init(options.server_header_buffer);
var server_header: std.heap.FixedBufferAllocator = .init(options.server_header_buffer);
const protocol, const valid_uri = try validateUri(uri, server_header.allocator());
if (protocol == .tls and @atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
@ -1654,7 +1675,7 @@ pub fn open(
.status = undefined,
.reason = undefined,
.keep_alive = undefined,
.parser = proto.HeadersParser.init(server_header.buffer[server_header.end_index..]),
.parser = .init(server_header.buffer[server_header.end_index..]),
},
.headers = options.headers,
.extra_headers = options.extra_headers,

View File

@ -172,7 +172,13 @@ pub const HeadersParser = struct {
const data_avail = r.next_chunk_length;
if (skip) {
try conn.fill();
conn.fill() catch |err| switch (err) {
error.EndOfStream => {
r.done = true;
return 0;
},
else => |e| return e,
};
const nread = @min(conn.peek().len, data_avail);
conn.drop(@intCast(nread));
@ -196,7 +202,13 @@ pub const HeadersParser = struct {
}
},
.chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {
try conn.fill();
conn.fill() catch |err| switch (err) {
error.EndOfStream => {
r.done = true;
return 0;
},
else => |e| return e,
};
const i = r.findChunkedLen(conn.peek());
conn.drop(@intCast(i));
@ -226,7 +238,13 @@ pub const HeadersParser = struct {
const out_avail = buffer.len - out_index;
if (skip) {
try conn.fill();
conn.fill() catch |err| switch (err) {
error.EndOfStream => {
r.done = true;
return 0;
},
else => |e| return e,
};
const nread = @min(conn.peek().len, data_avail);
conn.drop(@intCast(nread));

View File

@ -146,6 +146,11 @@ pub const Options = struct {
/// make a HTTPS connection.
http_disable_tls: bool = false,
/// This enables `std.http.Client` to log ssl secrets to the file specified by the SSLKEYLOGFILE
/// env var. Creating such a log file allows other programs with access to that file to decrypt
/// all `std.http.Client` traffic made by this program.
http_enable_ssl_key_log_file: bool = @import("builtin").mode == .Debug,
side_channels_mitigations: crypto.SideChannelsMitigations = crypto.default_side_channels_mitigations,
};