Skip to content

Commit 3d664d0

Browse files
committed
MB-69825: Add --dump-encryption-header to cbcat
Allows for easy inspection of the headers: File: couchbase-2025-12-16T14-40-29-audit.cef Encrypted: Key ID: d2e74da9-7430-429d-92ca-cc02cf00267b Key Derivation: NoDerivation Compression: ZLIB Change-Id: Ic14f59fc7b7b64bd5f662e01b51ef26208fa7325 Reviewed-on: https://review.couchbase.org/c/platform/+/237765 Tested-by: Build Bot <build@couchbase.com> Reviewed-by: Jim Walker <jim@couchbase.com>
1 parent c1b7a35 commit 3d664d0

5 files changed

Lines changed: 106 additions & 9 deletions

File tree

cbcrypto/cbcat.cc

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ static constexpr int EXIT_INCORRECT_PASSWORD = 2;
3131
static std::string password;
3232
static std::unique_ptr<DumpKeysRunner> dump_keys_runner;
3333
static KeyStore keyStore;
34+
bool printHeader = false;
35+
bool dumpEncryptionHeader = false;
3436

3537
/// The key lookup callback gets called from the FileReader whenever it
3638
/// encounters an encrypted file. It'll keep the keys around in a key store
@@ -125,14 +127,44 @@ void readKeyStoreFromStdin() {
125127
}
126128
}
127129

130+
void print_header(std::string_view filename,
131+
std::optional<EncryptedFileHeader> header) {
132+
if (!printHeader) {
133+
return;
134+
}
135+
std::cout << "File: " << filename << std::endl;
136+
if (header) {
137+
try {
138+
fmt::println(" Encrypted:");
139+
fmt::println(" Key ID: {}", header->get_id());
140+
fmt::println(" Key Derivation: {}",
141+
header->get_key_derivation());
142+
if (header->get_key_derivation() ==
143+
KeyDerivationMethod::PasswordBased) {
144+
fmt::println(" PBKDF2 Iterations: {}",
145+
header->get_pbkdf_iterations());
146+
}
147+
fmt::println(" Compression: {}", header->get_compression());
148+
} catch (const std::exception&) {
149+
// Ignore errors
150+
}
151+
} else {
152+
if (dumpEncryptionHeader) {
153+
std::cout << " Not an encrypted file or unable to read "
154+
"encryption header"
155+
<< std::endl;
156+
}
157+
}
158+
std::cout << std::string(70, '=') << std::endl;
159+
}
160+
128161
int main(int argc, char** argv) {
129162
using cb::getopt::Argument;
130163
cb::getopt::CommandLineOptionsParser parser;
131164

132165
std::string dumpKeysExecutable = INSTALL_ROOT "/bin/dump-keys";
133166
std::string gosecrets =
134167
INSTALL_ROOT "/var/lib/couchbase/config/gosecrets.cfg";
135-
bool printHeader = false;
136168
bool withKeyStore = false;
137169
bool stdinUsed = false;
138170

@@ -191,10 +223,18 @@ int main(int argc, char** argv) {
191223
"The JSON containing the keystore to use (use '-' to read from "
192224
"standard input)"});
193225

194-
parser.addOption({[&printHeader](auto value) { printHeader = true; },
226+
parser.addOption({[](auto value) { printHeader = true; },
195227
"print-header",
196228
"Print a header with the file name before the content of "
197229
"the file"});
230+
parser.addOption(
231+
{[](auto value) {
232+
dumpEncryptionHeader = true;
233+
printHeader = true;
234+
},
235+
"dump-encryption-header",
236+
"Print the information from the encryption header if the file is "
237+
"encrypted"});
198238
parser.addOption({[](auto) {
199239
std::cout << "Couchbase Server " << PRODUCT_VERSION
200240
<< std::endl;
@@ -215,13 +255,21 @@ int main(int argc, char** argv) {
215255
}
216256

217257
for (const auto& file : arguments) {
218-
if (printHeader) {
219-
std::cout << std::endl
220-
<< file << std::endl
221-
<< std::string(file.length(), '=') << std::endl;
222-
}
258+
bool header_printed = false;
259+
auto do_print_header =
260+
[&header_printed](std::string_view filename,
261+
std::optional<EncryptedFileHeader> header) {
262+
if (!header_printed) {
263+
print_header(filename, header);
264+
header_printed = true;
265+
}
266+
};
223267
try {
224268
auto reader = FileReader::create(file, key_lookup_callback);
269+
do_print_header(file,
270+
dumpEncryptionHeader
271+
? reader->get_encryption_header()
272+
: std::nullopt);
225273
reader->set_max_allowed_chunk_size(
226274
std::numeric_limits<uint32_t>::max());
227275
std::vector<uint8_t> blob(8192);
@@ -231,10 +279,12 @@ int main(int argc, char** argv) {
231279
std::cout.flush();
232280
}
233281
} catch (const cb::crypto::dump_keys::IncorrectPasswordError& e) {
282+
do_print_header(file, {});
234283
std::cerr << e.what() << std::endl;
235284
std::exit(EXIT_INCORRECT_PASSWORD);
236285

237286
} catch (const std::exception& e) {
287+
do_print_header(file, {});
238288
std::cerr << "Fatal error: " << e.what() << std::endl;
239289
return EXIT_FAILURE;
240290
}

cbcrypto/common.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@
2121

2222
namespace cb::crypto {
2323

24+
std::string format_as(const KeyDerivationMethod& keyDerivationMethod) {
25+
using namespace std::string_literals;
26+
switch (keyDerivationMethod) {
27+
case KeyDerivationMethod::NoDerivation:
28+
return "NoDerivation"s;
29+
case KeyDerivationMethod::KeyBased:
30+
return "KeyBased"s;
31+
case KeyDerivationMethod::PasswordBased:
32+
return "PasswordBased"s;
33+
}
34+
throw std::logic_error(fmt::format("Unknown key derivation method: {}",
35+
static_cast<int>(keyDerivationMethod)));
36+
}
37+
2438
[[nodiscard]] std::string format_as(const Cipher& cipher) {
2539
switch (cipher) {
2640
case Cipher::None:

cbcrypto/file_reader.cc

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ class FileStreamReader : public FileReader {
8383
return false;
8484
}
8585

86+
[[nodiscard]] std::optional<EncryptedFileHeader> get_encryption_header()
87+
override {
88+
return std::nullopt;
89+
}
90+
8691
std::string nextChunk() override {
8792
if (eof()) {
8893
return {};
@@ -158,6 +163,11 @@ class GZipFileReader : public FileReader {
158163
return false;
159164
}
160165

166+
[[nodiscard]] std::optional<EncryptedFileHeader> get_encryption_header()
167+
override {
168+
return std::nullopt;
169+
}
170+
161171
std::string nextChunk() override {
162172
if (eof()) {
163173
return {};
@@ -199,9 +209,10 @@ class GZipFileReader : public FileReader {
199209
class EncryptedFileReader : public FileReader {
200210
public:
201211
EncryptedFileReader(const KeyDerivationKey& kdk,
202-
const EncryptedFileHeader& header,
212+
const EncryptedFileHeader& header_,
203213
std::unique_ptr<FileStreamReader> underlying)
204-
: associated_data(header),
214+
: header(header_),
215+
associated_data(header),
205216
offset(sizeof(EncryptedFileHeader)),
206217
cipher(SymmetricCipher::create(kdk.cipher, header.derive_key(kdk))),
207218
file(std::move(underlying)) {
@@ -223,6 +234,11 @@ class EncryptedFileReader : public FileReader {
223234
return true;
224235
}
225236

237+
[[nodiscard]] std::optional<EncryptedFileHeader> get_encryption_header()
238+
override {
239+
return header;
240+
}
241+
226242
void set_max_allowed_chunk_size(std::size_t limit) override {
227243
max_allowed_chunk_size = limit;
228244
}
@@ -297,6 +313,7 @@ class EncryptedFileReader : public FileReader {
297313
current_chunk->append(decrypted.size());
298314
}
299315

316+
EncryptedFileHeader header;
300317
EncryptedFileAssociatedData associated_data;
301318
std::size_t offset;
302319
std::unique_ptr<SymmetricCipher> cipher;
@@ -314,6 +331,11 @@ class SnappyInflateReader : public FileReader {
314331
return underlying->is_encrypted();
315332
}
316333

334+
[[nodiscard]] std::optional<EncryptedFileHeader> get_encryption_header()
335+
override {
336+
return underlying->get_encryption_header();
337+
}
338+
317339
void set_max_allowed_chunk_size(std::size_t limit) override {
318340
underlying->set_max_allowed_chunk_size(limit);
319341
}
@@ -384,6 +406,11 @@ class ZlibInflateReader : public FileReader {
384406
return underlying->is_encrypted();
385407
}
386408

409+
[[nodiscard]] std::optional<EncryptedFileHeader> get_encryption_header()
410+
override {
411+
return underlying->get_encryption_header();
412+
}
413+
387414
void set_max_allowed_chunk_size(std::size_t limit) override {
388415
underlying->set_max_allowed_chunk_size(limit);
389416
}

include/cbcrypto/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum class KeyDerivationMethod : uint8_t {
3535
KeyBased = 1,
3636
PasswordBased = 2
3737
};
38+
std::string format_as(const KeyDerivationMethod& keyDerivationMethod);
3839

3940
/// A structure to hold the information needed by a single key derivation key
4041
struct KeyDerivationKey {

include/cbcrypto/file_reader.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#pragma once
1212

1313
#include <cbcrypto/common.h>
14+
#include <cbcrypto/encrypted_file_header.h>
1415
#include <filesystem>
1516
#include <functional>
1617
#include <span>
@@ -48,6 +49,10 @@ class FileReader {
4849
/// Is the file being read encrypted or not
4950
[[nodiscard]] virtual bool is_encrypted() const = 0;
5051

52+
/// Get the encryption header if the file is encrypted
53+
[[nodiscard]] virtual std::optional<EncryptedFileHeader>
54+
get_encryption_header() = 0;
55+
5156
/**
5257
* The file reader tries to read various length fields off the encrypted
5358
* file and allocate buffers of that size. To avoid trying to allocate

0 commit comments

Comments
 (0)