/*
 * Copyright 2014 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 "operation.h"

#include <keymaster/authorization_set.h>

#include "key.h"

namespace keymaster {

bool OperationFactory::supported(keymaster_padding_t padding) const {
    size_t padding_count;
    const keymaster_padding_t* supported_paddings = SupportedPaddingModes(&padding_count);
    for (size_t i = 0; i < padding_count; ++i)
        if (padding == supported_paddings[i])
            return true;
    return false;
}

bool OperationFactory::supported(keymaster_block_mode_t block_mode) const {
    size_t block_mode_count;
    const keymaster_block_mode_t* supported_block_modes = SupportedBlockModes(&block_mode_count);
    for (size_t i = 0; i < block_mode_count; ++i)
        if (block_mode == supported_block_modes[i])
            return true;
    return false;
}

bool OperationFactory::supported(keymaster_digest_t digest) const {
    size_t digest_count;
    const keymaster_digest_t* supported_digests = SupportedDigests(&digest_count);
    for (size_t i = 0; i < digest_count; ++i)
        if (digest == supported_digests[i])
            return true;
    return false;
}

inline bool is_public_key_algorithm(keymaster_algorithm_t algorithm) {
    switch (algorithm) {
    case KM_ALGORITHM_HMAC:
    case KM_ALGORITHM_AES:
        return false;
    case KM_ALGORITHM_RSA:
    case KM_ALGORITHM_EC:
        return true;
    }

    // Unreachable.
    assert(false);
    return false;
}

bool OperationFactory::is_public_key_operation() const {
    KeyType key_type = registry_key();

    if (!is_public_key_algorithm(key_type.algorithm))
        return false;

    switch (key_type.purpose) {
    case KM_PURPOSE_VERIFY:
    case KM_PURPOSE_ENCRYPT:
        return true;
    case KM_PURPOSE_SIGN:
    case KM_PURPOSE_DECRYPT:
    case KM_PURPOSE_DERIVE_KEY:
        return false;
    };

    // Unreachable.
    assert(false);
    return false;
}

bool OperationFactory::GetAndValidatePadding(const AuthorizationSet& begin_params, const Key& key,
                                             keymaster_padding_t* padding,
                                             keymaster_error_t* error) const {
    *error = KM_ERROR_UNSUPPORTED_PADDING_MODE;
    if (!begin_params.GetTagValue(TAG_PADDING, padding)) {
        LOG_E("%d padding modes specified in begin params", begin_params.GetTagCount(TAG_PADDING));
        return false;
    } else if (!supported(*padding)) {
        LOG_E("Padding mode %d not supported", *padding);
        return false;
    } else if (
        // If it's a public key operation, all padding modes are authorized.
        !is_public_key_operation() &&
        // Otherwise the key needs to authorize the specific mode.
        !key.authorizations().Contains(TAG_PADDING, *padding) &&
        !key.authorizations().Contains(TAG_PADDING_OLD, *padding)) {
        LOG_E("Padding mode %d was specified, but not authorized by key", *padding);
        *error = KM_ERROR_INCOMPATIBLE_PADDING_MODE;
        return false;
    }

    *error = KM_ERROR_OK;
    return true;
}

bool OperationFactory::GetAndValidateDigest(const AuthorizationSet& begin_params, const Key& key,
                                            keymaster_digest_t* digest,
                                            keymaster_error_t* error) const {
    *error = KM_ERROR_UNSUPPORTED_DIGEST;
    if (!begin_params.GetTagValue(TAG_DIGEST, digest)) {
        LOG_E("%d digests specified in begin params", begin_params.GetTagCount(TAG_DIGEST));
        return false;
    } else if (!supported(*digest)) {
        LOG_E("Digest %d not supported", *digest);
        return false;
    } else if (
        // If it's a public key operation, all digests are authorized.
        !is_public_key_operation() &&
        // Otherwise the key needs to authorize the specific digest.
        !key.authorizations().Contains(TAG_DIGEST, *digest) &&
        !key.authorizations().Contains(TAG_DIGEST_OLD, *digest)) {
        LOG_E("Digest %d was specified, but not authorized by key", *digest);
        *error = KM_ERROR_INCOMPATIBLE_DIGEST;
        return false;
    }
    *error = KM_ERROR_OK;
    return true;
}

keymaster_error_t Operation::UpdateForFinish(const AuthorizationSet& input_params,
                                             const Buffer& input) {
    if (!input_params.empty() || input.available_read()) {
        size_t input_consumed;
        Buffer output;
        AuthorizationSet output_params;
        keymaster_error_t error =
            Update(input_params, input, &output_params, &output, &input_consumed);
        if (error != KM_ERROR_OK)
            return error;
        assert(input_consumed == input.available_read());
        assert(output_params.empty());
        assert(output.available_read() == 0);
    }

    return KM_ERROR_OK;
}

}  // namespace keymaster