diff --git a/fixtures/chain-intermediate.crt b/fixtures/chain-intermediate.crt new file mode 100644 index 00000000..b54d4dea --- /dev/null +++ b/fixtures/chain-intermediate.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC/DCCAeSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJVEVT +VC1yb290MB4XDTIzMDgwMTAxNDUxM1oXDTI0MDczMTAxNDUxM1owHDEaMBgGA1UE +AwwRVEVTVC1pbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDYpxiPSJIcdHoDlt6fueRkX8zBC5u9aItDrStM6/VGQRM/NmeAWB/ek1pU +749TaPUAcx5uoMaP2FKqnUN9sExslD5cLY5c/ixAfxBw6dLL3oYHB5vgwr5YUyS2 +AHVnomYS4hXW1nhqK9oEBmx5MQzb7FFKXpamrOsGVMfVjROek/wz3YoW8fE30A8r +Zezc0uxkAyq7GQ4+ur/Em8qGhA7YXGmoHd4+h2PpU2Co8iW6i5Mftt8DfSFHFCyt +Yu9xMhX2o8HckPUXQTKVAcOY/S2JjpFdwjX6cc9iDZ3+ETMtIUtv+MSICaTU7PLv +eHAl5nodMctAI9+NaQkO81z8XdoZAgMBAAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYD +VR0OBBYEFEuSnkkMoeox4UP07oJsZGcEMYAWMB8GA1UdIwQYMBaAFBBrunbDxn3d +ZwfysAIt6DjRylEIMA0GCSqGSIb3DQEBCwUAA4IBAQB00UMFcTDyDUidWamh/fzS +Z6pv2ms0mKHXeVYdqUGtWPjl9uocWGJXdgD3C77Ifpx02zayhtpdfSvvajyEnTAd +XPM8jb/VBXpgW7wA7vMRoewvXLG4xITbh+HXKhDh1n+KLAJLSB4uBrmbmx1/pgpM +rXJPAGv4ARzkozcs98qWND3dWAjDNn+7Wxb0wVhcYgrmvyQyNAUdgYImxZSqn7rR +sP5rr7FbAZLGSHx0h9hzav3XuWRv7+mn1m9QplsvaJV2EehiY92C0JmNHt9BvMcX +XTWGWgXMVeacm/0W+3XmQWtltDnBQq8fUsEy0Uts6fJdD/5tEmvYfwEtpomxdFds +-----END CERTIFICATE----- diff --git a/fixtures/chain-intermediate.key b/fixtures/chain-intermediate.key new file mode 100644 index 00000000..0a6bc8c9 --- /dev/null +++ b/fixtures/chain-intermediate.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDYpxiPSJIcdHoD +lt6fueRkX8zBC5u9aItDrStM6/VGQRM/NmeAWB/ek1pU749TaPUAcx5uoMaP2FKq +nUN9sExslD5cLY5c/ixAfxBw6dLL3oYHB5vgwr5YUyS2AHVnomYS4hXW1nhqK9oE +Bmx5MQzb7FFKXpamrOsGVMfVjROek/wz3YoW8fE30A8rZezc0uxkAyq7GQ4+ur/E +m8qGhA7YXGmoHd4+h2PpU2Co8iW6i5Mftt8DfSFHFCytYu9xMhX2o8HckPUXQTKV +AcOY/S2JjpFdwjX6cc9iDZ3+ETMtIUtv+MSICaTU7PLveHAl5nodMctAI9+NaQkO +81z8XdoZAgMBAAECggEAT8s/MuqWEc/ebnr/FJIJKeTUy1bkrd6WyD6733FaXV0z +Ywk9FpqeZkIcN4Mh5SUXc2pyz2j8qNcSH0+bn2uywhzZWObYc6yTjM+ftQ6Rek/D +SkyFn0LqiypYL4Y8t4YrFRJa280S/XuYKPpaskocA9XmXL84ujueti68iQ0UKEeH +5dWNveO40q2jLMTHQyB/+IdPtKqrpTd5CWKsRxIAO78hv+iQSEIAReEmdtUVXCTK +5YhWDkfqNcvQ2+LWz+W0ISbsoKILiTbRxIJGpOGKSkBhVTgYlywyYaUVqISHqwuh +fbhGzLFQ+eF1RLrtcqhkGTAH2zg6fHD7sZUSkG4kVwKBgQD82plKYXIorp4Vzgma +DhSJJtmSbJGcYtTNVhaq3AFTBmbnvWuCQea7Iugo+vhoA+man60XilWdgmarwPUQ +pjRPDQu3DG41el6fik9obtiXi/lqK40z1v3HbofMRmXM5MgLDYc/b1qX+Y8qAYlJ +05hC5REZRgNFmCuarfW8jKwTewKBgQDbWTAEbYnZ9QlVdO1thQI401rgywRmdBow +Yd7IxkztPFknwW0CtqmodqW3TPUhebX1BJlS6GfBONFCgBfT6gC4SH1a1X7E0U0M +GBYQ3lyddZYs+axM3sF5jCcUKZB4HD8Bvgs1uN/G+lIEG76Ke5Xd0OIX6b2GL8+3 +iwIs0kIaewKBgE8Uy1ahDYQ9wMGPFB+zgaa3mNqbzBq+KlIiN6qubleaK/sUmhg+ +JjynGTcf7ysQ9jHe+NLg+A/wJc5X5g2T/c4vhVd1ss5U/F0nc9h0upcDNzmGb34k +InEKV7yC0/n2H76dN4yWdh4L9kOsAVUusXNSkzt1Uzaj9hdFixKyaGsbAoGANXzJ +HbtMSy7aaNFLeFJf2VWIqpo253jWTgf/mHvqlEsL/orHN0stkdvkyw7kE5anTSki +7jmn21EsjgfIR6+fH1Dl6Hl5IStcm19gOhrPAMKErVDwuAn1qdsISH1eMjvJDXa3 +KxF61/2AdGoDi6dZQy7Fg0lHIuqTv1ERZbab0fMCgYB2r7XmVoWI0rIU2ptCFqwO +KvBq8nwY/Nh10ETSGxLt77OSJJdQCcmzHuKc2Xb6mEqOj/lBZJibGrJcEHyGQTLg +RAYUR4wsGtrV3Lh0TSEV7aAjo/+hoOwsjJ7SW6BLphaBjJKfPKx6mGKSrS/W8R73 +F1SDXNNKT6Wg/XNKG9Pdow== +-----END PRIVATE KEY----- diff --git a/fixtures/chain-leaf.crt b/fixtures/chain-leaf.crt new file mode 100644 index 00000000..8f25af31 --- /dev/null +++ b/fixtures/chain-leaf.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMjCCAhqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UEAwwRVEVT +VC1pbnRlcm1lZGlhdGUwHhcNMjMwODAxMDE0NTEzWhcNMjQwNzMxMDE0NTEzWjAU +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCiLsCk5U0Xb0VSRaHJNd1q/X2aAgf7EPGi/4ul6XpZp8gT3iRu6sErsRc3 +3JS2svKRN0eRMXduvTO3q9S6aGgMAZUzy7jlDUzgRu8AckEiOATxfvb+4Bz0zGy/ +pVw0bIUiYDMOeJ8RgX0MVT6NrBTztDAj6npTXrtRYzD+SoOq4NkVYCxzDAJtO1+Q +Ie59WJGCK5wz+VjfUbJbxbpQFs1de17uq3LrE6BqksKiZsIgd4vGAembsrIm8Z5U +v3bAJukLFtlL380sCrhRM84IelbSPKQLTjAwPcVFvgC+SS0ezD3I5Og2FcZqVnLS +yjAsCmeq5FENll1y66i3QxK0XtQ7AgMBAAGjgYUwgYIwCwYDVR0PBAQDAgWgMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2NhbGhv +c3QwHQYDVR0OBBYEFAv7QgX2lWUAOdbV2sSERShHfiAxMB8GA1UdIwQYMBaAFEuS +nkkMoeox4UP07oJsZGcEMYAWMA0GCSqGSIb3DQEBCwUAA4IBAQBf5R8Jq11jmEV1 +0i7hNPDp6wDknG7IGO7En3yShvEaXAPZlRpGzJuJ39KDz9Qb/imi5juP/i701Tee +OjvKOH5NGGklwIlzlmbNvtZNuZQAKpAUza0dKu1rPF6OZl65rmyYr2LVWyal9XUK +GGxhyB8A0+5W4XnNjAHDkpNI1yR3DcB+WtSeybAPQrTUSZxpMNRCUMG/Ph+dBfdz +5qC+kmDQoWB+G710CEdGfobkDPzYTq3obbjpBuOUqxBJHqAK8SFAjPkEcnr1GKvG +9DDOojHM6GBYp9k8vSof3P7ptrjTHDjz5ItjGCCohk1zAIwRT2fTE1TOwI2qvYxo +395vKvYI +-----END CERTIFICATE----- diff --git a/fixtures/chain-leaf.key b/fixtures/chain-leaf.key new file mode 100644 index 00000000..bf579a9b --- /dev/null +++ b/fixtures/chain-leaf.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCiLsCk5U0Xb0VS +RaHJNd1q/X2aAgf7EPGi/4ul6XpZp8gT3iRu6sErsRc33JS2svKRN0eRMXduvTO3 +q9S6aGgMAZUzy7jlDUzgRu8AckEiOATxfvb+4Bz0zGy/pVw0bIUiYDMOeJ8RgX0M +VT6NrBTztDAj6npTXrtRYzD+SoOq4NkVYCxzDAJtO1+QIe59WJGCK5wz+VjfUbJb +xbpQFs1de17uq3LrE6BqksKiZsIgd4vGAembsrIm8Z5Uv3bAJukLFtlL380sCrhR +M84IelbSPKQLTjAwPcVFvgC+SS0ezD3I5Og2FcZqVnLSyjAsCmeq5FENll1y66i3 +QxK0XtQ7AgMBAAECggEAAmfHG6r69boEwS+fMqjF2+xejIYzMBoUO2Km45MO6X1/ +jivhRnPc4ZUCzyVKX1tQFa9INHMTXmUX+0VlJk6eHG95kwcWeu8zkK/8o3kOVj+W +cy5641TXmRnfEwiU0YI6h0P9/dz0HwJYpHIvN1KyDNtDS4USw2HITXC3LU5VnZcq +FWdjKTQNdnm5AamTyov2SXb9LKJkxicCKxyJODuMYWe6MX0G51DUvmfWr5bQ3VbD +eOG1Nf1g2pSaDA+xYTeLwrn/LvdATzXsvQZdD2y5u3m11WZFoczHA6MNciyt5Jk2 +kkXJlfxn10A3GXsedWxE1mq/VZ3l+vO311mdoqRiIQKBgQDQLxlkmmy3lbbNfVZc +yr8+7dal1puBTc2ZfIq7Kr4ZSCXaVO41hLpi299OY1UEOdoKfnFBg6c1icDuSfvi +MivU9a70h0XBTJXTYK+6FWXoRsiF9Ale/JmgMU/n0QiWoHMoi+jsXgQZ1WyUXcR6 +45HDbzfSnZIKrTyfJu8LuT9arwKBgQDHbtgDvXA8ewZJi0RWq0oXNS5XYr9ySe94 +LpWCoXT8hgeqNM8Ly43mUXHZiGsrbIHmgkAKJhylthc9CUCRJ4w0JL7UtkPZtcgi +UaydfJ4io9Sx0KgZ38e/1bq3rolC7kESNpGeJHMrF4hUAI2gEgZEtf0m5rzKpaGo +4yu8HVtSNQKBgBTi1MpaD9QvSgK9s19l2+AFXoaOzFUhqCHg884pUJ8atOl9odRu +t4BZjMLBhnMBFtX8r4IiIjFl25xMgd/Eps8bwuy3cZEeDN4DEj46DVpLV6zQuev0 +rbj7mYepWhI6kLMdrkWgfQrWdalA6whlMmeIDfKsak116eIRtuPXNvrzAoGAUVHW +TTgaot67QpPCCuEPdgUeX02JqO2S8ttz5W82h52TVIjx/+pBcy+0j7H7mRpGoZps +yHaf6cYlFaNHK6kHl1+AXLXxVr2z3KKXEyR0SsWo/dSKJvrDtWpOF4XYvGzwJaAQ +on5UY7bVxQLwvNt8qNnYXttpEeyVzYrME4mY6h0CgYBBVY08DRSkrrBwiY9sQWbF +XVdWuffdLuuI27lVcBl0UwJh37EwXN0fsWhZ3upAitAzE3aBNwF5KHvq00yCsMKr +A4bo7DeckePIfCAxdvp8kAuq+NwW8tkpMcqeb1ja2TPrgSy9eyHingzvMtYNDTxB +i45lVy8xNJU85Fmzex0B0g== +-----END PRIVATE KEY----- diff --git a/fixtures/chain-root.crt b/fixtures/chain-root.crt new file mode 100644 index 00000000..cbddc8f4 --- /dev/null +++ b/fixtures/chain-root.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5TCCAc2gAwIBAgIUXtcOjsFS175nGr07+htPEPGe2bYwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJVEVTVC1yb290MB4XDTIzMDgwMTAxNDUxM1oXDTMzMDcy +OTAxNDUxM1owFDESMBAGA1UEAwwJVEVTVC1yb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA1cgfqUNIP0L356sUVEieG0ObtMDrnwnEXGSXX7c3dlSd +oiXJStYDsC+DbyV2+yV24sgkDJM2YwuWvKntHb56hVXzeTydWekDrfLlHw9alYml +llJUXbsTIBBUdk2aT/dFzPyZTN8HhtU8327w7latxKJXrYNpJCn6fcRtSSyo6dIm +QXxvyF/kMLHIirBoOHGKXVVfZFRrDafFbux//1duw+TIjUfVin0rQE/z28iN+3TY +ihXtJ2fhVm+WWR1w7IzoLFV6Xu23JIk92Fxvowho4p6BTOzsGeHPdlPoR0b5ECbI +8VqSLgsa8WOPLmxTNmcuk1BHYVf+Dr3YU/Xb9TBa5QIDAQABoy8wLTAMBgNVHRME +BTADAQH/MB0GA1UdDgQWBBQQa7p2w8Z93WcH8rACLeg40cpRCDANBgkqhkiG9w0B +AQsFAAOCAQEAXp7kVoWuGbI1nep/FTL+PVptYQzD4IfnsonSKZvB7yYcGS0OZtGI +cNr52WOx4EpksG58JsQxjowu5kAdeSwGI5cCKdAWMA/BpJhT+uOP+9Y+QBXFBM25 +50cZax5FFFCKWUcOrv7SSeaGRe3X13G6pPULwwS5WqFb0LZdL0gI9GxN0S9X6F/N +g+T0akcluAe6xNIltnw6AeaUQXzK+jy/3zuSAulh6oiSm7kTU9kXLZ3Xiobko/MB +PaDKL+Ygt0c2hX5TRVJQ2Bdvn5z5kkBJxT7Rb9uj8kT9JETbwQD3bssZB+tPkwvE +O5mpOpR0Jkni8lJmOYrRYz9Jf9Cm0uQWlw== +-----END CERTIFICATE----- diff --git a/fixtures/chain-root.key b/fixtures/chain-root.key new file mode 100644 index 00000000..1bccb0af --- /dev/null +++ b/fixtures/chain-root.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDVyB+pQ0g/Qvfn +qxRUSJ4bQ5u0wOufCcRcZJdftzd2VJ2iJclK1gOwL4NvJXb7JXbiyCQMkzZjC5a8 +qe0dvnqFVfN5PJ1Z6QOt8uUfD1qViaWWUlRduxMgEFR2TZpP90XM/JlM3weG1Tzf +bvDuVq3Eoletg2kkKfp9xG1JLKjp0iZBfG/IX+QwsciKsGg4cYpdVV9kVGsNp8Vu +7H//V27D5MiNR9WKfStAT/PbyI37dNiKFe0nZ+FWb5ZZHXDsjOgsVXpe7bckiT3Y +XG+jCGjinoFM7OwZ4c92U+hHRvkQJsjxWpIuCxrxY48ubFM2Zy6TUEdhV/4OvdhT +9dv1MFrlAgMBAAECggEAALkNTyeb4u4TCh5MX0yV9eAKP4sEQPFp6Vx3UCq9oUTM +xtn7dQ+4/quEDw6NX6QGY6+EwuLsi1rKrUo8M9GLdumN45pBqsApWjk41Rx0LfVD +l6whMbMkPJ0eUmTiYX4KJy59EwMRP3KqvG0Szq60WVBDNtViHm38TtiPL6Qn0UKT +3bc6+b7VlGeasb6vyChcSeemz+SIS0MvOG5kSVCOdJ0fTWVtayRPp5xCqIdIBtn6 +fyeTALAKW7uQLrydmzei+JpUh38L6J/HLwTxC9R2Uq0kOgLV9ZxSFGNeIWPhih2Q +OgzqYgGMjSsYDeun+0aeS6j1z2ClIgywt5hSL6jHsQKBgQD3KifmzAUy14hweUak +hto8SuOT5ngV5Bt2RfOnu+dlxnRvZzyOIPoik6sLB6WK42x6T2AaHN9oSHOi1Jwd +2q3DP8PE/S4kFtaNmJVNFrMFoo/gP/ZRAvzo0qwFrIN6qYhkaG+s+ahwcj8dnnFj +E0JaPcSw+OxFWG8hOYzy1PeeMwKBgQDdbHmgz8A1dkq7PvONk9pBSfb5TaLRPY78 +bVkONJnjmEQVi8kuucQxbeXBGfuOB9pzGdHUlkRCyiWjOpJ82eihaa35gpW/y4Sz +fKEq5hXneuZO2kq9sV9AZBAub8aUH5GFU9fpORXGx6N+QNWfHkrBERdKO/8AdVs6 +7rKi6QpahwKBgQDOvUI2+Px4NHR5r9d5ExtER1fohGR52x1lZsmRyciaBs+px26N +a+QOO/pb9X9wlx5LiE1YSkJMlh2zW6diCWJC6Pk1sph/s2KveauYmZ4Q3pL9/kJo +LNmjXWRqMoyfc2MUqY6P3xwXQlisy7UILDnaBcSaSvxGXNxHrD3LeHvOpQKBgF8x +Nm0DQ7/4NhKf8rcoHEm7UblGPjw0eddd705jINGu8X5N1VUIOieB9qd40yPxjmGz +dPkvDPOl0l6FXNreF9vlAg1lrZmCFm/Pob4+oqYcuQynXkCFE80r96TvGvEtTTOD +oyw6BXmq9Eff+bbpn/u2rEuW1X9N9MW2Pwg4peHbAoGBAM+QoaXHB8e7TPPiEkxx +ppK8n34R5lhLc+DwvGCH6N3F+Dze/WlWRY+KzirE1QfJQTCJmp1cuQl1A0hzWUPL +X/xZUg+kA0yFu9EJROOrpv+f4MK8OzEUN/2n69tobQHU4446iGVOOMtnzoFvaI9H +h/z2c6S/DifigZ5sYSyL4TqJ +-----END PRIVATE KEY----- diff --git a/fixtures/chain.crt b/fixtures/chain.crt new file mode 100644 index 00000000..ce52dfd9 --- /dev/null +++ b/fixtures/chain.crt @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIDMjCCAhqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UEAwwRVEVT +VC1pbnRlcm1lZGlhdGUwHhcNMjMwODAxMDE0NTEzWhcNMjQwNzMxMDE0NTEzWjAU +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCiLsCk5U0Xb0VSRaHJNd1q/X2aAgf7EPGi/4ul6XpZp8gT3iRu6sErsRc3 +3JS2svKRN0eRMXduvTO3q9S6aGgMAZUzy7jlDUzgRu8AckEiOATxfvb+4Bz0zGy/ +pVw0bIUiYDMOeJ8RgX0MVT6NrBTztDAj6npTXrtRYzD+SoOq4NkVYCxzDAJtO1+Q +Ie59WJGCK5wz+VjfUbJbxbpQFs1de17uq3LrE6BqksKiZsIgd4vGAembsrIm8Z5U +v3bAJukLFtlL380sCrhRM84IelbSPKQLTjAwPcVFvgC+SS0ezD3I5Og2FcZqVnLS +yjAsCmeq5FENll1y66i3QxK0XtQ7AgMBAAGjgYUwgYIwCwYDVR0PBAQDAgWgMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2NhbGhv +c3QwHQYDVR0OBBYEFAv7QgX2lWUAOdbV2sSERShHfiAxMB8GA1UdIwQYMBaAFEuS +nkkMoeox4UP07oJsZGcEMYAWMA0GCSqGSIb3DQEBCwUAA4IBAQBf5R8Jq11jmEV1 +0i7hNPDp6wDknG7IGO7En3yShvEaXAPZlRpGzJuJ39KDz9Qb/imi5juP/i701Tee +OjvKOH5NGGklwIlzlmbNvtZNuZQAKpAUza0dKu1rPF6OZl65rmyYr2LVWyal9XUK +GGxhyB8A0+5W4XnNjAHDkpNI1yR3DcB+WtSeybAPQrTUSZxpMNRCUMG/Ph+dBfdz +5qC+kmDQoWB+G710CEdGfobkDPzYTq3obbjpBuOUqxBJHqAK8SFAjPkEcnr1GKvG +9DDOojHM6GBYp9k8vSof3P7ptrjTHDjz5ItjGCCohk1zAIwRT2fTE1TOwI2qvYxo +395vKvYI +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC/DCCAeSgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJVEVT +VC1yb290MB4XDTIzMDgwMTAxNDUxM1oXDTI0MDczMTAxNDUxM1owHDEaMBgGA1UE +AwwRVEVTVC1pbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDYpxiPSJIcdHoDlt6fueRkX8zBC5u9aItDrStM6/VGQRM/NmeAWB/ek1pU +749TaPUAcx5uoMaP2FKqnUN9sExslD5cLY5c/ixAfxBw6dLL3oYHB5vgwr5YUyS2 +AHVnomYS4hXW1nhqK9oEBmx5MQzb7FFKXpamrOsGVMfVjROek/wz3YoW8fE30A8r +Zezc0uxkAyq7GQ4+ur/Em8qGhA7YXGmoHd4+h2PpU2Co8iW6i5Mftt8DfSFHFCyt +Yu9xMhX2o8HckPUXQTKVAcOY/S2JjpFdwjX6cc9iDZ3+ETMtIUtv+MSICaTU7PLv +eHAl5nodMctAI9+NaQkO81z8XdoZAgMBAAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYD +VR0OBBYEFEuSnkkMoeox4UP07oJsZGcEMYAWMB8GA1UdIwQYMBaAFBBrunbDxn3d +ZwfysAIt6DjRylEIMA0GCSqGSIb3DQEBCwUAA4IBAQB00UMFcTDyDUidWamh/fzS +Z6pv2ms0mKHXeVYdqUGtWPjl9uocWGJXdgD3C77Ifpx02zayhtpdfSvvajyEnTAd +XPM8jb/VBXpgW7wA7vMRoewvXLG4xITbh+HXKhDh1n+KLAJLSB4uBrmbmx1/pgpM +rXJPAGv4ARzkozcs98qWND3dWAjDNn+7Wxb0wVhcYgrmvyQyNAUdgYImxZSqn7rR +sP5rr7FbAZLGSHx0h9hzav3XuWRv7+mn1m9QplsvaJV2EehiY92C0JmNHt9BvMcX +XTWGWgXMVeacm/0W+3XmQWtltDnBQq8fUsEy0Uts6fJdD/5tEmvYfwEtpomxdFds +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC5TCCAc2gAwIBAgIUXtcOjsFS175nGr07+htPEPGe2bYwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJVEVTVC1yb290MB4XDTIzMDgwMTAxNDUxM1oXDTMzMDcy +OTAxNDUxM1owFDESMBAGA1UEAwwJVEVTVC1yb290MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA1cgfqUNIP0L356sUVEieG0ObtMDrnwnEXGSXX7c3dlSd +oiXJStYDsC+DbyV2+yV24sgkDJM2YwuWvKntHb56hVXzeTydWekDrfLlHw9alYml +llJUXbsTIBBUdk2aT/dFzPyZTN8HhtU8327w7latxKJXrYNpJCn6fcRtSSyo6dIm +QXxvyF/kMLHIirBoOHGKXVVfZFRrDafFbux//1duw+TIjUfVin0rQE/z28iN+3TY +ihXtJ2fhVm+WWR1w7IzoLFV6Xu23JIk92Fxvowho4p6BTOzsGeHPdlPoR0b5ECbI +8VqSLgsa8WOPLmxTNmcuk1BHYVf+Dr3YU/Xb9TBa5QIDAQABoy8wLTAMBgNVHRME +BTADAQH/MB0GA1UdDgQWBBQQa7p2w8Z93WcH8rACLeg40cpRCDANBgkqhkiG9w0B +AQsFAAOCAQEAXp7kVoWuGbI1nep/FTL+PVptYQzD4IfnsonSKZvB7yYcGS0OZtGI +cNr52WOx4EpksG58JsQxjowu5kAdeSwGI5cCKdAWMA/BpJhT+uOP+9Y+QBXFBM25 +50cZax5FFFCKWUcOrv7SSeaGRe3X13G6pPULwwS5WqFb0LZdL0gI9GxN0S9X6F/N +g+T0akcluAe6xNIltnw6AeaUQXzK+jy/3zuSAulh6oiSm7kTU9kXLZ3Xiobko/MB +PaDKL+Ygt0c2hX5TRVJQ2Bdvn5z5kkBJxT7Rb9uj8kT9JETbwQD3bssZB+tPkwvE +O5mpOpR0Jkni8lJmOYrRYz9Jf9Cm0uQWlw== +-----END CERTIFICATE----- diff --git a/fixtures/chain.key b/fixtures/chain.key new file mode 100644 index 00000000..bf579a9b --- /dev/null +++ b/fixtures/chain.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCiLsCk5U0Xb0VS +RaHJNd1q/X2aAgf7EPGi/4ul6XpZp8gT3iRu6sErsRc33JS2svKRN0eRMXduvTO3 +q9S6aGgMAZUzy7jlDUzgRu8AckEiOATxfvb+4Bz0zGy/pVw0bIUiYDMOeJ8RgX0M +VT6NrBTztDAj6npTXrtRYzD+SoOq4NkVYCxzDAJtO1+QIe59WJGCK5wz+VjfUbJb +xbpQFs1de17uq3LrE6BqksKiZsIgd4vGAembsrIm8Z5Uv3bAJukLFtlL380sCrhR +M84IelbSPKQLTjAwPcVFvgC+SS0ezD3I5Og2FcZqVnLSyjAsCmeq5FENll1y66i3 +QxK0XtQ7AgMBAAECggEAAmfHG6r69boEwS+fMqjF2+xejIYzMBoUO2Km45MO6X1/ +jivhRnPc4ZUCzyVKX1tQFa9INHMTXmUX+0VlJk6eHG95kwcWeu8zkK/8o3kOVj+W +cy5641TXmRnfEwiU0YI6h0P9/dz0HwJYpHIvN1KyDNtDS4USw2HITXC3LU5VnZcq +FWdjKTQNdnm5AamTyov2SXb9LKJkxicCKxyJODuMYWe6MX0G51DUvmfWr5bQ3VbD +eOG1Nf1g2pSaDA+xYTeLwrn/LvdATzXsvQZdD2y5u3m11WZFoczHA6MNciyt5Jk2 +kkXJlfxn10A3GXsedWxE1mq/VZ3l+vO311mdoqRiIQKBgQDQLxlkmmy3lbbNfVZc +yr8+7dal1puBTc2ZfIq7Kr4ZSCXaVO41hLpi299OY1UEOdoKfnFBg6c1icDuSfvi +MivU9a70h0XBTJXTYK+6FWXoRsiF9Ale/JmgMU/n0QiWoHMoi+jsXgQZ1WyUXcR6 +45HDbzfSnZIKrTyfJu8LuT9arwKBgQDHbtgDvXA8ewZJi0RWq0oXNS5XYr9ySe94 +LpWCoXT8hgeqNM8Ly43mUXHZiGsrbIHmgkAKJhylthc9CUCRJ4w0JL7UtkPZtcgi +UaydfJ4io9Sx0KgZ38e/1bq3rolC7kESNpGeJHMrF4hUAI2gEgZEtf0m5rzKpaGo +4yu8HVtSNQKBgBTi1MpaD9QvSgK9s19l2+AFXoaOzFUhqCHg884pUJ8atOl9odRu +t4BZjMLBhnMBFtX8r4IiIjFl25xMgd/Eps8bwuy3cZEeDN4DEj46DVpLV6zQuev0 +rbj7mYepWhI6kLMdrkWgfQrWdalA6whlMmeIDfKsak116eIRtuPXNvrzAoGAUVHW +TTgaot67QpPCCuEPdgUeX02JqO2S8ttz5W82h52TVIjx/+pBcy+0j7H7mRpGoZps +yHaf6cYlFaNHK6kHl1+AXLXxVr2z3KKXEyR0SsWo/dSKJvrDtWpOF4XYvGzwJaAQ +on5UY7bVxQLwvNt8qNnYXttpEeyVzYrME4mY6h0CgYBBVY08DRSkrrBwiY9sQWbF +XVdWuffdLuuI27lVcBl0UwJh37EwXN0fsWhZ3upAitAzE3aBNwF5KHvq00yCsMKr +A4bo7DeckePIfCAxdvp8kAuq+NwW8tkpMcqeb1ja2TPrgSy9eyHingzvMtYNDTxB +i45lVy8xNJU85Fmzex0B0g== +-----END PRIVATE KEY----- diff --git a/fixtures/generate.bash b/fixtures/generate.bash new file mode 100755 index 00000000..f028ea70 --- /dev/null +++ b/fixtures/generate.bash @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +function prepare() { + local cwd=$1 + local fwd=$(readlink -f "$cwd") + mkdir -p "$cwd"/{certs,crl,newcerts,private} + echo 1000 > "$cwd/serial" + touch "$cwd"/{index.txt,index.txt.attr} + + echo ' + [ ca ] + default_ca = CA_default + [ CA_default ] + dir = '"$fwd"' + certs = $dir/certs # Where the issued certs are kept + crl_dir = $dir/crl # Where the issued crl are kept + database = $dir/index.txt # database index file. + new_certs_dir = $dir/newcerts # default place for new certs. + certificate = $dir/cacert.pem # The CA certificate + serial = $dir/serial # The current serial number + crl = $dir/crl.pem # The current CRL + private_key = $dir/private/ca.key.pem # The private key + RANDFILE = $dir/.rnd # private random number file + nameopt = default_ca + certopt = default_ca + policy = policy_match + default_days = 365 + default_md = sha256 + + [ policy_match ] + countryName = optional + stateOrProvinceName = optional + organizationName = optional + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + [req] + req_extensions = v3_req + distinguished_name = req_distinguished_name + + [req_distinguished_name] + + [v3_req]' > "$cwd/openssl.cnf" + + if [[ $cwd == out ]] ; then + echo "keyUsage = digitalSignature, keyEncipherment" >> "$cwd/openssl.cnf" + echo "extendedKeyUsage = serverAuth, clientAuth" >> "$cwd/openssl.cnf" + echo "subjectAltName = DNS:localhost" >> "$cwd/openssl.cnf" + else + echo "basicConstraints = CA:TRUE" >> "$cwd/openssl.cnf" + fi +} + +# chain generates three certificates in a chain. +function chain() { + rm {root,intermediate,out} -rf + prepare root + prepare intermediate + prepare out + + # Create root certificate and key. + openssl genrsa -out root/private/ca.key 2048 + openssl req -new -x509 -sha256 -days 3650 \ + -config root/openssl.cnf -extensions v3_req \ + -key root/private/ca.key --out root/certs/ca.crt \ + -subj '/CN=TEST-root' + + # Create intermediate key and request. + openssl genrsa -out intermediate/private/intermediate.key 2048 + openssl req -new -sha256 \ + -config intermediate/openssl.cnf -extensions v3_req \ + -key intermediate/private/intermediate.key -out intermediate/certs/intermediate.csr \ + -subj '/CN=TEST-intermediate' + + # Sign intermediate request with root to create a cert. + openssl ca -batch -notext -md sha256 \ + -config intermediate/openssl.cnf -extensions v3_req \ + -keyfile root/private/ca.key -cert root/certs/ca.crt \ + -in intermediate/certs/intermediate.csr \ + -out intermediate/certs/intermediate.crt + + # Create a key and request for an end certificate. + openssl req -new -days 365 -nodes -newkey rsa:2048 \ + -config out/openssl.cnf -extensions v3_req \ + -keyout out/private/localhost.key -out out/certs/localhost.csr \ + -subj "/CN=localhost" + + # Sign that with the intermediate. + openssl ca -batch \ + -config out/openssl.cnf -extensions v3_req \ + -keyfile intermediate/private/intermediate.key -cert intermediate/certs/intermediate.crt \ + -out out/certs/localhost.crt \ + -infiles out/certs/localhost.csr + + mv out/certs/localhost.crt chain-leaf.crt + mv out/private/localhost.key chain-leaf.key + mv intermediate/certs/intermediate.crt chain-intermediate.crt + mv intermediate/private/intermediate.key chain-intermediate.key + mv root/certs/ca.crt chain-root.crt + mv root/private/ca.key chain-root.key + + rm {out,intermediate,root} -r + + cat chain-leaf.crt chain-intermediate.crt chain-root.crt > chain.crt + cp chain-leaf.key chain.key +} + +# non-signing generates a self-signed certificate that has cert signing +# explicitly omitted. +function non-signing() { + openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout no-signing.key -out no-signing.crt \ + -addext "keyUsage = digitalSignature, keyEncipherment" \ + -addext "subjectAltName=DNS:localhost" \ + -subj "/CN=localhost" +} + +# self-signed generates a certificate without specifying key usage. +function self-signed() { + openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout self-signed.key -out self-signed.crt \ + -addext "subjectAltName=DNS:localhost" \ + -subj "/CN=localhost" +} + +function main() { + local name=$1 ; shift + "$name" "$@" +} + +main "$@" diff --git a/fixtures/no-signing.crt b/fixtures/no-signing.crt new file mode 100644 index 00000000..b1d44acf --- /dev/null +++ b/fixtures/no-signing.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDLDCCAhSgAwIBAgIUZVpY6+MUJZuW/UJhu7r8crizzh0wDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDgwMTAxMDQ0OFoXDTIzMDgz +MTAxMDQ0OFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzIxlOvO0DmWd0a2dA3f3uy6VuQ6kLHp+D4QNUvzAqSGE +y6WCUQD0UWxqC+tXoHq9wX35qPPon9Ei2e3VarWv9Fc0BNN2I2pMgJDipxjuQC+P +RMwDwCD+zsVQbfTel2ntfP4OAcs58964Rc9ZgMiZwxBWMOBbTUt68R3ba/oKKdEV +AOBequ32qrmLMhNzFKdEooe8DzpPOXO3kaTxOoSUTx85UJfwdL+6vFYhDHJ6pvRU +QvJj7G+H03YL4zBKRucAlI3jcaNAcNm5JCAliv4yPzo2PEl2aFfG2DL5lVhDU8S4 +62iTXpnPwSQAtloSNKE+xTzqsusfeJV/YFEnqTGYCwIDAQABo3YwdDAdBgNVHQ4E +FgQU35DsH5EDKMKE0VTWs+YSWjT6u1gwHwYDVR0jBBgwFoAU35DsH5EDKMKE0VTW +s+YSWjT6u1gwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCBaAwFAYDVR0RBA0w +C4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQDHgOSJfsSCD1SwFqY/k8eH +o95y7jqlCH48jw+BxHh7W/jr7NnUUXRP0NgQjhUcJSBZ8mWqCQtdpwttEe5eYYGg +0uA7FoiDfzEkyAs9QMAxDfgpHloafh1sJBdxiiUsu6LeYzzPcSajPEOlPNIpR7Uu +CFy1fr/PMAmOPeIuF6NNYfwk9Isevqf/U8R5QK99abWJnP8S2Lqk90EFAdO7RAAZ +g3xsVqO0Tzs9yvtHRSe92q5M/hyRMHTF8mPKQwTnSPXzxhVdYiTJ5VEZQOIhey0V +s1sGtEpSUwhNiMEypJJbDhDJ6S5pn9lqDbSoCiRjXufq3ltu90Zht+2kCUCjTSBb +-----END CERTIFICATE----- diff --git a/fixtures/no-signing.key b/fixtures/no-signing.key new file mode 100644 index 00000000..f8fdcda2 --- /dev/null +++ b/fixtures/no-signing.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDMjGU687QOZZ3R +rZ0Dd/e7LpW5DqQsen4PhA1S/MCpIYTLpYJRAPRRbGoL61eger3Bffmo8+if0SLZ +7dVqta/0VzQE03YjakyAkOKnGO5AL49EzAPAIP7OxVBt9N6Xae18/g4Byznz3rhF +z1mAyJnDEFYw4FtNS3rxHdtr+gop0RUA4F6q7faquYsyE3MUp0Sih7wPOk85c7eR +pPE6hJRPHzlQl/B0v7q8ViEMcnqm9FRC8mPsb4fTdgvjMEpG5wCUjeNxo0Bw2bkk +ICWK/jI/OjY8SXZoV8bYMvmVWENTxLjraJNemc/BJAC2WhI0oT7FPOqy6x94lX9g +USepMZgLAgMBAAECggEAN+IvkgCc88X9bRjCqfzvuLsMeseuQNyibjjErySQumSG +9GBejyT0mv5EpscAZL7D8wYo3Gju6CLqI6IfyYyj6tycQKlJQHm9Nu7ejYp5JR4Z +RVF7wNUC6Jjt1WyuF64ADUMXrpPIXIE/1QrGSDIGGE3xTl8tcpuplhBzLzfb6PpR +8A0aJnVSDeWWrJbVlBeAHZpOwx5Ty0RiP2qKcqY/56CvXNOPOyHAZ2DSEXoUGLVJ +WK6lDTXqJe4TGbV2OFOEUs37ZLT+szJm6pZfjO4+n+Ne9v2SXrkrZZY4dKvEJ7zk +HOi/J0Sp7Wp530AzyPqeUYqjkuWSJyHqlP2KyQa40QKBgQD7FJ4bvOINF5J5jTqX +cKJm7Iev8dBq5kqlnkabPA131UD6leozxhDy46tJJIe5Gf3bvOXia6pvzBQwFpK2 +HhMKrZeklNDYGOf8Q1qqpyvy+oMX9UVQAj0Gmix1TJ90hEXr6M1QCosv3iUHBVy3 +fX0mQ2ejYTKUy2H0451dsJ1bqQKBgQDQjmEy4T5N7Vj1rNUNaXgH6o1q5wYjYif5 +4/kRP37WnowtLbu0foObuMtr4feSqEQQQ+++JDxcKNXtPtw7cgWFdeuIpKBfynBM +nN4ZQtFo8btLmIxYNS81jum0z9xEwlrmxweyG0ic+AWpnSwJKKio2rGeV3AYOlUv +/5iGxzAGkwKBgB+XaoVm8LJhAucUZAjl/SkiHbh/no+0xjOshInHtpIbXP+qmTtG +cp99EfI0DHe3038wd2RT07AZZ3jdfjw38IFpcikdKGHoUFgnSWMrgZYi+xeqnrwA +bBlGkM15hJ6uffW+5wZpLTYqp1II1K+ptHN6C7I86pZaOMsNUKGXNVVxAoGBAI+g +DgYpImwuMV53WP30jUn+WMevNAX4Ggm42xTqwmHxLB4M6cCig+Yg/E5efs3L/Zup +/ZfXgo4BPVo2ORrjKjRAAiXHIf71/iJ0wWtqHacFGnQ0KSqx7cIXmpD/uPTNWCao +GH+q1HXtRJELgYEJNCWc/kdKdhLpUTNN9W+UC1nrAoGBALkHM/O3UOZ54Pe1jAYG +Z2616JijXvE1X9acAsfhcgZdx7ZyLav1uyrghy0AbAS7fxEWn+xy/WCDwc4WE4Xt +Hp2Rijb0SCFQfCsdQhHUkoxMZ2RC6TWXZQahHqUYkJE2hUv/b1gW1FvNdaOSPdo/ +thqRcHcdz3OvBOh1Of5VbK4Z +-----END PRIVATE KEY----- diff --git a/fixtures/self-signed.crt b/fixtures/self-signed.crt new file mode 100644 index 00000000..4a0c4c0f --- /dev/null +++ b/fixtures/self-signed.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHzCCAgegAwIBAgIUAIJ2Nm72cyMe8h50yxwciqUvyRowDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDgwMTAzMzAwNloXDTIzMDgz +MTAzMzAwNlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAqk+QAIy+nH7z7gy3ZddBg7KHmmgrSWO9UzqvqiGH27wG +5T3+ze4KcBBq56Hoyaot7zDt9eiNYgnPbpn4ia+LkuScovRcdPon2tzGP01Tis9f +0xuRzJHBG/87aJeMGGQ3Ov9hIiwfHxPI54a2eWLDvwMW2q5MJ51FQB3YNFXYSfEl +y8YdTqcw0sYqgttrA2oM0x7VLm0NMvXGNy9AztYTMRPOYCZKpv8CTcMIMDhfPgzd +qF+ZnAEIGRwxAlLyc26FHtHGlrXAzxKO3D395QMz7DwFB19Wrmc9+euMFHq4C+fs +LPHUxrPg6ls73WLCC+1tZt9EXq7d6IllmO1+b34mDwIDAQABo2kwZzAdBgNVHQ4E +FgQUYRtx682lSIu7655mQ/dMW7b9ppIwHwYDVR0jBBgwFoAUYRtx682lSIu7655m +Q/dMW7b9ppIwDwYDVR0TAQH/BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3Qw +DQYJKoZIhvcNAQELBQADggEBAJ6R4vEG7JPFTwY4fngppJuTldwNnm6R+sageiq6 +0eCUpEeTQp2321Ohx7YTZVt2LSPheAoH/H3xgaCstivn4JtOQI+3dHQiFL9DbtQP +Ihx+oYUIfHWU0PIRimtcBtN/FmP5FON6BmitrzPPJN8WWc5DBqoIj9he4KofweH3 +Wd2WCpdN0WuAcojBp9TDad+4LQxe0kO0mNGlThtHH9+MoswIGKFal1yTon3JDlP+ +1gFJzOmumw/yoMhsulazDAjPKzg8kE0W3la/NZ1xx1pUNde/H1VndRxxFSfG37gn +FnsTQCj8cfeTP74QldUP+OcJInx7FmBZd4S5A0kFPEoJK6M= +-----END CERTIFICATE----- diff --git a/fixtures/self-signed.key b/fixtures/self-signed.key new file mode 100644 index 00000000..43961729 --- /dev/null +++ b/fixtures/self-signed.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqT5AAjL6cfvPu +DLdl10GDsoeaaCtJY71TOq+qIYfbvAblPf7N7gpwEGrnoejJqi3vMO316I1iCc9u +mfiJr4uS5Jyi9Fx0+ifa3MY/TVOKz1/TG5HMkcEb/ztol4wYZDc6/2EiLB8fE8jn +hrZ5YsO/AxbarkwnnUVAHdg0VdhJ8SXLxh1OpzDSxiqC22sDagzTHtUubQ0y9cY3 +L0DO1hMxE85gJkqm/wJNwwgwOF8+DN2oX5mcAQgZHDECUvJzboUe0caWtcDPEo7c +Pf3lAzPsPAUHX1auZz3564wUergL5+ws8dTGs+DqWzvdYsIL7W1m30Rert3oiWWY +7X5vfiYPAgMBAAECggEAUtayP2I2EJ67fU1YI0QJTMqYpKUIcQD/hK0l5oW6tEmH +vRdjiby//PQvW41oUjbhcteE1ziPFGGbMLvZpPbq17805RogopFOs2jxULcG7Jyj +imZ9i1hV6o1B00TrUq3kI2E2WM3HMXGJJfxjYzWD6rPQ+PsQdbBMj7w0fnhG5OeI +g0SBC3JGmw9Nsnp9gX6fvMMaUPhMgvFb9V5p+7hlJ77Yho7sZ2SAgn82BeFx5znX +RIqYXeI2Vgef5ECEeCv8W7qFZ4sy0WUbq1EHdXFhfsksxCLyIxRdLmZ50sksJNrg +Si+VjMXVCDibkb/oS/nW25SSXvnrbvvYnvcycBXuUQKBgQDVUsbEe3h/MlzoEGId +crSf2hMV9NcKpx7Y0/IlLO/wgTlW3BeBOOFdA0eDgoC8Aqsw/vytkUY0jsRyyYCe +yanw1fpXB9J1OdIGLb3OrznZfQAblSihJHvKRU7pB+mBrar/+UBOmqMjzRi6AK9Q +qM09N0fREFrQ+6Qv1zrahwHV2QKBgQDMYey0bvZt3hKqJsoX+bQGxNanfj615c93 +qEThBaW4jMIr2pIZkVDb+y4Obmwb6hppn/u9/0HOsiLR/KaqS2z1TLq9i59b9hKl +H6JNJThbPomZHGHRD3HpJI6QpfT00egLEnRnPg0ssducBApUGX1W8IOCmbIYwXAl +risPVbXiJwKBgDAcvEHCSzn85OFeGJLltQE8kQNptjpr2NQ0cS+bQ/5tVr5VY2O8 +rW9p9u4dN+WvgGbLi7elxTzDWmE9OyoU96QezphkZj4ULV9BX8bG1HhN7gFKkeBO +NzE2koaSR0L9JU0YLT3NOLAxaLtCvkel2qxM1IC9fI4Xwz8a/uYcfvh5AoGAPq/G +Ty09jkMvzFprX+Eps23KPMM+7sGW2aeVwMLfqnQZ1iOK7iag+2fWH30E0acDBOSZ +7ROOlpwSi/+HCvJpb+9h02MwtJ8L5vOF7018NJhA0eJfqiSnlo+s3nbYZALBviuh +4kyo8811gyvGEzdiNzk7zOHhOzCRei0qbeCnEb8CgYEArQHfrXWuJCfELkx48ChI +gIyi13W0FhMcyH20xjC6+djZK37X1Mg0eSfVts8I2wmNecntCZqKBakpe9boh9++ +5gF4HfjFfZBtV/XH7qt3FW6jwGA+5E3zwZW2cS65VwXOkfyZ8tOmJyFc/OnZbUVA +1UnQhbqUBoaHQZLYdlHdc8M= +-----END PRIVATE KEY----- diff --git a/package.json b/package.json index 12243dad..2a994ae5 100644 --- a/package.json +++ b/package.json @@ -231,6 +231,7 @@ "webpack-cli": "^5.0.1" }, "dependencies": { + "@types/node-forge": "^1.3.4", "@types/ua-parser-js": "^0.7.36", "axios": "0.26.1", "date-fns": "^2.30.0", @@ -240,6 +241,7 @@ "jsonc-parser": "^3.2.0", "memfs": "^3.4.13", "ndjson": "^2.0.0", + "node-forge": "^1.3.1", "pretty-bytes": "^6.0.0", "semver": "^7.3.8", "tar-fs": "^2.1.1", diff --git a/src/commands.ts b/src/commands.ts index 079aac20..4372d6b6 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -3,7 +3,7 @@ import { getAuthenticatedUser, getWorkspaces, updateWorkspaceVersion } from "cod import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" import { extractAgents } from "./api-helper" -import { SelfSignedCertificateError } from "./error" +import { CertificateError } from "./error" import { Remote } from "./remote" import { Storage } from "./storage" import { OpenableTreeItem } from "./workspacesProvider" @@ -62,8 +62,8 @@ export class Commands { if (axios.isAxiosError(err) && err.response?.data) { message = err.response.data.detail } - if (err instanceof SelfSignedCertificateError) { - err.showInsecureNotification(this.storage) + if (err instanceof CertificateError) { + err.showNotification() return { message: err.message, @@ -199,8 +199,8 @@ export class Commands { quickPick.busy = false }) .catch((ex) => { - if (ex instanceof SelfSignedCertificateError) { - ex.showInsecureNotification(this.storage) + if (ex instanceof CertificateError) { + ex.showNotification() } return }) diff --git a/src/error.test.ts b/src/error.test.ts new file mode 100644 index 00000000..a966afd8 --- /dev/null +++ b/src/error.test.ts @@ -0,0 +1,224 @@ +import axios from "axios" +import * as fs from "fs/promises" +import https from "https" +import * as path from "path" +import { afterAll, beforeAll, it, expect, vi } from "vitest" +import { CertificateError, X509_ERR, X509_ERR_CODE } from "./error" + +// Before each test we make a request to sanity check that we really get the +// error we are expecting, then we run it through CertificateError. + +// TODO: These sanity checks need to be ran in an Electron environment to +// reflect real usage in VS Code. We should either revert back to the standard +// extension testing framework which I believe runs in a headless VS Code +// instead of using vitest or at least run the tests through Electron running as +// Node (for now I do this manually by shimming Node). +const isElectron = process.versions.electron || process.env.ELECTRON_RUN_AS_NODE + +// TODO: Remove the vscode mock once we revert the testing framework. +beforeAll(() => { + vi.mock("vscode", () => { + return {} + }) +}) + +const logger = { + writeToCoderOutputChannel(message: string) { + throw new Error(message) + }, +} + +const disposers: (() => void)[] = [] +afterAll(() => { + disposers.forEach((d) => d()) +}) + +async function startServer(certName: string): Promise { + const server = https.createServer( + { + key: await fs.readFile(path.join(__dirname, `../fixtures/${certName}.key`)), + cert: await fs.readFile(path.join(__dirname, `../fixtures/${certName}.crt`)), + }, + (req, res) => { + if (req.url?.endsWith("/error")) { + res.writeHead(500) + res.end("error") + return + } + res.writeHead(200) + res.end("foobar") + }, + ) + disposers.push(() => server.close()) + return new Promise((resolve, reject) => { + server.on("error", reject) + server.listen(0, "localhost", () => { + const address = server.address() + if (!address) { + throw new Error("Server has no address") + } + if (typeof address !== "string") { + const host = address.family === "IPv6" ? `[${address.address}]` : address.address + return resolve(`https://${host}:${address.port}`) + } + resolve(address) + }) + }) +} + +// Both environments give the "unable to verify" error with partial chains. +it("detects partial chains", async () => { + const address = await startServer("chain-leaf") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + ca: await fs.readFile(path.join(__dirname, "../fixtures/chain-leaf.crt")), + }), + }) + await expect(request).rejects.toHaveProperty("code", X509_ERR_CODE.UNABLE_TO_VERIFY_LEAF_SIGNATURE) + try { + await request + } catch (error) { + const wrapped = await CertificateError.maybeWrap(error, address, logger) + expect(wrapped instanceof CertificateError).toBeTruthy() + expect((wrapped as CertificateError).x509Err).toBe(X509_ERR.PARTIAL_CHAIN) + } +}) + +it("can bypass partial chain", async () => { + const address = await startServer("chain-leaf") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }) + await expect(request).resolves.toHaveProperty("data", "foobar") +}) + +// In Electron a self-issued certificate without the signing capability fails +// (again with the same "unable to verify" error) but in Node self-issued +// certificates are not required to have the signing capability. +it("detects self-signed certificates without signing capability", async () => { + const address = await startServer("no-signing") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + ca: await fs.readFile(path.join(__dirname, "../fixtures/no-signing.crt")), + servername: "localhost", + }), + }) + if (isElectron) { + await expect(request).rejects.toHaveProperty("code", X509_ERR_CODE.UNABLE_TO_VERIFY_LEAF_SIGNATURE) + try { + await request + } catch (error) { + const wrapped = await CertificateError.maybeWrap(error, address, logger) + expect(wrapped instanceof CertificateError).toBeTruthy() + expect((wrapped as CertificateError).x509Err).toBe(X509_ERR.NON_SIGNING) + } + } else { + await expect(request).resolves.toHaveProperty("data", "foobar") + } +}) + +it("can bypass self-signed certificates without signing capability", async () => { + const address = await startServer("no-signing") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }) + await expect(request).resolves.toHaveProperty("data", "foobar") +}) + +// Both environments give the same error code when a self-issued certificate is +// untrusted. +it("detects self-signed certificates", async () => { + const address = await startServer("self-signed") + const request = axios.get(address) + await expect(request).rejects.toHaveProperty("code", X509_ERR_CODE.DEPTH_ZERO_SELF_SIGNED_CERT) + try { + await request + } catch (error) { + const wrapped = await CertificateError.maybeWrap(error, address, logger) + expect(wrapped instanceof CertificateError).toBeTruthy() + expect((wrapped as CertificateError).x509Err).toBe(X509_ERR.UNTRUSTED_LEAF) + } +}) + +// Both environments have no problem if the self-issued certificate is trusted +// and has the signing capability. +it("is ok with trusted self-signed certificates", async () => { + const address = await startServer("self-signed") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + ca: await fs.readFile(path.join(__dirname, "../fixtures/self-signed.crt")), + servername: "localhost", + }), + }) + await expect(request).resolves.toHaveProperty("data", "foobar") +}) + +it("can bypass self-signed certificates", async () => { + const address = await startServer("self-signed") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }) + await expect(request).resolves.toHaveProperty("data", "foobar") +}) + +// Both environments give the same error code when the chain is complete but the +// root is not trusted. +it("detects an untrusted chain", async () => { + const address = await startServer("chain") + const request = axios.get(address) + await expect(request).rejects.toHaveProperty("code", X509_ERR_CODE.SELF_SIGNED_CERT_IN_CHAIN) + try { + await request + } catch (error) { + const wrapped = await CertificateError.maybeWrap(error, address, logger) + expect(wrapped instanceof CertificateError).toBeTruthy() + expect((wrapped as CertificateError).x509Err).toBe(X509_ERR.UNTRUSTED_CHAIN) + } +}) + +// Both environments have no problem if the chain is complete and the root is +// trusted. +it("is ok with chains with a trusted root", async () => { + const address = await startServer("chain") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + ca: await fs.readFile(path.join(__dirname, "../fixtures/chain-root.crt")), + servername: "localhost", + }), + }) + await expect(request).resolves.toHaveProperty("data", "foobar") +}) + +it("can bypass chain", async () => { + const address = await startServer("chain") + const request = axios.get(address, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }) + await expect(request).resolves.toHaveProperty("data", "foobar") +}) + +it("falls back with different error", async () => { + const address = await startServer("chain") + const request = axios.get(address + "/error", { + httpsAgent: new https.Agent({ + ca: await fs.readFile(path.join(__dirname, "../fixtures/chain-root.crt")), + servername: "localhost", + }), + }) + await expect(request).rejects.toMatch(/failed with status code 500/) + try { + await request + } catch (error) { + const wrapped = await CertificateError.maybeWrap(error, "1", logger) + expect(wrapped instanceof CertificateError).toBeFalsy() + expect(wrapped.message).toMatch(/failed with status code 500/) + } +}) diff --git a/src/error.ts b/src/error.ts index 4c247836..181ee67c 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,51 +1,156 @@ -import * as fs from "fs/promises" -import * as jsonc from "jsonc-parser" +import axios from "axios" +import * as forge from "node-forge" +import * as tls from "tls" import * as vscode from "vscode" -import { Storage } from "./storage" -export class SelfSignedCertificateError extends Error { - public static Notification = - "Your Coder deployment is using a self-signed certificate. VS Code uses a version of Electron that does not support registering self-signed intermediate certificates with extensions." +// X509_ERR_CODE represents error codes as returned from BoringSSL/OpenSSL. +export enum X509_ERR_CODE { + UNABLE_TO_VERIFY_LEAF_SIGNATURE = "UNABLE_TO_VERIFY_LEAF_SIGNATURE", + DEPTH_ZERO_SELF_SIGNED_CERT = "DEPTH_ZERO_SELF_SIGNED_CERT", + SELF_SIGNED_CERT_IN_CHAIN = "SELF_SIGNED_CERT_IN_CHAIN", +} + +// X509_ERR contains human-friendly versions of TLS errors. +export enum X509_ERR { + PARTIAL_CHAIN = "Your Coder deployment's certificate cannot be verified because a certificate is missing from its chain. To fix this your deployment's administrator should bundle the missing certificates or you can add the missing certificates directly to this system's trust store.", + // NON_SIGNING can be removed if BoringSSL is patched and the patch makes it + // into the version of Electron used by VS Code. + NON_SIGNING = "Your Coder deployment's certificate is not marked as being capable of signing. VS Code uses a version of Electron that does not support certificates like this even if they are self-issued. The certificate should be regenerated with the certificate signing capability.", + UNTRUSTED_LEAF = "Your Coder deployment's certificate does not appear to be trusted by this system. The certificate should be added to this system's trust store.", + UNTRUSTED_CHAIN = "Your Coder deployment's certificate chain does not appear to be trusted by this system. The root of the certificate chain should be added to this system's trust store. ", +} + +export interface Logger { + writeToCoderOutputChannel(message: string): void +} + +interface KeyUsage { + keyCertSign: boolean +} + +export class CertificateError extends Error { public static ActionAllowInsecure = "Allow Insecure" public static ActionViewMoreDetails = "View More Details" + public static InsecureMessage = + 'The Coder extension will no longer verify TLS on HTTPS requests. You can change this at any time with the "coder.insecure" property in your VS Code settings.' - constructor(message: string) { - super(`Your Coder deployment is using a self-signed certificate: ${message}`) + private constructor(message: string, public readonly x509Err?: X509_ERR) { + super("Secure connection to your Coder deployment failed: " + message) } - public viewMoreDetails(): Thenable { - return vscode.env.openExternal(vscode.Uri.parse("https://github.com/coder/vscode-coder/issues/105")) + // maybeWrap returns a CertificateError if the code is a certificate error + // otherwise it returns the original error. + static async maybeWrap(err: T, address: string, logger: Logger): Promise { + if (axios.isAxiosError(err)) { + switch (err.code) { + case X509_ERR_CODE.UNABLE_TO_VERIFY_LEAF_SIGNATURE: + // "Unable to verify" can mean different things so we will attempt to + // parse the certificate and determine which it is. + try { + const cause = await CertificateError.determineVerifyErrorCause(address) + return new CertificateError(err.message, cause) + } catch (error) { + logger.writeToCoderOutputChannel(`Failed to parse certificate from ${address}: ${error}`) + break + } + case X509_ERR_CODE.DEPTH_ZERO_SELF_SIGNED_CERT: + return new CertificateError(err.message, X509_ERR.UNTRUSTED_LEAF) + case X509_ERR_CODE.SELF_SIGNED_CERT_IN_CHAIN: + return new CertificateError(err.message, X509_ERR.UNTRUSTED_CHAIN) + } + } + return err } - // allowInsecure manually reads the settings file and updates the value of the - // "coder.insecure" property. - public async allowInsecure(storage: Storage): Promise { - let settingsContent = "{}" - try { - settingsContent = await fs.readFile(storage.getUserSettingsPath(), "utf8") - } catch (ex) { - // Ignore! It's probably because the file doesn't exist. - } - const edits = jsonc.modify(settingsContent, ["coder.insecure"], true, {}) - await fs.writeFile(storage.getUserSettingsPath(), jsonc.applyEdits(settingsContent, edits)) + // determineVerifyErrorCause fetches the certificate(s) from the specified + // address, parses the leaf, and returns the reason the certificate is giving + // an "unable to verify" error or throws if unable to figure it out. + static async determineVerifyErrorCause(address: string): Promise { + return new Promise((resolve, reject) => { + try { + const url = new URL(address) + const socket = tls.connect( + { + port: parseInt(url.port, 10) || 443, + host: url.hostname, + rejectUnauthorized: false, + }, + () => { + const x509 = socket.getPeerX509Certificate() + socket.destroy() + if (!x509) { + throw new Error("no peer certificate") + } + + // We use node-forge for two reasons: + // 1. Node/Electron only provide extended key usage. + // 2. Electron's checkIssued() will fail because it suffers from same + // the key usage bug that we are trying to work around here in the + // first place. + const cert = forge.pki.certificateFromPem(x509.toString()) + if (!cert.issued(cert)) { + return resolve(X509_ERR.PARTIAL_CHAIN) + } - vscode.window.showInformationMessage( - 'The Coder extension will no longer verify TLS on HTTPS requests. You can change this at any time with the "coder.insecure" property in your VS Code settings.', + // The key usage needs to exist but not have cert signing to fail. + const keyUsage = cert.getExtension({ name: "keyUsage" }) as KeyUsage | undefined + if (keyUsage && !keyUsage.keyCertSign) { + return resolve(X509_ERR.NON_SIGNING) + } else { + // This branch is currently untested; it does not appear possible to + // get the error "unable to verify" with a self-signed certificate + // unless the key usage was the issue since it would have errored + // with "self-signed certificate" instead. + return resolve(X509_ERR.UNTRUSTED_LEAF) + } + }, + ) + socket.on("error", reject) + } catch (error) { + reject(error) + } + }) + } + + viewMoreDetails(): Thenable { + return vscode.env.openExternal( + vscode.Uri.parse("https://github.com/coder/vscode-coder/issues/115#issuecomment-1631512493"), ) } - public async showInsecureNotification(storage: Storage): Promise { - const value = await vscode.window.showErrorMessage( - SelfSignedCertificateError.Notification, - SelfSignedCertificateError.ActionAllowInsecure, - SelfSignedCertificateError.ActionViewMoreDetails, + // allowInsecure updates the value of the "coder.insecure" property. + async allowInsecure(): Promise { + vscode.workspace.getConfiguration().update("coder.insecure", true, vscode.ConfigurationTarget.Global) + vscode.window.showInformationMessage(CertificateError.InsecureMessage) + } + + async showModal(title: string): Promise { + return this.showNotification(title, { + detail: this.x509Err || this.message, + modal: true, + useCustom: true, + }) + } + + async showNotification(title?: string, options: vscode.MessageOptions = {}): Promise { + const val = await vscode.window.showErrorMessage( + title || this.x509Err || this.message, + options, + // TODO: The insecure setting does not seem to work, even though it + // should, as proven by the tests. Even hardcoding rejectUnauthorized to + // false does not work; something seems to just be different when ran + // inside VS Code. Disabling the "Strict SSL" setting does not help + // either. For now avoid showing the button until this is sorted. + // CertificateError.ActionAllowInsecure, + CertificateError.ActionViewMoreDetails, ) - if (value === SelfSignedCertificateError.ActionViewMoreDetails) { - await this.viewMoreDetails() - return - } - if (value === SelfSignedCertificateError.ActionAllowInsecure) { - return this.allowInsecure(storage) + switch (val) { + case CertificateError.ActionViewMoreDetails: + await this.viewMoreDetails() + return + case CertificateError.ActionAllowInsecure: + await this.allowInsecure() + return } } } diff --git a/src/extension.ts b/src/extension.ts index b5aaf62c..720ab53e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,7 @@ import * as https from "https" import * as module from "module" import * as vscode from "vscode" import { Commands } from "./commands" -import { SelfSignedCertificateError } from "./error" +import { CertificateError } from "./error" import { Remote } from "./remote" import { Storage } from "./storage" import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" @@ -45,15 +45,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { axios.interceptors.response.use( (r) => r, - (err) => { - if (err) { - const msg = err.toString() as string - if (msg.indexOf("unable to verify the first certificate") !== -1) { - throw new SelfSignedCertificateError(msg) - } - } - - throw err + async (err) => { + throw await CertificateError.maybeWrap(err, err.config.baseURL, storage) }, ) @@ -144,26 +137,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { try { await remote.setup(vscodeProposed.env.remoteAuthority) } catch (ex) { - if (ex instanceof SelfSignedCertificateError) { - const prompt = await vscodeProposed.window.showErrorMessage( - "Failed to open workspace", - { - detail: SelfSignedCertificateError.Notification, - modal: true, - useCustom: true, - }, - SelfSignedCertificateError.ActionAllowInsecure, - SelfSignedCertificateError.ActionViewMoreDetails, - ) - if (prompt === SelfSignedCertificateError.ActionAllowInsecure) { - await ex.allowInsecure(storage) - await remote.reloadWindow() - return - } - if (prompt === SelfSignedCertificateError.ActionViewMoreDetails) { - await ex.viewMoreDetails() - return - } + if (ex instanceof CertificateError) { + return await ex.showModal("Failed to open workspace") } await vscodeProposed.window.showErrorMessage("Failed to open workspace", { detail: (ex as string).toString(), diff --git a/yarn.lock b/yarn.lock index d2a0e272..cf3c04d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -527,6 +527,13 @@ "@types/node" "*" "@types/through" "*" +"@types/node-forge@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.4.tgz#6447dc1901813f840dedb52324c229a14125aa7b" + integrity sha512-08scBQriFsBbm/CuBWOXRMD1RG7ydFW01EDR6vGX27nxcj6E/jGSCOLdICNd8ETwQlLFXVBVA854RX6Y7vPSrQ== + dependencies: + "@types/node" "*" + "@types/node@*": version "18.11.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" @@ -3433,6 +3440,11 @@ node-cleanup@^2.1.2: resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c" integrity sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw== +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-gyp-build@^4.3.0: version "4.6.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055"