unipdf/core/security/standard_r4.go
2019-05-16 20:44:51 +00:00

348 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package security
import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/rc4"
"encoding/binary"
"errors"
"github.com/unidoc/unipdf/v3/common"
)
var _ StdHandler = stdHandlerR4{}
const padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF" +
"\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C" +
"\xA9\xFE\x64\x53\x69\x7A"
// NewHandlerR4 creates a new standard security handler for R<=4.
func NewHandlerR4(id0 string, length int) StdHandler {
return stdHandlerR4{ID0: id0, Length: length}
}
// stdHandlerR4 is a standard security handler for R<=4.
// It uses RC4 and MD5 to generate encryption parameters.
// This legacy handler also requires Length parameter from
// Encrypt dictionary and ID0 from the trailer.
type stdHandlerR4 struct {
Length int
ID0 string
}
func (stdHandlerR4) paddedPass(pass []byte) []byte {
key := make([]byte, 32)
i := copy(key, pass)
for ; 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 pb [4]byte
binary.LittleEndian.PutUint32(pb[:], uint32(d.P))
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 {
h = md5.New()
for i := 0; i < 50; i++ {
h.Reset()
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 dictionarys 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 dictionarys 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 dictionarys 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
}
// GenerateParams generates and sets O and U parameters for the encryption dictionary.
// It expects R, P and EncryptMetadata fields to be set.
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
}
// Authenticate implements StdHandler interface.
func (sh stdHandlerR4) Authenticate(d *StdEncryptDict, pass []byte) ([]byte, Permissions, 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
}