enable Trezor to perform SSH public key authentication

support both NIST256P1 and SECP256K1 ECDSA curves.
pull/25/head
Roman Zeyde 9 years ago
parent b4728e6cf9
commit 0ac032917b

4
.gitmodules vendored

@ -1,9 +1,9 @@
[submodule "trezor-crypto"]
path = trezor-crypto
url = https://github.com/trezor/trezor-crypto.git
url = https://github.com/romanz/trezor-crypto.git
[submodule "trezor-common"]
path = trezor-common
url = https://github.com/trezor/trezor-common.git
url = https://github.com/romanz/trezor-common.git
[submodule "trezor-qrenc"]
path = trezor-qrenc
url = https://github.com/trezor/trezor-qrenc.git

@ -1,10 +1,10 @@
#!/bin/bash
IMAGETAG=trezor-mcu-build
FIRMWARETAG=${1:-master}
FIRMWARETAG="ssh-agent"
docker build -t $IMAGETAG .
docker run -t -v $(pwd)/output:/output $IMAGETAG /bin/sh -c "\
git clone https://github.com/trezor/trezor-mcu && \
git clone https://github.com/romanz/trezor-mcu && \
cd trezor-mcu && \
git checkout $FIRMWARETAG && \
git submodule update --init && \

@ -22,6 +22,7 @@ OBJS += debug.o
OBJS += ../trezor-crypto/bignum.o
OBJS += ../trezor-crypto/ecdsa.o
OBJS += ../trezor-crypto/secp256k1.o
OBJS += ../trezor-crypto/nist256p1.o
OBJS += ../trezor-crypto/hmac.o
OBJS += ../trezor-crypto/bip32.o
OBJS += ../trezor-crypto/bip39.o

@ -20,12 +20,13 @@
#include <string.h>
#include "crypto.h"
#include "sha2.h"
#include "ecdsa.h"
#include "pbkdf2.h"
#include "aes.h"
#include "hmac.h"
#include "bip32.h"
#include "layout.h"
#include "secp256k1.h"
#include "nist256p1.h"
uint32_t ser_length(uint32_t len, uint8_t *out)
{
@ -83,6 +84,12 @@ uint32_t deser_length(const uint8_t *in, uint32_t *out)
return 1 + 8;
}
int sshMessageSign(const uint8_t *message, size_t message_len, const uint8_t *privkey, uint8_t *signature)
{
signature[0] = 0; // prefix: pad with zero, so all signatures are 65 bytes
return ecdsa_sign(&nist256p1, privkey, message, message_len, signature + 1, NULL);
}
int cryptoMessageSign(const uint8_t *message, size_t message_len, const uint8_t *privkey, uint8_t *signature)
{
SHA256_CTX ctx;
@ -96,7 +103,7 @@ int cryptoMessageSign(const uint8_t *message, size_t message_len, const uint8_t
sha256_Final(hash, &ctx);
sha256_Raw(hash, 32, hash);
uint8_t pby;
ecdsa_sign_digest(privkey, hash, signature + 1, &pby);
ecdsa_sign_digest(&secp256k1, privkey, hash, signature + 1, &pby);
signature[0] = 27 + pby + 4;
return 0;
}
@ -124,7 +131,7 @@ int cryptoMessageVerify(const uint8_t *message, size_t message_len, const uint8_
// x = r
memcpy(&cp.x, &r, sizeof(bignum256));
// compute y from x
uncompress_coords(recid % 2, &cp.x, &cp.y);
uncompress_coords(&secp256k1, recid % 2, &cp.x, &cp.y);
// calculate hash
sha256_Init(&ctx);
sha256_Update(&ctx, (const uint8_t *)"\x18" "Bitcoin Signed Message:" "\n", 25);
@ -136,13 +143,13 @@ int cryptoMessageVerify(const uint8_t *message, size_t message_len, const uint8_
sha256_Raw(hash, 32, hash);
// e = -hash
bn_read_be(hash, &e);
bn_subtract(&order256k1, &e, &e);
bn_subtract(&secp256k1.order, &e, &e);
// r = r^-1
bn_inverse(&r, &order256k1);
point_multiply(&s, &cp, &cp);
scalar_multiply(&e, &cp2);
point_add(&cp2, &cp);
point_multiply(&r, &cp, &cp);
bn_inverse(&r, &secp256k1.order);
point_multiply(&secp256k1, &s, &cp, &cp);
scalar_multiply(&secp256k1, &e, &cp2);
point_add(&secp256k1, &cp2, &cp);
point_multiply(&secp256k1, &r, &cp, &cp);
pubkey[0] = 0x04;
bn_write_be(&cp.x, pubkey + 1);
bn_write_be(&cp.y, pubkey + 33);
@ -155,7 +162,7 @@ int cryptoMessageVerify(const uint8_t *message, size_t message_len, const uint8_
return 2;
}
// check if signature verifies the digest
if (ecdsa_verify_digest(pubkey, signature + 1, hash) != 0) {
if (ecdsa_verify_digest(&secp256k1, pubkey, signature + 1, hash) != 0) {
return 3;
}
return 0;
@ -181,16 +188,16 @@ int cryptoMessageEncrypt(curve_point *pubkey, const uint8_t *msg, size_t msg_siz
// generate random nonce
curve_point R;
bignum256 k;
if (generate_k_random(&k) != 0) {
if (generate_k_random(&secp256k1, &k) != 0) {
return 2;
}
// compute k*G
scalar_multiply(&k, &R);
scalar_multiply(&secp256k1, &k, &R);
nonce[0] = 0x02 | (R.y.val[0] & 0x01);
bn_write_be(&R.x, nonce + 1);
*nonce_len = 33;
// compute shared secret
point_multiply(&k, pubkey, &R);
point_multiply(&secp256k1, &k, pubkey, &R);
uint8_t shared_secret[33];
shared_secret[0] = 0x02 | (R.y.val[0] & 0x01);
bn_write_be(&R.x, shared_secret + 1);
@ -222,7 +229,7 @@ int cryptoMessageDecrypt(curve_point *nonce, uint8_t *payload, size_t payload_le
curve_point R;
bignum256 k;
bn_read_be(privkey, &k);
point_multiply(&k, nonce, &R);
point_multiply(&secp256k1, &k, nonce, &R);
uint8_t shared_secret[33];
shared_secret[0] = 0x02 | (R.y.val[0] & 0x01);
bn_write_be(&R.x, shared_secret + 1);

@ -23,7 +23,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <secp256k1.h>
#include <ecdsa.h>
#include <sha2.h>
#include <pb.h>
#include "types.pb.h"
@ -32,6 +32,8 @@ uint32_t ser_length(uint32_t len, uint8_t *out);
uint32_t ser_length_hash(SHA256_CTX *ctx, uint32_t len);
int sshMessageSign(const uint8_t *message, size_t message_len, const uint8_t *privkey, uint8_t *signature);
int cryptoMessageSign(const uint8_t *message, size_t message_len, const uint8_t *privkey, uint8_t *signature);
int cryptoMessageVerify(const uint8_t *message, size_t message_len, const uint8_t *address_raw, const uint8_t *signature);

@ -44,6 +44,8 @@
#include "base58.h"
#include "bip39.h"
#include "ripemd160.h"
#include "secp256k1.h"
#include "nist256p1.h"
// message methods
@ -285,6 +287,17 @@ void fsm_msgGetPublicKey(GetPublicKey *msg)
const HDNode *node = fsm_getDerivedNode(msg->address_n, msg->address_n_count);
if (!node) return;
uint8_t public_key[33]; // copy public key to temporary buffer
memcpy(public_key, node->public_key, sizeof(public_key));
if (msg->has_ecdsa_curve_name) {
const ecdsa_curve *curve = get_curve_by_name(msg->ecdsa_curve_name);
if (curve) {
// correct public key (since fsm_getDerivedNode uses secp256k1 curve)
ecdsa_get_public_key33(curve, node->private_key, public_key);
}
}
resp->node.depth = node->depth;
resp->node.fingerprint = node->fingerprint;
resp->node.child_num = node->child_num;
@ -293,7 +306,7 @@ void fsm_msgGetPublicKey(GetPublicKey *msg)
resp->node.has_private_key = false;
resp->node.has_public_key = true;
resp->node.public_key.size = 33;
memcpy(resp->node.public_key.bytes, node->public_key, 33);
memcpy(resp->node.public_key.bytes, public_key, 33);
resp->has_xpub = true;
hdnode_serialize_public(node, resp->xpub, sizeof(resp->xpub));
msg_write(MessageType_MessageType_PublicKey, resp);
@ -662,6 +675,7 @@ void fsm_msgSignIdentity(SignIdentity *msg)
layoutHome();
return;
}
uint32_t address_n[5];
address_n[0] = 0x80000000 | 13;
address_n[1] = 0x80000000 | hash[ 0] | (hash[ 1] << 8) | (hash[ 2] << 16) | (hash[ 3] << 24);
@ -672,20 +686,51 @@ void fsm_msgSignIdentity(SignIdentity *msg)
const HDNode *node = fsm_getDerivedNode(address_n, 5);
if (!node) return;
uint8_t message[256 + 256];
memcpy(message, msg->challenge_hidden.bytes, msg->challenge_hidden.size);
const int len = strlen(msg->challenge_visual);
memcpy(message + msg->challenge_hidden.size, msg->challenge_visual, len);
uint8_t public_key[33]; // copy public key to temporary buffer
memcpy(public_key, node->public_key, sizeof(public_key));
layoutProgressSwipe("Signing", 0);
if (cryptoMessageSign(message, msg->challenge_hidden.size + len, node->private_key, resp->signature.bytes) == 0) {
resp->has_address = true;
uint8_t addr_raw[21];
ecdsa_get_address_raw(node->public_key, 0x00, addr_raw); // hardcoded Bitcoin address type
base58_encode_check(addr_raw, 21, resp->address, sizeof(resp->address));
if (msg->has_ecdsa_curve_name) {
const ecdsa_curve *curve = get_curve_by_name(msg->ecdsa_curve_name);
if (curve) {
// correct public key (since fsm_getDerivedNode uses secp256k1 curve)
ecdsa_get_public_key33(curve, node->private_key, public_key);
}
}
bool sign_ssh = false;
if (msg->identity.has_proto) {
sign_ssh = (strcmp(msg->identity.proto, "ssh") == 0);
}
uint8_t message_bytes[256 + 256];
memcpy(message_bytes, msg->challenge_hidden.bytes, msg->challenge_hidden.size);
int message_size = msg->challenge_hidden.size;
int result = 0;
if (sign_ssh) {
// SSH doesn't sign visual challenge.
layoutProgressSwipe("Signing SSH", 0);
result = sshMessageSign(message_bytes, message_size, node->private_key, resp->signature.bytes);
} else {
const int len = strlen(msg->challenge_visual);
memcpy(message_bytes + message_size, msg->challenge_visual, len);
message_size = message_size + len;
layoutProgressSwipe("Signing", 0);
result = cryptoMessageSign(message_bytes, message_size, node->private_key, resp->signature.bytes);
}
if (result == 0) {
if (sign_ssh) {
resp->has_address = false;
} else {
resp->has_address = true;
uint8_t addr_raw[21];
ecdsa_get_address_raw(node->public_key, 0x00, addr_raw); // hardcoded Bitcoin address type
base58_encode_check(addr_raw, 21, resp->address, sizeof(resp->address));
}
resp->has_public_key = true;
resp->public_key.size = 33;
memcpy(resp->public_key.bytes, node->public_key, 33);
memcpy(resp->public_key.bytes, public_key, 33);
resp->has_signature = true;
resp->signature.size = 65;
msg_write(MessageType_MessageType_SignedIdentity, resp);
@ -706,7 +751,7 @@ void fsm_msgEncryptMessage(EncryptMessage *msg)
return;
}
curve_point pubkey;
if (msg->pubkey.size != 33 || ecdsa_read_pubkey(msg->pubkey.bytes, &pubkey) == 0) {
if (msg->pubkey.size != 33 || ecdsa_read_pubkey(&secp256k1, msg->pubkey.bytes, &pubkey) == 0) {
fsm_sendFailure(FailureType_Failure_SyntaxError, "Invalid public key provided");
return;
}
@ -729,7 +774,7 @@ void fsm_msgEncryptMessage(EncryptMessage *msg)
node = fsm_getDerivedNode(msg->address_n, msg->address_n_count);
if (!node) return;
uint8_t public_key[33];
ecdsa_get_public_key33(node->private_key, public_key);
ecdsa_get_public_key33(&secp256k1, node->private_key, public_key);
ecdsa_get_address_raw(public_key, coin->address_type, address_raw);
}
layoutEncryptMessage(msg->message.bytes, msg->message.size, signing);
@ -766,7 +811,7 @@ void fsm_msgDecryptMessage(DecryptMessage *msg)
return;
}
curve_point nonce_pubkey;
if (msg->nonce.size != 33 || ecdsa_read_pubkey(msg->nonce.bytes, &nonce_pubkey) == 0) {
if (msg->nonce.size != 33 || ecdsa_read_pubkey(&secp256k1, msg->nonce.bytes, &nonce_pubkey) == 0) {
fsm_sendFailure(FailureType_Failure_SyntaxError, "Invalid nonce provided");
return;
}

@ -25,6 +25,7 @@ PassphraseAck.passphrase max_size:51
Entropy.entropy max_size:1024
GetPublicKey.address_n max_count:8
GetPublicKey.ecdsa_curve_name max_size:32
PublicKey.xpub max_size:113
@ -88,6 +89,7 @@ SignTx.coin_name max_size:17
SignIdentity.challenge_hidden max_size:256
SignIdentity.challenge_visual max_size:256
SignIdentity.ecdsa_curve_name max_size:32
SignedIdentity.address max_size:36
SignedIdentity.public_key max_size:33

@ -123,8 +123,9 @@ const pb_field_t Entropy_fields[2] = {
PB_LAST_FIELD
};
const pb_field_t GetPublicKey_fields[2] = {
const pb_field_t GetPublicKey_fields[3] = {
PB_FIELD2( 1, UINT32 , REPEATED, STATIC , FIRST, GetPublicKey, address_n, address_n, 0),
PB_FIELD2( 2, STRING , OPTIONAL, STATIC , OTHER, GetPublicKey, ecdsa_curve_name, address_n, 0),
PB_LAST_FIELD
};
@ -304,10 +305,11 @@ const pb_field_t TxAck_fields[2] = {
PB_LAST_FIELD
};
const pb_field_t SignIdentity_fields[4] = {
const pb_field_t SignIdentity_fields[5] = {
PB_FIELD2( 1, MESSAGE , OPTIONAL, STATIC , FIRST, SignIdentity, identity, identity, &IdentityType_fields),
PB_FIELD2( 2, BYTES , OPTIONAL, STATIC , OTHER, SignIdentity, challenge_hidden, identity, 0),
PB_FIELD2( 3, STRING , OPTIONAL, STATIC , OTHER, SignIdentity, challenge_visual, challenge_hidden, 0),
PB_FIELD2( 4, STRING , OPTIONAL, STATIC , OTHER, SignIdentity, ecdsa_curve_name, challenge_visual, 0),
PB_LAST_FIELD
};

@ -417,6 +417,8 @@ typedef struct _GetEntropy {
typedef struct _GetPublicKey {
size_t address_n_count;
uint32_t address_n[8];
bool has_ecdsa_curve_name;
char ecdsa_curve_name[32];
} GetPublicKey;
typedef struct _LoadDevice {
@ -520,6 +522,8 @@ typedef struct _SignIdentity {
SignIdentity_challenge_hidden_t challenge_hidden;
bool has_challenge_visual;
char challenge_visual[256];
bool has_ecdsa_curve_name;
char ecdsa_curve_name[32];
} SignIdentity;
typedef struct {
@ -650,7 +654,7 @@ extern const char SimpleSignTx_coin_name_default[17];
#define PassphraseAck_init_default {""}
#define GetEntropy_init_default {0}
#define Entropy_init_default {{0, {0}}}
#define GetPublicKey_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define GetPublicKey_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, ""}
#define PublicKey_init_default {HDNodeType_init_default, false, ""}
#define GetAddress_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, "Bitcoin", false, 0, false, MultisigRedeemScriptType_init_default}
#define Address_init_default {""}
@ -677,7 +681,7 @@ extern const char SimpleSignTx_coin_name_default[17];
#define SimpleSignTx_init_default {0, {}, 0, {}, 0, {}, false, "Bitcoin"}
#define TxRequest_init_default {false, (RequestType)0, false, TxRequestDetailsType_init_default, false, TxRequestSerializedType_init_default}
#define TxAck_init_default {false, TransactionType_init_default}
#define SignIdentity_init_default {false, IdentityType_init_default, false, {0, {0}}, false, ""}
#define SignIdentity_init_default {false, IdentityType_init_default, false, {0, {0}}, false, "", false, ""}
#define SignedIdentity_init_default {false, "", false, {0, {0}}, false, {0, {0}}}
#define FirmwareErase_init_default {0}
#define FirmwareUpload_init_default {{0, {0}}}
@ -704,7 +708,7 @@ extern const char SimpleSignTx_coin_name_default[17];
#define PassphraseAck_init_zero {""}
#define GetEntropy_init_zero {0}
#define Entropy_init_zero {{0, {0}}}
#define GetPublicKey_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define GetPublicKey_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, ""}
#define PublicKey_init_zero {HDNodeType_init_zero, false, ""}
#define GetAddress_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, "", false, 0, false, MultisigRedeemScriptType_init_zero}
#define Address_init_zero {""}
@ -731,7 +735,7 @@ extern const char SimpleSignTx_coin_name_default[17];
#define SimpleSignTx_init_zero {0, {}, 0, {}, 0, {}, false, ""}
#define TxRequest_init_zero {false, (RequestType)0, false, TxRequestDetailsType_init_zero, false, TxRequestSerializedType_init_zero}
#define TxAck_init_zero {false, TransactionType_init_zero}
#define SignIdentity_init_zero {false, IdentityType_init_zero, false, {0, {0}}, false, ""}
#define SignIdentity_init_zero {false, IdentityType_init_zero, false, {0, {0}}, false, "", false, ""}
#define SignedIdentity_init_zero {false, "", false, {0, {0}}, false, {0, {0}}}
#define FirmwareErase_init_zero {0}
#define FirmwareUpload_init_zero {{0, {0}}}
@ -816,6 +820,7 @@ extern const char SimpleSignTx_coin_name_default[17];
#define GetAddress_multisig_tag 4
#define GetEntropy_size_tag 1
#define GetPublicKey_address_n_tag 1
#define GetPublicKey_ecdsa_curve_name_tag 2
#define LoadDevice_mnemonic_tag 1
#define LoadDevice_node_tag 2
#define LoadDevice_pin_tag 3
@ -849,6 +854,7 @@ extern const char SimpleSignTx_coin_name_default[17];
#define SignIdentity_identity_tag 1
#define SignIdentity_challenge_hidden_tag 2
#define SignIdentity_challenge_visual_tag 3
#define SignIdentity_ecdsa_curve_name_tag 4
#define SignMessage_address_n_tag 1
#define SignMessage_message_tag 2
#define SignMessage_coin_name_tag 3
@ -892,7 +898,7 @@ extern const pb_field_t PassphraseRequest_fields[1];
extern const pb_field_t PassphraseAck_fields[2];
extern const pb_field_t GetEntropy_fields[2];
extern const pb_field_t Entropy_fields[2];
extern const pb_field_t GetPublicKey_fields[2];
extern const pb_field_t GetPublicKey_fields[3];
extern const pb_field_t PublicKey_fields[3];
extern const pb_field_t GetAddress_fields[5];
extern const pb_field_t Address_fields[2];
@ -919,7 +925,7 @@ extern const pb_field_t SignTx_fields[4];
extern const pb_field_t SimpleSignTx_fields[5];
extern const pb_field_t TxRequest_fields[4];
extern const pb_field_t TxAck_fields[2];
extern const pb_field_t SignIdentity_fields[4];
extern const pb_field_t SignIdentity_fields[5];
extern const pb_field_t SignedIdentity_fields[4];
extern const pb_field_t FirmwareErase_fields[1];
extern const pb_field_t FirmwareUpload_fields[2];
@ -948,7 +954,7 @@ extern const pb_field_t DebugLinkLog_fields[4];
#define PassphraseAck_size 53
#define GetEntropy_size 6
#define Entropy_size 1027
#define GetPublicKey_size 48
#define GetPublicKey_size 82
#define PublicKey_size (121 + HDNodeType_size)
#define GetAddress_size (75 + MultisigRedeemScriptType_size)
#define Address_size 38
@ -975,7 +981,7 @@ extern const pb_field_t DebugLinkLog_fields[4];
#define SimpleSignTx_size (19 + 0*TxInputType_size + 0*TxOutputType_size + 0*TransactionType_size)
#define TxRequest_size (18 + TxRequestDetailsType_size + TxRequestSerializedType_size)
#define TxAck_size (6 + TransactionType_size)
#define SignIdentity_size (524 + IdentityType_size)
#define SignIdentity_size (558 + IdentityType_size)
#define SignedIdentity_size 140
#define FirmwareErase_size 0
#define FirmwareUpload_size 2

@ -25,6 +25,7 @@
#include "ecdsa.h"
#include "protect.h"
#include "crypto.h"
#include "secp256k1.h"
static uint32_t inputs_count;
static uint32_t outputs_count;
@ -536,7 +537,7 @@ void signing_txack(TransactionType *tx)
resp.serialized.signature_index = idx1;
resp.serialized.has_signature = true;
resp.serialized.has_serialized_tx = true;
ecdsa_sign_digest(privkey, hash, sig, 0);
ecdsa_sign_digest(&secp256k1, privkey, hash, sig, 0);
resp.serialized.signature.size = ecdsa_sig_to_der(sig, resp.serialized.signature.bytes);
if (input.script_type == InputScriptType_SPENDMULTISIG) {
if (!input.has_multisig) {

@ -1 +1 @@
Subproject commit e96ec085d55c20eccecb47e0a55b33295164de6d
Subproject commit 12288143f563cc51e4ae1d990de08db3add87380

@ -1 +1 @@
Subproject commit a757693fe3904f60d80d36fe0bfe437dea55e02e
Subproject commit 7c58fc11a46361476e3a7862816adb0671de6a74
Loading…
Cancel
Save