/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include static FILE* urandom = NULL; #if defined(__APPLE__) || defined(_WIN32) inline uint32_t bswap32 (uint32_t x) { uint32_t out = 0; for (int i=0; i < 4; ++i, x >>= 8) out = (out << 8) | (x & 0xFF); return out; } #define htobe32(x) bswap32((x)) #define htole32(x) ((uint32_t)(x)) #define be32toh(x) bswap32((x)) #define le32toh(x) ((uint32_t)(x)) #else #include #endif //read exactly one hex-encoded byte from a file, skipping all the fluff static int getHexEncodedByte(uint8_t *buf, uint32_t *ppos, uint32_t size) { int c, i; uint32_t pos = *ppos; uint8_t val = 0; //for first byte for (i = 0; i < 2; i++) { val <<= 4; while(1) { if (pos == size) return -1; c = buf[pos++]; *ppos = pos; if (c >= '0' && c <= '9') val += c - '0'; else if (c >= 'a' && c <= 'f') val += c + 10 - 'a'; else if (c >= 'A' && c <= 'F') val += c + 10 - 'A'; else if (i) //disallow everything between first and second nibble return -1; else if (c > 'f' && c <= 'z') //disallow nonalpha data return -1; else if (c > 'F' && c <= 'Z') //disallow nonalpha data return -1; else continue; break; } } return val; } //provide a random number for which the following property is true ((ret & 0xFF000000) && (ret & 0xFF0000) && (ret & 0xFF00) && (ret & 0xFF)) static uint32_t rand32_no_zero_bytes(void) { uint32_t i, v; uint8_t byte; if (!urandom) { urandom = fopen("/dev/urandom", "rb"); if (!urandom) { fprintf(stderr, "Failed to open /dev/urandom. Cannot procceed!\n"); exit(-2); } } for (v = 0, i = 0; i < 4; i++) { do { if (!fread(&byte, 1, 1, urandom)) { fprintf(stderr, "Failed to read /dev/urandom. Cannot procceed!\n"); exit(-3); } } while (!byte); v = (v << 8) | byte; } return v; } static void cleanup(void) { if (urandom) fclose(urandom); } struct RsaData { uint32_t num[RSA_LIMBS]; uint32_t exponent[RSA_LIMBS]; uint32_t modulus[RSA_LIMBS]; struct RsaState state; }; static bool validateSignature(uint8_t *sigPack, struct RsaData *rsa, bool verbose, uint32_t *refHash, bool preset) { int i; const uint32_t *rsaResult; const uint32_t *le32SigPack = (const uint32_t*)sigPack; //convert to native uint32_t; ignore possible alignment issues for (i = 0; i < RSA_LIMBS; i++) rsa->num[i] = le32toh(le32SigPack[i]); //update the user if (verbose) printHashRev(stderr, "RSA cyphertext", rsa->num, RSA_LIMBS); if (!preset) memcpy(rsa->modulus, sigPack + RSA_BYTES, RSA_BYTES); //do rsa op rsaResult = rsaPubOp(&rsa->state, rsa->num, rsa->modulus); //update the user if (verbose) printHashRev(stderr, "RSA plaintext", rsaResult, RSA_LIMBS); //verify padding is appropriate and valid if ((rsaResult[RSA_LIMBS - 1] & 0xffff0000) != 0x00020000) { fprintf(stderr, "Padding header is invalid\n"); return false; } //verify first two bytes of padding if (!(rsaResult[RSA_LIMBS - 1] & 0xff00) || !(rsaResult[RSA_LIMBS - 1] & 0xff)) { fprintf(stderr, "Padding bytes 0..1 are invalid\n"); return false; } //verify last 3 bytes of padding and the zero terminator if (!(rsaResult[8] & 0xff000000) || !(rsaResult[8] & 0xff0000) || !(rsaResult[8] & 0xff00) || (rsaResult[8] & 0xff)) { fprintf(stderr, "Padding last bytes & terminator invalid\n"); return false; } //verify middle padding bytes for (i = 9; i < RSA_LIMBS - 1; i++) { if (!(rsaResult[i] & 0xff000000) || !(rsaResult[i] & 0xff0000) || !(rsaResult[i] & 0xff00) || !(rsaResult[i] & 0xff)) { fprintf(stderr, "Padding word %d invalid\n", i); return false; } } if (verbose) { printHash(stderr, "Recovered hash ", rsaResult, SHA2_HASH_WORDS); printHash(stderr, "Calculated hash", refHash, SHA2_HASH_WORDS); } if (!preset) { // we're doing full verification, with key extracted from signature pack if (memcmp(rsaResult, refHash, SHA2_HASH_SIZE)) { fprintf(stderr, "hash mismatch\n"); return false; } } else { // we just decode the signature with key passed as an argument // in this case we return recovered hash memcpy(refHash, rsaResult, SHA2_HASH_SIZE); } return true; } #define SIGNATURE_BLOCK_SIZE (2 * RSA_BYTES) static int handleConvertKey(uint8_t **pbuf, uint32_t bufUsed, FILE *out, struct RsaData *rsa) { bool haveNonzero = false; uint8_t *buf = *pbuf; int i, c; uint32_t pos = 0; int ret; for (i = 0; i < (int)RSA_BYTES; i++) { //get a byte, skipping all zeroes (openssl likes to prepend one at times) do { c = getHexEncodedByte(buf, &pos, bufUsed); } while (c == 0 && !haveNonzero); haveNonzero = true; if (c < 0) { fprintf(stderr, "Invalid text RSA input data\n"); return 2; } buf[i] = c; } // change form BE to native; ignore alignment uint32_t *be32Buf = (uint32_t*)buf; for (i = 0; i < RSA_LIMBS; i++) rsa->num[RSA_LIMBS - i - 1] = be32toh(be32Buf[i]); //output in our binary format (little-endian) ret = fwrite(rsa->num, 1, RSA_BYTES, out) == RSA_BYTES ? 0 : 2; fprintf(stderr, "Conversion status: %d\n", ret); return ret; } static int handleVerify(uint8_t **pbuf, uint32_t bufUsed, struct RsaData *rsa, bool verbose, bool bareData) { struct Sha2state shaState; uint8_t *buf = *pbuf; uint32_t masterPubKey[RSA_LIMBS]; memcpy(masterPubKey, rsa->modulus, RSA_BYTES); if (!bareData) { struct ImageHeader *image = (struct ImageHeader *)buf; struct AppSecSignHdr *secHdr = (struct AppSecSignHdr *)&image[1]; int block = 0; uint8_t *sigPack; bool trusted = false; bool lastTrusted = false; int sigData; if (bufUsed < (sizeof(*image) + sizeof(*secHdr))) { fprintf(stderr, "Invalid signature header: file is too short\n"); return 2; } if (verbose) fprintf(stderr, "Original Data len=%" PRIu32 " b; file size=%" PRIu32 " b; diff=%" PRIu32 " b\n", secHdr->appDataLen, bufUsed, bufUsed - secHdr->appDataLen); if (!(image->aosp.flags & NANOAPP_SIGNED_FLAG)) { fprintf(stderr, "image is not marked as signed, can not verify\n"); return 2; } sigData = bufUsed - (secHdr->appDataLen + sizeof(*image) + sizeof(*secHdr)); if (sigData <= 0 || (sigData % SIGNATURE_BLOCK_SIZE) != 0) { fprintf(stderr, "Invalid signature header: data size mismatch\n"); return 2; } sha2init(&shaState); sha2processBytes(&shaState, buf, bufUsed - sigData); int nSig = sigData / SIGNATURE_BLOCK_SIZE; sigPack = buf + bufUsed - sigData; for (block = 0; block < nSig; ++block) { if (!validateSignature(sigPack, rsa, verbose, (uint32_t*)sha2finish(&shaState), false)) { fprintf(stderr, "Signature verification failed: signature block #%d\n", block); return 2; } if (memcmp(masterPubKey, rsa->modulus, RSA_BYTES) == 0) { fprintf(stderr, "Key in block %d is trusted\n", block); trusted = true; lastTrusted = true; } else { lastTrusted = false; } sha2init(&shaState); sha2processBytes(&shaState, sigPack+RSA_BYTES, RSA_BYTES); sigPack += SIGNATURE_BLOCK_SIZE; } if (trusted && !lastTrusted) { fprintf(stderr, "Trusted key is not the last in key sequence\n"); } return trusted ? 0 : 2; } else { uint8_t *sigPack = buf + bufUsed - SIGNATURE_BLOCK_SIZE; uint32_t *hash; // can not do signature chains in bare mode if (bufUsed > SIGNATURE_BLOCK_SIZE) { sha2init(&shaState); sha2processBytes(&shaState, buf, bufUsed - SIGNATURE_BLOCK_SIZE); hash = (uint32_t*)sha2finish(&shaState); printHash(stderr, "File hash", hash, SHA2_HASH_WORDS); if (verbose) printHashRev(stderr, "File PubKey", (uint32_t *)(sigPack + RSA_BYTES), RSA_LIMBS); if (!validateSignature(sigPack, rsa, verbose, hash, false)) { fprintf(stderr, "Signature verification failed on raw data\n"); return 2; } if (memcmp(masterPubKey, sigPack + RSA_BYTES, RSA_BYTES) == 0) { fprintf(stderr, "Signature verification passed and the key is trusted\n"); return 0; } else { fprintf(stderr, "Signature verification passed but the key is not trusted\n"); return 2; } } else { fprintf(stderr, "Not enough raw data to extract signature from\n"); return 2; } } return 0; } static int handleSign(uint8_t **pbuf, uint32_t bufUsed, FILE *out, struct RsaData *rsa, bool verbose, bool bareData) { struct Sha2state shaState; uint8_t *buf = *pbuf; uint32_t i; const uint32_t *hash; const uint32_t *rsaResult; int ret; if (!bareData) { struct ImageHeader *image = (struct ImageHeader *)buf; struct AppSecSignHdr *secHdr = (struct AppSecSignHdr *)&image[1]; uint32_t grow = sizeof(*secHdr); if (!(image->aosp.flags & NANOAPP_SIGNED_FLAG)) { // this is the 1st signature in the chain; inject header, set flag buf = reallocOrDie(buf, bufUsed + grow); *pbuf = buf; image = (struct ImageHeader *)buf; secHdr = (struct AppSecSignHdr *)&image[1]; fprintf(stderr, "Generating signature header\n"); image->aosp.flags |= NANOAPP_SIGNED_FLAG; memmove((uint8_t*)&image[1] + grow, &image[1], bufUsed - sizeof(*image)); secHdr->appDataLen = bufUsed - sizeof(*image); bufUsed += grow; fprintf(stderr, "Rehashing file\n"); sha2init(&shaState); sha2processBytes(&shaState, buf, bufUsed); } else { int sigSz = bufUsed - sizeof(*image) - sizeof(*secHdr) - secHdr->appDataLen; int numSigs = sigSz / SIGNATURE_BLOCK_SIZE; if ((numSigs * (int)SIGNATURE_BLOCK_SIZE) != sigSz) { fprintf(stderr, "Invalid signature block(s) detected\n"); return 2; } else { fprintf(stderr, "Found %d appended signature(s)\n", numSigs); // generating SHA256 of the last PubKey in chain fprintf(stderr, "Hashing last signature's PubKey\n"); sha2init(&shaState); sha2processBytes(&shaState, buf + bufUsed- RSA_BYTES, RSA_BYTES); } } } else { fprintf(stderr, "Signing raw data\n"); sha2init(&shaState); sha2processBytes(&shaState, buf, bufUsed); } //update the user on the progress hash = sha2finish(&shaState); if (verbose) printHash(stderr, "SHA2 hash", hash, SHA2_HASH_WORDS); memcpy(rsa->num, hash, SHA2_HASH_SIZE); i = SHA2_HASH_WORDS; //write padding rsa->num[i++] = rand32_no_zero_bytes() << 8; //low byte here must be zero as per padding spec for (;i < RSA_LIMBS - 1; i++) rsa->num[i] = rand32_no_zero_bytes(); rsa->num[i] = (rand32_no_zero_bytes() >> 16) | 0x00020000; //as per padding spec //update the user if (verbose) printHashRev(stderr, "RSA plaintext", rsa->num, RSA_LIMBS); //do the RSA thing fprintf(stderr, "Retriculating splines..."); rsaResult = rsaPrivOp(&rsa->state, rsa->num, rsa->exponent, rsa->modulus); fprintf(stderr, "DONE\n"); //update the user if (verbose) printHashRev(stderr, "RSA cyphertext", rsaResult, RSA_LIMBS); // output in a format that our microcontroller will be able to digest easily & directly // (an array of bytes representing little-endian 32-bit words) fwrite(buf, 1, bufUsed, out); fwrite(rsaResult, 1, sizeof(uint32_t[RSA_LIMBS]), out); ret = (fwrite(rsa->modulus, 1, RSA_BYTES, out) == RSA_BYTES) ? 0 : 2; fprintf(stderr, "Status: %s (%d)\n", ret == 0 ? "success" : "failed", ret); return ret; } static void fatalUsage(const char *name, const char *msg, const char *arg) { if (msg && arg) fprintf(stderr, "Error: %s: %s\n\n", msg, arg); else if (msg) fprintf(stderr, "Error: %s\n\n", msg); fprintf(stderr, "USAGE: %s [-v] [-e ] [-m ] [-t] [-s] [-b] []\n" " -v : be verbose\n" " -b : generate binary key from text file created by OpenSSL\n" " -s : sign post-processed file\n" " -t : verify signature of signed post-processed file\n" " -e : RSA binary private key\n" " -m : RSA binary public key\n" " -r : do not parse headers, do not generate headers (with -t, -s)\n" , name); exit(1); } int main(int argc, char **argv) { uint32_t bufUsed = 0; uint8_t *buf = NULL; int ret = -1; const char **strArg = NULL; const char *appName = argv[0]; const char *posArg[2] = { NULL }; uint32_t posArgCnt = 0; FILE *out = NULL; const char *prev = NULL; bool verbose = false; bool sign = false; bool verify = false; bool txt2bin = false; bool bareData = false; const char *keyPvtFile = NULL; const char *keyPubFile = NULL; int multi = 0; struct RsaData rsa; struct ImageHeader *image; //it might not matter, but we still like to try to cleanup after ourselves (void)atexit(cleanup); for (int i = 1; i < argc; i++) { if (argv[i][0] == '-') { prev = argv[i]; if (!strcmp(argv[i], "-v")) verbose = true; else if (!strcmp(argv[i], "-s")) sign = true; else if (!strcmp(argv[i], "-t")) verify = true; else if (!strcmp(argv[i], "-b")) txt2bin = true; else if (!strcmp(argv[i], "-e")) strArg = &keyPvtFile; else if (!strcmp(argv[i], "-m")) strArg = &keyPubFile; else if (!strcmp(argv[i], "-r")) bareData = true; else fatalUsage(appName, "unknown argument", argv[i]); } else { if (strArg) { *strArg = argv[i]; strArg = NULL; } else { if (posArgCnt < 2) posArg[posArgCnt++] = argv[i]; else fatalUsage(appName, "too many positional arguments", argv[i]); } prev = 0; } } if (prev) fatalUsage(appName, "missing argument after", prev); if (!posArgCnt) fatalUsage(appName, "missing input file name", NULL); if (sign) multi++; if (verify) multi++; if (txt2bin) multi++; if (multi != 1) fatalUsage(appName, "select either -s, -t, or -b", NULL); memset(&rsa, 0, sizeof(rsa)); if (sign && !(keyPvtFile && keyPubFile)) fatalUsage(appName, "We need both PUB (-m) and PVT (-e) keys for signing", NULL); if (verify && (!keyPubFile || keyPvtFile)) fatalUsage(appName, "We only need PUB (-m) key for signature checking", NULL); if (keyPvtFile) { if (!readFile(rsa.exponent, sizeof(rsa.exponent), keyPvtFile)) fatalUsage(appName, "Can't read PVT key from", keyPvtFile); #ifdef DEBUG_KEYS else if (verbose) printHashRev(stderr, "RSA exponent", rsa.exponent, RSA_LIMBS); #endif } if (keyPubFile) { if (!readFile(rsa.modulus, sizeof(rsa.modulus), keyPubFile)) fatalUsage(appName, "Can't read PUB key from", keyPubFile); else if (verbose) printHashRev(stderr, "RSA modulus", rsa.modulus, RSA_LIMBS); } buf = loadFile(posArg[0], &bufUsed); fprintf(stderr, "Read %" PRIu32 " bytes\n", bufUsed); image = (struct ImageHeader *)buf; if (!bareData && !txt2bin) { if (bufUsed >= sizeof(*image) && image->aosp.header_version == 1 && image->aosp.magic == NANOAPP_AOSP_MAGIC && image->layout.magic == GOOGLE_LAYOUT_MAGIC) { fprintf(stderr, "Found AOSP header\n"); } else { fprintf(stderr, "Unknown binary format\n"); return 2; } } if (!posArg[1]) out = stdout; else out = fopen(posArg[1], "w"); if (!out) fatalUsage(appName, "failed to create/open output file", posArg[1]); if (sign) ret = handleSign(&buf, bufUsed, out, &rsa, verbose, bareData); else if (verify) ret = handleVerify(&buf, bufUsed, &rsa, verbose, bareData); else if (txt2bin) ret = handleConvertKey(&buf, bufUsed, out, &rsa); free(buf); fclose(out); return ret; }