diff --git a/pdf/core/crypt.go b/pdf/core/crypt.go index d8b7b143..68466392 100644 --- a/pdf/core/crypt.go +++ b/pdf/core/crypt.go @@ -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 -} diff --git a/pdf/core/crypt_handlers.go b/pdf/core/crypt_handlers.go new file mode 100644 index 00000000..ca587b7c --- /dev/null +++ b/pdf/core/crypt_handlers.go @@ -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) +} diff --git a/pdf/core/crypt_handlers_r4.go b/pdf/core/crypt_handlers_r4.go new file mode 100644 index 00000000..4445d385 --- /dev/null +++ b/pdf/core/crypt_handlers_r4.go @@ -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 +} diff --git a/pdf/core/crypt_handlers_r4_test.go b/pdf/core/crypt_handlers_r4_test.go new file mode 100644 index 00000000..53a10806 --- /dev/null +++ b/pdf/core/crypt_handlers_r4_test.go @@ -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") + } +} diff --git a/pdf/core/crypt_handlers_r6.go b/pdf/core/crypt_handlers_r6.go new file mode 100644 index 00000000..915c3e26 --- /dev/null +++ b/pdf/core/crypt_handlers_r6.go @@ -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) +} diff --git a/pdf/core/crypt_handlers_r6_test.go b/pdf/core/crypt_handlers_r6_test.go new file mode 100644 index 00000000..16e46638 --- /dev/null +++ b/pdf/core/crypt_handlers_r6_test.go @@ -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") + } + } + }) + } + }) + } +} diff --git a/pdf/core/crypt_test.go b/pdf/core/crypt_test.go index f3ff8277..8c81c0bd 100644 --- a/pdf/core/crypt_test.go +++ b/pdf/core/crypt_test.go @@ -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") - } - } - }) - } - }) - } -} diff --git a/pdf/core/parser.go b/pdf/core/parser.go index c46743ab..359139d2 100644 --- a/pdf/core/parser.go +++ b/pdf/core/parser.go @@ -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("")) }