core: allow to write documents with AES encryption

This commit is contained in:
Denys Smirnov 2018-09-19 06:02:15 +03:00
parent 839f9ea3bc
commit eac1e899dc
3 changed files with 147 additions and 41 deletions

View File

@ -37,7 +37,7 @@ type PdfCrypt struct {
U []byte U []byte
OE []byte // R=6 OE []byte // R=6
UE []byte // R=6 UE []byte // R=6
P int P int // TODO (v3): uint32
Perms []byte // R=6 Perms []byte // R=6
EncryptMetadata bool EncryptMetadata bool
Id0 string Id0 string
@ -97,7 +97,7 @@ const (
// TODO (v3): Unexport. // TODO (v3): Unexport.
type CryptFilters map[string]CryptFilter 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. // TODO (v3): Unexport.
func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error { func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
crypt.CryptFilters = CryptFilters{} crypt.CryptFilters = CryptFilters{}
@ -213,6 +213,31 @@ func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
return nil 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("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 // PdfCryptMakeNew makes the document crypt handler based on the encryption dictionary
// and trailer dictionary. Returns an error on failure to process. // and trailer dictionary. Returns an error on failure to process.
func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCrypt, error) { func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCrypt, error) {
@ -273,6 +298,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
if !ok { if !ok {
return crypter, errors.New("Encrypt dictionary missing R") 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 { if *R < 2 || *R > 6 {
return crypter, fmt.Errorf("Invalid R (%d)", *R) return crypter, fmt.Errorf("Invalid R (%d)", *R)
} }
@ -1584,10 +1610,24 @@ func (crypt *PdfCrypt) Alg7(opass []byte) (bool, error) {
return auth, nil return auth, nil
} }
// encryptR6 is the algorithm opposite to alg2a (R>=5). // 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. // It generates U,O,UE,OE,Perms fields using AESv3 encryption.
// There is no algorithm number assigned to this function in the spec. // There is no algorithm number assigned to this function in the spec.
func (crypt *PdfCrypt) encryptR6(upass, opass []byte) error { func (crypt *PdfCrypt) generateR6(upass, opass []byte) error {
// all these field will be populated by functions below // all these field will be populated by functions below
crypt.U = nil crypt.U = nil
crypt.O = nil crypt.O = nil

View File

@ -284,7 +284,7 @@ func TestAESv3(t *testing.T) {
} }
// generate encryption parameters // generate encryption parameters
err := crypt.encryptR6([]byte(c.UserPass), []byte(c.OwnerPass)) err := crypt.generateR6([]byte(c.UserPass), []byte(c.OwnerPass))
if err != nil { if err != nil {
t.Fatal("Failed to encrypt:", err) t.Fatal("Failed to encrypt:", err)
} }

View File

@ -15,14 +15,15 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"strings"
"time" "time"
"github.com/unidoc/unidoc/common" "github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/common/license" "github.com/unidoc/unidoc/common/license"
. "github.com/unidoc/unidoc/pdf/core" . "github.com/unidoc/unidoc/pdf/core"
"github.com/unidoc/unidoc/pdf/model/fonts" "github.com/unidoc/unidoc/pdf/model/fonts"
"strings"
) )
var pdfCreator = "" var pdfCreator = ""
@ -342,8 +343,6 @@ func (this *PdfWriter) AddPage(page *PdfPage) error {
this.addObject(pageObj) this.addObject(pageObj)
// Traverse the page and record all object references. // Traverse the page and record all object references.
err := this.addObjects(pDict) err := this.addObjects(pDict)
if err != nil { if err != nil {
@ -476,8 +475,21 @@ func (this *PdfWriter) updateObjectNumbers() {
type EncryptOptions struct { type EncryptOptions struct {
Permissions AccessPermissions 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. // Encrypt the output file with a specified user/owner password.
func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptions) error { func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptions) error {
crypter := PdfCrypt{} crypter := PdfCrypt{}
@ -486,18 +498,59 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
crypter.EncryptedObjects = map[PdfObject]bool{} crypter.EncryptedObjects = map[PdfObject]bool{}
crypter.CryptFilters = CryptFilters{} crypter.CryptFilters = CryptFilters{}
crypter.CryptFilters["Default"] = CryptFilter{Cfm: "V2", Length: 128}
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 = "Default"
)
crypter.CryptFilters[defaultFilter] = cf
if crypter.V >= 4 {
crypter.StreamFilter = defaultFilter
crypter.StringFilter = defaultFilter
}
// Set // Set
crypter.P = -1 crypter.P = math.MaxUint32
crypter.V = 2
crypter.R = 3
crypter.Length = 128
crypter.EncryptMetadata = true crypter.EncryptMetadata = true
if options != nil { if options != nil {
crypter.P = int(options.Permissions.GetP()) 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. // Prepare the ID object for the trailer.
hashcode := md5.Sum([]byte(time.Now().Format(time.RFC850))) hashcode := md5.Sum([]byte(time.Now().Format(time.RFC850)))
id0 := PdfObjectString(hashcode[:]) id0 := PdfObjectString(hashcode[:])
@ -510,38 +563,51 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
this.ids = &PdfObjectArray{&id0, &id1} this.ids = &PdfObjectArray{&id0, &id1}
common.Log.Trace("Gen Id 0: % x", id0) common.Log.Trace("Gen Id 0: % x", id0)
crypter.Id0 = string(id0) // Generate encryption parameters
if crypter.R < 5 {
crypter.Id0 = string(id0)
// Make the O and U objects. // Make the O and U objects.
O, err := crypter.Alg3(userPass, ownerPass) O, err := crypter.Alg3(userPass, ownerPass)
if err != nil { if err != nil {
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err) common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
return err return err
}
crypter.O = []byte(O)
common.Log.Trace("gen O: % x", O)
U, key, err := crypter.Alg5(userPass)
if err != nil {
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
return err
}
common.Log.Trace("gen U: % x", U)
crypter.U = []byte(U)
crypter.EncryptionKey = key
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)))
}
} }
crypter.O = []byte(O) if crypter.V >= 4 {
common.Log.Trace("gen O: % x", O) if err := crypter.SaveCryptFilters(ed); err != nil {
U, key, err := crypter.Alg5(userPass) return err
if err != nil { }
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
return err
} }
common.Log.Trace("gen U: % x", U)
crypter.U = []byte(U)
crypter.EncryptionKey = key
// Generate the encryption dictionary. // Make an object to contain the encryption dictionary.
encDict := MakeDict() io := MakeIndirectObject(ed)
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
// Make an object to contain it.
io := MakeIndirectObject(encDict)
this.encryptObj = io this.encryptObj = io
this.addObject(io) this.addObject(io)