Arachne 1.0
Arachne - the perpetual stitcher of Wikidata entities.
Loading...
Searching...
No Matches
crypto.cpp
Go to the documentation of this file.
1#include "arachne/crypto.hpp"
2
3#include <algorithm>
4#include <bit>
5#include <cctype>
6#include <cerrno>
7#include <fstream>
8#include <limits>
9#include <stdexcept>
10#include <system_error>
11
12namespace arachne::crypto {
13namespace {
14
15 constexpr std::array<std::uint32_t, 64> round_constants {
16 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU,
17 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U, 0xd807aa98U, 0x12835b01U,
18 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U,
19 0xc19bf174U, 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU,
20 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, 0x983e5152U,
21 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, 0xc6e00bf3U, 0xd5a79147U,
22 0x06ca6351U, 0x14292967U, 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU,
23 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U,
24 0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U,
25 0xd6990624U, 0xf40e3585U, 0x106aa070U, 0x19a4c116U, 0x1e376c08U,
26 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU,
27 0x682e6ff3U, 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U,
28 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U,
29 };
30
31 constexpr char hexadecimal[] = "0123456789abcdef";
32
33 [[nodiscard]] std::uint32_t load_be32(const std::byte* bytes) noexcept {
34 return static_cast<std::uint32_t>(std::to_integer<unsigned>(bytes[0]))
35 << 24U
36 | static_cast<std::uint32_t>(std::to_integer<unsigned>(bytes[1]))
37 << 16U
38 | static_cast<std::uint32_t>(std::to_integer<unsigned>(bytes[2]))
39 << 8U
40 | static_cast<std::uint32_t>(std::to_integer<unsigned>(bytes[3]));
41 }
42
43 void store_be32(std::byte* output, const std::uint32_t value) noexcept {
44 output[0] = static_cast<std::byte>(value >> 24U);
45 output[1] = static_cast<std::byte>(value >> 16U);
46 output[2] = static_cast<std::byte>(value >> 8U);
47 output[3] = static_cast<std::byte>(value);
48 }
49
50 [[nodiscard]] std::string to_hex(const sha256_digest& digest) {
51 std::string result;
52 result.resize(digest.size() * 2U);
53 std::size_t index = 0;
54 for (const std::byte value : digest) {
55 const auto byte = std::to_integer<unsigned>(value);
56 result[index++] = hexadecimal[byte >> 4U];
57 result[index++] = hexadecimal[byte & 0x0fU];
58 }
59 return result;
60 }
61
62}
63
64sha256_hasher::sha256_hasher() noexcept { reset(); }
65
66void sha256_hasher::reset() noexcept {
67 state_ = { 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU,
68 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U };
69 buffer_.fill(std::byte { 0 });
70 digest_.fill(std::byte { 0 });
71 bit_count_ = 0;
72 buffered_ = 0;
73 finished_ = false;
74}
75
76void sha256_hasher::update(const std::span<const std::byte> bytes) {
77 if (finished_) {
78 throw std::logic_error("cannot update a finalized SHA-256 digest");
79 }
80 constexpr std::uint64_t max_bytes
81 = std::numeric_limits<std::uint64_t>::max() / 8U;
82 if (bytes.size() > max_bytes
83 || static_cast<std::uint64_t>(bytes.size())
84 > max_bytes - bit_count_ / 8U) {
85 throw std::length_error("SHA-256 input exceeds its length encoding");
86 }
87 bit_count_ += static_cast<std::uint64_t>(bytes.size()) * 8U;
88
89 std::size_t offset = 0;
90 if (buffered_ != 0) {
91 const std::size_t copied
92 = std::min(buffer_.size() - buffered_, bytes.size());
93 std::copy_n(bytes.data(), copied, buffer_.data() + buffered_);
94 buffered_ += copied;
95 offset += copied;
96 if (buffered_ == buffer_.size()) {
97 transform(buffer_.data());
98 buffered_ = 0;
99 }
100 }
101
102 while (bytes.size() - offset >= buffer_.size()) {
103 transform(bytes.data() + offset);
104 offset += buffer_.size();
105 }
106
107 if (offset != bytes.size()) {
108 buffered_ = bytes.size() - offset;
109 std::copy_n(bytes.data() + offset, buffered_, buffer_.data());
110 }
111}
112
113void sha256_hasher::update(const std::string_view bytes) {
114 update(std::as_bytes(std::span { bytes.data(), bytes.size() }));
115}
116
117sha256_digest sha256_hasher::finish() {
118 if (finished_) {
119 return digest_;
120 }
121
122 const std::uint64_t original_bit_count = bit_count_;
123 buffer_[buffered_++] = std::byte { 0x80 };
124 if (buffered_ > 56U) {
125 std::fill(
126 buffer_.begin() + static_cast<std::ptrdiff_t>(buffered_),
127 buffer_.end(), std::byte { 0 }
128 );
129 transform(buffer_.data());
130 buffered_ = 0;
131 }
132 std::fill(
133 buffer_.begin() + static_cast<std::ptrdiff_t>(buffered_),
134 buffer_.begin() + 56, std::byte { 0 }
135 );
136 for (std::size_t index = 0; index < 8U; ++index) {
137 const unsigned shift = static_cast<unsigned>((7U - index) * 8U);
138 buffer_[56U + index]
139 = static_cast<std::byte>(original_bit_count >> shift);
140 }
141 transform(buffer_.data());
142
143 for (std::size_t index = 0; index < state_.size(); ++index) {
144 store_be32(digest_.data() + index * 4U, state_[index]);
145 }
146 finished_ = true;
147 buffered_ = 0;
148 return digest_;
149}
150
151std::string sha256_hasher::finish_hex() { return to_hex(finish()); }
152
153void sha256_hasher::transform(const std::byte* const block) noexcept {
154 std::array<std::uint32_t, 64> schedule {};
155 for (std::size_t index = 0; index < 16U; ++index) {
156 schedule[index] = load_be32(block + index * 4U);
157 }
158 for (std::size_t index = 16; index < schedule.size(); ++index) {
159 const std::uint32_t s0 = std::rotr(schedule[index - 15U], 7)
160 ^ std::rotr(schedule[index - 15U], 18)
161 ^ (schedule[index - 15U] >> 3U);
162 const std::uint32_t s1 = std::rotr(schedule[index - 2U], 17)
163 ^ std::rotr(schedule[index - 2U], 19)
164 ^ (schedule[index - 2U] >> 10U);
165 schedule[index]
166 = schedule[index - 16U] + s0 + schedule[index - 7U] + s1;
167 }
168
169 std::uint32_t a = state_[0];
170 std::uint32_t b = state_[1];
171 std::uint32_t c = state_[2];
172 std::uint32_t d = state_[3];
173 std::uint32_t e = state_[4];
174 std::uint32_t f = state_[5];
175 std::uint32_t g = state_[6];
176 std::uint32_t h = state_[7];
177
178 for (std::size_t index = 0; index < schedule.size(); ++index) {
179 const std::uint32_t sum1
180 = std::rotr(e, 6) ^ std::rotr(e, 11) ^ std::rotr(e, 25);
181 const std::uint32_t choose = (e & f) ^ (~e & g);
182 const std::uint32_t temporary1
183 = h + sum1 + choose + round_constants[index] + schedule[index];
184 const std::uint32_t sum0
185 = std::rotr(a, 2) ^ std::rotr(a, 13) ^ std::rotr(a, 22);
186 const std::uint32_t majority = (a & b) ^ (a & c) ^ (b & c);
187 const std::uint32_t temporary2 = sum0 + majority;
188
189 h = g;
190 g = f;
191 f = e;
192 e = d + temporary1;
193 d = c;
194 c = b;
195 b = a;
196 a = temporary1 + temporary2;
197 }
198
199 state_[0] += a;
200 state_[1] += b;
201 state_[2] += c;
202 state_[3] += d;
203 state_[4] += e;
204 state_[5] += f;
205 state_[6] += g;
206 state_[7] += h;
207}
208
209std::string sha256(const std::span<const std::byte> bytes) {
210 sha256_hasher hasher;
211 hasher.update(bytes);
212 return hasher.finish_hex();
213}
214
215std::string sha256(const std::string_view bytes) {
216 sha256_hasher hasher;
217 hasher.update(bytes);
218 return hasher.finish_hex();
219}
220
221std::string sha256_file(const std::filesystem::path& path) {
222 std::ifstream input(path, std::ios::binary);
223 if (!input.is_open()) {
224 throw std::system_error(
225 errno == 0 ? EIO : errno, std::generic_category(),
226 "cannot open file for SHA-256"
227 );
228 }
229
230 sha256_hasher hasher;
231 std::array<char, 64U * 1024U> buffer {};
232 while (input) {
233 input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
234 const std::streamsize count = input.gcount();
235 if (count > 0) {
236 hasher.update(
237 std::as_bytes(
238 std::span { buffer.data(), static_cast<std::size_t>(count) }
239 )
240 );
241 }
242 }
243 if (!input.eof()) {
244 throw std::runtime_error("I/O error while calculating file SHA-256");
245 }
246 return hasher.finish_hex();
247}
248
250 const std::string_view artifact_ref
251) noexcept {
252 if (artifact_ref.empty() || artifact_ref.size() > 4096U
253 || artifact_ref.front() == '/' || artifact_ref.front() == '\\'
254 || artifact_ref.back() == '/' || artifact_ref.back() == '\\'
255 || artifact_ref.find('\0') != std::string_view::npos
256 || artifact_ref.find('\\') != std::string_view::npos) {
257 return false;
258 }
259
260 std::size_t start = 0;
261 while (start < artifact_ref.size()) {
262 const std::size_t slash = artifact_ref.find('/', start);
263 const std::size_t end
264 = slash == std::string_view::npos ? artifact_ref.size() : slash;
265 const std::string_view component
266 = artifact_ref.substr(start, end - start);
267 if (component.empty() || component == "." || component == ".."
268 || component.find(':') != std::string_view::npos
269 || component.back() == ' ' || component.back() == '.') {
270 return false;
271 }
272 if (std::ranges::any_of(component, [](const char character) {
273 const auto value = static_cast<unsigned char>(character);
274 return value < 0x20U || value == 0x7fU;
275 })) {
276 return false;
277 }
278 std::string device_name(component.substr(0, component.find('.')));
279 std::ranges::transform(
280 device_name, device_name.begin(), [](const char character) {
281 return static_cast<char>(
282 std::toupper(static_cast<unsigned char>(character))
283 );
284 }
285 );
286 const bool numbered_device = device_name.size() == 4U
287 && (device_name.starts_with("COM")
288 || device_name.starts_with("LPT"))
289 && device_name.back() >= '1' && device_name.back() <= '9';
290 if (device_name == "CON" || device_name == "PRN" || device_name == "AUX"
291 || device_name == "NUL" || numbered_device) {
292 return false;
293 }
294 if (slash == std::string_view::npos) {
295 break;
296 }
297 start = slash + 1U;
298 }
299 return true;
300}
301
302std::filesystem::path safe_artifact_path(
303 const std::filesystem::path& root, const std::string_view artifact_ref
304) {
305 if (!is_safe_relative_artifact_ref(artifact_ref)) {
306 throw std::invalid_argument("unsafe relative artifact reference");
307 }
308 return root / std::filesystem::path { artifact_ref };
309}
310
311}
void update(std::span< const std::byte > bytes)
Definition crypto.cpp:76
void transform(const std::byte *block) noexcept
Definition crypto.cpp:153
constexpr std::array< std::uint32_t, 64 > round_constants
Definition crypto.cpp:15
void store_be32(std::byte *output, const std::uint32_t value) noexcept
Definition crypto.cpp:43
std::string to_hex(const sha256_digest &digest)
Definition crypto.cpp:50
std::uint32_t load_be32(const std::byte *bytes) noexcept
Definition crypto.cpp:33
std::string sha256(std::string_view bytes)
Definition crypto.cpp:215
bool is_safe_relative_artifact_ref(std::string_view artifact_ref) noexcept
Definition crypto.cpp:249
std::filesystem::path safe_artifact_path(const std::filesystem::path &root, std::string_view artifact_ref)
Definition crypto.cpp:302
std::array< std::byte, 32 > sha256_digest
Definition crypto.hpp:14
std::string sha256_file(const std::filesystem::path &path)
Definition crypto.cpp:221