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         } else {
    183             LOGI("failed to verify against key %d\n", i);
    184         }
    185     }
    186     free(eocd);
    187     LOGE("failed to verify whole-file signature\n");
    188     return VERIFY_FAILURE;
    189 }
    190 
    191 // Reads a file containing one or more public keys as produced by
    192 // DumpPublicKey:  this is an RSAPublicKey struct as it would appear
    193 // as a C source literal, eg:
    194 //
    195 //  "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
    196 //
    197 // For key versions newer than the original 2048-bit e=3 keys
    198 // supported by Android, the string is preceded by a version
    199 // identifier, eg:
    200 //
    201 //  "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}"
    202 //
    203 // (Note that the braces and commas in this example are actual
    204 // characters the parser expects to find in the file; the ellipses
    205 // indicate more numbers omitted from this example.)
    206 //
    207 // The file may contain multiple keys in this format, separated by
    208 // commas.  The last key must not be followed by a comma.
    209 //
    210 // Returns NULL if the file failed to parse, or if it contain zero keys.
    211 RSAPublicKey*
    212 load_keys(const char* filename, int* numKeys) {
    213     RSAPublicKey* out = NULL;
    214     *numKeys = 0;
    215 
    216     FILE* f = fopen(filename, "r");
    217     if (f == NULL) {
    218         LOGE("opening %s: %s\n", filename, strerror(errno));
    219         goto exit;
    220     }
    221 
    222     {
    223         int i;
    224         bool done = false;
    225         while (!done) {
    226             ++*numKeys;
    227             out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));
    228             RSAPublicKey* key = out + (*numKeys - 1);
    229 
    230             char start_char;
    231             if (fscanf(f, " %c", &start_char) != 1) goto exit;
    232             if (start_char == '{') {
    233                 // a version 1 key has no version specifier.
    234                 key->exponent = 3;
    235             } else if (start_char == 'v') {
    236                 int version;
    237                 if (fscanf(f, "%d {", &version) != 1) goto exit;
    238                 if (version == 2) {
    239                     key->exponent = 65537;
    240                 } else {
    241                     goto exit;
    242                 }
    243             }
    244 
    245             if (fscanf(f, " %i , 0x%x , { %u",
    246                        &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
    247                 goto exit;
    248             }
    249             if (key->len != RSANUMWORDS) {
    250                 LOGE("key length (%d) does not match expected size\n", key->len);
    251                 goto exit;
    252             }
    253             for (i = 1; i < key->len; ++i) {
    254                 if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
    255             }
    256             if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
    257             for (i = 1; i < key->len; ++i) {
    258                 if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
    259             }
    260             fscanf(f, " } } ");
    261 
    262             // if the line ends in a comma, this file has more keys.
    263             switch (fgetc(f)) {
    264             case ',':
    265                 // more keys to come.
    266                 break;
    267 
    268             case EOF:
    269                 done = true;
    270                 break;
    271 
    272             default:
    273                 LOGE("unexpected character between keys\n");
    274                 goto exit;
    275             }
    276 
    277             LOGI("read key e=%d\n", key->exponent);
    278         }
    279     }
    280 
    281     fclose(f);
    282     return out;
    283 
    284 exit:
    285     if (f) fclose(f);
    286     free(out);
    287     *numKeys = 0;
    288     return NULL;
    289 }
    290