├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── live_stream_domains.rs ├── parse_sct_list_from_cert.rs └── simple_client │ ├── save_db_init.sql │ └── simple_client.rs ├── fuzz ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── corpus │ ├── ctclient_from_bytes │ │ └── valid-input.1 │ ├── leaf_extra_data_parsing │ │ ├── extra.precert │ │ └── extra.x509leaf │ └── leaf_parsing │ │ ├── le.precert │ │ └── le.x509leaf └── fuzz_targets │ ├── ctclient_from_bytes.rs │ ├── extra.precert │ ├── extra.x509leaf │ ├── le.precert │ ├── le.x509leaf │ ├── leaf_extra_data_parsing.rs │ └── leaf_parsing.rs ├── readme_img ├── demo.gif ├── example_parse_sct_list_from_cert.png └── example_simple_client.png └── src ├── certutils.rs ├── google_log_list.rs ├── internal ├── consistency.rs ├── digitally_signed_struct.rs ├── getentries.rs ├── inclusion.rs ├── leaf.rs ├── mod.rs └── openssl_ffi.rs ├── jsons.rs ├── lib.rs ├── long_tests.rs ├── sct.rs ├── sth.rs ├── test_data ├── precert-signing-ca.pem └── precert-signing.ca.tbs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | save.db 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler" 5 | version = "0.2.3" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "aho-corasick" 10 | version = "0.7.13" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "async-compression" 18 | version = "0.3.5" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "flate2 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 26 | ] 27 | 28 | [[package]] 29 | name = "atty" 30 | version = "0.2.14" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | dependencies = [ 33 | "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "autocfg" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "base64" 45 | version = "0.12.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | 48 | [[package]] 49 | name = "bitflags" 50 | version = "1.2.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | 53 | [[package]] 54 | name = "bumpalo" 55 | version = "3.4.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | 58 | [[package]] 59 | name = "bytes" 60 | version = "0.5.5" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "loom 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 64 | ] 65 | 66 | [[package]] 67 | name = "cc" 68 | version = "1.0.58" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | 71 | [[package]] 72 | name = "cfg-if" 73 | version = "0.1.10" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | 76 | [[package]] 77 | name = "core-foundation" 78 | version = "0.7.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | dependencies = [ 81 | "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 83 | ] 84 | 85 | [[package]] 86 | name = "core-foundation-sys" 87 | version = "0.7.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | 90 | [[package]] 91 | name = "crc32fast" 92 | version = "1.2.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | dependencies = [ 95 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "ctclient" 100 | version = "0.4.5" 101 | dependencies = [ 102 | "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)", 108 | "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", 109 | "reqwest 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", 110 | "rusqlite 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)", 111 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 114 | ] 115 | 116 | [[package]] 117 | name = "dtoa" 118 | version = "0.4.6" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | 121 | [[package]] 122 | name = "encoding_rs" 123 | version = "0.8.23" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 127 | ] 128 | 129 | [[package]] 130 | name = "env_logger" 131 | version = "0.7.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | dependencies = [ 134 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "fallible-iterator" 143 | version = "0.2.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | 146 | [[package]] 147 | name = "fallible-streaming-iterator" 148 | version = "0.1.9" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | 151 | [[package]] 152 | name = "flate2" 153 | version = "1.0.16" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | dependencies = [ 156 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 157 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "fnv" 164 | version = "1.0.7" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | 167 | [[package]] 168 | name = "foreign-types" 169 | version = "0.3.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "foreign-types-shared" 177 | version = "0.1.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | 180 | [[package]] 181 | name = "fuchsia-zircon" 182 | version = "0.3.3" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | dependencies = [ 185 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 186 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 187 | ] 188 | 189 | [[package]] 190 | name = "fuchsia-zircon-sys" 191 | version = "0.3.3" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | 194 | [[package]] 195 | name = "futures-channel" 196 | version = "0.3.5" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | dependencies = [ 199 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "futures-core" 204 | version = "0.3.5" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | 207 | [[package]] 208 | name = "futures-io" 209 | version = "0.3.5" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "futures-sink" 214 | version = "0.3.5" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "futures-task" 219 | version = "0.3.5" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | dependencies = [ 222 | "once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "futures-util" 227 | version = "0.3.5" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 231 | "futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 232 | "futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 233 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 234 | "pin-project 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)", 235 | "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 236 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 237 | ] 238 | 239 | [[package]] 240 | name = "generator" 241 | version = "0.6.21" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", 245 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 246 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 247 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 249 | ] 250 | 251 | [[package]] 252 | name = "getrandom" 253 | version = "0.1.14" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | dependencies = [ 256 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 257 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 258 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 259 | ] 260 | 261 | [[package]] 262 | name = "h2" 263 | version = "0.2.5" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | dependencies = [ 266 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 270 | "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 271 | "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 272 | "indexmap 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 273 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 274 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 275 | "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 276 | "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 277 | ] 278 | 279 | [[package]] 280 | name = "hermit-abi" 281 | version = "0.1.15" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | dependencies = [ 284 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 285 | ] 286 | 287 | [[package]] 288 | name = "http" 289 | version = "0.2.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | dependencies = [ 292 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 295 | ] 296 | 297 | [[package]] 298 | name = "http-body" 299 | version = "0.3.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | dependencies = [ 302 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 304 | ] 305 | 306 | [[package]] 307 | name = "httparse" 308 | version = "1.3.4" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | 311 | [[package]] 312 | name = "humantime" 313 | version = "1.3.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | dependencies = [ 316 | "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 317 | ] 318 | 319 | [[package]] 320 | name = "hyper" 321 | version = "0.13.6" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | dependencies = [ 324 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 325 | "futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 328 | "h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 329 | "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 330 | "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 331 | "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 332 | "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 333 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 334 | "pin-project 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)", 335 | "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 340 | ] 341 | 342 | [[package]] 343 | name = "hyper-tls" 344 | version = "0.4.3" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | dependencies = [ 347 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 348 | "hyper 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 352 | ] 353 | 354 | [[package]] 355 | name = "idna" 356 | version = "0.2.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", 362 | ] 363 | 364 | [[package]] 365 | name = "indexmap" 366 | version = "1.4.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | dependencies = [ 369 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 370 | ] 371 | 372 | [[package]] 373 | name = "iovec" 374 | version = "0.1.4" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | dependencies = [ 377 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 378 | ] 379 | 380 | [[package]] 381 | name = "itoa" 382 | version = "0.4.6" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | 385 | [[package]] 386 | name = "js-sys" 387 | version = "0.3.41" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | dependencies = [ 390 | "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 391 | ] 392 | 393 | [[package]] 394 | name = "kernel32-sys" 395 | version = "0.2.2" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | dependencies = [ 398 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 400 | ] 401 | 402 | [[package]] 403 | name = "lazy_static" 404 | version = "1.4.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | 407 | [[package]] 408 | name = "libc" 409 | version = "0.2.72" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | 412 | [[package]] 413 | name = "libsqlite3-sys" 414 | version = "0.18.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | dependencies = [ 417 | "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", 418 | "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 419 | ] 420 | 421 | [[package]] 422 | name = "linked-hash-map" 423 | version = "0.5.3" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | 426 | [[package]] 427 | name = "log" 428 | version = "0.4.8" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | dependencies = [ 431 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 432 | ] 433 | 434 | [[package]] 435 | name = "loom" 436 | version = "0.3.4" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | dependencies = [ 439 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "generator 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", 441 | "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 442 | ] 443 | 444 | [[package]] 445 | name = "lru-cache" 446 | version = "0.1.2" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | dependencies = [ 449 | "linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 450 | ] 451 | 452 | [[package]] 453 | name = "matches" 454 | version = "0.1.8" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | 457 | [[package]] 458 | name = "memchr" 459 | version = "2.3.3" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | 462 | [[package]] 463 | name = "mime" 464 | version = "0.3.16" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | 467 | [[package]] 468 | name = "mime_guess" 469 | version = "2.0.3" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | dependencies = [ 472 | "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 473 | "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 474 | ] 475 | 476 | [[package]] 477 | name = "miniz_oxide" 478 | version = "0.4.0" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | dependencies = [ 481 | "adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 482 | ] 483 | 484 | [[package]] 485 | name = "mio" 486 | version = "0.6.22" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | dependencies = [ 489 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 490 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 491 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 492 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 493 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 494 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 495 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 496 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 497 | "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 498 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 499 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 500 | ] 501 | 502 | [[package]] 503 | name = "miow" 504 | version = "0.2.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | dependencies = [ 507 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 508 | "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", 509 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 510 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 511 | ] 512 | 513 | [[package]] 514 | name = "native-tls" 515 | version = "0.2.4" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | dependencies = [ 518 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 519 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 521 | "openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)", 522 | "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 523 | "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", 524 | "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", 525 | "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 526 | "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 527 | "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 528 | ] 529 | 530 | [[package]] 531 | name = "net2" 532 | version = "0.2.34" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | dependencies = [ 535 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 536 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 537 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 538 | ] 539 | 540 | [[package]] 541 | name = "num_cpus" 542 | version = "1.13.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | dependencies = [ 545 | "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 546 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 547 | ] 548 | 549 | [[package]] 550 | name = "once_cell" 551 | version = "1.4.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | 554 | [[package]] 555 | name = "openssl" 556 | version = "0.10.30" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | dependencies = [ 559 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 562 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 563 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 564 | "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", 565 | ] 566 | 567 | [[package]] 568 | name = "openssl-probe" 569 | version = "0.1.2" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | 572 | [[package]] 573 | name = "openssl-sys" 574 | version = "0.9.58" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | dependencies = [ 577 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 578 | "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", 579 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 580 | "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", 581 | "vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", 582 | ] 583 | 584 | [[package]] 585 | name = "percent-encoding" 586 | version = "2.1.0" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | 589 | [[package]] 590 | name = "pin-project" 591 | version = "0.4.22" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | dependencies = [ 594 | "pin-project-internal 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)", 595 | ] 596 | 597 | [[package]] 598 | name = "pin-project-internal" 599 | version = "0.4.22" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | dependencies = [ 602 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 603 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 604 | "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", 605 | ] 606 | 607 | [[package]] 608 | name = "pin-project-lite" 609 | version = "0.1.7" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | 612 | [[package]] 613 | name = "pin-utils" 614 | version = "0.1.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | 617 | [[package]] 618 | name = "pkg-config" 619 | version = "0.3.18" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | 622 | [[package]] 623 | name = "ppv-lite86" 624 | version = "0.2.8" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | 627 | [[package]] 628 | name = "proc-macro2" 629 | version = "1.0.18" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | dependencies = [ 632 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 633 | ] 634 | 635 | [[package]] 636 | name = "quick-error" 637 | version = "1.2.3" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | 640 | [[package]] 641 | name = "quote" 642 | version = "1.0.7" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | dependencies = [ 645 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 646 | ] 647 | 648 | [[package]] 649 | name = "rand" 650 | version = "0.7.3" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | dependencies = [ 653 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 654 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 655 | "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 656 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 657 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 658 | ] 659 | 660 | [[package]] 661 | name = "rand_chacha" 662 | version = "0.2.2" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | dependencies = [ 665 | "ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 666 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 667 | ] 668 | 669 | [[package]] 670 | name = "rand_core" 671 | version = "0.5.1" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | dependencies = [ 674 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 675 | ] 676 | 677 | [[package]] 678 | name = "rand_hc" 679 | version = "0.2.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | dependencies = [ 682 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 683 | ] 684 | 685 | [[package]] 686 | name = "redox_syscall" 687 | version = "0.1.57" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | 690 | [[package]] 691 | name = "regex" 692 | version = "1.3.9" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | dependencies = [ 695 | "aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", 696 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 697 | "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", 698 | "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 699 | ] 700 | 701 | [[package]] 702 | name = "regex-syntax" 703 | version = "0.6.18" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | 706 | [[package]] 707 | name = "remove_dir_all" 708 | version = "0.5.3" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | dependencies = [ 711 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 712 | ] 713 | 714 | [[package]] 715 | name = "reqwest" 716 | version = "0.10.6" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | dependencies = [ 719 | "async-compression 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 720 | "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 721 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 722 | "encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", 723 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 724 | "futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 725 | "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 726 | "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 727 | "hyper 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)", 728 | "hyper-tls 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 729 | "js-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 730 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 731 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 732 | "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 733 | "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 734 | "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 735 | "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 736 | "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 737 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 738 | "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", 739 | "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 740 | "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 741 | "tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 742 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 743 | "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 744 | "wasm-bindgen-futures 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", 745 | "web-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 746 | "winreg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 747 | ] 748 | 749 | [[package]] 750 | name = "rusqlite" 751 | version = "0.23.1" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | dependencies = [ 754 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 755 | "fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 756 | "fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 757 | "libsqlite3-sys 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", 758 | "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 759 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 760 | "smallvec 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 761 | "time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 762 | ] 763 | 764 | [[package]] 765 | name = "rustc_version" 766 | version = "0.2.3" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | dependencies = [ 769 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 770 | ] 771 | 772 | [[package]] 773 | name = "ryu" 774 | version = "1.0.5" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | 777 | [[package]] 778 | name = "schannel" 779 | version = "0.1.19" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | dependencies = [ 782 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 783 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 784 | ] 785 | 786 | [[package]] 787 | name = "scoped-tls" 788 | version = "0.1.2" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | 791 | [[package]] 792 | name = "security-framework" 793 | version = "0.4.4" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | dependencies = [ 796 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 797 | "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 798 | "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 799 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 800 | "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 801 | ] 802 | 803 | [[package]] 804 | name = "security-framework-sys" 805 | version = "0.4.3" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | dependencies = [ 808 | "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 809 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 810 | ] 811 | 812 | [[package]] 813 | name = "semver" 814 | version = "0.9.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | dependencies = [ 817 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 818 | ] 819 | 820 | [[package]] 821 | name = "semver-parser" 822 | version = "0.7.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | 825 | [[package]] 826 | name = "serde" 827 | version = "1.0.114" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | dependencies = [ 830 | "serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 831 | ] 832 | 833 | [[package]] 834 | name = "serde_derive" 835 | version = "1.0.114" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | dependencies = [ 838 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 839 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 840 | "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", 841 | ] 842 | 843 | [[package]] 844 | name = "serde_json" 845 | version = "1.0.56" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | dependencies = [ 848 | "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 849 | "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 850 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 851 | ] 852 | 853 | [[package]] 854 | name = "serde_urlencoded" 855 | version = "0.6.1" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | dependencies = [ 858 | "dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 859 | "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 860 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 861 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 862 | ] 863 | 864 | [[package]] 865 | name = "slab" 866 | version = "0.4.2" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | 869 | [[package]] 870 | name = "smallvec" 871 | version = "1.4.1" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | 874 | [[package]] 875 | name = "socket2" 876 | version = "0.3.12" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | dependencies = [ 879 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 880 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 881 | "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", 882 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 883 | ] 884 | 885 | [[package]] 886 | name = "syn" 887 | version = "1.0.33" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | dependencies = [ 890 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 891 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 892 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 893 | ] 894 | 895 | [[package]] 896 | name = "tempfile" 897 | version = "3.1.0" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | dependencies = [ 900 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 901 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 902 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 903 | "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", 904 | "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 905 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 906 | ] 907 | 908 | [[package]] 909 | name = "termcolor" 910 | version = "1.1.0" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | dependencies = [ 913 | "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 914 | ] 915 | 916 | [[package]] 917 | name = "thread_local" 918 | version = "1.0.1" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | dependencies = [ 921 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 922 | ] 923 | 924 | [[package]] 925 | name = "time" 926 | version = "0.1.43" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | dependencies = [ 929 | "libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)", 930 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 931 | ] 932 | 933 | [[package]] 934 | name = "tinyvec" 935 | version = "0.3.3" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | 938 | [[package]] 939 | name = "tokio" 940 | version = "0.2.21" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | dependencies = [ 943 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 944 | "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 945 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 946 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 947 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 948 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 949 | "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", 950 | "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", 951 | "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 952 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 953 | ] 954 | 955 | [[package]] 956 | name = "tokio-tls" 957 | version = "0.3.1" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | dependencies = [ 960 | "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 961 | "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 962 | ] 963 | 964 | [[package]] 965 | name = "tokio-util" 966 | version = "0.3.1" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | dependencies = [ 969 | "bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", 970 | "futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 971 | "futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 972 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 973 | "pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 974 | "tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 975 | ] 976 | 977 | [[package]] 978 | name = "tower-service" 979 | version = "0.3.0" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | 982 | [[package]] 983 | name = "try-lock" 984 | version = "0.2.3" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | 987 | [[package]] 988 | name = "unicase" 989 | version = "2.6.0" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | dependencies = [ 992 | "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", 993 | ] 994 | 995 | [[package]] 996 | name = "unicode-bidi" 997 | version = "0.3.4" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | dependencies = [ 1000 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "unicode-normalization" 1005 | version = "0.1.13" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | dependencies = [ 1008 | "tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "unicode-xid" 1013 | version = "0.2.1" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | 1016 | [[package]] 1017 | name = "url" 1018 | version = "2.1.1" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | dependencies = [ 1021 | "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1022 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1023 | "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "vcpkg" 1028 | version = "0.2.10" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | 1031 | [[package]] 1032 | name = "version_check" 1033 | version = "0.9.2" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | 1036 | [[package]] 1037 | name = "want" 1038 | version = "0.3.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | dependencies = [ 1041 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1042 | "try-lock 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "wasi" 1047 | version = "0.9.0+wasi-snapshot-preview1" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | 1050 | [[package]] 1051 | name = "wasm-bindgen" 1052 | version = "0.2.64" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | dependencies = [ 1055 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 1056 | "serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)", 1057 | "serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)", 1058 | "wasm-bindgen-macro 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "wasm-bindgen-backend" 1063 | version = "0.2.64" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | dependencies = [ 1066 | "bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1067 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1068 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1069 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 1070 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 1071 | "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", 1072 | "wasm-bindgen-shared 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "wasm-bindgen-futures" 1077 | version = "0.4.14" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | dependencies = [ 1080 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 1081 | "js-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 1082 | "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1083 | "web-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "wasm-bindgen-macro" 1088 | version = "0.2.64" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | dependencies = [ 1091 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 1092 | "wasm-bindgen-macro-support 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "wasm-bindgen-macro-support" 1097 | version = "0.2.64" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | dependencies = [ 1100 | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", 1101 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 1102 | "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", 1103 | "wasm-bindgen-backend 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1104 | "wasm-bindgen-shared 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "wasm-bindgen-shared" 1109 | version = "0.2.64" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | 1112 | [[package]] 1113 | name = "web-sys" 1114 | version = "0.3.41" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | dependencies = [ 1117 | "js-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)", 1118 | "wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "winapi" 1123 | version = "0.2.8" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | 1126 | [[package]] 1127 | name = "winapi" 1128 | version = "0.3.9" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | dependencies = [ 1131 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1132 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "winapi-build" 1137 | version = "0.1.1" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | 1140 | [[package]] 1141 | name = "winapi-i686-pc-windows-gnu" 1142 | version = "0.4.0" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | 1145 | [[package]] 1146 | name = "winapi-util" 1147 | version = "0.1.5" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | dependencies = [ 1150 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "winapi-x86_64-pc-windows-gnu" 1155 | version = "0.4.0" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | 1158 | [[package]] 1159 | name = "winreg" 1160 | version = "0.7.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | dependencies = [ 1163 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "ws2_32-sys" 1168 | version = "0.2.1" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | dependencies = [ 1171 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 1172 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1173 | ] 1174 | 1175 | [metadata] 1176 | "checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" 1177 | "checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 1178 | "checksum async-compression 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9021768bcce77296b64648cc7a7460e3df99979b97ed5c925c38d1cc83778d98" 1179 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 1180 | "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 1181 | "checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 1182 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 1183 | "checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 1184 | "checksum bytes 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" 1185 | "checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" 1186 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 1187 | "checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 1188 | "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 1189 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 1190 | "checksum dtoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" 1191 | "checksum encoding_rs 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" 1192 | "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 1193 | "checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 1194 | "checksum fallible-streaming-iterator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 1195 | "checksum flate2 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "68c90b0fc46cf89d227cc78b40e494ff81287a92dd07631e5af0d06fe3cf885e" 1196 | "checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 1197 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 1198 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 1199 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 1200 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 1201 | "checksum futures-channel 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 1202 | "checksum futures-core 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 1203 | "checksum futures-io 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 1204 | "checksum futures-sink 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 1205 | "checksum futures-task 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 1206 | "checksum futures-util 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 1207 | "checksum generator 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68" 1208 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 1209 | "checksum h2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" 1210 | "checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 1211 | "checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" 1212 | "checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 1213 | "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 1214 | "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 1215 | "checksum hyper 0.13.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" 1216 | "checksum hyper-tls 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" 1217 | "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 1218 | "checksum indexmap 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" 1219 | "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 1220 | "checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 1221 | "checksum js-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)" = "c4b9172132a62451e56142bff9afc91c8e4a4500aa5b847da36815b63bfda916" 1222 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 1223 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1224 | "checksum libc 0.2.72 (registry+https://github.com/rust-lang/crates.io-index)" = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" 1225 | "checksum libsqlite3-sys 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd" 1226 | "checksum linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 1227 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 1228 | "checksum loom 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7" 1229 | "checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 1230 | "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1231 | "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 1232 | "checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1233 | "checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 1234 | "checksum miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" 1235 | "checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 1236 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 1237 | "checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" 1238 | "checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 1239 | "checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 1240 | "checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 1241 | "checksum openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)" = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" 1242 | "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 1243 | "checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" 1244 | "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1245 | "checksum pin-project 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" 1246 | "checksum pin-project-internal 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" 1247 | "checksum pin-project-lite 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" 1248 | "checksum pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1249 | "checksum pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" 1250 | "checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 1251 | "checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 1252 | "checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 1253 | "checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 1254 | "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1255 | "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1256 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1257 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1258 | "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 1259 | "checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 1260 | "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 1261 | "checksum remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1262 | "checksum reqwest 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" 1263 | "checksum rusqlite 0.23.1 (registry+https://github.com/rust-lang/crates.io-index)" = "45d0fd62e1df63d254714e6cb40d0a0e82e7a1623e7a27f679d851af092ae58b" 1264 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 1265 | "checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1266 | "checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1267 | "checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" 1268 | "checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" 1269 | "checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" 1270 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 1271 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 1272 | "checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 1273 | "checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 1274 | "checksum serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)" = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" 1275 | "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" 1276 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1277 | "checksum smallvec 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" 1278 | "checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" 1279 | "checksum syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 1280 | "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 1281 | "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 1282 | "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1283 | "checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1284 | "checksum tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" 1285 | "checksum tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" 1286 | "checksum tokio-tls 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" 1287 | "checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 1288 | "checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 1289 | "checksum try-lock 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1290 | "checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1291 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1292 | "checksum unicode-normalization 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" 1293 | "checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1294 | "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1295 | "checksum vcpkg 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 1296 | "checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1297 | "checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1298 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1299 | "checksum wasm-bindgen 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "6a634620115e4a229108b71bde263bb4220c483b3f07f5ba514ee8d15064c4c2" 1300 | "checksum wasm-bindgen-backend 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "3e53963b583d18a5aa3aaae4b4c1cb535218246131ba22a71f05b518098571df" 1301 | "checksum wasm-bindgen-futures 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dba48d66049d2a6cc8488702e7259ab7afc9043ad0dc5448444f46f2a453b362" 1302 | "checksum wasm-bindgen-macro 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8" 1303 | "checksum wasm-bindgen-macro-support 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75" 1304 | "checksum wasm-bindgen-shared 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)" = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae" 1305 | "checksum web-sys 0.3.41 (registry+https://github.com/rust-lang/crates.io-index)" = "863539788676619aac1a23e2df3655e96b32b0e05eb72ca34ba045ad573c625d" 1306 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1307 | "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1308 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1309 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1310 | "checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1311 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1312 | "checksum winreg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1313 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1314 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctclient" 3 | description = "Certificate Transparency Log client suitable for monitoring, quick SCT validation, gossiping, etc." 4 | license = "MIT" 5 | repository = "https://github.com/micromaomao/ctclient" 6 | version = "0.4.5" 7 | authors = ["Mao Wtm "] 8 | edition = "2018" 9 | documentation = "https://docs.rs/ctclient" 10 | readme = "README.md" 11 | 12 | [[example]] 13 | name = "simple_client" 14 | path = "examples/simple_client/simple_client.rs" 15 | 16 | [dependencies] 17 | reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "native-tls", "gzip", "json"] } 18 | serde = { version = "1.0.97", features = ["derive"] } 19 | serde_json = "1.0.40" 20 | serde_urlencoded = "0.6.1" 21 | base64 = "0.12.1" 22 | openssl = "0.10.24" 23 | foreign-types = "0.3.2" 24 | openssl-sys = "0.9.56" 25 | log = "0.4.7" 26 | lazy_static = "1.4.0" 27 | 28 | [dev-dependencies] 29 | # for the example binary 30 | env_logger = "0.7.1" 31 | rusqlite = "0.23.1" 32 | 33 | [build-dependencies] 34 | openssl-sys = "0.9.56" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔒 [Certificate Transparency](https://www.certificate-transparency.org/) Log client library 2 | 3 | [![live stream domain video demo](./readme_img/demo.gif)](./examples/live_stream_domains.rs) 4 | 5 | ![Crates.io](https://img.shields.io/crates/l/ctclient) [![Crates.io](https://img.shields.io/crates/v/ctclient)](https://crates.io/crates/ctclient) [![doc link](https://docs.rs/ctclient/badge.svg)](https://docs.rs/ctclient) 6 | 7 | Certificate Transparency Log client suitable for monitoring, quick SCT validation, gossiping, etc. 8 | 9 | (Not a full-fledged client with UI and everything - will work on that later. This is just a library to make your own client with.) 10 | 11 | ## Build requirement 12 | 13 | OpenSSL >= 1.1.0 14 | 15 | ## Features 16 | 17 | * [Monitor tree head update and certificates](https://docs.rs/ctclient/0.4/ctclient/struct.CTClient.html) 18 | * Verify consistency and inclusion proof (automatically or via [low level API](https://docs.rs/ctclient/0.4/ctclient/internal/index.html#functions)) 19 | * Verify Signed Tree Head (STH) and Signed Certificate Timestamp (SCT), and [fetch and verify inclusion proof to defend the SCT](https://docs.rs/ctclient/0.4/ctclient/struct.CTClient.html#method.check_inclusion_proof_for_sct). 20 | * More low level API to [mess with leaf data](https://docs.rs/ctclient/0.4/ctclient/internal/struct.Leaf.html#fields), [proof construction](https://docs.rs/ctclient/0.4/ctclient/internal/fn.consistency_proof_parts.html), etc. 21 | * [Extract SCT from certificate](https://docs.rs/ctclient/0.4/ctclient/struct.SignedCertificateTimestamp.html#method.from_cert_sct_extension) 22 | * Lots of comment in code intended as reference for other hackers. 23 | 24 | ## TODOs 25 | 26 | * **Implement gossiping protocols** 27 | * Use async IO (currently all API requests are blocking) 28 | * A helper to monitor multiple logs simultaneously 29 | * Certificate submission 30 | * More test coverage 31 | 32 | ## Examples & DEMOs 33 | 34 | Note that you can run those by cargo run --example name 35 | 36 | * `examples/parse_sct_list_from_cert.rs`: Parse a certificate with a "CT Precertificate SCTs" extension and print out the SCTs. Also check that the logs can provide an inclusion proof for those leafs based on the latest tree head. 37 | 38 | ![screenshot](readme_img/example_parse_sct_list_from_cert.png) 39 | 40 | * `examples/live_stream_domains.rs`: Read out certificates as they are published by a log and print out the CA and domain names. 41 | 42 | DEMO at the top of this README. 43 | 44 | * `examples/simple_client/simple_client.rs`: A simple SQLite-backed CT log client monitoring a single log. 45 | 46 | * Check that the tree is consistent (extend-only) each time a new tree head is received. 47 | * Download and inspect all certificates searching for a hard-coded domain name. 48 | * Store tree heads and matched certificates in SQLite database. 49 | * Intended to be a base on which more sophisticated clients can be built. 50 | 51 | ![screenshot](readme_img/example_simple_client.png) 52 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let openssl_version = unsafe { openssl_sys::OpenSSL_version_num() }; 3 | if openssl_version < 0x010100000 { 4 | eprintln!("This crate must be linked with OpenSSL version >= 1.1.0."); 5 | std::process::exit(1); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/live_stream_domains.rs: -------------------------------------------------------------------------------- 1 | use ctclient::{CTClient, certutils}; 2 | use openssl::x509::X509; 3 | use std::io::Write; 4 | 5 | fn main() { 6 | env_logger::init(); 7 | 8 | if std::env::args_os().len() != 1 { 9 | eprintln!("Expected no arguments."); 10 | std::process::exit(1); 11 | } 12 | 13 | // URL and public key copy-pasted from https://www.gstatic.com/ct/log_list/v2/all_logs_list.json . 14 | // Google's CT log updates very quickly so we use it here. 15 | let public_key = base64::decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Tx2p1yKY4015NyIYvdrk36es0uAc1zA4PQ+TGRY+3ZjUTIYY9Wyu+3q/147JG4vNVKLtDWarZwVqGkg6lAYzA==").unwrap(); 16 | const URL: &str = "https://ct.googleapis.com/logs/argon2020/"; 17 | let mut client = CTClient::new_from_latest_th(URL, &public_key).unwrap(); 18 | loop { 19 | let update_result = client.update(Some(|certs: &[X509]| { 20 | let leaf = &certs[0]; 21 | let ca = &certs[1]; 22 | let canames = certutils::get_common_names(ca).unwrap(); 23 | let caname = &canames[0]; 24 | if let Ok(domains) = certutils::get_dns_names(leaf) { 25 | print!("{}: ", caname); 26 | let mut first = true; 27 | for d in domains.into_iter() { 28 | if !first { 29 | print!(", "); 30 | } 31 | print!("{}", d); 32 | first = false; 33 | } 34 | print!("\n"); 35 | } 36 | })); 37 | if update_result.is_err() { 38 | eprintln!("Error: {}", update_result.unwrap_err()); 39 | } 40 | std::io::stdout().flush().unwrap(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/parse_sct_list_from_cert.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | use std::time::{Duration, SystemTime}; 3 | 4 | use openssl::x509::X509; 5 | 6 | use ctclient::CTClient; 7 | use ctclient::google_log_list::LogList; 8 | use ctclient::utils::u8_to_hex; 9 | 10 | fn main() { 11 | env_logger::init(); 12 | 13 | let args: Vec<_> = std::env::args_os().collect(); 14 | if args.len() != 2 { 15 | eprintln!("Expected 1 argument: chain.pem"); 16 | exit(1); 17 | } 18 | let pem_path = args.into_iter().nth(1).unwrap(); 19 | let chain = X509::stack_from_pem(&std::fs::read(pem_path).expect("Unable to read pem")).expect("Unable to parse pem"); 20 | if chain.len() < 2 { 21 | eprintln!("Expected at least 2 certs."); 22 | exit(1); 23 | } 24 | let sct_list = ctclient::SignedCertificateTimestamp::from_cert_sct_extension(chain[0].as_ref(), chain[1].as_ref()).expect("Unable to parse sct list"); 25 | if sct_list.is_empty() { 26 | println!("Did not found any SCTs in the certificate."); 27 | exit(0); 28 | } 29 | let ll = LogList::get().expect("Unable to fetch log list from Google."); 30 | for (i, sct) in sct_list.iter().enumerate() { 31 | println!("SCT {}:", i + 1); 32 | let log_id_b64 = base64::encode(&sct.log_id); 33 | println!(" log_id = {}", log_id_b64); 34 | let timestamp = sct.timestamp; 35 | let time = SystemTime::UNIX_EPOCH.checked_add(Duration::from_millis(timestamp)).unwrap(); 36 | println!(" timestamp = {} ({} days ago)", timestamp, (time.elapsed().unwrap().as_secs_f32() / 60f32 / 60f32 / 24f32).round()); 37 | let leaf_hash = sct.derive_leaf_hash(); 38 | println!(" calculated leaf hash: {}", u8_to_hex(&leaf_hash)); 39 | let log = ll.find_by_id(&sct.log_id); 40 | if let Some(log) = log { 41 | println!(" log is {}", log.base_url); 42 | if let Err(e) = sct.verify(&openssl::pkey::PKey::public_key_from_der(&log.pub_key).unwrap()) { 43 | println!(" Error: unable to verify SCT signature: {}", e); 44 | } 45 | let lc = CTClient::new_from_latest_th(&log.base_url, &log.pub_key); 46 | if lc.is_err() { 47 | println!(" unable to connect to log: {}", lc.unwrap_err()); 48 | continue; 49 | } 50 | let lc = lc.unwrap(); 51 | match lc.check_inclusion_proof_for_sct(&sct) { 52 | Ok(index) => { 53 | println!(" inclusion proof checked, leaf index is {}", index); 54 | } 55 | Err(e) => { 56 | println!(" inclusion proof errored: {}", e); 57 | } 58 | } 59 | } else { 60 | println!(" log is not known."); 61 | } 62 | // todo: move the inclusion proof check code into CTClient? 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/simple_client/save_db_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "ctlogs" ( 2 | "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 3 | "url" TEXT NOT NULL UNIQUE, 4 | "pub_key" BLOB NOT NULL, 5 | "checked_tree_size" INTEGER NOT NULL, 6 | "checked_tree_head" BLOB NOT NULL 7 | ); 8 | 9 | CREATE TABLE "received_signed_tree_heads" ( 10 | "log_id" INTEGER NOT NULL REFERENCES "ctlogs"("id"), 11 | "tree_size" INTEGER NOT NULL, 12 | "timestamp" INTEGER NOT NULL, 13 | "tree_hash" BLOB NOT NULL, 14 | "signature" BLOB NOT NULL 15 | ); 16 | 17 | CREATE INDEX sth_tree_size_idx ON "received_signed_tree_heads" ("log_id", "tree_size"); 18 | 19 | INSERT INTO "ctlogs" (id, url, pub_key, checked_tree_size, checked_tree_head) VALUES ( 20 | 0, 21 | 'https://ct.googleapis.com/logs/argon2020/', 22 | x'3059301306072a8648ce3d020106082a8648ce3d03010703420004e93c76a75c8a638d35e4dc8862f76b937e9eb34b80735cc0e0f43e4c6458fb766351321863d5b2bbedeaff5e3b246e2f35528bb4359aad9c15a86920ea5018cc', 23 | 0, 24 | x'0000000000000000000000000000000000000000000000000000000000000000' 25 | ); 26 | 27 | CREATE TABLE "found_my_certs" ( 28 | "log_id" INTEGER NOT NULL REFERENCES "ctlogs"("id"), 29 | "x509_der" BLOB NOT NULL, 30 | "ca_der" BLOB NOT NULL 31 | ); 32 | -------------------------------------------------------------------------------- /examples/simple_client/simple_client.rs: -------------------------------------------------------------------------------- 1 | use std::convert::{TryFrom, TryInto}; 2 | use std::env; 3 | use std::process::exit; 4 | 5 | use log::LevelFilter; 6 | use openssl::x509::X509; 7 | use rusqlite::{Connection, NO_PARAMS, OptionalExtension}; 8 | use rusqlite::types::Value; 9 | 10 | use ctclient::certutils::get_dns_names; 11 | use ctclient::CTClient; 12 | 13 | fn main () { 14 | env_logger::builder().filter_module("ctclient", LevelFilter::Info).init(); 15 | 16 | if env::args_os().len() != 2 { 17 | eprintln!("Usage: ctclient "); 18 | exit(1); 19 | } 20 | let save_path = env::args_os().nth(1).unwrap(); 21 | let save_db = match Connection::open(&save_path) { 22 | Ok(f) => f, 23 | Err(e) => { 24 | eprintln!("Can't open save db: {}", &e); 25 | exit(1); 26 | } 27 | }; 28 | if save_db.query_row("SELECT null FROM sqlite_master WHERE name = 'ctlogs'", NO_PARAMS, |_| {Ok(())}).optional().unwrap() == None { 29 | save_db.execute_batch(include_str!("save_db_init.sql")).expect("Can't run init.sql"); 30 | } 31 | let (url, pub_key, init_tree_size, init_tree_hash) = save_db.query_row("SELECT url, pub_key, checked_tree_size, checked_tree_head FROM ctlogs", NO_PARAMS, |row| { 32 | Ok((row.get::<_, String>(0)?, row.get::<_, Vec>(1)?, u64::try_from(row.get_raw(2).as_i64()?).expect("negative tree size?"), row.get::<_, Vec>(3)?)) 33 | }).unwrap(); 34 | let mut client = if init_tree_size == 0 && init_tree_hash == [0u8; 32] { 35 | CTClient::new_from_latest_th(&url, &pub_key).unwrap() 36 | } else { 37 | CTClient::new_from_perv_tree_hash(&url, &pub_key, init_tree_hash[..].try_into().unwrap(), init_tree_size).unwrap() 38 | }; 39 | let mut last_thash: [u8; 32] = init_tree_hash[..].try_into().unwrap(); 40 | loop { 41 | let sthresult = client.update(Some(|certs: &[X509]| { 42 | let head = &certs[0]; 43 | let mut dns_names = match get_dns_names(head) { 44 | Ok(d) => d, 45 | Err(e) => { 46 | eprintln!("Error getting dns names from certificate: {}", e); 47 | return; 48 | } 49 | }; 50 | dns_names.sort_unstable(); 51 | dns_names.dedup_by(|a, b| a.eq_ignore_ascii_case(&b)); 52 | for n in dns_names.iter_mut() { 53 | *n = n.to_ascii_lowercase(); 54 | if n.ends_with(".merkleforest.xyz") || n == "merkleforest.xyz" { 55 | save_db.execute(r#"INSERT INTO "found_my_certs" (log_id, x509_der, ca_der) VALUES (0, ?, ?);"#, &[ 56 | Value::Blob(head.to_der().unwrap()), 57 | Value::Blob(certs[1].to_der().unwrap()) 58 | ]).expect("Unable to record cert"); 59 | println!("Found cert with the following dns names: {}", dns_names.join(", ")); 60 | break; 61 | } 62 | } 63 | })); 64 | if let Some(sth) = sthresult.tree_head() { 65 | save_db.execute(r#"INSERT INTO "received_signed_tree_heads" (log_id, tree_size, "timestamp", tree_hash, signature) VALUES (0, ?, ?, ?, ?)"#, &[ 66 | Value::Integer(sth.tree_size.try_into().unwrap()), Value::Integer(sth.timestamp.try_into().unwrap()), 67 | Value::Blob(sth.root_hash.to_vec()), Value::Blob(sth.signature.to_vec()) 68 | ]).expect("Failed to insert"); 69 | } 70 | if sthresult.is_err() { 71 | eprintln!("Update error: {}", &sthresult.unwrap_err()); 72 | } else { 73 | let th = sthresult.tree_head().unwrap(); 74 | let new_thash = th.root_hash; 75 | if new_thash == last_thash { 76 | std::thread::sleep(std::time::Duration::from_secs(10)); 77 | } else { 78 | last_thash = new_thash; 79 | save_db.execute(r#"UPDATE ctlogs SET checked_tree_size = ?, checked_tree_head = ? WHERE url = ?"#, &[ 80 | Value::Integer(th.tree_size.try_into().unwrap()), Value::Blob(th.root_hash.to_vec()), 81 | Value::Text(url.clone()) 82 | ]).expect("Failed to update state"); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctclient-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies.ctclient] 11 | path = ".." 12 | [dependencies.libfuzzer-sys] 13 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 14 | 15 | # Prevent this from interfering with workspaces 16 | [workspace] 17 | members = ["."] 18 | 19 | [[bin]] 20 | name = "ctclient_from_bytes" 21 | path = "fuzz_targets/ctclient_from_bytes.rs" 22 | 23 | 24 | [[bin]] 25 | name = "leaf_parsing" 26 | path = "fuzz_targets/leaf_parsing.rs" 27 | 28 | [[bin]] 29 | name = "leaf_extra_data_parsing" 30 | path = "fuzz_targets/leaf_extra_data_parsing.rs" 31 | -------------------------------------------------------------------------------- /fuzz/corpus/ctclient_from_bytes/valid-input.1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/corpus/ctclient_from_bytes/valid-input.1 -------------------------------------------------------------------------------- /fuzz/corpus/leaf_extra_data_parsing/extra.precert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/corpus/leaf_extra_data_parsing/extra.precert -------------------------------------------------------------------------------- /fuzz/corpus/leaf_extra_data_parsing/extra.x509leaf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/corpus/leaf_extra_data_parsing/extra.x509leaf -------------------------------------------------------------------------------- /fuzz/corpus/leaf_parsing/le.precert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/corpus/leaf_parsing/le.precert -------------------------------------------------------------------------------- /fuzz/corpus/leaf_parsing/le.x509leaf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/corpus/leaf_parsing/le.x509leaf -------------------------------------------------------------------------------- /fuzz/fuzz_targets/ctclient_from_bytes.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate ctclient; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = ctclient::CTClient::from_bytes(data); 7 | }); 8 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/extra.precert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/fuzz_targets/extra.precert -------------------------------------------------------------------------------- /fuzz/fuzz_targets/extra.x509leaf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/fuzz_targets/extra.x509leaf -------------------------------------------------------------------------------- /fuzz/fuzz_targets/le.precert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/fuzz_targets/le.precert -------------------------------------------------------------------------------- /fuzz/fuzz_targets/le.x509leaf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/fuzz/fuzz_targets/le.x509leaf -------------------------------------------------------------------------------- /fuzz/fuzz_targets/leaf_extra_data_parsing.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate ctclient; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = ctclient::internal::Leaf::from_raw(include_bytes!("./le.precert"), data); 7 | let _ = ctclient::internal::Leaf::from_raw(include_bytes!("./le.x509leaf"), data); 8 | }); 9 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/leaf_parsing.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate ctclient; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = ctclient::internal::Leaf::from_raw(data, include_bytes!("./extra.precert")); 7 | let _ = ctclient::internal::Leaf::from_raw(data, include_bytes!("./extra.x509leaf")); 8 | }); 9 | -------------------------------------------------------------------------------- /readme_img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/readme_img/demo.gif -------------------------------------------------------------------------------- /readme_img/example_parse_sct_list_from_cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/readme_img/example_parse_sct_list_from_cert.png -------------------------------------------------------------------------------- /readme_img/example_simple_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/readme_img/example_simple_client.png -------------------------------------------------------------------------------- /src/certutils.rs: -------------------------------------------------------------------------------- 1 | //! Verious utilities for checking the content of a certificate. 2 | 3 | use openssl::x509::X509Ref; 4 | use crate::Error; 5 | 6 | /// Return the common name of the certificate. Usually will only contains one. May be empty. 7 | pub fn get_common_names>(cert: &R) -> Result, Error> { 8 | let cert = cert.as_ref(); 9 | let try_common_names: Vec<_> = cert.subject_name().entries_by_nid(openssl::nid::Nid::COMMONNAME) 10 | .map(|x| x.data().as_utf8()).collect(); 11 | let mut common_names: Vec = Vec::with_capacity(try_common_names.len()); 12 | for cn in try_common_names { 13 | if let Err(e) = cn { 14 | return Err(Error::BadCertificate(format!("While parsing common name: {}", &e))); 15 | } 16 | common_names.push(String::from(AsRef::::as_ref(&cn.unwrap()))); 17 | } 18 | Ok(common_names) 19 | } 20 | 21 | /// Return the DNS names in the subjectAlternativeNames of the certificate as well as its common name. 22 | /// 23 | /// Result may contain duplicate items. 24 | pub fn get_dns_names>(cert: &R) -> Result, Error> { 25 | let cert = cert.as_ref(); 26 | let mut names = get_common_names(cert)?; 27 | // fixme: common names may not be host names. 28 | if let Some(san) = cert.subject_alt_names() { 29 | for name in san.iter() { 30 | if let Some(name) = name.dnsname() { 31 | names.push(String::from(name)); 32 | } else if let Some(uri) = name.uri() { 33 | let url_parsed = reqwest::Url::parse(uri).map_err(|_| Error::BadCertificate("This certificate has a URI SNI, but the URI is not parsable.".to_owned()))?; 34 | if let Some(host) = url_parsed.domain() { 35 | names.push(String::from(host)); 36 | } 37 | } 38 | } 39 | } 40 | Ok(names) 41 | } 42 | -------------------------------------------------------------------------------- /src/google_log_list.rs: -------------------------------------------------------------------------------- 1 | //! Downloading of log list from Google. 2 | 3 | use crate::Error; 4 | use crate::internal::new_http_client; 5 | 6 | use serde::Deserialize; 7 | use std::collections::HashMap; 8 | 9 | #[derive(Debug, Deserialize, Clone)] 10 | struct ResponseJSON { 11 | operators: Vec 12 | } 13 | 14 | #[derive(Debug, Deserialize, Clone)] 15 | struct OperatorJSON { 16 | name: String, 17 | email: Vec, 18 | logs: Vec 19 | } 20 | 21 | #[derive(Debug, Deserialize, Clone)] 22 | struct LogJson { 23 | key: String, 24 | log_id: String, 25 | mmd: u64, 26 | url: String, 27 | state: HashMap, 28 | description: String 29 | } 30 | 31 | /// A downloaded log list. 32 | #[derive(Debug, Clone)] 33 | pub struct LogList { 34 | pub map_id_to_log: HashMap, Log> 35 | } 36 | 37 | /// A log in [`LogList`]. 38 | #[derive(Debug, Clone)] 39 | pub struct Log { 40 | pub pub_key: Vec, 41 | pub base_url: String, 42 | pub state: LogState, 43 | pub description: String 44 | } 45 | 46 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 47 | pub enum LogState { 48 | Pending, 49 | Qualified, 50 | Usable, 51 | Readonly, 52 | Retired, 53 | Rejected 54 | } 55 | 56 | impl LogList { 57 | /// Download the log list at runtime from [`https://www.gstatic.com/ct/log_list/v2/log_list.json`](https://www.gstatic.com/ct/log_list/v2/log_list.json). 58 | pub fn get() -> Result { 59 | LogList::get_with_url("https://www.gstatic.com/ct/log_list/v2/log_list.json") 60 | } 61 | 62 | /// Download the log list at runtime. 63 | pub fn get_with_url(url: &str) -> Result { 64 | let client = new_http_client()?; 65 | let json: ResponseJSON = client.get(url).send().map_err(|e| Error::NetIO(e))? 66 | .json().map_err(|e| Error::MalformedResponseBody(format!("{}", e)))?; 67 | let mut hm: HashMap, Log> = HashMap::with_capacity( 68 | json.operators.iter().map(|x| x.logs.len()).sum() 69 | ); 70 | fn b64_dec_err(e: base64::DecodeError) -> Error { 71 | Error::MalformedResponseBody(format!("Unable to decode base64: {}", e)) 72 | } 73 | for op in json.operators.iter() { 74 | for log in op.logs.iter() { 75 | let log_id = base64::decode(&log.log_id).map_err(b64_dec_err)?; 76 | let pub_key = base64::decode(&log.key).map_err(b64_dec_err)?; 77 | let base_url = log.url.to_owned(); 78 | if hm.contains_key(&log_id) { 79 | return Err(Error::MalformedResponseBody("Multiple logs returned with the same id.".to_owned())); 80 | } 81 | let state_keys: Vec<&str> = log.state.keys().map(|x| &x[..]).collect(); 82 | use LogState::*; 83 | let log_state = match &state_keys[..] { 84 | ["pending"] => Pending, 85 | ["qualified"] => Qualified, 86 | ["usable"] => Usable, 87 | ["readonly"] => Readonly, 88 | ["retired"] => Retired, 89 | ["rejected"] => Rejected, 90 | _ => return Err(Error::MalformedResponseBody(format!("Invalid log state object: {:?}", &log.state))) 91 | }; 92 | hm.insert(log_id, Log { 93 | pub_key, base_url, state: log_state, description: log.description.clone() 94 | }); 95 | } 96 | } 97 | 98 | Ok(LogList { 99 | map_id_to_log: hm 100 | }) 101 | } 102 | 103 | /// Lookup a [`Log`] by its 32-byte `log_id`. 104 | pub fn find_by_id<'a, 'b>(&'a self, id: &'b [u8]) -> Option<&'a Log> { 105 | self.map_id_to_log.get(id) 106 | } 107 | } 108 | 109 | #[test] 110 | fn test() { 111 | let ll = LogList::get().unwrap(); 112 | let nb_logs = ll.map_id_to_log.len(); 113 | assert!(nb_logs > 0); 114 | assert_eq!(ll.find_by_id(&base64::decode("sh4FzIuizYogTodm+Su5iiUgZ2va+nDnsklTLe+LkF4=").unwrap()).unwrap().base_url, "https://ct.googleapis.com/logs/argon2020/"); 115 | } 116 | -------------------------------------------------------------------------------- /src/internal/consistency.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use log::trace; 4 | use crate::Error; 5 | use crate::internal::get_json; 6 | use crate::jsons; 7 | use crate::utils::{combine_tree_hash, largest_power_of_2_smaller_than, u8_to_hex}; 8 | 9 | /// Function used by 10 | /// [`verify_consistency_proof`] to construct a consistency proof client side 11 | /// (which is used to check against the server proof) 12 | /// 13 | /// Returns an array of (u64, u64)s. Each (x: u64, y: u64) denotes that this part 14 | /// of the proof should be the hash of the subtree formed by leafs with number [x, y). 15 | /// 16 | /// This function is only useful to those who want to do some custom proof 17 | /// handling. You should probably use 18 | /// [`verify_consistency_proof`] instead. 19 | /// 20 | /// Will not omit the first component even if it's the same as `prev_tree_hash` 21 | /// (the server will). This means that the subtree represented by ret\[0] will always be 22 | /// contained within (0, from_size) (i.e. already known). 23 | /// 24 | /// # Example 25 | /// 26 | /// ``` 27 | /// # use ctclient::internal::consistency_proof_parts; 28 | /// // Examples from RFC 6962 2.1.3 (https://tools.ietf.org/html/rfc6962#section-2.1.3) 29 | /// assert_eq!(consistency_proof_parts(3, 7), vec![(2, 3), (3, 4), (0, 2), (4, 7)]); 30 | /// assert_eq!(consistency_proof_parts(4, 7), vec![(0, 4), (4, 7)]); 31 | /// assert_eq!(consistency_proof_parts(6, 7), vec![(4, 6), (6, 7), (0, 4)]); 32 | /// ``` 33 | pub fn consistency_proof_parts(from_size: u64, to_size: u64) -> Vec<(u64, u64)> { 34 | // The function 'verify_consistency_proof' contains detailed comments about the nature of consistency proofs. 35 | 36 | fn inner(result_store: &mut Vec<(u64, u64)>, subtree: (u64, u64), from_size: u64) { 37 | assert!(subtree.0 < subtree.1); 38 | assert!(from_size <= subtree.1); 39 | if from_size == subtree.1 { 40 | result_store.push(subtree); 41 | return; 42 | } 43 | let subtree_size = subtree.1 - subtree.0; 44 | let start_of_right_branch = largest_power_of_2_smaller_than(subtree_size); 45 | if from_size - subtree.0 <= start_of_right_branch { // go left 46 | result_store.push((subtree.0 + start_of_right_branch, subtree.1)); 47 | inner(result_store, (subtree.0, subtree.0 + start_of_right_branch), from_size); 48 | } else { // go right 49 | result_store.push((subtree.0, subtree.0 + start_of_right_branch)); 50 | inner(result_store, (subtree.0 + start_of_right_branch, subtree.1), from_size); 51 | } 52 | } 53 | let mut result_store = Vec::new(); 54 | inner(&mut result_store, (0, to_size), from_size); 55 | result_store.reverse(); 56 | result_store 57 | } 58 | 59 | #[test] 60 | fn consistency_proof_partial_test() { 61 | assert_eq!(consistency_proof_parts(753913835, 753913848).len(), 25); 62 | assert_eq!(consistency_proof_parts(6, 6), vec![(0, 6)]); 63 | assert_eq!(consistency_proof_parts(7, 7), vec![(0, 7)]); 64 | 65 | assert_eq!(consistency_proof_parts(4, 7), vec![(0, 4), (4, 7)]); 66 | } 67 | 68 | /// A subtree hash provided by the server in a consistency proof. 69 | pub struct ConsistencyProofPart { 70 | /// (start, non-inclusive end) 71 | pub subtree: (u64, u64), 72 | 73 | /// The hash of this subtree as from the proof 74 | pub server_hash: [u8; 32], 75 | } 76 | 77 | /// Verify that the consistency proof given by `server_provided_proof` gets us 78 | /// from `perv_root` to `next_root`, returning an `Ok(Vec)` 79 | /// if the proof checks, otherwise a `Err(String)` describing why the proof is 80 | /// invalid. 81 | /// 82 | /// This function is only useful to those who want to do some custom API calling. 83 | /// If you're using a [`CTClient`](crate::CTClient), it will handle proof 84 | /// checking for you. 85 | /// 86 | /// To fetch the consistency proof from the server and verifies it, call 87 | /// [`check_consistency_proof`]. 88 | /// 89 | /// # `Ok(Vec)` 90 | /// 91 | /// The `Ok` result of this function contains all components of the proof which 92 | /// describes a new tree (that's not in the previous tree). This can be useful if 93 | /// you want to then get all the new certificates and verify that those forms the 94 | /// new tree. 95 | /// 96 | /// To do this, calculate the leaf hash of all the new certificates, and call 97 | /// [`ConsistencyProofPart::verify`] with the array of leaf hashes. See its 98 | /// documentation for more info. 99 | /// 100 | /// # Panic 101 | /// 102 | /// `verify_consistency_proof` panics if `perv_size` > `next_size`. 103 | /// 104 | pub fn verify_consistency_proof(perv_size: u64, next_size: u64, server_provided_proof: &[[u8; 32]], perv_root: &[u8; 32], next_root: &[u8; 32]) -> Result, String> { 105 | // todo: add test for this. 106 | 107 | if perv_size > next_size { 108 | panic!("perv_size must be <= next_size"); 109 | } 110 | if perv_size == next_size { 111 | return Ok(Vec::new()); 112 | } 113 | if perv_size == 0 { 114 | // An empty tree is a subtree of every tree. No need to prove. 115 | return Ok(vec![ConsistencyProofPart{ 116 | subtree: (0, next_size), 117 | server_hash: *next_root 118 | }]); 119 | } 120 | 121 | // A consistency proof is an array of hashes of some subtrees of the current 122 | // tree. These subtrees will entirely cover the previous tree, and will also 123 | // include some new parts which is only in the current tree. To validate the 124 | // proof, we attempt to derive the new root hash based on these provided 125 | // hashes. If we got the same hash as the server signed tree hash, we know that 126 | // the previous tree is entirely contained in the new tree. In addition, we 127 | // also need to check that the hashes which corresponds to subtrees that 128 | // contains previous nodes are genuine. We do this by attempting to construct 129 | // the previous root hash based on these hashes, and see if we came up with a 130 | // hash that is the same as the `perv_root` provided by the caller. 131 | 132 | // A subtree is represented with (u64, u64), where the first number is the 133 | // starting index, and the second number is the non-inclusive ending index. For 134 | // example, (2, 4) denote the 2-level subtree made by the nodes with index 2 135 | // and 3, which looks like this: 136 | // 137 | // 23 138 | // / \ 139 | // 2 3 140 | 141 | // Calculate the proof ourselves first so that we know how to use the server 142 | // provided proof. 143 | let calculated_proof = consistency_proof_parts(perv_size, next_size); 144 | 145 | // The server will omit the first hash if it will otherwise simply be the 146 | // previous root hash. This happens when previous tree is a complete balanced 147 | // tree, sitting in the bottom-left corner of the current tree. Since these 148 | // trees always start at 0, we only need to check if the size is a power of 2 149 | // (hence a balanced tree) 150 | let omit_first = u64::is_power_of_two(perv_size); 151 | 152 | let mut expected_proof_len = calculated_proof.len(); 153 | if omit_first { 154 | expected_proof_len -= 1; 155 | } 156 | if server_provided_proof.len() != expected_proof_len { 157 | return Err(format!("wrong proof length: expected {}, got {}", expected_proof_len, server_provided_proof.len())); 158 | } 159 | 160 | let mut hashes = Vec::new(); 161 | hashes.reserve(calculated_proof.len()); 162 | if omit_first { 163 | hashes.push(perv_root.clone()); 164 | } 165 | hashes.extend_from_slice(server_provided_proof); 166 | assert_eq!(hashes.len(), calculated_proof.len()); 167 | 168 | // Now each element of `hashes` and `calculated_proof` match up 169 | // (hash[i] is the hash of the subtree calculated_proof[i]), we could start to 170 | // do our hashing, and try to derive the new root hash. 171 | 172 | let mut derived_new_hash = hashes[0]; 173 | let mut derived_new_hash_subtree = calculated_proof[0]; 174 | for (subtree, hash) in (1..hashes.len()).map(|i| (calculated_proof[i], &hashes[i])) { 175 | // Proof can't have overlapping subtrees 176 | assert_ne!(derived_new_hash_subtree.0, subtree.0); 177 | // Two possibilities: either the current proof part represent a subtree which 178 | // exists in the previous tree, or it represents an entirely new subtree. Proof entries 179 | // can't represent overlapping trees/trees that cover both old and new nodes (otherwise there is 180 | // no way to derive the hash of the old tree because the hashes to some part of the old tree is "mixed" together 181 | // with some part of the new tree). 182 | // 183 | // In the first case, it will always be the "left" branch, and in the second case, "right" branch. 184 | // 185 | // We need to combine the hashes in the right order: 186 | // Left branch (previous branch) first, then right branch (new branch). 187 | if subtree.0 > derived_new_hash_subtree.0 { 188 | // Right branch 189 | assert_eq!(subtree.0, derived_new_hash_subtree.1); 190 | derived_new_hash = combine_tree_hash(&derived_new_hash, hash); 191 | derived_new_hash_subtree = (derived_new_hash_subtree.0, subtree.1); 192 | } else { 193 | // Left branch 194 | assert_eq!(subtree.1, derived_new_hash_subtree.0); 195 | derived_new_hash = combine_tree_hash(hash, &derived_new_hash); 196 | derived_new_hash_subtree = (subtree.0, derived_new_hash_subtree.1); 197 | } 198 | } 199 | if derived_new_hash != *next_root { 200 | return Err(format!("calculated tree root {} does not match given tree root {}", u8_to_hex(&derived_new_hash), u8_to_hex(next_root))); 201 | } 202 | 203 | // Now make sure we can derive the hash of the previous tree from this proof. 204 | if omit_first { 205 | // we are sure that last tree is included in the new tree, because we used last tree's hash to calculate the new hash. 206 | trace!("consistency checked from {} to {}; previous tree is complete.", &u8_to_hex(perv_root), &u8_to_hex(next_root)); 207 | Ok(hashes.iter().zip(calculated_proof.iter()).skip(1).map(|(hash, subtree)| ConsistencyProofPart{subtree: *subtree, server_hash: *hash}).collect()) 208 | } else { 209 | // First component of proof is always a part of the previous tree. 210 | assert!(calculated_proof[0].1 <= perv_size); 211 | let mut derived_old_hash: [u8; 32] = hashes[0]; 212 | let mut derived_old_hash_subtree: (u64, u64) = calculated_proof[0]; 213 | let mut new_parts = Vec::new(); 214 | for (subtree, hash) in (1..hashes.len()).map(|i| (calculated_proof[i], &hashes[i])) { 215 | if subtree.1 <= perv_size { // if next_subtree is part of the previous tree... 216 | if subtree.0 > derived_old_hash_subtree.0 { 217 | assert_eq!(subtree.0, derived_old_hash_subtree.1); 218 | derived_old_hash = combine_tree_hash(&derived_old_hash, hash); 219 | derived_old_hash_subtree = (derived_old_hash_subtree.0, subtree.1); 220 | } else { 221 | assert_eq!(subtree.1, derived_old_hash_subtree.0); 222 | derived_old_hash = combine_tree_hash(hash, &derived_old_hash); 223 | derived_old_hash_subtree = (subtree.0, derived_old_hash_subtree.1); 224 | } 225 | } else { 226 | // Proof entries is either entirely new tree or entirely old tree. 227 | assert!(subtree.0 >= perv_size); 228 | new_parts.push(ConsistencyProofPart{ 229 | subtree, 230 | server_hash: *hash, 231 | }); 232 | } 233 | } 234 | if derived_old_hash != *perv_root { 235 | return Err(format!("calculated perv_root {} does not match given perv_root {}", u8_to_hex(&derived_old_hash), u8_to_hex(perv_root))); 236 | } 237 | 238 | trace!("consistency checked from {} to {}", &u8_to_hex(perv_root), &u8_to_hex(next_root)); 239 | Ok(new_parts) 240 | } 241 | } 242 | 243 | impl ConsistencyProofPart { 244 | /// Verify that an array of leaf_hashes could reconstruct this subtree's 245 | /// `server_hash`, returning `Ok(())` when success and `Err(String)` when 246 | /// failure, with a string describing the reason of failure. 247 | /// 248 | /// This function is only useful to those who want to do some custom API calling. 249 | /// If you're using a [`CTClient`](crate::CTClient), it will handle proof 250 | /// checking for you. 251 | /// 252 | /// # Panic 253 | /// 254 | /// `verify` panics when `leaf_hashes` does not have the right length, which 255 | /// should be `subtree.1 - subtree.0`. 256 | pub fn verify(&self, leaf_hashes: &[[u8; 32]]) -> Result<(), String> { 257 | let subtree_size = self.subtree.1 - self.subtree.0; 258 | if leaf_hashes.len() as u64 != subtree_size { 259 | panic!("expected leaf_hashes to have length {}, got {}", subtree_size, leaf_hashes.len()); 260 | } 261 | if subtree_size == 1 { 262 | return if leaf_hashes[0] != self.server_hash { 263 | Err(format!("expected leaf_hashes to be [{}], got [{}]", u8_to_hex(&self.server_hash), u8_to_hex(&leaf_hashes[0]))) 264 | } else { 265 | Ok(()) 266 | } 267 | } 268 | let mut round_hashes = Vec::from(leaf_hashes); 269 | loop { 270 | let mut new_round_hashes = Vec::new(); 271 | new_round_hashes.reserve(round_hashes.len() / 2); 272 | for i in 0..(round_hashes.len() / 2) { 273 | let hash_left = round_hashes[2*i]; 274 | let hash_right = round_hashes[2*i + 1]; 275 | new_round_hashes.push(combine_tree_hash(&hash_left, &hash_right)); 276 | } 277 | if round_hashes.len() % 2 != 0 { 278 | new_round_hashes.push(*round_hashes.last().unwrap()); 279 | } 280 | round_hashes = new_round_hashes; 281 | if round_hashes.len() == 1 { 282 | break; 283 | } 284 | } 285 | assert_eq!(round_hashes.len(), 1); 286 | let calculated_hash = round_hashes[0]; 287 | if self.server_hash == calculated_hash { 288 | Ok(()) 289 | } else { 290 | Err(format!("Subtree {:?}: calculated that tree hash should be {}, but got {} from consistency check.", &self.subtree, u8_to_hex(&calculated_hash), u8_to_hex(&self.server_hash))) 291 | } 292 | } 293 | } 294 | 295 | #[test] 296 | fn verify_consistency_proof_new_tree_leaf_hashes_test() { 297 | use crate::utils::sha256; 298 | fn h(s: &str) -> [u8; 32] { 299 | sha256(s.as_bytes()) 300 | } 301 | fn c(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] { 302 | combine_tree_hash(a, b) 303 | } 304 | 305 | (ConsistencyProofPart{ 306 | subtree: (0, 1), 307 | server_hash: h("a") 308 | }).verify(&[h("a")]).unwrap(); 309 | 310 | (ConsistencyProofPart{ 311 | subtree: (0, 1), 312 | server_hash: h("a") 313 | }).verify(&[h("b")]).expect_err("!"); 314 | 315 | (ConsistencyProofPart{ 316 | subtree: (2, 4), 317 | server_hash: c(&h("c"), &h("d")) 318 | }).verify(&[h("c"), h("d")]).unwrap(); 319 | 320 | (ConsistencyProofPart{ 321 | subtree: (0, 6), 322 | server_hash: c(&c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))), &c(&h("e"), &h("f"))) 323 | }).verify(&[h("a"), h("b"), h("c"), h("d"), h("e"), h("f")]).unwrap(); 324 | 325 | (ConsistencyProofPart{ 326 | subtree: (0, 6), 327 | server_hash: c(&c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))), &c(&h("e"), &h("f"))) 328 | }).verify(&[h("a"), h("b"), h("c"), h("g"), h("e"), h("f")]).expect_err("!"); 329 | 330 | (ConsistencyProofPart{ 331 | subtree: (0, 6), 332 | server_hash: c(&c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))), &c(&h("e"), &h("f"))) 333 | }).verify(&[h("a"), h("b"), h("c"), h("d"), h("e"), h("g")]).expect_err("!"); 334 | 335 | (ConsistencyProofPart{ 336 | subtree: (0, 4), 337 | server_hash: c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))) 338 | }).verify(&[h("a"), h("b"), h("c"), h("d")]).unwrap(); 339 | 340 | (ConsistencyProofPart{ 341 | subtree: (0, 4), 342 | server_hash: c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))) 343 | }).verify(&[h("c"), h("d"), h("a"), h("b")]).expect_err("!"); 344 | } 345 | 346 | /// Fetch the consistency proof from prev_size to next_size from the server and 347 | /// verifies it, returning a `Vec` if successful, which can later be 348 | /// used to verify the integrity of certificates downloaded from the server 349 | /// later. An `Err(...)` is returned if the proof is invalid, or some network 350 | /// error happened during the request. 351 | /// 352 | /// # `Ok(Vec)` 353 | /// 354 | /// The `Ok` result of this function contains all components of the proof which 355 | /// describes a new tree (that's not in the previous tree). This can be useful if 356 | /// you want to then get all the new certificates and verify that those forms the 357 | /// new tree. 358 | /// 359 | /// To do this, calculate the leaf hash of all the new certificates, and call 360 | /// [`ConsistencyProofPart::verify`] with the array of leaf hashes. See its 361 | /// documentation for more info. 362 | /// 363 | /// # Panics 364 | /// 365 | /// ...if prev_size >= next_size 366 | pub fn check_consistency_proof(client: &reqwest::blocking::Client, base_url: &reqwest::Url, prev_size: u64, next_size: u64, perv_root: &[u8; 32], next_root: &[u8; 32]) -> Result, Error> { 367 | assert!(prev_size < next_size); 368 | let server_consistency_proof: jsons::ConsistencyProof = get_json(client, base_url, &format!("ct/v1/get-sth-consistency?first={}&second={}", prev_size, next_size))?; 369 | let server_consistency_proof = server_consistency_proof.consistency; 370 | let mut parsed_server_proof: Vec<[u8; 32]> = Vec::new(); 371 | parsed_server_proof.reserve(server_consistency_proof.len()); 372 | let mut n = 0; 373 | for i in server_consistency_proof.into_iter() { 374 | n += 1; 375 | let decoded = base64::decode(&i).map_err(|e| Error::MalformedResponseBody(format!("Can not base64 decode consistency proof element: {}", &e)))?; 376 | if decoded.len() != 32 { 377 | return Err(Error::MalformedResponseBody("Consistency proof element has length other than 32.".to_owned())); 378 | } 379 | parsed_server_proof.push(decoded[..].try_into().unwrap()); 380 | } 381 | assert_eq!(parsed_server_proof.len(), n); 382 | verify_consistency_proof(prev_size, next_size, &parsed_server_proof, perv_root, next_root) 383 | .map_err(|e| Error::InvalidConsistencyProof{prev_size, new_size: next_size, desc: e}) 384 | } 385 | -------------------------------------------------------------------------------- /src/internal/digitally_signed_struct.rs: -------------------------------------------------------------------------------- 1 | use log::trace; 2 | use openssl::pkey::PKey; 3 | 4 | use crate::Error; 5 | use crate::utils; 6 | 7 | // https://docs.rs/rustls/0.15.2/src/rustls/msgs/enums.rs.html#720 8 | // We only need to handle these two cases because RFC says so. 9 | pub(crate) const SIGSCHEME_ECDSA_NISTP256_SHA256: u16 = 0x0403; 10 | pub(crate) const SIGSCHEME_RSA_PKCS1_SHA256: u16 = 0x0401; 11 | 12 | /// Verifies a TLS digitally-signed struct (see [the TLS 13 | /// RFC](https://tools.ietf.org/html/rfc5246#section-4.7) for more info.) 14 | /// 15 | /// This function is only useful to those who want to do some custom CT API 16 | /// calling. [`CTClient`](crate::CTClient) will automatically verify all 17 | /// signature. 18 | /// 19 | /// # Params 20 | /// 21 | /// * `dss`: the `DigitallySigned` struct. Often returned as a 22 | /// base64 "signature" json field by the CT server. De-base64 yourself before 23 | /// calling. 24 | /// 25 | /// * `pub_key`: use 26 | /// [openssl::pkey::PKey::public_key_from_der](openssl::pkey::PKey::public_key_from_der) 27 | /// to turn the key provided by google's ct log list into openssl key object. 28 | /// 29 | /// * `data`: the stuff to verify against. Server should have signed this. 30 | pub fn verify_dss(dss: &[u8], pub_key: &PKey, data: &[u8]) -> Result<(), Error> { 31 | // rustls crate contain code that parses this structure: 32 | // https://docs.rs/rustls/0.15.2/src/rustls/msgs/handshake.rs.html#1546 33 | // It shows that the struct begins with two bytes denoting the signature scheme, and 34 | // then follows a 2-byte length of the rest of the struct. 35 | 36 | if dss.len() > (1usize << 16usize) + 3 { 37 | return Err(Error::InvalidSignature(format!("dss too long. (len = {})", dss.len()))); 38 | } 39 | 40 | if dss.len() < 4 { 41 | return Err(Error::InvalidSignature(format!("Invalid dss: {}\n Too short. Expected at least 4 bytes.", &utils::u8_to_hex(dss)))); 42 | } 43 | let sig_type = u16::from_be_bytes([dss[0], dss[1]]); 44 | let length = u16::from_be_bytes([dss[2], dss[3]]); 45 | let rest = &dss[4..]; 46 | if rest.len() != length as usize { 47 | return Err(Error::InvalidSignature(format!("Invalid dss: {}\n It says there that there are {} bytes in the signature part, but I see {}.", &utils::u8_to_hex(dss), length, rest.len()))); 48 | } 49 | 50 | let signature_algorithm = match sig_type { 51 | SIGSCHEME_ECDSA_NISTP256_SHA256 => SignatureAlgorithm::Sha256Ecdsa, 52 | SIGSCHEME_RSA_PKCS1_SHA256 => SignatureAlgorithm::Sha256Rsa, 53 | _ => { 54 | return Err(Error::InvalidSignature(format!("Unknow signature scheme {:2x}", sig_type))); 55 | } 56 | }; 57 | 58 | verify_dss_raw(signature_algorithm, pub_key, rest, data) 59 | } 60 | 61 | use crate::internal::openssl_ffi::SignatureAlgorithm; 62 | 63 | /// Verifies a raw, ASN.1 encoded signature. 64 | pub fn verify_dss_raw(signature_algorithm: SignatureAlgorithm, pub_key: &PKey, raw_signature: &[u8], data: &[u8]) -> Result<(), Error> { 65 | use SignatureAlgorithm::*; 66 | match signature_algorithm { 67 | Sha256Ecdsa => { 68 | if pub_key.id() != openssl::pkey::Id::EC { 69 | return Err(Error::InvalidSignature(format!("dss says signature is EC, but key is {:?}", pub_key.id()))); 70 | } 71 | } 72 | Sha256Rsa => { 73 | if pub_key.id() != openssl::pkey::Id::RSA { 74 | return Err(Error::InvalidSignature(format!("dss says signature is RSA, but key is {:?}", pub_key.id()))); 75 | } 76 | } 77 | } 78 | 79 | let mut verifier = openssl::sign::Verifier::new(openssl::hash::MessageDigest::sha256(), pub_key).map_err(|e| Error::Unknown(format!("EVP_DigestVerifyInit: {}", &e)))?; 80 | if signature_algorithm == Sha256Rsa { 81 | verifier.set_rsa_padding(openssl::rsa::Padding::PKCS1).map_err(|e| Error::Unknown(format!("EVP_PKEY_CTX_set_rsa_padding: {}", &e)))?; 82 | } 83 | verifier.update(data).map_err(|e| Error::Unknown(format!("EVP_DigestUpdate: {}", &e)))?; 84 | if !verifier.verify(raw_signature).map_err(|e| Error::InvalidSignature(format!("EVP_DigestVerifyFinal: {}", &e)))? { 85 | return Err(Error::InvalidSignature(format!("Signature is invalid: signature = {}, data = {}.", &utils::u8_to_hex(raw_signature), &utils::u8_to_hex(data)))); 86 | } 87 | 88 | debug_assert!({ 89 | trace!("Signature checked for data {}", &utils::u8_to_hex(data)); 90 | true 91 | }); 92 | 93 | Ok(()) 94 | } 95 | 96 | #[test] 97 | fn verify_dss_test() { 98 | let key = PKey::public_key_from_der(&utils::hex_to_u8("3056301006072a8648ce3d020106052b8104000a0342000412c022d1b5cab048f419d46f111743cea4fcd54a05228d14cecd9cc1d120e4cc3e22e8481e5ccc3db16273a8d981ac144306d644a4227468fccd6580563ec8bd")[..]).unwrap(); 99 | verify_dss(&utils::hex_to_u8("040300473045022100ba6da0fb4d4440965dd1d096212da95880320113320ddc5202a0b280ac518349022005bb17637d4ed06facb4af5b4b9b9083210474998ac33809a6e10c9352032055"), &key, b"hello").unwrap(); 100 | verify_dss(&utils::hex_to_u8("0403004830460221009857dc5e2bcc0b67059a5bde9ead6a36614ab315423c0b2e4762ba7aca3f0181022100eab3af33367cb89d556c17c1ce7de1c2b8c2b80d709d0c3cbb45c8acc6809d1d"), &key, b"not hello").unwrap(); 101 | verify_dss(&utils::hex_to_u8("0403004830460221009857dc5e2bcc0b67059a5bde9ead6a36614ab315423c0b2e4762ba7aca3f0181022100eab3af33367cb89d556c17c1ce7de1c2b8c2b80d709d0c3cbb45c8acc6809d1d"), &key, b"hello").expect_err(""); 102 | 103 | // Don't panic. 104 | verify_dss(&utils::hex_to_u8(""), &key, b"hello").expect_err(""); 105 | verify_dss(&utils::hex_to_u8("00"), &key, b"hello").expect_err(""); 106 | verify_dss(&utils::hex_to_u8("0001"), &key, b"hello").expect_err(""); 107 | verify_dss(&utils::hex_to_u8("000102"), &key, b"hello").expect_err(""); 108 | verify_dss(&utils::hex_to_u8("00010203"), &key, b"hello").expect_err(""); 109 | verify_dss(&utils::hex_to_u8("0001020304"), &key, b"hello").expect_err(""); 110 | verify_dss(&utils::hex_to_u8("000102030405"), &key, b"hello").expect_err(""); 111 | } 112 | -------------------------------------------------------------------------------- /src/internal/getentries.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::ops::Range; 3 | 4 | use crate::Error; 5 | use crate::jsons; 6 | 7 | use super::get_json; 8 | use super::Leaf; 9 | 10 | /// An iterator over `Result`. 11 | /// 12 | /// After the first Err result, the iterator will not produce anything else. 13 | pub struct GetEntriesIter<'a> { 14 | requested_range: Range, 15 | done: bool, 16 | last_gotten_entries: (Range, Vec>), 17 | next_index: u64, 18 | pub batch_size: u64, 19 | 20 | client: &'a reqwest::blocking::Client, 21 | base_url: &'a reqwest::Url, 22 | } 23 | 24 | impl<'a> GetEntriesIter<'a> { 25 | fn new(range: std::ops::Range, client: &'a reqwest::blocking::Client, base_url: &'a reqwest::Url) -> Self { 26 | Self { 27 | last_gotten_entries: (range.start..range.start, Vec::new()), 28 | next_index: range.start, 29 | requested_range: range, 30 | done: false, 31 | batch_size: 500, 32 | 33 | client, base_url 34 | } 35 | } 36 | } 37 | 38 | impl<'a> Iterator for GetEntriesIter<'a> { 39 | type Item = Result; 40 | 41 | fn next(&mut self) -> Option { 42 | if self.done { 43 | return None; 44 | } 45 | if self.next_index >= self.requested_range.end { 46 | self.done = true; 47 | return None; 48 | } 49 | let (ref mut last_gotten_range, ref mut last_gotten_entries) = self.last_gotten_entries; 50 | assert!(self.next_index >= last_gotten_range.start); 51 | assert!(self.next_index <= last_gotten_range.end); 52 | if self.next_index == last_gotten_range.end { 53 | assert!(self.requested_range.end > last_gotten_range.end); // The case where there's no more to be fetched is checked at the beginning of this function. 54 | let mut next_sub_range = last_gotten_range.end..u64::min(last_gotten_range.end + self.batch_size, self.requested_range.end); 55 | let try_next_entries = get_json(self.client, self.base_url, &format!("ct/v1/get-entries?start={}&end={}", next_sub_range.start, next_sub_range.end - 1)).map(|x: jsons::GetEntries| x.entries); 56 | if let Ok(next_entries) = try_next_entries { 57 | next_sub_range.end = next_sub_range.start + next_entries.len() as u64; 58 | if next_entries.is_empty() { 59 | self.last_gotten_entries = (next_sub_range, Vec::new()); 60 | self.done = true; 61 | None 62 | } else { 63 | self.last_gotten_entries = (next_sub_range, next_entries.into_iter().map(Some).collect()); 64 | self.next_index += 1; 65 | let leaf_entry = self.last_gotten_entries.1[0].take().unwrap(); 66 | match Leaf::try_from(&leaf_entry) { 67 | Ok(leaf) => { 68 | Some(Ok(leaf)) 69 | }, 70 | Err(e) => { 71 | self.done = true; 72 | Some(Err(e)) 73 | } 74 | } 75 | } 76 | } else { 77 | let err = try_next_entries.unwrap_err(); 78 | self.done = true; 79 | Some(Err(err)) 80 | } 81 | } else { 82 | assert_eq!(last_gotten_entries.len() as u64, last_gotten_range.end - last_gotten_range.start); 83 | let leaf_entry = last_gotten_entries[(self.next_index - last_gotten_range.start) as usize].take().unwrap(); 84 | self.next_index += 1; 85 | match Leaf::try_from(&leaf_entry) { 86 | Ok(leaf) => { 87 | Some(Ok(leaf)) 88 | }, 89 | Err(e) => { 90 | self.done = true; 91 | Some(Err(e)) 92 | } 93 | } 94 | } 95 | } 96 | 97 | fn size_hint(&self) -> (usize, Option) { 98 | if self.done { 99 | return (0, Some(0)); 100 | } 101 | let rem_size = self.requested_range.end - self.next_index; 102 | if rem_size >= 1 { 103 | (1, Some(rem_size as usize)) 104 | } else { 105 | (0, Some(0)) 106 | } 107 | } 108 | } 109 | 110 | /// Request leaf entries from the CT log. Does not verify if these entries are 111 | /// consistent with the tree or anything like that. Returns an iterator over the 112 | /// leaves. 113 | /// 114 | /// After the first Err result, the iterator will not produce anything else. 115 | /// 116 | /// Uses `O(1)` memory itself. 117 | pub fn get_entries<'a>(client: &'a reqwest::blocking::Client, base_url: &'a reqwest::Url, range: Range) -> GetEntriesIter<'a> { 118 | GetEntriesIter::new(range, client, base_url) 119 | } 120 | -------------------------------------------------------------------------------- /src/internal/inclusion.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | use crate::Error; 3 | use crate::internal::get_json; 4 | use crate::jsons::AuditProof; 5 | use std::convert::TryInto; 6 | use crate::utils::{combine_tree_hash, u8_to_hex}; 7 | 8 | /// Returns an array of `Range`s. Each x..y denotes that this part 9 | /// of the proof should be the hash of the subtree formed by leafs with number [x, y). 10 | /// 11 | /// # Panics 12 | /// 13 | /// If `index` >= `tree_size`. 14 | /// 15 | /// # Examples 16 | /// 17 | /// ``` 18 | /// # use ctclient::internal::inclusion_proof_parts; 19 | /// // Examples from https://tools.ietf.org/html/rfc6962#section-2.1.3 20 | /// assert_eq!(inclusion_proof_parts(7, 0), vec![1..2, 2..4, 4..7]); 21 | /// assert_eq!(inclusion_proof_parts(7, 3), vec![2..3, 0..2, 4..7]); 22 | /// assert_eq!(inclusion_proof_parts(7, 4), vec![5..6, 6..7, 0..4]); 23 | /// assert_eq!(inclusion_proof_parts(7, 6), vec![4..6, 0..4]); 24 | /// ``` 25 | pub fn inclusion_proof_parts(tree_size: u64, index: u64) -> Vec> { 26 | assert!(index < tree_size); 27 | let mut current_subtree = index..(index + 1); 28 | let mut result = Vec::new(); 29 | while current_subtree.end - current_subtree.start < tree_size { 30 | let next_subtree_len = (current_subtree.end - current_subtree.start) * 2; 31 | let next_subtree_start = current_subtree.start / next_subtree_len * next_subtree_len; 32 | let next_subtree = next_subtree_start..(next_subtree_start + next_subtree_len); 33 | let mid = next_subtree_start + next_subtree_len / 2; 34 | if index < mid { 35 | // hash right 36 | if mid < tree_size { 37 | result.push(mid..u64::min(next_subtree.end, tree_size)); 38 | } else { 39 | // Happens if the last part of the tree is incomplete. 40 | // Do nothing. 41 | } 42 | } else { 43 | // hash left 44 | result.push(next_subtree_start..mid); 45 | } 46 | current_subtree = next_subtree; 47 | } 48 | result 49 | } 50 | 51 | #[test] 52 | fn test_inclusion_proof_parts() { 53 | assert_eq!(inclusion_proof_parts(1, 0), vec![]); 54 | assert_eq!(inclusion_proof_parts(2, 0), vec![1..2]); 55 | assert_eq!(inclusion_proof_parts(2, 1), vec![0..1]); 56 | assert_eq!(inclusion_proof_parts(3, 1), vec![0..1, 2..3]); 57 | assert_eq!(inclusion_proof_parts(5, 0), vec![1..2, 2..4, 4..5]); 58 | assert_eq!(inclusion_proof_parts(5, 4), vec![0..4]); 59 | } 60 | 61 | /// Fetch the required inclusion proof from the server and see if it convinces us that `leaf_hash` is 62 | /// in the tree with hash `tree_hash` and size `tree_size`. On success, return the index number of the 63 | /// leaf corresponding with the hash. 64 | pub fn check_inclusion_proof(client: &reqwest::blocking::Client, base_url: &reqwest::Url, tree_size: u64, tree_hash: &[u8; 32], leaf_hash: &[u8; 32]) -> Result { 65 | let res = fetch_inclusion_proof(client, base_url, tree_size, leaf_hash)?; 66 | if &res.calculated_tree_hash != tree_hash { 67 | return Err(Error::InvalidInclusionProof {tree_size, leaf_index: res.leaf_index, desc: 68 | format!("Expected the proof to yield a tree hash of {}, but instead got {}.", u8_to_hex(tree_hash), u8_to_hex(&res.calculated_tree_hash))}); 69 | } 70 | Ok(res.leaf_index) 71 | } 72 | 73 | pub struct FetchInclusionProofResult { 74 | pub calculated_tree_hash: [u8; 32], 75 | pub leaf_index: u64 76 | } 77 | 78 | pub fn fetch_inclusion_proof(client: &reqwest::blocking::Client, base_url: &reqwest::Url, tree_size: u64, leaf_hash: &[u8; 32]) -> Result { 79 | let json: AuditProof = get_json(client, base_url, 80 | &format!("ct/v1/get-proof-by-hash?{}", serde_urlencoded::to_string(&[ 81 | ("hash", base64::encode(leaf_hash)), 82 | ("tree_size", tree_size.to_string()) 83 | ]).map_err(|e| Error::Unknown(format!("{}", e)))?) 84 | )?; 85 | let leaf_index = json.leaf_index; 86 | if json.leaf_index >= tree_size { 87 | return Err(Error::InvalidInclusionProof {tree_size, leaf_index, desc: "returned leaf_index >= tree_size.".to_owned()}); 88 | } 89 | let proof_parts = inclusion_proof_parts(tree_size, leaf_index); 90 | if proof_parts.len() != json.audit_path.len() { 91 | return Err(Error::InvalidInclusionProof {tree_size, leaf_index, desc: format!("Expected proof with {} parts, got {}.", proof_parts.len(), json.audit_path.len())}); 92 | } 93 | let mut provided_proof: Vec<[u8; 32]> = Vec::with_capacity(proof_parts.len()); 94 | for i in 0..proof_parts.len() { 95 | let hash = base64::decode(&json.audit_path[i]).map_err(|e| { 96 | Error::MalformedResponseBody(format!("Unable to decode base64 in proof: {}", e)) 97 | })?; 98 | if hash.len() != 32 { 99 | return Err(Error::MalformedResponseBody("One or more component in the proof does not has length 32.".to_owned())); 100 | } 101 | provided_proof.push(hash[..].try_into().unwrap()); 102 | } 103 | let got_hash = hash_inclusion_proof(&proof_parts, &provided_proof, leaf_hash, leaf_index); 104 | Ok(FetchInclusionProofResult{ 105 | calculated_tree_hash: got_hash, 106 | leaf_index 107 | }) 108 | } 109 | 110 | /// Attempt to derive the root hash from the server provided inclusion proof and our calculated proof_parts. 111 | /// 112 | /// Used by [`check_inclusion_proof`]. 113 | pub fn hash_inclusion_proof(proof_parts: &[Range], provided_proof: &[[u8; 32]], leaf_hash: &[u8; 32], leaf_index: u64) -> [u8; 32] { 114 | let mut current_hash = *leaf_hash; 115 | let mut current_subtree = leaf_index..leaf_index + 1; 116 | assert_eq!(proof_parts.len(), provided_proof.len()); 117 | for (proof_part, proof_hash) in proof_parts.iter().zip(provided_proof.iter()) { 118 | if proof_part.start == current_subtree.end { 119 | // . 120 | // / \ 121 | // [current] [proof] 122 | current_hash = combine_tree_hash(¤t_hash, proof_hash); 123 | current_subtree = current_subtree.start..proof_part.end; 124 | } else if proof_part.end == current_subtree.start { 125 | // [proof] [current] 126 | current_hash = combine_tree_hash(proof_hash, ¤t_hash); 127 | current_subtree = proof_part.start..current_subtree.end; 128 | } else { 129 | unreachable!() 130 | } 131 | } 132 | current_hash 133 | } 134 | 135 | #[test] 136 | fn test() { 137 | } 138 | -------------------------------------------------------------------------------- /src/internal/leaf.rs: -------------------------------------------------------------------------------- 1 | use std::convert::{TryFrom, TryInto}; 2 | use std::fmt; 3 | 4 | use crate::Error; 5 | use crate::jsons; 6 | use crate::utils; 7 | 8 | /// A parsed leaf. 9 | /// 10 | /// Parse a JSON get-entries response to this with 11 | /// `TryFrom<&jsons::LeafEntry>::try_from`. 12 | pub struct Leaf { 13 | /// What they call "leaf hash". 14 | pub hash: [u8; 32], 15 | /// The leaf timestamp provided by the log. 16 | pub timestamp: u64, 17 | pub is_pre_cert: bool, 18 | /// The first cert is the end entity cert (or pre cert, if `is_pre_cert` is 19 | /// true), and the last is the root CA. 20 | pub x509_chain: Vec>, 21 | /// When is_pre_cert is true, this contains the tbs certificate found at the leaf input data. 22 | /// You should verify that this is the same certificate as x509_chain\[0], except not signed. 23 | pub tbs_cert: Option>, 24 | pub issuer_key_hash: Option>, 25 | /// Raw extensions data. Ignored. Length not included. 26 | pub extensions: Vec, 27 | } 28 | 29 | impl Leaf { 30 | pub fn from_raw(leaf_input: &[u8], extra_data: &[u8]) -> Result { 31 | let mut hash_data = Vec::new(); 32 | hash_data.reserve(1 + leaf_input.len()); 33 | hash_data.push(0); 34 | hash_data.extend_from_slice(leaf_input); 35 | let hash = utils::sha256(&hash_data); 36 | let is_pre_cert; 37 | let mut x509_chain; 38 | let mut tbs = None; 39 | /* 40 | type MerkleTreeLeaf struct { 41 | Version Version `tls:"maxval:255"` 42 | LeafType MerkleLeafType `tls:"maxval:255"` 43 | TimestampedEntry *TimestampedEntry `tls:"selector:LeafType,val:0"` 44 | } 45 | */ 46 | fn err_invalid() -> Result { 47 | Err(Error::MalformedResponseBody("Invalid leaf data.".to_owned())) 48 | } 49 | fn err_invalid_extra() -> Result { 50 | Err(Error::MalformedResponseBody("Invalid extra data.".to_owned())) 51 | } 52 | if leaf_input.len() < 2 { 53 | return err_invalid(); 54 | } 55 | let mut leaf_slice = &leaf_input[..]; 56 | let version = u8::from_be_bytes([leaf_slice[0]]); 57 | let leaf_type = u8::from_be_bytes([leaf_slice[1]]); 58 | if version != 0 || leaf_type != 0 { 59 | return err_invalid(); // TODO should ignore. 60 | } 61 | leaf_slice = &leaf_slice[2..]; 62 | /* 63 | type TimestampedEntry struct { 64 | Timestamp uint64 65 | EntryType LogEntryType `tls:"maxval:65535"` 66 | X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` 67 | PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` 68 | JSONEntry *JSONDataEntry `tls:"selector:EntryType,val:32768"` 69 | Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` 70 | } 71 | */ 72 | if leaf_slice.len() < 8 + 2 { 73 | return err_invalid(); 74 | } 75 | let timestamp = u64::from_be_bytes(leaf_slice[0..8].try_into().unwrap()); 76 | leaf_slice = &leaf_slice[8..]; 77 | let entry_type = u16::from_be_bytes([leaf_slice[0], leaf_slice[1]]); 78 | leaf_slice = &leaf_slice[2..]; 79 | let issuer_key_hash; 80 | match entry_type { 81 | 0 => { // x509_entry 82 | is_pre_cert = false; 83 | issuer_key_hash = None; 84 | // len is u24 85 | if leaf_slice.len() < 3 { 86 | return err_invalid(); 87 | } 88 | let len = u32::from_be_bytes([0, leaf_slice[0], leaf_slice[1], leaf_slice[2]]); 89 | leaf_slice = &leaf_slice[3..]; 90 | if leaf_slice.len() < len as usize { 91 | return err_invalid(); 92 | } 93 | let x509_end = &leaf_slice[..len as usize]; // DER certificate 94 | leaf_slice = &leaf_slice[len as usize..]; 95 | // rest of leaf_slice parsed below this match statement. 96 | 97 | // Extra data is [][]byte with all length u24. 98 | let mut extra_slice = &extra_data[..]; 99 | if extra_slice.len() < 3 { 100 | return err_invalid_extra(); 101 | } 102 | let chain_byte_len = u32::from_be_bytes([0, extra_slice[0], extra_slice[1], extra_slice[2]]); 103 | extra_slice = &extra_slice[3..]; 104 | if extra_slice.len() != chain_byte_len as usize { 105 | return err_invalid_extra(); 106 | } 107 | x509_chain = Vec::new(); 108 | x509_chain.push(Vec::from(x509_end)); 109 | while !extra_slice.is_empty() { 110 | if extra_slice.len() < 3 { 111 | return err_invalid_extra(); 112 | } 113 | let len = u32::from_be_bytes([0, extra_slice[0], extra_slice[1], extra_slice[2]]); 114 | extra_slice = &extra_slice[3..]; 115 | if extra_slice.len() < len as usize { 116 | return err_invalid_extra(); 117 | } 118 | let data = &extra_slice[..len as usize]; 119 | extra_slice = &extra_slice[len as usize..]; 120 | x509_chain.push(Vec::from(data)); 121 | } 122 | }, 123 | 1 => { // precert_entry 124 | /* 125 | type PreCert struct { 126 | IssuerKeyHash [sha256.Size]byte 127 | TBSCertificate []byte `tls:"minlen:1,maxlen:16777215"` // DER-encoded TBSCertificate 128 | } 129 | */ 130 | is_pre_cert = true; 131 | if leaf_slice.len() < 32 { 132 | return err_invalid(); 133 | } 134 | issuer_key_hash = Some(leaf_slice[0..32].to_owned()); 135 | leaf_slice = &leaf_slice[32..]; 136 | if leaf_slice.len() < 3 { 137 | return err_invalid(); 138 | } 139 | let len = u32::from_be_bytes([0, leaf_slice[0], leaf_slice[1], leaf_slice[2]]); 140 | leaf_slice = &leaf_slice[3..]; 141 | if leaf_slice.len() < len as usize { 142 | return err_invalid(); 143 | } 144 | let tbs_data = &leaf_slice[..len as usize]; 145 | leaf_slice = &leaf_slice[len as usize..]; 146 | // rest of leaf_slice parsed below this match statement. 147 | 148 | tbs = Some(tbs_data.to_vec()); 149 | 150 | /* Extra data: 151 | type PrecertChainEntry struct { 152 | PreCertificate ASN1Cert `tls:"minlen:1,maxlen:16777215"` 153 | CertificateChain []ASN1Cert `tls:"minlen:0,maxlen:16777215"` 154 | } 155 | */ 156 | 157 | let mut extra_slice = &extra_data[..]; 158 | if extra_slice.len() < 3 { 159 | return err_invalid_extra(); 160 | } 161 | let pre_cert_len = u32::from_be_bytes([0, extra_slice[0], extra_slice[1], extra_slice[2]]); 162 | extra_slice = &extra_slice[3..]; 163 | if extra_slice.len() < pre_cert_len as usize { 164 | return err_invalid_extra(); 165 | } 166 | let pre_cert_data = &extra_slice[..pre_cert_len as usize]; 167 | extra_slice = &extra_slice[pre_cert_len as usize..]; 168 | x509_chain = Vec::new(); 169 | x509_chain.push(Vec::from(pre_cert_data)); 170 | if extra_slice.len() < 3 { 171 | return err_invalid_extra(); 172 | } 173 | let rest_len = u32::from_be_bytes([0, extra_slice[0], extra_slice[1], extra_slice[2]]); 174 | extra_slice = &extra_slice[3..]; 175 | if extra_slice.len() != rest_len as usize { 176 | return err_invalid_extra(); 177 | } 178 | while !extra_slice.is_empty() { 179 | if extra_slice.len() < 3 { 180 | return err_invalid_extra(); 181 | } 182 | let len = u32::from_be_bytes([0, extra_slice[0], extra_slice[1], extra_slice[2]]); 183 | extra_slice = &extra_slice[3..]; 184 | if extra_slice.len() < len as usize { 185 | return err_invalid_extra(); 186 | } 187 | let data = &extra_slice[..len as usize]; 188 | extra_slice = &extra_slice[len as usize..]; 189 | x509_chain.push(Vec::from(data)); 190 | } 191 | }, 192 | _ => { 193 | return err_invalid(); // TODO should ignore. 194 | } 195 | } 196 | if leaf_slice.len() < 2 { 197 | return err_invalid(); 198 | } 199 | let extension_len = u16::from_be_bytes([leaf_slice[0], leaf_slice[1]]); 200 | leaf_slice = &leaf_slice[2..]; 201 | if leaf_slice.len() != extension_len as usize { 202 | return err_invalid(); 203 | } 204 | Ok(Leaf{hash, is_pre_cert, x509_chain, tbs_cert: tbs, timestamp, extensions: leaf_slice.to_owned(), issuer_key_hash}) 205 | } 206 | } 207 | 208 | impl TryFrom<&jsons::LeafEntry> for Leaf { 209 | type Error = Error; 210 | fn try_from(le: &jsons::LeafEntry) -> Result { 211 | let leaf_input = base64::decode(&le.leaf_input).map_err(|e| Error::MalformedResponseBody(format!("base64 decode leaf_input: {}", &e)))?; 212 | let extra_data = base64::decode(&le.extra_data).map_err(|e| Error::MalformedResponseBody(format!("base64 decode extra_data: {}", &e)))?; 213 | Leaf::from_raw(&leaf_input, &extra_data) 214 | } 215 | } 216 | 217 | impl fmt::Debug for Leaf { 218 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 219 | write!(f, "Leaf({})", &utils::u8_to_hex(&self.hash))?; 220 | if self.is_pre_cert { 221 | write!(f, " (pre_cert)")?; 222 | } 223 | Ok(()) 224 | } 225 | } 226 | 227 | /// Turn some raw leaf data into leaf hash. 228 | /// 229 | /// Used in [`crate::SignedCertificateTimestamp::derive_leaf_hash`]. 230 | pub mod leaf_hash_constructors { 231 | use crate::utils; 232 | 233 | pub fn with_x509(x509_endcert: &[u8], timestamp: u64, extensions_data: &[u8]) -> [u8; 32] { 234 | let mut hash_data: Vec = Vec::new(); 235 | hash_data.push(0); // hash type = leaf data 236 | // Merkle tree leaf_input: 237 | hash_data.push(0); // version = 0 238 | hash_data.push(0); // leaf type = 0 239 | hash_data.extend_from_slice(×tamp.to_be_bytes()); // timestamp 240 | hash_data.extend_from_slice(&0u16.to_be_bytes()); // entry type = x509_entry 241 | // all there is left is just chain[0]. 242 | assert!(x509_endcert.len() < 1<<24); 243 | hash_data.extend_from_slice(&(x509_endcert.len() as u32).to_be_bytes()[1..4]); // len of x509 244 | hash_data.extend_from_slice(&x509_endcert); // x509 data 245 | assert!(extensions_data.len() < 1<<16); 246 | hash_data.extend_from_slice(&(extensions_data.len() as u16).to_be_bytes()); // len of extensions 247 | hash_data.extend_from_slice(&extensions_data); // extensions 248 | utils::sha256(&hash_data) 249 | } 250 | 251 | pub fn with_precert(tbs: &[u8], issuer_key_hash: &[u8], timestamp: u64, extensions_data: &[u8]) -> [u8; 32] { 252 | assert_eq!(issuer_key_hash.len(), 32); 253 | let mut hash_data: Vec = Vec::new(); 254 | hash_data.push(0); // hash type = leaf data 255 | // Merkle tree leaf_input: 256 | hash_data.push(0); // version = 0 257 | hash_data.push(0); // leaf type = 0 258 | hash_data.extend_from_slice(×tamp.to_be_bytes()); // timestamp 259 | hash_data.extend_from_slice(&1u16.to_be_bytes()); // entry type = precert_entry 260 | hash_data.extend_from_slice(issuer_key_hash); // issuer_key_hash 261 | assert!(tbs.len() < 1<<24); 262 | hash_data.extend_from_slice(&(tbs.len() as u32).to_be_bytes()[1..4]); // len of tbs 263 | hash_data.extend_from_slice(tbs); // tbs 264 | assert!(extensions_data.len() < 1<<16); 265 | hash_data.extend_from_slice(&(extensions_data.len() as u16).to_be_bytes()); // len of extensions 266 | hash_data.extend_from_slice(&extensions_data); // extensions 267 | utils::sha256(&hash_data) 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/internal/mod.rs: -------------------------------------------------------------------------------- 1 | //! Things that are only useful if you are doing your own API calling. 2 | //! 3 | //! Note that the RFC calls inclusion proof "audit proof". 4 | 5 | use std::convert::TryInto; 6 | 7 | use log::{debug, trace}; 8 | use openssl::pkey::PKey; 9 | 10 | pub use consistency::*; 11 | pub use digitally_signed_struct::*; 12 | pub use getentries::*; 13 | pub use inclusion::*; 14 | pub use leaf::*; 15 | use crate::{Error, jsons, SignedTreeHead, utils}; 16 | 17 | mod consistency; 18 | mod getentries; 19 | mod leaf; 20 | mod digitally_signed_struct; 21 | pub mod openssl_ffi; 22 | mod inclusion; 23 | 24 | /// Construct a new [`reqwest::Client`] to be used with the 25 | /// functions in this module. You don't necessary need to use this. 26 | /// 27 | /// The client constructed will not store cookie or follow redirect. 28 | pub fn new_http_client() -> Result { 29 | use std::time; 30 | let mut def_headers = reqwest::header::HeaderMap::new(); 31 | def_headers.insert("User-Agent", reqwest::header::HeaderValue::from_static("rust-ctclient")); 32 | match reqwest::blocking::Client::builder() 33 | .connect_timeout(time::Duration::from_secs(5)) 34 | .tcp_nodelay() 35 | .gzip(true) 36 | .default_headers(def_headers) 37 | .redirect(reqwest::redirect::Policy::none()) 38 | .build() { 39 | Ok(r) => Ok(r), 40 | Err(e) => Err(Error::Unknown(format!("{}", &e))) 41 | } 42 | } 43 | 44 | /// Perform a GET request and parse the result as a JSON. 45 | pub fn get_json(client: &reqwest::blocking::Client, base_url: &reqwest::Url, path: &str) -> Result { 46 | let url = base_url.join(path).unwrap(); 47 | let url_str = url.as_str().to_owned(); 48 | let response = client.get(url).send().map_err(Error::NetIO)?; 49 | if response.status().as_u16() != 200 { 50 | debug!("GET {} -> {}", &url_str, response.status()); 51 | return Err(Error::InvalidResponseStatus(response.status())); 52 | } 53 | let response = response.text().map_err(Error::NetIO)?; 54 | if response.len() > 150 { 55 | debug!("GET {} -> {:?}...", &url_str, &response[..150]); 56 | } else { 57 | trace!("GET {} -> {:?}", &url_str, &response); 58 | } 59 | let json = serde_json::from_str(&response).map_err(|e| Error::MalformedResponseBody(format!("Unable to decode JSON: {} (response is {:?})", &e, &response)))?; 60 | Ok(json) 61 | } 62 | 63 | /// Check, verify and return the latest tree head from the CT log at 64 | /// `base_url`. 65 | /// 66 | /// This function is only useful to those who want to do some custom CT API 67 | /// calling. [`CTClient`](crate::CTClient) will automatically update its cache of 68 | /// tree root. If you use `CTClient`, call 69 | /// [`CTClient::get_checked_tree_head`](crate::CTClient::get_checked_tree_head) 70 | /// instead. 71 | /// 72 | /// # Params 73 | /// 74 | /// * `client`: A [`reqwest::Client`](reqwest::Client) instance. See 75 | /// [`CTClient::get_reqwest_client`](crate::CTClient::get_reqwest_client) 76 | pub fn check_tree_head(client: &reqwest::blocking::Client, base_url: &reqwest::Url, pub_key: &PKey) -> Result { 77 | let response: jsons::STH = get_json(client, base_url, "ct/v1/get-sth")?; 78 | let root_hash = base64::decode(&response.sha256_root_hash).map_err(|e| Error::MalformedResponseBody(format!("base64 decode failure on root sha256: {} (trying to decode {:?})", &e, &response.sha256_root_hash)))?; 79 | if root_hash.len() != 32 { 80 | return Err(Error::MalformedResponseBody(format!("Invalid server response: sha256_root_hash should have length of 32. Server response is {:?}", &response))); 81 | } 82 | let dss = base64::decode(&response.tree_head_signature).map_err(|e| Error::MalformedResponseBody(format!("base64 decode failure on signature: {} (trying to decode {:?})", &e, &response.tree_head_signature)))?; 83 | let sth = SignedTreeHead { 84 | tree_size: response.tree_size, 85 | timestamp: response.timestamp, 86 | root_hash: root_hash[..].try_into().unwrap(), 87 | signature: dss 88 | }; 89 | sth.verify(pub_key)?; 90 | trace!("{} tree head now on {} {}", base_url.as_str(), sth.tree_size, &utils::u8_to_hex(&sth.root_hash)); 91 | Ok(sth) 92 | } 93 | 94 | -------------------------------------------------------------------------------- /src/internal/openssl_ffi.rs: -------------------------------------------------------------------------------- 1 | //! Because `openssl` crate is incomplete. 2 | 3 | use std::convert::TryFrom; 4 | use std::ptr::null_mut; 5 | 6 | use foreign_types::{ForeignType, ForeignTypeRef}; 7 | use openssl::error::ErrorStack; 8 | use openssl::stack::{Stack, Stackable}; 9 | use openssl::x509::X509Ref; 10 | use openssl_sys::ASN1_OBJECT; 11 | 12 | use foreign::*; 13 | 14 | /// Because `openssl_sys` crate is incomplete. 15 | #[allow(non_camel_case_types)] 16 | pub mod foreign { 17 | pub enum SCT_LIST {} 18 | pub enum SCT {} 19 | 20 | pub type sct_version_t = i32; 21 | pub const SCT_VERSION_NOT_SET: sct_version_t = -1; 22 | pub const SCT_VERSION_V1: sct_version_t = 0; 23 | 24 | extern "C" { 25 | pub fn i2d_re_X509_tbs( 26 | x: *mut openssl_sys::X509, 27 | pp: *mut *mut std::os::raw::c_uchar, 28 | ) -> std::os::raw::c_int; 29 | 30 | pub fn X509_get_ext_by_OBJ( 31 | x: *const openssl_sys::X509, 32 | obj: *const openssl_sys::ASN1_OBJECT, 33 | lastpos: ::std::os::raw::c_int, 34 | ) -> ::std::os::raw::c_int; 35 | 36 | pub fn X509_get_ext(x: *const openssl_sys::X509, loc: ::std::os::raw::c_int) -> *mut openssl_sys::X509_EXTENSION; 37 | pub fn X509_delete_ext(x: *mut openssl_sys::X509, loc: ::std::os::raw::c_int) -> *mut openssl_sys::X509_EXTENSION; 38 | 39 | pub fn OBJ_txt2obj( 40 | s: *const ::std::os::raw::c_char, 41 | no_name: ::std::os::raw::c_int, 42 | ) -> *mut openssl_sys::ASN1_OBJECT; 43 | 44 | pub fn ASN1_OBJECT_free(a: *mut openssl_sys::ASN1_OBJECT); 45 | 46 | pub fn X509_EXTENSION_free(a: *mut openssl_sys::X509_EXTENSION); 47 | 48 | pub fn X509_dup(x509: *mut openssl_sys::X509) -> *mut openssl_sys::X509; 49 | 50 | pub fn X509_EXTENSION_get_data(ne: *mut openssl_sys::X509_EXTENSION) -> *mut openssl_sys::ASN1_OCTET_STRING; 51 | pub fn X509_EXTENSION_set_data( 52 | ex: *mut openssl_sys::X509_EXTENSION, 53 | data: *mut openssl_sys::ASN1_OCTET_STRING, 54 | ) -> ::std::os::raw::c_int; 55 | 56 | 57 | pub fn d2i_SCT_LIST( 58 | a: *mut *mut SCT_LIST, 59 | pp: *mut *const ::std::os::raw::c_uchar, 60 | len: ::std::os::raw::c_long, 61 | ) -> *mut SCT_LIST; 62 | 63 | pub fn SCT_LIST_free(a: *mut SCT_LIST); 64 | 65 | pub fn SCT_get_version(sct: *const SCT) -> sct_version_t; 66 | pub fn SCT_get0_log_id(sct: *const SCT, log_id: *mut *mut ::std::os::raw::c_uchar) -> ::std::os::raw::c_ulong; 67 | pub fn SCT_get_timestamp(sct: *const SCT) -> u64; 68 | pub fn SCT_get0_extensions(sct: *const SCT, ext: *mut *mut ::std::os::raw::c_uchar) -> ::std::os::raw::c_ulong; 69 | pub fn SCT_get_signature_nid(sct: *const SCT) -> ::std::os::raw::c_int; 70 | pub fn SCT_get0_signature(sct: *const SCT, sig: *mut *mut ::std::os::raw::c_uchar) -> ::std::os::raw::c_ulong; 71 | pub fn SCT_free(sct: *mut SCT); 72 | 73 | pub fn X509_set_issuer_name(x: *mut openssl_sys::X509, name: *mut openssl_sys::X509_NAME) -> ::std::os::raw::c_int; 74 | pub fn X509_get_subject_name(a: *const openssl_sys::X509) -> *mut openssl_sys::X509_NAME; 75 | 76 | pub fn ASN1_STRING_new() -> *mut openssl_sys::ASN1_STRING; 77 | pub fn ASN1_STRING_set( 78 | str: *mut openssl_sys::ASN1_STRING, 79 | data: *const ::std::os::raw::c_void, 80 | len: ::std::os::raw::c_int, 81 | ) -> ::std::os::raw::c_int; 82 | } 83 | } 84 | 85 | foreign_types::foreign_type! { 86 | type CType = foreign::SCT; 87 | fn drop = foreign::SCT_free; 88 | /// An owned reference to a openssl `SCT` struct. 89 | pub struct Sct; 90 | /// A reference to a openssl `SCT` struct. 91 | pub struct SctRef; 92 | } 93 | 94 | impl Stackable for Sct { 95 | type StackType = foreign::SCT_LIST; 96 | } 97 | 98 | /// An owned `STACK_OF(SCT)`. 99 | pub type SctList = Stack; 100 | 101 | pub fn x509_clone>(src: &R) -> Result { 102 | unsafe { 103 | let cloned_ptr = X509_dup(src.as_ref().as_ptr()); 104 | if cloned_ptr.is_null() { 105 | return Err(ErrorStack::get()); 106 | } 107 | Ok(openssl::x509::X509::from_ptr(cloned_ptr)) 108 | } 109 | } 110 | 111 | struct WrappedObjPointer(*mut ASN1_OBJECT); 112 | 113 | unsafe impl Sync for WrappedObjPointer {} 114 | 115 | impl Drop for WrappedObjPointer { 116 | fn drop(&mut self) { 117 | let ptr = self.0; 118 | unsafe { 119 | ASN1_OBJECT_free(ptr); 120 | } 121 | } 122 | } 123 | 124 | unsafe fn oid_to_obj(zero_terminated_oid: &'static str) -> WrappedObjPointer { 125 | let ptr = OBJ_txt2obj(zero_terminated_oid.as_ptr() as *const _, 1); 126 | if ptr.is_null() { 127 | panic!("OBJ_txt2obj failed."); 128 | } 129 | WrappedObjPointer(ptr) 130 | } 131 | 132 | lazy_static! { 133 | static ref POISON_ASN1_OBJECT: WrappedObjPointer = unsafe { oid_to_obj("1.3.6.1.4.1.11129.2.4.3\0") }; 134 | static ref SCT_LIST_ASN1_OBJECT: WrappedObjPointer = unsafe { oid_to_obj("1.3.6.1.4.1.11129.2.4.2\0") }; 135 | static ref AUTHORITY_KEY_IDENTIFIER: WrappedObjPointer = unsafe { oid_to_obj("2.5.29.35\0") }; 136 | static ref SUBJECT_KEY_IDENTIFIER: WrappedObjPointer = unsafe { oid_to_obj("2.5.29.14\0") }; 137 | } 138 | 139 | unsafe fn x509_remove_extension_by_obj(cert: &mut openssl::x509::X509, obj: *const ASN1_OBJECT) -> Result<(), ErrorStack> { 140 | let extpos = X509_get_ext_by_OBJ(cert.as_ptr(), obj, -1); 141 | if extpos == -1 { 142 | return Ok(()); 143 | } 144 | let ext = X509_delete_ext(cert.as_ptr(), extpos); 145 | if ext.is_null() { 146 | Err(ErrorStack::get()) 147 | } else { 148 | X509_EXTENSION_free(ext); 149 | Ok(()) 150 | } 151 | } 152 | 153 | pub fn x509_remove_poison(cert: &mut openssl::x509::X509) -> Result<(), ErrorStack> { 154 | unsafe { 155 | x509_remove_extension_by_obj(cert, POISON_ASN1_OBJECT.0) 156 | } 157 | } 158 | 159 | pub fn x509_remove_sct_list(cert: &mut openssl::x509::X509) -> Result<(), openssl::error::ErrorStack> { 160 | unsafe { 161 | x509_remove_extension_by_obj(cert, SCT_LIST_ASN1_OBJECT.0) 162 | } 163 | } 164 | 165 | unsafe fn asn1_string_to_bytes<'a>(asn1_str: *mut openssl_sys::ASN1_STRING) -> &'a [u8] { 166 | let data_len = usize::try_from(openssl_sys::ASN1_STRING_length(asn1_str)).unwrap(); 167 | let data_ptr = openssl_sys::ASN1_STRING_get0_data(asn1_str); 168 | assert!(!data_ptr.is_null()); 169 | &*std::ptr::slice_from_raw_parts(data_ptr, data_len) 170 | } 171 | 172 | fn bytes_to_asn1_string(bytes: &[u8]) -> openssl::asn1::Asn1String { 173 | unsafe { 174 | let asn1 = openssl::asn1::Asn1String::from_ptr(ASN1_STRING_new()); 175 | ASN1_STRING_set(asn1.as_ptr(), bytes.as_ptr() as *const _, bytes.len() as _); 176 | asn1 177 | } 178 | } 179 | 180 | fn x509_get_ext_data<'a>(cert: &'a X509Ref, ext: &WrappedObjPointer) -> Result, ErrorStack> { 181 | unsafe { 182 | let extpos = X509_get_ext_by_OBJ(cert.as_ptr(), ext.0, -1); 183 | if extpos == -1 { 184 | return Ok(None); 185 | } 186 | let ext = X509_get_ext(cert.as_ptr(), extpos); 187 | if ext.is_null() { 188 | return Err(ErrorStack::get()); 189 | } 190 | // ASN1_OCTET_STRING is the same as ASN1_STRING: https://www.openssl.org/docs/man1.1.1/man3/ASN1_STRING_get0_data.html#NOTES 191 | Ok(Some(asn1_string_to_bytes( 192 | X509_EXTENSION_get_data(ext) as *mut _ 193 | ))) 194 | } 195 | } 196 | 197 | fn x509_set_ext_data(cert: &mut openssl::x509::X509, ext: &WrappedObjPointer, data: &[u8]) -> Result<(), crate::Error> { 198 | unsafe { 199 | use crate::Error; 200 | let extpos = X509_get_ext_by_OBJ(cert.as_ptr(), ext.0, -1); 201 | if extpos == -1 { 202 | return Err(Error::Unknown("x509_set_ext_data: no such extension".to_owned())); 203 | } 204 | let ext = X509_get_ext(cert.as_ptr(), extpos); 205 | if ext.is_null() { 206 | return Err(Error::Unknown(ErrorStack::get().to_string())); 207 | } 208 | X509_EXTENSION_set_data(ext, bytes_to_asn1_string(data).as_ptr() as *mut _); 209 | Ok(()) 210 | } 211 | } 212 | 213 | pub fn x509_to_tbs>(cert: &R) -> Result, ErrorStack> { 214 | unsafe { 215 | let mut buf: *mut u8 = null_mut(); 216 | let ret = i2d_re_X509_tbs(cert.as_ref().as_ptr(), &mut buf as *mut _); 217 | if ret < 0 { 218 | return Err(ErrorStack::get()); 219 | } 220 | if buf.is_null() { 221 | return Ok(Vec::new()); 222 | } 223 | let size = usize::try_from(ret).unwrap(); 224 | let mut owned_buf = Vec::with_capacity(size); 225 | std::ptr::copy_nonoverlapping(buf, owned_buf.as_mut_ptr(), size); 226 | owned_buf.set_len(size); 227 | openssl_sys::CRYPTO_free(buf as *mut _, "openssl_ffi.rs\0".as_ptr() as *const _, line!() as i32); 228 | Ok(owned_buf) 229 | } 230 | } 231 | 232 | pub fn sct_list_from_x509>(cert: &R) -> Result, crate::Error> { 233 | let data = x509_get_ext_data(cert.as_ref(), &SCT_LIST_ASN1_OBJECT).map_err(|e| crate::Error::BadCertificate(format!("{}", e)))?; 234 | if data.is_none() { 235 | return Ok(None); 236 | } 237 | let data = data.unwrap(); 238 | if data.is_empty() { 239 | return Ok(None); 240 | } 241 | let mut pp = data.as_ptr(); 242 | unsafe { 243 | let res = d2i_SCT_LIST(std::ptr::null_mut(), &mut pp as *mut _, i64::try_from(data.len()).unwrap()); 244 | if res.is_null() { 245 | return Err(crate::Error::BadSct(format!("{}", ErrorStack::get()))); 246 | } 247 | if pp != data.as_ptr().add(data.len()) { 248 | return Err(crate::Error::BadSct("SCT extension data not fully consumed.".to_owned())); 249 | } 250 | Ok(Some(SctList::from_ptr(res))) 251 | } 252 | } 253 | 254 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 255 | pub enum SCTVersion { 256 | V1 257 | } 258 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)] 259 | pub enum SignatureAlgorithm { 260 | Sha256Rsa, 261 | Sha256Ecdsa 262 | } 263 | 264 | macro_rules! impl_get_data_fn { 265 | ($fnname:ident, $fn:expr) => { 266 | pub fn $fnname<'a>(&'a self) -> &'a [u8] { 267 | unsafe { 268 | let mut ptr: *mut u8 = std::ptr::null_mut(); 269 | let size = $fn(self.as_ptr(), &mut ptr as *mut _); 270 | &*std::ptr::slice_from_raw_parts(ptr, size as usize) 271 | } 272 | } 273 | }; 274 | } 275 | 276 | impl SctRef { 277 | pub fn version(&self) -> Option { 278 | let raw_version = unsafe {SCT_get_version(self.as_ptr())}; 279 | match raw_version { 280 | SCT_VERSION_V1 => Some(SCTVersion::V1), 281 | _ => None 282 | } 283 | } 284 | 285 | impl_get_data_fn!(log_id, SCT_get0_log_id); 286 | 287 | pub fn timestamp(&self) -> u64 { 288 | unsafe { 289 | SCT_get_timestamp(self.as_ptr()) 290 | } 291 | } 292 | 293 | impl_get_data_fn!(extensions, SCT_get0_extensions); 294 | 295 | pub fn signature_algorithm(&self) -> Option { 296 | let nid = unsafe { SCT_get_signature_nid(self.as_ptr()) }; 297 | match nid { 298 | 668 => Some(SignatureAlgorithm::Sha256Rsa), 299 | 794 => Some(SignatureAlgorithm::Sha256Ecdsa), 300 | _ => None 301 | } 302 | } 303 | 304 | impl_get_data_fn!(raw_signature, SCT_get0_signature); 305 | } 306 | 307 | /// Set the issuer name of `dst` to be the subject name of `src`, and also set the authorityKeyIdentifier of a to the 308 | /// subjectKeyIdentifier of b. 309 | pub fn x509_make_a_looks_like_issued_by_b(a: &mut openssl::x509::X509, b: &openssl::x509::X509Ref) -> Result<(), crate::Error> { 310 | use crate::Error; 311 | let subj_name = unsafe { X509_get_subject_name(b.as_ptr()) }; 312 | // subj_name is an internal pointer to data in src. 313 | let ret = unsafe { X509_set_issuer_name(a.as_ptr(), subj_name) }; 314 | // X509_set_issuer_name copies the data pointed to by subj_name. 315 | if ret != 1 { 316 | Err(Error::Unknown(ErrorStack::get().to_string())) 317 | } else { 318 | let subj_auth_keyid = x509_get_ext_data(b, &SUBJECT_KEY_IDENTIFIER) 319 | .map_err(|e| Error::Unknown(e.to_string()))? 320 | .ok_or_else(|| Error::Unknown("x509_get_ext_data returned None".to_owned()))?; 321 | if subj_auth_keyid.len() < 2 || subj_auth_keyid.len() > (1<<8) - 1 { 322 | return Err(Error::BadCertificate("Bad subjectKeyIdentifier".to_owned())); 323 | } 324 | let key = &subj_auth_keyid[2..]; 325 | if &subj_auth_keyid[0..2] != &[0x04, key.len() as u8] { 326 | return Err(Error::BadCertificate("Bad subjectKeyIdentifier".to_owned())); 327 | } 328 | let mut auth_data = vec![0x30, (key.len() + 2) as u8, 0x80, key.len() as u8]; 329 | auth_data.extend_from_slice(&key); 330 | x509_set_ext_data(a, &AUTHORITY_KEY_IDENTIFIER, &auth_data)?; 331 | Ok(()) 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/jsons.rs: -------------------------------------------------------------------------------- 1 | //! Structs for parsing server response. 2 | 3 | use serde::{Serialize, Deserialize}; 4 | 5 | #[derive(Serialize, Deserialize, Clone, Debug)] 6 | pub struct STH { 7 | pub tree_size: u64, 8 | pub timestamp: u64, 9 | pub sha256_root_hash: String, 10 | pub tree_head_signature: String, 11 | } 12 | 13 | #[derive(Serialize, Deserialize, Clone, Debug)] 14 | pub struct ConsistencyProof { 15 | pub consistency: Vec, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Clone, Debug)] 19 | pub struct GetEntries { 20 | pub entries: Vec, 21 | } 22 | 23 | #[derive(Serialize, Deserialize, Clone, Debug)] 24 | pub struct LeafEntry { 25 | pub leaf_input: String, 26 | pub extra_data: String, 27 | } 28 | 29 | #[derive(Serialize, Deserialize, Clone, Debug)] 30 | pub struct AuditProof { 31 | pub leaf_index: u64, 32 | pub audit_path: Vec, 33 | } 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Certificate Transparency Log client suitable for monitoring, quick 2 | //! SCT validation, gossiping, etc. 3 | //! 4 | //! The source code of this project contains some best-effort explanation 5 | //! comments for others trying to implement such a client. As of 2019, 6 | //! the documentation that exists out there are (in my opinion) pretty lacking, 7 | //! and I had some bad time trying to implement this. 8 | //! 9 | //! All `pub_key` are in DER format, which is the format returned (in base64) 10 | //! by google's trusted log list. `signature`s are *Digitally-signed structs*, and 11 | //! `raw_signature`s are ASN1-encoded signatures. 12 | //! 13 | //! Best effort are made to catch misbehavior by CT logs or invalid certificates. It is up 14 | //! to the user of this library to decide what to do when logs don't behave corrctly. 15 | //! 16 | //! This project is not intended to be a beginner friendly tutorial on how a 17 | //! CT log works. To learn more about CT, you can read [my blog article](https://blog.maowtm.org/ct/en.html) 18 | //! or [the RFC](https://tools.ietf.org/html/rfc6962). 19 | //! 20 | //! API calls are currently all blocking. If anyone is interested in rewriting them in Futures, PR is welcome. 21 | 22 | // todo: gossiping 23 | 24 | #[macro_use(lazy_static)] 25 | extern crate lazy_static; 26 | 27 | use std::{fmt, io, path}; 28 | 29 | use log::{info, warn}; 30 | use openssl::pkey::PKey; 31 | use openssl::x509::X509; 32 | 33 | use internal::new_http_client; 34 | pub use sct::{SctEntry, SignedCertificateTimestamp}; 35 | pub use sth::SignedTreeHead; 36 | 37 | use crate::internal::{check_inclusion_proof, Leaf, fetch_inclusion_proof, check_consistency_proof}; 38 | use crate::internal::openssl_ffi::{x509_clone, x509_make_a_looks_like_issued_by_b}; 39 | 40 | mod sth; 41 | mod sct; 42 | 43 | pub mod utils; 44 | pub mod jsons; 45 | pub mod internal; 46 | pub mod certutils; 47 | pub mod google_log_list; 48 | 49 | /// Errors that this library could produce. 50 | #[derive(Debug)] 51 | pub enum Error { 52 | /// Something strange happened. 53 | Unknown(String), 54 | 55 | /// You provided something bad. 56 | InvalidArgument(String), 57 | 58 | /// File IO error 59 | FileIO(path::PathBuf, io::Error), 60 | 61 | /// Network IO error 62 | NetIO(reqwest::Error), 63 | 64 | /// The CT server provided us with invalid signature. 65 | InvalidSignature(String), 66 | 67 | /// The CT server responded with something other than 200. 68 | InvalidResponseStatus(reqwest::StatusCode), 69 | 70 | /// Server responded with something bad (e.g. malformed JSON) 71 | MalformedResponseBody(String), 72 | 73 | /// Server returned an invalid consistency proof. 74 | InvalidConsistencyProof { prev_size: u64, new_size: u64, desc: String }, 75 | 76 | /// ConsistencyProofPart::verify failed 77 | CannotVerifyTreeData(String), 78 | 79 | /// Something's wrong with the certificate. 80 | BadCertificate(String), 81 | 82 | /// Server returned an invalid inclusion proof. 83 | InvalidInclusionProof { tree_size: u64, leaf_index: u64, desc: String }, 84 | 85 | /// A malformed SCT is given. 86 | BadSct(String), 87 | 88 | /// We asked for a certain entry expecting it to be there, but the server gave us nothing. 89 | ExpectedEntry(u64), 90 | } 91 | 92 | /// Either a fetched and checked [`SignedTreeHead`], or a [`SignedTreeHead`] that has a valid signature 93 | /// but did not pass some internal checks, or just an [`Error`]. 94 | #[derive(Debug)] 95 | pub enum SthResult { 96 | /// Got the new tree head. 97 | Ok(SignedTreeHead), 98 | 99 | /// Something went wrong and no tree head was received. 100 | Err(Error), 101 | 102 | /// Something went wrong, but the server returned a valid signed tree head. 103 | /// The underlying error is wrapped inside. You may wish to log this. 104 | ErrWithSth(Error, SignedTreeHead) 105 | } 106 | 107 | impl SthResult { 108 | /// Return a signed tree head, if there is one received. 109 | /// 110 | /// This can return a `Some` even when there is error, if for example, the server returned a valid signed 111 | /// tree head but failed to provide a consistency proof. You may wish to log this. 112 | pub fn tree_head(&self) -> Option<&SignedTreeHead> { 113 | match self { 114 | SthResult::Ok(sth) => Some(sth), 115 | SthResult::Err(_) => None, 116 | SthResult::ErrWithSth(_, sth) => Some(sth) 117 | } 118 | } 119 | 120 | pub fn is_ok(&self) -> bool { 121 | match self { 122 | SthResult::Ok(_) => true, 123 | _ => false 124 | } 125 | } 126 | 127 | pub fn is_err(&self) -> bool { 128 | !self.is_ok() 129 | } 130 | 131 | /// Return the [`SignedTreeHead`], if this is a Ok. Otherwise panic. 132 | pub fn unwrap(self) -> SignedTreeHead { 133 | match self { 134 | SthResult::Ok(sth) => sth, 135 | _ => { 136 | panic!("unwrap called on SthResult with error: {}", self.unwrap_err()) 137 | } 138 | } 139 | } 140 | 141 | /// Return the [`Error`], if this is an `Err` or `ErrWithSth`. Otherwise panic. 142 | pub fn unwrap_err(self) -> Error { 143 | match self { 144 | SthResult::ErrWithSth(e, _) => e, 145 | SthResult::Err(e) => e, 146 | _ => panic!("unwrap_err called on SthResult that is ok.") 147 | } 148 | } 149 | 150 | /// Return the [`SignedTreeHead`], if this is a `Ok` or `ErrWithSth`. Otherwise panic. 151 | pub fn unwrap_tree_head(self) -> SignedTreeHead { 152 | match self { 153 | SthResult::Ok(sth) => sth, 154 | SthResult::ErrWithSth(_, sth) => sth, 155 | SthResult::Err(e) => panic!("unwrap_tree_head called on SthResult with error: {}", e) 156 | } 157 | } 158 | } 159 | 160 | impl fmt::Display for Error { 161 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 162 | match self { 163 | Error::Unknown(desc) => write!(f, "{}", desc), 164 | Error::InvalidArgument(desc) => write!(f, "Invalid argument: {}", desc), 165 | Error::FileIO(path, e) => write!(f, "{}: {}", path.to_string_lossy(), &e), 166 | Error::NetIO(e) => write!(f, "Network IO error: {}", &e), 167 | Error::InvalidSignature(desc) => write!(f, "Invalid signature received: {}", &desc), 168 | Error::InvalidResponseStatus(response_code) => write!(f, "Server responded with {} {}", response_code.as_u16(), response_code.as_str()), 169 | Error::MalformedResponseBody(desc) => write!(f, "Unable to parse server response: {}", &desc), 170 | Error::InvalidConsistencyProof {prev_size, new_size, desc} => write!(f, "Server provided an invalid consistency proof from {} to {}: {}", prev_size, new_size, &desc), 171 | Error::CannotVerifyTreeData(desc) => write!(f, "The certificates returned by the server is inconsistent with the previously provided consistency proof: {}", &desc), 172 | Error::BadCertificate(desc) => write!(f, "The certificate returned by the server has a problem: {}", &desc), 173 | Error::InvalidInclusionProof {tree_size, leaf_index, desc} => write!(f, "Server provided an invalid inclusion proof of {} in tree with size {}: {}", leaf_index, tree_size, desc), 174 | Error::BadSct(desc) => write!(f, "The SCT received is invalid: {}", desc), 175 | Error::ExpectedEntry(leaf_index) => write!(f, "The server did not return the leaf with index {}, even though we believe it should be there.", leaf_index), 176 | } 177 | } 178 | } 179 | 180 | /// A stateful CT monitor. 181 | /// 182 | /// One instance of this struct only concerns with one particular log. To monitor multiple 183 | /// logs, you can create multiple such instances and run them on different threads. 184 | /// 185 | /// It remembers a last checked tree root, so that it only checks the newly added 186 | /// certificates in the log each time you call [`update`](Self::update). 187 | pub struct CTClient { 188 | base_url: reqwest::Url, 189 | pub_key: PKey, 190 | http_client: reqwest::blocking::Client, 191 | latest_size: u64, 192 | latest_tree_hash: [u8; 32] 193 | } 194 | 195 | impl fmt::Debug for CTClient { 196 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 197 | write!(f, "CT log {}: current root = {}, size = {}", self.base_url, utils::u8_to_hex(&self.latest_tree_hash[..]), self.latest_size) 198 | } 199 | } 200 | 201 | impl CTClient { 202 | /// Construct a new `CTClient` instance, and fetch the latest tree root. 203 | /// 204 | /// Previous certificates in this log will not be checked. 205 | /// 206 | /// # Errors 207 | /// 208 | /// * If `base_url` does not ends with `/`. 209 | /// 210 | /// # Example 211 | /// 212 | /// ``` 213 | /// use ctclient::CTClient; 214 | /// use base64::decode; 215 | /// // URL and public key copy-pasted from https://www.gstatic.com/ct/log_list/v2/all_logs_list.json . 216 | /// let public_key = decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01EAhx4o0zPQrXTcYjgCt4MVFsT0Pwjzb1RwrM0lhWDlxAYPP6/gyMCXNkOn/7KFsjL7rwk78tHMpY8rXn8AYg==").unwrap(); 217 | /// let client = CTClient::new_from_latest_th("https://ct.cloudflare.com/logs/nimbus2020/", &public_key).unwrap(); 218 | /// ``` 219 | pub fn new_from_latest_th(base_url: &str, pub_key: &[u8]) -> Result { 220 | if !base_url.ends_with('/') { 221 | return Err(Error::InvalidArgument("baseUrl must end with /".to_owned())); 222 | } 223 | let base_url = reqwest::Url::parse(base_url).map_err(|e| Error::InvalidArgument(format!("Unable to parse url: {}", &e)))?; 224 | let http_client = new_http_client()?; 225 | let evp_pkey = PKey::public_key_from_der(pub_key).map_err(|e| Error::InvalidArgument(format!("Error parsing public key: {}", &e)))?; 226 | let sth = internal::check_tree_head(&http_client, &base_url, &evp_pkey)?; 227 | Ok(CTClient{ 228 | base_url, 229 | pub_key: evp_pkey, 230 | http_client, 231 | latest_size: sth.tree_size, 232 | latest_tree_hash: sth.root_hash 233 | }) 234 | } 235 | 236 | /// Construct a new `CTClient` that will check all certificates included after 237 | /// the given tree state. 238 | /// 239 | /// Previous certificates in this log before the provided tree hash will not be checked. 240 | /// 241 | /// # Example 242 | /// 243 | /// ``` 244 | /// use ctclient::{CTClient, utils}; 245 | /// use base64::decode; 246 | /// // URL and public key copy-pasted from https://www.gstatic.com/ct/log_list/v2/all_logs_list.json . 247 | /// let public_key = decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01EAhx4o0zPQrXTcYjgCt4MVFsT0Pwjzb1RwrM0lhWDlxAYPP6/gyMCXNkOn/7KFsjL7rwk78tHMpY8rXn8AYg==").unwrap(); 248 | /// use std::convert::TryInto; 249 | /// // Tree captured on 2020-05-12 15:34:11 UTC 250 | /// let th: [u8; 32] = (&utils::hex_to_u8("63875e88a3e37dc5b6cdbe213fe1df490d40193e4777f79467958ee157de70d6")[..]).try_into().unwrap(); 251 | /// let client = CTClient::new_from_perv_tree_hash("https://ct.cloudflare.com/logs/nimbus2020/", &public_key, th, 299304276).unwrap(); 252 | /// ``` 253 | pub fn new_from_perv_tree_hash(base_url: &str, pub_key: &[u8], tree_hash: [u8; 32], tree_size: u64) -> Result { 254 | if !base_url.ends_with('/') { 255 | return Err(Error::InvalidArgument("baseUrl must end with /".to_owned())); 256 | } 257 | let base_url = reqwest::Url::parse(base_url).map_err(|e| Error::InvalidArgument(format!("Unable to parse url: {}", &e)))?; 258 | let http_client = new_http_client()?; 259 | let evp_pkey = PKey::public_key_from_der(pub_key).map_err(|e| Error::InvalidArgument(format!("Error parsing public key: {}", &e)))?; 260 | Ok(CTClient{ 261 | base_url, 262 | pub_key: evp_pkey, 263 | http_client, 264 | latest_size: tree_size, 265 | latest_tree_hash: tree_hash 266 | }) 267 | } 268 | 269 | /// Get the last checked tree head. Returns `(tree_size, root_hash)`. 270 | pub fn get_checked_tree_head(&self) -> (u64, [u8; 32]) { 271 | (self.latest_size, self.latest_tree_hash) 272 | } 273 | 274 | /// Get the underlying http client used to call CT APIs. 275 | pub fn get_reqwest_client(&self) -> &reqwest::blocking::Client { 276 | &self.http_client 277 | } 278 | 279 | /// Get the base_url of the log currently being monitored by this client. 280 | /// 281 | /// This is the url that was passed to the constructor. 282 | pub fn get_base_url(&self) -> &reqwest::Url { 283 | &self.base_url 284 | } 285 | 286 | /// Calls `self.update()` with `None` as `cert_handler`. 287 | pub fn light_update(&mut self) -> SthResult { 288 | self.update(None::) 289 | } 290 | 291 | /// Fetch the latest tree root, check all the new certificates if `cert_handler` is a Some, and update our 292 | /// internal "last checked tree root". 293 | /// 294 | /// This function should never panic, no matter what the server does to us. 295 | /// 296 | /// Return the latest [`SignedTreeHead`] (STH) returned by the server, even if 297 | /// it is the same as last time, or if it rolled back (new tree_size < current tree_size). 298 | /// 299 | /// To log the behavior of CT logs, store the returned tree head and signature in some kind 300 | /// of database (even when error). This can be used to prove a misconduct (such as a non-extending-only tree) 301 | /// in the future. 302 | /// 303 | /// Will only update the stored latest tree head if an [`Ok`](SthResult::Ok) is returned. 304 | pub fn update(&mut self, mut cert_handler: Option) -> SthResult 305 | where H: FnMut(&[X509]) 306 | { 307 | let mut delaycheck = std::time::Instant::now(); 308 | let sth = match internal::check_tree_head(&self.http_client, &self.base_url, &self.pub_key) { 309 | Ok(s) => s, 310 | Err(e) => return SthResult::Err(e) 311 | }; 312 | let new_tree_size = sth.tree_size; 313 | let new_tree_root = sth.root_hash; 314 | use std::cmp::Ordering; 315 | match new_tree_size.cmp(&self.latest_size) { 316 | Ordering::Equal => { 317 | if new_tree_root == self.latest_tree_hash { 318 | info!("{} remained the same.", self.base_url.as_str()); 319 | SthResult::Ok(sth) 320 | } else { 321 | SthResult::ErrWithSth( 322 | Error::InvalidConsistencyProof { 323 | prev_size: self.latest_size, new_size: new_tree_size, desc: format!("Server forked! {} and {} both correspond to tree_size {}", &utils::u8_to_hex(&self.latest_tree_hash), &utils::u8_to_hex(&new_tree_root), new_tree_size) 324 | }, sth 325 | ) 326 | } 327 | }, 328 | Ordering::Less => { 329 | // Make sure server isn't doing trick with us. 330 | match internal::check_consistency_proof( 331 | &self.http_client, 332 | &self.base_url, 333 | new_tree_size, 334 | self.latest_size, 335 | &new_tree_root, 336 | &self.latest_tree_hash 337 | ) { 338 | Ok(_) => { 339 | warn!("{} rolled back? {} -> {}", self.base_url.as_str(), self.latest_size, new_tree_size); 340 | SthResult::Ok(sth) 341 | }, 342 | Err(e) => { 343 | SthResult::ErrWithSth( 344 | Error::InvalidConsistencyProof { 345 | prev_size: new_tree_size, new_size: self.latest_size, desc: format!("Server rolled back, and can't provide a consistency proof from the rolled back tree to the original tree: {}", e) 346 | }, sth 347 | ) 348 | } 349 | } 350 | }, 351 | Ordering::Greater => { 352 | let consistency_proof_parts = match internal::check_consistency_proof(&self.http_client, 353 | &self.base_url, 354 | self.latest_size, 355 | new_tree_size, 356 | &self.latest_tree_hash, 357 | &new_tree_root 358 | ) { 359 | Ok(k) => k, 360 | Err(e) => return SthResult::ErrWithSth(e, sth) 361 | }; 362 | 363 | if cert_handler.is_some() { 364 | let i_start = self.latest_size; 365 | let mut leafs = internal::get_entries(&self.http_client, &self.base_url, i_start..new_tree_size); 366 | let mut leaf_hashes: Vec<[u8; 32]> = Vec::new(); 367 | leaf_hashes.reserve((new_tree_size - i_start) as usize); 368 | for i in i_start..new_tree_size { 369 | match leafs.next() { 370 | Some(Ok(leaf)) => { 371 | leaf_hashes.push(leaf.hash); 372 | if let Err(e) = self.check_leaf(&leaf, &mut cert_handler) { 373 | return SthResult::ErrWithSth(e, sth); 374 | } 375 | }, 376 | Some(Err(e)) => { 377 | return SthResult::ErrWithSth( 378 | if let Error::MalformedResponseBody(inner_e) = e { 379 | Error::MalformedResponseBody(format!("While parsing leaf #{}: {}", i, &inner_e)) 380 | } else { 381 | e 382 | }, sth 383 | ); 384 | }, 385 | None => { 386 | return SthResult::ErrWithSth(Error::ExpectedEntry(i), sth); 387 | } 388 | } 389 | if delaycheck.elapsed() > std::time::Duration::from_secs(1) { 390 | info!("{}: Catching up: {} / {} ({}%)", self.base_url.as_str(), i, new_tree_size, ((i - i_start) * 1000 / (new_tree_size - i_start)) as f32 / 10f32); 391 | delaycheck = std::time::Instant::now(); 392 | } 393 | } 394 | assert_eq!(leaf_hashes.len(), (new_tree_size - i_start) as usize); 395 | for proof_part in consistency_proof_parts.into_iter() { 396 | assert!(proof_part.subtree.0 >= i_start); 397 | assert!(proof_part.subtree.1 <= new_tree_size); 398 | if let Err(e) = proof_part.verify(&leaf_hashes[(proof_part.subtree.0 - i_start) as usize..(proof_part.subtree.1 - i_start) as usize]) { 399 | return SthResult::ErrWithSth(Error::CannotVerifyTreeData(e), sth); 400 | } 401 | } 402 | info!("{} updated to {} {} (read {} leaves)", self.base_url.as_str(), new_tree_size, &utils::u8_to_hex(&new_tree_root), new_tree_size - i_start); 403 | } else { 404 | info!("{} light updated to {} {}", self.base_url.as_str(), new_tree_size, &utils::u8_to_hex(&new_tree_root)); 405 | } 406 | 407 | self.latest_size = new_tree_size; 408 | self.latest_tree_hash = new_tree_root; 409 | SthResult::Ok(sth) 410 | } 411 | } 412 | } 413 | 414 | /// Called by [`Self::update`](crate::CTClient::update) for each leaf received 415 | /// to check the certificates. Usually no need to call yourself. 416 | pub fn check_leaf(&self, leaf: &internal::Leaf, cert_handler: &mut Option) -> Result<(), Error> 417 | where H: FnMut(&[X509]) 418 | { 419 | let chain: Vec<_> = leaf.x509_chain.iter().map(|der| { 420 | openssl::x509::X509::from_der(&der[..]) 421 | }).collect(); 422 | for rs in chain.iter() { 423 | if let Err(e) = rs { 424 | return Err(Error::BadCertificate(format!("While decoding certificate: {}", e))); 425 | } 426 | } 427 | let chain: Vec = chain.into_iter().map(|x| x.unwrap()).collect(); 428 | if chain.len() <= 1 { 429 | return Err(Error::BadCertificate("Empty certificate chain?".to_owned())); 430 | } 431 | for part in chain.windows(2) { 432 | let ca = &part[1]; 433 | let target = &part[0]; 434 | let ca_pkey = ca.public_key().map_err(|e| Error::BadCertificate(format!("Can't get public key from ca: {}", e)))?; 435 | let verify_success = target.verify(&ca_pkey).map_err(|e| Error::Unknown(format!("{}", e)))?; 436 | if !verify_success { 437 | return Err(Error::BadCertificate("Invalid certificate chain.".to_owned())); 438 | } 439 | } 440 | if let Some(tbs) = &leaf.tbs_cert { 441 | use internal::openssl_ffi::{x509_to_tbs, x509_remove_poison}; 442 | let cert = chain[0].as_ref(); 443 | let mut cert_clone = x509_clone(&cert).map_err(|e| Error::Unknown(format!("Duplicating certificate: {}", e)))?; 444 | x509_remove_poison(&mut cert_clone).map_err(|e| Error::Unknown(format!("While removing poison: {}", e)))?; 445 | let expected_tbs = x509_to_tbs(&cert_clone) 446 | .map_err(|e| Error::Unknown(format!("x509_to_tbs errored: {}", e)))?; 447 | if tbs != &expected_tbs { 448 | // Maybe the precert is signed with an intermediate precert signing CA. The TBS will nevertheless contain the 449 | // "true" CA as the issuer name. 450 | // In that case, chain[1] is the precert signing CA, and chain[2] is the "true" signing CA. 451 | let mut tbs_correct = false; 452 | if chain.len() > 2 { 453 | x509_make_a_looks_like_issued_by_b(&mut cert_clone, &chain[2]).map_err(|e| 454 | Error::Unknown(format!("x509_make_a_looks_like_issued_by_b failed: {}", e)))?; 455 | let new_expected_tbs = x509_to_tbs(&cert_clone) 456 | .map_err(|e| Error::Unknown(format!("x509_to_tbs errored: {}", e)))?; 457 | if tbs == &new_expected_tbs { 458 | tbs_correct = true; 459 | } 460 | } 461 | if !tbs_correct { 462 | return Err(Error::BadCertificate("TBS does not match pre-cert.".to_owned())); 463 | } 464 | } 465 | } 466 | 467 | if let Some(handler) = cert_handler { 468 | handler(&chain); 469 | } 470 | Ok(()) 471 | } 472 | 473 | /// Given a [`SignedCertificateTimestamp`], check that the CT log monitored by this client can provide 474 | /// an inclusion proof that backs the sct, and return the leaf index. 475 | /// 476 | /// Does not check the signature on the sct, and also does not check that the maximum merge delay has passed. 477 | pub fn check_inclusion_proof_for_sct(&self, sct: &SignedCertificateTimestamp) -> Result { 478 | let th = self.get_checked_tree_head(); 479 | check_inclusion_proof(self.get_reqwest_client(), &self.base_url, th.0, &th.1, &sct.derive_leaf_hash()) 480 | } 481 | 482 | pub fn first_leaf_after(&self, timestamp: u64) -> Result, Error> { 483 | let mut low = 0u64; 484 | let mut high = self.latest_size; 485 | let mut last_leaf: Option<(u64, Leaf)> = None; 486 | while low < high { 487 | let mid = (low + high - 1) / 2; 488 | let mut entries_iter = internal::get_entries(&self.http_client, &self.base_url, mid..mid + 1); 489 | entries_iter.batch_size = 1; 490 | match entries_iter.next() { 491 | None => return Err(Error::ExpectedEntry(mid)), 492 | Some(Err(e)) => return Err(e), 493 | Some(Ok(got_entry)) => { 494 | let got_timestamp = got_entry.timestamp; 495 | use std::cmp::Ordering::*; 496 | match got_timestamp.cmp(×tamp) { 497 | Equal => return Ok(Some((mid, got_entry))), 498 | Less => { 499 | low = mid + 1; 500 | }, 501 | Greater => { 502 | last_leaf = Some((mid, got_entry)); 503 | high = mid; 504 | } 505 | } 506 | } 507 | } 508 | } 509 | if low > self.latest_size { 510 | Ok(None) 511 | } else { 512 | Ok(Some(last_leaf.unwrap())) 513 | } 514 | } 515 | 516 | pub fn first_tree_head_after(&self, timestamp: u64) -> Result, Error> { 517 | let fla = self.first_leaf_after(timestamp)?; 518 | if fla.is_none() { 519 | return Ok(None); 520 | } 521 | let fla = fla.unwrap(); 522 | let tsize = fla.0 + 1; 523 | let inclusion_res = fetch_inclusion_proof(&self.http_client, &self.base_url, tsize, &fla.1.hash)?; 524 | if inclusion_res.leaf_index != fla.0 { 525 | return Err(Error::Unknown("inclusion result.leaf_index != expected".to_owned())); 526 | } 527 | Ok(Some((tsize, inclusion_res.calculated_tree_hash))) 528 | } 529 | 530 | pub fn rollback_to_timestamp(&mut self, timestamp: u64) -> Result<(), Error> { 531 | let res = self.first_tree_head_after(timestamp)?; 532 | if res.is_none() { 533 | return Ok(()); 534 | } 535 | let (tsize, thash) = res.unwrap(); 536 | if tsize < self.latest_size { 537 | check_consistency_proof(&self.http_client, &self.base_url, tsize, self.latest_size, &thash, &self.latest_tree_hash)?; 538 | self.latest_size = tsize; 539 | self.latest_tree_hash = thash; 540 | info!("{}: Rolled back to {} {}", self.base_url.as_str(), tsize, utils::u8_to_hex(&thash)); 541 | } 542 | Ok(()) 543 | } 544 | 545 | /// Serialize the state of this client into bytes 546 | pub fn as_bytes(&self) -> Result, Error> { 547 | // Scheme: (All integers are in big-endian, fixed array don't specify length) 548 | // [Version: u8] [base_url in UTF-8] 0x00 [tree_size: u64] [tree_hash: [u8; 32]] [len of pub_key: u32] [pub_key: [u8]: DER public key for this log] [sha256 of everything seen before: [u8; 32]] 549 | let mut v = Vec::new(); 550 | v.push(0u8); // Version = development 551 | let url_bytes = self.base_url.as_str().as_bytes(); 552 | assert!(!url_bytes.contains(&0u8)); 553 | v.extend_from_slice(url_bytes); 554 | v.push(0u8); 555 | v.extend_from_slice(&u64::to_be_bytes(self.latest_size)); 556 | assert_eq!(self.latest_tree_hash.len(), 32); 557 | v.extend_from_slice(&self.latest_tree_hash); 558 | let pub_key = self.pub_key.public_key_to_der().map_err(|e| Error::Unknown(format!("While encoding public key: {}", &e)))?; 559 | assert!(pub_key.len() < std::u32::MAX as usize); 560 | v.extend_from_slice(&u32::to_be_bytes(pub_key.len() as u32)); 561 | v.extend_from_slice(&pub_key); 562 | v.extend_from_slice(&utils::sha256(&v)); 563 | Ok(v) 564 | } 565 | 566 | /// Parse a byte string returned by [`Self::as_bytes`](CTClient::as_bytes). 567 | pub fn from_bytes(bytes: &[u8]) -> Result { 568 | use std::convert::TryInto; 569 | fn e_inval() -> Result { 570 | Err(Error::InvalidArgument("The bytes are invalid.".to_owned())) 571 | } 572 | let mut input = bytes; 573 | if input.is_empty() { 574 | return e_inval(); 575 | } 576 | let version = input[0]; 577 | input = &input[1..]; 578 | if version != 0 { 579 | return Err(Error::InvalidArgument("The bytes are encoded by a ctclient of higher version.".to_owned())); 580 | } 581 | let base_url_len = match input.iter().position(|x| *x == 0) { 582 | Some(k) => k, 583 | None => return e_inval() 584 | }; 585 | let base_url = std::str::from_utf8(&input[..base_url_len]).map_err(|e| Error::InvalidArgument(format!("Invalid UTF-8 in base_url: {}", &e)))?; 586 | input = &input[base_url_len + 1..]; 587 | if input.len() < 8 { 588 | return e_inval(); 589 | } 590 | let tree_size = u64::from_be_bytes(input[..8].try_into().unwrap()); 591 | input = &input[8..]; 592 | if input.len() < 32 { 593 | return e_inval(); 594 | } 595 | let tree_hash: [u8; 32] = input[..32].try_into().unwrap(); 596 | input = &input[32..]; 597 | if input.len() < 4 { 598 | return e_inval(); 599 | } 600 | let len_pub_key = u32::from_be_bytes(input[..4].try_into().unwrap()); 601 | input = &input[4..]; 602 | if input.len() < len_pub_key as usize { 603 | return e_inval(); 604 | } 605 | let pub_key = &input[..len_pub_key as usize]; 606 | input = &input[len_pub_key as usize..]; 607 | if input.len() < 32 { 608 | return e_inval(); 609 | } 610 | let checksum: [u8; 32] = input[..32].try_into().unwrap(); 611 | input = &input[32..]; 612 | if !input.is_empty() { 613 | return e_inval(); 614 | } 615 | let expect_checksum = utils::sha256(&bytes[..bytes.len() - 32]); 616 | #[cfg(not(fuzzing))] { 617 | if checksum != expect_checksum { 618 | return e_inval(); 619 | } 620 | } 621 | let pub_key = openssl::pkey::PKey::::public_key_from_der(pub_key).map_err(|e| Error::InvalidArgument(format!("Can't parse public key: {}", &e)))?; 622 | Ok(CTClient{ 623 | base_url: reqwest::Url::parse(base_url).map_err(|e| Error::InvalidArgument(format!("Unable to parse base_url: {}", &e)))?, 624 | pub_key, 625 | http_client: new_http_client()?, 626 | latest_size: tree_size, 627 | latest_tree_hash: tree_hash 628 | }) 629 | } 630 | } 631 | 632 | #[test] 633 | fn as_bytes_test() { 634 | let c = CTClient::new_from_latest_th("https://ct.googleapis.com/logs/argon2019/", &utils::hex_to_u8("3059301306072a8648ce3d020106082a8648ce3d030107034200042373109be1f35ef6986b6995961078ce49dbb404fc712c5a92606825c04a1aa1b0612d1b8714a9baf00133591d0530e94215e755d72af8b4a2ba45c946918756")).unwrap(); 635 | let mut bytes = c.as_bytes().unwrap(); 636 | println!("bytes: {}", &base64::encode(&bytes)); 637 | let mut c_clone = CTClient::from_bytes(&bytes).unwrap(); 638 | assert_eq!(c.latest_size, c_clone.latest_size); 639 | assert_eq!(c.latest_tree_hash, c_clone.latest_tree_hash); 640 | assert_eq!(c.base_url, c_clone.base_url); 641 | c_clone.light_update().unwrap(); // test public key 642 | let len = bytes.len(); 643 | bytes[len - 1] ^= 1; 644 | CTClient::from_bytes(&bytes).expect_err(""); 645 | } 646 | 647 | #[cfg(test)] 648 | mod long_tests; 649 | -------------------------------------------------------------------------------- /src/long_tests.rs: -------------------------------------------------------------------------------- 1 | use super::{CTClient, internal, utils}; 2 | 3 | #[test] 4 | fn check_leaf_test() { 5 | let c = CTClient::new_from_latest_th("https://ct.googleapis.com/logs/argon2019/", &utils::hex_to_u8("3059301306072a8648ce3d020106082a8648ce3d030107034200042373109be1f35ef6986b6995961078ce49dbb404fc712c5a92606825c04a1aa1b0612d1b8714a9baf00133591d0530e94215e755d72af8b4a2ba45c946918756")[..]).unwrap(); 6 | let none_ch = &mut None::; 7 | c.check_leaf(&internal::Leaf{ 8 | hash: [0u8; 32], 9 | is_pre_cert: false, 10 | x509_chain: vec![utils::hex_to_u8("308207333082061ba003020102021204209a743113b4ad1f04972548d2f954cff8300d06092a864886f70d01010b0500304a310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074312330210603550403131a4c6574277320456e637279707420417574686f72697479205833301e170d3139303731353232303633385a170d3139313031333232303633385a301e311c301a060355040313137777772e6c657473656e63727970742e6f726730820122300d06092a864886f70d01010105000382010f003082010a0282010100befeaade7a680c4f9e97a3368fa44dc68d223a12533fea943e09cc970f49858f32f2bae0d9ac3c4f327b4e7eb9796d80ff1c3da7017d32ab57bf302e4c1ed1b920af88d9dbdb2aae7070af3b4f3354fa311338639fe1c2333cf0fffa1657cb477dc1fd27013abfec4ce6d8c55d86c182ae35bbe3a5a21ab87360f55005ace74ddb17a67f67a668aa529a030bf41c78a83964cb258031d35692bf1e01ec34d0cd937e59286a9bba946d4a6708662233cf8d6969b772895ff3d25e023100006b1cbd488d9e5b25b9303a7420787baa98fbb747231bc4e7a7b4d80a912de4d2b0c046d5ca0d31cba5f91338fad2c9deaab9263479d095a16983e90482c02b7e98f50203010001a382043d30820439300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff04023000301d0603551d0e0416041474ad581f3fa559294a1625429c3801c8375d6807301f0603551d23041830168014a84a6a63047dddbae6d139b7a64565eff3a8eca1306f06082b0601050507010104633061302e06082b060105050730018622687474703a2f2f6f6373702e696e742d78332e6c657473656e63727970742e6f7267302f06082b060105050730028623687474703a2f2f636572742e696e742d78332e6c657473656e63727970742e6f72672f308201f10603551d11048201e8308201e4821b636572742e696e742d78312e6c657473656e63727970742e6f7267821b636572742e696e742d78322e6c657473656e63727970742e6f7267821b636572742e696e742d78332e6c657473656e63727970742e6f7267821b636572742e696e742d78342e6c657473656e63727970742e6f7267821c636572742e726f6f742d78312e6c657473656e63727970742e6f7267821f636572742e73746167696e672d78312e6c657473656e63727970742e6f7267821f636572742e7374672d696e742d78312e6c657473656e63727970742e6f72678220636572742e7374672d726f6f742d78312e6c657473656e63727970742e6f7267821263702e6c657473656e63727970742e6f7267821a63702e726f6f742d78312e6c657473656e63727970742e6f726782136370732e6c657473656e63727970742e6f7267821b6370732e726f6f742d78312e6c657473656e63727970742e6f7267821b63726c2e726f6f742d78312e6c657473656e63727970742e6f7267820f6c657473656e63727970742e6f726782166f726967696e2e6c657473656e63727970742e6f726782176f726967696e322e6c657473656e63727970742e6f726782167374617475732e6c657473656e63727970742e6f726782137777772e6c657473656e63727970742e6f7267304c0603551d20044530433008060667810c0102013037060b2b0601040182df130101013028302606082b06010505070201161a687474703a2f2f6370732e6c657473656e63727970742e6f726730820104060a2b06010401d6790204020481f50481f200f0007700747eda8331ad331091219cce254f4270c2bffd5e422008c6373579e6107bcc560000016bf7e353a10000040300483046022100fe6234e90cf63f5c4e7cde19c9fcfee7198789c1a8973da05737c402ebfb2421022100fde2209a71952b8b3363b8cfc0d5dccfc331e1a484dec6d3473ee30b18569d81007500293c519654c83965baaa50fc5807d4b76fbf587a2972dca4c30cf4e54547f4780000016bf7e353830000040300463044022018dd256746b16aa6c70e437be686bf7c1b92437313a3e12bd4d9bf0fb8531a0a02203bb90d1bb390e215c3a1a699ad208f8deffc233880c982cda8e079b0da02d60d300d06092a864886f70d01010b050003820101000c80becf81322cffa861e7dac7a5457752be56bb5fe71ab4bdd24031f9c61392bce6b8ea75fc59b2b17369d3622425979e2c5ada5be583462e1c1e61d3660a5fbba1a7ee7e5b0f9a2fb203865e6f8f2988b23d7ebda42b3e9608a271e78ce7bf0458411ea2da9cfdd1858994ba607b947346a8c8b182ae8ed2ec56b47b57cf895cc95d71491ae65c24e7cc27b9c0499cf4996bdff0c9a10a85c8cc38029fd446d30ab317a7e5e5f5e421cd791ba9baf5187e5f4985b00a0a1a219a22cc3112b3f9574806afea8acead501a41de954a46b91ef9bcd3c2c8de9a1caac366172340c61e48e8573be213e5f8917c66bcd74a11c9c8ca26ae82dc5e8d64299c410469"), utils::hex_to_u8("3082058d30820375a003020102021100d3b17226342332dcf40528512aec9c6a300d06092a864886f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f74205831301e170d3136313030363135343335355a170d3231313030363135343335355a304a310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074312330210603550403131a4c6574277320456e637279707420417574686f7269747920583330820122300d06092a864886f70d01010105000382010f003082010a02820101009cd30cf05ae52e47b7725d3783b3686330ead735261925e1bdbe35f170922fb7b84b4105aba99e350858ecb12ac468870ba3e375e4e6f3a76271ba7981601fd7919a9ff3d0786771c8690e9591cffee699e9603c48cc7eca4d7712249d471b5aebb9ec1e37001c9cac7ba705eace4aebbd41e53698b9cbfd6d3c9668df232a42900c867467c87fa59ab8526114133f65e98287cbdbfa0e56f68689f3853f9786afb0dc1aef6b0d95167dc42ba065b299043675806bac4af31b9049782fa2964f2a20252904c674c0d031cd8f31389516baa833b843f1b11fc3307fa27931133d2d36f8e3fcf2336ab93931c5afc48d0d1d641633aafa8429b6d40bc0d87dc3930203010001a382016730820163300e0603551d0f0101ff04040302018630120603551d130101ff040830060101ff02010030540603551d20044d304b3008060667810c010201303f060b2b0601040182df130101013030302e06082b060105050702011622687474703a2f2f6370732e726f6f742d78312e6c657473656e63727970742e6f7267301d0603551d0e04160414a84a6a63047dddbae6d139b7a64565eff3a8eca130330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e726f6f742d78312e6c657473656e63727970742e6f7267307206082b0601050507010104663064303006082b060105050730018624687474703a2f2f6f6373702e726f6f742d78312e6c657473656e63727970742e6f72672f303006082b060105050730028624687474703a2f2f636572742e726f6f742d78312e6c657473656e63727970742e6f72672f301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58f6e99b6e300d06092a864886f70d01010b0500038202010019cf7520342d3aa645ffd0d5e68cda32e89c6e1b41d127a8e250f270aac4e79346b4e810ab704fefb7ea04d29411b103fe5dbadf368c94368f137c448f0bf50157ad68b8c579c0d84a80d74ca31e247a1fd723e8c1623a76f9227d5e5ac44c50cdafddef6d36c080801ba43c7020d65421d3baef14a9bf073f410a36b1a2b00b20d51f67d0c3eb88f68a02c8c657b60cfc56f1d23f1769681cc8d7663a86f1192a654768c6d203e7ef74160b0621f90ca6a8114b4e5fe333db0841ea09797578ee47c842d381c5652d75d00e00169d1ceeb7584525e733635b634109e8e9feacfa733274b376e96b94e2cdd462f3ae3ac53146526eed34911ea0c2de5484e57820564cdd68f92e28641b1a99f2fb4d7fe3b85f5d7341ec79ed58d67a376570a7b1ba39f63e610ad9c086909a1ac8a8966e8a0b2b6dedd6fa0767e72904f7e2b2d1581552c7f1a39da6c0562cd49298d8f183b96c7c33a0e54baa9092f1da454a3414c77c4ec4a56c5d3fbfdeb9a8614a8520de428329627c1c9908a5461ff46b22d38651cb37cd604a426356b3c8d18f310953c1e2dc1bd4f1547767cf337b00d6d27cdec679bfcbe016fdb2a1f2913c1d2de89cd403cd664aa3379319797be219c21600c8ed0e4e0dff7ecf07a864cd29df41aa8530491073a74e89320e5bad4086c1b0940c8d26c5a749dc1cf85b147a7f236904adb20229d612c8a4c6a12d"), utils::hex_to_u8("3082056b30820353a0030201020211008210cfb0d240e3594463e0bb63828b00300d06092a864886f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f74205831301e170d3135303630343131303433385a170d3335303630343131303433385a304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f7420583130820222300d06092a864886f70d01010105000382020f003082020a0282020100ade82473f41437f39b9e2b57281c87bedcb7df38908c6e3ce657a078f775c2a2fef56a6ef6004f28dbde68866c4493b6b163fd14126bbf1fd2ea319b217ed1333cba48f5dd79dfb3b8ff12f1219a4bc18a8671694a66666c8f7e3c70bfad292206f3e4c0e680aee24b8fb7997e94039fd347977c99482353e838ae4f0a6f832ed149578c8074b6da2fd0388d7b0370211b75f2303cfa8faeddda63abeb164fc28e114b7ecf0be8ffb5772ef4b27b4ae04c12250c708d0329a0e15324ec13d9ee19bf10b34a8c3f89a36151deac870794f46371ec2ee26f5b9881e1895c34796c76ef3b906279e6dba49a2f26c5d010e10eded9108e16fbb7f7a8f7c7e50207988f360895e7e237960d36759efb0e72b11d9bbc03f94905d881dd05b42ad641e9ac0176950a0fd8dfd5bd121f352f28176cd298c1a80964776e4737baceac595e689d7f72d689c50641293e593edd26f524c911a75aa34c401f46a199b5a73a516e863b9e7d72a712057859ed3e5178150b038f8dd02f05b23e7b4a1c4b730512fcc6eae050137c439374b3ca74e78e1f0108d030d45b7136b407bac130305c48b7823b98a67d608aa2a32982ccbabd83041ba2830341a1d605f11bc2b6f0a87c863b46a8482a88dc769a76bf1f6aa53d198feb38f364dec82b0d0a28fff7dbe21542d422d0275de179fe18e77088ad4ee6d98b3ac6dd27516effbc64f533434f0203010001a3423040300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e0416041479b459e67bb6e5e40173800888c81a58f6e99b6e300d06092a864886f70d01010b05000382020100551f58a9bcb2a850d00cb1d81a6920272908ac61755c8a6ef882e5692fd5f6564bb9b8731059d321977ee74c71fbb2d260ad39a80bea17215685f1500e59ebcee059e9bac915ef869d8f8480f6e4e99190dc179b621b45f06695d27c6fc2ea3bef1fcfcbd6ae27f1a9b0c8aefd7d7e9afa2204ebffd97fea912b22b1170e8ff28a345b58d8fc01c954b9b826cc8a8833894c2d843c82dfee965705ba2cbbf7c4b7c74e3b82be31c822737392d1c280a43939103323824c3c9f86b255981dbe29868c229b9ee26b3b573a82704ddc09c789cb0a074d6ce85d8ec9efceabc7bbb52b4e45d64ad026cce572ca086aa595e315a1f7a4edc92c5fa5fbffac28022ebed77bbbe3717b9016d3075e46537c3707428cd3c4969cd599b52ae0951a8048ae4c3907cecc47a452952bbab8fbadd233537de51d4d6dd5a1b1c7426fe64027355ca328b7078de78d3390e7239ffb509c796c46d5b415b3966e7e9b0c963ab8522d3fd65be1fb08c284fe24a8a389daac6ae1182ab1a843615bd31fdc3b8d76f22de88d75df17336c3d53fb7bcb415fffdca2d06138e196b8ac5d8b37d775d533c09911ae9d41c1727584be0241425f67244894d19b27be073fb9b84f817451e17ab7ed9d23e2bee0d52804133c31039edd7a6c8fc60718c67fde478e3f289e0406cfa5543477bdec899be91743df5bdb5ffe8e1e57a2cd409d7e6222dade1827")], 11 | tbs_cert: None, 12 | issuer_key_hash: None, 13 | timestamp: 0, 14 | extensions: Vec::new() 15 | }, none_ch).unwrap(); 16 | c.check_leaf(&internal::Leaf{ 17 | hash: [0u8; 32], 18 | is_pre_cert: false, 19 | x509_chain: vec![utils::hex_to_u8("308207333082061ba003020102021204209a743113b4ad1f04972548d2f954cff8300d06092a864886f70d01010b0500304a310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074312330210603550403131a4c6574277320456e637279707420417574686f72697479205833301e170d3139303731353232303633385a170d3139313031333232303633385a301e311c301a060355040313137777772e6c657473656e63727970742e6f726730820122300d06092a864886f70d01010105000382010f003082010a0282010100befeaade7a680c4f9e97a3368fa44dc68d223a12533fea943e09cc970f49858f32f2bae0d9ac3c4f327b4e7eb9796d80ff1c3da7017d32ab57bf302e4c1ed1b920af88d9dbdb2aae7070af3b4f3354fa311338639fe1c2333cf0fffa1657cb477dc1fd27013abfec4ce6d8c55d86c182ae35bbe3a5a21ab87360f55005ace74ddb17a67f67a668aa529a030bf41c78a83964cb258031d35692bf1e01ec34d0cd937e59286a9bba946d4a6708662233cf8d6969b772895ff3d25e023100006b1cbd488d9e5b25b9303a7420787baa98fbb747231bc4e7a7b4d80a912de4d2b0c046d5ca0d31cba5f91338fad2c9deaab9263479d095a16983e90482c02b7e98f50203010001a382043d30820439300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff04023000301d0603551d0e0416041474ad581f3fa559294a1625429c3801c8375d6807301f0603551d23041830168014a84a6a63047dddbae6d139b7a64565eff3a8eca1306f06082b0601050507010104633061302e06082b060105050730018622687474703a2f2f6f6373702e696e742d78332e6c657473656e63727970742e6f7267302f06082b060105050730028623687474703a2f2f636572742e696e742d78332e6c657473656e63727970742e6f72672f308201f10603551d11048201e8308201e4821b636572742e696e742d78312e6c657473656e63727970742e6f7267821b636572742e696e742d78322e6c657473656e63727970742e6f7267821b636572742e696e742d78332e6c657473656e63727970742e6f7267821b636572742e696e742d78342e6c657473656e63727970742e6f7267821c636572742e726f6f742d78312e6c657473656e63727970742e6f7267821f636572742e73746167696e672d78312e6c657473656e63727970742e6f7267821f636572742e7374672d696e742d78312e6c657473656e63727970742e6f72678220636572742e7374672d726f6f742d78312e6c657473656e63727970742e6f7267821263702e6c657473656e63727970742e6f7267821a63702e726f6f742d78312e6c657473656e63727970742e6f726782136370732e6c657473656e63727970742e6f7267821b6370732e726f6f742d78312e6c657473656e63727970742e6f7267821b63726c2e726f6f742d78312e6c657473656e63727970742e6f7267820f6c657473656e63727970742e6f726782166f726967696e2e6c657473656e63727970742e6f726782176f726967696e322e6c657473656e63727970742e6f726782167374617475732e6c657473656e63727970742e6f726782137777772e6c657473656e63727970742e6f7267304c0603551d20044530433008060667810c0102013037060b2b0601040182df130101013028302606082b06010505070201161a687474703a2f2f6370732e6c657473656e63727970742e6f726730820104060a2b06010401d6790204020481f50481f200f0007700747eda8331ad331091219cce254f4270c2bffd5e422008c6373579e6107bcc560000016bf7e353a10000040300483046022100fe6234e90cf63f5c4e7cde19c9fcfee7198789c1a8973da05737c402ebfb2421022100fde2209a71952b8b3363b8cfc0d5dccfc331e1a484dec6d3473ee30b18569d81007500293c519654c83965baaa50fc5807d4b76fbf587a2972dca4c30cf4e54547f4780000016bf7e353830000040300463044022018dd256746b16aa6c70e437be686bf7c1b92437313a3e12bd4d9bf0fb8531a0a02203bb90d1bb390e215c3a1a699ad208f8deffc233880c982cda8e079b0da02d60d300d06092a864886f70d01010b050003820101000c80becf81322cffa861e7dac7a5457752be56bb5fe71ab4bdd24031f9c61392bce6b8ea75fc59b2b17369d3622425979e2c5ada5be583462e1c1e61d3660a5fbba1a7ee7e5b0f9a2fb203865e6f8f2988b23d7ebda42b3e9608a271e78ce7bf0458411ea2da9cfdd1858994ba607b947346a8c8b182ae8ed2ec56b47b57cf895cc95d71491ae65c24e7cc27b9c0499cf4996bdff0c9a10a85c8cc38029fd446d30ab317a7e5e5f5e421cd791ba9baf5187e5f4985b00a0a1a219a22cc3112b3f9574806afea8acead501a41de954a46b91ef9bcd3c2c8de9a1caac366172340c61e48e8573be213e5f8917c66bcd74a11c9c8ca26ae82dc5e8d64299c410469"), utils::hex_to_u8("3082056b30820353a0030201020211008210cfb0d240e3594463e0bb63828b00300d06092a864886f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f74205831301e170d3135303630343131303433385a170d3335303630343131303433385a304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f7420583130820222300d06092a864886f70d01010105000382020f003082020a0282020100ade82473f41437f39b9e2b57281c87bedcb7df38908c6e3ce657a078f775c2a2fef56a6ef6004f28dbde68866c4493b6b163fd14126bbf1fd2ea319b217ed1333cba48f5dd79dfb3b8ff12f1219a4bc18a8671694a66666c8f7e3c70bfad292206f3e4c0e680aee24b8fb7997e94039fd347977c99482353e838ae4f0a6f832ed149578c8074b6da2fd0388d7b0370211b75f2303cfa8faeddda63abeb164fc28e114b7ecf0be8ffb5772ef4b27b4ae04c12250c708d0329a0e15324ec13d9ee19bf10b34a8c3f89a36151deac870794f46371ec2ee26f5b9881e1895c34796c76ef3b906279e6dba49a2f26c5d010e10eded9108e16fbb7f7a8f7c7e50207988f360895e7e237960d36759efb0e72b11d9bbc03f94905d881dd05b42ad641e9ac0176950a0fd8dfd5bd121f352f28176cd298c1a80964776e4737baceac595e689d7f72d689c50641293e593edd26f524c911a75aa34c401f46a199b5a73a516e863b9e7d72a712057859ed3e5178150b038f8dd02f05b23e7b4a1c4b730512fcc6eae050137c439374b3ca74e78e1f0108d030d45b7136b407bac130305c48b7823b98a67d608aa2a32982ccbabd83041ba2830341a1d605f11bc2b6f0a87c863b46a8482a88dc769a76bf1f6aa53d198feb38f364dec82b0d0a28fff7dbe21542d422d0275de179fe18e77088ad4ee6d98b3ac6dd27516effbc64f533434f0203010001a3423040300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e0416041479b459e67bb6e5e40173800888c81a58f6e99b6e300d06092a864886f70d01010b05000382020100551f58a9bcb2a850d00cb1d81a6920272908ac61755c8a6ef882e5692fd5f6564bb9b8731059d321977ee74c71fbb2d260ad39a80bea17215685f1500e59ebcee059e9bac915ef869d8f8480f6e4e99190dc179b621b45f06695d27c6fc2ea3bef1fcfcbd6ae27f1a9b0c8aefd7d7e9afa2204ebffd97fea912b22b1170e8ff28a345b58d8fc01c954b9b826cc8a8833894c2d843c82dfee965705ba2cbbf7c4b7c74e3b82be31c822737392d1c280a43939103323824c3c9f86b255981dbe29868c229b9ee26b3b573a82704ddc09c789cb0a074d6ce85d8ec9efceabc7bbb52b4e45d64ad026cce572ca086aa595e315a1f7a4edc92c5fa5fbffac28022ebed77bbbe3717b9016d3075e46537c3707428cd3c4969cd599b52ae0951a8048ae4c3907cecc47a452952bbab8fbadd233537de51d4d6dd5a1b1c7426fe64027355ca328b7078de78d3390e7239ffb509c796c46d5b415b3966e7e9b0c963ab8522d3fd65be1fb08c284fe24a8a389daac6ae1182ab1a843615bd31fdc3b8d76f22de88d75df17336c3d53fb7bcb415fffdca2d06138e196b8ac5d8b37d775d533c09911ae9d41c1727584be0241425f67244894d19b27be073fb9b84f817451e17ab7ed9d23e2bee0d52804133c31039edd7a6c8fc60718c67fde478e3f289e0406cfa5543477bdec899be91743df5bdb5ffe8e1e57a2cd409d7e6222dade1827")], 20 | tbs_cert: None, 21 | issuer_key_hash: None, 22 | timestamp: 0, 23 | extensions: Vec::new() 24 | }, none_ch).unwrap_err(); 25 | c.check_leaf(&internal::Leaf{ 26 | hash: [0u8; 32], 27 | is_pre_cert: false, 28 | x509_chain: vec![utils::hex_to_u8("308206633082044ba003020102021409886ed27ab3eabca2c5626ff658115c05d70b68300d06092a864886f70d01010b0500304f311b301906092a864886f70d010901160c6d406d616f77746d2e6f72673130302e06035504030c274d616f77746d20636572746966696361746520666f72206c6f63616c206465762075736167652e3020170d3138313232323037353332315a180f32313138313132383037353332315a30553136303406035504030c2d6d616f77746d2e6f7267202d20485454505320636572746966696361746520666f72206465762075736167652e311b301906092a864886f70d010901160c6d406d616f77746d2e6f726730820222300d06092a864886f70d01010105000382020f003082020a0282020100c12b2d7f41bff7912e6daf4401549a084dbf4a3084200c824e6e1cb25bb954a1a9e534be644307fa5d9ccd76f8abb673df694ca65f993f8c9fba6b7ff87491bf4e5a42f80d55c2331a291f5cff0b6273b569b94ad181a1d4027b01f47ad8b237b0aac3031ae3a4d0ef3d7a462a9b70926292542f2950a2d94868edbdad338376cc26a2542782cd0db10de0fe92913c18d4c23ef8b3a6310e44ed68b76d1a4f3576cbf22ff5ae3a146dd853856114fda9db3bda502dac71a684baca6770bca8d6936bc18ce19368736ee4f87bc0f7dbd3b323549780df21a0adb825df1428ef833a43a90849a22ee320289d4ff8657145321f92305fda58752cf20f6f7310839d4b6842f2162c395b6cd2b05cc2133787334da58395390df2c5f55d6c3b9f9f733bbdb5ab2f6f635c8673cc8067e678e7a3c5275b97e6abe3dacecb673c96f2622d5d38c7e264ee5ca8f2a96c0001aa338cb41c992170f6de93512e91034394b5e15a96c77fb5bde21bc67166285f1b85f255723e3ec96cd5dce3bde358641d4463b1c9175134c4ecdf6dc072a403e0f5e2dd42f92f19fe041773e8d30c39ab4087f827e406976972e044098da8084a7ab3b0a2ef62e6ca1ede30c08829996b03e4d88f6e818c7dc0e548752af9ba90be888bf091ce97aa2e7429f9f76de09273e5f9af78922532d514b71edb289b87881a414b9755a3d68a8739b079cc3410070203010001a382012d30820129300c0603551d130101ff04023000300e0603551d0f0101ff0404030205a030200603551d250101ff0416301406082b0601050507030106082b060105050703023081e60603551d110101ff0481db3081d8820c2a2e6d616f77746d2e6f7267820d2a2e736368737263682e78797a820d2a2e736368737263682e6f7267820a6d616f77746d2e6f7267820b736368737263682e78797a820b736368737263682e6f726782076d77772e6d6f6582092a2e6d77772e6d6f65820b6675636b6369652e636f6d820d2a2e6675636b6369652e636f6d82076e6369632e676782092a2e6e6369632e6767820870617065722e7363820a2a2e70617065722e7363821064696469676f746f666665722e636f6d82122a2e64696469676f746f666665722e636f6d8704c0a83801300d06092a864886f70d01010b050003820201006201d5231de9440cbab1d25a79547adb9af03054c3dcead9c53fbf5e82fc26b3775e879a5a050e18d15883d2524eef30297c3da3dfc80e47d90fa45249552857369c1eb5667176406df890182dc8cf35076cf67e6e425fe1df084c04e2c6f0fe9e65cb4ad6f3b9544303ec582a7a9ebb7d6d35f3b7c2a83fe750ac1a42b030e457ab739e5883534a6f63aaf1dd73d9cae74ea1c9309c31e908af9b987937307cf6e9b817c8e81cf139cb20ec6fded9de64220f017754e6eeb170305546aeca4be0f7418aad7b7593b8076cc3335f4c914382cb2d89d7e5d0715a32ff57b759a8813caaf78b94ec2cd89e1570bec191b9f14fe54c14b0d7214de7b6af6d970720b02ccefbc7870968cc3f8f2da4593b78ebdee37c83a1bc65418a9c85c732bfa4b8f4d0abab6530a40a2d68ba959b5319571323b9af684757148a522758d06d44074abd6cc27277a65454d4748f13087676142391e11ea59c9839a6baa7003d990f98d69bea7bd58816986ddb4be215f4c86429bab91dbda772435547ba215fb974a99337e31b51132ed2e79d643fe4b434ed84d7be07ba153ce54ca50bd6082fa6104923d601b0cdbdc91274374d6d42451f3172789d316d9bc9be1a9f1cf37a5ba8002a7716399b4516b672c1a420cf0bc881af5e219ce5e7c8a779ade41716607e3c6b3e334978cf4ad82ce1470d58d90c87cf967bdd2d67740c6f3435525d"), utils::hex_to_u8("3082058d30820375a003020102021100d3b17226342332dcf40528512aec9c6a300d06092a864886f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f74205831301e170d3136313030363135343335355a170d3231313030363135343335355a304a310b300906035504061302555331163014060355040a130d4c6574277320456e6372797074312330210603550403131a4c6574277320456e637279707420417574686f7269747920583330820122300d06092a864886f70d01010105000382010f003082010a02820101009cd30cf05ae52e47b7725d3783b3686330ead735261925e1bdbe35f170922fb7b84b4105aba99e350858ecb12ac468870ba3e375e4e6f3a76271ba7981601fd7919a9ff3d0786771c8690e9591cffee699e9603c48cc7eca4d7712249d471b5aebb9ec1e37001c9cac7ba705eace4aebbd41e53698b9cbfd6d3c9668df232a42900c867467c87fa59ab8526114133f65e98287cbdbfa0e56f68689f3853f9786afb0dc1aef6b0d95167dc42ba065b299043675806bac4af31b9049782fa2964f2a20252904c674c0d031cd8f31389516baa833b843f1b11fc3307fa27931133d2d36f8e3fcf2336ab93931c5afc48d0d1d641633aafa8429b6d40bc0d87dc3930203010001a382016730820163300e0603551d0f0101ff04040302018630120603551d130101ff040830060101ff02010030540603551d20044d304b3008060667810c010201303f060b2b0601040182df130101013030302e06082b060105050702011622687474703a2f2f6370732e726f6f742d78312e6c657473656e63727970742e6f7267301d0603551d0e04160414a84a6a63047dddbae6d139b7a64565eff3a8eca130330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e726f6f742d78312e6c657473656e63727970742e6f7267307206082b0601050507010104663064303006082b060105050730018624687474703a2f2f6f6373702e726f6f742d78312e6c657473656e63727970742e6f72672f303006082b060105050730028624687474703a2f2f636572742e726f6f742d78312e6c657473656e63727970742e6f72672f301f0603551d2304183016801479b459e67bb6e5e40173800888c81a58f6e99b6e300d06092a864886f70d01010b0500038202010019cf7520342d3aa645ffd0d5e68cda32e89c6e1b41d127a8e250f270aac4e79346b4e810ab704fefb7ea04d29411b103fe5dbadf368c94368f137c448f0bf50157ad68b8c579c0d84a80d74ca31e247a1fd723e8c1623a76f9227d5e5ac44c50cdafddef6d36c080801ba43c7020d65421d3baef14a9bf073f410a36b1a2b00b20d51f67d0c3eb88f68a02c8c657b60cfc56f1d23f1769681cc8d7663a86f1192a654768c6d203e7ef74160b0621f90ca6a8114b4e5fe333db0841ea09797578ee47c842d381c5652d75d00e00169d1ceeb7584525e733635b634109e8e9feacfa733274b376e96b94e2cdd462f3ae3ac53146526eed34911ea0c2de5484e57820564cdd68f92e28641b1a99f2fb4d7fe3b85f5d7341ec79ed58d67a376570a7b1ba39f63e610ad9c086909a1ac8a8966e8a0b2b6dedd6fa0767e72904f7e2b2d1581552c7f1a39da6c0562cd49298d8f183b96c7c33a0e54baa9092f1da454a3414c77c4ec4a56c5d3fbfdeb9a8614a8520de428329627c1c9908a5461ff46b22d38651cb37cd604a426356b3c8d18f310953c1e2dc1bd4f1547767cf337b00d6d27cdec679bfcbe016fdb2a1f2913c1d2de89cd403cd664aa3379319797be219c21600c8ed0e4e0dff7ecf07a864cd29df41aa8530491073a74e89320e5bad4086c1b0940c8d26c5a749dc1cf85b147a7f236904adb20229d612c8a4c6a12d"), utils::hex_to_u8("3082056b30820353a0030201020211008210cfb0d240e3594463e0bb63828b00300d06092a864886f70d01010b0500304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f74205831301e170d3135303630343131303433385a170d3335303630343131303433385a304f310b300906035504061302555331293027060355040a1320496e7465726e65742053656375726974792052657365617263682047726f7570311530130603550403130c4953524720526f6f7420583130820222300d06092a864886f70d01010105000382020f003082020a0282020100ade82473f41437f39b9e2b57281c87bedcb7df38908c6e3ce657a078f775c2a2fef56a6ef6004f28dbde68866c4493b6b163fd14126bbf1fd2ea319b217ed1333cba48f5dd79dfb3b8ff12f1219a4bc18a8671694a66666c8f7e3c70bfad292206f3e4c0e680aee24b8fb7997e94039fd347977c99482353e838ae4f0a6f832ed149578c8074b6da2fd0388d7b0370211b75f2303cfa8faeddda63abeb164fc28e114b7ecf0be8ffb5772ef4b27b4ae04c12250c708d0329a0e15324ec13d9ee19bf10b34a8c3f89a36151deac870794f46371ec2ee26f5b9881e1895c34796c76ef3b906279e6dba49a2f26c5d010e10eded9108e16fbb7f7a8f7c7e50207988f360895e7e237960d36759efb0e72b11d9bbc03f94905d881dd05b42ad641e9ac0176950a0fd8dfd5bd121f352f28176cd298c1a80964776e4737baceac595e689d7f72d689c50641293e593edd26f524c911a75aa34c401f46a199b5a73a516e863b9e7d72a712057859ed3e5178150b038f8dd02f05b23e7b4a1c4b730512fcc6eae050137c439374b3ca74e78e1f0108d030d45b7136b407bac130305c48b7823b98a67d608aa2a32982ccbabd83041ba2830341a1d605f11bc2b6f0a87c863b46a8482a88dc769a76bf1f6aa53d198feb38f364dec82b0d0a28fff7dbe21542d422d0275de179fe18e77088ad4ee6d98b3ac6dd27516effbc64f533434f0203010001a3423040300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e0416041479b459e67bb6e5e40173800888c81a58f6e99b6e300d06092a864886f70d01010b05000382020100551f58a9bcb2a850d00cb1d81a6920272908ac61755c8a6ef882e5692fd5f6564bb9b8731059d321977ee74c71fbb2d260ad39a80bea17215685f1500e59ebcee059e9bac915ef869d8f8480f6e4e99190dc179b621b45f06695d27c6fc2ea3bef1fcfcbd6ae27f1a9b0c8aefd7d7e9afa2204ebffd97fea912b22b1170e8ff28a345b58d8fc01c954b9b826cc8a8833894c2d843c82dfee965705ba2cbbf7c4b7c74e3b82be31c822737392d1c280a43939103323824c3c9f86b255981dbe29868c229b9ee26b3b573a82704ddc09c789cb0a074d6ce85d8ec9efceabc7bbb52b4e45d64ad026cce572ca086aa595e315a1f7a4edc92c5fa5fbffac28022ebed77bbbe3717b9016d3075e46537c3707428cd3c4969cd599b52ae0951a8048ae4c3907cecc47a452952bbab8fbadd233537de51d4d6dd5a1b1c7426fe64027355ca328b7078de78d3390e7239ffb509c796c46d5b415b3966e7e9b0c963ab8522d3fd65be1fb08c284fe24a8a389daac6ae1182ab1a843615bd31fdc3b8d76f22de88d75df17336c3d53fb7bcb415fffdca2d06138e196b8ac5d8b37d775d533c09911ae9d41c1727584be0241425f67244894d19b27be073fb9b84f817451e17ab7ed9d23e2bee0d52804133c31039edd7a6c8fc60718c67fde478e3f289e0406cfa5543477bdec899be91743df5bdb5ffe8e1e57a2cd409d7e6222dade1827")], 29 | tbs_cert: None, 30 | issuer_key_hash: None, 31 | timestamp: 0, 32 | extensions: Vec::new() 33 | }, none_ch).unwrap_err(); 34 | } 35 | -------------------------------------------------------------------------------- /src/sct.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use openssl::pkey::PKey; 4 | use openssl::sha::sha256; 5 | use openssl::x509::X509Ref; 6 | 7 | use crate::Error; 8 | use crate::internal::leaf_hash_constructors; 9 | use crate::internal::openssl_ffi::{sct_list_from_x509, SCTVersion, SignatureAlgorithm, x509_clone, x509_remove_sct_list, x509_to_tbs}; 10 | use crate::internal::verify_dss_raw; 11 | 12 | fn to_unknown_err(openssl_err: openssl::error::ErrorStack) -> Error { 13 | Error::Unknown(format!("{}", openssl_err)) 14 | } 15 | 16 | /// An unverified *Signed Certificate Timestamp* (SCT). 17 | #[derive(Debug, Clone)] 18 | pub struct SignedCertificateTimestamp { 19 | pub log_id: [u8; 32], 20 | pub timestamp: u64, 21 | pub extensions_data: Vec, 22 | pub entry: SctEntry, 23 | pub signature_algorithm: SignatureAlgorithm, 24 | /// Raw signature encoded in ASN.1 25 | pub raw_signature: Vec 26 | } 27 | 28 | /// Either a X509 der, or (in case of pre-cert) tbs and issuer key hash. 29 | /// 30 | /// Used within [`SignedCertificateTimestamp`] 31 | #[derive(Debug, Clone)] 32 | pub enum SctEntry { 33 | X509(Vec), 34 | PreCert { tbs: Vec, issuer_key_hash: [u8; 32] } 35 | } 36 | 37 | impl SignedCertificateTimestamp { 38 | /// Extract a list of SCTs from the SCT List extension of the given openssl-parsed certificate, 39 | /// if the extension is there. 40 | /// 41 | /// If the certificate does not contain the extension, `Ok(vec![])` is returned. 42 | /// 43 | /// Will not verify the signature. Call [`self.verify`](Self::verify) with the log's public key to verify. 44 | pub fn from_cert_sct_extension(cert: &X509Ref, issuer: &X509Ref) -> Result, Error> { 45 | let sctlist = sct_list_from_x509(cert)?; 46 | if sctlist.is_none() { 47 | return Ok(Vec::new()); 48 | } 49 | let sctlist = sctlist.unwrap(); 50 | let tbs = { 51 | let mut cert_clone = x509_clone(cert).map_err(to_unknown_err)?; 52 | x509_remove_sct_list(&mut cert_clone).map_err(to_unknown_err)?; 53 | x509_to_tbs(&cert_clone).map_err(to_unknown_err)? 54 | }; 55 | let issuer_key_hash = { 56 | let k = issuer.public_key() 57 | .map_err(|e| Error::BadCertificate(format!("Can't parse public key from issuer: {}", e)))? 58 | .public_key_to_der().map_err(to_unknown_err)?; 59 | sha256(&k) 60 | }; 61 | let mut scts = Vec::with_capacity(sctlist.len()); 62 | for raw_sct in sctlist.into_iter() { 63 | if raw_sct.version() != Some(SCTVersion::V1) { 64 | return Err(Error::BadCertificate("Invalid SCT version.".to_owned())); 65 | } 66 | scts.push(SignedCertificateTimestamp { 67 | log_id: raw_sct.log_id().try_into().map_err(|_| Error::BadCertificate("Expected log_id to have len 32".to_owned()))?, 68 | timestamp: raw_sct.timestamp(), 69 | extensions_data: raw_sct.extensions().to_vec(), 70 | entry: SctEntry::PreCert { tbs: tbs.clone(), issuer_key_hash: issuer_key_hash.clone() }, 71 | signature_algorithm: raw_sct.signature_algorithm().ok_or_else(|| Error::BadSct("Unknown signature algorithm.".to_owned()))?, 72 | raw_signature: raw_sct.raw_signature().to_vec() 73 | }); 74 | } 75 | Ok(scts) 76 | } 77 | 78 | /// Derive the corresponding Merkle leaf hash from this SCTs. 79 | /// 80 | /// Can be used to check inclusion, for example. 81 | pub fn derive_leaf_hash(&self) -> [u8; 32] { 82 | match &self.entry { 83 | SctEntry::PreCert { tbs, issuer_key_hash } => { 84 | leaf_hash_constructors::with_precert(&tbs[..], &issuer_key_hash[..], self.timestamp, &self.extensions_data) 85 | } 86 | SctEntry::X509(x509) => { 87 | leaf_hash_constructors::with_x509(&x509, self.timestamp, &self.extensions_data) 88 | } 89 | } 90 | } 91 | 92 | /// Check the signature in this SCT. 93 | /// 94 | /// To get the log public key, lookup the log with `self.log_id` by e.g. using [`crate::google_log_list::LogList::find_by_id`]. 95 | pub fn verify(&self, log_public_key: &PKey) -> Result<(), Error> { 96 | // type CertificateTimestamp struct { 97 | let mut signed_data: Vec = Vec::new(); 98 | // SCTVersion Version `tls:"maxval:255"` 99 | signed_data.push(0u8); 100 | // SignatureType SignatureType `tls:"maxval:255"` 101 | signed_data.push(0u8); 102 | // Timestamp uint64 103 | signed_data.extend_from_slice(&self.timestamp.to_be_bytes()); 104 | // EntryType LogEntryType `tls:"maxval:65535"` 105 | signed_data.extend_from_slice(&match &self.entry { 106 | SctEntry::X509(_) => 0u16, 107 | SctEntry::PreCert { tbs: _, issuer_key_hash: _ } => 1u16 108 | }.to_be_bytes()); 109 | match &self.entry { 110 | // X509Entry *ASN1Cert `tls:"selector:EntryType,val:0"` 111 | SctEntry::X509(cert) => { 112 | let len = cert.len(); 113 | if len > 1<<24 { 114 | return Err(Error::BadSct("Certificate too long.".to_owned())); 115 | } 116 | signed_data.extend_from_slice(&u32::to_be_bytes(len as u32)[1..4]); 117 | signed_data.extend_from_slice(cert); 118 | }, 119 | // PrecertEntry *PreCert `tls:"selector:EntryType,val:1"` 120 | SctEntry::PreCert { tbs, issuer_key_hash } => { 121 | signed_data.extend_from_slice(issuer_key_hash); 122 | let len = tbs.len(); 123 | if len > 1<<24 { 124 | return Err(Error::BadSct("TBS certificate too long.".to_owned())); 125 | } 126 | signed_data.extend_from_slice(&u32::to_be_bytes(len as u32)[1..4]); 127 | signed_data.extend_from_slice(tbs); 128 | } 129 | } 130 | // Extensions CTExtensions `tls:"minlen:0,maxlen:65535"` 131 | let ext_len = self.extensions_data.len(); 132 | if ext_len > 1<<16 { 133 | return Err(Error::BadSct("extension data too long.".to_owned())); 134 | } 135 | signed_data.extend_from_slice(&u16::to_be_bytes(ext_len as u16)); 136 | signed_data.extend_from_slice(&self.extensions_data); 137 | // } 138 | verify_dss_raw(self.signature_algorithm, log_public_key, &self.raw_signature, &signed_data) 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /src/sth.rs: -------------------------------------------------------------------------------- 1 | use openssl::pkey::PKey; 2 | 3 | use crate::{Error, internal}; 4 | 5 | /// An unverified *signed tree head* (STH), as returned from the server. This encapsulate the state of the tree at 6 | /// some point in time. 7 | /// 8 | /// This struct stores the signature but does not store the public key or log id. 9 | #[derive(Clone, Debug, PartialEq, Eq)] 10 | pub struct SignedTreeHead { 11 | pub tree_size: u64, 12 | pub timestamp: u64, 13 | pub root_hash: [u8; 32], 14 | /// Digitally signed struct 15 | pub signature: Vec 16 | } 17 | 18 | impl SignedTreeHead { 19 | /// Verify the contained signature against the log's public key. 20 | pub fn verify(&self, pub_key: &PKey) -> Result<(), Error> { 21 | let mut verify_body: Vec = Vec::new(); 22 | /* 23 | From go source: 24 | type TreeHeadSignature struct { 25 | Version Version `tls:"maxval:255"` 26 | SignatureType SignatureType `tls:"maxval:255"` // == TreeHashSignatureType 27 | Timestamp uint64 28 | TreeSize uint64 29 | SHA256RootHash SHA256Hash 30 | } 31 | */ 32 | verify_body.push(0); // Version = 0 33 | verify_body.push(1); // SignatureType = TreeHashSignatureType 34 | verify_body.extend_from_slice(&self.timestamp.to_be_bytes()); // Timestamp 35 | verify_body.extend_from_slice(&self.tree_size.to_be_bytes()); // TreeSize 36 | verify_body.extend_from_slice(&self.root_hash); 37 | internal::verify_dss(&self.signature, pub_key, &verify_body).map_err(|e| { 38 | match e { 39 | Error::InvalidSignature(desc) => Error::InvalidSignature(format!("When checking STH signature: {}", &desc)), 40 | other => other 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test_data/precert-signing-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID+jCCAuKgAwIBAgIHBaZWEVeEbjANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQG 3 | EwJHQjEPMA0GA1UEBwwGTG9uZG9uMTowOAYDVQQKDDFHb29nbGUgQ2VydGlmaWNh 4 | dGUgVHJhbnNwYXJlbmN5IChQcmVjZXJ0IFNpZ25pbmcpMRkwFwYDVQQFExAxNTkw 5 | MjYzNDcxNjc1ODI3MB4XDTIwMDUyMzE5NTExMVoXDTIwMDgxNTA2MDYwNlowYzEL 6 | MAkGA1UEBhMCR0IxDzANBgNVBAcMBkxvbmRvbjEoMCYGA1UECgwfR29vZ2xlIENl 7 | cnRpZmljYXRlIFRyYW5zcGFyZW5jeTEZMBcGA1UEBRMQMTU5MDI2MzQ3MTg5OTc1 8 | ODCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALnxR95tLlBtuQS0rHQq 9 | XMdCRPuQJS5WMnJ92pHIU1vtmveeJrrnLupRXZDMwuy4FM6LKLHSBZsyLpTRHoFC 10 | tdRNiZElRF1AKXomRXwW5jzulb1DbY+Z1bBNo7VA1IfMPwEqpN4iDlFLVK11LWGS 11 | 06JmGr/KpYSSOjKlfBJExMQUyq/Vn6uSltgs9JNg9XSEttu/RwTsQr9Qvj9Homhu 12 | WuSi7jzMQlnzYPrr2K64j73mth2u67Ipy9XqxMSKi7juryJBU7cWwJK/G6QqUWHG 13 | wLGFilFFHD+gRbpG0DbNa+9WKOU7zEhryW+ULH7FWvhMNHqGQwsfzAh/1R7SBJGs 14 | 6IkCAwEAAaOBoDCBnTATBgNVHSUEDDAKBggrBgEFBQcDATAjBgNVHREEHDAaghhm 15 | bG93ZXJzLXRvLXRoZS13b3JsZC5jb20wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAW 16 | gBTf6n5iLmr0hlEX4h01JOgl9KSHszAdBgNVHQ4EFgQU7VqOdTd6jXZZ5OisEBho 17 | Ugvgks4wEwYKKwYBBAHWeQIEAwEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAJhv 18 | UrIeNYnB1CyhCneziThfwHzwVLFrSVM6RuzChfLDYq67IONRzvx63U09SYCzWBq6 19 | xWF5Gfjp1t4PbpzxpcyQ7Vgjq67QJw0wGrX5WV1zEY4mxW0oDoygf5a38poFRZ+C 20 | C6L9ym/tnrw0IWBH9NkQpQdLbgMkSU5m+3tPan92tKEwLq6PydLcpEEC10NMI3Vc 21 | lE/mOq31qFyKYSxD6dyi3kxzeM4fbps8oClTtmFEg1BIxZ/FsQcoCPYhiQhmftBh 22 | MG0YFhgTyrVezgE4LrjEbHGBW45xbJMTOmMbKD1AIitZT5Sf7362zfsqJ70RAdlO 23 | y+kgg3EpeJm1+KYURHM= 24 | -----END CERTIFICATE----- 25 | -----BEGIN CERTIFICATE----- 26 | MIIE4jCCAsqgAwIBAgIHBaZWEVQZszANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQG 27 | EwJHQjEPMA0GA1UECAwGTG9uZG9uMRcwFQYDVQQKDA5Hb29nbGUgVUsgTHRkLjEh 28 | MB8GA1UECwwYQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MSMwIQYDVQQDDBpNZXJn 29 | ZSBEZWxheSBJbnRlcm1lZGlhdGUgMTAeFw0yMDA1MjMxOTUxMTFaFw0yMDA4MTUw 30 | NjA3MDZaMHUxCzAJBgNVBAYTAkdCMQ8wDQYDVQQHDAZMb25kb24xOjA4BgNVBAoM 31 | MUdvb2dsZSBDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgKFByZWNlcnQgU2lnbmlu 32 | ZykxGTAXBgNVBAUTEDE1OTAyNjM0NzE2NzU4MjcwggEiMA0GCSqGSIb3DQEBAQUA 33 | A4IBDwAwggEKAoIBAQDP0jr0vvzzc1dwE8aG8FngyREh2WQlpz9SrJyK7Ch+qTqy 34 | o9wWGtGNd51EodlLaU8EgfMb+DQIFsT5rV8D0HUZzFpyNU4cVfaWdndPAoIdYXe5 35 | y9tyNS16msTTcnZzMME/rgqya35MiQypHMkJUz05jut1fp2i5f9N8oq4Rg+nMRHO 36 | zB9S3Ipv6WzQYIN1npcgTixmf3B08TXtBlEoIUgNu/tB2OEo2Dl7bfY1fS6pi2LT 37 | vXcAAHqevOHxC495Kng6vqN2T7UIs2qWk1ukxHQgb0ar3JJNTdi+nvjJbVLSZzTD 38 | PxcJ7RbXoPBVfcGBfF71RluQFxmWytXg1u6gSlK3AgMBAAGjbTBrMBgGA1UdJQEB 39 | /wQOMAwGCisGAQQB1nkCBAQwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTp 40 | PAThgC/ChBMtJnCe8v0az6r+xjAdBgNVHQ4EFgQU3+p+Yi5q9IZRF+IdNSToJfSk 41 | h7MwDQYJKoZIhvcNAQEFBQADggIBAEX2WkTc+ZauM5YhGKGSeOl2d8BtrPYxinHm 42 | g65o4tGAR0WxfBsaT2CxLccjRoI0sTBVfZ/O5wXy1slnwB+IGZPC7m56DVSkojtr 43 | suRKRKLVD6MZJQnPu9sIvg8zopMofdkmZhrHVnK7RL2/+OmpOze61LxqS5djDypy 44 | 8XBkE6So04MXRMTSyc3b8mUB8tF+jxxj4+l/ps2JaTx8qh9g3wVMg1xS6Vj9wvdV 45 | v+9AIbnGyfCPlI1ZT5rjG1UvMV+A3Muujz2GKZceklHzO1W78XCadlp+jqMvryxQ 46 | +4OktchRSZP4Dk39FUW2VUZd2xsC1OFOL63rtrutEXwnq3G3drHEZUp3t7/+xuDQ 47 | 9kyI/sB2Wfvv2KAYVHBkIzKvp8L4pNNZNLzJBzZMUEtq7SL0SakJNMECkQqwoGTQ 48 | 28cHNJh9MZJv4RfdsJybH9wxIYyZzshp9lvRTMBC0SK7J9lftlE9dv7QQ6jNnpPz 49 | 8NATvvjme70UeIkcw2vR30u1Lip+nx+m3XvdRTyBr0yM/WhCZ0fgU/vhrQ5P6ZkU 50 | aADVbjmbW6f1oTSp1W7d0ImF9d2hUIdSM964oGM1UiGbhvOCrqSjcPyFsGSQNXr9 51 | FiiUIk9tpgOcSMfCo7s3SuQHPxaeT+blfCJ/ntupA5gimRK8+lWeya+b0iviBFRs 52 | OEmw7t03 53 | -----END CERTIFICATE----- 54 | -----BEGIN CERTIFICATE----- 55 | MIIFyDCCA7CgAwIBAgICEAEwDQYJKoZIhvcNAQEFBQAwfTELMAkGA1UEBhMCR0Ix 56 | DzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNV 57 | BAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEhMB8GA1UEAwwYTWVyZ2UgRGVs 58 | YXkgTW9uaXRvciBSb290MB4XDTE0MDcxNzEyMjYzMFoXDTE5MDcxNjEyMjYzMFow 59 | fzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xl 60 | IFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEjMCEG 61 | A1UEAwwaTWVyZ2UgRGVsYXkgSW50ZXJtZWRpYXRlIDEwggIiMA0GCSqGSIb3DQEB 62 | AQUAA4ICDwAwggIKAoICAQDB6HT+/5ru8wO7+mNFOIH6r43BwiwJZB2vQwOB8zvB 63 | V79sTIqNV7Grx5KFnSDyGRUJxZfEN7FGc96lr0vqFDlt1DbcYgVV15U+Dt4B9/+0 64 | Tz/3zeZO0kVjTg3wqvzpw6xetj2N4dlpysiFQZVAOp+dHUw9zu3xNR7dlFdDvFSr 65 | dFsgT7Uln+Pt9pXCz5C4hsSP9oC3RP7CaRtDRSQrMcNvMRi3J8XeXCXsGqMKTCRh 66 | xRGe9ruQ2Bbm5ExbmVW/ou00Fr9uSlPJL6+sDR8Li/PTW+DU9hygXSj8Zi36WI+6 67 | PuA4BHDAEt7Z5Ru/Hnol76dFeExJ0F6vjc7gUnNh7JExJgBelyz0uGORT4NhWC7S 68 | RWP/ngPFLoqcoyZMVsGGtOxSt+aVzkKuF+x64CVxMeHb9I8t3iQubpHqMEmIE1oV 69 | SCsF/AkTVTKLOeWG6N06SjoUy5fu9o+faXKMKR8hldLM5z1K6QhFsb/F+uBAuU/D 70 | WaKVEZgbmWautW06fF5I+OyoFeW+hrPTbmon4OLE3ubjDxKnyTa4yYytWSisojjf 71 | w5z58sUkbLu7KAy2+Z60m/0deAiVOQcsFkxwgzcXRt7bxN7By5Q5Bzrz8uYPjFBf 72 | BnlhqMU5RU/FNBFY7Mx4Uy8+OcMYfJQ5/A/4julXEx1HjfBj3VCyrT/noHDpBeOG 73 | iwIDAQABo1AwTjAdBgNVHQ4EFgQU6TwE4YAvwoQTLSZwnvL9Gs+q/sYwHwYDVR0j 74 | BBgwFoAU8197dUnjeEE5aiC2fGtMXMk9WEEwDAYDVR0TBAUwAwEB/zANBgkqhkiG 75 | 9w0BAQUFAAOCAgEACFjL1UXy6S4JkGrDnz1VwTYHplFDY4bG6Q8Sh3Og6z9HJdiv 76 | Nft/iAQ2tIHyz0eAGCXeVPE/j1kgvz2RbnUxQd5eWdLeu/w/wiZyHxWhbTt6Rhjq 77 | BVFjnx0st7n6rRt+Bw8jpugZfD11SbumVT/V20Gc45lHf2oEgbkPUcnTB9gssFz5 78 | Z4KKGs5lIHz4a20WeSJF3PJLTBefkRhHNufi/LhjpLXImwrC82g5ChBZS5XIVuJZ 79 | x3VkMWiYz4emgX0YWF/JdtaB2dUQ7yrTforQ5J9b1JnJ7H/o9DsX3/ubfQ39gwDB 80 | xTicnqC+Q3Dcv3i9PvwjCNJQuGa7ygMcDEn/d6elQg2qHxtqRE02ZlOXTC0XnDAJ 81 | hx7myJFA/Knv3yO9S4jG6665KG9Y88/CHkh08YLR7NYFiRmwOxjbe3lb6csl/FFm 82 | qUXvjhEzzWAxKjI09GSd9hZkB8u17Mg46eEYwF3ufIlqmYdlWufjSc2BZuaNNN6j 83 | tK6JKp8jhQUycehgtUK+NlBQOXTzu28miDdasoSH2mdR0PLDo1547+MLGdV4COvq 84 | LERTmQrYHrliicD5nFCA+CCSvGEjo0DGOmF/O8StwSmNiKJ4ppPvk2iGEdO07e0L 85 | bQI+2fbC6og2SDGXUlsbG85wqQw0A7CU1fQSqhFBuZZauDFMUvdy3v/BAIw= 86 | -----END CERTIFICATE----- 87 | -----BEGIN CERTIFICATE----- 88 | MIIFzTCCA7WgAwIBAgIJAJ7TzLHRLKJyMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV 89 | BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQu 90 | MSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMMGE1l 91 | cmdlIERlbGF5IE1vbml0b3IgUm9vdDAeFw0xNDA3MTcxMjA1NDNaFw00MTEyMDIx 92 | MjA1NDNaMH0xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoM 93 | Dkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVu 94 | Y3kxITAfBgNVBAMMGE1lcmdlIERlbGF5IE1vbml0b3IgUm9vdDCCAiIwDQYJKoZI 95 | hvcNAQEBBQADggIPADCCAgoCggIBAKoWHPIgXtgaxWVIPNpCaj2y5Yj9t1ixe5Pq 96 | jWhJXVNKAbpPbNHA/AoSivecBm3FTD9DfgW6J17mHb+cvbKSgYNzgTk5e2GJrnOP 97 | 7yubYJpt2OCw0OILJD25NsApzcIiCvLA4aXkqkGgBq9FiVfisReNJxVu8MtxfhbV 98 | QCXZf0PpkW+yQPuF99V5Ri+grHbHYlaEN1C/HM3+t2yMR4hkd2RNXsMjViit9qCc 99 | hIi/pQNt5xeQgVGmtYXyc92ftTMrmvduj7+pHq9DEYFt3ifFxE8v0GzCIE1xR/d7 100 | prFqKl/KRwAjYUcpU4vuazywcmRxODKuwWFVDrUBkGgCIVIjrMJWStH5i7WTSSTr 101 | VtOD/HWYvkXInZlSgcDvsNIG0pptJaEKSP4jUzI3nFymnoNZn6pnfdIII/XISpYS 102 | Veyl1IcdVMod8HdKoRew9CzW6f2n6KSKU5I8X5QEM1NUTmRLWmVi5c75/CvS/PzO 103 | MyMzXPf+fE2Dwbf4OcR5AZLTupqp8yCTqo7ny+cIBZ1TjcZjzKG4JTMaqDZ1Sg0T 104 | 3mO/ZbbiBE3N8EHxoMWpw8OP50z1dtRRwj6qUZ2zLvngOb2EihlMO15BpVZC3Cg9 105 | 29c9Hdl65pUd4YrYnQBQB/rn6IvHo8zot8zElgOg22fHbViijUt3qnRggB40N30M 106 | XkYGwuJbAgMBAAGjUDBOMB0GA1UdDgQWBBTzX3t1SeN4QTlqILZ8a0xcyT1YQTAf 107 | BgNVHSMEGDAWgBTzX3t1SeN4QTlqILZ8a0xcyT1YQTAMBgNVHRMEBTADAQH/MA0G 108 | CSqGSIb3DQEBBQUAA4ICAQB3HP6jRXmpdSDYwkI9aOzQeJH4x/HDi/PNMOqdNje/ 109 | xdNzUy7HZWVYvvSVBkZ1DG/ghcUtn/wJ5m6/orBn3ncnyzgdKyXbWLnCGX/V61Pg 110 | IPQpuGo7HzegenYaZqWz7NeXxGaVo3/y1HxUEmvmvSiioQM1cifGtz9/aJsJtIkn 111 | 5umlImenKKEV1Ly7R3Uz3Cjz/Ffac1o+xU+8NpkLF/67fkazJCCMH6dCWgy6SL3A 112 | OB6oKFIVJhw8SD8vptHaDbpJSRBxifMtcop/85XUNDCvO4zkvlB1vPZ9ZmYZQdyL 113 | 43NA+PkoKy0qrdaQZZMq1Jdp+Lx/yeX255/zkkILp43jFyd44rZ+TfGEQN1WHlp4 114 | RMjvoGwOX1uGlfoGkRSgBRj7TBn514VYMbXu687RS4WY2v+kny3PUFv/ZBfYSyjo 115 | NZnU4Dce9kstgv+gaKMQRPcyL+4vZU7DV8nBIfNFilCXKMN/VnNBKtDV52qmtOsV 116 | ghgai+QE09w15x7dg+44gIfWFHxNhvHKys+s4BBN8fSxAMLOsb5NGFHE8x58RAkm 117 | IYWHjyPM6zB5AUPw1b2A0sDtQmCqoxJZfZUKrzyLz8gS2aVujRYN13KklHQ3EKfk 118 | eKBG2KXVBe5rjMN/7Anf1MtXxsTY6O8qIuHZ5QlXhSYzE41yIlPlG6d7AGnTiBIg 119 | eg== 120 | -----END CERTIFICATE----- 121 | -------------------------------------------------------------------------------- /src/test_data/precert-signing.ca.tbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micromaomao/ctclient/c2e780eea087b806e7c3ed89554ceaf7aa242e33/src/test_data/precert-signing.ca.tbs -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Some utility functions. 2 | 3 | pub use openssl::sha::sha256; 4 | 5 | #[test] 6 | fn sha256_test() { 7 | assert_eq!(u8_to_hex(&sha256(b"")), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); 8 | assert_eq!(u8_to_hex(&sha256(b"hello")), "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"); 9 | } 10 | 11 | /// Convert a hex string with no whitespace or other sepreator into `&[u8]`. 12 | /// 13 | /// # Example: 14 | /// ```rust 15 | /// # use ctclient::utils::hex_to_u8; 16 | /// assert_eq!(&hex_to_u8("aabb"), b"\xaa\xbb"); 17 | /// ``` 18 | pub fn hex_to_u8(hex: &str) -> Vec { 19 | if hex.len() % 2 != 0 { 20 | panic!("partial hex?"); 21 | } 22 | let mut vec = Vec::new(); 23 | vec.reserve(hex.len() / 2); 24 | for i in 0..(hex.len() / 2) { 25 | let hex = &hex[i*2..(i+1)*2]; 26 | vec.push(u8::from_str_radix(hex, 16).unwrap()); 27 | } 28 | vec 29 | } 30 | 31 | /// Convert a `&[u8]` byte array to a lower-case, no-sepreator hex string. 32 | /// 33 | /// # Example: 34 | /// ```rust 35 | /// # use ctclient::utils::u8_to_hex; 36 | /// assert_eq!(&u8_to_hex(b"\xaa\xbb"), "aabb"); 37 | /// ``` 38 | pub fn u8_to_hex(bytes: &[u8]) -> String { 39 | let mut buf = String::new(); 40 | for i in bytes { 41 | buf.push_str(&format!("{:02x?}", i)); 42 | } 43 | buf 44 | } 45 | 46 | #[test] 47 | fn hex_to_u8_test() { 48 | assert_eq!(hex_to_u8("deadbeef"), vec![0xde, 0xad, 0xbe, 0xef]); 49 | assert_eq!(hex_to_u8("DEADBEEF"), vec![0xde, 0xad, 0xbe, 0xef]); 50 | } 51 | #[test] 52 | fn u8_to_hex_test() { 53 | assert_eq!(u8_to_hex(&[0xde, 0xad, 0xbe, 0xef]), "deadbeef"); 54 | assert_eq!(u8_to_hex(&[0x01, 0x02, 0x03, 0x04]), "01020304"); 55 | } 56 | 57 | /// Calculate `sha256(0x01 || left || right)` 58 | pub fn combine_tree_hash(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { 59 | let mut buf = Vec::new(); 60 | buf.reserve(32 + 32 + 1); 61 | buf.push(1); 62 | buf.extend_from_slice(left); 63 | buf.extend_from_slice(right); 64 | sha256(&buf[..]) 65 | } 66 | 67 | /// For a tree of size `n`, return the size of the left branch of the root. 68 | pub fn largest_power_of_2_smaller_than(mut n: u64) -> u64 { 69 | if n <= 1 { 70 | return 0; 71 | } 72 | if n == 2 { 73 | return 1; 74 | } 75 | n -= 1; 76 | // I'm sure the compiler can optimize this nicely. 77 | let mut pow: u64 = 1; 78 | loop { 79 | n /= 2; 80 | pow *= 2; 81 | if n == 1 { 82 | return pow; 83 | } 84 | } 85 | } 86 | 87 | #[test] 88 | fn test_largest_power_of_2_smaller_than() { 89 | assert_eq!(largest_power_of_2_smaller_than(0), 0); 90 | assert_eq!(largest_power_of_2_smaller_than(1), 0); 91 | assert_eq!(largest_power_of_2_smaller_than(2), 1); 92 | assert_eq!(largest_power_of_2_smaller_than(3), 2); 93 | assert_eq!(largest_power_of_2_smaller_than(4), 2); 94 | assert_eq!(largest_power_of_2_smaller_than(5), 4); 95 | assert_eq!(largest_power_of_2_smaller_than(6), 4); 96 | assert_eq!(largest_power_of_2_smaller_than(7), 4); 97 | assert_eq!(largest_power_of_2_smaller_than(8), 4); 98 | assert_eq!(largest_power_of_2_smaller_than(9), 8); 99 | 100 | assert_eq!(largest_power_of_2_smaller_than(1u64<<33u64), 1u64<<32u64); 101 | assert_eq!(largest_power_of_2_smaller_than((1u64<<34u64) - 100u64), 1u64<<33u64); 102 | } 103 | --------------------------------------------------------------------------------