1/*
2 * Copyright (c) 2024-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <algorithm>
17#include <filesystem>
18
19#include "file_utils.h"
20#include "zip_entry.h"
21#include "zip_signer.h"
22
23namespace OHOS {
24namespace SignatureTools {
25bool ZipSigner::Init(std::ifstream& inputFile)
26{
27    if (!inputFile.good()) {
28        return false;
29    }
30
31    /* 1. get eocd data */
32    m_endOfCentralDirectory = GetZipEndOfCentralDirectory(inputFile);
33    if (!m_endOfCentralDirectory) {
34        SIGNATURE_TOOLS_LOGE("get eocd data failed.");
35        return false;
36    }
37
38    m_cDOffset = m_endOfCentralDirectory->GetOffset();
39
40    /* 2. use eocd's cd offset, get cd data */
41    if (!GetZipCentralDirectory(inputFile)) {
42        SIGNATURE_TOOLS_LOGE("get zip central directory failed.");
43        return false;
44    }
45
46    /* 3. use cd's entry offset and file size, get entry data */
47    if (!GetZipEntries(inputFile)) {
48        SIGNATURE_TOOLS_LOGE("get zip entries failed.");
49        return false;
50    }
51
52    ZipEntry* endEntry = m_zipEntries[m_zipEntries.size() - 1];
53    CentralDirectory* endCD = endEntry->GetCentralDirectory();
54    ZipEntryData* endEntryData = endEntry->GetZipEntryData();
55    m_signingOffset = endCD->GetOffset() + endEntryData->GetLength();
56
57    /* 4. file all data - eocd - cd - entry = sign block */
58    m_signingBlock = GetSigningBlock(inputFile);
59
60    return true;
61}
62
63EndOfCentralDirectory* ZipSigner::GetZipEndOfCentralDirectory(std::ifstream& input)
64{
65    /* move file pointer to the end */
66    input.seekg(0, std::ios::end);
67    uint64_t fileSize = static_cast<uint64_t>(input.tellg());
68    /* move file pointer to the begin */
69    input.seekg(0, std::ios::beg);
70
71    if (fileSize < EndOfCentralDirectory::EOCD_LENGTH) {
72        SIGNATURE_TOOLS_LOGE("find zip eocd failed");
73        return nullptr;
74    }
75
76    /* try to read EOCD without comment */
77    int eocdLength = EndOfCentralDirectory::EOCD_LENGTH;
78    m_eOCDOffset = fileSize - eocdLength;
79
80    std::string retStr;
81    int ret = FileUtils::ReadFileByOffsetAndLength(input, m_eOCDOffset, eocdLength, retStr);
82    if (0 != ret) {
83        SIGNATURE_TOOLS_LOGE("read eocd without comment failed in file");
84        return nullptr;
85    }
86
87    std::optional<EndOfCentralDirectory*> eocdByBytes = EndOfCentralDirectory::GetEOCDByBytes(retStr);
88    if (eocdByBytes) {
89        return eocdByBytes.value();
90    }
91
92    /* try to search EOCD with comment */
93    uint64_t eocdMaxLength = std::min(static_cast<uint64_t>(EndOfCentralDirectory::EOCD_LENGTH + MAX_COMMENT_LENGTH),
94        fileSize);
95    m_eOCDOffset = static_cast<uint64_t>(input.tellg()) - eocdMaxLength;
96
97    retStr.clear();
98    ret = FileUtils::ReadFileByOffsetAndLength(input, m_eOCDOffset, eocdMaxLength, retStr);
99    if (0 != ret) {
100        SIGNATURE_TOOLS_LOGE("read eocd with comment failed in file");
101        return nullptr;
102    }
103
104    for (uint64_t start = 0; start < eocdMaxLength; start++) {
105        eocdByBytes = EndOfCentralDirectory::GetEOCDByBytes(retStr, start);
106        if (eocdByBytes) {
107            m_eOCDOffset += start;
108            return eocdByBytes.value();
109        }
110    }
111    SIGNATURE_TOOLS_LOGE("read zip failed: can not find eocd in file");
112    PrintErrorNumberMsg("ZIP_ERROR", ZIP_ERROR, "can not find eocd in file");
113    return nullptr;
114}
115
116bool ZipSigner::GetZipCentralDirectory(std::ifstream& input)
117{
118    input.seekg(0, std::ios::beg);
119
120    uint16_t cDtotal = m_endOfCentralDirectory->GetcDTotal();
121    m_zipEntries.reserve(cDtotal);
122    /* read full central directory bytes */
123    std::string retStr;
124
125    int ret = FileUtils::ReadFileByOffsetAndLength(input, m_cDOffset, m_endOfCentralDirectory->GetcDSize(), retStr);
126    if (0 != ret) {
127        SIGNATURE_TOOLS_LOGE("read full central directory failed in file");
128        return false;
129    }
130
131    if (retStr.size() < CentralDirectory::CD_LENGTH) {
132        SIGNATURE_TOOLS_LOGE("find zip cd failed");
133        return false;
134    }
135
136    ByteBuffer bf(retStr.c_str(), retStr.size());
137
138    std::string::size_type offset = 0;
139    /* one by one format central directory */
140    while (offset < retStr.size()) {
141        CentralDirectory* cd = new CentralDirectory();
142        if (!CentralDirectory::GetCentralDirectory(bf, cd)) {
143            return false;
144        }
145        ZipEntry* entry = new ZipEntry();
146        entry->SetCentralDirectory(cd);
147        m_zipEntries.emplace_back(entry);
148        offset += cd->GetLength();
149    }
150
151    if (offset + m_cDOffset != m_eOCDOffset) {
152        SIGNATURE_TOOLS_LOGE("cd end offset not equals to eocd offset, maybe this is a zip64 file");
153        return false;
154    }
155    return true;
156}
157
158std::string ZipSigner::GetSigningBlock(std::ifstream& file)
159{
160    uint64_t size = m_cDOffset - m_signingOffset;
161    if (size < 0) {
162        SIGNATURE_TOOLS_LOGE("signing offset in front of entry end");
163        return "";
164    }
165    if (size == 0) {
166        return "";
167    }
168
169    std::string retStr;
170    int ret = FileUtils::ReadFileByOffsetAndLength(file, m_signingOffset, size, retStr);
171    if (0 != ret) {
172        SIGNATURE_TOOLS_LOGE("read signing block failed in file");
173        return "";
174    }
175    return retStr;
176}
177
178bool ZipSigner::GetZipEntries(std::ifstream& input)
179{
180    /* use central directory data, find entry data */
181    for (auto& entry : m_zipEntries) {
182        CentralDirectory* cd = entry->GetCentralDirectory();
183        uint32_t offset = cd->GetOffset();
184        uint32_t unCompressedSize = cd->GetUnCompressedSize();
185        uint32_t compressedSize = cd->GetCompressedSize();
186        uint32_t fileSize = cd->GetMethod() == FILE_UNCOMPRESS_METHOD_FLAG ? unCompressedSize : compressedSize;
187
188        ZipEntryData* zipEntryData = ZipEntryData::GetZipEntry(input, offset, fileSize);
189        if (!zipEntryData) {
190            return false;
191        }
192        if (m_cDOffset - offset < zipEntryData->GetLength()) {
193            SIGNATURE_TOOLS_LOGE("cd offset in front of entry end");
194            return false;
195        }
196        entry->SetZipEntryData(zipEntryData);
197    }
198    return true;
199}
200
201bool ZipSigner::ToFile(std::ifstream& input, std::ofstream& output)
202{
203    SIGNATURE_TOOLS_LOGI("Zip To File begin");
204    if (!input.good()) {
205        SIGNATURE_TOOLS_LOGE("read zip input file failed");
206        return false;
207    }
208    if (!output.good()) {
209        SIGNATURE_TOOLS_LOGE("read zip output file failed");
210        return false;
211    }
212
213    for (const auto& entry : m_zipEntries) {
214        ZipEntryData* zipEntryData = entry->GetZipEntryData();
215        std::string zipEntryHeaderStr = zipEntryData->GetZipEntryHeader()->ToBytes();
216        if (!FileUtils::WriteByteToOutFile(zipEntryHeaderStr, output)) {
217            return false;
218        }
219
220        uint32_t fileOffset = zipEntryData->GetFileOffset();
221        uint32_t fileSize = zipEntryData->GetFileSize();
222        bool isSuccess = FileUtils::AppendWriteFileByOffsetToFile(input, output, fileOffset, fileSize);
223        if (!isSuccess) {
224            SIGNATURE_TOOLS_LOGE("write zip data failed");
225            return false;
226        }
227        DataDescriptor* dataDescriptor = zipEntryData->GetDataDescriptor();
228        if (dataDescriptor) {
229            std::string dataDescriptorStr = dataDescriptor->ToBytes();
230            if (!FileUtils::WriteByteToOutFile(dataDescriptorStr, output)) {
231                return false;
232            }
233        }
234    }
235
236    if (!m_signingBlock.empty()) {
237        if (!FileUtils::WriteByteToOutFile(m_signingBlock, output)) {
238            return false;
239        }
240    }
241
242    for (const auto& entry : m_zipEntries) {
243        CentralDirectory* cd = entry->GetCentralDirectory();
244        if (!FileUtils::WriteByteToOutFile(cd->ToBytes(), output)) {
245            return false;
246        }
247    }
248
249    if (!FileUtils::WriteByteToOutFile(m_endOfCentralDirectory->ToBytes(), output)) {
250        return false;
251    }
252
253    SIGNATURE_TOOLS_LOGI("Zip To File end");
254    return true;
255}
256
257void ZipSigner::Alignment(int alignment)
258{
259    Sort();
260    bool isFirstUnRunnableFile = true;
261    for (const auto& entry : m_zipEntries) {
262        ZipEntryData* zipEntryData = entry->GetZipEntryData();
263        short method = zipEntryData->GetZipEntryHeader()->GetMethod();
264        if (method != FILE_UNCOMPRESS_METHOD_FLAG && !isFirstUnRunnableFile) {
265            /* only align uncompressed entry and the first compress entry. */
266            break;
267        }
268        int alignBytes;
269        if (method == FILE_UNCOMPRESS_METHOD_FLAG &&
270            FileUtils::IsRunnableFile(zipEntryData->GetZipEntryHeader()->GetFileName())) {
271            /* .abc and .so file align 4096 byte. */
272            alignBytes = 4096;
273        } else if (isFirstUnRunnableFile) {
274            /* the first file after runnable file, align 4096 byte. */
275            alignBytes = 4096;
276            isFirstUnRunnableFile = false;
277        } else {
278            /* normal file align 4 byte. */
279            alignBytes = alignment;
280        }
281        int add = entry->Alignment(alignBytes);
282        if (add > 0) {
283            ResetOffset();
284        }
285    }
286}
287
288void ZipSigner::RemoveSignBlock()
289{
290    m_signingBlock = std::string();
291    ResetOffset();
292}
293
294void ZipSigner::Sort()
295{
296    /* sort uncompress file (so, abc, an) - other uncompress file - compress file */
297    std::sort(m_zipEntries.begin(), m_zipEntries.end(), [&](ZipEntry* entry1, ZipEntry* entry2) {
298        short entry1Method = entry1->GetZipEntryData()->GetZipEntryHeader()->GetMethod();
299        short entry2Method = entry2->GetZipEntryData()->GetZipEntryHeader()->GetMethod();
300        std::string entry1FileName = entry1->GetZipEntryData()->GetZipEntryHeader()->GetFileName();
301        std::string entry2FileName = entry2->GetZipEntryData()->GetZipEntryHeader()->GetFileName();
302        if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG && entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) {
303            bool isRunnableFile1 = FileUtils::IsRunnableFile(entry1FileName);
304            bool isRunnableFile2 = FileUtils::IsRunnableFile(entry2FileName);
305            if (isRunnableFile1 && isRunnableFile2) {
306                return entry1FileName < entry2FileName;
307            } else if (isRunnableFile1) {
308                return true;
309            } else if (isRunnableFile2) {
310                return false;
311            }
312        } else if (entry1Method == FILE_UNCOMPRESS_METHOD_FLAG) {
313            return true;
314        } else if (entry2Method == FILE_UNCOMPRESS_METHOD_FLAG) {
315            return false;
316        }
317        return entry1FileName < entry2FileName;
318    });
319    ResetOffset();
320}
321
322void ZipSigner::ResetOffset()
323{
324    uint32_t offset = 0U;
325    uint32_t cdLength = 0U;
326    for (const auto& entry : m_zipEntries) {
327        entry->GetCentralDirectory()->SetOffset(offset);
328        offset += entry->GetZipEntryData()->GetLength();
329        cdLength += entry->GetCentralDirectory()->GetLength();
330    }
331    if (!m_signingBlock.empty()) {
332        offset += m_signingBlock.size();
333    }
334    m_cDOffset = offset;
335    m_endOfCentralDirectory->SetOffset(offset);
336    m_endOfCentralDirectory->SetcDSize(cdLength);
337    offset += cdLength;
338    m_eOCDOffset = offset;
339}
340
341std::vector<ZipEntry*>& ZipSigner::GetZipEntries()
342{
343    return m_zipEntries;
344}
345
346void ZipSigner::SetZipEntries(const std::vector<ZipEntry*>& zipEntries)
347{
348    m_zipEntries = zipEntries;
349}
350
351uint32_t ZipSigner::GetSigningOffset()
352{
353    return m_signingOffset;
354}
355
356void ZipSigner::SetSigningOffset(uint32_t signingOffset)
357{
358    m_signingOffset = signingOffset;
359}
360
361std::string ZipSigner::GetSigningBlock()
362{
363    return m_signingBlock;
364}
365
366void ZipSigner::SetSigningBlock(const std::string& signingBlock)
367{
368    m_signingBlock = signingBlock;
369}
370
371uint32_t ZipSigner::GetCDOffset()
372{
373    return m_cDOffset;
374}
375
376void ZipSigner::SetCDOffset(uint32_t cDOffset)
377{
378    m_cDOffset = cDOffset;
379}
380
381uint32_t ZipSigner::GetEOCDOffset()
382{
383    return m_eOCDOffset;
384}
385
386void ZipSigner::SetEOCDOffset(uint32_t eOCDOffset)
387{
388    m_eOCDOffset = eOCDOffset;
389}
390
391EndOfCentralDirectory* ZipSigner::GetEndOfCentralDirectory()
392{
393    return m_endOfCentralDirectory;
394}
395
396void ZipSigner::SetEndOfCentralDirectory(EndOfCentralDirectory* endOfCentralDirectory)
397{
398    m_endOfCentralDirectory = endOfCentralDirectory;
399}
400} // namespace SignatureTools
401} // namespace OHOS