Files
pdf-odin-llm/crypt.odin
T

1093 lines
39 KiB
Odin
Raw Normal View History

2026-09-25 23:32:29 +02:00
package pdf
import "core:crypto"
import "core:crypto/aes"
import "core:crypto/legacy/md5"
import "core:crypto/sha2"
// The standard security handler (ISO 32000-2, 7.6.4).
//
// Decryption is transparent: `crypt_init` derives the file encryption key once,
// and the parser then routes every string and every stream payload through
// `decrypt_string` / `decrypt_stream` as it reads them, so nothing above this
// file ever sees ciphertext.
//
// Two things are deliberately never decrypted, because they are the inputs the
// key was derived from: the /Encrypt dictionary itself, and the /ID strings in
// the trailer. A third is never encrypted in the first place, because it has
// to be readable before the /Encrypt dictionary it points at can be found: a
// cross-reference stream (ISO 32000-2, 7.5.8.2). A fourth is not decrypted
// here because it already was: anything that came out of an object stream,
// which is decrypted once as a whole when its container is. Those objects
// carry Ref{0,0}, and that is the signal used below.
// Crypt_Method is what a crypt filter does to bytes. `Identity` covers both
// /Identity and a filter with /CFM /None -- "hand the bytes back untouched".
Crypt_Method :: enum u8 {
Identity,
Rc4,
Aes_128,
Aes_256,
}
// Password_Kind records which of the two passwords the caller supplied.
// A file encrypted with an empty user password -- "opens without prompting,
// but you may not print it" -- authenticates as `User`.
Password_Kind :: enum u8 {
None,
User,
Owner,
}
// Permission is one bit of the /P entry, numbered as the format numbers them
// minus one: /P bit position 3 (printing) is bit index 2 here.
// ISO 32000-2, table 22.
Permission :: enum u32 {
Print = 2,
Modify = 3,
Copy = 4,
Annotate = 5,
Fill_Forms = 8,
Extract = 9, // extraction for accessibility
Assemble = 10,
Print_High_Res = 11,
}
Permissions :: bit_set[Permission; u32]
// Encryption is the decrypted state of the standard security handler: what was
// negotiated, and the file encryption key everything else is derived from.
// It lives in the Document's arena and is reclaimed with it.
Encryption :: struct {
// The /Encrypt dictionary's own object, so it can be excluded from
// decryption if anything asks for it a second time. {0,0} when the
// dictionary was written directly in the trailer.
ref: Ref,
version: int, // /V
revision: int, // /R
key_bits: int, // effective file key length, in bits
// /P, as the signed 32-bit value the format defines. Stored signed because
// almost every file writes it negative (bits 13..32 are all 1s).
permission_bits: i32,
encrypt_metadata: bool, // /EncryptMetadata
authenticated_as: Password_Kind,
stream_method: Crypt_Method, // /StmF
string_method: Crypt_Method, // /StrF
embedded_method: Crypt_Method, // /EFF
// The file encryption key: 5..16 bytes for RC4 and AES-128, 32 for
// AES-256. Zeroed by `crypt_destroy`.
key: []byte,
}
// The padding string of ISO 32000-2, 7.6.4.3.2 (Algorithm 2, step a). Every
// password shorter than 32 bytes is extended with a prefix of it.
@(private, rodata)
CRYPT_PAD := [32]byte{
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
}
// "sAlT", appended to the per-object key input for AES-128 so that a file
// cannot be downgraded from AESV2 to V2 with the same key (7.6.3.1).
@(private, rodata)
CRYPT_AES_SALT := [4]byte{0x73, 0x41, 0x6C, 0x54}
// ---------------------------------------------------------------------------
// Setup
// ---------------------------------------------------------------------------
// crypt_init reads the trailer's /Encrypt dictionary and derives the file
// encryption key from `password`.
//
// The password is tried as the user password first and then as the owner
// password, because the empty user password -- a document that opens without
// prompting but restricts printing or copying -- is by far the common case.
// An unencrypted document clears `doc.encrypt` and returns .None.
//
// Internal: this is a step of `open_bytes`, not an entry point. There is no
// public equivalent because there is nothing a caller could call it on -- a
// document whose password was refused is closed before `open_bytes` returns,
// so no locked `^Document` ever escapes the package. Retrying with a second
// password means calling `open_bytes` again with a different
// `Open_Options.password`; the file bytes are already in memory, and reusing
// the Document would mean re-decrypting objects the old key already cached.
@(private)
crypt_init :: proc(doc: ^Document, password: []byte) -> Error {
if doc == nil { return .Invalid_Argument }
doc.encrypt = nil
raw := dict_get_raw(doc.trailer, "Encrypt")
if raw == nil { return .None }
enc_ref, _ := to_ref(raw)
// Resolving happens while doc.encrypt is still nil, which is exactly why
// the /Encrypt dictionary's own strings come back untouched.
ed, is_dict := to_dict(resolve(doc, raw))
if !is_dict || ed == nil {
// /Encrypt is present, so the file is encrypted, and we cannot read the
// dictionary that says how. There is nothing to degrade to: carrying on
// would hand ciphertext to every layer above.
return .Bad_Encrypt_Dict
}
// A missing /Filter is assumed to be the standard handler; a named one that
// is not /Standard is a plug-in we do not have.
if f := dict_name(doc, ed, "Filter", "Standard"); f != "Standard" {
return .Unsupported_Encryption
}
version := int(dict_int(doc, ed, "V", 0))
revision := int(dict_int(doc, ed, "R", 0))
switch {
case version <= 0: version = 1
// V3 is an unpublished Adobe variant with a 40..128 bit key; every viewer
// treats it as V2, and the files that use it decrypt correctly that way.
case version == 3: version = 2
}
if version > 5 { return .Unsupported_Encryption }
if revision == 0 {
switch version {
case 1: revision = 2
case 2: revision = 3
case 4: revision = 4
case: revision = 6
}
}
if revision < 2 || revision > 6 { return .Unsupported_Encryption }
// V5 without R5/R6, or R5/R6 without V5, is a file that contradicts itself:
// /V 5 promises a 32-byte key and only R5/R6 can produce one. The /U value
// settles it -- 48 bytes is the R5/R6 layout, 32 the older one -- because
// that is the field the algorithms actually consume.
u_len := len(dict_bytes(doc, ed, "U"))
if revision >= 5 && version != 5 {
version = 5
add_repair(doc, .Bad_Filter_Data, 0, enc_ref)
} else if version == 5 && revision < 5 {
if u_len >= 48 {
revision = 6
} else {
version = 4
}
add_repair(doc, .Bad_Filter_Data, 0, enc_ref)
}
enc := new(Encryption, doc.allocator)
enc.ref = enc_ref
enc.version = version
enc.revision = revision
enc.encrypt_metadata = dict_bool(doc, ed, "EncryptMetadata", true)
// /P is a 32-bit field. Files write it as a negative integer, as the
// equivalent unsigned value, and occasionally as a real; take the low 32
// bits of whatever arrived and read them as signed.
p_raw := dict_int(doc, ed, "P", -1)
p_u := u32(transmute(u64)p_raw & 0xffff_ffff)
enc.permission_bits = transmute(i32)p_u
// Crypt filters (V4 and V5). Below that, everything is RC4 with the file key.
cf_bits := 0
if version >= 4 {
cf := dict_dict(doc, ed, "CF")
stmf := dict_name(doc, ed, "StmF", "Identity")
strf := dict_name(doc, ed, "StrF", "Identity")
// /EFF defaults to the stream filter, so an unspecified embedded-file
// filter never leaves attachments in the clear when streams are not.
eff := dict_name(doc, ed, "EFF", stmf)
m, b, err := crypt_filter_lookup(doc, cf, stmf, enc_ref)
if err != .None { return err }
enc.stream_method = m
if b > cf_bits { cf_bits = b }
m, b, err = crypt_filter_lookup(doc, cf, strf, enc_ref)
if err != .None { return err }
enc.string_method = m
if b > cf_bits { cf_bits = b }
m, b, err = crypt_filter_lookup(doc, cf, eff, enc_ref)
if err != .None { return err }
enc.embedded_method = m
if b > cf_bits { cf_bits = b }
// /CF is required for V5, so a V5 file that names no filter at all is
// broken and the only thing it can have meant is AES-256. An explicit
// /Identity is left alone: that one is a deliberate configuration.
if version == 5 {
if !dict_has(ed, "StmF") && enc.stream_method == .Identity {
enc.stream_method = .Aes_256
add_repair(doc, .Bad_Filter_Data, 0, enc_ref)
}
if !dict_has(ed, "StrF") && enc.string_method == .Identity {
enc.string_method = .Aes_256
}
if !dict_has(ed, "EFF") && enc.embedded_method == .Identity {
enc.embedded_method = enc.stream_method
}
}
} else {
enc.stream_method = .Rc4
enc.string_method = .Rc4
enc.embedded_method = .Rc4
}
// How long the file key is. /Length is in bits; a crypt filter's /Length is
// in bytes per the spec but in bits in plenty of files, so it is normalised
// in crypt_filter_lookup.
key_bits := 40
if version >= 2 {
key_bits = int(dict_int(doc, ed, "Length", 40))
}
if version == 4 && cf_bits > 0 {
key_bits = cf_bits
}
uses_aes_128 := enc.stream_method == .Aes_128 ||
enc.string_method == .Aes_128 ||
enc.embedded_method == .Aes_128
n: int
switch {
case version == 5:
n = 32
// An AES-128 filter needs a full 16-byte key whatever /Length claims, and
// files that pair /AESV2 with /Length 40 exist. A 10-byte key is not a
// legal AES key at all, so the method wins over the declared length.
case uses_aes_128:
n = 16
// V1 is 40-bit by definition, and so is R2 whatever /Length says
// (7.6.4.3.2, step i).
case version == 1, revision == 2:
n = 5
case:
// The clamp is what keeps a /Length of 2^31 from sizing a buffer.
n = clamp(key_bits / 8, 5, 16)
}
enc.key_bits = n * 8
enc.key = make([]byte, n, doc.allocator)
id0 := crypt_first_id(doc)
err: Error
if revision >= 5 {
err = crypt_auth_r56(doc, ed, enc, password)
} else {
err = crypt_auth_r234(doc, ed, enc, password, id0)
}
if err != .None {
return err
}
doc.encrypt = enc
return .None
}
// crypt_destroy wipes the file encryption key and detaches the handler. The
// memory itself belongs to the Document's arena and is released by `close`.
//
// Internal: `close` calls this, and calling it on a Document still in use
// would leave every later stream decoding to ciphertext.
@(private)
crypt_destroy :: proc(doc: ^Document) {
if doc == nil || doc.encrypt == nil { return }
if len(doc.encrypt.key) > 0 {
crypto.zero_explicit(raw_data(doc.encrypt.key), len(doc.encrypt.key))
}
doc.encrypt.authenticated_as = .None
doc.encrypt = nil
}
// ---------------------------------------------------------------------------
// Public accessors
// ---------------------------------------------------------------------------
// document_is_encrypted reports whether the file carried an /Encrypt
// dictionary. It stays true after a successful open: the document was
// encrypted, it is simply readable now.
document_is_encrypted :: proc(doc: ^Document) -> bool {
return doc != nil && doc.encrypt != nil
}
// encryption_permissions returns what the document's /P bits allow.
//
// An unencrypted document, and one opened with the owner password, allow
// everything: the owner is not subject to the restrictions they set
// (ISO 32000-2, 7.6.4.2).
encryption_permissions :: proc(doc: ^Document) -> Permissions {
all := Permissions{.Print, .Modify, .Copy, .Annotate, .Fill_Forms, .Extract, .Assemble, .Print_High_Res}
if doc == nil || doc.encrypt == nil { return all }
if doc.encrypt.authenticated_as == .Owner { return all }
bits := transmute(u32)doc.encrypt.permission_bits
out: Permissions
for p in Permission {
if bits & (u32(1) << u32(p)) != 0 {
out += {p}
}
}
// Revision 2 only defined bits 3..6; the rest were reserved and set to 1,
// so reporting them would be meaningless.
if doc.encrypt.revision == 2 {
out &= {.Print, .Modify, .Copy, .Annotate}
}
return out
}
// encryption_method_name names the scheme in use, for a UI or a log line.
// Not stable enough to parse.
encryption_method_name :: proc(doc: ^Document) -> string {
if doc == nil || doc.encrypt == nil { return "none" }
// Streams in the clear but strings encrypted is a real configuration -- a
// file that protects only its attachments -- so fall back to reporting the
// string filter rather than calling the whole document unencrypted.
if doc.encrypt.stream_method == .Identity {
if doc.encrypt.string_method == .Identity {
return "none (identity crypt filter)"
}
return crypt_method_name(doc.encrypt.string_method, doc.encrypt.key_bits)
}
return crypt_method_name(doc.encrypt.stream_method, doc.encrypt.key_bits)
}
@(private)
crypt_method_name :: proc(method: Crypt_Method, key_bits: int) -> string {
switch method {
case .Identity:
return "none"
case .Rc4:
// 40 and 128 are the only lengths anything produces; a file that picked
// some other multiple of 8 is legal and gets the unqualified name.
switch key_bits {
case 40: return "RC4 40-bit"
case 128: return "RC4 128-bit"
}
return "RC4"
case .Aes_128:
return "AES-128"
case .Aes_256:
return "AES-256"
}
return "unknown"
}
// ---------------------------------------------------------------------------
// Decryption of parsed objects
// ---------------------------------------------------------------------------
// decrypt_string decrypts one string object read out of indirect object `ref`.
// The result lives in the Document's arena. When nothing has to be done -- no
// encryption, an /Identity filter, or an object that was already decrypted as
// part of its object stream -- `data` is returned unchanged.
decrypt_string :: proc(doc: ^Document, ref: Ref, data: []byte) -> []byte {
if doc == nil || doc.encrypt == nil { return data }
return crypt_apply(doc, ref, data, doc.encrypt.string_method)
}
// decrypt_stream decrypts one stream payload read out of indirect object `ref`.
// The result lives in the Document's arena; see `decrypt_string` for when the
// input is passed through.
//
// `dict` is the stream's own dictionary when the caller has it, and is what
// says "this is a cross-reference stream, leave it alone". Passing it matters
// only after the document is open: `xref_load` reads the /Type /XRef stream
// before `crypt_init` has run, so `doc.encrypt` is still nil there and the
// exemption is free -- but the same object read again through `get_stream`
// reaches here with the key in place, and a second pass over plaintext turns
// the xref table into noise.
//
// One stream this cannot recognise even from its dictionary is the document's
// /Metadata, which is left in the clear when /EncryptMetadata is false. The
// caller knows which object that is; `Encryption.encrypt_metadata` says
// whether it matters.
decrypt_stream :: proc(doc: ^Document, ref: Ref, data: []byte, dict: ^Dict = nil) -> []byte {
if doc == nil || doc.encrypt == nil { return data }
// ISO 32000-2, 7.5.8.2: "the cross-reference stream shall not be
// encrypted". Read the /Type without resolving it: this runs while the
// object is still being parsed, and a genuine cross-reference stream
// always spells its /Type out directly.
if dict != nil {
if ty, ok := to_name(dict_get_raw(dict, "Type")); ok && ty == "XRef" {
return data
}
}
return crypt_apply(doc, ref, data, doc.encrypt.stream_method)
}
// decrypt_embedded_file decrypts an embedded file stream, which a file may
// protect with a different filter from the one used for page content -- the
// "encrypt the attachments only" configuration, where /StmF and /StrF are
// /Identity and only /EFF names a real filter.
decrypt_embedded_file :: proc(doc: ^Document, ref: Ref, data: []byte) -> []byte {
if doc == nil || doc.encrypt == nil { return data }
return crypt_apply(doc, ref, data, doc.encrypt.embedded_method)
}
@(private)
crypt_apply :: proc(doc: ^Document, ref: Ref, data: []byte, method: Crypt_Method) -> []byte {
enc := doc.encrypt
if method == .Identity || len(data) == 0 { return data }
// Ref{0,0} means "came out of an object stream", which was decrypted whole.
// Decrypting again would destroy it.
if ref.num == 0 { return data }
// The /Encrypt dictionary is the key's own input and is never encrypted.
if enc.ref.num != 0 && ref.num == enc.ref.num { return data }
key_buf: [32]byte
key_len := crypt_object_key(enc, method, ref, key_buf[:])
if key_len == 0 { return data }
key := key_buf[:key_len]
switch method {
case .Identity:
return data
case .Rc4:
out := make([]byte, len(data), doc.allocator)
rc4_crypt(key, out, data)
return out
case .Aes_128, .Aes_256:
return crypt_aes_decrypt(doc, key, data, ref)
}
return data
}
// crypt_aes_decrypt strips the 16-byte IV prefix, decrypts in CBC mode and
// removes the PKCS#7 padding (ISO 32000-2, 7.6.3.2).
//
// Anything that cannot be a valid payload -- shorter than one IV plus one
// block, a trailing partial block, padding that does not check out -- decrypts
// to nothing. A truncated or corrupt stream is a damaged object, not a reason
// to fail the document, and an empty stream is what every viewer shows for one.
@(private)
crypt_aes_decrypt :: proc(doc: ^Document, key, data: []byte, ref: Ref) -> []byte {
if len(data) <= aes.BLOCK_SIZE {
add_repair(doc, .Bad_Filter_Data, 0, ref)
return nil
}
body := data[aes.BLOCK_SIZE:]
// Trailing bytes that do not fill a block cannot be decrypted; dropping
// them recovers the blocks that came before, which is all that is left.
n := (len(body) / aes.BLOCK_SIZE) * aes.BLOCK_SIZE
if n == 0 {
add_repair(doc, .Bad_Filter_Data, 0, ref)
return nil
}
if n != len(body) {
add_repair(doc, .Bad_Filter_Data, 0, ref)
}
out := make([]byte, n, doc.allocator)
if !aes_cbc_decrypt(out, body[:n], key, data[:aes.BLOCK_SIZE]) {
add_repair(doc, .Bad_Filter_Data, 0, ref)
return nil
}
pad := int(out[n - 1])
if pad < 1 || pad > aes.BLOCK_SIZE || pad > n {
add_repair(doc, .Bad_Filter_Data, 0, ref)
return nil
}
for i in n - pad ..< n {
if int(out[i]) != pad {
add_repair(doc, .Bad_Filter_Data, 0, ref)
return nil
}
}
return out[:n - pad]
}
// crypt_object_key builds the key for one indirect object (Algorithm 1,
// ISO 32000-2, 7.6.3.1). Returns the number of bytes written to `out`.
//
// AES-256 skips this entirely: V5 uses the file key for every object, which is
// what made the 2048-object-per-key limit of the earlier scheme go away.
@(private)
crypt_object_key :: proc(enc: ^Encryption, method: Crypt_Method, ref: Ref, out: []byte) -> int {
if method == .Aes_256 {
if len(enc.key) != aes.KEY_SIZE_256 { return 0 }
copy(out, enc.key)
return aes.KEY_SIZE_256
}
n := len(enc.key)
if n == 0 || n > 16 { return 0 }
ctx: md5.Context
md5.init(&ctx)
md5.update(&ctx, enc.key)
// Low three bytes of the object number then low two of the generation,
// both low-order byte first.
ext := [5]byte{
byte(ref.num),
byte(ref.num >> 8),
byte(ref.num >> 16),
byte(ref.gen),
byte(ref.gen >> 8),
}
md5.update(&ctx, ext[:])
if method == .Aes_128 {
md5.update(&ctx, CRYPT_AES_SALT[:])
}
digest: [md5.DIGEST_SIZE]byte
md5.final(&ctx, digest[:])
key_len := min(n + 5, 16)
copy(out[:key_len], digest[:key_len])
return key_len
}
// ---------------------------------------------------------------------------
// Crypt filters
// ---------------------------------------------------------------------------
@(private)
crypt_filter_lookup :: proc(doc: ^Document, cf: ^Dict, name: string, ref: Ref) -> (method: Crypt_Method, bits: int, err: Error) {
if name == "" || name == "Identity" {
return .Identity, 0, .None
}
f := dict_dict(doc, cf, name)
if f == nil {
// /StmF naming a filter that /CF does not define. The file is broken;
// passing the bytes through at least leaves any part of the document
// that uses a filter we *can* read intact.
add_repair(doc, .Bad_Filter_Data, 0, ref)
return .Identity, 0, .None
}
// The spec says a crypt filter's /Length is in bytes, but producers write
// bits often enough that every reader guesses. Nothing legal is both a
// plausible byte count and a plausible bit count, so 40 splits them.
n := int(dict_int(doc, f, "Length", 0))
if n > 0 && n < 40 { n *= 8 }
if n > 256 { n = 256 }
switch dict_name(doc, f, "CFM", "None") {
case "None": return .Identity, 0, .None
case "V2": return .Rc4, n, .None
case "AESV2": return .Aes_128, 128, .None
case "AESV3": return .Aes_256, 256, .None
}
// An unrecognised /CFM is a scheme we would silently mangle every object
// with. Better to say so.
return .Identity, 0, .Unsupported_Encryption
}
// crypt_first_id returns the first element of the trailer's /ID array, an input
// to the key derivation for R2..R4. A missing or malformed /ID is common in
// damaged files and reads as empty, which is what other readers do.
@(private)
crypt_first_id :: proc(doc: ^Document) -> []byte {
id, _ := to_bytes(array_get(doc, dict_array(doc, doc.trailer, "ID"), 0))
return id
}
// ---------------------------------------------------------------------------
// Key derivation, revisions 2 to 4
// ---------------------------------------------------------------------------
@(private)
crypt_auth_r234 :: proc(doc: ^Document, ed: ^Dict, enc: ^Encryption, password, id0: []byte) -> Error {
ovalue, uvalue: [32]byte
crypt_fixed(dict_bytes(doc, ed, "O"), ovalue[:])
crypt_fixed(dict_bytes(doc, ed, "U"), uvalue[:])
// As the user password.
crypt_key_r234(password, ovalue[:], enc.permission_bits, id0,
enc.revision, len(enc.key), enc.encrypt_metadata, enc.key)
if crypt_check_user(enc.key, enc.revision, id0, uvalue[:]) {
enc.authenticated_as = .User
return .None
}
// As the owner password: /O decrypts to the user password (Algorithm 7),
// which is then checked the ordinary way.
recovered: [32]byte
crypt_owner_password(password, ovalue[:], enc.revision, len(enc.key), recovered[:])
crypt_key_r234(recovered[:], ovalue[:], enc.permission_bits, id0,
enc.revision, len(enc.key), enc.encrypt_metadata, enc.key)
if crypt_check_user(enc.key, enc.revision, id0, uvalue[:]) {
enc.authenticated_as = .Owner
return .None
}
crypto.zero_explicit(raw_data(enc.key), len(enc.key))
return .Wrong_Password
}
// crypt_pad_password writes the 32-byte padded password of Algorithm 2 step (a).
@(private)
crypt_pad_password :: proc(password: []byte, out: []byte) {
n := min(len(password), 32)
copy(out[:n], password[:n])
copy(out[n:32], CRYPT_PAD[:32 - n])
}
// crypt_fixed copies `src` into `out`, zero-filling what is missing. /O and /U
// are fixed-width fields, and a file that stores them short would otherwise
// index out of bounds.
@(private)
crypt_fixed :: proc(src: []byte, out: []byte) {
n := min(len(src), len(out))
copy(out[:n], src[:n])
for i in n ..< len(out) {
out[i] = 0
}
}
// crypt_key_r234 is Algorithm 2: the file encryption key for revisions 2 to 4.
@(private)
crypt_key_r234 :: proc(password, ovalue: []byte, permissions: i32, id0: []byte,
revision, n: int, encrypt_metadata: bool, out: []byte) {
// The key cannot be longer than the MD5 digest it is cut from, whatever a
// contradictory /V and /R pair asked for.
key_len := min(n, len(out), md5.DIGEST_SIZE)
if key_len <= 0 { return }
padded: [32]byte
crypt_pad_password(password, padded[:])
ctx: md5.Context
md5.init(&ctx)
md5.update(&ctx, padded[:])
md5.update(&ctx, ovalue[:32])
// /P as four bytes, low-order first, of the *unsigned* interpretation.
pu := transmute(u32)permissions
pbytes := [4]byte{byte(pu), byte(pu >> 8), byte(pu >> 16), byte(pu >> 24)}
md5.update(&ctx, pbytes[:])
md5.update(&ctx, id0)
// R4 with /EncryptMetadata false folds in four 0xFF bytes, so that a file
// whose metadata was left in the clear gets a different key from one whose
// metadata was not (7.6.4.3.2, step f).
if revision >= 4 && !encrypt_metadata {
ff := [4]byte{0xff, 0xff, 0xff, 0xff}
md5.update(&ctx, ff[:])
}
digest: [md5.DIGEST_SIZE]byte
md5.final(&ctx, digest[:])
// R3 and later iterate 50 times over the *first n bytes* of the digest.
// Algorithm 3, which builds /O, iterates over all 16 -- the asymmetry is
// real and getting it wrong only shows up on owner passwords.
if revision >= 3 {
for _ in 0 ..< 50 {
md5.init(&ctx)
md5.update(&ctx, digest[:key_len])
md5.final(&ctx, digest[:])
}
}
copy(out[:key_len], digest[:key_len])
}
// crypt_user_value is Algorithm 4 (R2) and Algorithm 5 (R3+): what /U must
// contain if `key` is the right file encryption key.
@(private)
crypt_user_value :: proc(key: []byte, revision: int, id0: []byte, out: []byte) {
n := min(len(key), 16)
if n == 0 {
crypt_fixed(nil, out[:32])
return
}
if revision == 2 {
copy(out[:32], CRYPT_PAD[:])
rc4_crypt(key[:n], out[:32], out[:32])
return
}
ctx: md5.Context
md5.init(&ctx)
md5.update(&ctx, CRYPT_PAD[:])
md5.update(&ctx, id0)
digest: [md5.DIGEST_SIZE]byte
md5.final(&ctx, digest[:])
rc4_crypt(key[:n], digest[:], digest[:])
tmp: [16]byte
for i in 1 ..= 19 {
for j in 0 ..< n {
tmp[j] = key[j] ~ byte(i)
}
rc4_crypt(tmp[:n], digest[:], digest[:])
}
copy(out[:16], digest[:])
// The last 16 bytes are explicitly arbitrary padding and are never checked.
for i in 16 ..< 32 {
out[i] = 0
}
}
// crypt_check_user is Algorithm 6. R2 compares all 32 bytes; R3 and later only
// the first 16, the rest being padding the producer chose freely.
@(private)
crypt_check_user :: proc(key: []byte, revision: int, id0: []byte, uvalue: []byte) -> bool {
computed: [32]byte
crypt_user_value(key, revision, id0, computed[:])
n := revision == 2 ? 32 : 16
return crypt_equal(computed[:n], uvalue[:n])
}
// crypt_owner_password is Algorithm 7: decrypt /O back into the padded user
// password. Whether it *is* the user password is decided by the caller.
@(private)
crypt_owner_password :: proc(password, ovalue: []byte, revision, n: int, out: []byte) {
padded: [32]byte
crypt_pad_password(password, padded[:])
ctx: md5.Context
md5.init(&ctx)
md5.update(&ctx, padded[:])
digest: [md5.DIGEST_SIZE]byte
md5.final(&ctx, digest[:])
if revision >= 3 {
for _ in 0 ..< 50 {
md5.init(&ctx)
md5.update(&ctx, digest[:])
md5.final(&ctx, digest[:])
}
}
key_len := revision == 2 ? 5 : min(max(n, 1), 16)
copy(out[:32], ovalue[:32])
if revision == 2 {
rc4_crypt(digest[:key_len], out[:32], out[:32])
return
}
tmp: [16]byte
for i := 19; i >= 0; i -= 1 {
for j in 0 ..< key_len {
tmp[j] = digest[j] ~ byte(i)
}
rc4_crypt(tmp[:key_len], out[:32], out[:32])
}
}
// ---------------------------------------------------------------------------
// Key derivation, revisions 5 and 6 (AES-256)
//
// R5 is Adobe's deprecated extension: a bare SHA-256. R6 is what ISO 32000-2
// standardised, with the hardening loop of Algorithm 2.B in front of it.
// Both share the /U and /O layout: 32 bytes of hash, an 8-byte validation
// salt, an 8-byte key salt.
// ---------------------------------------------------------------------------
@(private)
crypt_auth_r56 :: proc(doc: ^Document, ed: ^Dict, enc: ^Encryption, password: []byte) -> Error {
ovalue, uvalue: [48]byte
crypt_fixed(dict_bytes(doc, ed, "O"), ovalue[:])
crypt_fixed(dict_bytes(doc, ed, "U"), uvalue[:])
// ISO 32000-2 asks for SASLprep here. Every implementation in practice uses
// the UTF-8 bytes as given, truncated to 127, and files produced from
// non-ASCII passwords are rare enough that the difference has never bitten.
pw := password
if len(pw) > 127 { pw = pw[:127] }
hash: [32]byte
intermediate: [32]byte
wrapped: []byte
crypt_hash_r56(enc.revision, pw, uvalue[32:40], nil, hash[:])
if crypt_equal(hash[:], uvalue[:32]) {
enc.authenticated_as = .User
crypt_hash_r56(enc.revision, pw, uvalue[40:48], nil, intermediate[:])
wrapped = dict_bytes(doc, ed, "UE")
} else {
// The owner check folds in the whole 48-byte /U value, which is what
// stops an /O from being lifted into another document.
crypt_hash_r56(enc.revision, pw, ovalue[32:40], uvalue[:], hash[:])
if !crypt_equal(hash[:], ovalue[:32]) {
crypto.zero_explicit(raw_data(enc.key), len(enc.key))
return .Wrong_Password
}
enc.authenticated_as = .Owner
crypt_hash_r56(enc.revision, pw, ovalue[40:48], uvalue[:], intermediate[:])
wrapped = dict_bytes(doc, ed, "OE")
}
if len(wrapped) < 32 || len(enc.key) != 32 {
// Authenticated, but the wrapped file key is missing or truncated.
return .Bad_Encrypt_Dict
}
zero_iv: [16]byte
if !aes_cbc_decrypt(enc.key, wrapped[:32], intermediate[:], zero_iv[:]) {
return .Bad_Encrypt_Dict
}
crypt_check_perms(doc, ed, enc)
return .None
}
// crypt_check_perms is Algorithm 13: /Perms is the permission bits encrypted
// with the file key, so that they cannot be edited without it. A mismatch is
// recorded and ignored -- the document still decrypts, and refusing to open a
// file whose permissions were tampered with helps nobody.
@(private)
crypt_check_perms :: proc(doc: ^Document, ed: ^Dict, enc: ^Encryption) {
perms := dict_bytes(doc, ed, "Perms")
if len(perms) < 16 || len(enc.key) != aes.KEY_SIZE_256 { return }
ctx: aes.Context_ECB
aes.init_ecb(&ctx, enc.key)
defer aes.reset_ecb(&ctx)
out: [16]byte
aes.decrypt_ecb(&ctx, out[:], perms[:16])
if out[9] != 'a' || out[10] != 'd' || out[11] != 'b' {
add_repair(doc, .Bad_Filter_Data, 0, enc.ref)
return
}
p := u32(out[0]) | u32(out[1]) << 8 | u32(out[2]) << 16 | u32(out[3]) << 24
if transmute(i32)p != enc.permission_bits {
add_repair(doc, .Bad_Filter_Data, 0, enc.ref)
}
}
// The hardening loop of Algorithm 2.B works on `password || K || udata`
// repeated 64 times. K is a SHA-2 digest, so at most 64 bytes; the password is
// truncated to 127 and udata is the 48-byte /U value. That caps one round at
// 239 * 64 = 15296 bytes, small enough to size the buffers for the worst case
// and keep the whole thing off the heap.
@(private)
CRYPT_R6_MAX_SEGMENT :: 127 + 64 + 48
@(private)
CRYPT_R6_MAX_BLOCK :: CRYPT_R6_MAX_SEGMENT * 64
// The exit test is `last byte of E <= round - 32`, and the last byte is at most
// 255, so round 288 ends the loop whatever the data does. The cap exists only
// so that a bug here cannot become a hang.
@(private)
CRYPT_R6_MAX_ROUNDS :: 512
// crypt_hash_r56 is Algorithm 2.B for R6, and the plain SHA-256 of R5.
// `out` receives 32 bytes.
@(private)
crypt_hash_r56 :: proc(revision: int, password, salt, udata: []byte, out: []byte) {
pw := password
if len(pw) > 127 { pw = pw[:127] }
ud := udata
if len(ud) > 48 { ud = ud[:48] }
k: [64]byte
ctx: sha2.Context_256
sha2.init_256(&ctx)
sha2.update(&ctx, pw)
sha2.update(&ctx, salt)
sha2.update(&ctx, ud)
sha2.final(&ctx, k[:32])
if revision >= 6 {
crypt_hash_2b(pw, ud, k[:])
}
copy(out[:32], k[:32])
}
// crypt_hash_2b runs the hardening rounds of Algorithm 2.B in place on `k`,
// whose first 32 bytes arrive holding the initial SHA-256 and whose first 32
// bytes leave holding the answer.
//
// It is a separate procedure so that its two 15 KB buffers are not on the stack
// of every R5 hash as well.
@(private)
crypt_hash_2b :: proc(pw, ud: []byte, k: []byte) {
k1: [CRYPT_R6_MAX_BLOCK]byte
e: [CRYPT_R6_MAX_BLOCK]byte
k_len := 32
last: byte = 0
for round := 0; round < 64 || int(last) > round - 32; round += 1 {
if round >= CRYPT_R6_MAX_ROUNDS { break }
seg := len(pw) + k_len + len(ud)
if seg <= 0 || seg > CRYPT_R6_MAX_SEGMENT { break }
off := 0
copy(k1[off:], pw); off += len(pw)
copy(k1[off:], k[:k_len]); off += k_len
copy(k1[off:], ud)
for j in 1 ..< 64 {
copy(k1[j * seg:], k1[:seg])
}
// seg * 64 is always a whole number of AES blocks, whatever seg is.
total := seg * 64
if !aes_cbc_encrypt(e[:total], k1[:total], k[:16], k[16:32]) { break }
// The first 16 bytes of E as a big-endian integer, modulo 3. 256 is
// 1 mod 3, so the byte sum has the same residue.
sum := 0
for z in 0 ..< 16 {
sum += int(e[z])
}
switch sum % 3 {
case 0:
ctx: sha2.Context_256
sha2.init_256(&ctx)
sha2.update(&ctx, e[:total])
sha2.final(&ctx, k[:32])
k_len = 32
case 1:
ctx: sha2.Context_512
sha2.init_384(&ctx)
sha2.update(&ctx, e[:total])
sha2.final(&ctx, k[:48])
k_len = 48
case:
ctx: sha2.Context_512
sha2.init_512(&ctx)
sha2.update(&ctx, e[:total])
sha2.final(&ctx, k[:64])
k_len = 64
}
last = e[total - 1]
}
}
// ---------------------------------------------------------------------------
// RC4
//
// core has no ARC4 -- reasonably, since nothing new should use it -- so this is
// the whole of it. Keys here are 5 to 16 bytes.
// ---------------------------------------------------------------------------
@(private)
Rc4_State :: struct {
s: [256]byte,
i, j: byte,
}
@(private)
rc4_init :: proc "contextless" (ctx: ^Rc4_State, key: []byte) {
for i in 0 ..< 256 {
ctx.s[i] = byte(i)
}
ctx.i, ctx.j = 0, 0
if len(key) == 0 { return }
j: byte = 0
for i in 0 ..< 256 {
j = j + ctx.s[i] + key[i % len(key)]
ctx.s[i], ctx.s[j] = ctx.s[j], ctx.s[i]
}
}
// rc4_xor runs the keystream over `src` into `dst`. The two may be the same
// slice. Bytes past len(src) in dst are untouched.
@(private)
rc4_xor :: proc "contextless" (ctx: ^Rc4_State, dst, src: []byte) {
n := min(len(dst), len(src))
i, j := ctx.i, ctx.j
for pos in 0 ..< n {
i += 1
j += ctx.s[i]
ctx.s[i], ctx.s[j] = ctx.s[j], ctx.s[i]
dst[pos] = src[pos] ~ ctx.s[byte(ctx.s[i] + ctx.s[j])]
}
ctx.i, ctx.j = i, j
}
// rc4_crypt is the one-shot form. RC4 is its own inverse, so this both
// encrypts and decrypts. An empty key passes the data through rather than
// dividing by zero.
@(private)
rc4_crypt :: proc "contextless" (key, dst, src: []byte) {
if len(key) == 0 {
copy(dst, src)
return
}
ctx: Rc4_State
rc4_init(&ctx, key)
rc4_xor(&ctx, dst, src)
}
// ---------------------------------------------------------------------------
// AES-CBC
//
// core:crypto/aes has ECB, CTR and GCM but not CBC, which is the one mode PDF
// uses. Building it on encrypt_ecb/decrypt_ecb is a dozen lines and keeps the
// constant-time block implementation core provides.
//
// Neither of these does padding: PDF's PKCS#7 padding is handled by
// crypt_aes_decrypt, and Algorithm 2.B's inner loop is explicitly unpadded.
// ---------------------------------------------------------------------------
@(private)
aes_key_size_ok :: proc "contextless" (key: []byte) -> bool {
switch len(key) {
case aes.KEY_SIZE_128, aes.KEY_SIZE_192, aes.KEY_SIZE_256:
return true
}
return false
}
// aes_cbc_encrypt encrypts `src` into `dst`. `src` must be a whole number of
// blocks; `dst` must be at least as long. Returns false on a bad key size, a
// short destination or a partial final block -- never a panic, because every
// one of those can be reached from a file.
@(private)
aes_cbc_encrypt :: proc(dst, src, key, iv: []byte) -> bool {
if !aes_key_size_ok(key) { return false }
if len(iv) != aes.BLOCK_SIZE { return false }
if len(src) % aes.BLOCK_SIZE != 0 { return false }
if len(dst) < len(src) { return false }
ctx: aes.Context_ECB
aes.init_ecb(&ctx, key)
defer aes.reset_ecb(&ctx)
prev: [aes.BLOCK_SIZE]byte
copy(prev[:], iv)
block: [aes.BLOCK_SIZE]byte
for off := 0; off < len(src); off += aes.BLOCK_SIZE {
for i in 0 ..< aes.BLOCK_SIZE {
block[i] = src[off + i] ~ prev[i]
}
aes.encrypt_ecb(&ctx, dst[off:off + aes.BLOCK_SIZE], block[:])
copy(prev[:], dst[off:off + aes.BLOCK_SIZE])
}
return true
}
// aes_cbc_decrypt decrypts `src` into `dst`, which may be the same slice.
@(private)
aes_cbc_decrypt :: proc(dst, src, key, iv: []byte) -> bool {
if !aes_key_size_ok(key) { return false }
if len(iv) != aes.BLOCK_SIZE { return false }
if len(src) % aes.BLOCK_SIZE != 0 { return false }
if len(dst) < len(src) { return false }
ctx: aes.Context_ECB
aes.init_ecb(&ctx, key)
defer aes.reset_ecb(&ctx)
prev: [aes.BLOCK_SIZE]byte
copy(prev[:], iv)
saved: [aes.BLOCK_SIZE]byte
for off := 0; off < len(src); off += aes.BLOCK_SIZE {
// Saved before decrypting, so that dst and src may alias.
copy(saved[:], src[off:off + aes.BLOCK_SIZE])
aes.decrypt_ecb(&ctx, dst[off:off + aes.BLOCK_SIZE], src[off:off + aes.BLOCK_SIZE])
for i in 0 ..< aes.BLOCK_SIZE {
dst[off + i] ~= prev[i]
}
copy(prev[:], saved[:])
}
return true
}
// crypt_equal compares in constant time. Password checks are the one place in
// this library where how long a comparison took is information.
@(private)
crypt_equal :: proc "contextless" (a, b: []byte) -> bool {
if len(a) != len(b) { return false }
diff: byte = 0
for i in 0 ..< len(a) {
diff |= a[i] ~ b[i]
}
return diff == 0
}