///|
fn pdf_crypt_concat_bytes(parts : ArrayView[BytesView]) -> @core.PdfBytes {
@core.pdf_bytes_concat_views(parts)
}
///|
fn pdf_aesv3_cbc_encrypt_no_padding_with_iv(
key : BytesView,
iv : BytesView,
data : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
guard data.length() % 16 == 0 else {
raise InvalidCryptoDataLength(data.length())
}
let output = Array::make(data.length(), b'\x00')
let mut previous = pdf_crypt_chop_view(pdf_crypt_iv_name, iv, 16)
let mut position = 0
let mut output_position = 0
while position < data.length() {
let block = Array::make(16, b'\x00')
for index in 0..<16 {
block[index] = (data[position + index].to_int() ^ previous[index].to_int()).to_byte()
}
previous = @crypt_core.pdf_aes_encrypt_block(key, Bytes::from_array(block))
for byte in previous {
output[output_position] = byte
output_position += 1
}
position += 16
}
Bytes::from_array(output)
}
///|
fn pdf_aesv3_shamix_mod3(data : BytesView) -> Int {
let mut total = 0
for index in 0..<16 {
total += data[index].to_int()
}
total % 3
}
///|
fn pdf_aesv3_shamix_repeat(data : BytesView, count : Int) -> @core.PdfBytes {
let output = Array::make(data.length() * count, b'\x00')
let mut position = 0
for _ in 0.. @core.PdfBytes {
match pdf_aesv3_shamix_mod3(data) {
0 => @crypt_core.pdf_sha256_digest(data)
1 => @crypt_core.pdf_sha384_digest(data)
_ => @crypt_core.pdf_sha512_digest(data)
}
}
///|
fn pdf_aesv3_shamix(
password : BytesView,
user_entry : BytesView?,
input : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
let user_data = match user_entry {
Some(value) => value
None => []
}
let mut digest = @crypt_core.pdf_sha256_digest(input)
let mut round = 0
let mut last_encrypted_byte = 0
while !(round >= 64 && last_encrypted_byte <= round - 32) {
round += 1
let repeated_input = pdf_crypt_concat_bytes([password, digest, user_data])
let repeated = pdf_aesv3_shamix_repeat(repeated_input, 64)
let encrypted = pdf_aesv3_cbc_encrypt_no_padding_with_iv(
digest[:16],
digest[16:32],
repeated,
)
last_encrypted_byte = encrypted[encrypted.length() - 1].to_int()
digest = pdf_aesv3_shamix_next_hash(encrypted)
}
digest[:32].to_owned()
}
///|
fn pdf_aesv3_password_hash(
iso : Bool,
password : BytesView,
salt : BytesView,
user_entry? : BytesView = [],
) -> @core.PdfBytes raise @core.PdfError {
let input = pdf_crypt_concat_bytes([password, salt, user_entry])
if iso {
let user_data = if user_entry.length() == 0 {
None
} else {
Some(user_entry)
}
pdf_aesv3_shamix(password, user_data, input)
} else {
@crypt_core.pdf_sha256_digest(input)
}
}
///|
fn pdf_aesv3_random_field_key(
field : @crypt_core.PdfAesV3RandomField,
) -> @core.PdfName {
match field {
PdfAesV3FileKey => pdf_crypt_file_key_name
PdfAesV3UserValidationSalt => pdf_crypt_user_validation_salt_name
PdfAesV3UserKeySalt => pdf_crypt_user_key_salt_name
PdfAesV3OwnerValidationSalt => pdf_crypt_owner_validation_salt_name
PdfAesV3OwnerKeySalt => pdf_crypt_owner_key_salt_name
PdfAesV3PermissionsPadding => pdf_crypt_perms_padding_name
}
}
///|
fn pdf_aesv3_random_bytes(
random_provider : (@crypt_core.PdfAesV3RandomField, Int) -> @core.PdfBytes raise @core.PdfError,
field : @crypt_core.PdfAesV3RandomField,
length : Int,
) -> @core.PdfBytes raise @core.PdfError {
pdf_crypt_chop_string(
pdf_aesv3_random_field_key(field),
random_provider(field, length),
length,
)
}
///|
fn pdf_aesv3_entry(
iso : Bool,
password : BytesView,
validation_salt : BytesView,
key_salt : BytesView,
user_entry? : BytesView = [],
) -> @core.PdfBytes raise @core.PdfError {
let digest = pdf_aesv3_password_hash(
iso,
password,
validation_salt,
user_entry~,
)
pdf_crypt_concat_bytes([digest[:32], validation_salt, key_salt])
}
///|
fn pdf_aesv3_cbc_encrypt_no_padding(
key : BytesView,
data : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
pdf_aesv3_cbc_encrypt_no_padding_with_iv(key, Bytes::new(16), data)
}
///|
fn pdf_aesv3_wrap_file_key(
iso : Bool,
password : BytesView,
key_salt : BytesView,
file_key : BytesView,
user_entry? : BytesView = [],
) -> @core.PdfBytes raise @core.PdfError {
let digest = pdf_aesv3_password_hash(iso, password, key_salt, user_entry~)
pdf_aesv3_cbc_encrypt_no_padding(digest, file_key)
}
///|
fn pdf_aesv3_permissions_entry(
file_key : BytesView,
permissions : Int,
encrypt_metadata : Bool,
padding : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
let block = Array::make(16, b'\x00')
let mut position = 0
for shift in [0, 8, 16, 24] {
block[position] = @crypt_core.pdf_crypt_low_byte(permissions, shift)
position += 1
}
for _ in 0..<4 {
block[position] = (255).to_byte()
position += 1
}
block[position] = if encrypt_metadata { b'T' } else { b'F' }
block[position + 1] = b'a'
block[position + 2] = b'd'
block[position + 3] = b'b'
position += 4
for
byte in pdf_crypt_chop_view(
pdf_aesv3_random_field_key(PdfAesV3PermissionsPadding),
padding,
4,
) {
block[position] = byte
position += 1
}
@crypt_core.pdf_aes_encrypt_block(file_key, Bytes::from_array(block))
}
///|
/// Authenticate an AESV3 user password entry.
///
/// `iso` selects the ISO/PDF 2.0 hash variant. Short `/U` entries raise
/// `@core.PdfError::EncryptionDictionaryEntryTooShort`.
fn pdf_authenticate_user_password_aesv3(
iso : Bool,
password : BytesView,
user_entry : BytesView,
) -> Bool raise @core.PdfError {
if user_entry.length() < 48 {
raise EncryptionDictionaryEntryTooShort(pdf_crypt_u_name)
}
let expected = pdf_aesv3_password_hash(iso, password, user_entry[32:40])
@crypt_core.pdf_bytes_prefix_equal(user_entry, expected, 32)
}
///|
/// Authenticate an AESV3 owner password entry.
///
/// Owner authentication incorporates the 48-byte user entry as required by the
/// AESV3 security algorithm. Short `/O` or `/U` entries raise `@core.PdfError`.
fn pdf_authenticate_owner_password_aesv3(
iso : Bool,
password : BytesView,
user_entry : BytesView,
owner_entry : BytesView,
) -> Bool raise @core.PdfError {
if owner_entry.length() < 48 {
raise EncryptionDictionaryEntryTooShort(pdf_crypt_o_name)
}
if user_entry.length() < 48 {
raise EncryptionDictionaryEntryTooShort(pdf_crypt_u_name)
}
let expected = pdf_aesv3_password_hash(
iso,
password,
owner_entry[32:40],
user_entry=user_entry[:48],
)
@crypt_core.pdf_bytes_prefix_equal(owner_entry, expected, 32)
}
///|
fn pdf_aesv3_crypt_entry(
key : @core.PdfName,
entry : @core.PdfBytes?,
) -> @core.PdfBytes raise @core.PdfError {
match entry {
Some(value) => pdf_crypt_chop_string(key, value, 32)
None => raise EncryptionDictionaryEntryExpected(key)
}
}
///|
fn pdf_aesv3_unwrap_file_key(
digest : @core.PdfBytes,
encrypted_key : @core.PdfBytes,
) -> @core.PdfBytes raise @core.PdfError {
let zero_iv = Bytes::new(16)
let data = pdf_crypt_concat_bytes([zero_iv, encrypted_key])
@crypt_core.pdf_aes_cbc_decrypt(digest, data, remove_padding=false)
}
///|
fn pdf_aesv3_permissions_key() -> @core.PdfName {
pdf_crypt_perms_name
}
///|
fn pdf_aesv3_permissions_from_entry(
file_key : BytesView,
permissions_entry : BytesView,
) -> Int raise @core.PdfError {
if permissions_entry.length() < 16 {
raise EncryptionDictionaryEntryTooShort(pdf_aesv3_permissions_key())
}
let decrypted = @crypt_core.pdf_aes_ecb_decrypt(
file_key,
permissions_entry[:16],
remove_padding=false,
)
guard decrypted[9] == b'a' && decrypted[10] == b'd' && decrypted[11] == b'b' else {
raise EncryptionPermissionsCorrupt
}
decrypted[0].to_int() |
(decrypted[1].to_int() << 8) |
(decrypted[2].to_int() << 16) |
(decrypted[3].to_int() << 24)
}
///|
fn @crypt_core.PdfEncryptionValues::validate_aesv3_permissions(
self : @crypt_core.PdfEncryptionValues,
file_key : BytesView,
) -> Unit raise @core.PdfError {
let permissions_entry = match self.permissions_entry {
Some(value) => value
None => raise EncryptionDictionaryEntryExpected(pdf_aesv3_permissions_key())
}
let permissions = pdf_aesv3_permissions_from_entry(
file_key, permissions_entry,
)
if permissions != self.permissions {
raise EncryptionPermissionsMismatch(permissions, self.permissions)
}
}
///|
fn pdf_file_key_for_user_password_aesv3(
iso : Bool,
password : BytesView,
user_entry : BytesView,
user_encryption_key : @core.PdfBytes?,
) -> @core.PdfBytes? raise @core.PdfError {
if pdf_authenticate_user_password_aesv3(iso, password, user_entry) {
let digest = pdf_aesv3_password_hash(iso, password, user_entry[40:48])
let encrypted_key = pdf_aesv3_crypt_entry(
pdf_crypt_ue_name, user_encryption_key,
)
Some(pdf_aesv3_unwrap_file_key(digest, encrypted_key))
} else {
None
}
}
///|
fn pdf_file_key_for_owner_password_aesv3(
iso : Bool,
password : BytesView,
user_entry : BytesView,
owner_entry : BytesView,
owner_encryption_key : @core.PdfBytes?,
) -> @core.PdfBytes? raise @core.PdfError {
if pdf_authenticate_owner_password_aesv3(
iso, password, user_entry, owner_entry,
) {
let digest = pdf_aesv3_password_hash(
iso,
password,
owner_entry[40:48],
user_entry=user_entry[:48],
)
let encrypted_key = pdf_aesv3_crypt_entry(
pdf_crypt_oe_name, owner_encryption_key,
)
Some(pdf_aesv3_unwrap_file_key(digest, encrypted_key))
} else {
None
}
}