mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-02 22:17:06 +08:00
Merge pull request #204 from dennwc/aes3_encrypt
Implement AES encryption (128 and 256 bits)
This commit is contained in:
commit
6f85ca4fa8
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
)
|
||||
@ -36,7 +37,7 @@ type PdfCrypt struct {
|
||||
U []byte
|
||||
OE []byte // R=6
|
||||
UE []byte // R=6
|
||||
P int
|
||||
P int // TODO (v3): uint32
|
||||
Perms []byte // R=6
|
||||
EncryptMetadata bool
|
||||
Id0 string
|
||||
@ -76,6 +77,9 @@ const padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF" +
|
||||
"\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C" +
|
||||
"\xA9\xFE\x64\x53\x69\x7A"
|
||||
|
||||
// StandardCryptFilter is a default name for a standard crypt filter.
|
||||
const StandardCryptFilter = "StdCF"
|
||||
|
||||
// CryptFilter represents information from a CryptFilter dictionary.
|
||||
// TODO (v3): Unexport.
|
||||
type CryptFilter struct {
|
||||
@ -96,7 +100,7 @@ const (
|
||||
// TODO (v3): Unexport.
|
||||
type CryptFilters map[string]CryptFilter
|
||||
|
||||
// LoadCryptFilters loads crypt filter information from the encryption dictionary (V4 only).
|
||||
// LoadCryptFilters loads crypt filter information from the encryption dictionary (V>=4).
|
||||
// TODO (v3): Unexport.
|
||||
func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
|
||||
crypt.CryptFilters = CryptFilters{}
|
||||
@ -212,6 +216,32 @@ func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveCryptFilters saves crypt filter information to the encryption dictionary (V>=4).
|
||||
// TODO (v3): Unexport.
|
||||
func (crypt *PdfCrypt) SaveCryptFilters(ed *PdfObjectDictionary) error {
|
||||
if crypt.V < 4 {
|
||||
return errors.New("can only be used with V>=4")
|
||||
}
|
||||
cf := MakeDict()
|
||||
ed.Set("CF", cf)
|
||||
|
||||
for name, filter := range crypt.CryptFilters {
|
||||
if name == "Identity" {
|
||||
continue
|
||||
}
|
||||
v := MakeDict()
|
||||
cf.Set(PdfObjectName(name), v)
|
||||
|
||||
v.Set("Type", MakeName("CryptFilter"))
|
||||
v.Set("AuthEvent", MakeName("DocOpen"))
|
||||
v.Set("CFM", MakeName(string(filter.Cfm)))
|
||||
v.Set("Length", MakeInteger(int64(filter.Length)))
|
||||
}
|
||||
ed.Set("StrF", MakeName(crypt.StringFilter))
|
||||
ed.Set("StmF", MakeName(crypt.StreamFilter))
|
||||
return nil
|
||||
}
|
||||
|
||||
// PdfCryptMakeNew makes the document crypt handler based on the encryption dictionary
|
||||
// and trailer dictionary. Returns an error on failure to process.
|
||||
func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCrypt, error) {
|
||||
@ -254,7 +284,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
|
||||
crypter.V = int(*V)
|
||||
// Default algorithm is V2.
|
||||
crypter.CryptFilters = CryptFilters{}
|
||||
crypter.CryptFilters["Default"] = CryptFilter{Cfm: "V2", Length: crypter.Length}
|
||||
crypter.CryptFilters[StandardCryptFilter] = CryptFilter{Cfm: "V2", Length: crypter.Length}
|
||||
} else if *V >= 4 && *V <= 5 {
|
||||
crypter.V = int(*V)
|
||||
if err := crypter.LoadCryptFilters(ed); err != nil {
|
||||
@ -272,6 +302,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
|
||||
if !ok {
|
||||
return crypter, errors.New("Encrypt dictionary missing R")
|
||||
}
|
||||
// TODO(dennwc): according to spec, R should be validated according to V value
|
||||
if *R < 2 || *R > 6 {
|
||||
return crypter, fmt.Errorf("Invalid R (%d)", *R)
|
||||
}
|
||||
@ -687,14 +718,13 @@ func (crypt *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]b
|
||||
}
|
||||
|
||||
// The padded length is indicated by the last values. Remove those.
|
||||
if cfMethod == CryptFilterAESV2 {
|
||||
|
||||
padLen := int(buf[len(buf)-1])
|
||||
if padLen >= len(buf) {
|
||||
common.Log.Debug("Illegal pad length")
|
||||
return buf, fmt.Errorf("Invalid pad length for %s", cfMethod)
|
||||
}
|
||||
buf = buf[:len(buf)-padLen]
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
@ -740,7 +770,7 @@ func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64)
|
||||
|
||||
dict := so.PdfObjectDictionary
|
||||
|
||||
streamFilter := "Default" // Default RC4.
|
||||
streamFilter := StandardCryptFilter // Default RC4.
|
||||
if crypt.V >= 4 {
|
||||
streamFilter = crypt.StreamFilter
|
||||
common.Log.Trace("this.StreamFilter = %s", crypt.StreamFilter)
|
||||
@ -795,7 +825,7 @@ func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64)
|
||||
if s, isString := obj.(*PdfObjectString); isString {
|
||||
common.Log.Trace("Decrypting string!")
|
||||
|
||||
stringFilter := "Default"
|
||||
stringFilter := StandardCryptFilter
|
||||
if crypt.V >= 4 {
|
||||
// Currently only support Identity / RC4.
|
||||
common.Log.Trace("with %s filter", crypt.StringFilter)
|
||||
@ -889,7 +919,7 @@ func (crypt *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]b
|
||||
}
|
||||
|
||||
cfMethod := cf.Cfm
|
||||
if cfMethod == "V2" {
|
||||
if cfMethod == CryptFilterV2 {
|
||||
// Standard RC4 algorithm.
|
||||
ciph, err := rc4.NewCipher(okey)
|
||||
if err != nil {
|
||||
@ -926,23 +956,23 @@ func (crypt *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]b
|
||||
// vector is a 16-byte random number that is stored as the first
|
||||
// 16 bytes of the encrypted stream or string.
|
||||
|
||||
if cfMethod == CryptFilterAESV2 {
|
||||
pad := 16 - len(buf)%16
|
||||
const block = aes.BlockSize // 16
|
||||
|
||||
pad := block - len(buf)%block
|
||||
for i := 0; i < pad; i++ {
|
||||
buf = append(buf, byte(pad))
|
||||
}
|
||||
common.Log.Trace("Padded to %d bytes", len(buf))
|
||||
}
|
||||
|
||||
// Generate random 16 bytes, place in beginning of buffer.
|
||||
ciphertext := make([]byte, 16+len(buf))
|
||||
iv := ciphertext[:16]
|
||||
ciphertext := make([]byte, block+len(buf))
|
||||
iv := ciphertext[:block]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCEncrypter(ciph, iv)
|
||||
mode.CryptBlocks(ciphertext[aes.BlockSize:], buf)
|
||||
mode.CryptBlocks(ciphertext[block:], buf)
|
||||
|
||||
buf = ciphertext
|
||||
common.Log.Trace("to (%d): % x", len(buf), buf)
|
||||
@ -990,7 +1020,7 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64)
|
||||
|
||||
dict := so.PdfObjectDictionary
|
||||
|
||||
streamFilter := "Default" // Default RC4.
|
||||
streamFilter := StandardCryptFilter // Default RC4.
|
||||
if crypt.V >= 4 {
|
||||
// For now. Need to change when we add support for more than
|
||||
// Identity / RC4.
|
||||
@ -1047,7 +1077,7 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64)
|
||||
if s, isString := obj.(*PdfObjectString); isString {
|
||||
common.Log.Trace("Encrypting string!")
|
||||
|
||||
stringFilter := "Default"
|
||||
stringFilter := StandardCryptFilter
|
||||
if crypt.V >= 4 {
|
||||
common.Log.Trace("with %s filter", crypt.StringFilter)
|
||||
if crypt.StringFilter == "Identity" {
|
||||
@ -1117,6 +1147,14 @@ 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)
|
||||
@ -1184,10 +1222,8 @@ func (crypt *PdfCrypt) alg2a(pass []byte) (bool, error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if crypt.ivAESZero == nil {
|
||||
crypt.ivAESZero = make([]byte, aes.BlockSize)
|
||||
}
|
||||
iv := crypt.ivAESZero
|
||||
|
||||
iv := crypt.aesZeroIV()
|
||||
cbc := cipher.NewCBCDecrypter(ac, iv)
|
||||
fkey := make([]byte, 32)
|
||||
cbc.CryptBlocks(fkey, ekey)
|
||||
@ -1578,6 +1614,188 @@ func (crypt *PdfCrypt) Alg7(opass []byte) (bool, error) {
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// GenerateParams generates encryption parameters for specified passwords.
|
||||
// Can be called only for R>=5.
|
||||
func (crypt *PdfCrypt) GenerateParams(upass, opass []byte) error {
|
||||
if crypt.R < 5 {
|
||||
// TODO(dennwc): move code for R<5 from PdfWriter.Encrypt
|
||||
return errors.New("can be used only for R>=5")
|
||||
}
|
||||
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.U = nil
|
||||
crypt.O = nil
|
||||
crypt.UE = nil
|
||||
crypt.OE = nil
|
||||
crypt.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.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.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])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
iv := crypt.aesZeroIV()
|
||||
cbc := cipher.NewCBCEncrypter(ac, iv)
|
||||
UE := make([]byte, 32)
|
||||
cbc.CryptBlocks(UE, crypt.EncryptionKey[:32])
|
||||
crypt.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 (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.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.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.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.P)) | (math.MaxUint32 << 32)
|
||||
|
||||
// step b: record permissions
|
||||
Perms := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(Perms[:8], perms)
|
||||
|
||||
// step c: record EncryptMetadata
|
||||
if crypt.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.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)
|
||||
@ -1611,7 +1829,8 @@ func (crypt *PdfCrypt) alg12(opass []byte) ([]byte, error) {
|
||||
// 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 := crypt.Perms[:16]
|
||||
perms := make([]byte, 16)
|
||||
copy(perms, crypt.Perms[:16])
|
||||
|
||||
ac, err := aes.NewCipher(fkey[:32])
|
||||
if err != nil {
|
||||
@ -1628,5 +1847,16 @@ func (crypt *PdfCrypt) alg13(fkey []byte) (bool, error) {
|
||||
if p != crypt.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.EncryptMetadata {
|
||||
return false, errors.New("metadata encryption validation failed")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -8,8 +8,13 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
)
|
||||
@ -144,7 +149,7 @@ func TestDecryption1(t *testing.T) {
|
||||
crypter.DecryptedObjects = map[PdfObject]bool{}
|
||||
// Default algorithm is V2 (RC4).
|
||||
crypter.CryptFilters = CryptFilters{}
|
||||
crypter.CryptFilters["Default"] = CryptFilter{Cfm: "V2", Length: crypter.Length}
|
||||
crypter.CryptFilters[StandardCryptFilter] = CryptFilter{Cfm: "V2", Length: crypter.Length}
|
||||
crypter.V = 2
|
||||
crypter.R = 3
|
||||
crypter.P = -3904
|
||||
@ -230,3 +235,98 @@ func BenchmarkAlg2b(b *testing.B) {
|
||||
_ = 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{
|
||||
V: 5, R: R,
|
||||
P: perms,
|
||||
EncryptionKey: append([]byte{}, fkey...),
|
||||
EncryptMetadata: c.EncMeta,
|
||||
}
|
||||
|
||||
// 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.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")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,12 @@ func MakeArrayFromFloats(vals []float64) *PdfObjectArray {
|
||||
return &array
|
||||
}
|
||||
|
||||
// MakeBool creates an PdfObjectBool from a bool.
|
||||
func MakeBool(val bool) *PdfObjectBool {
|
||||
v := PdfObjectBool(val)
|
||||
return &v
|
||||
}
|
||||
|
||||
// MakeFloat creates an PdfObjectFloat from a float64.
|
||||
func MakeFloat(val float64) *PdfObjectFloat {
|
||||
num := PdfObjectFloat(val)
|
||||
|
@ -15,14 +15,15 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unidoc/common"
|
||||
"github.com/unidoc/unidoc/common/license"
|
||||
. "github.com/unidoc/unidoc/pdf/core"
|
||||
"github.com/unidoc/unidoc/pdf/model/fonts"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var pdfCreator = ""
|
||||
@ -342,8 +343,6 @@ func (this *PdfWriter) AddPage(page *PdfPage) error {
|
||||
|
||||
this.addObject(pageObj)
|
||||
|
||||
|
||||
|
||||
// Traverse the page and record all object references.
|
||||
err := this.addObjects(pDict)
|
||||
if err != nil {
|
||||
@ -476,8 +475,21 @@ func (this *PdfWriter) updateObjectNumbers() {
|
||||
|
||||
type EncryptOptions struct {
|
||||
Permissions AccessPermissions
|
||||
Algorithm EncryptionAlgorithm
|
||||
}
|
||||
|
||||
// EncryptionAlgorithm is used in EncryptOptions to change the default algorithm used to encrypt the document.
|
||||
type EncryptionAlgorithm int
|
||||
|
||||
const (
|
||||
// RC4_128bit uses RC4 encryption (128 bit)
|
||||
RC4_128bit = EncryptionAlgorithm(iota)
|
||||
// AES_128bit uses AES encryption (128 bit, PDF 1.6)
|
||||
AES_128bit
|
||||
// AES_256bit uses AES encryption (256 bit, PDF 2.0)
|
||||
AES_256bit
|
||||
)
|
||||
|
||||
// Encrypt the output file with a specified user/owner password.
|
||||
func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptions) error {
|
||||
crypter := PdfCrypt{}
|
||||
@ -486,18 +498,59 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
|
||||
crypter.EncryptedObjects = map[PdfObject]bool{}
|
||||
|
||||
crypter.CryptFilters = CryptFilters{}
|
||||
crypter.CryptFilters["Default"] = CryptFilter{Cfm: "V2", Length: 128}
|
||||
|
||||
// Set
|
||||
crypter.P = -1
|
||||
algo := RC4_128bit
|
||||
if options != nil {
|
||||
algo = options.Algorithm
|
||||
}
|
||||
|
||||
var cf CryptFilter
|
||||
switch algo {
|
||||
case RC4_128bit:
|
||||
crypter.V = 2
|
||||
crypter.R = 3
|
||||
crypter.Length = 128
|
||||
cf = CryptFilter{Cfm: CryptFilterV2, Length: 16}
|
||||
case AES_128bit:
|
||||
this.SetVersion(1, 5)
|
||||
crypter.V = 4
|
||||
crypter.R = 4
|
||||
crypter.Length = 128
|
||||
cf = CryptFilter{Cfm: CryptFilterAESV2, Length: 16}
|
||||
case AES_256bit:
|
||||
this.SetVersion(2, 0)
|
||||
crypter.V = 5
|
||||
crypter.R = 6 // TODO(dennwc): a way to set R=5?
|
||||
crypter.Length = 256
|
||||
cf = CryptFilter{Cfm: CryptFilterAESV3, Length: 32}
|
||||
default:
|
||||
return fmt.Errorf("unsupported algorithm: %v", options.Algorithm)
|
||||
}
|
||||
const (
|
||||
defaultFilter = StandardCryptFilter
|
||||
)
|
||||
crypter.CryptFilters[defaultFilter] = cf
|
||||
if crypter.V >= 4 {
|
||||
crypter.StreamFilter = defaultFilter
|
||||
crypter.StringFilter = defaultFilter
|
||||
}
|
||||
|
||||
// Set
|
||||
crypter.P = math.MaxUint32
|
||||
crypter.EncryptMetadata = true
|
||||
if options != nil {
|
||||
crypter.P = int(options.Permissions.GetP())
|
||||
}
|
||||
|
||||
// Generate the encryption dictionary.
|
||||
ed := MakeDict()
|
||||
ed.Set("Filter", MakeName("Standard"))
|
||||
ed.Set("P", MakeInteger(int64(crypter.P)))
|
||||
ed.Set("V", MakeInteger(int64(crypter.V)))
|
||||
ed.Set("R", MakeInteger(int64(crypter.R)))
|
||||
ed.Set("Length", MakeInteger(int64(crypter.Length)))
|
||||
this.encryptDict = ed
|
||||
|
||||
// Prepare the ID object for the trailer.
|
||||
hashcode := md5.Sum([]byte(time.Now().Format(time.RFC850)))
|
||||
id0 := PdfObjectString(hashcode[:])
|
||||
@ -510,6 +563,8 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
|
||||
this.ids = &PdfObjectArray{&id0, &id1}
|
||||
common.Log.Trace("Gen Id 0: % x", id0)
|
||||
|
||||
// Generate encryption parameters
|
||||
if crypter.R < 5 {
|
||||
crypter.Id0 = string(id0)
|
||||
|
||||
// Make the O and U objects.
|
||||
@ -529,19 +584,30 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
|
||||
crypter.U = []byte(U)
|
||||
crypter.EncryptionKey = key
|
||||
|
||||
// Generate the encryption dictionary.
|
||||
encDict := MakeDict()
|
||||
encDict.Set("Filter", MakeName("Standard"))
|
||||
encDict.Set("P", MakeInteger(int64(crypter.P)))
|
||||
encDict.Set("V", MakeInteger(int64(crypter.V)))
|
||||
encDict.Set("R", MakeInteger(int64(crypter.R)))
|
||||
encDict.Set("Length", MakeInteger(int64(crypter.Length)))
|
||||
encDict.Set("O", &O)
|
||||
encDict.Set("U", &U)
|
||||
this.encryptDict = encDict
|
||||
ed.Set("O", &O)
|
||||
ed.Set("U", &U)
|
||||
} else { // R >= 5
|
||||
err := crypter.GenerateParams(userPass, ownerPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ed.Set("O", MakeString(string(crypter.O)))
|
||||
ed.Set("U", MakeString(string(crypter.U)))
|
||||
ed.Set("OE", MakeString(string(crypter.OE)))
|
||||
ed.Set("UE", MakeString(string(crypter.UE)))
|
||||
ed.Set("EncryptMetadata", MakeBool(crypter.EncryptMetadata))
|
||||
if crypter.R > 5 {
|
||||
ed.Set("Perms", MakeString(string(crypter.Perms)))
|
||||
}
|
||||
}
|
||||
if crypter.V >= 4 {
|
||||
if err := crypter.SaveCryptFilters(ed); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make an object to contain it.
|
||||
io := MakeIndirectObject(encDict)
|
||||
// Make an object to contain the encryption dictionary.
|
||||
io := MakeIndirectObject(ed)
|
||||
this.encryptObj = io
|
||||
this.addObject(io)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user