David Zeuthen | c75ac31 | 2019-10-28 13:16:45 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2019, The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include <iomanip> |
| 18 | #include <iostream> |
| 19 | #include <sstream> |
| 20 | |
| 21 | #include <gmock/gmock.h> |
| 22 | #include <gtest/gtest.h> |
| 23 | |
| 24 | #include <android/hardware/identity/support/IdentityCredentialSupport.h> |
| 25 | |
| 26 | #include <cppbor.h> |
| 27 | #include <cppbor_parse.h> |
| 28 | |
| 29 | using std::optional; |
| 30 | using std::string; |
| 31 | using std::vector; |
| 32 | |
| 33 | namespace android { |
| 34 | namespace hardware { |
| 35 | namespace identity { |
| 36 | |
| 37 | TEST(IdentityCredentialSupport, encodeHex) { |
| 38 | EXPECT_EQ("", support::encodeHex(vector<uint8_t>({}))); |
| 39 | EXPECT_EQ("01", support::encodeHex(vector<uint8_t>({1}))); |
| 40 | EXPECT_EQ("000102030405060708090a0b0c0d0e0f10", |
| 41 | support::encodeHex( |
| 42 | vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}))); |
| 43 | EXPECT_EQ("0102ffe060", support::encodeHex(vector<uint8_t>({1, 2, 255, 224, 96}))); |
| 44 | } |
| 45 | |
| 46 | TEST(IdentityCredentialSupport, decodeHex) { |
| 47 | EXPECT_EQ(vector<uint8_t>({}), support::decodeHex("")); |
| 48 | EXPECT_EQ(vector<uint8_t>({1}), support::decodeHex("01")); |
| 49 | |
| 50 | EXPECT_EQ(vector<uint8_t>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), |
| 51 | support::decodeHex("000102030405060708090a0b0c0d0e0f10")); |
| 52 | |
| 53 | EXPECT_FALSE(support::decodeHex("0g")); |
| 54 | EXPECT_FALSE(support::decodeHex("0")); |
| 55 | EXPECT_FALSE(support::decodeHex("012")); |
| 56 | } |
| 57 | |
| 58 | TEST(IdentityCredentialSupport, CborPrettyPrint) { |
| 59 | EXPECT_EQ("'Some text'", support::cborPrettyPrint(cppbor::Tstr("Some text").encode())); |
| 60 | |
| 61 | EXPECT_EQ("''", support::cborPrettyPrint(cppbor::Tstr("").encode())); |
| 62 | |
| 63 | EXPECT_EQ("{0x01, 0x00, 0x02, 0xf0, 0xff, 0x40}", |
| 64 | support::cborPrettyPrint( |
| 65 | cppbor::Bstr(vector<uint8_t>({1, 0, 2, 240, 255, 64})).encode())); |
| 66 | |
| 67 | EXPECT_EQ("{}", support::cborPrettyPrint(cppbor::Bstr(vector<uint8_t>()).encode())); |
| 68 | |
| 69 | EXPECT_EQ("true", support::cborPrettyPrint(cppbor::Bool(true).encode())); |
| 70 | |
| 71 | EXPECT_EQ("false", support::cborPrettyPrint(cppbor::Bool(false).encode())); |
| 72 | |
| 73 | EXPECT_EQ("42", support::cborPrettyPrint(cppbor::Uint(42).encode())); |
| 74 | |
| 75 | EXPECT_EQ("9223372036854775807", // 0x7fff ffff ffff ffff |
| 76 | support::cborPrettyPrint(cppbor::Uint(std::numeric_limits<int64_t>::max()).encode())); |
| 77 | |
| 78 | EXPECT_EQ("-42", support::cborPrettyPrint(cppbor::Nint(-42).encode())); |
| 79 | |
| 80 | EXPECT_EQ("-9223372036854775808", // -0x8000 0000 0000 0000 |
| 81 | support::cborPrettyPrint(cppbor::Nint(std::numeric_limits<int64_t>::min()).encode())); |
| 82 | } |
| 83 | |
| 84 | TEST(IdentityCredentialSupport, CborPrettyPrintCompound) { |
| 85 | cppbor::Array array = cppbor::Array("foo", "bar", "baz"); |
| 86 | EXPECT_EQ("['foo', 'bar', 'baz', ]", support::cborPrettyPrint(array.encode())); |
| 87 | |
| 88 | cppbor::Map map = cppbor::Map().add("foo", 42).add("bar", 43).add("baz", 44); |
| 89 | EXPECT_EQ( |
| 90 | "{\n" |
| 91 | " 'foo' : 42,\n" |
| 92 | " 'bar' : 43,\n" |
| 93 | " 'baz' : 44,\n" |
| 94 | "}", |
| 95 | support::cborPrettyPrint(map.encode())); |
| 96 | |
| 97 | cppbor::Array array2 = cppbor::Array(cppbor::Tstr("Some text"), cppbor::Nint(-42)); |
| 98 | EXPECT_EQ("['Some text', -42, ]", support::cborPrettyPrint(array2.encode())); |
| 99 | |
| 100 | cppbor::Map map2 = cppbor::Map().add(42, "foo").add(43, "bar").add(44, "baz"); |
| 101 | EXPECT_EQ( |
| 102 | "{\n" |
| 103 | " 42 : 'foo',\n" |
| 104 | " 43 : 'bar',\n" |
| 105 | " 44 : 'baz',\n" |
| 106 | "}", |
| 107 | support::cborPrettyPrint(map2.encode())); |
| 108 | |
| 109 | cppbor::Array deeplyNestedArrays = |
| 110 | cppbor::Array(cppbor::Array(cppbor::Array("a", "b", "c")), |
| 111 | cppbor::Array(cppbor::Array("d", "e", cppbor::Array("f", "g")))); |
| 112 | EXPECT_EQ( |
| 113 | "[\n" |
| 114 | " ['a', 'b', 'c', ],\n" |
| 115 | " [\n 'd',\n" |
| 116 | " 'e',\n" |
| 117 | " ['f', 'g', ],\n" |
| 118 | " ],\n" |
| 119 | "]", |
| 120 | support::cborPrettyPrint(deeplyNestedArrays.encode())); |
| 121 | |
| 122 | EXPECT_EQ( |
| 123 | "[\n" |
| 124 | " {0x0a, 0x0b},\n" |
| 125 | " 'foo',\n" |
| 126 | " 42,\n" |
| 127 | " ['foo', 'bar', 'baz', ],\n" |
| 128 | " {\n" |
| 129 | " 'foo' : 42,\n" |
| 130 | " 'bar' : 43,\n" |
| 131 | " 'baz' : 44,\n" |
| 132 | " },\n" |
| 133 | " {\n" |
| 134 | " 'deep1' : ['Some text', -42, ],\n" |
| 135 | " 'deep2' : {\n" |
| 136 | " 42 : 'foo',\n" |
| 137 | " 43 : 'bar',\n" |
| 138 | " 44 : 'baz',\n" |
| 139 | " },\n" |
| 140 | " },\n" |
| 141 | "]", |
| 142 | support::cborPrettyPrint(cppbor::Array(cppbor::Bstr(vector<uint8_t>{10, 11}), |
| 143 | cppbor::Tstr("foo"), cppbor::Uint(42), |
| 144 | std::move(array), std::move(map), |
| 145 | (cppbor::Map() |
| 146 | .add("deep1", std::move(array2)) |
| 147 | .add("deep2", std::move(map2)))) |
| 148 | .encode())); |
| 149 | } |
| 150 | |
| 151 | TEST(IdentityCredentialSupport, Signatures) { |
| 152 | vector<uint8_t> data = {1, 2, 3}; |
| 153 | |
| 154 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 155 | ASSERT_TRUE(keyPair); |
| 156 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 157 | ASSERT_TRUE(privKey); |
| 158 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 159 | ASSERT_TRUE(pubKey); |
| 160 | |
| 161 | optional<vector<uint8_t>> signature = support::signEcDsa(privKey.value(), data); |
| 162 | ASSERT_TRUE( |
| 163 | support::checkEcDsaSignature(support::sha256(data), signature.value(), pubKey.value())); |
| 164 | |
| 165 | // Manipulate the signature, check that verification fails. |
| 166 | vector<uint8_t> modifiedSignature = signature.value(); |
| 167 | modifiedSignature[0] ^= 0xff; |
| 168 | ASSERT_FALSE( |
| 169 | support::checkEcDsaSignature(support::sha256(data), modifiedSignature, pubKey.value())); |
| 170 | |
| 171 | // Manipulate the data being checked, check that verification fails. |
| 172 | vector<uint8_t> modifiedDigest = support::sha256(data); |
| 173 | modifiedDigest[0] ^= 0xff; |
| 174 | ASSERT_FALSE(support::checkEcDsaSignature(modifiedDigest, signature.value(), pubKey.value())); |
| 175 | } |
| 176 | |
| 177 | string replaceLine(const string& str, ssize_t lineNumber, const string& replacement) { |
| 178 | vector<string> lines; |
| 179 | std::istringstream f(str); |
| 180 | string s; |
| 181 | while (std::getline(f, s, '\n')) { |
| 182 | lines.push_back(s); |
| 183 | } |
| 184 | |
| 185 | size_t numLines = lines.size(); |
| 186 | if (lineNumber < 0) { |
| 187 | lineNumber = numLines - (-lineNumber); |
| 188 | } |
| 189 | |
| 190 | string ret; |
| 191 | size_t n = 0; |
| 192 | for (const string& line : lines) { |
| 193 | if (n == lineNumber) { |
| 194 | ret += replacement + "\n"; |
| 195 | } else { |
| 196 | ret += line + "\n"; |
| 197 | } |
| 198 | n++; |
| 199 | } |
| 200 | return ret; |
| 201 | } |
| 202 | |
| 203 | TEST(IdentityCredentialSupport, CoseSignatures) { |
| 204 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 205 | ASSERT_TRUE(keyPair); |
| 206 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 207 | ASSERT_TRUE(privKey); |
| 208 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 209 | ASSERT_TRUE(pubKey); |
| 210 | |
| 211 | vector<uint8_t> data = {1, 2, 3}; |
| 212 | optional<vector<uint8_t>> coseSign1 = support::coseSignEcDsa( |
| 213 | privKey.value(), data, {} /* detachedContent */, {} /* x5chain */); |
| 214 | ASSERT_TRUE(support::coseCheckEcDsaSignature(coseSign1.value(), {} /* detachedContent */, |
| 215 | pubKey.value())); |
| 216 | |
| 217 | optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value()); |
| 218 | ASSERT_TRUE(payload); |
| 219 | ASSERT_EQ(data, payload.value()); |
| 220 | |
| 221 | // Finally, check that |coseSign1| are the bytes of a valid COSE_Sign1 message |
| 222 | string out = support::cborPrettyPrint(coseSign1.value()); |
| 223 | out = replaceLine(out, -2, " [] // Signature Removed"); |
| 224 | EXPECT_EQ( |
| 225 | "[\n" |
| 226 | " {0xa1, 0x01, 0x26},\n" // Bytes of {1:-7} 1 is 'alg' label and -7 is "ECDSA 256" |
| 227 | " {},\n" |
| 228 | " {0x01, 0x02, 0x03},\n" |
| 229 | " [] // Signature Removed\n" |
| 230 | "]\n", |
| 231 | out); |
| 232 | } |
| 233 | |
| 234 | TEST(IdentityCredentialSupport, CoseSignaturesAdditionalData) { |
| 235 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 236 | ASSERT_TRUE(keyPair); |
| 237 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 238 | ASSERT_TRUE(privKey); |
| 239 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 240 | ASSERT_TRUE(pubKey); |
| 241 | |
| 242 | vector<uint8_t> detachedContent = {1, 2, 3}; |
| 243 | optional<vector<uint8_t>> coseSign1 = support::coseSignEcDsa(privKey.value(), {} /* data */, |
| 244 | detachedContent, {} /* x5chain */); |
| 245 | ASSERT_TRUE( |
| 246 | support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value())); |
| 247 | |
| 248 | optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value()); |
| 249 | ASSERT_TRUE(payload); |
| 250 | ASSERT_EQ(0, payload.value().size()); |
| 251 | |
| 252 | // Finally, check that |coseSign1| are the bytes of a valid COSE_Sign1 message |
| 253 | string out = support::cborPrettyPrint(coseSign1.value()); |
| 254 | out = replaceLine(out, -2, " [] // Signature Removed"); |
| 255 | EXPECT_EQ( |
| 256 | "[\n" |
| 257 | " {0xa1, 0x01, 0x26},\n" // Bytes of {1:-7} 1 is 'alg' label and -7 is "ECDSA 256" |
| 258 | " {},\n" |
| 259 | " null,\n" |
| 260 | " [] // Signature Removed\n" |
| 261 | "]\n", |
| 262 | out); |
| 263 | } |
| 264 | |
| 265 | vector<uint8_t> generateCertChain(size_t numCerts) { |
| 266 | vector<vector<uint8_t>> certs; |
| 267 | |
| 268 | for (size_t n = 0; n < numCerts; n++) { |
| 269 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 270 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 271 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 272 | |
| 273 | optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate( |
| 274 | pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0); |
| 275 | certs.push_back(cert.value()); |
| 276 | } |
| 277 | return support::certificateChainJoin(certs); |
| 278 | } |
| 279 | |
| 280 | TEST(IdentityCredentialSupport, CoseSignaturesX5ChainWithSingleCert) { |
| 281 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 282 | ASSERT_TRUE(keyPair); |
| 283 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 284 | ASSERT_TRUE(privKey); |
| 285 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 286 | ASSERT_TRUE(pubKey); |
| 287 | |
| 288 | vector<uint8_t> certChain = generateCertChain(1); |
| 289 | optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(certChain); |
| 290 | ASSERT_EQ(1, splitCerts.value().size()); |
| 291 | |
| 292 | vector<uint8_t> detachedContent = {1, 2, 3}; |
| 293 | optional<vector<uint8_t>> coseSign1 = |
| 294 | support::coseSignEcDsa(privKey.value(), {} /* data */, detachedContent, certChain); |
| 295 | ASSERT_TRUE( |
| 296 | support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value())); |
| 297 | |
| 298 | optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value()); |
| 299 | ASSERT_TRUE(payload); |
| 300 | ASSERT_EQ(0, payload.value().size()); |
| 301 | |
| 302 | optional<vector<uint8_t>> certsRecovered = support::coseSignGetX5Chain(coseSign1.value()); |
| 303 | EXPECT_EQ(certsRecovered.value(), certChain); |
| 304 | } |
| 305 | |
| 306 | TEST(IdentityCredentialSupport, CoseSignaturesX5ChainWithMultipleCerts) { |
| 307 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 308 | ASSERT_TRUE(keyPair); |
| 309 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 310 | ASSERT_TRUE(privKey); |
| 311 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 312 | ASSERT_TRUE(pubKey); |
| 313 | |
| 314 | vector<uint8_t> certChain = generateCertChain(5); |
| 315 | optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(certChain); |
| 316 | ASSERT_EQ(5, splitCerts.value().size()); |
| 317 | |
| 318 | vector<uint8_t> detachedContent = {1, 2, 3}; |
| 319 | optional<vector<uint8_t>> coseSign1 = |
| 320 | support::coseSignEcDsa(privKey.value(), {} /* data */, detachedContent, certChain); |
| 321 | ASSERT_TRUE( |
| 322 | support::coseCheckEcDsaSignature(coseSign1.value(), detachedContent, pubKey.value())); |
| 323 | |
| 324 | optional<vector<uint8_t>> payload = support::coseSignGetPayload(coseSign1.value()); |
| 325 | ASSERT_TRUE(payload); |
| 326 | ASSERT_EQ(0, payload.value().size()); |
| 327 | |
| 328 | optional<vector<uint8_t>> certsRecovered = support::coseSignGetX5Chain(coseSign1.value()); |
| 329 | EXPECT_EQ(certsRecovered.value(), certChain); |
| 330 | } |
| 331 | |
| 332 | TEST(IdentityCredentialSupport, CertificateChain) { |
| 333 | optional<vector<uint8_t>> keyPair = support::createEcKeyPair(); |
| 334 | ASSERT_TRUE(keyPair); |
| 335 | optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 336 | ASSERT_TRUE(privKey); |
| 337 | optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 338 | ASSERT_TRUE(pubKey); |
| 339 | |
| 340 | optional<vector<uint8_t>> cert = support::ecPublicKeyGenerateCertificate( |
| 341 | pubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0); |
| 342 | |
| 343 | optional<vector<uint8_t>> extractedPubKey = |
| 344 | support::certificateChainGetTopMostKey(cert.value()); |
| 345 | ASSERT_TRUE(extractedPubKey); |
| 346 | ASSERT_EQ(pubKey.value(), extractedPubKey.value()); |
| 347 | |
| 348 | // We expect to the chain returned by ecPublicKeyGenerateCertificate() to only have a |
| 349 | // single element |
| 350 | optional<vector<vector<uint8_t>>> splitCerts = support::certificateChainSplit(cert.value()); |
| 351 | ASSERT_EQ(1, splitCerts.value().size()); |
| 352 | ASSERT_EQ(splitCerts.value()[0], cert.value()); |
| 353 | |
| 354 | optional<vector<uint8_t>> otherKeyPair = support::createEcKeyPair(); |
| 355 | ASSERT_TRUE(otherKeyPair); |
| 356 | optional<vector<uint8_t>> otherPrivKey = support::ecKeyPairGetPrivateKey(keyPair.value()); |
| 357 | ASSERT_TRUE(otherPrivKey); |
| 358 | optional<vector<uint8_t>> otherPubKey = support::ecKeyPairGetPublicKey(keyPair.value()); |
| 359 | ASSERT_TRUE(otherPubKey); |
| 360 | optional<vector<uint8_t>> otherCert = support::ecPublicKeyGenerateCertificate( |
| 361 | otherPubKey.value(), privKey.value(), "0001", "someIssuer", "someSubject", 0, 0); |
| 362 | |
| 363 | // Now both cert and otherCert are two distinct certificates. Let's make a |
| 364 | // chain and check that certificateChainSplit() works as expected. |
| 365 | ASSERT_NE(cert.value(), otherCert.value()); |
| 366 | const vector<vector<uint8_t>> certs2 = {cert.value(), otherCert.value()}; |
| 367 | vector<uint8_t> certs2combined = support::certificateChainJoin(certs2); |
| 368 | ASSERT_EQ(certs2combined.size(), cert.value().size() + otherCert.value().size()); |
| 369 | optional<vector<vector<uint8_t>>> splitCerts2 = support::certificateChainSplit(certs2combined); |
| 370 | ASSERT_EQ(certs2, splitCerts2.value()); |
| 371 | } |
| 372 | |
| 373 | vector<uint8_t> strToVec(const string& str) { |
| 374 | vector<uint8_t> ret; |
| 375 | size_t size = str.size(); |
| 376 | ret.resize(size); |
| 377 | memcpy(ret.data(), str.data(), size); |
| 378 | return ret; |
| 379 | } |
| 380 | |
| 381 | // Test vector from https://en.wikipedia.org/wiki/HMAC |
| 382 | TEST(IdentityCredentialSupport, hmacSha256) { |
| 383 | vector<uint8_t> key = strToVec("key"); |
| 384 | vector<uint8_t> data = strToVec("The quick brown fox jumps over the lazy dog"); |
| 385 | |
| 386 | vector<uint8_t> expected = |
| 387 | support::decodeHex("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8") |
| 388 | .value(); |
| 389 | |
| 390 | optional<vector<uint8_t>> hmac = support::hmacSha256(key, data); |
| 391 | ASSERT_TRUE(hmac); |
| 392 | ASSERT_EQ(expected, hmac.value()); |
| 393 | } |
| 394 | |
| 395 | // See also CoseMac0 test in UtilUnitTest.java inside cts/tests/tests/identity/ |
| 396 | TEST(IdentityCredentialSupport, CoseMac0) { |
| 397 | vector<uint8_t> key; |
| 398 | key.resize(32); |
| 399 | vector<uint8_t> data = {0x10, 0x11, 0x12, 0x13}; |
| 400 | vector<uint8_t> detachedContent = {}; |
| 401 | |
| 402 | optional<vector<uint8_t>> mac = support::coseMac0(key, data, detachedContent); |
| 403 | ASSERT_TRUE(mac); |
| 404 | |
| 405 | EXPECT_EQ( |
| 406 | "[\n" |
| 407 | " {0xa1, 0x01, 0x05},\n" |
| 408 | " {},\n" |
| 409 | " {0x10, 0x11, 0x12, 0x13},\n" |
| 410 | " {0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, 0xd8, " |
| 411 | "0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, 0x5e, 0xbb, " |
| 412 | "0xe2, 0x2d, 0x42, 0xbe, 0x53},\n" |
| 413 | "]", |
| 414 | support::cborPrettyPrint(mac.value())); |
| 415 | } |
| 416 | |
| 417 | TEST(IdentityCredentialSupport, CoseMac0DetachedContent) { |
| 418 | vector<uint8_t> key; |
| 419 | key.resize(32); |
| 420 | vector<uint8_t> data = {}; |
| 421 | vector<uint8_t> detachedContent = {0x10, 0x11, 0x12, 0x13}; |
| 422 | |
| 423 | optional<vector<uint8_t>> mac = support::coseMac0(key, data, detachedContent); |
| 424 | ASSERT_TRUE(mac); |
| 425 | |
| 426 | // Same HMAC as in CoseMac0 test, only difference is that payload is null. |
| 427 | EXPECT_EQ( |
| 428 | "[\n" |
| 429 | " {0xa1, 0x01, 0x05},\n" |
| 430 | " {},\n" |
| 431 | " null,\n" |
| 432 | " {0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, 0xd8, " |
| 433 | "0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, 0x5e, 0xbb, " |
| 434 | "0xe2, 0x2d, 0x42, 0xbe, 0x53},\n" |
| 435 | "]", |
| 436 | support::cborPrettyPrint(mac.value())); |
| 437 | } |
| 438 | |
David Zeuthen | 34abaae | 2020-10-26 20:26:36 -0400 | [diff] [blame] | 439 | // Generates a private key in DER format for a small value of 'd'. |
| 440 | // |
| 441 | // Used for test vectors. |
| 442 | // |
| 443 | vector<uint8_t> p256PrivateKeyFromD(uint8_t d) { |
| 444 | vector<uint8_t> privateUncompressed; |
| 445 | privateUncompressed.resize(32); |
| 446 | privateUncompressed[31] = d; |
| 447 | optional<vector<uint8_t>> privateKey = support::ecPrivateKeyToKeyPair(privateUncompressed); |
| 448 | return privateKey.value(); |
| 449 | } |
| 450 | |
| 451 | std::pair<vector<uint8_t>, vector<uint8_t>> p256PrivateKeyGetXandY( |
| 452 | const vector<uint8_t> privateKey) { |
| 453 | optional<vector<uint8_t>> publicUncompressed = support::ecKeyPairGetPublicKey(privateKey); |
| 454 | vector<uint8_t> x = vector<uint8_t>(publicUncompressed.value().begin() + 1, |
| 455 | publicUncompressed.value().begin() + 33); |
| 456 | vector<uint8_t> y = vector<uint8_t>(publicUncompressed.value().begin() + 33, |
| 457 | publicUncompressed.value().begin() + 65); |
| 458 | return std::make_pair(x, y); |
| 459 | } |
| 460 | |
| 461 | const cppbor::Item* findValueForTstr(const cppbor::Map* map, const string& keyValue) { |
| 462 | // TODO: Need cast until libcppbor's Map::get() is marked as const |
| 463 | auto [item, found] = ((cppbor::Map*)map)->get(keyValue); |
| 464 | if (!found) { |
| 465 | return nullptr; |
| 466 | } |
| 467 | return item.get(); |
| 468 | } |
| 469 | |
| 470 | const cppbor::Array* findArrayValueForTstr(const cppbor::Map* map, const string& keyValue) { |
| 471 | const cppbor::Item* item = findValueForTstr(map, keyValue); |
| 472 | if (item == nullptr) { |
| 473 | return nullptr; |
| 474 | } |
| 475 | return item->asArray(); |
| 476 | } |
| 477 | |
| 478 | const cppbor::Map* findMapValueForTstr(const cppbor::Map* map, const string& keyValue) { |
| 479 | const cppbor::Item* item = findValueForTstr(map, keyValue); |
| 480 | if (item == nullptr) { |
| 481 | return nullptr; |
| 482 | } |
| 483 | return item->asMap(); |
| 484 | } |
| 485 | |
| 486 | const cppbor::Semantic* findSemanticValueForTstr(const cppbor::Map* map, const string& keyValue) { |
| 487 | const cppbor::Item* item = findValueForTstr(map, keyValue); |
| 488 | if (item == nullptr) { |
| 489 | return nullptr; |
| 490 | } |
| 491 | return item->asSemantic(); |
| 492 | } |
| 493 | |
| 494 | const std::string findStringValueForTstr(const cppbor::Map* map, const string& keyValue) { |
| 495 | const cppbor::Item* item = findValueForTstr(map, keyValue); |
| 496 | if (item == nullptr) { |
| 497 | return nullptr; |
| 498 | } |
| 499 | const cppbor::Tstr* tstr = item->asTstr(); |
| 500 | if (tstr == nullptr) { |
| 501 | return ""; |
| 502 | } |
| 503 | return tstr->value(); |
| 504 | } |
| 505 | |
| 506 | TEST(IdentityCredentialSupport, testVectors_18013_5) { |
| 507 | // This is a test against known vectors for ISO 18013-5. |
| 508 | // |
| 509 | // The objective of this test is to verify that support::calcEMacKey() and |
| 510 | // support::calcMac() agree with the given test vectors. |
| 511 | // |
| 512 | |
| 513 | // We're given static device key: |
| 514 | // |
| 515 | // x: 28412803729898893058558238221310261427084375743576167377786533380249859400145 |
| 516 | // y: 65403602826180996396520286939226973026599920614829401631985882360676038096704 |
| 517 | // d: 11 |
| 518 | // |
| 519 | vector<uint8_t> deviceKey = p256PrivateKeyFromD(11); |
| 520 | auto [deviceKeyX, deviceKeyY] = p256PrivateKeyGetXandY(deviceKey); |
| 521 | EXPECT_EQ(support::encodeHex(deviceKeyX), |
| 522 | "3ed113b7883b4c590638379db0c21cda16742ed0255048bf433391d374bc21d1"); |
| 523 | EXPECT_EQ(support::encodeHex(deviceKeyY), |
| 524 | "9099209accc4c8a224c843afa4f4c68a090d04da5e9889dae2f8eefce82a3740"); |
| 525 | |
| 526 | // We're given Ephemeral reader key: |
| 527 | // |
| 528 | // x: 59535862115950685744176693329402396749019581632805653266809849538337418304154 |
| 529 | // y: 53776829996815113213100700404832701936765102413212294632483274374518863708344 |
| 530 | // d: 20 |
| 531 | // |
| 532 | vector<uint8_t> ephemeralReaderKey = p256PrivateKeyFromD(20); |
| 533 | auto [ephemeralReaderKeyX, ephemeralReaderKeyY] = p256PrivateKeyGetXandY(ephemeralReaderKey); |
| 534 | EXPECT_EQ(support::encodeHex(ephemeralReaderKeyX), |
| 535 | "83a01a9378395bab9bcd6a0ad03cc56d56e6b19250465a94a234dc4c6b28da9a"); |
| 536 | EXPECT_EQ(support::encodeHex(ephemeralReaderKeyY), |
| 537 | "76e49b6de2f73234ae6a5eb9d612b75c9f2202bb6923f54ff8240aaa86f640b8"); |
| 538 | vector<uint8_t> ephemeralReaderKeyPublic = |
| 539 | support::ecKeyPairGetPublicKey(ephemeralReaderKey).value(); |
| 540 | |
| 541 | // We're given SessionEstablishment. |
| 542 | // |
| 543 | // SessionEstablishment = { |
| 544 | // "eReaderKey" : EReaderKeyBytes, |
| 545 | // "data" : bstr ; Encrypted mdoc request |
| 546 | // } |
| 547 | // |
| 548 | // Fish out EReaderKey from this. |
| 549 | // |
| 550 | // Note that the test vector below is incorrect insofar that it uses |
| 551 | // "eReaderKeyBytes" instead of just "eReaderKey". This will be corrected in |
| 552 | // the future. |
| 553 | // |
| 554 | optional<vector<uint8_t>> sessionEstablishmentEncoded = support::decodeHex( |
| 555 | "a26f655265616465724b65794279746573d818584ba40102200121582083a01a9378395bab9bcd6a0ad03c" |
| 556 | "c56d56e6b19250465a94a234dc4c6b28da9a22582076e49b6de2f73234ae6a5eb9d612b75c9f2202bb6923" |
| 557 | "f54ff8240aaa86f640b864646174615902d945b31040c57491acb6d46a71f6c1f67a0b837df1bda9089fd0" |
| 558 | "3d0b1fdac3eeb2874a4ef6f90c97d03397186ba00a91102faae7e992e15f761d5662c3c37e3c6c2cfd2ebc" |
| 559 | "0bf59dbb8795e377bd7dd353230a41ba2d82294b45871a39b42ca531f26b52f46e356fbaf5075c8fd5b8b0" |
| 560 | "8a0df4a1d2e1bdd2e5d69169c1efbb51e393e608d833d325bebfbccb2e15ec08f94b264582fa7b93f7cebc" |
| 561 | "aa69f4f0cac2744d4fe35b04df26b2ae69273eed33024949080c1c95a6ef046beede959e9494297dd770af" |
| 562 | "4ac6fdd56783aa012555c213dc05cf0f41d1c95119720fcfe1621027f80e2ddd56ea3c1fc596f7b2579333" |
| 563 | "5a887ec788092b4a69d23b6219e27d0249b50b3fdcb95b5227007689362e0416b3bae3dae7cb56b4394666" |
| 564 | "4e3a3f60dce8d0b678fcd754bebf87bd2b0278dd782d952488a46f2874e34c2dd97bb74084a62b850e9719" |
| 565 | "252cd1dca7dbf1858193f6cf093cb3735312bbe1138cf29d8f350e285923f8ef07065299926720b42264e8" |
| 566 | "fd5d4b133e72f47c4e999ea689c353f8b41e50a59838e1a0d09eca4a557f77a9c389a0591ad1639119ce86" |
| 567 | "edc3320130480ee5101effae6066e8c85aac9ead2ae83e49c1e508aab02f753decbb522ea2200d62fd5d26" |
| 568 | "094bd35100bffaa1cdc6af9f7e9cfe7b63da6b5671cd5ac2cf5da450c72addc64cde441f3b7f7fdaf930ad" |
| 569 | "1e13388e8a7308d8ca4607e59e082db431a232e7e12cb692baeb4b2127e110ff24cea322ffdbc2e4d9c4c6" |
| 570 | "bed27753137d07897c8613627a799a560cf1a2d1edb3de029442862940a5ed7785eea8b6ace93aa6af0792" |
| 571 | "fd82877f62d07b757d0179ecbb7347004ecc9c0690d41f75f188cb17ffd2cec2ad8c9675466bb33b737a2a" |
| 572 | "e7592b2dcb8132aced2e572266f3f5413a5f9d6d4339a1e4662622af2e7e157a4ea3bfd5c4247e2ec91d8c" |
| 573 | "5c3c17427d5edfae673d0e0f782a8d40fa805fd8bc82ae3cb21a65cdad863e02309f6b01d1753fa884b778" |
| 574 | "f6e019a2004d8964deeb11f1fd478fcb"); |
| 575 | ASSERT_TRUE(sessionEstablishmentEncoded); |
| 576 | auto [sessionEstablishmentItem, _se, _se2] = cppbor::parse(sessionEstablishmentEncoded.value()); |
| 577 | const cppbor::Map* sessionEstablishment = sessionEstablishmentItem->asMap(); |
| 578 | ASSERT_NE(sessionEstablishment, nullptr); |
| 579 | const cppbor::Semantic* eReaderKeyBytes = |
| 580 | findSemanticValueForTstr(sessionEstablishment, "eReaderKeyBytes"); |
| 581 | ASSERT_NE(eReaderKeyBytes, nullptr); |
| 582 | ASSERT_EQ(eReaderKeyBytes->value(), 24); |
| 583 | const cppbor::Bstr* eReaderKeyBstr = eReaderKeyBytes->child()->asBstr(); |
| 584 | ASSERT_NE(eReaderKeyBstr, nullptr); |
| 585 | vector<uint8_t> eReaderKeyEncoded = eReaderKeyBstr->value(); |
| 586 | // TODO: verify this agrees with ephemeralReaderKeyX and ephemeralReaderKeyY |
| 587 | |
| 588 | // We're given DeviceEngagement. |
| 589 | // |
| 590 | vector<uint8_t> deviceEngagementEncoded = |
| 591 | support::decodeHex( |
| 592 | "a20063312e30018201d818584ba401022001215820cef66d6b2a3a993e591214d1ea223fb545ca" |
| 593 | "6c471c48306e4c36069404c5723f225820878662a229aaae906e123cdd9d3b4c10590ded29fe75" |
| 594 | "1eeeca34bbaa44af0773") |
| 595 | .value(); |
| 596 | |
| 597 | // Now calculate SessionTranscriptBytes. It is defined as |
| 598 | // |
| 599 | // SessionTranscript = [ |
| 600 | // DeviceEngagementBytes, |
| 601 | // EReaderKeyBytes, |
| 602 | // Handover |
| 603 | // ] |
| 604 | // |
| 605 | // SessionTranscriptBytes = #6.24(bstr .cbor SessionTranscript) |
| 606 | // |
| 607 | cppbor::Array sessionTranscript; |
| 608 | sessionTranscript.add(cppbor::Semantic(24, deviceEngagementEncoded)); |
| 609 | sessionTranscript.add(cppbor::Semantic(24, eReaderKeyEncoded)); |
| 610 | sessionTranscript.add(cppbor::Null()); |
| 611 | vector<uint8_t> sessionTranscriptEncoded = sessionTranscript.encode(); |
| 612 | vector<uint8_t> sessionTranscriptBytes = |
| 613 | cppbor::Semantic(24, sessionTranscriptEncoded).encode(); |
| 614 | |
| 615 | // The expected EMacKey is 4c1ebb8aacc633465390fa44edfdb49cb57f2e079aaa771d812584699c0b97e2 |
| 616 | // |
| 617 | // Verify that support::calcEMacKey() gets the same result. |
| 618 | // |
| 619 | optional<vector<uint8_t>> eMacKey = |
| 620 | support::calcEMacKey(support::ecKeyPairGetPrivateKey(deviceKey).value(), // private key |
| 621 | ephemeralReaderKeyPublic, // public key |
| 622 | sessionTranscriptBytes); // sessionTranscriptBytes |
| 623 | ASSERT_TRUE(eMacKey); |
| 624 | ASSERT_EQ(support::encodeHex(eMacKey.value()), |
| 625 | "4c1ebb8aacc633465390fa44edfdb49cb57f2e079aaa771d812584699c0b97e2"); |
| 626 | |
| 627 | // Also do it the other way around |
| 628 | // |
| 629 | optional<vector<uint8_t>> eMacKey2 = support::calcEMacKey( |
| 630 | support::ecKeyPairGetPrivateKey(ephemeralReaderKey).value(), // private key |
| 631 | support::ecKeyPairGetPublicKey(deviceKey).value(), // public key |
| 632 | sessionTranscriptBytes); // sessionTranscriptBytes |
| 633 | ASSERT_TRUE(eMacKey2); |
| 634 | ASSERT_EQ(support::encodeHex(eMacKey2.value()), |
| 635 | "4c1ebb8aacc633465390fa44edfdb49cb57f2e079aaa771d812584699c0b97e2"); |
| 636 | |
| 637 | // We're given DeviceResponse |
| 638 | // |
| 639 | vector<uint8_t> deviceResponseEncoded = |
| 640 | support::decodeHex( |
| 641 | "a36776657273696f6e63312e3069646f63756d656e747381a367646f6354797065756f72672e69" |
| 642 | "736f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d6553706163" |
| 643 | "6573a2716f72672e69736f2e31383031332e352e3181d8185863a4686469676573744944016672" |
| 644 | "616e646f6d58208798645b20ea200e19ffabac92624bee6aec63aceedecfb1b80077d22bfc20e9" |
| 645 | "71656c656d656e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456" |
| 646 | "616c756563446f656b636f6d2e6578616d706c6581d8185864a468646967657374494401667261" |
| 647 | "6e646f6d5820218ecf13521b53f4b96abaebe56417afec0e4c91fc8fb26086cd1e5cdc1a94ff71" |
| 648 | "656c656d656e744964656e7469666965726f616e6f746865725f656c656d656e746c656c656d65" |
| 649 | "6e7456616c75650a6a697373756572417574688443a10126a118215901d2308201ce30820174a0" |
| 650 | "0302010202141f7d44f4f107c5ee3f566049cf5d72de294b0d23300a06082a8648ce3d04030230" |
| 651 | "233114301206035504030c0b75746f7069612069616361310b3009060355040613025553301e17" |
| 652 | "0d3230313030313030303030305a170d3231313030313030303030305a30213112301006035504" |
| 653 | "030c0975746f706961206473310b30090603550406130255533059301306072a8648ce3d020106" |
| 654 | "082a8648ce3d03010703420004301d9e502dc7e05da85da026a7ae9aa0fac9db7d52a95b3e3e3f" |
| 655 | "9aa0a1b45b8b6551b6f6b3061223e0d23c026b017d72298d9ae46887ca61d58db6aea17ee267a3" |
| 656 | "8187308184301e0603551d120417301581136578616d706c65406578616d706c652e636f6d301c" |
| 657 | "0603551d1f041530133011a00fa00d820b6578616d706c652e636f6d301d0603551d0e04160414" |
| 658 | "7bef4db59a1ffb07592bfc57f4743b8a73aea792300e0603551d0f0101ff040403020780301506" |
| 659 | "03551d250101ff040b3009060728818c5d050102300a06082a8648ce3d04030203480030450220" |
| 660 | "21d52fb1fbda80e5bfda1e8dfb1bc7bf0acb7261d5c9ff54425af76eb21571c602210082bf301f" |
| 661 | "89e0a2cb9ca9c9050352de80b47956764f7a3e07bf6a8cd87528a3b55901d2d8185901cda66776" |
| 662 | "657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c76616c75" |
| 663 | "6544696765737473a2716f72672e69736f2e31383031332e352e31a20058203b22af1126771f02" |
| 664 | "f0ea0d546d4ee3c5b51637381154f5211b79daf5f9facaa8015820f2cba0ce3cde5df901a3da75" |
| 665 | "13a4d7f7225fdfe5a306544529bf3dbcce655ca06b636f6d2e6578616d706c65a200582072636d" |
| 666 | "ddc282424a63499f4b3927aaa3b74da7b9c0134178bf735e949e4a761e01582006322d3cbe6603" |
| 667 | "876bdacc5b6679b51b0fc53d029c244fd5ea719d9028459c916d6465766963654b6579496e666f" |
| 668 | "a1696465766963654b6579a4010220012158203ed113b7883b4c590638379db0c21cda16742ed0" |
| 669 | "255048bf433391d374bc21d12258209099209accc4c8a224c843afa4f4c68a090d04da5e9889da" |
| 670 | "e2f8eefce82a374067646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c" |
| 671 | "76616c6964697479496e666fa3667369676e6564c074323032302d31302d30315431333a33303a" |
| 672 | "30325a6976616c696446726f6dc074323032302d31302d30315431333a33303a30325a6a76616c" |
| 673 | "6964556e74696cc074323032312d31302d30315431333a33303a30325a5840273ec1b59817d571" |
| 674 | "b5a2c5c0ab0ea213d42acb18547fd7097afcc888a22ecbb863c6461ce0e240880895b4aaa84308" |
| 675 | "784571c7be7aa3a2e7e3a2ea1a145ed1966c6465766963655369676e6564a26a6e616d65537061" |
| 676 | "636573d81841a06a64657669636541757468a1696465766963654d61638443a10105a0f6582009" |
| 677 | "da7c964ac004ec36ec64edd0c1abf50c03433c215c3ddb144768abcdf20a60667374617475730" |
| 678 | "0") |
| 679 | .value(); |
| 680 | auto [deviceResponseItem, _, _2] = cppbor::parse(deviceResponseEncoded); |
| 681 | const cppbor::Map* deviceResponse = deviceResponseItem->asMap(); |
| 682 | ASSERT_NE(deviceResponse, nullptr); |
| 683 | const cppbor::Array* documents = findArrayValueForTstr(deviceResponse, "documents"); |
| 684 | ASSERT_NE(documents, nullptr); |
| 685 | ASSERT_EQ(documents->size(), 1); |
| 686 | const cppbor::Map* document = ((*documents)[0])->asMap(); |
| 687 | ASSERT_NE(document, nullptr); |
| 688 | |
| 689 | // Get docType |
| 690 | string docType = findStringValueForTstr(document, "docType"); |
| 691 | ASSERT_EQ(docType, "org.iso.18013.5.1.mDL"); |
| 692 | |
| 693 | // Drill down... |
| 694 | const cppbor::Map* deviceSigned = findMapValueForTstr(document, "deviceSigned"); |
| 695 | ASSERT_NE(deviceSigned, nullptr); |
| 696 | |
| 697 | // Dig out the encoded form of DeviceNameSpaces |
| 698 | // |
| 699 | const cppbor::Semantic* deviceNameSpacesBytes = |
| 700 | findSemanticValueForTstr(deviceSigned, "nameSpaces"); |
| 701 | ASSERT_NE(deviceNameSpacesBytes, nullptr); |
| 702 | ASSERT_EQ(deviceNameSpacesBytes->value(), 24); |
| 703 | const cppbor::Bstr* deviceNameSpacesBstr = deviceNameSpacesBytes->child()->asBstr(); |
| 704 | ASSERT_NE(deviceNameSpacesBstr, nullptr); |
| 705 | vector<uint8_t> deviceNameSpacesEncoded = deviceNameSpacesBstr->value(); |
| 706 | |
| 707 | // (For this version of 18013-5, DeviceNameSpaces is always supposed to be empty, check that.) |
| 708 | EXPECT_EQ(deviceNameSpacesEncoded, cppbor::Map().encode()); |
| 709 | |
| 710 | const cppbor::Map* deviceAuth = findMapValueForTstr(deviceSigned, "deviceAuth"); |
| 711 | ASSERT_NE(deviceAuth, nullptr); |
| 712 | // deviceMac is is the COSE_Mac0.. dig out the encoded form to check that |
| 713 | // support::calcMac() gives exactly the same bytes. |
| 714 | // |
| 715 | const cppbor::Array* deviceMac = findArrayValueForTstr(deviceAuth, "deviceMac"); |
| 716 | ASSERT_NE(deviceMac, nullptr); |
| 717 | vector<uint8_t> deviceMacEncoded = deviceMac->encode(); |
| 718 | |
| 719 | // Now we calculate what it should be.. |
| 720 | optional<vector<uint8_t>> calculatedMac = |
| 721 | support::calcMac(sessionTranscriptEncoded, // SessionTranscript |
| 722 | docType, // DocType |
| 723 | deviceNameSpacesEncoded, // DeviceNamespaces |
| 724 | eMacKey.value()); // EMacKey |
| 725 | ASSERT_TRUE(calculatedMac); |
| 726 | |
| 727 | // ... and hopefully it's the same! |
| 728 | ASSERT_EQ(calculatedMac.value().size(), deviceMacEncoded.size()); |
| 729 | EXPECT_TRUE(memcmp(calculatedMac.value().data(), deviceMacEncoded.data(), |
| 730 | deviceMacEncoded.size()) == 0); |
| 731 | } |
| 732 | |
David Zeuthen | c75ac31 | 2019-10-28 13:16:45 -0400 | [diff] [blame] | 733 | } // namespace identity |
| 734 | } // namespace hardware |
| 735 | } // namespace android |
| 736 | |
| 737 | int main(int argc, char** argv) { |
| 738 | ::testing::InitGoogleTest(&argc, argv); |
| 739 | return RUN_ALL_TESTS(); |
| 740 | } |