///|
let default_crypto_provider : Lazy[Result[@crypto.Provider, String]] = Lazy(() => {
Ok(@crypto.Provider::open()) catch {
CryptoUnavailable(message) => Err(message)
InvalidLength(length) => Err("OpenSSL rejected length \{length}")
OperationFailed(message) => Err(message)
}
})
///|
fn crypto_provider() -> @crypto.Provider raise DtlsError {
match default_crypto_provider.force() {
Ok(provider) => provider
Err(message) => raise CryptoUnavailable(message)
}
}
///|
fn[T] crypto_operation(
operation : () -> T raise @crypto.CryptoError,
) -> T raise DtlsError {
operation() catch {
CryptoUnavailable(message) => raise CryptoUnavailable(message)
InvalidLength(length) =>
raise CryptoUnavailable("OpenSSL rejected length \{length}")
OperationFailed(message) => raise CryptoUnavailable(message)
}
}
///|
fn append_bytes(left : Bytes, right : Bytes) -> Bytes {
let result = left.to_array()
for byte in right {
result.push(byte)
}
Bytes::from_array(result)
}
///|
fn append_three(first : Bytes, second : Bytes, third : Bytes) -> Bytes {
append_bytes(append_bytes(first, second), third)
}
///|
fn p_hash_secret(
secret : @crypto.Secret,
seed : Bytes,
requested_length : Int,
) -> Bytes raise DtlsError {
if requested_length < 0 {
raise InvalidHandshake("TLS PRF output length cannot be negative")
}
if requested_length == 0 {
return b""
}
let provider = crypto_provider()
let output : Array[Byte] = []
let mut a = seed
while output.length() < requested_length {
a = crypto_operation(() => provider.hmac(Sha256, secret, a))
let block = crypto_operation(() => {
provider.hmac(Sha256, secret, append_bytes(a, seed))
})
for byte in block {
if output.length() < requested_length {
output.push(byte)
}
}
}
Bytes::from_array(output)
}
///|
fn extended_master_secret_key(
pre_master_secret : @crypto.Secret,
session_hash : Bytes,
) -> @crypto.Secret raise DtlsError {
@crypto.Secret::from_bytes(
p_hash_secret(
pre_master_secret,
append_bytes(b"extended master secret", session_hash),
48,
),
)
}
///|
fn psk_pre_master_secret(psk : Bytes) -> @crypto.Secret raise DtlsError {
if psk.is_empty() || psk.length() > 0xffff {
raise InvalidHandshake("DTLS PSK must contain 1..65535 bytes")
}
let result : Array[Byte] = [
(psk.length() >> 8).to_byte(),
psk.length().to_byte(),
]
for index = 0; index < psk.length(); index = index + 1 {
result.push(0)
}
result.push((psk.length() >> 8).to_byte())
result.push(psk.length().to_byte())
for byte in psk {
result.push(byte)
}
@crypto.Secret::from_bytes(Bytes::from_array(result))
}
///|
fn encryption_keys_with_lengths(
master_secret : @crypto.Secret,
client_random : Bytes,
server_random : Bytes,
mac_length~ : Int,
key_length~ : Int,
iv_length~ : Int,
) -> EncryptionKeys raise DtlsError {
if master_secret.length() != 48 ||
client_random.length() != 32 ||
server_random.length() != 32 {
raise InvalidHandshake("invalid DTLS key schedule input length")
}
if mac_length < 0 || key_length < 1 || iv_length < 0 {
raise InvalidHandshake("invalid DTLS key schedule output lengths")
}
let side_length = mac_length + key_length + iv_length
let material = p_hash_secret(
master_secret,
append_three(b"key expansion", server_random, client_random),
side_length * 2,
)
let client_mac_end = mac_length
let server_mac_end = client_mac_end + mac_length
let client_key_end = server_mac_end + key_length
let server_key_end = client_key_end + key_length
let client_iv_end = server_key_end + iv_length
let server_iv_end = client_iv_end + iv_length
{
client_mac_key: material[0:client_mac_end].to_owned(),
server_mac_key: material[client_mac_end:server_mac_end].to_owned(),
client_write_key: material[server_mac_end:client_key_end].to_owned(),
server_write_key: material[client_key_end:server_key_end].to_owned(),
client_write_iv: material[server_key_end:client_iv_end].to_owned(),
server_write_iv: material[client_iv_end:server_iv_end].to_owned(),
}
}
///|
fn encryption_keys_for_secret(
master_secret : @crypto.Secret,
client_random : Bytes,
server_random : Bytes,
suite? : CipherSuite = EcdheEcdsaAes128GcmSha256,
) -> EncryptionKeys raise DtlsError {
let (mac_length, key_length, iv_length) = match suite {
EcdheEcdsaAes256CbcSha | EcdheRsaAes256CbcSha => (20, 32, 0)
EcdheRsaChacha20Poly1305Sha256 | EcdheEcdsaChacha20Poly1305Sha256 =>
(0, 32, 12)
_ => (0, 16, 4)
}
encryption_keys_with_lengths(
master_secret,
client_random,
server_random,
mac_length~,
key_length~,
iv_length~,
)
}
///|
fn verify_data_for_secret(
master_secret : @crypto.Secret,
handshake_messages : Bytes,
label : Bytes,
) -> Bytes raise DtlsError {
if master_secret.length() != 48 {
raise InvalidHandshake("DTLS master secret must contain 48 bytes")
}
let provider = crypto_provider()
let digest = crypto_operation(() => {
provider.digest(Sha256, handshake_messages)
})
p_hash_secret(master_secret, append_bytes(label, digest), 12)
}
///|
fn client_verify_data_for_secret(
master_secret : @crypto.Secret,
handshake_messages : Bytes,
) -> Bytes raise DtlsError {
verify_data_for_secret(master_secret, handshake_messages, b"client finished")
}
///|
fn server_verify_data_for_secret(
master_secret : @crypto.Secret,
handshake_messages : Bytes,
) -> Bytes raise DtlsError {
verify_data_for_secret(master_secret, handshake_messages, b"server finished")
}
///|
fn transcript_hash(handshake_messages : Bytes) -> Bytes raise DtlsError {
let provider = crypto_provider()
crypto_operation(() => provider.digest(Sha256, handshake_messages))
}