-
Notifications
You must be signed in to change notification settings - Fork 7
/
index.bs
1196 lines (915 loc) · 53.5 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: Signature-based Integrity
Shortname: signature-based-sri
Level: none
Status: w3c/CG-DRAFT
Group: wicg
Repository: wicg/signature-based-sri
URL: https://wicg.github.io/signature-based-sri/
Editor: Mike West, Google LLC., [email protected]
Abstract:
A monkey-patch spec that enhances SRI with signature-based
integrity checks. These are conceptually similar to the
content-based checks currently defined, but have different
properties that seem interesting to explore.
Complain About: accidental-2119 yes, missing-example-ids yes
Markup Shorthands: markdown yes, css no
Toggle Diffs: true
</pre>
<pre class="anchors">
urlPrefix: https://fetch.spec.whatwg.org/; type: dfn; spec: FETCH
text: fetch response handover; url: #fetch-finale
urlPrefix: https://www.rfc-editor.org/rfc/rfc9651; type: dfn; spec: RFC9651
text: structured header; url: #name-introduction
for: structured header
text: token; url: #name-tokens
text: list; url: #name-list
text: string; url: #name-string
text: inner list; url: #name-inner-list
text: dictionary; url: #name-dictionary
text: parameters; url: #name-parameters
urlPrefix: https://w3c.github.io/webappsec-subresource-integrity/; spec: SRI
type: dfn
text: valid SRI hash algorithm token
urlPrefix: https://lpardue.github.io/draft-pardue-http-identity-digest/draft-pardue-httpbis-identity-digest.html; spec: ID.pardue-httpbis-identity-digest
type: http-header;
text: Identity-Digest; url: name-the-identity-digest-field
urlPrefix: https://www.rfc-editor.org/rfc/rfc9421.html; spec: RFC9421
type: dfn
text: signature base; url: name-creating-the-signature-base
text: component identifier; url: covered-components
text: derived components; url: name-derived-components
text: signature parameters; url: signature-params
text: sf; url: http-field-structured
text: alg; url: section-2.3-4.8
text: created; url: section-2.3-4.2
text: expires; url: section-2.3-4.4
text: keyid; url: section-2.3-4.10
text: tag; url: section-2.3-4.12
text: req; url: section-2.4-2.2.1
text: @method; url: name-method
text: @target-uri; url: name-target-uri
text: @authority; url: name-authority
text: @scheme; url: name-scheme
text: @request-target; url: name-request-target
text: @path; url: name-path
text: @query; url: name-query
text: @query-param; url: name-query-parameters
text: @status; url: name-status-code
type: http-header;
text: Accept-Signature; url: name-the-accept-signature-field
text: Signature-Input; url: name-the-signature-input-field
text: Signature; url: name-the-signature-field
urlPrefix: https://www.rfc-editor.org/rfc/rfc9651.html; spec: STRUCTURED-FIELDS
type: abstract-op
text: parsing structured fields; url: text-parse
urlPrefix: https://www.rfc-editor.org/rfc/rfc9110.html; spec: HTTP-SEMANTICS
type: dfn
text: content codings; url: name-content-codings
</pre>
<pre class="link-defaults">
spec:html; type:element; text:script
spec:html; type:element; text:link
</pre>
<pre class="biblio">
{
"PCIv4-SRI-Gaps": {
"authors": [ "Yoav Weiss", "Ilya Grigorik" ],
"href": "https://docs.google.com/document/d/1RcUpbpWPxXTyW0Qwczs9GCTLPD3-LcbbhL4ooBUevTM/edit?usp=sharing",
"title": "PCIv4: SRI gaps and opportunities"
},
"ID.pardue-httpbis-identity-digest": {
"authors": [ "Lucas Pardue", "Mike West" ],
"href": "https://lpardue.github.io/draft-pardue-http-identity-digest/draft-pardue-httpbis-identity-digest.html",
"title": "HTTP Identity Digest"
}
}
</pre>
<style>
.line-no {
display: none;
}
.line {
min-height: 1.5em;
}
.highlight-line {
font-weight: bold;
}
</style>
Introduction {#intro}
=====================
**This section is non-normative.**
Subresource Integrity [[SRI]] defines a mechanism by which developers can
ensure that script or stylesheet loaded into their pages' contexts are
*exactly* those scripts or stylesheets the developer expected. By specifying
a SHA-256 hash of a resource's content, any malicious or accidental deviation
will be blocked before being executed. This is an excellent defense, but its
deployment turns out to be brittle. If the resource living at a specific URL
is dynamic, then content-based integrity checks require pages and the
resources they depend upon to update in lockstep. This turns out to be
~impossible in practice, which makes SRI less usable than it could be.
Particularly as the industry becomes more interested in supply-chain integrity
(see Shopify's [[PCIv4-SRI-Gaps]], for instance), it seems reasonable to explore
alternatives to static hashes that could allow wider deployment of these checks,
and therefore better understanding of the application experiences that
developers are *actually* composing.
This document outlines the changes that would be necessary to [[Fetch]], and
[[SRI]] in order to support the simplest version of a signature-based check:
<div class="example" id="basic-example">
Pages will embed an Ed25519 public key assertion into `integrity`
attributes:
<xmp highlight="html">
<script src="https://my.cdn/script.js"
crossorigin="anonymous"
integrity="ed25519-[base64-encoded-public-key]"
...></script>
</xmp>
Servers will deliver a signature over the resource content using the
corresponding private key along with the resource as an HTTP message
signature [[RFC9421]]:
<xmp highlight="http">
HTTP/1.1 200 OK
Accept-Ranges: none
Vary: Accept-Encoding
Content-Type: text/javascript; charset=UTF-8
Access-Control-Allow-Origin: *
Identity-Digest: sha-512=:[base64-encoded digest of `console.log("Hello, world!");`]:
Signature-Input: sig1=("identity-digest";sf); keyid="[base64-encoded public key]"; tag="sri"
Signature: sig1=:[base64-encoded result of Ed25519(signature base)]:
console.log("Hello, world!");
</xmp>
The user agent will validate the signature using the expected public key
before executing the response.
That's it!
</div>
The goal here is to flesh out the proposal for discussion, recognizing that it
might be too simple to ship. Then again, it might be *just* simple enough...
Signatures are not Hashes {#signatures-vs-hashes}
-------------------------------------------------
Subresource Integrity's existing hash-based checks ensure that specific, known
_content_ executes. It doesn't care who made the file or from which server it
was retrieved: as long as the content matches the expectation, we're good to
go. This gives developers the ability to ensure that a specific set of audited
scripts are the only ones that can execute in their pages, providing a strong
defense against some kinds of threats.
The signature-based checks described briefly above are different. Rather than
validating that a specific script or stylesheet is known-good, they instead
act as a proof of _provenance_ which ensures that scripts will only execute if
they're signed with a known private key. Assuming good key-management practices
(easy, right?), this gives a guarantee which is different in kind, but
similarly removes the necessity to trust intermediaries.
With these properties in mind, signature-based integrity checks aim to protect
against attackers who might be able to manipulate the content of resources that
a site depends upon, but who cannot gain access to the signing key.
High-Level Overview {#overview}
-------------------------------
The mechanism described in the remainder of this document can be broken down
into a few independent parts, layered on top of one another to achieve the goals
developers are aiming for.
1. **Server-initiated integrity checks**: Servers can deliver an
[:Identity-Digest:] header along with responses that contain one or more
digests of the response's content _after_ decoding any [=content codings=]
(gzip, brotli, etc).
If such a header is present, user agents can enforce it by synthesizing a
network error if the delivered content does not match the asserted digest.
See [[#monkey-patch-fetch]] below for more details.
2. **Server-initiated signature checks**: Servers can deliver HTTP Message
Signature headers ([:Signature:] and [:Signature-Input:] from [[RFC9421]])
that allow the verification of request/response metadata. We can construct
these headers in such a way that user agents can enforce them, and further
ensure that the signed metadata includes the server-initiated integrity
checks noted above. Enforcing signature verification, then, means ensuring
that the private key's possessor signed the specific content in question.
See the [=verification requirements for SRI=] described below for more
detail about these headers' construction.
3. **Client-initiated integrity checks**: Pages need to be able to specify
[=request/integrity metadata=] for <{script}> and <{link}> elements that
can be matched against the server-initiated checks described above.
The work necessary is described in [[#monkey-patch-sri]] below.
4. **CSP-driven enforcement**: As described in [[CSP#external-hash]], it's
possible today to safely allow JavaScript execution by specifying
[=request/integrity metadata=] on a given element, matching that metadata
against a page's active policies, and relying upon SRI to enforce the
constraints the metadata declares. The same should be possible for
signatures (and should fall out of CSP's specification without much
additional work).
ISSUE(36): TODO(mkwst): Write up this integration, which requires at least
a grammar update in CSP.
Implementing the mechanism in this document therefore requires:
1. Implementing [:Identity-Digest:] checks, at least for the subset of
resource types upon which SRI can act: scripts and stylesheets.
2. Implementing the subset of HTTP Message Signatures required to support the
headers which meet the [=verification requirements for SRI=].
3. Implementing the patches against SRI necessary to support the new integrity
types, described in [[#monkey-patch-sri]].
Revisiting the example above, the following things might happen to ensure that
we're only executing script correctly signed with a key we expect:
1. Prior to sending the request, the page's CSP will verify the content of the
relevent <{script}> element's <{script/integrity}> attribute, ensuring that
any public keys asserted match the page's requirements.
2. The user agent receives response headers for `https://my.cdn/script.js`,
parses the [:Signature-Input:] header, and uses it to verify the
[:Signature:] header's content, blocking the response if verification fails.
This verification shows that we'll only be dealing with responses for which
we have proof that the private key's possessor signed this response,
including the integrity information.
3. The user agent matches the public key contained in the [:Signature-Input:]
header with the request's [=request/integrity metadata=], blocking the
response if there's a mismatch. This ensures that we're meeting the page's
requirements for resource inclusion.
4. Once the response has streamed in, we validate the integrity information
contained in the [:Identity-Digest:] headers against the response body,
refusing to execute any mismatched responses.
5. We're done, executing probably-safe JavaScript to our heart's content.
Monkey Patches {#monkey-patches}
================================
Extending SRI to support signatures will require changes to three
specifications, along with some additional infrastructure.
Patches to SRI {#monkey-patch-sri}
----------------------------------
At a high level, we'll make the following changes to SRI:
1. We'll define a profile of HTTP Message Signatures that meets the specific
needs we have for this feature, specifying the requirements for signatures
intended as proofs of integrity/provenance that can be enforced upon by
clients without any pre-existing relationship to the server which
delivered them. This requires locking down the components and properties
of the signature itself, as well as some of the decision points available
during the generation of the signature base
2. We'll define the accepted algorithm values. Currently, these are left up to
user agents in order to allow for future flexibility: given that the years
since SRI's introduction have left the set of accepted algorithms and their
practical ordering unchanged, we should define that explicitly.
3. With known algorithms, we can adjust the prioritization model to return a
set of the strongest content-based and signature-based algorithms specified
in a given element. This would enable developers to specify both a hash and
signature expectation for a resource, ensuring both that known resources
load, and that they're accepted by a trusted party.
4. Finally, we'll adjust the matching algorithm to correctly handle signatures
by passing the public key in to the comparison operation.
The following sections add content and adjust algorithms accordingly.
<h4 id="profile">The `SRI` HTTP Message Signature Profile</h4>
This document defines an HTTP Message Signature profile that specifies the
requirements for signatures intended as proofs of integrity/provenance that can
be enforced upon by clients without any pre-existing relationship to the server
which delivered them. This requires locking down the components and properties
of the signature itself, as well as some of the decision points
available during the generation of the signature base (Section 2.5 of
[[RFC9421]]).
At a high-level, the constraints are simple: this profile supports only Ed25519
signatures, requires that the public key portion of the verification key
material be included in the signature's input, and specifies the ordering of the
components and properties to remove potential ambiguity about the signature's
construction. The rest of this section spells out those constraints more
formally as the <dfn>verification requirements for SRI</dfn>, following the
guidelines from Section 1.4 of [[RFC9421]]:
: **Components and Parameters**:
:: The signature's input MUST:
1. Include the following [=component identifiers=] with their associated
constraints:
* `identity-digest`, which MUST include the <a>`sf`</a> parameter and
no other parameters.
Note: We'll extend the set of allowed headers over time. The limitation
to `identity-digest` is artificial, and aimed towards making a prototype
of this approach as simple as possible to implement and evaluate as we
decide what makes sense to ship at scale.
2. Include the following [=signature parameters=] with their associated
constraints:
* <a>`keyid`</a>, whose value MUST be a string containing a
[=forgiving-base64 encode|base64 encoding=] of the public key
portion of the signature's verification key material.
* <a>`tag`</a>, whose value MUST be the string `sri`
ISSUE(34): Perhaps something more specific to make room for
variants in the future that have different constraints?
`enforce-ed25519-provenance`? `ed25519-integrity`? Etc?
3. **Not** include the <a>`alg`</a> [=signature parameter=].
Note: The algorithm can be determined unambigiously from the
<a>`type`</a>, as this profile only supports Ed25519.
[Section 7.3.6 of RFC9421](https://www.rfc-editor.org/rfc/rfc9421.html#section-7.3.6)
suggests dropping the <a>`alg`</a> parameter in these cases, which
is the recommendation we're following here.
The signature's input MAY include the following [=derived components=] as
part of the list of [=component identifiers=], each of which MUST include
the <a>`req`</a> parameter and no other paramters:
* <a dfn>`@authority`</a>
* <a dfn>`@method`</a>
* <a dfn>`@path`</a>
* <a dfn>`@query-param`</a>
* <a dfn>`@query`</a>
* <a dfn>`@request-target`</a>
* <a dfn>`@scheme`</a>
* <a dfn>`@status`</a>
* <a dfn>`@target-uri`</a>
The signature's input MAY include the following [=signature parameters=],
with their associated constraints:
* <a>`created`</a>, an integer whose value MUST represent a time in the past.
* <a>`expires`</a>, an integer whose value MUST represent a time in the future.
* `nonce`, which is a string whose value SHOULD be generated in a fashion
which guarantees uniqueness.
: **Structured Field Types**:
:: * The `identity-digest` component references the [:Identity-Digest:]
header defined in [[ID.pardue-httpbis-identity-digest]]. It is a
[=structured header/Dictionary=] Structured Field.
: **Retrieving the Key Material**:
:: The public key of the verification key material can be directly extracted
from the signature input's <a>`keyid`</a> parameter, where it's represented
as a [=forgiving-base64 encode|base64 encoded=] string.
: **Signature Algorithms**:
:: The only signature algorithm identifier allowed is "`ed25519`", as defined
in [Section 3.3.6 of RFC9421](https://www.rfc-editor.org/rfc/rfc9421.html#section-3.3.6).
: **Determine Key/Algorithm Appropriateness**:
:: Since the only accepted algorithm is `ed25519`, it is appropriate for any
context in which this profile will be used.
: **Derivation Context**
:: The context for derivation of message components from an HTTP message and
its application context is the HTTP message itself, encompassing the
response with which the signature was delivered, and the request to which
it responds.
: **Error Reporting from Verifier to Signer**
:: No error reporting is required.
Clients MUST represent verification failures as [=network errors=],
consistent with [[FETCH]]'s handling of other server-specified constraints
on the usage of response data.
: **Security Considerations**
:: See [[#security]].
: **Other**
:: The HTTP Message Signature must be delivered with a response.
:: The [:Identity-Digest:] header must be [=Identity-Digest/valid for SRI=].
:: When instructed to "determine an order" while constructing the signature
base, clients and servers both MUST choose the same order as the
[:Signature-Input:] header they consume or produce, respectively.
<div class="example" id="example-verification-requirements">
Valid [:Signature-Input:] header values would therefore include:
* `("identity-digest";sf);keyid="MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=";tag="sri"`
</div>
<div class="note">
Note: These requirements are fairly draconian, allowing only a very small subset
of the flexibility allowed by the HTTP Message Signature format. It is entirely
probable that we can expand the scope of allowed signature inputs in the future,
but as we're figuring out how to do signature validation on the client it seems
prudent to provide as much strict guidance as possible in order to keep the
initial complexity under control.
For posterity, this set of requirements has a few helpful implications:
1. Specifying the `tag` parameter as "`sri`" is a pretty clear signal that the
developer is aiming to validate the integrity and/or provenance of a given
subresource, and can therefore be reasonably expected to adhere to the set
of constraints and processing instructions described in this document.
Developers specifying that tag can be expected to be unsurprised when
resources are blocked if their signatures don't properly validate.
2. Specifying the `keyid` parameter as a base64 encoding of the signer's public
key makes it possible for validation to be enforced whether or not the
resource was requested from a page requiring integrity.
3. Supporting only the "`ed25519`" algorithm is a good place to start as
the keys are small and the algorithm is broadly supported. Choosing one
algorithm simplifies initial implementations, and reduces the set of choices
we ask developers to make about crypto primitives.
4. The [:Signature-Input:] header is very flexible as specified, and most of
the restrictions here aim to reduce its complexity as we gain implementation
experience on both the client and server sides of the signature generation
process. [[RFC9421]] leaves several important questions about the
serialization of the "[=signature base=]" open to agreement between the
signer and verifier: we're locking most of those joints down here in order
to ensure that we start with a simple story for both sides.
To that end, we're supporting signatures only over the one specific header
necessary to meaningfully assert something about the resource's body. We're
explicitly specifying strict serialization of that header, and we're
requiring it to be a header, not a trailer.
5. In order to avoid potential disagreements between servers and clients about
the serialization of a [=signature base=] for a given response, we're
specifying how both sides ought to "Determine an order for any signature
parameters" through reference to the header as-delivered. Whatever order
the server produces in the [:Signature-Input:] header is the order that
the client will expect the signature base to represent.
</div>
#### `Identity-Digest` Validation for SRI #### {#identity-digest-validation-for-sri}
<div algorithm>
An [:Identity-Digest:] [=header=] (|header|) is
<dfn for="Identity-Digest">valid for SRI</dfn> if the following steps return
"`valid`":
1. Let |parsed| be the result of [$parsing structured fields$] with `input_string`
set to |header|'s [=header/value=], and `header_type` set to
"<a for="structured header">`dictionary`</a>".
2. If parsing failed or if |parsed| [=map/is empty=], return "`invalid`".
3. [=map/iterate|For each=] |key| → |value| of |parsed|:
1. If |value| is not a [=byte sequence=], return "`invalid`".
2. If |key| is not [=list/contain|contained within=] the [=/list=]
« "sha-256", "sha-384", "sha-512" », return "`invalid`".
3. If |key| is "`sha-256`", and |value|'s [=byte sequence/length=] is
not 32, return "`invalid`".
4. If |key| is "`sha-384`", and |value|'s [=byte sequence/length=] is
not 48, return "`invalid`".
5. If |key| is "`sha-512`", and |value|'s [=byte sequence/length=] is
not 64, return "`invalid`".
4. Return "`valid`".
</div>
<h4 id="parsing" algorithm>Parse |metadata|.</h4>
First, we'll define valid signature algorithms:
* <ins>The <dfn>valid SRI signature algorithm token set</dfn> is the
[=ordered set=] « "`ed25519`" » (corresponding to Ed25519 [[!RFC8032]]).</ins>
* <ins>A string is a <dfn>valid SRI signature algorithm token</dfn> if its
[=ASCII lowercase=] is [=set/contained=] in the
[=valid SRI signature algorithm token set=].
Then, we'll adjust SRI's <dfn abstract-op>Parse |metadata|</dfn>. algorithm as
follows:
This algorithm accepts a string, and returns a map containing one set of hash
expressions whose hash functions are understood by the user agent, and one set
of signature expressions which are likewise understood:
1. Let |result| be <del>the empty set</del><ins>the [=ordered map=]
«[ "hashes" → « », "signatures" → « » ]».</ins>
2. For each |item| returned by <a lt="strictly split">splitting</a>
|metadata| on spaces:
1. Let |expression-and-options| be the result of
<a lt="strictly split">splitting</a> |item| on U+003F (?).
2. Let |algorithm-expression| be |expression-and-options|[0].
3. Let |base64-value| be the empty string.
4. Let |algorithm-and-value| be the result of
<a lt="strictly split">splitting</a> |algorithm-expression| on U+002D (-).
5. Let |algorithm| be |algorithm-and-value|[0].
6. If |algorithm-and-value|[1] <a for=list>exists</a>, set
|base64-value| to |algorithm-and-value|[1].
7. <del>If |algorithm| is not a [=valid SRI hash algorithm token=], then [=iteration/continue=].</del>
8. Let <del>|metadata|</del><ins>|data|</ins> be the ordered map «["alg" → |algorithm|, "val" → |base64-value|]».</del>
9. <del><a for=list>Append</a> |metadata| to |result|.</del>
11. <ins>If |algorithm| is a [=valid SRI hash algorithm token=], then [=set/append=] |data| to |result|["`hashes`"].</ins>
12. <ins>Otherwise, if |algorithm| is a [=valid SRI signature algorithm token=], then [=set/append=] |data| to |result|["`signatures`"].</ins>
3. Return |result|.
<h4 id="matching" algorithm>Do |bytes| and |response| match <var ignore>metadataList</var>?</h4>
Since we adjusted the result of [[#parsing]] above, we need to adjust the
matching algorithm to match. The core change will be processing both hashing
and signature algorithms: if only one kind is present, the story will be
similar to today, and multiple strong algorithms can be present, allowing
multiple distinct resources. If both hashing and signature algorithms are
present, both will be required to match. This is conceptually similar to
the [application of multiple Content Security Policies](https://w3c.github.io/webappsec-csp/#multiple-policies).
In order to validate signatures, we'll need to change Fetch to pass in the
relevant HTTP response header. For the moment, let's simply pass in the
entire [=/response=] (|response|), as that makes the integration with [[RFC9421]] somewhat
explicable.
To <dfn abstract-op>perform client-initiated integrity checks</dfn> for a given
[=byte sequence=] (|bytes|), [=/request=] (|request|), and [=/response=]
(|response|), execute the following steps. They return "`passed`" or "`failed`":
1. Let |parsedMetadata| be the result of executing [$Parse metadata$] on |request|'s [=request/integrity metadata=].
2. If both |parsedMetadata|["`hashes`"] and |parsedMetadata|["`signatures`"] are [=set/empty=] set, return "`passed`".
3. Let |hash-metadata| be the result of executing [[SRI#get-the-strongest-metadata]] on |parsedMetadata|["`hashes`"].</a>.
4. Let |signature-metadata| be the result of executing [[SRI#get-the-strongest-metadata]] on |parsedMetadata|["`signatures`"].</ins>
5. Let |hash-match| be `true` if |hash-metadata| is [=list/empty=], and `false` otherwise.</ins>
6. Let |signature-match| be `true` if |signature-metadata| is [=list/empty=], and `false` otherwise.</ins>
7. For each |item| in |hash-metadata|</ins>:
1. Let |algorithm| be the |item|["`alg`"].
2. Let |expectedValue| be the |item|["`val`"].
3. Let |actualValue| be the result of [[SRI#apply-algorithm-to-response]] on |algorithm| and |bytes|.
4. If |actualValue| is a case-sensitive match for
|expectedValue|, set |hash-match| to `true` and [=iteration/break=].</ins>
8. For each |item| in |signature-metadata|:</ins>
1. Let |algorithm| be the |item|["`alg`"].</ins>
2. Let |public key| be the |item|["`val`"].</ins>
3. Let |result| be the result of [$validating an integrity signature$]
over |request| and |response| using |algorithm| and |public key|.</ins>
4. If |result| is "`valid`", set |signature-match| to `true` and [=iteration/break=].</ins>
9. Return "`passed`" if both |hash-match| and |signature-match| are `true`. Otherwise return "`failed`".</ins>
<h4 id="validation" algorithm>Validate a signature over |response| using |algorithm| and |public key|</h4>
The matching algorithm above calls into a new signature validation function.
Let's write that down. At core, it will execute the Ed25519 validation steps
from [[RFC8032]] using signatures extracted from HTTP Message Signature
headers defined in [[RFC9421]], then compare valid signatures against the
expected public key.
To <dfn abstract-op lt="validating an integrity signature">validate an integrity signature</dfn>
over a [=/response=] |response|, [=string=] |algorithm|, and [=string=] |public key|, execute the
following steps. They return `valid` if the signature is valid, or `invalid` otherwise.
1. Let |result| be the result of verifying an HTTP Message Signature as defined in
[Section 3.2](https://www.rfc-editor.org/rfc/rfc9421.html#name-verifying-a-signature)
of [[!RFC9421]], given |response| as the signature context, the [=verification
requirements for SRI=], and the following processing instructions:
1. When executing Step 1.1 of the verification algorithm referenced above,
"determine which signature should be processed for this message" by
evaluating all signatures whose input's <a>`tag`</a> parameter
[=string/is=] "`sri`".
2. When executing Step 4 of the verification algorithm, use the [=verification
requirements for SRI=] described above.
3. When executing Step 5 of the verification algorithm:
1. "Determine the verification key material" by
[=forgiving-base64 decode|base64 decoding=] the signature input's
<a>`keyid`</a> parameter.
2. "Determine the trustworthiness of the key material" by comparing
the signature input's <a>`keyid`</a> parameter to |public key|.
If the two do not match, fail validation for this signature.
4. Assert: When executing Step 6, the result of "Determine the algorithm"
[=string/is=] "`ed25519`" due to the [=verification requirements for
SRI=] applied above.
2. If |result| is failure, return "`invalid`".
3. Otherwise, return "`valid`".
Patches to Fetch {#monkey-patch-fetch}
--------------------------------------
Support for this feature would require changes to [[Fetch#main-fetch]] to support
enforcement of server-initiated integrity checks through [:Identity-Digest:],
[:Signature:], and [:Signature-Input:], and to pass the right set of information
into the version of [[SRI#does-response-match-metadatalist]] altered by this
specification in order to enable signature-based checks that require information
from the request ([=request/integrity metadata=] on the one hand, request headers
and properties for signature components on the other) and the response (integrity
headers and the body).
It would also require changes to [[Fetch#http-network-or-cache-fetch]] to support
setting the [:Accept-Signature:] header on outgoing requests based on their
[=request/integrity metadata=].
### Main Fetch ### {#monkey-patch-main-fetch}
[[Fetch#main-fetch]] step 22 will be updated as follows:
<ol start=21 algorithm="monkey patching Main Fetch">
<li>
If |request|'s [=request/integrity metadata=] is not the empty string,
<ins>or <var ignore>internalResponse</var>'s [=response/header list=]
[=list/contains=] [:Identity-Digest:],</ins> then:
<ol>
<li>
Let <var ignore>processBodyError</var> be this step: run [=fetch response handover=]
given <var ignore>fetchParams</var> and a [=network error=].
</li>
<li>
If <var ignore>response</var>'s [=response/body=] is null, then run <var ignore>processBodyError</var> and
abort these steps.
</li>
<li>
Let <var ignore>processBody</var> given <var ignore>bytes</var> be these steps:
<ol>
<li><ins>
<a lt="perform server-initiated integrity checks" abstract-op>Perform
server-initiated integrity checks</a> on <var ignore>bytes</var>,
<var ignore>request</var>, and <var ignore>internalResponse</var>.
If the result is "`failed`", then run <var ignore>processBodyError</var> and
abort these steps.
</ins></li>
<li>
<del>
If <var ignore>bytes</var> do not match |request|'s
[=request/integrity metadata=]
</del>
<ins>
<a abstract-op>Perform client-initiated integrity checks</a> given
<var ignore>request</var> and <var ignore>internalResponse</var>.
If the result is "`failed`",
</ins>
then run <var ignore>processBodyError</var> and abort these steps. [[!SRI]]
</li>
<li>
Set <var ignore>response</var>'s <a for=response>body</a> to <var ignore>bytes</var>
[=byte sequence/as a body=].
</li>
<li>
Run [=fetch response handover=] given <var ignore>fetchParams</var> and <var ignore>response</var>.
</li>
</ol>
</li>
</ol>
</li>
</ol>
### HTTP-network-or-cache Fetch ### {#monkey-patch-http-network-or-cache-fetch}
[[Fetch#http-network-or-cache-fetch]] will be updated by injecting the following
step between the existing step 13 and 14:
<ol start=13 algorithm="monkey patching HTTP-network-or-cache fetch">
<li><p><a abstract-op lt="append the Fetch metadata headers for a request">Append the Fetch metadata headers for <var>httpRequest</var></a>. [[!FETCH-METADATA]]</p></li>
<li><ins>
<a lt="append the Accept-Signature header" abstract-op>Append the `Accept-Signature` header</a> for |httpRequest|.
</ins></li>
<li><p>If <var>httpRequest</var>'s <a for=request>initiator</a> is "<code>prefetch</code>", then
<a>set a structured field value</a> given (`<a http-header><code>Sec-Purpose</code></a>`,
the <a data-lt="structured field token">token</a> <code>prefetch</code>) in
<var>httpRequest</var>'s <a for=request>header list</a>.
</li>
</ol>
#### Append `Accept-Signature` #### {#append-accept-signature}
When a [=request=]'s [=request/integrity metadata=] contains signature-based assertions,
user agents will attach [:Accept-Signature:] headers to the request to inform servers
about the client's expectations. The header's value will match the grammar defined in
[[RFC9421]], and contain the expected public key(s) as <a>`keyid`</a> parameters.
<div class="example">
A request generated from the following HTML element:
```html
<script src="https://my.cdn/script.js"
crossorigin="anonymous"
integrity="ed25519-JrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs="></script>
```
would contain the following header:
```http
Accept-Signature: sig0=("identity-digest";sf);keyid="JrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=";type="sri"
```
If multiple keys are acceptable (e.g. to support key rotation), the [:Accept-Signature:]
header will contain multiple acceptable signatures. That is, the following HTML:
```html
<script src="https://my.cdn/script.js"
crossorigin="anonymous"
integrity="ed25519-JrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
ed25519-xDnP380zcL4rJ76rXYjeHlfMyPZEOqpJYjsjEppbuXE="></script>
```
would produce the following header in its request:
```http
NOTE: '\' line wrapping per RFC 8792
Accept-Signature: sig0=("identity-digest";sf);keyid="JrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=";type="sri" \
sig1=("identity-digest";sf);keyid="xDnP380zcL4rJ76rXYjeHlfMyPZEOqpJYjsjEppbuXE=";type="sri"
```
</div>
<div algorithm="append `accept-signature`">
To <dfn abstract-op>append the `Accept-Signature` header</dfn> for a [=request=]
(|request|):
1. If |request|'s [=request/header list=] [=header list/contains=] [:Accept-Signature:], return.
Note: Developers can set an [:Accept-Signature:] header for use by their
own application. In this case, the user agent will not set additional
[:Accept-Signature:] headers, and may perform a [=CORS-preflight request=].
2. If |request|'s [=request/integrity metadata=] is [=string/empty=], return.
3. Let |parsed| be the result of executing [$Parse metadata$] on |request|'s
[=request/integrity metadata=].
4. If |parsed|["`signatures`"] is [=set/empty=], return.
6. Let |counter| be 0.
7. For each |signature| in |parsed|["`signatures`"]:
1. Let |value| be the [=string/concatenation=] of « \`<code>sig</code>\`, counter, \`<code>=("identity-digest";sf);keyid="</code>\`, |signature|["`val`"], \`<code>";type="sri"</code>\` ».
2. [=header list/Append=] (\`<code>Accept-Signature</code>\`, |value|) to |request|'s [=request/header list=].
</div>
ISSUE(21): To support this change, we also need to clean up the processing in
[=CORS-safelisted request-header=] to support this header, as discussed in
Server-Initiated Integrity Checks {#server-initiated}
---------------------------------
<div algorithm="server-initiated integrity checks">
Note: This set of algorithms could live either in [[Fetch]] or [[SRI]].
To <dfn abstract-op>perform server-initiated integrity checks</dfn> given a
[=byte sequence=] (|bytes|), a [=/request=] (|request|), and a [=/response=]
(|response|), execute the following steps. They return "`passed`" or
"`failed`" as appropriate:
1. <a lt="verify Identity-Digest assertions" abstract-op>Verify
`Identify-Digest` assertions</a> for |bytes| and |response|. If the
result is "`failed`", return "`failed`".
2. <a lt="verify SRI Message Signature assertions" abstract-op>Verify SRI
Message Signature assertions</a> for |request|, and |response|.
If the result is "`failed`", return "`failed`".
3. Return "`passed`".
</div>
### `Identity-Digest` Validation ### {#identity-digest-validation}
<div algorithm="identity-digest validation">
To <dfn abstract-op>verify `Identity-Digest` assertions</dfn> given a
[=byte sequence=] (|bytes|) and a [=/response=] (|response|), execute the
following steps. They return "`verified`" or "`failed`":
1. Let |header| be the result of [=header list/get a structured field value|getting=]
the [:Identity-Digest:] header as a "<a for="structured header">`dictionary`</a>"
from |response|'s [=response/header list=].
2. If |header| is `null`, return "`verified`".
3. [=map/iterate|For each=] |alg| → |digest| of |header|:
1. If |alg| is not one of "sha-256", "sha-384", or "sha-512", then [=continue=].
2. Let |body digest| be the result of executing [[SRI#apply-algorithm-to-response]]
on |alg| and |bytes|.
3. If |body digest| matches |digest|, continue.
4. Return "`failed`".
4. Return "`verified`".
Note: This algorithm requires *all* valid digests delivered via
[:Identity-Digest:] to match the response's decoded body. Since the server
controls both the body and the headers, it seems unnecessary to allow the
flexibility of allowing the asserted digests to match more than one resource
(as we do in client-initiated checks, which need to support servers' content
negotiation).
</div>
### `Signature` and `Signature-Input` Enforcement ### {#signature-enforcement}
<div algorithm="signature validation">
To <dfn abstract-op>verify `SRI` Message Signature assertions</dfn> given a
[=/request=] (|request|), and a [=/response=] (|response|), execute the
following steps. They return "`verified`" or "`failed`":
1. Let |inputs| be the result of [=header list/get a structured field value|getting=]
the [:Signature-Input:] header as a "<a for="structured header">`dictionary`</a>"
from |response|'s [=response/header list=].
2. Let |signatures| be the result of [=header list/get a structured field value|getting=]
the [:Signature:] header as a "<a for="structured header">`dictionary`</a>" from
|response|'s [=response/header list=].
3. [=map/iterate|For each=] |key| → |components| of |inputs|:
1. If |signatures| does not [=map/contain=] |key|, [=continue=].
2. If any of the following requirements for |components| are not met,
[=continue=]:
1. |components| is a parameterized [=structured header/Inner List=].
2. |components| [=list/size=] is 1.
3. |components|[0] [=string/is=] the [=string=] "`identity-digest`".
4. |components|[0] has a single [=structured header/parameter=]: <a>`sf`</a>.
3. Let |params| be |components| [=structured header/parameters=].
4. If any of the following requirements for |params| are not met, [=continue=]:
1. |params| [=map/does not contain=] <a>`alg`</a>.
2. |params| [=map/contains=] <a>`keyid`</a>, and its value is
a [=structured header/string=] which, when [=forgiving-base64 decoded=],
returns a [=byte sequence=] whose [=byte sequence/length=] is 32.
3. |params| [=map/contains=] <a>`tag`</a>, and its value
[=string/is=] the [=structured header/string=] "`sri`".
5. If |params| [=map/contains=] <a>`expires`</a>, and |params|["`expires`"]
is greater than the number of seconds between the [=Unix epoch=] and the
[=wall clock/unsafe current time=], return "`failed`".
ISSUE(34): Should we try to leave some flexibility here for user agents
to accept recently-expired signatures / deal with clock skew?
6. Let |signature-params| be the result of executing the algorithm in
[Section 2.3](https://www.rfc-editor.org/rfc/rfc9421.html#signature-params)
of [[!RFC9421]] on |components|.
7. Let |signature base| be the result of executing the algorithm in
[Section 2.5](https://www.rfc-editor.org/rfc/rfc9421.html#create-sig-input)
of [[!RFC9421]] on |request|, |response, and |signature-params|.
If this algorithm produces an error, return "`failed`".
Note: Errors here might represent invalid component or parameter names,
missing headers, etc.
8. Let |public key| be the result of [=forgiving-base64 decoding=]
|params|["`keyid`"].
9. Execute Ed25519's "`Verify`" algorithm as defined in
[Section 5.1.7](https://rfc-editor.org/rfc/rfc8032#section-5.1.7) of
[[!RFC8023]], to verify the signature |signatures|["`key`"] over the
message |signature base| using |public key|.
10. If verification failed, return "`failed`".
4. Return "`verified`".
Note: This is a reformulation and simplification of the steps described in
[Section 3.2](https://www.rfc-editor.org/rfc/rfc9421.html#name-verifying-a-signature)
of [[!RFC9421]], making the integration with the [[#profile]] described above
explict.
Note: This algorithm requires *all* valid signatures delivered with the response
to be verified in order to return "`verified`"
</div>
Deployment Scenarios {#deployment-scenarios}
=======================================
**This section is non-normative.**
Signature-based SRI is meant to be a general primitive that can be used in a wide variety of ways that we can't possibly exhaustively document. But below we document a few different scenarios for how signature-based SRI can be used to enable new functionality for the web.
Non-versioned third-party libraries {#deployment-scenario-3p}
-------------------------------------------
The web is built on composability and it is quite common to include JS from third-parties (e.g. analytics scripts or tools for [real user monitoring](https://en.wikipedia.org/wiki/Real_user_monitoring)). These scripts are often non-versioned to allow third-parties to continually update and improve these libraries. Signature-based SRI makes it possible to enable integrity validation for these libraries, to ensure that the included libraries are built and served in a trustworthy manner.
### Architectural Notes ### {#deployment-scenario-3p-notes}
In this deployment scenario, `third-party.com/library.js` would deploy signature-based SRI. `third-party.com` would then document that when including the library, reliant websites should specify `integrity="ed25519-[base64-encoded-public-key]"`.
If `third-party.com` offers multiple different libraries for different purposes, it is recommended to use isolated keys for each library. This ensures that an attacker can't swap in `third-party.com/foo.js` for `third-party.com/bar.js`.
Protecting first-party libraries {#deployment-scenario-1p}
-------------------------------------------
An alternate deployment scenario is a site using this to protect first-party resources. In many cases, hash-based SRI can work well for first-party use cases. But, some sites have deploy processes where they deploy the main-page separately from subresources, in which case it is possible for any hashes specified in the main-page to become out of date with the contents of subresources. Signature-based SRI makes it possible to enable integrity validation for these first-party resources without adding any constraints on how web apps are deployed.
Deployment Considerations {#deployment}
=======================================
**This section is non-normative.**
Key Management {#deployment-key-management}
-------------------------------------------
Key management is hard. This proposal doesn't change that.
It aims instead to be very lightweight. Perhaps it errs in that direction, but
the goal is to be the simplest possible mechanimsm that supports known
use-cases.
A different take on this proposal could be arbitrarily complex, replicating
aspects of the web PKI to chain trust, allow delegation, etc. That seems like
more than we need today, and substantially more work. Perhaps something small
is good enough?
Key Rotation {#deployment-key-rotation}
---------------------------------------
Since this design relies on websites pinning a specific public key in the
`integrity` attribute, this design does not easily support key rotation. If a
signing key is compromised, there is no easy way to rotate the key and ensure
that reliant websites check signatures against an updated public key.
For now, we think this is probably enough. If the key is compromised, the
security model falls back to the status quo web security model, meaning that
the impact of a compromised key is limited. In the future if this does turn
out to be a significant issue, we could also explore alternate designs that
do support key rotation. One simple proposal could be adding support for the
client to signal the requested public key in request headers, allowing
different parties to specify different public keys. A more complex proposal
could support automated key rotation.
Note: This proposal does support pinning multiple keys for a single
resource, so it will be possible to support rotation in a coordinated way
without requiring each entity to move in lockstep.
Key Discovery {#deployment-key-discovery}
-----------------------------------------