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#include <climits>
16#include <cstdlib>
17#include <regex>
18#include <unordered_map>
19#include <vector>
20#include <algorithm>
21
22#include "securec.h"
23#include "hap_signer_block_utils.h"
24#include "signature_info.h"
25#include "options.h"
26#include "openssl/pem.h"
27#include "pkcs7_data.h"
28#include "hap_utils.h"
29#include "string_utils.h"
30#include "verify_code_signature.h"
31#include "param_constants.h"
32#include "file_utils.h"
33#include "nlohmann/json.hpp"
34#include "verify_hap.h"
35
36using namespace nlohmann;
37namespace OHOS {
38namespace SignatureTools {
39const int32_t VerifyHap::HEX_PRINT_LENGTH = 3;
40const int32_t VerifyHap::DIGEST_BLOCK_LEN_OFFSET = 8;
41const int32_t VerifyHap::DIGEST_ALGORITHM_OFFSET = 12;
42const int32_t VerifyHap::DIGEST_LEN_OFFSET = 16;
43const int32_t VerifyHap::DIGEST_OFFSET_IN_CONTENT = 20;
44const std::string VerifyHap::HAP_APP_PATTERN = "[^]*.hap$";
45const std::string VerifyHap::HQF_APP_PATTERN = "[^]*.hqf$";
46const std::string VerifyHap::HSP_APP_PATTERN = "[^]*.hsp$";
47const std::string VerifyHap::APP_APP_PATTERN = "[^]*.app$";
48static constexpr int ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH = 12;
49
50VerifyHap::VerifyHap() : isPrintCert(true)
51{
52}
53
54VerifyHap::VerifyHap(bool printCert)
55{
56    isPrintCert = printCert;
57}
58
59void VerifyHap::setIsPrintCert(bool printCert)
60{
61    isPrintCert = printCert;
62}
63
64bool VerifyHap::HapOutPutPkcs7(PKCS7* p7, const std::string& outPutPath)
65{
66    std::string p7bContent = StringUtils::Pkcs7ToString(p7);
67    if (p7bContent.empty()) {
68        SIGNATURE_TOOLS_LOGE("p7b to string failed!\n");
69        return false;
70    }
71    if (FileUtils::Write(p7bContent, outPutPath) < 0) {
72        SIGNATURE_TOOLS_LOGE("p7b write to file falied!\n");
73        return false;
74    }
75    return true;
76}
77
78bool VerifyHap::outputOptionalBlocks(const std::string& outputProfileFile, const std::string& outputProofFile,
79                                     const std::string& outputPropertyFile,
80                                     const std::vector<OptionalBlock>& optionBlocks)
81{
82    for (auto& optionBlock : optionBlocks) {
83        if (optionBlock.optionalType == HapUtils::HAP_PROFILE_BLOCK_ID) {
84            if (!writeOptionalBytesToFile(optionBlock, outputProfileFile)) {
85                return false;
86            }
87        } else if (optionBlock.optionalType == HapUtils::HAP_PROPERTY_BLOCK_ID) {
88            if (!writeOptionalBytesToFile(optionBlock, outputPropertyFile)) {
89                return false;
90            }
91        } else if (optionBlock.optionalType == HapUtils::HAP_PROOF_OF_ROTATION_BLOCK_ID) {
92            if (!writeOptionalBytesToFile(optionBlock, outputProofFile)) {
93                return false;
94            }
95        } else {
96            SIGNATURE_TOOLS_LOGE("Unsupported Block Id: %d", optionBlock.optionalType);
97            return false;
98        }
99    }
100    return true;
101}
102bool VerifyHap::writeOptionalBytesToFile(const OptionalBlock& optionalBlock, const std::string& path)
103{
104    if (path.empty()) {
105        return true;
106    }
107    std::string optionBlockString(optionalBlock.optionalBlockValue.GetBufferPtr(),
108                          optionalBlock.optionalBlockValue.GetCapacity());
109    if (FileUtils::Write(optionBlockString, path) < 0) {
110        PrintErrorNumberMsg("IO_ERROR", IO_ERROR, "write optional bytes to file:" + path + " falied!");
111        return false;
112    }
113    return true;
114}
115
116bool VerifyHap::HapOutPutCertChain(std::vector<X509*>& certs, const std::string& outPutPath)
117{
118    if (isPrintCert) {
119        if (!PrintCertChainToCmd(certs)) {
120            SIGNATURE_TOOLS_LOGE("print cert chain to cmd failed\n");
121            return false;
122        }
123    }
124    VerifyHapOpensslUtils::GetOpensslErrorMessage();
125    SIGNATURE_TOOLS_LOGD("outPutPath = %s", outPutPath.c_str());
126    std::vector<std::string> certStr;
127    for (auto& cert : certs) {
128        certStr.emplace_back(StringUtils::SubjectToString(cert));
129        certStr.emplace_back(StringUtils::x509CertToString(cert));
130    }
131    std::string outPutCertChainContent;
132    for (auto& certstr : certStr) {
133        outPutCertChainContent += certstr;
134    }
135    if (FileUtils::Write(outPutCertChainContent, outPutPath) < 0) {
136        SIGNATURE_TOOLS_LOGE("certChain write to file falied!\n");
137        return false;
138    }
139    return true;
140}
141
142int32_t VerifyHap::Verify(const std::string& filePath, Options* options)
143{
144    SIGNATURE_TOOLS_LOGD("Start Verify");
145    std::string standardFilePath;
146    if (!CheckFilePath(filePath, standardFilePath)) {
147        SIGNATURE_TOOLS_LOGE("Check file path%s failed", filePath.c_str());
148        return IO_ERROR;
149    }
150    RandomAccessFile hapFile;
151    if (!hapFile.Init(standardFilePath)) {
152        SIGNATURE_TOOLS_LOGE("%s init failed", standardFilePath.c_str());
153        return ZIP_ERROR;
154    }
155    int32_t resultCode = Verify(hapFile, options, filePath);
156    if (resultCode != RET_OK) {
157        PrintErrorNumberMsg("VERIFY_ERROR", VERIFY_ERROR, standardFilePath + " verify failed");
158    }
159    return resultCode;
160}
161
162bool VerifyHap::CheckFilePath(const std::string& filePath, std::string& standardFilePath)
163{
164    char path[PATH_MAX] = { 0x00 };
165    if (filePath.size() > PATH_MAX || realpath(filePath.c_str(), path) == nullptr) {
166        PrintErrorNumberMsg("IO_ERROR", IO_ERROR,
167                            filePath + " does not exist or is over " + std::to_string(PATH_MAX) + " chars");
168        return false;
169    }
170    standardFilePath = std::string(path);
171    std::string standardFilePathTmp = std::string(path);
172    std::transform(standardFilePathTmp.begin(), standardFilePathTmp.end(), standardFilePathTmp.begin(),
173                   [](unsigned char c) { return std::tolower(c); });
174    bool ret = (!std::regex_match(standardFilePathTmp, std::regex(HAP_APP_PATTERN)) &&
175                !std::regex_match(standardFilePathTmp, std::regex(HSP_APP_PATTERN)) &&
176                !std::regex_match(standardFilePathTmp, std::regex(APP_APP_PATTERN)) &&
177                !std::regex_match(standardFilePathTmp, std::regex(HQF_APP_PATTERN)));
178    if (ret) {
179        PrintErrorNumberMsg("COMMAND_PARAM_ERROR", COMMAND_PARAM_ERROR,
180                            "only support format is [hap, hqf, hsp, app]");
181        return false;
182    }
183    return true;
184}
185
186int32_t VerifyHap::Verify(RandomAccessFile& hapFile, Options* options, const std::string& filePath)
187{
188    SignatureInfo hapSignInfo;
189    if (!HapSignerBlockUtils::FindHapSignature(hapFile, hapSignInfo)) {
190        return ZIP_ERROR;
191    }
192
193    if (CheckCodeSign(filePath, hapSignInfo.optionBlocks) == false) {
194        SIGNATURE_TOOLS_LOGE("check coode sign failed\n");
195        return VERIFY_ERROR;
196    }
197
198    Pkcs7Context pkcs7Context;
199    if (!VerifyAppPkcs7(pkcs7Context, hapSignInfo.hapSignatureBlock)) {
200        return PARSE_ERROR;
201    }
202
203    if (!GetDigestAndAlgorithm(pkcs7Context)) {
204        SIGNATURE_TOOLS_LOGE("Get digest failed");
205        return PARSE_ERROR;
206    }
207
208    STACK_OF(X509_CRL)* x509Crl = nullptr;
209    if (!VerifyHapOpensslUtils::GetCrlStack(pkcs7Context.p7, x509Crl)) {
210        SIGNATURE_TOOLS_LOGE("Get Crl stack failed");
211        return PARSE_ERROR;
212    }
213
214    if (!VerifyCertOpensslUtils::VerifyCrl(pkcs7Context.certChain[0], x509Crl, pkcs7Context)) {
215        SIGNATURE_TOOLS_LOGE("Verify Crl stack failed");
216        return VERIFY_ERROR;
217    }
218
219    if (!HapSignerBlockUtils::VerifyHapIntegrity(pkcs7Context, hapFile, hapSignInfo)) {
220        SIGNATURE_TOOLS_LOGE("Verify Integrity failed");
221        return VERIFY_ERROR;
222    }
223    if (!HapOutPutCertChain(pkcs7Context.certChain[0],
224        options->GetString(Options::OUT_CERT_CHAIN))) {
225        SIGNATURE_TOOLS_LOGE("out put cert chain failed");
226        return IO_ERROR;
227    }
228
229    if (!outputOptionalBlocks(options->GetString(ParamConstants::PARAM_VERIFY_PROFILE_FILE),
230                              options->GetString(ParamConstants::PARAM_VERIFY_PROOF_FILE),
231                              options->GetString(ParamConstants::PARAM_VERIFY_PROPERTY_FILE),
232                              hapSignInfo.optionBlocks)) {
233        SIGNATURE_TOOLS_LOGE("output Optional Blocks failed");
234        return IO_ERROR;
235    }
236    return RET_OK;
237}
238
239bool VerifyHap::CheckCodeSign(const std::string& hapFilePath,
240                              const std::vector<OptionalBlock>& optionalBlocks)const
241{
242    std::unordered_map<int, ByteBuffer> map;
243    for (const OptionalBlock& block : optionalBlocks) {
244        map.emplace(block.optionalType, block.optionalBlockValue);
245    }
246    bool codeSignFlag = map.find(HapUtils::HAP_PROPERTY_BLOCK_ID) != map.end() &&
247        map[HapUtils::HAP_PROPERTY_BLOCK_ID].GetCapacity() > 0;
248    if (codeSignFlag) {
249        ByteBuffer propertyBlockArray = map[HapUtils::HAP_PROPERTY_BLOCK_ID];
250        std::vector<std::string> fileNameArray = StringUtils::SplitString(hapFilePath, '.');
251        if (fileNameArray.size() < ParamConstants::FILE_NAME_MIN_LENGTH) {
252            PrintErrorNumberMsg("VERIFY_ERROR", VERIFY_ERROR, "ZIP64 format not supported.");
253            return false;
254        }
255
256        if (propertyBlockArray.GetCapacity() < ZIP_HEAD_OF_SUBSIGNING_BLOCK_LENGTH)
257            return false;
258        uint32_t blockType;
259        propertyBlockArray.GetUInt32(OFFSET_ZERO, blockType);
260        uint32_t blockLength;
261        propertyBlockArray.GetUInt32(OFFSET_FOUR, blockLength);
262        uint32_t blockOffset;
263        propertyBlockArray.GetUInt32(OFFSET_EIGHT, blockOffset);
264
265        if (blockType != HapUtils::HAP_CODE_SIGN_BLOCK_ID) {
266            PrintErrorNumberMsg("VERIFY_ERROR", VERIFY_ERROR, "code sign data not exist in hap " + hapFilePath);
267            return false;
268        }
269        auto ite = map.find(HapUtils::HAP_PROFILE_BLOCK_ID);
270        if (ite == map.end())
271            return false;
272        ByteBuffer profileArray = ite->second;
273        std::string profileArray_(profileArray.GetBufferPtr(), profileArray.GetCapacity());
274        std::string profileContent;
275        if (GetProfileContent(profileArray_, profileContent) < 0) {
276            SIGNATURE_TOOLS_LOGE("get profile content failed, file: %s", hapFilePath.c_str());
277            return false;
278        }
279        std::string suffix = fileNameArray[fileNameArray.size() - 1];
280        bool isCodeSign = VerifyCodeSignature::VerifyHap(hapFilePath, blockOffset, blockLength,
281                                                         suffix, profileContent);
282        if (!isCodeSign) {
283            SIGNATURE_TOOLS_LOGE("verify codesign failed, file: %s", hapFilePath.c_str());
284            return false;
285        }
286        SIGNATURE_TOOLS_LOGI("verify codesign success.");
287        return true;
288    }
289    SIGNATURE_TOOLS_LOGI("can not find codesign block.");
290    return true;
291}
292
293int VerifyHap::GetProfileContent(const std::string profile, std::string& ret)
294{
295    json obj = json::parse(profile, nullptr, false);
296    if (!obj.is_discarded() && obj.is_structured()) {
297        ret = profile;
298        return 0;
299    }
300    PKCS7Data p7Data;
301    if (p7Data.Parse(profile) < 0) {
302        ret = profile;
303        return -1;
304    }
305    if (p7Data.Verify() < 0) {
306        PrintErrorNumberMsg("PKCS7_VERIFY_ERROR", VERIFY_ERROR,
307                            "Verify profile pkcs7 failed! Profile is invalid");
308        ret = profile;
309        return -1;
310    }
311    if (p7Data.GetContent(ret) < 0) {
312        PrintErrorNumberMsg("PKCS7_VERIFY_ERROR", VERIFY_ERROR,
313                            "Check profile failed, signed profile content is not byte array");
314        ret = profile;
315        return -1;
316    }
317    return 0;
318}
319
320bool VerifyHap::VerifyAppPkcs7(Pkcs7Context& pkcs7Context, const ByteBuffer& hapSignatureBlock)
321{
322    const unsigned char* pkcs7Block = reinterpret_cast<const unsigned char*>(hapSignatureBlock.GetBufferPtr());
323    uint32_t pkcs7Len = static_cast<unsigned int>(hapSignatureBlock.GetCapacity());
324    if (!VerifyHapOpensslUtils::ParsePkcs7Package(pkcs7Block, pkcs7Len, pkcs7Context)) {
325        SIGNATURE_TOOLS_LOGE("parse pkcs7 failed");
326        return false;
327    }
328    if (!VerifyHapOpensslUtils::GetCertChains(pkcs7Context.p7, pkcs7Context)) {
329        SIGNATURE_TOOLS_LOGE("GetCertChains from pkcs7 failed");
330        return false;
331    }
332    if (!VerifyHapOpensslUtils::VerifyPkcs7(pkcs7Context)) {
333        SIGNATURE_TOOLS_LOGE("verify signature failed");
334        return false;
335    }
336    return true;
337}
338
339bool VerifyHap::GetDigestAndAlgorithm(Pkcs7Context& digest)
340{
341    /*
342     * contentinfo format:
343     * int: version
344     * int: block number
345     * digest blocks:
346     * each digest block format:
347     * int: length of sizeof(digestblock) - 4
348     * int: Algorithm ID
349     * int: length of digest
350     * byte[]: digest
351     */
352     /* length of sizeof(digestblock - 4) */
353    int32_t digestBlockLen;
354    if (!digest.content.GetInt32(DIGEST_BLOCK_LEN_OFFSET, digestBlockLen)) {
355        SIGNATURE_TOOLS_LOGE("get digestBlockLen failed");
356        return false;
357    }
358    /* Algorithm ID */
359    if (!digest.content.GetInt32(DIGEST_ALGORITHM_OFFSET, digest.digestAlgorithm)) {
360        SIGNATURE_TOOLS_LOGE("get digestAlgorithm failed");
361        return false;
362    }
363    /* length of digest */
364    int32_t digestlen;
365    if (!digest.content.GetInt32(DIGEST_LEN_OFFSET, digestlen)) {
366        SIGNATURE_TOOLS_LOGE("get digestlen failed");
367        return false;
368    }
369    int32_t sum = sizeof(digestlen) + sizeof(digest.digestAlgorithm) + digestlen;
370    if (sum != digestBlockLen) {
371        SIGNATURE_TOOLS_LOGE("digestBlockLen: %d is not equal to sum: %d",
372                             digestBlockLen, sum);
373        return false;
374    }
375    /* set position to the digest start point */
376    digest.content.SetPosition(DIGEST_OFFSET_IN_CONTENT);
377    /* set limit to the digest end point */
378    digest.content.SetLimit(DIGEST_OFFSET_IN_CONTENT + digestlen);
379    digest.content.Slice();
380    return true;
381}
382
383int32_t VerifyHap::WriteVerifyOutput(Pkcs7Context& pkcs7Context, std::vector<int8_t>& profile, Options* options)
384{
385    if (pkcs7Context.certChain.size() > 0) {
386        bool flag = VerifyHap::HapOutPutCertChain(pkcs7Context.certChain[0],
387            options->GetString(Options::OUT_CERT_CHAIN));
388        if (!flag) {
389            SIGNATURE_TOOLS_LOGE("out put cert chain failed");
390            return IO_ERROR;
391        }
392    }
393    if (pkcs7Context.p7 == nullptr) {
394        std::string p7bContent(profile.begin(), profile.end());
395        bool writeFlag = FileUtils::Write(p7bContent, options->GetString(Options::OUT_PROFILE)) < 0;
396        if (writeFlag) {
397            SIGNATURE_TOOLS_LOGE("p7b write to file falied!\n");
398            return IO_ERROR;
399        }
400        return RET_OK;
401    }
402    bool pkcs7flag = VerifyHap::HapOutPutPkcs7(pkcs7Context.p7, options->GetString(Options::OUT_PROFILE));
403    if (!pkcs7flag) {
404        SIGNATURE_TOOLS_LOGE("out put p7b failed");
405        return IO_ERROR;
406    }
407    return RET_OK;
408}
409
410bool VerifyHap::PrintCertChainToCmd(std::vector<X509*>& certChain)
411{
412    BIO* outFd = BIO_new_fp(stdout, BIO_NOCLOSE);
413    if (!outFd) {
414        PrintErrorNumberMsg("IO_ERROR", IO_ERROR, "The stdout stream may have errors");
415        return false;
416    }
417    uint64_t format = XN_FLAG_SEP_COMMA_PLUS; // Print according to RFC2253
418    uint64_t content = X509_FLAG_NO_EXTENSIONS | X509_FLAG_NO_ATTRIBUTES | X509_FLAG_NO_HEADER | X509_FLAG_NO_SIGDUMP;
419    int num = 0;
420    for (auto& cert : certChain) {
421        PrintMsg("+++++++++++++++++++++++++++++++++certificate #" + std::to_string(num) +
422                 "+++++++++++++++++++++++++++++++++++++");
423        if (!X509_print_ex(outFd, cert, format, content)) {
424            VerifyHapOpensslUtils::GetOpensslErrorMessage();
425            SIGNATURE_TOOLS_LOGE("print x509 cert to cmd failed");
426            BIO_free(outFd);
427            return false;
428        }
429        ++num;
430    }
431    BIO_free(outFd);
432    return true;
433}
434} // namespace SignatureTools
435} // namespace OHOS