Program Listing for File crypto_algo.hpp

Return to documentation for file (libspookyaction/include/desfire/crypto_algo.hpp)

//
// Created by Pietro Saccardi on 02/01/2021.
//

#ifndef DESFIRE_CRYPTO_ALGO_HPP
#define DESFIRE_CRYPTO_ALGO_HPP

#include <algorithm>
#include <cstdint>
#include <desfire/log.h>
#include <iterator>
#include <mlab/bin_data.hpp>
#include <mlab/mathutils.hpp>
#include <utility>

namespace desfire {

    struct randbytes {
        std::size_t n;
        constexpr explicit randbytes(std::size_t len) : n{len} {}
    };

    static constexpr std::uint16_t crc16_init = 0x6363;
    static constexpr std::uint32_t crc32_init = 0xffffffff;

    [[nodiscard]] std::uint16_t compute_crc16(mlab::range<std::uint8_t const *> data, std::uint16_t init = crc16_init);
    [[nodiscard]] std::uint32_t compute_crc32(mlab::range<std::uint8_t const *> data, std::uint32_t init = crc32_init);
    [[nodiscard]] inline std::uint16_t compute_crc16(mlab::bin_data const &data, std::uint16_t init = crc16_init);
    [[nodiscard]] inline std::uint32_t compute_crc32(mlab::bin_data const &data, std::uint32_t init = crc32_init);
    [[nodiscard]] std::uint16_t compute_crc16(std::uint8_t data, std::uint16_t init = crc16_init);
    [[nodiscard]] std::uint32_t compute_crc32(std::uint8_t data, std::uint32_t init = crc32_init);

    template <class It>
    void lshift_sequence(It begin, It end, unsigned lshift);

    template <class Container>
    void set_key_version(Container &c, std::uint8_t v);

    template <class Container>
    [[nodiscard]] std::uint8_t get_key_version(Container const &c);

    template <class ByteIterator, class N, class Fn, class BytesContainer>
    [[nodiscard]] std::pair<ByteIterator, bool> find_crc_tail(ByteIterator begin, ByteIterator end, Fn &&crc_fn, N init, std::size_t block_size,
                                                              bool incremental_crc, BytesContainer const &valid_padding_bytes);
    template <class ByteIterator, class N, class Fn>
    [[nodiscard]] std::pair<ByteIterator, bool> find_crc_tail(ByteIterator begin, ByteIterator end, Fn &&crc_fn, N init, std::size_t block_size,
                                                              bool incremental_crc);

}// namespace desfire

namespace mlab {
#ifndef DOXYGEN_SHOULD_SKIP_THIS
    bin_data &operator<<(bin_data &bd, desfire::randbytes const &rndb);
#endif
}// namespace mlab

namespace desfire {

    template <class It>
    void lshift_sequence(It begin, It end, unsigned lshift) {
        using value_type = typename std::iterator_traits<It>::value_type;
        static_assert(std::is_integral_v<value_type> and std::is_unsigned_v<value_type>);
        static constexpr unsigned value_nbits = sizeof(value_type) * 8;
        const unsigned complementary_rshift = value_nbits - std::min(value_nbits, lshift);
        if (begin != end) {
            It prev = begin++;
            for (; begin != end; prev = begin++) {
                *prev = ((*prev) << lshift) | ((*begin) >> complementary_rshift);
            }
            *prev <<= lshift;
        }
    }

    template <class ByteIterator, class N, class Fn, class BytesContainer>
    std::pair<ByteIterator, bool> find_crc_tail(ByteIterator begin, ByteIterator end, Fn &&crc_fn, N init,
                                                std::size_t block_size, bool incremental_crc,
                                                BytesContainer const &valid_padding_bytes) {
        static const auto nonzero_byte_pred = [&](std::uint8_t b) -> bool {
            return std::find(std::begin(valid_padding_bytes), std::end(valid_padding_bytes), b) == std::end(valid_padding_bytes);
        };
        const bool multiple_of_block_size = std::distance(begin, end) % block_size == 0;
        if (not multiple_of_block_size) {
            DESFIRE_LOGE("Cannot scan for CRC tail if data length is not a multiple of the block size.");
        }
        if (begin != end and multiple_of_block_size) {
            // Find the last nonzero byte, get and iterator to the element past that.
            // You just have to scan the last block, and in the worst case, the last non-padding byte is the first
            // byte of the last block.
            // This is achieved by reverse scanning for a nonzero byte, and getting the underlying iterator.
            // Since the reverse iterator holds an underlying iterator to the next element (in the normal traversal
            // sense), we can just get that.
            const auto rev_end = std::reverse_iterator<ByteIterator>(end);
            auto end_payload = std::find_if(rev_end, rev_end + block_size, nonzero_byte_pred).base();
            // Compute the crc until the supposed end of the payload
            N crc = crc_fn(begin, end_payload, init);
            while (crc != N(0) and end_payload != end) {
                if (incremental_crc) {
                    // Update the crc with one byte at a time
                    crc = crc_fn(end_payload, std::next(end_payload), crc);
                } else {
                    // Recalculate the crc on the whole new sequence
                    crc = crc_fn(begin, std::next(end_payload), init);
                }
                // Keep advancing the supposed end of the payload until end
                ++end_payload;
            }
            return {end_payload, crc == N(0)};
        }
        return {end, false};
    }

    template <class ByteIterator, class N, class Fn>
    std::pair<ByteIterator, bool> find_crc_tail(ByteIterator begin, ByteIterator end, Fn &&crc_fn, N init, std::size_t block_size,
                                                bool incremental_crc) {
        static constexpr std::array<std::uint8_t, 2> default_padding_bytes = {0x00, 0x80};
        return find_crc_tail(begin, end, std::forward<Fn>(crc_fn), init, block_size, incremental_crc, default_padding_bytes);
    }

    template <class Container>
    void set_key_version(Container &c, std::uint8_t v) {
        std::uint_fast8_t i = 0;
        for (auto &b : c) {
            static_assert(std::is_same_v<decltype(b), std::uint8_t &>);
            if (++i > 8) {
                break;
            }
            b = (b & 0b11111110) | (v >> 7);
            v <<= 1;
        }
    }

    template <class Container>
    std::uint8_t get_key_version(Container const &c) {
        std::uint8_t v = 0x0;
        std::uint_fast8_t i = 0;
        for (std::uint8_t b : c) {
            if (++i > 8) {
                break;
            }
            v = (v << 1) | (b & 0b00000001);
        }
        return v;
    }

    std::uint16_t compute_crc16(mlab::bin_data const &data, std::uint16_t init) {
        return compute_crc16(data.data_view(), init);
    }
    std::uint32_t compute_crc32(mlab::bin_data const &data, std::uint32_t init) {
        return compute_crc32(data.data_view(), init);
    }


}// namespace desfire

#endif//DESFIRE_CRYPTO_ALGO_HPP