Skip to content

Commit

Permalink
Ensure error messages don't leak private key
Browse files Browse the repository at this point in the history
Since NixOS#8766, invalid base64 is rendered in errors, but we don't actually
want to show this in the case of an invalid private keys.
  • Loading branch information
Ericson2314 committed Sep 17, 2024
1 parent 00013c7 commit 18f855c
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 20 deletions.
5 changes: 3 additions & 2 deletions src/libstore/machines.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ static Machine parseBuilderLine(const std::set<std::string> & defaultSystems, co
const auto & str = tokens[fieldIndex];
try {
base64Decode(str);
} catch (const Error & e) {
throw FormatError("bad machine specification: a column #%lu in a row: '%s' is not valid base64 string: %s", fieldIndex, line, e.what());
} catch (FormatError & e) {
e.addTrace({}, "while parsing machine specification at a column #%lu in a row: '%s'", fieldIndex, line);
throw;
}
return str;
};
Expand Down
14 changes: 12 additions & 2 deletions src/libstore/ssh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

namespace nix {

static std::string parsePublicHostKey(std::string_view host, std::string_view sshPublicHostKey)
{
try {
return base64Decode(sshPublicHostKey);
} catch (Error & e) {
e.addTrace({}, "while decoding ssh public host key for host '%s'", host);
throw;
}
}

SSHMaster::SSHMaster(
std::string_view host,
std::string_view keyFile,
Expand All @@ -15,7 +25,7 @@ SSHMaster::SSHMaster(
: host(host)
, fakeSSH(host == "localhost")
, keyFile(keyFile)
, sshPublicHostKey(sshPublicHostKey)
, sshPublicHostKey(parsePublicHostKey(host, sshPublicHostKey))
, useMaster(useMaster && !fakeSSH)
, compress(compress)
, logFD(logFD)
Expand All @@ -39,7 +49,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
std::filesystem::path fileName = state->tmpDir->path() / "host-key";
auto p = host.rfind("@");
std::string thost = p != std::string::npos ? std::string(host, p + 1) : host;
writeFile(fileName.string(), thost + " " + base64Decode(sshPublicHostKey) + "\n");
writeFile(fileName.string(), thost + " " + sshPublicHostKey + "\n");
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
}
if (compress)
Expand Down
3 changes: 3 additions & 0 deletions src/libstore/ssh.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ private:
const std::string host;
bool fakeSSH;
const std::string keyFile;
/**
* Raw bytes, not Base64 encoding.
*/
const std::string sshPublicHostKey;
const bool useMaster;
const bool compress;
Expand Down
17 changes: 11 additions & 6 deletions src/libutil/signature/local-keys.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@ BorrowedCryptoValue BorrowedCryptoValue::parse(std::string_view s)
return {s.substr(0, colon), s.substr(colon + 1)};
}

Key::Key(std::string_view s)
Key::Key(std::string_view s, bool hideValue)
{
auto ss = BorrowedCryptoValue::parse(s);

name = ss.name;
key = ss.payload;

if (name == "" || key == "")
throw Error("key is corrupt");
try {
if (name == "" || key == "")
throw FormatError("key is corrupt");

key = base64Decode(key);
key = base64Decode(key);
} catch (Error & e) {
e.addTrace({}, "while decoding key named '%s'", name);
throw;
}
}

std::string Key::to_string() const
Expand All @@ -33,7 +38,7 @@ std::string Key::to_string() const
}

SecretKey::SecretKey(std::string_view s)
: Key(s)
: Key{s, true}
{
if (key.size() != crypto_sign_SECRETKEYBYTES)
throw Error("secret key is not valid");
Expand Down Expand Up @@ -66,7 +71,7 @@ SecretKey SecretKey::generate(std::string_view name)
}

PublicKey::PublicKey(std::string_view s)
: Key(s)
: Key{s, false}
{
if (key.size() != crypto_sign_PUBLICKEYBYTES)
throw Error("public key is not valid");
Expand Down
12 changes: 8 additions & 4 deletions src/libutil/signature/local-keys.hh
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@ struct Key
std::string name;
std::string key;

std::string to_string() const;

protected:

/**
* Construct Key from a string in the format
* ‘<name>:<key-in-base64>’.
*
* @param hideValue Avoid displaying the raw Base64 in error
* messages to avoid leaking private keys.
*/
Key(std::string_view s);

std::string to_string() const;
Key(std::string_view s, bool hideValue);

protected:
Key(std::string_view name, std::string && key)
: name(name), key(std::move(key)) { }
};
Expand Down
7 changes: 5 additions & 2 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ std::string base64Encode(std::string_view s)
}


std::string base64Decode(std::string_view s)
std::string base64Decode(std::string_view s, bool hideValue)
{
constexpr char npos = -1;
constexpr std::array<char, 256> base64DecodeChars = [&] {
Expand All @@ -244,7 +244,10 @@ std::string base64Decode(std::string_view s)

char digit = base64DecodeChars[(unsigned char) c];
if (digit == npos) {
throw Error("invalid character in Base64 string: '%c' in '%s'", c, s.data());
auto msg = hideValue
? "<redacted>"
: fmt("'%s'", s.data());
throw FormatError("invalid character in Base64 string: '%c' in %s", c, msg);
}

bits += 6;
Expand Down
11 changes: 9 additions & 2 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,17 @@ constexpr char treeNull[] = " ";


/**
* Base64 encoding/decoding.
* Encode arbitrary bytes as Base64.
*/
std::string base64Encode(std::string_view s);
std::string base64Decode(std::string_view s);

/**
* Decode arbitrary bytes to Base64.
*
* @param hideValue Avoid displaying the raw Base64 in error messages,
* e.g. to avoid leaking private keys.
*/
std::string base64Decode(std::string_view s, bool hideValue = false);


/**
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/libexpr/nix_api_expr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh"

#include "gmock/gmock.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace nixC {
Expand Down
15 changes: 14 additions & 1 deletion tests/unit/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <limits.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include <numeric>

Expand Down Expand Up @@ -99,7 +100,19 @@ TEST(base64Decode, decodeAString)

TEST(base64Decode, decodeThrowsOnInvalidChar)
{
ASSERT_THROW(base64Decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
ASSERT_THROW(base64Decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), FormatError);
}

TEST(base64Decode, decodeThrowsNoLeak)
{
std::string msg = "cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0=";
try {
base64Decode(msg, true);
} catch (FormatError & e) {
EXPECT_THAT(e.msg(), testing::Not(testing::HasSubstr(msg)));
return;
}
EXPECT_TRUE(false);
}

/* ----------------------------------------------------------------------------
Expand Down

0 comments on commit 18f855c

Please sign in to comment.