mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-01 22:17:29 +08:00
core: decouple security handlers with different revisions from PdfCrypt
This commit is contained in:
parent
7e9f3dd7e2
commit
1c19ba9e96
@ -6,19 +6,10 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rc4"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
@ -546,47 +537,30 @@ func (crypt *PdfCrypt) GetAccessPermissions() AccessPermissions {
|
||||
return crypt.encryptStd.P
|
||||
}
|
||||
|
||||
// Check whether the specified password can be used to decrypt the document.
|
||||
func (crypt *PdfCrypt) authenticate(password []byte) (bool, error) {
|
||||
// Also build the encryption/decryption key.
|
||||
|
||||
crypt.authenticated = false
|
||||
func (crypt *PdfCrypt) securityHandler() stdSecurityHandler {
|
||||
if crypt.encryptStd.R >= 5 {
|
||||
authenticated, err := crypt.alg2a(password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
crypt.authenticated = authenticated
|
||||
return authenticated, err
|
||||
return stdHandlerR6{}
|
||||
}
|
||||
return stdHandlerR4{
|
||||
ID0: crypt.id0,
|
||||
Length: crypt.encrypt.Length,
|
||||
}
|
||||
}
|
||||
|
||||
// Try user password.
|
||||
common.Log.Trace("Debugging authentication - user pass")
|
||||
authenticated, err := crypt.alg6(password)
|
||||
// Check whether the specified password can be used to decrypt the document.
|
||||
// Also build the encryption/decryption key.
|
||||
func (crypt *PdfCrypt) authenticate(password []byte) (bool, error) {
|
||||
crypt.authenticated = false
|
||||
h := crypt.securityHandler()
|
||||
fkey, perm, err := h.Authenticate(&crypt.encryptStd, password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if perm == 0 || len(fkey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if authenticated {
|
||||
common.Log.Trace("this.authenticated = True")
|
||||
crypt.authenticated = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Try owner password also.
|
||||
// May not be necessary if only want to get all contents.
|
||||
// (user pass needs to be known or empty).
|
||||
common.Log.Trace("Debugging authentication - owner pass")
|
||||
authenticated, err = crypt.alg7(password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if authenticated {
|
||||
common.Log.Trace("this.authenticated = True")
|
||||
crypt.authenticated = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
crypt.authenticated = true
|
||||
crypt.encryptionKey = fkey
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check access rights and permissions for a specified password. If either user/owner password is specified,
|
||||
@ -596,68 +570,15 @@ func (crypt *PdfCrypt) authenticate(password []byte) (bool, error) {
|
||||
// The AccessPermissions shows what access the user has for editing etc.
|
||||
// An error is returned if there was a problem performing the authentication.
|
||||
func (crypt *PdfCrypt) checkAccessRights(password []byte) (bool, AccessPermissions, error) {
|
||||
// Try owner password -> full rights.
|
||||
var (
|
||||
isOwner bool
|
||||
err error
|
||||
)
|
||||
if crypt.encryptStd.R >= 5 {
|
||||
var h []byte
|
||||
h, err = crypt.alg12(password)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
isOwner = len(h) != 0
|
||||
} else {
|
||||
isOwner, err = crypt.alg7(password)
|
||||
}
|
||||
h := crypt.securityHandler()
|
||||
// TODO(dennwc): it computes an encryption key as well; if necessary, define a new interface method to optimize this
|
||||
fkey, perm, err := h.Authenticate(&crypt.encryptStd, password)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
} else if perm == 0 || len(fkey) == 0 {
|
||||
return false, 0, nil
|
||||
}
|
||||
if isOwner {
|
||||
// owner -> full rights.
|
||||
return true, PermOwner, nil
|
||||
}
|
||||
|
||||
// Try user password.
|
||||
var isUser bool
|
||||
if crypt.encryptStd.R >= 5 {
|
||||
var h []byte
|
||||
h, err = crypt.alg11(password)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
isUser = len(h) != 0
|
||||
} else {
|
||||
isUser, err = crypt.alg6(password)
|
||||
}
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
if isUser {
|
||||
// User password specified correctly -> access granted with specified permissions.
|
||||
return true, crypt.encryptStd.P, nil
|
||||
}
|
||||
|
||||
// Cannot even view the file.
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
func (crypt *PdfCrypt) paddedPass(pass []byte) []byte {
|
||||
key := make([]byte, 32)
|
||||
if len(pass) >= 32 {
|
||||
for i := 0; i < 32; i++ {
|
||||
key[i] = pass[i]
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(pass); i++ {
|
||||
key[i] = pass[i]
|
||||
}
|
||||
for i := len(pass); i < 32; i++ {
|
||||
key[i] = padding[i-len(pass)]
|
||||
}
|
||||
}
|
||||
return key
|
||||
return true, perm, nil
|
||||
}
|
||||
|
||||
// Generates a key for encrypting a specific object based on the
|
||||
@ -1073,724 +994,13 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64)
|
||||
return nil
|
||||
}
|
||||
|
||||
// aesZeroIV allocates a zero-filled buffer that serves as an initialization vector for AESv3.
|
||||
func (crypt *PdfCrypt) aesZeroIV() []byte {
|
||||
if crypt.ivAESZero == nil {
|
||||
crypt.ivAESZero = make([]byte, aes.BlockSize)
|
||||
}
|
||||
return crypt.ivAESZero
|
||||
}
|
||||
|
||||
// alg2a retrieves the encryption key from an encrypted document (R >= 5).
|
||||
// It returns false if the password was wrong.
|
||||
// 7.6.4.3.2 Algorithm 2.A (page 83)
|
||||
func (crypt *PdfCrypt) alg2a(pass []byte) (bool, error) {
|
||||
// O & U: 32 byte hash + 8 byte Validation Salt + 8 byte Key Salt
|
||||
|
||||
// step a: Unicode normalization
|
||||
// TODO(dennwc): make sure that UTF-8 strings are normalized
|
||||
|
||||
// step b: truncate to 127 bytes
|
||||
if len(pass) > 127 {
|
||||
pass = pass[:127]
|
||||
}
|
||||
|
||||
// step c: test pass against the owner key
|
||||
h, err := crypt.alg12(pass)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var (
|
||||
data []byte // data to hash
|
||||
ekey []byte // encrypted file key
|
||||
ukey []byte // user key; set only when using owner's password
|
||||
)
|
||||
if len(h) != 0 {
|
||||
// owner password valid
|
||||
|
||||
// step d: compute an intermediate owner key
|
||||
str := make([]byte, len(pass)+8+48)
|
||||
i := copy(str, pass)
|
||||
i += copy(str[i:], crypt.encryptStd.O[40:48]) // owner Key Salt
|
||||
i += copy(str[i:], crypt.encryptStd.U[0:48])
|
||||
|
||||
data = str
|
||||
ekey = crypt.encryptStd.OE
|
||||
ukey = crypt.encryptStd.U[0:48]
|
||||
} else {
|
||||
// check user password
|
||||
h, err = crypt.alg11(pass)
|
||||
if err == nil && len(h) == 0 {
|
||||
// try default password
|
||||
h, err = crypt.alg11([]byte(""))
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if len(h) == 0 {
|
||||
// wrong password
|
||||
return false, nil
|
||||
}
|
||||
// step e: compute an intermediate user key
|
||||
str := make([]byte, len(pass)+8)
|
||||
i := copy(str, pass)
|
||||
i += copy(str[i:], crypt.encryptStd.U[40:48]) // user Key Salt
|
||||
|
||||
data = str
|
||||
ekey = crypt.encryptStd.UE
|
||||
ukey = nil
|
||||
}
|
||||
ekey = ekey[:32]
|
||||
|
||||
// intermediate key
|
||||
ikey := crypt.alg2b(data, pass, ukey)
|
||||
|
||||
ac, err := aes.NewCipher(ikey[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := crypt.aesZeroIV()
|
||||
cbc := cipher.NewCBCDecrypter(ac, iv)
|
||||
fkey := make([]byte, 32)
|
||||
cbc.CryptBlocks(fkey, ekey)
|
||||
|
||||
crypt.encryptionKey = fkey
|
||||
|
||||
if crypt.encryptStd.R == 5 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return crypt.alg13(fkey)
|
||||
}
|
||||
|
||||
// alg2b computes a hash for R=5 and R=6.
|
||||
func (crypt *PdfCrypt) alg2b(data, pwd, userKey []byte) []byte {
|
||||
if crypt.encryptStd.R == 5 {
|
||||
return alg2b_R5(data)
|
||||
}
|
||||
return alg2b(data, pwd, userKey)
|
||||
}
|
||||
|
||||
// alg2b_R5 computes a hash for R=5, used in a deprecated extension.
|
||||
// It's used the same way as a hash described in Algorithm 2.B, but it doesn't use the original password
|
||||
// and the user key to calculate the hash.
|
||||
func alg2b_R5(data []byte) []byte {
|
||||
h := sha256.New()
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// repeat repeats first n bytes of buf until the end of the buffer.
|
||||
// It assumes that the length of buf is a multiple of n.
|
||||
func repeat(buf []byte, n int) {
|
||||
bp := n
|
||||
for bp < len(buf) {
|
||||
copy(buf[bp:], buf[:bp])
|
||||
bp *= 2
|
||||
}
|
||||
}
|
||||
|
||||
// alg2b computes a hash for R=6.
|
||||
// 7.6.4.3.3 Algorithm 2.B (page 83)
|
||||
func alg2b(data, pwd, userKey []byte) []byte {
|
||||
var (
|
||||
s256, s384, s512 hash.Hash
|
||||
)
|
||||
s256 = sha256.New()
|
||||
hbuf := make([]byte, 64)
|
||||
|
||||
h := s256
|
||||
h.Write(data)
|
||||
K := h.Sum(hbuf[:0])
|
||||
|
||||
buf := make([]byte, 64*(127+64+48))
|
||||
|
||||
round := func(rnd int) (E []byte) {
|
||||
// step a: repeat pass+K 64 times
|
||||
n := len(pwd) + len(K) + len(userKey)
|
||||
part := buf[:n]
|
||||
i := copy(part, pwd)
|
||||
i += copy(part[i:], K[:])
|
||||
i += copy(part[i:], userKey)
|
||||
if i != n {
|
||||
panic("wrong size")
|
||||
}
|
||||
K1 := buf[:n*64]
|
||||
repeat(K1, n)
|
||||
|
||||
// step b: encrypt K1 with AES-128 CBC
|
||||
ac, err := aes.NewCipher(K[0:16])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cbc := cipher.NewCBCEncrypter(ac, K[16:32])
|
||||
cbc.CryptBlocks(K1, K1)
|
||||
E = K1
|
||||
|
||||
// step c: use 16 bytes of E as big-endian int, select the next hash
|
||||
b := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
b += int(E[i] % 3)
|
||||
}
|
||||
var h hash.Hash
|
||||
switch b % 3 {
|
||||
case 0:
|
||||
h = s256
|
||||
case 1:
|
||||
if s384 == nil {
|
||||
s384 = sha512.New384()
|
||||
}
|
||||
h = s384
|
||||
case 2:
|
||||
if s512 == nil {
|
||||
s512 = sha512.New()
|
||||
}
|
||||
h = s512
|
||||
}
|
||||
|
||||
// step d: take the hash of E, use as a new K
|
||||
h.Reset()
|
||||
h.Write(E)
|
||||
K = h.Sum(hbuf[:0])
|
||||
|
||||
return E
|
||||
}
|
||||
|
||||
for i := 0; ; {
|
||||
E := round(i)
|
||||
b := uint8(E[len(E)-1])
|
||||
// from the spec, it appears that i should be incremented after
|
||||
// the test, but that doesn't match what Adobe does
|
||||
i++
|
||||
if i >= 64 && b <= uint8(i-32) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return K[:32]
|
||||
}
|
||||
|
||||
// alg2 computes an encryption key.
|
||||
func (crypt *PdfCrypt) alg2(pass []byte) []byte {
|
||||
common.Log.Trace("alg2")
|
||||
key := crypt.paddedPass(pass)
|
||||
|
||||
h := md5.New()
|
||||
h.Write(key)
|
||||
|
||||
// Pass O.
|
||||
h.Write(crypt.encryptStd.O)
|
||||
|
||||
// Pass P (Lower order byte first).
|
||||
var p = uint32(crypt.encryptStd.P)
|
||||
var pb []byte
|
||||
for i := 0; i < 4; i++ {
|
||||
pb = append(pb, byte(((p >> uint(8*i)) & 0xff)))
|
||||
}
|
||||
h.Write(pb)
|
||||
common.Log.Trace("go P: % x", pb)
|
||||
|
||||
// Pass ID[0] from the trailer
|
||||
h.Write([]byte(crypt.id0))
|
||||
|
||||
common.Log.Trace("this.R = %d encryptMetadata %v", crypt.encryptStd.R, crypt.encryptStd.EncryptMetadata)
|
||||
if (crypt.encryptStd.R >= 4) && !crypt.encryptStd.EncryptMetadata {
|
||||
h.Write([]byte{0xff, 0xff, 0xff, 0xff})
|
||||
}
|
||||
hashb := h.Sum(nil)
|
||||
|
||||
if crypt.encryptStd.R >= 3 {
|
||||
for i := 0; i < 50; i++ {
|
||||
h = md5.New()
|
||||
h.Write(hashb[0 : crypt.encrypt.Length/8])
|
||||
hashb = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
if crypt.encryptStd.R >= 3 {
|
||||
return hashb[0 : crypt.encrypt.Length/8]
|
||||
}
|
||||
|
||||
return hashb[0:5]
|
||||
}
|
||||
|
||||
// Create the RC4 encryption key.
|
||||
func (crypt *PdfCrypt) alg3Key(pass []byte) []byte {
|
||||
h := md5.New()
|
||||
okey := crypt.paddedPass(pass)
|
||||
h.Write(okey)
|
||||
|
||||
if crypt.encryptStd.R >= 3 {
|
||||
for i := 0; i < 50; i++ {
|
||||
hashb := h.Sum(nil)
|
||||
h = md5.New()
|
||||
h.Write(hashb)
|
||||
}
|
||||
}
|
||||
|
||||
encKey := h.Sum(nil)
|
||||
if crypt.encryptStd.R == 2 {
|
||||
encKey = encKey[0:5]
|
||||
} else {
|
||||
encKey = encKey[0 : crypt.encrypt.Length/8]
|
||||
}
|
||||
return encKey
|
||||
}
|
||||
|
||||
// alg3 computes the encryption dictionary’s O (owner password) value.
|
||||
func (crypt *PdfCrypt) alg3(upass, opass []byte) (string, error) {
|
||||
// Return O string val.
|
||||
O := ""
|
||||
|
||||
var encKey []byte
|
||||
if len(opass) > 0 {
|
||||
encKey = crypt.alg3Key(opass)
|
||||
} else {
|
||||
encKey = crypt.alg3Key(upass)
|
||||
}
|
||||
|
||||
ociph, err := rc4.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return O, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
|
||||
ukey := crypt.paddedPass(upass)
|
||||
encrypted := make([]byte, len(ukey))
|
||||
ociph.XORKeyStream(encrypted, ukey)
|
||||
|
||||
if crypt.encryptStd.R >= 3 {
|
||||
encKey2 := make([]byte, len(encKey))
|
||||
for i := 0; i < 19; i++ {
|
||||
for j := 0; j < len(encKey); j++ {
|
||||
encKey2[j] = encKey[j] ^ byte(i+1)
|
||||
}
|
||||
ciph, err := rc4.NewCipher(encKey2)
|
||||
if err != nil {
|
||||
return O, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
ciph.XORKeyStream(encrypted, encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
O = string(encrypted)
|
||||
return O, nil
|
||||
}
|
||||
|
||||
// alg4 computes the encryption dictionary’s U (user password) value (Security handlers of revision 2).
|
||||
func (crypt *PdfCrypt) alg4(upass []byte) (string, []byte, error) {
|
||||
U := ""
|
||||
|
||||
ekey := crypt.alg2(upass)
|
||||
ciph, err := rc4.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return U, ekey, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
|
||||
s := []byte(padding)
|
||||
encrypted := make([]byte, len(s))
|
||||
ciph.XORKeyStream(encrypted, s)
|
||||
|
||||
U = string(encrypted)
|
||||
return U, ekey, nil
|
||||
}
|
||||
|
||||
// alg5 computes the encryption dictionary’s U (user password) value (Security handlers of revision 3 or greater).
|
||||
func (crypt *PdfCrypt) alg5(upass []byte) (string, []byte, error) {
|
||||
U := ""
|
||||
|
||||
ekey := crypt.alg2(upass)
|
||||
|
||||
h := md5.New()
|
||||
h.Write([]byte(padding))
|
||||
h.Write([]byte(crypt.id0))
|
||||
hash := h.Sum(nil)
|
||||
|
||||
common.Log.Trace("alg5")
|
||||
common.Log.Trace("ekey: % x", ekey)
|
||||
common.Log.Trace("ID: % x", crypt.id0)
|
||||
|
||||
if len(hash) != 16 {
|
||||
return U, ekey, errors.New("Hash length not 16 bytes")
|
||||
}
|
||||
|
||||
ciph, err := rc4.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return U, ekey, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
encrypted := make([]byte, 16)
|
||||
ciph.XORKeyStream(encrypted, hash)
|
||||
|
||||
// Do the following 19 times: Take the output from the previous
|
||||
// invocation of the RC4 function and pass it as input to a new
|
||||
// invocation of the function; use an encryption key generated by
|
||||
// taking each byte of the original encryption key obtained in step
|
||||
// (a) and performing an XOR (exclusive or) operation between that
|
||||
// byte and the single-byte value of the iteration counter (from 1 to 19).
|
||||
ekey2 := make([]byte, len(ekey))
|
||||
for i := 0; i < 19; i++ {
|
||||
for j := 0; j < len(ekey); j++ {
|
||||
ekey2[j] = ekey[j] ^ byte(i+1)
|
||||
}
|
||||
ciph, err = rc4.NewCipher(ekey2)
|
||||
if err != nil {
|
||||
return U, ekey, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
ciph.XORKeyStream(encrypted, encrypted)
|
||||
common.Log.Trace("i = %d, ekey: % x", i, ekey2)
|
||||
common.Log.Trace("i = %d -> % x", i, encrypted)
|
||||
}
|
||||
|
||||
bb := make([]byte, 32)
|
||||
for i := 0; i < 16; i++ {
|
||||
bb[i] = encrypted[i]
|
||||
}
|
||||
|
||||
// Append 16 bytes of arbitrary padding to the output from the final
|
||||
// invocation of the RC4 function and store the 32-byte result as
|
||||
// the value of the U entry in the encryption dictionary.
|
||||
_, err = rand.Read(bb[16:32])
|
||||
if err != nil {
|
||||
return U, ekey, errors.New("Failed to gen rand number")
|
||||
}
|
||||
|
||||
U = string(bb)
|
||||
return U, ekey, nil
|
||||
}
|
||||
|
||||
// alg6 authenticates the user password.
|
||||
func (crypt *PdfCrypt) alg6(upass []byte) (bool, error) {
|
||||
var uo string
|
||||
var err error
|
||||
var key []byte
|
||||
if crypt.encryptStd.R == 2 {
|
||||
uo, key, err = crypt.alg4(upass)
|
||||
} else if crypt.encryptStd.R >= 3 {
|
||||
uo, key, err = crypt.alg5(upass)
|
||||
} else {
|
||||
return false, errors.New("invalid R")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
common.Log.Trace("check: % x == % x ?", string(uo), string(crypt.encryptStd.U))
|
||||
|
||||
uGen := string(uo) // Generated U from specified pass.
|
||||
uDoc := string(crypt.encryptStd.U) // U from the document.
|
||||
if crypt.encryptStd.R >= 3 {
|
||||
// comparing on the first 16 bytes in the case of security
|
||||
// handlers of revision 3 or greater),
|
||||
if len(uGen) > 16 {
|
||||
uGen = uGen[0:16]
|
||||
}
|
||||
if len(uDoc) > 16 {
|
||||
uDoc = uDoc[0:16]
|
||||
}
|
||||
}
|
||||
|
||||
if uGen == uDoc {
|
||||
crypt.encryptionKey = key
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// alg7 authenticates the owner password.
|
||||
func (crypt *PdfCrypt) alg7(opass []byte) (bool, error) {
|
||||
encKey := crypt.alg3Key(opass)
|
||||
|
||||
decrypted := make([]byte, len(crypt.encryptStd.O))
|
||||
if crypt.encryptStd.R == 2 {
|
||||
ciph, err := rc4.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return false, errors.New("Failed cipher")
|
||||
}
|
||||
ciph.XORKeyStream(decrypted, crypt.encryptStd.O)
|
||||
} else if crypt.encryptStd.R >= 3 {
|
||||
s := append([]byte{}, crypt.encryptStd.O...)
|
||||
for i := 0; i < 20; i++ {
|
||||
//newKey := encKey
|
||||
newKey := append([]byte{}, encKey...)
|
||||
for j := 0; j < len(encKey); j++ {
|
||||
newKey[j] ^= byte(19 - i)
|
||||
}
|
||||
ciph, err := rc4.NewCipher(newKey)
|
||||
if err != nil {
|
||||
return false, errors.New("Failed cipher")
|
||||
}
|
||||
ciph.XORKeyStream(decrypted, s)
|
||||
s = append([]byte{}, decrypted...)
|
||||
}
|
||||
} else {
|
||||
return false, errors.New("invalid R")
|
||||
}
|
||||
|
||||
auth, err := crypt.alg6(decrypted)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// generateParams generates encryption parameters for specified passwords.
|
||||
func (crypt *PdfCrypt) generateParams(upass, opass []byte) error {
|
||||
if crypt.encryptStd.R < 5 {
|
||||
// Make the O and U objects.
|
||||
O, err := crypt.alg3(upass, opass)
|
||||
if err != nil {
|
||||
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
|
||||
return err
|
||||
}
|
||||
crypt.encryptStd.O = []byte(O)
|
||||
common.Log.Trace("gen O: % x", O)
|
||||
U, key, err := crypt.alg5(upass)
|
||||
if err != nil {
|
||||
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
|
||||
return err
|
||||
}
|
||||
common.Log.Trace("gen U: % x", U)
|
||||
crypt.encryptStd.U = []byte(U)
|
||||
crypt.encryptionKey = key
|
||||
return nil
|
||||
}
|
||||
crypt.encryptionKey = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, crypt.encryptionKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return crypt.generateR6(upass, opass)
|
||||
}
|
||||
|
||||
// generateR6 is the algorithm opposite to alg2a (R>=5).
|
||||
// It generates U,O,UE,OE,Perms fields using AESv3 encryption.
|
||||
// There is no algorithm number assigned to this function in the spec.
|
||||
func (crypt *PdfCrypt) generateR6(upass, opass []byte) error {
|
||||
// all these field will be populated by functions below
|
||||
crypt.encryptStd.U = nil
|
||||
crypt.encryptStd.O = nil
|
||||
crypt.encryptStd.UE = nil
|
||||
crypt.encryptStd.OE = nil
|
||||
crypt.encryptStd.Perms = nil // populated only for R=6
|
||||
|
||||
if len(upass) > 127 {
|
||||
upass = upass[:127]
|
||||
}
|
||||
if len(opass) > 127 {
|
||||
opass = opass[:127]
|
||||
}
|
||||
// generate U and UE
|
||||
if err := crypt.alg8(upass); err != nil {
|
||||
return err
|
||||
}
|
||||
// generate O and OE
|
||||
if err := crypt.alg9(opass); err != nil {
|
||||
return err
|
||||
}
|
||||
if crypt.encryptStd.R == 5 {
|
||||
return nil
|
||||
}
|
||||
// generate Perms
|
||||
return crypt.alg10()
|
||||
}
|
||||
|
||||
// alg8 computes the encryption dictionary's U (user password) and UE (user encryption) values (R>=5).
|
||||
// 7.6.4.4.6 Algorithm 8 (page 86)
|
||||
func (crypt *PdfCrypt) alg8(upass []byte) error {
|
||||
// step a: compute U (user password)
|
||||
var rbuf [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, rbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
valSalt := rbuf[0:8]
|
||||
keySalt := rbuf[8:16]
|
||||
|
||||
str := make([]byte, len(upass)+len(valSalt))
|
||||
i := copy(str, upass)
|
||||
i += copy(str[i:], valSalt)
|
||||
|
||||
h := crypt.alg2b(str, upass, nil)
|
||||
|
||||
U := make([]byte, len(h)+len(valSalt)+len(keySalt))
|
||||
i = copy(U, h[:32])
|
||||
i += copy(U[i:], valSalt)
|
||||
i += copy(U[i:], keySalt)
|
||||
|
||||
crypt.encryptStd.U = U
|
||||
|
||||
// step b: compute UE (user encryption)
|
||||
|
||||
// str still contains a password, reuse it
|
||||
i = len(upass)
|
||||
i += copy(str[i:], keySalt)
|
||||
|
||||
h = crypt.alg2b(str, upass, nil)
|
||||
|
||||
ac, err := aes.NewCipher(h[:32])
|
||||
h := crypt.securityHandler()
|
||||
ekey, err := h.GenerateParams(&crypt.encryptStd, opass, upass)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
iv := crypt.aesZeroIV()
|
||||
cbc := cipher.NewCBCEncrypter(ac, iv)
|
||||
UE := make([]byte, 32)
|
||||
cbc.CryptBlocks(UE, crypt.encryptionKey[:32])
|
||||
crypt.encryptStd.UE = UE
|
||||
|
||||
crypt.encryptionKey = ekey
|
||||
return nil
|
||||
}
|
||||
|
||||
// alg9 computes the encryption dictionary's O (owner password) and OE (owner encryption) values (R>=5).
|
||||
// 7.6.4.4.7 Algorithm 9 (page 86)
|
||||
func (crypt *PdfCrypt) alg9(opass []byte) error {
|
||||
// step a: compute O (owner password)
|
||||
var rbuf [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, rbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
valSalt := rbuf[0:8]
|
||||
keySalt := rbuf[8:16]
|
||||
userKey := crypt.encryptStd.U[:48]
|
||||
|
||||
str := make([]byte, len(opass)+len(valSalt)+len(userKey))
|
||||
i := copy(str, opass)
|
||||
i += copy(str[i:], valSalt)
|
||||
i += copy(str[i:], userKey)
|
||||
|
||||
h := crypt.alg2b(str, opass, userKey)
|
||||
|
||||
O := make([]byte, len(h)+len(valSalt)+len(keySalt))
|
||||
i = copy(O, h[:32])
|
||||
i += copy(O[i:], valSalt)
|
||||
i += copy(O[i:], keySalt)
|
||||
|
||||
crypt.encryptStd.O = O
|
||||
|
||||
// step b: compute OE (owner encryption)
|
||||
|
||||
// str still contains a password and a user key - reuse both, but overwrite the salt
|
||||
i = len(opass)
|
||||
i += copy(str[i:], keySalt)
|
||||
// i += len(userKey)
|
||||
|
||||
h = crypt.alg2b(str, opass, userKey)
|
||||
|
||||
ac, err := aes.NewCipher(h[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := crypt.aesZeroIV()
|
||||
cbc := cipher.NewCBCEncrypter(ac, iv)
|
||||
OE := make([]byte, 32)
|
||||
cbc.CryptBlocks(OE, crypt.encryptionKey[:32])
|
||||
crypt.encryptStd.OE = OE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// alg10 computes the encryption dictionary's Perms (permissions) value (R=6).
|
||||
// 7.6.4.4.8 Algorithm 10 (page 87)
|
||||
func (crypt *PdfCrypt) alg10() error {
|
||||
// step a: extend permissions to 64 bits
|
||||
perms := uint64(uint32(crypt.encryptStd.P)) | (math.MaxUint32 << 32)
|
||||
|
||||
// step b: record permissions
|
||||
Perms := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(Perms[:8], perms)
|
||||
|
||||
// step c: record EncryptMetadata
|
||||
if crypt.encryptStd.EncryptMetadata {
|
||||
Perms[8] = 'T'
|
||||
} else {
|
||||
Perms[8] = 'F'
|
||||
}
|
||||
|
||||
// step d: write "adb" magic
|
||||
copy(Perms[9:12], "adb")
|
||||
|
||||
// step e: write 4 bytes of random data
|
||||
|
||||
// spec doesn't specify them as generated "from a strong random source",
|
||||
// but we will use the cryptographic random generator anyway
|
||||
if _, err := io.ReadFull(rand.Reader, Perms[12:16]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// step f: encrypt permissions
|
||||
ac, err := aes.NewCipher(crypt.encryptionKey[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ecb := newECBEncrypter(ac)
|
||||
ecb.CryptBlocks(Perms, Perms)
|
||||
|
||||
crypt.encryptStd.Perms = Perms[:16]
|
||||
return nil
|
||||
}
|
||||
|
||||
// alg11 authenticates the user password (R >= 5) and returns the hash.
|
||||
func (crypt *PdfCrypt) alg11(upass []byte) ([]byte, error) {
|
||||
str := make([]byte, len(upass)+8)
|
||||
i := copy(str, upass)
|
||||
i += copy(str[i:], crypt.encryptStd.U[32:40]) // user Validation Salt
|
||||
|
||||
h := crypt.alg2b(str, upass, nil)
|
||||
h = h[:32]
|
||||
if !bytes.Equal(h, crypt.encryptStd.U[:32]) {
|
||||
return nil, nil
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// alg12 authenticates the owner password (R >= 5) and returns the hash.
|
||||
// 7.6.4.4.10 Algorithm 12 (page 87)
|
||||
func (crypt *PdfCrypt) alg12(opass []byte) ([]byte, error) {
|
||||
str := make([]byte, len(opass)+8+48)
|
||||
i := copy(str, opass)
|
||||
i += copy(str[i:], crypt.encryptStd.O[32:40]) // owner Validation Salt
|
||||
i += copy(str[i:], crypt.encryptStd.U[0:48])
|
||||
|
||||
h := crypt.alg2b(str, opass, crypt.encryptStd.U[0:48])
|
||||
h = h[:32]
|
||||
if !bytes.Equal(h, crypt.encryptStd.O[:32]) {
|
||||
return nil, nil
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// alg13 validates user permissions (P+EncryptMetadata vs Perms) for R=6.
|
||||
// 7.6.4.4.11 Algorithm 13 (page 87)
|
||||
func (crypt *PdfCrypt) alg13(fkey []byte) (bool, error) {
|
||||
perms := make([]byte, 16)
|
||||
copy(perms, crypt.encryptStd.Perms[:16])
|
||||
|
||||
ac, err := aes.NewCipher(fkey[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ecb := newECBDecrypter(ac)
|
||||
ecb.CryptBlocks(perms, perms)
|
||||
|
||||
if !bytes.Equal(perms[9:12], []byte("adb")) {
|
||||
return false, errors.New("decoded permissions are invalid")
|
||||
}
|
||||
p := AccessPermissions(binary.LittleEndian.Uint32(perms[0:4]))
|
||||
if p != crypt.encryptStd.P {
|
||||
return false, errors.New("permissions validation failed")
|
||||
}
|
||||
encMeta := true
|
||||
if perms[8] == 'T' {
|
||||
encMeta = true
|
||||
} else if perms[8] == 'F' {
|
||||
encMeta = false
|
||||
} else {
|
||||
return false, errors.New("decoded metadata encryption flag is invalid")
|
||||
}
|
||||
if encMeta != crypt.encryptStd.EncryptMetadata {
|
||||
return false, errors.New("metadata encryption validation failed")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
17
pdf/core/crypt_handlers.go
Normal file
17
pdf/core/crypt_handlers.go
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
type stdSecurityHandler interface {
|
||||
// GenerateParams uses owner and user passwords to set encryption parameters and generate an encryption key.
|
||||
// It assumes that R, P and EncryptMetadata are already set.
|
||||
GenerateParams(d *stdEncryptDict, ownerPass, userPass []byte) ([]byte, error)
|
||||
|
||||
// Authenticate uses encryption dictionary parameters and the password to calculate
|
||||
// the document encryption key. It also returns permissions that should be granted to a user.
|
||||
// In case of failed authentication, it returns empty key and zero permissions with no error.
|
||||
Authenticate(d *stdEncryptDict, pass []byte) ([]byte, AccessPermissions, error)
|
||||
}
|
340
pdf/core/crypt_handlers_r4.go
Normal file
340
pdf/core/crypt_handlers_r4.go
Normal file
@ -0,0 +1,340 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rc4"
|
||||
"errors"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
)
|
||||
|
||||
var _ stdSecurityHandler = stdHandlerR4{}
|
||||
|
||||
type stdHandlerR4 struct {
|
||||
Length int
|
||||
ID0 string
|
||||
}
|
||||
|
||||
func (sh stdHandlerR4) paddedPass(pass []byte) []byte {
|
||||
key := make([]byte, 32)
|
||||
if len(pass) >= 32 {
|
||||
for i := 0; i < 32; i++ {
|
||||
key[i] = pass[i]
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(pass); i++ {
|
||||
key[i] = pass[i]
|
||||
}
|
||||
for i := len(pass); i < 32; i++ {
|
||||
key[i] = padding[i-len(pass)]
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// alg2 computes an encryption key.
|
||||
func (sh stdHandlerR4) alg2(d *stdEncryptDict, pass []byte) []byte {
|
||||
common.Log.Trace("alg2")
|
||||
key := sh.paddedPass(pass)
|
||||
|
||||
h := md5.New()
|
||||
h.Write(key)
|
||||
|
||||
// Pass O.
|
||||
h.Write(d.O)
|
||||
|
||||
// Pass P (Lower order byte first).
|
||||
var p = uint32(d.P)
|
||||
var pb []byte
|
||||
for i := 0; i < 4; i++ {
|
||||
pb = append(pb, byte(((p >> uint(8*i)) & 0xff)))
|
||||
}
|
||||
h.Write(pb)
|
||||
common.Log.Trace("go P: % x", pb)
|
||||
|
||||
// Pass ID[0] from the trailer
|
||||
h.Write([]byte(sh.ID0))
|
||||
|
||||
common.Log.Trace("this.R = %d encryptMetadata %v", d.R, d.EncryptMetadata)
|
||||
if (d.R >= 4) && !d.EncryptMetadata {
|
||||
h.Write([]byte{0xff, 0xff, 0xff, 0xff})
|
||||
}
|
||||
hashb := h.Sum(nil)
|
||||
|
||||
if d.R >= 3 {
|
||||
for i := 0; i < 50; i++ {
|
||||
h = md5.New()
|
||||
h.Write(hashb[0 : sh.Length/8])
|
||||
hashb = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
if d.R >= 3 {
|
||||
return hashb[0 : sh.Length/8]
|
||||
}
|
||||
|
||||
return hashb[0:5]
|
||||
}
|
||||
|
||||
// Create the RC4 encryption key.
|
||||
func (sh stdHandlerR4) alg3Key(R int, pass []byte) []byte {
|
||||
h := md5.New()
|
||||
okey := sh.paddedPass(pass)
|
||||
h.Write(okey)
|
||||
|
||||
if R >= 3 {
|
||||
for i := 0; i < 50; i++ {
|
||||
hashb := h.Sum(nil)
|
||||
h = md5.New()
|
||||
h.Write(hashb)
|
||||
}
|
||||
}
|
||||
|
||||
encKey := h.Sum(nil)
|
||||
if R == 2 {
|
||||
encKey = encKey[0:5]
|
||||
} else {
|
||||
encKey = encKey[0 : sh.Length/8]
|
||||
}
|
||||
return encKey
|
||||
}
|
||||
|
||||
// alg3 computes the encryption dictionary’s O (owner password) value.
|
||||
func (sh stdHandlerR4) alg3(R int, upass, opass []byte) ([]byte, error) {
|
||||
var encKey []byte
|
||||
if len(opass) > 0 {
|
||||
encKey = sh.alg3Key(R, opass)
|
||||
} else {
|
||||
encKey = sh.alg3Key(R, upass)
|
||||
}
|
||||
|
||||
ociph, err := rc4.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
|
||||
ukey := sh.paddedPass(upass)
|
||||
encrypted := make([]byte, len(ukey))
|
||||
ociph.XORKeyStream(encrypted, ukey)
|
||||
|
||||
if R >= 3 {
|
||||
encKey2 := make([]byte, len(encKey))
|
||||
for i := 0; i < 19; i++ {
|
||||
for j := 0; j < len(encKey); j++ {
|
||||
encKey2[j] = encKey[j] ^ byte(i+1)
|
||||
}
|
||||
ciph, err := rc4.NewCipher(encKey2)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
ciph.XORKeyStream(encrypted, encrypted)
|
||||
}
|
||||
}
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// alg4 computes the encryption dictionary’s U (user password) value (Security handlers of revision 2).
|
||||
func (sh stdHandlerR4) alg4(ekey []byte, upass []byte) ([]byte, error) {
|
||||
ciph, err := rc4.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
|
||||
s := []byte(padding)
|
||||
encrypted := make([]byte, len(s))
|
||||
ciph.XORKeyStream(encrypted, s)
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// alg5 computes the encryption dictionary’s U (user password) value (Security handlers of revision 3 or greater).
|
||||
func (sh stdHandlerR4) alg5(ekey []byte, upass []byte) ([]byte, error) {
|
||||
h := md5.New()
|
||||
h.Write([]byte(padding))
|
||||
h.Write([]byte(sh.ID0))
|
||||
hash := h.Sum(nil)
|
||||
|
||||
common.Log.Trace("alg5")
|
||||
common.Log.Trace("ekey: % x", ekey)
|
||||
common.Log.Trace("ID: % x", sh.ID0)
|
||||
|
||||
if len(hash) != 16 {
|
||||
return nil, errors.New("Hash length not 16 bytes")
|
||||
}
|
||||
|
||||
ciph, err := rc4.NewCipher(ekey)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
encrypted := make([]byte, 16)
|
||||
ciph.XORKeyStream(encrypted, hash)
|
||||
|
||||
// Do the following 19 times: Take the output from the previous
|
||||
// invocation of the RC4 function and pass it as input to a new
|
||||
// invocation of the function; use an encryption key generated by
|
||||
// taking each byte of the original encryption key obtained in step
|
||||
// (a) and performing an XOR (exclusive or) operation between that
|
||||
// byte and the single-byte value of the iteration counter (from 1 to 19).
|
||||
ekey2 := make([]byte, len(ekey))
|
||||
for i := 0; i < 19; i++ {
|
||||
for j := 0; j < len(ekey); j++ {
|
||||
ekey2[j] = ekey[j] ^ byte(i+1)
|
||||
}
|
||||
ciph, err = rc4.NewCipher(ekey2)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed rc4 ciph")
|
||||
}
|
||||
ciph.XORKeyStream(encrypted, encrypted)
|
||||
common.Log.Trace("i = %d, ekey: % x", i, ekey2)
|
||||
common.Log.Trace("i = %d -> % x", i, encrypted)
|
||||
}
|
||||
|
||||
bb := make([]byte, 32)
|
||||
for i := 0; i < 16; i++ {
|
||||
bb[i] = encrypted[i]
|
||||
}
|
||||
|
||||
// Append 16 bytes of arbitrary padding to the output from the final
|
||||
// invocation of the RC4 function and store the 32-byte result as
|
||||
// the value of the U entry in the encryption dictionary.
|
||||
_, err = rand.Read(bb[16:32])
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to gen rand number")
|
||||
}
|
||||
return bb, nil
|
||||
}
|
||||
|
||||
// alg6 authenticates the user password and returns the document encryption key.
|
||||
// It returns an nil key in case authentication failed.
|
||||
func (sh stdHandlerR4) alg6(d *stdEncryptDict, upass []byte) ([]byte, error) {
|
||||
var (
|
||||
uo []byte
|
||||
err error
|
||||
)
|
||||
ekey := sh.alg2(d, upass)
|
||||
if d.R == 2 {
|
||||
uo, err = sh.alg4(ekey, upass)
|
||||
} else if d.R >= 3 {
|
||||
uo, err = sh.alg5(ekey, upass)
|
||||
} else {
|
||||
return nil, errors.New("invalid R")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
common.Log.Trace("check: % x == % x ?", string(uo), string(d.U))
|
||||
|
||||
uGen := uo // Generated U from specified pass.
|
||||
uDoc := d.U // U from the document.
|
||||
if d.R >= 3 {
|
||||
// comparing on the first 16 bytes in the case of security
|
||||
// handlers of revision 3 or greater),
|
||||
if len(uGen) > 16 {
|
||||
uGen = uGen[0:16]
|
||||
}
|
||||
if len(uDoc) > 16 {
|
||||
uDoc = uDoc[0:16]
|
||||
}
|
||||
}
|
||||
|
||||
if !bytes.Equal(uGen, uDoc) {
|
||||
return nil, nil
|
||||
}
|
||||
return ekey, nil
|
||||
}
|
||||
|
||||
// alg7 authenticates the owner password and returns the document encryption key.
|
||||
//// It returns an nil key in case authentication failed.
|
||||
func (sh stdHandlerR4) alg7(d *stdEncryptDict, opass []byte) ([]byte, error) {
|
||||
encKey := sh.alg3Key(d.R, opass)
|
||||
|
||||
decrypted := make([]byte, len(d.O))
|
||||
if d.R == 2 {
|
||||
ciph, err := rc4.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed cipher")
|
||||
}
|
||||
ciph.XORKeyStream(decrypted, d.O)
|
||||
} else if d.R >= 3 {
|
||||
s := append([]byte{}, d.O...)
|
||||
for i := 0; i < 20; i++ {
|
||||
//newKey := encKey
|
||||
newKey := append([]byte{}, encKey...)
|
||||
for j := 0; j < len(encKey); j++ {
|
||||
newKey[j] ^= byte(19 - i)
|
||||
}
|
||||
ciph, err := rc4.NewCipher(newKey)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed cipher")
|
||||
}
|
||||
ciph.XORKeyStream(decrypted, s)
|
||||
s = append([]byte{}, decrypted...)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("invalid R")
|
||||
}
|
||||
|
||||
ekey, err := sh.alg6(d, decrypted)
|
||||
if err != nil {
|
||||
// TODO(dennwc): this doesn't look right, but it was in the old code
|
||||
return nil, nil
|
||||
}
|
||||
return ekey, nil
|
||||
}
|
||||
|
||||
func (sh stdHandlerR4) GenerateParams(d *stdEncryptDict, opass, upass []byte) ([]byte, error) {
|
||||
// Make the O and U objects.
|
||||
O, err := sh.alg3(d.R, upass, opass)
|
||||
if err != nil {
|
||||
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
|
||||
return nil, err
|
||||
}
|
||||
d.O = O
|
||||
common.Log.Trace("gen O: % x", O)
|
||||
// requires O
|
||||
ekey := sh.alg2(d, upass)
|
||||
|
||||
U, err := sh.alg5(ekey, upass)
|
||||
if err != nil {
|
||||
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
|
||||
return nil, err
|
||||
}
|
||||
d.U = U
|
||||
common.Log.Trace("gen U: % x", U)
|
||||
return ekey, nil
|
||||
}
|
||||
|
||||
func (sh stdHandlerR4) Authenticate(d *stdEncryptDict, pass []byte) ([]byte, AccessPermissions, error) {
|
||||
// Try owner password.
|
||||
// May not be necessary if only want to get all contents.
|
||||
// (user pass needs to be known or empty).
|
||||
common.Log.Trace("Debugging authentication - owner pass")
|
||||
ekey, err := sh.alg7(d, pass)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if ekey != nil {
|
||||
common.Log.Trace("this.authenticated = True")
|
||||
return ekey, PermOwner, nil
|
||||
}
|
||||
|
||||
// Try user password.
|
||||
common.Log.Trace("Debugging authentication - user pass")
|
||||
ekey, err = sh.alg6(d, pass)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if ekey != nil {
|
||||
common.Log.Trace("this.authenticated = True")
|
||||
return ekey, d.P, nil
|
||||
}
|
||||
// Cannot even view the file.
|
||||
return nil, 0, nil
|
||||
}
|
139
pdf/core/crypt_handlers_r4_test.go
Normal file
139
pdf/core/crypt_handlers_r4_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/unidoc/unidoc/common"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
common.SetLogger(common.ConsoleLogger{})
|
||||
}
|
||||
|
||||
func TestR4Padding(t *testing.T) {
|
||||
sh := stdHandlerR4{}
|
||||
|
||||
// Case 1 empty pass, should match padded string.
|
||||
key := sh.paddedPass([]byte(""))
|
||||
if len(key) != 32 {
|
||||
t.Errorf("Fail, expected padded pass length = 32 (%d)", len(key))
|
||||
}
|
||||
if key[0] != 0x28 {
|
||||
t.Errorf("key[0] != 0x28 (%q in %q)", key[0], key)
|
||||
}
|
||||
if key[31] != 0x7A {
|
||||
t.Errorf("key[31] != 0x7A (%q in %q)", key[31], key)
|
||||
}
|
||||
|
||||
// Case 2, non empty pass.
|
||||
key = sh.paddedPass([]byte("bla"))
|
||||
if len(key) != 32 {
|
||||
t.Errorf("Fail, expected padded pass length = 32 (%d)", len(key))
|
||||
}
|
||||
if string(key[0:3]) != "bla" {
|
||||
t.Errorf("Expecting start with bla (%s)", key)
|
||||
}
|
||||
if key[3] != 0x28 {
|
||||
t.Errorf("key[3] != 0x28 (%q in %q)", key[3], key)
|
||||
}
|
||||
if key[31] != 0x64 {
|
||||
t.Errorf("key[31] != 0x64 (%q in %q)", key[31], key)
|
||||
}
|
||||
}
|
||||
|
||||
// Test algorithm 2.
|
||||
func TestAlg2(t *testing.T) {
|
||||
sh := stdHandlerR4{
|
||||
// V: 2,
|
||||
ID0: string([]byte{0x4e, 0x00, 0x99, 0xe5, 0x36, 0x78, 0x93, 0x24,
|
||||
0xff, 0xd5, 0x82, 0xe4, 0xec, 0x0e, 0xa3, 0xb4}),
|
||||
Length: 128,
|
||||
}
|
||||
d := &stdEncryptDict{
|
||||
R: 3,
|
||||
P: 0xfffff0c0,
|
||||
EncryptMetadata: true,
|
||||
O: []byte{0xE6, 0x00, 0xEC, 0xC2, 0x02, 0x88, 0xAD, 0x8B,
|
||||
0x5C, 0x72, 0x64, 0xA9, 0x5C, 0x29, 0xC6, 0xA8, 0x3E, 0xE2, 0x51,
|
||||
0x76, 0x79, 0xAA, 0x02, 0x18, 0xBE, 0xCE, 0xEA, 0x8B, 0x79, 0x86,
|
||||
0x72, 0x6A, 0x8C, 0xDB},
|
||||
}
|
||||
|
||||
key := sh.alg2(d, []byte(""))
|
||||
|
||||
keyExp := []byte{0xf8, 0x94, 0x9c, 0x5a, 0xf5, 0xa0, 0xc0, 0xca,
|
||||
0x30, 0xb8, 0x91, 0xc1, 0xbb, 0x2c, 0x4f, 0xf5}
|
||||
|
||||
if string(key) != string(keyExp) {
|
||||
common.Log.Debug(" Key (%d): % x", len(key), key)
|
||||
common.Log.Debug("KeyExp (%d): % x", len(keyExp), keyExp)
|
||||
t.Errorf("alg2 -> key != expected\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Test algorithm 3.
|
||||
func TestAlg3(t *testing.T) {
|
||||
sh := stdHandlerR4{
|
||||
// V: 2,
|
||||
ID0: string([]byte{0x4e, 0x00, 0x99, 0xe5, 0x36, 0x78, 0x93, 0x24,
|
||||
0xff, 0xd5, 0x82, 0xe4, 0xec, 0x0e, 0xa3, 0xb4}),
|
||||
Length: 128,
|
||||
}
|
||||
|
||||
Oexp := []byte{0xE6, 0x00, 0xEC, 0xC2, 0x02, 0x88, 0xAD, 0x8B,
|
||||
0x0d, 0x64, 0xA9, 0x29, 0xC6, 0xA8, 0x3E, 0xE2, 0x51,
|
||||
0x76, 0x79, 0xAA, 0x02, 0x18, 0xBE, 0xCE, 0xEA, 0x8B, 0x79, 0x86,
|
||||
0x72, 0x6A, 0x8C, 0xDB}
|
||||
O, err := sh.alg3(3, []byte(""), []byte("test"))
|
||||
if err != nil {
|
||||
t.Errorf("crypt alg3 error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if string(O) != string(Oexp) {
|
||||
common.Log.Debug(" O (%d): % x", len(O), O)
|
||||
common.Log.Debug("Oexp (%d): % x", len(Oexp), Oexp)
|
||||
t.Errorf("alg3 -> key != expected")
|
||||
}
|
||||
}
|
||||
|
||||
// Test algorithm 5 for computing dictionary's U (user password) value
|
||||
// valid for R >= 3.
|
||||
func TestAlg5(t *testing.T) {
|
||||
sh := stdHandlerR4{
|
||||
// V: 2,
|
||||
ID0: string([]byte{0x4e, 0x00, 0x99, 0xe5, 0x36, 0x78, 0x93, 0x24,
|
||||
0xff, 0xd5, 0x82, 0xe4, 0xec, 0x0e, 0xa3, 0xb4}),
|
||||
Length: 128,
|
||||
}
|
||||
d := &stdEncryptDict{
|
||||
R: 3,
|
||||
P: 0xfffff0c0,
|
||||
EncryptMetadata: true,
|
||||
O: []byte{0xE6, 0x00, 0xEC, 0xC2, 0x02, 0x88, 0xAD, 0x8B,
|
||||
0x5C, 0x72, 0x64, 0xA9, 0x5C, 0x29, 0xC6, 0xA8, 0x3E, 0xE2, 0x51,
|
||||
0x76, 0x79, 0xAA, 0x02, 0x18, 0xBE, 0xCE, 0xEA, 0x8B, 0x79, 0x86,
|
||||
0x72, 0x6A, 0x8C, 0xDB},
|
||||
}
|
||||
|
||||
ekey := sh.alg2(d, []byte(""))
|
||||
U, err := sh.alg5(ekey, []byte(""))
|
||||
if err != nil {
|
||||
t.Errorf("Error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
Uexp := []byte{0x59, 0x66, 0x38, 0x6c, 0x76, 0xfe, 0x95, 0x7d, 0x3d,
|
||||
0x0d, 0x14, 0x3d, 0x36, 0xfd, 0x01, 0x3d, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
if string(U[0:16]) != string(Uexp[0:16]) {
|
||||
common.Log.Info(" U (%d): % x", len(U), U)
|
||||
common.Log.Info("Uexp (%d): % x", len(Uexp), Uexp)
|
||||
t.Errorf("U != expected\n")
|
||||
}
|
||||
}
|
460
pdf/core/crypt_handlers_r6.go
Normal file
460
pdf/core/crypt_handlers_r6.go
Normal file
@ -0,0 +1,460 @@
|
||||
/*
|
||||
* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.md', which is part of this source code package.
|
||||
*/
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
var _ stdSecurityHandler = stdHandlerR6{}
|
||||
|
||||
type stdHandlerR6 struct{}
|
||||
|
||||
// alg2a retrieves the encryption key from an encrypted document (R >= 5).
|
||||
// 7.6.4.3.2 Algorithm 2.A (page 83)
|
||||
func (sh stdHandlerR6) alg2a(d *stdEncryptDict, pass []byte) ([]byte, AccessPermissions, error) {
|
||||
// O & U: 32 byte hash + 8 byte Validation Salt + 8 byte Key Salt
|
||||
|
||||
// step a: Unicode normalization
|
||||
// TODO(dennwc): make sure that UTF-8 strings are normalized
|
||||
|
||||
// step b: truncate to 127 bytes
|
||||
if len(pass) > 127 {
|
||||
pass = pass[:127]
|
||||
}
|
||||
|
||||
// step c: test pass against the owner key
|
||||
h, err := sh.alg12(d, pass)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var (
|
||||
data []byte // data to hash
|
||||
ekey []byte // encrypted file key
|
||||
ukey []byte // user key; set only when using owner's password
|
||||
)
|
||||
var perm AccessPermissions
|
||||
if len(h) != 0 {
|
||||
// owner password valid
|
||||
perm = PermOwner
|
||||
|
||||
// step d: compute an intermediate owner key
|
||||
str := make([]byte, len(pass)+8+48)
|
||||
i := copy(str, pass)
|
||||
i += copy(str[i:], d.O[40:48]) // owner Key Salt
|
||||
i += copy(str[i:], d.U[0:48])
|
||||
|
||||
data = str
|
||||
ekey = d.OE
|
||||
ukey = d.U[0:48]
|
||||
} else {
|
||||
// check user password
|
||||
h, err = sh.alg11(d, pass)
|
||||
if err == nil && len(h) == 0 {
|
||||
// try default password
|
||||
h, err = sh.alg11(d, []byte(""))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
} else if len(h) == 0 {
|
||||
// wrong password
|
||||
return nil, 0, nil
|
||||
}
|
||||
perm = d.P
|
||||
// step e: compute an intermediate user key
|
||||
str := make([]byte, len(pass)+8)
|
||||
i := copy(str, pass)
|
||||
i += copy(str[i:], d.U[40:48]) // user Key Salt
|
||||
|
||||
data = str
|
||||
ekey = d.UE
|
||||
ukey = nil
|
||||
}
|
||||
ekey = ekey[:32]
|
||||
|
||||
// intermediate key
|
||||
ikey := sh.alg2b(d.R, data, pass, ukey)
|
||||
|
||||
ac, err := aes.NewCipher(ikey[:32])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := cipher.NewCBCDecrypter(ac, iv)
|
||||
fkey := make([]byte, 32)
|
||||
cbc.CryptBlocks(fkey, ekey)
|
||||
|
||||
if d.R == 5 {
|
||||
return fkey, perm, nil
|
||||
}
|
||||
// validate permissions
|
||||
err = sh.alg13(d, fkey)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return fkey, perm, nil
|
||||
}
|
||||
|
||||
// alg2b_R5 computes a hash for R=5, used in a deprecated extension.
|
||||
// It's used the same way as a hash described in Algorithm 2.B, but it doesn't use the original password
|
||||
// and the user key to calculate the hash.
|
||||
func alg2b_R5(data []byte) []byte {
|
||||
h := sha256.New()
|
||||
h.Write(data)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// repeat repeats first n bytes of buf until the end of the buffer.
|
||||
// It assumes that the length of buf is a multiple of n.
|
||||
func repeat(buf []byte, n int) {
|
||||
bp := n
|
||||
for bp < len(buf) {
|
||||
copy(buf[bp:], buf[:bp])
|
||||
bp *= 2
|
||||
}
|
||||
}
|
||||
|
||||
// alg2b computes a hash for R=6.
|
||||
// 7.6.4.3.3 Algorithm 2.B (page 83)
|
||||
func alg2b(data, pwd, userKey []byte) []byte {
|
||||
var (
|
||||
s256, s384, s512 hash.Hash
|
||||
)
|
||||
s256 = sha256.New()
|
||||
hbuf := make([]byte, 64)
|
||||
|
||||
h := s256
|
||||
h.Write(data)
|
||||
K := h.Sum(hbuf[:0])
|
||||
|
||||
buf := make([]byte, 64*(127+64+48))
|
||||
|
||||
round := func(rnd int) (E []byte) {
|
||||
// step a: repeat pass+K 64 times
|
||||
n := len(pwd) + len(K) + len(userKey)
|
||||
part := buf[:n]
|
||||
i := copy(part, pwd)
|
||||
i += copy(part[i:], K[:])
|
||||
i += copy(part[i:], userKey)
|
||||
if i != n {
|
||||
panic("wrong size")
|
||||
}
|
||||
K1 := buf[:n*64]
|
||||
repeat(K1, n)
|
||||
|
||||
// step b: encrypt K1 with AES-128 CBC
|
||||
ac, err := aes.NewCipher(K[0:16])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cbc := cipher.NewCBCEncrypter(ac, K[16:32])
|
||||
cbc.CryptBlocks(K1, K1)
|
||||
E = K1
|
||||
|
||||
// step c: use 16 bytes of E as big-endian int, select the next hash
|
||||
b := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
b += int(E[i] % 3)
|
||||
}
|
||||
var h hash.Hash
|
||||
switch b % 3 {
|
||||
case 0:
|
||||
h = s256
|
||||
case 1:
|
||||
if s384 == nil {
|
||||
s384 = sha512.New384()
|
||||
}
|
||||
h = s384
|
||||
case 2:
|
||||
if s512 == nil {
|
||||
s512 = sha512.New()
|
||||
}
|
||||
h = s512
|
||||
}
|
||||
|
||||
// step d: take the hash of E, use as a new K
|
||||
h.Reset()
|
||||
h.Write(E)
|
||||
K = h.Sum(hbuf[:0])
|
||||
|
||||
return E
|
||||
}
|
||||
|
||||
for i := 0; ; {
|
||||
E := round(i)
|
||||
b := uint8(E[len(E)-1])
|
||||
// from the spec, it appears that i should be incremented after
|
||||
// the test, but that doesn't match what Adobe does
|
||||
i++
|
||||
if i >= 64 && b <= uint8(i-32) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return K[:32]
|
||||
}
|
||||
|
||||
// alg2b computes a hash for R=5 and R=6.
|
||||
func (sh stdHandlerR6) alg2b(R int, data, pwd, userKey []byte) []byte {
|
||||
if R == 5 {
|
||||
return alg2b_R5(data)
|
||||
}
|
||||
return alg2b(data, pwd, userKey)
|
||||
}
|
||||
|
||||
// alg8 computes the encryption dictionary's U (user password) and UE (user encryption) values (R>=5).
|
||||
// 7.6.4.4.6 Algorithm 8 (page 86)
|
||||
func (sh stdHandlerR6) alg8(d *stdEncryptDict, ekey []byte, upass []byte) error {
|
||||
// step a: compute U (user password)
|
||||
var rbuf [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, rbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
valSalt := rbuf[0:8]
|
||||
keySalt := rbuf[8:16]
|
||||
|
||||
str := make([]byte, len(upass)+len(valSalt))
|
||||
i := copy(str, upass)
|
||||
i += copy(str[i:], valSalt)
|
||||
|
||||
h := sh.alg2b(d.R, str, upass, nil)
|
||||
|
||||
U := make([]byte, len(h)+len(valSalt)+len(keySalt))
|
||||
i = copy(U, h[:32])
|
||||
i += copy(U[i:], valSalt)
|
||||
i += copy(U[i:], keySalt)
|
||||
|
||||
d.U = U
|
||||
|
||||
// step b: compute UE (user encryption)
|
||||
|
||||
// str still contains a password, reuse it
|
||||
i = len(upass)
|
||||
i += copy(str[i:], keySalt)
|
||||
|
||||
h = sh.alg2b(d.R, str, upass, nil)
|
||||
|
||||
ac, err := aes.NewCipher(h[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := cipher.NewCBCEncrypter(ac, iv)
|
||||
UE := make([]byte, 32)
|
||||
cbc.CryptBlocks(UE, ekey[:32])
|
||||
d.UE = UE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// alg9 computes the encryption dictionary's O (owner password) and OE (owner encryption) values (R>=5).
|
||||
// 7.6.4.4.7 Algorithm 9 (page 86)
|
||||
func (sh stdHandlerR6) alg9(d *stdEncryptDict, ekey []byte, opass []byte) error {
|
||||
// step a: compute O (owner password)
|
||||
var rbuf [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, rbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
valSalt := rbuf[0:8]
|
||||
keySalt := rbuf[8:16]
|
||||
userKey := d.U[:48]
|
||||
|
||||
str := make([]byte, len(opass)+len(valSalt)+len(userKey))
|
||||
i := copy(str, opass)
|
||||
i += copy(str[i:], valSalt)
|
||||
i += copy(str[i:], userKey)
|
||||
|
||||
h := sh.alg2b(d.R, str, opass, userKey)
|
||||
|
||||
O := make([]byte, len(h)+len(valSalt)+len(keySalt))
|
||||
i = copy(O, h[:32])
|
||||
i += copy(O[i:], valSalt)
|
||||
i += copy(O[i:], keySalt)
|
||||
|
||||
d.O = O
|
||||
|
||||
// step b: compute OE (owner encryption)
|
||||
|
||||
// str still contains a password and a user key - reuse both, but overwrite the salt
|
||||
i = len(opass)
|
||||
i += copy(str[i:], keySalt)
|
||||
// i += len(userKey)
|
||||
|
||||
h = sh.alg2b(d.R, str, opass, userKey)
|
||||
|
||||
ac, err := aes.NewCipher(h[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := make([]byte, aes.BlockSize)
|
||||
cbc := cipher.NewCBCEncrypter(ac, iv)
|
||||
OE := make([]byte, 32)
|
||||
cbc.CryptBlocks(OE, ekey[:32])
|
||||
d.OE = OE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// alg10 computes the encryption dictionary's Perms (permissions) value (R=6).
|
||||
// 7.6.4.4.8 Algorithm 10 (page 87)
|
||||
func (sh stdHandlerR6) alg10(d *stdEncryptDict, ekey []byte) error {
|
||||
// step a: extend permissions to 64 bits
|
||||
perms := uint64(uint32(d.P)) | (math.MaxUint32 << 32)
|
||||
|
||||
// step b: record permissions
|
||||
Perms := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(Perms[:8], perms)
|
||||
|
||||
// step c: record EncryptMetadata
|
||||
if d.EncryptMetadata {
|
||||
Perms[8] = 'T'
|
||||
} else {
|
||||
Perms[8] = 'F'
|
||||
}
|
||||
|
||||
// step d: write "adb" magic
|
||||
copy(Perms[9:12], "adb")
|
||||
|
||||
// step e: write 4 bytes of random data
|
||||
|
||||
// spec doesn't specify them as generated "from a strong random source",
|
||||
// but we will use the cryptographic random generator anyway
|
||||
if _, err := io.ReadFull(rand.Reader, Perms[12:16]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// step f: encrypt permissions
|
||||
ac, err := aes.NewCipher(ekey[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ecb := newECBEncrypter(ac)
|
||||
ecb.CryptBlocks(Perms, Perms)
|
||||
|
||||
d.Perms = Perms[:16]
|
||||
return nil
|
||||
}
|
||||
|
||||
// alg11 authenticates the user password (R >= 5) and returns the hash.
|
||||
func (sh stdHandlerR6) alg11(d *stdEncryptDict, upass []byte) ([]byte, error) {
|
||||
str := make([]byte, len(upass)+8)
|
||||
i := copy(str, upass)
|
||||
i += copy(str[i:], d.U[32:40]) // user Validation Salt
|
||||
|
||||
h := sh.alg2b(d.R, str, upass, nil)
|
||||
h = h[:32]
|
||||
if !bytes.Equal(h, d.U[:32]) {
|
||||
return nil, nil
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// alg12 authenticates the owner password (R >= 5) and returns the hash.
|
||||
// 7.6.4.4.10 Algorithm 12 (page 87)
|
||||
func (sh stdHandlerR6) alg12(d *stdEncryptDict, opass []byte) ([]byte, error) {
|
||||
str := make([]byte, len(opass)+8+48)
|
||||
i := copy(str, opass)
|
||||
i += copy(str[i:], d.O[32:40]) // owner Validation Salt
|
||||
i += copy(str[i:], d.U[0:48])
|
||||
|
||||
h := sh.alg2b(d.R, str, opass, d.U[0:48])
|
||||
h = h[:32]
|
||||
if !bytes.Equal(h, d.O[:32]) {
|
||||
return nil, nil
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// alg13 validates user permissions (P+EncryptMetadata vs Perms) for R=6.
|
||||
// 7.6.4.4.11 Algorithm 13 (page 87)
|
||||
func (sh stdHandlerR6) alg13(d *stdEncryptDict, fkey []byte) error {
|
||||
perms := make([]byte, 16)
|
||||
copy(perms, d.Perms[:16])
|
||||
|
||||
ac, err := aes.NewCipher(fkey[:32])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ecb := newECBDecrypter(ac)
|
||||
ecb.CryptBlocks(perms, perms)
|
||||
|
||||
if !bytes.Equal(perms[9:12], []byte("adb")) {
|
||||
return errors.New("decoded permissions are invalid")
|
||||
}
|
||||
p := AccessPermissions(binary.LittleEndian.Uint32(perms[0:4]))
|
||||
if p != d.P {
|
||||
return errors.New("permissions validation failed")
|
||||
}
|
||||
encMeta := true
|
||||
if perms[8] == 'T' {
|
||||
encMeta = true
|
||||
} else if perms[8] == 'F' {
|
||||
encMeta = false
|
||||
} else {
|
||||
return errors.New("decoded metadata encryption flag is invalid")
|
||||
}
|
||||
if encMeta != d.EncryptMetadata {
|
||||
return errors.New("metadata encryption validation failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateR6 is the algorithm opposite to alg2a (R>=5).
|
||||
// It generates U,O,UE,OE,Perms fields using AESv3 encryption.
|
||||
// There is no algorithm number assigned to this function in the spec.
|
||||
func (sh stdHandlerR6) GenerateParams(d *stdEncryptDict, opass, upass []byte) ([]byte, error) {
|
||||
ekey := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, ekey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// all these field will be populated by functions below
|
||||
d.U = nil
|
||||
d.O = nil
|
||||
d.UE = nil
|
||||
d.OE = nil
|
||||
d.Perms = nil // populated only for R=6
|
||||
|
||||
if len(upass) > 127 {
|
||||
upass = upass[:127]
|
||||
}
|
||||
if len(opass) > 127 {
|
||||
opass = opass[:127]
|
||||
}
|
||||
// generate U and UE
|
||||
if err := sh.alg8(d, ekey, upass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// generate O and OE
|
||||
if err := sh.alg9(d, ekey, opass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.R == 5 {
|
||||
return ekey, nil
|
||||
}
|
||||
// generate Perms
|
||||
if err := sh.alg10(d, ekey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ekey, nil
|
||||
}
|
||||
|
||||
func (sh stdHandlerR6) Authenticate(d *stdEncryptDict, pass []byte) ([]byte, AccessPermissions, error) {
|
||||
return sh.alg2a(d, pass)
|
||||
}
|
111
pdf/core/crypt_handlers_r6_test.go
Normal file
111
pdf/core/crypt_handlers_r6_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkAlg2b(b *testing.B) {
|
||||
// hash runs a variable number of rounds, so we need to have a
|
||||
// deterministic random source to make benchmark results comparable
|
||||
r := rand.New(rand.NewSource(1234567))
|
||||
const n = 20
|
||||
pass := make([]byte, n)
|
||||
r.Read(pass)
|
||||
data := make([]byte, n+8+48)
|
||||
r.Read(data)
|
||||
user := make([]byte, 48)
|
||||
r.Read(user)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = alg2b(data, pass, user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdHandlerR6(t *testing.T) {
|
||||
var cases = []struct {
|
||||
Name string
|
||||
EncMeta bool
|
||||
UserPass string
|
||||
OwnerPass string
|
||||
}{
|
||||
{
|
||||
Name: "simple", EncMeta: true,
|
||||
UserPass: "user", OwnerPass: "owner",
|
||||
},
|
||||
{
|
||||
Name: "utf8", EncMeta: false,
|
||||
UserPass: "æøå-u", OwnerPass: "æøå-o",
|
||||
},
|
||||
{
|
||||
Name: "long", EncMeta: true,
|
||||
UserPass: strings.Repeat("user", 80),
|
||||
OwnerPass: strings.Repeat("owner", 80),
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
perms = 0x12345678
|
||||
)
|
||||
|
||||
for _, R := range []int{5, 6} {
|
||||
R := R
|
||||
t.Run(fmt.Sprintf("R=%d", R), func(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
sh := stdHandlerR6{} // V=5
|
||||
d := &stdEncryptDict{
|
||||
R: R, P: perms,
|
||||
EncryptMetadata: c.EncMeta,
|
||||
}
|
||||
|
||||
// generate encryption parameters
|
||||
ekey, err := sh.GenerateParams(d, []byte(c.OwnerPass), []byte(c.UserPass))
|
||||
if err != nil {
|
||||
t.Fatal("Failed to encrypt:", err)
|
||||
}
|
||||
|
||||
// Perms and EncryptMetadata are checked as a part of alg2a
|
||||
|
||||
// decrypt using user password
|
||||
key, uperm, err := sh.alg2a(d, []byte(c.UserPass))
|
||||
if err != nil || uperm != perms {
|
||||
t.Error("Failed to authenticate user pass:", err)
|
||||
} else if !bytes.Equal(ekey, key) {
|
||||
t.Error("wrong encryption key")
|
||||
}
|
||||
|
||||
// decrypt using owner password
|
||||
key, uperm, err = sh.alg2a(d, []byte(c.OwnerPass))
|
||||
if err != nil || uperm != PermOwner {
|
||||
t.Error("Failed to authenticate owner pass:", err, uperm)
|
||||
} else if !bytes.Equal(ekey, key) {
|
||||
t.Error("wrong encryption key")
|
||||
}
|
||||
|
||||
// try to elevate user permissions
|
||||
d.P = PermOwner
|
||||
|
||||
key, uperm, err = sh.alg2a(d, []byte(c.UserPass))
|
||||
if R == 5 {
|
||||
// it's actually possible with R=5, since Perms is not generated
|
||||
if err != nil || uperm != PermOwner {
|
||||
t.Error("Failed to authenticate user pass:", err)
|
||||
}
|
||||
} else {
|
||||
// not possible in R=6, should return an error
|
||||
if err == nil || uperm == PermOwner {
|
||||
t.Error("was able to elevate permissions with R=6")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -8,13 +8,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
)
|
||||
@ -23,141 +17,6 @@ func init() {
|
||||
common.SetLogger(common.ConsoleLogger{})
|
||||
}
|
||||
|
||||
func TestPadding(t *testing.T) {
|
||||
crypter := PdfCrypt{}
|
||||
|
||||
// Case 1 empty pass, should match padded string.
|
||||
key := crypter.paddedPass([]byte(""))
|
||||
if len(key) != 32 {
|
||||
t.Errorf("Fail, expected padded pass length = 32 (%d)", len(key))
|
||||
}
|
||||
if key[0] != 0x28 {
|
||||
t.Errorf("key[0] != 0x28 (%q in %q)", key[0], key)
|
||||
}
|
||||
if key[31] != 0x7A {
|
||||
t.Errorf("key[31] != 0x7A (%q in %q)", key[31], key)
|
||||
}
|
||||
|
||||
// Case 2, non empty pass.
|
||||
key = crypter.paddedPass([]byte("bla"))
|
||||
if len(key) != 32 {
|
||||
t.Errorf("Fail, expected padded pass length = 32 (%d)", len(key))
|
||||
}
|
||||
if string(key[0:3]) != "bla" {
|
||||
t.Errorf("Expecting start with bla (%s)", key)
|
||||
}
|
||||
if key[3] != 0x28 {
|
||||
t.Errorf("key[3] != 0x28 (%q in %q)", key[3], key)
|
||||
}
|
||||
if key[31] != 0x64 {
|
||||
t.Errorf("key[31] != 0x64 (%q in %q)", key[31], key)
|
||||
}
|
||||
}
|
||||
|
||||
// Test algorithm 2.
|
||||
func TestAlg2(t *testing.T) {
|
||||
crypter := PdfCrypt{
|
||||
encrypt: encryptDict{
|
||||
V: 2,
|
||||
Length: 128,
|
||||
},
|
||||
encryptStd: stdEncryptDict{
|
||||
R: 3,
|
||||
P: 0xfffff0c0,
|
||||
EncryptMetadata: true,
|
||||
O: []byte{0xE6, 0x00, 0xEC, 0xC2, 0x02, 0x88, 0xAD, 0x8B,
|
||||
0x5C, 0x72, 0x64, 0xA9, 0x5C, 0x29, 0xC6, 0xA8, 0x3E, 0xE2, 0x51,
|
||||
0x76, 0x79, 0xAA, 0x02, 0x18, 0xBE, 0xCE, 0xEA, 0x8B, 0x79, 0x86,
|
||||
0x72, 0x6A, 0x8C, 0xDB},
|
||||
},
|
||||
id0: string([]byte{0x4e, 0x00, 0x99, 0xe5, 0x36, 0x78, 0x93, 0x24,
|
||||
0xff, 0xd5, 0x82, 0xe4, 0xec, 0x0e, 0xa3, 0xb4}),
|
||||
}
|
||||
|
||||
key := crypter.alg2([]byte(""))
|
||||
|
||||
keyExp := []byte{0xf8, 0x94, 0x9c, 0x5a, 0xf5, 0xa0, 0xc0, 0xca,
|
||||
0x30, 0xb8, 0x91, 0xc1, 0xbb, 0x2c, 0x4f, 0xf5}
|
||||
|
||||
if string(key) != string(keyExp) {
|
||||
common.Log.Debug(" Key (%d): % x", len(key), key)
|
||||
common.Log.Debug("KeyExp (%d): % x", len(keyExp), keyExp)
|
||||
t.Errorf("alg2 -> key != expected\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Test algorithm 3.
|
||||
func TestAlg3(t *testing.T) {
|
||||
crypter := PdfCrypt{
|
||||
encrypt: encryptDict{
|
||||
V: 2,
|
||||
Length: 128,
|
||||
},
|
||||
encryptStd: stdEncryptDict{
|
||||
R: 3,
|
||||
P: 0xfffff0c0,
|
||||
EncryptMetadata: true,
|
||||
},
|
||||
id0: string([]byte{0x4e, 0x00, 0x99, 0xe5, 0x36, 0x78, 0x93, 0x24,
|
||||
0xff, 0xd5, 0x82, 0xe4, 0xec, 0x0e, 0xa3, 0xb4}),
|
||||
}
|
||||
|
||||
Oexp := []byte{0xE6, 0x00, 0xEC, 0xC2, 0x02, 0x88, 0xAD, 0x8B,
|
||||
0x0d, 0x64, 0xA9, 0x29, 0xC6, 0xA8, 0x3E, 0xE2, 0x51,
|
||||
0x76, 0x79, 0xAA, 0x02, 0x18, 0xBE, 0xCE, 0xEA, 0x8B, 0x79, 0x86,
|
||||
0x72, 0x6A, 0x8C, 0xDB}
|
||||
O, err := crypter.alg3([]byte(""), []byte("test"))
|
||||
if err != nil {
|
||||
t.Errorf("crypt alg3 error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if string(O) != string(Oexp) {
|
||||
common.Log.Debug(" O (%d): % x", len(O), O)
|
||||
common.Log.Debug("Oexp (%d): % x", len(Oexp), Oexp)
|
||||
t.Errorf("alg3 -> key != expected")
|
||||
}
|
||||
}
|
||||
|
||||
// Test algorithm 5 for computing dictionary's U (user password) value
|
||||
// valid for R >= 3.
|
||||
func TestAlg5(t *testing.T) {
|
||||
crypter := PdfCrypt{
|
||||
encrypt: encryptDict{
|
||||
V: 2,
|
||||
Length: 128,
|
||||
},
|
||||
encryptStd: stdEncryptDict{
|
||||
R: 3,
|
||||
P: 0xfffff0c0,
|
||||
EncryptMetadata: true,
|
||||
O: []byte{0xE6, 0x00, 0xEC, 0xC2, 0x02, 0x88, 0xAD, 0x8B,
|
||||
0x5C, 0x72, 0x64, 0xA9, 0x5C, 0x29, 0xC6, 0xA8, 0x3E, 0xE2, 0x51,
|
||||
0x76, 0x79, 0xAA, 0x02, 0x18, 0xBE, 0xCE, 0xEA, 0x8B, 0x79, 0x86,
|
||||
0x72, 0x6A, 0x8C, 0xDB},
|
||||
},
|
||||
id0: string([]byte{0x4e, 0x00, 0x99, 0xe5, 0x36, 0x78, 0x93, 0x24,
|
||||
0xff, 0xd5, 0x82, 0xe4, 0xec, 0x0e, 0xa3, 0xb4}),
|
||||
}
|
||||
|
||||
U, _, err := crypter.alg5([]byte(""))
|
||||
if err != nil {
|
||||
t.Errorf("Error %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
Uexp := []byte{0x59, 0x66, 0x38, 0x6c, 0x76, 0xfe, 0x95, 0x7d, 0x3d,
|
||||
0x0d, 0x14, 0x3d, 0x36, 0xfd, 0x01, 0x3d, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
if string(U[0:16]) != string(Uexp[0:16]) {
|
||||
common.Log.Info(" U (%d): % x", len(U), U)
|
||||
common.Log.Info("Uexp (%d): % x", len(Uexp), Uexp)
|
||||
t.Errorf("U != expected\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Test decrypting. Example with V=2, R=3, using standard algorithm.
|
||||
func TestDecryption1(t *testing.T) {
|
||||
crypter := PdfCrypt{
|
||||
@ -235,121 +94,3 @@ func TestDecryption1(t *testing.T) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAlg2b(b *testing.B) {
|
||||
// hash runs a variable number of rounds, so we need to have a
|
||||
// deterministic random source to make benchmark results comparable
|
||||
r := rand.New(rand.NewSource(1234567))
|
||||
const n = 20
|
||||
pass := make([]byte, n)
|
||||
r.Read(pass)
|
||||
data := make([]byte, n+8+48)
|
||||
r.Read(data)
|
||||
user := make([]byte, 48)
|
||||
r.Read(user)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = alg2b(data, pass, user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESv3(t *testing.T) {
|
||||
const keySize = 32
|
||||
|
||||
seed := time.Now().UnixNano()
|
||||
rand := rand.New(rand.NewSource(seed))
|
||||
|
||||
var cases = []struct {
|
||||
Name string
|
||||
EncMeta bool
|
||||
UserPass string
|
||||
OwnerPass string
|
||||
}{
|
||||
{
|
||||
Name: "simple", EncMeta: true,
|
||||
UserPass: "user", OwnerPass: "owner",
|
||||
},
|
||||
{
|
||||
Name: "utf8", EncMeta: false,
|
||||
UserPass: "æøå-u", OwnerPass: "æøå-o",
|
||||
},
|
||||
{
|
||||
Name: "long", EncMeta: true,
|
||||
UserPass: strings.Repeat("user", 80),
|
||||
OwnerPass: strings.Repeat("owner", 80),
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
perms = 0x12345678
|
||||
)
|
||||
|
||||
for _, R := range []int{5, 6} {
|
||||
R := R
|
||||
t.Run(fmt.Sprintf("R=%d", R), func(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
fkey := make([]byte, keySize)
|
||||
rand.Read(fkey)
|
||||
|
||||
crypt := &PdfCrypt{
|
||||
encrypt: encryptDict{
|
||||
V: 5,
|
||||
},
|
||||
encryptStd: stdEncryptDict{
|
||||
R: R, P: perms,
|
||||
EncryptMetadata: c.EncMeta,
|
||||
},
|
||||
encryptionKey: append([]byte{}, fkey...),
|
||||
}
|
||||
|
||||
// generate encryption parameters
|
||||
err := crypt.generateR6([]byte(c.UserPass), []byte(c.OwnerPass))
|
||||
if err != nil {
|
||||
t.Fatal("Failed to encrypt:", err)
|
||||
}
|
||||
|
||||
// Perms and EncryptMetadata are checked as a part of alg2a
|
||||
|
||||
// decrypt using user password
|
||||
crypt.encryptionKey = nil
|
||||
ok, err := crypt.alg2a([]byte(c.UserPass))
|
||||
if err != nil || !ok {
|
||||
t.Error("Failed to authenticate user pass:", err)
|
||||
} else if !bytes.Equal(crypt.encryptionKey, fkey) {
|
||||
t.Error("wrong encryption key")
|
||||
}
|
||||
|
||||
// decrypt using owner password
|
||||
crypt.encryptionKey = nil
|
||||
ok, err = crypt.alg2a([]byte(c.OwnerPass))
|
||||
if err != nil || !ok {
|
||||
t.Error("Failed to authenticate owner pass:", err)
|
||||
} else if !bytes.Equal(crypt.encryptionKey, fkey) {
|
||||
t.Error("wrong encryption key")
|
||||
}
|
||||
|
||||
// try to elevate user permissions
|
||||
crypt.encryptStd.P = math.MaxUint32
|
||||
|
||||
crypt.encryptionKey = nil
|
||||
ok, err = crypt.alg2a([]byte(c.UserPass))
|
||||
if R == 5 {
|
||||
// it's actually possible with R=5, since Perms is not generated
|
||||
if err != nil || !ok {
|
||||
t.Error("Failed to authenticate user pass:", err)
|
||||
}
|
||||
} else {
|
||||
// not possible in R=6, should return an error
|
||||
if err == nil || ok {
|
||||
t.Error("was able to elevate permissions with R=6")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1628,6 +1628,7 @@ func (parser *PdfParser) Decrypt(password []byte) (bool, error) {
|
||||
}
|
||||
|
||||
if !authenticated {
|
||||
// TODO(dennwc): R6 handler will try it automatically, make R4 do the same
|
||||
authenticated, err = parser.crypter.authenticate([]byte(""))
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user