mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-25 23:01:02 +00:00
fix(legacy): Generate CoSi nonce randomly.
This commit is contained in:
parent
ad5a572b75
commit
b7dde50d5f
1
legacy/firmware/.changelog.d/noissue.security
Normal file
1
legacy/firmware/.changelog.d/noissue.security
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fix key extraction vulnerability in Cothority Collective Signing (CoSi).
|
@ -44,7 +44,6 @@
|
|||||||
#include "protect.h"
|
#include "protect.h"
|
||||||
#include "recovery.h"
|
#include "recovery.h"
|
||||||
#include "reset.h"
|
#include "reset.h"
|
||||||
#include "rfc6979.h"
|
|
||||||
#include "rng.h"
|
#include "rng.h"
|
||||||
#include "secp256k1.h"
|
#include "secp256k1.h"
|
||||||
#include "signing.h"
|
#include "signing.h"
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
static uint8_t cosi_nonce[32] = {0};
|
||||||
|
static bool cosi_nonce_is_set = false;
|
||||||
|
|
||||||
void fsm_msgCipherKeyValue(const CipherKeyValue *msg) {
|
void fsm_msgCipherKeyValue(const CipherKeyValue *msg) {
|
||||||
CHECK_INITIALIZED
|
CHECK_INITIALIZED
|
||||||
|
|
||||||
@ -256,8 +259,6 @@ void fsm_msgCosiCommit(const CosiCommit *msg) {
|
|||||||
|
|
||||||
CHECK_INITIALIZED
|
CHECK_INITIALIZED
|
||||||
|
|
||||||
CHECK_PARAM(msg->has_data, _("No data provided"));
|
|
||||||
|
|
||||||
CHECK_PIN
|
CHECK_PIN
|
||||||
|
|
||||||
if (!fsm_checkCosiPath(msg->address_n_count, msg->address_n)) {
|
if (!fsm_checkCosiPath(msg->address_n_count, msg->address_n)) {
|
||||||
@ -265,30 +266,21 @@ void fsm_msgCosiCommit(const CosiCommit *msg) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutCosiCommitSign(msg->address_n, msg->address_n_count, msg->data.bytes,
|
|
||||||
msg->data.size, false);
|
|
||||||
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
|
|
||||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
|
||||||
layoutHome();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n,
|
const HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n,
|
||||||
msg->address_n_count, NULL);
|
msg->address_n_count, NULL);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|
||||||
uint8_t nonce[32];
|
if (!cosi_nonce_is_set) {
|
||||||
sha256_Raw(msg->data.bytes, msg->data.size, nonce);
|
random_buffer(cosi_nonce, sizeof(cosi_nonce));
|
||||||
rfc6979_state rng;
|
cosi_nonce_is_set = true;
|
||||||
init_rfc6979(node->private_key, nonce, NULL, &rng);
|
}
|
||||||
generate_rfc6979(nonce, &rng);
|
|
||||||
|
|
||||||
resp->has_commitment = true;
|
resp->has_commitment = true;
|
||||||
resp->has_pubkey = true;
|
resp->has_pubkey = true;
|
||||||
resp->commitment.size = 32;
|
resp->commitment.size = 32;
|
||||||
resp->pubkey.size = 32;
|
resp->pubkey.size = 32;
|
||||||
|
|
||||||
ed25519_publickey(nonce, resp->commitment.bytes);
|
ed25519_publickey(cosi_nonce, resp->commitment.bytes);
|
||||||
ed25519_publickey(node->private_key, resp->pubkey.bytes);
|
ed25519_publickey(node->private_key, resp->pubkey.bytes);
|
||||||
|
|
||||||
msg_write(MessageType_MessageType_CosiCommitment, resp);
|
msg_write(MessageType_MessageType_CosiCommitment, resp);
|
||||||
@ -306,6 +298,12 @@ void fsm_msgCosiSign(const CosiSign *msg) {
|
|||||||
CHECK_PARAM(msg->has_global_pubkey && msg->global_pubkey.size == 32,
|
CHECK_PARAM(msg->has_global_pubkey && msg->global_pubkey.size == 32,
|
||||||
_("Invalid global pubkey"));
|
_("Invalid global pubkey"));
|
||||||
|
|
||||||
|
if (!cosi_nonce_is_set) {
|
||||||
|
fsm_sendFailure(FailureType_Failure_ProcessError, _("CoSi nonce not set"));
|
||||||
|
layoutHome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!fsm_checkCosiPath(msg->address_n_count, msg->address_n)) {
|
if (!fsm_checkCosiPath(msg->address_n_count, msg->address_n)) {
|
||||||
layoutHome();
|
layoutHome();
|
||||||
return;
|
return;
|
||||||
@ -313,8 +311,8 @@ void fsm_msgCosiSign(const CosiSign *msg) {
|
|||||||
|
|
||||||
CHECK_PIN
|
CHECK_PIN
|
||||||
|
|
||||||
layoutCosiCommitSign(msg->address_n, msg->address_n_count, msg->data.bytes,
|
layoutCosiSign(msg->address_n, msg->address_n_count, msg->data.bytes,
|
||||||
msg->data.size, true);
|
msg->data.size);
|
||||||
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
|
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
|
||||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||||
layoutHome();
|
layoutHome();
|
||||||
@ -325,17 +323,13 @@ void fsm_msgCosiSign(const CosiSign *msg) {
|
|||||||
msg->address_n_count, NULL);
|
msg->address_n_count, NULL);
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
|
||||||
uint8_t nonce[32];
|
|
||||||
sha256_Raw(msg->data.bytes, msg->data.size, nonce);
|
|
||||||
rfc6979_state rng;
|
|
||||||
init_rfc6979(node->private_key, nonce, NULL, &rng);
|
|
||||||
generate_rfc6979(nonce, &rng);
|
|
||||||
|
|
||||||
resp->signature.size = 32;
|
resp->signature.size = 32;
|
||||||
|
cosi_nonce_is_set = false;
|
||||||
|
|
||||||
ed25519_cosi_sign(msg->data.bytes, msg->data.size, node->private_key, nonce,
|
ed25519_cosi_sign(msg->data.bytes, msg->data.size, node->private_key,
|
||||||
msg->global_commitment.bytes, msg->global_pubkey.bytes,
|
cosi_nonce, msg->global_commitment.bytes,
|
||||||
resp->signature.bytes);
|
msg->global_pubkey.bytes, resp->signature.bytes);
|
||||||
|
memzero(cosi_nonce, sizeof(cosi_nonce));
|
||||||
|
|
||||||
msg_write(MessageType_MessageType_CosiSignature, resp);
|
msg_write(MessageType_MessageType_CosiSignature, resp);
|
||||||
layoutHome();
|
layoutHome();
|
||||||
|
@ -1220,18 +1220,13 @@ static inline bool is_slip18(const uint32_t *address_n,
|
|||||||
(address_n[1] & PATH_UNHARDEN_MASK) <= 9;
|
(address_n[1] & PATH_UNHARDEN_MASK) <= 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count,
|
void layoutCosiSign(const uint32_t *address_n, size_t address_n_count,
|
||||||
const uint8_t *data, uint32_t len, bool final_sign) {
|
const uint8_t *data, uint32_t len) {
|
||||||
char *desc = final_sign ? _("CoSi sign message?") : _("CoSi commit message?");
|
char *desc = _("CoSi sign message?");
|
||||||
char desc_buf[32] = {0};
|
char desc_buf[32] = {0};
|
||||||
if (is_slip18(address_n, address_n_count)) {
|
if (is_slip18(address_n, address_n_count)) {
|
||||||
if (final_sign) {
|
strlcpy(desc_buf, _("CoSi sign index #?"), sizeof(desc_buf));
|
||||||
strlcpy(desc_buf, _("CoSi sign index #?"), sizeof(desc_buf));
|
desc_buf[16] = '0' + (address_n[1] & PATH_UNHARDEN_MASK);
|
||||||
desc_buf[16] = '0' + (address_n[1] & PATH_UNHARDEN_MASK);
|
|
||||||
} else {
|
|
||||||
strlcpy(desc_buf, _("CoSi commit index #?"), sizeof(desc_buf));
|
|
||||||
desc_buf[18] = '0' + (address_n[1] & PATH_UNHARDEN_MASK);
|
|
||||||
}
|
|
||||||
desc = desc_buf;
|
desc = desc_buf;
|
||||||
}
|
}
|
||||||
char str[4][17] = {0};
|
char str[4][17] = {0};
|
||||||
|
@ -105,8 +105,8 @@ void layoutNEMTransferPayload(const uint8_t *payload, size_t length,
|
|||||||
void layoutNEMMosaicDescription(const char *description);
|
void layoutNEMMosaicDescription(const char *description);
|
||||||
void layoutNEMLevy(const NEMMosaicDefinition *definition, uint8_t network);
|
void layoutNEMLevy(const NEMMosaicDefinition *definition, uint8_t network);
|
||||||
|
|
||||||
void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count,
|
void layoutCosiSign(const uint32_t *address_n, size_t address_n_count,
|
||||||
const uint8_t *data, uint32_t len, bool final_sign);
|
const uint8_t *data, uint32_t len);
|
||||||
|
|
||||||
void layoutConfirmAutoLockDelay(uint32_t delay_ms);
|
void layoutConfirmAutoLockDelay(uint32_t delay_ms);
|
||||||
void layoutConfirmSafetyChecks(SafetyCheckLevel safety_checks_level);
|
void layoutConfirmSafetyChecks(SafetyCheckLevel safety_checks_level);
|
||||||
|
@ -24,75 +24,87 @@ from trezorlib.tools import parse_path
|
|||||||
|
|
||||||
pytestmark = pytest.mark.skip_t2
|
pytestmark = pytest.mark.skip_t2
|
||||||
|
|
||||||
|
DIGEST = sha256(b"this is not a pipe").digest()
|
||||||
|
|
||||||
def test_cosi_commit(client: Client):
|
|
||||||
digest = sha256(b"this is a message").digest()
|
|
||||||
|
|
||||||
c0 = cosi.commit(client, parse_path("m/10018h/0h"), digest)
|
def test_cosi_pubkey(client: Client):
|
||||||
c1 = cosi.commit(client, parse_path("m/10018h/1h"), digest)
|
c0 = cosi.commit(client, parse_path("m/10018h/0h"))
|
||||||
c2 = cosi.commit(client, parse_path("m/10018h/2h"), digest)
|
c1 = cosi.commit(client, parse_path("m/10018h/1h"))
|
||||||
|
c2 = cosi.commit(client, parse_path("m/10018h/2h"))
|
||||||
|
|
||||||
assert c0.pubkey != c1.pubkey
|
assert c0.pubkey != c1.pubkey
|
||||||
assert c0.pubkey != c2.pubkey
|
assert c0.pubkey != c2.pubkey
|
||||||
assert c1.pubkey != c2.pubkey
|
assert c1.pubkey != c2.pubkey
|
||||||
|
|
||||||
|
|
||||||
|
def test_cosi_nonce(client: Client):
|
||||||
|
# The nonce/commitment must change after each signing.
|
||||||
|
c0 = cosi.commit(client, parse_path("m/10018h/0h"))
|
||||||
|
cosi.sign(client, parse_path("m/10018h/0h"), DIGEST, c0.commitment, c0.pubkey)
|
||||||
|
c1 = cosi.commit(client, parse_path("m/10018h/0h"))
|
||||||
assert c0.commitment != c1.commitment
|
assert c0.commitment != c1.commitment
|
||||||
assert c0.commitment != c2.commitment
|
|
||||||
assert c1.commitment != c2.commitment
|
|
||||||
|
|
||||||
digestb = sha256(b"this is a different message").digest()
|
|
||||||
|
|
||||||
c0b = cosi.commit(client, parse_path("m/10018h/0h"), digestb)
|
|
||||||
c1b = cosi.commit(client, parse_path("m/10018h/1h"), digestb)
|
|
||||||
c2b = cosi.commit(client, parse_path("m/10018h/2h"), digestb)
|
|
||||||
|
|
||||||
assert c0.pubkey == c0b.pubkey
|
|
||||||
assert c1.pubkey == c1b.pubkey
|
|
||||||
assert c2.pubkey == c2b.pubkey
|
|
||||||
|
|
||||||
assert c0.commitment != c0b.commitment
|
|
||||||
assert c1.commitment != c1b.commitment
|
|
||||||
assert c2.commitment != c2b.commitment
|
|
||||||
|
|
||||||
|
|
||||||
def test_cosi_sign(client: Client):
|
def test_cosi_sign1(client: Client):
|
||||||
digest = sha256(b"this is a message").digest()
|
# Single party signature.
|
||||||
|
commit = cosi.commit(client, parse_path("m/10018h/0h"))
|
||||||
c0 = cosi.commit(client, parse_path("m/10018h/0h"), digest)
|
sig = cosi.sign(
|
||||||
c1 = cosi.commit(client, parse_path("m/10018h/1h"), digest)
|
client, parse_path("m/10018h/0h"), DIGEST, commit.commitment, commit.pubkey
|
||||||
c2 = cosi.commit(client, parse_path("m/10018h/2h"), digest)
|
)
|
||||||
|
signature = cosi.combine_sig(commit.commitment, [sig.signature])
|
||||||
global_pk = cosi.combine_keys([c0.pubkey, c1.pubkey, c2.pubkey])
|
cosi.verify_combined(signature, DIGEST, commit.pubkey)
|
||||||
global_R = cosi.combine_keys([c0.commitment, c1.commitment, c2.commitment])
|
|
||||||
|
|
||||||
# fmt: off
|
|
||||||
sig0 = cosi.sign(client, parse_path("m/10018h/0h"), digest, global_R, global_pk)
|
|
||||||
sig1 = cosi.sign(client, parse_path("m/10018h/1h"), digest, global_R, global_pk)
|
|
||||||
sig2 = cosi.sign(client, parse_path("m/10018h/2h"), digest, global_R, global_pk)
|
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
sig = cosi.combine_sig(global_R, [sig0.signature, sig1.signature, sig2.signature])
|
|
||||||
|
|
||||||
cosi.verify_combined(sig, digest, global_pk)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cosi_compat(client: Client):
|
def test_cosi_sign2(client: Client):
|
||||||
digest = sha256(b"this is not a pipe").digest()
|
# Two party signature.
|
||||||
remote_commit = cosi.commit(client, parse_path("m/10018h/0h"), digest)
|
remote_commit = cosi.commit(client, parse_path("m/10018h/1h"))
|
||||||
|
|
||||||
local_privkey = sha256(b"private key").digest()[:32]
|
local_privkey = sha256(b"private key").digest()[:32]
|
||||||
local_pubkey = cosi.pubkey_from_privkey(local_privkey)
|
local_pubkey = cosi.pubkey_from_privkey(local_privkey)
|
||||||
local_nonce, local_commitment = cosi.get_nonce(local_privkey, digest, 42)
|
local_nonce, local_commitment = cosi.get_nonce(local_privkey, DIGEST, 42)
|
||||||
|
|
||||||
global_pk = cosi.combine_keys([remote_commit.pubkey, local_pubkey])
|
global_pk = cosi.combine_keys([remote_commit.pubkey, local_pubkey])
|
||||||
global_R = cosi.combine_keys([remote_commit.commitment, local_commitment])
|
global_R = cosi.combine_keys([remote_commit.commitment, local_commitment])
|
||||||
|
|
||||||
remote_sig = cosi.sign(
|
remote_sig = cosi.sign(
|
||||||
client, parse_path("m/10018h/0h"), digest, global_R, global_pk
|
client, parse_path("m/10018h/1h"), DIGEST, global_R, global_pk
|
||||||
)
|
)
|
||||||
local_sig = cosi.sign_with_privkey(
|
local_sig = cosi.sign_with_privkey(
|
||||||
digest, local_privkey, global_pk, local_nonce, global_R
|
DIGEST, local_privkey, global_pk, local_nonce, global_R
|
||||||
)
|
)
|
||||||
sig = cosi.combine_sig(global_R, [remote_sig.signature, local_sig])
|
signature = cosi.combine_sig(global_R, [remote_sig.signature, local_sig])
|
||||||
|
|
||||||
cosi.verify_combined(sig, digest, global_pk)
|
cosi.verify_combined(signature, DIGEST, global_pk)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cosi_sign3(client: Client):
|
||||||
|
# Three party signature.
|
||||||
|
remote_commit = cosi.commit(client, parse_path("m/10018h/2h"))
|
||||||
|
|
||||||
|
local_privkey1 = sha256(b"private key").digest()[:32]
|
||||||
|
local_pubkey1 = cosi.pubkey_from_privkey(local_privkey1)
|
||||||
|
local_nonce1, local_commitment1 = cosi.get_nonce(local_privkey1, DIGEST, 42)
|
||||||
|
|
||||||
|
local_privkey2 = sha256(b"private key").digest()[:32]
|
||||||
|
local_pubkey2 = cosi.pubkey_from_privkey(local_privkey2)
|
||||||
|
local_nonce2, local_commitment2 = cosi.get_nonce(local_privkey2, DIGEST, 42)
|
||||||
|
|
||||||
|
global_pk = cosi.combine_keys([remote_commit.pubkey, local_pubkey1, local_pubkey2])
|
||||||
|
global_R = cosi.combine_keys(
|
||||||
|
[remote_commit.commitment, local_commitment1, local_commitment2]
|
||||||
|
)
|
||||||
|
|
||||||
|
remote_sig = cosi.sign(
|
||||||
|
client, parse_path("m/10018h/2h"), DIGEST, global_R, global_pk
|
||||||
|
)
|
||||||
|
local_sig1 = cosi.sign_with_privkey(
|
||||||
|
DIGEST, local_privkey1, global_pk, local_nonce1, global_R
|
||||||
|
)
|
||||||
|
local_sig2 = cosi.sign_with_privkey(
|
||||||
|
DIGEST, local_privkey2, global_pk, local_nonce2, global_R
|
||||||
|
)
|
||||||
|
signature = cosi.combine_sig(
|
||||||
|
global_R, [remote_sig.signature, local_sig1, local_sig2]
|
||||||
|
)
|
||||||
|
|
||||||
|
cosi.verify_combined(signature, DIGEST, global_pk)
|
||||||
|
@ -393,9 +393,11 @@
|
|||||||
"T1_ethereum-test_signtx.py::test_signtx_eip1559[unknown_erc20]": "548c1f22918351e9cbcc1e16d8ba67bc2e7460b9a92cfc6c8bfa0a2b063e68da",
|
"T1_ethereum-test_signtx.py::test_signtx_eip1559[unknown_erc20]": "548c1f22918351e9cbcc1e16d8ba67bc2e7460b9a92cfc6c8bfa0a2b063e68da",
|
||||||
"T1_ethereum-test_signtx.py::test_signtx_eip1559_access_list": "f6c5f398d4e80fc8f93cf70e9b10de24b9a968db04dc6ea21b28d1a273f04ca1",
|
"T1_ethereum-test_signtx.py::test_signtx_eip1559_access_list": "f6c5f398d4e80fc8f93cf70e9b10de24b9a968db04dc6ea21b28d1a273f04ca1",
|
||||||
"T1_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "f6c5f398d4e80fc8f93cf70e9b10de24b9a968db04dc6ea21b28d1a273f04ca1",
|
"T1_ethereum-test_signtx.py::test_signtx_eip1559_access_list_larger": "f6c5f398d4e80fc8f93cf70e9b10de24b9a968db04dc6ea21b28d1a273f04ca1",
|
||||||
"T1_misc-test_cosi.py::test_cosi_commit": "c943c92750d6072a3b272c1a9dca815ee7504293e4c1d85a76d5af9c2e415297",
|
"T1_misc-test_cosi.py::test_cosi_nonce": "6990c238036b79368fea1dc1e3e8871d7788322bbee7425d14c53623bc8182e8",
|
||||||
"T1_misc-test_cosi.py::test_cosi_compat": "dfdc383ac1dd9f1bb63b52c8623d3788491e7a954f6f0374fe0963e823a5e0c5",
|
"T1_misc-test_cosi.py::test_cosi_pubkey": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
"T1_misc-test_cosi.py::test_cosi_sign": "54f589eaca23d0e1a055280920b9a40c7ba7a5b34779270090a8361113af4574",
|
"T1_misc-test_cosi.py::test_cosi_sign1": "6990c238036b79368fea1dc1e3e8871d7788322bbee7425d14c53623bc8182e8",
|
||||||
|
"T1_misc-test_cosi.py::test_cosi_sign2": "0852e7433ec54a0ace301b683417107a9805095acd954010b665266503a79f36",
|
||||||
|
"T1_misc-test_cosi.py::test_cosi_sign3": "d04fca2d97f7117ffc79ae52d192086aba2f9c64e89db27e20a930e9048f1d81",
|
||||||
"T1_misc-test_msg_cipherkeyvalue.py::test_decrypt": "a849d9e11e222d1348ce3135680a2a61ed265692adeba4bd48af2fdc65207e61",
|
"T1_misc-test_msg_cipherkeyvalue.py::test_decrypt": "a849d9e11e222d1348ce3135680a2a61ed265692adeba4bd48af2fdc65207e61",
|
||||||
"T1_misc-test_msg_cipherkeyvalue.py::test_decrypt_badlen": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
"T1_misc-test_msg_cipherkeyvalue.py::test_decrypt_badlen": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
"T1_misc-test_msg_cipherkeyvalue.py::test_encrypt": "e36d5b5db4b3733c9f484b149c0f966b3dd66e8b54e1b5bccf9da5cca7abed91",
|
"T1_misc-test_msg_cipherkeyvalue.py::test_encrypt": "e36d5b5db4b3733c9f484b149c0f966b3dd66e8b54e1b5bccf9da5cca7abed91",
|
||||||
|
Loading…
Reference in New Issue
Block a user