1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/sm/ecdh_key.h"
16
17 #include <cstring>
18 #include <memory>
19
20 #include "pw_bluetooth_sapphire/internal/host/common/uint256.h"
21 #include "pw_unit_test/framework.h"
22
23 namespace bt::sm {
24 namespace {
25
TEST(EcdhKeyTest,ParseSerializedKey)26 TEST(EcdhKeyTest, ParseSerializedKey) {
27 // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1
28 const UInt256 kDebugPubKeyX{0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC,
29 0xDB, 0xFD, 0xF4, 0xAC, 0x11, 0x91, 0xF4, 0xEF,
30 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83, 0x2C, 0x5E,
31 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20};
32 const UInt256 kDebugPubKeyY{0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74,
33 0x24, 0x30, 0xED, 0x8F, 0xC2, 0x45, 0x63, 0x76,
34 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A, 0x32, 0x63,
35 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC};
36 // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1, converted to
37 // little-endian to match transport format.
38 const sm::PairingPublicKeyParams kSerializedKey{
39 .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
40 0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
41 0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
42 .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
43 0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
44 0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
45 auto new_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
46 ASSERT_TRUE(new_key.has_value());
47 ASSERT_EQ(kDebugPubKeyX, new_key->GetPublicKeyX());
48 ASSERT_EQ(kDebugPubKeyY, new_key->GetPublicKeyY());
49 }
50
TEST(EcdhKeyTest,PointOffP256CurveXValueParsesToNullopt)51 TEST(EcdhKeyTest, PointOffP256CurveXValueParsesToNullopt) {
52 // These values come from the debug ECDH key in V5.1 Vol. 3 Part H
53 // Section 2.3.5.6.1 (converted to little-endian). The debug ECDH key values
54 // are on the P-256 curve, but by changing only the X-coordinate's
55 // most-significant byte from 0x20 to 0x00, we create a point off the P-256
56 // curve.
57 const sm::PairingPublicKeyParams kSerializedKey{
58 .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
59 0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
60 0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x00},
61 .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
62 0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
63 0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
64 auto new_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
65 ASSERT_EQ(new_key, std::nullopt);
66 }
67
TEST(EcdhKeyTest,PointOffP256CurveYValueParsesToNullopt)68 TEST(EcdhKeyTest, PointOffP256CurveYValueParsesToNullopt) {
69 // These values come from the debug ECDH key in V5.1 Vol. 3 Part H
70 // Section 2.3.5.6.1 (converted to little-endian). The debug ECDH key values
71 // are on the P-256 curve, but by changing only the Y-coordinate's
72 // most-significant byte from 0xDC to 0x00, we create a point off the P-256
73 // curve.
74 const sm::PairingPublicKeyParams kSerializedKey{
75 .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
76 0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
77 0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
78 .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
79 0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
80 0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0x00}};
81 auto new_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
82 ASSERT_EQ(new_key, std::nullopt);
83 }
84
TEST(EcdhKeyTest,CreateGivesValidKey)85 TEST(EcdhKeyTest, CreateGivesValidKey) {
86 std::optional<LocalEcdhKey> new_key = LocalEcdhKey::Create();
87 ASSERT_TRUE(new_key.has_value());
88 auto serialized_pub_key = new_key->GetSerializedPublicKey();
89 std::optional<EcdhKey> parsed_key =
90 EcdhKey::ParseFromPublicKey(serialized_pub_key);
91 ASSERT_TRUE(parsed_key.has_value());
92 }
93
94 // Test vector taken from NIST ECDH P-256 test vector 0 in first link, described
95 // in second link:
96 // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/components/ecccdhtestvectors.zip
97 // Described here:
98 // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/components/ecccdhvs.pdf
99 // Local private key is dIUT value, Peer X and Y are taken from QCAVSx and
100 // QCAVSy, ExpectedDHKey is ZIUT. The examples are given in human-readable
101 // big-endian format, but here we've converted them to little-endian format for
102 // consistency with the bt-host stack.
TEST(EcdhKeyTest,CalculateDhKeyWorks)103 TEST(EcdhKeyTest, CalculateDhKeyWorks) {
104 std::optional<LocalEcdhKey> local_key = LocalEcdhKey::Create();
105 ASSERT_TRUE(local_key.has_value());
106 const UInt256 kSamplePrivateKey{
107 0x34, 0xA5, 0xC1, 0x2B, 0xB6, 0xAD, 0x0B, 0xD8, 0x2E, 0xD2, 0xB6,
108 0x1F, 0xAF, 0x58, 0x90, 0x3D, 0xE0, 0xEA, 0x2E, 0x63, 0x14, 0x62,
109 0x0D, 0xF8, 0xDA, 0x9D, 0xB2, 0x1E, 0xF7, 0xC5, 0x7D, 0x7D};
110 local_key->SetPrivateKeyForTesting(kSamplePrivateKey);
111
112 const sm::PairingPublicKeyParams kSerializedKey{
113 .x = {0x87, 0xD2, 0x33, 0x88, 0x83, 0xCC, 0xE7, 0x2C, 0xB4, 0xF6, 0x4D,
114 0x3A, 0xCE, 0xAC, 0x6B, 0x1B, 0xB9, 0x0D, 0x64, 0x65, 0xCA, 0x32,
115 0xC6, 0x5C, 0x4C, 0x58, 0x56, 0x7F, 0xF7, 0x48, 0x0C, 0x70},
116 .y = {0xAC, 0xA4, 0x5F, 0xB8, 0xCA, 0x82, 0x17, 0x44, 0xE0, 0xDF, 0x40,
117 0xF6, 0xFB, 0x46, 0x8D, 0x94, 0xC5, 0xDC, 0x51, 0x5C, 0xBA, 0x20,
118 0xDB, 0x0D, 0x06, 0x9B, 0xFD, 0xE3, 0x09, 0xE5, 0x71, 0xDB}};
119
120 auto public_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
121 ASSERT_TRUE(public_key.has_value());
122 UInt256 dhkey = local_key->CalculateDhKey(*public_key);
123 const UInt256 kExpectedDhKey{0x7B, 0xBD, 0x97, 0x89, 0x77, 0xD7, 0x0D, 0x04,
124 0x68, 0x1E, 0x56, 0x60, 0x20, 0x85, 0xC5, 0xCC,
125 0x25, 0x2D, 0xDD, 0xFB, 0x34, 0xA4, 0x54, 0x2E,
126 0x01, 0xFF, 0x20, 0x64, 0x10, 0x62, 0xFC, 0x46};
127 ASSERT_EQ(kExpectedDhKey, dhkey);
128 }
129
TEST(EcdhKeyTest,PublicKeyXAndYComparisonSameKey)130 TEST(EcdhKeyTest, PublicKeyXAndYComparisonSameKey) {
131 // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1, converted to
132 // little-endian.
133 const sm::PairingPublicKeyParams kSerializedKey{
134 .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
135 0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
136 0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
137 .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
138 0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
139 0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
140 auto ecdh_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
141 auto same_ecdh_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
142 ASSERT_TRUE(ecdh_key.has_value());
143 ASSERT_TRUE(same_ecdh_key.has_value());
144 ASSERT_EQ(ecdh_key->GetPublicKeyX(), same_ecdh_key->GetPublicKeyX());
145 ASSERT_EQ(ecdh_key->GetPublicKeyY(), same_ecdh_key->GetPublicKeyY());
146 }
147
TEST(EcdhKeyTest,PublicKeyXAndYComparisonDifferentKeys)148 TEST(EcdhKeyTest, PublicKeyXAndYComparisonDifferentKeys) {
149 // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1, converted to
150 // little-endian.
151 const sm::PairingPublicKeyParams kSpecSampleSerializedKey{
152 .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
153 0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
154 0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
155 .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
156 0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
157 0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
158 // Test vector taken from NIST ECDH P-256 test vector 0, same as in
159 // CalculateDhKeyWorks test.
160 const sm::PairingPublicKeyParams kNistSampleSerializedKey{
161 .x = {0x87, 0xD2, 0x33, 0x88, 0x83, 0xCC, 0xE7, 0x2C, 0xB4, 0xF6, 0x4D,
162 0x3A, 0xCE, 0xAC, 0x6B, 0x1B, 0xB9, 0x0D, 0x64, 0x65, 0xCA, 0x32,
163 0xC6, 0x5C, 0x4C, 0x58, 0x56, 0x7F, 0xF7, 0x48, 0x0C, 0x70},
164 .y = {0xAC, 0xA4, 0x5F, 0xB8, 0xCA, 0x82, 0x17, 0x44, 0xE0, 0xDF, 0x40,
165 0xF6, 0xFB, 0x46, 0x8D, 0x94, 0xC5, 0xDC, 0x51, 0x5C, 0xBA, 0x20,
166 0xDB, 0x0D, 0x06, 0x9B, 0xFD, 0xE3, 0x09, 0xE5, 0x71, 0xDB}};
167
168 auto spec_key = EcdhKey::ParseFromPublicKey(kSpecSampleSerializedKey);
169 auto nist_key = EcdhKey::ParseFromPublicKey(kNistSampleSerializedKey);
170 ASSERT_TRUE(spec_key.has_value());
171 ASSERT_TRUE(nist_key.has_value());
172 ASSERT_NE(spec_key->GetPublicKeyX(), nist_key->GetPublicKeyX());
173 ASSERT_NE(spec_key->GetPublicKeyY(), nist_key->GetPublicKeyY());
174 }
175
176 } // namespace
177 } // namespace bt::sm
178