// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// TLS 1.2's RSA key exchange: the premaster secret in RSAES-PKCS1-v1_5 (RFC 5246 §7.4.7.1).
///|
/// How long a premaster secret is, the two version octets included.
pub let premaster_size : Int32 = 48
///|
/// Wrap a premaster secret for `key`'s owner (RFC 8017 §7.2.1). `Size` when the key is too short to
/// hold it with the eight octets of filler the padding demands.
pub fn wrap_premaster(
key : @rsa.PublicKey,
premaster : BytesView,
rand~ : (Int32) -> Bytes,
) -> Bytes raise @spec.Broken {
let fill = key.size() - premaster.length() - 3
if fill < 8 {
raise @spec.Size(want=premaster.length() + 11, got=key.size())
}
let filler : Array[Byte] = []
while filler.length() < fill {
for b in rand(fill - filler.length()) {
if b != b'\x00' {
filler.push(b)
}
}
}
let block = Buffer()
block.write_byte(b'\x00')
block.write_byte(b'\x02')
block.write_bytes(Bytes::from_array(filler))
block.write_byte(b'\x00')
block.write_bytesview(premaster)
key.rsaep(block.to_bytes()[:])
}
///|
/// Unwrap a premaster secret, whose first two octets must be `version`. A block that is wrong in any
/// way gives a random secret instead of an error: the handshake goes on and fails at Finished like
/// any other mismatch, since telling the sender which blocks were well formed is Bleichenbacher's
/// oracle. The block is walked whole and the choice is made by masking, so nothing branches on what
/// was decrypted.
pub fn unwrap_premaster(
key : @rsa.PrivateKey,
wrapped : BytesView,
version~ : Int32,
rand~ : (Int32) -> Bytes,
) -> Bytes {
let stand_in = rand(premaster_size)
let block = key.rsadp(wrapped) catch { _ => return stand_in }
let start = block.length() - premaster_size
if start < 11 {
return stand_in
}
let mut bad = block[0].to_int() | (block[1].to_int() ^ 2)
for i in 2..<(start - 1) {
bad = bad | (((block[i].to_int() - 1) >> 31) & 1)
}
bad = bad |
block[start - 1].to_int() |
(block[start].to_int() ^ (version >> 8)) |
(block[start + 1].to_int() ^ (version & 0xff))
let keep = (bad - 1) >> 31
Bytes::makei(premaster_size, i => {
let chosen = (block[start + i].to_int() & keep) |
(stand_in[i].to_int() & keep.lnot())
chosen.to_byte()
})
}