Web Inspector: provide a way to edit the user agent of a remote target
[WebKit-https.git] / Source / WebCore / loader / SubresourceIntegrity.cpp
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 }