///|
#cfg(target="native")
priv suberror QuicCryptoError {
BadHex
HkdfOutputTooLarge
} derive(Debug, ToJson)
///|
#cfg(target="native")
#warnings("-unused_field")
priv struct QuicInitialSecrets {
initial_secret : Bytes
client_initial_secret : Bytes
server_initial_secret : Bytes
}
///|
#cfg(target="native")
priv enum QuicInitialSecretSide {
QuicClientInitial
QuicServerInitial
}
///|
#cfg(target="native")
#warnings("-unused_field")
priv struct QuicPacketKeys {
key : Bytes
iv : Bytes
hp : Bytes
}
///|
#cfg(target="native")
fn hex_value(c : UInt16) -> Int raise {
if c >= '0' && c <= '9' {
c.to_int() - '0'.to_int()
} else if c >= 'a' && c <= 'f' {
c.to_int() - 'a'.to_int() + 10
} else if c >= 'A' && c <= 'F' {
c.to_int() - 'A'.to_int() + 10
} else {
raise BadHex
}
}
///|
#cfg(target="native")
fn bytes_from_hex(hex : String) -> Bytes raise {
guard hex.length() % 2 == 0 else { raise BadHex }
let out = @buffer.new()
for i = 0; i < hex.length(); i = i + 2 {
let hi = hex_value(hex.code_unit_at(i))
let lo = hex_value(hex.code_unit_at(i + 1))
out.write_byte(((hi << 4) | lo).to_byte())
}
out.contents()
}
///|
#cfg(target="native")
fn quic_hkdf_extract(salt : Bytes, ikm : Bytes) -> Bytes {
@crypto.hmac(@crypto.SHA256::new(), salt, ikm).unsafe_reinterpret_as_bytes()
}
///|
#cfg(target="native")
fn quic_hkdf_expand(prk : Bytes, info : Bytes, len : Int) -> Bytes raise {
guard len <= 8160 else { raise HkdfOutputTooLarge }
let out = @buffer.new()
let mut previous = b""
for counter = 1; out.length() < len; counter = counter + 1 {
let msg = @buffer.new()
msg.write_bytes(previous)
msg.write_bytes(info)
msg.write_byte(counter.to_byte())
previous = @crypto.hmac(@crypto.SHA256::new(), prk, msg.contents()).unsafe_reinterpret_as_bytes()
let remaining = len - out.length()
out.write_bytes(previous[:@cmp.minimum(remaining, previous.length())])
}
out.contents()
}
///|
#cfg(target="native")
fn quic_hkdf_expand_label(
secret : Bytes,
label : String,
context : Bytes,
len : Int,
) -> Bytes raise {
let full_label = "tls13 \{label}"
let label_bytes = @utf8.encode(full_label)
let info = @buffer.new()
info.write_byte(((len >> 8) & 0xff).to_byte())
info.write_byte((len & 0xff).to_byte())
info.write_byte(label_bytes.length().to_byte())
info.write_bytes(label_bytes)
info.write_byte(context.length().to_byte())
info.write_bytes(context)
quic_hkdf_expand(secret, info.contents(), len)
}
///|
#cfg(target="native")
fn quic_v1_initial_salt() -> Bytes {
bytes_from_hex("38762cf7f55934b34d179ae6a4c80cadccbb7f0a") catch {
_ => abort("invalid QUIC v1 salt")
}
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn quic_initial_secrets(dcid : Bytes) -> QuicInitialSecrets raise {
let initial_secret = quic_hkdf_extract(quic_v1_initial_salt(), dcid)
{
initial_secret,
client_initial_secret: quic_hkdf_expand_label(
initial_secret, "client in", b"", 32,
),
server_initial_secret: quic_hkdf_expand_label(
initial_secret, "server in", b"", 32,
),
}
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn quic_initial_key(secret : Bytes) -> Bytes raise {
quic_hkdf_expand_label(secret, "quic key", b"", 16)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn quic_initial_iv(secret : Bytes) -> Bytes raise {
quic_hkdf_expand_label(secret, "quic iv", b"", 12)
}
///|
#cfg(target="native")
#warnings("-unused_value")
fn quic_initial_hp(secret : Bytes) -> Bytes raise {
quic_hkdf_expand_label(secret, "quic hp", b"", 16)
}
///|
#cfg(target="native")
fn quic_packet_keys_from_secret(secret : Bytes) -> QuicPacketKeys raise {
{
key: quic_initial_key(secret),
iv: quic_initial_iv(secret),
hp: quic_initial_hp(secret),
}
}
///|
#cfg(target="native")
fn quic_initial_packet_keys(
original_destination_connection_id : Bytes,
side : QuicInitialSecretSide,
) -> QuicPacketKeys raise {
let secrets = quic_initial_secrets(original_destination_connection_id)
match side {
QuicClientInitial =>
quic_packet_keys_from_secret(secrets.client_initial_secret)
QuicServerInitial =>
quic_packet_keys_from_secret(secrets.server_initial_secret)
}
}