Bug 1930690 - certificate transparency: support static-ct-api ("tiled") logs a=diannaS

Differential Revision: https://phabricator.services.mozilla.com/D258972
This commit is contained in:
Dana Keeler
2025-07-29 20:16:42 +00:00
committed by dsmith@mozilla.com
parent c68862e175
commit e85c3d250f
27 changed files with 691 additions and 169 deletions

View File

@@ -219,7 +219,8 @@ void CertVerifier::LoadKnownCTLogs() {
const CTLogOperatorInfo& logOperator = const CTLogOperatorInfo& logOperator =
kCTLogOperatorList[log.operatorIndex]; kCTLogOperatorList[log.operatorIndex];
CTLogVerifier logVerifier(logOperator.id, log.state, log.timestamp); CTLogVerifier logVerifier(logOperator.id, log.state, log.format,
log.timestamp);
rv = logVerifier.Init(publicKey); rv = logVerifier.Init(publicKey);
if (rv != Success) { if (rv != Success) {
MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log"); MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");

View File

@@ -14,7 +14,7 @@
#include <stddef.h> #include <stddef.h>
static const PRTime kCTExpirationTime = INT64_C(1759749898000000); static const PRTime kCTExpirationTime = INT64_C(1759796803000000);
namespace mozilla::ct { namespace mozilla::ct {
@@ -23,10 +23,16 @@ enum class CTLogState {
Retired, Retired,
}; };
enum class CTLogFormat {
RFC6962,
Tiled,
};
struct CTLogInfo { struct CTLogInfo {
// See bug 1338873 about making these fields const. // See bug 1338873 about making these fields const.
const char* name; const char* name;
CTLogState state; CTLogState state;
CTLogFormat format;
uint64_t timestamp; uint64_t timestamp;
// Index within kCTLogOperatorList. // Index within kCTLogOperatorList.
size_t operatorIndex; size_t operatorIndex;
@@ -41,7 +47,7 @@ struct CTLogOperatorInfo {
}; };
const CTLogInfo kCTLogList[] = { const CTLogInfo kCTLogList[] = {
{"Google 'Argon2025h1' log", CTLogState::Admissible, {"Google 'Argon2025h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -51,7 +57,7 @@ const CTLogInfo kCTLogList[] = {
"\xfb\xae\xbe\xc8\x23\x52\x20\x2b\xaa\x44\x05\xfe\x54\xf9\xd5\xf1\x1d\x45" "\xfb\xae\xbe\xc8\x23\x52\x20\x2b\xaa\x44\x05\xfe\x54\xf9\xd5\xf1\x1d\x45"
"\x9a", "\x9a",
91}, 91},
{"Google 'Argon2025h2' log", CTLogState::Admissible, {"Google 'Argon2025h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -61,7 +67,7 @@ const CTLogInfo kCTLogList[] = {
"\x56\x4e\x98\xe8\xaa\x26\x29\x36\x1e\x28\x60\x6f\xeb\x15\x6e\xf7\x7c\xd0" "\x56\x4e\x98\xe8\xaa\x26\x29\x36\x1e\x28\x60\x6f\xeb\x15\x6e\xf7\x7c\xd0"
"\xba", "\xba",
91}, 91},
{"Google 'Argon2026h1' log", CTLogState::Admissible, {"Google 'Argon2026h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z 1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -71,7 +77,7 @@ const CTLogInfo kCTLogList[] = {
"\x06\x4f\x64\x58\x75\x14\x5d\x56\x52\xe6\x6a\x2b\x14\x4c\xec\x81\xd1\xea" "\x06\x4f\x64\x58\x75\x14\x5d\x56\x52\xe6\x6a\x2b\x14\x4c\xec\x81\xd1\xea"
"\x3e", "\x3e",
91}, 91},
{"Google 'Argon2026h2' log", CTLogState::Admissible, {"Google 'Argon2026h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z 1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -81,7 +87,7 @@ const CTLogInfo kCTLogList[] = {
"\xf5\x35\x50\x9c\xa1\xd3\x49\x4d\x13\xd5\x3b\x6a\x0e\xea\x45\x9d\x24\x13" "\xf5\x35\x50\x9c\xa1\xd3\x49\x4d\x13\xd5\x3b\x6a\x0e\xea\x45\x9d\x24\x13"
"\x22", "\x22",
91}, 91},
{"Google 'Xenon2025h1' log", CTLogState::Admissible, {"Google 'Xenon2025h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -91,7 +97,7 @@ const CTLogInfo kCTLogList[] = {
"\x12\xe6\x54\x78\x50\xdc\xff\x6d\xfd\x1c\xa7\xb6\x3a\x1f\xf9\x26\xa9\x1b" "\x12\xe6\x54\x78\x50\xdc\xff\x6d\xfd\x1c\xa7\xb6\x3a\x1f\xf9\x26\xa9\x1b"
"\xbd", "\xbd",
91}, 91},
{"Google 'Xenon2025h2' log", CTLogState::Admissible, {"Google 'Xenon2025h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -101,7 +107,7 @@ const CTLogInfo kCTLogList[] = {
"\xa0\x3e\x9d\x94\x1c\xb2\xb7\x4a\xf2\x51\xec\x40\xed\x62\x47\xa4\x03\x49" "\xa0\x3e\x9d\x94\x1c\xb2\xb7\x4a\xf2\x51\xec\x40\xed\x62\x47\xa4\x03\x49"
"\x86", "\x86",
91}, 91},
{"Google 'Xenon2026h1' log", CTLogState::Admissible, {"Google 'Xenon2026h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z 1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -111,7 +117,7 @@ const CTLogInfo kCTLogList[] = {
"\xeb\x03\x0a\x30\xcc\x63\x6c\xd9\x3c\xbe\xf5\x7b\x94\xba\x94\xd3\xbf\x88" "\xeb\x03\x0a\x30\xcc\x63\x6c\xd9\x3c\xbe\xf5\x7b\x94\xba\x94\xd3\xbf\x88"
"\x4c", "\x4c",
91}, 91},
{"Google 'Xenon2026h2' log", CTLogState::Admissible, {"Google 'Xenon2026h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z 1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google 0, // operated by Google
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -121,7 +127,7 @@ const CTLogInfo kCTLogList[] = {
"\x8a\x72\x30\x65\x86\x43\x53\xdc\x11\x44\x18\x49\x98\x25\x68\xa7\x3c\x05" "\x8a\x72\x30\x65\x86\x43\x53\xdc\x11\x44\x18\x49\x98\x25\x68\xa7\x3c\x05"
"\xbf", "\xbf",
91}, 91},
{"Cloudflare 'Nimbus2025'", CTLogState::Admissible, {"Cloudflare 'Nimbus2025'", CTLogState::Admissible, CTLogFormat::RFC6962,
1702969200000, // 2023-12-19T07:00:00Z 1702969200000, // 2023-12-19T07:00:00Z
1, // operated by Cloudflare 1, // operated by Cloudflare
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -131,7 +137,7 @@ const CTLogInfo kCTLogList[] = {
"\x81\x5b\x4a\x14\x41\xec\xaf\xa9\x5d\x0e\xab\x12\x19\x71\xcd\x43\xef\xbb" "\x81\x5b\x4a\x14\x41\xec\xaf\xa9\x5d\x0e\xab\x12\x19\x71\xcd\x43\xef\xbb"
"\x97", "\x97",
91}, 91},
{"Cloudflare 'Nimbus2026'", CTLogState::Admissible, {"Cloudflare 'Nimbus2026'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731088800000, // 2024-11-08T18:00:00Z 1731088800000, // 2024-11-08T18:00:00Z
1, // operated by Cloudflare 1, // operated by Cloudflare
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -141,7 +147,7 @@ const CTLogInfo kCTLogList[] = {
"\xbf\x91\x64\x46\x6e\x0e\x27\x13\xea\xbb\x6f\x46\x27\x58\x86\xef\x40\x21" "\xbf\x91\x64\x46\x6e\x0e\x27\x13\xea\xbb\x6f\x46\x27\x58\x86\xef\x40\x21"
"\xa3", "\xa3",
91}, 91},
{"DigiCert Yeti2025 Log", CTLogState::Retired, {"DigiCert Yeti2025 Log", CTLogState::Retired, CTLogFormat::RFC6962,
1753315200000, // 2025-07-24T00:00:00Z 1753315200000, // 2025-07-24T00:00:00Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -151,7 +157,7 @@ const CTLogInfo kCTLogList[] = {
"\x95\x24\x7c\xd8\x91\x98\x48\x3b\xf0\xf0\xdf\x21\xf1\xb0\x81\x5a\x59\x25" "\x95\x24\x7c\xd8\x91\x98\x48\x3b\xf0\xf0\xdf\x21\xf1\xb0\x81\x5a\x59\x25"
"\x43", "\x43",
91}, 91},
{"DigiCert Nessie2025 Log", CTLogState::Retired, {"DigiCert Nessie2025 Log", CTLogState::Retired, CTLogFormat::RFC6962,
1744758000000, // 2025-04-15T23:00:00Z 1744758000000, // 2025-04-15T23:00:00Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -161,7 +167,7 @@ const CTLogInfo kCTLogList[] = {
"\x75\x80\xb7\x53\xa7\x85\xd5\xbc\xab\x47\x06\x55\xdb\xb5\xdf\x88\xa1\x6f" "\x75\x80\xb7\x53\xa7\x85\xd5\xbc\xab\x47\x06\x55\xdb\xb5\xdf\x88\xa1\x6f"
"\x38", "\x38",
91}, 91},
{"DigiCert 'Wyvern2025h1' Log", CTLogState::Retired, {"DigiCert 'Wyvern2025h1' Log", CTLogState::Retired, CTLogFormat::RFC6962,
1744670000000, // 2025-04-14T22:33:20Z 1744670000000, // 2025-04-14T22:33:20Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -171,7 +177,7 @@ const CTLogInfo kCTLogList[] = {
"\x0d\x96\x58\x44\x9d\x3b\x8a\x80\xc5\xc8\xbe\xe1\x89\x46\x6b\x48\x4c\xd6" "\x0d\x96\x58\x44\x9d\x3b\x8a\x80\xc5\xc8\xbe\xe1\x89\x46\x6b\x48\x4c\xd6"
"\x09", "\x09",
91}, 91},
{"DigiCert 'Wyvern2025h2' Log", CTLogState::Admissible, {"DigiCert 'Wyvern2025h2' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
1724900983000, // 2024-08-29T03:09:43Z 1724900983000, // 2024-08-29T03:09:43Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -181,7 +187,7 @@ const CTLogInfo kCTLogList[] = {
"\x1c\x63\xde\x95\xe2\x81\x69\x97\x8d\x1e\xa8\xb7\x66\x51\x25\x75\x4d\x78" "\x1c\x63\xde\x95\xe2\x81\x69\x97\x8d\x1e\xa8\xb7\x66\x51\x25\x75\x4d\x78"
"\x2e", "\x2e",
91}, 91},
{"DigiCert 'Wyvern2026h1'", CTLogState::Admissible, {"DigiCert 'Wyvern2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z 1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -191,7 +197,7 @@ const CTLogInfo kCTLogList[] = {
"\xd5\xd1\x83\xf8\x7a\xdf\x1e\x07\xbc\x15\xcd\xc0\x4a\xcd\x2a\x31\x71\x07" "\xd5\xd1\x83\xf8\x7a\xdf\x1e\x07\xbc\x15\xcd\xc0\x4a\xcd\x2a\x31\x71\x07"
"\x55", "\x55",
91}, 91},
{"DigiCert 'Wyvern2026h2'", CTLogState::Admissible, {"DigiCert 'Wyvern2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z 1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -201,7 +207,7 @@ const CTLogInfo kCTLogList[] = {
"\xde\x9b\x8c\x13\x92\xb7\xad\x3d\x0f\xa1\x9c\x8f\x48\xce\x74\x27\x18\x23" "\xde\x9b\x8c\x13\x92\xb7\xad\x3d\x0f\xa1\x9c\x8f\x48\xce\x74\x27\x18\x23"
"\x99", "\x99",
91}, 91},
{"DigiCert 'Sphinx2025h1' Log", CTLogState::Retired, {"DigiCert 'Sphinx2025h1' Log", CTLogState::Retired, CTLogFormat::RFC6962,
1744670000000, // 2025-04-14T22:33:20Z 1744670000000, // 2025-04-14T22:33:20Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -211,7 +217,7 @@ const CTLogInfo kCTLogList[] = {
"\x45\x31\x17\xd3\x8d\xf2\xe7\xce\x18\x11\x58\x98\x2c\x60\x6f\x58\x20\x36" "\x45\x31\x17\xd3\x8d\xf2\xe7\xce\x18\x11\x58\x98\x2c\x60\x6f\x58\x20\x36"
"\x6e", "\x6e",
91}, 91},
{"DigiCert 'Sphinx2025h2' Log", CTLogState::Admissible, {"DigiCert 'Sphinx2025h2' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
1724900983000, // 2024-08-29T03:09:43Z 1724900983000, // 2024-08-29T03:09:43Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -221,7 +227,7 @@ const CTLogInfo kCTLogList[] = {
"\x1a\x27\x54\x85\x5d\xc1\x7b\x24\xa8\x34\xe3\xcd\x88\xce\xd4\x50\x1b\xbe" "\x1a\x27\x54\x85\x5d\xc1\x7b\x24\xa8\x34\xe3\xcd\x88\xce\xd4\x50\x1b\xbe"
"\x69", "\x69",
91}, 91},
{"DigiCert 'Sphinx2026h1'", CTLogState::Admissible, {"DigiCert 'Sphinx2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z 1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -231,7 +237,7 @@ const CTLogInfo kCTLogList[] = {
"\xbc\xc3\x9e\x05\x02\x9a\x08\x01\xb5\x49\x23\x35\xc4\xd3\x50\x2b\x51\xe9" "\xbc\xc3\x9e\x05\x02\x9a\x08\x01\xb5\x49\x23\x35\xc4\xd3\x50\x2b\x51\xe9"
"\xf4", "\xf4",
91}, 91},
{"DigiCert 'Sphinx2026h2'", CTLogState::Admissible, {"DigiCert 'Sphinx2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z 1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert 2, // operated by DigiCert
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -241,7 +247,7 @@ const CTLogInfo kCTLogList[] = {
"\x38\x8c\xa4\x38\x2e\xac\x95\x0c\xeb\xed\x4f\x64\xbc\x45\x42\xf7\x06\x7a" "\x38\x8c\xa4\x38\x2e\xac\x95\x0c\xeb\xed\x4f\x64\xbc\x45\x42\xf7\x06\x7a"
"\xcd", "\xcd",
91}, 91},
{"Sectigo 'Sabre2025h1'", CTLogState::Admissible, {"Sectigo 'Sabre2025h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -251,7 +257,7 @@ const CTLogInfo kCTLogList[] = {
"\x41\xae\x70\xb3\x31\xa2\xe3\xfa\x3d\x5f\x2c\x5d\x04\xcd\xb4\x9d\x55\xab" "\x41\xae\x70\xb3\x31\xa2\xe3\xfa\x3d\x5f\x2c\x5d\x04\xcd\xb4\x9d\x55\xab"
"\x41", "\x41",
91}, 91},
{"Sectigo 'Sabre2025h2'", CTLogState::Admissible, {"Sectigo 'Sabre2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -261,7 +267,7 @@ const CTLogInfo kCTLogList[] = {
"\xaf\x89\x8b\xf5\x58\xd8\xba\xeb\x7b\x83\x52\xe9\xf4\xe0\xa5\xcd\xcd\x92" "\xaf\x89\x8b\xf5\x58\xd8\xba\xeb\x7b\x83\x52\xe9\xf4\xe0\xa5\xcd\xcd\x92"
"\xcc", "\xcc",
91}, 91},
{"Sectigo 'Mammoth2025h1'", CTLogState::Admissible, {"Sectigo 'Mammoth2025h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -271,7 +277,7 @@ const CTLogInfo kCTLogList[] = {
"\x9b\x4e\x72\x62\x4b\x3c\x0c\x32\xdd\x86\xfb\xeb\x3e\x66\xcd\x77\x58\x5b" "\x9b\x4e\x72\x62\x4b\x3c\x0c\x32\xdd\x86\xfb\xeb\x3e\x66\xcd\x77\x58\x5b"
"\xe5", "\xe5",
91}, 91},
{"Sectigo 'Mammoth2025h2'", CTLogState::Admissible, {"Sectigo 'Mammoth2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -281,7 +287,7 @@ const CTLogInfo kCTLogList[] = {
"\xb1\x15\x67\x66\xa0\x7c\x0b\x5b\x62\x7f\x6c\x9a\x6a\x30\x9b\x68\x02\x16" "\xb1\x15\x67\x66\xa0\x7c\x0b\x5b\x62\x7f\x6c\x9a\x6a\x30\x9b\x68\x02\x16"
"\x6f", "\x6f",
91}, 91},
{"Sectigo 'Mammoth2026h1'", CTLogState::Admissible, {"Sectigo 'Mammoth2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z 1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -291,7 +297,7 @@ const CTLogInfo kCTLogList[] = {
"\x9b\x50\x28\xe2\x9e\x58\xa5\xa5\xfa\xf9\xe3\xfa\x15\x25\xe3\x14\x13\x32" "\x9b\x50\x28\xe2\x9e\x58\xa5\xa5\xfa\xf9\xe3\xfa\x15\x25\xe3\x14\x13\x32"
"\xc4", "\xc4",
91}, 91},
{"Sectigo 'Mammoth2026h2'", CTLogState::Admissible, {"Sectigo 'Mammoth2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z 1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -301,7 +307,7 @@ const CTLogInfo kCTLogList[] = {
"\xff\xc2\x97\x71\xe5\x7e\x27\xf5\x72\xb1\x8f\x24\x27\x57\x0a\x0d\x74\xc0" "\xff\xc2\x97\x71\xe5\x7e\x27\xf5\x72\xb1\x8f\x24\x27\x57\x0a\x0d\x74\xc0"
"\xb6", "\xb6",
91}, 91},
{"Sectigo 'Sabre2026h1'", CTLogState::Admissible, {"Sectigo 'Sabre2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z 1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -311,7 +317,7 @@ const CTLogInfo kCTLogList[] = {
"\x46\x67\x51\xf0\xde\xb6\xc9\x9e\xaa\xe2\x80\x6d\xce\x25\x81\x34\xd7\x6a" "\x46\x67\x51\xf0\xde\xb6\xc9\x9e\xaa\xe2\x80\x6d\xce\x25\x81\x34\xd7\x6a"
"\x60", "\x60",
91}, 91},
{"Sectigo 'Sabre2026h2'", CTLogState::Admissible, {"Sectigo 'Sabre2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z 1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -321,7 +327,7 @@ const CTLogInfo kCTLogList[] = {
"\x65\x35\x63\xf0\x49\xbe\x72\xd1\xaa\x9d\xaf\x7d\x08\xc4\xb4\x8d\x59\x3d" "\x65\x35\x63\xf0\x49\xbe\x72\xd1\xaa\x9d\xaf\x7d\x08\xc4\xb4\x8d\x59\x3d"
"\x73", "\x73",
91}, 91},
{"Sectigo 'Elephant2025h2'", CTLogState::Admissible, {"Sectigo 'Elephant2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z 1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -331,7 +337,7 @@ const CTLogInfo kCTLogList[] = {
"\xc3\xc4\x42\xc1\x5b\x0a\x85\x16\xce\xa8\xc1\x0e\xc5\x6e\x10\xda\x9e\x0a" "\xc3\xc4\x42\xc1\x5b\x0a\x85\x16\xce\xa8\xc1\x0e\xc5\x6e\x10\xda\x9e\x0a"
"\x42", "\x42",
91}, 91},
{"Sectigo 'Elephant2026h1'", CTLogState::Admissible, {"Sectigo 'Elephant2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z 1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -341,7 +347,7 @@ const CTLogInfo kCTLogList[] = {
"\xc9\x65\x87\xe0\xd4\x7f\x0c\x22\x5a\xd9\xb0\x2e\x98\x7a\xd7\x25\xd0\x1c" "\xc9\x65\x87\xe0\xd4\x7f\x0c\x22\x5a\xd9\xb0\x2e\x98\x7a\xd7\x25\xd0\x1c"
"\x69", "\x69",
91}, 91},
{"Sectigo 'Elephant2026h2'", CTLogState::Admissible, {"Sectigo 'Elephant2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z 1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -351,7 +357,7 @@ const CTLogInfo kCTLogList[] = {
"\x09\xfc\xdd\x0f\xbc\x5c\x56\x39\x90\x62\x96\xed\x35\x48\x71\x44\xc4\x6d" "\x09\xfc\xdd\x0f\xbc\x5c\x56\x39\x90\x62\x96\xed\x35\x48\x71\x44\xc4\x6d"
"\x98", "\x98",
91}, 91},
{"Sectigo 'Elephant2027h1'", CTLogState::Admissible, {"Sectigo 'Elephant2027h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z 1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -361,7 +367,7 @@ const CTLogInfo kCTLogList[] = {
"\xa0\x6a\xda\x7f\xb0\x64\xcc\xa6\x5f\xec\xf0\xbc\x81\x80\x12\x73\x0d\xb0" "\xa0\x6a\xda\x7f\xb0\x64\xcc\xa6\x5f\xec\xf0\xbc\x81\x80\x12\x73\x0d\xb0"
"\xa0", "\xa0",
91}, 91},
{"Sectigo 'Elephant2027h2'", CTLogState::Admissible, {"Sectigo 'Elephant2027h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z 1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -371,7 +377,7 @@ const CTLogInfo kCTLogList[] = {
"\x1e\xdc\x8a\xec\x20\x61\x7e\x52\x25\x32\x4e\xd3\xd9\x0a\xe7\xe3\x0f\xed" "\x1e\xdc\x8a\xec\x20\x61\x7e\x52\x25\x32\x4e\xd3\xd9\x0a\xe7\xe3\x0f\xed"
"\xf2", "\xf2",
91}, 91},
{"Sectigo 'Tiger2025h2'", CTLogState::Admissible, {"Sectigo 'Tiger2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z 1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -381,7 +387,7 @@ const CTLogInfo kCTLogList[] = {
"\xd8\xa1\x24\x59\x2f\xb8\x4f\xbf\xdb\x60\xe5\xef\xe1\xd0\xcd\xcf\x3a\xc4" "\xd8\xa1\x24\x59\x2f\xb8\x4f\xbf\xdb\x60\xe5\xef\xe1\xd0\xcd\xcf\x3a\xc4"
"\xc6", "\xc6",
91}, 91},
{"Sectigo 'Tiger2026h1'", CTLogState::Admissible, {"Sectigo 'Tiger2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z 1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -391,7 +397,7 @@ const CTLogInfo kCTLogList[] = {
"\xef\xf5\xd0\x63\x03\x85\xb7\xd1\x5e\x6f\xc4\xf0\x41\x6f\xa6\xa9\x73\x79" "\xef\xf5\xd0\x63\x03\x85\xb7\xd1\x5e\x6f\xc4\xf0\x41\x6f\xa6\xa9\x73\x79"
"\xc6", "\xc6",
91}, 91},
{"Sectigo 'Tiger2026h2'", CTLogState::Admissible, {"Sectigo 'Tiger2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z 1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -401,7 +407,7 @@ const CTLogInfo kCTLogList[] = {
"\x08\x44\xb5\x10\xbd\x41\xfb\x60\x44\x0b\xe3\xf9\x6d\x47\xf0\x17\x8e\x25" "\x08\x44\xb5\x10\xbd\x41\xfb\x60\x44\x0b\xe3\xf9\x6d\x47\xf0\x17\x8e\x25"
"\x9c", "\x9c",
91}, 91},
{"Sectigo 'Tiger2027h1'", CTLogState::Admissible, {"Sectigo 'Tiger2027h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z 1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -411,7 +417,7 @@ const CTLogInfo kCTLogList[] = {
"\xdc\xd8\x27\x5c\x35\xf0\x9b\x22\x5c\x17\x04\x58\x81\x53\x81\x32\x16\x98" "\xdc\xd8\x27\x5c\x35\xf0\x9b\x22\x5c\x17\x04\x58\x81\x53\x81\x32\x16\x98"
"\x84", "\x84",
91}, 91},
{"Sectigo 'Tiger2027h2'", CTLogState::Admissible, {"Sectigo 'Tiger2027h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z 1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo 3, // operated by Sectigo
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -421,7 +427,7 @@ const CTLogInfo kCTLogList[] = {
"\xa3\x4f\x6b\xa3\x37\xdd\xaa\x18\xde\x8a\x12\x25\xdb\x9c\xbd\x03\x72\x61" "\xa3\x4f\x6b\xa3\x37\xdd\xaa\x18\xde\x8a\x12\x25\xdb\x9c\xbd\x03\x72\x61"
"\xc9", "\xc9",
91}, 91},
{"Let's Encrypt 'Oak2025h1'", CTLogState::Admissible, {"Let's Encrypt 'Oak2025h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
4, // operated by Let's Encrypt 4, // operated by Let's Encrypt
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -431,7 +437,7 @@ const CTLogInfo kCTLogList[] = {
"\xf9\x3c\x58\x54\x5b\x37\x10\xb1\xab\xd8\x83\xfb\x84\xf1\x95\x3f\x2e\x2f" "\xf9\x3c\x58\x54\x5b\x37\x10\xb1\xab\xd8\x83\xfb\x84\xf1\x95\x3f\x2e\x2f"
"\x1c", "\x1c",
91}, 91},
{"Let's Encrypt 'Oak2025h2'", CTLogState::Admissible, {"Let's Encrypt 'Oak2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
4, // operated by Let's Encrypt 4, // operated by Let's Encrypt
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -441,7 +447,7 @@ const CTLogInfo kCTLogList[] = {
"\xc9\xd7\x3d\xbb\xc1\xf7\x71\x86\x69\xf4\xb3\x5f\x90\x09\xaa\xae\xbd\x8d" "\xc9\xd7\x3d\xbb\xc1\xf7\x71\x86\x69\xf4\xb3\x5f\x90\x09\xaa\xae\xbd\x8d"
"\xa9", "\xa9",
91}, 91},
{"Let's Encrypt 'Oak2026h1'", CTLogState::Admissible, {"Let's Encrypt 'Oak2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1730678400000, // 2024-11-04T00:00:00Z 1730678400000, // 2024-11-04T00:00:00Z
4, // operated by Let's Encrypt 4, // operated by Let's Encrypt
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -451,7 +457,7 @@ const CTLogInfo kCTLogList[] = {
"\x48\x90\x23\x40\xde\x7a\x4d\x89\x32\xfb\xd7\x0a\xeb\x5e\x8c\xa2\xf1\xf6" "\x48\x90\x23\x40\xde\x7a\x4d\x89\x32\xfb\xd7\x0a\xeb\x5e\x8c\xa2\xf1\xf6"
"\x49", "\x49",
91}, 91},
{"Let's Encrypt 'Oak2026h2'", CTLogState::Admissible, {"Let's Encrypt 'Oak2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1730678400000, // 2024-11-04T00:00:00Z 1730678400000, // 2024-11-04T00:00:00Z
4, // operated by Let's Encrypt 4, // operated by Let's Encrypt
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -461,7 +467,7 @@ const CTLogInfo kCTLogList[] = {
"\xf4\xfc\x5c\xa9\x8c\x5f\xfb\x0d\x60\xe4\x2c\x0f\x16\xec\x2a\xb2\x6d\xeb" "\xf4\xfc\x5c\xa9\x8c\x5f\xfb\x0d\x60\xe4\x2c\x0f\x16\xec\x2a\xb2\x6d\xeb"
"\x15", "\x15",
91}, 91},
{"TrustAsia Log2025a", CTLogState::Admissible, {"TrustAsia Log2025a", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
5, // operated by TrustAsia 5, // operated by TrustAsia
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -471,7 +477,7 @@ const CTLogInfo kCTLogList[] = {
"\x90\x58\xba\x22\xd5\xf9\xf5\x69\x54\xb7\xa8\x94\x4e\x32\x09\xae\x26\x11" "\x90\x58\xba\x22\xd5\xf9\xf5\x69\x54\xb7\xa8\x94\x4e\x32\x09\xae\x26\x11"
"\x4d", "\x4d",
91}, 91},
{"TrustAsia Log2025b", CTLogState::Admissible, {"TrustAsia Log2025b", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z 1701000000000, // 2023-11-26T12:00:00Z
5, // operated by TrustAsia 5, // operated by TrustAsia
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -481,7 +487,7 @@ const CTLogInfo kCTLogList[] = {
"\xbd\x2f\xa9\xcf\xe8\x7b\x5e\xe1\x4b\x60\xe5\x38\x43\x60\x97\xc1\x5b\x2f" "\xbd\x2f\xa9\xcf\xe8\x7b\x5e\xe1\x4b\x60\xe5\x38\x43\x60\x97\xc1\x5b\x2f"
"\x65", "\x65",
91}, 91},
{"TrustAsia 'log2026a'", CTLogState::Admissible, {"TrustAsia 'log2026a'", CTLogState::Admissible, CTLogFormat::RFC6962,
1726790400000, // 2024-09-20T00:00:00Z 1726790400000, // 2024-09-20T00:00:00Z
5, // operated by TrustAsia 5, // operated by TrustAsia
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -491,7 +497,7 @@ const CTLogInfo kCTLogList[] = {
"\x89\x17\xe8\x5b\x2e\xc5\xac\x00\x05\xc9\x76\x37\x45\x97\x03\x15\xff\x60" "\x89\x17\xe8\x5b\x2e\xc5\xac\x00\x05\xc9\x76\x37\x45\x97\x03\x15\xff\x60"
"\x59", "\x59",
91}, 91},
{"TrustAsia 'log2026b'", CTLogState::Admissible, {"TrustAsia 'log2026b'", CTLogState::Admissible, CTLogFormat::RFC6962,
1726790400000, // 2024-09-20T00:00:00Z 1726790400000, // 2024-09-20T00:00:00Z
5, // operated by TrustAsia 5, // operated by TrustAsia
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -501,7 +507,7 @@ const CTLogInfo kCTLogList[] = {
"\x9d\x7d\x05\x53\xc7\x9e\x94\xea\x9b\x57\x46\xbf\x4f\xa4\x7e\xfb\xdf\xfa" "\x9d\x7d\x05\x53\xc7\x9e\x94\xea\x9b\x57\x46\xbf\x4f\xa4\x7e\xfb\xdf\xfa"
"\x85", "\x85",
91}, 91},
{"Bogus placeholder log to unbreak misbehaving CT libraries", CTLogState::Retired, {"Bogus placeholder log to unbreak misbehaving CT libraries", CTLogState::Retired, CTLogFormat::RFC6962,
1750489200000, // 2025-06-21T07:00:00Z 1750489200000, // 2025-06-21T07:00:00Z
6, // operated by Geomys 6, // operated by Geomys
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -511,8 +517,38 @@ const CTLogInfo kCTLogList[] = {
"\x75\xe3\x66\x75\xa9\x59\x70\x2d\xe2\x5a\x8b\xc0\x7c\x0a\x6f\x5d\x2d\xf7" "\x75\xe3\x66\x75\xa9\x59\x70\x2d\xe2\x5a\x8b\xc0\x7c\x0a\x6f\x5d\x2d\xf7"
"\x37", "\x37",
91}, 91},
{"Geomys 'Tuscolo2025h2'", CTLogState::Admissible, CTLogFormat::Tiled,
1749782400000, // 2025-06-13T02:40:00Z
6, // operated by Geomys
"\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\x2b\xd7\x78\x18\x6b\x73\x6e\x4c\x30"
"\xb1\x8a\x44\xb6\xf9\xd4\xf4\xa2\xa0\x66\xbe\xbb\x32\xc1\xa5\x07\xb9\xa7"
"\x17\xc0\xd6\xf5\xb1\xe7\x9e\x01\xf3\x3f\x35\xcf\x6d\xda\x3b\x9f\xe1\x72"
"\x0b\x04\x83\x62\xa1\x07\x6d\xa9\x75\x67\x40\x82\x57\x26\x43\xeb\x04\x3f"
"\xa3",
91},
{"Geomys 'Tuscolo2026h1'", CTLogState::Admissible, CTLogFormat::Tiled,
1749782400000, // 2025-06-13T02:40:00Z
6, // operated by Geomys
"\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\x5c\x73\x32\x0d\x80\x8e\x38\x3b"
"\x87\x5f\x99\x22\xf4\x3d\x2d\x5e\xb2\x16\xf7\x63\xea\xe4\x62\xf5\x86\xef"
"\xb5\x19\xd2\x0a\x04\xb6\x49\xa9\xa4\x1f\x9e\x96\x70\xf4\x5a\x05\x34\x58"
"\x01\x13\xc3\x22\xbe\x49\xc0\xba\xa0\x24\x05\x40\xfc\xdb\xc3\xc5\xd2\x57"
"\x3b",
91},
{"Geomys 'Tuscolo2026h2'", CTLogState::Admissible, CTLogFormat::Tiled,
1749782400000, // 2025-06-13T02:40:00Z
6, // operated by Geomys
"\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\x0e\x8f\xd2\x2e\xc9\x4e\xc7\x7d"
"\x5d\xfc\xc5\xd7\xfe\xfa\x6a\xf4\x56\x03\x75\xd7\x23\x83\x52\xb6\xc1\x4e"
"\xfd\xa1\x6d\x06\x9e\x92\x63\xba\x25\x0c\x43\x22\x3d\x21\x52\xc4\x6c\xc5"
"\x42\x32\x80\xd6\xbf\x6f\x80\x6f\xe8\x15\x0c\x2e\xae\x55\x8c\xcb\xd1\x50"
"\x37",
91},
#ifdef DEBUG #ifdef DEBUG
{"Mozilla Test RSA Log 1", CTLogState::Admissible, {"Mozilla Test RSA Log 1", CTLogState::Admissible, CTLogFormat::RFC6962,
1721666666000, // 2024-07-22T16:44:26Z 1721666666000, // 2024-07-22T16:44:26Z
7, // operated by Mozilla Test Org 1 7, // operated by Mozilla Test Org 1
"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05" "\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
@@ -535,7 +571,7 @@ const CTLogInfo kCTLogList[] = {
294}, 294},
#endif // DEBUG #endif // DEBUG
#ifdef DEBUG #ifdef DEBUG
{"Mozilla Test EC Log", CTLogState::Admissible, {"Mozilla Test EC Log", CTLogState::Admissible, CTLogFormat::RFC6962,
1721666666000, // 2024-07-22T16:44:26Z 1721666666000, // 2024-07-22T16:44:26Z
7, // operated by Mozilla Test Org 1 7, // operated by Mozilla Test Org 1
"\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48" "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
@@ -547,7 +583,7 @@ const CTLogInfo kCTLogList[] = {
91}, 91},
#endif // DEBUG #endif // DEBUG
#ifdef DEBUG #ifdef DEBUG
{"Mozilla Test RSA Log 2", CTLogState::Admissible, {"Mozilla Test RSA Log 2", CTLogState::Admissible, CTLogFormat::RFC6962,
1721666666000, // 2024-07-22T16:44:26Z 1721666666000, // 2024-07-22T16:44:26Z
8, // operated by Mozilla Test Org 2 8, // operated by Mozilla Test Org 2
"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05" "\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
@@ -569,6 +605,29 @@ const CTLogInfo kCTLogList[] = {
"\xbb\x02\x03\x01\x00\x01", "\xbb\x02\x03\x01\x00\x01",
294}, 294},
#endif // DEBUG #endif // DEBUG
#ifdef DEBUG
{"Mozilla Test RSA Log 4", CTLogState::Admissible, CTLogFormat::Tiled,
1750853366000, // 2025-06-25T12:09:26Z
9, // operated by Mozilla Test Org 3
"\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
"\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xb5\x49\x89"
"\x5c\x9d\x00\x10\x8d\x11\xa1\xf9\x9f\x87\xa9\xe3\xd1\xa5\xdb\x5d\xfa\xec"
"\xf1\x88\xda\x57\xbf\x64\x13\x68\x8f\x2c\xe4\x72\x2c\xff\x10\x90\x38\xc1"
"\x74\x02\xc9\x3a\x2a\x47\x3d\xbd\x28\x6a\xed\x3f\xdc\xd3\x63\xcf\x5a\x29"
"\x14\x77\x01\xbd\xd8\x18\xd7\x61\x5b\xf9\x35\x6b\xd5\xd3\xc8\x33\x6a\xaa"
"\x8c\x09\x71\x36\x8a\x06\xc3\xcd\x44\x61\xb9\x3e\x51\x42\x4e\x17\x44\xbb"
"\x2e\xaa\xd4\x6a\xab\x38\xce\x19\x68\x21\x96\x1f\x87\x71\x4a\x16\x63\x69"
"\x3f\x09\x76\x1c\xdf\x4d\x6b\xa1\x25\xea\xce\xc7\xbe\x27\x0d\x38\x8f\x78"
"\x9f\x6c\xdf\x78\xae\x31\x44\xed\x28\xc4\x5e\x79\x29\x38\x63\xa7\xa2\x2a"
"\x48\x98\x0a\x36\xa4\x0e\x72\xd5\x79\xc9\xb9\x25\xdf\xf8\xc7\x93\x36\x2f"
"\xfd\x68\x97\xa7\xc1\x75\x4c\x5e\x97\xc9\x67\xc3\xea\xdd\x1a\xae\x8a\xa2"
"\xcc\xce\x34\x8a\x01\x69\xb8\x0e\x28\xa2\xd7\x0c\x1a\x96\x0c\x6f\x33\x5f"
"\x2d\xa0\x9b\x9b\x64\x3f\x5a\xbf\xba\x49\xe8\xaa\xa9\x81\xe9\x60\xe2\x7d"
"\x87\x48\x0b\xdd\x55\xdd\x94\x17\xfa\x18\x50\x9f\xbb\x55\x4c\xcf\x81\xa4"
"\x39\x7e\x8b\xa8\x12\x8a\x34\xbd\xf2\x78\x65\xc1\x89\xe5\x73\x4f\xb2\x29"
"\x05\x02\x03\x01\x00\x01",
294},
#endif // DEBUG
}; };
const CTLogOperatorInfo kCTLogOperatorList[] = { const CTLogOperatorInfo kCTLogOperatorList[] = {
@@ -585,6 +644,9 @@ const CTLogOperatorInfo kCTLogOperatorList[] = {
#ifdef DEBUG #ifdef DEBUG
{"Mozilla Test Org 2", 8}, {"Mozilla Test Org 2", 8},
#endif // DEBUG #endif // DEBUG
#ifdef DEBUG
{"Mozilla Test Org 3", 9},
#endif // DEBUG
}; };
} // namespace mozilla::ct } // namespace mozilla::ct

View File

@@ -114,10 +114,11 @@ class SignatureParamsTrustDomain final : public TrustDomain {
}; };
CTLogVerifier::CTLogVerifier(CTLogOperatorId operatorId, CTLogState state, CTLogVerifier::CTLogVerifier(CTLogOperatorId operatorId, CTLogState state,
uint64_t timestamp) CTLogFormat format, uint64_t timestamp)
: mSignatureAlgorithm(DigitallySigned::SignatureAlgorithm::Anonymous), : mSignatureAlgorithm(DigitallySigned::SignatureAlgorithm::Anonymous),
mOperatorId(operatorId), mOperatorId(operatorId),
mState(state), mState(state),
mFormat(format),
mTimestamp(timestamp) {} mTimestamp(timestamp) {}
pkix::Result CTLogVerifier::Init(Input subjectPublicKeyInfo) { pkix::Result CTLogVerifier::Init(Input subjectPublicKeyInfo) {

View File

@@ -30,9 +30,10 @@ class CTLogVerifier {
public: public:
// |operatorId| The numeric ID of the log operator. // |operatorId| The numeric ID of the log operator.
// |logState| "Qualified", "Usable", "ReadOnly", or "Retired". // |logState| "Qualified", "Usable", "ReadOnly", or "Retired".
// |logFormat| "RFC6962" or "Tiled"
// |timestamp| timestamp associated with logState. // |timestamp| timestamp associated with logState.
CTLogVerifier(CTLogOperatorId operatorId, CTLogState logState, CTLogVerifier(CTLogOperatorId operatorId, CTLogState logState,
uint64_t timestamp); CTLogFormat logFormat, uint64_t timestamp);
// Initializes the verifier with the given subjectPublicKeyInfo. // Initializes the verifier with the given subjectPublicKeyInfo.
// |subjectPublicKeyInfo| is a DER-encoded SubjectPublicKeyInfo. // |subjectPublicKeyInfo| is a DER-encoded SubjectPublicKeyInfo.
@@ -46,6 +47,7 @@ class CTLogVerifier {
CTLogOperatorId operatorId() const { return mOperatorId; } CTLogOperatorId operatorId() const { return mOperatorId; }
CTLogState state() const { return mState; } CTLogState state() const { return mState; }
CTLogFormat format() const { return mFormat; }
uint64_t timestamp() const { return mTimestamp; } uint64_t timestamp() const { return mTimestamp; }
// Verifies that |sct| contains a valid signature for |entry|. // Verifies that |sct| contains a valid signature for |entry|.
@@ -74,6 +76,7 @@ class CTLogVerifier {
DigitallySigned::SignatureAlgorithm mSignatureAlgorithm; DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
CTLogOperatorId mOperatorId; CTLogOperatorId mOperatorId;
CTLogState mState; CTLogState mState;
CTLogFormat mFormat;
uint64_t mTimestamp; uint64_t mTimestamp;
}; };

View File

@@ -75,11 +75,13 @@ bool LogWasQualifiedForSct(const VerifiedSCT& verifiedSct,
// lifetime of the certificate. If the certificate lifetime is less than or // lifetime of the certificate. If the certificate lifetime is less than or
// equal to 180 days, N is 2. Otherwise, N is 3. // equal to 180 days, N is 2. Otherwise, N is 3.
// Among these SCTs, at least two must be issued from distinct log operators. // Among these SCTs, at least two must be issued from distinct log operators.
// Additionally, at least one must be issued from an RFC6962 log.
CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts, CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
uint64_t certIssuanceTime, uint64_t certIssuanceTime,
Duration certLifetime) { Duration certLifetime) {
size_t admissibleCount = 0; size_t admissibleCount = 0;
size_t admissibleOrRetiredCount = 0; size_t admissibleOrRetiredCount = 0;
size_t rfc6962Count = 0;
std::set<CTLogOperatorId> logOperators; std::set<CTLogOperatorId> logOperators;
std::set<Buffer> logIds; std::set<Buffer> logIds;
for (const auto& verifiedSct : verifiedScts) { for (const auto& verifiedSct : verifiedScts) {
@@ -90,6 +92,14 @@ CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
!LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) { !LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) {
continue; continue;
} }
// SCTs from tiled logs "MUST" have a valid leaf index extension.
if (verifiedSct.logFormat == CTLogFormat::Tiled &&
verifiedSct.sct.leafIndex.isNothing()) {
continue;
}
if (verifiedSct.logFormat == CTLogFormat::RFC6962) {
rfc6962Count++;
}
// Note that a single SCT can count for both the "from a log that was // Note that a single SCT can count for both the "from a log that was
// admissible" case and the "from a log that was admissible or retired" // admissible" case and the "from a log that was admissible or retired"
// case. // case.
@@ -104,7 +114,8 @@ CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
} }
size_t requiredEmbeddedScts = GetRequiredEmbeddedSctsCount(certLifetime); size_t requiredEmbeddedScts = GetRequiredEmbeddedSctsCount(certLifetime);
if (admissibleCount < 1 || admissibleOrRetiredCount < requiredEmbeddedScts) { if (admissibleCount < 1 || admissibleOrRetiredCount < requiredEmbeddedScts ||
rfc6962Count < 1) {
return CTPolicyCompliance::NotEnoughScts; return CTPolicyCompliance::NotEnoughScts;
} }
if (logIds.size() < requiredEmbeddedScts || logOperators.size() < 2) { if (logIds.size() < requiredEmbeddedScts || logOperators.size() < 2) {
@@ -117,10 +128,12 @@ CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
// or OCSP response): // or OCSP response):
// There must be at least two SCTs from logs that were Admissible (i.e. // There must be at least two SCTs from logs that were Admissible (i.e.
// Qualified, Usable, or ReadOnly) at the time of the check. Among these SCTs, // Qualified, Usable, or ReadOnly) at the time of the check. Among these SCTs,
// at least two must be issued from distinct log operators. // at least two must be issued from distinct log operators. Additionally, at
// least one must be issued from an RFC6962 log.
CTPolicyCompliance NonEmbeddedSCTsCompliant( CTPolicyCompliance NonEmbeddedSCTsCompliant(
const VerifiedSCTList& verifiedScts) { const VerifiedSCTList& verifiedScts) {
size_t admissibleCount = 0; size_t admissibleCount = 0;
size_t rfc6962Count = 0;
std::set<CTLogOperatorId> logOperators; std::set<CTLogOperatorId> logOperators;
std::set<Buffer> logIds; std::set<Buffer> logIds;
for (const auto& verifiedSct : verifiedScts) { for (const auto& verifiedSct : verifiedScts) {
@@ -130,12 +143,20 @@ CTPolicyCompliance NonEmbeddedSCTsCompliant(
if (verifiedSct.logState != CTLogState::Admissible) { if (verifiedSct.logState != CTLogState::Admissible) {
continue; continue;
} }
// SCTs from tiled logs "MUST" have a valid leaf index extension.
if (verifiedSct.logFormat == CTLogFormat::Tiled &&
verifiedSct.sct.leafIndex.isNothing()) {
continue;
}
admissibleCount++; admissibleCount++;
if (verifiedSct.logFormat == CTLogFormat::RFC6962) {
rfc6962Count++;
}
logIds.insert(verifiedSct.sct.logId); logIds.insert(verifiedSct.sct.logId);
logOperators.insert(verifiedSct.logOperatorId); logOperators.insert(verifiedSct.logOperatorId);
} }
if (admissibleCount < 2) { if (admissibleCount < 2 || rfc6962Count < 1) {
return CTPolicyCompliance::NotEnoughScts; return CTPolicyCompliance::NotEnoughScts;
} }
if (logIds.size() < 2 || logOperators.size() < 2) { if (logIds.size() < 2 || logOperators.size() < 2) {

View File

@@ -360,6 +360,11 @@ Result DecodeSignedCertificateTimestamp(Reader& reader,
InputToBuffer(extensions, result.extensions); InputToBuffer(extensions, result.extensions);
result.timestamp = timestamp; result.timestamp = timestamp;
rv = result.DecodeExtensions();
if (rv != Success) {
return rv;
}
output = std::move(result); output = std::move(result);
return Success; return Success;
} }

View File

@@ -13,11 +13,12 @@ namespace ct {
VerifiedSCT::VerifiedSCT(SignedCertificateTimestamp&& sct, SCTOrigin origin, VerifiedSCT::VerifiedSCT(SignedCertificateTimestamp&& sct, SCTOrigin origin,
CTLogOperatorId logOperatorId, CTLogState logState, CTLogOperatorId logOperatorId, CTLogState logState,
uint64_t logTimestamp) CTLogFormat logFormat, uint64_t logTimestamp)
: sct(std::move(sct)), : sct(std::move(sct)),
origin(origin), origin(origin),
logOperatorId(logOperatorId), logOperatorId(logOperatorId),
logState(logState), logState(logState),
logFormat(logFormat),
logTimestamp(logTimestamp) {} logTimestamp(logTimestamp) {}
void CTVerifyResult::Reset() { void CTVerifyResult::Reset() {

View File

@@ -28,13 +28,14 @@ enum class SCTOrigin {
struct VerifiedSCT { struct VerifiedSCT {
VerifiedSCT(SignedCertificateTimestamp&& sct, SCTOrigin origin, VerifiedSCT(SignedCertificateTimestamp&& sct, SCTOrigin origin,
CTLogOperatorId logOperatorId, CTLogState logState, CTLogOperatorId logOperatorId, CTLogState logState,
uint64_t logTimestamp); CTLogFormat logFormat, uint64_t logTimestamp);
// The original SCT. // The original SCT.
SignedCertificateTimestamp sct; SignedCertificateTimestamp sct;
SCTOrigin origin; SCTOrigin origin;
CTLogOperatorId logOperatorId; CTLogOperatorId logOperatorId;
CTLogState logState; CTLogState logState;
CTLogFormat logFormat;
uint64_t logTimestamp; uint64_t logTimestamp;
}; };

View File

@@ -178,7 +178,8 @@ pkix::Result MultiLogCTVerifier::VerifySingleSCT(
} }
VerifiedSCT verifiedSct(std::move(sct), origin, matchingLog->operatorId(), VerifiedSCT verifiedSct(std::move(sct), origin, matchingLog->operatorId(),
matchingLog->state(), matchingLog->timestamp()); matchingLog->state(), matchingLog->format(),
matchingLog->timestamp());
result.verifiedScts.push_back(std::move(verifiedSct)); result.verifiedScts.push_back(std::move(verifiedSct));
return Success; return Success;
} }

View File

@@ -6,9 +6,72 @@
#include "SignedCertificateTimestamp.h" #include "SignedCertificateTimestamp.h"
#include "CTUtils.h"
namespace mozilla { namespace mozilla {
namespace ct { namespace ct {
pkix::Result SignedCertificateTimestamp::DecodeExtensions() {
if (extensions.empty()) {
return pkix::Success;
}
// `extensions` is a sequence of Extension:
// struct {
// ExtensionType extension_type;
// opaque extension_data<0..2^16-1>;
// } Extension;
const size_t kExtensionDataLengthBytes = 2;
// Currently, the only supported extension type is `leaf_index`. Others are
// ignored.
// enum {
// leaf_index(0), (255)
// } ExtensionType;
const size_t kExtensionTypeLength = 1;
const uint8_t kExtensionTypeLeafIndex = 0;
pkix::Input input;
pkix::Result rv = input.Init(extensions.data(), extensions.size());
if (rv != pkix::Success) {
return rv;
}
pkix::Reader reader(input);
while (!reader.AtEnd()) {
uint8_t extensionType;
rv = ReadUint<kExtensionTypeLength>(reader, extensionType);
if (rv != pkix::Success) {
return rv;
}
pkix::Input extensionData;
rv = ReadVariableBytes<kExtensionDataLengthBytes>(reader, extensionData);
if (rv != pkix::Success) {
return rv;
}
if (extensionType == kExtensionTypeLeafIndex) {
// Duplicate extensions are not allowed.
if (leafIndex.isSome()) {
return pkix::Result::ERROR_EXTENSION_VALUE_INVALID;
}
// A leaf index is a big-endian, unsigned 40-bit value. In other words,
// it is 5 8-bit bytes, like so:
// uint8 uint40[5];
// uint40 LeafIndex;
const size_t kLeafIndexLength = 5;
uint64_t leafIndexValue;
pkix::Reader leafIndexReader(extensionData);
rv = ReadUint<kLeafIndexLength>(leafIndexReader, leafIndexValue);
if (rv != pkix::Success) {
return rv;
}
if (!leafIndexReader.AtEnd()) {
return pkix::Result::ERROR_EXTENSION_VALUE_INVALID;
}
leafIndex.emplace(leafIndexValue);
}
}
return pkix::Success;
}
void LogEntry::Reset() { void LogEntry::Reset() {
type = LogEntry::Type::X509; type = LogEntry::Type::X509;
leafCertificate.clear(); leafCertificate.clear();

View File

@@ -8,6 +8,7 @@
#define SignedCertificateTimestamp_h #define SignedCertificateTimestamp_h
#include "Buffer.h" #include "Buffer.h"
#include "mozilla/Maybe.h"
#include "mozpkix/Input.h" #include "mozpkix/Input.h"
#include "mozpkix/Result.h" #include "mozpkix/Result.h"
@@ -65,12 +66,17 @@ struct SignedCertificateTimestamp {
V1 = 0, V1 = 0,
}; };
pkix::Result DecodeExtensions();
Version version; Version version;
Buffer logId; Buffer logId;
// "timestamp" is the current time in milliseconds, measured since the epoch, // "timestamp" is the current time in milliseconds, measured since the epoch,
// ignoring leap seconds. See RFC 6962, Section 3.2. // ignoring leap seconds. See RFC 6962, Section 3.2.
uint64_t timestamp; uint64_t timestamp;
Buffer extensions; Buffer extensions;
// Maybe the index of the entry in the log, if specified by a LeafIndex
// extension in `extensions`.
Maybe<uint64_t> leafIndex;
DigitallySigned signature; DigitallySigned signature;
}; };

View File

@@ -33,7 +33,8 @@ class CTLogVerifierTest : public ::testing::Test {
void TearDown() override { signature_cache_free(mSignatureCache); } void TearDown() override { signature_cache_free(mSignatureCache); }
protected: protected:
CTLogVerifier mLog = CTLogVerifier(-1, CTLogState::Admissible, 0); CTLogVerifier mLog =
CTLogVerifier(-1, CTLogState::Admissible, CTLogFormat::RFC6962, 0);
// For some reason, the templating makes it impossible to use UniquePtr here. // For some reason, the templating makes it impossible to use UniquePtr here.
SignatureCache* mSignatureCache; SignatureCache* mSignatureCache;
}; };
@@ -115,7 +116,7 @@ TEST_F(CTLogVerifierTest, ExcessDataInPublicKey) {
std::string extra = "extra"; std::string extra = "extra";
key.insert(key.end(), extra.begin(), extra.end()); key.insert(key.end(), extra.begin(), extra.end());
CTLogVerifier log(-1, CTLogState::Admissible, 0); CTLogVerifier log(-1, CTLogState::Admissible, CTLogFormat::RFC6962, 0);
EXPECT_NE(Success, log.Init(InputForBuffer(key))); EXPECT_NE(Success, log.Init(InputForBuffer(key)));
} }

View File

@@ -39,7 +39,8 @@ class CTObjectsExtractorTest : public ::testing::Test {
Buffer mEmbeddedCert; Buffer mEmbeddedCert;
Buffer mCaCert; Buffer mCaCert;
Buffer mCaCertSPKI; Buffer mCaCertSPKI;
CTLogVerifier mLog = CTLogVerifier(-1, CTLogState::Admissible, 0); CTLogVerifier mLog =
CTLogVerifier(-1, CTLogState::Admissible, CTLogFormat::RFC6962, 0);
}; };
TEST_F(CTObjectsExtractorTest, ExtractPrecert) { TEST_F(CTObjectsExtractorTest, ExtractPrecert) {

View File

@@ -35,25 +35,30 @@ class CTPolicyEnforcerTest : public ::testing::Test {
void AddSct(VerifiedSCTList& verifiedScts, size_t logNo, void AddSct(VerifiedSCTList& verifiedScts, size_t logNo,
CTLogOperatorId operatorId, SCTOrigin origin, uint64_t timestamp, CTLogOperatorId operatorId, SCTOrigin origin, uint64_t timestamp,
CTLogState logState = CTLogState::Admissible) { CTLogState logState = CTLogState::Admissible,
CTLogFormat logFormat = CTLogFormat::RFC6962,
Maybe<uint64_t> leafIndex = Nothing()) {
SignedCertificateTimestamp sct; SignedCertificateTimestamp sct;
sct.version = SignedCertificateTimestamp::Version::V1; sct.version = SignedCertificateTimestamp::Version::V1;
sct.timestamp = timestamp; sct.timestamp = timestamp;
sct.leafIndex = leafIndex;
Buffer logId; Buffer logId;
GetLogId(logId, logNo); GetLogId(logId, logNo);
sct.logId = std::move(logId); sct.logId = std::move(logId);
VerifiedSCT verifiedSct(std::move(sct), origin, operatorId, logState, VerifiedSCT verifiedSct(std::move(sct), origin, operatorId, logState,
LOG_TIMESTAMP); logFormat, LOG_TIMESTAMP);
verifiedScts.push_back(std::move(verifiedSct)); verifiedScts.push_back(std::move(verifiedSct));
} }
void AddMultipleScts(VerifiedSCTList& verifiedScts, size_t logsCount, void AddMultipleScts(VerifiedSCTList& verifiedScts, size_t logsCount,
uint8_t operatorsCount, SCTOrigin origin, uint8_t operatorsCount, SCTOrigin origin,
uint64_t timestamp, uint64_t timestamp,
CTLogState logState = CTLogState::Admissible) { CTLogState logState = CTLogState::Admissible,
CTLogFormat logFormat = CTLogFormat::RFC6962) {
for (size_t logNo = 0; logNo < logsCount; logNo++) { for (size_t logNo = 0; logNo < logsCount; logNo++) {
CTLogOperatorId operatorId = logNo % operatorsCount; CTLogOperatorId operatorId = logNo % operatorsCount;
AddSct(verifiedScts, logNo, operatorId, origin, timestamp, logState); AddSct(verifiedScts, logNo, operatorId, origin, timestamp, logState,
logFormat);
} }
} }
@@ -292,5 +297,84 @@ TEST_F(CTPolicyEnforcerTest,
} }
} }
TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithAtLeastOneRFC6962Log) {
VerifiedSCTList scts;
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::RFC6962);
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::Compliant);
}
TEST_F(CTPolicyEnforcerTest,
ConformsToCTPolicyWithAtLeastOneRFC6962LogEmbedded) {
VerifiedSCTList scts;
// 3 embedded SCTs required for DEFAULT_LIFETIME.
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::RFC6962);
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::Compliant);
}
TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyWithNoRFC6962Logs) {
VerifiedSCTList scts;
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
}
TEST_F(CTPolicyEnforcerTest,
DoesNotConformToCTPolicyWithNoRFC6962LogsEmbedded) {
VerifiedSCTList scts;
// 3 embedded SCTs required for DEFAULT_LIFETIME.
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
}
TEST_F(CTPolicyEnforcerTest,
DoesNotConformToCTPolicyWithSCTFromTiledLogWithNoLeafIndex) {
VerifiedSCTList scts;
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Nothing());
AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::RFC6962);
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
}
TEST_F(CTPolicyEnforcerTest,
DoesNotConformToCTPolicyWithSCTFromTiledLogWithNoLeafIndexEmbedded) {
VerifiedSCTList scts;
// 3 embedded SCTs required for DEFAULT_LIFETIME.
AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Some(23));
AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::Tiled, Nothing());
AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1,
CTLogState::Admissible, CTLogFormat::RFC6962);
CheckCompliance(scts, DEFAULT_LIFETIME, CTPolicyCompliance::NotEnoughScts);
}
} // namespace ct } // namespace ct
} // namespace mozilla } // namespace mozilla

View File

@@ -192,6 +192,90 @@ TEST_F(CTSerializationTest, DecodesSignedCertificateTimestamp) {
const size_t expectedSignatureLength = 71; const size_t expectedSignatureLength = 71;
EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size()); EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size());
EXPECT_TRUE(sct.extensions.empty()); EXPECT_TRUE(sct.extensions.empty());
EXPECT_TRUE(sct.leafIndex.isNothing());
}
TEST_F(CTSerializationTest,
DecodesSignedCertificateTimestampWithLeafIndexExtension) {
Buffer encodedSctBuffer =
GetTestSignedCertificateTimestampWithLeafIndexExtension();
Input encodedSctInput = InputForBuffer(encodedSctBuffer);
Reader encodedSctReader(encodedSctInput);
SignedCertificateTimestamp sct;
ASSERT_EQ(Success, DecodeSignedCertificateTimestamp(encodedSctReader, sct));
EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
const uint64_t expectedTime = 1365181456089;
EXPECT_EQ(expectedTime, sct.timestamp);
const size_t expectedSignatureLength = 71;
EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size());
EXPECT_FALSE(sct.extensions.empty());
ASSERT_TRUE(sct.leafIndex.isSome());
EXPECT_EQ(sct.leafIndex.value(), 52U);
}
TEST_F(CTSerializationTest,
FailsDecodingSignedCertificateTimestampWithTwoLeafIndexExtensions) {
Buffer encodedSctBuffer =
GetTestSignedCertificateTimestampWithTwoLeafIndexExtensions();
Input encodedSctInput = InputForBuffer(encodedSctBuffer);
Reader encodedSctReader(encodedSctInput);
SignedCertificateTimestamp sct;
ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
DecodeSignedCertificateTimestamp(encodedSctReader, sct));
}
TEST_F(CTSerializationTest,
DecodesSignedCertificateTimestampWithUnknownExtension) {
Buffer encodedSctBuffer =
GetTestSignedCertificateTimestampWithUnknownExtension();
Input encodedSctInput = InputForBuffer(encodedSctBuffer);
Reader encodedSctReader(encodedSctInput);
SignedCertificateTimestamp sct;
ASSERT_EQ(Success, DecodeSignedCertificateTimestamp(encodedSctReader, sct));
EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
const uint64_t expectedTime = 1365181456089;
EXPECT_EQ(expectedTime, sct.timestamp);
const size_t expectedSignatureLength = 71;
EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size());
EXPECT_FALSE(sct.extensions.empty());
EXPECT_TRUE(sct.leafIndex.isNothing());
}
TEST_F(CTSerializationTest,
DecodesSignedCertificateTimestampWithUnknownAndLeafIndexExtensions) {
Buffer encodedSctBuffer =
GetTestSignedCertificateTimestampWithUnknownAndLeafIndexExtensions();
Input encodedSctInput = InputForBuffer(encodedSctBuffer);
Reader encodedSctReader(encodedSctInput);
SignedCertificateTimestamp sct;
ASSERT_EQ(Success, DecodeSignedCertificateTimestamp(encodedSctReader, sct));
EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
const uint64_t expectedTime = 1365181456089;
EXPECT_EQ(expectedTime, sct.timestamp);
const size_t expectedSignatureLength = 71;
EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size());
EXPECT_FALSE(sct.extensions.empty());
ASSERT_TRUE(sct.leafIndex.isSome());
EXPECT_EQ(sct.leafIndex.value(), 81U);
}
TEST_F(CTSerializationTest,
FailsDecodingSignedCertificateTimestampWithTooShortExtension) {
Buffer encodedSctBuffer =
GetTestSignedCertificateTimestampWithTooShortExtension();
Input encodedSctInput = InputForBuffer(encodedSctBuffer);
Reader encodedSctReader(encodedSctInput);
SignedCertificateTimestamp sct;
ASSERT_EQ(Result::ERROR_BAD_DER,
DecodeSignedCertificateTimestamp(encodedSctReader, sct));
} }
TEST_F(CTSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp) { TEST_F(CTSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp) {

View File

@@ -87,6 +87,55 @@ const char kTestSignedCertificateTimestamp[] =
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456" "08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"89a2c0187ef5a5"; "89a2c0187ef5a5";
// The signatures on the following "SCT"s are not actually valid. The intent is
// to test the parsing of extensions.
const char kTestSignedCertificateTimestampWithLeafIndexExtension[] =
"00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
"db27ded9"
"0008" // 8 bytes of extensions
"0000050000000034" // leaf_index of 52
"0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"89a2c0187ef5a5";
const char kTestSignedCertificateTimestampWithTwoLeafIndexExtensions[] =
"00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
"db27ded9"
"0010" // 16 bytes of extensions
"0000050000000034" // leaf_index of 52
"0000050000000051" // leaf_index of 81
"0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"89a2c0187ef5a5";
const char kTestSignedCertificateTimestampWithUnknownExtension[] =
"00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
"db27ded9"
"0008" // 8 bytes of extensions
"0100050000000034" // an (unknown) extension with id 1
"0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"89a2c0187ef5a5";
const char kTestSignedCertificateTimestampWithUnknownAndLeafIndexExtensions[] =
"00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
"db27ded9"
"0010" // 16 bytes of extensions
"0100050000000034" // an (unknown) extension with id 1
"0000050000000051" // leaf_index of 81
"0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"89a2c0187ef5a5";
const char kTestSignedCertificateTimestampWithTooShortExtension[] =
"00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
"db27ded9"
"0008" // 8 bytes of extensions
"000005000034" // 3 bytes of extension data when there should be 5
"0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"89a2c0187ef5a5";
// ct-server-key-public.pem // ct-server-key-public.pem
const char kEcP256PublicKey[] = const char kEcP256PublicKey[] =
"3059301306072a8648ce3d020106082a8648ce3d0301070342000499783cb14533c0161a5a" "3059301306072a8648ce3d020106082a8648ce3d0301070342000499783cb14533c0161a5a"
@@ -527,6 +576,27 @@ Buffer GetTestSignedCertificateTimestamp() {
return HexToBytes(kTestSignedCertificateTimestamp); return HexToBytes(kTestSignedCertificateTimestamp);
} }
Buffer GetTestSignedCertificateTimestampWithLeafIndexExtension() {
return HexToBytes(kTestSignedCertificateTimestampWithLeafIndexExtension);
}
Buffer GetTestSignedCertificateTimestampWithTwoLeafIndexExtensions() {
return HexToBytes(kTestSignedCertificateTimestampWithTwoLeafIndexExtensions);
}
Buffer GetTestSignedCertificateTimestampWithUnknownExtension() {
return HexToBytes(kTestSignedCertificateTimestampWithUnknownExtension);
}
Buffer GetTestSignedCertificateTimestampWithUnknownAndLeafIndexExtensions() {
return HexToBytes(
kTestSignedCertificateTimestampWithUnknownAndLeafIndexExtensions);
}
Buffer GetTestSignedCertificateTimestampWithTooShortExtension() {
return HexToBytes(kTestSignedCertificateTimestampWithTooShortExtension);
}
Buffer GetTestInclusionProof() { return HexToBytes(kTestInclusionProof); } Buffer GetTestInclusionProof() { return HexToBytes(kTestInclusionProof); }
Buffer GetTestInclusionProofUnexpectedData() { Buffer GetTestInclusionProofUnexpectedData() {

View File

@@ -39,8 +39,13 @@ Buffer GetTestDigitallySigned();
// Returns the source data of the test DigitallySigned. // Returns the source data of the test DigitallySigned.
Buffer GetTestDigitallySignedData(); Buffer GetTestDigitallySignedData();
// Returns the binary representation of a test serialized SCT. // Returns the binary representation of various test SCTs.
Buffer GetTestSignedCertificateTimestamp(); Buffer GetTestSignedCertificateTimestamp();
Buffer GetTestSignedCertificateTimestampWithLeafIndexExtension();
Buffer GetTestSignedCertificateTimestampWithTwoLeafIndexExtensions();
Buffer GetTestSignedCertificateTimestampWithUnknownExtension();
Buffer GetTestSignedCertificateTimestampWithUnknownAndLeafIndexExtensions();
Buffer GetTestSignedCertificateTimestampWithTooShortExtension();
// Returns the binary representation of a test serialized InclusionProof. // Returns the binary representation of a test serialized InclusionProof.
Buffer GetTestInclusionProof(); Buffer GetTestInclusionProof();

View File

@@ -30,7 +30,8 @@ class MultiLogCTVerifierTest : public ::testing::Test {
abort(); abort();
} }
CTLogVerifier log(mLogOperatorID, CTLogState::Admissible, 0); CTLogVerifier log(mLogOperatorID, CTLogState::Admissible,
CTLogFormat::RFC6962, 0);
; ;
ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey()))); ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey())));
mVerifier.AddLog(std::move(log)); mVerifier.AddLog(std::move(log));
@@ -222,7 +223,8 @@ TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog) {
TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromDisqualifiedLog) { TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromDisqualifiedLog) {
MultiLogCTVerifier verifier; MultiLogCTVerifier verifier;
const uint64_t retiredTime = 12345u; const uint64_t retiredTime = 12345u;
CTLogVerifier log(mLogOperatorID, CTLogState::Retired, retiredTime); CTLogVerifier log(mLogOperatorID, CTLogState::Retired, CTLogFormat::RFC6962,
retiredTime);
ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey()))); ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey())));
verifier.AddLog(std::move(log)); verifier.AddLog(std::move(log));

View File

@@ -51,6 +51,12 @@ function add_tests_in_mode(mode) {
Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT, Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT,
true true
); );
// This certificate has an embedded SCT from a tiled log.
add_ct_test(
"ct-tiled-valid.example.com",
Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT,
true
);
// This certificate has only 2 embedded SCTs, and so is not policy-compliant. // This certificate has only 2 embedded SCTs, and so is not policy-compliant.
add_ct_test( add_ct_test(
"ct-insufficient-scts.example.com", "ct-insufficient-scts.example.com",

View File

@@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
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
jSTJxocud+zZajmigOfMIvcYUKVD3ieiLg==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,4 @@
issuer:Test CA
subject:ct-tiled-valid.example.com
extension:subjectAlternativeName:*.example.com
extension:embeddedSCTList:default:20160101,secp256r1:20160101,ev:20160101:567502607955

View File

@@ -1,5 +1,5 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIF3DCCBMSgAwIBAgIUS2SODmE9dXhGEYS8JfCogtARiyEwDQYJKoZIhvcNAQEL MIIFRTCCBC2gAwIBAgIUG4/k+n+iibx5ZgMTEJsAirIw46YwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAiGA8yMDIzMTEyODAwMDAwMFoYDzIwMjYw BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAiGA8yMDIzMTEyODAwMDAwMFoYDzIwMjYw
MjA1MDAwMDAwWjAlMSMwIQYDVQQDDBpjdC11bmtub3duLWxvZy5leGFtcGxlLmNv MjA1MDAwMDAwWjAlMSMwIQYDVQQDDBpjdC11bmtub3duLWxvZy5leGFtcGxlLmNv
bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogG bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogG
@@ -8,27 +8,24 @@ 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 WDb3zi3AJNht
XAJpdu5HwOj2+4aCNw6drMg+b/Dig+EpOTSBhalofyJxi3Lv/Kedzc9wlxqvmUvm
iMxWJob8wwaFRoL4YNBxxZEYI+Hebw8kePHhtwZ7h9q/BsgFajsWn5ay3PXmVvD2
Em16teHaNfB2r3e6sjuZBw==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -1,4 +1,4 @@
issuer:Test CA issuer:Test CA
subject:ct-unknown-log.example.com subject:ct-unknown-log.example.com
extension:subjectAlternativeName:*.example.com extension:subjectAlternativeName:*.example.com
extension:embeddedSCTList:default:20160101,secp256r1:20160101,ev:20160101 extension:embeddedSCTList:default:20160101,secp256r1:20160101,secp384r1:20160101

View File

@@ -1,6 +1,6 @@
{ {
"version": "59.18", "version": "59.19",
"log_list_timestamp": "2025-07-27T12:54:32Z", "log_list_timestamp": "2025-07-28T12:53:51Z",
"operators": [ "operators": [
{ {
"name": "Google", "name": "Google",

View File

@@ -36,7 +36,7 @@ certificatePolicies:[<policy OID>,...]
nameConstraints:{permitted,excluded}:[<dNSName|directoryName>,...] nameConstraints:{permitted,excluded}:[<dNSName|directoryName>,...]
nsCertType:sslServer nsCertType:sslServer
TLSFeature:[<TLSFeature>,...] TLSFeature:[<TLSFeature>,...]
embeddedSCTList:[<key specification>:<YYYYMMDD>,...] embeddedSCTList:[<key specification>:<YYYYMMDD>[:<leaf index>],...]
delegationUsage: delegationUsage:
Where: Where:
@@ -109,12 +109,12 @@ class UnknownBaseError(Error):
"""Base class for handling unexpected input in this module.""" """Base class for handling unexpected input in this module."""
def __init__(self, value): def __init__(self, value):
super(UnknownBaseError, self).__init__() super().__init__()
self.value = value self.value = value
self.category = "input" self.category = "input"
def __str__(self): def __str__(self):
return 'Unknown %s type "%s"' % (self.category, repr(self.value)) return f'Unknown {self.category} type "{repr(self.value)}"'
class UnknownAlgorithmTypeError(UnknownBaseError): class UnknownAlgorithmTypeError(UnknownBaseError):
@@ -210,18 +210,18 @@ class InvalidSCTSpecification(Error):
"""Helper exception type to handle invalid SCT specifications.""" """Helper exception type to handle invalid SCT specifications."""
def __init__(self, value): def __init__(self, value):
super(InvalidSCTSpecification, self).__init__() super().__init__()
self.value = value self.value = value
def __str__(self): def __str__(self):
return repr('invalid SCT specification "{}"' % self.value) return f'invalid SCT specification "{self.value}"'
class InvalidSerialNumber(Error): class InvalidSerialNumber(Error):
"""Exception type to handle invalid serial numbers.""" """Exception type to handle invalid serial numbers."""
def __init__(self, value): def __init__(self, value):
super(InvalidSerialNumber, self).__init__() super().__init__()
self.value = value self.value = value
def __str__(self): def __str__(self):
@@ -254,7 +254,7 @@ def stringToDN(string, tag=None):
optional implicit tag in cases where the Name needs to be tagged optional implicit tag in cases where the Name needs to be tagged
differently.""" differently."""
if string and "/" not in string: if string and "/" not in string:
string = "/CN=%s" % string string = f"/CN={string}"
rdns = rfc2459.RDNSequence() rdns = rfc2459.RDNSequence()
pattern = "/(C|ST|L|O|OU|CN|emailAddress)=" pattern = "/(C|ST|L|O|OU|CN|emailAddress)="
split = re.split(pattern, string) split = re.split(pattern, string)
@@ -621,10 +621,11 @@ class Certificate:
def addCertificatePolicies(self, policyOIDs, critical): def addCertificatePolicies(self, policyOIDs, critical):
policies = rfc2459.CertificatePolicies() policies = rfc2459.CertificatePolicies()
for pos, policyOID in enumerate(policyOIDs.split(",")): for pos, policyOID in enumerate(policyOIDs.split(",")):
if policyOID == "any": policyOIDMapped = policyOID
policyOID = "2.5.29.32.0" if policyOIDMapped == "any":
policyOIDMapped = "2.5.29.32.0"
policy = rfc2459.PolicyInformation() policy = rfc2459.PolicyInformation()
policyIdentifier = rfc2459.CertPolicyId(policyOID) policyIdentifier = rfc2459.CertPolicyId(policyOIDMapped)
policy["policyIdentifier"] = policyIdentifier policy["policyIdentifier"] = policyIdentifier
policies.setComponentByPosition(pos, policy) policies.setComponentByPosition(pos, policy)
self.addExtension(rfc2459.id_ce_certificatePolicies, policies, critical) self.addExtension(rfc2459.id_ce_certificatePolicies, policies, critical)
@@ -696,15 +697,20 @@ class Certificate:
(scts, critical) = self.savedEmbeddedSCTListData (scts, critical) = self.savedEmbeddedSCTListData
encodedSCTs = [] encodedSCTs = []
for sctSpec in scts.split(","): for sctSpec in scts.split(","):
match = re.search(r"(\w+):(\d{8})", sctSpec) match = re.search(r"(\w+):(\d{8}):?(\d+)?", sctSpec)
if not match: if not match:
raise InvalidSCTSpecification(sctSpec) raise InvalidSCTSpecification(sctSpec)
keySpec = match.group(1) keySpec = match.group(1)
leafIndex = match.group(3)
if leafIndex:
leafIndex = int(leafIndex)
key = pykey.keyFromSpecification(keySpec) key = pykey.keyFromSpecification(keySpec)
time = datetime.datetime.strptime(match.group(2), "%Y%m%d") time = datetime.datetime.strptime(match.group(2), "%Y%m%d")
tbsCertificate = self.getTBSCertificate() tbsCertificate = self.getTBSCertificate()
tbsDER = encoder.encode(tbsCertificate) tbsDER = encoder.encode(tbsCertificate)
sct = pyct.SCT(key, time, pyct.PrecertEntry(tbsDER, self.issuerKey)) sct = pyct.SCT(
key, time, pyct.PrecertEntry(tbsDER, self.issuerKey), leafIndex
)
signed = sct.signAndEncode() signed = sct.signAndEncode()
lengthPrefix = pack("!H", len(signed)) lengthPrefix = pack("!H", len(signed))
encodedSCTs.append(lengthPrefix + signed) encodedSCTs.append(lengthPrefix + signed)

View File

@@ -16,6 +16,7 @@ to the output object. The specification is as follows:
timestamp:<YYYYMMDD> timestamp:<YYYYMMDD>
[key:<key specification>] [key:<key specification>]
[tamper] [tamper]
[leafIndex:<leaf index>]
certificate: certificate:
<certificate specification> <certificate specification>
@@ -48,7 +49,7 @@ class InvalidKeyError(Exception):
self.key = key self.key = key
def __str__(self): def __str__(self):
return 'Invalid key: "%s"' % str(self.key) return f'Invalid key: "{str(self.key)}"'
class UnknownSignedEntryType(Exception): class UnknownSignedEntryType(Exception):
@@ -58,7 +59,7 @@ class UnknownSignedEntryType(Exception):
self.signedEntry = signedEntry self.signedEntry = signedEntry
def __str__(self): def __str__(self):
return 'Unknown SignedEntry type: "%s"' % str(self.signedEntry) return f'Unknown SignedEntry type: "{str(self.signedEntry)}"'
class SignedEntry: class SignedEntry:
@@ -84,11 +85,12 @@ class X509Entry(SignedEntry):
class SCT: class SCT:
"""SCT represents a Signed Certificate Timestamp.""" """SCT represents a Signed Certificate Timestamp."""
def __init__(self, key, date, signedEntry): def __init__(self, key, date, signedEntry, leafIndex=None):
self.key = key self.key = key
self.timestamp = calendar.timegm(date.timetuple()) * 1000 self.timestamp = calendar.timegm(date.timetuple()) * 1000
self.signedEntry = signedEntry self.signedEntry = signedEntry
self.tamper = False self.tamper = False
self.leafIndex = leafIndex
def signAndEncode(self): def signAndEncode(self):
"""Returns a signed and encoded representation of the """Returns a signed and encoded representation of the
@@ -100,8 +102,7 @@ class SCT:
# entry_type (two bytes (one 0 byte followed by one 0 byte for # entry_type (two bytes (one 0 byte followed by one 0 byte for
# X509Entry or one 1 byte for PrecertEntry) # X509Entry or one 1 byte for PrecertEntry)
# signed_entry (bytes of X509Entry or PrecertEntry) # signed_entry (bytes of X509Entry or PrecertEntry)
# extensions (2-byte-length-prefixed, currently empty (so two 0 # extensions (2-byte-length-prefixed)
# bytes))
# A X509Entry is: # A X509Entry is:
# certificate (3-byte-length-prefixed data) # certificate (3-byte-length-prefixed data)
# A PrecertEntry is: # A PrecertEntry is:
@@ -125,7 +126,18 @@ class SCT:
) )
else: else:
raise UnknownSignedEntryType(self.signedEntry) raise UnknownSignedEntryType(self.signedEntry)
data = b"\0\0" + timestamp + b"\0" + entry_with_type + b"\0\0" extensions = []
if self.leafIndex:
# An extension consists of 1 byte to identify the extension type, 2
# big-endian bytes for the length of the extension data, and then
# the extension data.
# The type of leaf_index is 0, and its data consists of 5 bytes.
extensions = [b"\0\0\5" + self.leafIndex.to_bytes(5, byteorder="big")]
extensionsLength = sum(map(len, extensions))
extensionsEncoded = extensionsLength.to_bytes(2, byteorder="big") + b"".join(
extensions
)
data = b"\0\0" + timestamp + b"\0" + entry_with_type + extensionsEncoded
if isinstance(self.key, pykey.ECCKey): if isinstance(self.key, pykey.ECCKey):
signatureByte = b"\3" signatureByte = b"\3"
elif isinstance(self.key, pykey.RSAKey): elif isinstance(self.key, pykey.RSAKey):
@@ -143,8 +155,7 @@ class SCT:
# id (32 bytes of SHA-256 hash of the signing key, as # id (32 bytes of SHA-256 hash of the signing key, as
# DER-encoded SPKI) # DER-encoded SPKI)
# timestamp (8 bytes, milliseconds since the epoch) # timestamp (8 bytes, milliseconds since the epoch)
# extensions (2-byte-length-prefixed data, currently # extensions (2-byte-length-prefixed data)
# empty)
# hash (one 4 byte representing sha256) # hash (one 4 byte representing sha256)
# signature (one byte - 1 for RSA and 3 for ECDSA) # signature (one byte - 1 for RSA and 3 for ECDSA)
# signature (2-byte-length-prefixed data) # signature (2-byte-length-prefixed data)
@@ -156,7 +167,8 @@ class SCT:
b"\0" b"\0"
+ key_id + key_id
+ timestamp + timestamp
+ b"\0\0\4" + extensionsEncoded
+ b"\4"
+ signatureByte + signatureByte
+ signature_len_prefix + signature_len_prefix
+ signature + signature
@@ -168,26 +180,30 @@ class SCT:
certificateSpecification = StringIO() certificateSpecification = StringIO()
readingCertificateSpecification = False readingCertificateSpecification = False
tamper = False tamper = False
leafIndex = None
for line in specStream.readlines(): for line in specStream.readlines():
line = line.strip() lineStripped = line.strip()
if readingCertificateSpecification: if readingCertificateSpecification:
print(line, file=certificateSpecification) print(lineStripped, file=certificateSpecification)
elif line == "certificate:": elif lineStripped == "certificate:":
readingCertificateSpecification = True readingCertificateSpecification = True
elif line.startswith("key:"): elif lineStripped.startswith("key:"):
key = pykey.keyFromSpecification(line[len("key:") :]) key = pykey.keyFromSpecification(lineStripped[len("key:") :])
elif line.startswith("timestamp:"): elif lineStripped.startswith("timestamp:"):
timestamp = datetime.datetime.strptime( timestamp = datetime.datetime.strptime(
line[len("timestamp:") :], "%Y%m%d" lineStripped[len("timestamp:") :], "%Y%m%d"
) )
elif line == "tamper": elif lineStripped == "tamper":
tamper = True tamper = True
elif lineStripped.startswith("leafIndex:"):
leafIndex = int(lineStripped[len("leafIndex:") :])
else: else:
raise pycert.UnknownParameterTypeError(line) raise pycert.UnknownParameterTypeError(lineStripped)
certificateSpecification.seek(0) certificateSpecification.seek(0)
certificate = pycert.Certificate(certificateSpecification).toDER() certificate = pycert.Certificate(certificateSpecification).toDER()
sct = SCT(key, timestamp, X509Entry(certificate)) sct = SCT(key, timestamp, X509Entry(certificate))
sct.tamper = tamper sct.tamper = tamper
sct.leafIndex = leafIndex
return sct return sct

View File

@@ -58,10 +58,16 @@ enum class CTLogState {
Retired, Retired,
}; };
enum class CTLogFormat {
RFC6962,
Tiled,
};
struct CTLogInfo { struct CTLogInfo {
// See bug 1338873 about making these fields const. // See bug 1338873 about making these fields const.
const char* name; const char* name;
CTLogState state; CTLogState state;
CTLogFormat format;
uint64_t timestamp; uint64_t timestamp;
// Index within kCTLogOperatorList. // Index within kCTLogOperatorList.
size_t operatorIndex; size_t operatorIndex;
@@ -122,7 +128,7 @@ def get_operator_index(json_data, target_name):
LOG_INFO_TEMPLATE = """\ LOG_INFO_TEMPLATE = """\
{$description, $state, {$description, $state, $log_format,
$timestamp, // $timestamp_comment $timestamp, // $timestamp_comment
$operator_index,$spaces // $operator_comment $operator_index,$spaces // $operator_comment
$indented_log_key, $indented_log_key,
@@ -140,56 +146,71 @@ def map_state(state):
states to be included are 'qualified', 'usable', 'readonly', or 'retired'. states to be included are 'qualified', 'usable', 'readonly', or 'retired'.
Valid states that are not to be included are 'pending' or 'rejected'. Valid states that are not to be included are 'pending' or 'rejected'.
""" """
if state == "qualified" or state == "usable" or state == "readonly": if state in {"qualified", "usable", "readonly"}:
return "CTLogState::Admissible" return "CTLogState::Admissible"
elif state == "retired": elif state == "retired":
return "CTLogState::Retired" return "CTLogState::Retired"
elif state == "pending" or state == "rejected": elif state in {"pending", "rejected"}:
return None return None
else: else:
raise UnhandledLogStateException("unhandled log state '%s'" % state) raise UnhandledLogStateException(f"unhandled log state '{state}'")
def get_initializer_for_log(log, operator, json_data, log_format):
log_key = base64.b64decode(log["key"])
operator_name = operator["name"]
operator_index = get_operator_index(json_data, operator_name)
state = list(log["state"].keys())[0]
timestamp_comment = log["state"][state]["timestamp"]
timestamp = get_timestamp(timestamp_comment)
state = map_state(state)
if state is None:
return None
is_test_log = "test_only" in operator and operator["test_only"]
prefix = ""
suffix = ","
if is_test_log:
prefix = "#ifdef DEBUG\n"
suffix = ",\n#endif // DEBUG"
num_spaces = len(str(timestamp)) - len(str(operator_index))
spaces = " " * num_spaces
tmpl = Template(LOG_INFO_TEMPLATE)
toappend = tmpl.substitute(
# Use json.dumps for C-escaping strings.
# Not perfect but close enough.
description=json.dumps(log["description"]),
operator_index=operator_index,
operator_comment=f"operated by {operator_name}".replace("/", "|"),
state=state,
log_format=log_format,
timestamp=timestamp,
spaces=spaces,
timestamp_comment=timestamp_comment,
# Maximum line width is 80.
indented_log_key="\n".join([f' "{l}"' for l in get_hex_lines(log_key, 74)]),
log_key_len=len(log_key),
)
return prefix + toappend + suffix
def get_log_info_structs(json_data): def get_log_info_structs(json_data):
"""Return array of CTLogInfo initializers for the known logs.""" """Return array of CTLogInfo initializers for the known logs."""
tmpl = Template(LOG_INFO_TEMPLATE)
initializers = [] initializers = []
for operator in json_data["operators"]: for operator in json_data["operators"]:
operator_name = operator["name"] if "logs" in operator:
for log in operator["logs"]: for log in operator["logs"]:
log_key = base64.b64decode(log["key"]) initializer = get_initializer_for_log(
operator_index = get_operator_index(json_data, operator_name) log, operator, json_data, "CTLogFormat::RFC6962"
state = list(log["state"].keys())[0] )
timestamp_comment = log["state"][state]["timestamp"] if initializer:
timestamp = get_timestamp(timestamp_comment) initializers.append(initializer)
state = map_state(state) if "tiled_logs" in operator:
if state is None: for log in operator["tiled_logs"]:
continue initializer = get_initializer_for_log(
is_test_log = "test_only" in operator and operator["test_only"] log, operator, json_data, "CTLogFormat::Tiled"
prefix = "" )
suffix = "," if initializer:
if is_test_log: initializers.append(initializer)
prefix = "#ifdef DEBUG\n"
suffix = ",\n#endif // DEBUG"
num_spaces = len(str(timestamp)) - len(str(operator_index))
spaces = " " * num_spaces
toappend = tmpl.substitute(
# Use json.dumps for C-escaping strings.
# Not perfect but close enough.
description=json.dumps(log["description"]),
operator_index=operator_index,
operator_comment=f"operated by {operator_name}".replace("/", "|"),
state=state,
timestamp=timestamp,
spaces=spaces,
timestamp_comment=timestamp_comment,
# Maximum line width is 80.
indented_log_key="\n".join(
[f' "{l}"' for l in get_hex_lines(log_key, 74)]
),
log_key_len=len(log_key),
)
initializers.append(prefix + toappend + suffix)
return initializers return initializers
@@ -301,8 +322,34 @@ def patch_in_test_logs(json_data):
} }
], ],
} }
mozilla_test_operator_3 = {
"name": "Mozilla Test Org 3",
"id": max_id + 3,
"test_only": True,
"tiled_logs": [
{
"description": "Mozilla Test RSA Log 4",
# `openssl x509 -noout -pubkey -in <path/to/evroot.pem>`
"key": """
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtUmJXJ0AEI0Rofmfh6nj
0aXbXfrs8YjaV79kE2iPLORyLP8QkDjBdALJOipHPb0oau0/3NNjz1opFHcBvdgY
12Fb+TVr1dPIM2qqjAlxNooGw81EYbk+UUJOF0S7LqrUaqs4zhloIZYfh3FKFmNp
Pwl2HN9Na6El6s7HvicNOI94n2zfeK4xRO0oxF55KThjp6IqSJgKNqQOctV5ybkl
3/jHkzYv/WiXp8F1TF6XyWfD6t0aroqizM40igFpuA4ootcMGpYMbzNfLaCbm2Q/
Wr+6SeiqqYHpYOJ9h0gL3VXdlBf6GFCfu1VMz4GkOX6LqBKKNL3yeGXBieVzT7Ip
BQIDAQAB
""",
"state": {
"qualified": {
"timestamp": "2025-06-25T12:09:26Z",
},
},
}
],
}
json_data["operators"].append(mozilla_test_operator_1) json_data["operators"].append(mozilla_test_operator_1)
json_data["operators"].append(mozilla_test_operator_2) json_data["operators"].append(mozilla_test_operator_2)
json_data["operators"].append(mozilla_test_operator_3)
def get_content_at(url): def get_content_at(url):
@@ -348,7 +395,7 @@ def run(args):
hash_alg = rsa.verify(json_text, signature, key) hash_alg = rsa.verify(json_text, signature, key)
if hash_alg != "SHA-256": if hash_alg != "SHA-256":
raise UnsupportedSignatureHashAlgorithmException( raise UnsupportedSignatureHashAlgorithmException(
"unsupported hash algorithm '%s'" % hash_alg f"unsupported hash algorithm '{hash_alg}'"
) )
print("Writing output: ", args.json_file_out) print("Writing output: ", args.json_file_out)
with open(args.json_file_out, "wb") as json_file_out: with open(args.json_file_out, "wb") as json_file_out:
@@ -373,7 +420,7 @@ def parse_arguments_and_run():
"Downloads the JSON file from the known source " "Downloads the JSON file from the known source "
"of truth by default, but can also operate on a " "of truth by default, but can also operate on a "
"previously-downloaded file. See https://certificate.transparency.dev/google/", "previously-downloaded file. See https://certificate.transparency.dev/google/",
epilog="Example: ./mach python %s" % os.path.basename(sys.argv[0]), epilog=f"Example: ./mach python {os.path.basename(sys.argv[0])}",
) )
arg_parser.add_argument( arg_parser.add_argument(