/*
 * Copyright (C) 2017 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 "KeymasterHidlTest.h"

namespace android {
namespace hardware {
namespace keymaster {
namespace V4_0 {
namespace test {

/**
 * HmacKeySharingTest extends KeymasterHidlTest with some utilities that make writing HMAC sharing
 * tests easier.
 */
class HmacKeySharingTest : public KeymasterHidlTest {
   protected:
    struct GetParamsResult {
        ErrorCode error;
        HmacSharingParameters params;
        auto tie() { return std::tie(error, params); }
    };

    struct ComputeHmacResult {
        ErrorCode error;
        HidlBuf sharing_check;
        auto tie() { return std::tie(error, sharing_check); }
    };

    using KeymasterVec = std::vector<sp<IKeymasterDevice>>;
    using ByteString = std::basic_string<uint8_t>;
    // using NonceVec = std::vector<HidlBuf>;

    GetParamsResult getHmacSharingParameters(IKeymasterDevice& keymaster) {
        GetParamsResult result;
        EXPECT_TRUE(keymaster
                        .getHmacSharingParameters([&](auto error, auto params) {
                            result.tie() = std::tie(error, params);
                        })
                        .isOk());
        return result;
    }

    hidl_vec<HmacSharingParameters> getHmacSharingParameters(const KeymasterVec& keymasters) {
        std::vector<HmacSharingParameters> paramsVec;
        for (auto& keymaster : keymasters) {
            auto result = getHmacSharingParameters(*keymaster);
            EXPECT_EQ(ErrorCode::OK, result.error);
            if (result.error == ErrorCode::OK) paramsVec.push_back(std::move(result.params));
        }
        return paramsVec;
    }

    ComputeHmacResult computeSharedHmac(IKeymasterDevice& keymaster,
                                        const hidl_vec<HmacSharingParameters>& params) {
        ComputeHmacResult result;
        EXPECT_TRUE(keymaster
                        .computeSharedHmac(params,
                                           [&](auto error, auto params) {
                                               result.tie() = std::tie(error, params);
                                           })
                        .isOk());
        return result;
    }

    std::vector<ComputeHmacResult> computeSharedHmac(
        const KeymasterVec& keymasters, const hidl_vec<HmacSharingParameters>& paramsVec) {
        std::vector<ComputeHmacResult> resultVec;
        for (auto& keymaster : keymasters) {
            resultVec.push_back(computeSharedHmac(*keymaster, paramsVec));
        }
        return resultVec;
    }

    std::vector<ByteString> copyNonces(const hidl_vec<HmacSharingParameters>& paramsVec) {
        std::vector<ByteString> nonces;
        for (auto& param : paramsVec) {
            nonces.emplace_back(param.nonce.data(), param.nonce.size());
        }
        return nonces;
    }

    void verifyResponses(const HidlBuf& expected, const std::vector<ComputeHmacResult>& responses) {
        for (auto& response : responses) {
            EXPECT_EQ(ErrorCode::OK, response.error);
            EXPECT_EQ(expected, response.sharing_check) << "Sharing check values should match.";
        }
    }
};

TEST_F(HmacKeySharingTest, GetParameters) {
    auto result1 = getHmacSharingParameters(keymaster());
    EXPECT_EQ(ErrorCode::OK, result1.error);

    auto result2 = getHmacSharingParameters(keymaster());
    EXPECT_EQ(ErrorCode::OK, result2.error);

    ASSERT_EQ(result1.params.seed, result2.params.seed)
        << "A given keymaster should always return the same seed.";
    ASSERT_EQ(result1.params.nonce, result2.params.nonce)
        << "A given keymaster should always return the same nonce until restart.";
}

TEST_F(HmacKeySharingTest, ComputeSharedHmac) {
    auto params = getHmacSharingParameters(all_keymasters());
    ASSERT_EQ(all_keymasters().size(), params.size())
        << "One or more keymasters failed to provide parameters.";

    auto nonces = copyNonces(params);
    EXPECT_EQ(all_keymasters().size(), nonces.size());
    std::sort(nonces.begin(), nonces.end());
    std::unique(nonces.begin(), nonces.end());
    EXPECT_EQ(all_keymasters().size(), nonces.size());

    auto responses = computeSharedHmac(all_keymasters(), params);
    ASSERT_GT(responses.size(), 0U);
    verifyResponses(responses[0].sharing_check, responses);

    // Do it a second time.  Should get the same answers.
    params = getHmacSharingParameters(all_keymasters());
    ASSERT_EQ(all_keymasters().size(), params.size())
        << "One or more keymasters failed to provide parameters.";

    responses = computeSharedHmac(all_keymasters(), params);
    ASSERT_GT(responses.size(), 0U);
    ASSERT_EQ(32U, responses[0].sharing_check.size());
    verifyResponses(responses[0].sharing_check, responses);
}

template <class F>
class final_action {
   public:
    explicit final_action(F f) : f_(move(f)) {}
    ~final_action() { f_(); }

   private:
    F f_;
};

template <class F>
inline final_action<F> finally(const F& f) {
    return final_action<F>(f);
}

TEST_F(HmacKeySharingTest, ComputeSharedHmacCorruptNonce) {
    // Important: The execution of this test gets the keymaster implementations on the device out of
    // sync with respect to the HMAC key.  Granted that VTS tests aren't run on in-use production
    // devices, this still has the potential to cause confusion.  To mitigate that, we always
    // (barring crashes :-/) re-run the unmodified agreement process on our way out.
    auto fixup_hmac = finally(
        [&]() { computeSharedHmac(all_keymasters(), getHmacSharingParameters(all_keymasters())); });

    auto params = getHmacSharingParameters(all_keymasters());
    ASSERT_EQ(all_keymasters().size(), params.size())
        << "One or more keymasters failed to provide parameters.";

    // All should be well in the normal case
    auto responses = computeSharedHmac(all_keymasters(), params);

    ASSERT_GT(responses.size(), 0U);
    HidlBuf correct_response = responses[0].sharing_check;
    verifyResponses(correct_response, responses);

    // Pick a random param, a random byte within the param's nonce, and a random bit within
    // the byte.  Flip that bit.
    size_t param_to_tweak = rand() % params.size();
    uint8_t byte_to_tweak = rand() % sizeof(params[param_to_tweak].nonce);
    uint8_t bit_to_tweak = rand() % 8;
    params[param_to_tweak].nonce[byte_to_tweak] ^= (1 << bit_to_tweak);

    responses = computeSharedHmac(all_keymasters(), params);
    for (size_t i = 0; i < responses.size(); ++i) {
        if (i == param_to_tweak) {
            EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error)
                << "Keymaster that provided tweaked param should fail to compute HMAC key";
        } else {
            EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed";
            EXPECT_NE(correct_response, responses[i].sharing_check)
                << "Others should calculate a different HMAC key, due to the tweaked nonce.";
        }
    }
}

TEST_F(HmacKeySharingTest, ComputeSharedHmacCorruptSeed) {
    // Important: The execution of this test gets the keymaster implementations on the device out of
    // sync with respect to the HMAC key.  Granted that VTS tests aren't run on in-use production
    // devices, this still has the potential to cause confusion.  To mitigate that, we always
    // (barring crashes :-/) re-run the unmodified agreement process on our way out.
    auto fixup_hmac = finally(
        [&]() { computeSharedHmac(all_keymasters(), getHmacSharingParameters(all_keymasters())); });

    auto params = getHmacSharingParameters(all_keymasters());
    ASSERT_EQ(all_keymasters().size(), params.size())
        << "One or more keymasters failed to provide parameters.";

    // All should be well in the normal case
    auto responses = computeSharedHmac(all_keymasters(), params);

    ASSERT_GT(responses.size(), 0U);
    HidlBuf correct_response = responses[0].sharing_check;
    verifyResponses(correct_response, responses);

    // Pick a random param and modify the seed.  We just increase the seed length by 1.  It doesn't
    // matter what value is in the additional byte; it changes the seed regardless.
    auto param_to_tweak = rand() % params.size();
    auto& to_tweak = params[param_to_tweak].seed;
    ASSERT_TRUE(to_tweak.size() == 32 || to_tweak.size() == 0);
    if (!to_tweak.size()) {
        to_tweak.resize(32);  // Contents don't matter; a little randomization is nice.
    }
    to_tweak[0]++;

    responses = computeSharedHmac(all_keymasters(), params);
    for (size_t i = 0; i < responses.size(); ++i) {
        if (i == param_to_tweak) {
            EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, responses[i].error)
                << "Keymaster that provided tweaked param should fail to compute HMAC key ";
        } else {
            EXPECT_EQ(ErrorCode::OK, responses[i].error) << "Others should succeed";
            EXPECT_NE(correct_response, responses[i].sharing_check)
                << "Others should calculate a different HMAC key, due to the tweaked nonce.";
        }
    }
}

}  // namespace test
}  // namespace V4_0
}  // namespace keymaster
}  // namespace hardware
}  // namespace android