From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- security/manager/tools/.eslintrc.js | 13 + security/manager/tools/KnownRootHashes.json | 1108 ++++++++++++++++++++ security/manager/tools/PreloadedHPKPins.json | 217 ++++ .../crtshToIdentifyingStruct.py | 159 +++ .../crtshToIdentifyingStruct/requirements.txt | 4 + security/manager/tools/dumpGoogleRoots.js | 104 ++ security/manager/tools/genHPKPStaticPins.js | 674 ++++++++++++ security/manager/tools/genRootCAHashes.js | 269 +++++ security/manager/tools/getCTKnownLogs.py | 334 ++++++ security/manager/tools/log_list.json | 128 +++ 10 files changed, 3010 insertions(+) create mode 100644 security/manager/tools/.eslintrc.js create mode 100644 security/manager/tools/KnownRootHashes.json create mode 100644 security/manager/tools/PreloadedHPKPins.json create mode 100644 security/manager/tools/crtshToIdentifyingStruct/crtshToIdentifyingStruct.py create mode 100644 security/manager/tools/crtshToIdentifyingStruct/requirements.txt create mode 100644 security/manager/tools/dumpGoogleRoots.js create mode 100644 security/manager/tools/genHPKPStaticPins.js create mode 100644 security/manager/tools/genRootCAHashes.js create mode 100755 security/manager/tools/getCTKnownLogs.py create mode 100644 security/manager/tools/log_list.json (limited to 'security/manager/tools') 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..5397aedb08 --- /dev/null +++ b/security/manager/tools/KnownRootHashes.json @@ -0,0 +1,1108 @@ +// 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=" + } + ], + "maxBin": 219 +} \ 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..4afc14373b --- /dev/null +++ b/security/manager/tools/PreloadedHPKPins.json @@ -0,0 +1,217 @@ +// -*- 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/net/+/master/http/transport_security_state_static.pins?format=TEXT", + "json_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.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", + // Dropbox + "dropbox.com", + "www.dropbox.com", + // Twitter + "api.twitter.com", + "business.twitter.com", + "dev.twitter.com", + "mobile.twitter.com", + "oauth.twitter.com", + "platform.twitter.com", + "twimg.com", + "www.twitter.com", + // Tor + "torproject.org", + "blog.torproject.org", + "check.torproject.org", + "dist.torproject.org", + "www.torproject.org", + // SpiderOak + "spideroak.com" + ], + "exclude_domains" : [ + // Chrome's entry for twitter.com doesn't include subdomains, so replace + // it with our own entry below which also uses an expanded pinset. + "twitter.com" + ] + }, + "pinsets": [ + { + "name": "mozilla_services", + "sha256_hashes": [ + "DigiCert Global Root CA", + "DigiCert High Assurance EV Root CA", + "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", + "Cybertrust Global Root", + "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", + // "GeoTrust Global CA", + "GlobalSign ECC Root CA - R4", + "GlobalSign ECC Root CA - R5", + "GlobalSign Root CA", + "GlobalSign Root CA - R2", + "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 }, + // Expand twitter's pinset to include all of *.twitter.com and use + // twitterCDN. More specific rules take precedence because we search for + // exact domain name first. + { "name": "twitter.com", "include_subdomains": true, + "pins": "twitterCDN", "test_mode": false } + ], + // 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..ce8ae1467a --- /dev/null +++ b/security/manager/tools/crtshToIdentifyingStruct/crtshToIdentifyingStruct.py @@ -0,0 +1,159 @@ +#!/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 re +import requests +import sys +import io + +from pyasn1.codec.der import decoder +from pyasn1.codec.der import encoder +from pyasn1_modules import pem +from pyasn1_modules import rfc5280 + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.x509.oid import NameOID + +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, rest = 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..6d9650a2ab --- /dev/null +++ b/security/manager/tools/dumpGoogleRoots.js @@ -0,0 +1,104 @@ +/* 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] + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +Services.prefs.setBoolPref("network.process.enabled", false); + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest"]); + +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/genHPKPStaticPins.js b/security/manager/tools/genHPKPStaticPins.js new file mode 100644 index 0000000000..aebacd6d3c --- /dev/null +++ b/security/manager/tools/genHPKPStaticPins.js @@ -0,0 +1,674 @@ +/* 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/. */ + +// 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 \ +// [path to]/genHPKPStaticpins.js \ +// [absolute path to]/PreloadedHPKPins.json \ +// [an unused argument - see bug 1205406] \ +// [absolute path to]/StaticHPKPins.h +"use strict"; + +if (arguments.length != 3) { + throw new Error( + "Usage: genHPKPStaticPins.js " + + " " + + " " + + "" + ); +} + +var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); +var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm"); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest"]); + +var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB +); + +const SHA256_PREFIX = "sha256/"; +const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_"; + +// Pins expire in 14 weeks (6 weeks on Beta + 8 weeks on stable) +const PINNING_MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 14; + +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" + + "/* PublicKeyPinningService.cpp, you shouldn't be #including it. */\n" + + "/*****************************************************************************/\n" + + "#include " + + "\n"; + +const DOMAINHEADER = + "/* Domainlist */\n" + + "struct TransportSecurityPreload {\n" + + " // See bug 1338873 about making these fields const.\n" + + " const char* mHost;\n" + + " bool mIncludeSubdomains;\n" + + " bool mTestMode;\n" + + " bool mIsMoz;\n" + + " int32_t mId;\n" + + " const StaticFingerprints* pinset;\n" + + "};\n\n"; + +const PINSETDEF = + "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" + + "struct StaticFingerprints {\n" + + " // See bug 1338873 about making these fields const.\n" + + " size_t size;\n" + + " const char* const* data;\n" + + "};\n\n"; + +// Command-line arguments +var gStaticPins = parseJson(arguments[0]); + +// arguments[1] is ignored for now. See bug 1205406. + +// Open the output file. +var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); +file.initWithPath(arguments[2]); +var gFileOutputStream = FileUtils.openSafeFileOutputStream(file); + +function writeString(string) { + gFileOutputStream.write(string, string.length); +} + +function readFileToString(filename) { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(filename); + 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 buf; +} + +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; +} + +function download(filename) { + let req = new XMLHttpRequest(); + req.open("GET", filename, false); // doing the request synchronously + try { + req.send(); + } catch (e) { + throw new Error(`ERROR: problem downloading '${filename}': ${e}`); + } + + if (req.status != 200) { + throw new Error( + "ERROR: problem downloading '" + filename + "': status " + req.status + ); + } + + let resultDecoded; + try { + resultDecoded = atob(req.responseText); + } catch (e) { + throw new Error( + "ERROR: could not decode data as base64 from '" + filename + "': " + e + ); + } + return resultDecoded; +} + +function downloadAsJson(filename) { + // we have to filter out '//' comments, while not mangling the json + let result = download(filename).replace(/^(\s*)?\/\/[^\n]*\n/gm, ""); + let data = null; + try { + data = JSON.parse(result); + } catch (e) { + throw new Error( + "ERROR: could not parse data from '" + filename + "': " + e + ); + } + return data; +} + +// Returns a Subject Public Key Digest from the given pem, if it exists. +function getSKDFromPem(pem) { + let cert = gCertDB.constructX509FromBase64(pem, pem.length); + return cert.sha256SubjectPublicKeyInfoDigest; +} + +/** + * Hashes |input| using the SHA-256 algorithm in the following manner: + * btoa(sha256(atob(input))) + * + * @argument {String} input Base64 string to decode and return the hash of. + * @returns {String} Base64 encoded SHA-256 hash. + */ +function sha256Base64(input) { + let decodedValue; + try { + decodedValue = atob(input); + } catch (e) { + throw new Error(`ERROR: could not decode as base64: '${input}': ${e}`); + } + + // Convert |decodedValue| to an array so that it can be hashed by the + // nsICryptoHash instance below. + // In most cases across the code base, convertToByteArray() of + // nsIScriptableUnicodeConverter is used to do this, but the method doesn't + // seem to work here. + let data = []; + for (let i = 0; i < decodedValue.length; i++) { + data[i] = decodedValue.charCodeAt(i); + } + + let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + + // true is passed so that the hasher returns a Base64 encoded string. + return hasher.finish(true); +} + +// Downloads the static certs file and tries to map Google Chrome nicknames +// to Mozilla nicknames, as well as storing any hashes for pins for which we +// don't have root PEMs. Each entry consists of a line containing the name of +// the pin followed either by a hash in the format "sha256/" + base64(hash), +// a PEM encoded public key, or a PEM encoded certificate. +// For certificates that we have in our database, +// return a map of Google's nickname to ours. For ones that aren't return a +// map of Google's nickname to SHA-256 values. This code is modeled after agl's +// https://github.com/agl/transport-security-state-generate, which doesn't +// live in the Chromium repo because go is not an official language in +// Chromium. +// For all of the entries in this file: +// - If the entry has a hash format, find the Mozilla pin name (cert nickname) +// and stick the hash into certSKDToName +// - If the entry has a PEM format, parse the PEM, find the Mozilla pin name +// and stick the hash in certSKDToName +// We MUST be able to find a corresponding cert nickname for the Chrome names, +// otherwise we skip all pinsets referring to that Chrome name. +function downloadAndParseChromeCerts(filename, certNameToSKD, certSKDToName) { + // Prefixes that we care about. + const BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; + const END_CERT = "-----END CERTIFICATE-----"; + const BEGIN_PUB_KEY = "-----BEGIN PUBLIC KEY-----"; + const END_PUB_KEY = "-----END PUBLIC KEY-----"; + + // Parsing states. + const PRE_NAME = 0; + const POST_NAME = 1; + const IN_CERT = 2; + const IN_PUB_KEY = 3; + let state = PRE_NAME; + + let lines = download(filename).split("\n"); + let pemCert = ""; + let pemPubKey = ""; + let hash = ""; + let chromeNameToHash = {}; + let chromeNameToMozName = {}; + let chromeName; + for (let line of lines) { + // Skip comments and newlines. + if (line.length == 0 || line[0] == "#") { + continue; + } + switch (state) { + case PRE_NAME: + chromeName = line; + state = POST_NAME; + break; + case POST_NAME: + if (line.startsWith(SHA256_PREFIX)) { + hash = line.substring(SHA256_PREFIX.length); + chromeNameToHash[chromeName] = hash; + certNameToSKD[chromeName] = hash; + certSKDToName[hash] = chromeName; + state = PRE_NAME; + } else if (line.startsWith(BEGIN_CERT)) { + state = IN_CERT; + } else if (line.startsWith(BEGIN_PUB_KEY)) { + state = IN_PUB_KEY; + } else { + throw new Error( + "ERROR: couldn't parse Chrome certificate file line: " + line + ); + } + break; + case IN_CERT: + if (line.startsWith(END_CERT)) { + state = PRE_NAME; + hash = getSKDFromPem(pemCert); + pemCert = ""; + let mozName; + if (hash in certSKDToName) { + mozName = certSKDToName[hash]; + } else { + // Not one of our built-in certs. Prefix the name with + // GOOGLE_PIN_. + mozName = GOOGLE_PIN_PREFIX + chromeName; + dump( + "Can't find hash in builtin certs for Chrome nickname " + + chromeName + + ", inserting " + + mozName + + "\n" + ); + certSKDToName[hash] = mozName; + certNameToSKD[mozName] = hash; + } + chromeNameToMozName[chromeName] = mozName; + } else { + pemCert += line; + } + break; + case IN_PUB_KEY: + if (line.startsWith(END_PUB_KEY)) { + state = PRE_NAME; + hash = sha256Base64(pemPubKey); + pemPubKey = ""; + chromeNameToHash[chromeName] = hash; + certNameToSKD[chromeName] = hash; + certSKDToName[hash] = chromeName; + } else { + pemPubKey += line; + } + break; + default: + throw new Error( + "ERROR: couldn't parse Chrome certificate file " + line + ); + } + } + return [chromeNameToHash, chromeNameToMozName]; +} + +// We can only import pinsets from chrome if for every name in the pinset: +// - We have a hash from Chrome's static certificate file +// - We have a builtin cert +// If the pinset meets these requirements, we store a map array of pinset +// objects: +// { +// pinset_name : { +// // Array of names with entries in certNameToSKD +// sha256_hashes: [] +// } +// } +// and an array of imported pinset entries: +// { name: string, include_subdomains: boolean, test_mode: boolean, +// pins: pinset_name } +function downloadAndParseChromePins( + filename, + chromeNameToHash, + chromeNameToMozName, + certNameToSKD, + certSKDToName +) { + let chromePreloads = downloadAsJson(filename); + let chromePins = chromePreloads.pinsets; + let chromeImportedPinsets = {}; + let chromeImportedEntries = []; + + chromePins.forEach(function(pin) { + let valid = true; + let pinset = { name: pin.name, sha256_hashes: [] }; + // Translate the Chrome pinset format to ours + pin.static_spki_hashes.forEach(function(name) { + if (name in chromeNameToHash) { + let hash = chromeNameToHash[name]; + pinset.sha256_hashes.push(certSKDToName[hash]); + + // We should have already added hashes for all of these when we + // imported the certificate file. + if (!certNameToSKD[name]) { + throw new Error("ERROR: No hash for name: " + name); + } + } else if (name in chromeNameToMozName) { + pinset.sha256_hashes.push(chromeNameToMozName[name]); + } else { + dump( + "Skipping Chrome pinset " + + pinset.name + + ", couldn't find " + + "builtin " + + name + + " from cert file\n" + ); + valid = false; + } + }); + if (valid) { + chromeImportedPinsets[pinset.name] = pinset; + } + }); + + // Grab the domain entry lists. Chrome's entry format is similar to + // ours, except theirs includes a HSTS mode. + const cData = gStaticPins.chromium_data; + let entries = chromePreloads.entries; + entries.forEach(function(entry) { + // HSTS entry only + if (!entry.pins) { + return; + } + let pinsetName = cData.substitute_pinsets[entry.pins]; + if (!pinsetName) { + pinsetName = entry.pins; + } + + // We trim the entry name here to avoid breaking hostname comparisons in the + // HPKP implementation. + entry.name = entry.name.trim(); + + let isProductionDomain = cData.production_domains.includes(entry.name); + let isProductionPinset = cData.production_pinsets.includes(pinsetName); + let excludeDomain = cData.exclude_domains.includes(entry.name); + let isTestMode = !isProductionPinset && !isProductionDomain; + if (entry.pins && !excludeDomain && chromeImportedPinsets[entry.pins]) { + chromeImportedEntries.push({ + name: entry.name, + include_subdomains: entry.include_subdomains, + test_mode: isTestMode, + is_moz: false, + pins: pinsetName, + }); + } + }); + return [chromeImportedPinsets, chromeImportedEntries]; +} + +// Returns a pair of maps [certNameToSKD, certSKDToName] between cert +// nicknames and digests of the SPKInfo for the mozilla trust store +function loadNSSCertinfo(extraCertificates) { + let allCerts = gCertDB.getCerts(); + let certNameToSKD = {}; + let certSKDToName = {}; + for (let cert of allCerts) { + if (!cert.isBuiltInRoot) { + continue; + } + let name = cert.displayName; + let SKD = cert.sha256SubjectPublicKeyInfoDigest; + certNameToSKD[name] = SKD; + certSKDToName[SKD] = name; + } + + for (let cert of extraCertificates) { + let name = cert.commonName; + let SKD = cert.sha256SubjectPublicKeyInfoDigest; + certNameToSKD[name] = SKD; + certSKDToName[SKD] = name; + } + + { + // This is the pinning test certificate. The key hash identifies the + // default RSA key from pykey. + let name = "End Entity Test Cert"; + let SKD = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="; + certNameToSKD[name] = SKD; + certSKDToName[SKD] = name; + } + return [certNameToSKD, certSKDToName]; +} + +function parseJson(filename) { + let json = stripComments(readFileToString(filename)); + return JSON.parse(json); +} + +function nameToAlias(certName) { + // change the name to a string valid as a c identifier + // remove non-ascii characters + certName = certName.replace(/[^[:ascii:]]/g, "_"); + // replace non word characters + certName = certName.replace(/[^A-Za-z0-9]/g, "_"); + + return "k" + certName + "Fingerprint"; +} + +function compareByName(a, b) { + return a.name.localeCompare(b.name); +} + +function genExpirationTime() { + let now = new Date(); + let nowMillis = now.getTime(); + let expirationMillis = nowMillis + PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000; + let expirationMicros = expirationMillis * 1000; + return ( + "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" + + expirationMicros + + ");\n" + ); +} + +function writeFullPinset(certNameToSKD, certSKDToName, pinset) { + if (!pinset.sha256_hashes || pinset.sha256_hashes.length == 0) { + throw new Error(`ERROR: Pinset ${pinset.name} does not contain any hashes`); + } + writeFingerprints( + certNameToSKD, + certSKDToName, + pinset.name, + pinset.sha256_hashes + ); +} + +function writeFingerprints(certNameToSKD, certSKDToName, name, hashes) { + let varPrefix = "kPinset_" + name; + writeString("static const char* const " + varPrefix + "_Data[] = {\n"); + let SKDList = []; + for (let certName of hashes) { + if (!(certName in certNameToSKD)) { + throw new Error(`ERROR: Can't find '${certName}' in certNameToSKD`); + } + SKDList.push(certNameToSKD[certName]); + } + for (let skd of SKDList.sort()) { + writeString(" " + nameToAlias(certSKDToName[skd]) + ",\n"); + } + if (hashes.length == 0) { + // ANSI C requires that an initialiser list be non-empty. + writeString(" 0\n"); + } + writeString("};\n"); + writeString( + "static const StaticFingerprints " + + varPrefix + + " = {\n " + + "sizeof(" + + varPrefix + + "_Data) / sizeof(const char*),\n " + + varPrefix + + "_Data\n};\n\n" + ); +} + +function writeEntry(entry) { + let printVal = ` { "${entry.name}", `; + if (entry.include_subdomains) { + printVal += "true, "; + } else { + printVal += "false, "; + } + // Default to test mode if not specified. + let testMode = true; + if (entry.hasOwnProperty("test_mode")) { + testMode = entry.test_mode; + } + if (testMode) { + printVal += "true, "; + } else { + printVal += "false, "; + } + if ( + entry.is_moz || + (entry.pins.includes("mozilla") && entry.pins != "mozilla_test") + ) { + printVal += "true, "; + } else { + printVal += "false, "; + } + if ("id" in entry) { + if (entry.id >= 256) { + throw new Error("ERROR: Not enough buckets in histogram"); + } + if (entry.id >= 0) { + printVal += entry.id + ", "; + } + } else { + printVal += "-1, "; + } + printVal += "&kPinset_" + entry.pins; + printVal += " },\n"; + writeString(printVal); +} + +function writeDomainList(chromeImportedEntries) { + writeString("/* Sort hostnames for binary search. */\n"); + writeString( + "static const TransportSecurityPreload " + + "kPublicKeyPinningPreloadList[] = {\n" + ); + let count = 0; + let mozillaDomains = {}; + gStaticPins.entries.forEach(function(entry) { + mozillaDomains[entry.name] = true; + }); + // For any domain for which we have set pins, exclude them from + // chromeImportedEntries. + for (let i = chromeImportedEntries.length - 1; i >= 0; i--) { + if (mozillaDomains[chromeImportedEntries[i].name]) { + dump( + "Skipping duplicate pinset for domain " + + JSON.stringify(chromeImportedEntries[i], undefined, 2) + + "\n" + ); + chromeImportedEntries.splice(i, 1); + } + } + let sortedEntries = gStaticPins.entries; + sortedEntries.push.apply(sortedEntries, chromeImportedEntries); + for (let entry of sortedEntries.sort(compareByName)) { + count++; + writeEntry(entry); + } + writeString("};\n"); + + writeString("\n// Pinning Preload List Length = " + count + ";\n"); + writeString("\nstatic const int32_t kUnknownId = -1;\n"); +} + +function writeFile( + certNameToSKD, + certSKDToName, + chromeImportedPinsets, + chromeImportedEntries +) { + // Compute used pins from both Chrome's and our pinsets, so we can output + // them later. + let usedFingerprints = {}; + let mozillaPins = {}; + gStaticPins.pinsets.forEach(function(pinset) { + mozillaPins[pinset.name] = true; + pinset.sha256_hashes.forEach(function(name) { + usedFingerprints[name] = true; + }); + }); + for (let key in chromeImportedPinsets) { + let pinset = chromeImportedPinsets[key]; + pinset.sha256_hashes.forEach(function(name) { + usedFingerprints[name] = true; + }); + } + + writeString(FILE_HEADER); + + // Write actual fingerprints. + Object.keys(usedFingerprints) + .sort() + .forEach(function(certName) { + if (certName) { + writeString("/* " + certName + " */\n"); + writeString("static const char " + nameToAlias(certName) + "[] =\n"); + writeString(' "' + certNameToSKD[certName] + '";\n'); + writeString("\n"); + } + }); + + // Write the pinsets + writeString(PINSETDEF); + writeString("/* PreloadedHPKPins.json pinsets */\n"); + gStaticPins.pinsets.sort(compareByName).forEach(function(pinset) { + writeFullPinset(certNameToSKD, certSKDToName, pinset); + }); + writeString("/* Chrome static pinsets */\n"); + for (let key in chromeImportedPinsets) { + if (mozillaPins[key]) { + dump("Skipping duplicate pinset " + key + "\n"); + } else { + dump("Writing pinset " + key + "\n"); + writeFullPinset(certNameToSKD, certSKDToName, chromeImportedPinsets[key]); + } + } + + // Write the domainlist entries. + writeString(DOMAINHEADER); + writeDomainList(chromeImportedEntries); + writeString("\n"); + writeString(genExpirationTime()); +} + +function loadExtraCertificates(certStringList) { + let constructedCerts = []; + for (let certString of certStringList) { + constructedCerts.push(gCertDB.constructX509FromBase64(certString)); + } + return constructedCerts; +} + +var extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates); +var [certNameToSKD, certSKDToName] = loadNSSCertinfo(extraCertificates); +var [chromeNameToHash, chromeNameToMozName] = downloadAndParseChromeCerts( + gStaticPins.chromium_data.cert_file_url, + certNameToSKD, + certSKDToName +); +var [chromeImportedPinsets, chromeImportedEntries] = downloadAndParseChromePins( + gStaticPins.chromium_data.json_file_url, + chromeNameToHash, + chromeNameToMozName, + certNameToSKD, + certSKDToName +); + +writeFile( + certNameToSKD, + certSKDToName, + chromeImportedPinsets, + chromeImportedEntries +); + +FileUtils.closeSafeFileOutputStream(gFileOutputStream); diff --git a/security/manager/tools/genRootCAHashes.js b/security/manager/tools/genRootCAHashes.js new file mode 100644 index 0000000000..9e80ca90aa --- /dev/null +++ b/security/manager/tools/genRootCAHashes.js @@ -0,0 +1,269 @@ +/* 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.import( + "resource://gre/modules/FileUtils.jsm" +); +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); +const { CommonUtils } = ChromeUtils.import( + "resource://services-common/utils.js" +); + +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 " + ); +} + +var trustAnchorsFile = FileUtils.getFile("CurWorkD", [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..536d7ab0e1 --- /dev/null +++ b/security/manager/tools/getCTKnownLogs.py @@ -0,0 +1,334 @@ +#!/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 +""" + +from __future__ import print_function +from string import Template +import argparse +import base64 +import datetime +import json +import os.path +import sys +import textwrap +import urllib2 + +import six + + +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 + +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(ord(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_and_index(json_data, operator_id): + """ 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["id"] == operator_id + ] + assert len(matches) != 0, "No operators with id {0} defined.".format(operator_id) + assert len(matches) == 1, "Found multiple operators with id {0}.".format( + operator_id + ) + return matches[0] + + +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 log in json_data["logs"]: + log_key = decodebytes(log["key"]) + # "operated_by" is a list, we assume here it always contains one item. + operated_by = log["operated_by"] + assert len(operated_by) == 1, "operated_by must contain one item." + operator, operator_index = get_operator_and_index(json_data, operated_by[0]) + 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 = [] + 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=operator["id"]) + 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 = 0 + for operator in json_data["operators"]: + if operator["id"] > max_id: + max_id = operator["id"] + mozilla_test_operator_1 = { + "name": "Mozilla Test Org 1", + "id": max_id + 1, + "test_only": True, + } + mozilla_test_operator_2 = { + "name": "Mozilla Test Org 2", + "id": max_id + 2, + "test_only": True, + } + json_data["operators"].append(mozilla_test_operator_1) + json_data["operators"].append(mozilla_test_operator_2) + # The easiest way to get this is + # `openssl x509 -noout -pubkey -in ` + mozilla_rsa_log_1 = { + "description": "Mozilla Test RSA Log 1", + "key": """ + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuohRqESOFtZB/W62iAY2 + ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptuGobya+KvWnVramRxCHqlWqdF + h/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO7RWCD/F+rWkasdMCOosqQe6n + cOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgfqDfTiEPvJxbYVbdmWqp+ApAv + OnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/ytHSXTCe+5Fw6naOGzey8ib2nj + tIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcxuLP+SSP6clHEMdUDrNoYCjXt + jQIDAQAB + """, + "operated_by": [max_id + 1], + } + # Similarly, + # `openssl x509 -noout -pubkey -in ` + mozilla_rsa_log_2 = { + "description": "Mozilla Test RSA Log 2", + "key": """ + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXXGUmYJn3cIKmeR8bh2 + w39c5TiwbErNIrHL1G+mWtoq3UHIwkmKxKOzwfYUh/QbaYlBvYClHDwSAkTFhKTE + SDMF5ROMAQbPCL6ahidguuai6PNvI8XZgxO53683g0XazlHU1tzSpss8xwbrzTBw + 7JjM5AqlkdcpWn9xxb5maR0rLf7ISURZC8Wj6kn9k7HXU0BfF3N2mZWGZiVHl+1C + aQiICBFCIGmYikP+5Izmh4HdIramnNKDdRMfkysSjOKG+n0lHAYq0n7wFvGHzdVO + gys1uJMPdLqQqovHYWckKrH9bWIUDRjEwLjGj8N0hFcyStfehuZVLx0eGR1xIWjT + uwIDAQAB + """, + "operated_by": [max_id + 2], + } + # `openssl x509 -noout -pubkey -in