From 6b3aaba14e92acfeb1afc2010f75f78bae127211 Mon Sep 17 00:00:00 2001 From: Kayra Date: Wed, 28 Aug 2024 00:00:48 +0300 Subject: [PATCH] feat!: accept bundles instead of just certificates (#60) --- .github/workflows/build-rock.yaml | 108 ++++-- internal/api/handlers_test.go | 272 ++++++++------ internal/certdb/certdb.go | 1 + internal/certdb/certdb_test.go | 41 +-- internal/certdb/validation.go | 75 +++- internal/certdb/validation_test.go | 335 +++++++++++++----- internal/metrics/metrics_test.go | 35 +- .../app/certificate_requests/components.tsx | 78 ++-- ui/src/app/certificate_requests/row.tsx | 17 +- ui/src/app/utils.ts | 23 +- 10 files changed, 663 insertions(+), 322 deletions(-) diff --git a/.github/workflows/build-rock.yaml b/.github/workflows/build-rock.yaml index 29dc529..12da33c 100644 --- a/.github/workflows/build-rock.yaml +++ b/.github/workflows/build-rock.yaml @@ -46,44 +46,80 @@ jobs: curl -XPOST -k -d '{"username":"admin", "password": "Admin1234"}' https://localhost:3000/api/v1/accounts export ADMIN_TOKEN=$(curl -XPOST -k -d '{"username":"admin", "password": "Admin1234"}' https://localhost:3000/login) curl -XPOST -k -d '-----BEGIN CERTIFICATE REQUEST----- - MIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk - MzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG - 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi - pUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI - 69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW - XcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO - yae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW - Kl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ - KoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs - dC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3 - DQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V - FGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa - uHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87 - cAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+ - RSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1 - H9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI + MIICsTCCAZkCAQAwbDELMAkGA1UEBhMCQ0ExFDASBgNVBAgMC05vdmEgU2NvdGlh + MRAwDgYDVQQHDAdIYWxpZmF4MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 + eSBMdGQxEjAQBgNVBAMMCWFwcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP + ADCCAQoCggEBAOhDSpNbeFiXMQzQcobExHqYMEGzqpX8N9+AR6/HPZWBybgx1hr3 + ejqsKornzpVph/dO9UC7O9aBlG071O9VQGHt3OU3rkZIk2009vYwLuSrAlJtnUne + p7KKn2lZGvh7jVyZE5RkS0X27vlT0soANsmcVq/82VneHrF/nbDcK6DOjQpS5o5l + EiNk2CIpYGUkw3WnQF4pBk8t4bNOl3nfpaAOfnmNuBX3mWyfPnaKMCENMpDqL9FR + V/O5bIPLmyH30OHUEJUkWOmFt9GFi+QfMoM0fR34KmRbDz79hZZb/yVPZZJl7l6i + FWXkNR3gxdEnwCZkTgWk5OqS9dCJOtsDE8ECAwEAAaAAMA0GCSqGSIb3DQEBCwUA + A4IBAQCqBX5WaNv/HjkzAyNXYuCToCb8GjmiMqL54t+1nEI1QTm6axQXivEbQT3x + GIh7uQYC06wHE23K6Znc1/G+o3y6lID07rvhBNal1qoXUiq6CsAqk+DXYdd8MEh5 + joerEedFqcW+WTUDcqddfIyDAGPqrM9j6/E+aFYyZjJ/xRuMf1zlWMljRiwj1NI9 + NxqjsYYQ3zxfUjv8gxXm0hN8Up1O9saoEF+zbuWNdiUWd6Ih3/3u5VBNSxgVOrDQ + CeXyyzkMx1pWTx0rWa7NSa+DMKVVzv46pck/9kLB4gPL8zqvIOMQsf74N0VcbVfd + 9jQR8mPXQYPUERl1ZhNrkzkyA0kd -----END CERTIFICATE REQUEST-----' -H "Authorization: Bearer $ADMIN_TOKEN" 'https://localhost:3000/api/v1/certificate_requests' curl -XPOST -k -d '-----BEGIN CERTIFICATE----- - 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 + ccma2Ab7ZPU= + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + 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 + btZkbfdL5A== -----END CERTIFICATE-----' -H "Authorization: Bearer $ADMIN_TOKEN" 'https://localhost:3000/api/v1/certificate_requests/1/certificate' docker exec gocert /usr/bin/pebble notices docker exec gocert /usr/bin/pebble notices | grep gocert\\.com/certificate/update diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go index 7635abe..d7556a2 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/handlers_test.go @@ -16,91 +16,128 @@ import ( ) const ( - validCSR1 = `-----BEGIN CERTIFICATE REQUEST----- -MIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn -oJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+ -L0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg -FzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR -+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC -+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq -hkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD -AQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL -BQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60 -cuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5 -zHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2 -Y/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID -uMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS -y+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI= + AppleCSR = `-----BEGIN CERTIFICATE REQUEST----- +MIICsTCCAZkCAQAwbDELMAkGA1UEBhMCQ0ExFDASBgNVBAgMC05vdmEgU2NvdGlh +MRAwDgYDVQQHDAdIYWxpZmF4MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEjAQBgNVBAMMCWFwcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOhDSpNbeFiXMQzQcobExHqYMEGzqpX8N9+AR6/HPZWBybgx1hr3 +ejqsKornzpVph/dO9UC7O9aBlG071O9VQGHt3OU3rkZIk2009vYwLuSrAlJtnUne +p7KKn2lZGvh7jVyZE5RkS0X27vlT0soANsmcVq/82VneHrF/nbDcK6DOjQpS5o5l +EiNk2CIpYGUkw3WnQF4pBk8t4bNOl3nfpaAOfnmNuBX3mWyfPnaKMCENMpDqL9FR +V/O5bIPLmyH30OHUEJUkWOmFt9GFi+QfMoM0fR34KmRbDz79hZZb/yVPZZJl7l6i +FWXkNR3gxdEnwCZkTgWk5OqS9dCJOtsDE8ECAwEAAaAAMA0GCSqGSIb3DQEBCwUA +A4IBAQCqBX5WaNv/HjkzAyNXYuCToCb8GjmiMqL54t+1nEI1QTm6axQXivEbQT3x +GIh7uQYC06wHE23K6Znc1/G+o3y6lID07rvhBNal1qoXUiq6CsAqk+DXYdd8MEh5 +joerEedFqcW+WTUDcqddfIyDAGPqrM9j6/E+aFYyZjJ/xRuMf1zlWMljRiwj1NI9 +NxqjsYYQ3zxfUjv8gxXm0hN8Up1O9saoEF+zbuWNdiUWd6Ih3/3u5VBNSxgVOrDQ +CeXyyzkMx1pWTx0rWa7NSa+DMKVVzv46pck/9kLB4gPL8zqvIOMQsf74N0VcbVfd +9jQR8mPXQYPUERl1ZhNrkzkyA0kd -----END CERTIFICATE REQUEST-----` - validCSR2 = `-----BEGIN CERTIFICATE REQUEST----- -MIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk -MzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi -pUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI -69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW -XcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO -yae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW -Kl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ -KoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs -dC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3 -DQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V -FGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa -uHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87 -cAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+ -RSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1 -H9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI + BananaCSR = `-----BEGIN CERTIFICATE REQUEST----- +MIICrjCCAZYCAQAwaTELMAkGA1UEBhMCVFIxDjAMBgNVBAgMBUl6bWlyMRIwEAYD +VQQHDAlOYXJsaWRlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDETMBEGA1UEAwwKYmFuYW5hLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAK+vJMxO1GTty09/E4M/RbTCPABleCuYc/uzj72KWaIvoDaanuJ4NBWM +2aUiepxWdMNTR6oe31gLq4agLYT309tXwCeBLQnOxvBFWONmBG1qo0fQkvT5kSoq +AO29D7hkQ0gVwg7EF3qOd0JgbDm/yvexKpYLVvWMQAngHwZRnd5vHGk6M3P7G4oG +mIj/CL2bF6va7GWODYHb+a7jI1nkcsrk+vapc+doVszcoJ+2ryoK6JndOSGjt9SD +uxulWZHQO32XC0btyub63pom4QxRtRXmb1mjM37XEwXJSsQO1HOnmc6ycqUK53p0 +jF8Qbs0m8y/p2NHFGTUfiyNYA3EdkjUCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IB +AQA+hq8kS2Y1Y6D8qH97Mnnc6Ojm61Q5YJ4MghaTD+XXbueTCx4DfK7ujYzK3IEF +pH1AnSeJCsQeBdjT7p6nv5GcwqWXWztNKn9zibXiASK/yYKwqvQpjSjSeqGEh+Sa +9C9SHeaPhZrJRj0i3NkqmN8moWasF9onW6MNKBX0B+pvBB+igGPcjCIFIFGUUaky +upMXY9IG3LlWvlt+HTfuMZV+zSOZgD9oyqkh5K9XRKNq/mnNz/1llUCBZRmfeRBY ++sJ4M6MJRztiyX4/Fjb8UHQviH931rkiEGtG826IvWIyiRSnAeE8B/VzL0GlT9Zq +ge6lFRxB1FlDuU4Blef8FnOI -----END CERTIFICATE REQUEST-----` - validCSR3 = `-----BEGIN CERTIFICATE REQUEST----- -MIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDN7tHggWTtxiT5Sh5Npoif8J2BdpJjtMdpZ7Vu -NVzMxW/eojSRlq0p3nafmpjnSdSH1k/XMmPsgmv9txxEHMw1LIUJUef2QVrQTI6J -4ueu9NvexZWXZ+UxFip63PKyn/CkZRFiHCRIGzDDPxM2aApjghXy9ISMtGqDVSnr -5hQDu2U1CEiUWKMoTpyk/KlBZliDDOzaGm3cQuzKWs6Stjzpq+uX4ecJAXZg5Cj+ -+JUETH93A/VOfsiiHXoKeTnFMCsmJgEHz2DZixw8EN8XgpOp5BA2n8Y/xS+Ren5R -ZH7uNJI/SmQ0yrR+2bYR6hm+4bCzspyCfzbiuI5IS9+2eXA/AgMBAAGgWDBWBgkq -hkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD -AQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL -BQADggEBAB/aPfYLbnCubYyKnxLRipoLr3TBSYFnRfcxiZR1o+L3/tuv2NlrXJjY -K13xzzPhwuZwd6iKfX3xC33sKgnUNFawyE8IuAmyhJ2cl97iA2lwoYcyuWP9TOEx -LT60zxp7PHsKo53gqaqRJ5B9RZtiv1jYdUZvynHP4J5JG7Zwaa0VNi/Cx5cwGW8K -rfvNABPUAU6xIqqYgd2heDPF6kjvpoNiOl056qIAbk0dbmpqOJf/lxKBRfqlHhSC -0qRScGu70l2Oxl89YSsfGtUyQuzTkLshI2VkEUM+W/ZauXbxLd8SyWveH3/7mDC+ -Sgi7T+lz+c1Tw+XFgkqryUwMeG2wxt8= + StrawberryCSR = `-----BEGIN CERTIFICATE REQUEST----- +MIICrzCCAZcCAQAwajELMAkGA1UEBhMCSVQxDzANBgNVBAgMBlBhZG92YTEOMAwG +A1UEBwwFUGFkdWExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEX +MBUGA1UEAwwOc3RyYXdiZXJyeS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDXXHpy+3LLRCImyEQitM9eUdgYkexLz2PcAf89tTpkpt3L1woJw0bv ++YR80UcR2Pg+7uUVm4XSKFvcdyWg8yADHIDDZkEmKFEbrOLUsWWTQEsCpFt5MU4u +6YnYXV0YflPXmRsJRd90NOen+wlM2ajK1gGTtLPdJ6axz15LdcT2uXXIvWhncjgL +CvVpd/x44AMxD/BPf/d27VO5hEjxR//DtcOmS/jA+Zf1+dyIAWs2LH+ctsaPLOcg +1rBiRrHtGL8wmPwgwK9b+QLiq9Ik+dx1Jl6BvC36LRk2CxTxfZ6e4UdYVhtnjMW2 +VEUAVg9LtowvXTexESUv6Mh4uQF6pW5ZAgMBAAGgADANBgkqhkiG9w0BAQsFAAOC +AQEAW40HaxjVSDNKeWJ8StWGfstdvk3dwqjsfLgmnBBZSLcGppYEnnRlJxhMJ9Ks +x2IYw7wJ55kOJ7V+SunKPPoY+7PwNDV9Llxp58vvE8CFnOc3WcL9pA2V5LbTXwtT +R7jID5GZjOv0bn3x1WXuKVW5tkYdT6sW14rfGut1T+r1kYls+JQ5ap+BzfMtThZz +38PCnEMmSo0/KmgUu5/LakPoy3JPaFB0bCgViZSWlxiSR44YZPsVaRL8E7Zt/qjJ +glRL/48q/tORtxv18/Girl6oiQholkADaH3j2gB3t/fCLp8guAVLWB9DzhwrqWwP +GFl9zB5HDoij2l0kHrb44TuonQ== -----END CERTIFICATE REQUEST-----` - validCert2 = `-----BEGIN CERTIFICATE----- -MIIDrDCCApSgAwIBAgIURKr+jf7hj60SyAryIeN++9wDdtkwDQYJKoZIhvcNAQEL -BQAwOTELMAkGA1UEBhMCVVMxKjAoBgNVBAMMIXNlbGYtc2lnbmVkLWNlcnRpZmlj -YXRlcy1vcGVyYXRvcjAeFw0yNDAzMjcxMjQ4MDRaFw0yNTAzMjcxMjQ4MDRaMEcx -FjAUBgNVBAMMDTEwLjE1Mi4xODMuNTMxLTArBgNVBC0MJDM5YWNlMTk1LWRjNWEt -NDMyYi04MDkwLWFmZTZhYjRiNDljZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAIzOVs/h0bQ73kc3g5BAzOKK5yGnn3vHfJ5hYqVGrfaglN1Pe/fBUeOt -Ax4wixsRccZqEYrcx7WkeHsOR9N+h5RgdtMmrPwjA4ElCOvQ8gLMj8BCajlgRjtS -y+dKl3Zef4zpDtr6APY+VLA9QrVkpJcoWzYVjKWKo9jm1l3GmI/Rsf08GIcPMbCf -0EVsgq7441cx/PuH63w4QVTsssDe7KviN1sAm8gQXDfLjsmntG/6uflCVIqoKXjg -If+GO6iN2GqzI+HJu3cJoglY3nVMIeuc1ch7UFHy3fJxVipfe++ZjQXNconLyg1N -2QeYWdTGbaWzAcGgNcjEhK7P34eSml8CAwEAAaOBnTCBmjAhBgNVHSMEGjAYgBYE -FN/vgl9cAapV7hH9lEyM7qYS958aMB0GA1UdDgQWBBRJJDZkHr64VqTC24DPQVld -Ba3iPDAMBgNVHRMBAf8EAjAAMEgGA1UdEQRBMD+CN3ZhdWx0LWs4cy0wLnZhdWx0 -LWs4cy1lbmRwb2ludHMudmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWyHBAqYtzUwDQYJ -KoZIhvcNAQELBQADggEBAEH9NTwDiSsoQt/QXkWPMBrB830K0dlwKl5WBNgVxFP+ -hSfQ86xN77jNSp2VxOksgzF9J9u/ubAXvSFsou4xdP8MevBXoFJXeqMERq5RW3gc -WyhXkzguv3dwH+n43GJFP6MQ+n9W/nPZCUQ0Iy7ueAvj0HFhGyZzAE2wxNFZdvCs -gCX3nqYpp70oZIFDrhmYwE5ij5KXlHD4/1IOfNUKCDmQDgGPLI1tVtwQLjeRq7Hg -XVelpl/LXTQawmJyvDaVT/Q9P+WqoDiMjrqF6Sy7DzNeeccWVqvqX5TVS6Ky56iS -Mvo/+PAJHkBciR5Xn+Wg2a+7vrZvT6CBoRSOTozlLSM= + BananaCert = `-----BEGIN CERTIFICATE----- +MIIEUTCCAjkCFE8lmuBE85/RPw2M17Kzl93O+9IIMA0GCSqGSIb3DQEBCwUAMGEx +CzAJBgNVBAYTAlRSMQ4wDAYDVQQIDAVJem1pcjESMBAGA1UEBwwJTmFybGlkZXJl +MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAMMAm1l +MB4XDTI0MDYyODA4NDIyMFoXDTI1MDYyODA4NDIyMFowaTELMAkGA1UEBhMCVFIx +DjAMBgNVBAgMBUl6bWlyMRIwEAYDVQQHDAlOYXJsaWRlcmUxITAfBgNVBAoMGElu +dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKYmFuYW5hLmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+vJMxO1GTty09/E4M/RbTCPABl +eCuYc/uzj72KWaIvoDaanuJ4NBWM2aUiepxWdMNTR6oe31gLq4agLYT309tXwCeB +LQnOxvBFWONmBG1qo0fQkvT5kSoqAO29D7hkQ0gVwg7EF3qOd0JgbDm/yvexKpYL +VvWMQAngHwZRnd5vHGk6M3P7G4oGmIj/CL2bF6va7GWODYHb+a7jI1nkcsrk+vap +c+doVszcoJ+2ryoK6JndOSGjt9SDuxulWZHQO32XC0btyub63pom4QxRtRXmb1mj +M37XEwXJSsQO1HOnmc6ycqUK53p0jF8Qbs0m8y/p2NHFGTUfiyNYA3EdkjUCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAgEAVZJZD0/ojZSOVIesZvrjLG0agSp0tsXY+hEt +I/knpYLvRcAd8b3Jx9gk+ug+FwDQ4IBIkTX18qhK2fgVUuMR/ubfpQeCMbp64N3Q +kmN/E1eu0bl6hhHAL7jEbi0DE3vAN9huQxAIu5pCyLvZIrPJtvuyj2jOpJBZwGoP +539lfEM++XALzI4qKQ6Z0a0rJZ4HoruKiYwEFZ7VkmRLD0uef6NMZRqa/Vx+o0uT +1TjH4AeDDmJmP/aHlHbpXkHQ9h9rfTa6Qbypo+T9pGDhd02O1tEqrHfiQyNWJxb0 +rbR+owT32iCfayzKKqhmAYSF2d9XKWEhulgxWDaXgvUbq4Y+fgfU2qMVz5uusTDh +a9Mp9dsYWySWEUcEa4v2w6FfaaVXE1S9ubm+HoIVtotuutL5fn86q19pAAePYjLQ +ybiETp5LU3chuYmMlCiDRNGHYhN5nvGcttqRdWIBe454RRPNo4iGVl13l6aG8rmI +xDfk5lIwObalbELv+mEIGI1j/j4//nJFXByxlLHm5/BF8rmvHDj1aPtPRw9DLgSX +ejhjjec1xnkBR+JF0g474hLdPjCnA0aqLQInZbjJJm5iXzyXBg1cy7KvIBy3ZkrR +Pp7ObjaWxjCT3O6nEH3w6Ozsyg2cHXQIdVXLvNnV1bxUbPnfhQosKGKgU6s+lcLM +SRhHB2k= -----END CERTIFICATE-----` + IssuerCert = `-----BEGIN CERTIFICATE----- +MIIFozCCA4ugAwIBAgIUDjtO3bEluUX3tzvrckATlycRVfwwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVFIxDjAMBgNVBAgMBUl6bWlyMRIwEAYDVQQHDAlOYXJs +aWRlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDELMAkGA1UE +AwwCbWUwHhcNMjQwNjI4MDYwNTQ5WhcNMzQwNjI2MDYwNTQ5WjBhMQswCQYDVQQG +EwJUUjEOMAwGA1UECAwFSXptaXIxEjAQBgNVBAcMCU5hcmxpZGVyZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQswCQYDVQQDDAJtZTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJU+5YaFlpn+bWvVri5L6EkmbAPuavsI +/KXY7ufRmc5qb08o1na9lLJ/7TuMD4K36Idnq20n1JohSlrdymBpNZ8O3m5fYYtk +hx5WADlBZsKnC5aZJIChEb4bYcOFLP+d3PooVsAKBxW0Q6TECviQcK7GxaxEZw0L +7FRhX2c9+CxbvRGP6OGVggXZxwkZik/JJ9aym+fltt9QvlxQVBq/GlFYZYC+H8jV +Z6RnUjugnWcTm9PAsQ6+EHEevAW+dWaDP+gr9AgKKz1EXbc1mVKAVOLHjb+Ue7RC +vFoar/YxYIszD58dOSB/GuAxn+JAjWbnOu7jeX3XeWlKOagUJF9L9TgMIUWdiuJG +8Uu/kK2MjyRFdT8opnPFAXrK7vSuMBzhRtswAlWc8xoZWeSQF+NpjU+swbg8ySYT +LfZxVB+s/ftxnGU3RM/RWdbZhb0DAuIBsFAGCbnj+Q61/cK4i58JVjUqzLk+XOwR +55LAyS0Y5pj9jDc5mqvS0z7ot7s2OBM1+o8e3KJgdMSXorYkv3toHMGEIUmPQZCX +JtRCjFNgnoWeLDc+oLiN6BlPx7bS4MDN9tMPCJwF6vnxFzLAzdRqY3D7uRS3chsx +7ClMR9MDsSxplC7tptXgv8UTzh1XZjWGCeZq0Gbe927Hmwy2q8k/BFwnR4PIVSiE +7YAZPb0CPmrfAgMBAAGjUzBRMB0GA1UdDgQWBBRgLXukRHTovOG6g9Z5eCaeh6Sx +aTAfBgNVHSMEGDAWgBRgLXukRHTovOG6g9Z5eCaeh6SxaTAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQA9TpgTrGmnyxKB2ne76LNQadiijVPpS6/U +OPFAX4EPJ0V5DhDreJjsZJC6Is2Q9+qsPpn/nlW7bvZUVHGodUKcE+TQWFiMtLvu +8ifzk8x1R46aqhTyxb7WBBFfvbvdmlEENKTmTS6A/C3nYgmkfk5N7x84iTowmsVl +Yzz9iRzxkqQ+mU3L2/Sp5nXPYWfzV9WXIJdxWcot7f4CJ79eVFu4D9hYfzcPQ9P9 +0qCBRbH/01D2E/3uTHhZPPmK2Tp1ao5SuGLppjMPX8VWVL5CMTXOj+1LF0nJJc/J +9MrqXwtlLyKGP6HX8qALbaXwcv7db6bF+aEsgWmIEB+0ecGk9IR3XQn7I379CO3v +J2oUCZ++lV9e2tcRehUprE1v8i+DFhPtS1iNjrO7KnDYkXimR5zI+3sGFI9/9wY0 +4PAV/roZFiEJHe5kA49vwIihJaDgy/SPIYgG/vhdj+WeIbi1ilEi12ou7VF0tyiE +j3eXaMAL8EAKxCUZbXcuwmK9qistAYXBFFEK9M08FwLH8HM4LoPjshMg3II9Ncs8 +p3to8U99/ZeFbJRzEUF9poZ7VwxBEcgfWD1RV0+gNLC3Au2yuc4C3anknOv7Db/r +jdzVA8yTI8cZ/RtRohp5H/s+j2tcdfB3Zt+wfS4nLxqN/kf7qv2VSdPbXyTyz/ft +btZkbfdL5A== +-----END CERTIFICATE----- +` ) -const ( - expectedGetAllCertsResponseBody1 = "[{\"id\":1,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn\\noJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+\\nL0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg\\nFzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR\\n+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC\\n+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60\\ncuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5\\nzHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2\\nY/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID\\nuMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS\\ny+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"}]" - expectedGetAllCertsResponseBody2 = "[{\"id\":1,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn\\noJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+\\nL0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg\\nFzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR\\n+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC\\n+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60\\ncuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5\\nzHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2\\nY/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID\\nuMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS\\ny+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"},{\"id\":2,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk\\nMzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG\\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi\\npUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI\\n69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW\\nXcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO\\nyae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW\\nKl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ\\nKoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs\\ndC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3\\nDQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V\\nFGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa\\nuHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87\\ncAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+\\nRSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1\\nH9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"}]" - expectedGetAllCertsResponseBody3 = "[{\"id\":2,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk\\nMzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG\\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi\\npUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI\\n69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW\\nXcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO\\nyae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW\\nKl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ\\nKoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs\\ndC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3\\nDQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V\\nFGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa\\nuHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87\\ncAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+\\nRSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1\\nH9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"-----BEGIN CERTIFICATE-----\\nMIIDrDCCApSgAwIBAgIURKr+jf7hj60SyAryIeN++9wDdtkwDQYJKoZIhvcNAQEL\\nBQAwOTELMAkGA1UEBhMCVVMxKjAoBgNVBAMMIXNlbGYtc2lnbmVkLWNlcnRpZmlj\\nYXRlcy1vcGVyYXRvcjAeFw0yNDAzMjcxMjQ4MDRaFw0yNTAzMjcxMjQ4MDRaMEcx\\nFjAUBgNVBAMMDTEwLjE1Mi4xODMuNTMxLTArBgNVBC0MJDM5YWNlMTk1LWRjNWEt\\nNDMyYi04MDkwLWFmZTZhYjRiNDljZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAIzOVs/h0bQ73kc3g5BAzOKK5yGnn3vHfJ5hYqVGrfaglN1Pe/fBUeOt\\nAx4wixsRccZqEYrcx7WkeHsOR9N+h5RgdtMmrPwjA4ElCOvQ8gLMj8BCajlgRjtS\\ny+dKl3Zef4zpDtr6APY+VLA9QrVkpJcoWzYVjKWKo9jm1l3GmI/Rsf08GIcPMbCf\\n0EVsgq7441cx/PuH63w4QVTsssDe7KviN1sAm8gQXDfLjsmntG/6uflCVIqoKXjg\\nIf+GO6iN2GqzI+HJu3cJoglY3nVMIeuc1ch7UFHy3fJxVipfe++ZjQXNconLyg1N\\n2QeYWdTGbaWzAcGgNcjEhK7P34eSml8CAwEAAaOBnTCBmjAhBgNVHSMEGjAYgBYE\\nFN/vgl9cAapV7hH9lEyM7qYS958aMB0GA1UdDgQWBBRJJDZkHr64VqTC24DPQVld\\nBa3iPDAMBgNVHRMBAf8EAjAAMEgGA1UdEQRBMD+CN3ZhdWx0LWs4cy0wLnZhdWx0\\nLWs4cy1lbmRwb2ludHMudmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWyHBAqYtzUwDQYJ\\nKoZIhvcNAQELBQADggEBAEH9NTwDiSsoQt/QXkWPMBrB830K0dlwKl5WBNgVxFP+\\nhSfQ86xN77jNSp2VxOksgzF9J9u/ubAXvSFsou4xdP8MevBXoFJXeqMERq5RW3gc\\nWyhXkzguv3dwH+n43GJFP6MQ+n9W/nPZCUQ0Iy7ueAvj0HFhGyZzAE2wxNFZdvCs\\ngCX3nqYpp70oZIFDrhmYwE5ij5KXlHD4/1IOfNUKCDmQDgGPLI1tVtwQLjeRq7Hg\\nXVelpl/LXTQawmJyvDaVT/Q9P+WqoDiMjrqF6Sy7DzNeeccWVqvqX5TVS6Ky56iS\\nMvo/+PAJHkBciR5Xn+Wg2a+7vrZvT6CBoRSOTozlLSM=\\n-----END CERTIFICATE-----\"},{\"id\":3,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDN7tHggWTtxiT5Sh5Npoif8J2BdpJjtMdpZ7Vu\\nNVzMxW/eojSRlq0p3nafmpjnSdSH1k/XMmPsgmv9txxEHMw1LIUJUef2QVrQTI6J\\n4ueu9NvexZWXZ+UxFip63PKyn/CkZRFiHCRIGzDDPxM2aApjghXy9ISMtGqDVSnr\\n5hQDu2U1CEiUWKMoTpyk/KlBZliDDOzaGm3cQuzKWs6Stjzpq+uX4ecJAXZg5Cj+\\n+JUETH93A/VOfsiiHXoKeTnFMCsmJgEHz2DZixw8EN8XgpOp5BA2n8Y/xS+Ren5R\\nZH7uNJI/SmQ0yrR+2bYR6hm+4bCzspyCfzbiuI5IS9+2eXA/AgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBAB/aPfYLbnCubYyKnxLRipoLr3TBSYFnRfcxiZR1o+L3/tuv2NlrXJjY\\nK13xzzPhwuZwd6iKfX3xC33sKgnUNFawyE8IuAmyhJ2cl97iA2lwoYcyuWP9TOEx\\nLT60zxp7PHsKo53gqaqRJ5B9RZtiv1jYdUZvynHP4J5JG7Zwaa0VNi/Cx5cwGW8K\\nrfvNABPUAU6xIqqYgd2heDPF6kjvpoNiOl056qIAbk0dbmpqOJf/lxKBRfqlHhSC\\n0qRScGu70l2Oxl89YSsfGtUyQuzTkLshI2VkEUM+W/ZauXbxLd8SyWveH3/7mDC+\\nSgi7T+lz+c1Tw+XFgkqryUwMeG2wxt8=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"},{\"id\":4,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn\\noJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+\\nL0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg\\nFzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR\\n+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC\\n+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60\\ncuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5\\nzHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2\\nY/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID\\nuMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS\\ny+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"rejected\"}]" - expectedGetAllCertsResponseBody4 = "[{\"id\":2,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk\\nMzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG\\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi\\npUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI\\n69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW\\nXcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO\\nyae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW\\nKl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ\\nKoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs\\ndC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3\\nDQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V\\nFGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa\\nuHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87\\ncAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+\\nRSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1\\nH9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"},{\"id\":3,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDN7tHggWTtxiT5Sh5Npoif8J2BdpJjtMdpZ7Vu\\nNVzMxW/eojSRlq0p3nafmpjnSdSH1k/XMmPsgmv9txxEHMw1LIUJUef2QVrQTI6J\\n4ueu9NvexZWXZ+UxFip63PKyn/CkZRFiHCRIGzDDPxM2aApjghXy9ISMtGqDVSnr\\n5hQDu2U1CEiUWKMoTpyk/KlBZliDDOzaGm3cQuzKWs6Stjzpq+uX4ecJAXZg5Cj+\\n+JUETH93A/VOfsiiHXoKeTnFMCsmJgEHz2DZixw8EN8XgpOp5BA2n8Y/xS+Ren5R\\nZH7uNJI/SmQ0yrR+2bYR6hm+4bCzspyCfzbiuI5IS9+2eXA/AgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBAB/aPfYLbnCubYyKnxLRipoLr3TBSYFnRfcxiZR1o+L3/tuv2NlrXJjY\\nK13xzzPhwuZwd6iKfX3xC33sKgnUNFawyE8IuAmyhJ2cl97iA2lwoYcyuWP9TOEx\\nLT60zxp7PHsKo53gqaqRJ5B9RZtiv1jYdUZvynHP4J5JG7Zwaa0VNi/Cx5cwGW8K\\nrfvNABPUAU6xIqqYgd2heDPF6kjvpoNiOl056qIAbk0dbmpqOJf/lxKBRfqlHhSC\\n0qRScGu70l2Oxl89YSsfGtUyQuzTkLshI2VkEUM+W/ZauXbxLd8SyWveH3/7mDC+\\nSgi7T+lz+c1Tw+XFgkqryUwMeG2wxt8=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"},{\"id\":4,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn\\noJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+\\nL0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg\\nFzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR\\n+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC\\n+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60\\ncuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5\\nzHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2\\nY/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID\\nuMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS\\ny+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"rejected\"}]" - expectedGetCertReqResponseBody1 = "{\"id\":2,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk\\nMzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG\\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi\\npUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI\\n69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW\\nXcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO\\nyae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW\\nKl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ\\nKoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs\\ndC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3\\nDQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V\\nFGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa\\nuHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87\\ncAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+\\nRSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1\\nH9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"}" - expectedGetCertReqResponseBody2 = "{\"id\":4,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3\\nDQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn\\noJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+\\nL0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg\\nFzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR\\n+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC\\n+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq\\nhkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD\\nAQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL\\nBQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60\\ncuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5\\nzHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2\\nY/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID\\nuMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS\\ny+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI=\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"}" - expectedGetCertReqResponseBody3 = "{\"id\":2,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk\\nMzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG\\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi\\npUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI\\n69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW\\nXcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO\\nyae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW\\nKl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ\\nKoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs\\ndC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3\\nDQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V\\nFGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa\\nuHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87\\ncAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+\\nRSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1\\nH9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"-----BEGIN CERTIFICATE-----\\nMIIDrDCCApSgAwIBAgIURKr+jf7hj60SyAryIeN++9wDdtkwDQYJKoZIhvcNAQEL\\nBQAwOTELMAkGA1UEBhMCVVMxKjAoBgNVBAMMIXNlbGYtc2lnbmVkLWNlcnRpZmlj\\nYXRlcy1vcGVyYXRvcjAeFw0yNDAzMjcxMjQ4MDRaFw0yNTAzMjcxMjQ4MDRaMEcx\\nFjAUBgNVBAMMDTEwLjE1Mi4xODMuNTMxLTArBgNVBC0MJDM5YWNlMTk1LWRjNWEt\\nNDMyYi04MDkwLWFmZTZhYjRiNDljZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\\nAQoCggEBAIzOVs/h0bQ73kc3g5BAzOKK5yGnn3vHfJ5hYqVGrfaglN1Pe/fBUeOt\\nAx4wixsRccZqEYrcx7WkeHsOR9N+h5RgdtMmrPwjA4ElCOvQ8gLMj8BCajlgRjtS\\ny+dKl3Zef4zpDtr6APY+VLA9QrVkpJcoWzYVjKWKo9jm1l3GmI/Rsf08GIcPMbCf\\n0EVsgq7441cx/PuH63w4QVTsssDe7KviN1sAm8gQXDfLjsmntG/6uflCVIqoKXjg\\nIf+GO6iN2GqzI+HJu3cJoglY3nVMIeuc1ch7UFHy3fJxVipfe++ZjQXNconLyg1N\\n2QeYWdTGbaWzAcGgNcjEhK7P34eSml8CAwEAAaOBnTCBmjAhBgNVHSMEGjAYgBYE\\nFN/vgl9cAapV7hH9lEyM7qYS958aMB0GA1UdDgQWBBRJJDZkHr64VqTC24DPQVld\\nBa3iPDAMBgNVHRMBAf8EAjAAMEgGA1UdEQRBMD+CN3ZhdWx0LWs4cy0wLnZhdWx0\\nLWs4cy1lbmRwb2ludHMudmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWyHBAqYtzUwDQYJ\\nKoZIhvcNAQELBQADggEBAEH9NTwDiSsoQt/QXkWPMBrB830K0dlwKl5WBNgVxFP+\\nhSfQ86xN77jNSp2VxOksgzF9J9u/ubAXvSFsou4xdP8MevBXoFJXeqMERq5RW3gc\\nWyhXkzguv3dwH+n43GJFP6MQ+n9W/nPZCUQ0Iy7ueAvj0HFhGyZzAE2wxNFZdvCs\\ngCX3nqYpp70oZIFDrhmYwE5ij5KXlHD4/1IOfNUKCDmQDgGPLI1tVtwQLjeRq7Hg\\nXVelpl/LXTQawmJyvDaVT/Q9P+WqoDiMjrqF6Sy7DzNeeccWVqvqX5TVS6Ky56iS\\nMvo/+PAJHkBciR5Xn+Wg2a+7vrZvT6CBoRSOTozlLSM=\\n-----END CERTIFICATE-----\"}" - expectedGetCertReqResponseBody4 = "{\"id\":2,\"csr\":\"-----BEGIN CERTIFICATE REQUEST-----\\nMIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk\\nMzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG\\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi\\npUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI\\n69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW\\nXcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO\\nyae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW\\nKl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ\\nKoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs\\ndC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3\\nDQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V\\nFGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa\\nuHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87\\ncAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+\\nRSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1\\nH9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI\\n-----END CERTIFICATE REQUEST-----\",\"certificate\":\"\"}" +var ( + expectedGetAllCertsResponseBody1 = fmt.Sprintf("[{\"id\":1,\"csr\":\"%s\",\"certificate\":\"\"}]", trimmed(AppleCSR)) + expectedGetAllCertsResponseBody2 = fmt.Sprintf("[{\"id\":1,\"csr\":\"%s\",\"certificate\":\"\"},{\"id\":2,\"csr\":\"%s\",\"certificate\":\"\"}]", trimmed(AppleCSR), trimmed(BananaCSR)) + expectedGetAllCertsResponseBody3 = fmt.Sprintf("[{\"id\":2,\"csr\":\"%s\",\"certificate\":\"%s\\n%s\"},{\"id\":3,\"csr\":\"%s\",\"certificate\":\"\"},{\"id\":4,\"csr\":\"%s\",\"certificate\":\"rejected\"}]", trimmed(BananaCSR), trimmed(BananaCert), trimmed(IssuerCert), trimmed(StrawberryCSR), trimmed(AppleCSR)) + expectedGetAllCertsResponseBody4 = fmt.Sprintf("[{\"id\":2,\"csr\":\"%s\",\"certificate\":\"\"},{\"id\":3,\"csr\":\"%s\",\"certificate\":\"\"},{\"id\":4,\"csr\":\"%s\",\"certificate\":\"rejected\"}]", trimmed(BananaCSR), trimmed(StrawberryCSR), trimmed(AppleCSR)) + expectedGetCertReqResponseBody1 = fmt.Sprintf("{\"id\":2,\"csr\":\"%s\",\"certificate\":\"\"}", trimmed(BananaCSR)) + expectedGetCertReqResponseBody2 = fmt.Sprintf("{\"id\":4,\"csr\":\"%s\",\"certificate\":\"\"}", trimmed(AppleCSR)) + expectedGetCertReqResponseBody3 = fmt.Sprintf("{\"id\":2,\"csr\":\"%s\",\"certificate\":\"%s\\n%s\"}", trimmed(BananaCSR), trimmed(BananaCert), trimmed(IssuerCert)) + expectedGetCertReqResponseBody4 = fmt.Sprintf("{\"id\":2,\"csr\":\"%s\",\"certificate\":\"\"}", trimmed(BananaCSR)) ) const ( @@ -140,7 +177,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status int }{ { - desc: "healthcheck success", + desc: "1: healthcheck success", method: "GET", path: "/status", data: "", @@ -148,7 +185,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "empty get csrs success", + desc: "2: empty get csrs success", method: "GET", path: "/api/v1/certificate_requests", data: "", @@ -156,7 +193,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "post csr1 fail", + desc: "3: post csr1 fail", method: "POST", path: "/api/v1/certificate_requests", data: "this is very clearly not a csr", @@ -164,15 +201,15 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusBadRequest, }, { - desc: "post csr1 success", + desc: "4: post csr1 success", method: "POST", path: "/api/v1/certificate_requests", - data: validCSR1, + data: AppleCSR, response: "1", status: http.StatusCreated, }, { - desc: "get csrs 1 success", + desc: "5: get csrs 1 success", method: "GET", path: "/api/v1/certificate_requests", data: "", @@ -180,15 +217,15 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "post csr2 success", + desc: "6: post csr2 success", method: "POST", path: "/api/v1/certificate_requests", - data: validCSR2, + data: BananaCSR, response: "2", status: http.StatusCreated, }, { - desc: "get csrs 2 success", + desc: "7: get csrs 2 success", method: "GET", path: "/api/v1/certificate_requests", data: "", @@ -196,23 +233,23 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "post csr2 fail", + desc: "8: post csr2 fail", method: "POST", path: "/api/v1/certificate_requests", - data: validCSR2, + data: BananaCSR, response: "error: given csr already recorded", status: http.StatusBadRequest, }, { - desc: "post csr3 success", + desc: "9: post csr3 success", method: "POST", path: "/api/v1/certificate_requests", - data: validCSR3, + data: StrawberryCSR, response: "3", status: http.StatusCreated, }, { - desc: "delete csr1 success", + desc: "10: delete csr1 success", method: "DELETE", path: "/api/v1/certificate_requests/1", data: "", @@ -220,7 +257,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusAccepted, }, { - desc: "delete csr5 fail", + desc: "11: delete csr5 fail", method: "DELETE", path: "/api/v1/certificate_requests/5", data: "", @@ -228,7 +265,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusNotFound, }, { - desc: "get csr1 fail", + desc: "12: get csr1 fail", method: "GET", path: "/api/v1/certificate_requests/1", data: "", @@ -236,7 +273,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusNotFound, }, { - desc: "get csr2 success", + desc: "13: get csr2 success", method: "GET", path: "/api/v1/certificate_requests/2", data: "", @@ -244,15 +281,15 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "post csr4 success", + desc: "14: post csr4 success", method: "POST", path: "/api/v1/certificate_requests", - data: validCSR1, + data: AppleCSR, response: "4", status: http.StatusCreated, }, { - desc: "get csr4 success", + desc: "15: get csr4 success", method: "GET", path: "/api/v1/certificate_requests/4", data: "", @@ -260,31 +297,31 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "post cert2 fail 1", + desc: "16: post cert2 fail 1", method: "POST", path: "/api/v1/certificate_requests/4/certificate", - data: validCert2, - response: "error: cert validation failed: certificate does not match CSR", + data: BananaCert, + response: "error: cert validation failed: less than 2 certificate PEM strings were found", status: http.StatusBadRequest, }, { - desc: "post cert2 fail 2", + desc: "17: post cert2 fail 2", method: "POST", path: "/api/v1/certificate_requests/4/certificate", data: "some random data that's clearly not a cert", - response: "error: cert validation failed: PEM Certificate string not found or malformed", + response: "error: cert validation failed: less than 2 certificate PEM strings were found", status: http.StatusBadRequest, }, { - desc: "post cert2 success", + desc: "18: post cert2 success", method: "POST", path: "/api/v1/certificate_requests/2/certificate", - data: validCert2, + data: fmt.Sprintf("%s\n%s", BananaCert, IssuerCert), response: "1", status: http.StatusCreated, }, { - desc: "get csr2 success", + desc: "19: get csr2 success", method: "GET", path: "/api/v1/certificate_requests/2", data: "", @@ -292,7 +329,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "reject csr4 success", + desc: "20: reject csr4 success", method: "POST", path: "/api/v1/certificate_requests/4/certificate/reject", data: "", @@ -300,7 +337,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusAccepted, }, { - desc: "get all csrs success", + desc: "21: get all csrs success", method: "GET", path: "/api/v1/certificate_requests", data: "", @@ -308,7 +345,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "delete csr2 cert success", + desc: "22: delete csr2 cert success", method: "DELETE", path: "/api/v1/certificate_requests/2/certificate", data: "", @@ -316,7 +353,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusAccepted, }, { - desc: "get csr2 success", + desc: "23: get csr2 success", method: "GET", path: "/api/v1/certificate_requests/2", data: "", @@ -324,7 +361,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "get csrs 3 success", + desc: "24: get csrs 3 success", method: "GET", path: "/api/v1/certificate_requests", data: "", @@ -332,7 +369,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "healthcheck success", + desc: "25: healthcheck success", method: "GET", path: "/status", data: "", @@ -340,7 +377,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { status: http.StatusOK, }, { - desc: "metrics endpoint success", + desc: "26: metrics endpoint success", method: "GET", path: "/metrics", data: "", @@ -349,7 +386,7 @@ func TestGoCertCertificatesHandlers(t *testing.T) { }, } for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { + t.Run(fmt.Sprintf("step %s", tC.desc), func(t *testing.T) { req, err := http.NewRequest(tC.method, ts.URL+tC.path, strings.NewReader(tC.data)) req.Header.Set("Authorization", "Bearer "+adminToken) if err != nil { @@ -890,3 +927,8 @@ func prepareUserAccounts(url string, client *http.Client, adminToken, nonAdminTo *nonAdminToken = string(resBody) } } + +// trimmed removes all whitespace and newlines from a given string +func trimmed(s string) string { + return strings.ReplaceAll(strings.TrimSpace(s), "\n", "\\n") +} diff --git a/internal/certdb/certdb.go b/internal/certdb/certdb.go index 9dbbf52..bd85fd5 100644 --- a/internal/certdb/certdb.go +++ b/internal/certdb/certdb.go @@ -126,6 +126,7 @@ func (db *CertificateRequestsRepository) UpdateCSR(id string, cert string) (int6 if err != nil { return 0, errors.New("cert validation failed: " + err.Error()) } + cert = sanitizeCertificateBundle(cert) } result, err := db.conn.Exec(fmt.Sprintf(queryUpdateCSR, db.certificateTable), cert, csr.ID) if err != nil { diff --git a/internal/certdb/certdb_test.go b/internal/certdb/certdb_test.go index 2928923..a72f8b0 100644 --- a/internal/certdb/certdb_test.go +++ b/internal/certdb/certdb_test.go @@ -1,6 +1,7 @@ package certdb_test import ( + "fmt" "log" "strconv" "strings" @@ -25,15 +26,15 @@ func TestCSRsEndToEnd(t *testing.T) { } defer db.Close() - id1, err := db.CreateCSR(ValidCSR1) + id1, err := db.CreateCSR(AppleCSR) if err != nil { t.Fatalf("Couldn't complete Create: %s", err) } - id2, err := db.CreateCSR(ValidCSR2) + id2, err := db.CreateCSR(BananaCSR) if err != nil { t.Fatalf("Couldn't complete Create: %s", err) } - id3, err := db.CreateCSR(ValidCSR3) + id3, err := db.CreateCSR(StrawberryCSR) if err != nil { t.Fatalf("Couldn't complete Create: %s", err) } @@ -49,7 +50,7 @@ func TestCSRsEndToEnd(t *testing.T) { if err != nil { t.Fatalf("Couldn't complete Retrieve: %s", err) } - if retrievedCSR.CSR != ValidCSR1 { + if retrievedCSR.CSR != AppleCSR { t.Fatalf("The CSR from the database doesn't match the CSR that was given") } @@ -60,14 +61,14 @@ func TestCSRsEndToEnd(t *testing.T) { if len(res) != 2 { t.Fatalf("CSR's weren't deleted from the DB properly") } - - _, err = db.UpdateCSR(strconv.FormatInt(id2, 10), ValidCert2) + var BananaCertBundle = strings.TrimSpace(fmt.Sprintf("%s%s", BananaCert, IssuerCert)) + _, err = db.UpdateCSR(strconv.FormatInt(id2, 10), BananaCertBundle) if err != nil { t.Fatalf("Couldn't complete Update: %s", err) } retrievedCSR, _ = db.RetrieveCSR(strconv.FormatInt(id2, 10)) - if retrievedCSR.Certificate != ValidCert2 { - t.Fatalf("The certificate that was uploaded does not match the certificate that was given.\n Retrieved: %s\nGiven: %s", retrievedCSR.Certificate, ValidCert2) + if retrievedCSR.Certificate != BananaCertBundle { + t.Fatalf("The certificate that was uploaded does not match the certificate that was given.\n Retrieved: %s\nGiven: %s", retrievedCSR.Certificate, BananaCertBundle) } _, err = db.UpdateCSR(strconv.FormatInt(id2, 10), "") if err != nil { @@ -87,13 +88,13 @@ func TestCreateFails(t *testing.T) { db, _ := certdb.NewCertificateRequestsRepository(":memory:", "CertificateReqs") //nolint:errcheck defer db.Close() - InvalidCSR := strings.ReplaceAll(ValidCSR1, "/", "+") + InvalidCSR := strings.ReplaceAll(AppleCSR, "M", "i") if _, err := db.CreateCSR(InvalidCSR); err == nil { t.Fatalf("Expected error due to invalid CSR") } - db.CreateCSR(ValidCSR1) //nolint:errcheck - if _, err := db.CreateCSR(ValidCSR1); err == nil { + db.CreateCSR(AppleCSR) //nolint:errcheck + if _, err := db.CreateCSR(AppleCSR); err == nil { t.Fatalf("Expected error due to duplicate CSR") } } @@ -102,13 +103,13 @@ func TestUpdateFails(t *testing.T) { db, _ := certdb.NewCertificateRequestsRepository(":memory:", "CertificateRequests") //nolint:errcheck defer db.Close() - id1, _ := db.CreateCSR(ValidCSR1) //nolint:errcheck - id2, _ := db.CreateCSR(ValidCSR2) //nolint:errcheck - InvalidCert := strings.ReplaceAll(ValidCert2, "/", "+") + id1, _ := db.CreateCSR(AppleCSR) //nolint:errcheck + id2, _ := db.CreateCSR(BananaCSR) //nolint:errcheck + InvalidCert := strings.ReplaceAll(BananaCert, "/", "+") if _, err := db.UpdateCSR(strconv.FormatInt(id2, 10), InvalidCert); err == nil { t.Fatalf("Expected updating with invalid cert to fail") } - if _, err := db.UpdateCSR(strconv.FormatInt(id1, 10), ValidCert2); err == nil { + if _, err := db.UpdateCSR(strconv.FormatInt(id1, 10), BananaCert); err == nil { t.Fatalf("Expected updating with mismatched cert to fail") } } @@ -117,7 +118,7 @@ func TestRetrieve(t *testing.T) { db, _ := certdb.NewCertificateRequestsRepository(":memory:", "CertificateRequests") //nolint:errcheck defer db.Close() - db.CreateCSR(ValidCSR1) //nolint:errcheck + db.CreateCSR(AppleCSR) //nolint:errcheck if _, err := db.RetrieveCSR("this is definitely not an id"); err == nil { t.Fatalf("Expected failure looking for nonexistent CSR") } @@ -181,19 +182,19 @@ func Example() { if err != nil { log.Fatalln(err) } - _, err = db.CreateCSR(ValidCSR2) + _, err = db.CreateCSR(BananaCSR) if err != nil { log.Fatalln(err) } - _, err = db.UpdateCSR(ValidCSR2, ValidCert2) + _, err = db.UpdateCSR(BananaCSR, BananaCert) if err != nil { log.Fatalln(err) } - entry, err := db.RetrieveCSR(ValidCSR2) + entry, err := db.RetrieveCSR(BananaCSR) if err != nil { log.Fatalln(err) } - if entry.Certificate != ValidCert2 { + if entry.Certificate != BananaCert { log.Fatalln("Retrieved Certificate doesn't match Stored Certificate") } err = db.Close() diff --git a/internal/certdb/validation.go b/internal/certdb/validation.go index ae191ae..be4e525 100644 --- a/internal/certdb/validation.go +++ b/internal/certdb/validation.go @@ -1,10 +1,13 @@ package certdb import ( + "bytes" "crypto/rsa" "crypto/x509" "encoding/pem" "errors" + "fmt" + "strings" ) // ValidateCertificateRequest validates the given CSR string to the following: @@ -27,19 +30,49 @@ func ValidateCertificateRequest(csr string) error { } // ValidateCertificate validates the given Cert string to the following: -// The cert string must be a valid PEM string, and should be of type CERTIFICATE -// The PEM string should be able to be parsed into a x509 Certificate +// +// The string must include 2 or more PEM formatted certificate strings. +// Each cert must be a valid PEM string, and should be capable of being parsed into type x509 CERTIFICATE +// Each subsequent certificate in the string should be the issuer of the previous string, which means: +// +// All except the first certificate should have the "is a CA" Basic Constraint. +// The public key of the certificate should match the public key of the following certificate. +// The issuer field of the certificate should match the subject field of the following certificate. func ValidateCertificate(cert string) error { - certBlock, _ := pem.Decode([]byte(cert)) - if certBlock == nil { - return errors.New("PEM Certificate string not found or malformed") + certData := []byte(cert) + certificates := []*x509.Certificate{} + + for { + certBlock, rest := pem.Decode(certData) + if certBlock == nil { + break + } + if certBlock.Type != "CERTIFICATE" { + return errors.New("a given PEM string was not a certificate") + } + certificate, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return err + } + certificates = append(certificates, certificate) + certData = rest } - if certBlock.Type != "CERTIFICATE" { - return errors.New("given PEM string not a certificate") + + if len(certificates) < 2 { + return errors.New("less than 2 certificate PEM strings were found") } - _, err := x509.ParseCertificate(certBlock.Bytes) - if err != nil { - return err + + for i, firstCert := range certificates[:len(certificates)-1] { + secondCert := certificates[i+1] + if !secondCert.IsCA { + return fmt.Errorf("invalid certificate chain: certificate %d is not a certificate authority", i+1) + } + if !bytes.Equal(firstCert.RawIssuer, secondCert.RawSubject) { + return fmt.Errorf("invalid certificate chain: certificate %d, certificate %d: subjects do not match", i, i+1) + } + if err := firstCert.CheckSignatureFrom(secondCert); err != nil { + return fmt.Errorf("invalid certificate chain: certificate %d, certificate %d: keys do not match: %s", i, i+1, err.Error()) + } } // TODO: We should validate the actual certificate parameters here too. (Has the required fields etc) return nil @@ -47,7 +80,7 @@ func ValidateCertificate(cert string) error { // CertificateMatchesCSR makes sure that the given certificate and CSR match. // The given CSR and Cert must pass their respective validation functions -// The given cert and CSR must share the same public key +// The given CSR and Cert must share the same public key func CertificateMatchesCSR(cert string, csr string) error { if err := ValidateCertificateRequest(csr); err != nil { return err @@ -66,3 +99,23 @@ func CertificateMatchesCSR(cert string, csr string) error { } return nil } + +// SanitizeCertificateBundle takes in a valid certificate string and formats it +// The final string has no trailing or leading whitespace, and only a single +// newline character between certificate PEM strings +func sanitizeCertificateBundle(cert string) string { + var buff bytes.Buffer + certData := []byte(cert) + for { + certBlock, rest := pem.Decode(certData) + if certBlock == nil { + break + } + err := pem.Encode(&buff, certBlock) + if err != nil { + return "" + } + certData = rest + } + return strings.TrimSpace(buff.String()) +} diff --git a/internal/certdb/validation_test.go b/internal/certdb/validation_test.go index 6e0940e..8abf1ff 100644 --- a/internal/certdb/validation_test.go +++ b/internal/certdb/validation_test.go @@ -8,87 +8,210 @@ import ( "github.com/canonical/gocert/internal/certdb" ) -var ValidCSR1 string = `-----BEGIN CERTIFICATE REQUEST----- -MIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDC5KgrADpuOUPwSh0YLmpWF66VTcciIGC2HcGn -oJknL7pm5q9qhfWGIdvKKlIA6cBB32jPd0QcYDsx7+AvzEvBuO7mq7v2Q1sPU4Q+ -L0s2pLJges6/cnDWvk/p5eBjDLOqHhUNzpMUga9SgIod8yymTZm3eqQvt1ABdwTg -FzBs5QdSm2Ny1fEbbcRE+Rv5rqXyJb2isXSujzSuS22VqslDIyqnY5WaLg+pjZyR -+0j13ecJsdh6/MJMUZWheimV2Yv7SFtxzFwbzBMO9YFS098sy4F896eBHLNe9cUC -+d1JDtLaewlMogjHBHAxmP54dhe6vvc78anElKKP4hm5N5nlAgMBAAGgWDBWBgkq -hkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD -AQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL -BQADggEBACP1VKEGVYKoVLMDJS+EZ0CPwIYWsO4xBXgK6atHe8WIChVn/8I7eo60 -cuMDiy4LR70G++xL1tpmYGRbx21r9d/shL2ehp9VdClX06qxlcGxiC/F8eThRuS5 -zHcdNqSVyMoLJ0c7yWHJahN5u2bn1Lov34yOEqGGpWCGF/gT1nEvM+p/v30s89f2 -Y/uPl4g3jpGqLCKTASWJDGnZLroLICOzYTVs5P3oj+VueSUwYhGK5tBnS2x5FHID -uMNMgwl0fxGMQZjrlXyCBhXBm1k6PmwcJGJF5LQ31c+5aTTMFU7SyZhlymctB8mS -y+ErBQsRpcQho6Ok+HTXQQUcx7WNcwI= +const ( + AppleCSR string = `-----BEGIN CERTIFICATE REQUEST----- +MIICsTCCAZkCAQAwbDELMAkGA1UEBhMCQ0ExFDASBgNVBAgMC05vdmEgU2NvdGlh +MRAwDgYDVQQHDAdIYWxpZmF4MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEjAQBgNVBAMMCWFwcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOhDSpNbeFiXMQzQcobExHqYMEGzqpX8N9+AR6/HPZWBybgx1hr3 +ejqsKornzpVph/dO9UC7O9aBlG071O9VQGHt3OU3rkZIk2009vYwLuSrAlJtnUne +p7KKn2lZGvh7jVyZE5RkS0X27vlT0soANsmcVq/82VneHrF/nbDcK6DOjQpS5o5l +EiNk2CIpYGUkw3WnQF4pBk8t4bNOl3nfpaAOfnmNuBX3mWyfPnaKMCENMpDqL9FR +V/O5bIPLmyH30OHUEJUkWOmFt9GFi+QfMoM0fR34KmRbDz79hZZb/yVPZZJl7l6i +FWXkNR3gxdEnwCZkTgWk5OqS9dCJOtsDE8ECAwEAAaAAMA0GCSqGSIb3DQEBCwUA +A4IBAQCqBX5WaNv/HjkzAyNXYuCToCb8GjmiMqL54t+1nEI1QTm6axQXivEbQT3x +GIh7uQYC06wHE23K6Znc1/G+o3y6lID07rvhBNal1qoXUiq6CsAqk+DXYdd8MEh5 +joerEedFqcW+WTUDcqddfIyDAGPqrM9j6/E+aFYyZjJ/xRuMf1zlWMljRiwj1NI9 +NxqjsYYQ3zxfUjv8gxXm0hN8Up1O9saoEF+zbuWNdiUWd6Ih3/3u5VBNSxgVOrDQ +CeXyyzkMx1pWTx0rWa7NSa+DMKVVzv46pck/9kLB4gPL8zqvIOMQsf74N0VcbVfd +9jQR8mPXQYPUERl1ZhNrkzkyA0kd -----END CERTIFICATE REQUEST----- ` -var ValidCSR2 string = `-----BEGIN CERTIFICATE REQUEST----- -MIIC5zCCAc8CAQAwRzEWMBQGA1UEAwwNMTAuMTUyLjE4My41MzEtMCsGA1UELQwk -MzlhY2UxOTUtZGM1YS00MzJiLTgwOTAtYWZlNmFiNGI0OWNmMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjM5Wz+HRtDveRzeDkEDM4ornIaefe8d8nmFi -pUat9qCU3U9798FR460DHjCLGxFxxmoRitzHtaR4ew5H036HlGB20yas/CMDgSUI -69DyAsyPwEJqOWBGO1LL50qXdl5/jOkO2voA9j5UsD1CtWSklyhbNhWMpYqj2ObW -XcaYj9Gx/TwYhw8xsJ/QRWyCrvjjVzH8+4frfDhBVOyywN7sq+I3WwCbyBBcN8uO -yae0b/q5+UJUiqgpeOAh/4Y7qI3YarMj4cm7dwmiCVjedUwh65zVyHtQUfLd8nFW -Kl9775mNBc1yicvKDU3ZB5hZ1MZtpbMBwaA1yMSErs/fh5KaXwIDAQABoFswWQYJ -KoZIhvcNAQkOMUwwSjBIBgNVHREEQTA/hwQKmLc1gjd2YXVsdC1rOHMtMC52YXVs -dC1rOHMtZW5kcG9pbnRzLnZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3 -DQEBCwUAA4IBAQCJt8oVDbiuCsik4N5AOJIT7jKsMb+j0mizwjahKMoCHdx+zv0V -FGkhlf0VWPAdEu3gHdJfduX88WwzJ2wBBUK38UuprAyvfaZfaYUgFJQNC6DH1fIa -uHYEhvNJBdFJHaBvW7lrSFi57fTA9IEPrB3m/XN3r2F4eoHnaJJqHZmMwqVHck87 -cAQXk3fvTWuikHiCHqqdSdjDYj/8cyiwCrQWpV245VSbOE0WesWoEnSdFXVUfE1+ -RSKeTRuuJMcdGqBkDnDI22myj0bjt7q8eqBIjTiLQLnAFnQYpcCrhc8dKU9IJlv1 -H9Hay4ZO9LRew3pEtlx2WrExw/gpUcWM8rTI + BananaCSR string = `-----BEGIN CERTIFICATE REQUEST----- +MIICrjCCAZYCAQAwaTELMAkGA1UEBhMCVFIxDjAMBgNVBAgMBUl6bWlyMRIwEAYD +VQQHDAlOYXJsaWRlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0 +ZDETMBEGA1UEAwwKYmFuYW5hLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAK+vJMxO1GTty09/E4M/RbTCPABleCuYc/uzj72KWaIvoDaanuJ4NBWM +2aUiepxWdMNTR6oe31gLq4agLYT309tXwCeBLQnOxvBFWONmBG1qo0fQkvT5kSoq +AO29D7hkQ0gVwg7EF3qOd0JgbDm/yvexKpYLVvWMQAngHwZRnd5vHGk6M3P7G4oG +mIj/CL2bF6va7GWODYHb+a7jI1nkcsrk+vapc+doVszcoJ+2ryoK6JndOSGjt9SD +uxulWZHQO32XC0btyub63pom4QxRtRXmb1mjM37XEwXJSsQO1HOnmc6ycqUK53p0 +jF8Qbs0m8y/p2NHFGTUfiyNYA3EdkjUCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IB +AQA+hq8kS2Y1Y6D8qH97Mnnc6Ojm61Q5YJ4MghaTD+XXbueTCx4DfK7ujYzK3IEF +pH1AnSeJCsQeBdjT7p6nv5GcwqWXWztNKn9zibXiASK/yYKwqvQpjSjSeqGEh+Sa +9C9SHeaPhZrJRj0i3NkqmN8moWasF9onW6MNKBX0B+pvBB+igGPcjCIFIFGUUaky +upMXY9IG3LlWvlt+HTfuMZV+zSOZgD9oyqkh5K9XRKNq/mnNz/1llUCBZRmfeRBY ++sJ4M6MJRztiyX4/Fjb8UHQviH931rkiEGtG826IvWIyiRSnAeE8B/VzL0GlT9Zq +ge6lFRxB1FlDuU4Blef8FnOI -----END CERTIFICATE REQUEST-----` -var ValidCSR3 string = `-----BEGIN CERTIFICATE REQUEST----- -MIICszCCAZsCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDN7tHggWTtxiT5Sh5Npoif8J2BdpJjtMdpZ7Vu -NVzMxW/eojSRlq0p3nafmpjnSdSH1k/XMmPsgmv9txxEHMw1LIUJUef2QVrQTI6J -4ueu9NvexZWXZ+UxFip63PKyn/CkZRFiHCRIGzDDPxM2aApjghXy9ISMtGqDVSnr -5hQDu2U1CEiUWKMoTpyk/KlBZliDDOzaGm3cQuzKWs6Stjzpq+uX4ecJAXZg5Cj+ -+JUETH93A/VOfsiiHXoKeTnFMCsmJgEHz2DZixw8EN8XgpOp5BA2n8Y/xS+Ren5R -ZH7uNJI/SmQ0yrR+2bYR6hm+4bCzspyCfzbiuI5IS9+2eXA/AgMBAAGgWDBWBgkq -hkiG9w0BCQ4xSTBHMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcD -AQYIKwYBBQUHAwIwFgYDVR0RBA8wDYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEL -BQADggEBAB/aPfYLbnCubYyKnxLRipoLr3TBSYFnRfcxiZR1o+L3/tuv2NlrXJjY -K13xzzPhwuZwd6iKfX3xC33sKgnUNFawyE8IuAmyhJ2cl97iA2lwoYcyuWP9TOEx -LT60zxp7PHsKo53gqaqRJ5B9RZtiv1jYdUZvynHP4J5JG7Zwaa0VNi/Cx5cwGW8K -rfvNABPUAU6xIqqYgd2heDPF6kjvpoNiOl056qIAbk0dbmpqOJf/lxKBRfqlHhSC -0qRScGu70l2Oxl89YSsfGtUyQuzTkLshI2VkEUM+W/ZauXbxLd8SyWveH3/7mDC+ -Sgi7T+lz+c1Tw+XFgkqryUwMeG2wxt8= + StrawberryCSR string = `-----BEGIN CERTIFICATE REQUEST----- +MIICrzCCAZcCAQAwajELMAkGA1UEBhMCSVQxDzANBgNVBAgMBlBhZG92YTEOMAwG +A1UEBwwFUGFkdWExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEX +MBUGA1UEAwwOc3RyYXdiZXJyeS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDXXHpy+3LLRCImyEQitM9eUdgYkexLz2PcAf89tTpkpt3L1woJw0bv ++YR80UcR2Pg+7uUVm4XSKFvcdyWg8yADHIDDZkEmKFEbrOLUsWWTQEsCpFt5MU4u +6YnYXV0YflPXmRsJRd90NOen+wlM2ajK1gGTtLPdJ6axz15LdcT2uXXIvWhncjgL +CvVpd/x44AMxD/BPf/d27VO5hEjxR//DtcOmS/jA+Zf1+dyIAWs2LH+ctsaPLOcg +1rBiRrHtGL8wmPwgwK9b+QLiq9Ik+dx1Jl6BvC36LRk2CxTxfZ6e4UdYVhtnjMW2 +VEUAVg9LtowvXTexESUv6Mh4uQF6pW5ZAgMBAAGgADANBgkqhkiG9w0BAQsFAAOC +AQEAW40HaxjVSDNKeWJ8StWGfstdvk3dwqjsfLgmnBBZSLcGppYEnnRlJxhMJ9Ks +x2IYw7wJ55kOJ7V+SunKPPoY+7PwNDV9Llxp58vvE8CFnOc3WcL9pA2V5LbTXwtT +R7jID5GZjOv0bn3x1WXuKVW5tkYdT6sW14rfGut1T+r1kYls+JQ5ap+BzfMtThZz +38PCnEMmSo0/KmgUu5/LakPoy3JPaFB0bCgViZSWlxiSR44YZPsVaRL8E7Zt/qjJ +glRL/48q/tORtxv18/Girl6oiQholkADaH3j2gB3t/fCLp8guAVLWB9DzhwrqWwP +GFl9zB5HDoij2l0kHrb44TuonQ== -----END CERTIFICATE REQUEST----- ` -var ValidCert2 string = `-----BEGIN CERTIFICATE----- -MIIDrDCCApSgAwIBAgIURKr+jf7hj60SyAryIeN++9wDdtkwDQYJKoZIhvcNAQEL -BQAwOTELMAkGA1UEBhMCVVMxKjAoBgNVBAMMIXNlbGYtc2lnbmVkLWNlcnRpZmlj -YXRlcy1vcGVyYXRvcjAeFw0yNDAzMjcxMjQ4MDRaFw0yNTAzMjcxMjQ4MDRaMEcx -FjAUBgNVBAMMDTEwLjE1Mi4xODMuNTMxLTArBgNVBC0MJDM5YWNlMTk1LWRjNWEt -NDMyYi04MDkwLWFmZTZhYjRiNDljZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAIzOVs/h0bQ73kc3g5BAzOKK5yGnn3vHfJ5hYqVGrfaglN1Pe/fBUeOt -Ax4wixsRccZqEYrcx7WkeHsOR9N+h5RgdtMmrPwjA4ElCOvQ8gLMj8BCajlgRjtS -y+dKl3Zef4zpDtr6APY+VLA9QrVkpJcoWzYVjKWKo9jm1l3GmI/Rsf08GIcPMbCf -0EVsgq7441cx/PuH63w4QVTsssDe7KviN1sAm8gQXDfLjsmntG/6uflCVIqoKXjg -If+GO6iN2GqzI+HJu3cJoglY3nVMIeuc1ch7UFHy3fJxVipfe++ZjQXNconLyg1N -2QeYWdTGbaWzAcGgNcjEhK7P34eSml8CAwEAAaOBnTCBmjAhBgNVHSMEGjAYgBYE -FN/vgl9cAapV7hH9lEyM7qYS958aMB0GA1UdDgQWBBRJJDZkHr64VqTC24DPQVld -Ba3iPDAMBgNVHRMBAf8EAjAAMEgGA1UdEQRBMD+CN3ZhdWx0LWs4cy0wLnZhdWx0 -LWs4cy1lbmRwb2ludHMudmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWyHBAqYtzUwDQYJ -KoZIhvcNAQELBQADggEBAEH9NTwDiSsoQt/QXkWPMBrB830K0dlwKl5WBNgVxFP+ -hSfQ86xN77jNSp2VxOksgzF9J9u/ubAXvSFsou4xdP8MevBXoFJXeqMERq5RW3gc -WyhXkzguv3dwH+n43GJFP6MQ+n9W/nPZCUQ0Iy7ueAvj0HFhGyZzAE2wxNFZdvCs -gCX3nqYpp70oZIFDrhmYwE5ij5KXlHD4/1IOfNUKCDmQDgGPLI1tVtwQLjeRq7Hg -XVelpl/LXTQawmJyvDaVT/Q9P+WqoDiMjrqF6Sy7DzNeeccWVqvqX5TVS6Ky56iS -Mvo/+PAJHkBciR5Xn+Wg2a+7vrZvT6CBoRSOTozlLSM= + BananaCert string = `-----BEGIN CERTIFICATE----- +MIIEUTCCAjkCFE8lmuBE85/RPw2M17Kzl93O+9IIMA0GCSqGSIb3DQEBCwUAMGEx +CzAJBgNVBAYTAlRSMQ4wDAYDVQQIDAVJem1pcjESMBAGA1UEBwwJTmFybGlkZXJl +MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAMMAm1l +MB4XDTI0MDYyODA4NDIyMFoXDTI1MDYyODA4NDIyMFowaTELMAkGA1UEBhMCVFIx +DjAMBgNVBAgMBUl6bWlyMRIwEAYDVQQHDAlOYXJsaWRlcmUxITAfBgNVBAoMGElu +dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKYmFuYW5hLmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK+vJMxO1GTty09/E4M/RbTCPABl +eCuYc/uzj72KWaIvoDaanuJ4NBWM2aUiepxWdMNTR6oe31gLq4agLYT309tXwCeB +LQnOxvBFWONmBG1qo0fQkvT5kSoqAO29D7hkQ0gVwg7EF3qOd0JgbDm/yvexKpYL +VvWMQAngHwZRnd5vHGk6M3P7G4oGmIj/CL2bF6va7GWODYHb+a7jI1nkcsrk+vap +c+doVszcoJ+2ryoK6JndOSGjt9SDuxulWZHQO32XC0btyub63pom4QxRtRXmb1mj +M37XEwXJSsQO1HOnmc6ycqUK53p0jF8Qbs0m8y/p2NHFGTUfiyNYA3EdkjUCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAgEAVZJZD0/ojZSOVIesZvrjLG0agSp0tsXY+hEt +I/knpYLvRcAd8b3Jx9gk+ug+FwDQ4IBIkTX18qhK2fgVUuMR/ubfpQeCMbp64N3Q +kmN/E1eu0bl6hhHAL7jEbi0DE3vAN9huQxAIu5pCyLvZIrPJtvuyj2jOpJBZwGoP +539lfEM++XALzI4qKQ6Z0a0rJZ4HoruKiYwEFZ7VkmRLD0uef6NMZRqa/Vx+o0uT +1TjH4AeDDmJmP/aHlHbpXkHQ9h9rfTa6Qbypo+T9pGDhd02O1tEqrHfiQyNWJxb0 +rbR+owT32iCfayzKKqhmAYSF2d9XKWEhulgxWDaXgvUbq4Y+fgfU2qMVz5uusTDh +a9Mp9dsYWySWEUcEa4v2w6FfaaVXE1S9ubm+HoIVtotuutL5fn86q19pAAePYjLQ +ybiETp5LU3chuYmMlCiDRNGHYhN5nvGcttqRdWIBe454RRPNo4iGVl13l6aG8rmI +xDfk5lIwObalbELv+mEIGI1j/j4//nJFXByxlLHm5/BF8rmvHDj1aPtPRw9DLgSX +ejhjjec1xnkBR+JF0g474hLdPjCnA0aqLQInZbjJJm5iXzyXBg1cy7KvIBy3ZkrR +Pp7ObjaWxjCT3O6nEH3w6Ozsyg2cHXQIdVXLvNnV1bxUbPnfhQosKGKgU6s+lcLM +SRhHB2k= +-----END CERTIFICATE----- +` + StrawberryCert string = `-----BEGIN CERTIFICATE----- +MIIEUjCCAjoCFE8lmuBE85/RPw2M17Kzl93O+9IJMA0GCSqGSIb3DQEBCwUAMGEx +CzAJBgNVBAYTAlRSMQ4wDAYDVQQIDAVJem1pcjESMBAGA1UEBwwJTmFybGlkZXJl +MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxCzAJBgNVBAMMAm1l +MB4XDTI0MDYyODA4NDIzN1oXDTI1MDYyODA4NDIzN1owajELMAkGA1UEBhMCSVQx +DzANBgNVBAgMBlBhZG92YTEOMAwGA1UEBwwFUGFkdWExITAfBgNVBAoMGEludGVy +bmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAwwOc3RyYXdiZXJyeS5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXXHpy+3LLRCImyEQitM9eUdgY +kexLz2PcAf89tTpkpt3L1woJw0bv+YR80UcR2Pg+7uUVm4XSKFvcdyWg8yADHIDD +ZkEmKFEbrOLUsWWTQEsCpFt5MU4u6YnYXV0YflPXmRsJRd90NOen+wlM2ajK1gGT +tLPdJ6axz15LdcT2uXXIvWhncjgLCvVpd/x44AMxD/BPf/d27VO5hEjxR//DtcOm +S/jA+Zf1+dyIAWs2LH+ctsaPLOcg1rBiRrHtGL8wmPwgwK9b+QLiq9Ik+dx1Jl6B +vC36LRk2CxTxfZ6e4UdYVhtnjMW2VEUAVg9LtowvXTexESUv6Mh4uQF6pW5ZAgMB +AAEwDQYJKoZIhvcNAQELBQADggIBAIZP5KCkgnoZ8SvnRpQT1rA1d1aneiRdnIKI +WznmGdZAJOWGDVjP0fywdDmpxbK9+6qljzwvAm/cRVEGBJXKHfPvpNtLgO/TCKIG +KOhNVttvgyIKB/LhcN36+qdfZrSUD0XqB2e+y5tzY/WSUy00zHVqohHcBydL//xe +mKiHiOwZ1QwZkjmYv2Lqd1xHaU28B98k7wvQuhxKSB2lvlCKBm5NjiQx+ZyG/NMC +W9zGSBRjz+elrSFJFJiIO5gLVBJXOQz029yBdju+PrGG4i5fLAvJSSyCVgNKAK8S +9x17WmsGBxdAEWiOrYYSUbaJGGlJ+GV5z/2hjGx7SDGV6I4YaHBnD7ZvLQ684uka +K8LNVT06RmvkvisdW9edJzzZzzu+B8GuGCV49CUUWInCIVTfIk/FtViUDOiXL8gM +Wk5OqODXJcGI4oK6N+4zfT25XBOMgON6O6JUF6cJtte5/Pv6EZhZeNjkxOGZMy9b +Dh+wnIt2whBkOv4YmE5/P5h4K4xom+XCz3ec0llP/1ehvY5nCFsDNe7qQ2Zlroi3 +dYaWAI3cEWYFs2BkcL6yoC/o2lGUnFGzg+zPU0KTwypAseQurBabmbdNXggqhXWZ +X2iP1fzriAc7Go/uLVH4qezAhR+KisfUrkCw8Jyma8lbkmY0f0OWir6cfWxBfuDf +JUdt8AH0 +-----END CERTIFICATE----- + ` + + IssuerCert string = `-----BEGIN CERTIFICATE----- +MIIFozCCA4ugAwIBAgIUDjtO3bEluUX3tzvrckATlycRVfwwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVFIxDjAMBgNVBAgMBUl6bWlyMRIwEAYDVQQHDAlOYXJs +aWRlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDELMAkGA1UE +AwwCbWUwHhcNMjQwNjI4MDYwNTQ5WhcNMzQwNjI2MDYwNTQ5WjBhMQswCQYDVQQG +EwJUUjEOMAwGA1UECAwFSXptaXIxEjAQBgNVBAcMCU5hcmxpZGVyZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQswCQYDVQQDDAJtZTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJU+5YaFlpn+bWvVri5L6EkmbAPuavsI +/KXY7ufRmc5qb08o1na9lLJ/7TuMD4K36Idnq20n1JohSlrdymBpNZ8O3m5fYYtk +hx5WADlBZsKnC5aZJIChEb4bYcOFLP+d3PooVsAKBxW0Q6TECviQcK7GxaxEZw0L +7FRhX2c9+CxbvRGP6OGVggXZxwkZik/JJ9aym+fltt9QvlxQVBq/GlFYZYC+H8jV +Z6RnUjugnWcTm9PAsQ6+EHEevAW+dWaDP+gr9AgKKz1EXbc1mVKAVOLHjb+Ue7RC +vFoar/YxYIszD58dOSB/GuAxn+JAjWbnOu7jeX3XeWlKOagUJF9L9TgMIUWdiuJG +8Uu/kK2MjyRFdT8opnPFAXrK7vSuMBzhRtswAlWc8xoZWeSQF+NpjU+swbg8ySYT +LfZxVB+s/ftxnGU3RM/RWdbZhb0DAuIBsFAGCbnj+Q61/cK4i58JVjUqzLk+XOwR +55LAyS0Y5pj9jDc5mqvS0z7ot7s2OBM1+o8e3KJgdMSXorYkv3toHMGEIUmPQZCX +JtRCjFNgnoWeLDc+oLiN6BlPx7bS4MDN9tMPCJwF6vnxFzLAzdRqY3D7uRS3chsx +7ClMR9MDsSxplC7tptXgv8UTzh1XZjWGCeZq0Gbe927Hmwy2q8k/BFwnR4PIVSiE +7YAZPb0CPmrfAgMBAAGjUzBRMB0GA1UdDgQWBBRgLXukRHTovOG6g9Z5eCaeh6Sx +aTAfBgNVHSMEGDAWgBRgLXukRHTovOG6g9Z5eCaeh6SxaTAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQA9TpgTrGmnyxKB2ne76LNQadiijVPpS6/U +OPFAX4EPJ0V5DhDreJjsZJC6Is2Q9+qsPpn/nlW7bvZUVHGodUKcE+TQWFiMtLvu +8ifzk8x1R46aqhTyxb7WBBFfvbvdmlEENKTmTS6A/C3nYgmkfk5N7x84iTowmsVl +Yzz9iRzxkqQ+mU3L2/Sp5nXPYWfzV9WXIJdxWcot7f4CJ79eVFu4D9hYfzcPQ9P9 +0qCBRbH/01D2E/3uTHhZPPmK2Tp1ao5SuGLppjMPX8VWVL5CMTXOj+1LF0nJJc/J +9MrqXwtlLyKGP6HX8qALbaXwcv7db6bF+aEsgWmIEB+0ecGk9IR3XQn7I379CO3v +J2oUCZ++lV9e2tcRehUprE1v8i+DFhPtS1iNjrO7KnDYkXimR5zI+3sGFI9/9wY0 +4PAV/roZFiEJHe5kA49vwIihJaDgy/SPIYgG/vhdj+WeIbi1ilEi12ou7VF0tyiE +j3eXaMAL8EAKxCUZbXcuwmK9qistAYXBFFEK9M08FwLH8HM4LoPjshMg3II9Ncs8 +p3to8U99/ZeFbJRzEUF9poZ7VwxBEcgfWD1RV0+gNLC3Au2yuc4C3anknOv7Db/r +jdzVA8yTI8cZ/RtRohp5H/s+j2tcdfB3Zt+wfS4nLxqN/kf7qv2VSdPbXyTyz/ft +btZkbfdL5A== +-----END CERTIFICATE----- +` + WrongPKIssuerCert string = `-----BEGIN CERTIFICATE----- +MIIDozCCAougAwIBAgIUE8WQeUw8YMVJlt37CjOBXJ+R7wQwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVFIxDjAMBgNVBAgMBUl6bWlyMRIwEAYDVQQHDAlOYXJs +aWRlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDELMAkGA1UE +AwwCbWUwHhcNMjQwODIyMDkyMDI2WhcNMzQwODIwMDkyMDI2WjBhMQswCQYDVQQG +EwJUUjEOMAwGA1UECAwFSXptaXIxEjAQBgNVBAcMCU5hcmxpZGVyZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQswCQYDVQQDDAJtZTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAI5KK5H6JN8n3ZF8gp4DRLtIuE16MMZu +H6Br59in71lIE64TMiL6ScqVu7x+etUlvTDcLBX5yYpQ5gZLwB9MyqTRqctZDtP8 +82Pa/XIkknFPhcfYN/njINKp2mm1P5zsSm8bznhiCnrfxsYZ13lrJBPjsceRgnD4 +Z3207STUO9XIKb1qDUo2tRS1t49g4XiYhEaeATftXladO8AjM99ERXF41MRl8TOm +tRvhl0QrJnEn7CTOhbgN9HYdE9Bu6nOVWLM0zjyeqFJGFlWMTCRYwxYx1/jr6vwl +sF8N+8mkuMpQg13oQdFNpCK9YyoWRoC9zKJbh727VSPzqpyR2I1upg8CAwEAAaNT +MFEwHQYDVR0OBBYEFMi9/T/yZ5n6/PFIketk1fsDpXLgMB8GA1UdIwQYMBaAFMi9 +/T/yZ5n6/PFIketk1fsDpXLgMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAB+pIJCiRzaWAht4pBMmbrDaymIhaHeBsAkFmleAZo0cKixAZp4cP2J6 +zIN4pEHchsX259wRiAoy0oQ4D1B2fUE+4FYKdIUMQqXh3h8eXaOJAea/OOLHU+9q +nJoQ/4LqsLpwEGB0ZUJN8RO+LML3U1FyY+5Y7tNj5JlpWMtBebAEdhDS91fVdAp+ +jALl5X1Wbx/dtBQnubm1YolBVYXnI2zYywa8IgpnguCu9NIp3uqSVf0xcBEnNIny +W5/mfOoXTnuKZKTEvButfrlkLsABQvVepitmZGv+q/f4crCkhms8B23WMRLdteiK +BqHOQR7Y7LSxxC+bAa1QdhgumR3PL8I= -----END CERTIFICATE-----` + WrongSubjectIssuerCert string = `-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIUWJY4vKnl3+kQ487QtMfzLDBTnAowDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVFIxDjAMBgNVBAgMBUl6bWlyMRIwEAYDVQQHDAlOYXJs +aWRlcmUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEOMAwGA1UE +AwwFbm90bWUwHhcNMjQwODIyMDkxOTQ4WhcNMzQwODIwMDkxOTQ4WjBkMQswCQYD +VQQGEwJUUjEOMAwGA1UECAwFSXptaXIxEjAQBgNVBAcMCU5hcmxpZGVyZTEhMB8G +A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ4wDAYDVQQDDAVub3RtZTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJU+5YaFlpn+bWvVri5L6Ekm +bAPuavsI/KXY7ufRmc5qb08o1na9lLJ/7TuMD4K36Idnq20n1JohSlrdymBpNZ8O +3m5fYYtkhx5WADlBZsKnC5aZJIChEb4bYcOFLP+d3PooVsAKBxW0Q6TECviQcK7G +xaxEZw0L7FRhX2c9+CxbvRGP6OGVggXZxwkZik/JJ9aym+fltt9QvlxQVBq/GlFY +ZYC+H8jVZ6RnUjugnWcTm9PAsQ6+EHEevAW+dWaDP+gr9AgKKz1EXbc1mVKAVOLH +jb+Ue7RCvFoar/YxYIszD58dOSB/GuAxn+JAjWbnOu7jeX3XeWlKOagUJF9L9TgM +IUWdiuJG8Uu/kK2MjyRFdT8opnPFAXrK7vSuMBzhRtswAlWc8xoZWeSQF+NpjU+s +wbg8ySYTLfZxVB+s/ftxnGU3RM/RWdbZhb0DAuIBsFAGCbnj+Q61/cK4i58JVjUq +zLk+XOwR55LAyS0Y5pj9jDc5mqvS0z7ot7s2OBM1+o8e3KJgdMSXorYkv3toHMGE +IUmPQZCXJtRCjFNgnoWeLDc+oLiN6BlPx7bS4MDN9tMPCJwF6vnxFzLAzdRqY3D7 +uRS3chsx7ClMR9MDsSxplC7tptXgv8UTzh1XZjWGCeZq0Gbe927Hmwy2q8k/BFwn +R4PIVSiE7YAZPb0CPmrfAgMBAAGjUzBRMB0GA1UdDgQWBBRgLXukRHTovOG6g9Z5 +eCaeh6SxaTAfBgNVHSMEGDAWgBRgLXukRHTovOG6g9Z5eCaeh6SxaTAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAwNq0z26m13RBvZX/uOR5tIQ6j +l/JpSMhocr6GUTKx1NEmyaO9UEAdwHi7nFGocCbCeMNPBxpaJGkSTxe5HefhDJOI +QcnOo1yY9q5HXsp2SPvXjkZ2Palg1rV/u8BChVvULDDT+JtABJlll+cfggh1pkZv +Z3V7Zh7u7gWbnsSnM0X3zVpxGf/cqZNEoHesAaWJA4yYIH2wr5TwqksXGPFE/g/Z +fhUDeI7OP8kM/A8HnCXdxUok2Zf/wyuoPvrFUaPrcYkZK3omT6H24VdyejuBe2k5 ++e0ij3nU8DxKEbKn6XaJFhBzAmP1APi8fLIwO6gig/XUWrfKrqO0ax4Vgl4r88Ht +y4hiHmP9kgWjYqUijLpK5ap5607tfbtZ0QIS54HAPAjE77ZdsEGfkAZPmyCTPg41 +Q+YWZJS8HogVTZKY267x7u4lQ68jSVBxpeRHGYzd2HWxWGKVQq8pEa2bob9zby/N +QNRikyGkbp7ep5HgBrZeJJJ5zdaqNzVmXY0JIfhkUypSiCe5X1WgZ9GVCC9wi72D +y6MHDTAyVHrSouCqfh9XD6RDN58d+u9kLEg0WJD55wH4E4z+ZZhEMicCWfT/rn+b +b3dRTVslxdJ0dOApn/6zwfRMXgI7j2yRSkA7F39ekwlPhJy2bGrEDgTlDK33AwPU +wM1PZYERQJNOGMAI5Q== +-----END CERTIFICATE-----` +) func TestCSRValidationSuccess(t *testing.T) { - cases := []string{ValidCSR1, ValidCSR2, ValidCSR3} + cases := []string{AppleCSR, BananaCSR, StrawberryCSR} for i, c := range cases { t.Run(fmt.Sprintf("ValidCSR%d", i), func(t *testing.T) { @@ -102,12 +225,12 @@ func TestCSRValidationSuccess(t *testing.T) { func TestCSRValidationFail(t *testing.T) { var wrongString = "this is a real csr!!!" var wrongStringErr = "PEM Certificate Request string not found or malformed" - var ValidCSRWithoutWhitespace = strings.ReplaceAll(ValidCSR1, "\n", "") + var ValidCSRWithoutWhitespace = strings.ReplaceAll(AppleCSR, "\n", "") var ValidCSRWithoutWhitespaceErr = "PEM Certificate Request string not found or malformed" - var wrongPemType = strings.ReplaceAll(ValidCSR1, "CERTIFICATE REQUEST", "SOME RANDOM PEM TYPE") + var wrongPemType = strings.ReplaceAll(AppleCSR, "CERTIFICATE REQUEST", "SOME RANDOM PEM TYPE") var wrongPemTypeErr = "given PEM string not a certificate request" - var InvalidCSR = strings.ReplaceAll(ValidCSR1, "/", "p") - var InvalidCSRErr = "asn1: syntax error: invalid boolean" + var InvalidCSR = strings.ReplaceAll(AppleCSR, "s", "p") + var InvalidCSRErr = "asn1: syntax error: data truncated" cases := []struct { input string @@ -134,6 +257,10 @@ func TestCSRValidationFail(t *testing.T) { for i, c := range cases { t.Run(fmt.Sprintf("InvalidCSR%d", i), func(t *testing.T) { err := certdb.ValidateCertificateRequest(c.input) + if err == nil { + t.Errorf("No error received. Expected: %s", c.expectedErr) + return + } if err.Error() != c.expectedErr { t.Errorf("Expected error not found:\nReceived: %s\nExpected: %s", err, c.expectedErr) } @@ -142,7 +269,7 @@ func TestCSRValidationFail(t *testing.T) { } func TestCertValidationSuccess(t *testing.T) { - cases := []string{ValidCert2} + cases := []string{fmt.Sprintf("%s\n%s", BananaCert, IssuerCert)} for i, c := range cases { t.Run(fmt.Sprintf("ValidCert%d", i), func(t *testing.T) { @@ -155,13 +282,19 @@ func TestCertValidationSuccess(t *testing.T) { func TestCertValidationFail(t *testing.T) { var wrongCertString = "this is a real cert!!!" - var wrongCertStringErr = "PEM Certificate string not found or malformed" - var ValidCertWithoutWhitespace = strings.ReplaceAll(ValidCert2, "\n", "") - var ValidCertWithoutWhitespaceErr = "PEM Certificate string not found or malformed" - var wrongPemType = strings.ReplaceAll(ValidCert2, "CERTIFICATE", "SOME RANDOM PEM TYPE") - var wrongPemTypeErr = "given PEM string not a certificate" - var InvalidCert = strings.ReplaceAll(ValidCert2, "M", "i") + var wrongCertStringErr = "less than 2 certificate PEM strings were found" + var wrongPemType = strings.ReplaceAll(BananaCert, "CERTIFICATE", "SOME RANDOM PEM TYPE") + var wrongPemTypeErr = "a given PEM string was not a certificate" + var InvalidCert = strings.ReplaceAll(BananaCert, "M", "i") var InvalidCertErr = "x509: malformed certificate" + var singleCert = BananaCert + var singleCertErr = "less than 2 certificate PEM strings were found" + var issuerCertPKDoesNotMatch = fmt.Sprintf("%s\n%s", BananaCert, WrongPKIssuerCert) + var issuerCertPKDoesNotMatchErr = "invalid certificate chain: certificate 0, certificate 1: keys do not match" + var issuerCertSubjectDoesNotMatch = fmt.Sprintf("%s\n%s", BananaCert, WrongSubjectIssuerCert) + var issuerCertSubjectDoesNotMatchErr = "invalid certificate chain: certificate 0, certificate 1: subjects do not match" + var issuerCertNotCA = fmt.Sprintf("%s\n%s", BananaCert, StrawberryCert) + var issuerCertNotCaErr = "invalid certificate chain: certificate 1 is not a certificate authority" cases := []struct { inputCert string @@ -171,10 +304,6 @@ func TestCertValidationFail(t *testing.T) { inputCert: wrongCertString, expectedErr: wrongCertStringErr, }, - { - inputCert: ValidCertWithoutWhitespace, - expectedErr: ValidCertWithoutWhitespaceErr, - }, { inputCert: wrongPemType, expectedErr: wrongPemTypeErr, @@ -183,12 +312,32 @@ func TestCertValidationFail(t *testing.T) { inputCert: InvalidCert, expectedErr: InvalidCertErr, }, + { + inputCert: singleCert, + expectedErr: singleCertErr, + }, + { + inputCert: issuerCertPKDoesNotMatch, + expectedErr: issuerCertPKDoesNotMatchErr, + }, + { + inputCert: issuerCertSubjectDoesNotMatch, + expectedErr: issuerCertSubjectDoesNotMatchErr, + }, + { + inputCert: issuerCertNotCA, + expectedErr: issuerCertNotCaErr, + }, } for i, c := range cases { t.Run(fmt.Sprintf("InvalidCert%d", i), func(t *testing.T) { err := certdb.ValidateCertificate(c.inputCert) - if err.Error() != c.expectedErr { + if err == nil { + t.Errorf("No error received. Expected: %s", c.expectedErr) + return + } + if !strings.HasPrefix(err.Error(), c.expectedErr) { t.Errorf("Expected error not found:\nReceived: %s\n Expected: %s", err, c.expectedErr) } }) @@ -201,8 +350,8 @@ func TestCertificateMatchesCSRSuccess(t *testing.T) { inputCert string }{ { - inputCSR: ValidCSR2, - inputCert: ValidCert2, + inputCSR: BananaCSR, + inputCert: fmt.Sprintf("%s\n%s", BananaCert, IssuerCert), }, } @@ -225,8 +374,8 @@ func TestCertificateMatchesCSRFail(t *testing.T) { expectedErr string }{ { - inputCSR: ValidCSR1, - inputCert: ValidCert2, + inputCSR: AppleCSR, + inputCert: fmt.Sprintf("%s\n%s", BananaCert, IssuerCert), expectedErr: certificateDoesNotMatchErr, }, } @@ -234,6 +383,10 @@ func TestCertificateMatchesCSRFail(t *testing.T) { for i, c := range cases { t.Run(fmt.Sprintf("InvalidCert%d", i), func(t *testing.T) { err := certdb.CertificateMatchesCSR(c.inputCert, c.inputCSR) + if err == nil { + t.Errorf("No error received. Expected: %s", c.expectedErr) + return + } if err.Error() != c.expectedErr { t.Errorf("Expected error not found:\nReceived: %s\n Expected: %s", err, c.expectedErr) } diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 0ec14c2..a94286c 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -51,18 +51,27 @@ func TestPrometheusHandler(t *testing.T) { } // Generates a CSR and Certificate with the given days remaining -func generateCertPair(daysRemaining int) (string, string) { +func generateCertPair(daysRemaining int) (string, string, string) { NotAfterTime := time.Now().AddDate(0, 0, daysRemaining) - key, _ := rsa.GenerateKey(rand.Reader, 2048) + certKey, _ := rsa.GenerateKey(rand.Reader, 2048) + caKey, _ := rsa.GenerateKey(rand.Reader, 2048) csrTemplate := x509.CertificateRequest{} + caTemplate := x509.Certificate{ + SerialNumber: big.NewInt(1), + NotAfter: time.Now().AddDate(1, 0, 0), + IsCA: true, + BasicConstraintsValid: true, + } certTemplate := x509.Certificate{ - SerialNumber: big.NewInt(1), + SerialNumber: big.NewInt(2), NotAfter: NotAfterTime, } - csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, key) - certBytes, _ := x509.CreateCertificate(rand.Reader, &certTemplate, &certTemplate, &key.PublicKey, key) + csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, certKey) + caBytes, _ := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey) + caCertificate, _ := x509.ParseCertificate(caBytes) + certBytes, _ := x509.CreateCertificate(rand.Reader, &certTemplate, caCertificate, &certKey.PublicKey, caKey) var buff bytes.Buffer pem.Encode(&buff, &pem.Block{ //nolint:errcheck @@ -76,19 +85,25 @@ func generateCertPair(daysRemaining int) (string, string) { Bytes: certBytes, }) cert := buff.String() - return csr, cert + buff.Reset() + pem.Encode(&buff, &pem.Block{ //nolint:errcheck + Type: "CERTIFICATE", + Bytes: caBytes, + }) + ca := buff.String() + return csr, cert, ca } func initializeTestDB(t *testing.T, db *certdb.CertificateRequestsRepository) { for i, v := range []int{5, 10, 32} { - csr, cert := generateCertPair(v) + csr, cert, ca := generateCertPair(v) _, err := db.CreateCSR(csr) if err != nil { - t.Fatalf("couldn't create test csr:%s", err) + t.Fatalf("couldn't create test csr: %s", err) } - _, err = db.UpdateCSR(fmt.Sprint(i+1), cert) + _, err = db.UpdateCSR(fmt.Sprint(i+1), fmt.Sprintf("%s%s", cert, ca)) if err != nil { - t.Fatalf("couldn't create test cert:%s", err) + t.Fatalf("couldn't create test cert: %s", err) } } } diff --git a/ui/src/app/certificate_requests/components.tsx b/ui/src/app/certificate_requests/components.tsx index 529fc97..3e94b68 100644 --- a/ui/src/app/certificate_requests/components.tsx +++ b/ui/src/app/certificate_requests/components.tsx @@ -1,7 +1,7 @@ -import { Dispatch, SetStateAction, useState, ChangeEvent } from "react" +import { Dispatch, SetStateAction, useState, ChangeEvent, useEffect } from "react" import { useMutation, useQueryClient } from "react-query" import { ConfirmationModalData } from "./row" -import { extractCert, csrMatchesCertificate } from "../utils" +import { csrMatchesCertificate, splitBundle, validateBundle } from "../utils" import { postCertToID } from "../queries" import { useCookies } from "react-cookie" @@ -33,27 +33,43 @@ export function ConfirmationModal({ modalData, setModalData }: ConfirmationModal } function SubmitCertificate({ existingCSRText, existingCertText, certText, onClickFunc }: { existingCSRText: string, existingCertText: string, certText: string, onClickFunc: any }) { - let certIsValid = false - let certMatchesCSR = false - try { - extractCert(certText) - certIsValid = true - if (csrMatchesCertificate(existingCSRText, certText)) { - certMatchesCSR = true + const [validationErrorText, setValidationErrorText] = useState("") + useEffect(() => { + const validateCertificate = async () => { + try { + const certs = splitBundle(certText) + if (certs.length < 2) { + setValidationErrorText("bundle with 2 certificates required") + return + } + if (!csrMatchesCertificate(existingCSRText, certs[0])) { + setValidationErrorText("Certificate does not match request") + return + } + let validationMessage = await validateBundle(certText) + if (validationMessage != "") { + setValidationErrorText("Bundle validation failed: " + validationMessage) + return + } + } + catch { + setValidationErrorText("A certificate is invalid") + return + } + setValidationErrorText("") } - } - catch { } + validateCertificate() + }, [existingCSRText, existingCertText, certText]) - const validationComponent = certText == "" ? - <> : - !certIsValid ? -
Invalid Certificate
: - existingCertText == certText ? -
Certificate is identical to the one uploaded
: - !certMatchesCSR ? -
Certificate does not match the request
: -
Valid Certificate
- const buttonComponent = certIsValid && certMatchesCSR && existingCertText != certText ? : + const validationComponent = certText != "" && validationErrorText == "" ? ( +
Valid Certificate
+ ) : ( +
{validationErrorText}
) + const buttonComponent = validationErrorText == "" ? ( + + ) : ( + + ) return ( <> {validationComponent} @@ -70,11 +86,17 @@ interface SubmitCertificateModalProps { } export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCertificateModalProps) { const [cookies, setCookie, removeCookie] = useCookies(['user_token']); + const [errorText, setErrorText] = useState("") const queryClient = useQueryClient() const mutation = useMutation(postCertToID, { onSuccess: () => { queryClient.invalidateQueries('csrs') + setErrorText("") + setFormOpen(false) }, + onError: (e: Error) => { + setErrorText(e.message) + } }) const [certificatePEMString, setCertificatePEMString] = useState("") const handleTextChange = (event: ChangeEvent) => { @@ -94,10 +116,6 @@ export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCer reader.readAsText(file); } }; - const handleSubmit = () => { - mutation.mutate({ id: id, authToken: cookies.user_token, cert: certificatePEMString }) - setFormOpen(false) - } return (
- + {errorText != "" && +
+
+
Error
+

{errorText.split("error: ")}

+
+
+ } + mutation.mutate({ id: id, authToken: cookies.user_token, cert: certificatePEMString })} />
diff --git a/ui/src/app/certificate_requests/row.tsx b/ui/src/app/certificate_requests/row.tsx index e5d8d2a..780159e 100644 --- a/ui/src/app/certificate_requests/row.tsx +++ b/ui/src/app/certificate_requests/row.tsx @@ -1,6 +1,6 @@ import { useState, Dispatch, SetStateAction, useEffect, useRef } from "react" import { UseMutationResult, useMutation, useQueryClient } from "react-query" -import { extractCSR, extractCert } from "../utils" +import { extractCSR, extractCert, splitBundle } from "../utils" import { RequiredCSRParams, deleteCSR, rejectCSR, revokeCertificate } from "../queries" import { ConfirmationModal, SubmitCertificateModal, SuccessNotification } from "./components" import "./../globals.scss" @@ -31,7 +31,9 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio const [confirmationModalData, setConfirmationModalData] = useState(null) const csrObj = extractCSR(csr) - const certObj = extractCert(certificate) + const certs = splitBundle(certificate) + const clientCertificate = certs.at(0) + const certObj = clientCertificate ? extractCert(clientCertificate) : null const queryClient = useQueryClient() const deleteMutation = useMutation(deleteCSR, { @@ -61,7 +63,7 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio const blob = new Blob([csr], { type: 'text/plain' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); - link.download = "csr-" + id.toString() + ".pem"; // TODO: change this to .pem + link.download = "csr-" + (csrObj.commonName !== undefined ? csrObj.commonName : id.toString()) + ".pem"; document.body.appendChild(link); link.click(); document.body.removeChild(link); @@ -86,15 +88,6 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio }) } - - const toggleActionMenu = () => { - if (ActionMenuExpanded == id) { - setActionMenuExpanded(0) - } else { - setActionMenuExpanded(id) - } - } - const getFieldDisplay = (label: string, field: string | undefined, compareField?: string | undefined) => { const isMismatched = compareField !== undefined && compareField !== field; return field ? ( diff --git a/ui/src/app/utils.ts b/ui/src/app/utils.ts index cfcfa77..9b128ee 100644 --- a/ui/src/app/utils.ts +++ b/ui/src/app/utils.ts @@ -1,4 +1,4 @@ -import { CertificationRequest, Certificate, Extensions } from "pkijs"; +import { CertificationRequest, Certificate, Extensions, CertificateChainValidationEngine } from "pkijs"; import { fromBER } from "asn1js"; import * as pvutils from "pvutils"; @@ -275,4 +275,25 @@ export const passwordIsValid = (pw: string) => { return true } return false +} + +export const splitBundle = (bundle: string): string[] => { + const pemPattern = /-----BEGIN CERTIFICATE-----(?:.|\n)*?-----END CERTIFICATE-----/g; + const pemMatches = bundle.match(pemPattern); + return pemMatches ? pemMatches.map((e) => e.toString()) : []; +} + +export const validateBundle = async (bundle: string) => { + const bundleList = splitBundle(bundle) + const extractedCerts = bundleList.map((cert) => loadCertificate(cert)) + const rootCa = extractedCerts.at(-1) + if (rootCa == undefined) { + return "less than 2 certificates found." + } + const chainEngine = new CertificateChainValidationEngine({ + certs: extractedCerts, + trustedCerts: [rootCa] + }) + const result = await chainEngine.verify() + return result.resultMessage } \ No newline at end of file