From 7bd4ba688d1fcace0492f8f15c7bdf7065a8c68e Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Thu, 4 Oct 2018 05:35:00 +0300 Subject: [PATCH] core: split crypt filter methods into a separate package --- pdf/core/crypt.go | 184 ++++++----- pdf/core/crypt_filters.go | 389 ------------------------ pdf/core/security/auth.go | 9 + pdf/core/security/crypt/filter_aesv2.go | 49 +++ pdf/core/security/crypt/filter_aesv3.go | 173 +++++++++++ pdf/core/security/crypt/filter_v2.go | 125 ++++++++ pdf/core/security/crypt/filters.go | 108 +++++++ pdf/model/writer.go | 12 +- 8 files changed, 560 insertions(+), 489 deletions(-) delete mode 100644 pdf/core/crypt_filters.go create mode 100644 pdf/core/security/auth.go create mode 100644 pdf/core/security/crypt/filter_aesv2.go create mode 100644 pdf/core/security/crypt/filter_aesv3.go create mode 100644 pdf/core/security/crypt/filter_v2.go create mode 100644 pdf/core/security/crypt/filters.go diff --git a/pdf/core/crypt.go b/pdf/core/crypt.go index b17887c0..929afa88 100644 --- a/pdf/core/crypt.go +++ b/pdf/core/crypt.go @@ -14,6 +14,7 @@ import ( "github.com/unidoc/unidoc/common" "github.com/unidoc/unidoc/pdf/core/security" + crypto "github.com/unidoc/unidoc/pdf/core/security/crypt" ) type Version struct { @@ -28,7 +29,7 @@ type EncryptInfo struct { } // PdfCryptNewEncrypt makes the document crypt handler based on a specified crypt filter. -func PdfCryptNewEncrypt(cf CryptFilter, userPass, ownerPass []byte, perm security.Permissions) (*PdfCrypt, *EncryptInfo, error) { +func PdfCryptNewEncrypt(cf crypto.Filter, userPass, ownerPass []byte, perm security.Permissions) (*PdfCrypt, *EncryptInfo, error) { crypter := &PdfCrypt{ encryptedObjects: make(map[PdfObject]bool), cryptFilters: make(cryptFilters), @@ -37,26 +38,19 @@ func PdfCryptNewEncrypt(cf CryptFilter, userPass, ownerPass []byte, perm securit EncryptMetadata: true, }, } - // TODO(dennwc): define it in the CF interface var vers Version - switch cf.(type) { - case cryptFilterV2: - crypter.encrypt.V = 2 - crypter.encryptStd.R = 3 - case cryptFilterAESV2: - vers.Major, vers.Minor = 1, 5 - crypter.encrypt.V = 4 - crypter.encryptStd.R = 4 - case cryptFilterAESV3: - vers.Major, vers.Minor = 2, 0 - crypter.encrypt.V = 5 - crypter.encryptStd.R = 6 - } if cf != nil { + v := cf.PDFVersion() + vers.Major, vers.Minor = v[0], v[1] + + V, R := cf.HandlerVersion() + crypter.encrypt.V = V + crypter.encryptStd.R = R + crypter.encrypt.Length = cf.KeyLength() * 8 } const ( - defaultFilter = StandardCryptFilter + defaultFilter = stdCryptFilter ) crypter.cryptFilters[defaultFilter] = cf if crypter.encrypt.V >= 4 { @@ -97,6 +91,28 @@ func PdfCryptNewEncrypt(cf CryptFilter, userPass, ownerPass []byte, perm securit }, nil } +// PdfCrypt provides PDF encryption/decryption support. +// The PDF standard supports encryption of strings and streams (Section 7.6). +// TODO (v3): Consider unexporting. +type PdfCrypt struct { + encrypt encryptDict + encryptStd security.StdEncryptDict + + id0 string + encryptionKey []byte + decryptedObjects map[PdfObject]bool + encryptedObjects map[PdfObject]bool + authenticated bool + // Crypt filters (V4). + cryptFilters cryptFilters + streamFilter string + stringFilter string + + parser *PdfParser + + decryptedObjNum map[int]struct{} +} + // encodeEncryptStd encodes fields of standard security handler to an Encrypt dictionary. func encodeEncryptStd(d *security.StdEncryptDict, ed *PdfObjectDictionary) { ed.Set("R", MakeInteger(int64(d.R))) @@ -200,26 +216,32 @@ func decodeEncryptStd(d *security.StdEncryptDict, ed *PdfObjectDictionary) error return nil } -// PdfCrypt provides PDF encryption/decryption support. -// The PDF standard supports encryption of strings and streams (Section 7.6). -// TODO (v3): Consider unexporting. -type PdfCrypt struct { - encrypt encryptDict - encryptStd security.StdEncryptDict +func decodeCryptFilter(cf *crypto.FilterDict, d *PdfObjectDictionary) error { + // If Type present, should be CryptFilter. + if typename, ok := d.Get("Type").(*PdfObjectName); ok { + if string(*typename) != "CryptFilter" { + return fmt.Errorf("CF dict type != CryptFilter (%s)", typename) + } + } - id0 string - encryptionKey []byte - decryptedObjects map[PdfObject]bool - encryptedObjects map[PdfObject]bool - authenticated bool - // Crypt filters (V4). - cryptFilters cryptFilters - streamFilter string - stringFilter string + // Method. + name, ok := d.Get("CFM").(*PdfObjectName) + if !ok { + return fmt.Errorf("Unsupported crypt filter (None)") + } + cf.CFM = string(*name) - parser *PdfParser + // Auth event + if event, ok := d.Get("AuthEvent").(*PdfObjectName); ok { + cf.AuthEvent = security.AuthEvent(*event) + } else { + cf.AuthEvent = security.EventDocOpen + } - decryptedObjNum map[int]struct{} + if length, ok := d.Get("Length").(*PdfObjectInteger); ok { + cf.Length = int(*length) + } + return nil } func (crypt *PdfCrypt) newEncyptDict() *PdfObjectDictionary { @@ -262,65 +284,31 @@ func (crypt *PdfCrypt) String() string { return str } -type authEvent string - -const ( - authEventDocOpen = authEvent("DocOpen") - authEventEFOpen = authEvent("EFOpen") -) - -type cryptFiltersDict map[string]cryptFilterDict - // encryptDict is a set of field common to all encryption dictionaries. type encryptDict struct { - Filter string // (Required) The name of the preferred security handler for this document. - V int // (Required) A code specifying the algorithm to be used in encrypting and decrypting the document. - SubFilter string // Completely specifies the format and interpretation of the encryption dictionary. - Length int // The length of the encryption key, in bits. - CF cryptFiltersDict // Crypt filters dictionary. - StmF string // The filter that shall be used by default when decrypting streams. - StrF string // The filter that shall be used when decrypting all strings in the document. - EFF string // The filter that shall be used when decrypting embedded file streams. + Filter string // (Required) The name of the preferred security handler for this document. + V int // (Required) A code specifying the algorithm to be used in encrypting and decrypting the document. + SubFilter string // Completely specifies the format and interpretation of the encryption dictionary. + Length int // The length of the encryption key, in bits. + + StmF string // The filter that shall be used by default when decrypting streams. + StrF string // The filter that shall be used when decrypting all strings in the document. + EFF string // The filter that shall be used when decrypting embedded file streams. + + CF map[string]crypto.FilterDict // Crypt filters dictionary. } -// StandardCryptFilter is a default name for a standard crypt filter. -const StandardCryptFilter = "StdCF" +// stdCryptFilter is a default name for a standard crypt filter. +const stdCryptFilter = "StdCF" func newCryptFiltersV2(length int) cryptFilters { return cryptFilters{ - StandardCryptFilter: NewCryptFilterV2(length), + stdCryptFilter: crypto.NewFilterV2(length), } } -// NewCryptFilterV2 creates a RC4-based filter with a specified key length (in bytes). -func NewCryptFilterV2(length int) CryptFilter { - f, err := newCryptFilterV2(cryptFilterDict{Length: length}) - if err != nil { - panic(err) - } - return f -} - -// NewCryptFilterAESV2 creates an AES-based filter with a 128 bit key (AESV2). -func NewCryptFilterAESV2() CryptFilter { - f, err := newCryptFilterAESV2(cryptFilterDict{}) - if err != nil { - panic(err) - } - return f -} - -// NewCryptFilterAESV3 creates an AES-based filter with a 256 bit key (AESV3). -func NewCryptFilterAESV3() CryptFilter { - f, err := newCryptFilterAESV3(cryptFilterDict{}) - if err != nil { - panic(err) - } - return f -} - // cryptFilters is a map of crypt filter name and underlying CryptFilter info. -type cryptFilters map[string]CryptFilter +type cryptFilters map[string]crypto.Filter // loadCryptFilters loads crypt filter information from the encryption dictionary (V>=4). func (crypt *PdfCrypt) loadCryptFilters(ed *PdfObjectDictionary) error { @@ -365,22 +353,18 @@ func (crypt *PdfCrypt) loadCryptFilters(ed *PdfObjectDictionary) error { continue } - var cfd cryptFilterDict - if err := cfd.ReadFrom(dict); err != nil { + var cfd crypto.FilterDict + if err := decodeCryptFilter(&cfd, dict); err != nil { return err } - fnc, err := getCryptFilterMethod(cfd.CFM) - if err != nil { - return err - } - cf, err := fnc(cfd) + cf, err := crypto.NewFilter(cfd) if err != nil { return err } crypt.cryptFilters[string(name)] = cf } // Cannot be overwritten. - crypt.cryptFilters["Identity"] = cryptFilteridentity{} + crypt.cryptFilters["Identity"] = crypto.NewIdentity() // StrF strings filter. crypt.stringFilter = "Identity" @@ -403,6 +387,18 @@ func (crypt *PdfCrypt) loadCryptFilters(ed *PdfObjectDictionary) error { return nil } +func encodeCryptFilter(cf crypto.Filter, event security.AuthEvent) *PdfObjectDictionary { + if event == "" { + event = security.EventDocOpen + } + v := MakeDict() + v.Set("Type", MakeName("CryptFilter")) // optional + v.Set("AuthEvent", MakeName(string(event))) + v.Set("CFM", MakeName(cf.Name())) + v.Set("Length", MakeInteger(int64(cf.KeyLength()))) + return v +} + // saveCryptFilters saves crypt filter information to the encryption dictionary (V>=4). func (crypt *PdfCrypt) saveCryptFilters(ed *PdfObjectDictionary) error { if crypt.encrypt.V < 4 { @@ -415,7 +411,7 @@ func (crypt *PdfCrypt) saveCryptFilters(ed *PdfObjectDictionary) error { if name == "Identity" { continue } - v := cryptFilterToDict(filter, "") + v := encodeCryptFilter(filter, "") cf.Set(PdfObjectName(name), v) } ed.Set("StrF", MakeName(crypt.stringFilter)) @@ -654,7 +650,7 @@ func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) // TODO: Check for crypt filter (V4). // The Crypt filter shall be the first filter in the Filter array entry. - streamFilter := StandardCryptFilter // Default RC4. + streamFilter := stdCryptFilter // Default RC4. if crypt.encrypt.V >= 4 { streamFilter = crypt.streamFilter common.Log.Trace("this.streamFilter = %s", crypt.streamFilter) @@ -708,7 +704,7 @@ func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64) case *PdfObjectString: common.Log.Trace("Decrypting string!") - stringFilter := StandardCryptFilter + stringFilter := stdCryptFilter if crypt.encrypt.V >= 4 { // Currently only support Identity / RC4. common.Log.Trace("with %s filter", crypt.stringFilter) @@ -837,7 +833,7 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) // TODO: Check for crypt filter (V4). // The Crypt filter shall be the first filter in the Filter array entry. - streamFilter := StandardCryptFilter // Default RC4. + streamFilter := stdCryptFilter // Default RC4. if crypt.encrypt.V >= 4 { // For now. Need to change when we add support for more than // Identity / RC4. @@ -893,7 +889,7 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64) case *PdfObjectString: common.Log.Trace("Encrypting string!") - stringFilter := StandardCryptFilter + stringFilter := stdCryptFilter if crypt.encrypt.V >= 4 { common.Log.Trace("with %s filter", crypt.stringFilter) if crypt.stringFilter == "Identity" { diff --git a/pdf/core/crypt_filters.go b/pdf/core/crypt_filters.go deleted file mode 100644 index 05b8276d..00000000 --- a/pdf/core/crypt_filters.go +++ /dev/null @@ -1,389 +0,0 @@ -package core - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/md5" - "crypto/rand" - "crypto/rc4" - "fmt" - "io" - - "github.com/unidoc/unidoc/common" -) - -var ( - cryptMethods = make(map[string]cryptFilterFunc) -) - -// cryptFilterDict represents information from a CryptFilter dictionary. -type cryptFilterDict struct { - CFM string // The method used, if any, by the PDF reader to decrypt data. - AuthEvent authEvent - Length int // in bytes -} - -func (cf *cryptFilterDict) ReadFrom(d *PdfObjectDictionary) error { - // If Type present, should be CryptFilter. - if typename, ok := d.Get("Type").(*PdfObjectName); ok { - if string(*typename) != "CryptFilter" { - return fmt.Errorf("CF dict type != CryptFilter (%s)", typename) - } - } - - // Method. - name, ok := d.Get("CFM").(*PdfObjectName) - if !ok { - return fmt.Errorf("Unsupported crypt filter (None)") - } - cf.CFM = string(*name) - - // Auth event - if event, ok := d.Get("AuthEvent").(*PdfObjectName); ok { - cf.AuthEvent = authEvent(*event) - } else { - cf.AuthEvent = authEventDocOpen - } - - if length, ok := d.Get("Length").(*PdfObjectInteger); ok { - cf.Length = int(*length) - } - return nil -} - -// cryptFilterFunc is used to construct crypt filters from CryptFilter dictionary -type cryptFilterFunc func(d cryptFilterDict) (CryptFilter, error) - -// registerCryptFilterMethod registers a CFM. -func registerCryptFilterMethod(name string, fnc cryptFilterFunc) { - if _, ok := cryptMethods[name]; ok { - panic("already registered") - } - cryptMethods[name] = fnc -} - -// getCryptFilterMethod check if a CFM with a specified name is supported an returns its implementation. -func getCryptFilterMethod(name string) (cryptFilterFunc, error) { - f := cryptMethods[string(name)] - if f == nil { - return nil, fmt.Errorf("unsupported crypt filter: %q", name) - } - return f, nil -} - -func init() { - // Register supported crypt filter methods. - // Table 25, CFM (page 92) - registerCryptFilterMethod("V2", newCryptFilterV2) - registerCryptFilterMethod("AESV2", newCryptFilterAESV2) - registerCryptFilterMethod("AESV3", newCryptFilterAESV3) -} - -// CryptFilter is a common interface for crypt filter methods. -type CryptFilter interface { - // Name returns a name of the filter that should be used in CFM field of Encrypt dictionary. - Name() string - // KeyLength returns a length of the encryption key in bytes. - KeyLength() int - // MakeKey generates a object encryption key based on file encryption key and object numbers. - // Used only for legacy filters - AESV3 doesn't change the key for each object. - MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error) - // EncryptBytes encrypts a buffer using object encryption key, as returned by MakeKey. - // Implementation may reuse a buffer and encrypt data in-place. - EncryptBytes(p []byte, okey []byte) ([]byte, error) - // DecryptBytes decrypts a buffer using object encryption key, as returned by MakeKey. - // Implementation may reuse a buffer and decrypt data in-place. - DecryptBytes(p []byte, okey []byte) ([]byte, error) -} - -func cryptFilterToDict(cf CryptFilter, event authEvent) *PdfObjectDictionary { - if event == "" { - event = authEventDocOpen - } - v := MakeDict() - v.Set("Type", MakeName("CryptFilter")) // optional - v.Set("AuthEvent", MakeName(string(event))) - v.Set("CFM", MakeName(cf.Name())) - v.Set("Length", MakeInteger(int64(cf.KeyLength()))) - return v -} - -type cryptFilteridentity struct{} - -func (cryptFilteridentity) Name() string { - return "Identity" -} - -func (cryptFilteridentity) KeyLength() int { - return 0 -} - -func (cryptFilteridentity) MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error) { - return fkey, nil -} - -func (cryptFilteridentity) EncryptBytes(p []byte, okey []byte) ([]byte, error) { - return p, nil -} - -func (cryptFilteridentity) DecryptBytes(p []byte, okey []byte) ([]byte, error) { - return p, nil -} - -// makeKeyV2 is a common object key generation shared by V2 and AESV2 crypt filters. -func makeKeyV2(objNum, genNum uint32, ekey []byte, isAES bool) ([]byte, error) { - key := make([]byte, len(ekey)+5) - for i := 0; i < len(ekey); i++ { - key[i] = ekey[i] - } - for i := 0; i < 3; i++ { - b := byte((objNum >> uint32(8*i)) & 0xff) - key[i+len(ekey)] = b - } - for i := 0; i < 2; i++ { - b := byte((genNum >> uint32(8*i)) & 0xff) - key[i+len(ekey)+3] = b - } - if isAES { - // If using the AES algorithm, extend the encryption key an - // additional 4 bytes by adding the value “sAlT”, which - // corresponds to the hexadecimal values 0x73, 0x41, 0x6C, 0x54. - key = append(key, 0x73) - key = append(key, 0x41) - key = append(key, 0x6C) - key = append(key, 0x54) - } - - // Take the MD5. - h := md5.New() - h.Write(key) - hashb := h.Sum(nil) - - if len(ekey)+5 < 16 { - return hashb[0 : len(ekey)+5], nil - } - - return hashb, nil -} - -func newCryptFilterV2(d cryptFilterDict) (CryptFilter, error) { - if d.Length%8 != 0 { - return nil, fmt.Errorf("Crypt filter length not multiple of 8 (%d)", d.Length) - } - // Standard security handler expresses the length in multiples of 8 (16 means 128) - // We only deal with standard so far. (Public key not supported yet). - if d.Length < 5 || d.Length > 16 { - if d.Length == 64 || d.Length == 128 { - common.Log.Debug("STANDARD VIOLATION: Crypt Length appears to be in bits rather than bytes - assuming bits (%d)", d.Length) - d.Length /= 8 - } else { - return nil, fmt.Errorf("Crypt filter length not in range 40 - 128 bit (%d)", d.Length) - } - } - return cryptFilterV2{length: d.Length}, nil -} - -// cryptFilterV2 is a RC4-based filter -type cryptFilterV2 struct { - length int -} - -func (cryptFilterV2) Name() string { - return "V2" -} - -func (f cryptFilterV2) KeyLength() int { - return f.length -} - -func (f cryptFilterV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) { - return makeKeyV2(objNum, genNum, ekey, false) -} - -func (cryptFilterV2) EncryptBytes(buf []byte, okey []byte) ([]byte, error) { - // Standard RC4 algorithm. - ciph, err := rc4.NewCipher(okey) - if err != nil { - return nil, err - } - common.Log.Trace("RC4 Encrypt: % x", buf) - ciph.XORKeyStream(buf, buf) - common.Log.Trace("to: % x", buf) - return buf, nil -} - -func (cryptFilterV2) DecryptBytes(buf []byte, okey []byte) ([]byte, error) { - // Standard RC4 algorithm. - ciph, err := rc4.NewCipher(okey) - if err != nil { - return nil, err - } - common.Log.Trace("RC4 Decrypt: % x", buf) - ciph.XORKeyStream(buf, buf) - common.Log.Trace("to: % x", buf) - return buf, nil -} - -// cryptFilterAES implements a generic AES encryption and decryption algorithm used by AESV2 and AESV3 filter methods. -type cryptFilterAES struct{} - -func (cryptFilterAES) EncryptBytes(buf []byte, okey []byte) ([]byte, error) { - // Strings and streams encrypted with AES shall use a padding - // scheme that is described in Internet RFC 2898, PKCS #5: - // Password-Based Cryptography Specification Version 2.0; see - // the Bibliography. For an original message length of M, - // the pad shall consist of 16 - (M mod 16) bytes whose value - // shall also be 16 - (M mod 16). - // - // A 9-byte message has a pad of 7 bytes, each with the value - // 0x07. The pad can be unambiguously removed to determine the - // original message length when decrypting. Note that the pad is - // present when M is evenly divisible by 16; it contains 16 bytes - // of 0x10. - - ciph, err := aes.NewCipher(okey) - if err != nil { - return nil, err - } - - common.Log.Trace("AES Encrypt (%d): % x", len(buf), buf) - - // If using the AES algorithm, the Cipher Block Chaining (CBC) - // mode, which requires an initialization vector, is used. The - // block size parameter is set to 16 bytes, and the initialization - // vector is a 16-byte random number that is stored as the first - // 16 bytes of the encrypted stream or string. - - 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, 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[block:], buf) - - buf = ciphertext - common.Log.Trace("to (%d): % x", len(buf), buf) - - return buf, nil -} - -func (cryptFilterAES) DecryptBytes(buf []byte, okey []byte) ([]byte, error) { - // Strings and streams encrypted with AES shall use a padding - // scheme that is described in Internet RFC 2898, PKCS #5: - // Password-Based Cryptography Specification Version 2.0; see - // the Bibliography. For an original message length of M, - // the pad shall consist of 16 - (M mod 16) bytes whose value - // shall also be 16 - (M mod 16). - // - // A 9-byte message has a pad of 7 bytes, each with the value - // 0x07. The pad can be unambiguously removed to determine the - // original message length when decrypting. Note that the pad is - // present when M is evenly divisible by 16; it contains 16 bytes - // of 0x10. - - ciph, err := aes.NewCipher(okey) - if err != nil { - return nil, err - } - - // If using the AES algorithm, the Cipher Block Chaining (CBC) - // mode, which requires an initialization vector, is used. The - // block size parameter is set to 16 bytes, and the initialization - // vector is a 16-byte random number that is stored as the first - // 16 bytes of the encrypted stream or string. - if len(buf) < 16 { - common.Log.Debug("ERROR AES invalid buf %s", buf) - return buf, fmt.Errorf("AES: Buf len < 16 (%d)", len(buf)) - } - - iv := buf[:16] - buf = buf[16:] - - if len(buf)%16 != 0 { - common.Log.Debug(" iv (%d): % x", len(iv), iv) - common.Log.Debug("buf (%d): % x", len(buf), buf) - return buf, fmt.Errorf("AES buf length not multiple of 16 (%d)", len(buf)) - } - - mode := cipher.NewCBCDecrypter(ciph, iv) - - common.Log.Trace("AES Decrypt (%d): % x", len(buf), buf) - common.Log.Trace("chop AES Decrypt (%d): % x", len(buf), buf) - mode.CryptBlocks(buf, buf) - common.Log.Trace("to (%d): % x", len(buf), buf) - - if len(buf) == 0 { - common.Log.Trace("Empty buf, returning empty string") - return buf, nil - } - - // The padded length is indicated by the last values. Remove those. - - padLen := int(buf[len(buf)-1]) - if padLen >= len(buf) { - common.Log.Debug("Illegal pad length") - return buf, fmt.Errorf("Invalid pad length") - } - buf = buf[:len(buf)-padLen] - - return buf, nil -} - -func newCryptFilterAESV2(d cryptFilterDict) (CryptFilter, error) { - if d.Length != 0 && d.Length != 16 { - return nil, fmt.Errorf("Invalid AESV2 crypt filter length (%d)", d.Length) - } - return cryptFilterAESV2{}, nil -} - -// cryptFilterAESV2 is an AES-based filter (128 bit key, PDF 1.6) -type cryptFilterAESV2 struct { - cryptFilterAES -} - -func (cryptFilterAESV2) Name() string { - return "AESV2" -} - -func (cryptFilterAESV2) KeyLength() int { - return 128 / 8 -} - -func (cryptFilterAESV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) { - return makeKeyV2(objNum, genNum, ekey, true) -} - -func newCryptFilterAESV3(d cryptFilterDict) (CryptFilter, error) { - if d.Length != 0 && d.Length != 32 { - return nil, fmt.Errorf("Invalid AESV3 crypt filter length (%d)", d.Length) - } - return cryptFilterAESV3{}, nil -} - -// cryptFilterAESV3 is an AES-based filter (256 bit key, PDF 2.0) -type cryptFilterAESV3 struct { - cryptFilterAES -} - -func (cryptFilterAESV3) Name() string { - return "AESV3" -} - -func (cryptFilterAESV3) KeyLength() int { - return 256 / 8 -} - -func (cryptFilterAESV3) MakeKey(_, _ uint32, ekey []byte) ([]byte, error) { - return ekey, nil -} diff --git a/pdf/core/security/auth.go b/pdf/core/security/auth.go new file mode 100644 index 00000000..1e6cd28d --- /dev/null +++ b/pdf/core/security/auth.go @@ -0,0 +1,9 @@ +package security + +// AuthEvent is an event type that triggers authentication. +type AuthEvent string + +const ( + EventDocOpen = AuthEvent("DocOpen") // document open + EventEFOpen = AuthEvent("EFOpen") // embedded file open +) diff --git a/pdf/core/security/crypt/filter_aesv2.go b/pdf/core/security/crypt/filter_aesv2.go new file mode 100644 index 00000000..409f9eac --- /dev/null +++ b/pdf/core/security/crypt/filter_aesv2.go @@ -0,0 +1,49 @@ +package crypt + +import "fmt" + +func init() { + registerFilter("AESV2", newFilterAESV2) +} + +// NewFilterAESV2 creates an AES-based filter with a 128 bit key (AESV2). +func NewFilterAESV2() Filter { + f, err := newFilterAESV2(FilterDict{}) + if err != nil { + panic(err) + } + return f +} + +func newFilterAESV2(d FilterDict) (Filter, error) { + if d.Length != 0 && d.Length != 16 { + return nil, fmt.Errorf("Invalid AESV2 crypt filter length (%d)", d.Length) + } + return filterAESV2{}, nil +} + +// filterAESV2 is an AES-based filter (128 bit key, PDF 1.6) +type filterAESV2 struct { + filterAES +} + +func (filterAESV2) PDFVersion() [2]int { + return [2]int{1, 5} +} + +func (filterAESV2) HandlerVersion() (V, R int) { + V, R = 4, 4 + return +} + +func (filterAESV2) Name() string { + return "AESV2" +} + +func (filterAESV2) KeyLength() int { + return 128 / 8 +} + +func (filterAESV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) { + return makeKeyV2(objNum, genNum, ekey, true) +} diff --git a/pdf/core/security/crypt/filter_aesv3.go b/pdf/core/security/crypt/filter_aesv3.go new file mode 100644 index 00000000..9ca7eb26 --- /dev/null +++ b/pdf/core/security/crypt/filter_aesv3.go @@ -0,0 +1,173 @@ +package crypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "io" + + "github.com/unidoc/unidoc/common" +) + +func init() { + registerFilter("AESV3", newFilterAESV3) +} + +// NewFilterAESV3 creates an AES-based filter with a 256 bit key (AESV3). +func NewFilterAESV3() Filter { + f, err := newFilterAESV3(FilterDict{}) + if err != nil { + panic(err) + } + return f +} + +func newFilterAESV3(d FilterDict) (Filter, error) { + if d.Length != 0 && d.Length != 32 { + return nil, fmt.Errorf("Invalid AESV3 crypt filter length (%d)", d.Length) + } + return filterAESV3{}, nil +} + +// filterAES implements a generic AES encryption and decryption algorithm used by AESV2 and AESV3 filter methods. +type filterAES struct{} + +func (filterAES) EncryptBytes(buf []byte, okey []byte) ([]byte, error) { + // Strings and streams encrypted with AES shall use a padding + // scheme that is described in Internet RFC 2898, PKCS #5: + // Password-Based Cryptography Specification Version 2.0; see + // the Bibliography. For an original message length of M, + // the pad shall consist of 16 - (M mod 16) bytes whose value + // shall also be 16 - (M mod 16). + // + // A 9-byte message has a pad of 7 bytes, each with the value + // 0x07. The pad can be unambiguously removed to determine the + // original message length when decrypting. Note that the pad is + // present when M is evenly divisible by 16; it contains 16 bytes + // of 0x10. + + ciph, err := aes.NewCipher(okey) + if err != nil { + return nil, err + } + + common.Log.Trace("AES Encrypt (%d): % x", len(buf), buf) + + // If using the AES algorithm, the Cipher Block Chaining (CBC) + // mode, which requires an initialization vector, is used. The + // block size parameter is set to 16 bytes, and the initialization + // vector is a 16-byte random number that is stored as the first + // 16 bytes of the encrypted stream or string. + + 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, 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[block:], buf) + + buf = ciphertext + common.Log.Trace("to (%d): % x", len(buf), buf) + + return buf, nil +} + +func (filterAES) DecryptBytes(buf []byte, okey []byte) ([]byte, error) { + // Strings and streams encrypted with AES shall use a padding + // scheme that is described in Internet RFC 2898, PKCS #5: + // Password-Based Cryptography Specification Version 2.0; see + // the Bibliography. For an original message length of M, + // the pad shall consist of 16 - (M mod 16) bytes whose value + // shall also be 16 - (M mod 16). + // + // A 9-byte message has a pad of 7 bytes, each with the value + // 0x07. The pad can be unambiguously removed to determine the + // original message length when decrypting. Note that the pad is + // present when M is evenly divisible by 16; it contains 16 bytes + // of 0x10. + + ciph, err := aes.NewCipher(okey) + if err != nil { + return nil, err + } + + // If using the AES algorithm, the Cipher Block Chaining (CBC) + // mode, which requires an initialization vector, is used. The + // block size parameter is set to 16 bytes, and the initialization + // vector is a 16-byte random number that is stored as the first + // 16 bytes of the encrypted stream or string. + if len(buf) < 16 { + common.Log.Debug("ERROR AES invalid buf %s", buf) + return buf, fmt.Errorf("AES: Buf len < 16 (%d)", len(buf)) + } + + iv := buf[:16] + buf = buf[16:] + + if len(buf)%16 != 0 { + common.Log.Debug(" iv (%d): % x", len(iv), iv) + common.Log.Debug("buf (%d): % x", len(buf), buf) + return buf, fmt.Errorf("AES buf length not multiple of 16 (%d)", len(buf)) + } + + mode := cipher.NewCBCDecrypter(ciph, iv) + + common.Log.Trace("AES Decrypt (%d): % x", len(buf), buf) + common.Log.Trace("chop AES Decrypt (%d): % x", len(buf), buf) + mode.CryptBlocks(buf, buf) + common.Log.Trace("to (%d): % x", len(buf), buf) + + if len(buf) == 0 { + common.Log.Trace("Empty buf, returning empty string") + return buf, nil + } + + // The padded length is indicated by the last values. Remove those. + + padLen := int(buf[len(buf)-1]) + if padLen >= len(buf) { + common.Log.Debug("Illegal pad length") + return buf, fmt.Errorf("Invalid pad length") + } + buf = buf[:len(buf)-padLen] + + return buf, nil +} + +// filterAESV3 is an AES-based filter (256 bit key, PDF 2.0) +type filterAESV3 struct { + filterAES +} + +func (filterAESV3) PDFVersion() [2]int { + return [2]int{2, 0} +} + +func (filterAESV3) HandlerVersion() (V, R int) { + V, R = 5, 6 + return +} + +func (filterAESV3) Name() string { + return "AESV3" +} + +func (filterAESV3) KeyLength() int { + return 256 / 8 +} + +func (filterAESV3) MakeKey(_, _ uint32, ekey []byte) ([]byte, error) { + return ekey, nil +} diff --git a/pdf/core/security/crypt/filter_v2.go b/pdf/core/security/crypt/filter_v2.go new file mode 100644 index 00000000..d1eb5794 --- /dev/null +++ b/pdf/core/security/crypt/filter_v2.go @@ -0,0 +1,125 @@ +package crypt + +import ( + "crypto/md5" + "crypto/rc4" + "fmt" + + "github.com/unidoc/unidoc/common" +) + +func init() { + registerFilter("V2", newFilterV2) +} + +// NewFilterV2 creates a RC4-based filter with a specified key length (in bytes). +func NewFilterV2(length int) Filter { + f, err := newFilterV2(FilterDict{Length: length}) + if err != nil { + panic(err) + } + return f +} + +func newFilterV2(d FilterDict) (Filter, error) { + if d.Length%8 != 0 { + return nil, fmt.Errorf("Crypt filter length not multiple of 8 (%d)", d.Length) + } + // Standard security handler expresses the length in multiples of 8 (16 means 128) + // We only deal with standard so far. (Public key not supported yet). + if d.Length < 5 || d.Length > 16 { + if d.Length == 64 || d.Length == 128 { + common.Log.Debug("STANDARD VIOLATION: Crypt Length appears to be in bits rather than bytes - assuming bits (%d)", d.Length) + d.Length /= 8 + } else { + return nil, fmt.Errorf("Crypt filter length not in range 40 - 128 bit (%d)", d.Length) + } + } + return filterV2{length: d.Length}, nil +} + +// makeKeyV2 is a common object key generation shared by V2 and AESV2 crypt filters. +func makeKeyV2(objNum, genNum uint32, ekey []byte, isAES bool) ([]byte, error) { + key := make([]byte, len(ekey)+5) + for i := 0; i < len(ekey); i++ { + key[i] = ekey[i] + } + for i := 0; i < 3; i++ { + b := byte((objNum >> uint32(8*i)) & 0xff) + key[i+len(ekey)] = b + } + for i := 0; i < 2; i++ { + b := byte((genNum >> uint32(8*i)) & 0xff) + key[i+len(ekey)+3] = b + } + if isAES { + // If using the AES algorithm, extend the encryption key an + // additional 4 bytes by adding the value “sAlT”, which + // corresponds to the hexadecimal values 0x73, 0x41, 0x6C, 0x54. + key = append(key, 0x73) + key = append(key, 0x41) + key = append(key, 0x6C) + key = append(key, 0x54) + } + + // Take the MD5. + h := md5.New() + h.Write(key) + hashb := h.Sum(nil) + + if len(ekey)+5 < 16 { + return hashb[0 : len(ekey)+5], nil + } + + return hashb, nil +} + +// filterV2 is a RC4-based filter +type filterV2 struct { + length int +} + +func (f filterV2) PDFVersion() [2]int { + return [2]int{} // TODO(dennwc): unspecified; check what it should be +} + +func (f filterV2) HandlerVersion() (V, R int) { + V, R = 2, 3 + return +} + +func (filterV2) Name() string { + return "V2" +} + +func (f filterV2) KeyLength() int { + return f.length +} + +func (f filterV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) { + return makeKeyV2(objNum, genNum, ekey, false) +} + +func (filterV2) EncryptBytes(buf []byte, okey []byte) ([]byte, error) { + // Standard RC4 algorithm. + ciph, err := rc4.NewCipher(okey) + if err != nil { + return nil, err + } + common.Log.Trace("RC4 Encrypt: % x", buf) + ciph.XORKeyStream(buf, buf) + common.Log.Trace("to: % x", buf) + return buf, nil +} + +func (filterV2) DecryptBytes(buf []byte, okey []byte) ([]byte, error) { + // Standard RC4 algorithm. + ciph, err := rc4.NewCipher(okey) + if err != nil { + return nil, err + } + common.Log.Trace("RC4 Decrypt: % x", buf) + ciph.XORKeyStream(buf, buf) + common.Log.Trace("to: % x", buf) + return buf, nil +} diff --git a/pdf/core/security/crypt/filters.go b/pdf/core/security/crypt/filters.go new file mode 100644 index 00000000..77ba0fd4 --- /dev/null +++ b/pdf/core/security/crypt/filters.go @@ -0,0 +1,108 @@ +package crypt + +import ( + "fmt" + + "github.com/unidoc/unidoc/pdf/core/security" +) + +var ( + filterMethods = make(map[string]filterFunc) +) + +// filterFunc is used to construct crypt filters from CryptFilter dictionary +type filterFunc func(d FilterDict) (Filter, error) + +// NewFilter creates CryptFilter from a corresponding dictionary. +func NewFilter(d FilterDict) (Filter, error) { + fnc, err := getFilter(d.CFM) + if err != nil { + return nil, err + } + cf, err := fnc(d) + if err != nil { + return nil, err + } + return cf, nil +} + +// NewIdentity creates an identity filter that bypasses all data without changes. +func NewIdentity() Filter { + return filterIdentity{} +} + +// FilterDict represents information from a CryptFilter dictionary. +type FilterDict struct { + CFM string // The method used, if any, by the PDF reader to decrypt data. + AuthEvent security.AuthEvent + Length int // in bytes +} + +// registerFilter register supported crypt filter methods. +// Table 25, CFM (page 92) +func registerFilter(name string, fnc filterFunc) { + if _, ok := filterMethods[name]; ok { + panic("already registered") + } + filterMethods[name] = fnc +} + +// getFilter check if a CFM with a specified name is supported an returns its implementation. +func getFilter(name string) (filterFunc, error) { + f := filterMethods[string(name)] + if f == nil { + return nil, fmt.Errorf("unsupported crypt filter: %q", name) + } + return f, nil +} + +// Filter is a common interface for crypt filter methods. +type Filter interface { + // Name returns a name of the filter that should be used in CFM field of Encrypt dictionary. + Name() string + // KeyLength returns a length of the encryption key in bytes. + KeyLength() int + // PDFVersion reports the minimal version of PDF document that introduced this filter. + PDFVersion() [2]int + // HandlerVersion reports V and R parameters that should be used for this filter. + HandlerVersion() (V, R int) + // MakeKey generates a object encryption key based on file encryption key and object numbers. + // Used only for legacy filters - AESV3 doesn't change the key for each object. + MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error) + // EncryptBytes encrypts a buffer using object encryption key, as returned by MakeKey. + // Implementation may reuse a buffer and encrypt data in-place. + EncryptBytes(p []byte, okey []byte) ([]byte, error) + // DecryptBytes decrypts a buffer using object encryption key, as returned by MakeKey. + // Implementation may reuse a buffer and decrypt data in-place. + DecryptBytes(p []byte, okey []byte) ([]byte, error) +} + +type filterIdentity struct{} + +func (filterIdentity) PDFVersion() [2]int { + return [2]int{} +} + +func (filterIdentity) HandlerVersion() (V, R int) { + return +} + +func (filterIdentity) Name() string { + return "Identity" +} + +func (filterIdentity) KeyLength() int { + return 0 +} + +func (filterIdentity) MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error) { + return fkey, nil +} + +func (filterIdentity) EncryptBytes(p []byte, okey []byte) ([]byte, error) { + return p, nil +} + +func (filterIdentity) DecryptBytes(p []byte, okey []byte) ([]byte, error) { + return p, nil +} diff --git a/pdf/model/writer.go b/pdf/model/writer.go index 47a02e9b..cf7624e8 100644 --- a/pdf/model/writer.go +++ b/pdf/model/writer.go @@ -17,6 +17,8 @@ import ( "io" "strings" + "github.com/unidoc/unidoc/pdf/core/security/crypt" + "github.com/unidoc/unidoc/common" "github.com/unidoc/unidoc/common/license" . "github.com/unidoc/unidoc/pdf/core" @@ -676,16 +678,14 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio perm = options.Permissions } - var cf CryptFilter + var cf crypt.Filter switch algo { case RC4_128bit: - cf = NewCryptFilterV2(16) + cf = crypt.NewFilterV2(16) case AES_128bit: - this.SetVersion(1, 5) - cf = NewCryptFilterAESV2() + cf = crypt.NewFilterAESV2() case AES_256bit: - this.SetVersion(2, 0) - cf = NewCryptFilterAESV3() + cf = crypt.NewFilterAESV3() default: return fmt.Errorf("unsupported algorithm: %v", options.Algorithm) }