///|
fn pdf_crypto_fixed_array(data : BytesView) -> FixedArray[Byte] {
let output = FixedArray::make(data.length(), b'\x00')
for index, byte in data {
output[index] = byte
}
output
}
///|
fn pdf_aes_valid_key_length(length : Int) -> Bool {
length == 16 || length == 24 || length == 32
}
///|
fn pdf_aes_check_key(key : BytesView) -> Unit raise @core.PdfError {
guard pdf_aes_valid_key_length(key.length()) else { raise CryptoKeyExpected }
}
///|
fn pdf_aes_block(data : BytesView) -> FixedArray[Byte] raise @core.PdfError {
guard data.length() == 16 else {
raise InvalidCryptoDataLength(data.length())
}
pdf_crypto_fixed_array(data)
}
///|
fn pdf_aes_encrypt_key(
key : BytesView,
) -> FixedArray[Byte] raise @core.PdfError {
pdf_aes_check_key(key)
let fixed_key = pdf_crypto_fixed_array(key)
@rijndael.camlpdf_caml_aes_cook_encrypt_key(fixed_key, 0, fixed_key.length())
}
///|
fn pdf_aes_decrypt_key(
key : BytesView,
) -> FixedArray[Byte] raise @core.PdfError {
pdf_aes_check_key(key)
let fixed_key = pdf_crypto_fixed_array(key)
@rijndael.camlpdf_caml_aes_cook_decrypt_key(fixed_key, 0, fixed_key.length())
}
///|
/// Encrypt one 16-byte AES block with a 128-, 192-, or 256-bit key.
pub fn pdf_aes_encrypt_block(
key : BytesView,
block : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
let cooked_key = pdf_aes_encrypt_key(key)
let src = pdf_aes_block(block)
let dst = FixedArray::make(16, b'\x00')
@rijndael.camlpdf_caml_aes_encrypt(cooked_key, 0, src, 0, dst, 0)
Bytes::from_array(dst)
}
///|
/// Decrypt one 16-byte AES block with a 128-, 192-, or 256-bit key.
#warnings("-unused_value")
pub fn pdf_aes_decrypt_block(
key : BytesView,
block : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
let cooked_key = pdf_aes_decrypt_key(key)
let src = pdf_aes_block(block)
let dst = FixedArray::make(16, b'\x00')
@rijndael.camlpdf_caml_aes_decrypt(cooked_key, 0, src, 0, dst, 0)
Bytes::from_array(dst)
}
///|
fn pdf_aes_remove_padding(data : BytesView) -> @core.PdfBytes {
let length = data.length()
let padding = data[length - 1].to_int()
guard padding >= 1 && padding <= 16 else { return data.to_owned() }
for index in (length - padding).. @core.PdfBytes raise @core.PdfError {
guard data.length() > 16 else { return Bytes::new(0) }
guard data.length() % 16 == 0 else {
raise InvalidCryptoDataLength(data.length())
}
let cooked_key = pdf_aes_decrypt_key(key)
let output = Array::make(data.length() - 16, b'\x00')
let mut previous_ciphertext = pdf_aes_block(data[:16])
let mut position = 16
let mut output_position = 0
while position < data.length() {
let ciphertext = pdf_aes_block(data[position:position + 16])
let plaintext = FixedArray::make(16, b'\x00')
@rijndael.camlpdf_caml_aes_decrypt(
cooked_key, 0, ciphertext, 0, plaintext, 0,
)
for index in 0..<16 {
output[output_position + index] = (plaintext[index].to_int() ^
previous_ciphertext[index].to_int()).to_byte()
}
previous_ciphertext = ciphertext
position += 16
output_position += 16
}
let decrypted = Bytes::from_array(output)
if remove_padding {
pdf_aes_remove_padding(decrypted)
} else {
decrypted
}
}
///|
/// Encrypt bytes with AES-CBC and an explicit IV.
///
/// PKCS-style padding is added and the returned bytes include the IV as the
/// first block, matching PDF AES string and stream data layout.
pub fn pdf_aes_cbc_encrypt_with_iv(
key : BytesView,
iv : BytesView,
data : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
let cooked_key = pdf_aes_encrypt_key(key)
let mut previous_ciphertext = pdf_aes_block(iv)
let full_blocks = data.length() / 16
let overflow = data.length() % 16
let output = Array::make((full_blocks + 2) * 16, b'\x00')
for index in 0..<16 {
output[index] = previous_ciphertext[index]
}
let mut output_position = 16
for block_index in 0.. @core.PdfBytes raise @core.PdfError {
guard data.length() > 0 else { return Bytes::new(0) }
guard data.length() % 16 == 0 else {
raise InvalidCryptoDataLength(data.length())
}
let cooked_key = pdf_aes_decrypt_key(key)
let output = Array::make(data.length(), b'\x00')
let mut position = 0
while position < data.length() {
let ciphertext = pdf_aes_block(data[position:position + 16])
let plaintext = FixedArray::make(16, b'\x00')
@rijndael.camlpdf_caml_aes_decrypt(
cooked_key, 0, ciphertext, 0, plaintext, 0,
)
for index in 0..<16 {
output[position + index] = plaintext[index]
}
position += 16
}
let decrypted = Bytes::from_array(output)
if remove_padding {
pdf_aes_remove_padding(decrypted)
} else {
decrypted
}
}
///|
/// Encrypt AES-ECB data without adding padding.
///
/// The input length must be a non-zero multiple of 16 unless it is empty.
#warnings("-unused_value")
pub fn pdf_aes_ecb_encrypt(
key : BytesView,
data : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
guard data.length() % 16 == 0 else {
raise InvalidCryptoDataLength(data.length())
}
guard data.length() > 0 else { return Bytes::new(0) }
let cooked_key = pdf_aes_encrypt_key(key)
let output = Array::make(data.length(), b'\x00')
let mut position = 0
while position < data.length() {
let plaintext = pdf_aes_block(data[position:position + 16])
let ciphertext = FixedArray::make(16, b'\x00')
@rijndael.camlpdf_caml_aes_encrypt(
cooked_key, 0, plaintext, 0, ciphertext, 0,
)
for index in 0..<16 {
output[position + index] = ciphertext[index]
}
position += 16
}
Bytes::from_array(output)
}