mirror of
https://github.com/NixOS/nix.git
synced 2025-11-09 20:16:03 +01:00
Clean up Base* code
Make it separate from Hash, since other things can be base-encoded too. This isn't really needed for Nix, but it makes the code easier to read e.g. for someone reimplementing this stuff in a different language. (Of course, Base16/Base64 should be gotten off-the-shelf, but now the hash code, which is more bespoke, is less cluttered with the parts that would be from some library.) Many reimplementations of "Nix32" and our hash type already exist, so this cleanup is coming years too late, but I say better late than never / it is always good to nudge the code in the direction of being a "living spec". Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
This commit is contained in:
parent
664f06c94c
commit
991831227e
18 changed files with 357 additions and 244 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
#include "nix/fetchers/git-lfs-fetch.hh"
|
#include "nix/fetchers/git-lfs-fetch.hh"
|
||||||
#include "nix/fetchers/cache.hh"
|
#include "nix/fetchers/cache.hh"
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
#include "nix/util/finally.hh"
|
#include "nix/util/finally.hh"
|
||||||
#include "nix/util/processes.hh"
|
#include "nix/util/processes.hh"
|
||||||
#include "nix/util/signals.hh"
|
#include "nix/util/signals.hh"
|
||||||
|
|
@ -608,7 +609,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||||
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
|
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
|
||||||
std::string keyDecoded;
|
std::string keyDecoded;
|
||||||
try {
|
try {
|
||||||
keyDecoded = base64Decode(k.key);
|
keyDecoded = base64::decode(k.key);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
|
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
#include "nix/store/machines.hh"
|
#include "nix/store/machines.hh"
|
||||||
#include "nix/store/globals.hh"
|
#include "nix/store/globals.hh"
|
||||||
#include "nix/store/store-open.hh"
|
#include "nix/store/store-open.hh"
|
||||||
|
|
@ -158,7 +159,7 @@ static Machine parseBuilderLine(const StringSet & defaultSystems, const std::str
|
||||||
auto ensureBase64 = [&](size_t fieldIndex) {
|
auto ensureBase64 = [&](size_t fieldIndex) {
|
||||||
const auto & str = tokens[fieldIndex];
|
const auto & str = tokens[fieldIndex];
|
||||||
try {
|
try {
|
||||||
base64Decode(str);
|
base64::decode(str);
|
||||||
} catch (FormatError & e) {
|
} catch (FormatError & e) {
|
||||||
e.addTrace({}, "while parsing machine specification at a column #%lu in a row: '%s'", fieldIndex, line);
|
e.addTrace({}, "while parsing machine specification at a column #%lu in a row: '%s'", fieldIndex, line);
|
||||||
throw;
|
throw;
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@
|
||||||
#include "nix/util/environment-variables.hh"
|
#include "nix/util/environment-variables.hh"
|
||||||
#include "nix/util/util.hh"
|
#include "nix/util/util.hh"
|
||||||
#include "nix/util/exec.hh"
|
#include "nix/util/exec.hh"
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static std::string parsePublicHostKey(std::string_view host, std::string_view sshPublicHostKey)
|
static std::string parsePublicHostKey(std::string_view host, std::string_view sshPublicHostKey)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return base64Decode(sshPublicHostKey);
|
return base64::decode(sshPublicHostKey);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace({}, "while decoding ssh public host key for host '%s'", host);
|
e.addTrace({}, "while decoding ssh public host key for host '%s'", host);
|
||||||
throw;
|
throw;
|
||||||
|
|
|
||||||
68
src/libutil-tests/base-n.cc
Normal file
68
src/libutil-tests/base-n.cc
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
|
#include "nix/util/error.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
static const std::span<const std::byte> stringToByteSpan(const std::string_view s)
|
||||||
|
{
|
||||||
|
return {(const std::byte *) s.data(), s.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* base64::encode
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
TEST(base64Encode, emptyString)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(base64::encode(stringToByteSpan("")), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base64Encode, encodesAString)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(base64::encode(stringToByteSpan("quod erat demonstrandum")), "cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0=");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base64Encode, encodeAndDecode)
|
||||||
|
{
|
||||||
|
auto s = "quod erat demonstrandum";
|
||||||
|
auto encoded = base64::encode(stringToByteSpan(s));
|
||||||
|
auto decoded = base64::decode(encoded);
|
||||||
|
|
||||||
|
ASSERT_EQ(decoded, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base64Encode, encodeAndDecodeNonPrintable)
|
||||||
|
{
|
||||||
|
char s[256];
|
||||||
|
std::iota(std::rbegin(s), std::rend(s), 0);
|
||||||
|
|
||||||
|
auto encoded = base64::encode(std::as_bytes(std::span<const char>{std::string_view{s}}));
|
||||||
|
auto decoded = base64::decode(encoded);
|
||||||
|
|
||||||
|
EXPECT_EQ(decoded.length(), 255u);
|
||||||
|
ASSERT_EQ(decoded, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* base64::decode
|
||||||
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
TEST(base64Decode, emptyString)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(base64::decode(""), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base64Decode, decodeAString)
|
||||||
|
{
|
||||||
|
ASSERT_EQ(base64::decode("cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0="), "quod erat demonstrandum");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(base64Decode, decodeThrowsOnInvalidChar)
|
||||||
|
{
|
||||||
|
ASSERT_THROW(base64::decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -44,6 +44,7 @@ subdir('nix-meson-build-support/common')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'args.cc',
|
'args.cc',
|
||||||
|
'base-n.cc',
|
||||||
'canon-path.cc',
|
'canon-path.cc',
|
||||||
'checked-arithmetic.cc',
|
'checked-arithmetic.cc',
|
||||||
'chunked-vector.cc',
|
'chunked-vector.cc',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "nix/util/file-system.hh"
|
#include "nix/util/file-system.hh"
|
||||||
#include "nix/util/terminal.hh"
|
#include "nix/util/terminal.hh"
|
||||||
#include "nix/util/strings.hh"
|
#include "nix/util/strings.hh"
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
@ -48,60 +49,6 @@ TEST(hasSuffix, trivialCase)
|
||||||
ASSERT_TRUE(hasSuffix("foobar", "bar"));
|
ASSERT_TRUE(hasSuffix("foobar", "bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* base64Encode
|
|
||||||
* --------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
TEST(base64Encode, emptyString)
|
|
||||||
{
|
|
||||||
ASSERT_EQ(base64Encode(""), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base64Encode, encodesAString)
|
|
||||||
{
|
|
||||||
ASSERT_EQ(base64Encode("quod erat demonstrandum"), "cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0=");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base64Encode, encodeAndDecode)
|
|
||||||
{
|
|
||||||
auto s = "quod erat demonstrandum";
|
|
||||||
auto encoded = base64Encode(s);
|
|
||||||
auto decoded = base64Decode(encoded);
|
|
||||||
|
|
||||||
ASSERT_EQ(decoded, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base64Encode, encodeAndDecodeNonPrintable)
|
|
||||||
{
|
|
||||||
char s[256];
|
|
||||||
std::iota(std::rbegin(s), std::rend(s), 0);
|
|
||||||
|
|
||||||
auto encoded = base64Encode(s);
|
|
||||||
auto decoded = base64Decode(encoded);
|
|
||||||
|
|
||||||
EXPECT_EQ(decoded.length(), 255u);
|
|
||||||
ASSERT_EQ(decoded, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* base64Decode
|
|
||||||
* --------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
TEST(base64Decode, emptyString)
|
|
||||||
{
|
|
||||||
ASSERT_EQ(base64Decode(""), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base64Decode, decodeAString)
|
|
||||||
{
|
|
||||||
ASSERT_EQ(base64Decode("cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0="), "quod erat demonstrandum");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(base64Decode, decodeThrowsOnInvalidChar)
|
|
||||||
{
|
|
||||||
ASSERT_THROW(base64Decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* getLine
|
* getLine
|
||||||
* --------------------------------------------------------------------------*/
|
* --------------------------------------------------------------------------*/
|
||||||
|
|
|
||||||
114
src/libutil/base-n.cc
Normal file
114
src/libutil/base-n.cc
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "nix/util/array-from-string-literal.hh"
|
||||||
|
#include "nix/util/util.hh"
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
constexpr static const std::array<char, 16> base16Chars = "0123456789abcdef"_arrayNoNull;
|
||||||
|
|
||||||
|
std::string base16::encode(std::span<const std::byte> b)
|
||||||
|
{
|
||||||
|
std::string buf;
|
||||||
|
buf.reserve(b.size() * 2);
|
||||||
|
for (size_t i = 0; i < b.size(); i++) {
|
||||||
|
buf.push_back(base16Chars[(uint8_t) b.data()[i] >> 4]);
|
||||||
|
buf.push_back(base16Chars[(uint8_t) b.data()[i] & 0x0f]);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string base16::decode(std::string_view s)
|
||||||
|
{
|
||||||
|
auto parseHexDigit = [&](char c) {
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return c - '0';
|
||||||
|
if (c >= 'A' && c <= 'F')
|
||||||
|
return c - 'A' + 10;
|
||||||
|
if (c >= 'a' && c <= 'f')
|
||||||
|
return c - 'a' + 10;
|
||||||
|
throw FormatError("invalid character in Base16 string: '%c'", c);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(s.size() % 2 == 0);
|
||||||
|
auto decodedSize = s.size() / 2;
|
||||||
|
|
||||||
|
std::string res;
|
||||||
|
res.reserve(decodedSize);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < decodedSize; i++) {
|
||||||
|
res.push_back(parseHexDigit(s[i * 2]) << 4 | parseHexDigit(s[i * 2 + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static const std::array<char, 64> base64Chars =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"_arrayNoNull;
|
||||||
|
|
||||||
|
std::string base64::encode(std::span<const std::byte> s)
|
||||||
|
{
|
||||||
|
std::string res;
|
||||||
|
res.reserve((s.size() + 2) / 3 * 4);
|
||||||
|
int data = 0, nbits = 0;
|
||||||
|
|
||||||
|
for (std::byte c : s) {
|
||||||
|
data = data << 8 | (uint8_t) c;
|
||||||
|
nbits += 8;
|
||||||
|
while (nbits >= 6) {
|
||||||
|
nbits -= 6;
|
||||||
|
res.push_back(base64Chars[data >> nbits & 0x3f]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbits)
|
||||||
|
res.push_back(base64Chars[data << (6 - nbits) & 0x3f]);
|
||||||
|
while (res.size() % 4)
|
||||||
|
res.push_back('=');
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string base64::decode(std::string_view s)
|
||||||
|
{
|
||||||
|
constexpr char npos = -1;
|
||||||
|
constexpr std::array<char, 256> base64DecodeChars = [&] {
|
||||||
|
std::array<char, 256> result{};
|
||||||
|
for (auto & c : result)
|
||||||
|
c = npos;
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
result[base64Chars[i]] = i;
|
||||||
|
return result;
|
||||||
|
}();
|
||||||
|
|
||||||
|
std::string res;
|
||||||
|
// Some sequences are missing the padding consisting of up to two '='.
|
||||||
|
// vvv
|
||||||
|
res.reserve((s.size() + 2) / 4 * 3);
|
||||||
|
unsigned int d = 0, bits = 0;
|
||||||
|
|
||||||
|
for (char c : s) {
|
||||||
|
if (c == '=')
|
||||||
|
break;
|
||||||
|
if (c == '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char digit = base64DecodeChars[(unsigned char) c];
|
||||||
|
if (digit == npos)
|
||||||
|
throw FormatError("invalid character in Base64 string: '%c'", c);
|
||||||
|
|
||||||
|
bits += 6;
|
||||||
|
d = d << 6 | digit;
|
||||||
|
if (bits >= 8) {
|
||||||
|
res.push_back(d >> (bits - 8) & 0xff);
|
||||||
|
bits -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "nix/util/base-nix-32.hh"
|
#include "nix/util/base-nix-32.hh"
|
||||||
|
#include "nix/util/util.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
@ -16,12 +17,12 @@ constexpr const std::array<unsigned char, 256> BaseNix32::reverseMap = [] {
|
||||||
return map;
|
return map;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
std::string BaseNix32::encode(std::span<const uint8_t> originalData)
|
std::string BaseNix32::encode(std::span<const std::byte> bs)
|
||||||
{
|
{
|
||||||
if (originalData.size() == 0)
|
if (bs.size() == 0)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
size_t len = encodedLength(originalData.size());
|
size_t len = encodedLength(bs.size());
|
||||||
assert(len);
|
assert(len);
|
||||||
|
|
||||||
std::string s;
|
std::string s;
|
||||||
|
|
@ -31,12 +32,42 @@ std::string BaseNix32::encode(std::span<const uint8_t> originalData)
|
||||||
unsigned int b = n * 5;
|
unsigned int b = n * 5;
|
||||||
unsigned int i = b / 8;
|
unsigned int i = b / 8;
|
||||||
unsigned int j = b % 8;
|
unsigned int j = b % 8;
|
||||||
unsigned char c =
|
std::byte c = (bs.data()[i] >> j) | (i >= bs.size() - 1 ? std::byte{0} : bs.data()[i + 1] << (8 - j));
|
||||||
(originalData.data()[i] >> j) | (i >= originalData.size() - 1 ? 0 : originalData.data()[i + 1] << (8 - j));
|
s.push_back(characters[uint8_t(c & std::byte{0x1f})]);
|
||||||
s.push_back(characters[c & 0x1f]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string BaseNix32::decode(std::string_view s)
|
||||||
|
{
|
||||||
|
std::string res;
|
||||||
|
res.reserve((s.size() * 5 + 7) / 8); // ceiling(size * 5/8)
|
||||||
|
|
||||||
|
for (unsigned int n = 0; n < s.size(); ++n) {
|
||||||
|
char c = s[s.size() - n - 1];
|
||||||
|
auto digit_opt = BaseNix32::lookupReverse(c);
|
||||||
|
|
||||||
|
if (!digit_opt)
|
||||||
|
throw FormatError("invalid character in Nix32 (Nix's Base32 variation) string: '%c'", c);
|
||||||
|
|
||||||
|
uint8_t digit = *digit_opt;
|
||||||
|
|
||||||
|
unsigned int b = n * 5;
|
||||||
|
unsigned int i = b / 8;
|
||||||
|
unsigned int j = b % 8;
|
||||||
|
|
||||||
|
// Ensure res has enough space
|
||||||
|
res.resize(i + 1);
|
||||||
|
res[i] |= digit << j;
|
||||||
|
|
||||||
|
if (digit >> (8 - j)) {
|
||||||
|
res.resize(i + 2);
|
||||||
|
res[i + 1] |= digit >> (8 - j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "nix/util/archive.hh"
|
#include "nix/util/archive.hh"
|
||||||
#include "nix/util/configuration.hh"
|
#include "nix/util/configuration.hh"
|
||||||
#include "nix/util/split.hh"
|
#include "nix/util/split.hh"
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
#include "nix/util/base-nix-32.hh"
|
#include "nix/util/base-nix-32.hh"
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
@ -59,25 +60,6 @@ std::strong_ordering Hash::operator<=>(const Hash & h) const noexcept
|
||||||
return std::strong_ordering::equivalent;
|
return std::strong_ordering::equivalent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string base16Chars = "0123456789abcdef";
|
|
||||||
|
|
||||||
static std::string printHash16(const Hash & hash)
|
|
||||||
{
|
|
||||||
std::string buf;
|
|
||||||
buf.reserve(hash.hashSize * 2);
|
|
||||||
for (unsigned int i = 0; i < hash.hashSize; i++) {
|
|
||||||
buf.push_back(base16Chars[hash.hash[i] >> 4]);
|
|
||||||
buf.push_back(base16Chars[hash.hash[i] & 0x0f]);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string printHash32(const Hash & hash)
|
|
||||||
{
|
|
||||||
assert(hash.hashSize);
|
|
||||||
return BaseNix32::encode({&hash.hash[0], hash.hashSize});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string printHash16or32(const Hash & hash)
|
std::string printHash16or32(const Hash & hash)
|
||||||
{
|
{
|
||||||
assert(static_cast<char>(hash.algo));
|
assert(static_cast<char>(hash.algo));
|
||||||
|
|
@ -91,16 +73,20 @@ std::string Hash::to_string(HashFormat hashFormat, bool includeAlgo) const
|
||||||
s += printHashAlgo(algo);
|
s += printHashAlgo(algo);
|
||||||
s += hashFormat == HashFormat::SRI ? '-' : ':';
|
s += hashFormat == HashFormat::SRI ? '-' : ':';
|
||||||
}
|
}
|
||||||
|
const auto bytes = std::as_bytes(std::span<const uint8_t>{&hash[0], hashSize});
|
||||||
switch (hashFormat) {
|
switch (hashFormat) {
|
||||||
case HashFormat::Base16:
|
case HashFormat::Base16:
|
||||||
s += printHash16(*this);
|
assert(hashSize);
|
||||||
|
s += base16::encode(bytes);
|
||||||
break;
|
break;
|
||||||
case HashFormat::Nix32:
|
case HashFormat::Nix32:
|
||||||
s += printHash32(*this);
|
assert(hashSize);
|
||||||
|
s += BaseNix32::encode(bytes);
|
||||||
break;
|
break;
|
||||||
case HashFormat::Base64:
|
case HashFormat::Base64:
|
||||||
case HashFormat::SRI:
|
case HashFormat::SRI:
|
||||||
s += base64Encode(std::string_view((const char *) hash, hashSize));
|
assert(hashSize);
|
||||||
|
s += base64::encode(bytes);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
|
|
@ -180,65 +166,40 @@ Hash Hash::parseNonSRIUnprefixed(std::string_view s, HashAlgorithm algo)
|
||||||
Hash::Hash(std::string_view rest, HashAlgorithm algo, bool isSRI)
|
Hash::Hash(std::string_view rest, HashAlgorithm algo, bool isSRI)
|
||||||
: Hash(algo)
|
: Hash(algo)
|
||||||
{
|
{
|
||||||
if (!isSRI && rest.size() == base16Len()) {
|
auto [decode, formatName] = [&]() -> std::pair<decltype(base16::decode) *, std::string_view> {
|
||||||
|
if (isSRI) {
|
||||||
auto parseHexDigit = [&](char c) {
|
/* In the SRI case, we always are using Base64. If the
|
||||||
if (c >= '0' && c <= '9')
|
length is wrong, get an error later. */
|
||||||
return c - '0';
|
return {base64::decode, "SRI"};
|
||||||
if (c >= 'A' && c <= 'F')
|
|
||||||
return c - 'A' + 10;
|
|
||||||
if (c >= 'a' && c <= 'f')
|
|
||||||
return c - 'a' + 10;
|
|
||||||
throw BadHash("invalid base-16 hash '%s'", rest);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < hashSize; i++) {
|
|
||||||
hash[i] = parseHexDigit(rest[i * 2]) << 4 | parseHexDigit(rest[i * 2 + 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (!isSRI && rest.size() == base32Len()) {
|
|
||||||
|
|
||||||
for (unsigned int n = 0; n < rest.size(); ++n) {
|
|
||||||
char c = rest[rest.size() - n - 1];
|
|
||||||
auto digit_opt = BaseNix32::lookupReverse(c);
|
|
||||||
|
|
||||||
if (!digit_opt)
|
|
||||||
throw BadHash("invalid base-32 hash: '%s'", rest);
|
|
||||||
|
|
||||||
uint8_t digit = std::move(*digit_opt);
|
|
||||||
|
|
||||||
unsigned int b = n * 5;
|
|
||||||
unsigned int i = b / 8;
|
|
||||||
unsigned int j = b % 8;
|
|
||||||
hash[i] |= digit << j;
|
|
||||||
|
|
||||||
if (i < hashSize - 1) {
|
|
||||||
hash[i + 1] |= digit >> (8 - j);
|
|
||||||
} else {
|
} else {
|
||||||
if (digit >> (8 - j))
|
/* Otherwise, decide via the length of the hash (for the
|
||||||
throw BadHash("invalid base-32 hash '%s'", rest);
|
given algorithm) what base encoding it is. */
|
||||||
}
|
|
||||||
}
|
if (rest.size() == base16::encodedLength(hashSize))
|
||||||
|
return {base16::decode, "base16"};
|
||||||
|
|
||||||
|
if (rest.size() == BaseNix32::encodedLength(hashSize))
|
||||||
|
return {BaseNix32::decode, "nix32"};
|
||||||
|
|
||||||
|
if (rest.size() == base64::encodedLength(hashSize))
|
||||||
|
return {base64::decode, "Base64"};
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (isSRI || rest.size() == base64Len()) {
|
throw BadHash("hash '%s' has wrong length for hash algorithm '%s'", rest, printHashAlgo(this->algo));
|
||||||
|
}();
|
||||||
|
|
||||||
std::string d;
|
std::string d;
|
||||||
try {
|
try {
|
||||||
d = base64Decode(rest);
|
d = decode(rest);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace({}, "While decoding hash '%s'", rest);
|
e.addTrace({}, "While decoding hash '%s'", rest);
|
||||||
}
|
}
|
||||||
if (d.size() != hashSize)
|
if (d.size() != hashSize)
|
||||||
throw BadHash("invalid %s hash '%s'", isSRI ? "SRI" : "base-64", rest);
|
throw BadHash("invalid %s hash '%s' %d %d", formatName, rest);
|
||||||
assert(hashSize);
|
assert(hashSize);
|
||||||
memcpy(hash, d.data(), hashSize);
|
memcpy(hash, d.data(), hashSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
|
||||||
throw BadHash("hash '%s' has wrong length for hash algorithm '%s'", rest, printHashAlgo(this->algo));
|
|
||||||
}
|
|
||||||
|
|
||||||
Hash Hash::random(HashAlgorithm algo)
|
Hash Hash::random(HashAlgorithm algo)
|
||||||
{
|
{
|
||||||
Hash hash(algo);
|
Hash hash(algo);
|
||||||
|
|
|
||||||
27
src/libutil/include/nix/util/array-from-string-literal.hh
Normal file
27
src/libutil/include/nix/util/array-from-string-literal.hh
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
template<size_t sizeWithNull>
|
||||||
|
struct ArrayNoNullAdaptor
|
||||||
|
{
|
||||||
|
std::array<char, sizeWithNull - 1> data;
|
||||||
|
|
||||||
|
constexpr ArrayNoNullAdaptor(const char (&init)[sizeWithNull])
|
||||||
|
{
|
||||||
|
static_assert(sizeWithNull > 0);
|
||||||
|
std::copy_n(init, sizeWithNull - 1, data.data());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<ArrayNoNullAdaptor str>
|
||||||
|
constexpr auto operator""_arrayNoNull()
|
||||||
|
{
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
53
src/libutil/include/nix/util/base-n.hh
Normal file
53
src/libutil/include/nix/util/base-n.hh
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
namespace base16 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of a base-16 representation of this many bytes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr static inline size_t encodedLength(size_t origSize)
|
||||||
|
{
|
||||||
|
return origSize * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode arbitrary bytes as Base16.
|
||||||
|
*/
|
||||||
|
std::string encode(std::span<const std::byte> b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode arbitrary Base16 string to bytes.
|
||||||
|
*/
|
||||||
|
std::string decode(std::string_view s);
|
||||||
|
|
||||||
|
} // namespace base16
|
||||||
|
|
||||||
|
namespace base64 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of a base-64 representation of this many bytes.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr static inline size_t encodedLength(size_t origSize)
|
||||||
|
{
|
||||||
|
return ((4 * origSize / 3) + 3) & ~3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode arbitrary bytes as Base64.
|
||||||
|
*/
|
||||||
|
std::string encode(std::span<const std::byte> b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode arbitrary Base64 string to bytes.
|
||||||
|
*/
|
||||||
|
std::string decode(std::string_view s);
|
||||||
|
|
||||||
|
} // namespace base64
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -7,14 +7,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
|
#include "nix/util/array-from-string-literal.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
struct BaseNix32
|
struct BaseNix32
|
||||||
{
|
{
|
||||||
/// omitted: E O U T
|
/// omitted: E O U T
|
||||||
constexpr static std::array<char, 32> characters = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
|
constexpr static std::array<char, 32> characters = "0123456789abcdfghijklmnpqrsvwxyz"_arrayNoNull;
|
||||||
'b', 'c', 'd', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
||||||
'n', 'p', 'q', 'r', 's', 'v', 'w', 'x', 'y', 'z'};
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const std::array<uint8_t, 256> reverseMap;
|
static const std::array<uint8_t, 256> reverseMap;
|
||||||
|
|
@ -34,12 +34,14 @@ public:
|
||||||
/**
|
/**
|
||||||
* Returns the length of a base-32 representation of this hash.
|
* Returns the length of a base-32 representation of this hash.
|
||||||
*/
|
*/
|
||||||
static size_t encodedLength(size_t originalLength)
|
[[nodiscard]] constexpr static inline size_t encodedLength(size_t originalLength)
|
||||||
{
|
{
|
||||||
return (originalLength * 8 - 1) / 5 + 1;
|
return (originalLength * 8 - 1) / 5 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string encode(std::span<const uint8_t> originalData);
|
static std::string encode(std::span<const std::byte> originalData);
|
||||||
|
|
||||||
|
static std::string decode(std::string_view s);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|
|
||||||
|
|
@ -110,30 +110,6 @@ public:
|
||||||
*/
|
*/
|
||||||
std::strong_ordering operator<=>(const Hash & h2) const noexcept;
|
std::strong_ordering operator<=>(const Hash & h2) const noexcept;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of a base-16 representation of this hash.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] size_t base16Len() const
|
|
||||||
{
|
|
||||||
return hashSize * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of a base-32 representation of this hash.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] size_t base32Len() const
|
|
||||||
{
|
|
||||||
return (hashSize * 8 - 1) / 5 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of a base-64 representation of this hash.
|
|
||||||
*/
|
|
||||||
[[nodiscard]] size_t base64Len() const
|
|
||||||
{
|
|
||||||
return ((4 * hashSize / 3) + 3) & ~3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a string representation of the hash, in base-16, base-32
|
* Return a string representation of the hash, in base-16, base-32
|
||||||
* or base-64. By default, this is prefixed by the hash algo
|
* or base-64. By default, this is prefixed by the hash algo
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ headers = files(
|
||||||
'archive.hh',
|
'archive.hh',
|
||||||
'args.hh',
|
'args.hh',
|
||||||
'args/root.hh',
|
'args/root.hh',
|
||||||
|
'array-from-string-literal.hh',
|
||||||
|
'base-n.hh',
|
||||||
'base-nix-32.hh',
|
'base-nix-32.hh',
|
||||||
'callback.hh',
|
'callback.hh',
|
||||||
'canon-path.hh',
|
'canon-path.hh',
|
||||||
|
|
|
||||||
|
|
@ -179,16 +179,6 @@ constexpr char treeLast[] = "└───";
|
||||||
constexpr char treeLine[] = "│ ";
|
constexpr char treeLine[] = "│ ";
|
||||||
constexpr char treeNull[] = " ";
|
constexpr char treeNull[] = " ";
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode arbitrary bytes as Base64.
|
|
||||||
*/
|
|
||||||
std::string base64Encode(std::string_view s);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode arbitrary bytes to Base64.
|
|
||||||
*/
|
|
||||||
std::string base64Decode(std::string_view s);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove common leading whitespace from the lines in the string
|
* Remove common leading whitespace from the lines in the string
|
||||||
* 's'. For example, if every line is indented by at least 3 spaces,
|
* 's'. For example, if every line is indented by at least 3 spaces,
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ subdir('nix-meson-build-support/common')
|
||||||
sources = [config_priv_h] + files(
|
sources = [config_priv_h] + files(
|
||||||
'archive.cc',
|
'archive.cc',
|
||||||
'args.cc',
|
'args.cc',
|
||||||
|
'base-n.cc',
|
||||||
'base-nix-32.cc',
|
'base-nix-32.cc',
|
||||||
'canon-path.cc',
|
'canon-path.cc',
|
||||||
'compression.cc',
|
'compression.cc',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "nix/util/signature/local-keys.hh"
|
#include "nix/util/signature/local-keys.hh"
|
||||||
|
|
||||||
#include "nix/util/file-system.hh"
|
#include "nix/util/file-system.hh"
|
||||||
|
#include "nix/util/base-n.hh"
|
||||||
#include "nix/util/util.hh"
|
#include "nix/util/util.hh"
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ Key::Key(std::string_view s, bool sensitiveValue)
|
||||||
if (name == "" || key == "")
|
if (name == "" || key == "")
|
||||||
throw FormatError("key is corrupt");
|
throw FormatError("key is corrupt");
|
||||||
|
|
||||||
key = base64Decode(key);
|
key = base64::decode(key);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
std::string extra;
|
std::string extra;
|
||||||
if (!sensitiveValue)
|
if (!sensitiveValue)
|
||||||
|
|
@ -37,7 +38,7 @@ Key::Key(std::string_view s, bool sensitiveValue)
|
||||||
|
|
||||||
std::string Key::to_string() const
|
std::string Key::to_string() const
|
||||||
{
|
{
|
||||||
return name + ":" + base64Encode(key);
|
return name + ":" + base64::encode(std::as_bytes(std::span<const char>{key}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SecretKey::SecretKey(std::string_view s)
|
SecretKey::SecretKey(std::string_view s)
|
||||||
|
|
@ -52,7 +53,7 @@ std::string SecretKey::signDetached(std::string_view data) const
|
||||||
unsigned char sig[crypto_sign_BYTES];
|
unsigned char sig[crypto_sign_BYTES];
|
||||||
unsigned long long sigLen;
|
unsigned long long sigLen;
|
||||||
crypto_sign_detached(sig, &sigLen, (unsigned char *) data.data(), data.size(), (unsigned char *) key.data());
|
crypto_sign_detached(sig, &sigLen, (unsigned char *) data.data(), data.size(), (unsigned char *) key.data());
|
||||||
return name + ":" + base64Encode(std::string((char *) sig, sigLen));
|
return name + ":" + base64::encode(std::as_bytes(std::span<const unsigned char>{sig, sigLen}));
|
||||||
}
|
}
|
||||||
|
|
||||||
PublicKey SecretKey::toPublicKey() const
|
PublicKey SecretKey::toPublicKey() const
|
||||||
|
|
@ -93,7 +94,7 @@ bool PublicKey::verifyDetachedAnon(std::string_view data, std::string_view sig)
|
||||||
{
|
{
|
||||||
std::string sig2;
|
std::string sig2;
|
||||||
try {
|
try {
|
||||||
sig2 = base64Decode(sig);
|
sig2 = base64::decode(sig);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace({}, "while decoding signature '%s'", sig);
|
e.addTrace({}, "while decoding signature '%s'", sig);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,70 +204,6 @@ void ignoreExceptionExceptInterrupt(Verbosity lvl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
|
|
||||||
std::string base64Encode(std::string_view s)
|
|
||||||
{
|
|
||||||
std::string res;
|
|
||||||
res.reserve((s.size() + 2) / 3 * 4);
|
|
||||||
int data = 0, nbits = 0;
|
|
||||||
|
|
||||||
for (char c : s) {
|
|
||||||
data = data << 8 | (unsigned char) c;
|
|
||||||
nbits += 8;
|
|
||||||
while (nbits >= 6) {
|
|
||||||
nbits -= 6;
|
|
||||||
res.push_back(base64Chars[data >> nbits & 0x3f]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbits)
|
|
||||||
res.push_back(base64Chars[data << (6 - nbits) & 0x3f]);
|
|
||||||
while (res.size() % 4)
|
|
||||||
res.push_back('=');
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string base64Decode(std::string_view s)
|
|
||||||
{
|
|
||||||
constexpr char npos = -1;
|
|
||||||
constexpr std::array<char, 256> base64DecodeChars = [&] {
|
|
||||||
std::array<char, 256> result{};
|
|
||||||
for (auto & c : result)
|
|
||||||
c = npos;
|
|
||||||
for (int i = 0; i < 64; i++)
|
|
||||||
result[base64Chars[i]] = i;
|
|
||||||
return result;
|
|
||||||
}();
|
|
||||||
|
|
||||||
std::string res;
|
|
||||||
// Some sequences are missing the padding consisting of up to two '='.
|
|
||||||
// vvv
|
|
||||||
res.reserve((s.size() + 2) / 4 * 3);
|
|
||||||
unsigned int d = 0, bits = 0;
|
|
||||||
|
|
||||||
for (char c : s) {
|
|
||||||
if (c == '=')
|
|
||||||
break;
|
|
||||||
if (c == '\n')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
char digit = base64DecodeChars[(unsigned char) c];
|
|
||||||
if (digit == npos)
|
|
||||||
throw FormatError("invalid character in Base64 string: '%c'", c);
|
|
||||||
|
|
||||||
bits += 6;
|
|
||||||
d = d << 6 | digit;
|
|
||||||
if (bits >= 8) {
|
|
||||||
res.push_back(d >> (bits - 8) & 0xff);
|
|
||||||
bits -= 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string stripIndentation(std::string_view s)
|
std::string stripIndentation(std::string_view s)
|
||||||
{
|
{
|
||||||
size_t minIndent = 10000;
|
size_t minIndent = 10000;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue