/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at @.^.^.LICENSE.
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * CDDL HEADER END
 *
 * Copyright (c) RISC OS Developments 2026. All rights reserved.
 * SPDX-License-Identifier: CDDL-1.0
 */

/* RSA-2048 PKCS#1 v1.5 verification for SHA-256 manifests.  The public
   exponent is fixed at 65537.  No private-key operation is implemented. */

#include <stddef.h>
#include <string.h>

#include "rsa_verify.h"
#include "sha256.h"

#define RSA_WORDS 64u

typedef unsigned int word32;
typedef unsigned long long word64;

static void from_bytes(word32 out[RSA_WORDS], const unsigned char *in)
{
    unsigned int i;

    for(i = 0; i < RSA_WORDS; i++)
    {
        unsigned int p;
        p = TLSMANAGER_RSA_BYTES - 4u * (i + 1u);
        out[i] = ((word32)in[p] << 24) | ((word32)in[p + 1] << 16) |
                 ((word32)in[p + 2] << 8) | (word32)in[p + 3];
    }
}

static void to_bytes(unsigned char *out, const word32 in[RSA_WORDS])
{
    unsigned int i;

    for(i = 0; i < RSA_WORDS; i++)
    {
        unsigned int p;
        word32 value;
        p = TLSMANAGER_RSA_BYTES - 4u * (i + 1u);
        value = in[i];
        out[p] = (unsigned char)(value >> 24);
        out[p + 1] = (unsigned char)(value >> 16);
        out[p + 2] = (unsigned char)(value >> 8);
        out[p + 3] = (unsigned char)value;
    }
}

static int compare_words(const word32 *a, const word32 *b)
{
    int i;

    for(i = (int)RSA_WORDS - 1; i >= 0; i--)
    {
        if(a[i] < b[i]) return -1;
        if(a[i] > b[i]) return 1;
    }
    return 0;
}

static void subtract_words(word32 *a, const word32 *b)
{
    word64 borrow;
    unsigned int i;

    borrow = 0;
    for(i = 0; i < RSA_WORDS; i++)
    {
        word64 ai;
        word64 sub;
        ai = (word64)a[i];
        sub = (word64)b[i] + borrow;
        a[i] = (word32)(ai - sub);
        borrow = ai < sub;
    }
}

static void double_mod(word32 *a, const word32 *modulus)
{
    word32 original[RSA_WORDS];
    word32 carry;
    unsigned int i;

    memcpy(original, a, sizeof(original));
    carry = 0;
    for(i = 0; i < RSA_WORDS; i++)
    {
        word32 next;
        next = a[i] >> 31;
        a[i] = (a[i] << 1) | carry;
        carry = next;
    }
    if(carry || compare_words(a, modulus) >= 0)
    {
        if(carry)
        {
            /* 2*a crossed R.  Subtracting n in R-word arithmetic is the
               required reduction because both operands are below n. */
            subtract_words(a, modulus);
        }
        else subtract_words(a, modulus);
    }
    memset(original, 0, sizeof(original));
}

static word32 montgomery_inverse(word32 n)
{
    word32 x;
    unsigned int i;

    x = 1;
    for(i = 0; i < 5; i++) x *= 2u - n * x;
    return 0u - x;
}

static void montgomery_multiply(word32 out[RSA_WORDS],
                                const word32 a[RSA_WORDS],
                                const word32 b[RSA_WORDS],
                                const word32 modulus[RSA_WORDS],
                                word32 inverse)
{
    word32 t[RSA_WORDS + 1];
    unsigned int i;

    memset(t, 0, sizeof(t));
    for(i = 0; i < RSA_WORDS; i++)
    {
        word64 carry;
        word32 m;
        unsigned int j;

        carry = 0;
        for(j = 0; j < RSA_WORDS; j++)
        {
            word64 sum;
            sum = (word64)t[j] + (word64)a[j] * b[i] + carry;
            t[j] = (word32)sum;
            carry = sum >> 32;
        }
        {
            word64 sum;
            sum = (word64)t[RSA_WORDS] + carry;
            t[RSA_WORDS] = (word32)sum;
        }

        m = t[0] * inverse;
        carry = 0;
        for(j = 0; j < RSA_WORDS; j++)
        {
            word64 sum;
            sum = (word64)t[j] + (word64)m * modulus[j] + carry;
            if(j != 0) t[j - 1] = (word32)sum;
            carry = sum >> 32;
        }
        {
            word64 sum;
            sum = (word64)t[RSA_WORDS] + carry;
            t[RSA_WORDS - 1] = (word32)sum;
            t[RSA_WORDS] = (word32)(sum >> 32);
        }
    }
    memcpy(out, t, RSA_WORDS * sizeof(word32));
    if(t[RSA_WORDS] || compare_words(out, modulus) >= 0)
        subtract_words(out, modulus);
    memset(t, 0, sizeof(t));
}

static int check_encoding(const unsigned char encoded[TLSMANAGER_RSA_BYTES],
                          const unsigned char digest[SHA256_RESULTLEN])
{
    static const unsigned char prefix[] =
    {
        0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
        0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00,
        0x04, 0x20
    };
    unsigned int separator;
    unsigned int i;

    if(encoded[0] != 0 || encoded[1] != 1) return 0;
    separator = TLSMANAGER_RSA_BYTES - sizeof(prefix) -
                SHA256_RESULTLEN - 1u;
    if(separator < 10u || encoded[separator] != 0) return 0;
    for(i = 2; i < separator; i++)
        if(encoded[i] != 0xff) return 0;
    if(memcmp(encoded + separator + 1, prefix, sizeof(prefix)) != 0)
        return 0;
    return memcmp(encoded + separator + 1 + sizeof(prefix), digest,
                  SHA256_RESULTLEN) == 0;
}

int TLSManagerRSAVerifySHA256(
    const unsigned char modulus_bytes[TLSMANAGER_RSA_BYTES],
    const unsigned char signature_bytes[TLSMANAGER_RSA_BYTES],
    const unsigned char *message,
    size_t message_size)
{
    word32 modulus[RSA_WORDS];
    word32 signature[RSA_WORDS];
    word32 base[RSA_WORDS];
    word32 result[RSA_WORDS];
    word32 one[RSA_WORDS];
    word32 inverse;
    unsigned char encoded[TLSMANAGER_RSA_BYTES];
    unsigned char digest[SHA256_RESULTLEN];
    unsigned int i;
    int valid;

    if(!modulus_bytes || !signature_bytes || (!message && message_size))
        return 0;
    from_bytes(modulus, modulus_bytes);
    from_bytes(signature, signature_bytes);
    if(!(modulus[0] & 1u) || compare_words(signature, modulus) >= 0)
        return 0;

    memset(one, 0, sizeof(one));
    one[0] = 1;
    memcpy(base, signature, sizeof(base));
    for(i = 0; i < RSA_WORDS * 32u; i++) double_mod(base, modulus);
    memcpy(result, base, sizeof(result));
    inverse = montgomery_inverse(modulus[0]);
    for(i = 0; i < 16u; i++)
    {
        word32 temp[RSA_WORDS];
        montgomery_multiply(temp, result, result, modulus, inverse);
        memcpy(result, temp, sizeof(result));
        memset(temp, 0, sizeof(temp));
    }
    {
        word32 temp[RSA_WORDS];
        montgomery_multiply(temp, result, base, modulus, inverse);
        montgomery_multiply(result, temp, one, modulus, inverse);
        memset(temp, 0, sizeof(temp));
    }

    to_bytes(encoded, result);
    sha256_get_digest(message, message_size, digest);
    valid = check_encoding(encoded, digest);

    memset(modulus, 0, sizeof(modulus));
    memset(signature, 0, sizeof(signature));
    memset(base, 0, sizeof(base));
    memset(result, 0, sizeof(result));
    memset(one, 0, sizeof(one));
    memset(encoded, 0, sizeof(encoded));
    memset(digest, 0, sizeof(digest));
    return valid;
}
