// Copyright 2016 - 2022 The excelize Authors. All rights reserved. Use of // this source code is governed by a BSD-style license that can be found in // the LICENSE file. // // Package excelize providing a set of functions that allow you to write to and // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and // writing spreadsheet documents generated by Microsoft Excelâ„¢ 2007 and later. // Supports complex components by high compatibility, and provided streaming // API for generating or reading data from a worksheet with huge amounts of // data. This library needs Go version 1.15 or later. package excelize import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/rand" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/binary" "encoding/xml" "hash" "math" "reflect" "strings" "github.com/richardlehane/mscfb" "golang.org/x/crypto/md4" "golang.org/x/crypto/ripemd160" "golang.org/x/text/encoding/unicode" ) var ( blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption oleIdentifier = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1} iterCount = 50000 packageEncryptionChunkSize = 4096 packageOffset = 8 // First 8 bytes are the size of the stream sheetProtectionSpinCount = 1e5 ) // Encryption specifies the encryption structure, streams, and storages are // required when encrypting ECMA-376 documents. type Encryption struct { XMLName xml.Name `xml:"encryption"` KeyData KeyData `xml:"keyData"` DataIntegrity DataIntegrity `xml:"dataIntegrity"` KeyEncryptors KeyEncryptors `xml:"keyEncryptors"` } // KeyData specifies the cryptographic attributes used to encrypt the data. type KeyData struct { SaltSize int `xml:"saltSize,attr"` BlockSize int `xml:"blockSize,attr"` KeyBits int `xml:"keyBits,attr"` HashSize int `xml:"hashSize,attr"` CipherAlgorithm string `xml:"cipherAlgorithm,attr"` CipherChaining string `xml:"cipherChaining,attr"` HashAlgorithm string `xml:"hashAlgorithm,attr"` SaltValue string `xml:"saltValue,attr"` } // DataIntegrity specifies the encrypted copies of the salt and hash values // used to help ensure that the integrity of the encrypted data has not been // compromised. type DataIntegrity struct { EncryptedHmacKey string `xml:"encryptedHmacKey,attr"` EncryptedHmacValue string `xml:"encryptedHmacValue,attr"` } // KeyEncryptors specifies the key encryptors used to encrypt the data. type KeyEncryptors struct { KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"` } // KeyEncryptor specifies that the schema used by this encryptor is the schema // specified for password-based encryptors. type KeyEncryptor struct { XMLName xml.Name `xml:"keyEncryptor"` URI string `xml:"uri,attr"` EncryptedKey EncryptedKey `xml:"encryptedKey"` } // EncryptedKey used to generate the encrypting key. type EncryptedKey struct { XMLName xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"` SpinCount int `xml:"spinCount,attr"` EncryptedVerifierHashInput string `xml:"encryptedVerifierHashInput,attr"` EncryptedVerifierHashValue string `xml:"encryptedVerifierHashValue,attr"` EncryptedKeyValue string `xml:"encryptedKeyValue,attr"` KeyData } // StandardEncryptionHeader structure is used by ECMA-376 document encryption // [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify // encryption properties for an encrypted stream. type StandardEncryptionHeader struct { Flags uint32 SizeExtra uint32 AlgID uint32 AlgIDHash uint32 KeySize uint32 ProviderType uint32 Reserved1 uint32 Reserved2 uint32 CspName string } // StandardEncryptionVerifier structure is used by Office Binary Document RC4 // CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this // structure MUST specify the hashing algorithm and encryption algorithm used // in the EncryptionVerifier structure. type StandardEncryptionVerifier struct { SaltSize uint32 Salt []byte EncryptedVerifier []byte VerifierHashSize uint32 EncryptedVerifierHash []byte } // encryptionInfo structure is used for standard encryption with SHA1 // cryptographic algorithm. type encryption struct { BlockSize, SaltSize int EncryptedKeyValue, EncryptedVerifierHashInput, EncryptedVerifierHashValue, SaltValue []byte KeyBits uint32 } // Decrypt API decrypts the CFB file format with ECMA-376 agile encryption and // standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160, // SHA1, SHA256, SHA384 and SHA512 currently. func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { doc, err := mscfb.New(bytes.NewReader(raw)) if err != nil { return } encryptionInfoBuf, encryptedPackageBuf := extractPart(doc) mechanism, err := encryptionMechanism(encryptionInfoBuf) if err != nil || mechanism == "extensible" { return } if mechanism == "agile" { return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) } return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) } // Encrypt API encrypt data with the password. func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { encryptor := encryption{ EncryptedVerifierHashInput: make([]byte, 16), EncryptedVerifierHashValue: make([]byte, 32), SaltValue: make([]byte, 16), BlockSize: 16, KeyBits: 128, SaltSize: 16, } // Key Encryption encryptionInfoBuffer, err := encryptor.standardKeyEncryption(opt.Password) if err != nil { return nil, err } // Package Encryption encryptedPackage := make([]byte, 8) binary.LittleEndian.PutUint64(encryptedPackage, uint64(len(raw))) encryptedPackage = append(encryptedPackage, encryptor.encrypt(raw)...) // Create a new CFB compoundFile := cfb{} packageBuf = compoundFile.Writer(encryptionInfoBuffer, encryptedPackage) return packageBuf, nil } // extractPart extract data from storage by specified part name. func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []byte) { for entry, err := doc.Next(); err == nil; entry, err = doc.Next() { switch entry.Name { case "EncryptionInfo": buf := make([]byte, entry.Size) i, _ := doc.Read(buf) if i > 0 { encryptionInfoBuf = buf } case "EncryptedPackage": buf := make([]byte, entry.Size) i, _ := doc.Read(buf) if i > 0 { encryptedPackageBuf = buf } } } return } // encryptionMechanism parse password-protected documents created mechanism. func encryptionMechanism(buffer []byte) (mechanism string, err error) { if len(buffer) < 4 { err = ErrUnknownEncryptMechanism return } versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[:2]), binary.LittleEndian.Uint16(buffer[2:4]) if versionMajor == 4 && versionMinor == 4 { mechanism = "agile" return } else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 { mechanism = "standard" return } else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 { mechanism = "extensible" } err = ErrUnsupportedEncryptMechanism return } // ECMA-376 Standard Encryption // standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption. func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) ([]byte, error) { encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12]) block := encryptionInfoBuf[12 : 12+encryptionHeaderSize] header := StandardEncryptionHeader{ Flags: binary.LittleEndian.Uint32(block[:4]), SizeExtra: binary.LittleEndian.Uint32(block[4:8]), AlgID: binary.LittleEndian.Uint32(block[8:12]), AlgIDHash: binary.LittleEndian.Uint32(block[12:16]), KeySize: binary.LittleEndian.Uint32(block[16:20]), ProviderType: binary.LittleEndian.Uint32(block[20:24]), Reserved1: binary.LittleEndian.Uint32(block[24:28]), Reserved2: binary.LittleEndian.Uint32(block[28:32]), CspName: string(block[32:]), } block = encryptionInfoBuf[12+encryptionHeaderSize:] algIDMap := map[uint32]string{ 0x0000660E: "AES-128", 0x0000660F: "AES-192", 0x00006610: "AES-256", } algorithm := "AES" _, ok := algIDMap[header.AlgID] if !ok { algorithm = "RC4" } verifier := standardEncryptionVerifier(algorithm, block) secretKey, err := standardConvertPasswdToKey(header, verifier, opt) if err != nil { return nil, err } // decrypted data x := encryptedPackageBuf[8:] blob, err := aes.NewCipher(secretKey) if err != nil { return nil, err } decrypted := make([]byte, len(x)) size := 16 for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size { blob.Decrypt(decrypted[bs:be], x[bs:be]) } return decrypted, err } // standardEncryptionVerifier extract ECMA-376 standard encryption verifier. func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier { verifier := StandardEncryptionVerifier{ SaltSize: binary.LittleEndian.Uint32(blob[:4]), Salt: blob[4:20], EncryptedVerifier: blob[20:36], VerifierHashSize: binary.LittleEndian.Uint32(blob[36:40]), } if algorithm == "RC4" { verifier.EncryptedVerifierHash = blob[40:60] } else if algorithm == "AES" { verifier.EncryptedVerifierHash = blob[40:72] } return verifier } // standardConvertPasswdToKey generate intermediate key from given password. func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opt *Options) ([]byte, error) { encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() passwordBuffer, err := encoder.Bytes([]byte(opt.Password)) if err != nil { return nil, err } key := hashing("sha1", verifier.Salt, passwordBuffer) for i := 0; i < iterCount; i++ { iterator := createUInt32LEBuffer(i, 4) key = hashing("sha1", iterator, key) } var block int hFinal := hashing("sha1", key, createUInt32LEBuffer(block, 4)) cbRequiredKeyLength := int(header.KeySize) / 8 cbHash := sha1.Size buf1 := bytes.Repeat([]byte{0x36}, 64) buf1 = append(standardXORBytes(hFinal, buf1[:cbHash]), buf1[cbHash:]...) x1 := hashing("sha1", buf1) buf2 := bytes.Repeat([]byte{0x5c}, 64) buf2 = append(standardXORBytes(hFinal, buf2[:cbHash]), buf2[cbHash:]...) x2 := hashing("sha1", buf2) x3 := append(x1, x2...) keyDerived := x3[:cbRequiredKeyLength] return keyDerived, err } // standardXORBytes perform XOR operations for two bytes slice. func standardXORBytes(a, b []byte) []byte { r := make([][2]byte, len(a)) for i, e := range a { r[i] = [2]byte{e, b[i]} } buf := make([]byte, len(a)) for p, q := range r { buf[p] = q[0] ^ q[1] } return buf } // encrypt provides a function to encrypt given value with AES cryptographic // algorithm. func (e *encryption) encrypt(input []byte) []byte { inputBytes := len(input) if pad := inputBytes % e.BlockSize; pad != 0 { inputBytes += e.BlockSize - pad } var output, chunk []byte encryptedChunk := make([]byte, e.BlockSize) for i := 0; i < inputBytes; i += e.BlockSize { if i+e.BlockSize <= len(input) { chunk = input[i : i+e.BlockSize] } else { chunk = input[i:] } chunk = append(chunk, make([]byte, e.BlockSize-len(chunk))...) c, _ := aes.NewCipher(e.EncryptedKeyValue) c.Encrypt(encryptedChunk, chunk) output = append(output, encryptedChunk...) } return output } // standardKeyEncryption encrypt convert the password to an encryption key. func (e *encryption) standardKeyEncryption(password string) ([]byte, error) { if len(password) == 0 || len(password) > MaxFieldLength { return nil, ErrPasswordLengthInvalid } var storage cfb storage.writeUint16(0x0003) storage.writeUint16(0x0002) storage.writeUint32(0x24) storage.writeUint32(0xA4) storage.writeUint32(0x24) storage.writeUint32(0x00) storage.writeUint32(0x660E) storage.writeUint32(0x8004) storage.writeUint32(0x80) storage.writeUint32(0x18) storage.writeUint64(0x00) providerName := "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" storage.writeStrings(providerName) storage.writeUint16(0x00) storage.writeUint32(0x10) keyDataSaltValue, _ := randomBytes(16) verifierHashInput, _ := randomBytes(16) e.SaltValue = keyDataSaltValue e.EncryptedKeyValue, _ = standardConvertPasswdToKey( StandardEncryptionHeader{KeySize: e.KeyBits}, StandardEncryptionVerifier{Salt: e.SaltValue}, &Options{Password: password}) verifierHashInputKey := hashing("sha1", verifierHashInput) e.EncryptedVerifierHashInput = e.encrypt(verifierHashInput) e.EncryptedVerifierHashValue = e.encrypt(verifierHashInputKey) storage.writeBytes(e.SaltValue) storage.writeBytes(e.EncryptedVerifierHashInput) storage.writeUint32(0x14) storage.writeBytes(e.EncryptedVerifierHashValue) storage.position = 0 return storage.stream, nil } // ECMA-376 Agile Encryption // agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption. // Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, // SHA384 and SHA512. func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) { var encryptionInfo Encryption if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil { return } // Convert the password into an encryption key. key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo) if err != nil { return } // Use the key to decrypt the package key. encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) if err != nil { return } encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue) if err != nil { return } packageKey, _ := decrypt(key, saltValue, encryptedKeyValue) // Use the package key to decrypt the package. return decryptPackage(packageKey, encryptedPackageBuf, encryptionInfo) } // convertPasswdToKey convert the password into an encryption key. func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) { var b bytes.Buffer saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue) if err != nil { return } b.Write(saltValue) encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() passwordBuffer, err := encoder.Bytes([]byte(passwd)) if err != nil { return } b.Write(passwordBuffer) // Generate the initial hash. key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes()) // Now regenerate until spin count. for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ { iterator := createUInt32LEBuffer(i, 4) key = hashing(encryption.KeyData.HashAlgorithm, iterator, key) } // Now generate the final hash. key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey) // Truncate or pad as needed to get to length of keyBits. keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8 if len(key) < keyBytes { tmp := make([]byte, 0x36) key = append(key, tmp...) } else if len(key) > keyBytes { key = key[:keyBytes] } return } // hashing data by specified hash algorithm. func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) { hashMap := map[string]hash.Hash{ "md4": md4.New(), "md5": md5.New(), "ripemd-160": ripemd160.New(), "sha1": sha1.New(), "sha256": sha256.New(), "sha384": sha512.New384(), "sha512": sha512.New(), } handler, ok := hashMap[strings.ToLower(hashAlgorithm)] if !ok { return key } for _, buf := range buffer { _, _ = handler.Write(buf) } key = handler.Sum(nil) return key } // createUInt32LEBuffer create buffer with little endian 32-bit unsigned // integer. func createUInt32LEBuffer(value int, bufferSize int) []byte { buf := make([]byte, bufferSize) binary.LittleEndian.PutUint32(buf, uint32(value)) return buf } // parseEncryptionInfo parse the encryption info XML into an object. func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) { err = xml.Unmarshal(encryptionInfo, &encryption) return } // decrypt provides a function to decrypt input by given cipher algorithm, // cipher chaining, key and initialization vector. func decrypt(key, iv, input []byte) (packageKey []byte, err error) { block, err := aes.NewCipher(key) if err != nil { return input, err } cipher.NewCBCDecrypter(block, iv).CryptBlocks(input, input) return input, nil } // decryptPackage decrypt package by given packageKey and encryption // info. func decryptPackage(packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) { encryptedKey, offset := encryption.KeyData, packageOffset var i, start, end int var iv, outputChunk []byte for end < len(input) { start = end end = start + packageEncryptionChunkSize if end > len(input) { end = len(input) } // Grab the next chunk var inputChunk []byte if (end + offset) < len(input) { inputChunk = input[start+offset : end+offset] } else { inputChunk = input[start+offset : end] } // Pad the chunk if it is not an integer multiple of the block size remainder := len(inputChunk) % encryptedKey.BlockSize if remainder != 0 { inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...) } // Create the initialization vector iv, err = createIV(i, encryption) if err != nil { return } // Decrypt the chunk and add it to the array outputChunk, err = decrypt(packageKey, iv, inputChunk) if err != nil { return } outputChunks = append(outputChunks, outputChunk...) i++ } return } // createIV create an initialization vector (IV). func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) { encryptedKey := encryption.KeyData // Create the block key from the current index var blockKeyBuf []byte if reflect.TypeOf(blockKey).Kind() == reflect.Int { blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4) } else { blockKeyBuf = blockKey.([]byte) } saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) if err != nil { return nil, err } // Create the initialization vector by hashing the salt with the block key. // Truncate or pad as needed to meet the block size. iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...)) if len(iv) < encryptedKey.BlockSize { tmp := make([]byte, 0x36) iv = append(iv, tmp...) } else if len(iv) > encryptedKey.BlockSize { iv = iv[:encryptedKey.BlockSize] } return iv, nil } // randomBytes returns securely generated random bytes. It will return an // error if the system's secure random number generator fails to function // correctly, in which case the caller should not continue. func randomBytes(n int) ([]byte, error) { b := make([]byte, n) _, err := rand.Read(b) return b, err } // ISO Write Protection Method // genISOPasswdHash implements the ISO password hashing algorithm by given // plaintext password, name of the cryptographic hash algorithm, salt value // and spin count. func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashValue, saltValue string, err error) { if len(passwd) < 1 || len(passwd) > MaxFieldLength { err = ErrPasswordLengthInvalid return } algorithmName, ok := map[string]string{ "MD4": "md4", "MD5": "md5", "SHA-1": "sha1", "SHA-256": "sha256", "SHA-384": "sha384", "SHA-512": "sha512", }[hashAlgorithm] if !ok { err = ErrUnsupportedHashAlgorithm return } var b bytes.Buffer s, _ := randomBytes(16) if salt != "" { if s, err = base64.StdEncoding.DecodeString(salt); err != nil { return } } b.Write(s) encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() passwordBuffer, _ := encoder.Bytes([]byte(passwd)) b.Write(passwordBuffer) // Generate the initial hash. key := hashing(algorithmName, b.Bytes()) // Now regenerate until spin count. for i := 0; i < spinCount; i++ { iterator := createUInt32LEBuffer(i, 4) key = hashing(algorithmName, key, iterator) } hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s) return } // Compound File Binary Implements // cfb structure is used for the compound file binary (CFB) file format writer. type cfb struct { stream []byte position int } // writeBytes write bytes in the stream by a given value with an offset. func (c *cfb) writeBytes(value []byte) { pos := c.position for i := 0; i < len(value); i++ { for j := len(c.stream); j <= i+pos; j++ { c.stream = append(c.stream, 0) } c.stream[i+pos] = value[i] } c.position = pos + len(value) } // writeUint16 write an uint16 data type bytes in the stream by a given value // with an offset. func (c *cfb) writeUint16(value int) { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(value)) c.writeBytes(buf) } // writeUint32 write an uint32 data type bytes in the stream by a given value // with an offset. func (c *cfb) writeUint32(value int) { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(value)) c.writeBytes(buf) } // writeUint64 write an uint64 data type bytes in the stream by a given value // with an offset. func (c *cfb) writeUint64(value int) { buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, uint64(value)) c.writeBytes(buf) } // writeBytes write strings in the stream by a given value with an offset. func (c *cfb) writeStrings(value string) { encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() buffer, err := encoder.Bytes([]byte(value)) if err != nil { return } c.writeBytes(buffer) } // writeVersionStream provides a function to write compound file version // stream. func (c *cfb) writeVersionStream() []byte { var storage cfb storage.writeUint32(0x3c) storage.writeStrings("Microsoft.Container.DataSpaces") storage.writeUint32(0x01) storage.writeUint32(0x01) storage.writeUint32(0x01) return storage.stream } // writeDataSpaceMapStream provides a function to write compound file // DataSpaceMap stream. func (c *cfb) writeDataSpaceMapStream() []byte { var storage cfb storage.writeUint32(0x08) storage.writeUint32(0x01) storage.writeUint32(0x68) storage.writeUint32(0x01) storage.writeUint32(0x00) storage.writeUint32(0x20) storage.writeStrings("EncryptedPackage") storage.writeUint32(0x32) storage.writeStrings("StrongEncryptionDataSpace") storage.writeUint16(0x00) return storage.stream } // writeStrongEncryptionDataSpaceStream provides a function to write compound // file StrongEncryptionDataSpace stream. func (c *cfb) writeStrongEncryptionDataSpaceStream() []byte { var storage cfb storage.writeUint32(0x08) storage.writeUint32(0x01) storage.writeUint32(0x32) storage.writeStrings("StrongEncryptionTransform") storage.writeUint16(0x00) return storage.stream } // writePrimaryStream provides a function to write compound file Primary // stream. func (c *cfb) writePrimaryStream() []byte { var storage cfb storage.writeUint32(0x6C) storage.writeUint32(0x01) storage.writeUint32(0x4C) storage.writeStrings("{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}") storage.writeUint32(0x4E) storage.writeUint16(0x00) storage.writeUint32(0x01) storage.writeUint32(0x01) storage.writeUint32(0x01) storage.writeStrings("AES128") storage.writeUint32(0x00) storage.writeUint32(0x04) return storage.stream } // writeFileStream provides a function to write encrypted package in compound // file by a given buffer and the short sector allocation table. func (c *cfb) writeFileStream(encryptionInfoBuffer []byte, SSAT []int) ([]byte, []int) { var ( storage cfb miniProperties int stream = make([]byte, 0x100) ) if encryptionInfoBuffer != nil { copy(stream, encryptionInfoBuffer) } storage.writeBytes(stream) streamBlocks := len(stream) / 64 if len(stream)%64 > 0 { streamBlocks++ } for i := 1; i < streamBlocks; i++ { SSAT = append(SSAT, i) } SSAT = append(SSAT, -2) miniProperties += streamBlocks versionStream := make([]byte, 0x80) version := c.writeVersionStream() copy(versionStream, version) storage.writeBytes(versionStream) versionBlocks := len(versionStream) / 64 if len(versionStream)%64 > 0 { versionBlocks++ } for i := 1; i < versionBlocks; i++ { SSAT = append(SSAT, i+miniProperties) } SSAT = append(SSAT, -2) miniProperties += versionBlocks dataSpaceMap := make([]byte, 0x80) dataStream := c.writeDataSpaceMapStream() copy(dataSpaceMap, dataStream) storage.writeBytes(dataSpaceMap) dataSpaceMapBlocks := len(dataSpaceMap) / 64 if len(dataSpaceMap)%64 > 0 { dataSpaceMapBlocks++ } for i := 1; i < dataSpaceMapBlocks; i++ { SSAT = append(SSAT, i+miniProperties) } SSAT = append(SSAT, -2) miniProperties += dataSpaceMapBlocks dataSpaceStream := c.writeStrongEncryptionDataSpaceStream() storage.writeBytes(dataSpaceStream) dataSpaceStreamBlocks := len(dataSpaceStream) / 64 if len(dataSpaceStream)%64 > 0 { dataSpaceStreamBlocks++ } for i := 1; i < dataSpaceStreamBlocks; i++ { SSAT = append(SSAT, i+miniProperties) } SSAT = append(SSAT, -2) miniProperties += dataSpaceStreamBlocks primaryStream := make([]byte, 0x1C0) primary := c.writePrimaryStream() copy(primaryStream, primary) storage.writeBytes(primaryStream) primaryBlocks := len(primary) / 64 if len(primary)%64 > 0 { primaryBlocks++ } for i := 1; i < primaryBlocks; i++ { SSAT = append(SSAT, i+miniProperties) } SSAT = append(SSAT, -2) if len(SSAT) < 128 { for i := len(SSAT); i < 128; i++ { SSAT = append(SSAT, -1) } } storage.position = 0 return storage.stream, SSAT } // writeRootEntry provides a function to write compound file root directory // entry. The first entry in the first sector of the directory chain // (also referred to as the first element of the directory array, or stream // ID #0) is known as the root directory entry, and it is reserved for two // purposes. First, it provides a root parent for all objects that are // stationed at the root of the compound file. Second, its function is // overloaded to store the size and starting sector for the mini stream. func (c *cfb) writeRootEntry(customSectID int) []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("Root Entry") storage.position = 0x40 storage.writeUint16(0x16) storage.writeBytes([]byte{5, 0}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(customSectID) storage.writeUint32(0x340) return storage.stream } // writeEncryptionInfo provides a function to write compound file // writeEncryptionInfo stream. The writeEncryptionInfo stream contains // detailed information that is used to initialize the cryptography used to // encrypt the EncryptedPackage stream. func (c *cfb) writeEncryptionInfo() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("EncryptionInfo") storage.position = 0x40 storage.writeUint16(0x1E) storage.writeBytes([]byte{2, 1}) storage.writeUint32(0x03) storage.writeUint32(0x02) storage.writeUint32(-1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0xF8) return storage.stream } // writeEncryptedPackage provides a function to write compound file // writeEncryptedPackage stream. The writeEncryptedPackage stream is an // encrypted stream of bytes containing the entire ECMA-376 source file in // compressed form. func (c *cfb) writeEncryptedPackage(propertyCount, size int) []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("EncryptedPackage") storage.position = 0x40 storage.writeUint16(0x22) storage.writeBytes([]byte{2, 0}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(-1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(propertyCount) storage.writeUint32(size) return storage.stream } // writeDataSpaces provides a function to write compound file writeDataSpaces // stream. The data spaces structure consists of a set of interrelated // storages and streams in an OLE compound file. func (c *cfb) writeDataSpaces() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeUint16(0x06) storage.position = 0x40 storage.writeUint16(0x18) storage.writeBytes([]byte{1, 0}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(5) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) return storage.stream } // writeVersion provides a function to write compound file version. The // writeVersion structure specifies the version of a product or feature. It // contains a major and a minor version number. func (c *cfb) writeVersion() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("Version") storage.position = 0x40 storage.writeUint16(0x10) storage.writeBytes([]byte{2, 1}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(-1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(4) storage.writeUint32(76) return storage.stream } // writeDataSpaceMap provides a function to write compound file // writeDataSpaceMap stream. The writeDataSpaceMap structure associates // protected content with data space definitions. The data space definition, // in turn, describes the series of transforms that MUST be applied to that // protected content to restore it to its original form. By using a map to // associate data space definitions with content, a single data space // definition can be used to define the transforms applied to more than one // piece of protected content. However, a given piece of protected content can // be referenced only by a single data space definition. func (c *cfb) writeDataSpaceMap() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("DataSpaceMap") storage.position = 0x40 storage.writeUint16(0x1A) storage.writeBytes([]byte{2, 1}) storage.writeUint32(0x04) storage.writeUint32(0x06) storage.writeUint32(-1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(6) storage.writeUint32(112) return storage.stream } // writeDataSpaceInfo provides a function to write compound file // writeDataSpaceInfo storage. The writeDataSpaceInfo is a storage containing // the data space definitions used in the file. This storage must contain one // or more streams, each of which contains a DataSpaceDefinition structure. // The storage must contain exactly one stream for each DataSpaceMapEntry // structure in the DataSpaceMap stream. The name of each stream must be equal // to the DataSpaceName field of exactly one DataSpaceMapEntry structure // contained in the DataSpaceMap stream. func (c *cfb) writeDataSpaceInfo() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("DataSpaceInfo") storage.position = 0x40 storage.writeUint16(0x1C) storage.writeBytes([]byte{1, 1}) storage.writeUint32(-1) storage.writeUint32(8) storage.writeUint32(7) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) return storage.stream } // writeStrongEncryptionDataSpace provides a function to write compound file // writeStrongEncryptionDataSpace stream. func (c *cfb) writeStrongEncryptionDataSpace() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("StrongEncryptionDataSpace") storage.position = 0x40 storage.writeUint16(0x34) storage.writeBytes([]byte{2, 1}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(-1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(8) storage.writeUint32(64) return storage.stream } // writeTransformInfo provides a function to write compound file // writeTransformInfo storage. writeTransformInfo is a storage containing // definitions for the transforms used in the data space definitions stored in // the DataSpaceInfo storage. The stream contains zero or more definitions for // the possible transforms that can be applied to the data in content // streams. func (c *cfb) writeTransformInfo() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("TransformInfo") storage.position = 0x40 storage.writeUint16(0x1C) storage.writeBytes([]byte{1, 0}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(9) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) return storage.stream } // writeStrongEncryptionTransform provides a function to write compound file // writeStrongEncryptionTransform storage. func (c *cfb) writeStrongEncryptionTransform() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeStrings("StrongEncryptionTransform") storage.position = 0x40 storage.writeUint16(0x34) storage.writeBytes([]byte{1}) storage.writeBytes([]byte{1}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(0x0A) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) return storage.stream } // writePrimary provides a function to write compound file writePrimary stream. func (c *cfb) writePrimary() []byte { storage := cfb{stream: make([]byte, 128)} storage.writeUint16(0x06) storage.writeStrings("Primary") storage.position = 0x40 storage.writeUint16(0x12) storage.writeBytes([]byte{2, 1}) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(-1) storage.position = 0x64 storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(9) storage.writeUint32(208) return storage.stream } // writeNoneDir provides a function to write compound file writeNoneDir stream. func (c *cfb) writeNoneDir() []byte { storage := cfb{stream: make([]byte, 128)} storage.position = 0x40 storage.writeUint16(0x00) storage.writeUint16(0x00) storage.writeUint32(-1) storage.writeUint32(-1) storage.writeUint32(-1) return storage.stream } // writeDirectoryEntry provides a function to write compound file directory // entries. The directory entry array is an array of directory entries that // are grouped into a directory sector. Each storage object or stream object // within a compound file is represented by a single directory entry. The // space for the directory sectors that are holding the array is allocated // from the FAT. func (c *cfb) writeDirectoryEntry(propertyCount, customSectID, size int) []byte { var storage cfb if size < 0 { size = 0 } for _, entry := range [][]byte{ c.writeRootEntry(customSectID), c.writeEncryptionInfo(), c.writeEncryptedPackage(propertyCount, size), c.writeDataSpaces(), c.writeVersion(), c.writeDataSpaceMap(), c.writeDataSpaceInfo(), c.writeStrongEncryptionDataSpace(), c.writeTransformInfo(), c.writeStrongEncryptionTransform(), c.writePrimary(), c.writeNoneDir(), } { storage.writeBytes(entry) } return storage.stream } // writeMSAT provides a function to write compound file master sector allocation // table. func (c *cfb) writeMSAT(MSATBlocks, SATBlocks int, MSAT []int) []int { if MSATBlocks > 0 { cnt, MSATIdx := MSATBlocks*128+109, 0 for i := 0; i < cnt; i++ { if i < SATBlocks { bufferSize := i - 109 if bufferSize > 0 && bufferSize%0x80 == 0 { MSATIdx++ MSAT = append(MSAT, MSATIdx) } MSAT = append(MSAT, i+MSATBlocks) continue } MSAT = append(MSAT, -1) } return MSAT } for i := 0; i < 109; i++ { if i < SATBlocks { MSAT = append(MSAT, i) continue } MSAT = append(MSAT, -1) } return MSAT } // writeSAT provides a function to write compound file sector allocation // table. func (c *cfb) writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks int, SAT []int) (int, []int) { var blocks int if SATBlocks > 0 { for i := 1; i <= MSATBlocks; i++ { SAT = append(SAT, -4) } blocks = MSATBlocks for i := 1; i <= SATBlocks; i++ { SAT = append(SAT, -3) } blocks += SATBlocks for i := 1; i < SSATBlocks; i++ { SAT = append(SAT, i) } SAT = append(SAT, -2) blocks += SSATBlocks for i := 1; i < directoryBlocks; i++ { SAT = append(SAT, i+blocks) } SAT = append(SAT, -2) blocks += directoryBlocks for i := 1; i < fileBlocks; i++ { SAT = append(SAT, i+blocks) } SAT = append(SAT, -2) blocks += fileBlocks for i := 1; i < streamBlocks; i++ { SAT = append(SAT, i+blocks) } SAT = append(SAT, -2) } return blocks, SAT } // Writer provides a function to create compound file with given info stream // and package stream. // // MSAT - The master sector allocation table // SSAT - The short sector allocation table // SAT - The sector allocation table func (c *cfb) Writer(encryptionInfoBuffer, encryptedPackage []byte) []byte { var ( storage cfb MSAT, SAT, SSAT []int directoryBlocks, fileBlocks, SSATBlocks = 3, 2, 1 size = int(math.Max(float64(len(encryptedPackage)), float64(packageEncryptionChunkSize))) streamBlocks = len(encryptedPackage) / 0x200 ) if len(encryptedPackage)%0x200 > 0 { streamBlocks++ } propertyBlocks := directoryBlocks + fileBlocks + SSATBlocks blockSize := (streamBlocks + propertyBlocks) * 4 SATBlocks := blockSize / 0x200 if blockSize%0x200 > 0 { SATBlocks++ } MSATBlocks, blocksChanged := 0, true for blocksChanged { var SATCap, MSATCap int blocksChanged = false blockSize = (streamBlocks + propertyBlocks + SATBlocks + MSATBlocks) * 4 SATCap = blockSize / 0x200 if blockSize%0x200 > 0 { SATCap++ } if SATCap > SATBlocks { SATBlocks, blocksChanged = SATCap, true continue } if SATBlocks > 109 { blockRemains := (SATBlocks - 109) * 4 blockBuffer := blockRemains % 0x200 MSATCap = blockRemains / 0x200 if blockBuffer > 0 { MSATCap++ } if blockBuffer+(4*MSATCap) > 0x200 { MSATCap++ } if MSATCap > MSATBlocks { MSATBlocks, blocksChanged = MSATCap, true } } } MSAT = c.writeMSAT(MSATBlocks, SATBlocks, MSAT) blocks, SAT := c.writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks, SAT) for i := 0; i < 8; i++ { storage.writeBytes([]byte{oleIdentifier[i]}) } storage.writeBytes(make([]byte, 16)) storage.writeUint16(0x003E) storage.writeUint16(0x0003) storage.writeUint16(-2) storage.writeUint16(9) storage.writeUint32(6) storage.writeUint32(0) storage.writeUint32(0) storage.writeUint32(SATBlocks) storage.writeUint32(MSATBlocks + SATBlocks + SSATBlocks) storage.writeUint32(0) storage.writeUint32(0x00001000) storage.writeUint32(SATBlocks + MSATBlocks) storage.writeUint32(SSATBlocks) if MSATBlocks > 0 { storage.writeUint32(0) storage.writeUint32(MSATBlocks) } else { storage.writeUint32(-2) storage.writeUint32(0) } for _, block := range MSAT { storage.writeUint32(block) } for i := 0; i < SATBlocks*128; i++ { if i < len(SAT) { storage.writeUint32(SAT[i]) continue } storage.writeUint32(-1) } fileStream, SSATStream := c.writeFileStream(encryptionInfoBuffer, SSAT) for _, block := range SSATStream { storage.writeUint32(block) } directoryEntry := c.writeDirectoryEntry(blocks, blocks-fileBlocks, size) storage.writeBytes(directoryEntry) storage.writeBytes(fileStream) storage.writeBytes(encryptedPackage) return storage.stream }