xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/sm/ecdh_key_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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