| /* |
| * 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 <stdbool.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| #include <nanohub/nanohub.h> |
| #include <nanohub/nanoapp.h> |
| #include <nanohub/sha2.h> |
| #include <nanohub/rsa.h> |
| |
| 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 <endian.h> |
| #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 <pvt key>] [-m <pub key>] [-t] [-s] [-b] <input file> [<output file>]\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; |
| } |