351 lines
11 KiB
C
351 lines
11 KiB
C
/*
|
|
* 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 <inttypes.h>
|
|
|
|
#include <nanohub/aes.h>
|
|
#include <nanohub/sha2.h>
|
|
#include <nanohub/nanohub.h>
|
|
#include <nanohub/nanoapp.h>
|
|
|
|
static FILE* urandom = NULL;
|
|
|
|
static void cleanup(void)
|
|
{
|
|
if (urandom)
|
|
fclose(urandom);
|
|
}
|
|
|
|
static void rand_bytes(void *dst, uint32_t len)
|
|
{
|
|
if (!urandom) {
|
|
urandom = fopen("/dev/urandom", "rb");
|
|
if (!urandom) {
|
|
fprintf(stderr, "Failed to open /dev/urandom. Cannot procceed!\n");
|
|
exit(2);
|
|
}
|
|
|
|
//it might not matter, but we still like to try to cleanup after ourselves
|
|
(void)atexit(cleanup);
|
|
}
|
|
|
|
if (len != fread(dst, 1, len, urandom)) {
|
|
fprintf(stderr, "Failed to read /dev/urandom. Cannot procceed!\n");
|
|
exit(2);
|
|
}
|
|
}
|
|
|
|
static int handleEncrypt(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint64_t keyId, uint32_t *key)
|
|
{
|
|
uint32_t i;
|
|
struct AesCbcContext ctx;
|
|
struct ImageHeader *image;
|
|
uint32_t *data;
|
|
struct Sha2state shaState;
|
|
bool err = false;
|
|
struct AppSecEncrHdr encr;
|
|
uint32_t padLen = 0;
|
|
uint8_t *buf = *pbuf;
|
|
|
|
encr.keyID = keyId;
|
|
|
|
//FIXME: compatibility: all the devices has google secret key with id 1, so we
|
|
// can't simply change and enforce new key naming policy;
|
|
// first, key upload mechanism shall start working, and then we can have
|
|
// all the policies we want; for now, disable enforcement
|
|
|
|
// if (encr.keyID <= 0xFFFF)
|
|
// encr.keyID = AES_KEY_ID(encr.keyID);
|
|
|
|
fprintf(stderr, "Using Key ID: %016" PRIX64 "\n", encr.keyID);
|
|
rand_bytes(encr.IV, sizeof(encr.IV));
|
|
printHash(stderr, "Using IV", encr.IV, AES_BLOCK_WORDS);
|
|
|
|
if (bufUsed <= sizeof(*image)) {
|
|
fprintf(stderr, "Input file is too small\n");
|
|
return 2;
|
|
}
|
|
|
|
encr.dataLen = bufUsed;
|
|
|
|
if (((bufUsed - sizeof(*image)) % AES_BLOCK_SIZE) != 0)
|
|
padLen = AES_BLOCK_SIZE - ((bufUsed - sizeof(*image)) % AES_BLOCK_SIZE);
|
|
|
|
if (padLen) {
|
|
reallocOrDie(buf, bufUsed + padLen);
|
|
rand_bytes(buf + bufUsed, padLen);
|
|
bufUsed += padLen;
|
|
fprintf(stderr, "Padded to %" PRIu32 " bytes\n", bufUsed);
|
|
*pbuf = buf;
|
|
}
|
|
|
|
image = (struct ImageHeader *)buf;
|
|
|
|
if (bufUsed >= sizeof(*image) && image->aosp.magic == NANOAPP_AOSP_MAGIC &&
|
|
image->aosp.header_version == 1 && image->layout.magic == GOOGLE_LAYOUT_MAGIC) {
|
|
fprintf(stderr, "Found AOSP header\n");
|
|
} else {
|
|
fprintf(stderr, "Unknown binary format\n");
|
|
return 2;
|
|
}
|
|
|
|
if ((image->aosp.flags & NANOAPP_SIGNED_FLAG) != 0) {
|
|
fprintf(stderr, "data is marked as signed; encryption is not possible for signed data\n");
|
|
return 2;
|
|
}
|
|
if ((image->aosp.flags & NANOAPP_ENCRYPTED_FLAG) != 0) {
|
|
fprintf(stderr, "data is marked as encrypted; encryption is not possible for encrypted data\n");
|
|
return 2;
|
|
}
|
|
|
|
image->aosp.flags |= NANOAPP_ENCRYPTED_FLAG;
|
|
fwrite(image, sizeof(*image), 1, out);
|
|
data = (uint32_t *)(image + 1);
|
|
fprintf(stderr, "orig len: %" PRIu32 " bytes\n", encr.dataLen);
|
|
bufUsed -= sizeof(*image);
|
|
encr.dataLen -= sizeof(*image);
|
|
fwrite(&encr, sizeof(encr), 1, out);
|
|
sha2init(&shaState);
|
|
|
|
//encrypt and emit data
|
|
aesCbcInitForEncr(&ctx, key, encr.IV);
|
|
uint32_t outBuf[AES_BLOCK_WORDS];
|
|
for (i = 0; i < bufUsed/sizeof(uint32_t); i += AES_BLOCK_WORDS) {
|
|
aesCbcEncr(&ctx, data + i, outBuf);
|
|
int32_t sz = encr.dataLen - (i * sizeof(uint32_t));
|
|
sz = sz > AES_BLOCK_SIZE ? AES_BLOCK_SIZE : sz;
|
|
if (sz > 0) {
|
|
sha2processBytes(&shaState, data + i, sz);
|
|
fwrite(outBuf, AES_BLOCK_SIZE, 1, out);
|
|
}
|
|
}
|
|
const uint32_t *hash = sha2finish(&shaState);
|
|
|
|
printHash(stderr, "HASH", hash, SHA2_HASH_WORDS);
|
|
|
|
// finally, encrypt and output SHA2 hash
|
|
aesCbcEncr(&ctx, hash, outBuf);
|
|
fwrite(outBuf, AES_BLOCK_SIZE, 1, out);
|
|
aesCbcEncr(&ctx, hash + AES_BLOCK_WORDS, outBuf);
|
|
err = fwrite(outBuf, AES_BLOCK_SIZE, 1, out) != 1;
|
|
|
|
return err ? 2 : 0;
|
|
}
|
|
|
|
static int handleDecrypt(uint8_t **pbuf, uint32_t bufUsed, FILE *out, uint32_t *key)
|
|
{
|
|
struct AesCbcContext ctx;
|
|
struct ImageHeader *image;
|
|
struct Sha2state shaState;
|
|
struct AppSecEncrHdr *encr;
|
|
uint32_t *data;
|
|
bool err = false;
|
|
uint32_t fileHash[((SHA2_HASH_WORDS + AES_BLOCK_WORDS - 1) / AES_BLOCK_WORDS) * AES_BLOCK_WORDS], fileHashSz;
|
|
uint32_t outBuf[AES_BLOCK_WORDS];
|
|
uint32_t i;
|
|
uint8_t *buf = *pbuf;
|
|
|
|
//parse header
|
|
image = (struct ImageHeader*)buf;
|
|
if (bufUsed >= (sizeof(*image) + sizeof(*encr)) &&
|
|
image->aosp.header_version == 1 && image->aosp.magic == NANOAPP_AOSP_MAGIC &&
|
|
image->layout.magic == GOOGLE_LAYOUT_MAGIC) {
|
|
fprintf(stderr, "Found AOSP header\n");
|
|
if (!(image->aosp.flags & NANOAPP_ENCRYPTED_FLAG)) {
|
|
fprintf(stderr, "data is not marked as encrypted; can't decrypt\n");
|
|
return 2;
|
|
}
|
|
image->aosp.flags &= ~NANOAPP_ENCRYPTED_FLAG;
|
|
data = (uint32_t *)(image + 1);
|
|
encr = (struct AppSecEncrHdr *)data;
|
|
data = (uint32_t *)(encr + 1);
|
|
bufUsed -= sizeof(*image) + sizeof(*encr);
|
|
} else {
|
|
fprintf(stderr, "Unknown binary format\n");
|
|
return 2;
|
|
}
|
|
|
|
if (encr->dataLen > bufUsed) {
|
|
fprintf(stderr, "Claimed output size of %" PRIu32 "b invalid\n", encr->dataLen);
|
|
return 2;
|
|
}
|
|
fprintf(stderr, "Original size %" PRIu32 "b (%" PRIu32 "b of padding present)\n",
|
|
encr->dataLen, bufUsed - encr->dataLen);
|
|
if (!encr->keyID) {
|
|
fprintf(stderr, "Input data has invalid key ID\n");
|
|
return 2;
|
|
}
|
|
fprintf(stderr, "Using Key ID: %016" PRIX64 "\n", encr->keyID);
|
|
printHash(stderr, "Using IV", encr->IV, AES_BLOCK_WORDS);
|
|
|
|
fwrite(image, sizeof(*image), 1, out);
|
|
//decrypt and emit data
|
|
aesCbcInitForDecr(&ctx, key, encr->IV);
|
|
fileHashSz = 0;
|
|
sha2init(&shaState);
|
|
for (i = 0; i < bufUsed / sizeof(uint32_t); i += AES_BLOCK_WORDS) {
|
|
int32_t size = encr->dataLen - i * sizeof(uint32_t);
|
|
aesCbcDecr(&ctx, data + i, outBuf);
|
|
if (size > AES_BLOCK_SIZE)
|
|
size = AES_BLOCK_SIZE;
|
|
if (size > 0) {
|
|
sha2processBytes(&shaState, outBuf, size);
|
|
err = fwrite(outBuf, size, 1, out) != 1;
|
|
} else if (fileHashSz < sizeof(fileHash)) {
|
|
memcpy(((uint8_t*)fileHash) + fileHashSz, outBuf, AES_BLOCK_SIZE);
|
|
fileHashSz += AES_BLOCK_SIZE;
|
|
} else {
|
|
fprintf(stderr, "Too much input data\n");
|
|
return 2;
|
|
}
|
|
}
|
|
const uint32_t *calcHash = sha2finish(&shaState);
|
|
printHash(stderr, "HASH [calc]", calcHash, SHA2_HASH_WORDS);
|
|
printHash(stderr, "HASH [file]", fileHash, SHA2_HASH_WORDS);
|
|
|
|
bool verify = memcmp(fileHash, calcHash, SHA2_HASH_SIZE) == 0;
|
|
fprintf(stderr, "hash verification: %s\n", verify ? "passed" : "failed");
|
|
if (!verify)
|
|
return 2;
|
|
|
|
if (!err)
|
|
fprintf(stderr, "Done\n");
|
|
|
|
return err ? 2 : 0;
|
|
}
|
|
|
|
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 [-e] [-d] [-i <key id>] [-k <key file>] <input file> [<output file>]\n"
|
|
" -i : 64-bit hex number != 0\n"
|
|
" -e : encrypt post-processed file\n"
|
|
" -d : decrypt encrypted post-processed file\n"
|
|
" -k : binary file (32 byte size) containing AES-256 secret key\n"
|
|
, name);
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
uint32_t bufUsed = 0;
|
|
uint8_t *buf = NULL;
|
|
uint64_t keyId = 0;
|
|
int ret = -1;
|
|
uint32_t *u32Arg = NULL;
|
|
uint64_t *u64Arg = NULL;
|
|
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 decrypt = false;
|
|
bool encrypt = false;
|
|
const char *keyFile = NULL;
|
|
int multi = 0;
|
|
uint32_t key[AES_KEY_WORDS];
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
char *end = NULL;
|
|
if (argv[i][0] == '-') {
|
|
prev = argv[i];
|
|
if (!strcmp(argv[i], "-d"))
|
|
decrypt = true;
|
|
else if (!strcmp(argv[i], "-e"))
|
|
encrypt = true;
|
|
else if (!strcmp(argv[i], "-k"))
|
|
strArg = &keyFile;
|
|
else if (!strcmp(argv[i], "-i"))
|
|
u64Arg = &keyId;
|
|
else
|
|
fatalUsage(appName, "unknown argument", argv[i]);
|
|
} else {
|
|
if (u64Arg) {
|
|
uint64_t tmp = strtoull(argv[i], &end, 16);
|
|
if (*end == '\0')
|
|
*u64Arg = tmp;
|
|
u64Arg = NULL;
|
|
} else if (u32Arg) {
|
|
uint32_t tmp = strtoul(argv[i], &end, 16);
|
|
if (*end == '\0')
|
|
*u32Arg = tmp;
|
|
u32Arg = NULL;
|
|
} 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 (encrypt)
|
|
multi++;
|
|
if (decrypt)
|
|
multi++;
|
|
|
|
if (multi != 1)
|
|
fatalUsage(appName, "select either -d or -e", NULL);
|
|
|
|
if (!keyFile)
|
|
fatalUsage(appName, "no key file given", NULL);
|
|
|
|
if (encrypt && !keyId)
|
|
fatalUsage(appName, "Non-zero Key ID must be given to encrypt data", NULL);
|
|
|
|
//read key
|
|
if (!readFile(key, sizeof(key), keyFile))
|
|
fatalUsage(appName, "Key file does not exist or has incorrect size", keyFile);
|
|
|
|
buf = loadFile(posArg[0], &bufUsed);
|
|
fprintf(stderr, "Read %" PRIu32 " bytes\n", bufUsed);
|
|
|
|
if (!posArg[1])
|
|
out = stdout;
|
|
else
|
|
out = fopen(posArg[1], "w");
|
|
if (!out)
|
|
fatalUsage(appName, "failed to create/open output file", posArg[1]);
|
|
|
|
if (encrypt)
|
|
ret = handleEncrypt(&buf, bufUsed, out, keyId, key);
|
|
else if (decrypt)
|
|
ret = handleDecrypt(&buf, bufUsed, out, key);
|
|
|
|
free(buf);
|
|
fclose(out);
|
|
return ret;
|
|
}
|