/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "alloc-util.h"
#include "random-util.h"
#include "recovery-key.h"

const char modhex_alphabet[16] = {
        'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v'
};

int decode_modhex_char(char x) {

        for (size_t i = 0; i < ELEMENTSOF(modhex_alphabet); i++)
                /* Check both upper and lowercase */
                if (modhex_alphabet[i] == x || (modhex_alphabet[i] - 32) == x)
                        return i;

        return -EINVAL;
}

int normalize_recovery_key(const char *password, char **ret) {
        _cleanup_(erase_and_freep) char *mangled = NULL;
        size_t l;

        assert(password);
        assert(ret);

        l = strlen(password);
        if (!IN_SET(l,
                    RECOVERY_KEY_MODHEX_RAW_LENGTH*2,          /* syntax without dashes */
                    RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1))   /* syntax with dashes */
                return -EINVAL;

        mangled = new(char, RECOVERY_KEY_MODHEX_FORMATTED_LENGTH);
        if (!mangled)
                return -ENOMEM;

        for (size_t i = 0, j = 0; i < RECOVERY_KEY_MODHEX_RAW_LENGTH; i++) {
                size_t k;
                int a, b;

                if (l == RECOVERY_KEY_MODHEX_RAW_LENGTH*2)
                        /* Syntax without dashes */
                        k = i * 2;
                else {
                        /* Syntax with dashes */
                        assert(l == RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1);
                        k = i * 2 + i / 4;

                        if (i > 0 && i % 4 == 0 && password[k-1] != '-')
                                return -EINVAL;
                }

                a = decode_modhex_char(password[k]);
                if (a < 0)
                        return -EINVAL;
                b = decode_modhex_char(password[k+1]);
                if (b < 0)
                        return -EINVAL;

                mangled[j++] = modhex_alphabet[a];
                mangled[j++] = modhex_alphabet[b];

                if (i % 4 == 3)
                        mangled[j++] = '-';
        }

        mangled[RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1] = 0;

        *ret = TAKE_PTR(mangled);
        return 0;
}

int make_recovery_key(char **ret) {
        _cleanup_(erase_and_freep) char *formatted = NULL;
        _cleanup_(erase_and_freep) uint8_t *key = NULL;
        size_t j = 0;
        int r;

        assert(ret);

        key = new(uint8_t, RECOVERY_KEY_MODHEX_RAW_LENGTH);
        if (!key)
                return -ENOMEM;

        r = crypto_random_bytes(key, RECOVERY_KEY_MODHEX_RAW_LENGTH);
        if (r < 0)
                return r;

        /* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */
        formatted = new(char, RECOVERY_KEY_MODHEX_FORMATTED_LENGTH);
        if (!formatted)
                return -ENOMEM;

        for (size_t i = 0; i < RECOVERY_KEY_MODHEX_RAW_LENGTH; i++) {
                formatted[j++] = modhex_alphabet[key[i] >> 4];
                formatted[j++] = modhex_alphabet[key[i] & 0xF];

                if (i % 4 == 3)
                        formatted[j++] = '-';
        }

        assert(j == RECOVERY_KEY_MODHEX_FORMATTED_LENGTH);
        assert(formatted[RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1] == '-');
        formatted[RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1] = 0; /* replace final dash with a NUL */

        *ret = TAKE_PTR(formatted);
        return 0;
}
