fix(legacy): Generate CoSi nonce randomly.

pull/2455/head
Andrew Kozlik 2 years ago committed by matejcik
parent ad5a572b75
commit b7dde50d5f

@ -0,0 +1 @@
Fix key extraction vulnerability in Cothority Collective Signing (CoSi).

@ -44,7 +44,6 @@
#include "protect.h"
#include "recovery.h"
#include "reset.h"
#include "rfc6979.h"
#include "rng.h"
#include "secp256k1.h"
#include "signing.h"

@ -17,6 +17,9 @@
* 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) {
CHECK_INITIALIZED
@ -256,8 +259,6 @@ void fsm_msgCosiCommit(const CosiCommit *msg) {
CHECK_INITIALIZED
CHECK_PARAM(msg->has_data, _("No data provided"));
CHECK_PIN
if (!fsm_checkCosiPath(msg->address_n_count, msg->address_n)) {
@ -265,30 +266,21 @@ void fsm_msgCosiCommit(const CosiCommit *msg) {
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,
msg->address_n_count, NULL);
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);
if (!cosi_nonce_is_set) {
random_buffer(cosi_nonce, sizeof(cosi_nonce));
cosi_nonce_is_set = true;
}
resp->has_commitment = true;
resp->has_pubkey = true;
resp->commitment.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);
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,
_("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)) {
layoutHome();
return;
@ -313,8 +311,8 @@ void fsm_msgCosiSign(const CosiSign *msg) {
CHECK_PIN
layoutCosiCommitSign(msg->address_n, msg->address_n_count, msg->data.bytes,
msg->data.size, true);
layoutCosiSign(msg->address_n, msg->address_n_count, msg->data.bytes,
msg->data.size);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
layoutHome();
@ -325,17 +323,13 @@ void fsm_msgCosiSign(const CosiSign *msg) {
msg->address_n_count, NULL);
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;
cosi_nonce_is_set = false;
ed25519_cosi_sign(msg->data.bytes, msg->data.size, node->private_key, nonce,
msg->global_commitment.bytes, msg->global_pubkey.bytes,
resp->signature.bytes);
ed25519_cosi_sign(msg->data.bytes, msg->data.size, node->private_key,
cosi_nonce, msg->global_commitment.bytes,
msg->global_pubkey.bytes, resp->signature.bytes);
memzero(cosi_nonce, sizeof(cosi_nonce));
msg_write(MessageType_MessageType_CosiSignature, resp);
layoutHome();

@ -1220,18 +1220,13 @@ static inline bool is_slip18(const uint32_t *address_n,
(address_n[1] & PATH_UNHARDEN_MASK) <= 9;
}
void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count,
const uint8_t *data, uint32_t len, bool final_sign) {
char *desc = final_sign ? _("CoSi sign message?") : _("CoSi commit message?");
void layoutCosiSign(const uint32_t *address_n, size_t address_n_count,
const uint8_t *data, uint32_t len) {
char *desc = _("CoSi sign message?");
char desc_buf[32] = {0};
if (is_slip18(address_n, address_n_count)) {
if (final_sign) {
strlcpy(desc_buf, _("CoSi sign index #?"), sizeof(desc_buf));
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);
}
strlcpy(desc_buf, _("CoSi sign index #?"), sizeof(desc_buf));
desc_buf[16] = '0' + (address_n[1] & PATH_UNHARDEN_MASK);
desc = desc_buf;
}
char str[4][17] = {0};

@ -105,8 +105,8 @@ void layoutNEMTransferPayload(const uint8_t *payload, size_t length,
void layoutNEMMosaicDescription(const char *description);
void layoutNEMLevy(const NEMMosaicDefinition *definition, uint8_t network);
void layoutCosiCommitSign(const uint32_t *address_n, size_t address_n_count,
const uint8_t *data, uint32_t len, bool final_sign);
void layoutCosiSign(const uint32_t *address_n, size_t address_n_count,
const uint8_t *data, uint32_t len);
void layoutConfirmAutoLockDelay(uint32_t delay_ms);
void layoutConfirmSafetyChecks(SafetyCheckLevel safety_checks_level);

@ -24,75 +24,87 @@ from trezorlib.tools import parse_path
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)
c1 = cosi.commit(client, parse_path("m/10018h/1h"), digest)
c2 = cosi.commit(client, parse_path("m/10018h/2h"), digest)
def test_cosi_pubkey(client: Client):
c0 = cosi.commit(client, parse_path("m/10018h/0h"))
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 != c2.pubkey
assert c1.pubkey != c2.pubkey
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)
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.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_sign1(client: Client):
# Single party signature.
commit = cosi.commit(client, parse_path("m/10018h/0h"))
sig = cosi.sign(
client, parse_path("m/10018h/0h"), DIGEST, commit.commitment, commit.pubkey
)
signature = cosi.combine_sig(commit.commitment, [sig.signature])
cosi.verify_combined(signature, DIGEST, commit.pubkey)
def test_cosi_sign(client: Client):
digest = sha256(b"this is a message").digest()
def test_cosi_sign2(client: Client):
# Two party signature.
remote_commit = cosi.commit(client, parse_path("m/10018h/1h"))
c0 = cosi.commit(client, parse_path("m/10018h/0h"), digest)
c1 = cosi.commit(client, parse_path("m/10018h/1h"), digest)
c2 = cosi.commit(client, parse_path("m/10018h/2h"), digest)
local_privkey = sha256(b"private key").digest()[:32]
local_pubkey = cosi.pubkey_from_privkey(local_privkey)
local_nonce, local_commitment = cosi.get_nonce(local_privkey, DIGEST, 42)
global_pk = cosi.combine_keys([c0.pubkey, c1.pubkey, c2.pubkey])
global_R = cosi.combine_keys([c0.commitment, c1.commitment, c2.commitment])
global_pk = cosi.combine_keys([remote_commit.pubkey, local_pubkey])
global_R = cosi.combine_keys([remote_commit.commitment, local_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
remote_sig = cosi.sign(
client, parse_path("m/10018h/1h"), DIGEST, global_R, global_pk
)
local_sig = cosi.sign_with_privkey(
DIGEST, local_privkey, global_pk, local_nonce, global_R
)
signature = cosi.combine_sig(global_R, [remote_sig.signature, local_sig])
sig = cosi.combine_sig(global_R, [sig0.signature, sig1.signature, sig2.signature])
cosi.verify_combined(signature, DIGEST, global_pk)
cosi.verify_combined(sig, digest, global_pk)
def test_cosi_sign3(client: Client):
# Three party signature.
remote_commit = cosi.commit(client, parse_path("m/10018h/2h"))
def test_cosi_compat(client: Client):
digest = sha256(b"this is not a pipe").digest()
remote_commit = cosi.commit(client, parse_path("m/10018h/0h"), digest)
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_privkey = sha256(b"private key").digest()[:32]
local_pubkey = cosi.pubkey_from_privkey(local_privkey)
local_nonce, local_commitment = cosi.get_nonce(local_privkey, 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_pubkey])
global_R = cosi.combine_keys([remote_commit.commitment, local_commitment])
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/0h"), digest, global_R, global_pk
client, parse_path("m/10018h/2h"), DIGEST, global_R, global_pk
)
local_sig = cosi.sign_with_privkey(
digest, local_privkey, global_pk, local_nonce, global_R
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]
)
sig = cosi.combine_sig(global_R, [remote_sig.signature, local_sig])
cosi.verify_combined(sig, digest, global_pk)
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_access_list": "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_compat": "dfdc383ac1dd9f1bb63b52c8623d3788491e7a954f6f0374fe0963e823a5e0c5",
"T1_misc-test_cosi.py::test_cosi_sign": "54f589eaca23d0e1a055280920b9a40c7ba7a5b34779270090a8361113af4574",
"T1_misc-test_cosi.py::test_cosi_nonce": "6990c238036b79368fea1dc1e3e8871d7788322bbee7425d14c53623bc8182e8",
"T1_misc-test_cosi.py::test_cosi_pubkey": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"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_badlen": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"T1_misc-test_msg_cipherkeyvalue.py::test_encrypt": "e36d5b5db4b3733c9f484b149c0f966b3dd66e8b54e1b5bccf9da5cca7abed91",

Loading…
Cancel
Save