Skip to content

Commit 14741f8

Browse files
Add CertLog tools (#7)
Co-authored-by: Erik Schamper <[email protected]>
1 parent 7b76faf commit 14741f8

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import datetime
5+
import json
6+
import typing
7+
from pathlib import Path
8+
from typing import BinaryIO
9+
10+
from dissect.util.ts import wintimestamp
11+
12+
from dissect.database.ese import ESE
13+
from dissect.database.ese.c_ese import JET_coltyp
14+
from dissect.database.ese.util import RecordValue
15+
16+
if typing.TYPE_CHECKING:
17+
from collections.abc import Iterator
18+
19+
from dissect.database.ese.table import Table
20+
21+
CertLogValue = RecordValue | datetime.datetime
22+
23+
SKIP_TABLES = [
24+
"MSysObjects",
25+
"MSysObjectsShadow",
26+
"MSysObjids",
27+
"MSysLocales",
28+
]
29+
30+
# Value from certutil -view -restrict "RequestID=XX"
31+
REQUEST_TYPE = {0x100: "PKCS10", 0x40100: "PKCS10, Full Response"}
32+
REQUEST_DISPOSITION = {0x14: "Issued", 0x1F: "Denied", 0xF: "Ca cert"}
33+
34+
REQUEST_STATUS_CODE = {
35+
0x0: "The operation completed successfully",
36+
0x80094001: "CERTSRV_E_BAD_REQUESTSUBJECT",
37+
0x80094002: "CERTSRV_E_NO_REQUEST",
38+
0x80094003: "CERTSRV_E_BAD_REQUESTSTATUS",
39+
0x80094004: "CERTSRV_E_PROPERTY_EMPTY",
40+
0x80094005: "CERTSRV_E_INVALID_CA_CERTIFICATE",
41+
0x80094006: "CERTSRV_E_SERVER_SUSPENDED",
42+
0x80094007: "CERTSRV_E_ENCODING_LENGTH",
43+
0x80094008: "CERTSRV_E_ROLECONFLICT",
44+
0x80094009: "CERTSRV_E_RESTRICTEDOFFICER",
45+
0x8009400A: "CERTSRV_E_KEY_ARCHIVAL_NOT_CONFIGURED",
46+
0x8009400B: "CERTSRV_E_NO_VALID_KRA",
47+
0x8009400C: "CERTSRV_E_BAD_REQUEST_KEY_ARCHIVAL",
48+
0x8009400D: "CERTSRV_E_NO_CAADMIN_DEFINED",
49+
0x8009400E: "CERTSRV_E_BAD_RENEWAL_CERT_ATTRIBUTE",
50+
0x8009400F: "CERTSRV_E_NO_DB_SESSIONS",
51+
0x80094010: "CERTSRV_E_ALIGNMENT_FAULT",
52+
0x80094011: "CERTSRV_E_ENROLL_DENIED",
53+
0x80094012: "CERTSRV_E_TEMPLATE_DENIED",
54+
0x80094013: "CERTSRV_E_DOWNLEVEL_DC_SSL_OR_UPGRADE",
55+
0x80094014: "CERTSRV_E_ADMIN_DENIED_REQUEST",
56+
0x80094015: "CERTSRV_E_NO_POLICY_SERVER",
57+
0x80094016: "CERTSRV_E_WEAK_SIGNATURE_OR_KEY",
58+
0x80094017: "CERTSRV_E_KEY_ATTESTATION_NOT_SUPPORTED",
59+
0x80094018: "CERTSRV_E_ENCRYPTION_CERT_REQUIRED",
60+
0x80094800: "CERTSRV_E_UNSUPPORTED_CERT_TYPE",
61+
0x80094801: "CERTSRV_E_NO_CERT_TYPE",
62+
0x80094802: "CERTSRV_E_TEMPLATE_CONFLICT",
63+
0x80094803: "CERTSRV_E_SUBJECT_ALT_NAME_REQUIRED",
64+
0x80094804: "CERTSRV_E_ARCHIVED_KEY_REQUIRED",
65+
0x80094805: "CERTSRV_E_SMIME_REQUIRED",
66+
0x80094806: "CERTSRV_E_BAD_RENEWAL_SUBJECT",
67+
0x80094807: "CERTSRV_E_BAD_TEMPLATE_VERSION",
68+
0x80094808: "CERTSRV_E_TEMPLATE_POLICY_REQUIRED",
69+
0x80094809: "CERTSRV_E_SIGNATURE_POLICY_REQUIRED",
70+
0x8009480A: "CERTSRV_E_SIGNATURE_COUNT",
71+
0x8009480B: "CERTSRV_E_SIGNATURE_REJECTED",
72+
0x8009480C: "CERTSRV_E_ISSUANCE_POLICY_REQUIRED",
73+
0x8009480D: "CERTSRV_E_SUBJECT_UPN_REQUIRED",
74+
0x8009480E: "CERTSRV_E_SUBJECT_DIRECTORY_GUID_REQUIRED",
75+
0x8009480F: "CERTSRV_E_SUBJECT_DNS_REQUIRED",
76+
0x80094810: "CERTSRV_E_ARCHIVED_KEY_UNEXPECTED",
77+
0x80094811: "CERTSRV_E_KEY_LENGTH",
78+
0x80094812: "CERTSRV_E_SUBJECT_EMAIL_REQUIRED",
79+
0x80094813: "CERTSRV_E_UNKNOWN_CERT_TYPE",
80+
0x80094814: "CERTSRV_E_CERT_TYPE_OVERLAP",
81+
0x80094815: "CERTSRV_E_TOO_MANY_SIGNATURES",
82+
0x80094816: "CERTSRV_E_RENEWAL_BAD_PUBLIC_KEY",
83+
0x80094817: "CERTSRV_E_INVALID_EK",
84+
0x80094818: "CERTSRV_E_INVALID_IDBINDING",
85+
0x80094819: "CERTSRV_E_INVALID_ATTESTATION",
86+
0x8009481A: "CERTSRV_E_KEY_ATTESTATION",
87+
0x8009481B: "CERTSRV_E_CORRUPT_KEY_ATTESTATION",
88+
0x8009481C: "CERTSRV_E_EXPIRED_CHALLENGE",
89+
0x8009481D: "CERTSRV_E_INVALID_RESPONSE",
90+
0x8009481E: "CERTSRV_E_INVALID_REQUESTID",
91+
0x8009481F: "CERTSRV_E_REQUEST_PRECERTIFICATE_MISMATCH",
92+
0x80094820: "CERTSRV_E_PENDING_CLIENT_RESPONSE",
93+
0x80094821: "CERTSRV_E_SEC_EXT_DIRECTORY_SID_REQUIRED",
94+
}
95+
96+
97+
class CertLog:
98+
def __init__(self, fh: BinaryIO):
99+
self.db = ESE(fh)
100+
101+
def tables(self) -> list[Table]:
102+
return [table for table in self.db.tables() if table.name not in SKIP_TABLES]
103+
104+
def records(self, table_name: str) -> Iterator[dict[str, CertLogValue]]:
105+
try:
106+
table = self.db.table(table_name)
107+
except KeyError:
108+
return None
109+
110+
for record in table.records():
111+
record_data = {"TableName": table.name}
112+
113+
for column in table.columns:
114+
value = record.get(column.name)
115+
116+
if column.type == JET_coltyp.DateTime and value:
117+
value = wintimestamp(value)
118+
119+
if table.name == "Requests":
120+
if column.name == "StatusCode":
121+
value = REQUEST_STATUS_CODE.get(value & 0xFFFFFFFF, value & 0xFFFFFFFF)
122+
if column.name == "Disposition":
123+
value = REQUEST_DISPOSITION.get(value, value)
124+
if column.name == "RequestType":
125+
value = REQUEST_TYPE.get(value, value)
126+
127+
record_data[column.name] = value
128+
129+
yield record_data
130+
131+
def entries(self) -> Iterator[dict[str, CertLogValue]]:
132+
for table in self.tables():
133+
yield from self.records(table.name)
134+
135+
136+
def main() -> None:
137+
parser = argparse.ArgumentParser(description="dissect.database.ese Certlog parser")
138+
parser.add_argument("input", help="certlog database to read")
139+
parser.add_argument("-t", "--table", metavar="TABLE", help="show only content of TABLE (case sensitive)")
140+
parser.add_argument("-j", "--json", help="output in JSON format", action="store_true", default=False)
141+
args = parser.parse_args()
142+
143+
with Path(args.input).open("rb") as fh:
144+
parser = CertLog(fh)
145+
146+
for table in parser.tables():
147+
if args.table and table.name != args.table:
148+
continue
149+
for record in parser.records(table.name):
150+
if args.json:
151+
print(json.dumps(record, default=str))
152+
else:
153+
print(record)
154+
155+
156+
if __name__ == "__main__":
157+
main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:2acf223f81da32675ec94100ef3bd9b2ff5cf2d3aaccb764280030b73bd9afbd
3+
size 198900

tests/ese/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@ def sru_db() -> Iterator[BinaryIO]:
5858
@pytest.fixture
5959
def ual_db() -> Iterator[BinaryIO]:
6060
yield from open_file_gz("_data/ese/tools/Current.mdb.gz")
61+
62+
63+
@pytest.fixture
64+
def certlog_db() -> Iterator[BinaryIO]:
65+
yield from open_file_gz("_data/ese/tools/CertLog.edb.gz")

tests/ese/tools/test_certlog.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from __future__ import annotations
2+
3+
from typing import BinaryIO
4+
5+
from dissect.database.ese.tools.certlog import CertLog
6+
7+
8+
def test_certlog(certlog_db: BinaryIO) -> None:
9+
db = CertLog(certlog_db)
10+
assert len(list(db.records("Certificates"))) == 11
11+
assert len(list(db.records("Requests"))) == 11
12+
assert len(list(db.records("RequestAttributes"))) == 26
13+
assert len(list(db.records("CertificateExtensions"))) == 92
14+
assert len(list(db.records("CRLs"))) == 2

0 commit comments

Comments
 (0)