1093 lines
39 KiB
Odin
1093 lines
39 KiB
Odin
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
|
||
|
|
}
|