Home | History | Annotate | Download | only in recovery
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "common.h"
     18 #include "verifier.h"
     19 #include "ui.h"
     20 
     21 #include "mincrypt/rsa.h"
     22 #include "mincrypt/sha.h"
     23 
     24 #include <string.h>
     25 #include <stdio.h>
     26 #include <errno.h>
     27 
     28 extern RecoveryUI* ui;
     29 
     30 // Look for an RSA signature embedded in the .ZIP file comment given
     31 // the path to the zip.  Verify it matches one of the given public
     32 // keys.
     33 //
     34 // Return VERIFY_SUCCESS, VERIFY_FAILURE (if any error is encountered
     35 // or no key matches the signature).
     36 
     37 int verify_file(const char* path, const RSAPublicKey *pKeys, unsigned int numKeys) {
     38     ui->SetProgress(0.0);
     39 
     40     FILE* f = fopen(path, "rb");
     41     if (f == NULL) {
     42         LOGE("failed to open %s (%s)\n", path, strerror(errno));
     43         return VERIFY_FAILURE;
     44     }
     45 
     46     // An archive with a whole-file signature will end in six bytes:
     47     //
     48     //   (2-byte signature start) $ff $ff (2-byte comment size)
     49     //
     50     // (As far as the ZIP format is concerned, these are part of the
     51     // archive comment.)  We start by reading this footer, this tells
     52     // us how far back from the end we have to start reading to find
     53     // the whole comment.
     54 
     55 #define FOOTER_SIZE 6
     56 
     57     if (fseek(f, -FOOTER_SIZE, SEEK_END) != 0) {
     58         LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
     59         fclose(f);
     60         return VERIFY_FAILURE;
     61     }
     62 
     63     unsigned char footer[FOOTER_SIZE];
     64     if (fread(footer, 1, FOOTER_SIZE, f) != FOOTER_SIZE) {
     65         LOGE("failed to read footer from %s (%s)\n", path, strerror(errno));
     66         fclose(f);
     67         return VERIFY_FAILURE;
     68     }
     69 
     70     if (footer[2] != 0xff || footer[3] != 0xff) {
     71         fclose(f);
     72         return VERIFY_FAILURE;
     73     }
     74 
     75     size_t comment_size = footer[4] + (footer[5] << 8);
     76     size_t signature_start = footer[0] + (footer[1] << 8);
     77     LOGI("comment is %d bytes; signature %d bytes from end\n",
     78          comment_size, signature_start);
     79 
     80     if (signature_start - FOOTER_SIZE < RSANUMBYTES) {
     81         // "signature" block isn't big enough to contain an RSA block.
     82         LOGE("signature is too short\n");
     83         fclose(f);
     84         return VERIFY_FAILURE;
     85     }
     86 
     87 #define EOCD_HEADER_SIZE 22
     88 
     89     // The end-of-central-directory record is 22 bytes plus any
     90     // comment length.
     91     size_t eocd_size = comment_size + EOCD_HEADER_SIZE;
     92 
     93     if (fseek(f, -eocd_size, SEEK_END) != 0) {
     94         LOGE("failed to seek in %s (%s)\n", path, strerror(errno));
     95         fclose(f);
     96         return VERIFY_FAILURE;
     97     }
     98 
     99     // Determine how much of the file is covered by the signature.
    100     // This is everything except the signature data and length, which
    101     // includes all of the EOCD except for the comment length field (2
    102     // bytes) and the comment data.
    103     size_t signed_len = ftell(f) + EOCD_HEADER_SIZE - 2;
    104 
    105     unsigned char* eocd = (unsigned char*)malloc(eocd_size);
    106     if (eocd == NULL) {
    107         LOGE("malloc for EOCD record failed\n");
    108         fclose(f);
    109         return VERIFY_FAILURE;
    110     }
    111     if (fread(eocd, 1, eocd_size, f) != eocd_size) {
    112         LOGE("failed to read eocd from %s (%s)\n", path, strerror(errno));
    113         fclose(f);
    114         return VERIFY_FAILURE;
    115     }
    116 
    117     // If this is really is the EOCD record, it will begin with the
    118     // magic number $50 $4b $05 $06.
    119     if (eocd[0] != 0x50 || eocd[1] != 0x4b ||
    120         eocd[2] != 0x05 || eocd[3] != 0x06) {
    121         LOGE("signature length doesn't match EOCD marker\n");
    122         fclose(f);
    123         return VERIFY_FAILURE;
    124     }
    125 
    126     size_t i;
    127     for (i = 4; i < eocd_size-3; ++i) {
    128         if (eocd[i  ] == 0x50 && eocd[i+1] == 0x4b &&
    129             eocd[i+2] == 0x05 && eocd[i+3] == 0x06) {
    130             // if the sequence $50 $4b $05 $06 appears anywhere after
    131             // the real one, minzip will find the later (wrong) one,
    132             // which could be exploitable.  Fail verification if
    133             // this sequence occurs anywhere after the real one.
    134             LOGE("EOCD marker occurs after start of EOCD\n");
    135             fclose(f);
    136             return VERIFY_FAILURE;
    137         }
    138     }
    139 
    140 #define BUFFER_SIZE 4096
    141 
    142     SHA_CTX ctx;
    143     SHA_init(&ctx);
    144     unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE);
    145     if (buffer == NULL) {
    146         LOGE("failed to alloc memory for sha1 buffer\n");
    147         fclose(f);
    148         return VERIFY_FAILURE;
    149     }
    150 
    151     double frac = -1.0;
    152     size_t so_far = 0;
    153     fseek(f, 0, SEEK_SET);
    154     while (so_far < signed_len) {
    155         size_t size = BUFFER_SIZE;
    156         if (signed_len - so_far < size) size = signed_len - so_far;
    157         if (fread(buffer, 1, size, f) != size) {
    158             LOGE("failed to read data from %s (%s)\n", path, strerror(errno));
    159             fclose(f);
    160             return VERIFY_FAILURE;
    161         }
    162         SHA_update(&ctx, buffer, size);
    163         so_far += size;
    164         double f = so_far / (double)signed_len;
    165         if (f > frac + 0.02 || size == so_far) {
    166             ui->SetProgress(f);
    167             frac = f;
    168         }
    169     }
    170     fclose(f);
    171     free(buffer);
    172 
    173     const uint8_t* sha1 = SHA_final(&ctx);
    174     for (i = 0; i < numKeys; ++i) {
    175         // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
    176         // the signing tool appends after the signature itself.
    177         if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES,
    178                        RSANUMBYTES, sha1)) {
    179             LOGI("whole-file signature verified against key %d\n", i);
    180             free(eocd);
    181             return VERIFY_SUCCESS;
    182         }
    183     }
    184     free(eocd);
    185     LOGE("failed to verify whole-file signature\n");
    186     return VERIFY_FAILURE;
    187 }
    188