Add extra_data parsing support
This commit is contained in:
parent
94a8343be7
commit
e780d1aea8
10 changed files with 329 additions and 261 deletions
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
|
@ -7,16 +7,17 @@
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug executable 'ct'",
|
"name": "Debug unit tests in library 'ct'",
|
||||||
"cargo": {
|
"cargo": {
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"test",
|
||||||
"--bin=ct",
|
"--no-run",
|
||||||
|
"--lib",
|
||||||
"--package=ct"
|
"--package=ct"
|
||||||
],
|
],
|
||||||
"filter": {
|
"filter": {
|
||||||
"name": "ct",
|
"name": "ct",
|
||||||
"kind": "bin"
|
"kind": "lib"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
|
@ -25,17 +26,17 @@
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug unit tests in executable 'ct'",
|
"name": "Debug integration test 'log_entry_parsing'",
|
||||||
"cargo": {
|
"cargo": {
|
||||||
"args": [
|
"args": [
|
||||||
"test",
|
"test",
|
||||||
"--no-run",
|
"--no-run",
|
||||||
"--bin=ct",
|
"--test=log_entry_parsing",
|
||||||
"--package=ct"
|
"--package=ct"
|
||||||
],
|
],
|
||||||
"filter": {
|
"filter": {
|
||||||
"name": "ct",
|
"name": "log_entry_parsing",
|
||||||
"kind": "bin"
|
"kind": "test"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
|
|
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -191,6 +191,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"tokio",
|
||||||
"x509-parser",
|
"x509-parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1199,9 +1200,21 @@ dependencies = [
|
||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
|
@ -16,3 +16,6 @@ x509-parser = "0.16.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
base64ct = "1.6.0"
|
base64ct = "1.6.0"
|
||||||
|
reqwest = { version = "0.12.8", features = ["json"] }
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
tokio = { version = "1.41.0", features = ["rt-multi-thread", "macros"] }
|
||||||
|
|
|
@ -1,74 +1,5 @@
|
||||||
pub const LEAF_BASE64_BUFFER_SIZE: usize = 3072;
|
pub const LEAF_BASE64_BUFFER_SIZE: usize = 3072;
|
||||||
|
pub const EXTRA_DATA_BASE64_BUFFER_SIZE: usize = 10240;
|
||||||
|
|
||||||
pub const LEAF_HASH_PREFIX: u8 = 0x00;
|
pub const LEAF_HASH_PREFIX: u8 = 0x00;
|
||||||
pub const INTERIOR_HASH_PREFIX: u8 = 0x01;
|
pub const INTERIOR_HASH_PREFIX: u8 = 0x01;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) mod test_constants {
|
|
||||||
// TODO Write a script to populate this with random entries
|
|
||||||
pub(crate) const LEAF_INPUT_EXAMPLES: &[(&str, &str, u64)] = &[
|
|
||||||
(
|
|
||||||
"AAAAAAGSnKkrxAAAAAOJMIIDhTCCAwygAwIBAgISBIMWztGBRQE+UJezy64M233lMAoGCCqGSM49BAMDMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNjAeFw0yNDEwMTcyMTQ3NTRaFw0yNTAxMTUyMTQ3NTNaMB0xGzAZBgNVBAMTEnRlc3QubXlyaWF0aW9uLnh5ejBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCcExgJVV0VmpNBZLxu0fs4Gd/HY5a00y9gopHs8bHe7in2X+TqP+20WsT11e1RvddEXX7L+k2U9/jBiODsz7x6jggIVMIICETAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBGQZpFKlhRGYfUFTZCnc1mk50owMB8GA1UdIwQYMBaAFJMnRpgDqVFojpjWxEJI2yO/WJTSMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U2Lm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vZTYuaS5sZW5jci5vcmcvMB0GA1UdEQQWMBSCEnRlc3QubXlyaWF0aW9uLnh5ejATBgNVHSAEDDAKMAgGBmeBDAECATCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB3AObSMWNAd4zBEEEG13G5zsHSQPaWhIb7uocyHf0eN45QAAABkpypKigAAAQDAEgwRgIhAPoiAdbJaceE7OUIW1IkZdFD8MUSgrbMbe3b+Iaoy8hfAiEAm7BMs2I1ypCOSITJkJ/wAL190sYvE/aVSunjhUMelZ8AdgDgkrP8DB3I52g2H95huZZNClJ4GYpy1nLEsE2lbW9UBAAAAZKcqSpMAAAEAwBHMEUCIEnBfxjCFgX9kosEUzevs/svFmv2uWSOoa0vPU+iEZx8AiEAlqPAXBXYSTYb+SpCK918/9ScUxWdSpQpVnWmqpAhbjIwCgYIKoZIzj0EAwMDZwAwZAIwJMRuFTLJYtCaeQ90Nf9bwUfE2GtBKKaIhJIbPZ0qGsh2ZcAIkebpwx81c0JVrsaKAjA8YKB65FvxWhXEfvMTuZfGjhvOvruBo1LkXMQpKqiqYkSX6aRM3eoAqUFs09gE4h8AAA==",
|
|
||||||
"CN=test.myriation.xyz",
|
|
||||||
1729205185476
|
|
||||||
),
|
|
||||||
// Entries from https://oak.ct.letsencrypt.org/2025h2
|
|
||||||
(
|
|
||||||
"AAAAAAGJ/gGuKgAAAATzMIIE7zCCAtegAwIBAgIHBgMINo8NzTANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMRcwFQYDVQQKDA5Hb29nbGUgVUsgTHRkLjEhMB8GA1UECwwYQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MSMwIQYDVQQDDBpNZXJnZSBEZWxheSBJbnRlcm1lZGlhdGUgMTAeFw0yMzA4MTYxMTAxMTBaFw0yNTEyMDYwNjU0MThaMGMxCzAJBgNVBAYTAkdCMQ8wDQYDVQQHDAZMb25kb24xKDAmBgNVBAoMH0dvb2dsZSBDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxGTAXBgNVBAUTEDE2OTIxODM2NzAyMzA0NzcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCMCO5UNo/GSySPl+yBIvUSTqiw7AKSB4oUO2yeXwwBc32ePUPQKvuOTjRUEYE5/Yh7993HvCr3Nb+A5pLosYxCOQQRHZa+xSmtCYl2EEgwwqnJUOiUoccrN6FYXE3JGfYu4Moy+GofeM2h5n5nRi4tL2WanudJYuj63lhk/Msjfr/ntIU/18xNSQ9PdtIY9RinsN73zsGxWXlR1gamO1pbyLxbiImaRmF8zRWKXqODqb6ohPvThlbXB0P3r8uoroReanykMO8/WwAdZkD6TlD4ziQqt9eX8qe2NMsxrovnRcmAMIDEIjJ4kHaPg672SXKO2lN+tIu58XWRHcio1QGpAgMBAAGjgYswgYgwEwYDVR0lBAwwCgYIKwYBBQUHAwEwIwYDVR0RBBwwGoIYZmxvd2Vycy10by10aGUtd29ybGQuY29tMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU6TwE4YAvwoQTLSZwnvL9Gs+q/sYwHQYDVR0OBBYEFBo7+cBBDDd/KMeUsc1Vo3EpMz4VMA0GCSqGSIb3DQEBCwUAA4ICAQABe/kV4PedePHdRo/zTdCzPym6oPuPlOSBW76PvnX/TMcZSjbyis8ILrZ/DWlelVXFN44FTjQyh0gjOig06EgW20cF9pFuKPncskJLAu7FSOo3cYbRe/gySiWx17p6adkpbQo7jt0bctiFe2G/qXq5fr3p24+Ap7J7UmJ8ihcOVhKGVjj0tV2X7Q7LGQnzL1nK8uP61onh8uECI31YZ42CdqxDLxSSVvfLT4huZqy/QzWgva3dyVg62itefDePrgMHudPRijSfCuANbAA6bSlbpjNWkbNxRvuqygOuso14CzcJDF2eCPjfF0IxzxifWKbTge6Fd9C8XHavCEz1oWtJoBrXKQE0hsRcFfSFeV1xkxNXGAtUf0VA9PNZaZ4Z9+ghOQDnqeQQw6qxzp1AeR+DWUVUeBQDKt1/eYN0VXTpmMNisl3tEwRdmxTmSw2uFFRgHgAIFWDgKfNVg1PnTGje8frPOA7fTYqLFzeeDAUfaNXmizQoDB25ifJIhvRqX+gl6Ff+i+uXIeHc097UbRK2bPKIf8XtqD94n48OnfmJBvcGnTl5PaI4Bec3J4tpZ/w6FhZ17FIvP3hxaERmFt89SyYKzCLC2bhnVByHi2f8/jYJVv2nMycjoDxMTJUFRDCn+3BAcAyYbibc+BQLNlGMS04YDZerEJ4BmWTDd1nQZgAA",
|
|
||||||
"C=GB, L=London, O=Google Certificate Transparency, serialNumber=1692183670230477",
|
|
||||||
1692183670314
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGKCs8mKgAAAAU2MIIFMjCCAxqgAwIBAgIJAPXCmTa0J/XPMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxIzAhBgNVBAMMGk1lcmdlIERlbGF5IEludGVybWVkaWF0ZSAxMB4XDTI1MDgxNzA5NDIyMloXDTI1MDgxODA5NDIyMlowdTELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEPMA0GA1UEChMGR29vZ2xlMSEwHwYDVQQLExhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMTGGZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJtHYIEazPHQhDE0LDSZlFexMoMoRmAZ1711p5HW1LjQcEFpULoTa52o+LsqEC7plZd7V77TaTfYG51NRE9cOwl/ndxtItJTECnp09NL1LOvXHWckeR+EAUfA2uewCmdgJ+mjndVwIH98jqapH4+ANgiGQEf5TsYvQAQQUYUmXX2zkoTJlQ2ZRMr1pwxAXMQJrEsOq/IZhirK7TGu5gRikQuvWco3ZX2/G587HnC2uEVd8FkRzZjxVjMk9TZY3p+LVF+K51tArquD8zID8P8BFdOiZl4oEaWveRollsrBOSalaXLiH8m26j1UqhVNKgEuxGBZW2DIOzU/Z1hXr7dZRkCAwEAAaOBujCBtzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBTpPAThgC/ChBMtJnCe8v0az6r+xjBhBgNVHREEWjBYghhmbG93ZXJzLXRvLXRoZS13b3JsZC5jb22CPDIyLjE4LjA4LjIwMjMubGV0c2VuY3J5cHRfb2FrMjAyNWgyLmZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbTANBgkqhkiG9w0BAQsFAAOCAgEAgxaaRC6InFBZbrGbhytR6Am5EZv++G0VBTeh5JWJTNyhwF1wD0yyCP0uPMdlWBpQFBXmp/pwGQ72pBE4hboKDMz9wLPpSu1kHh0KUCt8ugjssfk59P9ECGcCxmg2mGIXE0kCFwlJ5xGlIzNiD/hyJGM28zwM6iZ+sITmF+2dzxBa54bVFPDtD3+A5d47ad5XIFAzzMIsJjfrEnFwLU69MffyuhABhIBUzlzU6mPKOPoXpsP9MDCwAEljiyksHKdOcxoPDlchTRvPAn+FngkELiR80HbHsi703UG3KGBSNrDiQCHNCmO/lF6c1kN+/Ij29zu+Wle8Q8veJAanKE8WDGnlU1pFw9nuCaIAtxX8AiMUDuLrAHj38Mj7MIQS4CzloAp4UMqvaupOc+cxJ/8psbUy+MkI6dMww3N5gZE6EV6WCF21Ogm/zqMQwy/Fj1P7dpl+BFYEmJzP0yhiZMh2a9okbpienKL8zaiz/b/27ffO/IxaxT5IwJ60k6Bj9kB9M3H9leFA5EgSpe7buoK86z8LQJ+KHCWErl616TsW2KocycD+dAyNsGk9OlSL8WpKDV5KFmyO1HWDCIpC8WYE/miWH1zwKbmDVJJOEVUBbfNWwy9EbgxHdDvwYChC9/EfIOxCOEj/JQZoEiHjVUy0PGsO59JaxG/citHaQr5Uv48AAA==",
|
|
||||||
"C=GB, L=London, O=Google, OU=Certificate Transparency, CN=flowers-to-the-world.com",
|
|
||||||
1692398462506
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGKDLItnQAB43aJADBzoMZJzGVt6UbAMXTSXFZv48OAW4RvUjaUN5gAAx0wggMZoAMCAQICCHHWBy81SwwSMA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xFzAVBgNVBAoMDkdvb2dsZSBVSyBMdGQuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxIzAhBgNVBAMMGk1lcmdlIERlbGF5IEludGVybWVkaWF0ZSAxMB4XDTI2MDEwNTE4MDkwNVoXDTI2MDEwNjE4MDkwNVowdTELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEPMA0GA1UEChMGR29vZ2xlMSEwHwYDVQQLExhDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kxITAfBgNVBAMTGGZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALlyEW37k0qzzAURkI8auNwkyG/p8lh802KPRueNahXxaf73M9vTMIl2czqosnYxhGKmcXdi8jgtRrUJ69+/qmxX7sG454Odz2ocWKGWcwNvhw/roqbATUBYG32sPpUkUTuyeB6nJY1aEQnDDTTIkbx8zBlKo9e3E9lkYVi5rLvbdGBwm+YaopW9TaVSl/npkPXCDUv5LDvMyKDREu2GW/NRLWErP9eg0FB9RpdaXRcAvrNh3/a+NMxRhjJX0oH3xhIvlkoCADJX2dU1V1Bd/QUuvSW/0BjdXRjl9ivmcN3WhhTE+ZpVUusJHJ6wB0bYLlWR5bCxqYbpmsRsov49tZ8CAwEAAaOBujCBtzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBTpPAThgC/ChBMtJnCe8v0az6r+xjBhBgNVHREEWjBYghhmbG93ZXJzLXRvLXRoZS13b3JsZC5jb22CPDA3LjE5LjA4LjIwMjMubGV0c2VuY3J5cHRfb2FrMjAyNWgyLmZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbQAA",
|
|
||||||
"C=GB, L=London, O=Google, OU=Certificate Transparency, CN=flowers-to-the-world.com",
|
|
||||||
1692430118301
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGKDNNwjwAB43aJADBzoMZJzGVt6UbAMXTSXFZv48OAW4RvUjaUN5gAAx4wggMaoAMCAQICCQCQAkpeGcWGRjANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMRcwFQYDVQQKDA5Hb29nbGUgVUsgTHRkLjEhMB8GA1UECwwYQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MSMwIQYDVQQDDBpNZXJnZSBEZWxheSBJbnRlcm1lZGlhdGUgMTAeFw0yNjAxMTgyMTU3MjNaFw0yNjAxMTkyMTU3MjNaMHUxCzAJBgNVBAYTAkdCMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2dsZTEhMB8GA1UECxMYQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MSEwHwYDVQQDExhmbG93ZXJzLXRvLXRoZS13b3JsZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZm2mTxtaoH0AIZ0ZVTduHeCIo0kC8M6T6OPQw07Ock+mhtMblMnk7KZAsmgCFWSa6sFQ/IdLuDreWAVSdYJqeNHtDb+Hrla+sDUXDheNEqBFFav5o/G3kO+csbp3sR9If6dZiGHwzLC8ismQT2QDUgvqw/sb/7GrojArouVbvhFR2HBjD73qF5/cOzmOuoLg1OhHIsn5dBtEmK5izkgrrk0CULanGiDzAcGP6i+ldAOnES8Y1hTkKyT1d9woxmBHmVZ/wyKXiL//6h6A6qTVQ0QGGf4RvhW9+v6LhBMQcZEyoSMc164cvc6iaThcbS3koAr9nUuMyG2XpLo5TCexvAgMBAAGjgbowgbcwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU6TwE4YAvwoQTLSZwnvL9Gs+q/sYwYQYDVR0RBFowWIIYZmxvd2Vycy10by10aGUtd29ybGQuY29tgjwwOC4xOS4wOC4yMDIzLmxldHNlbmNyeXB0X29hazIwMjVoMi5mbG93ZXJzLXRvLXRoZS13b3JsZC5jb20AAA==",
|
|
||||||
"C=GB, L=London, O=Google, OU=Certificate Transparency, CN=flowers-to-the-world.com",
|
|
||||||
1692432298127
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGKX6GKQwAAAAU1MIIFMTCCAxmgAwIBAgIIUh+QcsMl2wgwDQYJKoZIhvcNAQELBQAwfzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEjMCEGA1UEAwwaTWVyZ2UgRGVsYXkgSW50ZXJtZWRpYXRlIDEwHhcNMjUwODIyMjE1MDM3WhcNMjUwODIzMjE1MDM3WjB1MQswCQYDVQQGEwJHQjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29nbGUxITAfBgNVBAsTGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEhMB8GA1UEAxMYZmxvd2Vycy10by10aGUtd29ybGQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAilgJxY/IGyH6LHfhdfF+Yi1rvP7Fbeblmlvjw6tKJslXcT4KZkctPPa/jJxXG6KsMz/aoncQeXEVREnDocyIKhh5HdnIxuu0dovOvmd2zCbjDFHyEgkUzmUEfb0ysHwSETSGi7b4C0nNDrjsQ/w4TDx87CDjUM8xSiI9R6YpkGShEZ0bTpO/X3PtvHeAa1bctQ9sXyYdUW33Jh5yV7LOl7cnwAawlUTGowcip0kXVNQJYzmx3HOsklkiyrqQFPoNYPNWHxX5hxdCxvd1F1+ga1L+7hg5pZOnqOWMA8JbnktqH0Jb2TVgmWDpFe15qpKbJOfb/1XUef450bZqlOT7lwIDAQABo4G6MIG3MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFOk8BOGAL8KEEy0mcJ7y/RrPqv7GMGEGA1UdEQRaMFiCGGZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbYI8MDkuMDQuMDkuMjAyMy5sZXRzZW5jcnlwdF9vYWsyMDI1aDIuZmxvd2Vycy10by10aGUtd29ybGQuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQB8B5jCukPDWYAwTTL2tVKyMcQAZNU8N5C2pn4hnuvjHIIpXeDuGtDwW8iGQYoi7AN/a8+ImWbjcTgdlxicICc/52gNyLXNKLpPVK5Hwloq8JRkyShezS9izBLX9NzNhGN4DGUPaptIgcMcLPv1hjCQy3ZEbh2lip5j9ZqhUuMPaR8f+K0S6y8NlRjPgwXtJXIQMwuIvSfWIHPgwnf16v9p/eW8vcoLPD2aM5VD2XUZJrA/AT6EjVRH2rvdF0urDODFtm4EP6dDvRpcCAgAmmFUEyrTQIlvC0S6+c4PsIDcdI3TiF+XrAree54jBXGYH2pBqShH/sVfEKSJw17DTvat1tNgyBxIHWl+7AKH23L0W1w4xR35eZVyGsEkGi8EFQT70V7VFuGiUw53W1NF/2GxBet/ywDD5x/R6XyrbNP56B/KGSE24Z7iIyIl3FRKE4LHZ97fH2o8SkLHSskSwiVSQd3/gk9jf6d8fVIyFj2ZDvzFks0ACepvJ2hVbTJHvqVWoRyT5lxJ6Mmb01bhocJsxgcCgvs361lBSa4URS5a7u2jULcsFnx8niHjhMJHjXnhc+Me3UduIYGA/c2gJ+IX8dpPBeL/eYx+3bsCmcVH2w94j3IiC49APA6ZMhuBXh351/X4RcHODDiNLqe0rLPqPbfdl+d3r+pVLuRnb4lKygAA",
|
|
||||||
"C=GB, L=London, O=Google, OU=Certificate Transparency, CN=flowers-to-the-world.com",
|
|
||||||
1693821536835
|
|
||||||
),
|
|
||||||
// Entries from https://oak.ct.letsencrypt.org/2024h1
|
|
||||||
(
|
|
||||||
"AAAAAAGEy7byEAABJSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFAAA38wggN7oAMCAQICEAx1UGAuOrXH8cPGn545U3kwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEVMBMGA1UECxMMU2VydmVyIENBIDFCMQ8wDQYDVQQDEwZBbWF6b24wHhcNMjIxMjAxMDAwMDAwWhcNMjMxMjMxMjM1OTU5WjAqMSgwJgYDVQQDDB8qLmQzcHgwd3Y3ZjF0cTdrLmFtcGxpZnlhcHAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwOoOv6C59kRAOoUxIP5jsxS2hVqKWV4BAPuZEz1b2s2riCGJRDWjhHhT8yxFTDXOnhl0UxcJ82gaAnK92i00eeShWenqfGcPw6crMs2xmKQL2CywwOqKSpVquY2ySZDhQ2xdPCH4RjLNlUR/KDknrJXFU0LK6kTGpzmPcgokmqh544XoOTyOLh7DvYaJ1Hwv6cGk1vTXy8Z5YqUjWor+tNLzjDSbCSQY/WrG6kButBH3jCSt6KLqCcuRpBc2ObpSa2LMFjFVYEO34hWRRMLZzhaLCJtMo21s2mpSnbuV/esihwEOCU28MlzB2sVPXAbd0IofyqkRBVkbAyquUf+NWwIDAQABo4IBlzCCAZMwHwYDVR0jBBgwFoAUWaRmBlKge5WSPKOUByeWdFv5PdAwHQYDVR0OBBYEFMZcVj6Sv4Q7nebAzjRjAhKGI4nSMEkGA1UdEQRCMECCHyouZDNweDB3djdmMXRxN2suYW1wbGlmeWFwcC5jb22CHWQzcHgwd3Y3ZjF0cTdrLmFtcGxpZnlhcHAuY29tMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2NybC5zY2ExYi5hbWF6b250cnVzdC5jb20vc2NhMWItMS5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwdQYIKwYBBQUHAQEEaTBnMC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5zY2ExYi5hbWF6b250cnVzdC5jb20wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuc2NhMWIuYW1hem9udHJ1c3QuY29tL3NjYTFiLmNydDAMBgNVHRMBAf8EAjAAAAA=",
|
|
||||||
"CN=*.d3px0wv7f1tq7k.amplifyapp.com",
|
|
||||||
1669865075216
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGEy7dwawAB18tkPyr2nckv4fgo0dhAkaUtJ2hu2831xlO2SKhq8dgAA3MwggNvoAMCAQICEAE1jj/k+7zQMDuEt/osQZ8wDQYJKoZIhvcNAQELBQAwPDELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEcMBoGA1UEAxMTQW1hem9uIFJTQSAyMDQ4IE0wMjAeFw0yMjEyMDEwMDAwMDBaFw0yMzEyMzEyMzU5NTlaMCoxKDAmBgNVBAMMHyouZDE3cm5ydzhseWlyYjMuYW1wbGlmeWFwcC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8CPTybP53vjsRdvl95sAfLQkkPoojyq5ifzUBUUv/E9MYiUjatH+QSguLKFo/mD9NBN2JP7UKUR6BmBj0A5kUhTDWeM2DjO7W8tAN55fO17nyEs23bErQOJxuajM6Uf8q7M768IocncqHJ5wEnR1NoO+pZ+ubANRJsgygxfuNEwnBZTEX7Igvx4P+gMH/IvZwRkKPY01nsvIB3TZECNqF0CDkyVs3Lq6Aez0rvCRcg3StEaLhV2YpSre+XnrVCep0gsgFGDEia73QX04t7PicxqGmYh9ACvsJ+n5ktDhmDfwo3ErV72fGGu9GCQ/U0VrwN8z3RLm1Ocf3bWQ+ltBXAgMBAAGjggGVMIIBkTAfBgNVHSMEGDAWgBTAMVLNWlDDgnx0cc7L6Zz5euuC4jAdBgNVHQ4EFgQUPDyOfCFS8b1aJVy/6CCc9/3YQjUwSQYDVR0RBEIwQIIfKi5kMTdybnJ3OGx5aXJiMy5hbXBsaWZ5YXBwLmNvbYIdZDE3cm5ydzhseWlyYjMuYW1wbGlmeWFwcC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY3JsLnIybTAyLmFtYXpvbnRydXN0LmNvbS9yMm0wMi5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwdQYIKwYBBQUHAQEEaTBnMC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5yMm0wMi5hbWF6b250cnVzdC5jb20wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQucjJtMDIuYW1hem9udHJ1c3QuY29tL3IybTAyLmNlcjAMBgNVHRMBAf8EAjAAAAA=",
|
|
||||||
"CN=*.d17rnrw8lyirb3.amplifyapp.com",
|
|
||||||
1669865107563
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGEy7ecXQABJSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFAAA60wggOpoAMCAQICEA2e4xabSznm/RQ5D2RE1pgwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEVMBMGA1UECxMMU2VydmVyIENBIDFCMQ8wDQYDVQQDEwZBbWF6b24wHhcNMjIxMjAxMDAwMDAwWhcNMjMxMjMxMjM1OTU5WjBBMT8wPQYDVQQDEzZxeXl5Z3JucG5maDN6eGxzYTZxa2VuYnRxeS5hcC1zb3V0aC0xLmVzLmFtYXpvbmF3cy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXMwBZs3dFeAl3CKe24HMnApkzvHZLrURn2my9V+wvR4UrmG06aU49D5CX3c1zNU7OE9EF0iIru+SR3s65F7zD61N4OwQyAW8uk/ZjF7S8P/jmRlmBYUmFag6bJXUovrL6aSUkcafevhx1vysA5ebnphEoD2c7V3CPihFqgHyZvdUGT498lv/d2la6XLH7BNyI4oCjEH7waIREnSMxbh4IFmnuJxTKjKDPRm3wL/ArdHEAhrT3p9cOD248Ba5tiibQo5ULADyn5K8e71APOiVpBKia6mn2O4tR+tD4kVDJF4VEIY2/2qbmqz+xvkksR23yuNJIzqMbpEIFNp82w2EtAgMBAAGjggGuMIIBqjAfBgNVHSMEGDAWgBRZpGYGUqB7lZI8o5QHJ5Z0W/k90DAdBgNVHQ4EFgQUvY51uZyNjFkbakm4Kj8eheikuhUwYAYDVR0RBFkwV4I2cXl5eWdybnBuZmgzenhsc2E2cWtlbmJ0cXkuYXAtc291dGgtMS5lcy5hbWF6b25hd3MuY29tgh0qLmFwLXNvdXRoLTEuZXMuYW1hem9uYXdzLmNvbTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwuc2NhMWIuYW1hem9udHJ1c3QuY29tL3NjYTFiLTEuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAtBggrBgEFBQcwAYYhaHR0cDovL29jc3Auc2NhMWIuYW1hem9udHJ1c3QuY29tMDYGCCsGAQUFBzAChipodHRwOi8vY3J0LnNjYTFiLmFtYXpvbnRydXN0LmNvbS9zY2ExYi5jcnQwDAYDVR0TAQH/BAIwAAAA",
|
|
||||||
"CN=qyyygrnpnfh3zxlsa6qkenbtqy.ap-south-1.es.amazonaws.com",
|
|
||||||
1669865118813
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGDEsi8aAAB43aJADBzoMZJzGVt6UbAMXTSXFZv48OAW4RvUjaUN5gAAtswggLXoAMCAQICBwXoAWAfbEIwDQYJKoZIhvcNAQELBQAwfzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEjMCEGA1UEAwwaTWVyZ2UgRGVsYXkgSW50ZXJtZWRpYXRlIDEwHhcNMjIwOTA2MTIzMTI4WhcNMjQwMzI3MTIzNjAwWjBjMQswCQYDVQQGEwJHQjEPMA0GA1UEBwwGTG9uZG9uMSgwJgYDVQQKDB9Hb29nbGUgQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MRkwFwYDVQQFExAxNjYyNDY3NDg4ODM2Njc0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwu9Z9FYOVuAKPPWDFTjYhJEur3Co6LU/NbXJ4x3gHbXxHYmNl9OPOhURDW7p9X8MCtDyMjOKWBeEbzSxICXM+vvXMdEfOuB02HZ5RtbsHISbOw/vd1+pl3Nb0fiq/8Q086VzzrmfeBzFfG4BFBqOjb2g+J3retqDmvK9GCnp1kmwBV1jE05Bmdl2fjyZqE6xh6zgzGPvGpr2OC+pSytfd0oNS0V/WDSq5xI6TqFdcAY83vo+Vbt6KTbPLdU96aToxrd/evFAyyPyn/Vy9FUS+k4LcAYlycAZ5LML610kE5gYdh9UnUSdT04Sf94sYcaXEuHCu3Q7tx2a6nhsByLVuQIDAQABo4GLMIGIMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCMGA1UdEQQcMBqCGGZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFOk8BOGAL8KEEy0mcJ7y/RrPqv7GMB0GA1UdDgQWBBSMSOrfFXpOlTDmSRMME4rvGdOGuQAA",
|
|
||||||
"C=GB, L=London, O=Google Certificate Transparency, serialNumber=1662467488836674",
|
|
||||||
1662467488872
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"AAAAAAGDIBwiqQAB43aJADBzoMZJzGVt6UbAMXTSXFZv48OAW4RvUjaUN5gAAtswggLXoAMCAQICBwXoNW3movkwDQYJKoZIhvcNAQELBQAwfzELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEXMBUGA1UECgwOR29vZ2xlIFVLIEx0ZC4xITAfBgNVBAsMGENlcnRpZmljYXRlIFRyYW5zcGFyZW5jeTEjMCEGA1UEAwwaTWVyZ2UgRGVsYXkgSW50ZXJtZWRpYXRlIDEwHhcNMjIwOTA5MDIzNzM4WhcNMjQwNjEzMjE0OTAwWjBjMQswCQYDVQQGEwJHQjEPMA0GA1UEBwwGTG9uZG9uMSgwJgYDVQQKDB9Hb29nbGUgQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5MRkwFwYDVQQFExAxNjYyNjkxMDU4Mjk1NTQ1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv139MZdfpcq6+OQMmxbsO0a9UkyXjyObBcgJzQ+g8KtUdvQQptLmN++pqfnxmI18IyqtZyekpH/enlbEVIdcJ9O0hSMtTM92CaHudKLpV46SEhJcbzwUd9u1w7EdnKjjUq6gmj9zckOjTg09KJyJ95EKbv0ZpYFhsMOeTyhHidYy2wNCNJhO9nx3YE/cnQ9UVo8R3m9dlcDRgHtdQwMjMjPomKgsmJgEXVPPwjKMhVR25O2u21cqsVxItOgEa8WyeR/0awkm1+OMUdEtv0CDSqc5mwKc63dNAnHjJjVq68LJAPfzbR72lW0CxZETOIKI5Hd/jSrL96Cu57RXsC7YRQIDAQABo4GLMIGIMBMGA1UdJQQMMAoGCCsGAQUFBwMBMCMGA1UdEQQcMBqCGGZsb3dlcnMtdG8tdGhlLXdvcmxkLmNvbTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFOk8BOGAL8KEEy0mcJ7y/RrPqv7GMB0GA1UdDgQWBBSRIw7tu+hiIeKf0gO+lTZfhwxK6gAA",
|
|
||||||
"C=GB, L=London, O=Google Certificate Transparency, serialNumber=1662691058295545",
|
|
||||||
1662691058345
|
|
||||||
),
|
|
||||||
// Entries from https://ct.googleapis.com/logs/eu1/xenon2025h1
|
|
||||||
(
|
|
||||||
"AAAAAAGSl0salwAAAAPKMIIDxjCCA0ygAwIBAgISBPmkPV0s/bLTwy6BR95mo9omMAoGCCqGSM49BAMDMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNTAeFw0yNDEwMTYyMDQ3MDNaFw0yNTAxMTQyMDQ3MDJaMD4xPDA6BgNVBAMTM3BvY2h0YWJhbmsuby5hdml0by5yeWdpbm1sZDFhd28wdmUuZWlwLmVuc2ltcG9jLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKY6DHnXvMW2xICZcpJ//cr2ttCyBjUk8xonMewjCUPqXS0z79qgCaqFzbaOrqDc61LoOux19e3nhyIBL6QrM3WjggI0MIICMDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMlIByYCus9M860DtNvXARmDYBjJMB8GA1UdIwQYMBaAFJ8rX888IU+dBLftKyzExnCL0tcNMFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U1Lm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZodHRwOi8vZTUuaS5sZW5jci5vcmcvMD4GA1UdEQQ3MDWCM3BvY2h0YWJhbmsuby5hdml0by5yeWdpbm1sZDFhd28wdmUuZWlwLmVuc2ltcG9jLmNvbTATBgNVHSAEDDAKMAgGBmeBDAECATCCAQMGCisGAQQB1nkCBAIEgfQEgfEA7wB1AM8RVu7VLnyv84db2Wkum+kacWdKsBfsrAHSW3fOzDsIAAABkpdLGCsAAAQDAEYwRAIgSUOJf8l6+Tzmv+ClWY4C3MaDztQMXt6G1HGHlctLUawCIFa+17WSp6uk4RqCiY1e1LVUaNmbIDUhiR2AX05MhdQQAHYAE0rfGrWYQgl4DG/vTHqRpBa3I0nOWFdq367ap8Kr4CIAAAGSl0sZMwAABAMARzBFAiEAlN9seh/z9GSf2nMwvEOpx1noTqxv6yPKhB276U6jd0YCIE6L4TKVHY+7FIwSjP73VT+AJfzsss4f2KsqMlgHx1LVMAoGCCqGSM49BAMDA2gAMGUCMCNwRE9tv2JA3iIdpagARFw64TGL/4yec0YwknBvECnafyPwwkYo7mbvowPS4QowOgIxAKvb2zBO/+ykpeWBBOJ4wvoGAHCHcGUKeszXXBycjfvlSeh+l4DhXiLpAD6fn4ANfwAA",
|
|
||||||
"CN=pochtabank.o.avito.ryginmld1awo0ve.eip.ensimpoc.com",
|
|
||||||
1729115134615
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use x509_parser::prelude::TbsCertificate;
|
use x509_parser::prelude::{TbsCertificate, X509Certificate};
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -35,3 +35,15 @@ pub struct MerkleTreeLeaf<'a> {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub leaf_type: MerkleLeafType<'a>
|
pub leaf_type: MerkleLeafType<'a>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ChainEntry<'a> {
|
||||||
|
pub main_certificate: X509Certificate<'a>,
|
||||||
|
pub certificate_chain: Vec<X509Certificate<'a>>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EntryExtraData<'a> {
|
||||||
|
X509Certificate(Vec<X509Certificate<'a>>),
|
||||||
|
Precertificate(ChainEntry<'a>)
|
||||||
|
}
|
||||||
|
|
182
src/parsing/entry.rs
Normal file
182
src/parsing/entry.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
use x509_parser::{error::X509Error, prelude::X509Certificate};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
structures::{parse_tbs_certificate_der, parse_x509_der},
|
||||||
|
LeafParsingEnumType,
|
||||||
|
LeafParsingError
|
||||||
|
};
|
||||||
|
use crate::merkle::types::{
|
||||||
|
ChainEntry,
|
||||||
|
EntryExtraData,
|
||||||
|
LogEntryType,
|
||||||
|
MerkleLeafType,
|
||||||
|
MerkleTreeLeaf,
|
||||||
|
Version
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parses a MerkleTreeLeaf structure as specified in [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962):
|
||||||
|
/// ```txt
|
||||||
|
/// struct {
|
||||||
|
/// Version version;
|
||||||
|
/// MerkleLeafType leaf_type;
|
||||||
|
/// select (leaf_type) {
|
||||||
|
/// case timestamped_entry: TimestampedEntry;
|
||||||
|
/// }
|
||||||
|
/// } MerkleTreeLeaf;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This function assumes a binary format for the leaf, rather than a
|
||||||
|
/// base64-encoded version, so make sure to manually decode it from the HTTP
|
||||||
|
/// response before use.
|
||||||
|
pub fn parse_merkle_tree_leaf(
|
||||||
|
input: &[u8]
|
||||||
|
) -> nom::IResult<&[u8], MerkleTreeLeaf, LeafParsingError<&[u8]>> {
|
||||||
|
let (input, version /* Version version; */) = nom::number::complete::u8(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
||||||
|
let (input, leaf_type /* MerkleLeafType leaf_type; */) =
|
||||||
|
nom::number::complete::u8(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
||||||
|
|
||||||
|
let version = match version {
|
||||||
|
0 => Version::V1,
|
||||||
|
_ =>
|
||||||
|
return Err(nom::Err::Failure(LeafParsingError::InvalidEnum {
|
||||||
|
input,
|
||||||
|
enum_type: LeafParsingEnumType::Version
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
let (input, leaf_type) = match leaf_type {
|
||||||
|
// struct {
|
||||||
|
// uint64 timestamp;
|
||||||
|
// LogEntryType entry_type; /* 2 bytes */
|
||||||
|
// select(entry_type) {
|
||||||
|
// case x509_entry: ASN.1Cert;
|
||||||
|
// case precert_entry: PreCert;
|
||||||
|
// } signed_entry;
|
||||||
|
// CtExtensions extensions;
|
||||||
|
// } TimestampedEntry;
|
||||||
|
0 => {
|
||||||
|
let (input, timestamp /* uint64 timestamp; */) =
|
||||||
|
nom::number::complete::be_u64(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
||||||
|
let (input, entry_type /* LogEntryType entry_type; */) =
|
||||||
|
nom::number::complete::be_u16(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
||||||
|
|
||||||
|
let (input, entry_type) = match entry_type {
|
||||||
|
0 => {
|
||||||
|
let (input, der /* opaque ASN.1Cert<1..2^24-1> */) =
|
||||||
|
parse_x509_der(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::DerParsing(e)))?;
|
||||||
|
|
||||||
|
(input, LogEntryType::X509Entry(der))
|
||||||
|
}
|
||||||
|
// case precert_entry: PreCert;
|
||||||
|
1 => {
|
||||||
|
let (
|
||||||
|
input,
|
||||||
|
issuer_key_hash // opaque issuer_key_hash[32];
|
||||||
|
) = nom::bytes::complete::take(32usize)(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
||||||
|
let (
|
||||||
|
input,
|
||||||
|
tbs_certificate // TBSCertificate tbs_certificate;
|
||||||
|
) = parse_tbs_certificate_der(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::DerParsing(e)))?;
|
||||||
|
|
||||||
|
(input, LogEntryType::PrecertEntry {
|
||||||
|
issuer_key_hash: issuer_key_hash.try_into().map_err(|_| {
|
||||||
|
nom::Err::Failure(LeafParsingError::InvalidTakeLength)
|
||||||
|
})?,
|
||||||
|
tbs_certificate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
return Err(nom::Err::Failure(LeafParsingError::InvalidEnum {
|
||||||
|
input,
|
||||||
|
enum_type: LeafParsingEnumType::LogEntryType
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (
|
||||||
|
input,
|
||||||
|
extensions // opaque CtExtensions<0..2^16-1>
|
||||||
|
) = nom::multi::length_data(nom::number::complete::be_u16)(input)
|
||||||
|
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
||||||
|
|
||||||
|
(input, MerkleLeafType::TimeStampedEntry {
|
||||||
|
timestamp,
|
||||||
|
entry_type,
|
||||||
|
extensions
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ =>
|
||||||
|
return Err(nom::Err::Failure(LeafParsingError::InvalidEnum {
|
||||||
|
input,
|
||||||
|
enum_type: LeafParsingEnumType::MerkleLeafType
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((input, MerkleTreeLeaf { version, leaf_type }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a parser for an entry `extra_data`, with prior knowledge of the
|
||||||
|
/// leaf_input being an x509 entry rather than a precert entry
|
||||||
|
pub fn parse_entry_extra_data_x509<'a>(
|
||||||
|
input: &[u8]
|
||||||
|
) -> nom::IResult<&[u8], EntryExtraData, X509Error> {
|
||||||
|
nom::combinator::map(parse_certificate_chain, |chain| {
|
||||||
|
EntryExtraData::X509Certificate(chain)
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a parser for an entry `extra_data`, with prior knowledge of the
|
||||||
|
/// leaf_input being a precert entry rather than an x509 entry
|
||||||
|
pub fn parse_entry_extra_data_precert<'a>(
|
||||||
|
input: &[u8]
|
||||||
|
) -> nom::IResult<&[u8], EntryExtraData, X509Error> {
|
||||||
|
nom::combinator::map(parse_chain_entry, |entry| {
|
||||||
|
EntryExtraData::Precertificate(entry)
|
||||||
|
})(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an `ASN.1Cert [pre]certificate_chain<0..2^24-1>;` from the
|
||||||
|
/// X509ChainEntry or PrecertChainEntry structs
|
||||||
|
pub fn parse_certificate_chain(
|
||||||
|
input: &[u8]
|
||||||
|
) -> nom::IResult<&[u8], Vec<X509Certificate>, X509Error> {
|
||||||
|
dbg!(nom::combinator::map_parser(
|
||||||
|
nom::multi::length_data(
|
||||||
|
// Get slice containing the full chain
|
||||||
|
nom::number::complete::be_u24 // certificate_chain has a length field of u24
|
||||||
|
),
|
||||||
|
nom::multi::many0(
|
||||||
|
// Parse a length-data x509 DER as many times as possible
|
||||||
|
parse_x509_der
|
||||||
|
)
|
||||||
|
)(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pares an X509ChainEntry or PrecertChainEntry as specified in [RFC6962]:
|
||||||
|
/// ```txt
|
||||||
|
/// struct {
|
||||||
|
/// ASN.1Cert leaf_certificate;
|
||||||
|
/// ASN.1Cert certificate_chain<0..2^24-1>;
|
||||||
|
/// } X509ChainEntry;
|
||||||
|
///
|
||||||
|
/// struct {
|
||||||
|
/// ASN.1Cert pre_certificate;
|
||||||
|
/// ASN.1Cert precertificate_chain<0..2^24-1>;
|
||||||
|
/// } PrecertChainEntry;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [RFC6962]: https://datatracker.ietf.org/doc/html/rfc6962
|
||||||
|
pub fn parse_chain_entry(input: &[u8]) -> nom::IResult<&[u8], ChainEntry, X509Error> {
|
||||||
|
nom::combinator::map(
|
||||||
|
nom::sequence::pair(parse_x509_der, parse_certificate_chain),
|
||||||
|
|(main_certificate, certificate_chain)| ChainEntry {
|
||||||
|
main_certificate,
|
||||||
|
certificate_chain
|
||||||
|
}
|
||||||
|
)(input)
|
||||||
|
}
|
|
@ -1,115 +0,0 @@
|
||||||
use super::{
|
|
||||||
structures::{parse_tbs_certificate_der, parse_x509_der},
|
|
||||||
LeafParsingEnumType,
|
|
||||||
LeafParsingError
|
|
||||||
};
|
|
||||||
use crate::merkle::types::{LogEntryType, MerkleLeafType, MerkleTreeLeaf, Version};
|
|
||||||
|
|
||||||
/// Parses a MerkleTreeLeaf structure as specified in [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962):
|
|
||||||
/// ```txt
|
|
||||||
/// struct {
|
|
||||||
/// Version version;
|
|
||||||
/// MerkleLeafType leaf_type;
|
|
||||||
/// select (leaf_type) {
|
|
||||||
/// case timestamped_entry: TimestampedEntry;
|
|
||||||
/// }
|
|
||||||
/// } MerkleTreeLeaf;
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This function assumes a binary format for the leaf, rather than a
|
|
||||||
/// base64-encoded version, so make sure to manually decode it from the HTTP
|
|
||||||
/// response before use.
|
|
||||||
pub fn parse_merkle_tree_leaf(
|
|
||||||
input: &[u8]
|
|
||||||
) -> nom::IResult<&[u8], MerkleTreeLeaf, LeafParsingError<&[u8]>> {
|
|
||||||
let (input, version /* Version version; */) = nom::number::complete::u8(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
|
||||||
let (input, leaf_type /* MerkleLeafType leaf_type; */) =
|
|
||||||
nom::number::complete::u8(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
|
||||||
|
|
||||||
Ok((input, MerkleTreeLeaf {
|
|
||||||
version: match version {
|
|
||||||
0 => Version::V1,
|
|
||||||
_ =>
|
|
||||||
return Err(nom::Err::Failure(LeafParsingError::InvalidEnum {
|
|
||||||
input,
|
|
||||||
enum_type: LeafParsingEnumType::Version
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
leaf_type: match leaf_type {
|
|
||||||
// struct {
|
|
||||||
// uint64 timestamp;
|
|
||||||
// LogEntryType entry_type; /* 2 bytes */
|
|
||||||
// select(entry_type) {
|
|
||||||
// case x509_entry: ASN.1Cert;
|
|
||||||
// case precert_entry: PreCert;
|
|
||||||
// } signed_entry;
|
|
||||||
// CtExtensions extensions;
|
|
||||||
// } TimestampedEntry;
|
|
||||||
0 => {
|
|
||||||
let (input, timestamp /* uint64 timestamp; */) =
|
|
||||||
nom::number::complete::u64(nom::number::Endianness::Big)(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
|
||||||
let (input, entry_type /* LogEntryType entry_type; */) =
|
|
||||||
nom::number::complete::u16(nom::number::Endianness::Big)(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
|
||||||
|
|
||||||
let (input, entry_type) = match entry_type {
|
|
||||||
0 => {
|
|
||||||
let (input, der /* opaque ASN.1Cert<1..2^24-1> */) =
|
|
||||||
parse_x509_der(input).map_err(|e| {
|
|
||||||
e.map(|e| LeafParsingError::DerParsing(e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
(input, LogEntryType::X509Entry(der))
|
|
||||||
}
|
|
||||||
// case precert_entry: PreCert;
|
|
||||||
1 => {
|
|
||||||
let (
|
|
||||||
input,
|
|
||||||
issuer_key_hash // opaque issuer_key_hash[32];
|
|
||||||
) = nom::bytes::complete::take(32usize)(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
|
||||||
let (
|
|
||||||
input,
|
|
||||||
tbs_certificate // TBSCertificate tbs_certificate;
|
|
||||||
) = parse_tbs_certificate_der(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::DerParsing(e)))?;
|
|
||||||
|
|
||||||
(input, LogEntryType::PrecertEntry {
|
|
||||||
issuer_key_hash: issuer_key_hash.try_into().map_err(
|
|
||||||
|_| {
|
|
||||||
nom::Err::Failure(LeafParsingError::InvalidTakeLength)
|
|
||||||
}
|
|
||||||
)?,
|
|
||||||
tbs_certificate
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
return Err(nom::Err::Failure(LeafParsingError::InvalidEnum {
|
|
||||||
input,
|
|
||||||
enum_type: LeafParsingEnumType::LogEntryType
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (
|
|
||||||
_, // no more to parse
|
|
||||||
extensions // opaque CtExtensions<0..2^16-1>
|
|
||||||
) = nom::multi::length_data(nom::number::complete::be_u16)(input)
|
|
||||||
.map_err(|e| e.map(|e| LeafParsingError::Nom(e)))?;
|
|
||||||
|
|
||||||
MerkleLeafType::TimeStampedEntry {
|
|
||||||
timestamp,
|
|
||||||
entry_type,
|
|
||||||
extensions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ =>
|
|
||||||
return Err(nom::Err::Failure(LeafParsingError::InvalidEnum {
|
|
||||||
input,
|
|
||||||
enum_type: LeafParsingEnumType::MerkleLeafType
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod leaf;
|
pub mod entry;
|
||||||
pub mod structures;
|
pub mod structures;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -18,68 +18,3 @@ pub enum LeafParsingEnumType {
|
||||||
MerkleLeafType,
|
MerkleLeafType,
|
||||||
LogEntryType
|
LogEntryType
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use base64ct::Encoding;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
merkle::{
|
|
||||||
consts::{test_constants, LEAF_BASE64_BUFFER_SIZE},
|
|
||||||
types::{LogEntryType, MerkleLeafType}
|
|
||||||
},
|
|
||||||
parsing::leaf::parse_merkle_tree_leaf
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_leaf_inputs() {
|
|
||||||
let mut decoded_base64 = [0u8; LEAF_BASE64_BUFFER_SIZE];
|
|
||||||
for (i, (leaf_input, subject, timestamp)) in
|
|
||||||
test_constants::LEAF_INPUT_EXAMPLES.into_iter().enumerate()
|
|
||||||
{
|
|
||||||
decoded_base64[..leaf_input.len()].copy_from_slice(leaf_input.as_bytes());
|
|
||||||
let decoded_base64 = base64ct::Base64::decode_in_place(
|
|
||||||
&mut decoded_base64[..leaf_input.len()]
|
|
||||||
)
|
|
||||||
.expect("Should parse base64 properly");
|
|
||||||
|
|
||||||
let (_, parsed) =
|
|
||||||
parse_merkle_tree_leaf(decoded_base64).expect("should complete");
|
|
||||||
|
|
||||||
let MerkleLeafType::TimeStampedEntry {
|
|
||||||
timestamp: parsed_timestamp,
|
|
||||||
entry_type,
|
|
||||||
extensions: _
|
|
||||||
} = parsed.leaf_type;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
*timestamp, parsed_timestamp,
|
|
||||||
"leaf_input should have expected timestamp"
|
|
||||||
);
|
|
||||||
|
|
||||||
match entry_type {
|
|
||||||
LogEntryType::X509Entry(x509_cert) => {
|
|
||||||
assert_eq!(
|
|
||||||
&x509_cert.subject.to_string(),
|
|
||||||
subject,
|
|
||||||
"leaf_entry x509 cert should have expected subject field"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
LogEntryType::PrecertEntry {
|
|
||||||
issuer_key_hash: _,
|
|
||||||
tbs_certificate
|
|
||||||
} => {
|
|
||||||
assert_eq!(
|
|
||||||
&tbs_certificate.subject.to_string(),
|
|
||||||
subject,
|
|
||||||
"leaf_entry tbs cert should have expected subject field"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"[{i}] Correctly parsed leaf_input for subject '{subject}' issued at \
|
|
||||||
{timestamp}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
106
tests/log_entry_parsing.rs
Normal file
106
tests/log_entry_parsing.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use base64ct::Encoding;
|
||||||
|
use ct::{
|
||||||
|
merkle::{
|
||||||
|
consts::{EXTRA_DATA_BASE64_BUFFER_SIZE, LEAF_BASE64_BUFFER_SIZE},
|
||||||
|
types::{LogEntryType, MerkleLeafType, Version}
|
||||||
|
},
|
||||||
|
parsing::entry::{parse_entry_extra_data_precert, parse_entry_extra_data_x509}
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Entry {
|
||||||
|
leaf_input: String,
|
||||||
|
extra_data: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct GetEntriesResponse {
|
||||||
|
entries: Vec<Entry>
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_entries() -> impl Iterator<Item = Entry> {
|
||||||
|
let client = Arc::new(reqwest::Client::new());
|
||||||
|
let mut join_set: JoinSet<GetEntriesResponse> = JoinSet::new();
|
||||||
|
for i in 0..12u32 {
|
||||||
|
let client = client.clone();
|
||||||
|
join_set.spawn(async move {
|
||||||
|
client
|
||||||
|
.get("https://oak.ct.letsencrypt.org/2024h2/ct/v1/get-entries")
|
||||||
|
.query(&[("start", i * 256), ("end", (i + 1) * 256 - 1)])
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Request to ct log should succeed")
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.expect("Request to ct log should parse properly")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
join_set
|
||||||
|
.join_all()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|e| e.entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_letsencrypt_2024h2_parsing() {
|
||||||
|
let mut buffer = [0u8; EXTRA_DATA_BASE64_BUFFER_SIZE];
|
||||||
|
|
||||||
|
for (i, entry) in fetch_entries().await.enumerate() {
|
||||||
|
// Parse leaf_input
|
||||||
|
buffer[..entry.leaf_input.len()].copy_from_slice(entry.leaf_input.as_bytes());
|
||||||
|
let parsed =
|
||||||
|
base64ct::Base64::decode_in_place(&mut buffer[..entry.leaf_input.len()])
|
||||||
|
.expect("leaf_input should parse as base64 properly");
|
||||||
|
|
||||||
|
let (input, merkle_tree_leaf) =
|
||||||
|
ct::parsing::entry::parse_merkle_tree_leaf(parsed)
|
||||||
|
.expect("leaf_input should parse properly");
|
||||||
|
|
||||||
|
// leaf_input Assertions
|
||||||
|
assert_eq!(input.len(), 0, "All input should be parsed");
|
||||||
|
let MerkleLeafType::TimeStampedEntry { entry_type, .. } =
|
||||||
|
merkle_tree_leaf.leaf_type
|
||||||
|
else {
|
||||||
|
panic!("Merkle tree leaf should be TimestampedEntry");
|
||||||
|
};
|
||||||
|
let Version::V1 = merkle_tree_leaf.version else {
|
||||||
|
panic!("Merkle tree leaf should be Version::V1");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate this before we have to mutably borrow the buffer again to avoid
|
||||||
|
// multiple mutable references
|
||||||
|
let parse_entry_extra_data = match entry_type {
|
||||||
|
ct::merkle::types::LogEntryType::X509Entry(..) => parse_entry_extra_data_x509,
|
||||||
|
ct::merkle::types::LogEntryType::PrecertEntry { .. } =>
|
||||||
|
parse_entry_extra_data_precert,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse extra_data
|
||||||
|
buffer[..entry.extra_data.len()].copy_from_slice(entry.extra_data.as_bytes());
|
||||||
|
let parsed =
|
||||||
|
base64ct::Base64::decode_in_place(&mut buffer[..entry.extra_data.len()])
|
||||||
|
.expect("extra_data should parse as base64 properly");
|
||||||
|
|
||||||
|
let (input, parsed) =
|
||||||
|
parse_entry_extra_data(parsed).expect("extra_data should parse properly");
|
||||||
|
|
||||||
|
// extra_data assertions
|
||||||
|
assert_eq!(input.len(), 0, "All input should be parsed at entry {i}");
|
||||||
|
match parsed {
|
||||||
|
ct::merkle::types::EntryExtraData::X509Certificate(chain) =>
|
||||||
|
assert_ne!(chain.len(), 0, "x509 chain should not be 0 length"),
|
||||||
|
// TODO: Verify main_certificate against the tbs certificate loaded from
|
||||||
|
// leaf_entry
|
||||||
|
ct::merkle::types::EntryExtraData::Precertificate(chain_entry) => assert_ne!(
|
||||||
|
chain_entry.certificate_chain.len(),
|
||||||
|
0,
|
||||||
|
"precert chain should not be 0 length"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue