/* Copyright 2017 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rma_auth.h" #include "curve25519.h" #include "sha256.h" #include "base32.h" #define EC_COORDINATE_SZ 32 #define EC_PRIV_KEY_SZ 32 #define EC_P256_UNCOMPRESSED_PUB_KEY_SZ (EC_COORDINATE_SZ * 2 + 1) #define EC_P256_COMPRESSED_PUB_KEY_SZ (EC_COORDINATE_SZ + 1) #define SERVER_ADDRESS \ "https://www.google.com/chromeos/partner/console/cr50reset/request" /* Test server keys for x25519 and p256 curves. */ static const uint8_t rma_test_server_x25519_public_key[] = { 0x03, 0xae, 0x2d, 0x2c, 0x06, 0x23, 0xe0, 0x73, 0x0d, 0xd3, 0xb7, 0x92, 0xac, 0x54, 0xc5, 0xfd, 0x7e, 0x9c, 0xf0, 0xa8, 0xeb, 0x7e, 0x2a, 0xb5, 0xdb, 0xf4, 0x79, 0x5f, 0x8a, 0x0f, 0x28, 0x3f }; static const uint8_t rma_test_server_x25519_private_key[] = { 0x47, 0x3b, 0xa5, 0xdb, 0xc4, 0xbb, 0xd6, 0x77, 0x20, 0xbd, 0xd8, 0xbd, 0xc8, 0x7a, 0xbb, 0x07, 0x03, 0x79, 0xba, 0x7b, 0x52, 0x8c, 0xec, 0xb3, 0x4d, 0xaa, 0x69, 0xf5, 0x65, 0xb4, 0x31, 0xad }; #define RMA_TEST_SERVER_X25519_KEY_ID 0x10 #define RMA_PROD_SERVER_X25519_KEY_ID 0 /* * P256 curve keys, generated using openssl as follows: * * openssl ecparam -name prime256v1 -genkey -out key.pem * openssl ec -in key.pem -text -noout */ static const uint8_t rma_test_server_p256_private_key[] = { 0x54, 0xb0, 0x82, 0x92, 0x54, 0x92, 0xfc, 0x4a, 0xa7, 0x6b, 0xea, 0x8f, 0x30, 0xcc, 0xf7, 0x3d, 0xa2, 0xf6, 0xa7, 0xad, 0xf0, 0xec, 0x7d, 0xe9, 0x26, 0x75, 0xd1, 0xec, 0xde, 0x20, 0x8f, 0x81 }; /* * P256 public key in full form, x and y coordinates with a single byte * prefix, 65 bytes total. */ static const uint8_t rma_test_server_p256_public_key[] = { 0x04, 0xe7, 0xbe, 0x37, 0xaa, 0x68, 0xca, 0xcc, 0x68, 0xf4, 0x8c, 0x56, 0x65, 0x5a, 0xcb, 0xf8, 0xf4, 0x65, 0x3c, 0xd3, 0xc6, 0x1b, 0xae, 0xd6, 0x51, 0x7a, 0xcc, 0x00, 0x8d, 0x59, 0x6d, 0x1b, 0x0a, 0x66, 0xe8, 0x68, 0x5e, 0x6a, 0x82, 0x19, 0x81, 0x76, 0x84, 0x92, 0x7f, 0x8d, 0xb2, 0xbe, 0xf5, 0x39, 0x50, 0xd5, 0xfe, 0xee, 0x00, 0x67, 0xcf, 0x40, 0x5f, 0x68, 0x12, 0x83, 0x4f, 0xa4, 0x35 }; #define RMA_TEST_SERVER_P256_KEY_ID 0x20 #define RMA_PROD_SERVER_P256_KEY_ID 0x01 /* Default values which can change based on command line arguments. */ static uint8_t server_key_id = RMA_TEST_SERVER_X25519_KEY_ID; static uint8_t board_id[4] = {'Z', 'Z', 'C', 'R'}; static uint8_t device_id[8] = {'T', 'H', 'X', 1, 1, 3, 8, 0xfe}; static uint8_t hw_id[20] = "TESTSAMUS1234"; static char challenge[RMA_CHALLENGE_BUF_SIZE]; static char authcode[RMA_AUTHCODE_BUF_SIZE]; static char *progname; static char *short_opts = "a:b:c:d:hpk:tw:"; static const struct option long_opts[] = { /* name hasarg *flag val */ {"auth_code", 1, NULL, 'a'}, {"board_id", 1, NULL, 'b'}, {"challenge", 1, NULL, 'c'}, {"device_id", 1, NULL, 'd'}, {"help", 0, NULL, 'h'}, {"hw_id", 1, NULL, 'w'}, {"key_id", 1, NULL, 'k'}, {"p256", 0, NULL, 'p'}, {"test", 0, NULL, 't'}, {}, }; void panic_assert_fail(const char *fname, int linenum); void rand_bytes(void *buffer, size_t len); int safe_memcmp(const void *s1, const void *s2, size_t size); void panic_assert_fail(const char *fname, int linenum) { printf("\nASSERTION FAILURE at %s:%d\n", fname, linenum); } int safe_memcmp(const void *s1, const void *s2, size_t size) { const uint8_t *us1 = s1; const uint8_t *us2 = s2; int result = 0; if (size == 0) return 0; while (size--) result |= *us1++ ^ *us2++; return result != 0; } void rand_bytes(void *buffer, size_t len) { RAND_bytes(buffer, len); } /* * Generate a p256 key pair and calculate the shared secret based on our * private key and the server public key. * * Return the X coordinate of the generated public key and the shared secret. * * @pub_key - the compressed public key without the prefix; by convention * between RMA client and server the generated pubic key would * always have prefix of 0x03, (the Y coordinate value is odd), so * it is omitted from the key blob, which allows to keep the blob * size at 32 bytes. * @secret_seed - the product of multiplying of the server point by our * private key, only the 32 bytes of X coordinate are returned. */ static void p256_key_and_secret_seed(uint8_t pub_key[32], uint8_t secret_seed[32]) { const EC_GROUP *group; EC_KEY *key; EC_POINT *pub; EC_POINT *secret_point; uint8_t buf[EC_P256_UNCOMPRESSED_PUB_KEY_SZ]; /* Prepare structures to operate on. */ key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); group = EC_KEY_get0_group(key); pub = EC_POINT_new(group); /* * We might have to try multiple times, until the Y coordinate is an * odd value as required by convention. */ do { EC_KEY_generate_key(key); /* Extract public key into an octal array. */ EC_POINT_point2oct(group, EC_KEY_get0_public_key(key), POINT_CONVERSION_UNCOMPRESSED, buf, sizeof(buf), NULL); /* If Y coordinate is an odd value, we are done. */ } while (!(buf[sizeof(buf) - 1] & 1)); /* Copy X coordinate out. */ memcpy(pub_key, buf + 1, 32); /* * We have our private key and the server's point coordinates (aka * server public key). Let's multiply the coordinates by our private * key to get the shared secret. */ /* Load raw public key into the point structure. */ EC_POINT_oct2point(group, pub, rma_test_server_p256_public_key, sizeof(rma_test_server_p256_public_key), NULL); secret_point = EC_POINT_new(group); /* Multiply server public key by our private key. */ EC_POINT_mul(group, secret_point, 0, pub, EC_KEY_get0_private_key(key), 0); /* Pull the result back into the octal buffer. */ EC_POINT_point2oct(group, secret_point, POINT_CONVERSION_UNCOMPRESSED, buf, sizeof(buf), NULL); /* * Copy X coordinate into the output to use as the shared secret * seed. */ memcpy(secret_seed, buf + 1, 32); /* release resources */ EC_KEY_free(key); EC_POINT_free(pub); EC_POINT_free(secret_point); } /* * When imitating server side, calculate the secret value given the client's * compressed public key (X coordinate only with 0x03 prefix implied) and * knowing our (server) private key. * * @secret - array to return the X coordinate of the calculated point. * @raw_pub_key - X coordinate of the point calculated by the client, 0x03 * prefix implied. */ static void p256_calculate_secret(uint8_t secret[32], const uint8_t raw_pub_key[32]) { uint8_t raw_pub_key_x[EC_P256_COMPRESSED_PUB_KEY_SZ]; EC_KEY *key; const uint8_t *kp = raw_pub_key_x; EC_POINT *secret_point; const EC_GROUP *group; BIGNUM *priv; uint8_t buf[EC_P256_UNCOMPRESSED_PUB_KEY_SZ]; /* Express server private key as a BN. */ priv = BN_new(); BN_bin2bn(rma_test_server_p256_private_key, EC_PRIV_KEY_SZ, priv); /* * Populate a p256 key structure based on the compressed * representation of the client's public key. */ raw_pub_key_x[0] = 3; /* Implied by convention. */ memcpy(raw_pub_key_x + 1, raw_pub_key, sizeof(raw_pub_key_x) - 1); key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); group = EC_KEY_get0_group(key); key = o2i_ECPublicKey(&key, &kp, sizeof(raw_pub_key_x)); /* This is where the multiplication result will go. */ secret_point = EC_POINT_new(group); /* Multiply client's point by our private key. */ EC_POINT_mul(group, secret_point, 0, EC_KEY_get0_public_key(key), priv, 0); /* Pull the result back into the octal buffer. */ EC_POINT_point2oct(group, secret_point, POINT_CONVERSION_UNCOMPRESSED, buf, sizeof(buf), NULL); /* Copy X coordinate into the output to use as the shared secret. */ memcpy(secret, buf + 1, 32); } static int rma_server_side(const char *generated_challenge) { int key_id, version; uint8_t secret[32]; uint8_t hmac[32]; struct rma_challenge c; uint8_t *cptr = (uint8_t *)&c; /* Convert the challenge back into binary */ if (base32_decode(cptr, 8 * sizeof(c), generated_challenge, 9) != 8 * sizeof(c)) { printf("Error decoding challenge\n"); return -1; } version = RMA_CHALLENGE_GET_VERSION(c.version_key_id); key_id = RMA_CHALLENGE_GET_KEY_ID(c.version_key_id); printf("Challenge: %s\n", generated_challenge); printf("Version: %d\n", version); printf("Server KeyID: %d\n", key_id); if (version != RMA_CHALLENGE_VERSION) printf("Unsupported challenge version %d\n", version); /* Calculate the shared secret, use curve based on the key ID. */ switch (key_id) { case RMA_PROD_SERVER_X25519_KEY_ID: printf("Unsupported Prod KeyID %d\n", key_id); case RMA_TEST_SERVER_X25519_KEY_ID: X25519(secret, rma_test_server_x25519_private_key, c.device_pub_key); break; case RMA_PROD_SERVER_P256_KEY_ID: printf("Unsupported Prod KeyID %d\n", key_id); case RMA_TEST_SERVER_P256_KEY_ID: p256_calculate_secret(secret, c.device_pub_key); break; default: printf("Unknown KeyID %d\n", key_id); return 1; } /* * Auth code is a truncated HMAC of the ephemeral public key, BoardID, * and DeviceID. */ hmac_SHA256(hmac, secret, sizeof(secret), cptr + 1, sizeof(c) - 1); if (base32_encode(authcode, RMA_AUTHCODE_BUF_SIZE, hmac, RMA_AUTHCODE_CHARS * 5, 0)) { printf("Error encoding auth code\n"); return -1; } printf("Authcode: %s\n", authcode); return 0; }; static int rma_create_test_challenge(int p256_mode) { uint8_t temp[32]; /* Private key or HMAC */ uint8_t secret_seed[32]; struct rma_challenge c; uint8_t *cptr = (uint8_t *)&c; uint32_t bid; /* Clear the current challenge and authcode, if any */ memset(challenge, 0, sizeof(challenge)); memset(authcode, 0, sizeof(authcode)); memset(&c, 0, sizeof(c)); c.version_key_id = RMA_CHALLENGE_VKID_BYTE( RMA_CHALLENGE_VERSION, server_key_id); memcpy(&bid, board_id, sizeof(bid)); bid = be32toh(bid); memcpy(c.board_id, &bid, sizeof(c.board_id)); memcpy(c.device_id, device_id, sizeof(c.device_id)); if (p256_mode) { p256_key_and_secret_seed(c.device_pub_key, secret_seed); } else { /* Calculate a new ephemeral key pair */ X25519_keypair(c.device_pub_key, temp); /* Calculate the shared secret seed. */ X25519(secret_seed, temp, rma_test_server_x25519_public_key); } /* Encode the challenge */ if (base32_encode(challenge, sizeof(challenge), cptr, 8 * sizeof(c), 9)) return 1; /* * Auth code is a truncated HMAC of the ephemeral public key, BoardID, * and DeviceID. Those are all in the right order in the challenge * struct, after the version/key id byte. */ hmac_SHA256(temp, secret_seed, sizeof(secret_seed), cptr + 1, sizeof(c) - 1); if (base32_encode(authcode, sizeof(authcode), temp, RMA_AUTHCODE_CHARS * 5, 0)) return 1; return 0; } int rma_try_authcode(const char *code) { return safe_memcmp(authcode, code, RMA_AUTHCODE_CHARS); } static void dump_key(const char *title, const uint8_t *key, size_t key_size) { size_t i; const int bytes_per_line = 8; printf("\n\n\%s\n", title); for (i = 0; i < key_size; i++) printf("%02x%c", key[i], ((i + 1) % bytes_per_line) ? ' ':'\n'); if (i % bytes_per_line) printf("\n"); } static void print_params(int p_flag) { int i; const uint8_t *priv_key; const uint8_t *pub_key; int key_id; size_t pub_key_size; printf("\nBoard Id:\n"); for (i = 0; i < 4; i++) printf("%c ", board_id[i]); printf("\n\nDevice Id:\n"); for (i = 0; i < 3; i++) printf("%c ", device_id[i]); for (i = 3; i < 8; i++) printf("%02x ", device_id[i]); if (p_flag) { priv_key = rma_test_server_p256_private_key; pub_key = rma_test_server_p256_public_key; pub_key_size = sizeof(rma_test_server_p256_public_key); key_id = RMA_TEST_SERVER_P256_KEY_ID; } else { priv_key = rma_test_server_x25519_private_key; pub_key = rma_test_server_x25519_public_key; pub_key_size = sizeof(rma_test_server_x25519_public_key); key_id = RMA_TEST_SERVER_X25519_KEY_ID; } printf("\n\nServer Key Id:\n"); printf("%02x", key_id); /* Both private keys are of the same size */ dump_key("Server Private Key:", priv_key, EC_PRIV_KEY_SZ); dump_key("Server Public Key:", pub_key, pub_key_size); printf("\nChallenge:\n"); for (i = 0; i < RMA_CHALLENGE_CHARS; i++) { printf("%c", challenge[i]); if (((i + 1) % 5) == 0) printf(" "); if (((i + 1) % 40) == 0) printf("\n"); } printf("\nAuthorization Code:\n"); for (i = 0; i < RMA_AUTHCODE_BUF_SIZE; i++) printf("%c", authcode[i]); printf("\n\nChallenge String:\n"); printf("%s?challenge=", SERVER_ADDRESS); for (i = 0; i < RMA_CHALLENGE_CHARS; i++) printf("%c", challenge[i]); printf("&hwid=%s\n", hw_id); printf("\n"); } static void usage(void) { printf("\nUsage: %s [--p256] --key_id --board_id " "--device_id --hw_id |\n" " --auth_code |\n" " --challenge \n" "\n" "This is used to generate the cr50 or server responses for rma " "open.\n" "The cr50 side can be used to generate a challenge response " "and sends authoriztion code to reset device.\n" "The server side can generate an authcode from cr50's " "rma challenge.\n" "\n" " -c,--challenge The challenge generated by cr50\n" " -k,--key_id Index of the server private key\n" " -b,--board_id BoardID type field\n" " -d,--device_id Device-unique identifier\n" " -a,--auth_code Reset authorization code\n" " -w,--hw_id Hardware id\n" " -h,--help Show this message\n" " -p,--p256 Use prime256v1 curve instead of x25519\n" " -t,--test " "Generate challenge using default test inputs\n" "\n", progname); } static int atoh(char *v) { char hn; char ln; hn = toupper(*v); ln = toupper(*(v + 1)); hn -= (isdigit(hn) ? '0' : '7'); ln -= (isdigit(ln) ? '0' : '7'); if ((hn < 0 || hn > 0xf) || (ln < 0 || ln > 0xf)) return 0; return (hn << 4) | ln; } static int set_server_key_id(char *id) { /* verify length */ if (strlen(id) != 2) return 1; /* verify digits */ if (!isxdigit(*id) || !isxdigit(*(id+1))) return 1; server_key_id = atoh(id); return 0; } static int set_board_id(char *id) { int i; /* verify length */ if (strlen(id) != 8) return 1; /* verify digits */ for (i = 0; i < 8; i++) if (!isxdigit(*(id + i))) return 1; for (i = 0; i < 4; i++) board_id[i] = atoh((id + (i*2))); return 0; } static int set_device_id(char *id) { int i; /* verify length */ if (strlen(id) != 16) return 1; for (i = 0; i < 16; i++) if (!isxdigit(*(id + i))) return 1; for (i = 0; i < 8; i++) device_id[i] = atoh((id + (i*2))); return 0; } static int set_hw_id(char *id) { int i; int len; len = strlen(id); if (len > 20) len = 20; for (i = 0; i < 20; i++) hw_id[i] = *(id + i); return 0; } static int set_auth_code(char *code) { int i; if (strlen(code) != 8) return 1; for (i = 0; i < 8; i++) authcode[i] = *(code + i); authcode[i] = 0; return 0; } int main(int argc, char **argv) { int a_flag = 0; int b_flag = 0; int d_flag = 0; int k_flag = 0; int p_flag = 0; int t_flag = 0; int w_flag = 0; int i; progname = strrchr(argv[0], '/'); if (progname) progname++; else progname = argv[0]; opterr = 0; while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) { switch (i) { case 't': t_flag = 1; break; case 'c': return rma_server_side(optarg); case 'k': if (set_server_key_id(optarg)) { printf("Malformed key id\n"); return 1; } k_flag = 1; break; case 'b': if (set_board_id(optarg)) { printf("Malformed board id\n"); return 1; } b_flag = 1; break; case 'd': if (set_device_id(optarg)) { printf("Malformed device id\n"); return 1; } d_flag = 1; break; case 'a': if (set_auth_code(optarg)) { printf("Malformed authorization code\n"); return 1; } a_flag = 1; break; case 'w': if (set_hw_id(optarg)) { printf("Malformed hardware id\n"); return 1; } w_flag = 1; break; case 'h': usage(); return 0; case 0: /* auto-handled option */ break; case '?': if (optopt) printf("Unrecognized option: -%c\n", optopt); else printf("Unrecognized option: %s\n", argv[optind - 1]); break; case ':': printf("Missing argument to %s\n", argv[optind - 1]); break; case 'p': p_flag = 1; server_key_id = RMA_TEST_SERVER_P256_KEY_ID; break; default: printf("Internal error at %s:%d\n", __FILE__, __LINE__); return 1; } } if (a_flag) { FILE *acode; char verify_authcode[RMA_AUTHCODE_BUF_SIZE]; int rv; acode = fopen("/tmp/authcode", "r"); if (acode == NULL) { printf("Please generate challenge\n"); return 1; } rv = fread(verify_authcode, 1, RMA_AUTHCODE_BUF_SIZE, acode); if (rv != RMA_AUTHCODE_BUF_SIZE) { printf("Error reading saved authcode\n"); return 1; } if (strcmp(verify_authcode, authcode) == 0) printf("Code Accepted\n"); else printf("Invalid Code\n"); } else { if (!t_flag) { /* Use default values */ if (!k_flag || !b_flag || !d_flag || !w_flag) { printf("server-side: Flag -c is mandatory\n"); printf("cr50-side: Flags -k, -b, -d, and -w " "are mandatory\n"); return 1; } } rma_create_test_challenge(p_flag); { FILE *acode; acode = fopen("/tmp/authcode", "w"); if (acode < 0) return 1; fwrite(authcode, 1, RMA_AUTHCODE_BUF_SIZE, acode); fclose(acode); } print_params(p_flag); } return 0; }