mirror of
https://github.com/open62541/open62541.git
synced 2026-10-16 20:33:18 +08:00
fix(core): derive only the signing key for ECC Sign without AEAD
Part 6 6.8.1 excludes the encrypting key and IV from L for ECC policies without AEAD in SecurityMode Sign. We derived the full length, so Sign with nistP256/384 and brainpool failed with other stacks. Adds a test.
This commit is contained in:
committed by
Andreas Ebner
parent
bf8d24e0cf
commit
1e4eff47c8
@@ -92,6 +92,21 @@ captureIKMSlot(UA_SecureChannel *channel, const UA_ByteString *combined) {
|
||||
return UA_ByteString_copy(&ikmSlot, &channel->currentIKM);
|
||||
}
|
||||
|
||||
/* OPC UA Part 6, 6.8.1 Step 1: "When not using AuthenticatedEncryption with
|
||||
* Sign only, the EncryptionKeyLength and InitializationVectorLength are set to
|
||||
* 0 in the calculation of L." L is part of the HKDF salt and the length of the
|
||||
* derived key material, so only the signing key is derived for the ECC
|
||||
* policies without AEAD in the SecurityMode Sign. The encrypting key and IV
|
||||
* are not used in this mode and stay zeroed. */
|
||||
static size_t
|
||||
derivedKeyMaterialLength(const UA_SecureChannel *channel, size_t signKL,
|
||||
size_t totalLength) {
|
||||
if(channel->securityPolicy->policyType == UA_SECURITYPOLICYTYPE_ECC &&
|
||||
channel->securityMode == UA_MESSAGESECURITYMODE_SIGN)
|
||||
return signKL;
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_SecureChannel_generateLocalKeys(UA_SecureChannel *channel) {
|
||||
const UA_SecurityPolicy *sp = channel->securityPolicy;
|
||||
@@ -117,9 +132,9 @@ UA_SecureChannel_generateLocalKeys(UA_SecureChannel *channel) {
|
||||
UA_ByteString localSigningKey = {signKL, buf.data};
|
||||
UA_ByteString localEncryptingKey = {encrKL, &buf.data[signKL]};
|
||||
UA_ByteString localIv = {ivLen, &buf.data[signKL + encrKL]};
|
||||
|
||||
/* TODO: Signal that no ECC salt is generated. Find a clean solution for this. */
|
||||
buf.data[0] = 0x00;
|
||||
memset(buf.data, 0, buf.length);
|
||||
UA_ByteString derived = {
|
||||
derivedKeyMaterialLength(channel, signKL, buf.length), buf.data};
|
||||
|
||||
/* Build the IKM-prefixed `secret` input (the helper detects
|
||||
* the prepend via the length mismatch against the local
|
||||
@@ -141,7 +156,7 @@ UA_SecureChannel_generateLocalKeys(UA_SecureChannel *channel) {
|
||||
* both derive against the same previous accumulator, so the
|
||||
* advance to IKM_n happens exactly once, in generateRemoteKeys
|
||||
* (the last of the two passes). */
|
||||
res = sp->generateKey(sp, cc, &secretInput, &seedInput, &buf);
|
||||
res = sp->generateKey(sp, cc, &secretInput, &seedInput, &derived);
|
||||
UA_CHECK_STATUS(res, goto error);
|
||||
|
||||
/* Set the channel context */
|
||||
@@ -185,9 +200,9 @@ generateRemoteKeys(UA_SecureChannel *channel) {
|
||||
UA_ByteString remoteSigningKey = {signKL, buf.data};
|
||||
UA_ByteString remoteEncryptingKey = {encrKL, &buf.data[signKL]};
|
||||
UA_ByteString remoteIv = {ivLen, &buf.data[signKL + encrKL]};
|
||||
|
||||
/* TODO: Signal that no ECC salt is generated. Find a clean solution for this. */
|
||||
buf.data[0] = 0x00;
|
||||
memset(buf.data, 0, buf.length);
|
||||
UA_ByteString derived = {
|
||||
derivedKeyMaterialLength(channel, signKL, buf.length), buf.data};
|
||||
|
||||
/* Build the IKM-prefixed `secret` input. Both the local- and
|
||||
* remote-keys passes of one OPN derive against the *same* previous
|
||||
@@ -204,7 +219,7 @@ generateRemoteKeys(UA_SecureChannel *channel) {
|
||||
/* Generate key. The policy's wrapper XORs the previous accumulator
|
||||
* with the new shared secret and writes the result (IKM_n) back
|
||||
* into secretCombined. */
|
||||
res = sp->generateKey(sp, cc, &secretInput, &seedInput, &buf);
|
||||
res = sp->generateKey(sp, cc, &secretInput, &seedInput, &derived);
|
||||
UA_CHECK_STATUS(res, goto error);
|
||||
|
||||
/* This is the last derivation of the OPN: promote the just-updated
|
||||
|
||||
@@ -92,6 +92,62 @@ teardown_key_sizes(void) {
|
||||
memset(&keySizes, 0, sizeof(struct key_sizes));
|
||||
}
|
||||
|
||||
/* OPC UA Part 6, 6.8.1: for ECC policies without AuthenticatedEncryption in
|
||||
* the SecurityMode Sign, the encrypting key and IV are not part of L (the
|
||||
* length of the derived key material, also part of the HKDF salt). The
|
||||
* generateKey call of the SecurityPolicy gets L as the output length. */
|
||||
static size_t derivedLength;
|
||||
static UA_StatusCode
|
||||
(*testingGenerateKey)(const UA_SecurityPolicy *policy, void *channelContext,
|
||||
const UA_ByteString *secret, const UA_ByteString *seed,
|
||||
UA_ByteString *out);
|
||||
|
||||
static UA_StatusCode
|
||||
recordGenerateKey(const UA_SecurityPolicy *policy, void *channelContext,
|
||||
const UA_ByteString *secret, const UA_ByteString *seed,
|
||||
UA_ByteString *out) {
|
||||
derivedLength = out->length;
|
||||
return testingGenerateKey(policy, channelContext, secret, seed, out);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
UA_SecurityPolicyType policyType;
|
||||
UA_MessageSecurityMode securityMode;
|
||||
size_t derivedLength;
|
||||
} keyLengthCases[] = {
|
||||
/* Only the signing key */
|
||||
{UA_SECURITYPOLICYTYPE_ECC, UA_MESSAGESECURITYMODE_SIGN,
|
||||
DEFAULT_SYM_SIGNING_KEY_LENGTH},
|
||||
/* Signing key, encrypting key and IV (the block size) */
|
||||
{UA_SECURITYPOLICYTYPE_ECC, UA_MESSAGESECURITYMODE_SIGNANDENCRYPT,
|
||||
DEFAULT_SYM_SIGNING_KEY_LENGTH + DEFAULT_SYM_ENCRYPTION_KEY_LENGTH +
|
||||
DEFAULT_SYM_ENCRYPTION_BLOCK_SIZE},
|
||||
{UA_SECURITYPOLICYTYPE_ECC_AEAD, UA_MESSAGESECURITYMODE_SIGN,
|
||||
DEFAULT_SYM_SIGNING_KEY_LENGTH + DEFAULT_SYM_ENCRYPTION_KEY_LENGTH +
|
||||
DEFAULT_SYM_ENCRYPTION_BLOCK_SIZE},
|
||||
{UA_SECURITYPOLICYTYPE_RSA, UA_MESSAGESECURITYMODE_SIGN,
|
||||
DEFAULT_SYM_SIGNING_KEY_LENGTH + DEFAULT_SYM_ENCRYPTION_KEY_LENGTH +
|
||||
DEFAULT_SYM_ENCRYPTION_BLOCK_SIZE}
|
||||
};
|
||||
|
||||
START_TEST(SecureChannel_derivedKeyLength) {
|
||||
dummyPolicy.policyType = keyLengthCases[_i].policyType;
|
||||
testChannel.securityMode = keyLengthCases[_i].securityMode;
|
||||
testingGenerateKey = dummyPolicy.generateKey;
|
||||
dummyPolicy.generateKey = recordGenerateKey;
|
||||
|
||||
derivedLength = 0;
|
||||
ck_assert_uint_eq(UA_SecureChannel_generateLocalKeys(&testChannel),
|
||||
UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(derivedLength, keyLengthCases[_i].derivedLength);
|
||||
|
||||
derivedLength = 0;
|
||||
ck_assert_uint_eq(generateRemoteKeys(&testChannel), UA_STATUSCODE_GOOD);
|
||||
ck_assert_uint_eq(derivedLength, keyLengthCases[_i].derivedLength);
|
||||
|
||||
dummyPolicy.generateKey = testingGenerateKey;
|
||||
} END_TEST
|
||||
|
||||
START_TEST(SecureChannel_initAndDelete) {
|
||||
TestingPolicy(&dummyPolicy, dummyCertificate, &fCalled, &keySizes);
|
||||
UA_StatusCode retval;
|
||||
@@ -1032,6 +1088,14 @@ testSuite_SecureChannel(void) {
|
||||
tcase_add_test(tc_initAndDelete, SecureChannel_initAndDelete);
|
||||
suite_add_tcase(s, tc_initAndDelete);
|
||||
|
||||
TCase *tc_keys = tcase_create("Derived key length");
|
||||
tcase_add_checked_fixture(tc_keys, setup_funcs_called, teardown_funcs_called);
|
||||
tcase_add_checked_fixture(tc_keys, setup_key_sizes, teardown_key_sizes);
|
||||
tcase_add_checked_fixture(tc_keys, setup_secureChannel, teardown_secureChannel);
|
||||
tcase_add_loop_test(tc_keys, SecureChannel_derivedKeyLength, 0,
|
||||
sizeof(keyLengthCases) / sizeof(keyLengthCases[0]));
|
||||
suite_add_tcase(s, tc_keys);
|
||||
|
||||
TCase *tc_open = tcase_create("OpenSecureChannel security modes");
|
||||
tcase_add_checked_fixture(tc_open, setup_funcs_called, teardown_funcs_called);
|
||||
tcase_add_checked_fixture(tc_open, setup_key_sizes, teardown_key_sizes);
|
||||
|
||||
Reference in New Issue
Block a user