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