/*
* Copyright 2017 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 <stdint.h>
#include <stdio.h>
#include <string.h>
#include <openssl/ec.h>
#include <openssl/ecdh.h>
#include <openssl/obj_mac.h>
#define ECDH_KEY_LEN 33
#define ECDH_SHARED_SECRET_LEN 32
/* Computes P256 ECDH shared secret using |private_key| as generated by
* generate_p256_key() as the private key, and |other_public_key| as the public
* key. Writes ECDH_SHARED_SECRET_LEN bytes to |shared_secret| and returns 0 on
* success, returns -1 on failure.
*/
int shared_secret_compute(const uint8_t* private_key,
uint32_t private_key_len,
const uint8_t other_public_key[ECDH_KEY_LEN],
uint8_t shared_secret[ECDH_SHARED_SECRET_LEN]) {
int ret = -1;
EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_COMPRESSED);
EC_POINT* other_point = EC_POINT_new(group);
EC_KEY* pkey = EC_KEY_new();
if (!EC_POINT_oct2point(group, other_point, other_public_key, ECDH_KEY_LEN,
NULL)) {
fprintf(stderr, "Deserializing other_public_key failed\n");
goto end;
}
if (!d2i_ECPrivateKey(&pkey, &private_key, private_key_len)) {
fprintf(stderr, "Deserializing private_key failed\n");
goto end;
}
EC_KEY_set_group(pkey, group);
ret = ECDH_compute_key(shared_secret, ECDH_SHARED_SECRET_LEN, other_point,
pkey, NULL);
if (ret != ECDH_SHARED_SECRET_LEN) {
fprintf(stderr, "Failed to compute shared secret: %d\n", ret);
ret = -1;
goto end;
}
ret = 0;
end:
EC_POINT_free(other_point);
EC_GROUP_free(group);
EC_KEY_free(pkey);
return ret;
}
/* Generates a new EC Key to be used for computing a shared secret with the
* device. On success, returns 0 and writes key material and number of bytes
* allocated for |*private_key| to |*private_key_len|, and writes ECDH_KEY_LEN
* bytes to public_key. Returns -1 on failure.
*/
int generate_p256_key(uint8_t** private_key, uint32_t* private_key_len,
uint8_t public_key[ECDH_KEY_LEN]) {
int pkey_len = 0, ret = -1;
EC_GROUP* group = NULL;
const EC_POINT* point = NULL;
EC_KEY* pkey = NULL;
uint8_t* tmp;
if (!private_key || !private_key_len || !public_key) {
fprintf(stderr, "Invalid input parameters\n");
return -1;
}
pkey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!EC_KEY_generate_key(pkey)) {
fprintf(stderr, "Failed to generate key\n");
return -1;
}
pkey_len = i2d_ECPrivateKey(pkey, NULL);
if (pkey_len == -1) {
fprintf(stderr, "Failed to get private key length\n");
goto end;
}
*private_key_len = pkey_len;
*private_key = (uint8_t*)malloc(pkey_len);
tmp = *private_key;
if (!i2d_ECPrivateKey(pkey, &tmp)) {
fprintf(stderr, "Failed to serialize private key\n");
goto end;
}
group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
point = EC_KEY_get0_public_key(pkey);
if (!EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED,
public_key, ECDH_KEY_LEN, NULL)) {
fprintf(stderr, "Failed to serialize public key\n");
goto end;
}
ret = 0;
end:
EC_GROUP_free(group);
EC_KEY_free(pkey);
if (ret == -1 && *private_key) {
free(*private_key);
}
return ret;
}