Check RP id hash from responses
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Wed, 27 Mar 2024 13:32:17 +0000 (14:32 +0100)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Fri, 12 Apr 2024 10:05:05 +0000 (12:05 +0200)
Change-Id: I361624ad027caae1869059713362a4d91c5c1b27

srcs/ctap_message_processor.cpp
srcs/ctap_message_processor.h
tests/CMakeLists.txt
tests/ctap_message_processor_tests.cpp [new file with mode: 0644]
tests/message_tests.cpp

index 4b66470845f75579e9228823abd22586cfd6db8b..0aa9dcc5d1fbed63f59b48d3833956604202b189 100644 (file)
@@ -18,6 +18,9 @@
 #include "exception.h"
 #include "lowercase_hex_string_of.h"
 
+#include <cassert>
+#include <crypto/sha2.h>
+
 void CtapMessageProcessor::SetEncryptedTunnel(std::unique_ptr<IEncryptedTunnel> encryptedTunnel)
 {
     m_encryptedTunnel = std::move(encryptedTunnel);
@@ -46,6 +49,9 @@ CtapMessageProcessor::MakeCredential(const wauthn_client_data_s &clientData,
 
     auto bv = BufferView{resp.data() + 1, resp.size() - 1};
     res.mcResp.Deserialize(bv);
+
+    assert(options.rp);
+    VerifyRpIdHash(options.rp->id, res.mcResp.m_authData.m_rpIdHash);
     LogDebug("credentialId: " << LowercaseHexStringOf(
                  res.mcResp.m_authData.m_attestationData->m_credentialId));
 
@@ -82,6 +88,7 @@ CtapMessageProcessor::GetAssertion(const wauthn_client_data_s &clientData,
 
     auto bv = BufferView{resp.data() + 1, resp.size() - 1};
     res.gaResp.Deserialize(bv);
+    VerifyRpIdHash(options.rpId, res.gaResp.m_authData.m_rpIdHash);
 
     ShutdownMessage shutdown;
     Buffer shutdownMsg;
@@ -104,3 +111,12 @@ PostHandshakeResponse CtapMessageProcessor::receiveGetInfo()
     getInfo.Deserialize(bv);
     return getInfo;
 }
+
+void VerifyRpIdHash(const char *rpId, const BufferView &rpIdHash)
+{
+    assert(rpId);
+    CryptoBuffer rpIdBuffer(rpId, rpId + strlen(rpId));
+    auto expected = Crypto::Sha256Hash(rpIdBuffer);
+    if (BufferView(expected.data(), expected.size()) != rpIdHash)
+        THROW_UNKNOWN("RP id hash does not match");
+}
index 43a4008bb411246ee74a2ffd9269823825620665..5dabcc561603615f8470f97ba05df6164927555a 100644 (file)
@@ -65,3 +65,5 @@ private:
 
     std::unique_ptr<IEncryptedTunnel> m_encryptedTunnel;
 };
+
+void VerifyRpIdHash(const char *rpId, const BufferView &rpIdHash);
index ceef83743daab72bd157b2abd58489d97f142d7a..350ce83149a1a4b26bccf5dc681bddb52dad81e8 100644 (file)
@@ -43,6 +43,7 @@ SET(UNIT_TESTS_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/encrypted_tunnel_tests.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/base64_tests.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/message_tests.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/ctap_message_processor_tests.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/crypto/hkdf_unittest.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/crypto/hmac_unittest.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/crypto/sha2_unittest.cpp
diff --git a/tests/ctap_message_processor_tests.cpp b/tests/ctap_message_processor_tests.cpp
new file mode 100644 (file)
index 0000000..10b39d4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ *  Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+
+#include "ctap_message_processor.h"
+#include "exception.h"
+
+#include <gtest/gtest.h>
+
+TEST(CtapMessageProcessor, RpIdValidation)
+{
+    constexpr uint8_t blob[] = "\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd"
+                               "\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde";
+
+    BufferView view(blob, sizeof(blob) - 1);
+
+    EXPECT_NO_THROW(VerifyRpIdHash("acme.com", view));
+    EXPECT_THROW(VerifyRpIdHash("acme.co", view), Exception::Unknown);
+    view.remove_prefix(1);
+    EXPECT_THROW(VerifyRpIdHash("acme.com", view), Exception::Unknown);
+}
index 3a7e2f4dc4694a7f8564d8ee238dca56c475f216..cada7c982437c928850b18b0075ca315d106c98f 100644 (file)
@@ -17,6 +17,7 @@
 #include "exception.h"
 #include "message.h"
 
+#include <ctap_message_processor.h>
 #include <gtest/gtest.h>
 
 namespace {
@@ -558,8 +559,8 @@ TEST(Messages, ParseMakeCredentialResponse1)
     // example make credential response received from iPhone authenticator (w/o CTAP message type
     // prefix)
     constexpr uint8_t blob[] =
-        "\x00\xa4\x01\x64\x6e\x6f\x6e\x65\x02\x58\x98\xa3\x79\xa6\xf6\xee\xaf\xb9\xa5\x5e\x37\x8c"
-        "\x11\x80\x34\xe2\x75\x1e\x68\x2f\xab\x9f\x2d\x30\xab\x13\xd2\x12\x55\x86\xce\x19\x47\x5d"
+        "\x00\xa4\x01\x64\x6e\x6f\x6e\x65\x02\x58\x98\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b"
+        "\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x5d"
         "\x00\x00\x00\x00\xfb\xfc\x30\x07\x15\x4e\x4e\xcc\x8c\x0b\x6e\x02\x05\x57\xd7\xbd\x00\x14"
         "\x92\xa5\x07\x16\x15\x56\x78\x93\x64\x86\x22\xaf\xca\x66\x3d\x24\x50\x13\x8c\x58\xa5\x01"
         "\x02\x03\x26\x20\x01\x21\x58\x20\xc8\x39\x05\x61\x42\x33\x3e\x54\xcd\x7d\xed\x55\xb7\x64"
@@ -576,6 +577,7 @@ TEST(Messages, ParseMakeCredentialResponse1)
     ASSERT_EQ(msg.m_authDataRaw.size(), 152);
     AssertEq(msg.m_authDataRaw, blob + 11, msg.m_authDataRaw.size());
     AssertEq(msg.m_authData.m_rpIdHash, blob + 11, 32);
+    ASSERT_NO_THROW(VerifyRpIdHash("acme.com", msg.m_authData.m_rpIdHash));
     ASSERT_EQ(msg.m_authData.m_flags, blob[43]);
     ASSERT_TRUE(msg.m_authData.m_attestationData.has_value());
     AssertEq(msg.m_authData.m_attestationData->m_credentialId, blob + 66, 20);
@@ -593,8 +595,8 @@ TEST(Messages, ParseMakeCredentialResponse2)
     // example make credential response received from Android authenticator (w/o CTAP message type
     // prefix)
     constexpr uint8_t blob[] =
-        "\x00\xa3\x01\x64\x6e\x6f\x6e\x65\x02\x58\xa4\xa3\x79\xa6\xf6\xee\xaf\xb9\xa5\x5e\x37\x8c"
-        "\x11\x80\x34\xe2\x75\x1e\x68\x2f\xab\x9f\x2d\x30\xab\x13\xd2\x12\x55\x86\xce\x19\x47\x45"
+        "\x00\xa3\x01\x64\x6e\x6f\x6e\x65\x02\x58\xa4\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b"
+        "\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x45"
         "\x00\x00\x00\x00\x53\x41\x4d\x53\x55\x4e\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20"
         "\x8c\x5b\x4d\x2f\x62\x5f\xa0\xcf\x61\x44\xba\x1a\xa3\x2f\x5b\x4b\x65\xff\x21\x7c\xcb\x0d"
         "\x24\x10\x16\xa3\xb8\xc8\x2c\x94\xe6\x7f\xa5\x01\x02\x03\x26\x20\x01\x21\x58\x20\xf8\xa3"
@@ -612,6 +614,7 @@ TEST(Messages, ParseMakeCredentialResponse2)
     ASSERT_EQ(msg.m_authDataRaw.size(), 164);
     AssertEq(msg.m_authDataRaw, blob + 11, msg.m_authDataRaw.size());
     AssertEq(msg.m_authData.m_rpIdHash, blob + 11, 32);
+    ASSERT_NO_THROW(VerifyRpIdHash("acme.com", msg.m_authData.m_rpIdHash));
     ASSERT_EQ(msg.m_authData.m_flags, blob[43]);
     ASSERT_TRUE(msg.m_authData.m_attestationData.has_value());
     AssertEq(msg.m_authData.m_attestationData->m_credentialId, blob + 66, 32);
@@ -630,8 +633,8 @@ TEST(Messages, ParseGetAssertionResponse)
     constexpr uint8_t blob[] =
         "\x00\xa5\x01\xa2\x62\x69\x64\x54\xc7\x3c\x11\x45\x4b\x19\x08\xe5\x2c\x16\x8d\x45\x81\xf5"
         "\x9f\xb1\x15\x00\xbf\x51\x64\x74\x79\x70\x65\x6a\x70\x75\x62\x6c\x69\x63\x2d\x6b\x65\x79"
-        "\x02\x58\x25\x23\xf3\x84\xb5\x42\x6e\x09\x7e\x53\x43\x0c\xbd\x25\xec\x20\x11\xd3\xcf\xb1"
-        "\x4a\x7f\x67\xa8\x29\x94\xfe\x7f\xe7\xfe\x1b\xca\xdc\x1d\x00\x00\x00\x00\x03\x58\x48\x30"
+        "\x02\x58\x25\x11\x94\x22\x8d\xa8\xfd\xbd\xee\xfd\x26\x1b\xd7\xb6\x59\x5c\xfd\x70\xa5\x0d"
+        "\x70\xc6\x40\x7b\xcf\x01\x3d\xe9\x6d\x4e\xfb\x17\xde\x1d\x00\x00\x00\x00\x03\x58\x48\x30"
         "\x46\x02\x21\x00\xe2\x6e\xb7\xab\x29\x48\xcb\x1c\x72\xc9\xe2\x91\xc9\x74\xff\x2b\x44\xd9"
         "\xdf\x8a\xb2\x3c\xc9\x5a\xc0\xe7\xb8\x65\xc4\xe5\xde\x08\x02\x21\x00\xb1\xea\x2c\x5f\xf3"
         "\x41\x71\x0f\x18\x04\x77\xe1\x73\xf9\xbc\xac\x0f\x01\x14\x91\x33\x79\xca\xe2\x87\x22\xbb"
@@ -645,6 +648,7 @@ TEST(Messages, ParseGetAssertionResponse)
     ASSERT_GE(msg.m_authDataRaw.size(), 37);
     AssertEq(msg.m_authDataRaw, blob + 47, msg.m_authDataRaw.size());
     AssertEq(msg.m_authData.m_rpIdHash, blob + 47, 32);
+    ASSERT_NO_THROW(VerifyRpIdHash("acme.com", msg.m_authData.m_rpIdHash));
     ASSERT_EQ(msg.m_authData.m_flags, blob[79]);
     ASSERT_FALSE(msg.m_authData.m_attestationData.has_value());
     AssertEq(msg.m_credentialId, blob + 8, 20);