mirror of
https://github.com/polhenarejos/pico-hsm.git
synced 2026-01-17 01:18:06 +00:00
and add the Enterprise / Commercial licensing option.
Main changes:
- Replace GPLv3 headers with AGPLv3 headers in source files.
- Update LICENSE file to the full AGPLv3 text.
- Add ENTERPRISE.md describing the dual-licensing model:
* Community Edition: AGPLv3 (strong copyleft, including network use).
* Enterprise / Commercial Edition: proprietary license for production /
multi-user / OEM use without the obligation to disclose derivative code.
- Update README with a new "License and Commercial Use" section pointing to
ENTERPRISE.md and clarifying how companies can obtain a commercial license.
Why this change:
- AGPLv3 ensures that modified versions offered as a service or deployed
in production environments must provide corresponding source code.
- The Enterprise / Commercial edition provides organizations with an
alternative proprietary license that allows internal, large-scale, or OEM
use (bulk provisioning, policy enforcement, inventory / revocation,
custom attestation, signed builds) without AGPL disclosure obligations.
This commit formally marks the first release that is dual-licensed:
AGPLv3 for the Community Edition and a proprietary commercial license
for Enterprise customers.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
326 lines
12 KiB
C
326 lines
12 KiB
C
/*
|
|
* This file is part of the Pico HSM distribution (https://github.com/polhenarejos/pico-hsm).
|
|
* Copyright (c) 2022 Pol Henarejos.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "sc_hsm.h"
|
|
#include "files.h"
|
|
#include "random.h"
|
|
#include "kek.h"
|
|
#include "asn1.h"
|
|
|
|
const uint8_t *k1_seed = (const uint8_t *) "Bitcoin seed";
|
|
const uint8_t *p1_seed = (const uint8_t *) "Nist256p1 seed";
|
|
const uint8_t *sym_seed = (const uint8_t *) "Symmetric key seed";
|
|
mbedtls_ecp_keypair hd_context = { 0 };
|
|
uint8_t hd_keytype = 0;
|
|
|
|
int node_derive_bip_child(const mbedtls_ecp_keypair *parent,
|
|
const uint8_t cpar[32],
|
|
const uint8_t *i,
|
|
mbedtls_ecp_keypair *child,
|
|
uint8_t cchild[32]) {
|
|
uint8_t data[1 + 32 + 4], I[64], *iL = I, *iR = I + 32;
|
|
mbedtls_mpi il, kchild;
|
|
mbedtls_mpi_init(&il);
|
|
mbedtls_mpi_init(&kchild);
|
|
if (i[0] >= 0x80) {
|
|
if (mbedtls_mpi_cmp_int(&parent->d, 0) == 0) {
|
|
return PICOKEY_ERR_NULL_PARAM;
|
|
}
|
|
data[0] = 0x00;
|
|
mbedtls_mpi_write_binary(&parent->d, data + 1, 32);
|
|
}
|
|
else {
|
|
size_t olen = 0;
|
|
mbedtls_ecp_point_write_binary(&parent->grp,
|
|
&parent->Q,
|
|
MBEDTLS_ECP_PF_COMPRESSED,
|
|
&olen,
|
|
data,
|
|
33);
|
|
}
|
|
do {
|
|
memcpy(data + 33, i, 4);
|
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512),
|
|
cpar,
|
|
32,
|
|
data,
|
|
sizeof(data),
|
|
I);
|
|
mbedtls_mpi_read_binary(&il, iL, 32);
|
|
mbedtls_mpi_add_mpi(&kchild, &il, &parent->d);
|
|
mbedtls_mpi_mod_mpi(&kchild, &kchild, &parent->grp.N);
|
|
data[0] = 0x01;
|
|
memcpy(data + 1, iR, 32);
|
|
} while (mbedtls_mpi_cmp_mpi(&il,
|
|
&parent->grp.N) != -1 || mbedtls_mpi_cmp_int(&kchild, 0) == 0);
|
|
mbedtls_mpi_copy(&child->d, &kchild);
|
|
mbedtls_ecp_mul(&child->grp, &child->Q, &child->d, &child->grp.G, random_gen, NULL);
|
|
memcpy(cchild, iR, 32);
|
|
mbedtls_mpi_free(&il);
|
|
mbedtls_mpi_free(&kchild);
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int sha256_ripemd160(const uint8_t *buffer, size_t buffer_len, uint8_t *output) {
|
|
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buffer, buffer_len, output);
|
|
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_RIPEMD160), output, 32, output);
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int sha256_sha256(const uint8_t *buffer, size_t buffer_len, uint8_t *output) {
|
|
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buffer, buffer_len, output);
|
|
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), output, 32, output);
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int node_fingerprint_bip(mbedtls_ecp_keypair *ctx, uint8_t fingerprint[4]) {
|
|
size_t olen = 0;
|
|
uint8_t buffer[33];
|
|
mbedtls_ecp_point_write_binary(&ctx->grp,
|
|
&ctx->Q,
|
|
MBEDTLS_ECP_PF_COMPRESSED,
|
|
&olen,
|
|
buffer,
|
|
sizeof(buffer));
|
|
sha256_ripemd160(buffer, sizeof(buffer), buffer);
|
|
memcpy(fingerprint, buffer, 4);
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int node_fingerprint_slip(mbedtls_ecp_keypair *ctx, uint8_t fingerprint[4]) {
|
|
uint8_t buffer[32];
|
|
mbedtls_mpi_write_binary(&ctx->d, buffer, sizeof(buffer));
|
|
sha256_ripemd160(buffer, sizeof(buffer), buffer);
|
|
memcpy(fingerprint, buffer, 4);
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int load_master_bip(uint16_t mid, mbedtls_ecp_keypair *ctx, uint8_t chain[32],
|
|
uint8_t key_type[1]) {
|
|
uint8_t mkey[65];
|
|
mbedtls_ecp_keypair_init(ctx);
|
|
file_t *ef = search_file(EF_MASTER_SEED | mid);
|
|
if (!file_has_data(ef)) {
|
|
return PICOKEY_ERR_FILE_NOT_FOUND;
|
|
}
|
|
memcpy(mkey, file_get_data(ef), sizeof(mkey));
|
|
int r = mkek_decrypt(mkey + 1,
|
|
sizeof(mkey) - 1);
|
|
if (r != PICOKEY_OK) {
|
|
return PICOKEY_EXEC_ERROR;
|
|
}
|
|
if (mkey[0] == 0x1 || mkey[0] == 0x2) {
|
|
if (mkey[0] == 0x1) {
|
|
mbedtls_ecp_group_load(&ctx->grp, MBEDTLS_ECP_DP_SECP256K1);
|
|
}
|
|
else if (mkey[0] == 0x2) {
|
|
mbedtls_ecp_group_load(&ctx->grp, MBEDTLS_ECP_DP_SECP256R1);
|
|
}
|
|
else {
|
|
return PICOKEY_WRONG_DATA;
|
|
}
|
|
|
|
mbedtls_mpi_read_binary(&ctx->d, mkey + 1, 32);
|
|
memcpy(chain, mkey + 33, 32);
|
|
mbedtls_ecp_mul(&ctx->grp, &ctx->Q, &ctx->d, &ctx->grp.G, random_gen, NULL);
|
|
}
|
|
else if (mkey[0] == 0x3) {
|
|
mbedtls_mpi_read_binary(&ctx->d, mkey + 33, 32);
|
|
memcpy(chain, mkey + 1, 32);
|
|
}
|
|
key_type[0] = mkey[0];
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int node_derive_path(const uint8_t *path,
|
|
uint16_t path_len,
|
|
mbedtls_ecp_keypair *ctx,
|
|
uint8_t chain[32],
|
|
uint8_t fingerprint[4],
|
|
uint8_t *nodes,
|
|
uint8_t last_node[4],
|
|
uint8_t key_type[1]) {
|
|
uint8_t *tag_data = NULL, *p = NULL;
|
|
uint16_t tag_len = 0, tag = 0x0;
|
|
uint8_t node = 0, N[64] = { 0 };
|
|
int r = 0;
|
|
memset(last_node, 0, 4);
|
|
memset(fingerprint, 0, 4);
|
|
|
|
asn1_ctx_t ctxi;
|
|
asn1_ctx_init((uint8_t *)path, path_len, &ctxi);
|
|
for (; walk_tlv(&ctxi, &p, &tag, &tag_len, &tag_data); node++) {
|
|
if (tag == 0x02) {
|
|
if ((node == 0 && tag_len != 1) || (node != 0 && tag_len != 4)) {
|
|
return PICOKEY_WRONG_DATA;
|
|
}
|
|
if (node == 0) {
|
|
if ((r = load_master_bip(tag_data[0], ctx, chain, key_type)) != PICOKEY_OK) {
|
|
return r;
|
|
}
|
|
}
|
|
else if (node > 0) {
|
|
node_fingerprint_bip(ctx, fingerprint);
|
|
if ((r = node_derive_bip_child(ctx, chain, tag_data, ctx, chain)) != PICOKEY_OK) {
|
|
return r;
|
|
}
|
|
memcpy(last_node, tag_data, 4);
|
|
}
|
|
}
|
|
else if (tag == 0x04) {
|
|
if (node == 0) {
|
|
return PICOKEY_WRONG_DATA;
|
|
}
|
|
else if (node > 0) {
|
|
node_fingerprint_slip(ctx, fingerprint);
|
|
*(tag_data - 1) = 0;
|
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512),
|
|
chain,
|
|
32,
|
|
tag_data - 1,
|
|
tag_len + 1,
|
|
N);
|
|
memcpy(chain, N, 32);
|
|
mbedtls_mpi_read_binary(&ctx->d, N + 32, 32);
|
|
}
|
|
}
|
|
}
|
|
if (nodes) {
|
|
*nodes = node;
|
|
}
|
|
return PICOKEY_OK;
|
|
}
|
|
|
|
int cmd_bip_slip() {
|
|
uint8_t p1 = P1(apdu), p2 = P2(apdu);
|
|
if (p1 == 0x1 || p1 == 0x2 || p1 == 0x3) { // Master generation (K1 and P1)
|
|
if (p2 >= 10) {
|
|
return SW_INCORRECT_P1P2();
|
|
}
|
|
uint8_t mkey[65], *seed = mkey + 1, seed_len = 64;
|
|
const uint8_t *key_seed = NULL;
|
|
mbedtls_mpi il;
|
|
mbedtls_mpi_init(&il);
|
|
mbedtls_ecp_group grp;
|
|
mbedtls_ecp_group_init(&grp);
|
|
if (p1 == 0x1) {
|
|
mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256K1);
|
|
key_seed = k1_seed;
|
|
}
|
|
else if (p1 == 0x2) {
|
|
mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
|
|
key_seed = p1_seed;
|
|
}
|
|
else if (p1 == 0x3) {
|
|
key_seed = sym_seed;
|
|
}
|
|
if (apdu.nc == 0) {
|
|
seed_len = 64;
|
|
random_gen(NULL, seed, seed_len);
|
|
}
|
|
else {
|
|
seed_len = MIN((uint8_t)apdu.nc, 64);
|
|
memcpy(seed, apdu.data, seed_len);
|
|
}
|
|
if (p1 == 0x1 || p1 == 0x2) {
|
|
do {
|
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), key_seed,
|
|
strlen((char *) key_seed), seed, seed_len, seed);
|
|
mbedtls_mpi_read_binary(&il, seed, 32);
|
|
seed_len = 64;
|
|
} while (mbedtls_mpi_cmp_int(&il, 0) == 0 || mbedtls_mpi_cmp_mpi(&il, &grp.N) != -1);
|
|
mbedtls_ecp_group_free(&grp);
|
|
mbedtls_mpi_free(&il);
|
|
}
|
|
else if (p1 == 0x3) {
|
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), key_seed,
|
|
strlen((char *) key_seed), seed, seed_len, seed);
|
|
}
|
|
mkey[0] = p1;
|
|
file_t *ef = file_new(EF_MASTER_SEED | p2);
|
|
int r = mkek_encrypt(mkey + 1, sizeof(mkey) - 1);
|
|
if (r != PICOKEY_OK) {
|
|
return SW_EXEC_ERROR();
|
|
}
|
|
r = file_put_data(ef, mkey, sizeof(mkey));
|
|
if (r != PICOKEY_OK) {
|
|
return SW_EXEC_ERROR();
|
|
}
|
|
low_flash_available();
|
|
}
|
|
else if (p1 == 0xA) {
|
|
if (apdu.nc == 0) {
|
|
return SW_WRONG_LENGTH();
|
|
}
|
|
mbedtls_ecp_keypair ctx;
|
|
uint8_t chain[32] = { 0 }, fgpt[4] = { 0 }, last_node[4] = { 0 }, key_type = 0, nodes = 0;
|
|
size_t olen = 0;
|
|
int r =
|
|
node_derive_path(apdu.data, (uint16_t)apdu.nc, &ctx, chain, fgpt, &nodes, last_node, &key_type);
|
|
if (r != PICOKEY_OK) {
|
|
mbedtls_ecp_keypair_free(&ctx);
|
|
return SW_EXEC_ERROR();
|
|
}
|
|
uint8_t pubkey[33];
|
|
res_APDU_size = 0;
|
|
memcpy(res_APDU, "\x04\x88\xB2\x1E", 4);
|
|
res_APDU_size += 4;
|
|
res_APDU[res_APDU_size++] = nodes - 1;
|
|
memcpy(res_APDU + res_APDU_size, fgpt, 4);
|
|
res_APDU_size += 4;
|
|
memcpy(res_APDU + res_APDU_size, last_node, 4);
|
|
res_APDU_size += 4;
|
|
if (key_type == 0x1 || key_type == 0x2) {
|
|
memcpy(res_APDU + res_APDU_size, chain, 32);
|
|
res_APDU_size += 32;
|
|
mbedtls_ecp_point_write_binary(&ctx.grp,
|
|
&ctx.Q,
|
|
MBEDTLS_ECP_PF_COMPRESSED,
|
|
&olen,
|
|
pubkey,
|
|
sizeof(pubkey));
|
|
memcpy(res_APDU + res_APDU_size, pubkey, olen);
|
|
res_APDU_size += (uint16_t)olen;
|
|
}
|
|
else if (key_type == 0x3) {
|
|
sha256_sha256(chain, 32, chain);
|
|
memcpy(res_APDU + res_APDU_size, chain, 32);
|
|
res_APDU_size += 32;
|
|
mbedtls_mpi_write_binary(&ctx.d, pubkey, 32);
|
|
sha256_sha256(pubkey, 32, pubkey);
|
|
memcpy(res_APDU + res_APDU_size, pubkey, 32);
|
|
res_APDU_size += 32;
|
|
}
|
|
mbedtls_ecp_keypair_free(&ctx);
|
|
}
|
|
else if (p1 == 0x10) {
|
|
uint8_t chain[32] = { 0 }, fgpt[4] = { 0 }, last_node[4] = { 0 }, nodes = 0;
|
|
int r = node_derive_path(apdu.data,
|
|
(uint16_t)apdu.nc,
|
|
&hd_context,
|
|
chain,
|
|
fgpt,
|
|
&nodes,
|
|
last_node,
|
|
&hd_keytype);
|
|
if (r != PICOKEY_OK) {
|
|
mbedtls_ecp_keypair_free(&hd_context);
|
|
return SW_EXEC_ERROR();
|
|
}
|
|
}
|
|
return SW_OK();
|
|
}
|