2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file signature.cpp Implementation of signature validation routines. */
12 #include "signature.h"
15 #include "fileio_func.h"
16 #include "string_func.h"
18 #include "3rdparty/monocypher/monocypher.h"
19 #include "3rdparty/monocypher/monocypher-ed25519.h"
20 #include "3rdparty/nlohmann/json.hpp"
22 #include "safeguards.h"
24 /** The public keys used for signature validation. */
25 static const std::initializer_list
<std::array
<uint8_t, 32>> _public_keys_v1
= {
26 /* 2024-01-20 - Public key for Social Integration Plugins. */
27 { 0xed, 0x5d, 0x57, 0x47, 0x21, 0x99, 0x8b, 0x02, 0xdf, 0x6e, 0x3d, 0x69, 0xe1, 0x87, 0xca, 0xd0, 0x0e, 0x88, 0xc3, 0xe2, 0xb2, 0xa6, 0x7b, 0xc0, 0x42, 0xc8, 0xd6, 0x4b, 0x65, 0xe6, 0x48, 0xf7 },
31 * Calculate the 32-byte blake2b hash of a file.
33 * @param filename The filename to calculate the hash of.
34 * @return The 32-byte blake2b hash of the file, hex-encoded.
36 static std::string
CalculateHashV1(const std::string
&filename
)
38 auto f
= FioFOpenFile(filename
, "rb", NO_DIRECTORY
);
39 if (!f
.has_value()) return {};
41 std::array
<uint8_t, 32> digest
;
42 crypto_blake2b_ctx ctx
;
43 crypto_blake2b_init(&ctx
, digest
.size());
46 std::array
<uint8_t, 1024> buf
;
47 size_t len
= fread(buf
.data(), 1, buf
.size(), *f
);
49 crypto_blake2b_update(&ctx
, buf
.data(), len
);
52 crypto_blake2b_final(&ctx
, digest
.data());
53 return FormatArrayAsHex(digest
);
57 * Validate whether the checksum of a file is the same.
59 * @param filename The filename to validate the checksum of.
60 * @param checksum The expected checksum.
61 * @return True iff the checksum of the file is the same as the expected checksum.
63 static bool ValidateChecksum(const std::string
&filename
, const std::string
&checksum
)
65 /* Checksums are "<version>$<hash>". Split out the version. */
66 auto pos
= checksum
.find('$');
67 assert(pos
!= std::string::npos
); // Already validated by ValidateSchema().
68 const std::string version
= checksum
.substr(0, pos
);
69 const std::string hash
= checksum
.substr(pos
+ 1);
71 /* Calculate the checksum over the file. */
72 std::string calculated_hash
;
74 calculated_hash
= CalculateHashV1(filename
);
76 Debug(misc
, 0, "Failed to validate signature: unknown checksum version: {}", filename
);
80 /* Validate the checksum is the same. */
81 if (calculated_hash
.empty()) {
82 Debug(misc
, 0, "Failed to validate signature: couldn't calculate checksum for: {}", filename
);
85 if (calculated_hash
!= hash
) {
86 Debug(misc
, 0, "Failed to validate signature: checksum mismatch for: {}", filename
);
94 * Validate whether the signature is valid for this set of files.
96 * @param signature The signature to validate.
97 * @param files The files to validate the signature against.
98 * @param filename The filename of the signatures file (for error-reporting).
99 * @return True iff the signature is valid for this set of files.
101 static bool ValidateSignature(const std::string
&signature
, const nlohmann::json
&files
, const std::string
&filename
)
103 /* Signatures are "<version>$<signature>". Split out the version. */
104 auto pos
= signature
.find('$');
105 assert(pos
!= std::string::npos
); // Already validated by ValidateSchema().
106 const std::string version
= signature
.substr(0, pos
);
107 const std::string sig_value
= signature
.substr(pos
+ 1);
109 /* Create the message we are going to validate. */
110 std::string message
= files
.dump(-1);
112 /* Validate the signature. */
113 if (version
== "1") {
114 std::array
<uint8_t, 64> sig
;
115 if (sig_value
.size() != 128 || !ConvertHexToBytes(sig_value
, sig
)) {
116 Debug(misc
, 0, "Failed to validate signature: invalid signature: {}", filename
);
120 for (auto &pk_value
: _public_keys_v1
) {
121 /* Check if the message is valid with this public key. */
122 auto res
= crypto_ed25519_check(sig
.data(), pk_value
.data(), reinterpret_cast<uint8_t *>(message
.data()), message
.size());
128 Debug(misc
, 0, "Failed to validate signature: signature validation failed: {}", filename
);
131 Debug(misc
, 0, "Failed to validate signature: unknown signature version: {}", filename
);
139 * Validate the signatures file complies with the JSON schema.
141 * @param signatures The signatures JSON to validate.
142 * @param filename The filename of the signatures file (for error-reporting).
143 * @return True iff the signatures file complies with the JSON schema.
145 static bool ValidateSchema(const nlohmann::json
&signatures
, const std::string
&filename
)
147 if (signatures
["files"].is_null()) {
148 Debug(misc
, 0, "Failed to validate signature: no files found: {}", filename
);
152 if (signatures
["signature"].is_null()) {
153 Debug(misc
, 0, "Failed to validate signature: no signature found: {}", filename
);
157 for (auto &signature
: signatures
["files"]) {
158 if (signature
["filename"].is_null() || signature
["checksum"].is_null()) {
159 Debug(misc
, 0, "Failed to validate signature: invalid entry in files: {}", filename
);
163 const std::string sig_filename
= signature
["filename"];
164 const std::string sig_checksum
= signature
["checksum"];
166 if (sig_filename
.empty() || sig_checksum
.empty()) {
167 Debug(misc
, 0, "Failed to validate signature: invalid entry in files: {}", filename
);
171 auto pos
= sig_checksum
.find('$');
172 if (pos
== std::string::npos
) {
173 Debug(misc
, 0, "Failed to validate signature: invalid checksum format: {}", filename
);
178 const std::string signature
= signatures
["signature"];
179 auto pos
= signature
.find('$');
180 if (pos
== std::string::npos
) {
181 Debug(misc
, 0, "Failed to validate signature: invalid signature format: {}", filename
);
189 * Validate that the signatures mentioned in the signature file are matching
190 * the files in question.
192 * @return True iff the files in the signature file passed validation.
194 static bool _ValidateSignatureFile(const std::string
&filename
)
197 auto f
= FioFOpenFile(filename
, "rb", NO_DIRECTORY
, &filesize
);
198 if (!f
.has_value()) {
199 Debug(misc
, 0, "Failed to validate signature: file not found: {}", filename
);
203 std::string
text(filesize
, '\0');
204 size_t len
= fread(text
.data(), filesize
, 1, *f
);
206 Debug(misc
, 0, "Failed to validate signature: failed to read file: {}", filename
);
210 nlohmann::json signatures
;
212 signatures
= nlohmann::json::parse(text
);
213 } catch (nlohmann::json::exception
&) {
214 Debug(misc
, 0, "Failed to validate signature: not a valid JSON file: {}", filename
);
219 * The JSON file should look like:
224 * "checksum": "version$hash"
225 * "filename": "filename",
229 * "signature": "version$signature"
232 * The signature is a signed message of the content of "files", dumped as
233 * JSON without spaces / newlines, keys in the order as indicated above.
236 if (!ValidateSchema(signatures
, filename
)) {
240 if (!ValidateSignature(signatures
["signature"], signatures
["files"], filename
)) {
244 std::string dirname
= FS2OTTD(std::filesystem::path(OTTD2FS(filename
)).parent_path());
246 for (auto &signature
: signatures
["files"]) {
247 const std::string sig_filename
= dirname
+ PATHSEPCHAR
+ signature
["filename"].get
<std::string
>();
248 const std::string sig_checksum
= signature
["checksum"];
250 if (!ValidateChecksum(sig_filename
, sig_checksum
)) {
259 * Validate that the signatures mentioned in the signature file are matching
260 * the files in question.
262 * @note if ALLOW_INVALID_SIGNATURE is defined, this function will always
263 * return true (but will still report any errors in the console).
265 * @return True iff the files in the signature file passed validation.
267 bool ValidateSignatureFile(const std::string
&filename
)
269 auto res
= _ValidateSignatureFile(filename
);;
270 #if defined(ALLOW_INVALID_SIGNATURE)
271 (void)res
; // Ignore the result.