/* * Copyright (C) 2015 The Android Open Source Project * * 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 <gtest/gtest.h> #include <keymaster/android_keymaster_utils.h> #include <keymaster/logger.h> #include "../auth_token_table.h" using std::vector; inline bool operator==(const hw_auth_token_t& a, const hw_auth_token_t& b) { return (memcmp(&a, &b, sizeof(a)) == 0); } namespace keymaster { namespace test { class StdoutLogger : public Logger { public: StdoutLogger() { set_instance(this); } int log_msg(LogLevel level, const char* fmt, va_list args) const { int output_len = 0; switch (level) { case DEBUG_LVL: output_len = printf("DEBUG: "); break; case INFO_LVL: output_len = printf("INFO: "); break; case WARNING_LVL: output_len = printf("WARNING: "); break; case ERROR_LVL: output_len = printf("ERROR: "); break; case SEVERE_LVL: output_len = printf("SEVERE: "); break; } output_len += vprintf(fmt, args); output_len += printf("\n"); return output_len; } }; StdoutLogger logger; TEST(AuthTokenTableTest, Create) { AuthTokenTable table; } static hw_auth_token_t* make_token(uint64_t rsid, uint64_t ssid = 0, uint64_t challenge = 0, uint64_t timestamp = 0) { hw_auth_token_t* token = new hw_auth_token_t; token->user_id = rsid; token->authenticator_id = ssid; token->authenticator_type = hton(static_cast<uint32_t>(HW_AUTH_PASSWORD)); token->challenge = challenge; token->timestamp = hton(timestamp); return token; } static AuthorizationSet make_set(uint64_t rsid, uint32_t timeout = 10000) { AuthorizationSetBuilder builder; builder.Authorization(TAG_USER_ID, 10) .Authorization(TAG_USER_AUTH_TYPE, HW_AUTH_PASSWORD) .Authorization(TAG_USER_SECURE_ID, rsid); // Use timeout == 0 to indicate tags that require auth per operation. if (timeout != 0) builder.Authorization(TAG_AUTH_TIMEOUT, timeout); return builder.build(); } // Tests obviously run so fast that a real-time clock with a one-second granularity rarely changes // output during a test run. This test clock "ticks" one second every time it's called. static time_t monotonic_clock() { static time_t time = 0; return time++; } TEST(AuthTokenTableTest, SimpleAddAndFindTokens) { AuthTokenTable table; table.AddAuthenticationToken(make_token(1, 2)); table.AddAuthenticationToken(make_token(3, 4)); EXPECT_EQ(2U, table.size()); const hw_auth_token_t* found; ASSERT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(1U, found->user_id); EXPECT_EQ(2U, found->authenticator_id); ASSERT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(1U, found->user_id); EXPECT_EQ(2U, found->authenticator_id); ASSERT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(3U, found->user_id); EXPECT_EQ(4U, found->authenticator_id); ASSERT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(4), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(3U, found->user_id); EXPECT_EQ(4U, found->authenticator_id); ASSERT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(5), KM_PURPOSE_SIGN, 0, &found)); } TEST(AuthTokenTableTest, FlushTable) { AuthTokenTable table(3, monotonic_clock); table.AddAuthenticationToken(make_token(1)); table.AddAuthenticationToken(make_token(2)); table.AddAuthenticationToken(make_token(3)); const hw_auth_token_t* found; // All three should be in the table. EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); table.Clear(); EXPECT_EQ(0U, table.size()); } TEST(AuthTokenTableTest, TableOverflow) { AuthTokenTable table(3, monotonic_clock); table.AddAuthenticationToken(make_token(1)); table.AddAuthenticationToken(make_token(2)); table.AddAuthenticationToken(make_token(3)); const hw_auth_token_t* found; // All three should be in the table. EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); table.AddAuthenticationToken(make_token(4)); // Oldest should be gone. EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); // Others should be there, including the new one (4). Search for it first, then the others, so // 4 becomes the least recently used. EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(4), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); table.AddAuthenticationToken(make_token(5)); // 5 should have replaced 4. EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(4), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(5), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); table.AddAuthenticationToken(make_token(6)); table.AddAuthenticationToken(make_token(7)); // 2 and 5 should be gone EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(5), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(6), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(7), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); table.AddAuthenticationToken(make_token(8)); table.AddAuthenticationToken(make_token(9)); table.AddAuthenticationToken(make_token(10)); // Only the three most recent should be there. EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(3), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(4), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(5), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(6), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(7), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(8), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(9), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(10), KM_PURPOSE_SIGN, 0, &found)); } TEST(AuthTokenTableTest, AuthenticationNotRequired) { AuthTokenTable table; const hw_auth_token_t* found; EXPECT_EQ(AuthTokenTable::AUTH_NOT_REQUIRED, table.FindAuthorization( AuthorizationSetBuilder().Authorization(TAG_NO_AUTH_REQUIRED).build(), KM_PURPOSE_SIGN, 0 /* no challenge */, &found)); } TEST(AuthTokenTableTest, OperationHandleNotFound) { AuthTokenTable table; const hw_auth_token_t* found; table.AddAuthenticationToken(make_token(1, 0, 1, 5)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 2 /* non-matching challenge */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 1 /* matching challenge */, &found)); table.MarkCompleted(1); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 1 /* used challenge */, &found)); } TEST(AuthTokenTableTest, OperationHandleRequired) { AuthTokenTable table; const hw_auth_token_t* found; table.AddAuthenticationToken(make_token(1)); EXPECT_EQ(AuthTokenTable::OP_HANDLE_REQUIRED, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); } TEST(AuthTokenTableTest, AuthSidChanged) { AuthTokenTable table; const hw_auth_token_t* found; table.AddAuthenticationToken(make_token(1, 3, /* op handle */ 1)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_WRONG_SID, table.FindAuthorization(make_set(2, 0 /* no timeout */), KM_PURPOSE_SIGN, 1 /* op handle */, &found)); } TEST(AuthTokenTableTest, TokenExpired) { AuthTokenTable table(5, monotonic_clock); const hw_auth_token_t* found; auto key_info = make_set(1, 5 /* five second timeout */); // monotonic_clock "ticks" one second each time it's called, which is once per request, so the // sixth request should fail, since key_info says the key is good for five seconds. // // Note that this tests the decision of the AuthTokenTable to reject a request it knows is // expired. An additional check of the secure timestamp (in the token) will be made by // keymaster when the found token is passed to it. table.AddAuthenticationToken(make_token(1, 0)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(key_info, KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(key_info, KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(key_info, KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(key_info, KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(key_info, KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_EXPIRED, table.FindAuthorization(key_info, KM_PURPOSE_SIGN, 0 /* no op handle */, &found)); } TEST(AuthTokenTableTest, MarkNonexistentEntryCompleted) { AuthTokenTable table; // Marking a nonexistent entry completed is ignored. This test is mainly for code coverage. table.MarkCompleted(1); } TEST(AuthTokenTableTest, SupersededEntries) { AuthTokenTable table; const hw_auth_token_t* found; // Add two identical tokens, without challenges. The second should supersede the first, based // on timestamp (fourth arg to make_token). table.AddAuthenticationToken(make_token(1, 0, 0, 0)); table.AddAuthenticationToken(make_token(1, 0, 0, 1)); EXPECT_EQ(1U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(1U, ntoh(found->timestamp)); // Add a third token, this with a different RSID. It should not be superseded. table.AddAuthenticationToken(make_token(2, 0, 0, 2)); EXPECT_EQ(2U, table.size()); // Add two more, superseding each of the two in the table. table.AddAuthenticationToken(make_token(1, 0, 0, 3)); table.AddAuthenticationToken(make_token(2, 0, 0, 4)); EXPECT_EQ(2U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(3U, ntoh(found->timestamp)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0, &found)); EXPECT_EQ(4U, ntoh(found->timestamp)); // Add another, this one with a challenge value. It should supersede the old one since it is // newer, and matches other than the challenge. table.AddAuthenticationToken(make_token(1, 0, 1, 5)); EXPECT_EQ(2U, table.size()); // And another, also with a challenge. Because of the challenge values, the one just added // cannot be superseded. table.AddAuthenticationToken(make_token(1, 0, 2, 6)); EXPECT_EQ(3U, table.size()); // Should be able to find each of them, by specifying their challenge, with a key that is not // timed (timed keys don't care about challenges). EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout*/), KM_PURPOSE_SIGN, 1 /* challenge */, &found)); EXPECT_EQ(5U, ntoh(found->timestamp)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 2 /* challenge */, &found)); EXPECT_EQ(6U, ntoh(found->timestamp)); // Add another, without a challenge, and the same timestamp as the last one. This new one // actually could be considered already-superseded, but the table doesn't handle that case, // since it seems unlikely to occur in practice. table.AddAuthenticationToken(make_token(1, 0, 0, 6)); EXPECT_EQ(4U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0 /* challenge */, &found)); EXPECT_EQ(6U, ntoh(found->timestamp)); // Add another without a challenge but an increased timestamp. This should supersede the // previous challenge-free entry. table.AddAuthenticationToken(make_token(1, 0, 0, 7)); EXPECT_EQ(4U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 2 /* challenge */, &found)); EXPECT_EQ(6U, ntoh(found->timestamp)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0 /* challenge */, &found)); EXPECT_EQ(7U, ntoh(found->timestamp)); // Mark the entry with challenge 2 as complete. Since there's a newer challenge-free entry, the // challenge entry will be superseded. table.MarkCompleted(2); EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 2 /* challenge */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0 /* challenge */, &found)); EXPECT_EQ(7U, ntoh(found->timestamp)); // Add another SID 1 entry with a challenge. It supersedes the previous SID 1 entry with // no challenge (timestamp 7), but not the one with challenge 1 (timestamp 5). table.AddAuthenticationToken(make_token(1, 0, 3, 8)); EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 1 /* challenge */, &found)); EXPECT_EQ(5U, ntoh(found->timestamp)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 3 /* challenge */, &found)); EXPECT_EQ(8U, ntoh(found->timestamp)); // SID 2 entry is still there. EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(2), KM_PURPOSE_SIGN, 0 /* challenge */, &found)); EXPECT_EQ(4U, ntoh(found->timestamp)); // Mark the entry with challenge 3 as complete. Since the older challenge 1 entry is // incomplete, nothing is superseded. table.MarkCompleted(3); EXPECT_EQ(3U, table.size()); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 1 /* challenge */, &found)); EXPECT_EQ(5U, ntoh(found->timestamp)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0 /* challenge */, &found)); EXPECT_EQ(8U, ntoh(found->timestamp)); // Mark the entry with challenge 1 as complete. Since there's a newer one (with challenge 3, // completed), the challenge 1 entry is superseded and removed. table.MarkCompleted(1); EXPECT_EQ(2U, table.size()); EXPECT_EQ(AuthTokenTable::AUTH_TOKEN_NOT_FOUND, table.FindAuthorization(make_set(1, 0 /* no timeout */), KM_PURPOSE_SIGN, 1 /* challenge */, &found)); EXPECT_EQ(AuthTokenTable::OK, table.FindAuthorization(make_set(1), KM_PURPOSE_SIGN, 0 /* challenge */, &found)); EXPECT_EQ(8U, ntoh(found->timestamp)); } } // namespace keymaster } // namespace test