LibreWolf, Certificate Transparency and Untrusted CA

Hard to believe it's happening but I'm forced to trust some untrusted CA. It sounds crazy, I know.

The problem

I am the client of some local banks and from time to time I use their sites for banking. Most of them were using GlobalSign certificates which has recently started revoking them. And banks and some other companies were forced to switch to state CA run by The Ministry of Digital Development and Communications.

Here is the problem - those state-issued certificates are not recognized by Chrome, Firefox and Safari because they know nothing about this CA.

Possible solutions to this problem:

  • switch to Yandex browser which has state CA certificate included,
  • manually add state CA certificate to OS (making it available for all browsers),
  • manually add state CA certificate to selected browser(s).

I've tried Yandex browser (which is based on Chromium codebase, by the way) and was frustrated by it - too many AI-related options which I will never use. But it has a nice feature - support for Certificate Transparency (article is in Russian) for state-issued certificates.

Installing state CA certificate to OS was not an option to me.

Installing it to browser (LibreWolf is my browser of choice at the moment) worked but Certificate Transparency was missing as browser still knows nothing about correspondent CT logs.

Let's try to fix it.

Note

While experimenting and looking through the codebase I've found out that Certificate Transparency will work for builtin trusted CA only, not for imported one.

The solution

Caution

I'm neither LibreWolf nor Firefox developer so use all steps outlined below at your own risk.

After several try-and-fail attempts I've managed to create the build I needed - custom LibreWolf build with state CA certificate and CT logs support included.

Here is the workflow.

Step 0 - prepare build host

Debian is supported by LibreWolf out of the box, so it's a good choice to start with. I was using it to prepare my custom build and will use it to run my custom flavour of the browser. I use separate virtual machines for both tasks.

So, initial step is to install Debian into virtual machine.

Note

Unless otherwise noted, all opeartions below must be performed on this virtual machine.

Step 1 - download LibreWolf sources (tarball)

As a preliminary step be sure to read LibreWolf Build Instructions.

Go to LibreWolf Releases page, choose the latest one, download tarball and initialize git repository.

Git repository will help to prepare the patch but there is no need to commit and push it somewhere. You just have to add four files to index to be able to get diff later.

Files to be patched:

$ cd /workdir

$ wget https://codeberg.org/api/packages/librewolf/generic/librewolf-source/153.0.3-1/librewolf-153.0.3-1.source.tar.gz
$ wget https://codeberg.org/api/packages/librewolf/generic/librewolf-source/153.0.3-1/librewolf-153.0.3-1.source.tar.gz.sha256sum
$ wget https://codeberg.org/api/packages/librewolf/generic/librewolf-source/153.0.3-1/librewolf-153.0.3-1.source.tar.gz.sha512sum

$ sha256sum -c librewolf-153.0.3-1.source.tar.gz.sha256sum
librewolf-153.0.3-1.source.tar.gz: OK

$ sha512sum -c librewolf-153.0.3-1.source.tar.gz.sha512sum
librewolf-153.0.3-1.source.tar.gz: OK

$ tar xf librewolf-153.0.3-1.source.tar.gz
$ cd librewolf-153.0.3-1/

$ git init
$ git add security/nss/lib/ckfw/builtins/certdata.txt
$ git add security/nss/lib/ckfw/builtins/nssckbi.h
$ git add security/ct/CTKnownLogs.h
$ git add toolkit/components/certviewer/content/components/logNameTable.mjs

Step 2 - prepare diff for builtin trusted CA certificates

As a preliminary step be sure to read Updating NSS's Root Store (another copy is available here).

Assuming you already have new CA certificate downloaded you need to convert it to DER format as it's distributed in PEM format:

$ openssl x509 -outform der -in cert.pem -out cert.der

Now you can add it to builtin root store:

$ cd security/nss/lib/ckfw/builtins/

$ addbuiltin -n "Russian Trusted Root CA" -t "C,," -i cert.der >>certdata.txt

$ cd -
/workdir/librewolf-153.0.3-1

There is a chance you don't have addbuiltin tool installed (I was missing it too). I tried to find Debian package containing it but no luck there.

Fortunately, my Void Linux installation have it installed from nss package.

$ xbps-query -f nss |grep addbuiltin
/usr/libexec/nss/addbuiltin

So, I have to copy CA certificate and certdata.txt to my laptop, run two commands outlined above and copy new version of certdata.txt back to build host. Phew.

One more thing to do here - update nssckbi.h file to bump the version of the module.

After completing this step you must have two files updated - certdata.txt and nssckbi.h:

$ git status security/nss/
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   security/nss/lib/ckfw/builtins/certdata.txt
	new file:   security/nss/lib/ckfw/builtins/nssckbi.h

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   security/nss/lib/ckfw/builtins/certdata.txt
	modified:   security/nss/lib/ckfw/builtins/nssckbi.h

...

Be sure to check the diff:

$ git diff security/nss/
...

Step 3 - prepare diff for known CT Logs list

As a preliminary step be sure to look through CTKnownLogs.h file.

You should start with downloading CT Logs list:

$ cd /workdir && mkdir ctlog && cd ctlog

$ wget https://browser-resources.s3.yandex.net/ctlog/ctlog.json

$ cd ../librewolf-153.0.3-1/

I've prepared python script to convert downloaded json file data to put into CTKnownLogs.h file. This script is rather naive and looks ugly to show it here.

You can use python code from getCTKnownLogs.py script to convert json data to header file.

You should update two lists in CTKnownLogs.h:

  • kCTLogList containing CTLogInfo structs,
  • kCTLogOperatorList containing CTLogOperatorInfo structs.

It is important to put new CTLogInfo structs before Mozilla Test RSA logs as long as new CTLogOperatorInfo structs before Mozilla Test orgs which means you will have to update Mozilla Test orgs indexes in both structs.

This is needed to have proper CT Logs operators indexes in kCTLogOperatorList in production build.

Here is the diff I've prepared (you are encouraged to prepare your very own version though):

diff --git a/security/ct/CTKnownLogs.h b/security/ct/CTKnownLogs.h
index f5bc780..dc9376b 100644
--- a/security/ct/CTKnownLogs.h
+++ b/security/ct/CTKnownLogs.h
@@ -537,10 +537,70 @@ const CTLogInfo kCTLogList[] = {
      "\xc4\xb5\xc5\x87\x42\x14\x63\x3e\xf7\x6b\xd2\x3b\x93\x19\x14\xa8\xcf\x8d"
      "\xf3",
      91},
+    {"Yandex Agate-2026 log", CTLogState::Admissible, CTLogFormat::RFC6962,
+     1731002400000,  // 2024-11-07T18:00:00Z
+     8,              // operated by Yandex
+     "\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\x01\x00\x08\xc1\xf1\x1b\x06\x45\x2e"
+     "\xd5\x10\xd2\x2e\x79\x5c\x1a\x85\x65\x31\xb4\x5c\xf3\x04\x02\xd7\x2b\xac"
+     "\x58\xb0\x43\x47\xc7\x37\x70\x92\x32\x93\x41\xed\x23\xe5\x30\x7e\x06\xbd"
+     "\xf7\x3e\x3f\xec\xcb\x7c\x7e\x44\xb0\x55\x31\x90\x90\xc9\x22\xbf\x85\x2c"
+     "\x4f",
+     91},
+    {"Yandex Agate-2027 log", CTLogState::Admissible, CTLogFormat::RFC6962,
+     1764075600000,  // 2025-11-25T13:00:00Z
+     8,              // operated by Yandex
+     "\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\x58\xf0\x37\x2c\xed\x63\x27\x0c\x9c"
+     "\x41\xca\xee\x0a\xab\xde\xd9\x13\x34\x33\x5f\x47\x17\xb5\x7d\x20\x50\x81"
+     "\xb1\xe9\x10\x4d\x68\x37\x64\x1f\x26\xf4\xe1\x84\x75\x51\x55\x9c\xdb\xdf"
+     "\xe9\x3e\xa4\xb8\xb3\x76\x99\x15\x7a\x91\xfc\x25\x16\x26\xae\xac\x89\xf2"
+     "\x8f",
+     91},
+    {"VK 'NCA2026' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
+     1730999340000,  // 2024-11-07T17:09:00Z
+     9,              // operated by VK
+     "\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\xad\x19\x87\x2f\x48\xd0\xf2\x9e\x1c"
+     "\xb2\x02\xa7\x2f\x53\x5d\x14\xb5\x9e\x2d\x54\x36\xe1\x49\x04\xe9\x7b\x8c"
+     "\xc1\xfc\xd5\xd2\x46\x31\x92\x50\x8c\xa3\xac\x4a\x04\xe6\x38\x4d\x95\xcf"
+     "\x46\xda\xea\x63\x53\x55\x34\x84\x78\x4a\x5e\x95\xa0\x64\xac\xda\x57\xe9"
+     "\x6b",
+     91},
+    {"VK 'NCA2027' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
+     1766059740000,  // 2025-12-18T12:09:00Z
+     9,              // operated by VK
+     "\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\xd3\x7d\xec\x07\xd9\x09\xfc\x63\xe6"
+     "\x5d\x40\xf4\xad\x9b\x84\xe5\x49\xec\x73\x32\x5b\x99\x5c\xb8\x3f\x5c\xea"
+     "\x81\x58\x67\xca\x28\x64\x2e\xa0\xaf\x5f\x74\xaa\x98\xa6\x76\x0d\x25\xd3"
+     "\x0d\x77\xe3\xe4\x4b\x14\x9b\x97\x54\x49\x44\xa8\xe1\x10\x9e\x36\xaa\xd1"
+     "\xc1",
+     91},
+    {"The Ministry of Digital Development and Communications '2026' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
+     1732968000000,  // 2024-11-30T12:00:00Z
+     10,             // operated by The Ministry of Digital Development and Communications
+     "\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\x2c\xb9\x50\x11\xb0\xd8\xed\xe1\xcc"
+     "\x61\x67\x1e\xde\x6e\x60\xe0\x88\x6e\x60\x62\xb4\x93\xc9\x82\x37\x0e\xc6"
+     "\xe2\xb4\x49\xb8\x90\x20\x01\x31\xaf\xd8\xe0\xc7\x26\xc4\x67\x28\x09\x32"
+     "\x8f\x69\x2f\x5d\x1a\x7d\x2e\xa4\x46\x00\x90\xd5\xab\x23\xed\x75\x7b\x3c"
+     "\x6c",
+     91},
+    {"The Ministry of Digital Development and Communications '2027' Log", CTLogState::Admissible, CTLogFormat::RFC6962,
+     1764504000000,  // 2025-11-30T12:00:00Z
+     10,             // operated by The Ministry of Digital Development and Communications
+     "\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\xaa\xcb\xb1\x39\xab\xec\x5a\x0e\xdb"
+     "\xa6\x1b\xe7\xdb\xee\xa7\x51\x6d\x8b\xfd\x49\x90\xb7\x1b\x1b\x65\x09\xcf"
+     "\x70\x91\xab\x76\x14\x9d\x82\x43\x3c\x41\xe3\xdf\xc3\x41\x33\x05\x71\xa3"
+     "\x45\x11\xfc\x0b\xf4\x9a\x6b\x53\x98\xe3\x07\xa0\xdc\xf2\x6f\x99\x97\xea"
+     "\xf5",
+     91},
 #ifdef DEBUG
     {"Mozilla Test RSA Log 1", CTLogState::Admissible, CTLogFormat::RFC6962,
      1721666666000,  // 2024-07-22T16:44:26Z
-     8,              // operated by Mozilla Test Org 1
+     11              // operated by Mozilla Test Org 1
      "\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\xba\x88\x51"
      "\xa8\x44\x8e\x16\xd6\x41\xfd\x6e\xb6\x88\x06\x36\x10\x3d\x3c\x13\xd9\xea"
@@ -563,7 +623,7 @@ const CTLogInfo kCTLogList[] = {
 #ifdef DEBUG
     {"Mozilla Test EC Log", CTLogState::Admissible, CTLogFormat::RFC6962,
      1721666666000,  // 2024-07-22T16:44:26Z
-     8,              // operated by Mozilla Test Org 1
+     11,             // operated by Mozilla Test Org 1
      "\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\x4f\xbf\xbb\xbb\x61\xe0\xf8\xf9\xb1"
      "\xa6\x0a\x59\xac\x87\x04\xe2\xec\x05\x0b\x42\x3e\x3c\xf7\x2e\x92\x3f\x2c"
@@ -575,7 +635,7 @@ const CTLogInfo kCTLogList[] = {
 #ifdef DEBUG
     {"Mozilla Test RSA Log 2", CTLogState::Admissible, CTLogFormat::RFC6962,
      1721666666000,  // 2024-07-22T16:44:26Z
-     9,              // operated by Mozilla Test Org 2
+     12,             // operated by Mozilla Test Org 2
      "\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\xc1\x75\xc6"
      "\x52\x66\x09\x9f\x77\x08\x2a\x67\x91\xf1\xb8\x76\xc3\x7f\x5c\xe5\x38\xb0"
@@ -598,7 +658,7 @@ const CTLogInfo kCTLogList[] = {
 #ifdef DEBUG
     {"Mozilla Test RSA Log 4", CTLogState::Admissible, CTLogFormat::Tiled,
      1750853366000,  // 2025-06-25T12:09:26Z
-     10,             // operated by Mozilla Test Org 3
+     13,             // 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"
@@ -629,14 +689,17 @@ const CTLogOperatorInfo kCTLogOperatorList[] = {
     {"TrustAsia", 5},
     {"Geomys", 6},
     {"IPng Networks", 7},
+    {"Yandex", 8},
+    {"VK", 9},
+    {"The Ministry of Digital Development and Communications", 10},
 #ifdef DEBUG
-    {"Mozilla Test Org 1", 8},
+    {"Mozilla Test Org 1", 11},
 #endif  // DEBUG
 #ifdef DEBUG
-    {"Mozilla Test Org 2", 9},
+    {"Mozilla Test Org 2", 12},
 #endif  // DEBUG
 #ifdef DEBUG
-    {"Mozilla Test Org 3", 10},
+    {"Mozilla Test Org 3", 13},
 #endif  // DEBUG
 };

One more file to edit here - logNameTable.mjs which contains mapping between CT Log id and CT Log description.

Here is the diff (but you are still encouraged to prepare your very own version though):

diff --git a/toolkit/components/certviewer/content/components/logNameTable.mjs b/toolkit/components/certviewer/content/components/logNameTable.mjs
index c13717b..66ea360 100644
--- a/toolkit/components/certviewer/content/components/logNameTable.mjs
+++ b/toolkit/components/certviewer/content/components/logNameTable.mjs
@@ -58,5 +58,11 @@ export const logNameTable = {
   "utuIpG+cr6QJDoLlk1bbbni0pT9YLbCBl5UkLym2jJg=": "Google 'ParcelYard2026h2' log",
   "wjF+V0UZo0XufzjespBB68fCIVoiv3/Vta12mtkOUs0=": "DigiCert 'Wyvern2026h2'",
   "yKPEf8ezrbk1awE/anoSbeM6TkOlxkb5l605dZkdz5o=": "Sectigo 'Tiger2026h2'",
-  "yzj3FYl8hKFEX1vB3fvJbvKaWc1HCmkFhbDLFMMUWOc=": "Cloudflare 'Nimbus2026'"
+  "yzj3FYl8hKFEX1vB3fvJbvKaWc1HCmkFhbDLFMMUWOc=": "Cloudflare 'Nimbus2026'",
+  "Vh7HRN8a9wLmJoOt38SVz3WjGwxs6KDH6gEDMJC3SIU=": "Yandex Agate-2026 log",
+  "GukWh2bX94qcsIb4xJw8Xc64yZMAqa9L/7CbB0+BLBc=": "Yandex Agate-2027 log",
+  "eNkSvkIKwAIcul2iVDKRU9tIywYr/EMBJB4Qq8cWnmc=": "VK 'NCA2026' Log",
+  "1LN/8nNPJvHlUHXlhVtwT95xu30Fx1fT3OsTf2JDrSQ=": "VK 'NCA2027' Log",
+  "ybgRvMAcYl3KJ/TWxEfLX34Ov3LLWZGoMy+Zv5oqPko=": "The Ministry of Digital Development and Communications '2026' Log",
+  "TJig3bv0dZNmDDboZiaHFAn5fnh4TpGeqWr4HFMnXNY=": "The Ministry of Digital Development and Communications '2027' Log"
 }

After completing this step you must have two more files updated - CTKnownLogs.h and logNameTable.mjs:

$ git status security/ct/ toolkit/
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   security/ct/CTKnownLogs.h
	new file:   toolkit/components/certviewer/content/components/logNameTable.mjs

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   security/ct/CTKnownLogs.h
	modified:   toolkit/components/certviewer/content/components/logNameTable.mjs

  ...

Be sure to check the diff:

$ git diff security/ct/ toolkit/
...

Step 4 - prepare the patch

This is quite simple:

$ pwd
/workdir/librewolf-153.0.3-1

$ git diff >../ct-known-logs.patch

Step 5 - clone LibreWolf sources repository, include the patch and build

Pretty simple too (based on LibreWolf Build from Repository Instructions):

$ cd /workdir
$ git clone --recursive git@codeberg.org:librewolf/source.git librewolf-source --depth=1

$ cp ct-known-logs.patch librewolf-source/patches/
$ echo "patches/ct-known-logs.patch" >>assets/patches.txt

$ cd librewolf-source

$ make dir && make bootstrap

$ make build

$ make package

Voila!

In case of no errors you will have librewolf-153.0.3-1.en-US.linux-x86_64.tar.xz tarball - LibreWolf package to copy to target machine, unpack and run the browser:

$ cd /opt
$ sudo tar xf /workdir/librewolf-153.0.3-1.en-US.linux-x86_64.tar.xz

$ cd librewolf

$ ./librewolf

Step 6 - rebuild

In case you've updated the patch be sure to clean build dir and bootstrap:

$ pwd
/workdir/librewolf-source

$ make veryclean

$ make dir && make bootstrap

$ make build

$ make package

Step 7 - test CT Logs

Not sure my approach is good enough but to be sure CT Logs for state-issued certificates are working properly I've decided to prepare broken build.

I intentionally changed one byte of the CT Log key for all new logs in CTKnownLogs.h file, built the package and attempted to load some bank page using this broken build. I've got an error saying something is wrong with the site - something I expected.

Good to know it works.


Hard to believe it's happening in this world. Hard to say I'm happy I managed to find the solution. I'd be happy if I never had to deal with this problem.

Unfortunately, this is something you can't change. But you can try to minimize the risks arising from this situation.

Let's move on.

Last modified: 2026-08-11 16:00:00 UTC