1 | /* |
2 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "SubresourceIntegrity.h" |
28 | |
29 | #include "CachedResource.h" |
30 | #include "HTMLParserIdioms.h" |
31 | #include "ParsingUtilities.h" |
32 | #include "ResourceCryptographicDigest.h" |
33 | #include "SharedBuffer.h" |
34 | |
35 | namespace WebCore { |
36 | |
37 | namespace { |
38 | |
39 | template<typename CharacterType> |
40 | static bool isVCHAR(CharacterType c) |
41 | { |
42 | return c >= 0x21 && c <= 0x7e; |
43 | } |
44 | |
45 | template<typename CharacterType> |
46 | struct IntegrityMetadataParser { |
47 | public: |
48 | IntegrityMetadataParser(Optional<Vector<EncodedResourceCryptographicDigest>>& digests) |
49 | : m_digests(digests) |
50 | { |
51 | } |
52 | |
53 | bool operator()(const CharacterType*& position, const CharacterType* end) |
54 | { |
55 | // Initialize hashes to be something other WTF::nullopt, to indicate |
56 | // that at least one token was seen, and thus setting the empty flag |
57 | // from section 3.3.3 Parse metadata, to false. |
58 | if (!m_digests) |
59 | m_digests = Vector<EncodedResourceCryptographicDigest> { }; |
60 | |
61 | auto digest = parseEncodedCryptographicDigest(position, end); |
62 | if (!digest) |
63 | return false; |
64 | |
65 | // The spec allows for options following the digest, but so far, no |
66 | // specific options have been specified. Thus, we just parse and ignore |
67 | // them. Their syntax is a '?' follow by any number of VCHARs. |
68 | if (skipExactly<CharacterType>(position, end, '?')) |
69 | skipWhile<CharacterType, isVCHAR>(position, end); |
70 | |
71 | // After the base64 value and options, the current character pointed to by position |
72 | // should either be the end or a space. |
73 | if (position != end && !isHTMLSpace(*position)) |
74 | return false; |
75 | |
76 | m_digests->append(WTFMove(*digest)); |
77 | return true; |
78 | } |
79 | |
80 | private: |
81 | Optional<Vector<EncodedResourceCryptographicDigest>>& m_digests; |
82 | }; |
83 | |
84 | } |
85 | |
86 | template <typename CharacterType, typename Functor> |
87 | static inline void splitOnSpaces(const CharacterType* begin, const CharacterType* end, Functor&& functor) |
88 | { |
89 | const CharacterType* position = begin; |
90 | |
91 | skipWhile<CharacterType, isHTMLSpace>(position, end); |
92 | |
93 | while (position < end) { |
94 | if (!functor(position, end)) |
95 | skipWhile<CharacterType, isNotHTMLSpace>(position, end); |
96 | |
97 | skipWhile<CharacterType, isHTMLSpace>(position, end); |
98 | } |
99 | } |
100 | |
101 | static Optional<Vector<EncodedResourceCryptographicDigest>> parseIntegrityMetadata(const String& integrityMetadata) |
102 | { |
103 | if (integrityMetadata.isEmpty()) |
104 | return WTF::nullopt; |
105 | |
106 | Optional<Vector<EncodedResourceCryptographicDigest>> result; |
107 | |
108 | const StringImpl& stringImpl = *integrityMetadata.impl(); |
109 | if (stringImpl.is8Bit()) |
110 | splitOnSpaces(stringImpl.characters8(), stringImpl.characters8() + stringImpl.length(), IntegrityMetadataParser<LChar> { result }); |
111 | else |
112 | splitOnSpaces(stringImpl.characters16(), stringImpl.characters16() + stringImpl.length(), IntegrityMetadataParser<UChar> { result }); |
113 | |
114 | return result; |
115 | } |
116 | |
117 | static bool isResponseEligible(const CachedResource& resource) |
118 | { |
119 | // FIXME: The spec says this should check XXX. |
120 | return resource.isCORSSameOrigin(); |
121 | } |
122 | |
123 | static Optional<EncodedResourceCryptographicDigest::Algorithm> prioritizedHashFunction(EncodedResourceCryptographicDigest::Algorithm a, EncodedResourceCryptographicDigest::Algorithm b) |
124 | { |
125 | if (a == b) |
126 | return WTF::nullopt; |
127 | return (a > b) ? a : b; |
128 | } |
129 | |
130 | static Vector<EncodedResourceCryptographicDigest> strongestMetadataFromSet(Vector<EncodedResourceCryptographicDigest>&& set) |
131 | { |
132 | // 1. Let result be the empty set and strongest be the empty string. |
133 | Vector<EncodedResourceCryptographicDigest> result; |
134 | auto strongest = EncodedResourceCryptographicDigest::Algorithm::SHA256; |
135 | |
136 | // 2. For each item in set: |
137 | for (auto& item : set) { |
138 | // 1. If result is the empty set, add item to result and set strongest to item, skip to the next item. |
139 | if (result.isEmpty()) { |
140 | strongest = item.algorithm; |
141 | result.append(WTFMove(item)); |
142 | continue; |
143 | } |
144 | |
145 | // 2. Let currentAlgorithm be the alg component of strongest. |
146 | auto currentAlgorithm = strongest; |
147 | |
148 | // 3. Let newAlgorithm be the alg component of item. |
149 | auto newAlgorithm = item.algorithm; |
150 | |
151 | // 4. If the result of getPrioritizedHashFunction(currentAlgorithm, newAlgorithm) is |
152 | // the empty string, add item to result. If the result is newAlgorithm, set strongest |
153 | // to item, set result to the empty set, and add item to result. |
154 | auto priority = prioritizedHashFunction(currentAlgorithm, newAlgorithm); |
155 | if (!priority) |
156 | result.append(WTFMove(item)); |
157 | else if (priority.value() == newAlgorithm) { |
158 | strongest = item.algorithm; |
159 | |
160 | result.clear(); |
161 | result.append(WTFMove(item)); |
162 | } |
163 | } |
164 | |
165 | return result; |
166 | } |
167 | |
168 | bool matchIntegrityMetadata(const CachedResource& resource, const String& integrityMetadataList) |
169 | { |
170 | // FIXME: Consider caching digests on the CachedResource rather than always recomputing it. |
171 | |
172 | // 1. Let parsedMetadata be the result of parsing metadataList. |
173 | auto parsedMetadata = parseIntegrityMetadata(integrityMetadataList); |
174 | |
175 | // 2. If parsedMetadata is no metadata, return true. |
176 | if (!parsedMetadata) |
177 | return true; |
178 | |
179 | // 3. If response is not eligible for integrity validation, return false. |
180 | if (!isResponseEligible(resource)) |
181 | return false; |
182 | |
183 | // 4. If parsedMetadata is the empty set, return true. |
184 | if (parsedMetadata->isEmpty()) |
185 | return true; |
186 | |
187 | // 5. Let metadata be the result of getting the strongest metadata from parsedMetadata. |
188 | auto metadata = strongestMetadataFromSet(WTFMove(*parsedMetadata)); |
189 | |
190 | const auto* sharedBuffer = resource.resourceBuffer(); |
191 | |
192 | // 6. For each item in metadata: |
193 | for (auto& item : metadata) { |
194 | // 1. Let algorithm be the alg component of item. |
195 | auto algorithm = item.algorithm; |
196 | |
197 | // 2. Let expectedValue be the val component of item. |
198 | auto expectedValue = decodeEncodedResourceCryptographicDigest(item); |
199 | |
200 | // 3. Let actualValue be the result of applying algorithm to response. |
201 | auto actualValue = cryptographicDigestForBytes(algorithm, sharedBuffer ? sharedBuffer->data() : nullptr, sharedBuffer ? sharedBuffer->size() : 0); |
202 | |
203 | // 4. If actualValue is a case-sensitive match for expectedValue, return true. |
204 | if (expectedValue && actualValue.value == expectedValue->value) |
205 | return true; |
206 | } |
207 | |
208 | return false; |
209 | } |
210 | |
211 | } |
212 | |