summaryrefslogtreecommitdiffstats
path: root/security/manager/tools
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/tools')
-rw-r--r--security/manager/tools/.eslintrc.js13
-rw-r--r--security/manager/tools/KnownRootHashes.json1263
-rw-r--r--security/manager/tools/PreloadedHPKPins.json207
-rw-r--r--security/manager/tools/crtshToIdentifyingStruct/crtshToIdentifyingStruct.py156
-rw-r--r--security/manager/tools/crtshToIdentifyingStruct/requirements.txt4
-rw-r--r--security/manager/tools/dumpGoogleRoots.js96
-rw-r--r--security/manager/tools/genRootCAHashes.js273
-rwxr-xr-xsecurity/manager/tools/getCTKnownLogs.py330
-rw-r--r--security/manager/tools/log_list.json403
-rw-r--r--security/manager/tools/mach_commands.py129
-rwxr-xr-xsecurity/manager/tools/pycert.py805
-rwxr-xr-xsecurity/manager/tools/pycms.py219
-rw-r--r--security/manager/tools/pyct.py103
-rwxr-xr-xsecurity/manager/tools/pykey.py957
-rw-r--r--security/manager/tools/pypkcs12.py124
15 files changed, 5082 insertions, 0 deletions
diff --git a/security/manager/tools/.eslintrc.js b/security/manager/tools/.eslintrc.js
new file mode 100644
index 0000000000..44c1d9cba6
--- /dev/null
+++ b/security/manager/tools/.eslintrc.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // JS files in this folder are commonly xpcshell scripts where |arguments|
+ // is defined in the global scope.
+ arguments: false,
+ },
+};
diff --git a/security/manager/tools/KnownRootHashes.json b/security/manager/tools/KnownRootHashes.json
new file mode 100644
index 0000000000..e939b39ca7
--- /dev/null
+++ b/security/manager/tools/KnownRootHashes.json
@@ -0,0 +1,1263 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+//
+//***************************************************************************
+// This is an automatically generated file. It's used to maintain state for
+// runs of genRootCAHashes.js; you should never need to manually edit it
+//***************************************************************************
+
+// Notes:
+// binNumber 1 used to be for "GTE_CyberTrust_Global_Root", but that root was
+// removed from the built-in roots module, so now it is used to indicate that
+// the certificate is not a built-in and was found in the softoken (cert9.db).
+
+// binNumber 2 used to be for "Thawte_Server_CA", but that root was removed from
+// the built-in roots module, so now it is used to indicate that the certificate
+// is not a built-in and was found on an external PKCS#11 token.
+
+// binNumber 3 used to be for "Thawte_Premium_Server_CA", but that root was
+// removed from the built-in roots module, so now it is used to indicate that
+// the certificate is not a built-in and was temporarily imported from the OS as
+// part of the "Enterprise Roots" feature.
+
+{
+ "roots": [
+ {
+ "label": "OU_Equifax_Secure_Certificate_Authority_O_Equifax_C_US",
+ "binNumber": 4,
+ "sha256Fingerprint": "CCl6QEfbojaAxzHbbjF2U8p4SOG+vToLAXmnB/ks8Xg="
+ },
+ {
+ "label": "OU_VeriSign_Trust_Network_OU___c__1998_VeriSign__Inc____For_authorized_use_only__OU_Class_3_Public_Primary_Certification_Authority___G2_O__VeriSign__Inc___C_US",
+ "binNumber": 5,
+ "sha256Fingerprint": "g848Eiloilk9SF+BlzwPkZVDHto3zF42Qw55x6iIY4s="
+ },
+ {
+ "label": "GlobalSign_Root_CA",
+ "binNumber": 6,
+ "sha256Fingerprint": "69QQQOS7PsdCyeOB0x7ypBpItmhclufO88HfbNQzHJk="
+ },
+ {
+ "label": "GlobalSign",
+ "binNumber": 7,
+ "sha256Fingerprint": "ykLdQXRf0LgeuQI2LPnYv3Gdob0bHvyUb1tMmfQsG54="
+ },
+ {
+ "label": "VeriSign_Class_3_Public_Primary_Certification_Authority___G3",
+ "binNumber": 8,
+ "sha256Fingerprint": "6wTPXrHzmvp2LyuxIPKWy6Ugwbl9sViVZbgcuaF7ckQ="
+ },
+ {
+ "label": "VeriSign_Class_4_Public_Primary_Certification_Authority___G3",
+ "binNumber": 9,
+ "sha256Fingerprint": "44k2DQ/brrPSUFhLRzAxTiIvOcFWoCAUTo2WBWF5FQY="
+ },
+ {
+ "label": "Entrust_net_Certification_Authority__2048_",
+ "binNumber": 10,
+ "sha256Fingerprint": "bcRxcuAcvLC/YlgNiV/iuKya1PhzgB4MELnIN9IesXc="
+ },
+ {
+ "label": "Baltimore_CyberTrust_Root",
+ "binNumber": 11,
+ "sha256Fingerprint": "Fq9XqfZ2sKsSYJWqXrre8iqzERnWRKyVzUuT2/Pyaus="
+ },
+ {
+ "label": "Equifax_Secure_Global_eBusiness_CA_1",
+ "binNumber": 12,
+ "sha256Fingerprint": "Xwti6rXjU+plIWUWWPu2U1n0QygKSvvRBNd9EPnwTAc="
+ },
+ {
+ "label": "Equifax_Secure_eBusiness_CA_1",
+ "binNumber": 13,
+ "sha256Fingerprint": "z1b/RqShhhCd2WWEte61ilEMQnWw5flPQLuuhl4Z9nM="
+ },
+ {
+ "label": "AddTrust_Class_1_CA_Root",
+ "binNumber": 14,
+ "sha256Fingerprint": "jHIJJ5rATideFtB/07d16AFUtZaARuMfUt0ldmMk6ac="
+ },
+ {
+ "label": "AddTrust_External_CA_Root",
+ "binNumber": 15,
+ "sha256Fingerprint": "aH+kUTgieP/wyLEfjUPVdmccbrK86rQT+4PZZdBtL/I="
+ },
+ {
+ "label": "AddTrust_Public_CA_Root",
+ "binNumber": 16,
+ "sha256Fingerprint": "B5HKB0myB4Kq08fXvQzfyUhYNYQ+steZYAnOQ6tsaSc="
+ },
+ {
+ "label": "AddTrust_Qualified_CA_Root",
+ "binNumber": 17,
+ "sha256Fingerprint": "gJUhCAXbS7w1XkQo2P1uws3jq1+5eplCmI649NzQYBY="
+ },
+ {
+ "label": "Entrust_Root_Certification_Authority",
+ "binNumber": 18,
+ "sha256Fingerprint": "c8F2Q08bxtWt9FsOducnKHyN5XYWwebmFBorLLx9jkw="
+ },
+ {
+ "label": "OU_RSA_Security_2048_V3_O_RSA_Security_Inc",
+ "binNumber": 19,
+ "sha256Fingerprint": "r4tnYqHlKCKBYaldXFWe4mYnj3XXnoMBiaUDUGq9a0w="
+ },
+ {
+ "label": "GeoTrust_Global_CA",
+ "binNumber": 20,
+ "sha256Fingerprint": "/4VqLSUdzYjTZlb0UBJnmM+rqt5AeZxyLeTStds2pzo="
+ },
+ {
+ "label": "GeoTrust_Global_CA_2",
+ "binNumber": 21,
+ "sha256Fingerprint": "yi2CoIZ3By+KtnZP8DVnbP4+XjJeASFy3z+SCW23m4U="
+ },
+ {
+ "label": "GeoTrust_Universal_CA",
+ "binNumber": 22,
+ "sha256Fingerprint": "oEWbn2OyJVn1+l1MbbP59y/xk0IDNXjwc78dG0bLuRI="
+ },
+ {
+ "label": "GeoTrust_Universal_CA_2",
+ "binNumber": 23,
+ "sha256Fingerprint": "oCNPO8hSfKVijuyBrV1piV2laA3JHRy4R38z+Hi5Wws="
+ },
+ {
+ "label": "America_Online_Root_Certification_Authority_1",
+ "binNumber": 24,
+ "sha256Fingerprint": "d0BzEsY6FT1bwAtOUXWc39rCN9wqM7Z5RumOm/poCuM="
+ },
+ {
+ "label": "America_Online_Root_Certification_Authority_2",
+ "binNumber": 25,
+ "sha256Fingerprint": "fTtGWmAU5SbAr/zuISfSMRcnrYEcJoQtAGrzcwbMgL0="
+ },
+ {
+ "label": "Visa_eCommerce_Root",
+ "binNumber": 26,
+ "sha256Fingerprint": "afrJvVX7CseNU7vuXPHVl5if0KqrIKJRUb3xcz7n0SI="
+ },
+ {
+ "label": "Certum_CA",
+ "binNumber": 27,
+ "sha256Fingerprint": "2OD+vB2y440AlA830n1BNE2ZPnNLmdVlbZd41NgUNiQ="
+ },
+ {
+ "label": "AAA_Certificate_Services",
+ "binNumber": 28,
+ "sha256Fingerprint": "16eg+11+JzHXcelITrze9x1fDD4KKUh4K8g+4OppnvQ="
+ },
+ {
+ "label": "Secure_Certificate_Services",
+ "binNumber": 29,
+ "sha256Fingerprint": "vYHOO09lkdEaZ7X8ekf97yVSG/mqThi5498uNKeAO+g="
+ },
+ {
+ "label": "Trusted_Certificate_Services",
+ "binNumber": 30,
+ "sha256Fingerprint": "PwblVoHUlvW+Fp61OJ+fK4/2HhcI32iBckhJzV0ny2k="
+ },
+ {
+ "label": "QuoVadis_Root_Certification_Authority",
+ "binNumber": 31,
+ "sha256Fingerprint": "pF7eO7vwnIrhXHLvwHJo1pOiHJlv1R5nygeUYP1tiHM="
+ },
+ {
+ "label": "QuoVadis_Root_CA_2",
+ "binNumber": 32,
+ "sha256Fingerprint": "haDdfdcgrbf/Bfg9VCsgncf/RSj31nexg4n+peXEnoY="
+ },
+ {
+ "label": "QuoVadis_Root_CA_3",
+ "binNumber": 33,
+ "sha256Fingerprint": "GPH8fyBd+K3d63/gB91X4683WpxNjXNUa/Tx/tHhjTU="
+ },
+ {
+ "label": "OU_Security_Communication_RootCA1_O_SECOM_Trust_net_C_JP",
+ "binNumber": 34,
+ "sha256Fingerprint": "515y7Z9WDuxutIAAc6Q/w60ZGVo5IoIBeJWXSpkCa2w="
+ },
+ {
+ "label": "Sonera_Class2_CA",
+ "binNumber": 35,
+ "sha256Fingerprint": "eQi0AxTBOBALUY0HNYB/+/z4UYoAlTNxBbo4axU92Sc="
+ },
+ {
+ "label": "Staat_der_Nederlanden_Root_CA",
+ "binNumber": 36,
+ "sha256Fingerprint": "1B2CnowWWYIq+T/OYr/83iZPyE6LlQxf8nXQUjVGlaM="
+ },
+ {
+ "label": "UTN___DATACorp_SGC",
+ "binNumber": 37,
+ "sha256Fingerprint": "hfsvkd0SJ1oBRbY2U0+EAkrWi2m47ohoT/cRN1gFs0g="
+ },
+ {
+ "label": "UTN_USERFirst_Hardware",
+ "binNumber": 38,
+ "sha256Fingerprint": "bqVHQdAEZn7tG0gWY0qjp55uS5aVD4J52vyNm9iBITc="
+ },
+ {
+ "label": "Chambers_of_Commerce_Root",
+ "binNumber": 39,
+ "sha256Fingerprint": "DCWKEqVnSu8l8oun3Prs7qNI5UHm9cxO5jtxs2FgasM="
+ },
+ {
+ "label": "Global_Chambersign_Root",
+ "binNumber": 40,
+ "sha256Fingerprint": "7zy0F/yOv2+Xh2yeTs453h6l/mSRQdECi30RwLIpjO0="
+ },
+ {
+ "label": "NetLock_Kozjegyzoi__Class_A__Tanusitvanykiado",
+ "binNumber": 41,
+ "sha256Fingerprint": "fxLNX35eKQ7H2FF51bcsIKW+dQj/21v4GrloSn/J9mc="
+ },
+ {
+ "label": "XRamp_Global_Certification_Authority",
+ "binNumber": 42,
+ "sha256Fingerprint": "zs3ckFCZ2NrfxbHSCbc3y+LBjPssEMD/C88NMob8GqI="
+ },
+ {
+ "label": "OU_Go_Daddy_Class_2_Certification_Authority_O__The_Go_Daddy_Group__Inc___C_US",
+ "binNumber": 43,
+ "sha256Fingerprint": "w4Rr8kuek8pkJ0wOxnwezF4CT/ys0tdAGTUOgf5UauQ="
+ },
+ {
+ "label": "OU_Starfield_Class_2_Certification_Authority_O__Starfield_Technologies__Inc___C_US",
+ "binNumber": 44,
+ "sha256Fingerprint": "FGX6IFOXuHb6pvCplY5VkOQPzH+qT7fCyGd1Iftftlg="
+ },
+ {
+ "label": "StartCom_Certification_Authority",
+ "binNumber": 45,
+ "sha256Fingerprint": "x2apvvLUBxyGOjGqSSDoE7LRmGCMt7fP4hFDuDbfCeo="
+ },
+ {
+ "label": "O_Government_Root_Certification_Authority_C_TW",
+ "binNumber": 46,
+ "sha256Fingerprint": "dgApXu/oW54f1iTbdgYqqq5ZgYpU0ndM1MCywBEx4bM="
+ },
+ {
+ "label": "Swisscom_Root_CA_1",
+ "binNumber": 47,
+ "sha256Fingerprint": "IdsgEjZguy7UGCBdoR7nqFpl4rxuVbWvfniZyKJm2S4="
+ },
+ {
+ "label": "DigiCert_Assured_ID_Root_CA",
+ "binNumber": 48,
+ "sha256Fingerprint": "PpCZtQFej0hsALzqnREe5yH6ujVaibzx32lWHj3GMlw="
+ },
+ {
+ "label": "DigiCert_Global_Root_CA",
+ "binNumber": 49,
+ "sha256Fingerprint": "Q0ig6URMeMsmXgWNXolEtNhPlmK9Jtslf4k0pEPHAWE="
+ },
+ {
+ "label": "DigiCert_High_Assurance_EV_Root_CA",
+ "binNumber": 50,
+ "sha256Fingerprint": "dDHl9MPBzkaQd08LYeBUQIg7qaAe0Aumq9eAbtOxGM8="
+ },
+ {
+ "label": "Class_2_Primary_CA",
+ "binNumber": 51,
+ "sha256Fingerprint": "D5k8iu+Xuq9WhxQO1ZrRghu0r6zwqppYtdV6M4o6+8s="
+ },
+ {
+ "label": "DST_Root_CA_X3",
+ "binNumber": 52,
+ "sha256Fingerprint": "BocmAzGnJAPZCfEF5pvPDTLhvSST/8bZIG0RvNZ3Bzk="
+ },
+ {
+ "label": "DST_ACES_CA_X6",
+ "binNumber": 53,
+ "sha256Fingerprint": "dnyVWnZBLImvaI6QoccPVWz9a2Al2+oQQW1+toMfjEA="
+ },
+ {
+ "label": "T_RKTRUST_Elektronik_Sertifika_Hizmet_Sa_lay_c_s_",
+ "binNumber": 54,
+ "sha256Fingerprint": "RATjO14UDc+ZgFH9/IAox8gWFcXuc3sRG1iCM6m1NaA="
+ },
+ {
+ "label": "T_RKTRUST_Elektronik_Sertifika_Hizmet_Sa_lay_c_s_",
+ "binNumber": 55,
+ "sha256Fingerprint": "xHDPVH4jArl3+yndcaiae2wfYHd7Ayn1YBfzKL9Pa+Y="
+ },
+ {
+ "label": "SwissSign_Gold_CA___G2",
+ "binNumber": 56,
+ "sha256Fingerprint": "Yt0L6bn1ChY+oPjnXAU7HspX6lXIaI9kfGiB8sg1e5U="
+ },
+ {
+ "label": "SwissSign_Silver_CA___G2",
+ "binNumber": 57,
+ "sha256Fingerprint": "vmxNoru5ulm285OXaDdCRsPABZk/qY8CDR3tvtSKgdU="
+ },
+ {
+ "label": "GeoTrust_Primary_Certification_Authority",
+ "binNumber": 58,
+ "sha256Fingerprint": "N9UQBsUS6qtiZCHx7IySAT/F+CrpjuUz60YZuN600Gw="
+ },
+ {
+ "label": "thawte_Primary_Root_CA",
+ "binNumber": 59,
+ "sha256Fingerprint": "jXIvganBE8B5HfE2opZtsmyVCpcdtGtBmfTqVLeL+58="
+ },
+ {
+ "label": "VeriSign_Class_3_Public_Primary_Certification_Authority___G5",
+ "binNumber": 60,
+ "sha256Fingerprint": "ms+rfkPI2IDQayYqlN7u5LRlmYnD0Mrxm69kBeQat98="
+ },
+ {
+ "label": "SecureTrust_CA",
+ "binNumber": 61,
+ "sha256Fingerprint": "8cG1CuWiDdgDDsn2vCSCPdNntSVXWbTnG2H86fc3XXM="
+ },
+ {
+ "label": "Secure_Global_CA",
+ "binNumber": 62,
+ "sha256Fingerprint": "QgD1BDrIWQ67Un0gntFQMCn7y9QcobUG7CfxWt59rGk="
+ },
+ {
+ "label": "COMODO_Certification_Authority",
+ "binNumber": 63,
+ "sha256Fingerprint": "DCzWPfeAb6OZ7egJEWtXW/h5ifBlGPmAjIYFAxeLr2Y="
+ },
+ {
+ "label": "Network_Solutions_Certificate_Authority",
+ "binNumber": 64,
+ "sha256Fingerprint": "FfC6AKOsevOsiEwHKxARoHe9d8CX9AFksvhZir2Dhgw="
+ },
+ {
+ "label": "WellsSecure_Public_Root_Certificate_Authority",
+ "binNumber": 65,
+ "sha256Fingerprint": "pxJyrqqjz+hyf3+znw+z0eVCbpBgsG7m8T6aPFgzzUM="
+ },
+ {
+ "label": "COMODO_ECC_Certification_Authority",
+ "binNumber": 66,
+ "sha256Fingerprint": "F5OSegYUVJeJrc4vjzT38LZtDzrjo7hNIewV27pPrcc="
+ },
+ {
+ "label": "IGC_A",
+ "binNumber": 67,
+ "sha256Fingerprint": "ub6nhgqWLqNhHauXq22j4hwQaLl9VVde0OESecEciTI="
+ },
+ {
+ "label": "OU_Security_Communication_EV_RootCA1_O__SECOM_Trust_Systems_CO__LTD___C_JP",
+ "binNumber": 68,
+ "sha256Fingerprint": "oi26aB6XN24tOX1yiq46m2KWuf26YLwuEfZH8sZ1+zc="
+ },
+ {
+ "label": "OISTE_WISeKey_Global_Root_GA_CA",
+ "binNumber": 69,
+ "sha256Fingerprint": "Qckjhmq0yta3rVeAgVguAgeXpsvfT/94zoOWs4k31/U="
+ },
+ {
+ "label": "Microsec_e_Szigno_Root_CA",
+ "binNumber": 70,
+ "sha256Fingerprint": "Mno9dhq63qA065mEBidcsaR3bv2uL99tAWjqHE9VZ9A="
+ },
+ {
+ "label": "Certigna",
+ "binNumber": 71,
+ "sha256Fingerprint": "47ai2y7XzkiEL3rFMkHHtx1UFEv7QMEfPx0LQvXuoS0="
+ },
+ {
+ "label": "TC_TrustCenter_Class_2_CA_II",
+ "binNumber": 72,
+ "sha256Fingerprint": "5rj4dmSF+Aeuf42sFnBGHwfAoT7vOh/3F1ONerrTkbQ="
+ },
+ {
+ "label": "TC_TrustCenter_Class_3_CA_II",
+ "binNumber": 73,
+ "sha256Fingerprint": "jaCE/Pmc4Hci+JsyBZOYBvpcuBHhyBP2oQjH0zazQI4="
+ },
+ {
+ "label": "TC_TrustCenter_Universal_CA_I",
+ "binNumber": 74,
+ "sha256Fingerprint": "6/PAKoeJsft9URmV1mO3KQbZE84NXhBWiop34lhhZ+c="
+ },
+ {
+ "label": "Deutsche_Telekom_Root_CA_2",
+ "binNumber": 75,
+ "sha256Fingerprint": "thkaUNDDl399qZvNqshqIn2uuWeexwujsMnZInHBcNM="
+ },
+ {
+ "label": "ComSign_Secured_CA",
+ "binNumber": 76,
+ "sha256Fingerprint": "UHlBx0RgoLRwhiINTpkyVyq10bW7y4mAqxyxdlGoRNI="
+ },
+ {
+ "label": "Cybertrust_Global_Root",
+ "binNumber": 77,
+ "sha256Fingerprint": "lgrfAGPpY1Z1DCll3QoIZ9oLnL1ud3FK6vsjSas5PaM="
+ },
+ {
+ "label": "OU_ePKI_Root_Certification_Authority_O__Chunghwa_Telecom_Co___Ltd___C_TW",
+ "binNumber": 78,
+ "sha256Fingerprint": "wKb03GOiS/3PVO8qaggqCnLeNYA+L/X/Unrl2HIG39U="
+ },
+ {
+ "label": "T_B_TAK_UEKAE_K_k_Sertifika_Hizmet_Sa_lay_c_s____S_r_m_3",
+ "binNumber": 79,
+ "sha256Fingerprint": "5Mc0MNeltQkl30M3Cg0hbpp5udbbg3Ogxp6xzDHHxSo="
+ },
+ {
+ "label": "Buypass_Class_2_CA_1",
+ "binNumber": 80,
+ "sha256Fingerprint": "D06c3SZLAlVQ0XCAY0AhT+lENMmwL2l+xxD8X+r7Xjg="
+ },
+ {
+ "label": "Buypass_Class_3_CA_1",
+ "binNumber": 81,
+ "sha256Fingerprint": "t7ErFx+CHaqZDND+UIexKESLqOUYT4TFHgK1yPuWKyQ="
+ },
+ {
+ "label": "EBG_Elektronik_Sertifika_Hizmet_Sa_lay_c_s_",
+ "binNumber": 82,
+ "sha256Fingerprint": "Na5b3dj3rmNc/7pWgqjwC5X0hGLHEI7poOUpKwdKr7I="
+ },
+ {
+ "label": "OU_certSIGN_ROOT_CA_O_certSIGN_C_RO",
+ "binNumber": 83,
+ "sha256Fingerprint": "6qlixPpKa6/r5BUZbTUczYiNT1Pz+orm18RmqU5gQrs="
+ },
+ {
+ "label": "CNNIC_ROOT",
+ "binNumber": 84,
+ "sha256Fingerprint": "4oOTdz2oRaZ58ggMx/tEo7ehw3kst+t3Kf3Lao2Zrqc="
+ },
+ {
+ "label": "OU_ApplicationCA_O_Japanese_Government_C_JP",
+ "binNumber": 85,
+ "sha256Fingerprint": "LUdDfeF5USFaEvPFjlHHKaWAJu8fzApfs9ncAS9gDRk="
+ },
+ {
+ "label": "GeoTrust_Primary_Certification_Authority___G3",
+ "binNumber": 86,
+ "sha256Fingerprint": "tHi4EiUN+HhjXCqn7H0VXqpiXugpFuLNKUNhiGzR+9Q="
+ },
+ {
+ "label": "thawte_Primary_Root_CA___G2",
+ "binNumber": 87,
+ "sha256Fingerprint": "pDENUK8YpkRxkDcqhq+vi5Uf+0Mdg38eVoi0WXHtFVc="
+ },
+ {
+ "label": "thawte_Primary_Root_CA___G3",
+ "binNumber": 88,
+ "sha256Fingerprint": "SwP0WAetcPIb/Cyuccn95GBMBkz1/7aGuuXbqtf900w="
+ },
+ {
+ "label": "GeoTrust_Primary_Certification_Authority___G2",
+ "binNumber": 89,
+ "sha256Fingerprint": "Xtt6xDuCoGqHYejXvkl56/JhH33Xm/kcHGtWaiGe12Y="
+ },
+ {
+ "label": "VeriSign_Universal_Root_Certification_Authority",
+ "binNumber": 90,
+ "sha256Fingerprint": "I5lWESelcSXejO/qYQ3fL6B4tcgGf06CgpC/uGDoSzw="
+ },
+ {
+ "label": "VeriSign_Class_3_Public_Primary_Certification_Authority___G4",
+ "binNumber": 91,
+ "sha256Fingerprint": "ad3X6pC7V8k+E13IXqb81UgLYDI5vcRU/HWLKibPf3k="
+ },
+ {
+ "label": "NetLock_Arany__Class_Gold__F_tan_s_tv_ny",
+ "binNumber": 92,
+ "sha256Fingerprint": "bGHaw6Le8DFQa+A20qb+QBmU+9E9+cjUZlmSdMRG7Jg="
+ },
+ {
+ "label": "Staat_der_Nederlanden_Root_CA___G2",
+ "binNumber": 93,
+ "sha256Fingerprint": "ZoyDlH2mO3JL7OF0PDGg5q7Q247Fsxvjd7t4T5G2cW8="
+ },
+ {
+ "label": "CA_Disig",
+ "binNumber": 94,
+ "sha256Fingerprint": "kr9RGavsytCxMy3E4dBfunW1Z5BE7gyibpMfdE8vM88="
+ },
+ {
+ "label": "Juur_SK",
+ "binNumber": 95,
+ "sha256Fingerprint": "7MPpw0B1A77gkaqVL0E0j/iLqoY7ImS++sgHkBV06Tk="
+ },
+ {
+ "label": "Hongkong_Post_Root_CA_1",
+ "binNumber": 96,
+ "sha256Fingerprint": "+eZ9M2xRACrAVMYyAi1m3aLn4//xCtBh7THYu7QQz7I="
+ },
+ {
+ "label": "SecureSign_RootCA11",
+ "binNumber": 97,
+ "sha256Fingerprint": "vw/u+546WBrV+enbdYmYV0PSYQhcTTFPb11yWapCFhI="
+ },
+ {
+ "label": "ACEDICOM_Root",
+ "binNumber": 98,
+ "sha256Fingerprint": "A5UPtJpTHz4ZkZQjmN+p4Ooy17oc3ZvIXbV+2UALQ0o="
+ },
+ {
+ "label": "Microsec_e_Szigno_Root_CA_2009",
+ "binNumber": 99,
+ "sha256Fingerprint": "PF+B/qX6uCxkv6Lq7K/N6OB3/IYgp8rlNxY9827b83g="
+ },
+ {
+ "label": "e_Guven_Kok_Elektronik_Sertifika_Hizmet_Saglayicisi",
+ "binNumber": 100,
+ "sha256Fingerprint": "5gkHhGWkGXgMtqxMHAv7RlPZ2cxus5Rut/PWmZe61Zg="
+ },
+ {
+ "label": "GlobalSign",
+ "binNumber": 101,
+ "sha256Fingerprint": "y7Ui17fxJ61qAROGW98c1BAufQdZr2NafPRyDcljxTs="
+ },
+ {
+ "label": "Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068",
+ "binNumber": 102,
+ "sha256Fingerprint": "BASAKL8fKGTUj5rU2DKUNmqCiFZVPzsUMD+QFH9dQO8="
+ },
+ {
+ "label": "Izenpe_com",
+ "binNumber": 103,
+ "sha256Fingerprint": "JTDMjpgyFQK62W+bH7obCZ4tKZ4PRUi7kU82O8DUUx8="
+ },
+ {
+ "label": "Chambers_of_Commerce_Root___2008",
+ "binNumber": 104,
+ "sha256Fingerprint": "Bj5K+sSR39My8wibhULpRhfYk9f+lE4Qp5N+4p2Wk8A="
+ },
+ {
+ "label": "Global_Chambersign_Root___2008",
+ "binNumber": 105,
+ "sha256Fingerprint": "E2M1Q5M0p2mAFqDTJN5yKE4HnXtSILuPvXR4Fu6+uso="
+ },
+ {
+ "label": "Go_Daddy_Root_Certificate_Authority___G2",
+ "binNumber": 106,
+ "sha256Fingerprint": "RRQLMkfrnMjFtPDXtTCR9zKSCJ5uWmPidJ3TrKkZjto="
+ },
+ {
+ "label": "Starfield_Root_Certificate_Authority___G2",
+ "binNumber": 107,
+ "sha256Fingerprint": "LOHLC/nS+eECmT++IVFSw7LdDKveHGjlMZuDkVTbt/U="
+ },
+ {
+ "label": "Starfield_Services_Root_Certificate_Authority___G2",
+ "binNumber": 108,
+ "sha256Fingerprint": "Vo1pBaLIhwikswJRkO3P7bGXSmBqE8blKQ/LKuY+2rU="
+ },
+ {
+ "label": "AffirmTrust_Commercial",
+ "binNumber": 109,
+ "sha256Fingerprint": "A3arHVTF+YA85LLiAaDufu97V7Y26Kk8m41IYMlvX6c="
+ },
+ {
+ "label": "AffirmTrust_Networking",
+ "binNumber": 110,
+ "sha256Fingerprint": "CoHsWpKXd/FFkErzjV1Qn2a14sWPzbUxBYsOF/PwtBs="
+ },
+ {
+ "label": "AffirmTrust_Premium",
+ "binNumber": 111,
+ "sha256Fingerprint": "cKc/fzdrYAdCSJBFNLEUgtW/DmmOzEmN9SV36/LpO5o="
+ },
+ {
+ "label": "AffirmTrust_Premium_ECC",
+ "binNumber": 112,
+ "sha256Fingerprint": "vXH99tqX5M9i0WR63SWBsH15rfg5frTsupxehIiCFCM="
+ },
+ {
+ "label": "Certum_Trusted_Network_CA",
+ "binNumber": 113,
+ "sha256Fingerprint": "XFhGjVX1jkl+dDmC0rUAELbRZTdKz4On1KMtt2jEQI4="
+ },
+ {
+ "label": "Certinomis___Autorit__Racine",
+ "binNumber": 114,
+ "sha256Fingerprint": "/L/iiGIG9ysnWTyLBwKX4S12ntEO15MHBagJjv/BTRc="
+ },
+ {
+ "label": "Root_CA_Generalitat_Valenciana",
+ "binNumber": 115,
+ "sha256Fingerprint": "jE7f0ENI8yKWnn4ppM1NygBGVQYcFuGwdkIu80KtYw4="
+ },
+ {
+ "label": "A_Trust_nQual_03",
+ "binNumber": 116,
+ "sha256Fingerprint": "eTy/RVm5/eOKsi3xaGn2mIGuFMSwE5rHiKeKGvzKAvs="
+ },
+ {
+ "label": "TWCA_Root_Certification_Authority",
+ "binNumber": 117,
+ "sha256Fingerprint": "v9iP4RAcQa4+gBv4vlY1Dum60aa5vVFe3FxtW4cRrEQ="
+ },
+ {
+ "label": "OU_Security_Communication_RootCA2_O__SECOM_Trust_Systems_CO__LTD___C_JP",
+ "binNumber": 118,
+ "sha256Fingerprint": "UTss7LgQ1M3l3YU5Gt/Gwt1g2Hu3NtK1IUhKpHoOvvY="
+ },
+ {
+ "label": "EC_ACC",
+ "binNumber": 119,
+ "sha256Fingerprint": "iEl/AWAvMVQkauKMTVrvEPHYfrt2Ym9K4Lf5W6eWh5k="
+ },
+ {
+ "label": "Hellenic_Academic_and_Research_Institutions_RootCA_2011",
+ "binNumber": 120,
+ "sha256Fingerprint": "vBBPFaSL5wncpUKn4dS5328FRSfoAuqpLVlURCWK/nE="
+ },
+ {
+ "label": "Actalis_Authentication_Root_CA",
+ "binNumber": 121,
+ "sha256Fingerprint": "VZJghOyWOmS5biq+Ac4LqGpk+/68x6q1r8FVs3/XYGY="
+ },
+ {
+ "label": "OU_Trustis_FPS_Root_CA_O_Trustis_Limited_C_GB",
+ "binNumber": 122,
+ "sha256Fingerprint": "wbSCmaulII/pYwrOVcpooD7aWlGciAKg06Zzvo+OVX0="
+ },
+ {
+ "label": "StartCom_Certification_Authority",
+ "binNumber": 123,
+ "sha256Fingerprint": "4XiQ7gmj+/T0i5xBShfWN7elBkfpvHUjInJ/zBdCqRE="
+ },
+ {
+ "label": "StartCom_Certification_Authority_G2",
+ "binNumber": 124,
+ "sha256Fingerprint": "x7plZ96Tp5iuH6p5HnEtN4+uH5PEOX/qRBu3y+b9WZU="
+ },
+ {
+ "label": "Buypass_Class_2_Root_CA",
+ "binNumber": 125,
+ "sha256Fingerprint": "mhFAJRl8W7ldlOY9Vc1DeQhHtkayPN8RraSgDv8V+0g="
+ },
+ {
+ "label": "Buypass_Class_3_Root_CA",
+ "binNumber": 126,
+ "sha256Fingerprint": "7ffrvKJ6KjhNOHt9QBDGZuLttIQ+TCm0rh1bkzLmsk0="
+ },
+ {
+ "label": "T_TeleSec_GlobalRoot_Class_3",
+ "binNumber": 127,
+ "sha256Fingerprint": "/XPa0xxkT/G0O+8MzdqWcQuc2Ydeyn4xcHrz6W1SK70="
+ },
+ {
+ "label": "EE_Certification_Centre_Root_CA",
+ "binNumber": 128,
+ "sha256Fingerprint": "PoS6Q0KQhRbndXPAmS8JecoITkaFaB/xlcy6iiKbinY="
+ },
+ {
+ "label": "T_RKTRUST_Elektronik_Sertifika_Hizmet_Sa_lay_c_s_",
+ "binNumber": 129,
+ "sha256Fingerprint": "l4zZZvL6oHunqpUA2cAunXfyza2mrWunSvS5HGZZPFA="
+ },
+ {
+ "label": "D_TRUST_Root_Class_3_CA_2_2009",
+ "binNumber": 130,
+ "sha256Fingerprint": "SeekQqzw6mKHBQBUtSVktlDk9J5C40jWqjjgOelXscE="
+ },
+ {
+ "label": "D_TRUST_Root_Class_3_CA_2_EV_2009",
+ "binNumber": 131,
+ "sha256Fingerprint": "7sVJa5iM6YYluTQJLuwpCL7QsPMWwtRzDITq8fPTSIE="
+ },
+ {
+ "label": "PSCProcert",
+ "binNumber": 132,
+ "sha256Fingerprint": "PPw8FNH2hP8X44xDykQMALln7JM+i/4GTKHXLJDyrbA="
+ },
+ {
+ "label": "China_Internet_Network_Information_Center_EV_Certificates_Root",
+ "binNumber": 133,
+ "sha256Fingerprint": "HAHG9Nuy/vwiVYsryjJWP0mESs/DK3vksP9Zn56Mevc="
+ },
+ {
+ "label": "Swisscom_Root_CA_2",
+ "binNumber": 134,
+ "sha256Fingerprint": "8JsSLHEU9KCb1OpPSpnVWLRuTCXNgRQNKcBWE5FMOEE="
+ },
+ {
+ "label": "Swisscom_Root_EV_CA_2",
+ "binNumber": 135,
+ "sha256Fingerprint": "2V/qPKTu3OdM1251/G0f9ixEHw+ovHfwNLGeXbJYAV0="
+ },
+ {
+ "label": "CA_Disig_Root_R1",
+ "binNumber": 136,
+ "sha256Fingerprint": "+W8j9MPnnAd6RpiNWvWQBnag8DnLZF3RdUmyFsgkQM4="
+ },
+ {
+ "label": "CA_Disig_Root_R2",
+ "binNumber": 137,
+ "sha256Fingerprint": "4j1KA217cOn1lbFCIHnSuR7fux+2UaBjPqqKncX4BwM="
+ },
+ {
+ "label": "ACCVRAIZ1",
+ "binNumber": 138,
+ "sha256Fingerprint": "mm7AEuGn2p2+NBlNR4rXwNsYIvsHHfEpgUlu0QQ4QRM="
+ },
+ {
+ "label": "TWCA_Global_Root_CA",
+ "binNumber": 139,
+ "sha256Fingerprint": "WXaQB/doXQ/NUIcvn5XVdVpbK0V9gfNpK2EKmGcvDhs="
+ },
+ {
+ "label": "TeliaSonera_Root_CA_v1",
+ "binNumber": 140,
+ "sha256Fingerprint": "3Wk2/iH48HfBI6GlIcEiJPciVbc+A6cmBpPooksPo4k="
+ },
+ {
+ "label": "E_Tugra_Certification_Authority",
+ "binNumber": 141,
+ "sha256Fingerprint": "sL/VK7DX2b2Sv11NwT2iVcAsVC83g2XqiTkR9V5V8jw="
+ },
+ {
+ "label": "T_TeleSec_GlobalRoot_Class_2",
+ "binNumber": 142,
+ "sha256Fingerprint": "keL1eI1YEOunulhzfeFUio7KzQFFmLwLFD4EGxcFJVI="
+ },
+ {
+ "label": "Atos_TrustedRoot_2011",
+ "binNumber": 143,
+ "sha256Fingerprint": "81a+okS3qR6zXVPKmteGSs4Bji011fj5bd9opvQapHQ="
+ },
+ {
+ "label": "QuoVadis_Root_CA_1_G3",
+ "binNumber": 144,
+ "sha256Fingerprint": "ioZv0bJ2tX5XjpIcZYKKK+1Y6fLyiAVBNLfx9L/JzHQ="
+ },
+ {
+ "label": "QuoVadis_Root_CA_2_G3",
+ "binNumber": 145,
+ "sha256Fingerprint": "j+T7Cvk6TQ1n2wvrsj43xxvzJdy83SQOoE2vWLR+GEA="
+ },
+ {
+ "label": "QuoVadis_Root_CA_3_G3",
+ "binNumber": 146,
+ "sha256Fingerprint": "iO+B3iAusBhFLkP4ZHJc6l+9H8LZ0gVzBwnF2LhpD0Y="
+ },
+ {
+ "label": "DigiCert_Assured_ID_Root_G2",
+ "binNumber": 147,
+ "sha256Fingerprint": "fQXrtoIzn4yUUe4JTuv++nlToRTtsvRJSUUvq30vwYU="
+ },
+ {
+ "label": "DigiCert_Assured_ID_Root_G3",
+ "binNumber": 148,
+ "sha256Fingerprint": "fjfLi0xHCQyrNlUbpvRduEBoD7oWapUtsQBxf0MFP8I="
+ },
+ {
+ "label": "DigiCert_Global_Root_G2",
+ "binNumber": 149,
+ "sha256Fingerprint": "yzzLt2Ax5eATj43TmiP53kf/w15DwRRM6ifUalqxy18="
+ },
+ {
+ "label": "DigiCert_Global_Root_G3",
+ "binNumber": 150,
+ "sha256Fingerprint": "Ma1mSPgQQTjHOPOepDIBMzk+OhjMAilu+Xwqye9nMdA="
+ },
+ {
+ "label": "DigiCert_Trusted_Root_G4",
+ "binNumber": 151,
+ "sha256Fingerprint": "VS973PGnr55s5nIBf08Sq/dyQMeOdhrCA9HZ0grImYg="
+ },
+ {
+ "label": "Certification_Authority_of_WoSign",
+ "binNumber": 152,
+ "sha256Fingerprint": "SyLVpq7JnzzbeapewGg4R5zV7LpxZPfyLcHWX2PYVwg="
+ },
+ {
+ "label": "CA______",
+ "binNumber": 153,
+ "sha256Fingerprint": "1vA0vZSqIz8Cl+ykJFsoOXPkR6pZDzEMd/SP34MRIlQ="
+ },
+ {
+ "label": "COMODO_RSA_Certification_Authority",
+ "binNumber": 154,
+ "sha256Fingerprint": "UvDhxOWOxikpG2AxfwdGcbhdfqgNWwcnNGNTSzK0AjQ="
+ },
+ {
+ "label": "USERTrust_RSA_Certification_Authority",
+ "binNumber": 155,
+ "sha256Fingerprint": "55PJsC/YqhPiHDEiisywgRlkO3SciYlksXRtRsPUy9I="
+ },
+ {
+ "label": "USERTrust_ECC_Certification_Authority",
+ "binNumber": 156,
+ "sha256Fingerprint": "T/Rg1Uuchtq/vPxXEuBADSvtP7xNT72qhuBq3NKprXo="
+ },
+ {
+ "label": "GlobalSign",
+ "binNumber": 157,
+ "sha256Fingerprint": "vslJEcKVVnbbbApVCYbXbjugBWZ8RCyXYrT7t3PeIow="
+ },
+ {
+ "label": "GlobalSign",
+ "binNumber": 158,
+ "sha256Fingerprint": "F5+8FIo90A/STqE0WMxDv6f1nIGC14OlE/br7BAMiSQ="
+ },
+ {
+ "label": "Staat_der_Nederlanden_Root_CA___G3",
+ "binNumber": 159,
+ "sha256Fingerprint": "PE+wuVq4swAy9DK4b1Nf4XLBhdD9OYZYN882GH+m9Cg="
+ },
+ {
+ "label": "Staat_der_Nederlanden_EV_Root_CA",
+ "binNumber": 160,
+ "sha256Fingerprint": "TSSRQUz+lWdG7Ezvps9vcuKKEylDL52KkHrEy12twVo="
+ },
+ {
+ "label": "IdenTrust_Commercial_Root_CA_1",
+ "binNumber": 161,
+ "sha256Fingerprint": "XVZJm+TS4IvPytCKPjhyPVBQO95waUjkL1VgMBnlKK4="
+ },
+ {
+ "label": "IdenTrust_Public_Sector_Root_CA_1",
+ "binNumber": 162,
+ "sha256Fingerprint": "MNCJWppEiiYgkWNVItH1IBC1hnrK4Sx475WP1PQ4ny8="
+ },
+ {
+ "label": "Entrust_Root_Certification_Authority___G2",
+ "binNumber": 163,
+ "sha256Fingerprint": "Q99XdLA+f+9f5A2TGnvt8bsua0JzjE5tOEEQPTqn8zk="
+ },
+ {
+ "label": "Entrust_Root_Certification_Authority___EC1",
+ "binNumber": 164,
+ "sha256Fingerprint": "Au0OsowU2kUWXFZnkXANZFHX+1bwsqsdO46wcOVu3/U="
+ },
+ {
+ "label": "CFCA_EV_ROOT",
+ "binNumber": 165,
+ "sha256Fingerprint": "XMPXjk4dXkVUegTmhz5k+Qz5U20czC74APNVxMX9cP0="
+ },
+ {
+ "label": "T_RKTRUST_Elektronik_Sertifika_Hizmet_Sa_lay_c_s__H5",
+ "binNumber": 166,
+ "sha256Fingerprint": "STUbkDREwYXM3FxpPSTYVVyyCNaoFBMHaZ9K8GMZnXg="
+ },
+ {
+ "label": "T_RKTRUST_Elektronik_Sertifika_Hizmet_Sa_lay_c_s__H6",
+ "binNumber": 167,
+ "sha256Fingerprint": "jeeGVeG+f3hHgAuT9pTSHTaMwG4DPn+rBLteuZ2mtwA="
+ },
+ {
+ "label": "Certinomis___Root_CA",
+ "binNumber": 168,
+ "sha256Fingerprint": "Kpn1vBF0tzy7HWIIhOAcNOUcyzl42hJfDjMmiIO/QVg="
+ },
+ {
+ "label": "OISTE_WISeKey_Global_Root_GB_CA",
+ "binNumber": 169,
+ "sha256Fingerprint": "a5wI6G6w92fPrWXNmLYhSeVJSmf1hF570e0Bnye4a9Y="
+ },
+ {
+ "label": "Certification_Authority_of_WoSign_G2",
+ "binNumber": 170,
+ "sha256Fingerprint": "1Ielb4OwdILoXpYzlMHswsnlHQkD7pRrAsMBWB7ZnhY="
+ },
+ {
+ "label": "CA_WoSign_ECC_Root",
+ "binNumber": 171,
+ "sha256Fingerprint": "i0XaHAb3kesMq/Jr5Yj1+yMWXC5hS/iFVi0NzlCymwI="
+ },
+ {
+ "label": "SZAFIR_ROOT_CA2",
+ "binNumber": 172,
+ "sha256Fingerprint": "oTOdMygaC1blV9PTKxzn+TZ+sJS9X6cqflAEyN7Xyv4="
+ },
+ {
+ "label": "Certum_Trusted_Network_CA_2",
+ "binNumber": 173,
+ "sha256Fingerprint": "tnby7drod1zTbLD2PNHUYDlh9J5iZboBOi8DB7bQuAQ="
+ },
+ {
+ "label": "Hellenic_Academic_and_Research_Institutions_RootCA_2015",
+ "binNumber": 174,
+ "sha256Fingerprint": "oECSmgLOU7Ss9PL/xpgc5ElvdV5tRf4LKmkrzVJSPzY="
+ },
+ {
+ "label": "Hellenic_Academic_and_Research_Institutions_ECC_RootCA_2015",
+ "binNumber": 175,
+ "sha256Fingerprint": "RLVFqool5lpzyhXcJ/w20kwcuZU6BmU5sRWC3Eh7SDM="
+ },
+ {
+ "label": "Certplus_Root_CA_G1",
+ "binNumber": 176,
+ "sha256Fingerprint": "FSpAK/zfLNVIBU0idbOcf8o+wJeAeLDw6nblYabHQz4="
+ },
+ {
+ "label": "Certplus_Root_CA_G2",
+ "binNumber": 177,
+ "sha256Fingerprint": "bMBQQeZEXnRpbEz7yfgPVDt+q7tEtM5veHxqmXHELxc="
+ },
+ {
+ "label": "OpenTrust_Root_CA_G1",
+ "binNumber": 178,
+ "sha256Fingerprint": "VsdxKNmMGNkbTP3/vCXukQPUdY6iq62CapDzRX1GDrQ="
+ },
+ {
+ "label": "OpenTrust_Root_CA_G2",
+ "binNumber": 179,
+ "sha256Fingerprint": "J5lYKf5qdRXBv+hI+cR2HbFsIlkpJXv0DQiU8p6ouvI="
+ },
+ {
+ "label": "OpenTrust_Root_CA_G3",
+ "binNumber": 180,
+ "sha256Fingerprint": "t8NiMXBugQeMNny4lhmPHjII3ZJpSd2PVwmkEPdbYpI="
+ },
+ {
+ "label": "ISRG_Root_X1",
+ "binNumber": 181,
+ "sha256Fingerprint": "lrzsBiZJdvN0YHeazyjFp8/oo8Cq4RqP/O4FwL3fCMY="
+ },
+ {
+ "label": "OU_AC_RAIZ_FNMT_RCM_O_FNMT_RCM_C_ES",
+ "binNumber": 182,
+ "sha256Fingerprint": "68VXDCkBjE1nsaoSe68S9wO0YR68F7fatVc4lBebk/o="
+ },
+ {
+ "label": "Amazon_Root_CA_1",
+ "binNumber": 183,
+ "sha256Fingerprint": "js3miE89h7ESW6Maw/yxPXAW3n9XzJBP4cuXxq6YGW4="
+ },
+ {
+ "label": "Amazon_Root_CA_2",
+ "binNumber": 184,
+ "sha256Fingerprint": "G6WyqoxlQBqClgEY+AvsT2IwTYPOxHE6GcOcAR6kbbQ="
+ },
+ {
+ "label": "Amazon_Root_CA_3",
+ "binNumber": 185,
+ "sha256Fingerprint": "GM5s/nvxTmCy40e43+hoyzHQLrs62icVafUDQ7Rts6Q="
+ },
+ {
+ "label": "Amazon_Root_CA_4",
+ "binNumber": 186,
+ "sha256Fingerprint": "410oQZ7QICXPppA4zWI5YkWNpcaV+96jwisL+yWJcJI="
+ },
+ {
+ "label": "LuxTrust_Global_Root_2",
+ "binNumber": 187,
+ "sha256Fingerprint": "VEVfcSnCCxRHxBj5lxaPJMWPxQI79dpb4utuHdiQLtU="
+ },
+ {
+ "label": "TUBITAK_Kamu_SM_SSL_Kok_Sertifikasi___Surum_1",
+ "binNumber": 188,
+ "sha256Fingerprint": "Ru3DaJBG1TpFP7MQSrgNyuxliyZg6hYp3X6GeZBkhxY="
+ },
+ {
+ "label": "GDCA_TrustAUTH_R5_ROOT",
+ "binNumber": 189,
+ "sha256Fingerprint": "v/+P0EQzSH1qiqYMGil2ep/Cu7BeQg9xOhO5kokdOJM="
+ },
+ {
+ "label": "TrustCor_RootCert_CA_1",
+ "binNumber": 190,
+ "sha256Fingerprint": "1A6chs2P5GjBd2lZ9J6ndPpUhoS2xAbzkJJh9NziV1w="
+ },
+ {
+ "label": "TrustCor_RootCert_CA_2",
+ "binNumber": 191,
+ "sha256Fingerprint": "B1PpQDeMG9Xjg245Xa6ly4OeUEbxvQ6uGVHPEP7HyWU="
+ },
+ {
+ "label": "TrustCor_ECA_1",
+ "binNumber": 192,
+ "sha256Fingerprint": "WohdsZwB2RLFdZOIk4yvu98DGrLUjpHuFVibQpcdA5w="
+ },
+ {
+ "label": "SSL_com_Root_Certification_Authority_RSA",
+ "binNumber": 193,
+ "sha256Fingerprint": "hWZqVi7gvlzpJcHYiQpvdqh+wW1NfV8p6nQZzyASO2k="
+ },
+ {
+ "label": "SSL_com_Root_Certification_Authority_ECC",
+ "binNumber": 194,
+ "sha256Fingerprint": "NBe7BsxgB9oblhySC4q0zj+tgg5Kowuay8SnTr3OvGU="
+ },
+ {
+ "label": "SSL_com_EV_Root_Certification_Authority_RSA_R2",
+ "binNumber": 195,
+ "sha256Fingerprint": "LnvxbMIkhae74qqGlnUHYbCuOb47L+nQzG1O9zSRQlw="
+ },
+ {
+ "label": "SSL_com_EV_Root_Certification_Authority_ECC",
+ "binNumber": 196,
+ "sha256Fingerprint": "IqLB973tcEzB5wG19AjDEIgP6Va13ipKRPmchzolp8g="
+ },
+ {
+ "label": "GlobalSign",
+ "binNumber": 197,
+ "sha256Fingerprint": "LKvq/jfQbKIqunORwAM9JZgpUsRTZHNJdjo6ta1sz2k="
+ },
+ {
+ "label": "OISTE_WISeKey_Global_Root_GC_CA",
+ "binNumber": 198,
+ "sha256Fingerprint": "hWD5HDYk2rqVcLX+oNvjb/EagyO+lIaFT7PzSlVxGY0="
+ },
+ {
+ "label": "GTS_Root_R1",
+ "binNumber": 199,
+ "sha256Fingerprint": "KldUceMTQLwhWBy9LPE+FYRjID7OlLz508wZa/CaVHI="
+ },
+ {
+ "label": "GTS_Root_R2",
+ "binNumber": 200,
+ "sha256Fingerprint": "xF17sI5tZ+YuQjURC1ZOX3j9ku8FjIQK6k5kVddYXGA="
+ },
+ {
+ "label": "GTS_Root_R3",
+ "binNumber": 201,
+ "sha256Fingerprint": "FdW4d0YZ6n1Uzhym0LDEA+A3qRfxMeigTh5renG6vOU="
+ },
+ {
+ "label": "GTS_Root_R4",
+ "binNumber": 202,
+ "sha256Fingerprint": "ccylOR+eeUsEgCUws2PhIdqKMEO7JmYv6k3Kf8lRpL0="
+ },
+ {
+ "label": "UCA_Global_G2_Root",
+ "binNumber": 203,
+ "sha256Fingerprint": "m+oRyXb+AUdkwb5WpvkUtaVgMXq9mYg5M4LlFhqgSTw="
+ },
+ {
+ "label": "UCA_Extended_Validation_Root",
+ "binNumber": 204,
+ "sha256Fingerprint": "1Dr5s1RzdVyWhPwG19jLcO5cKOdz+ylOtB7nFyKSTSQ="
+ },
+ {
+ "label": "Certigna_Root_CA",
+ "binNumber": 205,
+ "sha256Fingerprint": "1I09I+7bUKRZ5VGXYBwnd0udexjJTVoFlRGhAlC5MWg="
+ },
+ {
+ "label": "emSign_Root_CA___G1",
+ "binNumber": 206,
+ "sha256Fingerprint": "QPavA0apmqHNHVVaTpzOYsf5Y0YD7kBmFYM9yMjQA2c="
+ },
+ {
+ "label": "emSign_ECC_Root_CA___G3",
+ "binNumber": 207,
+ "sha256Fingerprint": "hqHsugicSo07vic0xhK6NB2BPgQ8+eioYs1cV6Nrvms="
+ },
+ {
+ "label": "emSign_Root_CA___C1",
+ "binNumber": 208,
+ "sha256Fingerprint": "ElYJqjAdoKJJuXqCOctqNCFvRNysnzlUsUKS8ujIYI8="
+ },
+ {
+ "label": "emSign_ECC_Root_CA___C3",
+ "binNumber": 209,
+ "sha256Fingerprint": "vE2AmxUYnXjbPh2M9PlyanldoWQ8pfE1jh3bDtwNfrM="
+ },
+ {
+ "label": "Hongkong_Post_Root_CA_3",
+ "binNumber": 210,
+ "sha256Fingerprint": "Wi/APwyDsJC7+kBgSwmIRGx2Nhg9+YRuFxAaRH+479Y="
+ },
+ {
+ "label": "Entrust_Root_Certification_Authority___G4",
+ "binNumber": 211,
+ "sha256Fingerprint": "2zUX0fZzKi1auXxTPscHee4ycKYvtKxCODckYObwHog="
+ },
+ {
+ "label": "Microsoft_ECC_Root_Certificate_Authority_2017",
+ "binNumber": 212,
+ "sha256Fingerprint": "NY3znXZK+eG3ZunJct81LuFc+sInr2rR1w6OSm7cugI="
+ },
+ {
+ "label": "Microsoft_RSA_Root_Certificate_Authority_2017",
+ "binNumber": 213,
+ "sha256Fingerprint": "x0H3D0sqjYi/LnHBQSLvU+8Q66DPpeZM+iD0GIUwc+A="
+ },
+ {
+ "label": "e_Szigno_Root_CA_2017",
+ "binNumber": 214,
+ "sha256Fingerprint": "vrALMIObm8MsMuREeQWVBkHyZCGxXtCJGYtRiuLqG5k="
+ },
+ {
+ "label": "OU_certSIGN_ROOT_CA_G2_O_CERTSIGN_SA_C_RO",
+ "binNumber": 215,
+ "sha256Fingerprint": "ZXz+L6c/qjhGJXHzMqI2Okb85wIJUXEHAs37tu7aMwU="
+ },
+ {
+ "label": "Trustwave_Global_Certification_Authority",
+ "binNumber": 216,
+ "sha256Fingerprint": "l1UgFfXd/DyHiMAGlEVVQIiURQCE8QCGcIa8Giu1jcg="
+ },
+ {
+ "label": "Trustwave_Global_ECC_P256_Certification_Authority",
+ "binNumber": 217,
+ "sha256Fingerprint": "lFu8gl6lVPSJ0f1Rpz3fLqYkrHAZoFIFIlwip4zPqLQ="
+ },
+ {
+ "label": "Trustwave_Global_ECC_P384_Certification_Authority",
+ "binNumber": 218,
+ "sha256Fingerprint": "VZA4WcjAw+u4dZ7OTiVXIl/1dYu9OOvUgnZgHhvVgJc="
+ },
+ {
+ "label": "NAVER_Global_Root_Certification_Authority",
+ "binNumber": 219,
+ "sha256Fingerprint": "iPQ43Pj/0fqPQpEV/+X4KuHgbgxww3X6rXF7NKSecmU="
+ },
+ {
+ "label": "AC_RAIZ_FNMT_RCM_SERVIDORES_SEGUROS",
+ "binNumber": 220,
+ "sha256Fingerprint": "VUFTsT0s+d23U7++Gk4K4I0KpBhwWP5gorhisuS4e8s="
+ },
+ {
+ "label": "GlobalSign_Root_R46",
+ "binNumber": 221,
+ "sha256Fingerprint": "T6MSbY06EdHEhVpPgHy61s+RnTpaiLA76ixjctk8QMk="
+ },
+ {
+ "label": "GlobalSign_Root_E46",
+ "binNumber": 222,
+ "sha256Fingerprint": "y7nETYS4BD4QUOoxpp9RSVXXv9LixrSTAQGa1h2fUFg="
+ },
+ {
+ "label": "GLOBALTRUST_2020",
+ "binNumber": 223,
+ "sha256Fingerprint": "milqUYLR1FGi439Dm3Tar6JnUjMp+Q+aDSAHwzTiPJo="
+ },
+ {
+ "label": "ANF_Secure_Server_Root_CA",
+ "binNumber": 224,
+ "sha256Fingerprint": "+4/sdZFpuRBrHlEWRMYYxRMENz9sBkMIjYvv/RuZdZk="
+ },
+ {
+ "label": "Certum_EC_384_CA",
+ "binNumber": 225,
+ "sha256Fingerprint": "azKAhWJTGKpQ0XPJjYvaCdV+J0E9EUz3h6D10GwDDPY="
+ },
+ {
+ "label": "Certum_Trusted_Root_CA",
+ "binNumber": 226,
+ "sha256Fingerprint": "/naWVzhVdz43qV561NnMlsMBV8FdMXZbqbFXBOGueP0="
+ },
+ {
+ "label": "TunTrust_Root_CA",
+ "binNumber": 227,
+ "sha256Fingerprint": "LkQQKrWMuFQZRRyOGdms82Ysr7xhS2pTlgow99Di60E="
+ },
+ {
+ "label": "HARICA_TLS_RSA_Root_CA_2021",
+ "binNumber": 228,
+ "sha256Fingerprint": "2V0Ojtp5Ulv5vrEbFNIQDTKUmF8MYtn6vZzZmezLex0="
+ },
+ {
+ "label": "HARICA_TLS_ECC_Root_CA_2021",
+ "binNumber": 229,
+ "sha256Fingerprint": "P5nMR0rPzk3+1YeUZl5HjRVHc58ueA8btMqbEzCX1AE="
+ },
+ {
+ "label": "Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068",
+ "binNumber": 230,
+ "sha256Fingerprint": "V94Fg+/Ssm4DYdqZ2p30ZI3vfuhEHDtyivqbzeD5smo="
+ },
+ {
+ "label": "vTrus_ECC_Root_CA",
+ "binNumber": 231,
+ "sha256Fingerprint": "MPu6LDIjjiqYVHr5eTHlUEKLmz8cjutmM9z6hsWyfdM="
+ },
+ {
+ "label": "vTrus_Root_CA",
+ "binNumber": 232,
+ "sha256Fingerprint": "inHeZVkzb0JsJuU4gNANiKGNpMapHw3LYZTiBsXJY4c="
+ },
+ {
+ "label": "ISRG_Root_X2",
+ "binNumber": 233,
+ "sha256Fingerprint": "aXKbjhWobvwXelevtxcd/GSt0owvyozxUH40RTzLFHA="
+ },
+ {
+ "label": "HiPKI_Root_CA___G1",
+ "binNumber": 234,
+ "sha256Fingerprint": "8BXOPMI5v+8GS+nx0sQX4aAmSgqUvh8MjRIYZOtpScw="
+ },
+ {
+ "label": "GlobalSign",
+ "binNumber": 235,
+ "sha256Fingerprint": "sIXXC5ZPGRpz5K8NVK56Dgeq/a+bcd0IYhOKtzJaJKI="
+ },
+ {
+ "label": "GTS_Root_R1",
+ "binNumber": 236,
+ "sha256Fingerprint": "2UdDKr3nt/qQ/C5rWRAbEoDg4cfk5A+jxoh//1en9M8="
+ },
+ {
+ "label": "GTS_Root_R2",
+ "binNumber": 237,
+ "sha256Fingerprint": "jSXNlyKdv3A1a9pOs8xzQDHiTPAPr8/TLcdutYQcfqg="
+ },
+ {
+ "label": "GTS_Root_R3",
+ "binNumber": 238,
+ "sha256Fingerprint": "NNinPuII2bzbDZVlIJNLTkDmlIJZbotvc8hCawEKb0g="
+ },
+ {
+ "label": "GTS_Root_R4",
+ "binNumber": 239,
+ "sha256Fingerprint": "NJ36QFjF4mMSOzmK55VXPE4TE8g/5o+TVWzV6AMbPH0="
+ },
+ {
+ "label": "Telia_Root_CA_v2",
+ "binNumber": 240,
+ "sha256Fingerprint": "JCtpdC/LHlsqv5iJi5RXIYdUTltNmRF4ZXNiH2p0uCw="
+ },
+ {
+ "label": "D_TRUST_BR_Root_CA_1_2020",
+ "binNumber": 241,
+ "sha256Fingerprint": "5ZqqgWAJwiv/WyW6033zBvBJeXwfgdhasInmV72PAEQ="
+ },
+ {
+ "label": "D_TRUST_EV_Root_CA_1_2020",
+ "binNumber": 242,
+ "sha256Fingerprint": "CBcNGqNkU5AaL5WSReNH2wyNN6uqvFa4GqEA3JWJcNs="
+ },
+ {
+ "label": "DigiCert_TLS_ECC_P384_Root_G5",
+ "binNumber": 243,
+ "sha256Fingerprint": "AY4T8HclMs+Am9GxcoGGcoP8SMbhO+nGmBKFSkkMGwU="
+ },
+ {
+ "label": "DigiCert_TLS_RSA4096_Root_G5",
+ "binNumber": 244,
+ "sha256Fingerprint": "NxoA3AUzs3IafutA6EGecHmdKwoPLB2AaTFl987ErXU="
+ },
+ {
+ "label": "Certainly_Root_R1",
+ "binNumber": 245,
+ "sha256Fingerprint": "d7gs2GRMQwX3rMXLFWtFZ1AEAz1RxgxiAqjgwzRn06A="
+ },
+ {
+ "label": "Certainly_Root_E1",
+ "binNumber": 246,
+ "sha256Fingerprint": "tFhfIuSsdWpOhhKhNhxdnQMak/2E/rt3j6MGiw/ELcI="
+ },
+ {
+ "label": "E_Tugra_Global_Root_CA_RSA_v3",
+ "binNumber": 247,
+ "sha256Fingerprint": "72awsQo8258uNkjHa9KvGOrSv+bxF2VeKMQGDaGj9MI="
+ },
+ {
+ "label": "E_Tugra_Global_Root_CA_ECC_v3",
+ "binNumber": 248,
+ "sha256Fingerprint": "hz9Ghfp/VjYlJS5tNrzX8W/CSVHyZOR+G5VPSQjNyhM="
+ },
+ {
+ "label": "Security_Communication_RootCA3",
+ "binNumber": 249,
+ "sha256Fingerprint": "JKVcKrBRRC0GF3ZlQSOaStAy18VRdao0/94vvE9cUpQ="
+ },
+ {
+ "label": "Security_Communication_ECC_RootCA1",
+ "binNumber": 250,
+ "sha256Fingerprint": "50+9pVvVZMRzo2tEGqeZyKaOB3RA6CiLn6HlDku6yhE="
+ }
+ ],
+ "maxBin": 250
+} \ No newline at end of file
diff --git a/security/manager/tools/PreloadedHPKPins.json b/security/manager/tools/PreloadedHPKPins.json
new file mode 100644
index 0000000000..d4790e9525
--- /dev/null
+++ b/security/manager/tools/PreloadedHPKPins.json
@@ -0,0 +1,207 @@
+// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// The top-level element is a dictionary with two keys: "pinsets" maps details
+// of certificate pinning to a name and "entries" contains the HPKP details for
+// each host.
+//
+// "pinsets" is a list of objects. Each object has the following members:
+// name: (string) the name of the pinset
+// sha256_hashes: (list of strings) the set of allowed SPKIs hashes
+//
+// For a given pinset, a certificate is accepted if at least one of the
+// Subject Public Key Infos (SPKIs) is found in the chain. SPKIs are specified
+// as names, which must match up with the name given in the Mozilla root store.
+//
+// "entries" is a list of objects. Each object has the following members:
+// name: (string) the DNS name of the host in question
+// include_subdomains: (optional bool) whether subdomains of |name| are also covered
+// pins: (string) the |name| member of an object in |pinsets|
+//
+// "extra_certificates" is a list of base64-encoded certificates. These are used in
+// pinsets that reference certificates not in our root program (for example,
+// Facebook or intermediate CA certs).
+
+{
+ "chromium_data" : {
+ "cert_file_url": "https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/http/transport_security_state_static.pins?format=TEXT",
+ "json_file_url": "https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/http/transport_security_state_static_pins.json?format=TEXT",
+ "substitute_pinsets": {
+ // Use the larger google_root_pems pinset instead of google
+ "google": "google_root_pems"
+ },
+ "production_pinsets": [
+ "google_root_pems",
+ "facebook",
+ "ncsccs"
+ ],
+ "production_domains": [
+ // Chrome's test domains.
+ "pinningtest.appspot.com",
+ "pinning-test.badssl.com",
+ // Tor
+ "torproject.org",
+ "blog.torproject.org",
+ "check.torproject.org",
+ "dist.torproject.org",
+ "www.torproject.org",
+ // SpiderOak
+ "spideroak.com"
+ ],
+ "exclude_domains" : []
+ },
+ "pinsets": [
+ {
+ "name": "mozilla_services",
+ "sha256_hashes": [
+ // Current Digicert root hierarchy (G1)
+ // Digicert is migrating users off this root hierarchy
+ // https://knowledge.digicert.com/generalinformation/digicert-root-and-intermediate-ca-certificate-updates-2023.html
+ "DigiCert Global Root CA",
+ "DigiCert High Assurance EV Root CA",
+ // New Digicert root hierarchy (G2)
+ // Digicert is migrating users to this root hierarchy
+ // https://knowledge.digicert.com/generalinformation/digicert-root-and-intermediate-ca-certificate-updates-2023.html
+ "DigiCert Global Root G2",
+ // Future Digicert root hierarchy (G5)
+ // Digicert will be switching to this root hierarchy in the future
+ // https://knowledge.digicert.com/generalinformation/digicert-g5-root-and-intermediate-ca-certificate-migration.html
+ "DigiCert TLS ECC P384 Root G5",
+ "DigiCert TLS RSA4096 Root G5",
+ // Current Let’s Encrypt root hierachy
+ // https://letsencrypt.org/certificates/
+ "ISRG Root X1"
+ ]
+ },
+ // For pinning tests on pinning.example.com, the certificate must be 'End
+ // Entity Test Cert'
+ {
+ "name": "mozilla_test",
+ "sha256_hashes": [
+ "End Entity Test Cert"
+ ]
+ },
+ // Google's root PEMs. Chrome pins only to their intermediate certs, but
+ // they'd like us to be more liberal. For the initial list, we are using
+ // the certs from https://pki.google.com/roots.pem.
+ // We have no built-in for commented out CAs.
+ // This list should be updated via the dumpGoogleRoots.js script.
+ {
+ "name": "google_root_pems",
+ "sha256_hashes": [
+ "AffirmTrust Commercial",
+ "AffirmTrust Networking",
+ "AffirmTrust Premium",
+ "AffirmTrust Premium ECC",
+ "Baltimore CyberTrust Root",
+ "Comodo AAA Services root",
+ "COMODO Certification Authority",
+ "COMODO ECC Certification Authority",
+ "COMODO RSA Certification Authority",
+ "DigiCert Assured ID Root CA",
+ "DigiCert Assured ID Root G2",
+ "DigiCert Assured ID Root G3",
+ "DigiCert Global Root CA",
+ "DigiCert Global Root G2",
+ "DigiCert Global Root G3",
+ "DigiCert High Assurance EV Root CA",
+ "DigiCert Trusted Root G4",
+ "Entrust Root Certification Authority",
+ "Entrust Root Certification Authority - EC1",
+ "Entrust Root Certification Authority - G2",
+ "Entrust.net Premium 2048 Secure Server CA",
+ // "GlobalSign",
+ "GlobalSign ECC Root CA - R5",
+ "GlobalSign Root CA",
+ "GlobalSign Root CA - R3",
+ "GlobalSign Root CA - R6",
+ "Go Daddy Class 2 CA",
+ "Go Daddy Root Certificate Authority - G2",
+ // "GTS Root R1",
+ // "GTS Root R2",
+ // "GTS Root R3",
+ // "GTS Root R4",
+ "Starfield Class 2 CA",
+ "Starfield Root Certificate Authority - G2",
+ "USERTrust ECC Certification Authority",
+ "USERTrust RSA Certification Authority"
+ ]
+ }
+ // The list above should be updated via the dumpGoogleRoots.js script.
+ ],
+
+ "entries": [
+ // Only domains that are operationally crucial to Firefox can have per-host
+ // telemetry reporting (the "id") field
+ { "name": "addons.mozilla.org", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 1 },
+ { "name": "addons.mozilla.net", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 2 },
+ // AUS servers MUST remain in test mode
+ // see: https://bugzilla.mozilla.org/show_bug.cgi?id=1301956#c23
+ { "name": "aus4.mozilla.org", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": true, "id": 3 },
+ { "name": "aus5.mozilla.org", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": true, "id": 7 },
+ // Catchall for applications hosted under firefox.com
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1494431
+ { "name": "firefox.com", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": true, "id": 15 },
+ // Firefox Accounts & sync
+ // superseded by catchall for firefox.com, but leaving for tracking
+ { "name": "accounts.firefox.com", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 4 },
+ { "name": "api.accounts.firefox.com", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 5 },
+ { "name": "sync.services.mozilla.com", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 13 },
+ // Catch-all for all CDN resources, including product delivery
+ // Telemetry IDs added in bug 1521983.
+ { "name": "cdn.mozilla.net", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 16 },
+ { "name": "cdn.mozilla.org", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 17 },
+ { "name": "download.mozilla.org", "include_subdomains": false,
+ "pins": "mozilla_services", "test_mode": false, "id": 14 },
+ // Catch-all for everything hosted under services.mozilla.com
+ { "name": "services.mozilla.com", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": false, "id": 6 },
+ // Catch-all for everything hosted under telemetry.mozilla.org
+ // MUST remain in test mode in order to receive telemetry on broken pins
+ { "name": "telemetry.mozilla.org", "include_subdomains": true,
+ "pins": "mozilla_services", "test_mode": true, "id": 8 },
+ // Test Pilot
+ // superseded by catchall for firefox.com, but leaving for tracking
+ { "name": "testpilot.firefox.com", "include_subdomains": false,
+ "pins": "mozilla_services", "test_mode": false, "id": 9 },
+ // Crash report sites
+ { "name": "crash-reports.mozilla.com", "include_subdomains": false,
+ "pins": "mozilla_services", "test_mode": false, "id": 10 },
+ { "name": "crash-reports-xpsp2.mozilla.com", "include_subdomains": false,
+ "pins": "mozilla_services", "test_mode": false, "id": 11 },
+ { "name": "crash-stats.mozilla.org", "include_subdomains": false,
+ "pins": "mozilla_services", "test_mode": false, "id": 12 },
+ { "name": "include-subdomains.pinning.example.com",
+ "include_subdomains": true, "pins": "mozilla_test",
+ "test_mode": false },
+ // Example domain to collect per-host stats for telemetry tests.
+ { "name": "exclude-subdomains.pinning.example.com",
+ "include_subdomains": false, "pins": "mozilla_test",
+ "test_mode": false },
+ { "name": "test-mode.pinning.example.com", "include_subdomains": true,
+ "pins": "mozilla_test", "test_mode": true }
+ ],
+ // When pinning to non-root certs, like intermediates,
+ // place the PEM of the pinned certificate in this array
+ // so Firefox can find the subject DN and public key
+ "extra_certificates": [
+ // Subject: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
+ // Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1
+ "MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrXNSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHlNpi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7DcGu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgzuEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMBAAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEFBQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsGAQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYDVR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIBABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGxA/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRMUM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOuOsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vwp7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKRPB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5brUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt",
+ // Subject: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X4
+ // Issuer: C=US, O=Internet Security Research Group, CN=ISRG Root X1
+ "MIIFjTCCA3WgAwIBAgIRAJObmZ6kjhYNW0JZtD0gE9owDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0NDM0WhcNMjExMDA2MTU0NDM0WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3MgRW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhJHRCe7eRMdlz/ziq2M5EXLc5CtxErg29RbmXN2evvVBPX9MQVGv3QdqOY+ZtW8DoQKmMQfzRA4n/YmEJYNYHBXiakL0aZD5P3M93L4lry2evQU3FjQDAa/6NhNy18pUxqOj2kKBDSpN0XLM+Q2lLiSJHdFE+mWTDzSQB+YQvKHcXIqfdw2wITGYvN3TFb5OOsEY3FmHRUJjIsA9PWFN8rPbaLZZhUK1D3AqmT561Urmcju9O30azMdwg/GnCoyB1Puw4GzZOZmbS3/VmpJMve6YOlD5gPUpLHG+6tE0cPJFYbi9NxNpw2+0BOXbASefpNbUUBpDB5ZLiEP1rubSFAgMBAAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBTFsatOTLHNZDCTfsGEmQWr5gPiJTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEFBQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsGAQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYDVR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIBAF4tI1yGjZgld9lP01+zftU3aSV0un0d2GKUMO7GxvwTLWAKQz/eT+u3J4+GvpD+BMfopIxkJcDCzMChjjZtZZwJpIY7BatVrO6OkEmaRNITtbZ/hCwNkUnbk3C7EG3OGJZlo9b2wzA8v9WBsPzHpTvLfOr+dS57LLPZBhp3ArHaLbdk33lIONRPt9sseDEkmdHnVmGmBRf4+J0Wy67mddOvz5rHH8uzY94raOayf20gzzcmqmot4hPXtDG4Y49MoFMMT2kcWck3EOTAH6QiGWkGJ7cxMfSL3S0niA6wgFJtfETETOZu8AVDgENgCJ3DS0bz/dhVKvs3WRkaKuuR/W0nnC2VDdaFj4+CRF8LGtn/8ERaH48TktH5BDyDVcF9zfJ75Scxcy23jAL2N6w3n/t3nnqoXt9Im4FprDr+mP1g2Z6Lf2YA0jE3kZalgZ6lNHu4CmvJYoOTSJw9X2qlGl1K+B4U327rG1tRxgjM76pN6lIS02PMECoyKJigpOSBu4V8+LVaUMezCJH9Qf4EKeZTHddQ1t96zvNd2s9ewSKx/DblXbKsBDzIdHJ+qi6+F9DIVM5/ICdtDdulOO+dr/BXB+pBZ3uVxjRANvJKKpdxkePyluITSNZHbanWRN07gMvwBWOL060i4VrL9er1sBQrRjU9iNpZQGTnLVAxQVFu"
+ ]
+}
diff --git a/security/manager/tools/crtshToIdentifyingStruct/crtshToIdentifyingStruct.py b/security/manager/tools/crtshToIdentifyingStruct/crtshToIdentifyingStruct.py
new file mode 100644
index 0000000000..05e0842e2a
--- /dev/null
+++ b/security/manager/tools/crtshToIdentifyingStruct/crtshToIdentifyingStruct.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+This utility takes a series of https://crt.sh/ identifiers and writes to
+stdout all of those certs' distinguished name or SPKI fields in hex, with an
+array of all those. You'll need to post-process this list to handle any
+duplicates.
+
+Requires Python 3.
+"""
+import argparse
+import io
+import re
+import sys
+
+import requests
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.x509.oid import NameOID
+from pyasn1.codec.der import decoder, encoder
+from pyasn1_modules import pem, rfc5280
+
+assert sys.version_info >= (3, 2), "Requires Python 3.2 or later"
+
+
+def hex_string_for_struct(bytes):
+ return ["0x{:02X}".format(x) for x in bytes]
+
+
+def hex_string_human_readable(bytes):
+ return ["{:02X}".format(x) for x in bytes]
+
+
+def nameOIDtoString(oid):
+ if oid == NameOID.COUNTRY_NAME:
+ return "C"
+ if oid == NameOID.COMMON_NAME:
+ return "CN"
+ if oid == NameOID.LOCALITY_NAME:
+ return "L"
+ if oid == NameOID.ORGANIZATION_NAME:
+ return "O"
+ if oid == NameOID.ORGANIZATIONAL_UNIT_NAME:
+ return "OU"
+ raise Exception("Unknown OID: {}".format(oid))
+
+
+def print_block(pemData, identifierType="DN", crtshId=None):
+ substrate = pem.readPemFromFile(io.StringIO(pemData.decode("utf-8")))
+ cert, _ = decoder.decode(substrate, asn1Spec=rfc5280.Certificate())
+ octets = None
+
+ if identifierType == "DN":
+ der_subject = encoder.encode(cert["tbsCertificate"]["subject"])
+ octets = hex_string_for_struct(der_subject)
+ elif identifierType == "SPKI":
+ der_spki = encoder.encode(cert["tbsCertificate"]["subjectPublicKeyInfo"])
+ octets = hex_string_for_struct(der_spki)
+ else:
+ raise Exception("Unknown identifier type: " + identifierType)
+
+ cert = x509.load_pem_x509_certificate(pemData, default_backend())
+ common_name = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0]
+ block_name = "CA{}{}".format(
+ re.sub(r"[-:=_. ]", "", common_name.value), identifierType
+ )
+
+ fingerprint = hex_string_human_readable(cert.fingerprint(hashes.SHA256()))
+
+ dn_parts = [
+ "/{id}={value}".format(id=nameOIDtoString(part.oid), value=part.value)
+ for part in cert.subject
+ ]
+ distinguished_name = "".join(dn_parts)
+
+ print("// {dn}".format(dn=distinguished_name))
+ print("// SHA256 Fingerprint: " + ":".join(fingerprint[:16]))
+ print("// " + ":".join(fingerprint[16:]))
+ if crtshId:
+ print("// https://crt.sh/?id={crtsh} (crt.sh ID={crtsh})".format(crtsh=crtshId))
+ print("static const uint8_t {}[{}] = ".format(block_name, len(octets)) + "{")
+
+ while len(octets) > 0:
+ print(" " + ", ".join(octets[:13]) + ",")
+ octets = octets[13:]
+
+ print("};")
+ print()
+
+ return block_name
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-spki",
+ action="store_true",
+ help="Create a list of subject public key info fields",
+ )
+ parser.add_argument(
+ "-dn",
+ action="store_true",
+ help="Create a list of subject distinguished name fields",
+ )
+ parser.add_argument("-listname", help="Name of the final DataAndLength block")
+ parser.add_argument(
+ "certId", nargs="+", help="A list of PEM files on disk or crt.sh IDs"
+ )
+ args = parser.parse_args()
+
+ if not args.dn and not args.spki:
+ parser.print_help()
+ raise Exception("You must select either DN or SPKI matching")
+
+ blocks = []
+
+ print(
+ "// Script from security/manager/tools/crtshToIdentifyingStruct/"
+ + "crtshToIdentifyingStruct.py"
+ )
+ print("// Invocation: {}".format(" ".join(sys.argv)))
+ print()
+
+ identifierType = None
+ if args.dn:
+ identifierType = "DN"
+ else:
+ identifierType = "SPKI"
+
+ for certId in args.certId:
+ # Try a local file first, then crt.sh
+ try:
+ with open(certId, "rb") as pemFile:
+ blocks.append(
+ print_block(pemFile.read(), identifierType=identifierType)
+ )
+ except OSError:
+ r = requests.get("https://crt.sh/?d={}".format(certId))
+ r.raise_for_status()
+ blocks.append(
+ print_block(r.content, crtshId=certId, identifierType=identifierType)
+ )
+
+ print("static const DataAndLength " + args.listname + "[]= {")
+ for structName in blocks:
+ if len(structName) < 33:
+ print(" { " + "{name}, sizeof({name}) ".format(name=structName) + "},")
+ else:
+ print(" { " + "{},".format(structName))
+ print(" sizeof({})".format(structName) + " },")
+ print("};")
diff --git a/security/manager/tools/crtshToIdentifyingStruct/requirements.txt b/security/manager/tools/crtshToIdentifyingStruct/requirements.txt
new file mode 100644
index 0000000000..c5c04ec1cc
--- /dev/null
+++ b/security/manager/tools/crtshToIdentifyingStruct/requirements.txt
@@ -0,0 +1,4 @@
+cryptography >= 1.8
+requests >= 2.0
+pyasn1 >= 0.3
+pyasn1_modules >= 0.1 \ No newline at end of file
diff --git a/security/manager/tools/dumpGoogleRoots.js b/security/manager/tools/dumpGoogleRoots.js
new file mode 100644
index 0000000000..ab215e88e4
--- /dev/null
+++ b/security/manager/tools/dumpGoogleRoots.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// This file is a helper script that generates the list of certificates that
+// make up the preloaded pinset for Google properties.
+//
+// How to run this file:
+// 1. [obtain firefox source code]
+// 2. [build/obtain firefox binaries]
+// 3. run `[path to]/run-mozilla.sh [path to]/xpcshell dumpGoogleRoots.js'
+// 4. [paste the output into the appropriate section in
+// security/manager/tools/PreloadedHPKPins.json]
+
+Services.prefs.setBoolPref("network.process.enabled", false);
+
+function downloadRoots() {
+ let req = new XMLHttpRequest();
+ req.open("GET", "https://pki.google.com/roots.pem", false);
+ try {
+ req.send();
+ } catch (e) {
+ throw new Error("ERROR: problem downloading Google Root PEMs: " + e);
+ }
+
+ if (req.status != 200) {
+ throw new Error(
+ "ERROR: problem downloading Google Root PEMs. Status: " + req.status
+ );
+ }
+
+ let pem = req.responseText;
+ let roots = [];
+ let currentPEM = "";
+ let readingRoot = false;
+ let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ for (let line of pem.split(/[\r\n]/)) {
+ if (line == "-----END CERTIFICATE-----") {
+ if (currentPEM) {
+ roots.push(certDB.constructX509FromBase64(currentPEM));
+ }
+ currentPEM = "";
+ readingRoot = false;
+ continue;
+ }
+ if (readingRoot) {
+ currentPEM += line;
+ }
+ if (line == "-----BEGIN CERTIFICATE-----") {
+ readingRoot = true;
+ }
+ }
+ return roots;
+}
+
+function makeFormattedNickname(cert) {
+ if (cert.isBuiltInRoot) {
+ return `"${cert.displayName}"`;
+ }
+ // Otherwise, this isn't a built-in and we have to comment it out.
+ return `// "${cert.displayName}"`;
+}
+
+var roots = downloadRoots();
+var rootNicknames = [];
+for (var root of roots) {
+ rootNicknames.push(makeFormattedNickname(root));
+}
+rootNicknames.sort(function (rootA, rootB) {
+ let rootALowercase = rootA.toLowerCase().replace(/(^[^"]*")|"/g, "");
+ let rootBLowercase = rootB.toLowerCase().replace(/(^[^"]*")|"/g, "");
+ if (rootALowercase < rootBLowercase) {
+ return -1;
+ }
+ if (rootALowercase > rootBLowercase) {
+ return 1;
+ }
+ return 0;
+});
+dump(" {\n");
+dump(' "name": "google_root_pems",\n');
+dump(' "sha256_hashes": [\n');
+var first = true;
+for (var nickname of rootNicknames) {
+ if (!first) {
+ dump(",\n");
+ }
+ first = false;
+ dump(" " + nickname);
+}
+dump("\n");
+dump(" ]\n");
+dump(" }\n");
diff --git a/security/manager/tools/genRootCAHashes.js b/security/manager/tools/genRootCAHashes.js
new file mode 100644
index 0000000000..fbe6b847de
--- /dev/null
+++ b/security/manager/tools/genRootCAHashes.js
@@ -0,0 +1,273 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// How to run this file:
+// 1. [obtain firefox source code]
+// 2. [build/obtain firefox binaries]
+// 3. run `[path to]/run-mozilla.sh [path to]/xpcshell genRootCAHashes.js \
+// [absolute path to]/RootHashes.inc'
+
+const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+const CertDb = Cc[nsX509CertDB].getService(Ci.nsIX509CertDB);
+
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { CommonUtils } = ChromeUtils.importESModule(
+ "resource://services-common/utils.sys.mjs"
+);
+
+const FILENAME_OUTPUT = "RootHashes.inc";
+const FILENAME_TRUST_ANCHORS = "KnownRootHashes.json";
+const ROOT_NOT_ASSIGNED = -1;
+
+const JSON_HEADER = `// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+//
+//***************************************************************************
+// This is an automatically generated file. It's used to maintain state for
+// runs of genRootCAHashes.js; you should never need to manually edit it
+//***************************************************************************
+
+// Notes:
+// binNumber 1 used to be for "GTE_CyberTrust_Global_Root", but that root was
+// removed from the built-in roots module, so now it is used to indicate that
+// the certificate is not a built-in and was found in the softoken (cert9.db).
+
+// binNumber 2 used to be for "Thawte_Server_CA", but that root was removed from
+// the built-in roots module, so now it is used to indicate that the certificate
+// is not a built-in and was found on an external PKCS#11 token.
+
+// binNumber 3 used to be for "Thawte_Premium_Server_CA", but that root was
+// removed from the built-in roots module, so now it is used to indicate that
+// the certificate is not a built-in and was temporarily imported from the OS as
+// part of the "Enterprise Roots" feature.
+
+`;
+
+const FILE_HEADER =
+ "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
+ " * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
+ " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
+ "\n" +
+ "/*****************************************************************************/\n" +
+ "/* This is an automatically generated file. If you're not */\n" +
+ "/* RootCertificateTelemetryUtils.cpp, you shouldn't be #including it. */\n" +
+ "/*****************************************************************************/\n" +
+ "\n" +
+ "#define HASH_LEN 32\n";
+
+const FP_PREAMBLE =
+ "struct CertAuthorityHash {\n" +
+ " // See bug 1338873 about making these fields const.\n" +
+ " uint8_t hash[HASH_LEN];\n" +
+ " int32_t binNumber;\n" +
+ "};\n\n" +
+ "static const struct CertAuthorityHash ROOT_TABLE[] = {\n";
+
+const FP_POSTAMBLE = "};\n";
+
+// Helper
+function writeString(fos, string) {
+ fos.write(string, string.length);
+}
+
+// Remove all colons from a string
+function stripColons(hexString) {
+ return hexString.replace(/:/g, "");
+}
+
+// Expect an array of bytes and make it C-formatted
+function hexSlice(bytes, start, end) {
+ let ret = "";
+ for (let i = start; i < end; i++) {
+ let hex = (0 + bytes.charCodeAt(i).toString(16)).slice(-2).toUpperCase();
+ ret += "0x" + hex;
+ if (i < end - 1) {
+ ret += ", ";
+ }
+ }
+ return ret;
+}
+
+function stripComments(buf) {
+ let lines = buf.split("\n");
+ let entryRegex = /^\s*\/\//;
+ let data = "";
+ for (let i = 0; i < lines.length; i++) {
+ let match = entryRegex.exec(lines[i]);
+ if (!match) {
+ data = data + lines[i];
+ }
+ }
+ return data;
+}
+
+// Load the trust anchors JSON object from disk
+function loadTrustAnchors(file) {
+ if (file.exists()) {
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ stream.init(file, -1, 0, 0);
+ let buf = NetUtil.readInputStreamToString(stream, stream.available());
+ return JSON.parse(stripComments(buf));
+ }
+ // If there's no input file, bootstrap.
+ return { roots: [], maxBin: 0 };
+}
+
+// Saves our persistence file so that we don't lose track of the mapping
+// between bin numbers and the CA-hashes, even as CAs come and go.
+function writeTrustAnchors(file) {
+ let fos = FileUtils.openSafeFileOutputStream(file);
+
+ let serializedData = JSON.stringify(gTrustAnchors, null, " ");
+ fos.write(JSON_HEADER, JSON_HEADER.length);
+ fos.write(serializedData, serializedData.length);
+
+ FileUtils.closeSafeFileOutputStream(fos);
+}
+
+// Write the C++ header file
+function writeRootHashes(fos) {
+ try {
+ writeString(fos, FILE_HEADER);
+
+ // Output the sorted gTrustAnchors
+ writeString(fos, FP_PREAMBLE);
+ gTrustAnchors.roots.forEach(function (fp) {
+ let fpBytes = atob(fp.sha256Fingerprint);
+
+ writeString(fos, " {\n");
+ writeString(fos, " /* " + fp.label + " */\n");
+ writeString(fos, " { " + hexSlice(fpBytes, 0, 16) + ",\n");
+ writeString(fos, " " + hexSlice(fpBytes, 16, 32) + " },\n");
+ writeString(fos, " " + fp.binNumber + " /* Bin Number */\n");
+
+ writeString(fos, " },\n");
+ });
+ writeString(fos, FP_POSTAMBLE);
+
+ writeString(fos, "\n");
+ } catch (e) {
+ dump("ERROR: problem writing output: " + e + "\n");
+ }
+}
+
+// Scan our list (linearly) for the given fingerprint string
+function findTrustAnchorByFingerprint(sha256Fingerprint) {
+ for (let i = 0; i < gTrustAnchors.roots.length; i++) {
+ if (sha256Fingerprint == gTrustAnchors.roots[i].sha256Fingerprint) {
+ return i;
+ }
+ }
+ return ROOT_NOT_ASSIGNED;
+}
+
+// Get a clean label for a given certificate; usually the common name.
+function getLabelForCert(cert) {
+ let label = cert.commonName;
+
+ if (label.length < 5) {
+ label = cert.subjectName;
+ }
+
+ // replace non-ascii characters
+ label = label.replace(/[^[:ascii:]]/g, "_");
+ // replace non-word characters
+ label = label.replace(/[^A-Za-z0-9]/g, "_");
+ return label;
+}
+
+// Fill in the gTrustAnchors list with trust anchors from the database.
+function insertTrustAnchorsFromDatabase() {
+ // We only want CA certs for SSL
+ const CERT_TYPE = Ci.nsIX509Cert.CA_CERT;
+ const TRUST_TYPE = Ci.nsIX509CertDB.TRUSTED_SSL;
+
+ // Iterate through the whole Cert DB
+ for (let cert of CertDb.getCerts()) {
+ // Find the certificate in our existing list. Do it here because we need to check if
+ // it's untrusted too.
+
+ // If this is a trusted cert
+ if (CertDb.isCertTrusted(cert, CERT_TYPE, TRUST_TYPE)) {
+ // Base64 encode the hex string
+ let binaryFingerprint = CommonUtils.hexToBytes(
+ stripColons(cert.sha256Fingerprint)
+ );
+ let encodedFingerprint = btoa(binaryFingerprint);
+
+ // Scan to see if this is already in the database.
+ if (
+ findTrustAnchorByFingerprint(encodedFingerprint) == ROOT_NOT_ASSIGNED
+ ) {
+ // Let's get a usable name; some old certs do not have CN= filled out
+ let label = getLabelForCert(cert);
+
+ // Add to list
+ gTrustAnchors.maxBin += 1;
+ gTrustAnchors.roots.push({
+ label,
+ binNumber: gTrustAnchors.maxBin,
+ sha256Fingerprint: encodedFingerprint,
+ });
+ }
+ }
+ }
+}
+
+//
+// PRIMARY LOGIC
+//
+
+if (arguments.length != 1) {
+ throw new Error(
+ "Usage: genRootCAHashes.js <absolute path to current RootHashes.inc>"
+ );
+}
+
+var trustAnchorsFile = new FileUtils.File(
+ PathUtils.join(
+ Services.dirsvc.get("CurWorkD", Ci.nsIFile).path,
+ FILENAME_TRUST_ANCHORS
+ )
+);
+var rootHashesFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+rootHashesFile.initWithPath(arguments[0]);
+
+// Open the known hashes file; this is to ensure stable bin numbers.
+var gTrustAnchors = loadTrustAnchors(trustAnchorsFile);
+
+// Collect all certificate entries
+insertTrustAnchorsFromDatabase();
+
+// Update known hashes before we sort
+writeTrustAnchors(trustAnchorsFile);
+
+// Sort all trust anchors before writing, as AccumulateRootCA.cpp
+// will perform binary searches
+gTrustAnchors.roots.sort(function (a, b) {
+ // We need to work from the binary values, not the base64 values.
+ let aBin = atob(a.sha256Fingerprint);
+ let bBin = atob(b.sha256Fingerprint);
+
+ if (aBin < bBin) {
+ return -1;
+ }
+ if (aBin > bBin) {
+ return 1;
+ }
+ return 0;
+});
+
+// Write the output file.
+var rootHashesFileOutputStream =
+ FileUtils.openSafeFileOutputStream(rootHashesFile);
+writeRootHashes(rootHashesFileOutputStream);
+FileUtils.closeSafeFileOutputStream(rootHashesFileOutputStream);
diff --git a/security/manager/tools/getCTKnownLogs.py b/security/manager/tools/getCTKnownLogs.py
new file mode 100755
index 0000000000..677791bffd
--- /dev/null
+++ b/security/manager/tools/getCTKnownLogs.py
@@ -0,0 +1,330 @@
+#!/usr/bin/env python
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Parses a JSON file listing the known Certificate Transparency logs
+(log_list.json) and generates a C++ header file to be included in Firefox.
+
+The current log_list.json file available under security/manager/tools
+was originally downloaded from
+https://www.certificate-transparency.org/known-logs
+and edited to include the disqualification time for the disqualified logs using
+https://cs.chromium.org/chromium/src/net/cert/ct_known_logs_static-inc.h
+"""
+
+import argparse
+import base64
+import datetime
+import json
+import os.path
+import sys
+import textwrap
+from string import Template
+
+import six
+import urllib3
+
+
+def decodebytes(s):
+ if six.PY3:
+ return base64.decodebytes(six.ensure_binary(s))
+ return base64.decodestring(s)
+
+
+OUTPUT_TEMPLATE = """\
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This file was automatically generated by $prog. */
+
+#ifndef $include_guard
+#define $include_guard
+
+#include "CTLog.h"
+
+#include <stddef.h>
+
+struct CTLogInfo
+{
+ // See bug 1338873 about making these fields const.
+ const char* name;
+ // Index within kCTLogOperatorList.
+ mozilla::ct::CTLogStatus status;
+ // 0 for qualified logs, disqualification time for disqualified logs
+ // (in milliseconds, measured since the epoch, ignoring leap seconds).
+ uint64_t disqualificationTime;
+ size_t operatorIndex;
+ const char* key;
+ size_t keyLength;
+};
+
+struct CTLogOperatorInfo
+{
+ // See bug 1338873 about making these fields const.
+ const char* name;
+ mozilla::ct::CTLogOperatorId id;
+};
+
+const CTLogInfo kCTLogList[] = {
+$logs
+};
+
+const CTLogOperatorInfo kCTLogOperatorList[] = {
+$operators
+};
+
+#endif // $include_guard
+"""
+
+
+def get_disqualification_time(time_str):
+ """
+ Convert a time string such as "2017-01-01T00:00:00Z" to an integer
+ representing milliseconds since the epoch.
+ Timezones in the string are not supported and will result in an exception.
+ """
+ t = datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%SZ")
+ epoch = datetime.datetime.utcfromtimestamp(0)
+ seconds_since_epoch = (t - epoch).total_seconds()
+ return int(seconds_since_epoch * 1000)
+
+
+def get_hex_lines(blob, width):
+ """Convert a binary string to a multiline text of C escape sequences."""
+ text = "".join(["\\x{:02x}".format(c) for c in blob])
+ # When escaped, a single byte takes 4 chars (e.g. "\x00").
+ # Make sure we don't break an escaped byte between the lines.
+ return textwrap.wrap(text, width - width % 4)
+
+
+def get_operator_index(json_data, target_name):
+ """Return operator's entry from the JSON along with its array index."""
+ matches = [
+ (operator, index)
+ for (index, operator) in enumerate(json_data["operators"])
+ if operator["name"] == target_name
+ ]
+ assert len(matches) != 0, "No operators with id {0} defined.".format(target_name)
+ assert len(matches) == 1, "Found multiple operators with id {0}.".format(
+ target_name
+ )
+ return matches[0][1]
+
+
+def get_log_info_structs(json_data):
+ """Return array of CTLogInfo initializers for the known logs."""
+ tmpl = Template(
+ textwrap.dedent(
+ """\
+ { $description,
+ $status,
+ $disqualification_time, // $disqualification_time_comment
+ $operator_index, // $operator_comment
+ $indented_log_key,
+ $log_key_len }"""
+ )
+ )
+ initializers = []
+ for operator in json_data["operators"]:
+ operator_name = operator["name"]
+ for log in operator["logs"]:
+ log_key = decodebytes(log["key"])
+ operator_index = get_operator_index(json_data, operator_name)
+ if "disqualification_time" in log:
+ status = "mozilla::ct::CTLogStatus::Disqualified"
+ disqualification_time = get_disqualification_time(
+ log["disqualification_time"]
+ )
+ disqualification_time_comment = 'Date.parse("{0}")'.format(
+ log["disqualification_time"]
+ )
+ else:
+ status = "mozilla::ct::CTLogStatus::Included"
+ disqualification_time = 0
+ disqualification_time_comment = "no disqualification time"
+ is_test_log = "test_only" in operator and operator["test_only"]
+ prefix = ""
+ suffix = ","
+ if is_test_log:
+ prefix = "#ifdef DEBUG\n"
+ suffix = ",\n#endif // DEBUG"
+ toappend = tmpl.substitute(
+ # Use json.dumps for C-escaping strings.
+ # Not perfect but close enough.
+ description=json.dumps(log["description"]),
+ operator_index=operator_index,
+ operator_comment="operated by {0}".
+ # The comment must not contain "/".
+ format(operator_name).replace("/", "|"),
+ status=status,
+ disqualification_time=disqualification_time,
+ disqualification_time_comment=disqualification_time_comment,
+ # Maximum line width is 80.
+ indented_log_key="\n".join(
+ [' "{0}"'.format(l) for l in get_hex_lines(log_key, 74)]
+ ),
+ log_key_len=len(log_key),
+ )
+ initializers.append(prefix + toappend + suffix)
+ return initializers
+
+
+def get_log_operator_structs(json_data):
+ """Return array of CTLogOperatorInfo initializers."""
+ tmpl = Template(" { $name, $id }")
+ initializers = []
+ currentId = 0
+ for operator in json_data["operators"]:
+ prefix = ""
+ suffix = ","
+ is_test_log = "test_only" in operator and operator["test_only"]
+ if is_test_log:
+ prefix = "#ifdef DEBUG\n"
+ suffix = ",\n#endif // DEBUG"
+ toappend = tmpl.substitute(name=json.dumps(operator["name"]), id=currentId)
+ currentId += 1
+ initializers.append(prefix + toappend + suffix)
+ return initializers
+
+
+def generate_cpp_header_file(json_data, out_file):
+ """Generate the C++ header file for the known logs."""
+ filename = os.path.basename(out_file.name)
+ include_guard = filename.replace(".", "_").replace("/", "_")
+ log_info_initializers = get_log_info_structs(json_data)
+ operator_info_initializers = get_log_operator_structs(json_data)
+ out_file.write(
+ Template(OUTPUT_TEMPLATE).substitute(
+ prog=os.path.basename(sys.argv[0]),
+ include_guard=include_guard,
+ logs="\n".join(log_info_initializers),
+ operators="\n".join(operator_info_initializers),
+ )
+ )
+
+
+def patch_in_test_logs(json_data):
+ """Insert Mozilla-specific test log data."""
+ max_id = len(json_data["operators"])
+ mozilla_test_operator_1 = {
+ "name": "Mozilla Test Org 1",
+ "id": max_id + 1,
+ "test_only": True,
+ "logs": [
+ {
+ "description": "Mozilla Test RSA Log 1",
+ # `openssl x509 -noout -pubkey -in <path/to/default-ee.pem>`
+ "key": """
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2
+ ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdF
+ h/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6n
+ cOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAv
+ OnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2nj
+ tIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXt
+ jQIDAQAB
+ """,
+ "operated_by": [max_id + 1],
+ },
+ {
+ "description": "Mozilla Test EC Log",
+ # `openssl x509 -noout -pubkey -in <path/to/root_secp256r1_256.pem`
+ "key": """
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET7+7u2Hg+PmxpgpZrIcE4uwFC0I+
+ PPcukj8sT3lLRVwqadIzRWw2xBGdBwbgDu3I0ZOQ15kbey0HowTqoEqmwA==
+ """,
+ "operated_by": [max_id + 1],
+ },
+ ],
+ }
+ mozilla_test_operator_2 = {
+ "name": "Mozilla Test Org 2",
+ "id": max_id + 2,
+ "test_only": True,
+ "logs": [
+ {
+ "description": "Mozilla Test RSA Log 2",
+ # `openssl x509 -noout -pubkey -in <path/to/other-test-ca.pem>`
+ "key": """
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXXGUmYJn3cIKmeR8bh2
+ w39c5TiwbErNIrHL1G+mWtoq3UHIwkmKxKOzwfYUh/QbaYlBvYClHDwSAkTFhKTE
+ SDMF5ROMAQbPCL6ahidguuai6PNvI8XZgxO53683g0XazlHU1tzSpss8xwbrzTBw
+ 7JjM5AqlkdcpWn9xxb5maR0rLf7ISURZC8Wj6kn9k7HXU0BfF3N2mZWGZiVHl+1C
+ aQiICBFCIGmYikP+5Izmh4HdIramnNKDdRMfkysSjOKG+n0lHAYq0n7wFvGHzdVO
+ gys1uJMPdLqQqovHYWckKrH9bWIUDRjEwLjGj8N0hFcyStfehuZVLx0eGR1xIWjT
+ uwIDAQAB
+ """,
+ "operated_by": [max_id + 2],
+ }
+ ],
+ }
+ json_data["operators"].append(mozilla_test_operator_1)
+ json_data["operators"].append(mozilla_test_operator_2)
+
+
+def run(args):
+ """
+ Load the input JSON file and generate the C++ header according to the
+ command line arguments.
+ """
+ if args.file:
+ print("Reading file: ", args.file)
+ with open(args.file, "rb") as json_file:
+ json_text = json_file.read()
+ elif args.url:
+ print("Fetching URL: ", args.url)
+ json_request = urllib3.urlopen(args.url)
+ try:
+ json_text = json_request.read()
+ finally:
+ json_request.close()
+
+ json_data = json.loads(json_text)
+
+ print("Writing output: ", args.out)
+
+ patch_in_test_logs(json_data)
+
+ with open(args.out, "w") as out_file:
+ generate_cpp_header_file(json_data, out_file)
+
+ print("Done.")
+
+
+def parse_arguments_and_run():
+ """Parse the command line arguments and run the program."""
+ arg_parser = argparse.ArgumentParser(
+ description="Parses a JSON file listing the known "
+ "Certificate Transparency logs and generates "
+ "a C++ header file to be included in Firefox.",
+ epilog="Example: python %s --url" % os.path.basename(sys.argv[0]),
+ )
+
+ source_group = arg_parser.add_mutually_exclusive_group(required=True)
+ source_group.add_argument(
+ "--file",
+ nargs="?",
+ const="log_list.json",
+ help="Read the known CT logs JSON data from the "
+ "specified local file (%(const)s by default).",
+ )
+ source_group.add_argument(
+ "--url", help="Download the known CT logs JSON file " "from the specified URL."
+ )
+
+ arg_parser.add_argument(
+ "--out",
+ default="../../certverifier/CTKnownLogs.h",
+ help="Path and filename of the header file "
+ "to be generated. Defaults to %(default)s",
+ )
+
+ run(arg_parser.parse_args())
+
+
+if __name__ == "__main__":
+ parse_arguments_and_run()
diff --git a/security/manager/tools/log_list.json b/security/manager/tools/log_list.json
new file mode 100644
index 0000000000..39c0952634
--- /dev/null
+++ b/security/manager/tools/log_list.json
@@ -0,0 +1,403 @@
+{
+ "version": "20.36",
+ "log_list_timestamp": "2023-04-11T12:55:27Z",
+ "operators": [
+ {
+ "name": "Google",
+ "email": [
+ "google-ct-logs@googlegroups.com"
+ ],
+ "logs": [
+ {
+ "description": "Google 'Argon2023' log",
+ "log_id": "6D7Q2j71BjUy51covIlryQPTy9ERa+zraeF3fW0GvW4=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0JCPZFJOQqyEti5M8j13ALN3CAVHqkVM4yyOcKWCu2yye5yYeqDpEXYoALIgtM3TmHtNlifmt+4iatGwLpF3eA==",
+ "url": "https://ct.googleapis.com/logs/argon2023/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2019-12-17T18:38:01Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "Google 'Argon2024' log",
+ "log_id": "7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnWTAWUYr3MgDHQW0LagJ95lB7QT75Ve6JgT2EVLOFGU7L3YrwA==",
+ "url": "https://ct.googleapis.com/logs/us1/argon2024/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-01T18:54:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-01-01T00:00:00Z",
+ "end_exclusive": "2025-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "Google 'Xenon2023' log",
+ "log_id": "rfe++nz/EMiLnT2cHj4YarRnKV3PsQwkyoWGNOvcgoo=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEchY+C+/vzj5g3ZXLY3q5qY1Kb2zcYYCmRV4vg6yU84WI0KV00HuO/8XuQqLwLZPjwtCymeLhQunSxgAnaXSuzg==",
+ "url": "https://ct.googleapis.com/logs/xenon2023/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2019-12-17T18:38:01Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "Google 'Xenon2024' log",
+ "log_id": "dv+IPwq2+5VRwmHM9Ye6NLSkzbsp3GhCCp/mZ0xaOnQ=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuWDgNB415GUAk0+QCb1a7ETdjA/O7RE+KllGmjG2x5n33O89zY+GwjWlPtwpurvyVOKoDIMIUQbeIW02UI44TQ==",
+ "url": "https://ct.googleapis.com/logs/eu1/xenon2024/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-01T18:54:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-01-01T00:00:00Z",
+ "end_exclusive": "2025-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "Google 'Icarus' log",
+ "log_id": "KTxRllTIOWW6qlD8WAfUt2+/WHopctykwwz05UVH9Hg=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETtK8v7MICve56qTHHDhhBOuV4IlUaESxZryCfk9QbG9co/CqPvTsgPDbCpp6oFtyAHwlDhnvr7JijXRD9Cb2FA==",
+ "url": "https://ct.googleapis.com/icarus/",
+ "mmd": 86400,
+ "state": {
+ "retired": {
+ "timestamp": "2022-09-15T00:00:00Z"
+ }
+ }
+ },
+ {
+ "description": "Google 'Pilot' log",
+ "log_id": "pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTDM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==",
+ "url": "https://ct.googleapis.com/pilot/",
+ "mmd": 86400,
+ "state": {
+ "retired": {
+ "timestamp": "2022-09-15T00:00:00Z"
+ }
+ }
+ },
+ {
+ "description": "Google 'Rocketeer' log",
+ "log_id": "7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIFsYyDzBi7MxCAC/oJBXK7dHjG+1aLCOkHjpoHPqTyghLpzA9BYbqvnV16mAw04vUjyYASVGJCUoI3ctBcJAeg==",
+ "url": "https://ct.googleapis.com/rocketeer/",
+ "mmd": 86400,
+ "state": {
+ "retired": {
+ "timestamp": "2022-09-15T00:00:00Z"
+ }
+ }
+ },
+ {
+ "description": "Google 'Skydiver' log",
+ "log_id": "u9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e0YU=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEmyGDvYXsRJsNyXSrYc9DjHsIa2xzb4UR7ZxVoV6mrc9iZB7xjI6+NrOiwH+P/xxkRmOFG6Jel20q37hTh58rA==",
+ "url": "https://ct.googleapis.com/skydiver/",
+ "mmd": 86400,
+ "state": {
+ "retired": {
+ "timestamp": "2022-09-15T00:00:00Z"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Cloudflare",
+ "email": [
+ "ct-logs@cloudflare.com",
+ "brendan@cloudflare.com",
+ "nick@cloudflare.com",
+ "pat@cloudflare.com",
+ "zi@cloudflare.com",
+ "ivan@cloudflare.com"
+ ],
+ "logs": [
+ {
+ "description": "Cloudflare 'Nimbus2023' Log",
+ "log_id": "ejKMVNi3LbYg6jjgUh7phBZwMhOFTTvSK8E6V6NS61I=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi/8tkhjLRp0SXrlZdTzNkTd6HqmcmXiDJz3fAdWLgOhjmv4mohvRhwXul9bgW0ODgRwC9UGAgH/vpGHPvIS1qA==",
+ "url": "https://ct.cloudflare.com/logs/nimbus2023/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2019-10-31T19:22:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "Cloudflare 'Nimbus2024' Log",
+ "log_id": "2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6s=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd7Gbe4/mizX+OpIpLayKjVGKJfyTttegiyk3cR0zyswz6ii5H+Ksw6ld3Ze+9p6UJd02gdHrXSnDK0TxW8oVSA==",
+ "url": "https://ct.cloudflare.com/logs/nimbus2024/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-30T17:00:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-01-01T00:00:00Z",
+ "end_exclusive": "2025-01-01T00:00:00Z"
+ }
+ }
+ ]
+ },
+ {
+ "name": "DigiCert",
+ "email": [
+ "ctops@digicert.com"
+ ],
+ "logs": [
+ {
+ "description": "DigiCert Yeti2023 Log",
+ "log_id": "Nc8ZG7+xbFe/D61MbULLu7YnICZR6j/hKu+oA8M71kw=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfQ0DsdWYitzwFTvG3F4Nbj8Nv5XIVYzQpkyWsU4nuSYlmcwrAp6m092fsdXEw6w1BAeHlzaqrSgNfyvZaJ9y0Q==",
+ "url": "https://yeti2023.ct.digicert.com/log/",
+ "mmd": 86400,
+ "state": {
+ "retired": {
+ "timestamp": "2022-09-29T00:00:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "DigiCert Yeti2024 Log",
+ "log_id": "SLDja9qmRzQP5WoC+p0w6xxSActW3SyB2bu/qznYhHM=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV7jBbzCkfy7k8NDZYGITleN6405Tw7O4c4XBGA0jDliE0njvm7MeLBrewY+BGxlEWLcAd2AgGnLYgt6unrHGSw==",
+ "url": "https://yeti2024.ct.digicert.com/log/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-01T18:54:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-01-01T00:00:00Z",
+ "end_exclusive": "2025-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "DigiCert Yeti2025 Log",
+ "log_id": "fVkeEuF4KnscYWd8Xv340IdcFKBOlZ65Ay/ZDowuebg=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE35UAXhDBAfc34xB00f+yypDtMplfDDn+odETEazRs3OTIMITPEy1elKGhj3jlSR82JGYSDvw8N8h8bCBWlklQw==",
+ "url": "https://yeti2025.ct.digicert.com/log/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-01T18:54:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2025-01-01T00:00:00Z",
+ "end_exclusive": "2026-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "DigiCert Nessie2023 Log",
+ "log_id": "s3N3B+GEUPhjhtYFqdwRCUp5LbFnDAuH3PADDnk2pZo=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXu8iQwSCRSf2CbITGpUpBtFVt8+I0IU0d1C36Lfe1+fbwdaI0Z5FktfM2fBoI1bXBd18k2ggKGYGgdZBgLKTg==",
+ "url": "https://nessie2023.ct.digicert.com/log/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2019-10-31T19:22:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "DigiCert Nessie2024 Log",
+ "log_id": "c9meiRtMlnigIH1HneayxhzQUV5xGSqMa4AQesF3crU=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELfyieza/VpHp/j/oPfzDp+BhUuos6QWjnycXgQVwa4FhRIr4OxCAQu0DLwBQIfxBVISjVNUusnoWSyofK2YEKw==",
+ "url": "https://nessie2024.ct.digicert.com/log/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-01T18:54:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-01-01T00:00:00Z",
+ "end_exclusive": "2025-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "DigiCert Nessie2025 Log",
+ "log_id": "5tIxY0B3jMEQQQbXcbnOwdJA9paEhvu6hzId/R43jlA=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8vDwp4uBLgk5O59C2jhEX7TM7Ta72EN/FklXhwR/pQE09+hoP7d4H2BmLWeadYC3U6eF1byrRwZV27XfiKFvOA==",
+ "url": "https://nessie2025.ct.digicert.com/log/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-01T18:54:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2025-01-01T00:00:00Z",
+ "end_exclusive": "2026-01-01T00:00:00Z"
+ }
+ }
+ ]
+ },
+ {
+ "name": "Sectigo",
+ "email": [
+ "ctops@sectigo.com"
+ ],
+ "logs": [
+ {
+ "description": "Sectigo 'Sabre' CT log",
+ "log_id": "VYHUwhaQNgFK6gubVzxT8MDkOHhwJQgXL6OqHQcT0ww=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8m/SiQ8/xfiHHqtls9m7FyOMBg4JVZY9CgiixXGz0akvKD6DEL8S0ERmFe9U4ZiA0M4kbT5nmuk3I85Sk4bagA==",
+ "url": "https://sabre.ct.comodo.com/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2017-10-10T00:38:10Z"
+ }
+ }
+ },
+ {
+ "description": "Sectigo 'Mammoth' CT log",
+ "log_id": "b1N2rDHwMRnYmQCkURX/dxUcEdkCwQApBo2yCJo32RM=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7+R9dC4VFbbpuyOL+yy14ceAmEf7QGlo/EmtYU6DRzwat43f/3swtLr/L8ugFOOt1YU/RFmMjGCL17ixv66MZw==",
+ "url": "https://mammoth.ct.comodo.com/",
+ "mmd": 86400,
+ "state": {
+ "retired": {
+ "timestamp": "2023-01-15T00:00:00Z"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Let's Encrypt",
+ "email": [
+ "sre@letsencrypt.org"
+ ],
+ "logs": [
+ {
+ "description": "Let's Encrypt 'Oak2023' log",
+ "log_id": "tz77JN+cTbp18jnFulj0bF38Qs96nzXEnh0JgSXttJk=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsz0OeL7jrVxEXJu+o4QWQYLKyokXHiPOOKVUL3/TNFFquVzDSer7kZ3gijxzBp98ZTgRgMSaWgCmZ8OD74mFUQ==",
+ "url": "https://oak.ct.letsencrypt.org/2023/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2021-03-01T19:24:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-07T00:00:00Z"
+ }
+ },
+ {
+ "description": "Let's Encrypt 'Oak2024H1' log",
+ "log_id": "O1N3dT4tuYBOizBbBv5AO2fYT8P0x70ADS1yb+H61Bc=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVkPXfnvUcre6qVG9NpO36bWSD+pet0Wjkv3JpTyArBog7yUvuOEg96g6LgeN5uuk4n0kY59Gv5RzUo2Wrqkm/Q==",
+ "url": "https://oak.ct.letsencrypt.org/2024h1/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-30T17:00:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-12-20T00:00:00Z",
+ "end_exclusive": "2024-07-20T00:00:00Z"
+ }
+ },
+ {
+ "description": "Let's Encrypt 'Oak2024H2' log",
+ "log_id": "PxdLT9ciR1iUHWUchL4NEu2QN38fhWrrwb8ohez4ZG4=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE13PWU0fp88nVfBbC1o9wZfryUTapE4Av7fmU01qL6E8zz8PTidRfWmaJuiAfccvKu5+f81wtHqOBWa+Ss20waA==",
+ "url": "https://oak.ct.letsencrypt.org/2024h2/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2022-11-30T17:00:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-06-20T00:00:00Z",
+ "end_exclusive": "2025-01-20T00:00:00Z"
+ }
+ }
+ ]
+ },
+ {
+ "name": "TrustAsia",
+ "email": [
+ "trustasia-ct-logs@trustasia.com"
+ ],
+ "logs": [
+ {
+ "description": "Trust Asia Log2023",
+ "log_id": "6H6nZgvCbPYALvVyXT/g4zG5OTu5L79Y6zuQSdr1Q1o=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpBFS2xdBTpDUVlESMFL4mwPPTJ/4Lji18Vq6+ji50o8agdqVzDPsIShmxlY+YDYhINnUrF36XBmhBX3+ICP89Q==",
+ "url": "https://ct.trustasia.com/log2023/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2021-03-01T19:24:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2023-01-01T00:00:00Z",
+ "end_exclusive": "2024-01-01T00:00:00Z"
+ }
+ },
+ {
+ "description": "Trust Asia Log2024-2",
+ "log_id": "h0+1DcAp2ZMd5XPp8omejkUzs5LTiwpGJXS/D+6y/B4=",
+ "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEp2TieYE/YdfsxvhlKB2gtGYzwyXVCpV4nI/+pCrYj35y4P6of/ixLYXAjhJ0DS+Mq9d/eh7ZhDM56P2JX5ZICA==",
+ "url": "https://ct2024.trustasia.com/log2024/",
+ "mmd": 86400,
+ "state": {
+ "usable": {
+ "timestamp": "2023-02-03T08:00:00Z"
+ }
+ },
+ "temporal_interval": {
+ "start_inclusive": "2024-01-01T00:00:00Z",
+ "end_exclusive": "2025-01-01T00:00:00Z"
+ }
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/security/manager/tools/mach_commands.py b/security/manager/tools/mach_commands.py
new file mode 100644
index 0000000000..e543821dbe
--- /dev/null
+++ b/security/manager/tools/mach_commands.py
@@ -0,0 +1,129 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+from mach.decorators import Command, CommandArgument
+from mach.util import UserError
+from mozpack.files import FileFinder
+from mozpack.path import basedir
+
+
+def run_module_main_on(module, input_filename, output_is_binary):
+ """Run the given module (pycert or pykey) on the given
+ file."""
+ # By convention, the specification files have names of the form
+ # "name.ext.*spec", where "ext" is some extension, and the "*" in
+ # "*spec" identifies what kind of specification it represents
+ # (certspec or keyspec). Taking off the ".*spec" part results in the
+ # desired filename for this file.
+ output_filename = os.path.splitext(input_filename)[0]
+ mode = "w"
+ encoding = "utf-8"
+ newline = "\n"
+ if output_is_binary:
+ mode = "wb"
+ encoding = None
+ newline = None
+ with open(output_filename, mode=mode, encoding=encoding, newline=newline) as output:
+ module.main(output, input_filename)
+
+
+def is_certspec_file(filename):
+ """Returns True if the given filename is a certificate
+ specification file (.certspec) and False otherwise."""
+ return filename.endswith(".certspec")
+
+
+def is_keyspec_file(filename):
+ """Returns True if the given filename is a key specification
+ file (.keyspec) and False otherwise."""
+ return filename.endswith(".keyspec")
+
+
+def is_pkcs12spec_file(filename):
+ """Returns True if the given filename is a pkcs12
+ specification file (.pkcs12spec) and False otherwise."""
+ return filename.endswith(".pkcs12spec")
+
+
+def is_specification_file(filename):
+ """Returns True if the given filename is a specification
+ file supported by this script, and False otherewise."""
+ return (
+ is_certspec_file(filename)
+ or is_keyspec_file(filename)
+ or is_pkcs12spec_file(filename)
+ )
+
+
+def is_excluded_directory(directory, exclusions):
+ """Returns True if the given directory is in or is a
+ subdirectory of a directory in the list of exclusions and
+ False otherwise."""
+
+ for exclusion in exclusions:
+ if directory.startswith(exclusion):
+ return True
+ return False
+
+
+@Command(
+ "generate-test-certs",
+ category="devenv",
+ description="Generate test certificates and keys from specifications.",
+)
+@CommandArgument(
+ "specifications",
+ nargs="*",
+ help="Specification files for test certs. If omitted, all certs are regenerated.",
+)
+def generate_test_certs(command_context, specifications):
+ """Generate test certificates and keys from specifications."""
+ import pycert
+ import pykey
+ import pypkcs12
+
+ if not specifications:
+ specifications = find_all_specifications(command_context)
+
+ for specification in specifications:
+ output_is_binary = False
+ if is_certspec_file(specification):
+ module = pycert
+ elif is_keyspec_file(specification):
+ module = pykey
+ elif is_pkcs12spec_file(specification):
+ module = pypkcs12
+ output_is_binary = True
+ else:
+ raise UserError(
+ "'{}' is not a .certspec, .keyspec, or .pkcs12spec file".format(
+ specification
+ )
+ )
+ run_module_main_on(module, os.path.abspath(specification), output_is_binary)
+ return 0
+
+
+def find_all_specifications(command_context):
+ """Searches the source tree for all specification files
+ and returns them as a list."""
+ specifications = []
+ inclusions = [
+ "netwerk/test/unit",
+ "security/manager/ssl/tests",
+ "services/settings/test/unit/test_remote_settings_signatures",
+ "testing/xpcshell/moz-http2",
+ "toolkit/mozapps/extensions/test/xpcshell/data/productaddons",
+ ]
+ exclusions = ["security/manager/ssl/tests/unit/test_signed_apps"]
+ finder = FileFinder(command_context.topsrcdir)
+ for inclusion_path in inclusions:
+ for f, _ in finder.find(inclusion_path):
+ if basedir(f, exclusions):
+ continue
+ if is_specification_file(f):
+ specifications.append(os.path.join(command_context.topsrcdir, f))
+ return specifications
diff --git a/security/manager/tools/pycert.py b/security/manager/tools/pycert.py
new file mode 100755
index 0000000000..2fe90515e1
--- /dev/null
+++ b/security/manager/tools/pycert.py
@@ -0,0 +1,805 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Reads a certificate specification from stdin or a file and outputs a
+signed x509 certificate with the desired properties.
+
+The input format is as follows:
+
+issuer:<issuer distinguished name specification>
+subject:<subject distinguished name specification>
+[version:{1,2,3,4}]
+[validity:<YYYYMMDD-YYYYMMDD|duration in days>]
+[issuerKey:<key specification>]
+[subjectKey:<key specification>]
+[signature:{sha256WithRSAEncryption,sha1WithRSAEncryption,
+ md5WithRSAEncryption,ecdsaWithSHA256,ecdsaWithSHA384,
+ ecdsaWithSHA512}]
+[serialNumber:<integer in the interval [1, 127]>]
+[extension:<extension name:<extension-specific data>>]
+[...]
+
+Known extensions are:
+basicConstraints:[cA],[pathLenConstraint]
+keyUsage:[digitalSignature,nonRepudiation,keyEncipherment,
+ dataEncipherment,keyAgreement,keyCertSign,cRLSign]
+extKeyUsage:[serverAuth,clientAuth,codeSigning,emailProtection
+ nsSGC, # Netscape Server Gated Crypto
+ OCSPSigning,timeStamping]
+subjectAlternativeName:[<dNSName|directoryName|"ip4:"iPV4Address>,...]
+authorityInformationAccess:<OCSP URI>
+certificatePolicies:[<policy OID>,...]
+nameConstraints:{permitted,excluded}:[<dNSName|directoryName>,...]
+nsCertType:sslServer
+TLSFeature:[<TLSFeature>,...]
+embeddedSCTList:[<key specification>:<YYYYMMDD>,...]
+delegationUsage:
+
+Where:
+ [] indicates an optional field or component of a field
+ <> indicates a required component of a field
+ {} indicates a choice of exactly one value among a set of values
+ [a,b,c] indicates a list of potential values, of which zero or more
+ may be used
+
+For instance, the version field is optional. However, if it is
+specified, it must have exactly one value from the set {1,2,3,4}.
+
+Most fields have reasonable default values. By default one shared RSA
+key is used for all signatures and subject public key information
+fields. Using "issuerKey:<key specification>" or
+"subjectKey:<key specification>" causes a different key be used for
+signing or as the subject public key information field, respectively.
+See pykey.py for the list of available specifications.
+The signature algorithm is sha256WithRSAEncryption by default.
+
+The validity period may be specified as either concrete notBefore and
+notAfter values or as a validity period centered around 'now'. For the
+latter, this will result in a notBefore of 'now' - duration/2 and a
+notAfter of 'now' + duration/2.
+
+Issuer and subject distinguished name specifications are of the form
+'[stringEncoding]/C=XX/O=Example/CN=example.com'. C (country name), ST
+(state or province name), L (locality name), O (organization name), OU
+(organizational unit name), CN (common name) and emailAddress (email
+address) are currently supported. The optional stringEncoding field may
+be 'utf8String' or 'printableString'. If the given string does not
+contain a '/', it is assumed to represent a common name. If an empty
+string is provided, then an empty distinguished name is returned.
+DirectoryNames also use this format. When specifying a directoryName in
+a nameConstraints extension, the implicit form may not be used.
+
+If an extension name has '[critical]' after it, it will be marked as
+critical. Otherwise (by default), it will not be marked as critical.
+
+TLSFeature values can either consist of a named value (currently only
+'OCSPMustStaple' which corresponds to status_request) or a numeric TLS
+feature value (see rfc7633 for more information).
+
+If a serial number is not explicitly specified, it is automatically
+generated based on the contents of the certificate.
+"""
+
+import base64
+import datetime
+import hashlib
+import re
+import socket
+import sys
+from struct import pack
+
+import pyct
+import pykey
+import six
+from pyasn1.codec.der import decoder, encoder
+from pyasn1.type import constraint, tag, univ, useful
+from pyasn1_modules import rfc2459
+
+
+class Error(Exception):
+ """Base class for exceptions in this module."""
+
+ pass
+
+
+class UnknownBaseError(Error):
+ """Base class for handling unexpected input in this module."""
+
+ def __init__(self, value):
+ super(UnknownBaseError, self).__init__()
+ self.value = value
+ self.category = "input"
+
+ def __str__(self):
+ return 'Unknown %s type "%s"' % (self.category, repr(self.value))
+
+
+class UnknownAlgorithmTypeError(UnknownBaseError):
+ """Helper exception type to handle unknown algorithm types."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "algorithm"
+
+
+class UnknownParameterTypeError(UnknownBaseError):
+ """Helper exception type to handle unknown input parameters."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "parameter"
+
+
+class UnknownExtensionTypeError(UnknownBaseError):
+ """Helper exception type to handle unknown input extensions."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "extension"
+
+
+class UnknownKeyPurposeTypeError(UnknownBaseError):
+ """Helper exception type to handle unknown key purposes."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "keyPurpose"
+
+
+class UnknownKeyTargetError(UnknownBaseError):
+ """Helper exception type to handle unknown key targets."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "key target"
+
+
+class UnknownVersionError(UnknownBaseError):
+ """Helper exception type to handle unknown specified versions."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "version"
+
+
+class UnknownNameConstraintsSpecificationError(UnknownBaseError):
+ """Helper exception type to handle unknown specified
+ nameConstraints."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "nameConstraints specification"
+
+
+class UnknownDNTypeError(UnknownBaseError):
+ """Helper exception type to handle unknown DN types."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "DN"
+
+
+class UnknownNSCertTypeError(UnknownBaseError):
+ """Helper exception type to handle unknown nsCertType types."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "nsCertType"
+
+
+class UnknownTLSFeature(UnknownBaseError):
+ """Helper exception type to handle unknown TLS Features."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "TLSFeature"
+
+
+class UnknownDelegatedCredentialError(UnknownBaseError):
+ """Helper exception type to handle unknown Delegated Credential args."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "delegatedCredential"
+
+
+class InvalidSCTSpecification(Error):
+ """Helper exception type to handle invalid SCT specifications."""
+
+ def __init__(self, value):
+ super(InvalidSCTSpecification, self).__init__()
+ self.value = value
+
+ def __str__(self):
+ return repr('invalid SCT specification "{}"' % self.value)
+
+
+class InvalidSerialNumber(Error):
+ """Exception type to handle invalid serial numbers."""
+
+ def __init__(self, value):
+ super(InvalidSerialNumber, self).__init__()
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+def getASN1Tag(asn1Type):
+ """Helper function for returning the base tag value of a given
+ type from the pyasn1 package"""
+ return asn1Type.tagSet.baseTag.tagId
+
+
+def stringToAccessDescription(string):
+ """Helper function that takes a string representing a URI
+ presumably identifying an OCSP authority information access
+ location. Returns an AccessDescription usable by pyasn1."""
+ accessMethod = rfc2459.id_ad_ocsp
+ accessLocation = rfc2459.GeneralName()
+ accessLocation["uniformResourceIdentifier"] = string
+ sequence = univ.Sequence()
+ sequence.setComponentByPosition(0, accessMethod)
+ sequence.setComponentByPosition(1, accessLocation)
+ return sequence
+
+
+def stringToDN(string, tag=None):
+ """Takes a string representing a distinguished name or directory
+ name and returns a Name for use by pyasn1. See the documentation
+ for the issuer and subject fields for more details. Takes an
+ optional implicit tag in cases where the Name needs to be tagged
+ differently."""
+ if string and "/" not in string:
+ string = "/CN=%s" % string
+ rdns = rfc2459.RDNSequence()
+ pattern = "/(C|ST|L|O|OU|CN|emailAddress)="
+ split = re.split(pattern, string)
+ # split should now be [[encoding], <type>, <value>, <type>, <value>, ...]
+ if split[0]:
+ encoding = split[0]
+ else:
+ encoding = "utf8String"
+ for pos, (nameType, value) in enumerate(zip(split[1::2], split[2::2])):
+ ava = rfc2459.AttributeTypeAndValue()
+ if nameType == "C":
+ ava["type"] = rfc2459.id_at_countryName
+ nameComponent = rfc2459.X520countryName(value)
+ elif nameType == "ST":
+ ava["type"] = rfc2459.id_at_stateOrProvinceName
+ nameComponent = rfc2459.X520StateOrProvinceName()
+ elif nameType == "L":
+ ava["type"] = rfc2459.id_at_localityName
+ nameComponent = rfc2459.X520LocalityName()
+ elif nameType == "O":
+ ava["type"] = rfc2459.id_at_organizationName
+ nameComponent = rfc2459.X520OrganizationName()
+ elif nameType == "OU":
+ ava["type"] = rfc2459.id_at_organizationalUnitName
+ nameComponent = rfc2459.X520OrganizationalUnitName()
+ elif nameType == "CN":
+ ava["type"] = rfc2459.id_at_commonName
+ nameComponent = rfc2459.X520CommonName()
+ elif nameType == "emailAddress":
+ ava["type"] = rfc2459.emailAddress
+ nameComponent = rfc2459.Pkcs9email(value)
+ else:
+ raise UnknownDNTypeError(nameType)
+ if not nameType == "C" and not nameType == "emailAddress":
+ # The value may have things like '\0' (i.e. a slash followed by
+ # the number zero) that have to be decoded into the resulting
+ # '\x00' (i.e. a byte with value zero).
+ nameComponent[encoding] = six.ensure_binary(value).decode(
+ encoding="unicode_escape"
+ )
+ ava["value"] = nameComponent
+ rdn = rfc2459.RelativeDistinguishedName()
+ rdn.setComponentByPosition(0, ava)
+ rdns.setComponentByPosition(pos, rdn)
+ if tag:
+ name = rfc2459.Name().subtype(implicitTag=tag)
+ else:
+ name = rfc2459.Name()
+ name.setComponentByPosition(0, rdns)
+ return name
+
+
+def stringToAlgorithmIdentifiers(string):
+ """Helper function that converts a description of an algorithm
+ to a representation usable by the pyasn1 package and a hash
+ algorithm constant for use by pykey."""
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmType = None
+ algorithm = None
+ # We add Null parameters for RSA only
+ addParameters = False
+ if string == "sha1WithRSAEncryption":
+ algorithmType = pykey.HASH_SHA1
+ algorithm = rfc2459.sha1WithRSAEncryption
+ addParameters = True
+ elif string == "sha256WithRSAEncryption":
+ algorithmType = pykey.HASH_SHA256
+ algorithm = univ.ObjectIdentifier("1.2.840.113549.1.1.11")
+ addParameters = True
+ elif string == "md5WithRSAEncryption":
+ algorithmType = pykey.HASH_MD5
+ algorithm = rfc2459.md5WithRSAEncryption
+ addParameters = True
+ elif string == "ecdsaWithSHA256":
+ algorithmType = pykey.HASH_SHA256
+ algorithm = univ.ObjectIdentifier("1.2.840.10045.4.3.2")
+ elif string == "ecdsaWithSHA384":
+ algorithmType = pykey.HASH_SHA384
+ algorithm = univ.ObjectIdentifier("1.2.840.10045.4.3.3")
+ elif string == "ecdsaWithSHA512":
+ algorithmType = pykey.HASH_SHA512
+ algorithm = univ.ObjectIdentifier("1.2.840.10045.4.3.4")
+ else:
+ raise UnknownAlgorithmTypeError(string)
+ algorithmIdentifier["algorithm"] = algorithm
+ if addParameters:
+ # Directly setting parameters to univ.Null doesn't currently work.
+ nullEncapsulated = encoder.encode(univ.Null())
+ algorithmIdentifier["parameters"] = univ.Any(nullEncapsulated)
+ return (algorithmIdentifier, algorithmType)
+
+
+def datetimeToTime(dt):
+ """Takes a datetime object and returns an rfc2459.Time object with
+ that time as its value as a GeneralizedTime"""
+ time = rfc2459.Time()
+ time["generalTime"] = useful.GeneralizedTime(dt.strftime("%Y%m%d%H%M%SZ"))
+ return time
+
+
+def serialBytesToString(serialBytes):
+ """Takes a list of integers in the interval [0, 255] and returns
+ the corresponding serial number string."""
+ serialBytesLen = len(serialBytes)
+ if serialBytesLen > 127:
+ raise InvalidSerialNumber("{} bytes is too long".format(serialBytesLen))
+ # Prepend the ASN.1 INTEGER tag and length bytes.
+ stringBytes = [getASN1Tag(univ.Integer), serialBytesLen] + serialBytes
+ return bytes(stringBytes)
+
+
+class Certificate(object):
+ """Utility class for reading a certificate specification and
+ generating a signed x509 certificate"""
+
+ def __init__(self, paramStream):
+ self.versionValue = 2 # a value of 2 is X509v3
+ self.signature = "sha256WithRSAEncryption"
+ self.issuer = "Default Issuer"
+ actualNow = datetime.datetime.utcnow()
+ self.now = datetime.datetime.strptime(str(actualNow.year), "%Y")
+ aYearAndAWhile = datetime.timedelta(days=400)
+ self.notBefore = self.now - aYearAndAWhile
+ self.notAfter = self.now + aYearAndAWhile
+ self.subject = "Default Subject"
+ self.extensions = None
+ # The serial number can be automatically generated from the
+ # certificate specification. We need this value to depend in
+ # part of what extensions are present. self.extensions are
+ # pyasn1 objects. Depending on the string representation of
+ # these objects can cause the resulting serial number to change
+ # unexpectedly, so instead we depend on the original string
+ # representation of the extensions as specified.
+ self.extensionLines = None
+ self.savedEmbeddedSCTListData = None
+ self.subjectKey = pykey.keyFromSpecification("default")
+ self.issuerKey = pykey.keyFromSpecification("default")
+ self.serialNumber = None
+ self.decodeParams(paramStream)
+ # If a serial number wasn't specified, generate one based on
+ # the certificate contents.
+ if not self.serialNumber:
+ self.serialNumber = self.generateSerialNumber()
+ # This has to be last because the SCT signature depends on the
+ # contents of the certificate.
+ if self.savedEmbeddedSCTListData:
+ self.addEmbeddedSCTListData()
+
+ def generateSerialNumber(self):
+ """Generates a serial number for this certificate based on its
+ contents. Intended to be reproducible for compatibility with
+ the build system on OS X (see the comment above main, later in
+ this file)."""
+ hasher = hashlib.sha256()
+ hasher.update(six.ensure_binary(str(self.versionValue)))
+ hasher.update(six.ensure_binary(self.signature))
+ hasher.update(six.ensure_binary(self.issuer))
+ hasher.update(six.ensure_binary(str(self.notBefore)))
+ hasher.update(six.ensure_binary(str(self.notAfter)))
+ hasher.update(six.ensure_binary(self.subject))
+ if self.extensionLines:
+ for extensionLine in self.extensionLines:
+ hasher.update(six.ensure_binary(extensionLine))
+ if self.savedEmbeddedSCTListData:
+ # savedEmbeddedSCTListData is
+ # (embeddedSCTListSpecification, critical), where |critical|
+ # may be None
+ hasher.update(six.ensure_binary(self.savedEmbeddedSCTListData[0]))
+ if self.savedEmbeddedSCTListData[1]:
+ hasher.update(six.ensure_binary(self.savedEmbeddedSCTListData[1]))
+ serialBytes = [c for c in hasher.digest()[:20]]
+ # Ensure that the most significant bit isn't set (which would
+ # indicate a negative number, which isn't valid for serial
+ # numbers).
+ serialBytes[0] &= 0x7F
+ # Also ensure that the least significant bit on the most
+ # significant byte is set (to prevent a leading zero byte,
+ # which also wouldn't be valid).
+ serialBytes[0] |= 0x01
+ return serialBytesToString(serialBytes)
+
+ def decodeParams(self, paramStream):
+ for line in paramStream.readlines():
+ self.decodeParam(line.strip())
+
+ def decodeParam(self, line):
+ param = line.split(":")[0]
+ value = ":".join(line.split(":")[1:])
+ if param == "version":
+ self.setVersion(value)
+ elif param == "subject":
+ self.subject = value
+ elif param == "issuer":
+ self.issuer = value
+ elif param == "validity":
+ self.decodeValidity(value)
+ elif param == "extension":
+ self.decodeExtension(value)
+ elif param == "issuerKey":
+ self.setupKey("issuer", value)
+ elif param == "subjectKey":
+ self.setupKey("subject", value)
+ elif param == "signature":
+ self.signature = value
+ elif param == "serialNumber":
+ serialNumber = int(value)
+ # Ensure only serial numbers that conform to the rules listed in
+ # generateSerialNumber() are permitted.
+ if serialNumber < 1 or serialNumber > 127:
+ raise InvalidSerialNumber(value)
+ self.serialNumber = serialBytesToString([serialNumber])
+ else:
+ raise UnknownParameterTypeError(param)
+
+ def setVersion(self, version):
+ intVersion = int(version)
+ if intVersion >= 1 and intVersion <= 4:
+ self.versionValue = intVersion - 1
+ else:
+ raise UnknownVersionError(version)
+
+ def decodeValidity(self, duration):
+ match = re.search("([0-9]{8})-([0-9]{8})", duration)
+ if match:
+ self.notBefore = datetime.datetime.strptime(match.group(1), "%Y%m%d")
+ self.notAfter = datetime.datetime.strptime(match.group(2), "%Y%m%d")
+ else:
+ delta = datetime.timedelta(days=(int(duration) / 2))
+ self.notBefore = self.now - delta
+ self.notAfter = self.now + delta
+
+ def decodeExtension(self, extension):
+ match = re.search(r"([a-zA-Z]+)(\[critical\])?:(.*)", extension)
+ if not match:
+ raise UnknownExtensionTypeError(extension)
+ extensionType = match.group(1)
+ critical = match.group(2)
+ value = match.group(3)
+ if extensionType == "basicConstraints":
+ self.addBasicConstraints(value, critical)
+ elif extensionType == "keyUsage":
+ self.addKeyUsage(value, critical)
+ elif extensionType == "extKeyUsage":
+ self.addExtKeyUsage(value, critical)
+ elif extensionType == "subjectAlternativeName":
+ self.addSubjectAlternativeName(value, critical)
+ elif extensionType == "authorityInformationAccess":
+ self.addAuthorityInformationAccess(value, critical)
+ elif extensionType == "certificatePolicies":
+ self.addCertificatePolicies(value, critical)
+ elif extensionType == "nameConstraints":
+ self.addNameConstraints(value, critical)
+ elif extensionType == "nsCertType":
+ self.addNSCertType(value, critical)
+ elif extensionType == "TLSFeature":
+ self.addTLSFeature(value, critical)
+ elif extensionType == "embeddedSCTList":
+ self.savedEmbeddedSCTListData = (value, critical)
+ elif extensionType == "delegationUsage":
+ self.addDelegationUsage(critical)
+ else:
+ raise UnknownExtensionTypeError(extensionType)
+
+ if extensionType != "embeddedSCTList":
+ if not self.extensionLines:
+ self.extensionLines = []
+ self.extensionLines.append(extension)
+
+ def setupKey(self, subjectOrIssuer, value):
+ if subjectOrIssuer == "subject":
+ self.subjectKey = pykey.keyFromSpecification(value)
+ elif subjectOrIssuer == "issuer":
+ self.issuerKey = pykey.keyFromSpecification(value)
+ else:
+ raise UnknownKeyTargetError(subjectOrIssuer)
+
+ def addExtension(self, extensionType, extensionValue, critical):
+ if not self.extensions:
+ self.extensions = []
+ encapsulated = univ.OctetString(encoder.encode(extensionValue))
+ extension = rfc2459.Extension()
+ extension["extnID"] = extensionType
+ # critical is either the string '[critical]' or None.
+ # We only care whether or not it is truthy.
+ if critical:
+ extension["critical"] = True
+ extension["extnValue"] = encapsulated
+ self.extensions.append(extension)
+
+ def addBasicConstraints(self, basicConstraints, critical):
+ cA = basicConstraints.split(",")[0]
+ pathLenConstraint = basicConstraints.split(",")[1]
+ basicConstraintsExtension = rfc2459.BasicConstraints()
+ basicConstraintsExtension["cA"] = cA == "cA"
+ if pathLenConstraint:
+ pathLenConstraintValue = univ.Integer(int(pathLenConstraint)).subtype(
+ subtypeSpec=constraint.ValueRangeConstraint(0, float("inf"))
+ )
+ basicConstraintsExtension["pathLenConstraint"] = pathLenConstraintValue
+ self.addExtension(
+ rfc2459.id_ce_basicConstraints, basicConstraintsExtension, critical
+ )
+
+ def addKeyUsage(self, keyUsage, critical):
+ keyUsageExtension = rfc2459.KeyUsage(keyUsage)
+ self.addExtension(rfc2459.id_ce_keyUsage, keyUsageExtension, critical)
+
+ def keyPurposeToOID(self, keyPurpose):
+ if keyPurpose == "serverAuth":
+ return rfc2459.id_kp_serverAuth
+ if keyPurpose == "clientAuth":
+ return rfc2459.id_kp_clientAuth
+ if keyPurpose == "codeSigning":
+ return rfc2459.id_kp_codeSigning
+ if keyPurpose == "emailProtection":
+ return rfc2459.id_kp_emailProtection
+ if keyPurpose == "nsSGC":
+ return univ.ObjectIdentifier("2.16.840.1.113730.4.1")
+ if keyPurpose == "OCSPSigning":
+ return univ.ObjectIdentifier("1.3.6.1.5.5.7.3.9")
+ if keyPurpose == "timeStamping":
+ return rfc2459.id_kp_timeStamping
+ raise UnknownKeyPurposeTypeError(keyPurpose)
+
+ def addExtKeyUsage(self, extKeyUsage, critical):
+ extKeyUsageExtension = rfc2459.ExtKeyUsageSyntax()
+ for count, keyPurpose in enumerate(extKeyUsage.split(",")):
+ extKeyUsageExtension.setComponentByPosition(
+ count, self.keyPurposeToOID(keyPurpose)
+ )
+ self.addExtension(rfc2459.id_ce_extKeyUsage, extKeyUsageExtension, critical)
+
+ def addSubjectAlternativeName(self, names, critical):
+ IPV4_PREFIX = "ip4:"
+
+ subjectAlternativeName = rfc2459.SubjectAltName()
+ for count, name in enumerate(names.split(",")):
+ generalName = rfc2459.GeneralName()
+ if "/" in name:
+ directoryName = stringToDN(
+ name, tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)
+ )
+ generalName["directoryName"] = directoryName
+ elif "@" in name:
+ generalName["rfc822Name"] = name
+ elif name.startswith(IPV4_PREFIX):
+ generalName["iPAddress"] = socket.inet_pton(
+ socket.AF_INET, name[len(IPV4_PREFIX) :]
+ )
+ else:
+ # The string may have things like '\0' (i.e. a slash
+ # followed by the number zero) that have to be decoded into
+ # the resulting '\x00' (i.e. a byte with value zero).
+ generalName["dNSName"] = six.ensure_binary(name).decode(
+ "unicode_escape"
+ )
+ subjectAlternativeName.setComponentByPosition(count, generalName)
+ self.addExtension(
+ rfc2459.id_ce_subjectAltName, subjectAlternativeName, critical
+ )
+
+ def addAuthorityInformationAccess(self, ocspURI, critical):
+ sequence = univ.Sequence()
+ accessDescription = stringToAccessDescription(ocspURI)
+ sequence.setComponentByPosition(0, accessDescription)
+ self.addExtension(rfc2459.id_pe_authorityInfoAccess, sequence, critical)
+
+ def addCertificatePolicies(self, policyOIDs, critical):
+ policies = rfc2459.CertificatePolicies()
+ for pos, policyOID in enumerate(policyOIDs.split(",")):
+ if policyOID == "any":
+ policyOID = "2.5.29.32.0"
+ policy = rfc2459.PolicyInformation()
+ policyIdentifier = rfc2459.CertPolicyId(policyOID)
+ policy["policyIdentifier"] = policyIdentifier
+ policies.setComponentByPosition(pos, policy)
+ self.addExtension(rfc2459.id_ce_certificatePolicies, policies, critical)
+
+ def addNameConstraints(self, constraints, critical):
+ nameConstraints = rfc2459.NameConstraints()
+ if constraints.startswith("permitted:"):
+ (subtreesType, subtreesTag) = ("permittedSubtrees", 0)
+ elif constraints.startswith("excluded:"):
+ (subtreesType, subtreesTag) = ("excludedSubtrees", 1)
+ else:
+ raise UnknownNameConstraintsSpecificationError(constraints)
+ generalSubtrees = rfc2459.GeneralSubtrees().subtype(
+ implicitTag=tag.Tag(
+ tag.tagClassContext, tag.tagFormatConstructed, subtreesTag
+ )
+ )
+ subtrees = constraints[(constraints.find(":") + 1) :]
+ for pos, name in enumerate(subtrees.split(",")):
+ generalName = rfc2459.GeneralName()
+ if "/" in name:
+ directoryName = stringToDN(
+ name, tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4)
+ )
+ generalName["directoryName"] = directoryName
+ else:
+ generalName["dNSName"] = name
+ generalSubtree = rfc2459.GeneralSubtree()
+ generalSubtree["base"] = generalName
+ generalSubtrees.setComponentByPosition(pos, generalSubtree)
+ nameConstraints[subtreesType] = generalSubtrees
+ self.addExtension(rfc2459.id_ce_nameConstraints, nameConstraints, critical)
+
+ def addNSCertType(self, certType, critical):
+ if certType != "sslServer":
+ raise UnknownNSCertTypeError(certType)
+ self.addExtension(
+ univ.ObjectIdentifier("2.16.840.1.113730.1.1"),
+ univ.BitString("'01'B"),
+ critical,
+ )
+
+ def addDelegationUsage(self, critical):
+ if critical:
+ raise UnknownDelegatedCredentialError(critical)
+ self.addExtension(
+ univ.ObjectIdentifier("1.3.6.1.4.1.44363.44"), univ.Null(), critical
+ )
+
+ def addTLSFeature(self, features, critical):
+ namedFeatures = {"OCSPMustStaple": 5}
+ featureList = [f.strip() for f in features.split(",")]
+ sequence = univ.Sequence()
+ for pos, feature in enumerate(featureList):
+ featureValue = 0
+ try:
+ featureValue = int(feature)
+ except ValueError:
+ try:
+ featureValue = namedFeatures[feature]
+ except Exception:
+ raise UnknownTLSFeature(feature)
+ sequence.setComponentByPosition(pos, univ.Integer(featureValue))
+ self.addExtension(
+ univ.ObjectIdentifier("1.3.6.1.5.5.7.1.24"), sequence, critical
+ )
+
+ def addEmbeddedSCTListData(self):
+ (scts, critical) = self.savedEmbeddedSCTListData
+ encodedSCTs = []
+ for sctSpec in scts.split(","):
+ match = re.search(r"(\w+):(\d{8})", sctSpec)
+ if not match:
+ raise InvalidSCTSpecification(sctSpec)
+ keySpec = match.group(1)
+ key = pykey.keyFromSpecification(keySpec)
+ time = datetime.datetime.strptime(match.group(2), "%Y%m%d")
+ tbsCertificate = self.getTBSCertificate()
+ tbsDER = encoder.encode(tbsCertificate)
+ sct = pyct.SCT(key, time, tbsDER, self.issuerKey)
+ signed = sct.signAndEncode()
+ lengthPrefix = pack("!H", len(signed))
+ encodedSCTs.append(lengthPrefix + signed)
+ encodedSCTBytes = b"".join(encodedSCTs)
+ lengthPrefix = pack("!H", len(encodedSCTBytes))
+ extensionBytes = lengthPrefix + encodedSCTBytes
+ self.addExtension(
+ univ.ObjectIdentifier("1.3.6.1.4.1.11129.2.4.2"),
+ univ.OctetString(extensionBytes),
+ critical,
+ )
+
+ def getVersion(self):
+ return rfc2459.Version(self.versionValue).subtype(
+ explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0)
+ )
+
+ def getSerialNumber(self):
+ return decoder.decode(self.serialNumber)[0]
+
+ def getIssuer(self):
+ return stringToDN(self.issuer)
+
+ def getValidity(self):
+ validity = rfc2459.Validity()
+ validity["notBefore"] = self.getNotBefore()
+ validity["notAfter"] = self.getNotAfter()
+ return validity
+
+ def getNotBefore(self):
+ return datetimeToTime(self.notBefore)
+
+ def getNotAfter(self):
+ return datetimeToTime(self.notAfter)
+
+ def getSubject(self):
+ return stringToDN(self.subject)
+
+ def getTBSCertificate(self):
+ (signatureOID, _) = stringToAlgorithmIdentifiers(self.signature)
+ tbsCertificate = rfc2459.TBSCertificate()
+ tbsCertificate["version"] = self.getVersion()
+ tbsCertificate["serialNumber"] = self.getSerialNumber()
+ tbsCertificate["signature"] = signatureOID
+ tbsCertificate["issuer"] = self.getIssuer()
+ tbsCertificate["validity"] = self.getValidity()
+ tbsCertificate["subject"] = self.getSubject()
+ tbsCertificate[
+ "subjectPublicKeyInfo"
+ ] = self.subjectKey.asSubjectPublicKeyInfo()
+ if self.extensions:
+ extensions = rfc2459.Extensions().subtype(
+ explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)
+ )
+ for count, extension in enumerate(self.extensions):
+ extensions.setComponentByPosition(count, extension)
+ tbsCertificate["extensions"] = extensions
+ return tbsCertificate
+
+ def toDER(self):
+ (signatureOID, hashAlgorithm) = stringToAlgorithmIdentifiers(self.signature)
+ certificate = rfc2459.Certificate()
+ tbsCertificate = self.getTBSCertificate()
+ certificate["tbsCertificate"] = tbsCertificate
+ certificate["signatureAlgorithm"] = signatureOID
+ tbsDER = encoder.encode(tbsCertificate)
+ certificate["signatureValue"] = self.issuerKey.sign(tbsDER, hashAlgorithm)
+ return encoder.encode(certificate)
+
+ def toPEM(self):
+ output = "-----BEGIN CERTIFICATE-----"
+ der = self.toDER()
+ b64 = six.ensure_text(base64.b64encode(der))
+ while b64:
+ output += "\n" + b64[:64]
+ b64 = b64[64:]
+ output += "\n-----END CERTIFICATE-----"
+ return output
+
+
+# The build harness will call this function with an output
+# file-like object and a path to a file containing a
+# specification. This will read the specification and output
+# the certificate as PEM.
+def main(output, inputPath):
+ with open(inputPath) as configStream:
+ output.write(Certificate(configStream).toPEM() + "\n")
+
+
+# When run as a standalone program, this will read a specification from
+# stdin and output the certificate as PEM to stdout.
+if __name__ == "__main__":
+ print(Certificate(sys.stdin).toPEM())
diff --git a/security/manager/tools/pycms.py b/security/manager/tools/pycms.py
new file mode 100755
index 0000000000..1717513fdf
--- /dev/null
+++ b/security/manager/tools/pycms.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Reads a specification from stdin and outputs a PKCS7 (CMS) message with
+the desired properties.
+
+The specification format is as follows:
+
+sha1:<hex string>
+sha256:<hex string>
+signer:
+<pycert specification>
+
+Eith or both of sha1 and sha256 may be specified. The value of
+each hash directive is what will be put in the messageDigest
+attribute of the SignerInfo that corresponds to the signature
+algorithm defined by the hash algorithm and key type of the
+default key. Together, these comprise the signerInfos field of
+the SignedData. If neither hash is specified, the signerInfos
+will be an empty SET (i.e. there will be no actual signature
+information).
+The certificate specification must come last.
+"""
+
+import base64
+import sys
+from io import StringIO
+
+import pycert
+import pykey
+from pyasn1.codec.der import decoder, encoder
+from pyasn1.type import tag, univ
+from pyasn1_modules import rfc2315, rfc2459
+
+
+class Error(Exception):
+ """Base class for exceptions in this module."""
+
+ pass
+
+
+class UnknownDirectiveError(Error):
+ """Helper exception type to handle unknown specification
+ directives."""
+
+ def __init__(self, directive):
+ super(UnknownDirectiveError, self).__init__()
+ self.directive = directive
+
+ def __str__(self):
+ return "Unknown directive %s" % repr(self.directive)
+
+
+class CMS(object):
+ """Utility class for reading a CMS specification and
+ generating a CMS message"""
+
+ def __init__(self, paramStream):
+ self.sha1 = ""
+ self.sha256 = ""
+ signerSpecification = StringIO()
+ readingSignerSpecification = False
+ for line in paramStream.readlines():
+ if readingSignerSpecification:
+ print(line.strip(), file=signerSpecification)
+ elif line.strip() == "signer:":
+ readingSignerSpecification = True
+ elif line.startswith("sha1:"):
+ self.sha1 = line.strip()[len("sha1:") :]
+ elif line.startswith("sha256:"):
+ self.sha256 = line.strip()[len("sha256:") :]
+ else:
+ raise UnknownDirectiveError(line.strip())
+ signerSpecification.seek(0)
+ self.signer = pycert.Certificate(signerSpecification)
+ self.signingKey = pykey.keyFromSpecification("default")
+
+ def buildAuthenticatedAttributes(self, value, implicitTag=None):
+ """Utility function to build a pyasn1 AuthenticatedAttributes
+ object. Useful because when building a SignerInfo, the
+ authenticatedAttributes needs to be tagged implicitly, but when
+ signing an AuthenticatedAttributes, it needs the explicit SET
+ tag."""
+ if implicitTag:
+ authenticatedAttributes = rfc2315.Attributes().subtype(
+ implicitTag=implicitTag
+ )
+ else:
+ authenticatedAttributes = rfc2315.Attributes()
+ contentTypeAttribute = rfc2315.Attribute()
+ # PKCS#9 contentType
+ contentTypeAttribute["type"] = univ.ObjectIdentifier("1.2.840.113549.1.9.3")
+ contentTypeAttribute["values"] = univ.SetOf(rfc2459.AttributeValue())
+ # PKCS#7 data
+ contentTypeAttribute["values"][0] = univ.ObjectIdentifier(
+ "1.2.840.113549.1.7.1"
+ )
+ authenticatedAttributes[0] = contentTypeAttribute
+ hashAttribute = rfc2315.Attribute()
+ # PKCS#9 messageDigest
+ hashAttribute["type"] = univ.ObjectIdentifier("1.2.840.113549.1.9.4")
+ hashAttribute["values"] = univ.SetOf(rfc2459.AttributeValue())
+ hashAttribute["values"][0] = univ.OctetString(hexValue=value)
+ authenticatedAttributes[1] = hashAttribute
+ return authenticatedAttributes
+
+ def pykeyHashToDigestAlgorithm(self, pykeyHash):
+ """Given a pykey hash algorithm identifier, builds an
+ AlgorithmIdentifier for use with pyasn1."""
+ if pykeyHash == pykey.HASH_SHA1:
+ oidString = "1.3.14.3.2.26"
+ elif pykeyHash == pykey.HASH_SHA256:
+ oidString = "2.16.840.1.101.3.4.2.1"
+ else:
+ raise pykey.UnknownHashAlgorithmError(pykeyHash)
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmIdentifier["algorithm"] = univ.ObjectIdentifier(oidString)
+ # Directly setting parameters to univ.Null doesn't currently work.
+ nullEncapsulated = encoder.encode(univ.Null())
+ algorithmIdentifier["parameters"] = univ.Any(nullEncapsulated)
+ return algorithmIdentifier
+
+ def buildSignerInfo(self, certificate, pykeyHash, digestValue):
+ """Given a pyasn1 certificate, a pykey hash identifier
+ and a hash value, creates a SignerInfo with the
+ appropriate values."""
+ signerInfo = rfc2315.SignerInfo()
+ signerInfo["version"] = 1
+ issuerAndSerialNumber = rfc2315.IssuerAndSerialNumber()
+ issuerAndSerialNumber["issuer"] = self.signer.getIssuer()
+ issuerAndSerialNumber["serialNumber"] = certificate["tbsCertificate"][
+ "serialNumber"
+ ]
+ signerInfo["issuerAndSerialNumber"] = issuerAndSerialNumber
+ signerInfo["digestAlgorithm"] = self.pykeyHashToDigestAlgorithm(pykeyHash)
+ rsa = rfc2459.AlgorithmIdentifier()
+ rsa["algorithm"] = rfc2459.rsaEncryption
+ rsa["parameters"] = univ.Null()
+ authenticatedAttributes = self.buildAuthenticatedAttributes(
+ digestValue,
+ implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0),
+ )
+ authenticatedAttributesTBS = self.buildAuthenticatedAttributes(digestValue)
+ signerInfo["authenticatedAttributes"] = authenticatedAttributes
+ signerInfo["digestEncryptionAlgorithm"] = rsa
+ authenticatedAttributesEncoded = encoder.encode(authenticatedAttributesTBS)
+ signature = self.signingKey.sign(authenticatedAttributesEncoded, pykeyHash)
+ # signature will be a hexified bit string of the form
+ # "'<hex bytes>'H". For some reason that's what BitString wants,
+ # but since this is an OCTET STRING, we have to strip off the
+ # quotation marks and trailing "H".
+ signerInfo["encryptedDigest"] = univ.OctetString(hexValue=signature[1:-2])
+ return signerInfo
+
+ def toDER(self):
+ contentInfo = rfc2315.ContentInfo()
+ contentInfo["contentType"] = rfc2315.signedData
+
+ signedData = rfc2315.SignedData()
+ signedData["version"] = rfc2315.Version(1)
+
+ digestAlgorithms = rfc2315.DigestAlgorithmIdentifiers()
+ digestAlgorithms[0] = self.pykeyHashToDigestAlgorithm(pykey.HASH_SHA1)
+ signedData["digestAlgorithms"] = digestAlgorithms
+
+ dataContentInfo = rfc2315.ContentInfo()
+ dataContentInfo["contentType"] = rfc2315.data
+ signedData["contentInfo"] = dataContentInfo
+
+ certificates = rfc2315.ExtendedCertificatesAndCertificates().subtype(
+ implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
+ )
+ extendedCertificateOrCertificate = rfc2315.ExtendedCertificateOrCertificate()
+ certificate = decoder.decode(
+ self.signer.toDER(), asn1Spec=rfc2459.Certificate()
+ )[0]
+ extendedCertificateOrCertificate["certificate"] = certificate
+ certificates[0] = extendedCertificateOrCertificate
+ signedData["certificates"] = certificates
+
+ signerInfos = rfc2315.SignerInfos()
+
+ if len(self.sha1) > 0:
+ signerInfos[len(signerInfos)] = self.buildSignerInfo(
+ certificate, pykey.HASH_SHA1, self.sha1
+ )
+ if len(self.sha256) > 0:
+ signerInfos[len(signerInfos)] = self.buildSignerInfo(
+ certificate, pykey.HASH_SHA256, self.sha256
+ )
+ signedData["signerInfos"] = signerInfos
+
+ encoded = encoder.encode(signedData)
+ anyTag = univ.Any(encoded).subtype(
+ explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)
+ )
+
+ contentInfo["content"] = anyTag
+ return encoder.encode(contentInfo)
+
+ def toPEM(self):
+ output = "-----BEGIN PKCS7-----"
+ der = self.toDER()
+ b64 = base64.b64encode(der)
+ while b64:
+ output += "\n" + b64[:64]
+ b64 = b64[64:]
+ output += "\n-----END PKCS7-----\n"
+ return output
+
+
+# When run as a standalone program, this will read a specification from
+# stdin and output the certificate as PEM to stdout.
+if __name__ == "__main__":
+ print(CMS(sys.stdin).toPEM())
diff --git a/security/manager/tools/pyct.py b/security/manager/tools/pyct.py
new file mode 100644
index 0000000000..8f9d61b72b
--- /dev/null
+++ b/security/manager/tools/pyct.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Helper library for creating a Signed Certificate Timestamp given the
+details of a signing key, when to sign, and the certificate data to
+sign. Currently only supports precert_entry types. See RFC 6962.
+"""
+
+import binascii
+import calendar
+import hashlib
+from struct import pack
+
+import pykey
+from pyasn1.codec.der import encoder
+
+
+class InvalidKeyError(Exception):
+ """Helper exception to handle unknown key types."""
+
+ def __init__(self, key):
+ self.key = key
+
+ def __str__(self):
+ return 'Invalid key: "%s"' % str(self.key)
+
+
+class SCT(object):
+ """SCT represents a Signed Certificate Timestamp."""
+
+ def __init__(self, key, date, tbsCertificate, issuerKey):
+ self.key = key
+ self.timestamp = calendar.timegm(date.timetuple()) * 1000
+ self.tbsCertificate = tbsCertificate
+ self.issuerKey = issuerKey
+
+ def signAndEncode(self):
+ """Returns a signed and encoded representation of the SCT as a
+ string."""
+ # The signature is over the following data:
+ # sct_version (one 0 byte)
+ # signature_type (one 0 byte)
+ # timestamp (8 bytes, milliseconds since the epoch)
+ # entry_type (two bytes [0, 1] - currently only precert_entry is
+ # supported)
+ # signed_entry (bytes of PreCert)
+ # extensions (2-byte-length-prefixed, currently empty (so two 0
+ # bytes))
+ # A PreCert is:
+ # issuer_key_hash (32 bytes of SHA-256 hash of the issuing
+ # public key, as DER-encoded SPKI)
+ # tbs_certificate (3-byte-length-prefixed data)
+ timestamp = pack("!Q", self.timestamp)
+ hasher = hashlib.sha256()
+ hasher.update(encoder.encode(self.issuerKey.asSubjectPublicKeyInfo()))
+ issuer_key_hash = hasher.digest()
+ len_prefix = pack("!L", len(self.tbsCertificate))[1:]
+ data = (
+ b"\0\0"
+ + timestamp
+ + b"\0\1"
+ + issuer_key_hash
+ + len_prefix
+ + self.tbsCertificate
+ + b"\0\0"
+ )
+ if isinstance(self.key, pykey.ECCKey):
+ signatureByte = b"\3"
+ elif isinstance(self.key, pykey.RSAKey):
+ signatureByte = b"\1"
+ else:
+ raise InvalidKeyError(self.key)
+ # sign returns a hex string like "'<hex bytes>'H", but we want
+ # bytes here
+ hexSignature = self.key.sign(data, pykey.HASH_SHA256)
+ signature = binascii.unhexlify(hexSignature[1:-2])
+ # The actual data returned is the following:
+ # sct_version (one 0 byte)
+ # id (32 bytes of SHA-256 hash of the signing key, as
+ # DER-encoded SPKI)
+ # timestamp (8 bytes, milliseconds since the epoch)
+ # extensions (2-byte-length-prefixed data, currently
+ # empty)
+ # hash (one 4 byte representing sha256)
+ # signature (one byte - 1 for RSA and 3 for ECDSA)
+ # signature (2-byte-length-prefixed data)
+ hasher = hashlib.sha256()
+ hasher.update(encoder.encode(self.key.asSubjectPublicKeyInfo()))
+ key_id = hasher.digest()
+ signature_len_prefix = pack("!H", len(signature))
+ return (
+ b"\0"
+ + key_id
+ + timestamp
+ + b"\0\0\4"
+ + signatureByte
+ + signature_len_prefix
+ + signature
+ )
diff --git a/security/manager/tools/pykey.py b/security/manager/tools/pykey.py
new file mode 100755
index 0000000000..fec8021c26
--- /dev/null
+++ b/security/manager/tools/pykey.py
@@ -0,0 +1,957 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Reads a key specification from stdin or a file and outputs a
+PKCS #8 file representing the (private) key. Also provides
+methods for signing data and representing the key as a subject
+public key info for use with pyasn1.
+
+The key specification format is as follows:
+
+default: a 2048-bit RSA key
+alternate: a different 2048-bit RSA key
+ev: a 2048-bit RSA key that, when combined with the right pycert
+ specification, results in a certificate that is enabled for
+ extended validation in debug Firefox (see ExtendedValidation.cpp).
+evRSA2040: a 2040-bit RSA key that, when combined with the right pycert
+ specification, results in a certificate that is enabled for
+ extended validation in debug Firefox.
+rsa2040: a 2040-bit RSA key
+rsa1024: a 1024-bit RSA key
+rsa1016: a 1016-bit RSA key
+secp256k1: an ECC key on the curve secp256k1
+secp244r1: an ECC key on the curve secp244r1
+secp256r1: an ECC key on the curve secp256r1
+secp384r1: an ECC key on the curve secp384r1
+secp521r1: an ECC key on the curve secp521r1
+"""
+
+import base64
+import binascii
+import hashlib
+import math
+import sys
+
+import ecdsa
+import rsa
+import six
+from pyasn1.codec.der import encoder
+from pyasn1.type import namedtype, tag, univ
+from pyasn1_modules import rfc2459
+
+# "constants" to make it easier for consumers to specify hash algorithms
+HASH_MD5 = "hash:md5"
+HASH_SHA1 = "hash:sha1"
+HASH_SHA256 = "hash:sha256"
+HASH_SHA384 = "hash:sha384"
+HASH_SHA512 = "hash:sha512"
+
+
+# NOTE: With bug 1621441 we migrated from one library for ecdsa to another.
+# These libraries differ somewhat in terms of functionality and interface. In
+# order to ensure there are no diffs and that the generated signatures are
+# exactly the same between the two libraries, we need to patch some stuff in.
+
+
+def _gen_k(curve):
+ # This calculation is arbitrary, but it matches what we were doing pre-
+ # bug 1621441 (see the above NOTE). Crucially, this generation of k is
+ # non-random; the ecdsa library exposes an option to deterministically
+ # generate a value of k for us, but it doesn't match up to what we were
+ # doing before so we have to inject a custom value.
+ num_bytes = int(math.log(curve.order - 1, 2) + 1) // 8 + 8
+ entropy = int.from_bytes(b"\04" * num_bytes, byteorder="big")
+ p = curve.curve.p()
+ return (entropy % (p - 1)) + 1
+
+
+# As above, the library has built-in logic for truncating digests that are too
+# large, but they use a slightly different technique than our previous library.
+# Re-implement that logic here.
+def _truncate_digest(digest, curve):
+ i = int.from_bytes(digest, byteorder="big")
+ p = curve.curve.p()
+ while i > p:
+ i >>= 1
+ return i.to_bytes(math.ceil(i.bit_length() / 8), byteorder="big")
+
+
+def byteStringToHexifiedBitString(string):
+ """Takes a string of bytes and returns a hex string representing
+ those bytes for use with pyasn1.type.univ.BitString. It must be of
+ the form "'<hex bytes>'H", where the trailing 'H' indicates to
+ pyasn1 that the input is a hex string."""
+ return "'%s'H" % six.ensure_binary(string).hex()
+
+
+class UnknownBaseError(Exception):
+ """Base class for handling unexpected input in this module."""
+
+ def __init__(self, value):
+ super(UnknownBaseError, self).__init__()
+ self.value = value
+ self.category = "input"
+
+ def __str__(self):
+ return 'Unknown %s type "%s"' % (self.category, repr(self.value))
+
+
+class UnknownKeySpecificationError(UnknownBaseError):
+ """Helper exception type to handle unknown key specifications."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "key specification"
+
+
+class UnknownHashAlgorithmError(UnknownBaseError):
+ """Helper exception type to handle unknown key specifications."""
+
+ def __init__(self, value):
+ UnknownBaseError.__init__(self, value)
+ self.category = "hash algorithm"
+
+
+class UnsupportedHashAlgorithmError(Exception):
+ """Helper exception type for unsupported hash algorithms."""
+
+ def __init__(self, value):
+ super(UnsupportedHashAlgorithmError, self).__init__()
+ self.value = value
+
+ def __str__(self):
+ return 'Unsupported hash algorithm "%s"' % repr(self.value)
+
+
+class RSAPublicKey(univ.Sequence):
+ """Helper type for encoding an RSA public key"""
+
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType("N", univ.Integer()),
+ namedtype.NamedType("E", univ.Integer()),
+ )
+
+
+class RSAPrivateKey(univ.Sequence):
+ """Helper type for encoding an RSA private key"""
+
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType("version", univ.Integer()),
+ namedtype.NamedType("modulus", univ.Integer()),
+ namedtype.NamedType("publicExponent", univ.Integer()),
+ namedtype.NamedType("privateExponent", univ.Integer()),
+ namedtype.NamedType("prime1", univ.Integer()),
+ namedtype.NamedType("prime2", univ.Integer()),
+ namedtype.NamedType("exponent1", univ.Integer()),
+ namedtype.NamedType("exponent2", univ.Integer()),
+ namedtype.NamedType("coefficient", univ.Integer()),
+ )
+
+
+class ECPrivateKey(univ.Sequence):
+ """Helper type for encoding an EC private key
+ ECPrivateKey ::= SEQUENCE {
+ version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ privateKey OCTET STRING,
+ parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ (NOTE: parameters field is not supported)
+ publicKey [1] BIT STRING OPTIONAL
+ }"""
+
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType("version", univ.Integer()),
+ namedtype.NamedType("privateKey", univ.OctetString()),
+ namedtype.OptionalNamedType(
+ "publicKey",
+ univ.BitString().subtype(
+ explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)
+ ),
+ ),
+ )
+
+
+class ECPoint(univ.Sequence):
+ """Helper type for encoding a EC point"""
+
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType("x", univ.Integer()),
+ namedtype.NamedType("y", univ.Integer()),
+ )
+
+
+class PrivateKeyInfo(univ.Sequence):
+ """Helper type for encoding a PKCS #8 private key info"""
+
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType("version", univ.Integer()),
+ namedtype.NamedType("privateKeyAlgorithm", rfc2459.AlgorithmIdentifier()),
+ namedtype.NamedType("privateKey", univ.OctetString()),
+ )
+
+
+class RSAKey(object):
+ # For reference, when encoded as a subject public key info, the
+ # base64-encoded sha-256 hash of this key is
+ # VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=
+ sharedRSA_N = int(
+ "00ba8851a8448e16d641fd6eb6880636103d3c13d9eae4354ab4ecf56857"
+ "6c247bc1c725a8e0d81fbdb19c069b6e1a86f26be2af5a756b6a6471087a"
+ "a55aa74587f71cd5249c027ecd43fc1e69d038202993ab20c349e4dbb94c"
+ "c26b6c0eed15820ff17ead691ab1d3023a8b2a41eea770e00f0d8dfd660b"
+ "2bb02492a47db988617990b157903dd23bc5e0b8481fa837d38843ef2716"
+ "d855b7665aaa7e02902f3a7b10800624cc1c6c97ad96615bb7e29612c075"
+ "31a30c91ddb4caf7fcad1d25d309efb9170ea768e1b37b2f226f69e3b48a"
+ "95611dee26d6259dab91084e36cb1c24042cbf168b2fe5f18f991731b8b3"
+ "fe4923fa7251c431d503acda180a35ed8d",
+ 16,
+ )
+ sharedRSA_E = 65537
+ sharedRSA_D = int(
+ "009ecbce3861a454ecb1e0fe8f85dd43c92f5825ce2e997884d0e1a949da"
+ "a2c5ac559b240450e5ac9fe0c3e31c0eefa6525a65f0c22194004ee1ab46"
+ "3dde9ee82287cc93e746a91929c5e6ac3d88753f6c25ba5979e73e5d8fb2"
+ "39111a3cdab8a4b0cdf5f9cab05f1233a38335c64b5560525e7e3b92ad7c"
+ "7504cf1dc7cb005788afcbe1e8f95df7402a151530d5808346864eb370aa"
+ "79956a587862cb533791307f70d91c96d22d001a69009b923c683388c9f3"
+ "6cb9b5ebe64302041c78d908206b87009cb8cabacad3dbdb2792fb911b2c"
+ "f4db6603585be9ae0ca3b8e6417aa04b06e470ea1a3b581ca03a6781c931"
+ "5b62b30e6011f224725946eec57c6d9441",
+ 16,
+ )
+ sharedRSA_P = int(
+ "00dd6e1d4fffebf68d889c4d114cdaaa9caa63a59374286c8a5c29a717bb"
+ "a60375644d5caa674c4b8bc7326358646220e4550d7608ac27d55b6db74f"
+ "8d8127ef8fa09098b69147de065573447e183d22fe7d885aceb513d9581d"
+ "d5e07c1a90f5ce0879de131371ecefc9ce72e9c43dc127d238190de81177"
+ "3ca5d19301f48c742b",
+ 16,
+ )
+ sharedRSA_Q = int(
+ "00d7a773d9ebc380a767d2fec0934ad4e8b5667240771acdebb5ad796f47"
+ "8fec4d45985efbc9532968289c8d89102fadf21f34e2dd4940eba8c09d6d"
+ "1f16dcc29729774c43275e9251ddbe4909e1fd3bf1e4bedf46a39b8b3833"
+ "28ef4ae3b95b92f2070af26c9e7c5c9b587fedde05e8e7d86ca57886fb16"
+ "5810a77b9845bc3127",
+ 16,
+ )
+ sharedRSA_exp1 = int(
+ "0096472b41a610c0ade1af2266c1600e3671355ba42d4b5a0eb4e9d7eb35"
+ "81400ba5dd132cdb1a5e9328c7bbc0bbb0155ea192972edf97d12751d8fc"
+ "f6ae572a30b1ea309a8712dd4e33241db1ee455fc093f5bc9b592d756e66"
+ "21474f32c07af22fb275d340792b32ba2590bbb261aefb95a258eea53765"
+ "5315be9c24d191992d",
+ 16,
+ )
+ sharedRSA_exp2 = int(
+ "28b450a7a75a856413b2bda6f7a63e3d964fb9ecf50e3823ef6cc8e8fa26"
+ "ee413f8b9d1205540f12bbe7a0c76828b7ba65ad83cca4d0fe2a220114e1"
+ "b35d03d5a85bfe2706bd50fce6cfcdd571b46ca621b8ed47d605bbe765b0"
+ "aa4a0665ac25364da20154032e1204b8559d3e34fb5b177c9a56ff93510a"
+ "5a4a6287c151de2d",
+ 16,
+ )
+ sharedRSA_coef = int(
+ "28067b9355801d2ef52dfa96d8adb589673cf8ee8a9c6ff72aeeabe9ef6b"
+ "e58a4f4abf05f788947dc851fdaa34542147a71a246bfb054ee76aa346ab"
+ "cd2692cfc9e44c51e6f069c735e073ba019f6a7214961c91b26871caeabf"
+ "8f064418a02690e39a8d5ff3067b7cdb7f50b1f53418a703966c4fc774bf"
+ "7402af6c43247f43",
+ 16,
+ )
+
+ # For reference, when encoded as a subject public key info, the
+ # base64-encoded sha-256 hash of this key is
+ # MQj2tt1yGAfwFpWETYUCVrZxk2CD2705NKBQUlAaKJI=
+ alternateRSA_N = int(
+ "00c175c65266099f77082a6791f1b876c37f5ce538b06c4acd22b1cbd46f"
+ "a65ada2add41c8c2498ac4a3b3c1f61487f41b698941bd80a51c3c120244"
+ "c584a4c4483305e5138c0106cf08be9a862760bae6a2e8f36f23c5d98313"
+ "b9dfaf378345dace51d4d6dcd2a6cb3cc706ebcd3070ec98cce40aa591d7"
+ "295a7f71c5be66691d2b2dfec84944590bc5a3ea49fd93b1d753405f1773"
+ "7699958666254797ed426908880811422069988a43fee48ce68781dd22b6"
+ "a69cd28375131f932b128ce286fa7d251c062ad27ef016f187cdd54e832b"
+ "35b8930f74ba90aa8bc76167242ab1fd6d62140d18c4c0b8c68fc3748457"
+ "324ad7de86e6552f1d1e191d712168d3bb",
+ 16,
+ )
+ alternateRSA_E = 65537
+ alternateRSA_D = int(
+ "7e3f6d7cb839ef66ae5d7dd92ff5410bb341dc14728d39034570e1a37079"
+ "0f30f0681355fff41e2ad4e9a9d9fcebfbd127bdfab8c00affb1f3cea732"
+ "7ead47aa1621f2ac1ee14ca02f04b3b2786017980b181a449d03b03e69d1"
+ "12b83571e55434f012056575d2832ed6731dce799e37c83f6d51c55ab71e"
+ "b58015af05e1af15c747603ef7f27d03a6ff049d96bbf854c1e4e50ef5b0"
+ "58d0fb08180e0ac7f7be8f2ff1673d97fc9e55dba838077bbf8a7cff2962"
+ "857785269cd9d5bad2b57469e4afcd33c4ca2d2f699f11e7c8fbdcd484f0"
+ "8d8efb8a3cb8a972eb24bed972efaae4bb712093e48fe94a46eb629a8750"
+ "78c4021a9a2c93c9a70390e9d0a54401",
+ 16,
+ )
+ alternateRSA_P = int(
+ "00e63fc725a6ba76925a7ff8cb59c4f56dd7ec83fe85bf1f53e11cac9a81"
+ "258bcfc0ae819077b0f2d1477aaf868de6a8ecbeaf7bb22b196f2a9ad82d"
+ "3286f0d0cc29de719e5f2be8e509b7284d5963edd362f927887a4c4a8979"
+ "9d340d51b301ac7601ab27179024fcaadd38bf6522af63eb16461ec02a7f"
+ "27b06fe09ddda7c0a1",
+ 16,
+ )
+ alternateRSA_Q = int(
+ "00d718b1fe9f8f99f00e832ae1fbdc6fe2ab27f34e049c498010fa0eb708"
+ "4852182346083b5c96c3eee5592c014a410c6b930b165c13b5c26aa32eac"
+ "6e7c925a8551c25134f2f4a72c6421f19a73148a0edfaba5d3a6888b35cb"
+ "a18c00fd38ee5aaf0b545731d720761bbccdee744a52ca415e98e4de01cd"
+ "fe764c1967b3e8cadb",
+ 16,
+ )
+ alternateRSA_exp1 = int(
+ "01e5aca266c94a88d22e13c2b92ea247116c657a076817bdfd30db4b3a9d"
+ "3095b9a4b6749647e2f84e7a784fc7838b08c85971cf7a036fa30e3b91c3"
+ "c4d0df278f80c1b6e859d8456adb137defaa9f1f0ac5bac9a9184fd4ea27"
+ "9d722ea626f160d78aad7bc83845ccb29df115c83f61b7622b99bd439c60"
+ "9b5790a63c595181",
+ 16,
+ )
+ alternateRSA_exp2 = int(
+ "0080cc45d10d2484ee0d1297fc07bf80b3beff461ea27e1f38f371789c3a"
+ "f66b4a0edd2192c227791db4f1c77ae246bf342f31856b0f56581b58a95b"
+ "1131c0c5396db2a8c3c6f39ea2e336bc205ae6a2a0b36869fca98cbba733"
+ "cf01319a6f9bb26b7ca23d3017fc551cd8da8afdd17f6fa2e30d34868798"
+ "1cd6234d571e90b7df",
+ 16,
+ )
+ alternateRSA_coef = int(
+ "6f77c0c1f2ae7ac169561cca499c52bdfbe04cddccdbdc12aec5a85691e8"
+ "594b7ee29908f30e7b96aa6254b80ed4aeec9b993782bdfc79b69d8d58c6"
+ "8870fa4be1bc0c3527288c5c82bb4aebaf15edff110403fc78e6ace6a828"
+ "27bf42f0cfa751e507651c5638db9393dd23dd1f6b295151de44b77fe55a"
+ "7b0df271e19a65c0",
+ 16,
+ )
+
+ evRSA_N = int(
+ "00b549895c9d00108d11a1f99f87a9e3d1a5db5dfaecf188da57bf641368"
+ "8f2ce4722cff109038c17402c93a2a473dbd286aed3fdcd363cf5a291477"
+ "01bdd818d7615bf9356bd5d3c8336aaa8c0971368a06c3cd4461b93e5142"
+ "4e1744bb2eaad46aab38ce196821961f87714a1663693f09761cdf4d6ba1"
+ "25eacec7be270d388f789f6cdf78ae3144ed28c45e79293863a7a22a4898"
+ "0a36a40e72d579c9b925dff8c793362ffd6897a7c1754c5e97c967c3eadd"
+ "1aae8aa2ccce348a0169b80e28a2d70c1a960c6f335f2da09b9b643f5abf"
+ "ba49e8aaa981e960e27d87480bdd55dd9417fa18509fbb554ccf81a4397e"
+ "8ba8128a34bdf27865c189e5734fb22905",
+ 16,
+ )
+ evRSA_E = 65537
+ evRSA_D = int(
+ "00983d54f94d6f4c76eb23d6f93d78523530cf73b0d16254c6e781768d45"
+ "f55681d1d02fb2bd2aac6abc1c389860935c52a0d8f41482010394778314"
+ "1d864bff30803638a5c0152570ae9d18f3d8ca163efb475b0dddf32e7e16"
+ "ec7565e6bb5e025c41c5c66e57a03cede554221f83045347a2c4c451c3dc"
+ "e476b787ce0c057244be9e04ef13118dbbb3d5e0a6cc87029eafd4a69ed9"
+ "b14759b15e39d8a9884e56f54d2f9ab013f0d15f318a9ab6b2f73d1ec3c9"
+ "fe274ae89431a10640be7899b0011c5e5093a1834708689de100634dabde"
+ "60fbd6aaefa3a33df34a1f36f60c043036b748d1c9ee98c4031a0afec60e"
+ "fda0a990be524f5614eac4fdb34a52f951",
+ 16,
+ )
+ evRSA_P = int(
+ "00eadc2cb33e5ff1ca376bbd95bd6a1777d2cf4fac47545e92d11a6209b9"
+ "d5e4ded47834581c169b3c884742a09ea187505c1ca55414d8d25b497632"
+ "d5ec2aaa05233430fad49892777a7d68e038f561a3b8969e60b0a263defb"
+ "fda48a9b0ff39d95bc88b15267c8ade97b5107948e41e433249d87f7db10"
+ "9d5d74584d86bcc1d7",
+ 16,
+ )
+ evRSA_Q = int(
+ "00c59ae576a216470248d944a55b9e9bf93299da341ec56e558eba821abc"
+ "e1bf57b79cf411d2904c774f9dba1f15185f607b0574a08205d6ec28b66a"
+ "36d634232eaaf2fea37561abaf9d644b68db38c9964cb8c96ec0ac61eba6"
+ "4d05b446542f423976f5acde4ecc95536d2df578954f93f0cfd9c58fb78b"
+ "a2a76dd5ac284dc883",
+ 16,
+ )
+ evRSA_exp1 = int(
+ "00c1d2ef3906331c52aca64811f9fe425beb2898322fb3db51032ce8d7e9"
+ "fc32240be92019cf2480fcd5e329837127118b2a59a1bfe06c883e3a4447"
+ "f3f031cd9aebd0b8d368fc79740d2cce8eadb324df7f091eafe1564361d5"
+ "4920b01b0471230e5e47d93f8ed33963c517bc4fc78f6d8b1f9eba85bcce"
+ "db7033026508db6285",
+ 16,
+ )
+ evRSA_exp2 = int(
+ "008521b8db5694dfbe804a315f9efc9b65275c5490acf2a3456d65e6e610"
+ "bf9f647fc67501d4f5772f232ac70ccdef9fc2a6dfa415c7c41b6afc7af9"
+ "d07c3ca03f7ed93c09f0b99f2c304434322f1071709bbc1baa4c91575fa6"
+ "a959e07d4996956d95e22b57938b6e47c8d51ffedfc9bf888ce0d1a3e42b"
+ "65a89bed4b91d3e5f5",
+ 16,
+ )
+ evRSA_coef = int(
+ "00dc497b06b920c8be0b0077b798e977eef744a90ec2c5d7e6cbb22448fa"
+ "c72da81a33180e0d8a02e831460c7fc7fd3a612f7b9930b61b799f8e908e"
+ "632e9ba0409b6aa70b03a3ba787426263b5bd5843df8476edb5d14f6a861"
+ "3ebaf5b9cd5ca42f5fbd2802e08e4e49e5709f5151510caa5ab2c1c6eb3e"
+ "fe9295d16e8c25c916",
+ 16,
+ )
+
+ evRSA2040_N = int(
+ "00ca7020dc215f57914d343fae4a015111697af997a5ece91866499fc23f"
+ "1b88a118cbd30b10d91c7b9a0d4ee8972fcae56caf57f25fc1275a2a4dbc"
+ "b982428c32ef587bf2387410330a0ffb16b8029bd783969ef675f6de38c1"
+ "8f67193cb6c072f8b23d0b3374112627a57b90055771d9e62603f53788d7"
+ "f63afa724f5d108096df31f89f26b1eb5f7c4357980e008fcd55d827dd26"
+ "2395ca2f526a07897cc40c593b38716ebc0caa596719c6f29ac9b73a7a94"
+ "4748a3aa3e09e9eb4d461ea0027e540926614728b9d243975cf9a0541bef"
+ "d25e76b51f951110b0e7644fc7e38441791b6d2227384cb8004e23342372"
+ "b1cf5cc3e73e31b7bbefa160e6862ebb",
+ 16,
+ )
+ evRSA2040_E = 65537
+ evRSA2040_D = int(
+ "00b2db74bce92362abf72955a638ae8720ba3033bb7f971caf39188d7542"
+ "eaa1c1abb5d205b1e2111f4791c08911a2e141e8cfd7054702d23100b564"
+ "2c06e1a31b118afd1f9a2f396cced425c501d91435ca8656766ced2b93bb"
+ "b8669fce9bacd727d1dacb3dafabc3293e35389eef8ea0b58e1aeb1a20e6"
+ "a61f9fcd453f7567fe31d123b616a26fef4df1d6c9f7490111d028eefd1d"
+ "972045b1a242273dd7a67ebf111db2741a5a93c7b2289cc4a236f5a99a6e"
+ "c7a8206fdae1c1d04bdbb1980d4a298c5a17dae4186474a5f7835d882bce"
+ "f24aef4ed6f149f94d96c9f7d78e647fc778a9017ff208d3b4a1768b1821"
+ "62102cdab032fabbab38d5200a324649",
+ 16,
+ )
+ evRSA2040_P = int(
+ "0f3844d0d4d4d6a21acd76a6fc370b8550e1d7ec5a6234172e790f0029ae"
+ "651f6d5c59330ab19802b9d7a207de7a1fb778e3774fdbdc411750633d8d"
+ "1b3fe075006ffcfd1d10e763c7a9227d2d5f0c2dade1c9e659c350a159d3"
+ "6bb986f12636d4f9942b288bc0fe21da8799477173144249ca2e389e6c5c"
+ "25aa78c8cad7d4df",
+ 16,
+ )
+ evRSA2040_Q = int(
+ "0d4d0bedd1962f07a1ead6b23a4ed67aeaf1270f052a6d29ba074945c636"
+ "1a5c4f8f07bf859e067aed3f4e6e323ef2aa8a6acd340b0bdc7cfe4fd329"
+ "e3c97f870c7f7735792c6aa9d0f7e7542a28ed6f01b0e55a2b8d9c24a65c"
+ "6da314c95484f5c7c3954a81bb016b07ed17ee9b06039695bca059a79f8d"
+ "c2423d328d5265a5",
+ 16,
+ )
+ evRSA2040_exp1 = int(
+ "09f29a2ff05be8a96d614ba31b08935420a86c6bc42b99a6692ea0da5763"
+ "f01e596959b7ddce73ef9c2e4f6e5b40710887500d44ba0c3cd3132cba27"
+ "475f39c2df7552e2d123a2497a4f97064028769a48a3624657f72bf539f3"
+ "d0de234feccd3be8a0aa90c6bf6e9b0bed43070a24d061ff3ed1751a3ef2"
+ "ff7f6b90b9dbd5fb",
+ 16,
+ )
+ evRSA2040_exp2 = int(
+ "01a659e170cac120a03be1cf8f9df1caa353b03593bd7476e5853bd874c2"
+ "87388601c6c341ce9d1d284a5eef1a3a669d32b816a5eaecd8b7844fe070"
+ "64b9bca0c2b318d540277b3f7f1510d386bb36e03b04771e5d229e88893e"
+ "13b753bfb94518bb638e2404bd6e6a993c1668d93fc0b82ff08aaf34347d"
+ "3fe8397108c87ca5",
+ 16,
+ )
+ evRSA2040_coef = int(
+ "040257c0d4a21c0b9843297c65652db66304fb263773d728b6abfa06d37a"
+ "c0ca62c628023e09e37dc0a901e4ce1224180e2582a3aa4b6a1a7b98e2bd"
+ "70077aec14ac8ab66a755c71e0fc102471f9bbc1b46a95aa0b645f2c38e7"
+ "6450289619ea3f5e8ae61037bffcf8249f22aa4e76e2a01909f3feb290ce"
+ "93edf57b10ebe796",
+ 16,
+ )
+
+ rsa2040_N = int(
+ "00bac0652fdfbc0055882ffbaeaceec88fa2d083c297dd5d40664dd3d90f"
+ "52f9aa02bd8a50fba16e0fd991878ef475f9b350d9f8e3eb2abd717ce327"
+ "b09788531f13df8e3e4e3b9d616bb8a41e5306eed2472163161051180127"
+ "6a4eb66f07331b5cbc8bcae7016a8f9b3d4f2ac4553c624cf5263bcb348e"
+ "8840de6612870960a792191b138fb217f765cec7bff8e94f16b39419bf75"
+ "04c59a7e4f79bd6d173e9c7bf3d9d2a4e73cc180b0590a73d584fb7fc9b5"
+ "4fa544607e53fc685c7a55fd44a81d4142b6af51ea6fa6cea52965a2e8c5"
+ "d84f3ca024d6fbb9b005b9651ce5d9f2ecf40ed404981a9ffc02636e311b"
+ "095c6332a0c87dc39271b5551481774b",
+ 16,
+ )
+ rsa2040_E = 65537
+ rsa2040_D = int(
+ "603db267df97555cbed86b8df355034af28f1eb7f3e7829d239bcc273a7c"
+ "7a69a10be8f21f1b6c4b02c6bae3731c3158b5bbff4605f57ab7b7b2a0cb"
+ "a2ec005a2db5b1ea6e0aceea5bc745dcd2d0e9d6b80d7eb0ea2bc08127bc"
+ "e35fa50c42cc411871ba591e23ba6a38484a33eff1347f907ee9a5a92a23"
+ "11bb0b435510020f78e3bb00099db4d1182928096505fcba84f3ca1238fd"
+ "1eba5eea1f391bbbcc5424b168063fc17e1ca6e1912ccba44f9d0292308a"
+ "1fedb80612529b39f59d0a3f8180b5ba201132197f93a5815ded938df8e7"
+ "d93c9b15766588f339bb59100afda494a7e452d7dd4c9a19ce2ec3a33a18"
+ "b20f0b4dade172bee19f26f0dcbe41",
+ 16,
+ )
+ rsa2040_P = int(
+ "0ec3869cb92d406caddf7a319ab29448bc505a05913707873361fc5b986a"
+ "499fb65eeb815a7e37687d19f128087289d9bb8818e7bcca502c4900ad9a"
+ "ece1179be12ff3e467d606fc820ea8f07ac9ebffe2236e38168412028822"
+ "3e42dbe68dfd972a85a6447e51695f234da7911c67c9ab9531f33df3b994"
+ "32d4ee88c9a4efbb",
+ 16,
+ )
+ rsa2040_Q = int(
+ "0ca63934549e85feac8e0f5604303fd1849fe88af4b7f7e1213283bbc7a2"
+ "c2a509f9273c428c68de3db93e6145f1b400bd6d4a262614e9043ad362d4"
+ "eba4a6b995399c8934a399912199e841d8e8dbff0489f69e663796730b29"
+ "80530b31cb70695a21625ea2adccc09d930516fa872211a91e22dd89fd9e"
+ "b7da8574b72235b1",
+ 16,
+ )
+ rsa2040_exp1 = int(
+ "0d7d3a75e17f65f8a658a485c4095c10a4f66979e2b73bca9cf8ef21253e"
+ "1facac6d4791f58392ce8656f88f1240cc90c29653e3100c6d7a38ed44b1"
+ "63b339e5f3b6e38912126c69b3ceff2e5192426d9649b6ffca1abb75d2ba"
+ "2ed6d9a26aa383c5973d56216ff2edb90ccf887742a0f183ac92c94cf187"
+ "657645c7772d9ad7",
+ 16,
+ )
+ rsa2040_exp2 = int(
+ "03f550194c117f24bea285b209058032f42985ff55acebe88b16df9a3752"
+ "7b4e61dc91a68dbc9a645134528ce5f248bda2893c96cb7be79ee73996c7"
+ "c22577f6c2f790406f3472adb3b211b7e94494f32c5c6fcc0978839fe472"
+ "4c31b06318a2489567b4fca0337acb1b841227aaa5f6c74800a2306929f0"
+ "2ce038bad943df41",
+ 16,
+ )
+ rsa2040_coef = int(
+ "080a7dbfa8c2584814c71664c56eb62ce4caf16afe88d4499159d674774a"
+ "3a3ecddf1256c02fc91525c527692422d0aba94e5c41ee12dc71bb66f867"
+ "9fa17e096f28080851ba046eb31885c1414e8985ade599d907af17453d1c"
+ "caea2c0d06443f8367a6be154b125e390ee0d90f746f08801dd3f5367f59"
+ "fba2e5a67c05f375",
+ 16,
+ )
+
+ rsa1024_N = int(
+ "00d3a97440101eba8c5df9503e6f935eb52ffeb3ebe9d0dc5cace26f973c"
+ "a94cbc0d9c31d66c0c013bce9c82d0d480328df05fb6bcd7990a5312ddae"
+ "6152ad6ee61c8c1bdd8663c68bd36224a9882ae78e89f556dfdbe6f51da6"
+ "112cbfc27c8a49336b41afdb75321b52b24a7344d1348e646351a551c757"
+ "1ccda0b8fe35f61a75",
+ 16,
+ )
+ rsa1024_E = 65537
+ rsa1024_D = int(
+ "5b6708e185548fc07ff062dba3792363e106ff9177d60ee3227162391024"
+ "1813f958a318f26db8b6a801646863ebbc69190d6c2f5e7723433e99666d"
+ "76b3987892cd568f1f18451e8dc05477c0607ee348380ebb7f4c98d0c036"
+ "a0260bc67b2dab46cbaa4ce87636d839d8fddcbae2da3e02e8009a21225d"
+ "d7e47aff2f82699d",
+ 16,
+ )
+ rsa1024_P = int(
+ "00fcdee570323e8fc399dbfc63d8c1569546fb3cd6886c628668ab1e1d0f"
+ "ca71058febdf76d702970ad6579d80ac2f9521075e40ef8f3f39983bd819"
+ "07e898bad3",
+ 16,
+ )
+ rsa1024_Q = int(
+ "00d64801c955b4eb75fbae230faa8b28c9cc5e258be63747ff5ac8d2af25"
+ "3e9f6d6ce03ea2eb13ae0eb32572feb848c32ca00743635374338fedacd8"
+ "c5885f7897",
+ 16,
+ )
+ rsa1024_exp1 = int(
+ "76c0526d5b1b28368a75d5d42a01b9a086e20b9310241e2cd2d0b166a278"
+ "c694ff1e9d25d9193d47789b52bb0fa194de1af0b77c09007f12afdfeef9"
+ "58d108c3",
+ 16,
+ )
+ rsa1024_exp2 = int(
+ "008a41898d8b14217c4d782cbd15ef95d0a660f45ed09a4884f4e170367b"
+ "946d2f20398b907896890e88fe17b54bd7febe133ebc7720c86fe0649cca"
+ "7ca121e05f",
+ 16,
+ )
+ rsa1024_coef = int(
+ "22db133445f7442ea2a0f582031ee214ff5f661972986f172651d8d6b4ec"
+ "3163e99bff1c82fe58ec3d075c6d8f26f277020edb77c3ba821b9ba3ae18"
+ "ff8cb2cb",
+ 16,
+ )
+
+ rsa1016_N = int(
+ "00d29bb12fb84fddcd29b3a519cb66c43b8d8f8be545ba79384ce663ed03"
+ "df75991600eb920790d2530cece544db99a71f05896a3ed207165534aa99"
+ "057e47c47e3bc81ada6fa1e12e37268b5046a55268f9dad7ccb485d81a2e"
+ "19d50d4f0b6854acaf6d7be69d9a083136e15afa8f53c1c8c84fc6077279"
+ "dd0e55d7369a5bdd",
+ 16,
+ )
+ rsa1016_E = 65537
+ rsa1016_D = int(
+ "3c4965070bf390c251d5a2c5277c5b5fd0bdee85cad7fe2b27982bb28511"
+ "4a507004036ae1cf8ae54b25e4db39215abd7e903f618c2d8b2f08cc6cd1"
+ "2dbccd72205e4945b6b3df389e5e43de0a148bb2c84e2431fdbe5920b044"
+ "bb272f45ecff0721b7dfb60397fc613a9ea35c22300530cae8f9159c534d"
+ "f3bf0910951901",
+ 16,
+ )
+ rsa1016_P = int(
+ "0f9f17597c85b8051b9c69afb55ef576c996dbd09047d0ccde5b9d60ea5c"
+ "67fe4fac67be803f4b6ac5a3f050f76b966fb14f5cf105761e5ade6dd960"
+ "b183ba55",
+ 16,
+ )
+ rsa1016_Q = int(
+ "0d7b637112ce61a55168c0f9c9386fb279ab40cba0d549336bba65277263"
+ "aac782611a2c81d9b635cf78c40018859e018c5e9006d12e3d2ee6f346e7"
+ "9fa43369",
+ 16,
+ )
+ rsa1016_exp1 = int(
+ "09fd6c9a3ea6e91ae32070f9fc1c210ff9352f97be5d1eeb951bb39681e9"
+ "dc5b672a532221b3d8900c9a9d99b9d0a4e102dc450ca1b87b0b1389de65"
+ "16c0ae0d",
+ 16,
+ )
+ rsa1016_exp2 = int(
+ "0141b832491b7dd4a83308920024c79cae64bd447df883bb4c5672a96bab"
+ "48b7123b34f26324452cdceb17f21e570e347cbe2fd4c2d8f9910eac2cb6"
+ "d895b8c9",
+ 16,
+ )
+ rsa1016_coef = int(
+ "0458dd6aee18c88b2f9b81f1bc3075ae20dc1f9973d20724f20b06043d61"
+ "47c8789d4a07ae88bc82c8438c893e017b13947f62e0b18958a31eb664b1"
+ "9e64d3e0",
+ 16,
+ )
+
+ def __init__(self, specification):
+ if specification == "default":
+ self.RSA_N = self.sharedRSA_N
+ self.RSA_E = self.sharedRSA_E
+ self.RSA_D = self.sharedRSA_D
+ self.RSA_P = self.sharedRSA_P
+ self.RSA_Q = self.sharedRSA_Q
+ self.RSA_exp1 = self.sharedRSA_exp1
+ self.RSA_exp2 = self.sharedRSA_exp2
+ self.RSA_coef = self.sharedRSA_coef
+ elif specification == "alternate":
+ self.RSA_N = self.alternateRSA_N
+ self.RSA_E = self.alternateRSA_E
+ self.RSA_D = self.alternateRSA_D
+ self.RSA_P = self.alternateRSA_P
+ self.RSA_Q = self.alternateRSA_Q
+ self.RSA_exp1 = self.alternateRSA_exp1
+ self.RSA_exp2 = self.alternateRSA_exp2
+ self.RSA_coef = self.alternateRSA_coef
+ elif specification == "ev":
+ self.RSA_N = self.evRSA_N
+ self.RSA_E = self.evRSA_E
+ self.RSA_D = self.evRSA_D
+ self.RSA_P = self.evRSA_P
+ self.RSA_Q = self.evRSA_Q
+ self.RSA_exp1 = self.evRSA_exp1
+ self.RSA_exp2 = self.evRSA_exp2
+ self.RSA_coef = self.evRSA_coef
+ elif specification == "evRSA2040":
+ self.RSA_N = self.evRSA2040_N
+ self.RSA_E = self.evRSA2040_E
+ self.RSA_D = self.evRSA2040_D
+ self.RSA_P = self.evRSA2040_P
+ self.RSA_Q = self.evRSA2040_Q
+ self.RSA_exp1 = self.evRSA2040_exp1
+ self.RSA_exp2 = self.evRSA2040_exp2
+ self.RSA_coef = self.evRSA2040_coef
+ elif specification == "rsa2040":
+ self.RSA_N = self.rsa2040_N
+ self.RSA_E = self.rsa2040_E
+ self.RSA_D = self.rsa2040_D
+ self.RSA_P = self.rsa2040_P
+ self.RSA_Q = self.rsa2040_Q
+ self.RSA_exp1 = self.rsa2040_exp1
+ self.RSA_exp2 = self.rsa2040_exp2
+ self.RSA_coef = self.rsa2040_coef
+ elif specification == "rsa1024":
+ self.RSA_N = self.rsa1024_N
+ self.RSA_E = self.rsa1024_E
+ self.RSA_D = self.rsa1024_D
+ self.RSA_P = self.rsa1024_P
+ self.RSA_Q = self.rsa1024_Q
+ self.RSA_exp1 = self.rsa1024_exp1
+ self.RSA_exp2 = self.rsa1024_exp2
+ self.RSA_coef = self.rsa1024_coef
+ elif specification == "rsa1016":
+ self.RSA_N = self.rsa1016_N
+ self.RSA_E = self.rsa1016_E
+ self.RSA_D = self.rsa1016_D
+ self.RSA_P = self.rsa1016_P
+ self.RSA_Q = self.rsa1016_Q
+ self.RSA_exp1 = self.rsa1016_exp1
+ self.RSA_exp2 = self.rsa1016_exp2
+ self.RSA_coef = self.rsa1016_coef
+ else:
+ raise UnknownKeySpecificationError(specification)
+
+ def toDER(self):
+ privateKeyInfo = PrivateKeyInfo()
+ privateKeyInfo["version"] = 0
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmIdentifier["algorithm"] = rfc2459.rsaEncryption
+ # Directly setting parameters to univ.Null doesn't currently work.
+ nullEncapsulated = encoder.encode(univ.Null())
+ algorithmIdentifier["parameters"] = univ.Any(nullEncapsulated)
+ privateKeyInfo["privateKeyAlgorithm"] = algorithmIdentifier
+ rsaPrivateKey = RSAPrivateKey()
+ rsaPrivateKey["version"] = 0
+ rsaPrivateKey["modulus"] = self.RSA_N
+ rsaPrivateKey["publicExponent"] = self.RSA_E
+ rsaPrivateKey["privateExponent"] = self.RSA_D
+ rsaPrivateKey["prime1"] = self.RSA_P
+ rsaPrivateKey["prime2"] = self.RSA_Q
+ rsaPrivateKey["exponent1"] = self.RSA_exp1
+ rsaPrivateKey["exponent2"] = self.RSA_exp2
+ rsaPrivateKey["coefficient"] = self.RSA_coef
+ rsaPrivateKeyEncoded = encoder.encode(rsaPrivateKey)
+ privateKeyInfo["privateKey"] = univ.OctetString(rsaPrivateKeyEncoded)
+ return encoder.encode(privateKeyInfo)
+
+ def toPEM(self):
+ output = "-----BEGIN PRIVATE KEY-----"
+ der = self.toDER()
+ b64 = six.ensure_text(base64.b64encode(der))
+ while b64:
+ output += "\n" + b64[:64]
+ b64 = b64[64:]
+ output += "\n-----END PRIVATE KEY-----"
+ return output
+
+ def asSubjectPublicKeyInfo(self):
+ """Returns a subject public key info representing
+ this key for use by pyasn1."""
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmIdentifier["algorithm"] = rfc2459.rsaEncryption
+ # Directly setting parameters to univ.Null doesn't currently work.
+ nullEncapsulated = encoder.encode(univ.Null())
+ algorithmIdentifier["parameters"] = univ.Any(nullEncapsulated)
+ spki = rfc2459.SubjectPublicKeyInfo()
+ spki["algorithm"] = algorithmIdentifier
+ rsaKey = RSAPublicKey()
+ rsaKey["N"] = univ.Integer(self.RSA_N)
+ rsaKey["E"] = univ.Integer(self.RSA_E)
+ subjectPublicKey = univ.BitString(
+ byteStringToHexifiedBitString(encoder.encode(rsaKey))
+ )
+ spki["subjectPublicKey"] = subjectPublicKey
+ return spki
+
+ def sign(self, data, hashAlgorithm):
+ """Returns a hexified bit string representing a
+ signature by this key over the specified data.
+ Intended for use with pyasn1.type.univ.BitString"""
+ hashAlgorithmName = None
+ if hashAlgorithm == HASH_MD5:
+ hashAlgorithmName = "MD5"
+ elif hashAlgorithm == HASH_SHA1:
+ hashAlgorithmName = "SHA-1"
+ elif hashAlgorithm == HASH_SHA256:
+ hashAlgorithmName = "SHA-256"
+ elif hashAlgorithm == HASH_SHA384:
+ hashAlgorithmName = "SHA-384"
+ elif hashAlgorithm == HASH_SHA512:
+ hashAlgorithmName = "SHA-512"
+ else:
+ raise UnknownHashAlgorithmError(hashAlgorithm)
+ rsaPrivateKey = rsa.PrivateKey(
+ self.RSA_N, self.RSA_E, self.RSA_D, self.RSA_P, self.RSA_Q
+ )
+ signature = rsa.sign(data, rsaPrivateKey, hashAlgorithmName)
+ return byteStringToHexifiedBitString(signature)
+
+
+ecPublicKey = univ.ObjectIdentifier("1.2.840.10045.2.1")
+secp256k1 = univ.ObjectIdentifier("1.3.132.0.10")
+secp224r1 = univ.ObjectIdentifier("1.3.132.0.33")
+secp256r1 = univ.ObjectIdentifier("1.2.840.10045.3.1.7")
+secp384r1 = univ.ObjectIdentifier("1.3.132.0.34")
+secp521r1 = univ.ObjectIdentifier("1.3.132.0.35")
+
+
+def longToEvenLengthHexString(val):
+ h = format(val, "x")
+ if not len(h) % 2 == 0:
+ h = "0" + h
+ return h
+
+
+class ECCKey(object):
+ secp256k1KeyPair = (
+ "35ee7c7289d8fef7a86afe5da66d8bc2ebb6a8543fd2fead089f45ce7acd0fa6"
+ + "4382a9500c41dad770ffd4b511bf4b492eb1238800c32c4f76c73a3f3294e7c5",
+ "67cebc208a5fa3df16ec2bb34acc59a42ab4abb0538575ca99b92b6a2149a04f",
+ )
+
+ secp224r1KeyPair = (
+ "668d72cca6fd6a1b3557b5366104d84408ecb637f08e8c86bbff82cc"
+ + "00e88f0066d7af63c3298ba377348a1202b03b37fd6b1ff415aa311e",
+ "04389459926c3296c242b83e10a6cd2011c8fe2dae1b772ea5b21067",
+ )
+
+ secp256r1KeyPair = (
+ "4fbfbbbb61e0f8f9b1a60a59ac8704e2ec050b423e3cf72e923f2c4f794b455c"
+ + "2a69d233456c36c4119d0706e00eedc8d19390d7991b7b2d07a304eaa04aa6c0",
+ "2191403d5710bf15a265818cd42ed6fedf09add92d78b18e7a1e9feb95524702",
+ )
+
+ secp384r1KeyPair = (
+ "a1687243362b5c7b1889f379154615a1c73fb48dee863e022915db608e252de4b71"
+ + "32da8ce98e831534e6a9c0c0b09c8d639ade83206e5ba813473a11fa330e05da8c9"
+ + "6e4383fe27873da97103be2888cff002f05af71a1fddcc8374aa6ea9ce",
+ "035c7a1b10d9fafe837b64ad92f22f5ced0789186538669b5c6d872cec3d926122b"
+ + "393772b57602ff31365efe1393246",
+ )
+
+ secp521r1KeyPair = (
+ "014cdc9cacc47941096bc9cc66752ec27f597734fa66c62b792f88c519d6d37f0d1"
+ + "6ea1c483a1827a010b9128e3a08070ca33ef5f57835b7c1ba251f6cc3521dc42b01"
+ + "0653451981b445d343eed3782a35d6cff0ff484f5a883d209f1b9042b726703568b"
+ + "2f326e18b833bdd8aa0734392bcd19501e10d698a79f53e11e0a22bdd2aad90",
+ "014f3284fa698dd9fe1118dd331851cdfaac5a3829278eb8994839de9471c940b85"
+ + "8c69d2d05e8c01788a7d0b6e235aa5e783fc1bee807dcc3865f920e12cf8f2d29",
+ )
+
+ def __init__(self, specification):
+ if specification == "secp256k1":
+ key_pair = self.secp256k1KeyPair
+ self.keyOID = secp256k1
+ self.curve = ecdsa.SECP256k1
+ elif specification == "secp224r1":
+ key_pair = self.secp224r1KeyPair
+ self.keyOID = secp224r1
+ self.curve = ecdsa.NIST224p
+ elif specification == "secp256r1":
+ key_pair = self.secp256r1KeyPair
+ self.keyOID = secp256r1
+ self.curve = ecdsa.NIST256p
+ elif specification == "secp384r1":
+ key_pair = self.secp384r1KeyPair
+ self.keyOID = secp384r1
+ self.curve = ecdsa.NIST384p
+ elif specification == "secp521r1":
+ key_pair = self.secp521r1KeyPair
+ self.keyOID = secp521r1
+ self.curve = ecdsa.NIST521p
+ else:
+ raise UnknownKeySpecificationError(specification)
+
+ self.public_key, self.private_key = (
+ binascii.unhexlify(key_pair[0]),
+ binascii.unhexlify(key_pair[1]),
+ )
+ self.key = ecdsa.SigningKey.from_string(self.private_key, curve=self.curve)
+
+ def getPublicKeyHexifiedString(self):
+ """Returns the EC public key as a hex string using the uncompressed
+ point representation. This is intended to be used in the encoder
+ functions, as it surrounds the value with ''H to indicate its type."""
+ p1, p2 = (
+ self.public_key[: len(self.public_key) // 2],
+ self.public_key[len(self.public_key) // 2 :],
+ )
+ # We don't want leading zeroes.
+ p1, p2 = (p1.lstrip(b"\0"), p2.lstrip(b"\0"))
+ # '04' indicates that the points are in uncompressed form.
+ return byteStringToHexifiedBitString(b"\04" + p1 + p2)
+
+ def toPEM(self):
+ """Return the EC private key in PEM-encoded form."""
+ output = "-----BEGIN EC PRIVATE KEY-----"
+ der = self.toDER()
+ b64 = six.ensure_text(base64.b64encode(der))
+ while b64:
+ output += "\n" + b64[:64]
+ b64 = b64[64:]
+ output += "\n-----END EC PRIVATE KEY-----"
+ return output
+
+ def toDER(self):
+ """Return the EC private key in DER-encoded form, encoded per SEC 1
+ section C.4 format."""
+ privateKeyInfo = PrivateKeyInfo()
+ privateKeyInfo["version"] = 0
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmIdentifier["algorithm"] = ecPublicKey
+ algorithmIdentifier["parameters"] = self.keyOID
+ privateKeyInfo["privateKeyAlgorithm"] = algorithmIdentifier
+ ecPrivateKey = ECPrivateKey()
+ ecPrivateKey["version"] = 1
+ ecPrivateKey["privateKey"] = self.private_key
+ ecPrivateKey["publicKey"] = univ.BitString(
+ self.getPublicKeyHexifiedString()
+ ).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))
+ ecPrivateKeyEncoded = encoder.encode(ecPrivateKey)
+ privateKeyInfo["privateKey"] = univ.OctetString(ecPrivateKeyEncoded)
+ return encoder.encode(privateKeyInfo)
+
+ def asSubjectPublicKeyInfo(self):
+ """Returns a subject public key info representing
+ this key for use by pyasn1."""
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmIdentifier["algorithm"] = ecPublicKey
+ algorithmIdentifier["parameters"] = self.keyOID
+ spki = rfc2459.SubjectPublicKeyInfo()
+ spki["algorithm"] = algorithmIdentifier
+ spki["subjectPublicKey"] = univ.BitString(self.getPublicKeyHexifiedString())
+ return spki
+
+ def signRaw(self, data, hashAlgorithm):
+ """Performs the ECDSA signature algorithm over the given data.
+ The returned value is a string representing the bytes of the
+ resulting point when encoded by left-padding each of (r, s) to
+ the key size and concatenating them.
+ """
+ assert hashAlgorithm.startswith("hash:")
+ hashAlgorithm = hashAlgorithm[len("hash:") :]
+ k = _gen_k(self.curve)
+ digest = hashlib.new(hashAlgorithm, six.ensure_binary(data)).digest()
+ digest = _truncate_digest(digest, self.curve)
+ # NOTE: Under normal circumstances it's advisable to use
+ # sign_digest_deterministic. In this case we don't want the library's
+ # default generation of k, so we call the normal "sign" method and
+ # inject it here.
+ return self.key.sign_digest(digest, sigencode=ecdsa.util.sigencode_string, k=k)
+
+ def sign(self, data, hashAlgorithm):
+ """Returns a hexified bit string representing a
+ signature by this key over the specified data.
+ Intended for use with pyasn1.type.univ.BitString"""
+ # signRaw returns an encoded point, which is useful in some situations.
+ # However, for signatures on X509 certificates, we need to decode it so
+ # we can encode it as a BITSTRING consisting of a SEQUENCE of two
+ # INTEGERs.
+ raw = self.signRaw(data, hashAlgorithm)
+ point = ECPoint()
+ point["x"] = int.from_bytes(raw[: len(raw) // 2], byteorder="big")
+ point["y"] = int.from_bytes(raw[len(raw) // 2 :], byteorder="big")
+ return byteStringToHexifiedBitString(encoder.encode(point))
+
+
+def keyFromSpecification(specification):
+ """Pass in a specification, get the appropriate key back."""
+ if specification.startswith("secp"):
+ return ECCKey(specification)
+ return RSAKey(specification)
+
+
+# The build harness will call this function with an output file-like
+# object and a path to a file containing a specification. This will
+# read the specification and output the key as ASCII-encoded PKCS #8.
+def main(output, inputPath):
+ with open(inputPath) as configStream:
+ output.write(keyFromSpecification(configStream.read().strip()).toPEM() + "\n")
+
+
+# When run as a standalone program, this will read a specification from
+# stdin and output the certificate as PEM to stdout.
+if __name__ == "__main__":
+ print(keyFromSpecification(sys.stdin.read().strip()).toPEM())
diff --git a/security/manager/tools/pypkcs12.py b/security/manager/tools/pypkcs12.py
new file mode 100644
index 0000000000..e21c52076f
--- /dev/null
+++ b/security/manager/tools/pypkcs12.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Reads a specification from stdin or a file and outputs a PKCS12
+file with the desired properties.
+
+The input format currently consists of a pycert certificate
+specification (see pycert.py).
+Currently, keys other than the default key are not supported.
+The password that is used to encrypt and authenticate the file
+is "password".
+"""
+
+import base64
+import os
+import subprocess
+import sys
+from distutils.spawn import find_executable
+
+import mozinfo
+import pycert
+import pykey
+import six
+from mozfile import NamedTemporaryFile
+
+
+class Error(Exception):
+ """Base class for exceptions in this module."""
+
+ pass
+
+
+class OpenSSLError(Error):
+ """Class for handling errors when calling OpenSSL."""
+
+ def __init__(self, status):
+ super(OpenSSLError, self).__init__()
+ self.status = status
+
+ def __str__(self):
+ return "Error running openssl: %s " % self.status
+
+
+def runUtil(util, args):
+ env = os.environ.copy()
+ if mozinfo.os == "linux":
+ pathvar = "LD_LIBRARY_PATH"
+ app_path = os.path.dirname(util)
+ if pathvar in env:
+ env[pathvar] = "%s%s%s" % (app_path, os.pathsep, env[pathvar])
+ else:
+ env[pathvar] = app_path
+ proc = subprocess.run(
+ [util] + args,
+ env=env,
+ universal_newlines=True,
+ )
+ return proc.returncode
+
+
+class PKCS12(object):
+ """Utility class for reading a specification and generating
+ a PKCS12 file"""
+
+ def __init__(self, paramStream):
+ self.cert = pycert.Certificate(paramStream)
+ self.key = pykey.keyFromSpecification("default")
+
+ def toDER(self):
+ with NamedTemporaryFile(mode="wt+") as certTmp, NamedTemporaryFile(
+ mode="wt+"
+ ) as keyTmp, NamedTemporaryFile(mode="rb+") as pkcs12Tmp:
+ certTmp.write(self.cert.toPEM())
+ certTmp.flush()
+ keyTmp.write(self.key.toPEM())
+ keyTmp.flush()
+ openssl = find_executable("openssl")
+ status = runUtil(
+ openssl,
+ [
+ "pkcs12",
+ "-export",
+ "-inkey",
+ keyTmp.name,
+ "-in",
+ certTmp.name,
+ "-out",
+ pkcs12Tmp.name,
+ "-passout",
+ "pass:password",
+ ],
+ )
+ if status != 0:
+ raise OpenSSLError(status)
+ return pkcs12Tmp.read()
+
+ def toPEM(self):
+ output = "-----BEGIN PKCS12-----"
+ der = self.toDER()
+ b64 = six.ensure_text(base64.b64encode(der))
+ while b64:
+ output += "\n" + b64[:64]
+ b64 = b64[64:]
+ output += "\n-----END PKCS12-----"
+ return output
+
+
+# The build harness will call this function with an output
+# file-like object and a path to a file containing a
+# specification. This will read the specification and output
+# the PKCS12 file.
+def main(output, inputPath):
+ with open(inputPath) as configStream:
+ output.write(PKCS12(configStream).toDER())
+
+
+# When run as a standalone program, this will read a specification from
+# stdin and output the PKCS12 file as PEM to stdout.
+if __name__ == "__main__":
+ print(PKCS12(sys.stdin).toPEM())