Skip to content

Commit

Permalink
Cleanup encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
hhrutter committed Mar 3, 2019
1 parent 6e1af9e commit 067897d
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 85 deletions.
25 changes: 19 additions & 6 deletions cmd/pdfcpu/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,20 +389,33 @@ func prepareDecryptCommand(config *pdfcpu.Configuration) *api.Command {
return api.DecryptCommand(filenameIn, filenameOut, config)
}

func validEncryptOptions() bool {
return pageSelection == "" &&
(mode == "" || mode == "rc4" || mode == "aes") &&
(key == "" || key == "40" || key == "128") &&
(perm == "" || perm == "none" || perm == "all")
func validateEncryptFlags() {

if mode != "rc4" && mode != "aes" && mode != "" {
fmt.Fprintf(os.Stderr, "%s\n\n", "valid modes: rc4,aes default:aes")
os.Exit(1)
}

if key != "40" && key != "128" && key != "" {
fmt.Fprintf(os.Stderr, "%s\n\n", "supported key lengths: 40,128 default:128")
os.Exit(1)
}

if perm != "none" && perm != "all" && perm != "" {
fmt.Fprintf(os.Stderr, "%s\n\n", "supported permissions: none,all default:none (viewing is always allowed!)")
os.Exit(1)
}
}

func prepareEncryptCommand(config *pdfcpu.Configuration) *api.Command {

if len(flag.Args()) == 0 || len(flag.Args()) > 2 || !validEncryptOptions() {
if len(flag.Args()) == 0 || len(flag.Args()) > 2 {
fmt.Fprintf(os.Stderr, "%s\n\n", usageEncrypt)
os.Exit(1)
}

validateEncryptFlags()

if mode == "rc4" {
config.EncryptUsingAES = false
}
Expand Down
14 changes: 10 additions & 4 deletions cmd/pdfcpu/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ verbose, v ... turn on logging
vv ... verbose logging
upw ... user password, required unless = ""
inFile ... input pdf file
opwOld ... old owner password (supply user password on initial changeopw)
opwOld ... old owner password (provide user password on initial changeopw)
opwNew ... new owner password`

usageWMDescription = `<description> is a comma separated configuration string containing:
Expand Down Expand Up @@ -249,24 +249,28 @@ e.g. 'Draft' 'logo.png'
'Intentionally left blank, s:.75 abs, p:48' 'some.pdf, r:45'
'Confidental, f:Courier, s:0.75, c: 0.5 0.0 0.0, r:20' 'some.pdf:3, r:-90, s:0.75'`

usageStamp = "usage: pdfcpu stamp [-v(erbose)|vv] [-pages pageSelection] description inFile [outFile]"
usageStamp = "usage: pdfcpu stamp [-v(erbose)|vv] [-pages pageSelection] [-upw userpw] [-opw ownerpw] description inFile [outFile]"
usageLongStamp = `Stamp adds stamps for selected pages.
verbose, v ... turn on logging
vv ... verbose logging
pages ... page selection
upw ... user password
opw ... owner password
description ... font, font size, text, color, image/pdf file name, pdf page#, rotation, opacity, scale factor, render mode
inFile ... input pdf file
outFile ... output pdf file (default: inFile-new.pdf)
` + usageWMDescription

usageWatermark = "usage: pdfcpu watermark [-v(erbose)|vv] [-pages pageSelection] description inFile [outFile]"
usageWatermark = "usage: pdfcpu watermark [-v(erbose)|vv] [-pages pageSelection] [-upw userpw] [-opw ownerpw] description inFile [outFile]"
usageLongWatermark = `Watermark adds watermarks for selected pages.
verbose, v ... turn on logging
vv ... verbose logging
pages ... page selection
upw ... user password
opw ... owner password
description ... font, font size, text, color, image/pdf file name, pdf page#, rotation, opacity, scale factor, render mode
inFile ... input pdf file
outFile ... output pdf file (default: inFile-new.pdf)
Expand Down Expand Up @@ -309,12 +313,14 @@ description ... dimensions, format, position, offset, scale factor
'd:300 600, p:bl, o:20 20, s:1.0 abs' ... render the image anchored to bottom left corner with offset 20,20 and abs. scaling 1.0.
'p:full' ... render the image to a page with corresponding dimensions.`

usageRotate = "usage: pdfcpu rotate [-v(erbose)|vv] [-pages pageSelection] inFile rotation"
usageRotate = "usage: pdfcpu rotate [-v(erbose)|vv] [-pages pageSelection] [-upw userpw] [-opw ownerpw] inFile rotation"
usageLongRotate = `Rotate rotates selected pages.
verbose, v ... turn on logging
vv ... verbose logging
pages ... page selection
upw ... user password
opw ... owner password
inFile ... input pdf file
rotation ... a multiple of 90 degrees for clockwise rotation.`

Expand Down
4 changes: 2 additions & 2 deletions pkg/api/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func exampleProcessChangeUserPW() {

config := pdfcpu.NewDefaultConfiguration()

// supply existing owner pw like so
// Provide existing owner pw like so
config.OwnerPW = "opw"

pwOld := "pwOld"
Expand All @@ -283,7 +283,7 @@ func exampleProcessChangeOwnerPW() {

config := pdfcpu.NewDefaultConfiguration()

// supply existing user pw like so
// Provide existing user pw like so
config.UserPW = "upw"

// old and new owner pw
Expand Down
169 changes: 130 additions & 39 deletions pkg/pdfcpu/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var (
LISTPERMISSIONS: {0, 0},
ADDPERMISSIONS: {0, 0},
ADDWATERMARKS: {1, 0},
//DECRYPT: {1, 0},
}
)

Expand Down Expand Up @@ -185,7 +186,18 @@ func validateUserPassword(ctx *Context) (ok bool, key []byte, err error) {

//fmt.Printf("validateUserPassword: u =\n%v\n", u)

return bytes.HasPrefix(ctx.E.U, u), key, nil
match := false

switch ctx.E.R {

case 2:
match = bytes.Equal(ctx.E.U, u)

case 3, 4:
match = bytes.HasPrefix(ctx.E.U, u[:16])
}

return match, key, nil
}

func key(ownerpw, userpw string, r, l int) (key []byte) {
Expand Down Expand Up @@ -416,7 +428,7 @@ func supportedCFEntry(d Dict) (bool, error) {

func perms(p int) (list []string) {

list = append(list, fmt.Sprintf("%0b", uint32(p)&0x0F3C))
list = append(list, fmt.Sprintf("permission bits: %12b", uint32(p)&0x0F3C))
list = append(list, fmt.Sprintf("Bit 3: %t (print(rev2), print quality(rev>=3))", p&0x0004 > 0))
list = append(list, fmt.Sprintf("Bit 4: %t (modify other than controlled by bits 6,9,11)", p&0x0008 > 0))
list = append(list, fmt.Sprintf("Bit 5: %t (extract(rev2), extract other than controlled by bit 10(rev>=3))", p&0x0010 > 0))
Expand Down Expand Up @@ -717,32 +729,59 @@ func decryptKey(objNumber, generation int, key []byte, aes bool) []byte {
return dk
}

// EncryptString encrypts s using RC4 or AES.
func encryptString(needAES bool, s string, objNr, genNr int, key []byte) (*string, error) {
// EncryptBytes encrypts s using RC4 or AES.
func encryptBytes(needAES bool, b []byte, objNr, genNr int, key []byte) ([]byte, error) {

log.Debug.Printf("EncryptString begin obj:%d gen:%d key:%X aes:%t\n<%s>\n", objNr, genNr, key, needAES, s)
log.Debug.Printf("EncryptBytes begin obj:%d gen:%d key:%X aes:%t\n<%v>\n", objNr, genNr, key, needAES, b)

var s1 *string
var err error
k := decryptKey(objNr, genNr, key, needAES)
//logInfoCrypto.Printf("EncryptString k = %v\n", k)

if needAES {
b, err := encryptAESBytes([]byte(s), k)
bb, err := encryptAESBytes(b, k)
if err != nil {
return nil, err
}
sb := string(b)
s1 = &sb
return bb, nil
}

} else {
s1, err = applyRC4Cipher([]byte(s), objNr, genNr, key, needAES)
return applyRC4CipherBytes(b, objNr, genNr, key, needAES)
}

// EncryptString encrypts s using RC4 or AES.
func encryptString(needAES bool, s string, objNr, genNr int, key []byte) (*string, error) {

log.Debug.Printf("encryptString begin obj:%d gen:%d key:%X aes:%t\n<%s>\n", objNr, genNr, key, needAES, s)

b, err := encryptBytes(needAES, []byte(s), objNr, genNr, key)
if err != nil {
return nil, err
}

s1, err := Escape(string(b))
if err != nil {
return nil, err
}

return s1, err
}

// DecryptBytes decrypts bb using RC4 or AES.
func decryptBytes(needAES bool, b []byte, objNr, genNr int, key []byte) ([]byte, error) {

log.Debug.Printf("DecryptBytes begin obj:%d gen:%d key:%X aes:%t bb:%v\n", objNr, genNr, key, needAES, b)

k := decryptKey(objNr, genNr, key, needAES)

if needAES {
bb, err := decryptAESBytes(b, k)
if err != nil {
return nil, err
}
return bb, nil
}

return Escape(*s1)
return applyRC4CipherBytes(b, objNr, genNr, key, needAES)
}

// DecryptString decrypts s using RC4 or AES.
Expand All @@ -755,18 +794,29 @@ func decryptString(needAES bool, s string, objNr, genNr int, key []byte) (*strin
return nil, err
}

k := decryptKey(objNr, genNr, key, needAES)
b, err = decryptBytes(needAES, b, objNr, genNr, key)
if err != nil {
return nil, err
}

if needAES {
b, err = decryptAESBytes(b, k)
if err != nil {
return nil, err
}
s1 := string(b)
return &s1, nil
s1 := string(b)
return &s1, nil
}

func applyRC4CipherBytes(b []byte, objNr, genNr int, key []byte, needAES bool) ([]byte, error) {

log.Debug.Printf("applyRC4CipherBytes begin b:<%v> %d %d key:%X aes:%t\n", b, objNr, genNr, key, needAES)

c, err := rc4.NewCipher(decryptKey(objNr, genNr, key, needAES))
if err != nil {
return nil, err
}

return applyRC4Cipher(b, objNr, genNr, key, needAES)
c.XORKeyStream(b, b)

log.Debug.Printf("applyRC4Cipher end, rc4 returning: <%v>\n", b)

return b, nil
}

func applyRC4Cipher(b []byte, objNr, genNr int, key []byte, needAES bool) (*string, error) {
Expand Down Expand Up @@ -799,8 +849,20 @@ func encrypt(m map[string]Object, k string, v Object, objNr, genNr int, key []by
return nil
}

func encryptDict(d Dict, objNr, genNr int, key []byte, aes bool) error {

for k, v := range d {
err := encrypt(d, k, v, objNr, genNr, key, aes)
if err != nil {
return err
}
}

return nil
}

// EncryptDeepObject recurses over non trivial PDF objects and encrypts all strings encountered.
func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*StringLiteral, error) {
func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*HexLiteral, error) {

_, ok := objIn.(IndirectRef)
if ok {
Expand All @@ -810,19 +872,15 @@ func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*S
switch obj := objIn.(type) {

case StreamDict:
for k, v := range obj.Dict {
err := encrypt(obj.Dict, k, v, objNr, genNr, key, aes)
if err != nil {
return nil, err
}
err := encryptDict(obj.Dict, objNr, genNr, key, aes)
if err != nil {
return nil, err
}

case Dict:
for k, v := range obj {
err := encrypt(obj, k, v, objNr, genNr, key, aes)
if err != nil {
return nil, err
}
err := encryptDict(obj, objNr, genNr, key, aes)
if err != nil {
return nil, err
}

case Array:
Expand All @@ -837,14 +895,21 @@ func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*S
}

case StringLiteral:
s, err := encryptString(aes, obj.Value(), objNr, genNr, key)
s := obj.Value()
b, err := encryptBytes(aes, []byte(s), objNr, genNr, key)
if err != nil {
return nil, err
}
hl := NewHexLiteral(b)
return &hl, nil

sl := StringLiteral(*s)

return &sl, nil
case HexLiteral:
bb, err := encryptHexLiteral(obj, aes, objNr, genNr, key)
if err != nil {
return nil, err
}
hl := NewHexLiteral(bb)
return &hl, nil

default:

Expand Down Expand Up @@ -890,9 +955,15 @@ func decryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*S
if err != nil {
return nil, err
}

sl := StringLiteral(*s)
return &sl, nil

case HexLiteral:
bb, err := decryptHexLiteral(obj, aes, objNr, genNr, key)
if err != nil {
return nil, err
}
sl := StringLiteral(string(bb))
return &sl, nil

default:
Expand Down Expand Up @@ -985,7 +1056,7 @@ func encryptAESBytes(b, key []byte) ([]byte, error) {
mode := cipher.NewCBCEncrypter(cb, iv)
mode.CryptBlocks(data[aes.BlockSize:], b)

//fmt.Printf("encryptAESBytes after:\n%s\n", hex.Dump(data))
//fmt.Printf("encryptAESBytes after:\nlen=%d %s\n", len(data), hex.Dump(data))

return data, nil
}
Expand Down Expand Up @@ -1064,3 +1135,23 @@ func fileID(ctx *Context) (HexLiteral, error) {

return HexLiteral(hex.EncodeToString(m)), nil
}

func encryptHexLiteral(hl HexLiteral, needAES bool, objNr, genNr int, key []byte) ([]byte, error) {

bb, err := hl.Bytes()
if err != nil {
return nil, err
}

return encryptBytes(needAES, bb, objNr, genNr, key)
}

func decryptHexLiteral(hl HexLiteral, needAES bool, objNr, genNr int, key []byte) ([]byte, error) {

bb, err := hl.Bytes()
if err != nil {
return nil, err
}

return decryptBytes(needAES, bb, objNr, genNr, key)
}
Loading

0 comments on commit 067897d

Please sign in to comment.