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
use self::{
tree::NestedTree,
types::{
certification::{AssetPath, HashTreePath, NestedTreeKey, RequestHash, WitnessResult},
http::{
build_ic_certificate_expression_from_headers, response_hash, HeaderField, FALLBACK_FILE,
},
},
};
use crate::asset_certification::types::http::build_ic_certificate_expression_header;
use ic_certification::merge_hash_trees;
use ic_representation_independent_hash::Value;
use serde::Serialize;
use sha2::Digest;
pub mod tree;
pub mod types;
pub use ic_certification::HashTree;
pub type CertifiedResponses = NestedTree<NestedTreeKey, Vec<u8>>;
impl CertifiedResponses {
/// Certifies a response for a number of paths with certification v2.
///
/// # Arguments
/// * `paths`: path(s) to the resource
/// * `status_code`: HTTP status code of the response
/// * `headers`: All certified headers. It is possible to respond with additional headers, but only the ones supplied in this argument are certified
/// * `body`: Response body. Ignored if `body_hash.is_some()`
/// * `body_hash`: Hash of the response body. If supplied the response body will not be hashed, which can save a lot of computation
///
/// # Return Value
/// * `Vec<HashTreePath>`: `HashTreePath`s corresponding to the supplied `paths`. Can be used to remove or re-insert certification for a specific response without having to re-compute the full path
pub fn certify_response(
&mut self,
paths: &[&str],
status_code: u16,
headers: &[(String, Value)],
body: &[u8],
body_hash: Option<[u8; 32]>,
) -> Vec<HashTreePath> {
let certificate_expression = build_ic_certificate_expression_from_headers(headers);
let request_hash = RequestHash::default(); // request certification currently not supported
let body_hash = body_hash.unwrap_or_else(|| sha2::Sha256::digest(body).into());
let response_hash = response_hash(headers, status_code, &body_hash);
paths
.iter()
.map(|path| {
let asset_path = AssetPath::from(path);
let hash_tree_path = asset_path.hash_tree_path(
&certificate_expression,
&request_hash,
response_hash,
);
self.certify_response_precomputed(&hash_tree_path);
hash_tree_path
})
.collect()
}
/// Certifies a response for a number of paths with certification v1.
///
/// REPLACES a previously certified response for any given path because v1 certification only supports one certified response per path.
///
/// # Arguments
/// * `paths`: path(s) to the resource
/// * `body`: Response body
/// * `body_hash`: Hash of the response body. If supplied the response body will not be hashed, which can save a lot of computation
pub fn certify_response_v1(
&mut self,
paths: &[&str],
body: &[u8],
body_hash: Option<[u8; 32]>,
) {
let body_hash = body_hash.unwrap_or_else(|| sha2::Sha256::digest(body).into());
let hash_tree_paths: Vec<HashTreePath> = paths
.iter()
.map(|path| {
let asset_path = AssetPath::from(path);
asset_path.asset_hash_path_v1()
})
.collect();
for path in hash_tree_paths.iter() {
self.insert(path.as_vec(), Vec::from(body_hash));
}
}
/// Certifies a response that can be used if no certified response is available for the requested path with certification v2.
///
/// # Arguments
/// * `status_code`: HTTP status code of the response
/// * `headers`: All certified headers. It is possible to respond with additional headers, but only the ones supplied in this argument are certified
/// * `body`: Response body. Ignored if `body_hash.is_some()`
/// * `body_hash`: Hash of the response body. If supplied the response body will not be hashed, which can save a lot of computation
///
/// # Return Value
/// * `HashTreePath`: `HashTreePath` corresponding to the supplied response. Can be used to remove or re-insert certification for this specific response without having to re-compute the full path
pub fn certify_fallback_response(
&mut self,
status_code: u16,
headers: &[(String, Value)],
body: &[u8],
body_hash: Option<[u8; 32]>,
) -> HashTreePath {
let certificate_expression = build_ic_certificate_expression_from_headers(headers);
let cert_expr_header = build_ic_certificate_expression_header(&certificate_expression);
let cert_expr_header = (cert_expr_header.0, Value::String(cert_expr_header.1));
let mut certified_headers = Vec::from(headers);
certified_headers.push(cert_expr_header);
let request_hash = RequestHash::default(); // request certification currently not supported
let body_hash = body_hash.unwrap_or_else(|| sha2::Sha256::digest(body).into());
let response_hash = response_hash(&certified_headers, status_code, &body_hash);
let asset_path = AssetPath::fallback_path();
let hash_tree_path =
asset_path.hash_tree_path(&certificate_expression, &request_hash, response_hash);
self.certify_response_precomputed(&hash_tree_path);
hash_tree_path
}
/// Certifies a response that can be used if no certified response is available for the requested path with certification v1.
/// Alias for `self.certify_response_v1(&["/index.html"], body, body_hash)`.
///
/// REPLACES a previously certified response because v1 certification only supports one certified response per path.
pub fn certify_fallback_response_v1(&mut self, body: &[u8], body_hash: Option<[u8; 32]>) {
self.certify_response_v1(&[FALLBACK_FILE], body, body_hash)
}
/// Certifies a response. Expects a finished `HashTreePath`, skipping the (sometimes expensive) computation of the `HashTreePath`.
pub fn certify_response_precomputed(&mut self, path: &HashTreePath) {
self.insert(path.as_vec(), Vec::new());
}
/// Removes all certified responses for a path for certification v2
pub fn remove_responses_for_path(&mut self, path: &str) {
let key = AssetPath::from(path);
self.delete(key.asset_hash_path_root_v2().as_vec());
}
/// Removes the certified response for a path for certification v1
pub fn remove_responses_for_path_v1(&mut self, path: &str) {
let key = AssetPath::from(path);
self.delete(key.asset_hash_path_v1().as_vec());
}
/// Removes all certified fallback responses for certification v2
pub fn remove_fallback_responses(&mut self) {
self.delete(HashTreePath::not_found_base_path_v2().as_vec());
}
/// Removes the certified fallback response for certification v1
pub fn remove_fallback_responses_v1(&mut self) {
self.delete(HashTreePath::not_found_base_path_v1().as_vec());
}
/// Removes a specific response from the certified responses. Expects a finished `HashTreePath`, skipping the (sometimes expensive) computation of the `HashTreePath`.
pub fn remove_response_precomputed(&mut self, path: &HashTreePath) {
self.delete(path.as_vec());
}
/// If the path has certified responses this function creates a hash tree that proves...
/// * The path is part of the CertifiedResponses hash tree
///
/// The hash tree then includes certification for all valid responses for this path.
///
/// If the path has no certified responses this function creates a hash tree that proves...
/// * The absence of the path in the CertifiedResponses hash tree
/// * The presence/absence of a 404 response
///
/// The hash tree then includes certification for all valid responses for a 404 response.
///
/// # Return Value
/// `(found, tree)`
/// * `found`:
/// * WitnessResult::Found if `path` has a certified response.
/// * `WitnessResult::FallbackFound` if the path has no certified response, but the fallback path has.
/// * `WitnessResult::NoneFound` if both `path` and the fallback path have no certified response.
/// * `tree`: The `HashTree` as described above.
pub fn witness_path(&self, path: &str) -> (HashTree, WitnessResult) {
let path = AssetPath::from(path);
let hash_tree_path_root = path.asset_hash_path_root_v2();
if self.contains_path(hash_tree_path_root.as_vec()) {
(
self.witness(hash_tree_path_root.as_vec()),
WitnessResult::PathFound,
)
} else {
let absence_proof = self.witness(hash_tree_path_root.as_vec());
let fallback_paths = hash_tree_path_root.fallback_paths_v2();
let combined_proof =
fallback_paths
.into_iter()
.fold(absence_proof, |accumulator, path| {
let new_proof = self.witness(path.as_vec());
merge_hash_trees(accumulator, new_proof)
});
let fallback_path = HashTreePath::not_found_base_path_v2();
if self.contains_path(fallback_path.as_vec()) {
(combined_proof, WitnessResult::FallbackFound)
} else {
(combined_proof, WitnessResult::NoneFound)
}
}
}
pub fn expr_path(&self, path: &str) -> String {
let path = AssetPath::from(path);
let hash_tree_path_root = path.asset_hash_path_root_v2();
if self.contains_path(hash_tree_path_root.as_vec()) {
path.asset_hash_path_root_v2().expr_path()
} else {
HashTreePath::not_found_base_path_v2().expr_path()
}
}
/// If the path has certified responses this function creates a hash tree that proves...
/// * The path is part of the CertifiedResponses hash tree
///
/// The hash tree then includes certification the valid certification v1 response for this path.
///
/// If the path has no certified response this function creates a hash tree that proves...
/// * The absence of the path in the CertifiedResponses hash tree
/// * The presence/absence of a 404 response
///
/// The hash tree then includes certification for the valid certification v1 response for a 404 response.
///
/// # Arguments
/// * `path`: The path to generate a proof of presence/absence for
/// * `not_found_path`: The defined fall-back path to use if `path` is not certified by v1
///
/// # Return Value
/// `(found, tree)`
/// * `found`:
/// * WitnessResult::Found if `path` has a certified response.
/// * `WitnessResult::FallbackFound` if the path has no certified response, but the fallback path has.
/// * `WitnessResult::NoneFound` if both `path` and the fallback path have no certified response.
/// * `tree`: The `HashTree` as described above.
pub fn witness_path_v1(&self, path: &str) -> (HashTree, WitnessResult) {
let path = AssetPath::from(path);
let hash_tree_path = path.asset_hash_path_v1();
if self.contains_leaf(hash_tree_path.as_vec()) {
(
self.witness(hash_tree_path.as_vec()),
WitnessResult::PathFound,
)
} else {
let fallback_path = AssetPath::fallback_path_v1().asset_hash_path_v1();
let absence_proof = self.witness(hash_tree_path.as_vec());
let not_found_proof = self.witness(fallback_path.as_vec());
let combined_proof = merge_hash_trees(absence_proof, not_found_proof);
if self.contains_leaf(fallback_path.as_vec()) {
(combined_proof, WitnessResult::FallbackFound)
} else {
(combined_proof, WitnessResult::NoneFound)
}
}
}
/// Same as `witness_path`, but produces a header that can be returned as a `HttpResponse` header instead of a witness `HashTree`.
pub fn witness_to_header(
&self,
path: &str,
certificate: &[u8],
) -> (HeaderField, WitnessResult) {
let (witness, witness_result) = self.witness_path(path);
let expr_path = self.expr_path(path);
let mut serializer = serde_cbor::ser::Serializer::new(vec![]);
serializer.self_describe().unwrap();
witness.serialize(&mut serializer).unwrap();
(
(
"IC-Certificate".to_string(),
String::from("version=2, ")
+ "certificate=:"
+ &base64::encode(certificate)
+ ":, tree=:"
+ &base64::encode(serializer.into_inner())
+ ":, expr_path=:"
+ &expr_path
+ ":",
),
witness_result,
)
}
/// Same as `witness_path`, but produces a header that can be returned as a `HttpResponse` header instead of a witness `HashTree`.
pub fn witness_to_header_v1(
&self,
path: &str,
certificate: &[u8],
) -> (HeaderField, WitnessResult) {
let (witness, witness_result) = self.witness_path_v1(path);
let mut serializer = serde_cbor::ser::Serializer::new(vec![]);
serializer.self_describe().unwrap();
witness.serialize(&mut serializer).unwrap();
(
(
"IC-Certificate".to_string(),
String::from("certificate=:")
+ &base64::encode(certificate)
+ ":, tree=:"
+ &base64::encode(serializer.into_inner())
+ ":",
),
witness_result,
)
}
}