Ensure error messages don't leak private key

Since #8766, invalid base64 is rendered in errors, but we don't actually
want to show this in the case of an invalid private keys.

Co-Authored-By: Eelco Dolstra <edolstra@gmail.com>
This commit is contained in:
John Ericson 2024-09-17 15:25:30 -04:00 committed by John Ericson
parent d0c351bf43
commit 2b6b03d8df
10 changed files with 68 additions and 22 deletions

View file

@ -583,7 +583,13 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
std::string re = R"(Good "git" signature for \* with .* key SHA256:[)"; std::string re = R"(Good "git" signature for \* with .* key SHA256:[)";
for (const fetchers::PublicKey & k : publicKeys){ for (const fetchers::PublicKey & k : publicKeys){
// 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
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, base64Decode(k.key)).to_string(nix::HashFormat::Base64, false), "="); std::string keyDecoded;
try {
keyDecoded = base64Decode(k.key);
} catch (Error & e) {
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
}
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, keyDecoded).to_string(nix::HashFormat::Base64, false), "=");
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+" ); auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+" );
re += "(" + escaped_fingerprint + ")"; re += "(" + escaped_fingerprint + ")";
} }

View file

@ -159,8 +159,9 @@ static Machine parseBuilderLine(const std::set<std::string> & defaultSystems, co
const auto & str = tokens[fieldIndex]; const auto & str = tokens[fieldIndex];
try { try {
base64Decode(str); base64Decode(str);
} catch (const Error & e) { } catch (FormatError & e) {
throw FormatError("bad machine specification: a column #%lu in a row: '%s' is not valid base64 string: %s", fieldIndex, line, e.what()); e.addTrace({}, "while parsing machine specification at a column #%lu in a row: '%s'", fieldIndex, line);
throw;
} }
return str; return str;
}; };

View file

@ -7,6 +7,16 @@
namespace nix { 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( SSHMaster::SSHMaster(
std::string_view host, std::string_view host,
std::string_view keyFile, std::string_view keyFile,
@ -15,7 +25,7 @@ SSHMaster::SSHMaster(
: host(host) : host(host)
, fakeSSH(host == "localhost") , fakeSSH(host == "localhost")
, keyFile(keyFile) , keyFile(keyFile)
, sshPublicHostKey(sshPublicHostKey) , sshPublicHostKey(parsePublicHostKey(host, sshPublicHostKey))
, useMaster(useMaster && !fakeSSH) , useMaster(useMaster && !fakeSSH)
, compress(compress) , compress(compress)
, logFD(logFD) , logFD(logFD)
@ -39,7 +49,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
std::filesystem::path fileName = state->tmpDir->path() / "host-key"; std::filesystem::path fileName = state->tmpDir->path() / "host-key";
auto p = host.rfind("@"); auto p = host.rfind("@");
std::string thost = p != std::string::npos ? std::string(host, p + 1) : host; 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()}); args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
} }
if (compress) if (compress)

View file

@ -14,6 +14,9 @@ private:
const std::string host; const std::string host;
bool fakeSSH; bool fakeSSH;
const std::string keyFile; const std::string keyFile;
/**
* Raw bytes, not Base64 encoding.
*/
const std::string sshPublicHostKey; const std::string sshPublicHostKey;
const bool useMaster; const bool useMaster;
const bool compress; const bool compress;

View file

@ -245,7 +245,12 @@ Hash::Hash(std::string_view rest, HashAlgorithm algo, bool isSRI)
} }
else if (isSRI || rest.size() == base64Len()) { else if (isSRI || rest.size() == base64Len()) {
auto d = base64Decode(rest); std::string d;
try {
d = base64Decode(rest);
} catch (Error & e) {
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'", isSRI ? "SRI" : "base-64", rest);
assert(hashSize); assert(hashSize);

View file

@ -14,17 +14,25 @@ BorrowedCryptoValue BorrowedCryptoValue::parse(std::string_view s)
return {s.substr(0, colon), s.substr(colon + 1)}; return {s.substr(0, colon), s.substr(colon + 1)};
} }
Key::Key(std::string_view s) Key::Key(std::string_view s, bool sensitiveValue)
{ {
auto ss = BorrowedCryptoValue::parse(s); auto ss = BorrowedCryptoValue::parse(s);
name = ss.name; name = ss.name;
key = ss.payload; key = ss.payload;
if (name == "" || key == "") try {
throw Error("key is corrupt"); if (name == "" || key == "")
throw FormatError("key is corrupt");
key = base64Decode(key); key = base64Decode(key);
} catch (Error & e) {
std::string extra;
if (!sensitiveValue)
extra = fmt(" with raw value '%s'", key);
e.addTrace({}, "while decoding key named '%s'%s", name, extra);
throw;
}
} }
std::string Key::to_string() const std::string Key::to_string() const
@ -33,7 +41,7 @@ std::string Key::to_string() const
} }
SecretKey::SecretKey(std::string_view s) SecretKey::SecretKey(std::string_view s)
: Key(s) : Key{s, true}
{ {
if (key.size() != crypto_sign_SECRETKEYBYTES) if (key.size() != crypto_sign_SECRETKEYBYTES)
throw Error("secret key is not valid"); throw Error("secret key is not valid");
@ -66,7 +74,7 @@ SecretKey SecretKey::generate(std::string_view name)
} }
PublicKey::PublicKey(std::string_view s) PublicKey::PublicKey(std::string_view s)
: Key(s) : Key{s, false}
{ {
if (key.size() != crypto_sign_PUBLICKEYBYTES) if (key.size() != crypto_sign_PUBLICKEYBYTES)
throw Error("public key is not valid"); throw Error("public key is not valid");
@ -83,7 +91,12 @@ bool PublicKey::verifyDetached(std::string_view data, std::string_view sig) cons
bool PublicKey::verifyDetachedAnon(std::string_view data, std::string_view sig) const bool PublicKey::verifyDetachedAnon(std::string_view data, std::string_view sig) const
{ {
auto sig2 = base64Decode(sig); std::string sig2;
try {
sig2 = base64Decode(sig);
} catch (Error & e) {
e.addTrace({}, "while decoding signature '%s'", sig);
}
if (sig2.size() != crypto_sign_BYTES) if (sig2.size() != crypto_sign_BYTES)
throw Error("signature is not valid"); throw Error("signature is not valid");

View file

@ -31,15 +31,19 @@ struct Key
std::string name; std::string name;
std::string key; std::string key;
/**
* Construct Key from a string in the format
* <name>:<key-in-base64>.
*/
Key(std::string_view s);
std::string to_string() const; std::string to_string() const;
protected: protected:
/**
* Construct Key from a string in the format
* <name>:<key-in-base64>.
*
* @param sensitiveValue Avoid displaying the raw Base64 in error
* messages to avoid leaking private keys.
*/
Key(std::string_view s, bool sensitiveValue);
Key(std::string_view name, std::string && key) Key(std::string_view name, std::string && key)
: name(name), key(std::move(key)) { } : name(name), key(std::move(key)) { }
}; };

View file

@ -244,7 +244,7 @@ std::string base64Decode(std::string_view s)
char digit = base64DecodeChars[(unsigned char) c]; char digit = base64DecodeChars[(unsigned char) c];
if (digit == npos) if (digit == npos)
throw Error("invalid character in Base64 string: '%c'", c); throw FormatError("invalid character in Base64 string: '%c'", c);
bits += 6; bits += 6;
d = d << 6 | digit; d = d << 6 | digit;

View file

@ -172,9 +172,13 @@ constexpr char treeNull[] = " ";
/** /**
* Base64 encoding/decoding. * Encode arbitrary bytes as Base64.
*/ */
std::string base64Encode(std::string_view s); std::string base64Encode(std::string_view s);
/**
* Decode arbitrary bytes to Base64.
*/
std::string base64Decode(std::string_view s); std::string base64Decode(std::string_view s);

View file

@ -8,7 +8,7 @@
#include "tests/nix_api_expr.hh" #include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh" #include "tests/string_callback.hh"
#include "gmock/gmock.h" #include <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
namespace nixC { namespace nixC {