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 =
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);
if (rv != Success) {
MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");

View File

@@ -14,7 +14,7 @@
#include <stddef.h>
static const PRTime kCTExpirationTime = INT64_C(1759749898000000);
static const PRTime kCTExpirationTime = INT64_C(1759796803000000);
namespace mozilla::ct {
@@ -23,10 +23,16 @@ enum class CTLogState {
Retired,
};
enum class CTLogFormat {
RFC6962,
Tiled,
};
struct CTLogInfo {
// See bug 1338873 about making these fields const.
const char* name;
CTLogState state;
CTLogFormat format;
uint64_t timestamp;
// Index within kCTLogOperatorList.
size_t operatorIndex;
@@ -41,7 +47,7 @@ struct CTLogOperatorInfo {
};
const CTLogInfo kCTLogList[] = {
{"Google 'Argon2025h1' log", CTLogState::Admissible,
{"Google 'Argon2025h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google
"\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"
"\x9a",
91},
{"Google 'Argon2025h2' log", CTLogState::Admissible,
{"Google 'Argon2025h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google
"\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"
"\xba",
91},
{"Google 'Argon2026h1' log", CTLogState::Admissible,
{"Google 'Argon2026h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google
"\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"
"\x3e",
91},
{"Google 'Argon2026h2' log", CTLogState::Admissible,
{"Google 'Argon2026h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google
"\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"
"\x22",
91},
{"Google 'Xenon2025h1' log", CTLogState::Admissible,
{"Google 'Xenon2025h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google
"\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"
"\xbd",
91},
{"Google 'Xenon2025h2' log", CTLogState::Admissible,
{"Google 'Xenon2025h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
0, // operated by Google
"\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"
"\x86",
91},
{"Google 'Xenon2026h1' log", CTLogState::Admissible,
{"Google 'Xenon2026h1' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google
"\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"
"\x4c",
91},
{"Google 'Xenon2026h2' log", CTLogState::Admissible,
{"Google 'Xenon2026h2' log", CTLogState::Admissible, CTLogFormat::RFC6962,
1727734767000, // 2024-09-30T22:19:27Z
0, // operated by Google
"\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"
"\xbf",
91},
{"Cloudflare 'Nimbus2025'", CTLogState::Admissible,
{"Cloudflare 'Nimbus2025'", CTLogState::Admissible, CTLogFormat::RFC6962,
1702969200000, // 2023-12-19T07:00:00Z
1, // operated by Cloudflare
"\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"
"\x97",
91},
{"Cloudflare 'Nimbus2026'", CTLogState::Admissible,
{"Cloudflare 'Nimbus2026'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731088800000, // 2024-11-08T18:00:00Z
1, // operated by Cloudflare
"\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"
"\xa3",
91},
{"DigiCert Yeti2025 Log", CTLogState::Retired,
{"DigiCert Yeti2025 Log", CTLogState::Retired, CTLogFormat::RFC6962,
1753315200000, // 2025-07-24T00:00:00Z
2, // operated by DigiCert
"\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"
"\x43",
91},
{"DigiCert Nessie2025 Log", CTLogState::Retired,
{"DigiCert Nessie2025 Log", CTLogState::Retired, CTLogFormat::RFC6962,
1744758000000, // 2025-04-15T23:00:00Z
2, // operated by DigiCert
"\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"
"\x38",
91},
{"DigiCert 'Wyvern2025h1' Log", CTLogState::Retired,
{"DigiCert 'Wyvern2025h1' Log", CTLogState::Retired, CTLogFormat::RFC6962,
1744670000000, // 2025-04-14T22:33:20Z
2, // operated by DigiCert
"\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"
"\x09",
91},
{"DigiCert 'Wyvern2025h2' Log", CTLogState::Admissible,
{"DigiCert 'Wyvern2025h2' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
1724900983000, // 2024-08-29T03:09:43Z
2, // operated by DigiCert
"\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"
"\x2e",
91},
{"DigiCert 'Wyvern2026h1'", CTLogState::Admissible,
{"DigiCert 'Wyvern2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert
"\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"
"\x55",
91},
{"DigiCert 'Wyvern2026h2'", CTLogState::Admissible,
{"DigiCert 'Wyvern2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert
"\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"
"\x99",
91},
{"DigiCert 'Sphinx2025h1' Log", CTLogState::Retired,
{"DigiCert 'Sphinx2025h1' Log", CTLogState::Retired, CTLogFormat::RFC6962,
1744670000000, // 2025-04-14T22:33:20Z
2, // operated by DigiCert
"\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"
"\x6e",
91},
{"DigiCert 'Sphinx2025h2' Log", CTLogState::Admissible,
{"DigiCert 'Sphinx2025h2' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
1724900983000, // 2024-08-29T03:09:43Z
2, // operated by DigiCert
"\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"
"\x69",
91},
{"DigiCert 'Sphinx2026h1'", CTLogState::Admissible,
{"DigiCert 'Sphinx2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert
"\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"
"\xf4",
91},
{"DigiCert 'Sphinx2026h2'", CTLogState::Admissible,
{"DigiCert 'Sphinx2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1731024000000, // 2024-11-08T00:00:00Z
2, // operated by DigiCert
"\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"
"\xcd",
91},
{"Sectigo 'Sabre2025h1'", CTLogState::Admissible,
{"Sectigo 'Sabre2025h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo
"\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",
91},
{"Sectigo 'Sabre2025h2'", CTLogState::Admissible,
{"Sectigo 'Sabre2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo
"\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"
"\xcc",
91},
{"Sectigo 'Mammoth2025h1'", CTLogState::Admissible,
{"Sectigo 'Mammoth2025h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo
"\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"
"\xe5",
91},
{"Sectigo 'Mammoth2025h2'", CTLogState::Admissible,
{"Sectigo 'Mammoth2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
3, // operated by Sectigo
"\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"
"\x6f",
91},
{"Sectigo 'Mammoth2026h1'", CTLogState::Admissible,
{"Sectigo 'Mammoth2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo
"\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"
"\xc4",
91},
{"Sectigo 'Mammoth2026h2'", CTLogState::Admissible,
{"Sectigo 'Mammoth2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo
"\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"
"\xb6",
91},
{"Sectigo 'Sabre2026h1'", CTLogState::Admissible,
{"Sectigo 'Sabre2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo
"\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"
"\x60",
91},
{"Sectigo 'Sabre2026h2'", CTLogState::Admissible,
{"Sectigo 'Sabre2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1728925200000, // 2024-10-14T17:00:00Z
3, // operated by Sectigo
"\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"
"\x73",
91},
{"Sectigo 'Elephant2025h2'", CTLogState::Admissible,
{"Sectigo 'Elephant2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo
"\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"
"\x42",
91},
{"Sectigo 'Elephant2026h1'", CTLogState::Admissible,
{"Sectigo 'Elephant2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo
"\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"
"\x69",
91},
{"Sectigo 'Elephant2026h2'", CTLogState::Admissible,
{"Sectigo 'Elephant2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo
"\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"
"\x98",
91},
{"Sectigo 'Elephant2027h1'", CTLogState::Admissible,
{"Sectigo 'Elephant2027h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo
"\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",
91},
{"Sectigo 'Elephant2027h2'", CTLogState::Admissible,
{"Sectigo 'Elephant2027h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1747100000000, // 2025-05-13T01:33:20Z
3, // operated by Sectigo
"\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"
"\xf2",
91},
{"Sectigo 'Tiger2025h2'", CTLogState::Admissible,
{"Sectigo 'Tiger2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo
"\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"
"\xc6",
91},
{"Sectigo 'Tiger2026h1'", CTLogState::Admissible,
{"Sectigo 'Tiger2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo
"\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"
"\xc6",
91},
{"Sectigo 'Tiger2026h2'", CTLogState::Admissible,
{"Sectigo 'Tiger2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo
"\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"
"\x9c",
91},
{"Sectigo 'Tiger2027h1'", CTLogState::Admissible,
{"Sectigo 'Tiger2027h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo
"\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"
"\x84",
91},
{"Sectigo 'Tiger2027h2'", CTLogState::Admissible,
{"Sectigo 'Tiger2027h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1752066000000, // 2025-07-09T13:00:00Z
3, // operated by Sectigo
"\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"
"\xc9",
91},
{"Let's Encrypt 'Oak2025h1'", CTLogState::Admissible,
{"Let's Encrypt 'Oak2025h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
4, // operated by Let's Encrypt
"\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"
"\x1c",
91},
{"Let's Encrypt 'Oak2025h2'", CTLogState::Admissible,
{"Let's Encrypt 'Oak2025h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
4, // operated by Let's Encrypt
"\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"
"\xa9",
91},
{"Let's Encrypt 'Oak2026h1'", CTLogState::Admissible,
{"Let's Encrypt 'Oak2026h1'", CTLogState::Admissible, CTLogFormat::RFC6962,
1730678400000, // 2024-11-04T00:00:00Z
4, // operated by Let's Encrypt
"\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"
"\x49",
91},
{"Let's Encrypt 'Oak2026h2'", CTLogState::Admissible,
{"Let's Encrypt 'Oak2026h2'", CTLogState::Admissible, CTLogFormat::RFC6962,
1730678400000, // 2024-11-04T00:00:00Z
4, // operated by Let's Encrypt
"\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"
"\x15",
91},
{"TrustAsia Log2025a", CTLogState::Admissible,
{"TrustAsia Log2025a", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
5, // operated by TrustAsia
"\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"
"\x4d",
91},
{"TrustAsia Log2025b", CTLogState::Admissible,
{"TrustAsia Log2025b", CTLogState::Admissible, CTLogFormat::RFC6962,
1701000000000, // 2023-11-26T12:00:00Z
5, // operated by TrustAsia
"\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"
"\x65",
91},
{"TrustAsia 'log2026a'", CTLogState::Admissible,
{"TrustAsia 'log2026a'", CTLogState::Admissible, CTLogFormat::RFC6962,
1726790400000, // 2024-09-20T00:00:00Z
5, // operated by TrustAsia
"\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"
"\x59",
91},
{"TrustAsia 'log2026b'", CTLogState::Admissible,
{"TrustAsia 'log2026b'", CTLogState::Admissible, CTLogFormat::RFC6962,
1726790400000, // 2024-09-20T00:00:00Z
5, // operated by TrustAsia
"\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"
"\x85",
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
6, // operated by Geomys
"\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"
"\x37",
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
{"Mozilla Test RSA Log 1", CTLogState::Admissible,
{"Mozilla Test RSA Log 1", CTLogState::Admissible, CTLogFormat::RFC6962,
1721666666000, // 2024-07-22T16:44:26Z
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"
@@ -535,7 +571,7 @@ const CTLogInfo kCTLogList[] = {
294},
#endif // DEBUG
#ifdef DEBUG
{"Mozilla Test EC Log", CTLogState::Admissible,
{"Mozilla Test EC Log", CTLogState::Admissible, CTLogFormat::RFC6962,
1721666666000, // 2024-07-22T16:44:26Z
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"
@@ -547,7 +583,7 @@ const CTLogInfo kCTLogList[] = {
91},
#endif // 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
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"
@@ -569,6 +605,29 @@ const CTLogInfo kCTLogList[] = {
"\xbb\x02\x03\x01\x00\x01",
294},
#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[] = {
@@ -585,6 +644,9 @@ const CTLogOperatorInfo kCTLogOperatorList[] = {
#ifdef DEBUG
{"Mozilla Test Org 2", 8},
#endif // DEBUG
#ifdef DEBUG
{"Mozilla Test Org 3", 9},
#endif // DEBUG
};
} // namespace mozilla::ct

View File

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

View File

@@ -30,9 +30,10 @@ class CTLogVerifier {
public:
// |operatorId| The numeric ID of the log operator.
// |logState| "Qualified", "Usable", "ReadOnly", or "Retired".
// |logFormat| "RFC6962" or "Tiled"
// |timestamp| timestamp associated with logState.
CTLogVerifier(CTLogOperatorId operatorId, CTLogState logState,
uint64_t timestamp);
CTLogFormat logFormat, uint64_t timestamp);
// Initializes the verifier with the given subjectPublicKeyInfo.
// |subjectPublicKeyInfo| is a DER-encoded SubjectPublicKeyInfo.
@@ -46,6 +47,7 @@ class CTLogVerifier {
CTLogOperatorId operatorId() const { return mOperatorId; }
CTLogState state() const { return mState; }
CTLogFormat format() const { return mFormat; }
uint64_t timestamp() const { return mTimestamp; }
// Verifies that |sct| contains a valid signature for |entry|.
@@ -74,6 +76,7 @@ class CTLogVerifier {
DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
CTLogOperatorId mOperatorId;
CTLogState mState;
CTLogFormat mFormat;
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
// equal to 180 days, N is 2. Otherwise, N is 3.
// 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,
uint64_t certIssuanceTime,
Duration certLifetime) {
size_t admissibleCount = 0;
size_t admissibleOrRetiredCount = 0;
size_t rfc6962Count = 0;
std::set<CTLogOperatorId> logOperators;
std::set<Buffer> logIds;
for (const auto& verifiedSct : verifiedScts) {
@@ -90,6 +92,14 @@ CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
!LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) {
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
// admissible" case and the "from a log that was admissible or retired"
// case.
@@ -104,7 +114,8 @@ CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
}
size_t requiredEmbeddedScts = GetRequiredEmbeddedSctsCount(certLifetime);
if (admissibleCount < 1 || admissibleOrRetiredCount < requiredEmbeddedScts) {
if (admissibleCount < 1 || admissibleOrRetiredCount < requiredEmbeddedScts ||
rfc6962Count < 1) {
return CTPolicyCompliance::NotEnoughScts;
}
if (logIds.size() < requiredEmbeddedScts || logOperators.size() < 2) {
@@ -117,10 +128,12 @@ CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts,
// or OCSP response):
// 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,
// 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(
const VerifiedSCTList& verifiedScts) {
size_t admissibleCount = 0;
size_t rfc6962Count = 0;
std::set<CTLogOperatorId> logOperators;
std::set<Buffer> logIds;
for (const auto& verifiedSct : verifiedScts) {
@@ -130,12 +143,20 @@ CTPolicyCompliance NonEmbeddedSCTsCompliant(
if (verifiedSct.logState != CTLogState::Admissible) {
continue;
}
// SCTs from tiled logs "MUST" have a valid leaf index extension.
if (verifiedSct.logFormat == CTLogFormat::Tiled &&
verifiedSct.sct.leafIndex.isNothing()) {
continue;
}
admissibleCount++;
if (verifiedSct.logFormat == CTLogFormat::RFC6962) {
rfc6962Count++;
}
logIds.insert(verifiedSct.sct.logId);
logOperators.insert(verifiedSct.logOperatorId);
}
if (admissibleCount < 2) {
if (admissibleCount < 2 || rfc6962Count < 1) {
return CTPolicyCompliance::NotEnoughScts;
}
if (logIds.size() < 2 || logOperators.size() < 2) {

View File

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

View File

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

View File

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

View File

@@ -178,7 +178,8 @@ pkix::Result MultiLogCTVerifier::VerifySingleSCT(
}
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));
return Success;
}

View File

@@ -6,9 +6,72 @@
#include "SignedCertificateTimestamp.h"
#include "CTUtils.h"
namespace mozilla {
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() {
type = LogEntry::Type::X509;
leafCertificate.clear();

View File

@@ -8,6 +8,7 @@
#define SignedCertificateTimestamp_h
#include "Buffer.h"
#include "mozilla/Maybe.h"
#include "mozpkix/Input.h"
#include "mozpkix/Result.h"
@@ -65,12 +66,17 @@ struct SignedCertificateTimestamp {
V1 = 0,
};
pkix::Result DecodeExtensions();
Version version;
Buffer logId;
// "timestamp" is the current time in milliseconds, measured since the epoch,
// ignoring leap seconds. See RFC 6962, Section 3.2.
uint64_t timestamp;
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;
};

View File

@@ -33,7 +33,8 @@ class CTLogVerifierTest : public ::testing::Test {
void TearDown() override { signature_cache_free(mSignatureCache); }
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.
SignatureCache* mSignatureCache;
};
@@ -115,7 +116,7 @@ TEST_F(CTLogVerifierTest, ExcessDataInPublicKey) {
std::string extra = "extra";
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)));
}

View File

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

View File

@@ -35,25 +35,30 @@ class CTPolicyEnforcerTest : public ::testing::Test {
void AddSct(VerifiedSCTList& verifiedScts, size_t logNo,
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;
sct.version = SignedCertificateTimestamp::Version::V1;
sct.timestamp = timestamp;
sct.leafIndex = leafIndex;
Buffer logId;
GetLogId(logId, logNo);
sct.logId = std::move(logId);
VerifiedSCT verifiedSct(std::move(sct), origin, operatorId, logState,
LOG_TIMESTAMP);
logFormat, LOG_TIMESTAMP);
verifiedScts.push_back(std::move(verifiedSct));
}
void AddMultipleScts(VerifiedSCTList& verifiedScts, size_t logsCount,
uint8_t operatorsCount, SCTOrigin origin,
uint64_t timestamp,
CTLogState logState = CTLogState::Admissible) {
CTLogState logState = CTLogState::Admissible,
CTLogFormat logFormat = CTLogFormat::RFC6962) {
for (size_t logNo = 0; logNo < logsCount; logNo++) {
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 mozilla

View File

@@ -192,6 +192,90 @@ TEST_F(CTSerializationTest, DecodesSignedCertificateTimestamp) {
const size_t expectedSignatureLength = 71;
EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size());
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) {

View File

@@ -87,6 +87,55 @@ const char kTestSignedCertificateTimestamp[] =
"08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
"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
const char kEcP256PublicKey[] =
"3059301306072a8648ce3d020106082a8648ce3d0301070342000499783cb14533c0161a5a"
@@ -527,6 +576,27 @@ Buffer GetTestSignedCertificateTimestamp() {
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 GetTestInclusionProofUnexpectedData() {

View File

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

View File

@@ -30,7 +30,8 @@ class MultiLogCTVerifierTest : public ::testing::Test {
abort();
}
CTLogVerifier log(mLogOperatorID, CTLogState::Admissible, 0);
CTLogVerifier log(mLogOperatorID, CTLogState::Admissible,
CTLogFormat::RFC6962, 0);
;
ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey())));
mVerifier.AddLog(std::move(log));
@@ -222,7 +223,8 @@ TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog) {
TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromDisqualifiedLog) {
MultiLogCTVerifier verifier;
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())));
verifier.AddLog(std::move(log));

View File

@@ -51,6 +51,12 @@ function add_tests_in_mode(mode) {
Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT,
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.
add_ct_test(
"ct-insufficient-scts.example.com",

View File

@@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF5TCCBM2gAwIBAgIUa+BUsfUwwqRLVgVlQikCG31eMmkwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAiGA8yMDIzMTEyODAwMDAwMFoYDzIwMjYw
MjA1MDAwMDAwWjAlMSMwIQYDVQQDDBpjdC10aWxlZC12YWxpZC5leGFtcGxlLmNv
bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogG
NhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72xnAabbhqG8mvir1p1a2pkcQh6pVqn
RYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHu
p3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQ
Lzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p
47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo1
7Y0CAwEAAaOCAxowggMWMBgGA1UdEQQRMA+CDSouZXhhbXBsZS5jb20wggL4Bgor
BgEEAdZ5AgQCBIIC6ASCAuQC4gEvAFQiJZjzPTZIBULa7ODmuU3hXA7ujFkUykjX
XjxIToA/AAABUfp73AAAAAQBAQCJabucDWdKUJvzuE80Np7eZUCW2ZbLR3l/jX+N
o9R0I7QhVmvugafYV3Hy2yjAJUXIBCAFq/MKlSfbFGfg3aR7h1Y31zdaF7YMb3kF
1yvv7dmoxmF+ELqjmKALZBoLWq66uvOKrSfXfWo256tlc30FPAHQjisFxkw7LeJO
eZbuIIVBHMnyX/sVeDaIlXysiFVDrXs0c0Bv0vn+E7kiPScZfEAzW4tmN5ursFIy
PFR2CnzcpsQTr62nl1th2qteaQ3bb/kARlJk2cmFdWFhDs7NR4JVJ5cedknYyH5X
cximwIlAArlHSL/Angeuo51gqHwOdGPfdTfr/BfOLL3mpilFAHYAKrgwRDO5FN7S
8x5CB/JRwXo3oJJoUtkIAgb4Xlc5FioAAAFR+nvcAAAABAMARzBFAiBcdVGfExFQ
zV2K3iCjvAYwkf+yc3VfMWTs/ctCgApw5gIhAJZLBL70dv/nqol8uqJmab2wTrSz
tGQVeFOuTEbraKzRATcA6Mz6YX3GS9LYtKJsKw/1dnHx5n3gb4uVYfJVLXuUA5oA
AAFR+nvcAAAIAAAFhCHKllMEAQEAgLcoyQ3a159aRMB5gZkQ3vZQu7V9c+geuNPa
HqRfubtFYQU9PFuyaGf0WoxFuN38f0f7vhvCZ8mDnC3yj8DnoEx3gMfwtSRTWQHk
k0KSC61wlz3OI6Lf/B9uN8cw9cLSodBoyB6lDUGSlq3wz03+YXHHcCwospoZ8nwv
iiPo0N11M1IJYThYrpv270YIVjlTpln4TjmUKsIaB+JQgXWrzqjKmBxX/CkHQ6ul
Z/0rjVzBv8R1OjETk8MIlyB51N7Vb018Pek7AKo8M5QiwC2YHxhd4a55QfbTCMGC
//VaWYIvw1zMNocF6UKDPgZtqpzG04BHp502eDm4TYMfVpGISzANBgkqhkiG9w0B
AQsFAAOCAQEAR0KdN8iwoqCH+F8HL+vmlDXc2p/gGoRAHDS07bRi/cMlWFiWPfgp
iWVbXzuPDqZFHU3vYcmrZhi/MFlnnU/G5cRxPFRcJ8BPzNlnLb86PYYDSXTpiLsD
tdnwdMK7f1axuXjaNxlCznoQYjWh4hre9lzUjkPkO8l9kjtOxypSUnXQu96zLhnl
GrLe26P6NkUNagupy3za7USCi4Gej6nxFMH8Cou7mXdjPZMTWzf1DvgETOCFAwX7
lrPnJgl5GQ2u8C9H0FE1iI05krYaeNHGVb2Cq+4g5/ECEYPoTq9a75l7SeSngQEd
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-----
MIIF3DCCBMSgAwIBAgIUS2SODmE9dXhGEYS8JfCogtARiyEwDQYJKoZIhvcNAQEL
MIIFRTCCBC2gAwIBAgIUG4/k+n+iibx5ZgMTEJsAirIw46YwDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAiGA8yMDIzMTEyODAwMDAwMFoYDzIwMjYw
MjA1MDAwMDAwWjAlMSMwIQYDVQQDDBpjdC11bmtub3duLWxvZy5leGFtcGxlLmNv
bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqIUahEjhbWQf1utogG
@@ -8,27 +8,24 @@ RYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lMwmtsDu0Vgg/xfq1pGrHTAjqLKkHu
p3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF4LhIH6g304hD7ycW2FW3ZlqqfgKQ
Lzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20yvf8rR0l0wnvuRcOp2jhs3svIm9p
47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xxj5kXMbiz/kkj+nJRxDHVA6zaGAo1
7Y0CAwEAAaOCAxEwggMNMBgGA1UdEQQRMA+CDSouZXhhbXBsZS5jb20wggLvBgor
BgEEAdZ5AgQCBIIC3wSCAtsC2QEvAFQiJZjzPTZIBULa7ODmuU3hXA7ujFkUykjX
XjxIToA/AAABUfp73AAAAAQBAQCgHd8Scrtd8aIdXafr8o2gFUYzajTu2N0sbfC4
yb8C8KTCT2jcJJUoDvZ7KP2uPbzBwRvZvzMho885x+empY6u7esgTsq32PkW+rX5
NR0xWIcyWvgPIwk6IR/Y8WGI7ZYyEBNoqjxDKa+MpPUTMFn3W7WpidwCH7Fa7J49
hWxPLQ1G4RWoj5GjG0e/tFTzvQUWAbYROfQbbLQnJPXzEjt3C+3T6zCT/yo/HSiW
lC/ylgGc3lBgqnXqtmrGVvTez4ydnSM6DEKUCVtMyBCfVw3HSGzbrtMGvUUcsEI0
LcATaRJG/WdWjRLdacf2eO9bG8ngpLJQImK5p/9UOKM1TJW8AHUAKrgwRDO5FN7S
8x5CB/JRwXo3oJJoUtkIAgb4Xlc5FioAAAFR+nvcAAAABAMARjBEAiBcdVGfExFQ
zV2K3iCjvAYwkf+yc3VfMWTs/ctCgApw5gIgWAl+GxbSxwqeZmp4wx+zK38l6zW3
2KrjPq4QHn+9Fb4BLwDozPphfcZL0ti0omwrD/V2cfHmfeBvi5Vh8lUte5QDmgAA
AVH6e9wAAAAEAQEARvV0PDIvS8wZzGKzNBPdsAP/XPfF/ZzEA6z63dEKpIyiPgo1
vgoKc8EPyZfMirlvXU4pRSkuXFqmD7xKVjbfk6FdGlDeHm35qcQhg0wmmjGhJ+lJ
jVZB7Hqs6qCLxAR01HQy1sMkLT8iku+jvey+rdzF+hJLKZhACB+R0GR3gPnqN+mW
0HSCUwvYso0FIAkO8Xursq3jpjykaZVkAx2ETk3lnBwuMEw01Opn5497wyddA6WI
knVhzORNwH9NeH4vjo3vD49B57WbGN95G+DH7RHlrEwgyczOkS+BBwnt9u64pBxS
FgJj4PRggejuuQ/Eihu9GL5pbMPfUps4QTnFaDANBgkqhkiG9w0BAQsFAAOCAQEA
araaEEpMCOVu0Wi1DcyHdW1fdYPU+dX9GStO666LlgfJm9w3hcLCxVmn8Y4WUYbw
fmPghlsr0MNLg+6pyxmthDSKkE4yieckNkTUYybKZaEvFpaZQamHMtWzuPS9Uvab
XbxgC6/Z50nN3cPd6tLK7eyq2ZXzQf94vfn/EcJjkRGVdubPTw6URgu27FEJAxX1
XAJpdu5HwOj2+4aCNw6drMg+b/Dig+EpOTSBhalofyJxi3Lv/Kedzc9wlxqvmUvm
iMxWJob8wwaFRoL4YNBxxZEYI+Hebw8kePHhtwZ7h9q/BsgFajsWn5ay3PXmVvD2
Em16teHaNfB2r3e6sjuZBw==
7Y0CAwEAAaOCAnowggJ2MBgGA1UdEQQRMA+CDSouZXhhbXBsZS5jb20wggJYBgor
BgEEAdZ5AgQCBIICSASCAkQCQgEvAFQiJZjzPTZIBULa7ODmuU3hXA7ujFkUykjX
XjxIToA/AAABUfp73AAAAAQBAQCoorL4v69VDwyEYimAete5jKzdDsKvcwpHOdKm
0fRqi5wdqSIlqtjUyg1ThMC9+XY1INML8dcW12u50qjaqHUmb/2EcVi0qgrtTafI
yahbYk163rEmiW56AUt79egtYw05BfFb7JuJ51Hf5A4eCRWn2DA8u34/A7G1bse+
i2CKmRucCKti2P1kffZsX/nTj4e/Fa2Ch883T0upGRhuenLxPELeVap/PHrh5TJe
HxVC5+KwYeArMjiztQgpbw3k/7GoVeimSaZUbd5qmk27JJE1j2KUdqcjrLGYebZ8
ueRyUe1fomiI4kTitSo1ONLfP50Myegzbi+dEQm6gyVCRHJaAHYAKrgwRDO5FN7S
8x5CB/JRwXo3oJJoUtkIAgb4Xlc5FioAAAFR+nvcAAAABAMARzBFAiBcdVGfExFQ
zV2K3iCjvAYwkf+yc3VfMWTs/ctCgApw5gIhAKUxU/HmabPud/RzHLU7msT2kc62
iGdFqyuH0YqCReJuAJcAzXkXTRhLFnhJd6/HDruMins0z+2fuxg+i6a4e9MJANIA
AAFR+nvcAAAABAMAaDBmAjEA7QYnPqFoOlS02BpDdIRIljzmPr6BFwPs1z1y8KJU
BlnU7EVG6FbnXmVVt5Op9wDzAjEAuZvQO+69a4EqQct2T3/XNZr38qeLUdxdzSoh
yzj3fF54El39EGCOvdHaQB8IqpRMMA0GCSqGSIb3DQEBCwUAA4IBAQBxeaa3fzfc
MnYQi0mVcy2pMOkiyNa2Q0coKi+9hKRPIohG9Dddl2rHdnSBtjUBtt15cEJrfZPE
Q9nkGCUC6nRUH0id4cGJaDsTCMxik0P3gOMLtUpP2k1xux+sn0VRQ+iJdiIKkTh+
QJ53O6Apaf4lUUwPu5F/WYX685u8ruudphcf6gUeP87ldgyH3zQ4s9UQoER900pj
vmFJM/ZTOfBmSgLXevZYRE57ACi9fMjkGD/IXnj3sctl7EtM9jovygo4Hk4yjSpw
Cg7fMKIIzO2PU8WhrNlQgE+KKaDDNEZuD9p5MBh0tU+L8TUVU0Wtt2xTrb7lZXB1
WDb3zi3AJNht
-----END CERTIFICATE-----

View File

@@ -1,4 +1,4 @@
issuer:Test CA
subject:ct-unknown-log.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",
"log_list_timestamp": "2025-07-27T12:54:32Z",
"version": "59.19",
"log_list_timestamp": "2025-07-28T12:53:51Z",
"operators": [
{
"name": "Google",

View File

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

View File

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

View File

@@ -58,10 +58,16 @@ enum class CTLogState {
Retired,
};
enum class CTLogFormat {
RFC6962,
Tiled,
};
struct CTLogInfo {
// See bug 1338873 about making these fields const.
const char* name;
CTLogState state;
CTLogFormat format;
uint64_t timestamp;
// Index within kCTLogOperatorList.
size_t operatorIndex;
@@ -122,7 +128,7 @@ def get_operator_index(json_data, target_name):
LOG_INFO_TEMPLATE = """\
{$description, $state,
{$description, $state, $log_format,
$timestamp, // $timestamp_comment
$operator_index,$spaces // $operator_comment
$indented_log_key,
@@ -140,56 +146,71 @@ def map_state(state):
states to be included are 'qualified', 'usable', 'readonly', or 'retired'.
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"
elif state == "retired":
return "CTLogState::Retired"
elif state == "pending" or state == "rejected":
elif state in {"pending", "rejected"}:
return None
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):
"""Return array of CTLogInfo initializers for the known logs."""
tmpl = Template(LOG_INFO_TEMPLATE)
initializers = []
for operator in json_data["operators"]:
operator_name = operator["name"]
for log in operator["logs"]:
log_key = base64.b64decode(log["key"])
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:
continue
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
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)
if "logs" in operator:
for log in operator["logs"]:
initializer = get_initializer_for_log(
log, operator, json_data, "CTLogFormat::RFC6962"
)
if initializer:
initializers.append(initializer)
if "tiled_logs" in operator:
for log in operator["tiled_logs"]:
initializer = get_initializer_for_log(
log, operator, json_data, "CTLogFormat::Tiled"
)
if initializer:
initializers.append(initializer)
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_2)
json_data["operators"].append(mozilla_test_operator_3)
def get_content_at(url):
@@ -348,7 +395,7 @@ def run(args):
hash_alg = rsa.verify(json_text, signature, key)
if hash_alg != "SHA-256":
raise UnsupportedSignatureHashAlgorithmException(
"unsupported hash algorithm '%s'" % hash_alg
f"unsupported hash algorithm '{hash_alg}'"
)
print("Writing output: ", args.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 "
"of truth by default, but can also operate on a "
"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(