/src/serenity/Userland/Libraries/LibWeb/SRI/SRI.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Array.h> |
8 | | #include <AK/Base64.h> |
9 | | #include <AK/Vector.h> |
10 | | #include <LibCrypto/Hash/SHA2.h> |
11 | | #include <LibWeb/SRI/SRI.h> |
12 | | |
13 | | namespace Web::SRI { |
14 | | |
15 | | constexpr Array supported_hash_functions { |
16 | | // These are sorted by strength, low to high. |
17 | | // NOTE: We are specifically told to refuse MD5 and SHA1. |
18 | | // https://w3c.github.io/webappsec-subresource-integrity/#hash-functions |
19 | | "sha256"sv, |
20 | | "sha384"sv, |
21 | | "sha512"sv, |
22 | | }; |
23 | | |
24 | | // https://w3c.github.io/webappsec-subresource-integrity/#getprioritizedhashfunction |
25 | | static StringView get_prioritized_hash_function(StringView a, StringView b) |
26 | 0 | { |
27 | 0 | if (a == b) |
28 | 0 | return ""sv; |
29 | | |
30 | 0 | auto a_priority = supported_hash_functions.first_index_of(a).value(); |
31 | 0 | auto b_priority = supported_hash_functions.first_index_of(b).value(); |
32 | 0 | if (a_priority > b_priority) |
33 | 0 | return a; |
34 | 0 | return b; |
35 | 0 | } |
36 | | |
37 | | // https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-response |
38 | | ErrorOr<String> apply_algorithm_to_bytes(StringView algorithm, ByteBuffer const& bytes) |
39 | 0 | { |
40 | | // NOTE: The steps are duplicated here because each hash algorithm returns a different result type. |
41 | |
|
42 | 0 | if (algorithm == "sha256"sv) { |
43 | | // 1. Let result be the result of applying algorithm to bytes. |
44 | 0 | auto result = Crypto::Hash::SHA256::hash(bytes); |
45 | | |
46 | | // 2. Return the result of base64 encoding result. |
47 | 0 | return encode_base64(result.bytes()); |
48 | 0 | } |
49 | | |
50 | 0 | if (algorithm == "sha384"sv) { |
51 | | // 1. Let result be the result of applying algorithm to bytes. |
52 | 0 | auto result = Crypto::Hash::SHA384::hash(bytes); |
53 | | |
54 | | // 2. Return the result of base64 encoding result. |
55 | 0 | return encode_base64(result.bytes()); |
56 | 0 | } |
57 | | |
58 | 0 | if (algorithm == "sha512"sv) { |
59 | | // 1. Let result be the result of applying algorithm to bytes. |
60 | 0 | auto result = Crypto::Hash::SHA512::hash(bytes); |
61 | | |
62 | | // 2. Return the result of base64 encoding result. |
63 | 0 | return encode_base64(result.bytes()); |
64 | 0 | } |
65 | | |
66 | 0 | VERIFY_NOT_REACHED(); |
67 | 0 | } |
68 | | |
69 | | // https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata |
70 | | ErrorOr<Vector<Metadata>> parse_metadata(StringView metadata) |
71 | 0 | { |
72 | | // 1. Let result be the empty set. |
73 | 0 | Vector<Metadata> result; |
74 | | |
75 | | // 2. For each item returned by splitting metadata on spaces: |
76 | 0 | TRY(metadata.for_each_split_view(' ', SplitBehavior::Nothing, [&](StringView item) -> ErrorOr<void> { |
77 | | // 1. Let hash-with-opt-token-list be the result of splitting item on U+003F (?). |
78 | 0 | auto hash_with_opt_token_list = item.split_view('?'); |
79 | | |
80 | | // 2. Let hash-expression be hash-with-opt-token-list[0]. |
81 | 0 | auto hash_expression = hash_with_opt_token_list[0]; |
82 | | |
83 | | // 3. Let base64-value be the empty string. |
84 | 0 | StringView base64_value; |
85 | | |
86 | | // 4. Let hash-expr-token-list be the result of splitting hash-expression on U+002D (-). |
87 | 0 | auto hash_expr_token_list = hash_expression.split_view('-'); |
88 | | |
89 | | // 5. Let algorithm be hash-expr-token-list[0]. |
90 | 0 | auto algorithm = hash_expr_token_list[0]; |
91 | | |
92 | | // 6. If hash-expr-token-list[1] exists, set base64-value to hash-expr-token-list[1]. |
93 | 0 | if (hash_expr_token_list.size() >= 1) |
94 | 0 | base64_value = hash_expr_token_list[1]; |
95 | | |
96 | | // 7. If algorithm is not a hash function recognized by the user agent, continue. |
97 | 0 | if (!supported_hash_functions.contains_slow(algorithm)) |
98 | 0 | return {}; |
99 | | |
100 | | // 8. Let metadata be the ordered map «["alg" → algorithm, "val" → base64-value]». |
101 | | // Note: Since no options are defined (see the §3.1 Integrity metadata), a corresponding entry is not set in metadata. |
102 | | // If options are defined in a future version, hash-with-opt-token-list[1] can be utilized as options. |
103 | 0 | auto metadata = Metadata { |
104 | 0 | .algorithm = TRY(String::from_utf8(algorithm)), |
105 | 0 | .base64_value = TRY(String::from_utf8(base64_value)), |
106 | 0 | .options = {}, |
107 | 0 | }; |
108 | | |
109 | | // 9. Append metadata to result. |
110 | 0 | TRY(result.try_append(move(metadata))); |
111 | |
|
112 | 0 | return {}; |
113 | 0 | })); |
114 | | |
115 | | // 3. Return result. |
116 | 0 | return result; |
117 | 0 | } |
118 | | |
119 | | // https://w3c.github.io/webappsec-subresource-integrity/#get-the-strongest-metadata |
120 | | ErrorOr<Vector<Metadata>> get_strongest_metadata_from_set(Vector<Metadata> const& set) |
121 | 0 | { |
122 | | // 1. Let result be the empty set and strongest be the empty string. |
123 | 0 | Vector<Metadata> result; |
124 | 0 | Optional<Metadata> strongest; |
125 | | |
126 | | // 2. For each item in set: |
127 | 0 | for (auto const& item : set) { |
128 | | // 1. If result is the empty set, add item to result and set strongest to item, skip to the next item. |
129 | 0 | if (result.is_empty()) { |
130 | 0 | TRY(result.try_append(item)); |
131 | 0 | strongest = item; |
132 | 0 | continue; |
133 | 0 | } |
134 | | |
135 | | // 2. Let currentAlgorithm be the alg component of strongest. |
136 | 0 | auto& current_algorithm = strongest->algorithm; |
137 | | |
138 | | // 3. Let newAlgorithm be the alg component of item. |
139 | 0 | auto& new_algorithm = item.algorithm; |
140 | | |
141 | | // 4. If the result of getPrioritizedHashFunction(currentAlgorithm, newAlgorithm) is the empty string, add item to result. |
142 | 0 | auto prioritized_hash_function = get_prioritized_hash_function(current_algorithm, new_algorithm); |
143 | 0 | if (prioritized_hash_function.is_empty()) { |
144 | 0 | TRY(result.try_append(item)); |
145 | 0 | } |
146 | | // If the result is newAlgorithm, set strongest to item, set result to the empty set, and add item to result. |
147 | 0 | else if (prioritized_hash_function == new_algorithm) { |
148 | 0 | strongest = item; |
149 | 0 | result.clear_with_capacity(); |
150 | 0 | TRY(result.try_append(item)); |
151 | 0 | } |
152 | 0 | } |
153 | | |
154 | | // 3. Return result. |
155 | 0 | return result; |
156 | 0 | } |
157 | | |
158 | | // https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist |
159 | | ErrorOr<bool> do_bytes_match_metadata_list(ByteBuffer const& bytes, StringView metadata_list) |
160 | 0 | { |
161 | | // 1. Let parsedMetadata be the result of parsing metadataList. |
162 | 0 | auto parsed_metadata = TRY(parse_metadata(metadata_list)); |
163 | | |
164 | | // 2. If parsedMetadata is empty set, return true. |
165 | 0 | if (parsed_metadata.is_empty()) |
166 | 0 | return true; |
167 | | |
168 | | // 3. Let metadata be the result of getting the strongest metadata from parsedMetadata. |
169 | 0 | auto metadata = TRY(get_strongest_metadata_from_set(parsed_metadata)); |
170 | | |
171 | | // 4. For each item in metadata: |
172 | 0 | for (auto const& item : metadata) { |
173 | | // 1. Let algorithm be the item["alg"]. |
174 | 0 | auto& algorithm = item.algorithm; |
175 | | |
176 | | // 2. Let expectedValue be the item["val"]. |
177 | 0 | auto& expected_value = item.base64_value; |
178 | | |
179 | | // 3. Let actualValue be the result of applying algorithm to bytes. |
180 | 0 | auto actual_value = TRY(apply_algorithm_to_bytes(algorithm, bytes)); |
181 | | |
182 | | // 4. If actualValue is a case-sensitive match for expectedValue, return true. |
183 | 0 | if (actual_value == expected_value) |
184 | 0 | return true; |
185 | 0 | } |
186 | | |
187 | | // 5. Return false. |
188 | 0 | return false; |
189 | 0 | } |
190 | | |
191 | | } |