Skip to content

Commit 322d2c7

Browse files
authored
Merge pull request #11523 from obsidiansystems/base64Decode-no-leak-private-key-on-error
Ensure error messages don't leak private key
2 parents 68ba6ff + 2b6b03d commit 322d2c7

File tree

10 files changed

+68
-23
lines changed

10 files changed

+68
-23
lines changed

src/libfetchers/git-utils.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,13 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
583583
std::string re = R"(Good "git" signature for \* with .* key SHA256:[)";
584584
for (const fetchers::PublicKey & k : publicKeys){
585585
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
586-
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, base64Decode(k.key)).to_string(nix::HashFormat::Base64, false), "=");
586+
std::string keyDecoded;
587+
try {
588+
keyDecoded = base64Decode(k.key);
589+
} catch (Error & e) {
590+
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
591+
}
592+
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, keyDecoded).to_string(nix::HashFormat::Base64, false), "=");
587593
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+" );
588594
re += "(" + escaped_fingerprint + ")";
589595
}

src/libstore/machines.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ static Machine parseBuilderLine(const std::set<std::string> & defaultSystems, co
159159
const auto & str = tokens[fieldIndex];
160160
try {
161161
base64Decode(str);
162-
} catch (const Error & e) {
163-
throw FormatError("bad machine specification: a column #%lu in a row: '%s' is not valid base64 string: %s", fieldIndex, line, e.what());
162+
} catch (FormatError & e) {
163+
e.addTrace({}, "while parsing machine specification at a column #%lu in a row: '%s'", fieldIndex, line);
164+
throw;
164165
}
165166
return str;
166167
};

src/libstore/ssh.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77

88
namespace nix {
99

10+
static std::string parsePublicHostKey(std::string_view host, std::string_view sshPublicHostKey)
11+
{
12+
try {
13+
return base64Decode(sshPublicHostKey);
14+
} catch (Error & e) {
15+
e.addTrace({}, "while decoding ssh public host key for host '%s'", host);
16+
throw;
17+
}
18+
}
19+
1020
SSHMaster::SSHMaster(
1121
std::string_view host,
1222
std::string_view keyFile,
@@ -15,7 +25,7 @@ SSHMaster::SSHMaster(
1525
: host(host)
1626
, fakeSSH(host == "localhost")
1727
, keyFile(keyFile)
18-
, sshPublicHostKey(sshPublicHostKey)
28+
, sshPublicHostKey(parsePublicHostKey(host, sshPublicHostKey))
1929
, useMaster(useMaster && !fakeSSH)
2030
, compress(compress)
2131
, logFD(logFD)
@@ -39,7 +49,7 @@ void SSHMaster::addCommonSSHOpts(Strings & args)
3949
std::filesystem::path fileName = state->tmpDir->path() / "host-key";
4050
auto p = host.rfind("@");
4151
std::string thost = p != std::string::npos ? std::string(host, p + 1) : host;
42-
writeFile(fileName.string(), thost + " " + base64Decode(sshPublicHostKey) + "\n");
52+
writeFile(fileName.string(), thost + " " + sshPublicHostKey + "\n");
4353
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
4454
}
4555
if (compress)

src/libstore/ssh.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ private:
1414
const std::string host;
1515
bool fakeSSH;
1616
const std::string keyFile;
17+
/**
18+
* Raw bytes, not Base64 encoding.
19+
*/
1720
const std::string sshPublicHostKey;
1821
const bool useMaster;
1922
const bool compress;

src/libutil/hash.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,12 @@ Hash::Hash(std::string_view rest, HashAlgorithm algo, bool isSRI)
245245
}
246246

247247
else if (isSRI || rest.size() == base64Len()) {
248-
auto d = base64Decode(rest);
248+
std::string d;
249+
try {
250+
d = base64Decode(rest);
251+
} catch (Error & e) {
252+
e.addTrace({}, "While decoding hash '%s'", rest);
253+
}
249254
if (d.size() != hashSize)
250255
throw BadHash("invalid %s hash '%s'", isSRI ? "SRI" : "base-64", rest);
251256
assert(hashSize);

src/libutil/signature/local-keys.cc

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@ BorrowedCryptoValue BorrowedCryptoValue::parse(std::string_view s)
1414
return {s.substr(0, colon), s.substr(colon + 1)};
1515
}
1616

17-
Key::Key(std::string_view s)
17+
Key::Key(std::string_view s, bool sensitiveValue)
1818
{
1919
auto ss = BorrowedCryptoValue::parse(s);
2020

2121
name = ss.name;
2222
key = ss.payload;
2323

24-
if (name == "" || key == "")
25-
throw Error("key is corrupt");
26-
27-
key = base64Decode(key);
24+
try {
25+
if (name == "" || key == "")
26+
throw FormatError("key is corrupt");
27+
28+
key = base64Decode(key);
29+
} catch (Error & e) {
30+
std::string extra;
31+
if (!sensitiveValue)
32+
extra = fmt(" with raw value '%s'", key);
33+
e.addTrace({}, "while decoding key named '%s'%s", name, extra);
34+
throw;
35+
}
2836
}
2937

3038
std::string Key::to_string() const
@@ -33,7 +41,7 @@ std::string Key::to_string() const
3341
}
3442

3543
SecretKey::SecretKey(std::string_view s)
36-
: Key(s)
44+
: Key{s, true}
3745
{
3846
if (key.size() != crypto_sign_SECRETKEYBYTES)
3947
throw Error("secret key is not valid");
@@ -66,7 +74,7 @@ SecretKey SecretKey::generate(std::string_view name)
6674
}
6775

6876
PublicKey::PublicKey(std::string_view s)
69-
: Key(s)
77+
: Key{s, false}
7078
{
7179
if (key.size() != crypto_sign_PUBLICKEYBYTES)
7280
throw Error("public key is not valid");
@@ -83,7 +91,12 @@ bool PublicKey::verifyDetached(std::string_view data, std::string_view sig) cons
8391

8492
bool PublicKey::verifyDetachedAnon(std::string_view data, std::string_view sig) const
8593
{
86-
auto sig2 = base64Decode(sig);
94+
std::string sig2;
95+
try {
96+
sig2 = base64Decode(sig);
97+
} catch (Error & e) {
98+
e.addTrace({}, "while decoding signature '%s'", sig);
99+
}
87100
if (sig2.size() != crypto_sign_BYTES)
88101
throw Error("signature is not valid");
89102

src/libutil/signature/local-keys.hh

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@ struct Key
3131
std::string name;
3232
std::string key;
3333

34+
std::string to_string() const;
35+
36+
protected:
37+
3438
/**
3539
* Construct Key from a string in the format
3640
* ‘<name>:<key-in-base64>’.
41+
*
42+
* @param sensitiveValue Avoid displaying the raw Base64 in error
43+
* messages to avoid leaking private keys.
3744
*/
38-
Key(std::string_view s);
39-
40-
std::string to_string() const;
45+
Key(std::string_view s, bool sensitiveValue);
4146

42-
protected:
4347
Key(std::string_view name, std::string && key)
4448
: name(name), key(std::move(key)) { }
4549
};

src/libutil/util.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,8 @@ std::string base64Decode(std::string_view s)
243243
if (c == '\n') continue;
244244

245245
char digit = base64DecodeChars[(unsigned char) c];
246-
if (digit == npos) {
247-
throw Error("invalid character in Base64 string: '%c' in '%s'", c, s.data());
248-
}
246+
if (digit == npos)
247+
throw FormatError("invalid character in Base64 string: '%c'", c);
249248

250249
bits += 6;
251250
d = d << 6 | digit;

src/libutil/util.hh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,13 @@ constexpr char treeNull[] = " ";
172172

173173

174174
/**
175-
* Base64 encoding/decoding.
175+
* Encode arbitrary bytes as Base64.
176176
*/
177177
std::string base64Encode(std::string_view s);
178+
179+
/**
180+
* Decode arbitrary bytes to Base64.
181+
*/
178182
std::string base64Decode(std::string_view s);
179183

180184

tests/unit/libexpr/nix_api_expr.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "tests/nix_api_expr.hh"
99
#include "tests/string_callback.hh"
1010

11-
#include "gmock/gmock.h"
11+
#include <gmock/gmock.h>
1212
#include <gtest/gtest.h>
1313

1414
namespace nixC {

0 commit comments

Comments
 (0)