diff options
Diffstat (limited to 'verifier.cpp')
-rw-r--r-- | verifier.cpp | 466 |
1 files changed, 280 insertions, 186 deletions
diff --git a/verifier.cpp b/verifier.cpp index bf7071d13..2d1b0e74f 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -14,25 +14,27 @@ * limitations under the License. */ -#include "asn1_decoder.h" -#include "common.h" -#include "ui.h" -#include "verifier.h" - -#include "mincrypt/dsa_sig.h" -#include "mincrypt/p256.h" -#include "mincrypt/p256_ecdsa.h" -#include "mincrypt/rsa.h" -#include "mincrypt/sha.h" -#include "mincrypt/sha256.h" - #include <errno.h> #include <malloc.h> #include <stdio.h> #include <string.h> +#include <algorithm> +#include <memory> + +#include <openssl/ecdsa.h> +#include <openssl/obj_mac.h> + +#include "asn1_decoder.h" +#include "common.h" +#include "print_sha1.h" +#include "ui.h" +#include "verifier.h" + extern RecoveryUI* ui; +static constexpr size_t MiB = 1024 * 1024; + /* * Simple version of PKCS#7 SignedData extraction. This extracts the * signature OCTET STRING to be used for signature verification. @@ -113,7 +115,7 @@ static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_d // or no key matches the signature). int verify_file(unsigned char* addr, size_t length, - const Certificate* pKeys, unsigned int numKeys) { + const std::vector<Certificate>& keys) { ui->SetProgress(0.0); // An archive with a whole-file signature will end in six bytes: @@ -182,8 +184,7 @@ int verify_file(unsigned char* addr, size_t length, return VERIFY_FAILURE; } - size_t i; - for (i = 4; i < eocd_size-3; ++i) { + for (size_t i = 4; i < eocd_size-3; ++i) { if (eocd[i ] == 0x50 && eocd[i+1] == 0x4b && eocd[i+2] == 0x05 && eocd[i+3] == 0x06) { // if the sequence $50 $4b $05 $06 appears anywhere after @@ -195,30 +196,30 @@ int verify_file(unsigned char* addr, size_t length, } } -#define BUFFER_SIZE 4096 - bool need_sha1 = false; bool need_sha256 = false; - for (i = 0; i < numKeys; ++i) { - switch (pKeys[i].hash_len) { - case SHA_DIGEST_SIZE: need_sha1 = true; break; - case SHA256_DIGEST_SIZE: need_sha256 = true; break; + for (const auto& key : keys) { + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: need_sha1 = true; break; + case SHA256_DIGEST_LENGTH: need_sha256 = true; break; } } SHA_CTX sha1_ctx; SHA256_CTX sha256_ctx; - SHA_init(&sha1_ctx); - SHA256_init(&sha256_ctx); + SHA1_Init(&sha1_ctx); + SHA256_Init(&sha256_ctx); double frac = -1.0; size_t so_far = 0; while (so_far < signed_len) { - size_t size = signed_len - so_far; - if (size > BUFFER_SIZE) size = BUFFER_SIZE; + // On a Nexus 5X, experiment showed 16MiB beat 1MiB by 6% faster for a + // 1196MiB full OTA and 60% for an 89MiB incremental OTA. + // http://b/28135231. + size_t size = std::min(signed_len - so_far, 16 * MiB); - if (need_sha1) SHA_update(&sha1_ctx, addr + so_far, size); - if (need_sha256) SHA256_update(&sha256_ctx, addr + so_far, size); + if (need_sha1) SHA1_Update(&sha1_ctx, addr + so_far, size); + if (need_sha256) SHA256_Update(&sha256_ctx, addr + so_far, size); so_far += size; double f = so_far / (double)signed_len; @@ -228,15 +229,22 @@ int verify_file(unsigned char* addr, size_t length, } } - const uint8_t* sha1 = SHA_final(&sha1_ctx); - const uint8_t* sha256 = SHA256_final(&sha256_ctx); + uint8_t sha1[SHA_DIGEST_LENGTH]; + SHA1_Final(sha1, &sha1_ctx); + uint8_t sha256[SHA256_DIGEST_LENGTH]; + SHA256_Final(sha256, &sha256_ctx); - uint8_t* sig_der = NULL; + uint8_t* sig_der = nullptr; size_t sig_der_length = 0; + uint8_t* signature = eocd + eocd_size - signature_start; size_t signature_size = signature_start - FOOTER_SIZE; - if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der, - &sig_der_length)) { + + LOGI("signature (offset: 0x%zx, length: %zu): %s\n", + length - signature_start, signature_size, + print_hex(signature, signature_size).c_str()); + + if (!read_pkcs7(signature, signature_size, &sig_der, &sig_der_length)) { LOGE("Could not find signature DER block\n"); return VERIFY_FAILURE; } @@ -246,25 +254,28 @@ int verify_file(unsigned char* addr, size_t length, * any key can match, we need to try each before determining a verification * failure has happened. */ - for (i = 0; i < numKeys; ++i) { + size_t i = 0; + for (const auto& key : keys) { const uint8_t* hash; - switch (pKeys[i].hash_len) { - case SHA_DIGEST_SIZE: hash = sha1; break; - case SHA256_DIGEST_SIZE: hash = sha256; break; - default: continue; + int hash_nid; + switch (key.hash_len) { + case SHA_DIGEST_LENGTH: + hash = sha1; + hash_nid = NID_sha1; + break; + case SHA256_DIGEST_LENGTH: + hash = sha256; + hash_nid = NID_sha256; + break; + default: + continue; } // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that // the signing tool appends after the signature itself. - if (pKeys[i].key_type == Certificate::RSA) { - if (sig_der_length < RSANUMBYTES) { - // "signature" block isn't big enough to contain an RSA block. - LOGI("signature is too short for RSA key %zu\n", i); - continue; - } - - if (!RSA_verify(pKeys[i].rsa, sig_der, RSANUMBYTES, - hash, pKeys[i].hash_len)) { + if (key.key_type == Certificate::KEY_TYPE_RSA) { + if (!RSA_verify(hash_nid, hash, key.hash_len, sig_der, + sig_der_length, key.rsa.get())) { LOGI("failed to verify against RSA key %zu\n", i); continue; } @@ -272,18 +283,10 @@ int verify_file(unsigned char* addr, size_t length, LOGI("whole-file signature verified against RSA key %zu\n", i); free(sig_der); return VERIFY_SUCCESS; - } else if (pKeys[i].key_type == Certificate::EC - && pKeys[i].hash_len == SHA256_DIGEST_SIZE) { - p256_int r, s; - if (!dsa_sig_unpack(sig_der, sig_der_length, &r, &s)) { - LOGI("Not a DSA signature block for EC key %zu\n", i); - continue; - } - - p256_int p256_hash; - p256_from_bin(hash, &p256_hash); - if (!p256_ecdsa_verify(&(pKeys[i].ec->x), &(pKeys[i].ec->y), - &p256_hash, &r, &s)) { + } else if (key.key_type == Certificate::KEY_TYPE_EC + && key.hash_len == SHA256_DIGEST_LENGTH) { + if (!ECDSA_verify(0, hash, key.hash_len, sig_der, + sig_der_length, key.ec.get())) { LOGI("failed to verify against EC key %zu\n", i); continue; } @@ -292,14 +295,160 @@ int verify_file(unsigned char* addr, size_t length, free(sig_der); return VERIFY_SUCCESS; } else { - LOGI("Unknown key type %d\n", pKeys[i].key_type); + LOGI("Unknown key type %d\n", key.key_type); } + i++; + } + + if (need_sha1) { + LOGI("SHA-1 digest: %s\n", print_hex(sha1, SHA_DIGEST_LENGTH).c_str()); + } + if (need_sha256) { + LOGI("SHA-256 digest: %s\n", print_hex(sha256, SHA256_DIGEST_LENGTH).c_str()); } free(sig_der); LOGE("failed to verify whole-file signature\n"); return VERIFY_FAILURE; } +std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { + // Read key length in words and n0inv. n0inv is a precomputed montgomery + // parameter derived from the modulus and can be used to speed up + // verification. n0inv is 32 bits wide here, assuming the verification logic + // uses 32 bit arithmetic. However, BoringSSL may use a word size of 64 bits + // internally, in which case we don't have a valid n0inv. Thus, we just + // ignore the montgomery parameters and have BoringSSL recompute them + // internally. If/When the speedup from using the montgomery parameters + // becomes relevant, we can add more sophisticated code here to obtain a + // 64-bit n0inv and initialize the montgomery parameters in the key object. + uint32_t key_len_words = 0; + uint32_t n0inv = 0; + if (fscanf(file, " %i , 0x%x", &key_len_words, &n0inv) != 2) { + return nullptr; + } + + if (key_len_words > 8192 / 32) { + LOGE("key length (%d) too large\n", key_len_words); + return nullptr; + } + + // Read the modulus. + std::unique_ptr<uint32_t[]> modulus(new uint32_t[key_len_words]); + if (fscanf(file, " , { %u", &modulus[0]) != 1) { + return nullptr; + } + for (uint32_t i = 1; i < key_len_words; ++i) { + if (fscanf(file, " , %u", &modulus[i]) != 1) { + return nullptr; + } + } + + // Cconvert from little-endian array of little-endian words to big-endian + // byte array suitable as input for BN_bin2bn. + std::reverse((uint8_t*)modulus.get(), + (uint8_t*)(modulus.get() + key_len_words)); + + // The next sequence of values is the montgomery parameter R^2. Since we + // generally don't have a valid |n0inv|, we ignore this (see comment above). + uint32_t rr_value; + if (fscanf(file, " } , { %u", &rr_value) != 1) { + return nullptr; + } + for (uint32_t i = 1; i < key_len_words; ++i) { + if (fscanf(file, " , %u", &rr_value) != 1) { + return nullptr; + } + } + if (fscanf(file, " } } ") != 0) { + return nullptr; + } + + // Initialize the key. + std::unique_ptr<RSA, RSADeleter> key(RSA_new()); + if (!key) { + return nullptr; + } + + key->n = BN_bin2bn((uint8_t*)modulus.get(), + key_len_words * sizeof(uint32_t), NULL); + if (!key->n) { + return nullptr; + } + + key->e = BN_new(); + if (!key->e || !BN_set_word(key->e, exponent)) { + return nullptr; + } + + return key; +} + +struct BNDeleter { + void operator()(BIGNUM* bn) { + BN_free(bn); + } +}; + +std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) { + uint32_t key_len_bytes = 0; + if (fscanf(file, " %i", &key_len_bytes) != 1) { + return nullptr; + } + + std::unique_ptr<EC_GROUP, void (*)(EC_GROUP*)> group( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1), EC_GROUP_free); + if (!group) { + return nullptr; + } + + // Verify that |key_len| matches the group order. + if (key_len_bytes != BN_num_bytes(EC_GROUP_get0_order(group.get()))) { + return nullptr; + } + + // Read the public key coordinates. Note that the byte order in the file is + // little-endian, so we convert to big-endian here. + std::unique_ptr<uint8_t[]> bytes(new uint8_t[key_len_bytes]); + std::unique_ptr<BIGNUM, BNDeleter> point[2]; + for (int i = 0; i < 2; ++i) { + unsigned int byte = 0; + if (fscanf(file, " , { %u", &byte) != 1) { + return nullptr; + } + bytes[key_len_bytes - 1] = byte; + + for (size_t i = 1; i < key_len_bytes; ++i) { + if (fscanf(file, " , %u", &byte) != 1) { + return nullptr; + } + bytes[key_len_bytes - i - 1] = byte; + } + + point[i].reset(BN_bin2bn(bytes.get(), key_len_bytes, nullptr)); + if (!point[i]) { + return nullptr; + } + + if (fscanf(file, " }") != 0) { + return nullptr; + } + } + + if (fscanf(file, " } ") != 0) { + return nullptr; + } + + // Create and initialize the key. + std::unique_ptr<EC_KEY, ECKEYDeleter> key(EC_KEY_new()); + if (!key || !EC_KEY_set_group(key.get(), group.get()) || + !EC_KEY_set_public_key_affine_coordinates(key.get(), point[0].get(), + point[1].get())) { + return nullptr; + } + + return key; +} + // Reads a file containing one or more public keys as produced by // DumpPublicKey: this is an RSAPublicKey struct as it would appear // as a C source literal, eg: @@ -329,140 +478,85 @@ int verify_file(unsigned char* addr, size_t length, // 4: 2048-bit RSA key with e=65537 and SHA-256 hash // 5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash // -// Returns NULL if the file failed to parse, or if it contain zero keys. -Certificate* -load_keys(const char* filename, int* numKeys) { - Certificate* out = NULL; - *numKeys = 0; - - FILE* f = fopen(filename, "r"); - if (f == NULL) { +// Returns true on success, and appends the found keys (at least one) to certs. +// Otherwise returns false if the file failed to parse, or if it contains zero +// keys. The contents in certs would be unspecified on failure. +bool load_keys(const char* filename, std::vector<Certificate>& certs) { + std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose); + if (!f) { LOGE("opening %s: %s\n", filename, strerror(errno)); - goto exit; - } - - { - int i; - bool done = false; - while (!done) { - ++*numKeys; - out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate)); - Certificate* cert = out + (*numKeys - 1); - memset(cert, '\0', sizeof(Certificate)); - - char start_char; - if (fscanf(f, " %c", &start_char) != 1) goto exit; - if (start_char == '{') { - // a version 1 key has no version specifier. - cert->key_type = Certificate::RSA; - cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); - cert->rsa->exponent = 3; - cert->hash_len = SHA_DIGEST_SIZE; - } else if (start_char == 'v') { - int version; - if (fscanf(f, "%d {", &version) != 1) goto exit; - switch (version) { - case 2: - cert->key_type = Certificate::RSA; - cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); - cert->rsa->exponent = 65537; - cert->hash_len = SHA_DIGEST_SIZE; - break; - case 3: - cert->key_type = Certificate::RSA; - cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); - cert->rsa->exponent = 3; - cert->hash_len = SHA256_DIGEST_SIZE; - break; - case 4: - cert->key_type = Certificate::RSA; - cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey)); - cert->rsa->exponent = 65537; - cert->hash_len = SHA256_DIGEST_SIZE; - break; - case 5: - cert->key_type = Certificate::EC; - cert->ec = (ECPublicKey*)calloc(1, sizeof(ECPublicKey)); - cert->hash_len = SHA256_DIGEST_SIZE; - break; - default: - goto exit; - } - } + return false; + } - if (cert->key_type == Certificate::RSA) { - RSAPublicKey* key = cert->rsa; - if (fscanf(f, " %i , 0x%x , { %u", - &(key->len), &(key->n0inv), &(key->n[0])) != 3) { - goto exit; - } - if (key->len != RSANUMWORDS) { - LOGE("key length (%d) does not match expected size\n", key->len); - goto exit; - } - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit; - } - if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit; - for (i = 1; i < key->len; ++i) { - if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit; - } - fscanf(f, " } } "); - - LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len); - } else if (cert->key_type == Certificate::EC) { - ECPublicKey* key = cert->ec; - int key_len; - unsigned int byte; - uint8_t x_bytes[P256_NBYTES]; - uint8_t y_bytes[P256_NBYTES]; - if (fscanf(f, " %i , { %u", &key_len, &byte) != 2) goto exit; - if (key_len != P256_NBYTES) { - LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES); - goto exit; - } - x_bytes[P256_NBYTES - 1] = byte; - for (i = P256_NBYTES - 2; i >= 0; --i) { - if (fscanf(f, " , %u", &byte) != 1) goto exit; - x_bytes[i] = byte; - } - if (fscanf(f, " } , { %u", &byte) != 1) goto exit; - y_bytes[P256_NBYTES - 1] = byte; - for (i = P256_NBYTES - 2; i >= 0; --i) { - if (fscanf(f, " , %u", &byte) != 1) goto exit; - y_bytes[i] = byte; - } - fscanf(f, " } } "); - p256_from_bin(x_bytes, &key->x); - p256_from_bin(y_bytes, &key->y); - } else { - LOGE("Unknown key type %d\n", cert->key_type); - goto exit; + while (true) { + certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + Certificate& cert = certs.back(); + uint32_t exponent = 0; + + char start_char; + if (fscanf(f.get(), " %c", &start_char) != 1) return false; + if (start_char == '{') { + // a version 1 key has no version specifier. + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 3; + cert.hash_len = SHA_DIGEST_LENGTH; + } else if (start_char == 'v') { + int version; + if (fscanf(f.get(), "%d {", &version) != 1) return false; + switch (version) { + case 2: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 65537; + cert.hash_len = SHA_DIGEST_LENGTH; + break; + case 3: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 3; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + case 4: + cert.key_type = Certificate::KEY_TYPE_RSA; + exponent = 65537; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + case 5: + cert.key_type = Certificate::KEY_TYPE_EC; + cert.hash_len = SHA256_DIGEST_LENGTH; + break; + default: + return false; } + } - // if the line ends in a comma, this file has more keys. - switch (fgetc(f)) { - case ',': - // more keys to come. - break; - - case EOF: - done = true; - break; + if (cert.key_type == Certificate::KEY_TYPE_RSA) { + cert.rsa = parse_rsa_key(f.get(), exponent); + if (!cert.rsa) { + return false; + } - default: - LOGE("unexpected character between keys\n"); - goto exit; + LOGI("read key e=%d hash=%d\n", exponent, cert.hash_len); + } else if (cert.key_type == Certificate::KEY_TYPE_EC) { + cert.ec = parse_ec_key(f.get()); + if (!cert.ec) { + return false; } + } else { + LOGE("Unknown key type %d\n", cert.key_type); + return false; } - } - fclose(f); - return out; + // if the line ends in a comma, this file has more keys. + int ch = fgetc(f.get()); + if (ch == ',') { + // more keys to come. + continue; + } else if (ch == EOF) { + break; + } else { + LOGE("unexpected character between keys\n"); + return false; + } + } -exit: - if (f) fclose(f); - free(out); - *numKeys = 0; - return NULL; + return true; } |