/*
 * Copyright (c) 2015 Cossack Labs Limited
 *
 * 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 "soter/soter_sign_rsa.h"

#include <openssl/evp.h>
#include <openssl/rsa.h>

#include "soter/openssl/soter_engine.h"
#include "soter/openssl/soter_rsa_common.h"
#include "soter/soter_rsa_key.h"

soter_status_t soter_verify_init_rsa_pss_pkcs8(soter_sign_ctx_t* ctx,
                                               const void* private_key,
                                               const size_t private_key_length,
                                               const void* public_key,
                                               const size_t public_key_length)
{
    soter_status_t err = SOTER_FAIL;
    EVP_PKEY* pkey = NULL;
    EVP_PKEY_CTX* md_pkey_ctx = NULL;

    pkey = EVP_PKEY_new();
    if (!pkey) {
        return SOTER_NO_MEMORY;
    }

    if (!EVP_PKEY_set_type(pkey, EVP_PKEY_RSA)) {
        goto free_pkey;
    }

    ctx->pkey_ctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (!(ctx->pkey_ctx)) {
        err = SOTER_NO_MEMORY;
        goto free_pkey;
    }

    if (private_key && private_key_length != 0) {
        err = soter_rsa_import_key(pkey, private_key, private_key_length);
        if (err != SOTER_SUCCESS) {
            goto free_pkey_ctx;
        }
    }
    if (public_key && public_key_length != 0) {
        err = soter_rsa_import_key(pkey, public_key, public_key_length);
        if (err != SOTER_SUCCESS) {
            goto free_pkey_ctx;
        }
    }

    ctx->md_ctx = EVP_MD_CTX_create();
    if (!(ctx->md_ctx)) {
        err = SOTER_NO_MEMORY;
        goto free_pkey_ctx;
    }

    /* md_pkey_ctx is owned by ctx->md_ctx */
    if (!EVP_DigestVerifyInit(ctx->md_ctx, &md_pkey_ctx, EVP_sha256(), NULL, pkey)) {
        goto free_md_ctx;
    }
    if (!EVP_PKEY_CTX_set_rsa_padding(md_pkey_ctx, RSA_PKCS1_PSS_PADDING)) {
        goto free_md_ctx;
    }
    if (!EVP_PKEY_CTX_set_rsa_pss_saltlen(md_pkey_ctx, -2)) {
        goto free_md_ctx;
    }

    EVP_PKEY_free(pkey);
    return SOTER_SUCCESS;

free_md_ctx:
    EVP_MD_CTX_destroy(ctx->md_ctx);
    ctx->md_ctx = NULL;
free_pkey_ctx:
    EVP_PKEY_CTX_free(ctx->pkey_ctx);
    ctx->pkey_ctx = NULL;
free_pkey:
    EVP_PKEY_free(pkey);
    return err;
}

soter_status_t soter_verify_update_rsa_pss_pkcs8(soter_sign_ctx_t* ctx,
                                                 const void* data,
                                                 const size_t data_length)
{
    if (!EVP_DigestVerifyUpdate(ctx->md_ctx, data, data_length)) {
        return SOTER_FAIL;
    }
    return SOTER_SUCCESS;
}

soter_status_t soter_verify_final_rsa_pss_pkcs8(soter_sign_ctx_t* ctx,
                                                const void* signature,
                                                const size_t signature_length)
{
    if (!ctx) {
        return SOTER_INVALID_PARAMETER;
    }
    EVP_PKEY* pkey = EVP_PKEY_CTX_get0_pkey(ctx->pkey_ctx);
    if (!pkey) {
        return SOTER_INVALID_PARAMETER;
    }
    if (signature_length != (size_t)EVP_PKEY_size(pkey)) {
        return SOTER_FAIL;
    }
    if (!EVP_DigestVerifyFinal(ctx->md_ctx, (unsigned char*)signature, signature_length)) {
        return SOTER_FAIL;
    }
    return SOTER_SUCCESS;
}

soter_status_t soter_verify_cleanup_rsa_pss_pkcs8(soter_sign_ctx_t* ctx)
{
    if (!ctx) {
        return SOTER_INVALID_PARAMETER;
    }
    if (ctx->pkey_ctx) {
        EVP_PKEY_CTX_free(ctx->pkey_ctx);
        ctx->pkey_ctx = NULL;
    }
    if (ctx->md_ctx) {
        EVP_MD_CTX_destroy(ctx->md_ctx);
        ctx->md_ctx = NULL;
    }
    return SOTER_SUCCESS;
}
