eMudhra's update of 5 November gives three dates for certificates that chain to its emSign Root CA - G1: Chrome on 16 November, Firefox on 16 November, Apple on 30 November. It also says they mean different things. In Chrome and Firefox, certificates issued after the 16th stop being trusted and older ones keep working. In Apple's case every G1 certificate stops being trusted, whenever it was issued.
"Issued after" sounds like one check. It is not. Chrome and Firefox both store the date for this root as 16 November 2026 23:59:59 UTC, but they compare it against different fields of the certificate. So I read the two root programs' own data files and source, then wrote a small stdlib Python checker that reads the fields each browser reads and prints what applies to a given host. All of it was run on 12 November 2026.
What each source actually says
eMudhra's notice (last updated 5 November 2026) lists "Google Chrome: SCTNotAfter - November 16, 2026", the same for Mozilla Firefox, and "Apple: G1 removal - November 30, 2026". That is the CA describing what the root programs "have indicated". The programs publish their own data, so I went to that.
Chrome. The Chrome Root Store is a text file in the Chromium tree, root_store.textproto. The G1 entry, as of today:
# CN=emSign Root CA - G1, O=eMudhra Technologies Limited, OU=emSign PKI, C=IN # https://testevg1.emsign.com/ # Constraint date: Mon, Nov 16, 2026, 23:59:59 GMT+0000 trust_anchors { sha256_hex: "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367" crs_root_id: 27 ev_policy_oids: "2.23.140.1.1" constraints: { sct_not_after_sec: 1794873599 min_version: "153.0.8010.0" } constraints: { max_version_exclusive: "153.0.8010.0" } } The schema in root_store.proto defines sct_not_after_sec as "The leaf certificate must have at least one valid SCT timestamp that is not after the specified value", and says an anchor with constraints "is only trusted if at least one of the ConstraintSets is satisfied". So: Chrome 153.0.8010.0 and later trust a G1 leaf only if it carries at least one valid Signed Certificate Timestamp dated on or before 16 November 23:59:59 UTC. Older Chrome builds match the second set and trust G1 with no date at all. The same schema has a validity_starts_not_after_sec field for a notBefore cutoff; this entry does not use it.
Firefox. Mozilla tracks the change in Bug 2089115, "Remove email trust and set TLS distrust-after to 2026-11-16 23:59:59 UTC for emSign Root CA - G1", pushed to NSS on 5 November. In NSS's certdata.txt the root now carries CKA_NSS_SERVER_DISTRUST_AFTER with the comment "For Server Distrust After: Mon Nov 16 23:59:59 2026". What Firefox compares that value against is in its own source. In NSSCertDBTrustDomain.cpp the distrust check passes when the leaf's notBefore is on or before the distrust-after time; otherwise the result is ERROR_ISSUER_NO_LONGER_TRUSTED. Separately, MultiLogCTVerifier.cpp receives the same time and does not count any SCT whose timestamp is after it (sctsWithDistrustedTimestamps).
So Chrome reads SCT timestamps and Firefox reads notBefore, with a second rule that drops late SCTs. In the three certificates checked below, the SCTs are dated within three minutes of notBefore, so the two rules agree. Close to the cutoff they can disagree.
Apple. Apple's support page "About the compromised eMudhra intermediate CAs" (published 9 November) covers the G3 intermediates, and it lists emSign Root CA - G1 among five root certificates that macOS 27.2.1, 26.7.3 and 15.8.3 remove from /etc/ssl/cert.pem, the file that some macOS command-line tools, including curl, git, python3 and openssl, check against. I did not find the 30 November date on an Apple page, so that date rests on eMudhra's notice.
Reading the fields
Everything the check needs is in the leaf certificate. notBefore is in the validity sequence. Embedded SCTs sit in the X.509 extension 1.3.6.1.4.1.11129.2.4.2, whose value is an OCTET STRING wrapping another OCTET STRING, which holds the TLS-encoded SignedCertificateTimestampList from RFC 6962 section 3.3. Each SCT (section 3.2) starts with a version byte, a 32-byte log ID and a 64-bit timestamp in milliseconds since the epoch, followed by extensions and the log's signature.
A minimal DER reader first:
import hashlib, socket, ssl, sys from datetime import datetime, timezone def read_tlv(buf, i): tag = buf[i] length = buf[i + 1] i += 2 if length & 0x80: n = length & 0x7F length = int.from_bytes(buf[i:i + n], "big") i += n return tag, buf[i:i + length], i + length def children(buf): i, out = 0, [] while i < len(buf): tag, val, i = read_tlv(buf, i) out.append((tag, val)) return out def parse_time(tag, val): fmt = "%y%m%d%H%M%SZ" if tag == 0x17 else "%Y%m%d%H%M%SZ" return datetime.strptime(val.decode(), fmt).replace(tzinfo=timezone.utc) Then notBefore and the SCT list:
SCT_OID = bytes.fromhex("2b06010401d679020402") # 1.3.6.1.4.1.11129.2.4.2 def parse_sct_list(data): # RFC 6962 section 3.3: uint16 list length, then uint16-prefixed SCTs total = int.from_bytes(data[0:2], "big") i, out = 2, [] while i < 2 + total: n = int.from_bytes(data[i:i + 2], "big") sct = data[i + 2:i + 2 + n] log_id = sct[1:33] ms = int.from_bytes(sct[33:41], "big") out.append((log_id, datetime.fromtimestamp(ms / 1000, timezone.utc))) i += 2 + n return out def leaf_fields(der): _, cert, _ = read_tlv(der, 0) tbs = children(children(cert)[0][1]) pos = 1 if tbs[0][0] == 0xA0 else 0 # skip [0] version validity = children(tbs[pos + 3][1]) not_before = parse_time(*validity[0]) scts = [] for tag, val in tbs: if tag != 0xA3: # [3] extensions continue for _, ext in children(children(val)[0][1]): parts = children(ext) if parts[0][1] == SCT_OID: _, inner, _ = read_tlv(parts[-1][1], 0) # OCTET STRING in OCTET STRING scts = parse_sct_list(inner) return not_before, scts The parser stops at the timestamp. It does not verify SCT signatures or check that the log is one Chrome or Firefox recognises, and both browsers only count SCTs that pass those checks. For a certificate from a real CA that is unlikely to change the answer, but the script does not prove it.
To know whether a certificate is under G1 at all you need the chain, not only the leaf. Python 3.13 added SSLSocket.get_verified_chain(), which returns DER bytes. This machine runs 3.12, where the same method exists only on the private _sslobj, so the helper uses that as a fallback:
def fetch_chain(host): ctx = ssl.create_default_context() with socket.create_connection((host, 443), timeout=10) as sock: with ctx.wrap_socket(sock, server_hostname=host) as tls: if hasattr(tls, "get_verified_chain"): # Python 3.13+ return tls.get_verified_chain() chain = tls._sslobj.get_verified_chain() # 3.10 to 3.12 return [c.public_bytes(ssl._ssl.ENCODING_DER) for c in chain] The root is identified by the SHA-256 of its DER encoding. Chrome's textproto, NSS's certdata and Apple's support page all give the same fingerprint for emSign Root CA - G1, 40F6AF03...D0367.
The three rules in code
G1_ROOT = "40f6af0346a99aa1cd1d555a4e9cce62c7f9634603ee406615833dc8c8d00367" CHROME_SCT_NOT_AFTER = datetime.fromtimestamp(1794873599, timezone.utc) NSS_DISTRUST_AFTER = datetime(2026, 11, 16, 23, 59, 59, tzinfo=timezone.utc) APPLE_G1_REMOVAL = datetime(2026, 11, 30, tzinfo=timezone.utc) # eMudhra's date def chrome_ok(scts): # root_store.proto: at least one valid SCT not after sct_not_after_sec return any(ts <= CHROME_SCT_NOT_AFTER for _, ts in scts) def firefox_ok(not_before, scts): # NSSCertDBTrustDomain.cpp: notBefore <= distrust-after, else # ERROR_ISSUER_NO_LONGER_TRUSTED. MultiLogCTVerifier.cpp: later SCTs are not counted. floor = lambda ts: ts.replace(microsecond=0) counted = [ts for _, ts in scts if floor(ts) <= NSS_DISTRUST_AFTER] return not_before <= NSS_DISTRUST_AFTER, len(counted), len(scts) The Firefox function rounds SCT times down to the second because MultiLogCTVerifier.cpp does. Whether a certificate with zero counted SCTs then fails in Firefox depends on how Firefox enforces Certificate Transparency, which I did not check, so the script reports the count rather than a verdict.
def report(host): chain = fetch_chain(host) root = hashlib.sha256(chain[-1]).hexdigest() not_before, scts = leaf_fields(chain[0]) print(f"{host}") print(f" root sha256 {root[:16]}... G1: {root == G1_ROOT}") print(f" notBefore {not_before:%Y-%m-%d %H:%M:%S}") for log_id, ts in scts: print(f" SCT {ts:%Y-%m-%d %H:%M:%S.%f}"[:-3] + f" log {log_id[:4].hex()}") if root != G1_ROOT: print(" verdict not under emSign Root CA - G1; none of the three dates apply") return ok, counted, total = firefox_ok(not_before, scts) print(f" Chrome 153+ {'trusted' if chrome_ok(scts) else 'NOT trusted'} (SCT on or before 16 Nov)") print(f" Firefox {'trusted' if ok else 'NOT trusted'} (notBefore), " f"{counted} of {total} SCTs counted") print(f" Apple every G1 certificate, from {APPLE_G1_REMOVAL:%d %b %Y} (eMudhra)") Running it
eMudhra runs test hosts under G1, and Chrome's entry links one of them. I ran the checker against two G1 test hosts and against dev.to as a control:
testevg1.emsign.com root sha256 40f6af0346a99aa1... G1: True notBefore 2026-10-15 16:47:20 SCT 2026-10-15 16:49:36.522 log d6d58da9 SCT 2026-10-15 16:49:37.234 log 4c63dc98 SCT 2026-10-15 16:49:37.045 log 604c9aaf Chrome 153+ trusted (SCT on or before 16 Nov) Firefox trusted (notBefore), 3 of 3 SCTs counted Apple every G1 certificate, from 30 Nov 2026 (eMudhra) testovg1.emsign.com root sha256 40f6af0346a99aa1... G1: True notBefore 2026-10-15 16:21:07 SCT 2026-10-15 16:23:51.025 log d6d58da9 SCT 2026-10-15 16:23:51.712 log 4c63dc98 SCT 2026-10-15 16:23:51.015 log 604c9aaf Chrome 153+ trusted (SCT on or before 16 Nov) Firefox trusted (notBefore), 3 of 3 SCTs counted Apple every G1 certificate, from 30 Nov 2026 (eMudhra) dev.to root sha256 4fa3126d8d3a11d1... G1: False notBefore 2026-10-28 22:11:46 SCT 2026-10-28 22:11:48.281 log 1c9f682c SCT 2026-10-28 22:11:48.972 log 8eca470b SCT 2026-10-28 22:11:49.658 log 604c9aaf verdict not under emSign Root CA - G1; none of the three dates apply Both G1 certificates have a notBefore of 15 October and SCTs two to three minutes later, so the two browser rules agree: still trusted in Chrome and Firefox after the 16th. Apple's date, per eMudhra, applies to them anyway, because it covers every G1 certificate. openssl x509 -text on the same leaf prints the same three timestamps, which is a useful cross-check on the parser.
Where the two browser rules disagree
The live certificates do not reach the edge, so here are three made-up cases run through the same functions:
def edge_cases(): t = lambda s: datetime.fromisoformat(s).replace(tzinfo=timezone.utc) cases = { "issued the evening of 16 Nov": (t("2026-11-16 21:00:00"), [t("2026-11-16 21:02:10")]), "notBefore 23:58, SCTs after midnight": (t("2026-11-16 23:58:00"), [t("2026-11-17 00:00:41"), t("2026-11-17 00:00:42")]), "issued 20 Nov": (t("2026-11-20 09:00:00"), [t("2026-11-20 09:02:00")]), } for name, (nb, times) in cases.items(): scts = [(b"", ts) for ts in times] ok, counted, total = firefox_ok(nb, scts) print(f"{name:38} Chrome {'yes' if chrome_ok(scts) else 'no ':3} " f"Firefox notBefore {'yes' if ok else 'no '} SCTs counted {counted}/{total}") issued the evening of 16 Nov Chrome yes Firefox notBefore yes SCTs counted 1/1 notBefore 23:58, SCTs after midnight Chrome no Firefox notBefore yes SCTs counted 0/2 issued 20 Nov Chrome no Firefox notBefore no SCTs counted 0/1 In the middle case, a certificate with notBefore at 23:58 on 16 November whose SCTs were all logged after midnight passes Firefox's notBefore check, gets none of its SCTs counted there, and has no SCT that satisfies Chrome. notBefore is a value the CA writes into the certificate; an SCT timestamp is written by the log. That is why "issued after the 16th" is not one test: Chrome asks the logs, Firefox mostly asks the CA.
What this means for a G1 certificate
For anything that chains to emSign Root CA - G1:
- Certificates already deployed with SCTs on or before 16 November keep working in current Chrome and Firefox until they expire, by these rules. Apple's 30 November date, per eMudhra, does not care about issuance time, so those certificates need replacing before then for Apple clients.
- A G1 certificate whose SCTs are all dated 17 November or later fails in Chrome 153 and later, and one with a notBefore after the 16th fails Firefox's check.
- Clients that read a plain PEM bundle never see either browser rule. A PEM file lists certificates; it has nowhere to put "only if logged before 16 November". This Ubuntu machine's store still verified the testevg1 chain today. When a given bundle drops the root depends on who builds it and when.
For any distrust date, the question to ask is which field the client compares it against. Chrome answers it in root_store.proto, Firefox in its certificate verifier source.
Written with AI assistance from my own notes and test results. I checked every fact and command before publishing.