emsign-g3-distrust 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
emsign_g3/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Defensive tooling for eMudhra/emSign G3 CA distrust (Mozilla Bug 2083449)."""
2
+
3
+ __version__ = "0.2.0"
emsign_g3/cli.py ADDED
@@ -0,0 +1,79 @@
1
+ """Unified CLI for emsign-g3 distrust tooling."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+
6
+ from emsign_g3.distrust import run_distrust
7
+ from emsign_g3.export import run_export
8
+ from emsign_g3.paths import default_certs_dir, indicators_csv_path
9
+ from emsign_g3.scan import run_scan
10
+
11
+
12
+ def main(argv: list[str] | None = None) -> int:
13
+ parser = argparse.ArgumentParser(
14
+ prog="emsign-g3",
15
+ description="Detect and deny compromised eMudhra/emSign G3 intermediates (Bug 2083449).",
16
+ )
17
+ sub = parser.add_subparsers(dest="command", required=True)
18
+
19
+ scan_p = sub.add_parser("scan", help="inventory trust stores (read-only)")
20
+ scan_p.add_argument("--json", action="store_true", help="machine-readable output")
21
+
22
+ dist_p = sub.add_parser("distrust", help="plan or apply intermediate blocklist")
23
+ dist_p.add_argument("--apply", action="store_true", help="execute (default: dry run)")
24
+ dist_p.add_argument("--undo", action="store_true", help="roll back instead of distrust")
25
+ dist_p.add_argument(
26
+ "--certs-dir",
27
+ default=None,
28
+ help=f"directory with eight verified PEMs (default: {default_certs_dir()})",
29
+ )
30
+
31
+ exp_p = sub.add_parser("export-indicators", help="SIEM/SOAR indicator bundle")
32
+ exp_p.add_argument("--csv", default=None, help="path to indicators.csv")
33
+ exp_p.add_argument(
34
+ "--format",
35
+ choices=("json", "ndjson", "sigma", "elastic-kql"),
36
+ default="json",
37
+ help="output format (default: json)",
38
+ )
39
+ exp_p.add_argument(
40
+ "--ndjson",
41
+ action="store_true",
42
+ help="shorthand for --format ndjson (deprecated)",
43
+ )
44
+ exp_p.add_argument("--verify", action="store_true", help="verify PEMs match CSV")
45
+ exp_p.add_argument("--certs-dir", default=None, help="PEM directory for --verify")
46
+ exp_p.add_argument(
47
+ "--output-dir",
48
+ default=None,
49
+ help="write --format sigma or elastic-kql files to this directory",
50
+ )
51
+ exp_p.add_argument(
52
+ "--check-committed-rules",
53
+ action="store_true",
54
+ help="verify rules/sigma and rules/elastic match generators",
55
+ )
56
+
57
+ args = parser.parse_args(argv)
58
+
59
+ if args.command == "scan":
60
+ return run_scan(as_json=args.json)
61
+ if args.command == "distrust":
62
+ return run_distrust(apply=args.apply, undo=args.undo, certs_dir=args.certs_dir)
63
+ if args.command == "export-indicators":
64
+ return run_export(
65
+ csv_path=args.csv or indicators_csv_path(),
66
+ fmt=args.format,
67
+ ndjson=args.ndjson,
68
+ verify=args.verify,
69
+ certs_dir=args.certs_dir,
70
+ output_dir=args.output_dir,
71
+ check_committed_rules=args.check_committed_rules,
72
+ )
73
+
74
+ parser.error(f"unknown command: {args.command}")
75
+ return 2
76
+
77
+
78
+ if __name__ == "__main__":
79
+ raise SystemExit(main())
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDaDCCAu6gAwIBAgIQAKSu5njyM2K0wORgt3LyFDAKBggqhkjOPQQDAzBWMQsw
3
+ CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
4
+ ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTU1
5
+ NDA4WhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11
6
+ ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C
7
+ AQYFK4EEACIDYgAE557ZwEqXc8RzXlERHahawr1eZKs3S4DhLLwgOOweTw47gS/J
8
+ hZ4eZ6+wTi+LyX90hbplqpEe6KTkW2LVzAPVa+Z4s8OMtNyTfKJGDiOEU8iSk10p
9
+ 1E+R4VYGtjzO2rRoo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
10
+ WUIgkFQwHQYDVR0OBBYEFOIeL+ccGTkOVUtiwSAkVzUkiiUUMB0GA1UdJQQWMBQG
11
+ CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
12
+ KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
13
+ BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
14
+ aHR0cDovL29jc3AtYi5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
15
+ b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQi5wN2MwNwYDVR0f
16
+ BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
17
+ cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
18
+ AwMDaAAwZQIwLmsGXi6oFMJ2klkcEMXMug6gvxQWZWkewiDWVzQyl2L6z6DuQfPO
19
+ Ibh1grvdULDCAjEAxZvLlxUavG69J6wnSjC8XBbqNIQsJ6CE2QdNOe84Ptxb6VG8
20
+ gt2TlfJbHii+KNvK
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDaDCCAu6gAwIBAgIPWIsuPCu4zaE+GhIJJUMhMAoGCCqGSM49BAMDMFYxCzAJ
3
+ BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
4
+ MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM1
5
+ MDlaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk
6
+ aHJhIEluYzEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C
7
+ AQYFK4EEACIDYgAEOnp4YsjETTJN3WkDOmeCwO/u/kBV5ooSP6A3G6O9kZRRTuFy
8
+ pDPjYw8CvRiAN9nlFJ+bd9uUooHfmHsv6oiLdL3+j9pMEJnubKGVZv4+8OX2sJpV
9
+ bMyLpQVh3xsqRB0Bo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
10
+ WUIgkFQwHQYDVR0OBBYEFK5+EFw8KZbBqEcnBVnx4D3U5QrzMB0GA1UdJQQWMBQG
11
+ CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
12
+ KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
13
+ BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
14
+ aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
15
+ b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQy5wN2MwNwYDVR0f
16
+ BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
17
+ cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
18
+ AwMDaAAwZQIxAMEN2HWMocbuWdy1d33pH6JpMvP/V/DIS5PtZbv1Q7TXEbxrcyxg
19
+ yuddOJscYDJJlAIwB4sH1BpbScDzkdD4tJBSwrG/1Mx82vb8YaySehNuu1+1oXNd
20
+ AEKwaJ0mVzScy2+6
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDaDCCAu2gAwIBAgIQAMfEHv6S2CXwXq6EGkqCwTAKBggqhkjOPQQDAzBWMQsw
3
+ CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
4
+ ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTYw
5
+ MzMxWhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11
6
+ ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRVYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C
7
+ AQYFK4EEACIDYgAEJduoWmTTl9oLEFLp6lRuSHvv6pydnWigxerc/u91ppu11vwB
8
+ 9Cv7ym60C81kwHZ+Nv/QXZeoE8WqDGyVMOec9fCGZGjxOAZCCqXIUYQLHiUlQOeQ
9
+ 5LZUw4Uu1krx6GV9o4IBlTCCAZEwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
10
+ WUIgkFQwHQYDVR0OBBYEFD7cINmJRpiHe4c/2IFHslwV2EcGMB0GA1UdJQQWMBQG
11
+ CCsGAQUFBwMBBggrBgEFBQcDAjBcBgNVHSAEVTBTMDkGCysGAQQBg44hAQABMCow
12
+ KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
13
+ BAGDjiEBAngwBwYFZ4EMAQEwdQYIKwYBBQUHAQEEaTBnMCQGCCsGAQUFBzABhhho
14
+ dHRwOi8vb2NzcC1iLmVtc2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv
15
+ c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvRU1FVlRMU0NBRzNCLnA3YzA3BgNVHR8E
16
+ MDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3R0bHNjYWczLmNy
17
+ bDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQD
18
+ AwNpADBmAjEA9Jq999oaOUKhFwopw33iCmt0OHIn3gjT43GfuOHR/YckXdwoNdDA
19
+ lIEhz0NgRO3/AjEAvY/a3OMvktIZLYjn+WLKmp0QhrIMV+e6EpS7j4GVeOKouNp6
20
+ MN/P+ByGeiRMghzB
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDaDCCAu6gAwIBAgIQAIAJkopkEHYBrwX6LxnxCTAKBggqhkjOPQQDAzBWMQsw
3
+ CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
4
+ ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTUz
5
+ OTMxWhcNNDAwNDI4MTgzMDAwWjBAMQswCQYDVQQGEwJVUzEUMBIGA1UECgwLZU11
6
+ ZGhyYSBJbmMxGzAZBgNVBAMMEkVNIEVWIFRMUyBDQSAtIEczQzB2MBAGByqGSM49
7
+ AgEGBSuBBAAiA2IABC1W6Cmm+4LGSkaX2WCvlR6QZ66R+SbTQwbaSKiq63hptqZ+
8
+ bSdCbgRCqgOzbhmI3/Y2UU7hzwf1uks/8qBmRa59MdWGatMao9JG6wsxd+6Zy3Vm
9
+ xRbOdICKZo+R8MR/yqOCAZUwggGRMB8GA1UdIwQYMBaAFOE6qyF88eNNVK7Ucrf9
10
+ i1lCIJBUMB0GA1UdDgQWBBSmJV6AqU2385Z8G8eR35K87kvrXzAdBgNVHSUEFjAU
11
+ BggrBgEFBQcDAQYIKwYBBQUHAwIwXAYDVR0gBFUwUzA5BgsrBgEEAYOOIQEAATAq
12
+ MCgGCCsGAQUFBwIBFhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMA0GCysG
13
+ AQQBg44hAQJ4MAcGBWeBDAEBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
14
+ aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
15
+ b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRVZUTFNDQUczQy5wN2MwNwYDVR0f
16
+ BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
17
+ cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
18
+ AwMDaAAwZQIwBy1wTnzH4lXDTy2PD91ysLjvnlVN3JbJmc1BIMEU8TOLkYVHibMc
19
+ 9HeyonikFSfmAjEA6uE3QRxlrZTz/1/lXPTEMUhoauAtycagtlNFp79kjhvNa2/f
20
+ Aal1GNxsOutLnr0Y
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDWzCCAuCgAwIBAgIPeBiTP4+FtGGaKzX7qD0KMAoGCCqGSM49BAMDMFUxCzAJ
3
+ BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
4
+ MR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBDUyBDQSAtIEczMB4XDTI1MDQzMDE2MDgy
5
+ N1oXDTQwMDQyODE4MzAwMFowPjELMAkGA1UEBhMCTkwxEzARBgNVBAoMCmVNdWRo
6
+ cmEgQlYxGjAYBgNVBAMMEUVNIE9WIENTIENBIC0gRzNCMHYwEAYHKoZIzj0CAQYF
7
+ K4EEACIDYgAEqrsPsGZqz88B+tUr/P1MK/LLPDSX3nOVDlgZth/mlQmHQcjEkUOb
8
+ NKBDHK/+W/m0wcjorgAB+fTcitgsWfNjnpbNbD1NsM7+wmw9YlF7N3YTcnQ/jord
9
+ d7EAZPKr5eBQo4IBizCCAYcwHwYDVR0jBBgwFoAUaHQD+IXKjVRQVx0W4MjeRQ2r
10
+ zS0wHQYDVR0OBBYEFHn66OzP2w11jaRY1p+27dx8Sd7mMBMGA1UdJQQMMAoGCCsG
11
+ AQUFBwMDMF4GA1UdIARXMFUwOQYLKwYBBAGDjiEBAAEwKjAoBggrBgEFBQcCARYc
12
+ aHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTAOBgwrBgEEAYOOIQECgUgwCAYG
13
+ Z4EMAQQBMHQGCCsGAQUFBwEBBGgwZjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3At
14
+ Yi5lbXNpZ24uY29tMD4GCCsGAQUFBzAChjJodHRwOi8vcmVwb3NpdG9yeS5lbXNp
15
+ Z24uY29tL2NlcnRzL0VNT1ZDU0NBRzNCLnA3YzA2BgNVHR8ELzAtMCugKaAnhiVo
16
+ dHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3Rjc2NhZzMuY3JsMA4GA1UdDwEB/wQE
17
+ AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMAoGCCqGSM49BAMDA2kAMGYCMQC/zr03
18
+ YEELsNKqzUtH2dVFi4BaKFdyrr/6G1dIWp8KAVgsw2/u4lyyboUDaC9jDvICMQC9
19
+ HTxku84tNX9dmHOtH7aC2Nu/JM0jTf6KjZAvovCJzzv1IFWSZpVLZibynMLXOrM=
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDWzCCAuGgAwIBAgIPVEt/kiwxriIKSpIpFvbmMAoGCCqGSM49BAMDMFUxCzAJ
3
+ BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
4
+ MR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBDUyBDQSAtIEczMB4XDTI1MDQzMDE2Mzg0
5
+ MFoXDTQwMDQyODE4MzAwMFowPzELMAkGA1UEBhMCVVMxFDASBgNVBAoMC2VNdWRo
6
+ cmEgSW5jMRowGAYDVQQDDBFFTSBPViBDUyBDQSAtIEczQzB2MBAGByqGSM49AgEG
7
+ BSuBBAAiA2IABLnw4GNHAqacpqrdRDz9YsQbm6SyCSRpfXl052kaEm+5avs+NML9
8
+ O8X0gwYfJl3CpYQ2NHMjUQQ2so8E84YjYwZ5gOJQPgJTLWVGI1pxZaJZoe9Jb3Qb
9
+ X97neHII5Lj676OCAYswggGHMB8GA1UdIwQYMBaAFGh0A/iFyo1UUFcdFuDI3kUN
10
+ q80tMB0GA1UdDgQWBBTub3MpjW+/p7Mbw5U09hVWRHhmtzATBgNVHSUEDDAKBggr
11
+ BgEFBQcDAzBeBgNVHSAEVzBVMDkGCysGAQQBg44hAQABMCowKAYIKwYBBQUHAgEW
12
+ HGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDgYMKwYBBAGDjiEBAoFIMAgG
13
+ BmeBDAEEATB0BggrBgEFBQcBAQRoMGYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
14
+ LWMuZW1zaWduLmNvbTA+BggrBgEFBQcwAoYyaHR0cDovL3JlcG9zaXRvcnkuZW1z
15
+ aWduLmNvbS9jZXJ0cy9FTU9WQ1NDQUczQy5wN2MwNgYDVR0fBC8wLTAroCmgJ4Yl
16
+ aHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290Y3NjYWczLmNybDAOBgNVHQ8BAf8E
17
+ BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQDAwNoADBlAjEA8yPg
18
+ 8sntOhLwV2QXJ5LEaKSpo/fnE3uOgTadK73fDhyjiqr0Io9GVuQTYqBPbQ2TAjBg
19
+ euApmBS486+iei03lRrp139dPw6k28jjGNvWsAU7A1N86J1zPJVDEyVH+nMAWC0=
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDZjCCAu2gAwIBAgIPbK2jZ/scEACIEg6iAtVoMAoGCCqGSM49BAMDMFYxCzAJ
3
+ BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
4
+ MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNjAw
5
+ MTJaFw00MDA0MjgxODMwMDBaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVk
6
+ aHJhIEJWMRswGQYDVQQDDBJFTSBPViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIB
7
+ BgUrgQQAIgNiAARziXqg6zz9yosSQC/68KvE4SErr43ud4rJ7i1tuD+sKAxeBWOK
8
+ 56Ks1xwyKo42HN659pxDArndjXPRzGyqsswK7k4hbOl53VGKSvS2JuVoTN1lgDft
9
+ DIVOTMEvTPhkbQ2jggGWMIIBkjAfBgNVHSMEGDAWgBThOqshfPHjTVSu1HK3/YtZ
10
+ QiCQVDAdBgNVHQ4EFgQUm9hlUa/n5yw3rQjuaRnZvzeErgowHQYDVR0lBBYwFAYI
11
+ KwYBBQUHAwEGCCsGAQUFBwMCMF0GA1UdIARWMFQwOQYLKwYBBAGDjiEBAAEwKjAo
12
+ BggrBgEFBQcCARYcaHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTANBgsrBgEE
13
+ AYOOIQECbjAIBgZngQwBAgIwdQYIKwYBBQUHAQEEaTBnMCQGCCsGAQUFBzABhhho
14
+ dHRwOi8vb2NzcC1iLmVtc2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv
15
+ c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvRU1PVlRMU0NBRzNCLnA3YzA3BgNVHR8E
16
+ MDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3R0bHNjYWczLmNy
17
+ bDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQD
18
+ AwNnADBkAjBAk+5BizdJhrED3wb7F2Gm7bWI60dlNd075nghT2TrR4NWKZtJZcbc
19
+ 28xxIY/NONICMF6mPdsjosaqmvEMWqKcBh4vsIvXSKnQj5o8zrFB4oKS+accjYTe
20
+ 8Dz3bQjauvy9uw==
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ
3
+ BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk
4
+ MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3
5
+ NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk
6
+ aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C
7
+ AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ
8
+ fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+
9
+ N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L
10
+ WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG
11
+ CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow
12
+ KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB
13
+ BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY
14
+ aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw
15
+ b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f
16
+ BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j
17
+ cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E
18
+ AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c
19
+ NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79
20
+ 4rSb9vGqxZzelQth
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,12 @@
1
+ ca_common_name,type,sha256_fingerprint,serial,issuer,pem_basename,suricata_sid,tls_issuer_cn_match,bug_id
2
+ EM DV TLS CA - G3B,TLS,42c4accd12c4f59597925e1a08a7a90102608543809308d50ec427e118ba1493,A4AEE678F23362B4C0E460B772F214,emSign Root TLS CA - G3,EMDVTLSCAG3B,92083401,EM DV TLS CA - G3B,2083449
3
+ EM DV TLS CA - G3C,TLS,0164d7ceae5bb909c272af151ff44b1177ae9b47d9424b94f1d9558c30934d19,588B2E3C2BB8CDA13E1A1209254321,emSign Root TLS CA - G3,EMDVTLSCAG3C,92083402,EM DV TLS CA - G3C,2083449
4
+ EM EV TLS CA - G3B,TLS,e7568fe6e12331f4fd7de61da4c7559d0f8eb192a218d4741a3ba75184333838,C7C41EFE92D825F05EAE841A4A82C1,emSign Root TLS CA - G3,EMEVTLSCAG3B,92083403,EM EV TLS CA - G3B,2083449
5
+ EM EV TLS CA - G3C,TLS,432ec25f0d28de9006d97bf752f17bbb2cd41f669933fcb072d9a1148d0261f3,8009928A64107601AF05FA2F19F109,emSign Root TLS CA - G3,EMEVTLSCAG3C,92083404,EM EV TLS CA - G3C,2083449
6
+ EM OV TLS CA - G3B,TLS,98b896625c0c230adeec3eb2cf6aad5001ecf9156a68644e82374ce5833bc25b,6CADA367FB1C100088120EA202D568,emSign Root TLS CA - G3,EMOVTLSCAG3B,92083405,EM OV TLS CA - G3B,2083449
7
+ EM OV TLS CA - G3C,TLS,cb6e15165ac802e9b3f993f16c25d41f4e81c21ebde71ae1b012ac00bf9c7c3f,296926C227A3A1526392B19482A504,emSign Root TLS CA - G3,EMOVTLSCAG3C,92083406,EM OV TLS CA - G3C,2083449
8
+ EM OV CS CA - G3B,CodeSigning,9e39de2881273acc225a149c72ba3e0f51424963669ab633ee1dc7df5db067b4,7818933F8F85B4619A2B35FBA83D0A,emSign Root CS CA - G3,EMOVCSCAG3B,92083407,EM OV CS CA - G3B,2083449
9
+ EM OV CS CA - G3C,CodeSigning,4b2460a466f95fd40113bcdcca2ceb29b3b093921de45474da7df48874c630b1,544B7F922C31AE220A4A922916F6E6,emSign Root CS CA - G3,EMOVCSCAG3C,92083408,EM OV CS CA - G3C,2083449
10
+ emSign Root TLS CA - G3,RootTLS,7dd78d5f4f13459a83dff9abbba62edbaf6f2d102bf257fd712f4d9f2746ed8d,,,,,,2083449
11
+ emSign Root CS CA - G3,RootCS,f2edaaff3d9b701fb4d14c9f285f8c0bad90fc3bcc1fa07d47f4718edaba34a5,,,,,,2083449
12
+ AAA Certificate Services,CrossSign,d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4,,,,,,2083449
emsign_g3/distrust.py ADDED
@@ -0,0 +1,202 @@
1
+ """Explicitly distrust the eight compromised eMudhra/emSign G3 intermediates."""
2
+ from __future__ import annotations
3
+
4
+ import glob
5
+ import hashlib
6
+ import os
7
+ import platform
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from emsign_g3.indicators import expected_pem_fingerprints
13
+ from emsign_g3.paths import default_certs_dir
14
+
15
+
16
+ def sha256_of_pem(path: str | Path) -> str | None:
17
+ der = subprocess.run(
18
+ ["openssl", "x509", "-inform", "PEM", "-outform", "DER", "-in", str(path)],
19
+ capture_output=True,
20
+ ).stdout
21
+ return hashlib.sha256(der).hexdigest() if der else None
22
+
23
+
24
+ def load_certs(certs_dir: str | Path) -> list[tuple[str, str, str]]:
25
+ certs: list[tuple[str, str, str]] = []
26
+ expected = expected_pem_fingerprints()
27
+ for path in sorted(glob.glob(os.path.join(str(certs_dir), "EM*.pem"))):
28
+ name = os.path.splitext(os.path.basename(path))[0]
29
+ fp = sha256_of_pem(path)
30
+ want = expected.get(name)
31
+ if want and fp == want:
32
+ certs.append((name, path, fp))
33
+ else:
34
+ print(f" !! skip {name}: fingerprint mismatch or unknown ({fp})", file=sys.stderr)
35
+ return certs
36
+
37
+
38
+ def plan_for_os(system: str, certs: list[tuple[str, str, str]], undo: bool):
39
+ steps: list[tuple[str, list[str], str]] = []
40
+ if system == "Darwin":
41
+ kc = "/Library/Keychains/System.keychain"
42
+ for name, path, _ in certs:
43
+ if undo:
44
+ steps.append(
45
+ (
46
+ f"macOS: remove distrust for {name}",
47
+ ["sudo", "security", "remove-trusted-cert", "-d", path],
48
+ "re-run without --undo to re-apply",
49
+ )
50
+ )
51
+ else:
52
+ steps.append(
53
+ (
54
+ f"macOS: distrust {name} (admin System keychain)",
55
+ [
56
+ "sudo",
57
+ "security",
58
+ "add-trusted-cert",
59
+ "-d",
60
+ "-r",
61
+ "deny",
62
+ "-k",
63
+ kc,
64
+ path,
65
+ ],
66
+ f"sudo security remove-trusted-cert -d {path}",
67
+ )
68
+ )
69
+ elif system == "Linux":
70
+ blk = (
71
+ "/etc/pki/ca-trust/source/blocklist"
72
+ if os.path.isdir("/etc/pki/ca-trust")
73
+ else "/usr/local/share/ca-certificates/emsign-blocklist"
74
+ )
75
+ update = (
76
+ ["sudo", "update-ca-trust"]
77
+ if os.path.isdir("/etc/pki/ca-trust")
78
+ else ["sudo", "update-ca-certificates"]
79
+ )
80
+ for name, path, _ in certs:
81
+ dst = os.path.join(blk, f"{name}.pem")
82
+ if undo:
83
+ steps.append(
84
+ (
85
+ f"Linux: unblock {name}",
86
+ ["sudo", "rm", "-f", dst],
87
+ "restore by re-running without --undo",
88
+ )
89
+ )
90
+ else:
91
+ steps.append(
92
+ (
93
+ f"Linux: blocklist {name} -> {dst}",
94
+ ["sudo", "install", "-D", "-m", "644", path, dst],
95
+ f"sudo rm -f {dst} && {' '.join(update)}",
96
+ )
97
+ )
98
+ steps.append(("Linux: refresh trust store", update, "(same command re-runs)"))
99
+ elif system == "Windows":
100
+ for name, path, _ in certs:
101
+ if undo:
102
+ steps.append(
103
+ (
104
+ f"Windows: remove {name} from disallowed",
105
+ ["certutil", "-delstore", "disallowed", path],
106
+ "re-add with certutil -addstore -f disallowed",
107
+ )
108
+ )
109
+ else:
110
+ steps.append(
111
+ (
112
+ f"Windows: add {name} to disallowed store",
113
+ ["certutil", "-addstore", "-f", "disallowed", path],
114
+ f"certutil -delstore disallowed <serial-of-{name}>",
115
+ )
116
+ )
117
+ home = os.path.expanduser("~")
118
+ for base in (
119
+ os.path.join(home, ".mozilla", "firefox"),
120
+ os.path.join(home, "Library", "Application Support", "Firefox", "Profiles"),
121
+ os.path.join(os.environ.get("APPDATA", ""), "Mozilla", "Firefox", "Profiles"),
122
+ ):
123
+ if base and os.path.isdir(base):
124
+ for d in os.listdir(base):
125
+ prof = os.path.join(base, d)
126
+ if os.path.exists(os.path.join(prof, "cert9.db")):
127
+ for name, path, _ in certs:
128
+ if undo:
129
+ steps.append(
130
+ (
131
+ f"NSS {d}: delete distrust entry {name}",
132
+ [
133
+ "certutil",
134
+ "-D",
135
+ "-n",
136
+ f"emSign-distrust-{name}",
137
+ "-d",
138
+ "sql:" + prof,
139
+ ],
140
+ "re-run without --undo",
141
+ )
142
+ )
143
+ else:
144
+ steps.append(
145
+ (
146
+ f"NSS {d}: mark {name} explicitly distrusted (p,p,p)",
147
+ [
148
+ "certutil",
149
+ "-A",
150
+ "-n",
151
+ f"emSign-distrust-{name}",
152
+ "-t",
153
+ "p,p,p",
154
+ "-d",
155
+ "sql:" + prof,
156
+ "-i",
157
+ path,
158
+ ],
159
+ f"certutil -D -n emSign-distrust-{name} -d sql:{prof}",
160
+ )
161
+ )
162
+ return steps
163
+
164
+
165
+ def run_distrust(
166
+ *,
167
+ apply: bool = False,
168
+ undo: bool = False,
169
+ certs_dir: str | Path | None = None,
170
+ ) -> int:
171
+ certs_path = Path(certs_dir) if certs_dir else default_certs_dir()
172
+ system = platform.system()
173
+ certs = load_certs(certs_path)
174
+ if len(certs) != 8:
175
+ print(
176
+ f"WARNING: expected 8 verified intermediate PEMs, found {len(certs)} in "
177
+ f"{certs_path}. Proceeding with what verified.",
178
+ file=sys.stderr,
179
+ )
180
+ steps = plan_for_os(system, certs, undo)
181
+
182
+ verb = "ROLLBACK" if undo else "DISTRUST"
183
+ print(
184
+ f"# emSign-G3 {verb} plan on {platform.node()} ({system}) — "
185
+ f"{'APPLY' if apply else 'DRY RUN'}\n"
186
+ )
187
+ if not steps:
188
+ print("No supported trust store found to modify on this host.")
189
+ return 0
190
+ for desc, cmd, undo_hint in steps:
191
+ print(f"* {desc}")
192
+ print(f" cmd: {' '.join(cmd)}")
193
+ if not undo:
194
+ print(f" undo: {undo_hint}")
195
+ if apply:
196
+ r = subprocess.run(cmd, capture_output=True, text=True)
197
+ status = "ok" if r.returncode == 0 else f"FAILED rc={r.returncode}"
198
+ print(f" -> {status} {r.stderr.strip()[:160]}")
199
+ print()
200
+ if not apply:
201
+ print("Dry run only. Re-run with --apply (likely under sudo/Administrator) to execute.")
202
+ return 0
emsign_g3/elastic.py ADDED
@@ -0,0 +1,71 @@
1
+ """Generate Elastic KQL detection stubs from Bug 2083449 indicators."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ from pathlib import Path
6
+
7
+ from emsign_g3.indicators import COMPROMISED_TYPES, load_rows
8
+
9
+ MOZILLA_BUG = "2083449"
10
+ GENERIC_SID = 92083409
11
+
12
+ _SAFE = re.compile(r"[^a-zA-Z0-9_-]+")
13
+
14
+
15
+ def _slug(name: str) -> str:
16
+ return _SAFE.sub("_", name).strip("_").lower()
17
+
18
+
19
+ def build_elastic_kql_rule(row: dict) -> str:
20
+ cn = row["tls_issuer_cn_match"]
21
+ sid = int(row["suricata_sid"])
22
+ serial = row.get("serial") or "unknown"
23
+ lines = [
24
+ f"// eMudhra emSign G3 compromised issuer: {row['ca_common_name']}",
25
+ f"// Mozilla Bug {MOZILLA_BUG} | serial {serial} | Suricata sid {sid}",
26
+ "// Starter KQL for Suricata EVE / ECS-normalized TLS events.",
27
+ "// Adjust field names (e.g. suricata.eve.alert.signature_id) for your stack.",
28
+ f'alert.signature_id == {sid} or tls.issuerdn : *"{cn}"*',
29
+ "",
30
+ ]
31
+ return "\n".join(lines)
32
+
33
+
34
+ def build_elastic_kql_backstop() -> str:
35
+ return "\n".join(
36
+ [
37
+ "// eMudhra emSign G3 compromised-family issuer (generic review)",
38
+ f"// Mozilla Bug {MOZILLA_BUG} | Suricata sid {GENERIC_SID}",
39
+ "// Review hits manually; prefer per-CA rules in this directory.",
40
+ f"alert.signature_id == {GENERIC_SID}",
41
+ "",
42
+ ]
43
+ )
44
+
45
+
46
+ def elastic_kql_from_rows(rows: list[dict]) -> list[tuple[str, str]]:
47
+ out: list[tuple[str, str]] = []
48
+ for row in rows:
49
+ if row["type"] not in COMPROMISED_TYPES:
50
+ continue
51
+ base = row.get("pem_basename") or _slug(row["ca_common_name"])
52
+ out.append((f"emsign_g3_{base.lower()}.kql", build_elastic_kql_rule(row)))
53
+ out.append(("emsign_g3_generic_backstop.kql", build_elastic_kql_backstop()))
54
+ return out
55
+
56
+
57
+ def render_elastic_kql_bundle(rows: list[dict] | None = None) -> str:
58
+ rows = rows if rows is not None else load_rows()
59
+ parts = [content for _, content in elastic_kql_from_rows(rows)]
60
+ return "\n".join(parts)
61
+
62
+
63
+ def write_elastic_kql_rules(output_dir: Path, rows: list[dict] | None = None) -> list[Path]:
64
+ output_dir.mkdir(parents=True, exist_ok=True)
65
+ written: list[Path] = []
66
+ rows = rows if rows is not None else load_rows()
67
+ for filename, content in elastic_kql_from_rows(rows):
68
+ path = output_dir / filename
69
+ path.write_text(content, encoding="utf-8")
70
+ written.append(path)
71
+ return written