Compare commits

...

21 commits

Author SHA1 Message Date
Tim Wojtulewicz
e76a08524b Updating CHANGES and VERSION. 2025-05-09 07:29:54 -07:00
Christian Kreibich
61f094e928 Merge branch 'topic/christian/fix-broker-peering-overflows-metric'
* topic/christian/fix-broker-peering-overflows-metric:
  Bugfix: accurately track Broker buffer overflows w/ multiple peerings

(cherry picked from commit 8d79429555)
2025-05-08 15:15:25 -07:00
Tim Wojtulewicz
589f146549 Merge remote-tracking branch 'origin/topic/bbannier/bump-spicy-7.2' into release/7.2
* origin/topic/bbannier/bump-spicy-7.2:
  Bump auxil/spicy to latest release
2025-05-08 15:13:40 -07:00
Tim Wojtulewicz
9369a6e3dd Merge branch 'topic/timw/switch-broker-error-to-error'
* topic/timw/switch-broker-error-to-error:
  Downgrade broker clone FatalError to an Error

(cherry picked from commit 2c17c85f55)
2025-05-08 13:48:02 -07:00
Benjamin Bannier
7330606b74 Bump auxil/spicy to latest release 2025-05-08 21:55:04 +02:00
Tim Wojtulewicz
1807710a8c Update docs submodule [nomail] [skip ci] 2025-05-08 11:00:36 -07:00
Tim Wojtulewicz
6b5b2d1c77 Merge remote-tracking branch 'origin/topic/christian/news-fix'
* origin/topic/christian/news-fix:
  Fix Broker metrics naming bugs that snuck in w/ last-minute renaming [skip ci]

(cherry picked from commit 507974a1d8)
2025-05-07 14:11:33 -07:00
Tim Wojtulewicz
0d70dde10b Merge remote-tracking branch 'origin/topic/johanna/fix-failed-service-logging'
* origin/topic/johanna/fix-failed-service-logging:
  Fix policy/protocols/conn/failed-service-logging.zeek

(cherry picked from commit 6f8924596f)
2025-05-07 10:34:07 -07:00
Arne Welzel
e326e31d7e Merge remote-tracking branch 'origin/topic/awelzel/more-terminate-while-queueing-hardening'
* origin/topic/awelzel/more-terminate-while-queueing-hardening:
  btest/cluster/generic/publish-any: Apply Christian's fix from broker/publish-any
  wstest/terminate-while-queueing: Patch close_socket()

(cherry picked from commit 8089f5bed4)
2025-05-07 17:26:22 +02:00
Arne Welzel
3ee6a3d6c0 Merge remote-tracking branch 'origin/topic/awelzel/fix-flaky-terminate-while-queueing'
* origin/topic/awelzel/fix-flaky-terminate-while-queueing:
  cluster/websocket: Stop and wait for reply thread during Terminate()

(cherry picked from commit 135acc7c6d)
2025-05-07 14:06:41 +02:00
Arne Welzel
01747191b6 Merge remote-tracking branch 'origin/topic/awelzel/4420-simeon-breaking-websockets'
* origin/topic/awelzel/4420-simeon-breaking-websockets:
  Websocket: Close onloop during Terminate()
  OnLoop: notify_all() instead of notify_one()

(cherry picked from commit 4afb0ffeeb)
2025-05-07 11:13:03 +02:00
Tim Wojtulewicz
182641ccc1 Merge remote-tracking branch 'origin/topic/timw/storage-check-redis-server-version'
* origin/topic/timw/storage-check-redis-server-version:
  Add commands to the static methods for the Redis implementation
  Redis: Check server version when connecting

(cherry picked from commit f9aa9a430d)
2025-05-06 11:44:00 -07:00
Tim Wojtulewicz
4f683ee1f8 Merge remote-tracking branch 'origin/topic/timw/storage-redis-expire-string-view'
* origin/topic/timw/storage-redis-expire-string-view:
  Use std::string_view in Redis::DoExpire to avoid copies

(cherry picked from commit 58d71d2fa3)
2025-05-05 11:21:40 -07:00
Arne Welzel
99acfc6534 Merge remote-tracking branch 'origin/topic/awelzel/4405-quic-fragmented-crypto'
* origin/topic/awelzel/4405-quic-fragmented-crypto:
  Bump external/zeek-testing
  QUIC: Extract reset_crypto() function
  QUIC: Rename ConnectionIDInfo to Context
  QUIC: Switch initial_destination_conn_id to optional
  QUIC: Use initial destination conn_id for decryption
  QUIC: Handle CRYPTO frames across multiple INITIAL packets
  QUIC: Do not consume EncryptedLongPacketPayload
  QUIC: Fix ACK frame parsing

(cherry picked from commit 50ac8d1468)
2025-05-05 11:19:30 -07:00
Tim Wojtulewicz
b3166c9379 Merge remote-tracking branch 'origin/topic/vern/zam-inlining-temps'
* origin/topic/vern/zam-inlining-temps:
  fixed incorrect ZAM optimization of expressions seen in single-statement inlined functions

(cherry picked from commit e56de061f9)
2025-04-29 17:51:34 -07:00
Tim Wojtulewicz
85e2a2fc1e Merge remote-tracking branch 'origin/topic/timw/update-ct-ca-lists'
* origin/topic/timw/update-ct-ca-lists:
  External tests: add removed logs to CT list to prevent baseline changes
  Update Mozilla CA list and CT list to NSS 3.110

(cherry picked from commit 2cf8497bf7)
2025-04-29 08:54:12 -07:00
Arne Welzel
fb443815fa Fixup CHANGES 2025-04-28 19:55:07 +02:00
Arne Welzel
35140ada29 Merge remote-tracking branch 'origin/topic/awelzel/cluster-coverity-fixes'
* origin/topic/awelzel/cluster-coverity-fixes:
  broker/WebSocketShim: Check RegisterFd() return
  cluster/OnLoop: Fix coverity report about proc accessed without lock

(cherry picked from commit 5bf660a9ce)
2025-04-28 19:49:30 +02:00
Arne Welzel
05baf8858b Merge remote-tracking branch 'origin/topic/awelzel/3045-no-holes-in-vectors'
* origin/topic/awelzel/3045-no-holes-in-vectors:
  broker/Data/data_to_val: Fail on vectors/lists with holes

(cherry picked from commit 540baa89af)
2025-04-28 19:48:05 +02:00
Tim Wojtulewicz
8d294f83ad Merge remote-tracking branch 'origin/topic/timw/use-after-move'
* origin/topic/timw/use-after-move:
  Fix use-after-move in recent broker changes

(cherry picked from commit b9b268bd86)
2025-04-25 16:13:12 -07:00
Tim Wojtulewicz
6e47186c4a Updating CHANGES and VERSION. 2025-04-25 12:00:27 -07:00
58 changed files with 1065 additions and 190 deletions

View file

@ -80,6 +80,8 @@ have_2nd = "have_2nd"
ot1 = "ot1"
ot2 = "ot2"
uses_seh = "uses_seh"
ect0 = "ect0"
ect1 = "ect1"
[default.extend-words]
caf = "caf"

175
CHANGES
View file

@ -1,3 +1,178 @@
7.2.0 | 2025-05-08 15:15:25 -0700
* Release 7.2.0.
7.2.0-rc1.19 | 2025-05-08 15:14:29 -0700
* Bugfix: accurately track Broker buffer overflows w/ multiple peerings (Christian Kreibich, Corelight)
(cherry picked from commit 8d7942955573673a1eedd98d027a1efcaec485c8)
7.2.0-rc1.18 | 2025-05-08 14:47:17 -0700
* Bump auxil/spicy to latest release (Benjamin Bannier, Corelight)
7.2.0-rc1.16 | 2025-05-08 13:47:36 -0700
* Downgrade broker clone FatalError to an Error (Tim Wojtulewicz, Corelight)
(cherry picked from commit 2c17c85f557245b6163ced014fc1d8089e1fcdf2)
7.2.0-rc1.14 | 2025-05-07 14:11:04 -0700
* Fix Broker metrics naming bugs that snuck in w/ last-minute renaming [skip ci] (Christian Kreibich, Corelight)
(cherry picked from commit 507974a1d80442f5d5b2e7c5f4d3c45d44752d76)
7.2.0-rc1.13 | 2025-05-07 10:32:36 -0700
* Fix policy/protocols/conn/failed-service-logging.zeek (Johanna Amann, Corelight)
In GH-4422 it was pointed out that the protocols/conn/failed-service-logging.zeek
policy script only works when
`DPD::track_removed_services_in_connection=T` is set.
This was caused by a logic error in the script. This commit fixes this
logic error and introduces an additional test that checks that
failed-service-logging works even when the option is not set to true.
(cherry picked from commit 6f8924596fb86ee0c25fe26c810f3a8e3f9068a9)
7.2.0-rc1.12 | 2025-05-07 17:25:07 +0200
* btest/cluster/generic/publish-any: Apply Christian's fix from broker/publish-any (Arne Welzel, Corelight)
(cherry picked from commit 8089f5bed422e0da38dde631f88d6371c3d66c2f)
* wstest/terminate-while-queueing: Patch close_socket() (Arne Welzel, Corelight)
(cherry picked from commit 8089f5bed422e0da38dde631f88d6371c3d66c2f)
7.2.0-rc1.11 | 2025-05-07 14:06:06 +0200
* cluster/websocket: Stop and wait for reply thread during Terminate() (Arne Welzel, Corelight)
(cherry picked from commit 135acc7c6d2cd150b9090ca7563519ffadc2148e)
7.2.0-rc1.10 | 2025-05-07 11:10:09 +0200
* Websocket: Close onloop during Terminate() (Arne Welzel, Corelight)
(cherry picked from commit 4afb0ffeebd000211feb1e30891a3439235977bf)
* OnLoop: notify_all() instead of notify_one() (Arne Welzel, Corelight)
(cherry picked from commit 4afb0ffeebd000211feb1e30891a3439235977bf)
7.2.0-rc1.9 | 2025-05-06 11:42:23 -0700
* Add comments to the static methods for the Redis implementation (Tim Wojtulewicz, Corelight)
(cherry picked from commit f9aa9a430d93841f0fa8dd55f84ce16954a8cbd3)
* Redis: Check server version when connecting (Tim Wojtulewicz, Corelight)
(cherry picked from commit f9aa9a430d93841f0fa8dd55f84ce16954a8cbd3)
7.2.0-rc1.8 | 2025-05-05 11:21:13 -0700
* Use std::string_view in Redis::DoExpire to avoid copies (Tim Wojtulewicz, Corelight)
(cherry picked from commit 58d71d2fa35007695cd0f6ea3ec975df67c91bab)
7.2.0-rc1.7 | 2025-05-05 11:17:50 -0700
* QUIC: Extract reset_crypto() function (Arne Welzel, Corelight)
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
* QUIC: Rename ConnectionIDInfo to Context (Arne Welzel, Corelight)
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
* QUIC: Switch initial_destination_conn_id to optional (Arne Welzel, Corelight)
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
* QUIC: Use initial destination conn_id for decryption (Arne Welzel, Corelight)
Ensure the client side also uses the initial destination connection ID
for decryption purposes instead of the one from the current long header
packet. PCAP from local WiFi hotspot.
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
* QUIC: Handle CRYPTO frames across multiple INITIAL packets (Arne Welzel, Corelight)
Instead of sending the accumulated CRYPTO frames after processing an
INITIAL packet, add logic to determine the total length of the TLS
Client or Server Hello (by peeking into the first 4 byte). Once all
CRYPTO frames have arrived, flush the reassembled data to the TLS
analyzer at once.
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
* QUIC: Do not consume EncryptedLongPacketPayload (Arne Welzel, Corelight)
The payload is already consumed within the InitialPacket unit. Consuming
it again resulted in UDP datagrams with multiple packets to ignore
the remaining packets in the same UDP datagram. The baseline changes
showing I being followed by a new H indicates that the INITIAL packet
was followed by a HANDSHAKE packet, but previously Zeek discarded
these.
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
* QUIC: Fix ACK frame parsing (Arne Welzel, Corelight)
(cherry picked from commit 50ac8d1468603c710e109f1c050b3966dd91deda)
7.2.0-rc1.6 | 2025-04-29 17:51:10 -0700
* fixed incorrect ZAM optimization of expressions seen in single-statement inlined functions (Vern Paxson, Corelight)
(cherry picked from commit e56de061f9f59272d83b9ffa0540e965d47e4399)
7.2.0-rc1.5 | 2025-04-29 08:53:37 -0700
* External tests: add removed logs to CT list to prevent baseline changes (Johanna Amann, Corelight)
* Update Mozilla CA list and CT list to NSS 3.110 (Tim Wojtulewicz, Corelight)
7.2.0-rc1.3 | 2025-04-28 19:48:58 +0200
* broker/WebSocketShim: Check RegisterFd() return (Arne Welzel, Corelight)
(cherry picked from commit 5bf660a9ce6599581033e80b829d1103388b91cb)
* cluster/OnLoop: Fix coverity report about proc accessed without lock (Arne Welzel, Corelight)
(cherry picked from commit 5bf660a9ce6599581033e80b829d1103388b91cb)
* GH-3045: broker/Data/data_to_val: Fail on vectors/lists with holes (Arne Welzel, Corelight)
Instead of simply removing holes from vectors or lists when converting
from Val to Broker format, error out as the receiver has no chance to
reconstruct where the hole might have been.
We could encode holes with broker::none, but this will put unnecessary
burden on language bindings and users due to the potential optionality.
Think a std::vector<uint64_t> that technically needs to be a
std::vector<std::optional<uint64_t>> to represent optional elements
properly.
(cherry picked from commit 540baa89afc68038a2af1f0ff09bc57e361970ab)
7.2.0-rc1.1 | 2025-04-25 16:12:46 -0700
* Fix use-after-move in recent broker changes (Tim Wojtulewicz, Corelight)
(cherry picked from commit b9b268bd866b117f097413db9e80f80858cce0e0)
7.2.0-rc1 | 2025-04-25 11:10:16 -0700
* Update docs submodule [nomail] [skip ci] (Tim Wojtulewicz, Corelight)
7.2.0-dev.659 | 2025-04-25 10:33:32 -0700
* Ignore case when matching prefix in http analyzer (Kshitiz Bartariya)

15
NEWS
View file

@ -36,6 +36,8 @@ New Functionality
- The bundled version of ZeekJS has been updated to v0.17.0.
- The bundled version of Spicy has been updated to v1.13.0.
- Some DNS events are not raised when ``dns_skip_all_addl`` is set to true. Zeek now
raises a warning when a script declares these events while this option is set to true.
@ -72,6 +74,9 @@ New Functionality
backend for NATS that will be available as an external plugin, but it is not quite
ready yet. Both of the existing backends support usage in a cluster environment.
- The Redis backend requires at least redis-server version 6.2.0 or an equivalent
implementation.
- Improved alternative cluster backend support.
The ZeroMQ cluster backend added in Zeek 7.1 has received various correctness,
@ -106,9 +111,9 @@ New Functionality
metrics are available to understand the health of each peering's buffer,
regardless of the overflow policy active. These are:
- zeek_broker_peer_buffer_levels: a gauge of the current buffer fill level,
- zeek_broker_peer_buffer_messages: a gauge of the current buffer fill level,
- zeek_broker_peer_buffer_recent_max_levels: a gauge that tracks the maximum
- zeek_broker_peer_buffer_recent_max_messages: a gauge that tracks the maximum
buffer fill level seen over the last ``Broker::buffer_stats_reset_interval`.
- zeek_broker_peer_buffer_overflows_total: a counter that tracks the number
@ -256,6 +261,12 @@ Changed Functionality
our switch to use the C-Ares library back in the 5.0 release, but we never removed the
requirement from CMake.
- Publishing remote events with vector arguments that contain holes is now
rejected. The receiver side never had a chance to figure out where these
holes would have been. There's a chance this breaks scripts that accidentally
published vectors with holes. A reporter error is produced at runtime when
serialization of vectors with holes is attempted.
Removed Functionality
---------------------

View file

@ -1 +1 @@
7.2.0-dev.659
7.2.0

@ -1 +1 @@
Subproject commit e15e0bd959a03d06822ae76b53eef6181daf01a2
Subproject commit d594c99e7efcf8d02d8f4f0336c3253585c9f609

2
doc

@ -1 +1 @@
Subproject commit 858dd108b10a7d88852e01dc0134d6c0032f3c60
Subproject commit 9e34f868932eea34f0f2fab245da2d8237808c50

View file

@ -44,26 +44,61 @@ global broker_peer_buffer_overflows_cf = Telemetry::register_counter_family([
$help_text="Number of overflows in Broker's send buffers",
]);
# A helper to track overflow counts over past peerings as well as the current
# one. The peer_id field allows us to identify when the counter has reset: a
# Broker ID different from the one on file means it's a new peering.
type EpochData: record {
peer_id: string;
num_overflows: count &default=0;
num_past_overflows: count &default=0;
};
# This maps from a cluster node name to its EpochData.
global peering_epoch_data: table[string] of EpochData;
hook Telemetry::sync()
{
local peers = Broker::peering_stats();
local nn: NamedNode;
local labels: vector of string;
local ed: EpochData;
for ( peer, stats in peers )
for ( peer_id, stats in peers )
{
# Translate the Broker IDs to Zeek-level node names. We skip
# telemetry for peers where this mapping fails, i.e. ones for
# connections to external systems.
nn = nodeid_to_node(peer);
nn = nodeid_to_node(peer_id);
if ( |nn$name| > 0 )
if ( |nn$name| == 0 )
next;
labels = vector(nn$name);
Telemetry::gauge_family_set(broker_peer_buffer_messages_gf,
labels, stats$num_queued);
Telemetry::gauge_family_set(broker_peer_buffer_recent_max_messages_gf,
labels, stats$max_queued_recently);
if ( nn$name !in peering_epoch_data )
peering_epoch_data[nn$name] = EpochData($peer_id=peer_id);
ed = peering_epoch_data[nn$name];
if ( peer_id != ed$peer_id )
{
Telemetry::gauge_family_set(broker_peer_buffer_messages_gf,
vector(nn$name), stats$num_queued);
Telemetry::gauge_family_set(broker_peer_buffer_recent_max_messages_gf,
vector(nn$name), stats$max_queued_recently);
Telemetry::counter_family_set(broker_peer_buffer_overflows_cf,
vector(nn$name), stats$num_overflows);
# A new peering. Ensure that we account for overflows in
# past ones. There is a risk here that we might have
# missed a peering altogether if we scrape infrequently,
# but re-peering should be a rare event.
ed$peer_id = peer_id;
ed$num_past_overflows += ed$num_overflows;
}
ed$num_overflows = stats$num_overflows;
Telemetry::counter_family_set(broker_peer_buffer_overflows_cf,
labels, ed$num_past_overflows + ed$num_overflows);
}
}

View file

@ -1,9 +1,9 @@
#
# Do not edit this file. This file is automatically generated by gen-ct-list.pl
# File generated at Tue Jul 23 16:04:45 2024
# File generated at Wed Apr 23 09:34:55 2025
# File generated from https://www.gstatic.com/ct/log_list/v3/log_list.json
# Source file generated at: 2024-07-23T13:06:08Z
# Source file version: 39.1
# Source file generated at: 2025-04-23T12:55:20Z
# Source file version: 53.22
#
@load base/protocols/ssl
@ -11,45 +11,39 @@ module SSL;
## @docs-omit-value
redef ct_logs += {
["\xee\xcd\xd0\x64\xd5\xdb\x1a\xce\xc5\x5c\xb7\x9d\xb4\xcd\x13\xa2\x32\x87\x46\x7c\xbc\xec\xde\xc3\x51\x48\x59\x46\x71\x1f\xb5\x9b"] = CTInfo($description="Google 'Argon2024' log", $operator="Google", $url="https://ct.googleapis.com/logs/us1/argon2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x1d\xb9\x6c\xa9\xcb\x69\x94\xc5\x5c\xe6\xb6\xa6\x03\xbb\xd2\xb8\xdc\x54\x43\x17\x28\x99\x0c\x06\x01\x50\x1d\x9d\x64\xc0\x59\x46\x2b\xdc\xc8\x03\x1d\x05\xb4\x2d\xa8\x09\xf7\x99\x41\xed\x04\xfb\xe5\x57\xba\x26\x04\xf6\x11\x52\xce\x14\x65\x3b\x2f\x76\x2b\xc0"),
["\x4e\x75\xa3\x27\x5c\x9a\x10\xc3\x38\x5b\x6c\xd4\xdf\x3f\x52\xeb\x1d\xf0\xe0\x8e\x1b\x8d\x69\xc0\xb1\xfa\x64\xb1\x62\x9a\x39\xdf"] = CTInfo($description="Google 'Argon2025h1' log", $operator="Google", $url="https://ct.googleapis.com/logs/us1/argon2025h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x20\x82\xa1\xf9\x67\x68\xa8\xe4\xdb\x94\x98\xe2\xe1\x68\x87\xe4\x09\x6d\x20\x35\x33\x38\x3c\xaf\x14\xaa\xd7\x08\x18\xf0\xfd\x16\x9b\xd3\xff\x7c\x27\x82\xd4\x87\xb7\x4e\x24\x46\x3b\xfb\xae\xbe\xc8\x23\x52\x20\x2b\xaa\x44\x05\xfe\x54\xf9\xd5\xf1\x1d\x45\x9a"),
["\x12\xf1\x4e\x34\xbd\x53\x72\x4c\x84\x06\x19\xc3\x8f\x3f\x7a\x13\xf8\xe7\xb5\x62\x87\x88\x9c\x6d\x30\x05\x84\xeb\xe5\x86\x26\x3a"] = CTInfo($description="Google 'Argon2025h2' log", $operator="Google", $url="https://ct.googleapis.com/logs/us1/argon2025h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xaf\xe4\xf3\x94\x2c\xdf\xa6\x27\xb5\xfe\xb2\x61\x83\x19\xc8\x21\x3a\x23\xa8\xa9\x3d\x54\xaf\xbc\x31\x9a\x1c\xd3\xc1\xe3\xb6\xc2\xf3\x0f\xc7\xb9\xca\x3b\x1d\x79\x65\x61\x22\x25\x82\x56\x4e\x98\xe8\xaa\x26\x29\x36\x1e\x28\x60\x6f\xeb\x15\x6e\xf7\x7c\xd0\xba"),
["\x0e\x57\x94\xbc\xf3\xae\xa9\x3e\x33\x1b\x2c\x99\x07\xb3\xf7\x90\xdf\x9b\xc2\x3d\x71\x32\x25\xdd\x21\xa9\x25\xac\x61\xc5\x4e\x21"] = CTInfo($description="Google 'Argon2026h1' log", $operator="Google", $url="https://ct.googleapis.com/logs/us1/argon2026h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x07\xfc\x1e\xe8\x63\x8e\xff\x1c\x31\x8a\xfc\xb8\x1e\x19\x2b\x60\x50\x00\x3e\x8e\x9e\xda\x77\x37\xe3\xa5\xa8\xda\x8d\x94\xf8\x6b\xe8\x3d\x64\x8f\x27\x3f\x75\xb3\xfc\x6b\x12\xf0\x37\x06\x4f\x64\x58\x75\x14\x5d\x56\x52\xe6\x6a\x2b\x14\x4c\xec\x81\xd1\xea\x3e"),
["\xd7\x6d\x7d\x10\xd1\xa7\xf5\x77\xc2\xc7\xe9\x5f\xd7\x00\xbf\xf9\x82\xc9\x33\x5a\x65\xe1\xd0\xb3\x01\x73\x17\xc0\xc8\xc5\x69\x77"] = CTInfo($description="Google 'Argon2026h2' log", $operator="Google", $url="https://ct.googleapis.com/logs/us1/argon2026h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2a\x3a\x67\x8b\xfe\xba\x0c\x86\x2b\x4a\x51\x8a\xe9\x17\xfe\x7b\xa1\x76\x73\xfd\xbc\x65\x4b\xc3\x27\xbf\x4d\xf3\x5f\xa0\xca\x29\x80\x11\x20\x32\x78\xd6\x7e\xf9\x34\x60\x8c\x75\xa0\xf5\x35\x50\x9c\xa1\xd3\x49\x4d\x13\xd5\x3b\x6a\x0e\xea\x45\x9d\x24\x13\x22"),
["\x76\xff\x88\x3f\x0a\xb6\xfb\x95\x51\xc2\x61\xcc\xf5\x87\xba\x34\xb4\xa4\xcd\xbb\x29\xdc\x68\x42\x0a\x9f\xe6\x67\x4c\x5a\x3a\x74"] = CTInfo($description="Google 'Xenon2024' log", $operator="Google", $url="https://ct.googleapis.com/logs/eu1/xenon2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xb9\x60\xe0\x34\x1e\x35\xe4\x65\x00\x93\x4f\x90\x09\xbd\x5a\xec\x44\xdd\x8c\x0f\xce\xed\x11\x3e\x2a\x59\x46\x9a\x31\xb6\xc7\x99\xf7\xdc\xef\x3d\xcd\x8f\x86\xc2\x35\xa5\x3e\xdc\x29\xba\xbb\xf2\x54\xe2\xa8\x0c\x83\x08\x51\x06\xde\x21\x6d\x36\x50\x8e\x38\x4d"),
["\xcf\x11\x56\xee\xd5\x2e\x7c\xaf\xf3\x87\x5b\xd9\x69\x2e\x9b\xe9\x1a\x71\x67\x4a\xb0\x17\xec\xac\x01\xd2\x5b\x77\xce\xcc\x3b\x08"] = CTInfo($description="Google 'Xenon2025h1' log", $operator="Google", $url="https://ct.googleapis.com/logs/eu1/xenon2025h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x82\xe2\xce\x90\x40\x3f\x81\x0e\xdf\xea\xe1\x20\x2b\x5e\x2e\x30\x54\x46\x81\xb9\x58\xed\xaf\xbd\xff\x36\xa7\x9e\x0b\x5f\x6a\x6b\x91\xa5\xc1\x98\xe1\xf2\xcd\xeb\x17\x20\x70\xca\x2a\x12\xe6\x54\x78\x50\xdc\xff\x6d\xfd\x1c\xa7\xb6\x3a\x1f\xf9\x26\xa9\x1b\xbd"),
["\xdd\xdc\xca\x34\x95\xd7\xe1\x16\x05\xe7\x95\x32\xfa\xc7\x9f\xf8\x3d\x1c\x50\xdf\xdb\x00\x3a\x14\x12\x76\x0a\x2c\xac\xbb\xc8\x2a"] = CTInfo($description="Google 'Xenon2025h2' log", $operator="Google", $url="https://ct.googleapis.com/logs/eu1/xenon2025h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x6b\xe0\xaf\xed\x06\x7c\x3d\xef\xd9\x0e\xe4\x58\x4b\x04\xd8\x2a\x47\x99\x90\x89\x7a\xb9\x36\xa5\x75\xc8\x04\xb8\xcb\xe2\xaa\x2b\xb5\x68\x9d\x88\x29\xa2\xa5\xcf\xce\x2b\x9a\x15\x9b\xa0\x3e\x9d\x94\x1c\xb2\xb7\x4a\xf2\x51\xec\x40\xed\x62\x47\xa4\x03\x49\x86"),
["\x96\x97\x64\xbf\x55\x58\x97\xad\xf7\x43\x87\x68\x37\x08\x42\x77\xe9\xf0\x3a\xd5\xf6\xa4\xf3\x36\x6e\x46\xa4\x3f\x0f\xca\xa9\xc6"] = CTInfo($description="Google 'Xenon2026h1' log", $operator="Google", $url="https://ct.googleapis.com/logs/eu1/xenon2026h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x3a\x1f\xc8\xbb\xce\xd5\x90\x47\x34\xca\xca\x01\x04\x27\x21\x1c\xe2\x29\x3d\x92\xbb\x91\x45\xc7\x5a\x3e\xa5\xd4\xf2\x12\xe6\xe8\xe6\x43\xba\xf3\x7b\xc2\x38\xaf\xfc\x23\x8a\x05\x56\xeb\x03\x0a\x30\xcc\x63\x6c\xd9\x3c\xbe\xf5\x7b\x94\xba\x94\xd3\xbf\x88\x4c"),
["\xd8\x09\x55\x3b\x94\x4f\x7a\xff\xc8\x16\x19\x6f\x94\x4f\x85\xab\xb0\xf8\xfc\x5e\x87\x55\x26\x0f\x15\xd1\x2e\x72\xbb\x45\x4b\x14"] = CTInfo($description="Google 'Xenon2026h2' log", $operator="Google", $url="https://ct.googleapis.com/logs/eu1/xenon2026h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xe5\x77\x78\x95\x71\x28\xb3\x95\xc9\xa5\xcc\x7a\x4c\xe8\x32\x03\x96\x7b\xfc\x2e\x1d\xb9\xa4\xdb\x43\xa0\xbd\x69\x72\xf9\x45\xba\x9a\xc3\xe9\x96\xd5\x70\xe7\x0d\x7e\xc9\x95\x15\x27\x8a\x72\x30\x65\x86\x43\x53\xdc\x11\x44\x18\x49\x98\x25\x68\xa7\x3c\x05\xbf"),
["\xda\xb6\xbf\x6b\x3f\xb5\xb6\x22\x9f\x9b\xc2\xbb\x5c\x6b\xe8\x70\x91\x71\x6c\xbb\x51\x84\x85\x34\xbd\xa4\x3d\x30\x48\xd7\xfb\xab"] = CTInfo($description="Cloudflare 'Nimbus2024' Log", $operator="Cloudflare", $url="https://ct.cloudflare.com/logs/nimbus2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x77\xb1\x9b\x7b\x8f\xe6\x8b\x35\xfe\x3a\x92\x29\x2d\xac\x8a\x8d\x51\x8a\x25\xfc\x93\xb6\xd7\xa0\x8b\x29\x37\x71\x1d\x33\xca\xcc\x33\xea\x28\xb9\x1f\xe2\xac\xc3\xa9\x5d\xdd\x97\xbe\xf6\x9e\x94\x25\xdd\x36\x81\xd1\xeb\x5d\x29\xc3\x2b\x44\xf1\x5b\xca\x15\x48"),
["\xcc\xfb\x0f\x6a\x85\x71\x09\x65\xfe\x95\x9b\x53\xce\xe9\xb2\x7c\x22\xe9\x85\x5c\x0d\x97\x8d\xb6\xa9\x7e\x54\xc0\xfe\x4c\x0d\xb0"] = CTInfo($description="Cloudflare 'Nimbus2025'", $operator="Cloudflare", $url="https://ct.cloudflare.com/logs/nimbus2025/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x1a\x80\x1a\x15\x19\x19\x23\x79\xb4\xfa\xa0\x79\x8e\x8d\xd5\xc1\xdc\xc2\xb5\x96\x92\x7e\x94\xe0\xc3\x7e\x14\x7c\x0a\x0d\x2d\x46\xa8\x9d\x1b\xb1\x41\x65\x0c\x5f\x98\xc4\x5a\x17\x79\x81\x5b\x4a\x14\x41\xec\xaf\xa9\x5d\x0e\xab\x12\x19\x71\xcd\x43\xef\xbb\x97"),
["\x48\xb0\xe3\x6b\xda\xa6\x47\x34\x0f\xe5\x6a\x02\xfa\x9d\x30\xeb\x1c\x52\x01\xcb\x56\xdd\x2c\x81\xd9\xbb\xbf\xab\x39\xd8\x84\x73"] = CTInfo($description="DigiCert Yeti2024 Log", $operator="DigiCert", $url="https://yeti2024.ct.digicert.com/log/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x57\xb8\xc1\x6f\x30\xa4\x7f\x2e\xe4\xf0\xd0\xd9\x60\x62\x13\x95\xe3\x7a\xe3\x4e\x53\xc3\xb3\xb8\x73\x85\xc1\x18\x0d\x23\x0e\x58\x84\xd2\x78\xef\x9b\xb3\x1e\x2c\x1a\xde\xc1\x8f\x81\x1b\x19\x44\x58\xb7\x00\x77\x60\x20\x1a\x72\xd8\x82\xde\xae\x9e\xb1\xc6\x4b"),
["\xcb\x38\xf7\x15\x89\x7c\x84\xa1\x44\x5f\x5b\xc1\xdd\xfb\xc9\x6e\xf2\x9a\x59\xcd\x47\x0a\x69\x05\x85\xb0\xcb\x14\xc3\x14\x58\xe7"] = CTInfo($description="Cloudflare 'Nimbus2026'", $operator="Cloudflare", $url="https://ct.cloudflare.com/logs/nimbus2026/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd8\x5c\x61\x4f\xac\x6a\xd2\x20\x80\x4e\x8a\x42\xf6\x04\xad\x4b\xd4\xb1\x1c\x79\x8e\x29\x32\xde\x69\x53\x59\xeb\xad\x78\xf3\xc0\x2a\xf2\xd0\x11\x5d\x05\x7e\xeb\xe8\xc1\xd3\xdf\x37\xbf\x91\x64\x46\x6e\x0e\x27\x13\xea\xbb\x6f\x46\x27\x58\x86\xef\x40\x21\xa3"),
["\x7d\x59\x1e\x12\xe1\x78\x2a\x7b\x1c\x61\x67\x7c\x5e\xfd\xf8\xd0\x87\x5c\x14\xa0\x4e\x95\x9e\xb9\x03\x2f\xd9\x0e\x8c\x2e\x79\xb8"] = CTInfo($description="DigiCert Yeti2025 Log", $operator="DigiCert", $url="https://yeti2025.ct.digicert.com/log/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xdf\x95\x00\x5e\x10\xc1\x01\xf7\x37\xe3\x10\x74\xd1\xff\xb2\xca\x90\xed\x32\x99\x5f\x0c\x39\xfe\xa1\xd1\x13\x11\xac\xd1\xb3\x73\x93\x20\xc2\x13\x3c\x4c\xb5\x7a\x52\x86\x86\x3d\xe3\x95\x24\x7c\xd8\x91\x98\x48\x3b\xf0\xf0\xdf\x21\xf1\xb0\x81\x5a\x59\x25\x43"),
["\x73\xd9\x9e\x89\x1b\x4c\x96\x78\xa0\x20\x7d\x47\x9d\xe6\xb2\xc6\x1c\xd0\x51\x5e\x71\x19\x2a\x8c\x6b\x80\x10\x7a\xc1\x77\x72\xb5"] = CTInfo($description="DigiCert Nessie2024 Log", $operator="DigiCert", $url="https://nessie2024.ct.digicert.com/log/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\xfc\xa2\x7b\x36\xbf\x56\x91\xe9\xfe\x3f\xe8\x3d\xfc\xc3\xa7\xe0\x61\x52\xea\x2c\xe9\x05\xa3\x9f\x27\x17\x81\x05\x70\x6b\x81\x61\x44\x8a\xf8\x3b\x10\x80\x42\xed\x03\x2f\x00\x50\x21\xfc\x41\x54\x84\xa3\x54\xd5\x2e\xb2\x7a\x16\x4b\x2a\x1f\x2b\x66\x04\x2b"),
["\xe6\xd2\x31\x63\x40\x77\x8c\xc1\x10\x41\x06\xd7\x71\xb9\xce\xc1\xd2\x40\xf6\x96\x84\x86\xfb\xba\x87\x32\x1d\xfd\x1e\x37\x8e\x50"] = CTInfo($description="DigiCert Nessie2025 Log", $operator="DigiCert", $url="https://nessie2025.ct.digicert.com/log/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xf2\xf0\xf0\xa7\x8b\x81\x2e\x09\x39\x3b\x9f\x42\xda\x38\x44\x5f\xb4\xcc\xed\x36\xbb\xd8\x43\x7f\x16\x49\x57\x87\x04\x7f\xa5\x01\x34\xf7\xe8\x68\x3f\xb7\x78\x1f\x60\x66\x2d\x67\x9a\x75\x80\xb7\x53\xa7\x85\xd5\xbc\xab\x47\x06\x55\xdb\xb5\xdf\x88\xa1\x6f\x38"),
["\xb6\x9d\xdc\xbc\x3c\x1a\xbd\xef\x6f\x9f\xd6\x0c\x88\xb1\x06\x7b\x77\xf0\x82\x68\x8b\x2d\x78\x65\xd0\x4b\x39\xab\xe9\x27\xa5\x75"] = CTInfo($description="DigiCert 'Wyvern2024h1' Log", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2024h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x68\xa6\x79\x14\xd1\x58\xe7\xab\xaa\x29\x69\x7f\x60\xed\x68\xe8\x10\xf6\x07\x84\xc0\xfb\x59\x04\x5a\x09\xc9\x1d\xe1\x4b\xfb\xcd\xdc\x03\xf3\xa8\x2a\x46\xb9\x84\x4d\x69\x30\xec\x23\x35\xc1\x8e\xfc\x9f\xb4\x20\x24\xd7\x15\xac\x87\xf7\x1e\xc1\x0b\x3c\x76\x1a"),
["\x0c\x2a\xef\x2c\x4a\x5b\x98\x83\xd4\xdd\xa3\x82\xfe\x50\xfb\x51\x88\xb3\xe9\x73\x33\xa1\xec\x53\xa0\x9d\xc9\xa7\x9d\x0d\x08\x20"] = CTInfo($description="DigiCert 'Wyvern2024h2' Log", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2024h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa8\x73\x12\x9c\x54\xd0\x7a\x7d\xc5\xb5\x17\x2b\x71\x52\x89\x04\x90\xbb\x42\xf1\x9d\xf8\x1c\xde\x4c\xcf\x82\x3c\xbd\x37\x1b\x74\x4c\x3c\xc7\xa3\x13\x87\x01\x51\x13\x14\xda\xa2\x12\x98\x84\xce\x1c\xbe\xcf\x4f\x7a\xef\x15\xfa\xd0\xee\xed\xed\x07\xad\x71\x6d"),
["\x73\x20\x22\x0f\x08\x16\x8a\xf9\xf3\xc4\xa6\x8b\x0a\xb2\x6a\x9a\x4a\x00\xee\xf5\x77\x85\x8a\x08\x4d\x05\x00\xd4\xa5\x42\x44\x59"] = CTInfo($description="DigiCert 'Wyvern2025h1' Log", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2025h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa7\xcb\x80\x61\x86\x1b\x1f\xb5\xab\x2b\x20\x76\x59\x83\x66\x0e\xce\xae\xb8\x6f\x3b\x88\x02\xeb\x43\xf4\x87\x90\xcb\x8b\xda\xac\x0e\x19\x50\xe0\xf9\x24\x0e\xab\x26\x93\x8c\x3f\x9e\x0d\x96\x58\x44\x9d\x3b\x8a\x80\xc5\xc8\xbe\xe1\x89\x46\x6b\x48\x4c\xd6\x09"),
["\xed\x3c\x4b\xd6\xe8\x06\xc2\xa4\xa2\x00\x57\xdb\xcb\x24\xe2\x38\x01\xdf\x51\x2f\xed\xc4\x86\xc5\x70\x0f\x20\xdd\xb7\x3e\x3f\xe0"] = CTInfo($description="DigiCert 'Wyvern2025h2' Log", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2025h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xe0\xdb\x41\xef\xe4\x04\xbd\xcb\x6b\x2e\x4c\xcc\xf1\x6c\xde\x41\x58\x7f\xfe\x94\xf6\x7a\xf6\x60\xed\x8b\x76\x72\xa3\xa2\x1c\x31\x13\x32\x35\xa1\xf2\x08\xd2\x68\xc5\x34\xa7\x56\x08\x1c\x63\xde\x95\xe2\x81\x69\x97\x8d\x1e\xa8\xb7\x66\x51\x25\x75\x4d\x78\x2e"),
["\xdb\x07\x6c\xde\x6a\x8b\x78\xec\x58\xd6\x05\x64\x96\xeb\x6a\x26\xa8\xc5\x9e\x72\x12\x93\xe8\xac\x03\x27\xdd\xde\x89\xdb\x5a\x2a"] = CTInfo($description="DigiCert 'Sphinx2024h1' Log", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2024h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xc6\xe4\x29\x69\x98\xfe\x28\x92\x57\x12\x4d\x9e\xed\x0e\xe7\x32\xa2\xe6\x9c\x27\x78\xa4\x29\x7c\x99\xd5\xdb\xfa\x22\xc1\xdd\x5e\xa7\xf4\xd8\xea\xc8\xd7\x44\x8d\xe0\xf1\x8c\x0a\x01\x1d\xd8\x22\xa8\xd3\xeb\xc9\x22\x8e\x36\xfb\x4a\xb1\x70\x9c\x5d\xc1\xe8\x33"),
["\xdc\xc9\x5e\x6f\xa2\x99\xb9\xb0\xfd\xbd\x6c\xa6\xa3\x6e\x1d\x72\xc4\x21\x2f\xdd\x1e\x0f\x47\x55\x3a\x36\xd6\xcf\x1a\xd1\x1d\x8d"] = CTInfo($description="DigiCert 'Sphinx2024h2' Log", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2024h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xdb\x09\x41\x84\xe7\xd1\xf1\x5b\x25\x09\x7b\xe8\xc6\x98\x51\x5e\x29\x85\xfd\x81\xde\x89\xd7\xd0\x86\xa4\xb0\xe5\x15\xec\x5d\x7b\x17\x55\x5f\xc9\x79\x8d\xe4\x22\x36\xe7\xe9\xbf\x38\x3f\xd1\xe9\xd4\x09\x84\x81\xbe\xb6\xc1\xed\x1b\x17\xea\x26\x97\xba\xe9\x9a"),
["\x64\x11\xc4\x6c\xa4\x12\xec\xa7\x89\x1c\xa2\x02\x2e\x00\xbc\xab\x4f\x28\x07\xd4\x1e\x35\x27\xab\xea\xfe\xd5\x03\xc9\x7d\xcd\xf0"] = CTInfo($description="DigiCert 'Wyvern2026h1'", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2026h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xec\xbc\x34\x39\xe2\x9a\x8d\xb7\x99\x7a\x91\xf1\x05\x72\x52\xda\x93\x89\x5d\x3a\x07\x8b\x99\xed\x80\xa5\x16\xda\x73\x21\x20\xeb\x86\x96\x87\xc5\xc6\xd9\x17\xba\x6e\xb9\x4c\x13\x58\xd5\xd1\x83\xf8\x7a\xdf\x1e\x07\xbc\x15\xcd\xc0\x4a\xcd\x2a\x31\x71\x07\x55"),
["\xc2\x31\x7e\x57\x45\x19\xa3\x45\xee\x7f\x38\xde\xb2\x90\x41\xeb\xc7\xc2\x21\x5a\x22\xbf\x7f\xd5\xb5\xad\x76\x9a\xd9\x0e\x52\xcd"] = CTInfo($description="DigiCert 'Wyvern2026h2'", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2026h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7a\x73\xdb\x4a\xf2\xde\x4f\xec\xe1\x14\x1b\xbe\xa6\xa9\x3c\x21\xb8\x45\x12\xcd\x7a\x88\x26\x91\x20\x56\xf5\x49\x32\xc3\x75\x6c\xcb\xe9\x7c\x13\x75\x35\x9c\x6c\xec\xf1\x31\x3c\xc1\xde\x9b\x8c\x13\x92\xb7\xad\x3d\x0f\xa1\x9c\x8f\x48\xce\x74\x27\x18\x23\x99"),
["\xde\x85\x81\xd7\x50\x24\x7c\x6b\xcd\xcb\xaf\x56\x37\xc5\xe7\x81\xc6\x4c\xe4\x6e\xd6\x17\x63\x9f\x8f\x34\xa7\x26\xc9\xe2\xbd\x37"] = CTInfo($description="DigiCert 'Sphinx2025h1' Log", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2025h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xe3\x2f\x1f\x4d\x89\x05\x75\x29\x78\xbb\x22\x3d\x07\x62\x51\x14\x70\x94\xe7\x3c\xea\xf5\xee\xae\xa6\x48\x9a\x86\x52\x4e\x9e\x5c\xe3\x95\x97\x28\xbb\x52\x4b\x2a\xfd\xc8\xc9\x89\x4e\x45\x31\x17\xd3\x8d\xf2\xe7\xce\x18\x11\x58\x98\x2c\x60\x6f\x58\x20\x36\x6e"),
["\xa4\x42\xc5\x06\x49\x60\x61\x54\x8f\x0f\xd4\xea\x9c\xfb\x7a\x2d\x26\x45\x4d\x87\xa9\x7f\x2f\xdf\x45\x59\xf6\x27\x4f\x3a\x84\x54"] = CTInfo($description="DigiCert 'Sphinx2025h2' Log", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2025h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x41\x8c\x50\x13\x54\xb1\x19\x05\xb7\x7f\x4a\x20\x6e\xa3\x75\x63\xca\x34\xf4\xcc\x74\xea\x32\x3b\xb6\x8b\x03\x14\xa8\x52\x7f\x32\x87\x5e\x59\x9e\x0f\xab\x18\x9e\x29\x6c\xb5\x72\x77\x1a\x27\x54\x85\x5d\xc1\x7b\x24\xa8\x34\xe3\xcd\x88\xce\xd4\x50\x1b\xbe\x69"),
["\x49\x9c\x9b\x69\xde\x1d\x7c\xec\xfc\x36\xde\xcd\x87\x64\xa6\xb8\x5b\xaf\x0a\x87\x80\x19\xd1\x55\x52\xfb\xe9\xeb\x29\xdd\xf8\xc3"] = CTInfo($description="DigiCert 'Sphinx2026h1'", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2026h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xab\x84\xbe\xf8\x3c\x87\xa2\x42\x25\x9a\x66\x9c\xae\x2b\x52\xe7\x5a\xf9\x21\x1b\x19\x03\xa5\x07\xe2\x46\x0b\x1f\x8a\x5e\x7c\x6c\xae\xff\x19\x77\x86\xe8\x7b\xfc\xee\x6b\x36\x4f\xf2\xbc\xc3\x9e\x05\x02\x9a\x08\x01\xb5\x49\x23\x35\xc4\xd3\x50\x2b\x51\xe9\xf4"),
["\x94\x4e\x43\x87\xfa\xec\xc1\xef\x81\xf3\x19\x24\x26\xa8\x18\x65\x01\xc7\xd3\x5f\x38\x02\x01\x3f\x72\x67\x7d\x55\x37\x2e\x19\xd8"] = CTInfo($description="DigiCert 'Sphinx2026h2'", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2026h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xaa\xe0\xf4\x26\x44\x50\x4f\xfd\xa2\x9e\xe6\x80\xe0\x70\xb5\xb1\xce\x94\xa5\xf8\x97\x81\x44\x55\x42\x64\x1c\x22\x79\xa7\x64\x59\xd3\x89\x93\x21\x66\xfb\x09\x81\x60\x1f\x62\x55\x34\x38\x8c\xa4\x38\x2e\xac\x95\x0c\xeb\xed\x4f\x64\xbc\x45\x42\xf7\x06\x7a\xcd"),
["\x55\x81\xd4\xc2\x16\x90\x36\x01\x4a\xea\x0b\x9b\x57\x3c\x53\xf0\xc0\xe4\x38\x78\x70\x25\x08\x17\x2f\xa3\xaa\x1d\x07\x13\xd3\x0c"] = CTInfo($description="Sectigo 'Sabre' CT log", $operator="Sectigo", $url="https://sabre.ct.comodo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xf2\x6f\xd2\x89\x0f\x3f\xc5\xf8\x87\x1e\xab\x65\xb3\xd9\xbb\x17\x23\x8c\x06\x0e\x09\x55\x96\x3d\x0a\x08\xa2\xc5\x71\xb3\xd1\xa9\x2f\x28\x3e\x83\x10\xbf\x12\xd0\x44\x66\x15\xef\x54\xe1\x98\x80\xd0\xce\x24\x6d\x3e\x67\x9a\xe9\x37\x23\xce\x52\x93\x86\xda\x80"),
["\xa2\xe2\xbf\xd6\x1e\xde\x2f\x2f\x07\xa0\xd6\x4e\x6d\x37\xa7\xdc\x65\x43\xb0\xc6\xb5\x2e\xa2\xda\xb7\x8a\xf8\x9a\x6d\xf5\x17\xd8"] = CTInfo($description="Sectigo 'Sabre2024h1'", $operator="Sectigo", $url="https://sabre2024h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2c\x01\xf6\xce\x31\xbc\xaa\x14\x61\x51\xfe\x6b\x7a\x87\xae\xa6\xd3\x9b\xc7\x87\x2d\x0a\x5a\xc8\x4f\xb5\x54\xdc\xc9\x93\xa0\x00\xee\xca\x1c\xb9\xa7\xb6\x7b\x47\x3b\xe5\x4f\xaa\x6c\x16\x1c\x70\x2e\xc8\xec\x53\x5a\x4c\x21\x4c\x7e\x27\x0b\x13\x14\x5e\xfc\x85"),
["\x19\x98\x10\x71\x09\xf0\xd6\x52\x2e\x30\x80\xd2\x9e\x3f\x64\xbb\x83\x6e\x28\xcc\xf9\x0f\x52\x8e\xee\xdf\xce\x4a\x3f\x16\xb4\xca"] = CTInfo($description="Sectigo 'Sabre2024h2'", $operator="Sectigo", $url="https://sabre2024h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7a\x10\x4c\x8a\xe7\x22\x7b\x6d\x2a\xba\x8e\xfa\x6b\x4a\x81\xd5\x85\xae\x03\xef\xff\x4b\xfc\x4d\x53\x3d\xb7\x8c\xbb\x75\x09\xc9\xea\x16\x7e\xc1\x77\x16\xd2\xc2\x45\x74\x6d\x8d\xc4\xe1\x88\x37\xdf\xd4\xf3\x60\x65\xfc\xa0\x75\xf0\x20\x66\x8e\x4a\xcc\x19\xda"),
["\xe0\x92\xb3\xfc\x0c\x1d\xc8\xe7\x68\x36\x1f\xde\x61\xb9\x96\x4d\x0a\x52\x78\x19\x8a\x72\xd6\x72\xc4\xb0\x4d\xa5\x6d\x6f\x54\x04"] = CTInfo($description="Sectigo 'Sabre2025h1'", $operator="Sectigo", $url="https://sabre2025h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7e\x2f\x39\xf1\xe8\x23\x8e\xb3\x32\x04\xaf\x4d\x57\xf6\xdb\xc5\x74\xa4\x7a\x6d\x3b\x07\x51\x0c\x5a\xfb\x80\x30\x05\xc6\x5a\x0c\xc4\x76\xd6\x06\xa8\x57\x4d\xfb\xdf\xe4\x82\x90\xc2\x41\xae\x70\xb3\x31\xa2\xe3\xfa\x3d\x5f\x2c\x5d\x04\xcd\xb4\x9d\x55\xab\x41"),
["\x1a\x04\xff\x49\xd0\x54\x1d\x40\xaf\xf6\xa0\xc3\xbf\xf1\xd8\xc4\x67\x2f\x4e\xec\xee\x23\x40\x68\x98\x6b\x17\x40\x2e\xdc\x89\x7d"] = CTInfo($description="Sectigo 'Sabre2025h2'", $operator="Sectigo", $url="https://sabre2025h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x85\x13\x11\x2d\x7b\xf3\x93\x81\xe4\xb9\x7c\xd9\x64\x3b\xe7\xb5\x83\x99\x66\x79\x59\x47\x6a\x42\x5e\xd6\xbd\x63\x2e\xb7\x91\x4b\xae\xbc\x56\xc4\xc5\x6e\x09\xa0\xd7\x64\x1a\xc8\xc1\xaf\x89\x8b\xf5\x58\xd8\xba\xeb\x7b\x83\x52\xe9\xf4\xe0\xa5\xcd\xcd\x92\xcc"),
["\x29\xd0\x3a\x1b\xb6\x74\xaa\x71\x1c\xd3\x03\x5b\x65\x57\xc1\x4f\x8a\xa7\x8b\x4f\xe8\x38\x94\x49\xec\xa4\x53\xf9\x44\xbd\x24\x68"] = CTInfo($description="Sectigo 'Mammoth2024h1'", $operator="Sectigo", $url="https://mammoth2024h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa4\x59\x90\xf3\x71\x24\x24\xf7\xc3\x55\x27\x56\x9c\xa3\x59\x1e\xf7\xb7\x9f\xce\xab\x4e\x19\x66\x4d\xd0\x8a\xfa\x9d\x62\xa4\x24\xf0\x3b\x20\xe4\x1d\x14\x67\xc8\xfc\xe4\x37\xf2\x4b\x38\x54\x5a\xcf\x9f\x6b\x07\x90\xd0\x0e\x7e\x3d\x4c\x87\xb2\xe8\x3f\x07\xcc"),
["\x50\x85\x01\x58\xdc\xb6\x05\x95\xc0\x0e\x92\xa8\x11\x02\xec\xcd\xfe\x3f\x6b\x78\x58\x42\x9f\x57\x98\x35\x38\xc9\xda\x52\x50\x63"] = CTInfo($description="Sectigo 'Mammoth2024h1b'", $operator="Sectigo", $url="https://mammoth2024h1b.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa3\xd5\x07\x28\x7a\x04\x34\xae\xca\xbe\x80\x79\x4f\x3e\xf6\x41\xf4\x24\x04\xe1\xd6\x36\x5a\x1a\x09\xf2\xd1\xba\x84\x17\xae\x1e\xa1\x7c\x00\x1d\x54\x73\x90\x75\x21\xa8\xd1\xda\x5e\x10\xe1\x8c\xec\xb2\x8a\x8c\xc8\xe7\xdd\xcd\xe2\x07\xf0\x4e\x16\x02\x57\x37"),
["\xdf\xe1\x56\xeb\xaa\x05\xaf\xb5\x9c\x0f\x86\x71\x8d\xa8\xc0\x32\x4e\xae\x56\xd9\x6e\xa7\xf5\xa5\x6a\x01\xd1\xc1\x3b\xbe\x52\x5c"] = CTInfo($description="Sectigo 'Mammoth2024h2'", $operator="Sectigo", $url="https://mammoth2024h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x85\x66\x22\x24\x6e\xbe\x52\x62\x0a\xa0\xaf\xc3\x25\x1a\x36\x2e\xa7\x60\x89\xa2\x65\xbf\xa4\x5f\xbd\x85\x6a\x94\x05\x81\x35\x90\x54\x31\x95\xe7\x11\x9e\xa3\x2e\x0f\x85\xef\xa7\x88\x57\x8b\x63\x1a\x81\xc1\x41\x9d\x7d\xec\x01\x3a\xdb\xb9\xc1\x27\xf4\x65\x1e"),
["\x13\x4a\xdf\x1a\xb5\x98\x42\x09\x78\x0c\x6f\xef\x4c\x7a\x91\xa4\x16\xb7\x23\x49\xce\x58\x57\x6a\xdf\xae\xda\xa7\xc2\xab\xe0\x22"] = CTInfo($description="Sectigo 'Mammoth2025h1'", $operator="Sectigo", $url="https://mammoth2025h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x13\x3c\x41\xb5\x30\x7d\x2e\x4a\xa1\xa8\x6b\xd2\xc5\x57\x6b\x98\xfe\x7e\xef\xd5\x21\xe2\xba\x5d\xb0\xba\x85\x11\x6e\x94\xe0\x3d\xa8\x8e\x6d\x56\x8d\x44\x02\x9e\xb0\x83\xcc\x54\xdf\x9b\x4e\x72\x62\x4b\x3c\x0c\x32\xdd\x86\xfb\xeb\x3e\x66\xcd\x77\x58\x5b\xe5"),
["\xaf\x18\x1a\x28\xd6\x8c\xa3\xe0\xa9\x8a\x4c\x9c\x67\xab\x09\xf8\xbb\xbc\x22\xba\xae\xbc\xb1\x38\xa3\xa1\x9d\xd3\xf9\xb6\x03\x0d"] = CTInfo($description="Sectigo 'Mammoth2025h2'", $operator="Sectigo", $url="https://mammoth2025h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x88\xe2\xc7\xb3\xd7\x37\xa3\x91\xd7\xb3\xc5\xda\x07\x51\x04\x2b\x81\xed\xc2\x44\x3b\x75\xa0\xe6\x65\xe1\x4a\xba\x1b\xb1\x9c\xa9\x2a\x84\x31\x29\xae\x1d\x8b\xf1\x33\x9f\x12\x2e\x90\xb1\x15\x67\x66\xa0\x7c\x0b\x5b\x62\x7f\x6c\x9a\x6a\x30\x9b\x68\x02\x16\x6f"),
["\x3b\x53\x77\x75\x3e\x2d\xb9\x80\x4e\x8b\x30\x5b\x06\xfe\x40\x3b\x67\xd8\x4f\xc3\xf4\xc7\xbd\x00\x0d\x2d\x72\x6f\xe1\xfa\xd4\x17"] = CTInfo($description="Let's Encrypt 'Oak2024H1' log", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2024h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x56\x43\xd7\x7e\x7b\xd4\x72\xb7\xba\xa9\x51\xbd\x36\x93\xb7\xe9\xb5\x92\x0f\xea\x5e\xb7\x45\xa3\x92\xfd\xc9\xa5\x3c\x80\xac\x1a\x20\xef\x25\x2f\xb8\xe1\x20\xf7\xa8\x3a\x2e\x07\x8d\xe6\xeb\xa4\xe2\x7d\x24\x63\x9f\x46\xbf\x94\x73\x52\x8d\x96\xae\xa9\x26\xfd"),
["\x3f\x17\x4b\x4f\xd7\x22\x47\x58\x94\x1d\x65\x1c\x84\xbe\x0d\x12\xed\x90\x37\x7f\x1f\x85\x6a\xeb\xc1\xbf\x28\x85\xec\xf8\x64\x6e"] = CTInfo($description="Let's Encrypt 'Oak2024H2' log", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2024h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd7\x73\xd6\x53\x47\xe9\xf3\xc9\xd5\x7c\x16\xc2\xd6\x8f\x70\x65\xfa\xf2\x51\x36\xa9\x13\x80\x2f\xed\xf9\x94\xd3\x5a\x8b\xe8\x4f\x33\xcf\xc3\xd3\x89\xd4\x5f\x5a\x66\x89\xba\x20\x1f\x71\xcb\xca\xbb\x9f\x9f\xf3\x5c\x2d\x1e\xa3\x81\x59\xaf\x92\xb3\x6d\x30\x68"),
["\x25\x2f\x94\xc2\x2b\x29\xe9\x6e\x9f\x41\x1a\x72\x07\x2b\x69\x5c\x5b\x52\xff\x97\xa9\x0d\x25\x40\xbb\xfc\xdc\x51\xec\x4d\xee\x0b"] = CTInfo($description="Sectigo 'Mammoth2026h1'", $operator="Sectigo", $url="https://mammoth2026h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x9e\xcb\x0c\x8a\x51\xcc\x8a\xe2\x0b\xce\x85\xe6\xaf\x4d\x31\xdb\x1b\x6a\x4c\xfd\xb0\x79\x6b\x99\x97\xc0\x5d\xfb\x6e\x45\x50\x1d\x62\xaa\xc6\x9f\x9b\x6b\x05\x3d\xa2\xab\x2b\x5d\x88\x9b\x50\x28\xe2\x9e\x58\xa5\xa5\xfa\xf9\xe3\xfa\x15\x25\xe3\x14\x13\x32\xc4"),
["\x94\xb1\xc1\x8a\xb0\xd0\x57\xc4\x7b\xe0\xac\x04\x0e\x1f\x2c\xbc\x8d\xc3\x75\x72\x7b\xc9\x51\xf2\x0a\x52\x61\x26\x86\x3b\xa7\x3c"] = CTInfo($description="Sectigo 'Mammoth2026h2'", $operator="Sectigo", $url="https://mammoth2026h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xec\x83\x61\xf2\xd7\xb4\xbb\xe4\xe4\x3b\xeb\xc8\x63\x75\x98\xcf\x61\x90\x63\x14\x3d\x5f\x22\xdf\x74\xba\x50\xa7\x58\x9b\x69\x7d\xe6\x63\x89\x6d\xd9\xd7\x51\x84\x3f\xf8\x02\xd8\xc8\xff\xc2\x97\x71\xe5\x7e\x27\xf5\x72\xb1\x8f\x24\x27\x57\x0a\x0d\x74\xc0\xb6"),
["\x56\x6c\xd5\xa3\x76\xbe\x83\xdf\xe3\x42\xb6\x75\xc4\x9c\x23\x24\x98\xa7\x69\xba\xc3\x82\xcb\xab\x49\xa3\x87\x7d\x9a\xb3\x2d\x01"] = CTInfo($description="Sectigo 'Sabre2026h1'", $operator="Sectigo", $url="https://sabre2026h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x84\x26\xbc\x36\xbd\xd8\x8d\x3c\x87\x9e\xe0\x10\xaf\xcd\x94\xd9\xd7\xb9\x51\x80\x34\x7e\xf7\x58\x5c\x73\xea\xeb\x09\x93\xb8\x10\x7b\x90\x9c\x7d\xc7\xcd\x96\x43\xed\x53\x6e\x95\x21\x46\x67\x51\xf0\xde\xb6\xc9\x9e\xaa\xe2\x80\x6d\xce\x25\x81\x34\xd7\x6a\x60"),
["\x1f\x56\xd1\xab\x94\x70\x4a\x41\xdd\x3f\xea\xfd\xf4\x69\x93\x55\x30\x2c\x14\x31\xbf\xe6\x13\x46\x08\x9f\xff\xae\x79\x5d\xcc\x2f"] = CTInfo($description="Sectigo 'Sabre2026h2'", $operator="Sectigo", $url="https://sabre2026h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xce\x35\xca\xec\x39\x07\x82\xda\x77\x27\x86\xe4\xf2\x7e\xc5\xdc\x38\xf2\x9b\xa9\xab\x8c\xa7\xc0\xed\x83\x1e\x3e\x6a\x1b\xc0\xf0\x95\x56\xba\x32\x33\x4c\x75\x7c\x09\x07\xe9\xe1\x3e\x65\x35\x63\xf0\x49\xbe\x72\xd1\xaa\x9d\xaf\x7d\x08\xc4\xb4\x8d\x59\x3d\x73"),
["\xa2\xe3\x0a\xe4\x45\xef\xbd\xad\x9b\x7e\x38\xed\x47\x67\x77\x53\xd7\x82\x5b\x84\x94\xd7\x2b\x5e\x1b\x2c\xc4\xb9\x50\xa4\x47\xe7"] = CTInfo($description="Let's Encrypt 'Oak2025h1'", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2025h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x29\xe0\x69\x53\xd7\xa3\x9c\x26\x88\x65\xe5\xf7\xf4\x4b\x1d\x17\x9b\xc3\xbd\xff\x04\x2d\x31\xdd\x2c\xfc\x62\x92\x5e\x32\xe0\x48\x91\x38\x84\x1f\x4b\x87\xab\x72\x99\xcc\x1d\xf8\x7c\xf9\x3c\x58\x54\x5b\x37\x10\xb1\xab\xd8\x83\xfb\x84\xf1\x95\x3f\x2e\x2f\x1c"),
["\x0d\xe1\xf2\x30\x2b\xd3\x0d\xc1\x40\x62\x12\x09\xea\x55\x2e\xfc\x47\x74\x7c\xb1\xd7\xe9\x30\xef\x0e\x42\x1e\xb4\x7e\x4e\xaa\x34"] = CTInfo($description="Let's Encrypt 'Oak2025h2'", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2025h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xb5\x76\x30\x07\xad\xc6\xc8\xd2\xe4\x4b\xd2\xf5\xbe\xa2\x8d\x9c\xfd\x74\xfa\x3a\xd6\xfa\x59\x5d\xb6\x1c\x60\xd3\xdd\x1f\x63\x87\x86\xe3\x45\xe0\xd5\x1b\xc0\x35\x6a\xab\x27\x91\x95\xc9\xd7\x3d\xbb\xc1\xf7\x71\x86\x69\xf4\xb3\x5f\x90\x09\xaa\xae\xbd\x8d\xa9"),
["\x87\x4f\xb5\x0d\xc0\x29\xd9\x93\x1d\xe5\x73\xe9\xf2\x89\x9e\x8e\x45\x33\xb3\x92\xd3\x8b\x0a\x46\x25\x74\xbf\x0f\xee\xb2\xfc\x1e"] = CTInfo($description="Trust Asia Log2024-2", $operator="TrustAsia", $url="https://ct2024.trustasia.com/log2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa7\x64\xe2\x79\x81\x3f\x61\xd7\xec\xc6\xf8\x65\x28\x1d\xa0\xb4\x66\x33\xc3\x25\xd5\x0a\x95\x78\x9c\x8f\xfe\xa4\x2a\xd8\x8f\x7e\x72\xe0\xfe\xa8\x7f\xf8\xb1\x2d\x85\xc0\x8e\x12\x74\x0d\x2f\x8c\xab\xd7\x7f\x7a\x1e\xd9\x84\x33\x39\xe8\xfd\x89\x5f\x96\x48\x08"),
["\x19\x86\xd4\xc7\x28\xaa\x6f\xfe\xba\x03\x6f\x78\x2a\x4d\x01\x91\xaa\xce\x2d\x72\x31\x0f\xae\xce\x5d\x70\x41\x2d\x25\x4c\xc7\xd4"] = CTInfo($description="Let's Encrypt 'Oak2026h1'", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2026h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x99\xd4\x61\x70\x22\xfa\x77\x93\x0d\xb3\xc7\x80\x96\x22\x51\xbf\x25\x79\xb1\x01\x42\xe9\x41\x7b\x8b\x0c\xc7\xb2\x65\x5a\x89\xf4\xfa\xe2\x02\x46\xd4\x8a\xc7\xcc\x10\x07\x11\x27\x45\x48\x90\x23\x40\xde\x7a\x4d\x89\x32\xfb\xd7\x0a\xeb\x5e\x8c\xa2\xf1\xf6\x49"),
["\xac\xab\x30\x70\x6c\xeb\xec\x84\x31\xf4\x13\xd2\xf4\x91\x5f\x11\x1e\x42\x24\x43\xb1\xf2\xa6\x8c\x4f\x3c\x2b\x3b\xa7\x1e\x02\xc3"] = CTInfo($description="Let's Encrypt 'Oak2026h2'", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2026h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x6a\x70\x9d\xb3\x96\xe3\xec\x85\x36\x95\xc3\x4f\x9c\x8b\xd9\x7c\xc9\xd5\x91\x29\xae\xeb\xd6\x87\xdc\x04\xbc\x3b\xf6\x34\x0f\xf6\xdb\x08\xf7\x52\xa9\x88\xef\xbb\x3f\x59\xd6\xd4\xf6\xf4\xfc\x5c\xa9\x8c\x5f\xfb\x0d\x60\xe4\x2c\x0f\x16\xec\x2a\xb2\x6d\xeb\x15"),
["\x28\xe2\x81\x38\xfd\x83\x21\x45\xe9\xa9\xd6\xaa\x75\x37\x6d\x83\x77\xa8\x85\x12\xb3\xc0\x7f\x72\x41\x48\x21\xdc\xbd\xe9\x8c\x66"] = CTInfo($description="TrustAsia Log2025a", $operator="TrustAsia", $url="https://ct2025-a.trustasia.com/log2025a/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x70\xe5\xb1\xa4\x09\x79\x2b\x9d\xf8\xa3\xa0\xdf\x18\xef\x95\x5d\x03\x6c\x7b\xa1\x91\xa9\xb8\x80\x7d\xec\x5c\x02\x08\xe2\x6e\x2f\x7c\x32\x70\xbd\x96\x84\x5f\xa6\x62\xe9\x65\xb5\x7c\x90\x58\xba\x22\xd5\xf9\xf5\x69\x54\xb7\xa8\x94\x4e\x32\x09\xae\x26\x11\x4d"),
["\x28\x2c\x8b\xdd\x81\x0f\xf9\x09\x12\x0a\xce\x16\xd6\xe0\xec\x20\x1b\xea\x82\xa3\xa4\xaf\x19\xd9\xef\xfb\x59\xe8\x3f\xdc\x42\x68"] = CTInfo($description="TrustAsia Log2025b", $operator="TrustAsia", $url="https://ct2025-b.trustasia.com/log2025b/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xaa\xa0\x8b\xdb\x67\x14\x5d\x97\x89\x1d\x08\x8d\x06\xd7\xc1\x94\x8e\xb0\xfa\x4c\x46\xd5\x53\x08\x78\x2b\x04\x53\x6c\xf3\xde\xb1\xd1\x53\x40\xda\x90\x57\xe6\x1a\x9e\x3c\xc7\x03\xb8\xbd\x2f\xa9\xcf\xe8\x7b\x5e\xe1\x4b\x60\xe5\x38\x43\x60\x97\xc1\x5b\x2f\x65"),
["\x74\xdb\x9d\x58\xf7\xd4\x7e\x9d\xfd\x78\x7a\x16\x2a\x99\x1c\x18\xcf\x69\x8d\xa7\xc7\x29\x91\x8c\x9a\x18\xb0\x45\x0d\xba\x44\xbc"] = CTInfo($description="TrustAsia 'log2026a'", $operator="TrustAsia", $url="https://ct2026-a.trustasia.com/log2026a/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa7\x4e\x7a\xc9\xa6\x07\xf9\xff\x74\xec\x98\xcb\x49\xe1\x00\x24\xb3\x59\x2e\x83\xfd\xc0\x70\x35\x33\x4c\x63\xca\x74\x83\xc0\x3c\x5b\x53\x40\x7c\x31\x1f\x35\xa4\x5f\x0f\xe4\xee\x4f\x89\x17\xe8\x5b\x2e\xc5\xac\x00\x05\xc9\x76\x37\x45\x97\x03\x15\xff\x60\x59"),

File diff suppressed because one or more lines are too long

View file

@ -21,7 +21,7 @@ hook Analyzer::disabling_analyzer(c: connection, atype: AllAnalyzers::Tag, aid:
return;
# Only add if previously confirmed
if ( Analyzer::name(atype) !in c$service || Analyzer::name(atype) !in c$service_violation )
if ( Analyzer::name(atype) !in c$service && Analyzer::name(atype) !in c$service_violation )
return;
# Only log if dpd.zeek will disable

View file

@ -17,27 +17,104 @@ public function decrypt_crypto_payload(
): bytes &cxxname="QUIC_decrypt_crypto_payload";
##############
## Context - tracked in one connection
##############
# Can we decrypt?
function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, is_client: bool): bool {
function can_decrypt(long_header: LongHeaderPacket, context: Context, crypto: CryptoSinkUnit&): bool {
if ( ! long_header.is_initial )
return False;
if ( is_client )
return ! context.client_initial_processed;
if ( crypto == Null )
return False;
# This is the responder, can only decrypt if we have an initial
# destination_id from the client
return context.client_initial_processed
&& |context.initial_destination_conn_id| > 0
&& ! context.server_initial_processed;
# Can only decrypt the responder if we've seen the initial destination conn id.
if ( ! crypto.is_orig && ! context.initial_destination_conn_id )
return False;
# Only attempt decryption if we haven't flushed some SSL data yet.
return ! crypto.finished;
}
type ConnectionIDInfo = struct {
function reset_crypto(context: Context&) {
# Recreate all the crypto state on the next %init of Packet.
zeek::protocol_handle_close(context.ssl_handle);
unset context.ssl_handle;
context.client_crypto = Null;
context.server_crypto = Null;
context.client_sink = Null;
context.server_sink = Null;
context.initial_destination_conn_id = Null;
}
# This unit is connected with the server and client sinks receiving
# CRYPTO frames and forwards data to the SSL handle in the context.
type CryptoSinkUnit = unit(is_orig: bool, context: Context&) {
var buffered: bytes;
var length: uint32 = 0;
var is_orig: bool = is_orig;
var finished: bool;
# The first 4 bytes of crypto data contain the expected tag and a
# 24bit length from the TLS HandshakeMessage. Extract the length
# so we can determine when all CRYPTO frames have arrived.
#
# https://datatracker.ietf.org/doc/html/rfc8446#section-4
#
# struct {
# HandshakeType msg_type; /* handshake type */
# uint24 length; /* remaining bytes in message */
# ...
#
: uint8 {
self.buffered += $$;
}
len: uint8[3] {
self.length = (cast<uint32>($$[0]) << 16) + (cast<uint32>($$[1]) << 8) + cast<uint32>($$[2]) + 4;
self.buffered += $$[0];
self.buffered += $$[1];
self.buffered += $$[2];
}
: void &requires=(self.length <= 2**14 + 256) { # The length MUST NOT exceed 2^14 + 256 bytes (RFC 8446)
# The client or server hello data is forwarded to the SSL analyzer as a
# TLSPlaintext record with legacy_record_version set to \x03\x03 (1.3).
#
# enum {
# invalid(0),
# change_cipher_spec(20),
# alert(21),
# handshake(22),
# application_data(23),
# (255)
# } ContentType;
#
# struct {
# ContentType type;
# ProtocolVersion legacy_record_version;
# uint16 length;
# opaque fragment[TLSPlaintext.length];
# } TLSPlaintext;
#
# https://datatracker.ietf.org/doc/html/rfc8446#section-5.1
local length_bytes = pack(cast<uint16>(self.length), spicy::ByteOrder::Big);
zeek::protocol_data_in(is_orig, b"\x16\x03\x03" + length_bytes + self.buffered, context.ssl_handle);
}
: bytes &chunked &size=(self.length - 4) {
zeek::protocol_data_in(is_orig, $$, context.ssl_handle);
}
: void {
self.finished = True;
}
};
##############
## Context
##############
type Context = struct {
client_cid_len: uint8;
server_cid_len: uint8;
@ -46,26 +123,13 @@ type ConnectionIDInfo = struct {
# will make life miserable.
#
# https://quicwg.org/base-drafts/rfc9001.html#appendix-A
initial_destination_conn_id: bytes;
initial_destination_conn_id: optional<bytes>;
# Currently, this analyzer assumes that ClientHello
# and ServerHello fit into the first INITIAL packet (and
# that there is only one that we're interested in.
#
# But minimally the following section sounds like this might not
# hold in general and the Wireshark has samples showing
# the handshake spanning across more than two INITIAL packets.
# (quic-fragmented-handshakes.pcapng.gz)
#
# https://datatracker.ietf.org/doc/html/rfc9001#section-4.3
#
# Possible fix is to buffer up all CRYPTO frames across multiple
# INITIAL packets until we see a non-INITIAL frame.
#
# We also rely heavily on getting originator and responder right.
#
client_initial_processed: bool;
server_initial_processed: bool;
# Track crypto state.
client_crypto: CryptoSinkUnit&;
client_sink: sink&;
server_crypto: CryptoSinkUnit&;
server_sink: sink&;
ssl_handle: zeek::ProtocolHandle &optional;
};
@ -272,16 +336,28 @@ public type LongHeaderPacket = unit {
};
# A QUIC Frame.
public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto_sink: sink&) {
public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto: CryptoSinkUnit, crypto_sink: sink&) {
frame_type : uint8 &convert=cast<FrameType>($$);
# TODO: add other FrameTypes as well
switch ( self.frame_type ) {
FrameType::ACK1 -> a: ACKPayload;
FrameType::ACK2 -> b: ACKPayload;
FrameType::ACK1 -> a: ACKPayload(FrameType::ACK1);
FrameType::ACK2 -> b: ACKPayload(FrameType::ACK2);
FrameType::CRYPTO -> c: CRYPTOPayload(from_client) {
# Have the sink re-assemble potentially out-of-order cryptodata
crypto_sink.write(self.c.cryptodata, self.c.offset.result_);
# If the crypto unit has determined a valid length, ensure we
# don't attempt to write more bytes into the sink. If it doesn't,
# use 2000 bytes as an arbitrary limit required to observe the
# length of the contained Client Hello or Server Hello.
if ( crypto.length > 0 ) {
if ( |crypto_sink| > crypto.length )
throw "too much crypto data received %s > %s" % ( |crypto_sink|, crypto.length);
} else {
if ( |crypto_sink| > 2000 )
throw "too much crypto data without length received %s" % |crypto_sink|;
}
}
FrameType::CONNECTION_CLOSE1 -> : ConnectionClosePayload(header);
FrameType::PADDING -> : skip /\x00*/; # eat the padding
@ -298,11 +374,26 @@ type CRYPTOPayload = unit(from_client: bool) {
cryptodata: bytes &size=self.length.result_;
};
type ACKPayload = unit {
# https://datatracker.ietf.org/doc/html/rfc9000#ack-ranges
type ACKRange = unit {
gap: VariableLengthInteger;
ack_range_length: VariableLengthInteger;
};
type ACKECNCounts = unit {
ect0: VariableLengthInteger;
ect1: VariableLengthInteger;
ecn_ce: VariableLengthInteger;
};
# https://datatracker.ietf.org/doc/html/rfc9000#name-ack-frames
type ACKPayload = unit(frame_type: FrameType) {
latest_ack: VariableLengthInteger;
ack_delay: VariableLengthInteger;
ack_range_count: VariableLengthInteger;
first_ack_range: VariableLengthInteger;
ack_ranges: ACKRange[self.ack_range_count.result_];
ecn_counts: ACKECNCounts if(frame_type == FrameType::ACK2);
};
type ConnectionClosePayload = unit(header: LongHeaderPacket) {
@ -393,35 +484,18 @@ public type ShortPacketPayload = unit {
payload: skip bytes &eod;
};
# TODO: investigate whether we can do something useful with this
public type EncryptedLongPacketPayload = unit {
payload: skip bytes &eod;
};
# Buffer all crypto messages (which might be fragmented and unordered)
# into the following unit.
type CryptoBuffer = unit() {
var buffered: bytes;
: bytes &chunked &eod {
self.buffered += $$;
# print "crypto_buffer got data", |$$|, |self.buffered|;
}
};
##############
# QUIC packet parsing
#
# A UDP datagram contains one or more QUIC packets.
##############
type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
type Packet = unit(from_client: bool, context: Context&) {
var decrypted_data: bytes;
var packet_size: uint64 = 0;
var start: iterator<stream>;
sink crypto_sink;
var crypto_buffer: CryptoBuffer&;
var crypto: CryptoSinkUnit&;
var crypto_sink: sink&;
# Attach an SSL analyzer to this connection once.
on %init {
@ -430,6 +504,26 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
}
self.start = self.input();
# Initialize crypto state in context for both sides if not already done.
if ( context.client_crypto == Null ) {
assert ! context.server_crypto;
context.client_crypto = new CryptoSinkUnit(True, context);
context.client_sink = new sink;
context.client_sink.connect(context.client_crypto);
context.server_crypto = new CryptoSinkUnit(False, context);
context.server_sink = new sink;
context.server_sink.connect(context.server_crypto);
}
if ( from_client ) {
self.crypto = context.client_crypto;
self.crypto_sink = context.client_sink;
} else {
self.crypto = context.server_crypto;
self.crypto_sink = context.server_sink;
}
}
# Peek into the first byte and determine the header type.
@ -443,7 +537,6 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
self.set_input(self.start); # rewind
}
# Depending on the header, parse it and update the src/dest ConnectionID's
switch ( self.first_byte.header_form ) {
HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len);
@ -453,19 +546,16 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
# If we see a retry packet from the responder, reset the decryption
# context such that the next DCID from the client is used for decryption.
if ( self.long_header.is_retry ) {
context.client_initial_processed = False;
context.server_initial_processed = False;
context.initial_destination_conn_id = b"";
reset_crypto(context);
# Allow re-opening the SSL analyzer the next time around.
zeek::protocol_handle_close(context.ssl_handle);
unset context.ssl_handle;
self.crypto = Null;
self.crypto_sink = Null;
}
}
};
: void {
if (self?.long_header && can_decrypt(self.long_header, context, from_client))
if ( self?.long_header && can_decrypt(self.long_header, context, self.crypto ) )
# If we have parsed an initial packet that we can decrypt the payload,
# determine the size to store into a buffer.
self.packet_size = self.offset();
@ -473,30 +563,29 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
# Buffer the whole packet if we determined we have a chance to decrypt.
packet_data: bytes &parse-at=self.start &size=self.packet_size if ( self.packet_size > 0 ) {
self.crypto_buffer = new CryptoBuffer();
self.crypto_sink.connect(self.crypto_buffer);
if ( from_client ) {
context.server_cid_len = self.long_header.dest_conn_id_len;
context.client_cid_len = self.long_header.src_conn_id_len;
# This is the first INITIAL packet we attempt to decrypt and it is
# coming from the client. Use its destination connection ID for
# decryption purposes.
if ( ! context.initial_destination_conn_id ) {
context.initial_destination_conn_id = self.long_header.dest_conn_id;
}
# This means that here, we can try to decrypt the initial packet!
# All data is accessible via the `long_header` unit
self.decrypted_data = decrypt_crypto_payload(
self.long_header.version,
self.packet_data,
self.long_header.dest_conn_id,
*context.initial_destination_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client
);
# Assuming that the client set up the connection, this can be considered the first
# received Initial from the client. So disable change of ConnectionID's afterwards
if ( |context.initial_destination_conn_id| == 0 ) {
context.initial_destination_conn_id = self.long_header.dest_conn_id;
}
} else {
context.server_cid_len = self.long_header.src_conn_id_len;
context.client_cid_len = self.long_header.dest_conn_id_len;
@ -504,7 +593,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
self.decrypted_data = decrypt_crypto_payload(
self.long_header.version,
self.packet_data,
context.initial_destination_conn_id,
*context.initial_destination_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client
@ -521,51 +610,24 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
spicy::accept_input();
}
# Depending on the type of header and whether we were able to decrypt
# some of it, parse the remaining payload.
# If this packet has a SHORT header, consume until &eod, there's nothing
# we can do with it anyhow.
: ShortPacketPayload if (self.first_byte.header_form == HeaderForm::SHORT);
: EncryptedLongPacketPayload if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| == 0);
# If this was packet with a long header and decrypted data exists, attempt
# to parse the plain QUIC frames from it.
frames: Frame(self.long_header, from_client, self.crypto_sink)[] &parse-from=self.decrypted_data if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0);
# Once the Packet is fully parsed, pass the accumulated CRYPTO frames
# to the SSL analyzer as handshake data.
on %done {
# print "packet done", zeek::is_orig(), self.first_byte.header_form, |self.decrypted_data|;
if ( self.crypto_buffer != Null && |self.crypto_buffer.buffered| > 0 ) {
local handshake_data = self.crypto_buffer.buffered;
# The data is passed to the SSL analyzer as part of a HANDSHAKE (0x16) message with TLS1.3 (\x03\x03).
# The 2 length bytes are also passed, followed by the actual CRYPTO blob which contains a CLIENT HELLO or SERVER HELLO
local length_bytes = pack(cast<uint16>(|handshake_data|), spicy::ByteOrder::Big);
zeek::protocol_data_in(
from_client
, b"\x16\x03\x03" + length_bytes + handshake_data
, context.ssl_handle
);
# Stop decryption attempts after processing the very first INITIAL
# INITIAL packet for which we forwarded data to the SSL analyzer.
if ( from_client )
context.client_initial_processed = True;
else
context.server_initial_processed = True;
}
}
frames: Frame(self.long_header, from_client, self.crypto, self.crypto_sink)[] &parse-from=self.decrypted_data if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0);
};
##############
# Entrypoints
##############
public type RequestFrame = unit {
%context = ConnectionIDInfo;
%context = Context;
: Packet(True, self.context())[];
};
public type ResponseFrame = unit {
%context = ConnectionIDInfo;
%context = Context;
: Packet(False, self.context())[];
};

View file

@ -841,8 +841,10 @@ std::optional<broker::data> val_to_data(const Val* v) {
for ( auto i = 0u; i < vec->Size(); ++i ) {
auto item_val = vec->ValAt(i);
if ( ! item_val )
continue;
if ( ! item_val ) {
reporter->Error("serialization of vectors with holes is unsupported");
return std::nullopt;
}
auto item = val_to_data(item_val.get());
@ -864,8 +866,10 @@ std::optional<broker::data> val_to_data(const Val* v) {
for ( auto i = 0; i < list->Length(); ++i ) {
const auto& item_val = list->Idx(i);
if ( ! item_val )
continue;
if ( ! item_val ) {
reporter->Error("serialization of lists with holes is unsupported");
return std::nullopt;
}
auto item = val_to_data(item_val.get());

View file

@ -587,7 +587,7 @@ void Manager::DoInitPostScript() {
checkLogSeverity(stderrSeverityVal);
auto adapterVerbosity = static_cast<BrokerSeverityLevel>(std::max(logSeverityVal, stderrSeverityVal));
auto queue = std::make_shared<LoggerQueue>();
auto pbstate = std::make_shared<PeerBufferState>(options.peer_buffer_size,
auto pbstate = std::make_shared<PeerBufferState>(get_option("Broker::peer_buffer_size")->AsCount(),
get_option("Broker::buffer_stats_reset_interval")->AsDouble());
auto observer = std::make_shared<Observer>(adapterVerbosity, queue, pbstate);
broker::logger(observer); // *must* be called before creating the BrokerState
@ -2086,8 +2086,10 @@ detail::StoreHandleVal* Manager::MakeClone(const string& name, double resync_int
auto handle = new detail::StoreHandleVal{*result};
Ref(handle);
if ( ! handle->proxy.valid() )
reporter->FatalError("Failed to create clone for data store %s", name.c_str());
if ( ! handle->proxy.valid() ) {
reporter->Error("Failed to create clone for data store %s", name.c_str());
return nullptr;
}
data_stores.emplace(name, handle);
if ( ! iosource_mgr->RegisterFd(handle->proxy.mailbox().descriptor(), this) )

View file

@ -88,7 +88,10 @@ bool WebSocketShim::DoInit() {
state = std::make_unique<WebSocketState>();
zeek::iosource_mgr->Register(iosrc, false);
zeek::iosource_mgr->RegisterFd(state->hub.read_fd(), iosrc);
if ( ! zeek::iosource_mgr->RegisterFd(state->hub.read_fd(), iosrc) ) {
zeek::reporter->Error("Failed to register hub.read_fd() with iosource_mgr");
return false;
}
return true;
}

View file

@ -81,19 +81,24 @@ public:
* Close the IO source.
*/
void Close() {
if ( std::this_thread::get_id() != main_thread_id ) {
fprintf(stderr, "OnLoopProcess::Close() not called by main thread!");
abort();
}
zeek::iosource_mgr->UnregisterFd(flare.FD(), this);
// Ensure Process() is short-circuited from now on.
proc = nullptr;
{
// Close under lock to guarantee visibility for
// any pending queuers QueueForProcessing() calls.
std::scoped_lock lock(mtx);
SetClosed(true);
// Wake a process stuck in queueing.
cond.notify_one();
// Don't attempt to Process anymore.
proc = nullptr;
// Wake other threads stuck in queueing, if any.
cond.notify_all();
}
// Wait for any active queuers to vanish, should be quick.

View file

@ -247,6 +247,13 @@ void WebSocketEventDispatcher::Terminate() {
}
clients.clear();
onloop->Close();
// Wait for the reply_msg_thread to process any outstanding
// WebSocketReply messages before returning.
reply_msg_thread->SignalStop();
reply_msg_thread->WaitForStop();
}
void WebSocketEventDispatcher::QueueForProcessing(WebSocketEvent&& event) {

View file

@ -92,11 +92,18 @@ TraversalCode GenIDDefs::PreStmt(const Stmt* s) {
st->Traverse(this);
}
}
else
// If there's just a single statement then there are no
// expressions computed subsequent to it that we need to
// worry about, so just do ordinary traversal.
else {
// If there's just a single statement then there are two
// possibilities. If the statement is atomic (no sub-statements)
// then given that it's in reduced form, it's not going to have
// any expressions that we can leverage. OTOH, if it's compound
// (if/for/while/switch) then there's no safe re-using of
// expressions within it since they may-or-may-not wind up
// being computed. So we should always start a new block.
StartConfluenceBlock(s);
did_confluence = true;
block->Traverse(this);
}
if ( did_confluence )
EndConfluenceBlock();

View file

@ -2,6 +2,8 @@
#include "zeek/storage/backend/redis/Redis.h"
#include <algorithm>
#include "zeek/DebugLogger.h"
#include "zeek/Func.h"
#include "zeek/RunState.h"
@ -23,18 +25,37 @@ public:
std::string where;
};
/**
* Callback handler for OnConnect events from hiredis.
*
* @param ctx The async context that called this callback.
* @param status The status of the connection attempt.
*/
void redisOnConnect(const redisAsyncContext* ctx, int status) {
auto t = Tracer("connect");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
backend->OnConnect(status);
}
/**
* Callback handler for OnDisconnect events from hiredis.
*
* @param ctx The async context that called this callback.
* @param status The status of the disconnection attempt.
*/
void redisOnDisconnect(const redisAsyncContext* ctx, int status) {
auto t = Tracer("disconnect");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
backend->OnDisconnect(status);
}
/**
* Callback handler for SET commands.
*
* @param ctx The async context that called this callback.
* @param reply The reply from the server for the command.
* @param privdata A pointer to private data passed in the command.
*/
void redisPut(redisAsyncContext* ctx, void* reply, void* privdata) {
auto t = Tracer("put");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
@ -42,6 +63,13 @@ void redisPut(redisAsyncContext* ctx, void* reply, void* privdata) {
backend->HandlePutResult(static_cast<redisReply*>(reply), callback);
}
/**
* Callback handler for GET commands.
*
* @param ctx The async context that called this callback.
* @param reply The reply from the server for the command.
* @param privdata A pointer to private data passed in the command.
*/
void redisGet(redisAsyncContext* ctx, void* reply, void* privdata) {
auto t = Tracer("get");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
@ -49,6 +77,13 @@ void redisGet(redisAsyncContext* ctx, void* reply, void* privdata) {
backend->HandleGetResult(static_cast<redisReply*>(reply), callback);
}
/**
* Callback handler for DEL commands.
*
* @param ctx The async context that called this callback.
* @param reply The reply from the server for the command.
* @param privdata A pointer to private data passed in the command.
*/
void redisErase(redisAsyncContext* ctx, void* reply, void* privdata) {
auto t = Tracer("erase");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
@ -56,8 +91,15 @@ void redisErase(redisAsyncContext* ctx, void* reply, void* privdata) {
backend->HandleEraseResult(static_cast<redisReply*>(reply), callback);
}
/**
* Callback handler for ZADD commands.
*
* @param ctx The async context that called this callback.
* @param reply The reply from the server for the command.
* @param privdata A pointer to private data passed in the command.
*/
void redisZADD(redisAsyncContext* ctx, void* reply, void* privdata) {
auto t = Tracer("generic");
auto t = Tracer("zadd");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
// We don't care about the reply from the ZADD, mostly because blocking to poll
@ -67,12 +109,32 @@ void redisZADD(redisAsyncContext* ctx, void* reply, void* privdata) {
freeReplyObject(reply);
}
/**
* Callback handler for commands where there isn't a specific handler in the Redis class.
*
* @param ctx The async context that called this callback.
* @param reply The reply from the server for the command.
* @param privdata A pointer to private data passed in the command.
*/
void redisGeneric(redisAsyncContext* ctx, void* reply, void* privdata) {
auto t = Tracer("generic");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
backend->HandleGeneric(static_cast<redisReply*>(reply));
}
/**
* Callback handler for ZADD commands.
*
* @param ctx The async context that called this callback.
* @param reply The reply from the server for the command.
* @param privdata A pointer to private data passed in the command.
*/
void redisINFO(redisAsyncContext* ctx, void* reply, void* privdata) {
auto t = Tracer("generic");
auto backend = static_cast<zeek::storage::backend::redis::Redis*>(ctx->data);
backend->HandleInfoResult(static_cast<redisReply*>(reply));
}
// Because we called redisPollAttach in DoOpen(), privdata here is a
// redisPollEvents object. We can go through that object to get the context's
// data, which contains the backend. Because we overrode these callbacks in
@ -83,6 +145,14 @@ void redisGeneric(redisAsyncContext* ctx, void* reply, void* privdata) {
// we're reading a pcap, don't add the file descriptor into iosource_mgr. Manual
// calls to Poll() during that will handle reading/writing any data, and we
// don't want the contention with the main loop.
/**
* Callback from hiredis when a new reader is added to the context. This is called when
* data is ready to be read from the context for a command.
*
* @param privdata Private data passed back to the callback when it fires. We use this to
* get access to the redis backend object.
*/
void redisAddRead(void* privdata) {
auto t = Tracer("addread");
auto rpe = static_cast<redisPollEvents*>(privdata);
@ -93,6 +163,13 @@ void redisAddRead(void* privdata) {
rpe->reading = 1;
}
/**
* Callback from hiredis when a new reader is added to the context. This is called when no
* more data is ready to be read from the context for a command.
*
* @param privdata Private data passed back to the callback when it fires. We use this to
* get access to the redis backend object.
*/
void redisDelRead(void* privdata) {
auto t = Tracer("delread");
auto rpe = static_cast<redisPollEvents*>(privdata);
@ -103,6 +180,13 @@ void redisDelRead(void* privdata) {
rpe->reading = 0;
}
/**
* Callback from hiredis when a new writer is added to the context. This is called when
* data is ready to be written to the context for a command.
*
* @param privdata Private data passed back to the callback when it fires. We use this to
* get access to the redis backend object.
*/
void redisAddWrite(void* privdata) {
auto t = Tracer("addwrite");
auto rpe = static_cast<redisPollEvents*>(privdata);
@ -113,6 +197,13 @@ void redisAddWrite(void* privdata) {
rpe->writing = 1;
}
/**
* Callback from hiredis when a writer is removed from the context. This is called when no
* more data is ready to be written to the context for a command.
*
* @param privdata Private data passed back to the callback when it fires. We use this to
* get access to the redis backend object.
*/
void redisDelWrite(void* privdata) {
auto rpe = static_cast<redisPollEvents*>(privdata);
auto t = Tracer("delwrite");
@ -137,6 +228,8 @@ std::unique_lock<std::mutex> conditionally_lock(bool condition, std::mutex& mute
namespace zeek::storage::backend::redis {
constexpr char REQUIRED_VERSION[] = "6.2.0";
storage::BackendPtr Redis::Instantiate() { return make_intrusive<Redis>(); }
/**
@ -388,27 +481,33 @@ void Redis::DoExpire(double current_network_time) {
return;
}
std::vector<std::string> elements;
for ( size_t i = 0; i < reply->elements; i++ )
elements.emplace_back(reply->element[i]->str);
std::vector<std::string_view> elements;
elements.reserve(reply->elements);
freeReplyObject(reply);
for ( size_t i = 0; i < reply->elements; i++ )
elements.emplace_back(reply->element[i]->str, reply->element[i]->len);
// TODO: it's possible to pass multiple keys to a DEL operation but it requires
// building an array of the strings, building up the DEL command with entries,
// and passing the array as a block somehow. There's no guarantee it'd be faster
// anyways.
for ( const auto& e : elements ) {
status = redisAsyncCommand(async_ctx, redisGeneric, NULL, "DEL %s:%s", key_prefix.data(), e.c_str());
// redisAsyncCommand usually takes a printf-style string, except the parser used by
// hiredis doesn't handle lengths passed with strings correctly (it hangs indefinitely).
// Use util::fmt here instead it handles it.
status = redisAsyncCommand(async_ctx, redisGeneric, NULL,
util::fmt("DEL %s:%.*s", key_prefix.data(), static_cast<int>(e.size()), e.data()));
++active_ops;
Poll();
redisReply* reply = reply_queue.front();
redisReply* del_reply = reply_queue.front();
reply_queue.pop_front();
freeReplyObject(reply);
freeReplyObject(del_reply);
// TODO: do we care if this failed?
}
freeReplyObject(reply);
// Remove all of the elements from the range-set that match the time range.
redisAsyncCommand(async_ctx, redisGeneric, NULL, "ZREMRANGEBYSCORE %s_expire -inf %f", key_prefix.data(),
current_network_time);
@ -416,9 +515,9 @@ void Redis::DoExpire(double current_network_time) {
++active_ops;
Poll();
reply = reply_queue.front();
redisReply* rem_range_reply = reply_queue.front();
reply_queue.pop_front();
freeReplyObject(reply);
freeReplyObject(rem_range_reply);
// TODO: do we care if this failed?
}
@ -487,19 +586,67 @@ void Redis::HandleGeneric(redisReply* reply) {
reply_queue.push_back(reply);
}
void Redis::HandleInfoResult(redisReply* reply) {
DBG_LOG(DBG_STORAGE, "Redis backend: info event");
--active_ops;
auto lines = util::split(std::string{reply->str}, "\r\n");
OperationResult res = {ReturnCode::CONNECTION_FAILED};
if ( lines.empty() )
res.err_str = "INFO command return zero entries";
else {
std::string_view version_sv{REQUIRED_VERSION};
for ( const auto& e : lines ) {
// Skip empty lines and comments
if ( e.empty() || e[0] == '#' )
continue;
// We only care about the redis_version entry. Skip anything else.
if ( ! util::starts_with(e, "redis_version:") )
continue;
auto splits = util::split(e, ':');
DBG_LOG(DBG_STORAGE, "Redis backend: found server version %s", splits[1].c_str());
if ( std::lexicographical_compare(splits[1].begin(), splits[1].end(), version_sv.begin(),
version_sv.end()) )
res.err_str = util::fmt("Redis server version is too low: Found %s, need %s", splits[1].c_str(),
REQUIRED_VERSION);
else {
connected = true;
res.code = ReturnCode::SUCCESS;
}
}
}
if ( ! connected && res.err_str.empty() )
res.err_str = "INFO command did not return server version";
freeReplyObject(reply);
CompleteCallback(open_cb, res);
}
void Redis::OnConnect(int status) {
DBG_LOG(DBG_STORAGE, "Redis backend: connection event");
--active_ops;
if ( status == REDIS_OK ) {
connected = true;
CompleteCallback(open_cb, {ReturnCode::SUCCESS});
// The connection_established event is sent via the open callback handler.
return;
}
connected = false;
CompleteCallback(open_cb, {ReturnCode::CONNECTION_FAILED});
if ( status == REDIS_OK ) {
// Request the INFO block from the server that should contain the version information.
status = redisAsyncCommand(async_ctx, redisINFO, NULL, "INFO server");
if ( status == REDIS_ERR ) {
// TODO: do something with the error?
DBG_LOG(DBG_STORAGE, "INFO command failed: %s", async_ctx->errstr);
CompleteCallback(open_cb,
{ReturnCode::OPERATION_FAILED,
util::fmt("INFO command failed to retrieve server info: %s", async_ctx->errstr)});
return;
}
++active_ops;
}
// TODO: we could attempt to reconnect here
}

View file

@ -41,6 +41,7 @@ public:
void HandleGetResult(redisReply* reply, ResultCallback* callback);
void HandleEraseResult(redisReply* reply, ResultCallback* callback);
void HandleGeneric(redisReply* reply);
void HandleInfoResult(redisReply* reply);
/**
* Returns whether the backend is opened.
@ -60,6 +61,7 @@ private:
void DoPoll() override;
OperationResult ParseReplyError(std::string_view op_str, std::string_view reply_err_str) const;
OperationResult CheckServerVersion();
redisAsyncContext* async_ctx = nullptr;

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
error in ../manager.zeek, line 12: serialization of vectors with holes is unsupported
received termination signal

View file

@ -0,0 +1,6 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
node_up, worker-1
got pong, with, [1, 2, 3], 3
got pong, with, [1, 2, 3], 3
got pong, with, [4, 5, 6], 3
got pong, with, [4, 5, 6], 3

View file

@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
received termination signal

View file

@ -0,0 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
got ping, vector of count, [1, 2, 3], 3
got ping, vector of count, [4, 5, 6], 3
got finish!

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
connection closed ok
connection closed ok
connection closed ok

View file

@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
received termination signal

View file

@ -0,0 +1,5 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
B Cluster::websocket_client_added, [/zeek/wstest/ws1/]
B Cluster::websocket_client_added, [/zeek/wstest/ws2/]
B Cluster::websocket_client_added, [/zeek/wstest/ws3/]
D got 1000 pings from 3 clients, terminating

View file

@ -0,0 +1,2 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
T, [field1=3], 3

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents ip_proto failed_service
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string] count set[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 51354 127.0.0.1 21 tcp - 9.891089 34 71 SF T T 0 ShAdDaFf 13 718 10 599 - 6 ftp
#close XXXX-XX-XX-XX-XX-XX

View file

@ -7,5 +7,5 @@
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.17.0.2 34347 64.233.166.94 443 1 815d62c70884f4b51e8ccadd5beed372 e5ec6b26584229be98a164349ae910351c40d10b c15d62c70884f4b5 www.google.de h3 ISishIhHhhH
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.17.0.2 34347 64.233.166.94 443 1 815d62c70884f4b51e8ccadd5beed372 e5ec6b26584229be98a164349ae910351c40d10b c15d62c70884f4b5 www.google.de h3 ISishIHhHhhH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid server_name history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 www.google.de ZZZIiIIIISiIIIiiiiiishIIHH

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 TLSv13 TLS_AES_128_GCM_SHA256 X25519MLKEM768 www.google.de T - - F Cs

View file

@ -47,3 +47,4 @@ zerortt.pcap
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, initial_packet, CtPZjS20MLrsMUOJi2, T, 1, 3ec82f67,
1.0, handshake_packet, T, CtPZjS20MLrsMUOJi2, 1, 3ec82f67,
1.0, handshake_packet, T, CtPZjS20MLrsMUOJi2, 1, 3ec82f67,

View file

@ -7,6 +7,6 @@
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string string
1.000000 CtPZjS20MLrsMUOJi2 193.167.0.100 49394 193.167.100.100 443 1 15ae5e5e4962163f410b5529fc125bbc (empty) e483a751 server4:443 hq-interop ISZishZZZZZZZZZZZZZZZZZZZZZZZZZZZIH
1.000000 CtPZjS20MLrsMUOJi2 193.167.0.100 49394 193.167.100.100 443 1 15ae5e5e4962163f410b5529fc125bbc (empty) e483a751 server4:443 hq-interop ISZishZZZZZZZZZZZZZZZZZZZZZZZZZZZIHH
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 60492 193.167.100.100 443 1 b7c7841c64883e3261d840 (empty) 8d2041ac server4:443 hq-interop ISishhIH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 D quic,ssl

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid server_name history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 googleads.g.doubleclick.net IIIS

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 - - - googleads.g.doubleclick.net F - - F C

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid server_name history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 googleads.g.doubleclick.net IIISZZZiIiIIIIIIZ

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 - - - googleads.g.doubleclick.net F - - F C

View file

@ -7,5 +7,5 @@
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid client_scid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49320 127.0.0.1 443 quicv2 fa603212c8688817af3d3238735bc7 (empty) b168b5cc localhost quic-echo-example ISIIishIH
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 49320 127.0.0.1 443 quicv2 fa603212c8688817af3d3238735bc7 (empty) b168b5cc localhost quic-echo-example ISIIishIHH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -28,6 +28,7 @@ The wstest.main() helper will retry running test_fun on ConnectionRefusedError.
import json
import os
import socket
import time
from typing import Any, Callable, Optional, Union
@ -221,3 +222,58 @@ def main(f: Callable, *args, **kwargs):
break
except ConnectionRefusedError:
time.sleep(MAIN_SLEEP)
def monkey_patch_close_socket():
"""
Monkey patch websockets.sync.ClientConnection.close_socket()
What's commented out from the original implementation is calling
receive_eof() on self.protocol as well as acknowledge_pending_pings().
The reason for doing this is that in the scneario where the websockets
library detects a closed socket during sending, it'll call close_socket()
which in turn invokes protocol.receive_eof().
However, when the concurrently running recv_events() thread is racing
and has just successfully received the server's CLOSE frame, the EOF
set on protocol results in an EOFError for the receiving thread instead
of processing the CLOSE frame. It then further reports an
"unexpected internal error".
Changing the close_socket() implemenation allows the EOF condition
to be set only on the receiving side, avoiding the race.
"""
def __custom_close_socket(self):
"""
The original implementation is taken from Connection.close_socket()
in sync/connection.py (version 15.0.1).
"""
# shutdown() is required to interrupt recv() on Linux.
try:
self.socket.shutdown(socket.SHUT_RDWR)
except OSError:
pass # socket is already closed
self.socket.close()
# Calling protocol.receive_eof() is safe because it's idempotent.
# This guarantees that the protocol state becomes CLOSED.
#
# Commented out: Let the recv_events() threads do EOF handling.
#
# with self.protocol_mutex:
# self.protocol.receive_eof()
# assert self.protocol.state is CLOSED
# Abort recv() with a ConnectionClosed exception.
self.recv_messages.close()
# Acknowledge pings sent with the ack_on_close option.
#
# Commented out: Asserts on protcol.state is CLOSED
#
# self.acknowledge_pending_pings()
ClientConnection.close_socket = __custom_close_socket

View file

@ -57,8 +57,6 @@ event send_any()
local e = Cluster::make_event(ping, i, type_name(val), val);
Cluster::publish_hrw(Cluster::worker_pool, cat(i), e);
++i;
schedule 0.05sec { send_any() };
}
event pong(c: count, what: string, val: any)
@ -66,10 +64,16 @@ event pong(c: count, what: string, val: any)
++pongs;
print "got pong", pongs, "with", c, what, type_name(val), val;
# We send 5 pings in 3 different variations and
# get two pongs for each.
# The manager sends 5 types of pings, in 3 different ways. The worker
# answers each ping in two ways, for a total of 30 expected pongs at the
# manager. Every batch of pings involves 6 pongs.
if ( pongs == 30 )
Cluster::publish(Cluster::worker_topic, finish);
else if ( pongs > 0 && pongs % 6 == 0 )
{
# Wait for a batch to complete before sending the next.
event send_any();
}
}
event Cluster::node_up(name: string, id: string)

View file

@ -0,0 +1,92 @@
# @TEST-DOC: Attempt to send an event with holes. It should fail.
#
# @TEST-REQUIRES: have-zeromq
#
# @TEST-PORT: XPUB_PORT
# @TEST-PORT: XSUB_PORT
# @TEST-PORT: LOG_PULL_PORT
#
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-no-logger.zeek cluster-layout.zeek
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
#
# @TEST-EXEC: zeek -b --parse-only common.zeek manager.zeek worker.zeek
#
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek"
# @TEST-EXEC: btest-bg-run worker-1 "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=worker-1 zeek -b ../worker.zeek"
#
# @TEST-EXEC: btest-bg-wait 30
# @TEST-EXEC: btest-diff ./manager/.stderr
# @TEST-EXEC: btest-diff ./manager/.stdout
# @TEST-EXEC: btest-diff ./worker-1/.stdout
# @TEST-EXEC: btest-diff ./worker-1/.stderr
# @TEST-START-FILE common.zeek
@load ./zeromq-test-bootstrap.zeek
redef Log::default_rotation_interval = 0sec;
global finish: event() &is_used;
global ping: event(v: vector of count) &is_used;
global pong: event(v: vector of count) &is_used;
# @TEST-END-FILE
# @TEST-START-FILE manager.zeek
@load ./common.zeek
event send_pings()
{
local v1 = vector(1, 2, 3);
assert Cluster::publish(Cluster::worker_topic, ping, v1);
# Publish with a vector with a hole, fails!
local v2 = vector(1);
v2[2] = 3;
assert ! Cluster::publish(Cluster::worker_topic, ping, v2);
local v3 = vector(4, 5, 6);
assert Cluster::publish(Cluster::worker_topic, ping, v3);
}
global pongs = 0;
event pong(v: vector of count)
{
++pongs;
print "got pong", "with", v, |v|;
# Two of the three pings go through, the worker sends 2 pongs
# for each ping, so stop after 4.
if ( pongs == 4 )
Cluster::publish(Cluster::worker_topic, finish);
}
event Cluster::node_up(name: string, id: string)
{
print "node_up", name;
event send_pings();
}
event Cluster::node_down(name: string, id: string)
{
terminate();
}
# @TEST-END-FILE
# @TEST-START-FILE worker.zeek
@load ./common.zeek
event ping(v: vector of count)
{
print "got ping", type_name(v), cat(v), |v|;
Cluster::publish(Cluster::manager_topic, pong, v);
local e = Cluster::make_event(pong, v);
Cluster::publish(Cluster::manager_topic, e);
}
event finish()
{
print "got finish!";
terminate();
}
# @TEST-END-FILE

View file

@ -0,0 +1,103 @@
# @TEST-DOC: Regression test for #4420. Clients publish fast and Zeek terminates after receiving 1000 events. Previously this would result in a hang at Zeek shutdown.
#
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
#
# @TEST-PORT: WEBSOCKET_PORT
#
# @TEST-EXEC: cp $FILES/ws/wstest.py .
# @TEST-EXEC: zeek -b --parse-only manager.zeek
# @TEST-EXEC: python3 -m py_compile client.py
#
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && zeek -b ../manager.zeek >out"
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
#
# @TEST-EXEC: btest-bg-wait 30
# @TEST-EXEC: sort ./manager/out > ./manager/out.sorted
# @TEST-EXEC: btest-diff ./manager/out.sorted
# @TEST-EXEC: btest-diff ./manager/.stderr
# @TEST-EXEC: btest-diff ./client/out
# @TEST-EXEC: btest-diff ./client/.stderr
# @TEST-START-FILE manager.zeek
redef exit_only_after_terminate = T;
# Force dispatcher queue being full quickly!
redef Cluster::default_websocket_max_event_queue_size = 1;
global ping_count = 0;
global ping: event(msg: string, c: count) &is_used;
global clients: set[string] = set();
event ping(client: string, n: count) &is_used
{
++ping_count;
add clients[client];
if ( ping_count == 1000 )
{
print fmt("D got 1000 pings from %s clients, terminating", |clients|);
terminate();
}
}
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
{
print "B Cluster::websocket_client_added", subscriptions;
}
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
{
print "E Cluster::websocket_client_lost";
}
event zeek_init()
{
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
Cluster::subscribe("/test/pings/");
}
# @TEST-END-FILE
# @TEST-START-FILE client.py
import websockets.exceptions
import wstest
wstest.monkey_patch_close_socket()
def run(ws_url):
with (
wstest.connect("ws1", ws_url) as tc1,
wstest.connect("ws2", ws_url) as tc2,
wstest.connect("ws3", ws_url) as tc3,
):
clients = [tc1, tc2, tc3]
for tc in clients:
tc.hello_v1([])
stop = False;
i = 0
saw_closed_ok = set()
while len(saw_closed_ok) < 3:
for idx, tc in enumerate(clients, 1):
if idx in saw_closed_ok: # Have seen a ConnectionClosedOK for this client?
continue
try:
i += 1
tc.send_json(wstest.build_event_v1("/test/pings/", "ping", [f"tc{idx}", i]))
except websockets.exceptions.ConnectionClosedOK as e:
print("connection closed ok")
assert e.code == 1001, f"expected code 1001, got {e.code} - {e}" # Remote going away
i -= 1
saw_closed_ok.add(idx)
assert len(saw_closed_ok) == 3
assert i >= 1000, f"expected to send at least 1000 events, only sent {i}"
if __name__ == "__main__":
wstest.main(run, wstest.WS4_URL_V1)
# @TEST-END-FILE

View file

@ -0,0 +1,30 @@
# @TEST-DOC: Regression test for incorrect reuse of temporaries when inlining
# @TEST-REQUIRES: test "${ZEEK_USE_CPP}" != "1"
# @TEST-EXEC: zeek -b -O ZAM %INPUT >output
# @TEST-EXEC: btest-diff output
type R: record {
field1: count;
};
global yes = T;
function pred(my_R: R): bool
{
# In the following, my_R$field1 will never be evaluated ...
if ( yes || my_R$field1 == 4 )
return T;
else
return F;
}
event zeek_init()
{
local my_R = R($field1=3);
# ... prior to the regression, the third argument in this print
# was taken from the temporary in pred() that *would* have held
# my_R$field1 had that ever been evaluated, but instead it holds
# the default ZVal value of 0.
print pred(my_R), my_R, my_R$field1;
}

View file

@ -5,3 +5,7 @@
@load policy/protocols/conn/failed-service-logging
redef DPD::track_removed_services_in_connection = T;
# @TEST-START-NEXT
@load policy/protocols/conn/failed-service-logging

View file

@ -0,0 +1,12 @@
# @TEST-DOC: PCAP for which decryption failed due to not using the initial destination connection ID consistently.
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic-decrypt-fail-google-de-51833.pcap base/protocols/quic
# @TEST-EXEC: test ! -f analyzer.log
# @TEST-EXEC: test ! -f dpd.log
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: zeek-cut -m ts uid server_name history < quic.log > quic.log.cut
# @TEST-EXEC: btest-diff quic.log.cut
# @TEST-EXEC: zeek-cut -m ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history < ssl.log > ssl.log.cut
# @TEST-EXEC: btest-diff ssl.log.cut

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Pcap with CRYPTO frames fragemented over multiple INITIAL packets. The pcap only contains 3 INITIAL packets. Check what logs are created.
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic-multiple-initial-fragmented-crypto-only-initial.pcap base/protocols/quic
# @TEST-EXEC: test ! -f analyzer.log
# @TEST-EXEC: test ! -f dpd.log
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: zeek-cut -m ts uid server_name history < quic.log > quic.log.cut
# @TEST-EXEC: btest-diff quic.log.cut
# @TEST-EXEC: zeek-cut -m ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history < ssl.log > ssl.log.cut
# @TEST-EXEC: btest-diff ssl.log.cut

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Pcap with CRYPTO frames fragemented over multiple INITIAL packets.
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic-multiple-initial-fragmented-crypto.pcap base/protocols/quic
# @TEST-EXEC: test ! -f analyzer.log
# @TEST-EXEC: test ! -f dpd.log
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: zeek-cut -m ts uid server_name history < quic.log > quic.log.cut
# @TEST-EXEC: btest-diff quic.log.cut
# @TEST-EXEC: zeek-cut -m ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history < ssl.log > ssl.log.cut
# @TEST-EXEC: btest-diff ssl.log.cut

View file

@ -1 +1 @@
b7c465d5a208546bd27801496eb7e6035b208f27
5fd78ecbdd834feff545f3a3c19b974d927ffb91

View file

@ -0,0 +1 @@
../../scripts/external-ct-list.zeek

View file

@ -1,6 +1,7 @@
# Sets some testing specific options.
@load external-ca-list
@load external-ct-list
@load protocols/conn/failed-service-logging
redef DPD::track_removed_services_in_connection=T;

View file

@ -0,0 +1,22 @@
# Re-add CT logs that we need for tests
module SSL;
redef SSL::ct_logs += {
["\xee\xcd\xd0\x64\xd5\xdb\x1a\xce\xc5\x5c\xb7\x9d\xb4\xcd\x13\xa2\x32\x87\x46\x7c\xbc\xec\xde\xc3\x51\x48\x59\x46\x71\x1f\xb5\x9b"] = CTInfo($description="Google 'Argon2024' log", $operator="Google", $url="https://ct.googleapis.com/logs/us1/argon2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x1d\xb9\x6c\xa9\xcb\x69\x94\xc5\x5c\xe6\xb6\xa6\x03\xbb\xd2\xb8\xdc\x54\x43\x17\x28\x99\x0c\x06\x01\x50\x1d\x9d\x64\xc0\x59\x46\x2b\xdc\xc8\x03\x1d\x05\xb4\x2d\xa8\x09\xf7\x99\x41\xed\x04\xfb\xe5\x57\xba\x26\x04\xf6\x11\x52\xce\x14\x65\x3b\x2f\x76\x2b\xc0"),
["\x76\xff\x88\x3f\x0a\xb6\xfb\x95\x51\xc2\x61\xcc\xf5\x87\xba\x34\xb4\xa4\xcd\xbb\x29\xdc\x68\x42\x0a\x9f\xe6\x67\x4c\x5a\x3a\x74"] = CTInfo($description="Google 'Xenon2024' log", $operator="Google", $url="https://ct.googleapis.com/logs/eu1/xenon2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xb9\x60\xe0\x34\x1e\x35\xe4\x65\x00\x93\x4f\x90\x09\xbd\x5a\xec\x44\xdd\x8c\x0f\xce\xed\x11\x3e\x2a\x59\x46\x9a\x31\xb6\xc7\x99\xf7\xdc\xef\x3d\xcd\x8f\x86\xc2\x35\xa5\x3e\xdc\x29\xba\xbb\xf2\x54\xe2\xa8\x0c\x83\x08\x51\x06\xde\x21\x6d\x36\x50\x8e\x38\x4d"),
["\xda\xb6\xbf\x6b\x3f\xb5\xb6\x22\x9f\x9b\xc2\xbb\x5c\x6b\xe8\x70\x91\x71\x6c\xbb\x51\x84\x85\x34\xbd\xa4\x3d\x30\x48\xd7\xfb\xab"] = CTInfo($description="Cloudflare 'Nimbus2024' Log", $operator="Cloudflare", $url="https://ct.cloudflare.com/logs/nimbus2024/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x77\xb1\x9b\x7b\x8f\xe6\x8b\x35\xfe\x3a\x92\x29\x2d\xac\x8a\x8d\x51\x8a\x25\xfc\x93\xb6\xd7\xa0\x8b\x29\x37\x71\x1d\x33\xca\xcc\x33\xea\x28\xb9\x1f\xe2\xac\xc3\xa9\x5d\xdd\x97\xbe\xf6\x9e\x94\x25\xdd\x36\x81\xd1\xeb\x5d\x29\xc3\x2b\x44\xf1\x5b\xca\x15\x48"),
["\x48\xb0\xe3\x6b\xda\xa6\x47\x34\x0f\xe5\x6a\x02\xfa\x9d\x30\xeb\x1c\x52\x01\xcb\x56\xdd\x2c\x81\xd9\xbb\xbf\xab\x39\xd8\x84\x73"] = CTInfo($description="DigiCert Yeti2024 Log", $operator="DigiCert", $url="https://yeti2024.ct.digicert.com/log/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x57\xb8\xc1\x6f\x30\xa4\x7f\x2e\xe4\xf0\xd0\xd9\x60\x62\x13\x95\xe3\x7a\xe3\x4e\x53\xc3\xb3\xb8\x73\x85\xc1\x18\x0d\x23\x0e\x58\x84\xd2\x78\xef\x9b\xb3\x1e\x2c\x1a\xde\xc1\x8f\x81\x1b\x19\x44\x58\xb7\x00\x77\x60\x20\x1a\x72\xd8\x82\xde\xae\x9e\xb1\xc6\x4b"),
["\x73\xd9\x9e\x89\x1b\x4c\x96\x78\xa0\x20\x7d\x47\x9d\xe6\xb2\xc6\x1c\xd0\x51\x5e\x71\x19\x2a\x8c\x6b\x80\x10\x7a\xc1\x77\x72\xb5"] = CTInfo($description="DigiCert Nessie2024 Log", $operator="DigiCert", $url="https://nessie2024.ct.digicert.com/log/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\xfc\xa2\x7b\x36\xbf\x56\x91\xe9\xfe\x3f\xe8\x3d\xfc\xc3\xa7\xe0\x61\x52\xea\x2c\xe9\x05\xa3\x9f\x27\x17\x81\x05\x70\x6b\x81\x61\x44\x8a\xf8\x3b\x10\x80\x42\xed\x03\x2f\x00\x50\x21\xfc\x41\x54\x84\xa3\x54\xd5\x2e\xb2\x7a\x16\x4b\x2a\x1f\x2b\x66\x04\x2b"),
["\xb6\x9d\xdc\xbc\x3c\x1a\xbd\xef\x6f\x9f\xd6\x0c\x88\xb1\x06\x7b\x77\xf0\x82\x68\x8b\x2d\x78\x65\xd0\x4b\x39\xab\xe9\x27\xa5\x75"] = CTInfo($description="DigiCert 'Wyvern2024h1' Log", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2024h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x68\xa6\x79\x14\xd1\x58\xe7\xab\xaa\x29\x69\x7f\x60\xed\x68\xe8\x10\xf6\x07\x84\xc0\xfb\x59\x04\x5a\x09\xc9\x1d\xe1\x4b\xfb\xcd\xdc\x03\xf3\xa8\x2a\x46\xb9\x84\x4d\x69\x30\xec\x23\x35\xc1\x8e\xfc\x9f\xb4\x20\x24\xd7\x15\xac\x87\xf7\x1e\xc1\x0b\x3c\x76\x1a"),
["\x0c\x2a\xef\x2c\x4a\x5b\x98\x83\xd4\xdd\xa3\x82\xfe\x50\xfb\x51\x88\xb3\xe9\x73\x33\xa1\xec\x53\xa0\x9d\xc9\xa7\x9d\x0d\x08\x20"] = CTInfo($description="DigiCert 'Wyvern2024h2' Log", $operator="DigiCert", $url="https://wyvern.ct.digicert.com/2024h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa8\x73\x12\x9c\x54\xd0\x7a\x7d\xc5\xb5\x17\x2b\x71\x52\x89\x04\x90\xbb\x42\xf1\x9d\xf8\x1c\xde\x4c\xcf\x82\x3c\xbd\x37\x1b\x74\x4c\x3c\xc7\xa3\x13\x87\x01\x51\x13\x14\xda\xa2\x12\x98\x84\xce\x1c\xbe\xcf\x4f\x7a\xef\x15\xfa\xd0\xee\xed\xed\x07\xad\x71\x6d"),
["\xdb\x07\x6c\xde\x6a\x8b\x78\xec\x58\xd6\x05\x64\x96\xeb\x6a\x26\xa8\xc5\x9e\x72\x12\x93\xe8\xac\x03\x27\xdd\xde\x89\xdb\x5a\x2a"] = CTInfo($description="DigiCert 'Sphinx2024h1' Log", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2024h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xc6\xe4\x29\x69\x98\xfe\x28\x92\x57\x12\x4d\x9e\xed\x0e\xe7\x32\xa2\xe6\x9c\x27\x78\xa4\x29\x7c\x99\xd5\xdb\xfa\x22\xc1\xdd\x5e\xa7\xf4\xd8\xea\xc8\xd7\x44\x8d\xe0\xf1\x8c\x0a\x01\x1d\xd8\x22\xa8\xd3\xeb\xc9\x22\x8e\x36\xfb\x4a\xb1\x70\x9c\x5d\xc1\xe8\x33"),
["\xdc\xc9\x5e\x6f\xa2\x99\xb9\xb0\xfd\xbd\x6c\xa6\xa3\x6e\x1d\x72\xc4\x21\x2f\xdd\x1e\x0f\x47\x55\x3a\x36\xd6\xcf\x1a\xd1\x1d\x8d"] = CTInfo($description="DigiCert 'Sphinx2024h2' Log", $operator="DigiCert", $url="https://sphinx.ct.digicert.com/2024h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xdb\x09\x41\x84\xe7\xd1\xf1\x5b\x25\x09\x7b\xe8\xc6\x98\x51\x5e\x29\x85\xfd\x81\xde\x89\xd7\xd0\x86\xa4\xb0\xe5\x15\xec\x5d\x7b\x17\x55\x5f\xc9\x79\x8d\xe4\x22\x36\xe7\xe9\xbf\x38\x3f\xd1\xe9\xd4\x09\x84\x81\xbe\xb6\xc1\xed\x1b\x17\xea\x26\x97\xba\xe9\x9a"),
["\xa2\xe2\xbf\xd6\x1e\xde\x2f\x2f\x07\xa0\xd6\x4e\x6d\x37\xa7\xdc\x65\x43\xb0\xc6\xb5\x2e\xa2\xda\xb7\x8a\xf8\x9a\x6d\xf5\x17\xd8"] = CTInfo($description="Sectigo 'Sabre2024h1'", $operator="Sectigo", $url="https://sabre2024h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2c\x01\xf6\xce\x31\xbc\xaa\x14\x61\x51\xfe\x6b\x7a\x87\xae\xa6\xd3\x9b\xc7\x87\x2d\x0a\x5a\xc8\x4f\xb5\x54\xdc\xc9\x93\xa0\x00\xee\xca\x1c\xb9\xa7\xb6\x7b\x47\x3b\xe5\x4f\xaa\x6c\x16\x1c\x70\x2e\xc8\xec\x53\x5a\x4c\x21\x4c\x7e\x27\x0b\x13\x14\x5e\xfc\x85"),
["\x19\x98\x10\x71\x09\xf0\xd6\x52\x2e\x30\x80\xd2\x9e\x3f\x64\xbb\x83\x6e\x28\xcc\xf9\x0f\x52\x8e\xee\xdf\xce\x4a\x3f\x16\xb4\xca"] = CTInfo($description="Sectigo 'Sabre2024h2'", $operator="Sectigo", $url="https://sabre2024h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7a\x10\x4c\x8a\xe7\x22\x7b\x6d\x2a\xba\x8e\xfa\x6b\x4a\x81\xd5\x85\xae\x03\xef\xff\x4b\xfc\x4d\x53\x3d\xb7\x8c\xbb\x75\x09\xc9\xea\x16\x7e\xc1\x77\x16\xd2\xc2\x45\x74\x6d\x8d\xc4\xe1\x88\x37\xdf\xd4\xf3\x60\x65\xfc\xa0\x75\xf0\x20\x66\x8e\x4a\xcc\x19\xda"),
["\x29\xd0\x3a\x1b\xb6\x74\xaa\x71\x1c\xd3\x03\x5b\x65\x57\xc1\x4f\x8a\xa7\x8b\x4f\xe8\x38\x94\x49\xec\xa4\x53\xf9\x44\xbd\x24\x68"] = CTInfo($description="Sectigo 'Mammoth2024h1'", $operator="Sectigo", $url="https://mammoth2024h1.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa4\x59\x90\xf3\x71\x24\x24\xf7\xc3\x55\x27\x56\x9c\xa3\x59\x1e\xf7\xb7\x9f\xce\xab\x4e\x19\x66\x4d\xd0\x8a\xfa\x9d\x62\xa4\x24\xf0\x3b\x20\xe4\x1d\x14\x67\xc8\xfc\xe4\x37\xf2\x4b\x38\x54\x5a\xcf\x9f\x6b\x07\x90\xd0\x0e\x7e\x3d\x4c\x87\xb2\xe8\x3f\x07\xcc"),
["\x50\x85\x01\x58\xdc\xb6\x05\x95\xc0\x0e\x92\xa8\x11\x02\xec\xcd\xfe\x3f\x6b\x78\x58\x42\x9f\x57\x98\x35\x38\xc9\xda\x52\x50\x63"] = CTInfo($description="Sectigo 'Mammoth2024h1b'", $operator="Sectigo", $url="https://mammoth2024h1b.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xa3\xd5\x07\x28\x7a\x04\x34\xae\xca\xbe\x80\x79\x4f\x3e\xf6\x41\xf4\x24\x04\xe1\xd6\x36\x5a\x1a\x09\xf2\xd1\xba\x84\x17\xae\x1e\xa1\x7c\x00\x1d\x54\x73\x90\x75\x21\xa8\xd1\xda\x5e\x10\xe1\x8c\xec\xb2\x8a\x8c\xc8\xe7\xdd\xcd\xe2\x07\xf0\x4e\x16\x02\x57\x37"),
["\xdf\xe1\x56\xeb\xaa\x05\xaf\xb5\x9c\x0f\x86\x71\x8d\xa8\xc0\x32\x4e\xae\x56\xd9\x6e\xa7\xf5\xa5\x6a\x01\xd1\xc1\x3b\xbe\x52\x5c"] = CTInfo($description="Sectigo 'Mammoth2024h2'", $operator="Sectigo", $url="https://mammoth2024h2.ct.sectigo.com/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x85\x66\x22\x24\x6e\xbe\x52\x62\x0a\xa0\xaf\xc3\x25\x1a\x36\x2e\xa7\x60\x89\xa2\x65\xbf\xa4\x5f\xbd\x85\x6a\x94\x05\x81\x35\x90\x54\x31\x95\xe7\x11\x9e\xa3\x2e\x0f\x85\xef\xa7\x88\x57\x8b\x63\x1a\x81\xc1\x41\x9d\x7d\xec\x01\x3a\xdb\xb9\xc1\x27\xf4\x65\x1e"),
["\x3b\x53\x77\x75\x3e\x2d\xb9\x80\x4e\x8b\x30\x5b\x06\xfe\x40\x3b\x67\xd8\x4f\xc3\xf4\xc7\xbd\x00\x0d\x2d\x72\x6f\xe1\xfa\xd4\x17"] = CTInfo($description="Let's Encrypt 'Oak2024H1' log", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2024h1/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x56\x43\xd7\x7e\x7b\xd4\x72\xb7\xba\xa9\x51\xbd\x36\x93\xb7\xe9\xb5\x92\x0f\xea\x5e\xb7\x45\xa3\x92\xfd\xc9\xa5\x3c\x80\xac\x1a\x20\xef\x25\x2f\xb8\xe1\x20\xf7\xa8\x3a\x2e\x07\x8d\xe6\xeb\xa4\xe2\x7d\x24\x63\x9f\x46\xbf\x94\x73\x52\x8d\x96\xae\xa9\x26\xfd"),
["\x3f\x17\x4b\x4f\xd7\x22\x47\x58\x94\x1d\x65\x1c\x84\xbe\x0d\x12\xed\x90\x37\x7f\x1f\x85\x6a\xeb\xc1\xbf\x28\x85\xec\xf8\x64\x6e"] = CTInfo($description="Let's Encrypt 'Oak2024H2' log", $operator="Let's Encrypt", $url="https://oak.ct.letsencrypt.org/2024h2/", $maximum_merge_delay=86400, $key="\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd7\x73\xd6\x53\x47\xe9\xf3\xc9\xd5\x7c\x16\xc2\xd6\x8f\x70\x65\xfa\xf2\x51\x36\xa9\x13\x80\x2f\xed\xf9\x94\xd3\x5a\x8b\xe8\x4f\x33\xcf\xc3\xd3\x89\xd4\x5f\x5a\x66\x89\xba\x20\x1f\x71\xcb\xca\xbb\x9f\x9f\xf3\x5c\x2d\x1e\xa3\x81\x59\xaf\x92\xb3\x6d\x30\x68"),
};