You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

341 lines
5.7 KiB

/**
* @file bytes.cpp
*
* @brief Byte-related operations.
*
* @copyright The contents of this file have been placed into the public domain;
* see the file COPYING for more details.
*/
#include "bytes.h"
#include <iostream>
#include <stdexcept>
#include <cassert>
#include <cstdlib>
namespace CppTotp
{
namespace Bytes
{
void clearByteString(ByteString * bstr)
{
volatile Byte * bs = const_cast<volatile Byte *>(bstr->data());
for (size_t i = 0; i < bstr->size(); ++i)
{
bs[i] = Byte(0);
}
}
void swizzleByteStrings(ByteString * target, ByteString * source)
{
clearByteString(target);
target->assign(*source);
clearByteString(source);
}
static char nibbleToLCHex(uint8_t nib)
{
if (nib < 0xa)
{
return static_cast<char>(nib + '0');
}
else if (nib < 0x10)
{
return static_cast<char>((nib - 10) + 'a');
}
else
{
assert(0 && "not actually a nibble");
return '\0';
}
}
static uint8_t hexToNibble(char c)
{
if (c >= '0' && c <= '9')
{
return static_cast<uint8_t>(c - '0');
}
else if (c >= 'A' && c <= 'F')
{
return static_cast<uint8_t>(c - 'A' + 10);
}
else if (c >= 'a' && c <= 'f')
{
return static_cast<uint8_t>(c - 'a' + 10);
}
else
{
assert(0 && "not actually a hex digit");
return 0xff;
}
}
std::string toHexString(const ByteString & bstr)
{
std::string ret;
for (Byte b : bstr)
{
ret.push_back(nibbleToLCHex((b >> 4) & 0x0F));
ret.push_back(nibbleToLCHex((b >> 0) & 0x0F));
}
return ret;
}
ByteString fromHexStringSkipUnknown(const std::string & str)
{
std::string hstr;
for (char c : str)
{
if (
(c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f')
)
{
hstr.push_back(c);
}
// ignore otherwise
}
if (hstr.size() % 2 != 0)
{
throw std::invalid_argument("hex string (unknown characters ignored) length not divisible by 2");
}
ByteString ret;
for (size_t i = 0; i < hstr.size(); i += 2)
{
uint8_t top = hexToNibble(hstr[i+0]);
uint8_t btm = hexToNibble(hstr[i+1]);
ret.push_back((top << 4) | btm);
}
return ret;
}
Bytes::ByteString u32beToByteString(uint32_t num)
{
Bytes::ByteString ret;
ret.push_back((num >> 24) & 0xFF);
ret.push_back((num >> 16) & 0xFF);
ret.push_back((num >> 8) & 0xFF);
ret.push_back((num >> 0) & 0xFF);
return ret;
}
Bytes::ByteString u64beToByteString(uint64_t num)
{
Bytes::ByteString left = u32beToByteString((num >> 32) & 0xFFFFFFFF);
Bytes::ByteString right = u32beToByteString((num >> 0) & 0xFFFFFFFF);
return left + right;
}
static ByteString b32ChunkToBytes(const std::string & str)
{
ByteString ret;
uint64_t whole = 0x00;
size_t padcount = 0;
size_t finalcount;
if (str.length() != 8)
{
throw std::invalid_argument("incorrect length of base32 chunk");
}
size_t i;
for (i = 0; i < 8; ++i)
{
char c = str[i];
uint64_t bits;
if (c == '=')
{
bits = 0;
++padcount;
}
else if (padcount > 0)
{
throw std::invalid_argument("padding character followed by non-padding character");
}
else if (c >= 'A' && c <= 'Z')
{
bits = static_cast<Byte>(c - 'A');
}
else if (c >= '2' && c <= '7')
{
bits = static_cast<Byte>(c - '2' + 26);
}
else
{
throw std::invalid_argument("not a base32 character: " + std::string(1, c));
}
// shift into the chunk
whole |= (bits << ((7-i)*5));
}
switch (padcount)
{
case 0:
finalcount = 5;
break;
case 1:
finalcount = 4;
break;
case 3:
finalcount = 3;
break;
case 4:
finalcount = 2;
break;
case 6:
finalcount = 1;
break;
default:
throw std::invalid_argument("invalid number of padding characters");
}
for (i = 0; i < finalcount; ++i)
{
// shift out of the chunk
ret.push_back(static_cast<Byte>((whole >> ((4-i)*8)) & 0xFF));
}
return ret;
}
static inline uint64_t u64(uint8_t n)
{
return static_cast<uint64_t>(n);
}
static std::string bytesToB32Chunk(const ByteString & bs)
{
if (bs.size() < 1 || bs.size() > 5)
{
throw std::invalid_argument("need a chunk of at least 1 and at most 5 bytes");
}
uint64_t whole = 0x00;
size_t putchars = 2;
std::string ret;
// shift into the chunk
whole |= (u64(bs[0]) << 32);
if (bs.size() > 1)
{
whole |= (u64(bs[1]) << 24);
putchars += 2; // at least 4
}
if (bs.size() > 2)
{
whole |= (u64(bs[2]) << 16);
++putchars; // at least 5
}
if (bs.size() > 3)
{
whole |= (u64(bs[3]) << 8);
putchars += 2; // at least 7
}
if (bs.size() > 4)
{
whole |= u64(bs[4]);
++putchars; // at least 8
}
size_t i;
for (i = 0; i < putchars; ++i)
{
// shift out of the chunk
Byte val = (whole >> ((7-i)*5)) & 0x1F;
// map bits to base32
if (val < 26)
{
ret.push_back(static_cast<char>(val + 'A'));
}
else
{
ret.push_back(static_cast<char>(val - 26 + '2'));
}
}
// pad
for (i = putchars; i < 8; ++i)
{
ret.push_back('=');
}
return ret;
}
ByteString fromBase32(const std::string & b32str)
{
if (b32str.size() % 8 != 0)
{
throw std::invalid_argument("base32 string length not divisible by 8");
}
ByteString ret;
for (size_t i = 0; i < b32str.size(); i += 8)
{
std::string sub(b32str, i, 8);
ByteString chk = b32ChunkToBytes(sub);
ret.append(chk);
}
return ret;
}
ByteString fromUnpaddedBase32(const std::string & b32str)
{
std::string newstr = b32str;
while (newstr.size() % 8 != 0)
{
newstr.push_back('=');
}
return fromBase32(newstr);
}
std::string toBase32(const ByteString & bs)
{
std::string ret;
size_t i, j, len;
for (j = 0; j < bs.size() / 5; ++j)
{
i = j * 5;
ByteString sub(bs, i, 5);
std::string chk = bytesToB32Chunk(sub);
ret.append(chk);
}
i = j * 5;
len = bs.size() - i;
if (len > 0)
{
// block of size < 5 remains
ByteString sub(bs, i, std::string::npos);
std::string chk = bytesToB32Chunk(sub);
ret.append(chk);
}
return ret;
}
}
}