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
35namespace WebCore {
36
37namespace {
38
39template<typename CharacterType>
40static bool isVCHAR(CharacterType c)
41{
42 return c >= 0x21 && c <= 0x7e;
43}
44
45template<typename CharacterType>
46struct IntegrityMetadataParser {
47public:
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
80private:
81 Optional<Vector<EncodedResourceCryptographicDigest>>& m_digests;
82};
83
84}
85
86template <typename CharacterType, typename Functor>
87static 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
101static 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
117static bool isResponseEligible(const CachedResource& resource)
118{
119 // FIXME: The spec says this should check XXX.
120 return resource.isCORSSameOrigin();
121}
122
123static 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
130static 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
168bool 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