From 57a3be286d7f8ccfe853cfe292b59c789558e355 Mon Sep 17 00:00:00 2001 From: Harry Harpham Date: Sun, 3 May 2015 23:57:27 -0400 Subject: [PATCH] Expand and re-factor helpers/testsuite, add bundler and scan tests - Add TestServer to helpers/testsuite for tests requiring a configurable, local TLS server - Remove CreateSelfSignedCert from helpers/testsuite due to overlap with initca.New - Complete re-do of SignCertificate in helpers/testsuite using internal packages (rather than parsing CLI) - Re-factor helpers/testsuite into logical distinct files for maintainability - Add tests to helpers/testsuite for TestServer - Add tests to bundler to test BundleFromRemote against local testsuite.TestServer (TLS) - Add tests to scan to test against local testsuite.TestServer (both TLS and TCP) --- api/bundle/bundle.go | 2 +- bundler/bundle_from_remote_test.go | 52 +- bundler/bundler.go | 8 +- bundler/bundler_test.go | 2 +- bundler/testdata/cert/cert_chain.crt | 70 +++ bundler/testdata/cert/decrypted.ssl.key | 27 + cli/bundle/bundle.go | 2 +- helpers/testsuite/certificates.go | 130 +++++ helpers/testsuite/certificates_test.go | 196 ++++++++ helpers/testsuite/internal_helpers.go | 234 +++++++++ helpers/testsuite/servers.go | 230 +++++++++ helpers/testsuite/servers_test.go | 181 +++++++ .../testsuite/testdata/cert/cert_chain.crt | 70 +++ .../testsuite/testdata/cert/decrypted.ssl.key | 27 + .../testdata/{initCA => }/cfssl_output.pem | 2 +- helpers/testsuite/testdata/initCA/ca_csr.json | 23 - helpers/testsuite/testing_helpers.go | 361 -------------- helpers/testsuite/testing_helpers_test.go | 465 ------------------ scan/connectivity_test.go | 57 +++ scan/scan_common_test.go | 63 +++ scan/testdata/cert/cert_chain.crt | 70 +++ scan/testdata/cert/decrypted.ssl.key | 27 + 22 files changed, 1438 insertions(+), 861 deletions(-) create mode 100644 bundler/testdata/cert/cert_chain.crt create mode 100644 bundler/testdata/cert/decrypted.ssl.key create mode 100644 helpers/testsuite/certificates.go create mode 100644 helpers/testsuite/certificates_test.go create mode 100644 helpers/testsuite/internal_helpers.go create mode 100644 helpers/testsuite/servers.go create mode 100644 helpers/testsuite/servers_test.go create mode 100644 helpers/testsuite/testdata/cert/cert_chain.crt create mode 100644 helpers/testsuite/testdata/cert/decrypted.ssl.key rename helpers/testsuite/testdata/{initCA => }/cfssl_output.pem (99%) delete mode 100644 helpers/testsuite/testdata/initCA/ca_csr.json delete mode 100644 helpers/testsuite/testing_helpers.go delete mode 100644 helpers/testsuite/testing_helpers_test.go create mode 100644 scan/connectivity_test.go create mode 100644 scan/testdata/cert/cert_chain.crt create mode 100644 scan/testdata/cert/decrypted.ssl.key diff --git a/api/bundle/bundle.go b/api/bundle/bundle.go index c7d45b4dd..1c9e03496 100644 --- a/api/bundle/bundle.go +++ b/api/bundle/bundle.go @@ -52,7 +52,7 @@ func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error { var result *bundler.Bundle switch matched[0] { case "domain": - bundle, err := h.bundler.BundleFromRemote(blob["domain"], blob["ip"], bf) + bundle, err := h.bundler.BundleFromRemote(blob["domain"], blob["ip"], "443", bf) if err != nil { log.Warningf("couldn't bundle from remote: %v", err) return err diff --git a/bundler/bundle_from_remote_test.go b/bundler/bundle_from_remote_test.go index 74ed9afaa..83387adb4 100644 --- a/bundler/bundle_from_remote_test.go +++ b/bundler/bundle_from_remote_test.go @@ -3,8 +3,12 @@ package bundler // This test file contains tests on checking the correctness of BundleFromRemote import ( "flag" + "io/ioutil" + "net" + "net/http" "testing" + "github.com/cloudflare/cfssl/helpers/testsuite" "github.com/cloudflare/cfssl/ubiquity" ) @@ -39,6 +43,14 @@ const ( InvalidIP = "300.300.300.300" ) +var ( + certFile = "testdata/cert/cert_chain.crt" + keyFile = "testdata/cert/decrypted.ssl.key" + + addr = "127.0.0.1" + port = "7050" +) + func getBundleHostnameChecker(hostname string) func(*testing.T, *Bundle) { return func(t *testing.T, bundle *Bundle) { if bundle == nil { @@ -100,7 +112,7 @@ func TestBundleFromRemote(t *testing.T) { for _, bf := range []BundleFlavor{Ubiquitous, Optimal} { for _, test := range remoteTests { b := test.bundlerConstructor(t) - bundle, err := b.BundleFromRemote(test.hostname, test.ip, bf) + bundle, err := b.BundleFromRemote(test.hostname, test.ip, "443", bf) if test.errorCallback != nil { test.errorCallback(t, err) } else { @@ -113,6 +125,38 @@ func TestBundleFromRemote(t *testing.T) { } } } + + // Test bundler against a local test server. + cert, err := ioutil.ReadFile(certFile) + if err != nil { + t.Fatal(err.Error()) + } + key, err := ioutil.ReadFile(keyFile) + if err != nil { + t.Fatal(err.Error()) + } + + tlsServer := testsuite.NewTestServer( + http.Server{Addr: net.JoinHostPort(addr, port)}) + err = tlsServer.UseDefaultTLSConfig(cert, key) + if err != nil { + t.Fatal(err.Error()) + } + err = tlsServer.Start() + if err != nil { + t.Fatal(err.Error()) + } + + defer tlsServer.Kill() + + newBundler, err := NewBundler(certFile, certFile) + if err != nil { + t.Fatal(err.Error()) + } + _, err = newBundler.BundleFromRemote("harryharpham.me", addr, port, Optimal) + if err != nil { + t.Fatal(err.Error()) + } } var remoteSNITests = []remoteTest{ @@ -152,7 +196,7 @@ func TestBundleFromRemoteSNI(t *testing.T) { for _, bf := range []BundleFlavor{Ubiquitous, Optimal} { for _, test := range remoteSNITests { b := test.bundlerConstructor(t) - bundle, err := b.BundleFromRemote(test.hostname, test.ip, bf) + bundle, err := b.BundleFromRemote(test.hostname, test.ip, "443", bf) if test.errorCallback != nil { test.errorCallback(t, err) } else { @@ -172,7 +216,7 @@ func TestBundleFromRemoteFlavor(t *testing.T) { ubiquity.Platforms = nil ubiquity.LoadPlatforms(testMetadata) - bundle, err := b.BundleFromRemote(ECCCertSite, "", Ubiquitous) + bundle, err := b.BundleFromRemote(ECCCertSite, "", "443", Ubiquitous) if err != nil { t.Errorf("expected no error. but an error occurred: %s", err.Error()) } @@ -183,7 +227,7 @@ func TestBundleFromRemoteFlavor(t *testing.T) { t.Error("expected no untrusted platforms. Got ", bundle.Status.Untrusted) } - bundle, err = b.BundleFromRemote(ECCCertSite, "", Optimal) + bundle, err = b.BundleFromRemote(ECCCertSite, "", "443", Optimal) if err != nil { t.Errorf("expected no error. but an error occurred: %s", err.Error()) } diff --git a/bundler/bundler.go b/bundler/bundler.go index 98a4dd13d..2ecdd4d26 100644 --- a/bundler/bundler.go +++ b/bundler/bundler.go @@ -218,9 +218,9 @@ func (b *Bundler) BundleFromPEMorDER(certsRaw, keyPEM []byte, flavor BundleFlavo // BundleFromRemote fetches the certificate served by the server at // serverName (or ip, if the ip argument is not the empty string). It // is expected that the method will be able to make a connection at -// port 443. The certificate used by the server in this connection is +// the input port. The certificate used by the server in this connection is // used to build the bundle, which will necessarily be keyless. -func (b *Bundler) BundleFromRemote(serverName, ip string, flavor BundleFlavor) (*Bundle, error) { +func (b *Bundler) BundleFromRemote(serverName, ip, port string, flavor BundleFlavor) (*Bundle, error) { config := &tls.Config{ RootCAs: b.RootPool, ServerName: serverName, @@ -229,9 +229,9 @@ func (b *Bundler) BundleFromRemote(serverName, ip string, flavor BundleFlavor) ( // Dial by IP if present var dialName string if ip != "" { - dialName = ip + ":443" + dialName = net.JoinHostPort(ip, port) } else { - dialName = serverName + ":443" + dialName = net.JoinHostPort(serverName, port) } log.Debugf("bundling from remote %s", dialName) diff --git a/bundler/bundler_test.go b/bundler/bundler_test.go index 4db9ae426..f5db6d1b0 100644 --- a/bundler/bundler_test.go +++ b/bundler/bundler_test.go @@ -239,7 +239,7 @@ func TestBundleWithRSAKeyMarshalJSON(t *testing.T) { // Test marshal to JSON on hostnames func TestBundleHostnamesMarshalJSON(t *testing.T) { b := newBundler(t) - bundle, _ := b.BundleFromRemote("www.cloudflare.com", "", Ubiquitous) + bundle, _ := b.BundleFromRemote("www.cloudflare.com", "", "443", Ubiquitous) hostnames, _ := json.Marshal(bundle.Hostnames) expectedOne := []byte(`["www.cloudflare.com","cloudflare.com"]`) expectedTheOther := []byte(`["cloudflare.com","www.cloudflare.com"]`) diff --git a/bundler/testdata/cert/cert_chain.crt b/bundler/testdata/cert/cert_chain.crt new file mode 100644 index 000000000..136699c33 --- /dev/null +++ b/bundler/testdata/cert/cert_chain.crt @@ -0,0 +1,70 @@ +-----BEGIN CERTIFICATE----- +MIIGTDCCBTSgAwIBAgIHBTzQSxghazANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE +BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE +aWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0YXJ0Q29tIENs +YXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMB4XDTE1MDIxODAy +NTcyM1oXDTE2MDIxOTE5NDAzMVowUDELMAkGA1UEBhMCVVMxHzAdBgNVBAMTFnNl +Y3VyZS5oYXJyeWhhcnBoYW0ubWUxIDAeBgkqhkiG9w0BCQEWEWh3aDMzQGNvcm5l +bGwuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IWSkznetjqg +0XafBKw56jhIh10JopL2mTpSjhGt3BPNF8nCkUnspzN2CRzwn8owd05NQwX8uPBu +0EWsqTTPKFXR37mS3GpQ/aQhV14vQZbgSG0xex3UYFGVHe3/EE7tTEJzNnJBSYuW +9t7CyJLugRT5Pni++xGUbOnAH9n57ZbtmZ1YEhbw69McumE3rYpwMHFXeMoGGjCj +QzTYF5PRc8IWFaAbQAIvnvQRnmgrj9J763E08T8gaxLocW7S0V8x7WRV93W4AkG4 +ieBQM6pcU0ME6lyRDEiL1gEM/GQ6Nb/DljkoquEMmA7PgB1I/EcLDj+BV5B6U6jP +82LCaj0GaQIDAQABo4IC7DCCAugwCQYDVR0TBAIwADALBgNVHQ8EBAMCA6gwEwYD +VR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFLK8DJjvvpQCcKTg/2Rv7G3aCgnw +MB8GA1UdIwQYMBaAFOtCNNCYsKuf9BtrCPfMZC7vDixFMDIGA1UdEQQrMCmCFnNl +Y3VyZS5oYXJyeWhhcnBoYW0ubWWCD2hhcnJ5aGFycGhhbS5tZTCCAVYGA1UdIASC +AU0wggFJMAgGBmeBDAECATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcC +ARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjCB9wYIKwYBBQUH +AgIwgeowJxYgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBARqB +vlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBhY2NvcmRpbmcgdG8gdGhlIENs +YXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0YXJ0Q29tIENB +IHBvbGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2Ug +aW4gY29tcGxpYW5jZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4w +NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1j +cmwuY3JsMIGOBggrBgEFBQcBAQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2Nz +cC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9zZXJ2ZXIvY2EwQgYIKwYBBQUHMAKG +Nmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5jbGFzczEuc2VydmVy +LmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8wDQYJ +KoZIhvcNAQELBQADggEBADVeNvC2WIv3VljSQNS1Kd5M/CyWI1vnAlIDg381EQQF +a1rEgqCF9P7ZBEf+G1+pzt3IhJq2jcxq93RQyWW5cyc+IYQcRaPKK4zC8YWPM12r +p4r9hNTXLOZ4zFclcciPURkZZ8RTw4strq9cnYkt1T4sNR4gBdG0pMHOJt4jJhAj +dCvSmLHXVsUYNld2Tg30Nvq1vi4HaR/Okq5A2bUkD1Gph/2u6op2kapGBXGRRkOn +uYqcKKpIPQT2AtBcQiYoYPjCV/Txlgpdj2JKhVjUwmbIRhob0sBwGMQv45aRkGEi +zOOQAxdW4vOa0VX79s7ePmrnYz0pTfnWtBXC+vajIM0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIHFxU9nqs/vzANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQG +EwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERp +Z2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDcxMDE0MjA1NDE3WhcNMjIxMDE0MjA1 +NDE3WjCBjDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzAp +BgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNV +BAMTL1N0YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVy +IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj +0PREGBiEgFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo +/OenJOJApgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn6 +6+6CPAVvkvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+v +WjhwRRI/ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxD +KslIDlc5xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21T +Lwb0pwIDAQABo4IBTDCCAUgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E +BAMCAQYwHQYDVR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaA +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGkGCCsGAQUFBwEBBF0wWzAnBggrBgEFBQcw +AYYbaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL2NhMDAGCCsGAQUFBzAChiRodHRw +Oi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9jYS5jcnQwMgYDVR0fBCswKTAnoCWg +I4YhaHR0cDovL2NybC5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMEMGA1UdIAQ8MDow +OAYEVR0gADAwMC4GCCsGAQUFBwIBFiJodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9w +b2xpY3kucGRmMA0GCSqGSIb3DQEBCwUAA4ICAQCBnsOw7dxamNbdJb/ydkh4Qb6E +qgEU+G9hCCIGXwhWRZMYczNJMrpVvyLq5mNOmrFPC7bJrqYV+vEOYHNXrzthLyOG +FFOVQe2cxbmQecFOvbkWVlYAIaTG42sHKVi+RFsG2jRNZcFhHnsFnLPMyE6148lZ +wVdZGsxZvpeHReNUpW0jh7uq90sShFzHs4f7wJ5XmiHOL7fZbnFV6uE/OoFnBWif +CRnd9+RE3uCospESPCRPdbG+Q4GQ+MBS1moXDTRB6DcNoHvqC6eU3r8/Fn/DeA9w +9JHPXUfrAhZYKyOQUIqcfE5bvssaY+oODVxji6BMk8VSVHsJ4FSC1/7Pkt/UPoQp +FVh38wIJnvEUeNVmVl3HHFYTd50irdKYPBC63qi2V/YYI6bJKmbrjfP9Vhyt9uNr +y3Kh4W22ktDuCCvWC7n/gqerdq+VlTRfNt7D/mB0irnaKjEVNCXBXm9V/978J+Ez +8aplGZccQ9jnc9kiPtUp5dj45E3V8vKqzp9srSSI5Xapdg+ZcPY+6HNuVB+MadRp +ZW2One/Qnzg9B4GnVX7MOETImdoP4kXpostFuxoY/5LxCU1LJAIENV4txvT50lX2 +GBXCkxllRLWOgdyll11ift/4IO1aCOGDijGIfh498YisM1LGxytmGcxvbJERVri+ +gGpWAZ5J6dvtf0s+bA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/bundler/testdata/cert/decrypted.ssl.key b/bundler/testdata/cert/decrypted.ssl.key new file mode 100644 index 000000000..fe973abf7 --- /dev/null +++ b/bundler/testdata/cert/decrypted.ssl.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA6IWSkznetjqg0XafBKw56jhIh10JopL2mTpSjhGt3BPNF8nC +kUnspzN2CRzwn8owd05NQwX8uPBu0EWsqTTPKFXR37mS3GpQ/aQhV14vQZbgSG0x +ex3UYFGVHe3/EE7tTEJzNnJBSYuW9t7CyJLugRT5Pni++xGUbOnAH9n57ZbtmZ1Y +Ehbw69McumE3rYpwMHFXeMoGGjCjQzTYF5PRc8IWFaAbQAIvnvQRnmgrj9J763E0 +8T8gaxLocW7S0V8x7WRV93W4AkG4ieBQM6pcU0ME6lyRDEiL1gEM/GQ6Nb/Dljko +quEMmA7PgB1I/EcLDj+BV5B6U6jP82LCaj0GaQIDAQABAoIBAQDOIryS8NbUTp2L +lNHc3qJpVoRWHNER45/ir6XIdTvgw10rHZ70FslKYYDfC1z3C9RzsGMmbARkICLd +WSd8ymhspchtwqLD7Szn773vKnxSdWNYUhVvxD4nNaww4HEjEBWnabGiLyBaBvpK +2altD3WJ0gI0s+77vdYLyB6fHitHyLk7yd12cpv8Q85VE+aZFNbVsaDLli/h+afT +0b9pRcF4J5k+Rk4UO9ZxQ99Zxi2cwA+AU1hoZYH2E0ti7hJD/577qNZ07aIiGO65 +2i7y28QKpQT0Pcc1dhVVMVYX7i0uUNCu+lsK0GO5A7amAYbm+cDF+NMUlWXx2hFz +R32lKOXRAoGBAPRdHEB9OdD+5UNGIAT66IWUC2NZIT2bhAyJrdlrmHIP/RYFzUom +k9hjoVpDtWey5NzFDmVIFlp2LMHM/sxOYNra9ddR6YMiJtmq3OMwXJjcFjKOzq/s +xA2Tag9b9Ds4vzcAgVLcaO1fGzNOupP1ETUI+aRdpiIkL9we11xnMfmrAoGBAPOY +Gq/FJ8XHmckPJim9dOKvrX/P9uqLkZjnWEZfrdjDYRjH+CB84RV8hk0fWVJvQmnV +USpgblPxmQA143Zm/z5UePRa1fhscAepBlARg9dacSee6/aBXVqXOtn72yZcIca6 +25ZDlqMMt0OsdWuwcMrCc0cSPEngnn8PY7iUxXQ7AoGBAL9xcwDTGsix6cua1NsR +97kvEvzyhyhM/lcURi9gLD6waAco5HyDXLDa3T5qbWSgssyDVZPjth+JFed/Qt+t +SBQuFwxqEBe8WdOOGR+kc+cOBhX169fIFpDMPPtrfxwmiSf+LVNNeqb9K8I2m9Xu +2VxDe9FZzR5SW2yIfRDXiD+hAoGBALCoFnJyYK2mW3jca6jJpQLgtMqEX2UpYqWo +4PPoX4Nb7gayvteQuQuEC0HTAkwuM9Pcx8gbtudZxsPIUPWFLIZfbHiJh7NpsLVd +dx6KFTsQdA9vyWAz4SfZlOyRIhVnq3cb/DO1QIFur1l8sIswohvpeQCJ0P8izs8G +siMigTARAoGBANSp96QqJ7gNYzHN18GhBwwkCOBE4HC1GkTGlROmZG3M0N9+j7WI +MTBjf0VS9V+aRqqvIFGTv3kpk6Wp0fxyXGL8vFK3DoyrGeWr3URXlHYiAyjfa3dr +URXCnqHcaMDfgzrBZf4Hi2sTsZYximWv/v+YUGStwbWcgwJ9Eep2gixp +-----END RSA PRIVATE KEY----- diff --git a/cli/bundle/bundle.go b/cli/bundle/bundle.go index 8dbda4935..0f6cb3bf1 100644 --- a/cli/bundle/bundle.go +++ b/cli/bundle/bundle.go @@ -60,7 +60,7 @@ func bundlerMain(args []string, c cli.Config) (err error) { } } } else if c.Domain != "" { - bundle, err = b.BundleFromRemote(c.Domain, c.IP, flavor) + bundle, err = b.BundleFromRemote(c.Domain, c.IP, "443", flavor) if err != nil { return } diff --git a/helpers/testsuite/certificates.go b/helpers/testsuite/certificates.go new file mode 100644 index 000000000..52b8ce6a4 --- /dev/null +++ b/helpers/testsuite/certificates.go @@ -0,0 +1,130 @@ +// Functions which allow for the creation of dummy certificates, chains, and +// keys from certificate requests. + +package testsuite + +import ( + "reflect" + "time" + + "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/csr" + "github.com/cloudflare/cfssl/helpers" + "github.com/cloudflare/cfssl/initca" + "github.com/cloudflare/cfssl/signer" + "github.com/cloudflare/cfssl/signer/local" +) + +// CreateCertificateChain creates a chain of certificates from a slice of +// requests. The first request is the root certificate and the last is the +// leaf. The chain is returned as a slice of PEM-encoded bytes. +func CreateCertificateChain(requests []csr.CertificateRequest, + options SignOptions) (certChain []byte, key []byte, err error) { + + // Create the root certificate using the first request. This will be + // self-signed. + certChain = make([]byte, 0) + rootCert, _, prevKey, err := initca.New(&requests[0]) + if err != nil { + return nil, nil, err + } + certChain = append(certChain, rootCert...) + + // For each of the next requests, create a certificate signed by the + // previous certificate. + prevCert := rootCert + for _, request := range requests[1:] { + cert, key, err := SignCertificate(request, prevCert, prevKey, options) + if err != nil { + return nil, nil, err + } + certChain = append(certChain, byte('\n')) + certChain = append(certChain, cert...) + prevCert = cert + prevKey = key + } + + return certChain, key, nil +} + +// SignOptions allows callers to configure the signer according to their needs +// when calling SignCertificate. Any of these can be left nil, in which case +// standard defaults will be used. +type SignOptions struct { + Hosts []string + Profile config.SigningProfile + Label string + SerialSeq string + WhiteList signer.Whitelist +} + +// SignCertificate takes a request and a certificate / key combination. A new +// certificate and key are generated from the request and signed by the input +// certificate. The 'options' argument allows the internal signer to be +// configured as needed. The returned certificate and key are PEM-encoded bytes. +func SignCertificate(request csr.CertificateRequest, signerCert, signerKey []byte, + options SignOptions) (encodedCert, encodedKey []byte, err error) { + + // The default profile (used when options.Profile is not set). + // Allows the signed certificate can be used as intermediate CA and server + // auth certificate. + defaultProfile := config.SigningProfile{ + Usage: []string{"cert sign", "server auth"}, + CA: true, + Expiry: time.Hour, + ExpiryString: "1h", + } + + // If options.Profile is the zero value, replace it with the default profile. + emptyProfile := config.SigningProfile{} + if reflect.DeepEqual(options.Profile, emptyProfile) { + options.Profile = defaultProfile + } + + // Generate the signer using the certificate, key, and profile. + policy := &config.Signing{ + Profiles: map[string]*config.SigningProfile{}, + Default: &options.Profile, + } + priv, err := helpers.ParsePrivateKeyPEM(signerKey) + if err != nil { + return nil, nil, err + } + parsedSignerCert, err := helpers.ParseCertificatePEM(signerCert) + if err != nil { + return nil, nil, err + } + sigAlgo := signer.DefaultSigAlgo(priv) + localSigner, err := local.NewSigner(priv, parsedSignerCert, sigAlgo, policy) + if err != nil { + return nil, nil, err + } + + // Get the CSR bytes and key bytes for the request. Store the CSR bytes in + // a temporary file. + csrBytes, encodedKey, err := csr.ParseRequest(&request) + if err != nil { + return nil, nil, err + } + + // Construct the subject from the request and options.WhiteList. + subject := &signer.Subject{ + CN: request.CN, + Names: request.Names, + Whitelist: &options.WhiteList, + } + + // Create a sign request. + // Note: we do not need to set a profile as the signer will use the default + // we already set. + signReq := signer.SignRequest{ + Hosts: options.Hosts, + Request: string(csrBytes), + Subject: subject, + Label: options.Label, + SerialSeq: options.SerialSeq, + } + + encodedCert, err = localSigner.Sign(signReq) + return +} diff --git a/helpers/testsuite/certificates_test.go b/helpers/testsuite/certificates_test.go new file mode 100644 index 000000000..e538b8634 --- /dev/null +++ b/helpers/testsuite/certificates_test.go @@ -0,0 +1,196 @@ +package testsuite + +import ( + "io/ioutil" + "os" + "strconv" + "testing" + + "github.com/cloudflare/cfssl/csr" + "github.com/cloudflare/cfssl/helpers" +) + +const ( + testDataDirectory = "testdata" + initCADirectory = testDataDirectory + string(os.PathSeparator) + "initCA" + preMadeOutput = testDataDirectory + string(os.PathSeparator) + "cfssl_output.pem" + csrFile = testDataDirectory + string(os.PathSeparator) + "cert_csr.json" +) + +var ( + keyRequest = csr.KeyRequest{ + Algo: "rsa", + Size: 2048, + } + CAConfig = csr.CAConfig{ + PathLength: 1, + Expiry: "1/1/2016", + } + baseRequest = csr.CertificateRequest{ + CN: "example.com", + Names: []csr.Name{ + { + C: "US", + ST: "California", + L: "San Francisco", + O: "Internet Widgets, LLC", + OU: "Certificate Authority", + }, + }, + Hosts: []string{"ca.example.com"}, + KeyRequest: &keyRequest, + } + baseRequest2 = csr.CertificateRequest{ + CN: "test.com", + Names: []csr.Name{ + { + C: "US", + ST: "Texas", + L: "Austin", + O: "Internet Widgets, LLC", + OU: "Certificate Authority", + }, + }, + Hosts: []string{"ca.test.com"}, + KeyRequest: &keyRequest, + } + CARequest = csr.CertificateRequest{ + CN: "example.com", + Names: []csr.Name{ + { + C: "US", + ST: "California", + L: "San Francisco", + O: "Internet Widgets, LLC", + OU: "Certificate Authority", + }, + }, + Hosts: []string{"ca.example.com"}, + KeyRequest: &keyRequest, + CA: &CAConfig, + } +) + +func TestCreateCertificateChain(t *testing.T) { + + // N is the number of certificates that will be chained together. + N := 10 + + // We'll use the default signing options. + opts := SignOptions{} + + // --- TEST: Create a chain of one certificate. --- // + + encodedChainFromCode, _, err := CreateCertificateChain( + []csr.CertificateRequest{CARequest}, opts) + checkError(err, t) + + // Now compare to a pre-made certificate chain using a JSON file containing + // the same request data. + + CLIOutputFile := preMadeOutput + CLIOutput, err := ioutil.ReadFile(CLIOutputFile) + checkError(err, t) + encodedChainFromCLI, err := cleanCLIOutput(CLIOutput, "cert") + checkError(err, t) + + chainFromCode, err := helpers.ParseCertificatesPEM(encodedChainFromCode) + checkError(err, t) + chainFromCLI, err := helpers.ParseCertificatesPEM(encodedChainFromCLI) + checkError(err, t) + + if !chainsEqual(chainFromCode, chainFromCLI) { + unequalFieldSlices := checkFieldsOfChains(chainFromCode, chainFromCLI) + for i, unequalFields := range unequalFieldSlices { + if len(unequalFields) > 0 { + t.Log("The certificate chains held unequal fields for chain " + strconv.Itoa(i)) + t.Log("The following fields were unequal:") + for _, field := range unequalFields { + t.Log("\t" + field) + } + } + } + t.Fatal("Certificate chains unequal.") + } + + // --- TEST: Create a chain of N certificates. --- // + + // First we make a slice of N requests. We make each slightly different. + + cnGrabBag := []string{"example", "invalid", "test"} + topLevelDomains := []string{".com", ".net", ".org"} + subDomains := []string{"www.", "secure.", "ca.", ""} + countryGrabBag := []string{"USA", "China", "England", "Vanuatu"} + stateGrabBag := []string{"California", "Texas", "Alaska", "London"} + localityGrabBag := []string{"San Francisco", "Houston", "London", "Oslo"} + orgGrabBag := []string{"Internet Widgets, LLC", "CloudFlare, Inc."} + orgUnitGrabBag := []string{"Certificate Authority", "Systems Engineering"} + + requests := make([]csr.CertificateRequest, N) + requests[0] = CARequest + for i := 1; i < N; i++ { + requests[i] = baseRequest + + cn := randomElement(cnGrabBag) + tld := randomElement(topLevelDomains) + subDomain1 := randomElement(subDomains) + subDomain2 := randomElement(subDomains) + country := randomElement(countryGrabBag) + state := randomElement(stateGrabBag) + locality := randomElement(localityGrabBag) + org := randomElement(orgGrabBag) + orgUnit := randomElement(orgUnitGrabBag) + + requests[i].CN = cn + "." + tld + requests[i].Names = []csr.Name{ + {C: country, + ST: state, + L: locality, + O: org, + OU: orgUnit, + }, + } + hosts := []string{subDomain1 + requests[i].CN} + if subDomain2 != subDomain1 { + hosts = append(hosts, subDomain2+requests[i].CN) + } + requests[i].Hosts = hosts + } + + // Now we make a certificate chain out of these requests. + encodedCertChain, _, err := CreateCertificateChain(requests, opts) + checkError(err, t) + + // To test this chain, we compare the data encoded in each certificate to + // each request we used to generate the chain. + chain, err := helpers.ParseCertificatesPEM(encodedCertChain) + checkError(err, t) + + if len(chain) != len(requests) { + t.Log("Length of chain: " + strconv.Itoa(len(chain))) + t.Log("Length of requests: " + strconv.Itoa(len(requests))) + t.Fatal("Length of chain not equal to length of requests.") + } + + mismatchOccurred := false + for i := 0; i < len(chain); i++ { + certEqualsRequest, unequalFields := certEqualsRequest(chain[i], requests[i]) + if !certEqualsRequest { + mismatchOccurred = true + t.Log( + "Certificate " + strconv.Itoa(i) + " and request " + + strconv.Itoa(i) + " unequal.", + ) + t.Log("Unequal fields for index " + strconv.Itoa(i) + ":") + for _, field := range unequalFields { + t.Log("\t" + field) + } + } + } + + // TODO: check that each certificate is actually signed by the previous one + + if mismatchOccurred { + t.Fatal("Unequal certificate(s) and request(s) found.") + } +} diff --git a/helpers/testsuite/internal_helpers.go b/helpers/testsuite/internal_helpers.go new file mode 100644 index 000000000..c40f08a1c --- /dev/null +++ b/helpers/testsuite/internal_helpers.go @@ -0,0 +1,234 @@ +// Helper functions used internally in the testsuite package. + +package testsuite + +import ( + "crypto/x509" + "errors" + "io/ioutil" + "math" + "math/rand" + "os" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/cloudflare/cfssl/csr" +) + +// Creates a temporary file with the given data. Returns the file name. +func createTempFile(data []byte) (fileName string, err error) { + // Avoid overwriting a file in the currect directory by choosing an unused + // file name. + baseName := "temp" + tempFileName := baseName + tryIndex := 0 + for { + if _, err := os.Stat(tempFileName); err == nil { + tempFileName = baseName + strconv.Itoa(tryIndex) + tryIndex++ + } else { + break + } + } + + readWritePermissions := os.FileMode(0664) + err = ioutil.WriteFile(tempFileName, data, readWritePermissions) + if err != nil { + return "", err + } + + return tempFileName, nil +} + +// Checks the CLI Output for failure. +func checkCLIOutput(CLIOutput []byte) error { + outputString := string(CLIOutput) + // Proper output will contain the substring "---BEGIN" somewhere + failureOccurred := !strings.Contains(outputString, "---BEGIN") + if failureOccurred { + return errors.New("Failure occurred during CLI execution: " + outputString) + } + return nil +} + +// Returns the cleaned up PEM encoding for the item specified (for example, +// 'cert' or 'key'). +func cleanCLIOutput(CLIOutput []byte, item string) (cleanedOutput []byte, err error) { + outputString := string(CLIOutput) + // The keyword will be surrounded by quotes. + itemString := "\"" + item + "\"" + // We should only search for the keyword beyond this point. + eligibleSearchIndex := strings.Index(outputString, "{") + outputString = outputString[eligibleSearchIndex:] + // Make sure the item is present in the output. + if strings.Index(outputString, itemString) == -1 { + return nil, errors.New("Item " + item + " not found in CLI Output") + } + // We add 2 for the [:"] that follows the item + startIndex := strings.Index(outputString, itemString) + len(itemString) + 2 + outputString = outputString[startIndex:] + endIndex := strings.Index(outputString, "\\n\"") + outputString = outputString[:endIndex] + outputString = strings.Replace(outputString, "\\n", "\n", -1) + + return []byte(outputString), nil +} + +// ========== Below are helpers for the test files in this package ========= // + +// Compare two x509 certificate chains. We only compare relevant data to +// determine equality. +func chainsEqual(chain1, chain2 []*x509.Certificate) bool { + if len(chain1) != len(chain2) { + return false + } + + for i := 0; i < len(chain1); i++ { + cert1 := nullifyTimeDependency(chain1[i]) + cert2 := nullifyTimeDependency(chain2[i]) + if !reflect.DeepEqual(cert1, cert2) { + return false + } + } + return true +} + +// When comparing certificates created at different times for equality, we do +// not want to worry about fields which are dependent on the time of creation. +// Thus we nullify these fields before comparing the certificates. +func nullifyTimeDependency(cert *x509.Certificate) *x509.Certificate { + cert.Raw = nil + cert.RawTBSCertificate = nil + cert.RawSubjectPublicKeyInfo = nil + cert.Signature = nil + cert.PublicKey = nil + cert.SerialNumber = nil + cert.NotBefore = time.Time{} + cert.NotAfter = time.Time{} + cert.Extensions = nil + cert.SubjectKeyId = nil + cert.AuthorityKeyId = nil + + return cert +} + +// Compares two structs and returns a list containing the names of all fields +// for which the two structs hold different values. +func checkFields(struct1, struct2 interface{}, typeOfStructs reflect.Type) []string { + v1 := reflect.ValueOf(struct1) + v2 := reflect.ValueOf(struct2) + + var unequalFields []string + for i := 0; i < v1.NumField(); i++ { + if !reflect.DeepEqual(v1.Field(i).Interface(), v2.Field(i).Interface()) { + unequalFields = append(unequalFields, typeOfStructs.Field(i).Name) + } + } + + return unequalFields +} + +// Runs checkFields on the corresponding elements of chain1 and chain2. Element +// i of the returned slice contains a slice of the fields for which certificate +// i in chain1 had different values than certificate i of chain2. +func checkFieldsOfChains(chain1, chain2 []*x509.Certificate) [][]string { + minLen := math.Min(float64(len(chain1)), float64(len(chain2))) + typeOfCert := reflect.TypeOf(*chain1[0]) + + var unequalFields [][]string + for i := 0; i < int(minLen); i++ { + unequalFields = append(unequalFields, checkFields( + *chain1[i], *chain2[i], typeOfCert)) + } + + return unequalFields +} + +// Compares a certificate to a request. Returns (true, []) if both items +// contain matching data (for the things that can match). Otherwise, returns +// (false, unequalFields) where unequalFields contains the names of all fields +// which did not match. +func certEqualsRequest(cert *x509.Certificate, request csr.CertificateRequest) (bool, []string) { + equal := true + var unequalFields []string + + if cert.Subject.CommonName != request.CN { + equal = false + unequalFields = append(unequalFields, "Common Name") + } + + nameData := make(map[string]map[string]bool) + nameData["Country"] = make(map[string]bool) + nameData["Organization"] = make(map[string]bool) + nameData["OrganizationalUnit"] = make(map[string]bool) + nameData["Locality"] = make(map[string]bool) + nameData["Province"] = make(map[string]bool) + for _, name := range request.Names { + nameData["Country"][name.C] = true + nameData["Organization"][name.O] = true + nameData["OrganizationalUnit"][name.OU] = true + nameData["Locality"][name.L] = true + nameData["Province"][name.ST] = true + } + for _, country := range cert.Subject.Country { + if _, exists := nameData["Country"][country]; !exists { + equal = false + unequalFields = append(unequalFields, "Country") + } + } + for _, organization := range cert.Subject.Organization { + if _, exists := nameData["Organization"][organization]; !exists { + equal = false + unequalFields = append(unequalFields, "Organization") + } + } + for _, organizationalUnit := range cert.Subject.OrganizationalUnit { + if _, exists := nameData["OrganizationalUnit"][organizationalUnit]; !exists { + equal = false + unequalFields = append(unequalFields, "OrganizationalUnit") + } + } + for _, locality := range cert.Subject.Locality { + if _, exists := nameData["Locality"][locality]; !exists { + equal = false + unequalFields = append(unequalFields, "Locality") + } + } + for _, province := range cert.Subject.Province { + if _, exists := nameData["Province"][province]; !exists { + equal = false + unequalFields = append(unequalFields, "Province") + } + } + + // TODO: check hosts + + if cert.BasicConstraintsValid && request.CA != nil { + if cert.MaxPathLen != request.CA.PathLength { + equal = false + unequalFields = append(unequalFields, "Max Path Length") + } + // TODO: check expiry + } + + // TODO: check isCA + + return equal, unequalFields +} + +// Returns a random element of the input slice. +func randomElement(set []string) string { + return set[rand.Intn(len(set))] +} + +// Just to clean the code up a bit. +func checkError(err error, t *testing.T) { + if err != nil { + // t.Fatal is more clean, but a panic gives more information for debugging + panic(err) + // t.Fatal(err.Error()) + } +} diff --git a/helpers/testsuite/servers.go b/helpers/testsuite/servers.go new file mode 100644 index 000000000..dcdc65891 --- /dev/null +++ b/helpers/testsuite/servers.go @@ -0,0 +1,230 @@ +// Servers which can be configured then run locally to test various parts of +// CFSSL project. + +package testsuite + +import ( + "bufio" + "crypto/tls" + "errors" + "net" + "net/http" + "os" + "os/exec" + "strings" + "time" +) + +// TestServer wraps http.Server, so it is as flexible as the standard library's +// server. In addition, it has convenience functions for setting up the server's +// TLS config as well as a Stop() function which kills the server. +type TestServer struct { + *http.Server + innerListener *net.TCPListener + tempFiles []string +} + +// CFSSLServer represents a CFSSL API server. The Addr and Port fields must be +// initiated for the server to start successfully. If any of the other exported +// fields are left blank, the default values are used. These are as definied +// in the documentation for the 'cfssl serve' command. +type CFSSLServer struct { + Addr string + Port string + CA []byte + CABundle []byte + CAKey []byte + IntBundle []byte + + process *os.Process + tempFiles []string +} + +// NewTestServer initializes a TestServer from an http.Server object. +func NewTestServer(innerServer http.Server) TestServer { + return TestServer{&innerServer, nil, nil} +} + +// Start the test server. If TLSConfig is nil, the server will only use TCP. +func (ts *TestServer) Start() error { + tcpAddr, err := net.ResolveTCPAddr("tcp", ts.Addr) + if err != nil { + return err + } + ts.innerListener, err = net.ListenTCP("tcp", tcpAddr) + if err != nil { + return err + } + + if ts.TLSConfig == nil { + ts.tempFiles = make([]string, 0) + go ts.Serve(ts.innerListener) + } else { + tlsListener := tls.NewListener(ts.innerListener, ts.TLSConfig) + go ts.Serve(tlsListener) + } + + return nil +} + +// UseDefaultTLSConfig is a convenience function which allows the caller to ask +// that a server use the following configuration: +// - all cipher suites supported +// - minimum version set to TLSv1 +// - the input certificate pair loaded +func (ts *TestServer) UseDefaultTLSConfig(cert, key []byte) error { + ts.TLSConfig = &tls.Config{} + ts.TLSConfig.CipherSuites = []uint16{ + tls.TLS_RSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + } + ts.TLSConfig.MinVersion = tls.VersionTLS10 + return ts.LoadCertificatePair(cert, key) +} + +// LoadCertificatePair sets the certificate and key pair that the server will +// use for TLS authentication. Assumes that ts.TLSConfig has been initialized. +func (ts *TestServer) LoadCertificatePair(cert, key []byte) error { + if ts.TLSConfig == nil { + return errors.New("TLSConfig needs to be initialized") + } + + certFile, err := createTempFile(cert) + if err != nil { + os.Remove(certFile) + return err + } + keyFile, err := createTempFile(key) + if err != nil { + os.Remove(certFile) + os.Remove(keyFile) + return err + } + ts.tempFiles = []string{certFile, keyFile} + + ts.TLSConfig.Certificates = make([]tls.Certificate, 1) + ts.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + return err +} + +// Kill the test server. +func (ts *TestServer) Kill() error { + err := ts.innerListener.Close() + if err != nil { + return err + } + for _, file := range ts.tempFiles { + os.Remove(file) + } + return nil +} + +// Start the CFSSLServer. Both the address and port number are assumed to be +// valid. +func (server *CFSSLServer) Start() error { + // This value is explained below. + startupTime := time.Second + + args := []string{"serve", "-address", server.Addr, "-port", server.Port} + var tempCAFile, tempCABundleFile, tempCAKeyFile, tempIntBundleFile string + var err error + server.tempFiles = make([]string, 0) + if len(server.CA) > 0 { + tempCAFile, err = createTempFile(server.CA) + server.tempFiles = append(server.tempFiles, tempCAFile) + args = append(args, "-ca") + args = append(args, tempCAFile) + } + if len(server.CABundle) > 0 { + tempCABundleFile, err = createTempFile(server.CABundle) + server.tempFiles = append(server.tempFiles, tempCABundleFile) + args = append(args, "-ca-bundle") + args = append(args, tempCABundleFile) + } + if len(server.CAKey) > 0 { + tempCAKeyFile, err = createTempFile(server.CAKey) + server.tempFiles = append(server.tempFiles, tempCAKeyFile) + args = append(args, "-ca-key") + args = append(args, tempCAKeyFile) + } + if len(server.IntBundle) > 0 { + tempIntBundleFile, err = createTempFile(server.IntBundle) + server.tempFiles = append(server.tempFiles, tempIntBundleFile) + args = append(args, "-int-bundle") + args = append(args, tempIntBundleFile) + } + // If an error occurred in the creation of any file, return an error. + if err != nil { + for _, file := range server.tempFiles { + os.Remove(file) + } + return err + } + + command := exec.Command("cfssl", args...) + + stdErrPipe, err := command.StderrPipe() + if err != nil { + for _, file := range server.tempFiles { + os.Remove(file) + } + return err + } + + err = command.Start() + if err != nil { + for _, file := range server.tempFiles { + os.Remove(file) + } + return err + } + + // We check to see if the address given is already in use. There is no way + // to do this other than to just wait and see if an error message pops up. + // Therefore we wait for startupTime, and if we don't see an error message + // by then, we deem the server ready and return. + + errorOccurred := make(chan bool) + go func() { + scanner := bufio.NewScanner(stdErrPipe) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "address already in use") { + errorOccurred <- true + } + } + }() + + select { + case <-errorOccurred: + for _, file := range server.tempFiles { + os.Remove(file) + } + fullAddress := net.JoinHostPort(server.Addr, server.Port) + return errors.New( + "Error occurred on server: address " + fullAddress + + " already in use.") + case <-time.After(startupTime): + server.process = command.Process + return nil + } +} + +// Kill a running CFSSL server. +func (server *CFSSLServer) Kill() error { + for _, file := range server.tempFiles { + os.Remove(file) + } + return server.process.Kill() +} diff --git a/helpers/testsuite/servers_test.go b/helpers/testsuite/servers_test.go new file mode 100644 index 000000000..cdff6b68f --- /dev/null +++ b/helpers/testsuite/servers_test.go @@ -0,0 +1,181 @@ +package testsuite + +import ( + "encoding/json" + "io/ioutil" + "net" + "net/http" + "os" + "os/exec" + "strings" + "testing" + + "github.com/cloudflare/cfssl/bundler" + "github.com/cloudflare/cfssl/initca" + "github.com/cloudflare/cfssl/scan" +) + +var ( + address = "127.0.0.1" + portTLS = "7050" + portTCP = "8050" + portCFSSL = "9050" + certFile = "testdata/cert/cert_chain.crt" + keyFile = "testdata/cert/decrypted.ssl.key" +) + +func TestStartTestServer(t *testing.T) { + + cert, err := ioutil.ReadFile(certFile) + checkError(err, t) + key, err := ioutil.ReadFile(keyFile) + checkError(err, t) + + // Make and start a TLS server and a TCP server. + tlsServer := NewTestServer(http.Server{Addr: net.JoinHostPort(address, portTLS)}) + err = tlsServer.UseDefaultTLSConfig(cert, key) + checkError(err, t) + err = tlsServer.Start() + checkError(err, t) + tcpServer := NewTestServer(http.Server{Addr: net.JoinHostPort(address, portTCP)}) + err = tcpServer.Start() + checkError(err, t) + + // Kill the servers upon completion or failure of the tests. + defer func() { + err = tlsServer.Kill() + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + }() + defer func() { + err = tcpServer.Kill() + if err != nil { + t.Log(err.Error()) + t.FailNow() + } + }() + + // === TEST: bundle using the test server ============================== // + + newBundler, err := bundler.NewBundler(certFile, certFile) + checkError(err, t) + _, err = newBundler.BundleFromRemote("harryharpham.me", address, portTLS, bundler.Optimal) + checkError(err, t) + + // === TEST: perform all of the default scans on the test server ======= // + + results, err := scan.Default.RunScans(tlsServer.Addr, ".", ".") + checkError(err, t) + for family, familyResult := range results { + // We cannot test broad scans locally. + if family == "Broad" { + continue + } + for scanner, result := range familyResult { + if result.Error != "" { + t.Fatal("An error occurred during the following scan: " + + "[Family: " + family + ", Scanner: " + scanner + + ", Grade: " + result.Grade + "]") + } + if result.Grade != scan.Good.String() { + t.Fatal("The following scan failed: [Family: " + family + + ", Scanner: " + scanner + ", Grade: " + result.Grade + "]") + } + } + } + + // === TEST: test the connectivity scan's response to a TCP-only server // + + dnsLookup := scan.Connectivity.Scanners["DNSLookup"] + tcpDial := scan.Connectivity.Scanners["TCPDial"] + tlsDial := scan.Connectivity.Scanners["TLSDial"] + + grade, _, err := dnsLookup.Scan(tcpServer.Addr) + if grade != scan.Good { + t.Log("TCP server failed DNSLookup scan (grade = " + grade.String() + ")") + t.FailNow() + } + checkError(err, t) + + grade, _, err = tcpDial.Scan(tcpServer.Addr) + if grade != scan.Good { + t.Log("TCP server failed TCPDial scan (grade = " + grade.String() + ")") + t.FailNow() + } + checkError(err, t) + + grade, _, err = tlsDial.Scan(tcpServer.Addr) + if grade != scan.Bad { + t.Log("TCP server did not fail TLSDial scan (grade = " + grade.String() + + ", error = " + err.Error() + ")") + t.FailNow() + } +} + +func TestStartCFSSLServer(t *testing.T) { + // We will test on this address and port. Be sure that these are free or + // the test will fail. + + CACert, _, CAKey, err := initca.New(&CARequest) + checkError(err, t) + + // Set up a test server using our CA certificate and key. + server := CFSSLServer{ + Addr: address, + Port: portCFSSL, + CA: CACert, + CAKey: CAKey, + } + err = server.Start() + checkError(err, t) + + // Kill the server upon either completion or failure of the tests. + defer func() { + err = server.Kill() + checkError(err, t) + }() + + // Try to start up a second server at the same address and port number. We + // should get an 'address in use' error. + server2 := CFSSLServer{ + Addr: server.Addr, + Port: server.Port, + CA: CACert, + CAKey: CAKey, + } + err = server2.Start() + if err == nil || !strings.Contains(err.Error(), "Error occurred on server: address") { + t.Fatal("Two servers allowed on same address and port.") + } + + // Now make a request of our server and check that no error occurred. + + // First we need a request to send to our server. We marshall the request + // into JSON format and write it to a temporary file. + jsonBytes, err := json.Marshal(baseRequest) + checkError(err, t) + tempFile, err := createTempFile(jsonBytes) + if err != nil { + os.Remove(tempFile) + panic(err) + } + + // Now we make the request and check the output. + remoteServerString := "-remote=" + net.JoinHostPort(address, portCFSSL) + command := exec.Command( + "cfssl", "gencert", remoteServerString, "-hostname="+baseRequest.CN, tempFile) + CLIOutput, err := command.CombinedOutput() + os.Remove(tempFile) + checkError(err, t) + err = checkCLIOutput(CLIOutput) + checkError(err, t) + // The output should contain the certificate, request, and private key. + _, err = cleanCLIOutput(CLIOutput, "cert") + checkError(err, t) + _, err = cleanCLIOutput(CLIOutput, "csr") + checkError(err, t) + _, err = cleanCLIOutput(CLIOutput, "key") + checkError(err, t) +} diff --git a/helpers/testsuite/testdata/cert/cert_chain.crt b/helpers/testsuite/testdata/cert/cert_chain.crt new file mode 100644 index 000000000..136699c33 --- /dev/null +++ b/helpers/testsuite/testdata/cert/cert_chain.crt @@ -0,0 +1,70 @@ +-----BEGIN CERTIFICATE----- +MIIGTDCCBTSgAwIBAgIHBTzQSxghazANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE +BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE +aWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0YXJ0Q29tIENs +YXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMB4XDTE1MDIxODAy +NTcyM1oXDTE2MDIxOTE5NDAzMVowUDELMAkGA1UEBhMCVVMxHzAdBgNVBAMTFnNl +Y3VyZS5oYXJyeWhhcnBoYW0ubWUxIDAeBgkqhkiG9w0BCQEWEWh3aDMzQGNvcm5l +bGwuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IWSkznetjqg +0XafBKw56jhIh10JopL2mTpSjhGt3BPNF8nCkUnspzN2CRzwn8owd05NQwX8uPBu +0EWsqTTPKFXR37mS3GpQ/aQhV14vQZbgSG0xex3UYFGVHe3/EE7tTEJzNnJBSYuW +9t7CyJLugRT5Pni++xGUbOnAH9n57ZbtmZ1YEhbw69McumE3rYpwMHFXeMoGGjCj +QzTYF5PRc8IWFaAbQAIvnvQRnmgrj9J763E08T8gaxLocW7S0V8x7WRV93W4AkG4 +ieBQM6pcU0ME6lyRDEiL1gEM/GQ6Nb/DljkoquEMmA7PgB1I/EcLDj+BV5B6U6jP +82LCaj0GaQIDAQABo4IC7DCCAugwCQYDVR0TBAIwADALBgNVHQ8EBAMCA6gwEwYD +VR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFLK8DJjvvpQCcKTg/2Rv7G3aCgnw +MB8GA1UdIwQYMBaAFOtCNNCYsKuf9BtrCPfMZC7vDixFMDIGA1UdEQQrMCmCFnNl +Y3VyZS5oYXJyeWhhcnBoYW0ubWWCD2hhcnJ5aGFycGhhbS5tZTCCAVYGA1UdIASC +AU0wggFJMAgGBmeBDAECATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcC +ARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjCB9wYIKwYBBQUH +AgIwgeowJxYgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBARqB +vlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBhY2NvcmRpbmcgdG8gdGhlIENs +YXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0YXJ0Q29tIENB +IHBvbGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2Ug +aW4gY29tcGxpYW5jZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4w +NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1j +cmwuY3JsMIGOBggrBgEFBQcBAQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2Nz +cC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9zZXJ2ZXIvY2EwQgYIKwYBBQUHMAKG +Nmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5jbGFzczEuc2VydmVy +LmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8wDQYJ +KoZIhvcNAQELBQADggEBADVeNvC2WIv3VljSQNS1Kd5M/CyWI1vnAlIDg381EQQF +a1rEgqCF9P7ZBEf+G1+pzt3IhJq2jcxq93RQyWW5cyc+IYQcRaPKK4zC8YWPM12r +p4r9hNTXLOZ4zFclcciPURkZZ8RTw4strq9cnYkt1T4sNR4gBdG0pMHOJt4jJhAj +dCvSmLHXVsUYNld2Tg30Nvq1vi4HaR/Okq5A2bUkD1Gph/2u6op2kapGBXGRRkOn +uYqcKKpIPQT2AtBcQiYoYPjCV/Txlgpdj2JKhVjUwmbIRhob0sBwGMQv45aRkGEi +zOOQAxdW4vOa0VX79s7ePmrnYz0pTfnWtBXC+vajIM0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIHFxU9nqs/vzANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQG +EwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERp +Z2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDcxMDE0MjA1NDE3WhcNMjIxMDE0MjA1 +NDE3WjCBjDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzAp +BgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNV +BAMTL1N0YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVy +IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj +0PREGBiEgFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo +/OenJOJApgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn6 +6+6CPAVvkvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+v +WjhwRRI/ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxD +KslIDlc5xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21T +Lwb0pwIDAQABo4IBTDCCAUgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E +BAMCAQYwHQYDVR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaA +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGkGCCsGAQUFBwEBBF0wWzAnBggrBgEFBQcw +AYYbaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL2NhMDAGCCsGAQUFBzAChiRodHRw +Oi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9jYS5jcnQwMgYDVR0fBCswKTAnoCWg +I4YhaHR0cDovL2NybC5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMEMGA1UdIAQ8MDow +OAYEVR0gADAwMC4GCCsGAQUFBwIBFiJodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9w +b2xpY3kucGRmMA0GCSqGSIb3DQEBCwUAA4ICAQCBnsOw7dxamNbdJb/ydkh4Qb6E +qgEU+G9hCCIGXwhWRZMYczNJMrpVvyLq5mNOmrFPC7bJrqYV+vEOYHNXrzthLyOG +FFOVQe2cxbmQecFOvbkWVlYAIaTG42sHKVi+RFsG2jRNZcFhHnsFnLPMyE6148lZ +wVdZGsxZvpeHReNUpW0jh7uq90sShFzHs4f7wJ5XmiHOL7fZbnFV6uE/OoFnBWif +CRnd9+RE3uCospESPCRPdbG+Q4GQ+MBS1moXDTRB6DcNoHvqC6eU3r8/Fn/DeA9w +9JHPXUfrAhZYKyOQUIqcfE5bvssaY+oODVxji6BMk8VSVHsJ4FSC1/7Pkt/UPoQp +FVh38wIJnvEUeNVmVl3HHFYTd50irdKYPBC63qi2V/YYI6bJKmbrjfP9Vhyt9uNr +y3Kh4W22ktDuCCvWC7n/gqerdq+VlTRfNt7D/mB0irnaKjEVNCXBXm9V/978J+Ez +8aplGZccQ9jnc9kiPtUp5dj45E3V8vKqzp9srSSI5Xapdg+ZcPY+6HNuVB+MadRp +ZW2One/Qnzg9B4GnVX7MOETImdoP4kXpostFuxoY/5LxCU1LJAIENV4txvT50lX2 +GBXCkxllRLWOgdyll11ift/4IO1aCOGDijGIfh498YisM1LGxytmGcxvbJERVri+ +gGpWAZ5J6dvtf0s+bA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/helpers/testsuite/testdata/cert/decrypted.ssl.key b/helpers/testsuite/testdata/cert/decrypted.ssl.key new file mode 100644 index 000000000..fe973abf7 --- /dev/null +++ b/helpers/testsuite/testdata/cert/decrypted.ssl.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA6IWSkznetjqg0XafBKw56jhIh10JopL2mTpSjhGt3BPNF8nC +kUnspzN2CRzwn8owd05NQwX8uPBu0EWsqTTPKFXR37mS3GpQ/aQhV14vQZbgSG0x +ex3UYFGVHe3/EE7tTEJzNnJBSYuW9t7CyJLugRT5Pni++xGUbOnAH9n57ZbtmZ1Y +Ehbw69McumE3rYpwMHFXeMoGGjCjQzTYF5PRc8IWFaAbQAIvnvQRnmgrj9J763E0 +8T8gaxLocW7S0V8x7WRV93W4AkG4ieBQM6pcU0ME6lyRDEiL1gEM/GQ6Nb/Dljko +quEMmA7PgB1I/EcLDj+BV5B6U6jP82LCaj0GaQIDAQABAoIBAQDOIryS8NbUTp2L +lNHc3qJpVoRWHNER45/ir6XIdTvgw10rHZ70FslKYYDfC1z3C9RzsGMmbARkICLd +WSd8ymhspchtwqLD7Szn773vKnxSdWNYUhVvxD4nNaww4HEjEBWnabGiLyBaBvpK +2altD3WJ0gI0s+77vdYLyB6fHitHyLk7yd12cpv8Q85VE+aZFNbVsaDLli/h+afT +0b9pRcF4J5k+Rk4UO9ZxQ99Zxi2cwA+AU1hoZYH2E0ti7hJD/577qNZ07aIiGO65 +2i7y28QKpQT0Pcc1dhVVMVYX7i0uUNCu+lsK0GO5A7amAYbm+cDF+NMUlWXx2hFz +R32lKOXRAoGBAPRdHEB9OdD+5UNGIAT66IWUC2NZIT2bhAyJrdlrmHIP/RYFzUom +k9hjoVpDtWey5NzFDmVIFlp2LMHM/sxOYNra9ddR6YMiJtmq3OMwXJjcFjKOzq/s +xA2Tag9b9Ds4vzcAgVLcaO1fGzNOupP1ETUI+aRdpiIkL9we11xnMfmrAoGBAPOY +Gq/FJ8XHmckPJim9dOKvrX/P9uqLkZjnWEZfrdjDYRjH+CB84RV8hk0fWVJvQmnV +USpgblPxmQA143Zm/z5UePRa1fhscAepBlARg9dacSee6/aBXVqXOtn72yZcIca6 +25ZDlqMMt0OsdWuwcMrCc0cSPEngnn8PY7iUxXQ7AoGBAL9xcwDTGsix6cua1NsR +97kvEvzyhyhM/lcURi9gLD6waAco5HyDXLDa3T5qbWSgssyDVZPjth+JFed/Qt+t +SBQuFwxqEBe8WdOOGR+kc+cOBhX169fIFpDMPPtrfxwmiSf+LVNNeqb9K8I2m9Xu +2VxDe9FZzR5SW2yIfRDXiD+hAoGBALCoFnJyYK2mW3jca6jJpQLgtMqEX2UpYqWo +4PPoX4Nb7gayvteQuQuEC0HTAkwuM9Pcx8gbtudZxsPIUPWFLIZfbHiJh7NpsLVd +dx6KFTsQdA9vyWAz4SfZlOyRIhVnq3cb/DO1QIFur1l8sIswohvpeQCJ0P8izs8G +siMigTARAoGBANSp96QqJ7gNYzHN18GhBwwkCOBE4HC1GkTGlROmZG3M0N9+j7WI +MTBjf0VS9V+aRqqvIFGTv3kpk6Wp0fxyXGL8vFK3DoyrGeWr3URXlHYiAyjfa3dr +URXCnqHcaMDfgzrBZf4Hi2sTsZYximWv/v+YUGStwbWcgwJ9Eep2gixp +-----END RSA PRIVATE KEY----- diff --git a/helpers/testsuite/testdata/initCA/cfssl_output.pem b/helpers/testsuite/testdata/cfssl_output.pem similarity index 99% rename from helpers/testsuite/testdata/initCA/cfssl_output.pem rename to helpers/testsuite/testdata/cfssl_output.pem index 02f818db3..42ad0611c 100644 --- a/helpers/testsuite/testdata/initCA/cfssl_output.pem +++ b/helpers/testsuite/testdata/cfssl_output.pem @@ -1 +1 @@ -{"cert":"-----BEGIN CERTIFICATE-----\nMIIEBjCCAvCgAwIBAgIIcoeD7FP4p0gwCwYJKoZIhvcNAQELMIGQMQswCQYDVQQG\nEwJVUzEeMBwGA1UEChMVSW50ZXJuZXQgV2lkZ2V0cywgTExDMR4wHAYDVQQLExVD\nZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR\nBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAMTC2V4YW1wbGUuY29tMB4XDTE1MDMy\nMzIwMTcwMloXDTE1MDMyMzIwMjIwMlowgZAxCzAJBgNVBAYTAlVTMR4wHAYDVQQK\nExVJbnRlcm5ldCBXaWRnZXRzLCBMTEMxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1\ndGhvcml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv\ncm5pYTEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQC/S+yLMwj5TEIWSujzOf3VBMCyiW+KM17fvSK6Wp14OElFs53G\nNNRVJDkIOyx66I29YqvYTO28WyLX3X8z8tS3hH8C9Bp/lG5X644hiQpl5vHSJfxa\nQdvtJZevY6lwnz9JDha1X0P7XzHeyJVC8u3Uq8i4X7t2GC23p0Jz5hEs6YBn6cQa\nui1I40U9teNUPE4ofUOt2EUx2uDiRs0+IOy5gM/GH7XDv88eWwSm/loqwYp5CdAv\n0/jnPqisqen0KXEQNFgZJZSzZy2a1GE4Ie/yc9R8udnkUd5pgIk26T4UDSpNVnDA\n5N7mH2MpcQ4l1ErTVk8WWrM+6X5UVIkWA56LAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIABjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBR8CfC68fIBAkJcjFOD\n6SDYLUTanTAfBgNVHSMEGDAWgBR8CfC68fIBAkJcjFOD6SDYLUTanTALBgkqhkiG\n9w0BAQsDggEBACZdSOd9C+ZiVGSqK113CvvQWMVxgEiprbQ+p2jJ+tmcZdAEDgDO\nEEdjPs1ygdhwJwiQL1C5Ak4Xc91aamP2dZtMlM02UVzTrjsOyC7FShq238pyaeQX\nzXyOOSnriMeuJvipLiStk1WhrbRkq1Zfy5CmX6FYd1dfuljwoDgfAY9GtCi0T6MY\n6vu9f8/S2ei4sTtqkF2/KbBq3K17WqsMmDxCYMWxBaY1z92i01lypPw3tM3k4Gkn\nJnil6RYlqg9odhazZcFXsNNywxg2S2Q2pJ1JLGDypgBpqzMQjd7zP6WOz9MTP8yK\n+V2sF/dDlAQCvl8EPvigqCCOXtL6uHYo0Fw=\n-----END CERTIFICATE-----\n","key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAv0vsizMI+UxCFkro8zn91QTAsolvijNe370iulqdeDhJRbOd\nxjTUVSQ5CDsseuiNvWKr2EztvFsi191/M/LUt4R/AvQaf5RuV+uOIYkKZebx0iX8\nWkHb7SWXr2OpcJ8/SQ4WtV9D+18x3siVQvLt1KvIuF+7dhgtt6dCc+YRLOmAZ+nE\nGrotSONFPbXjVDxOKH1DrdhFMdrg4kbNPiDsuYDPxh+1w7/PHlsEpv5aKsGKeQnQ\nL9P45z6orKnp9ClxEDRYGSWUs2ctmtRhOCHv8nPUfLnZ5FHeaYCJNuk+FA0qTVZw\nwOTe5h9jKXEOJdRK01ZPFlqzPul+VFSJFgOeiwIDAQABAoIBAEzKaZYnUn7gwda5\nE3Iv4VlVoxUIXqIXpQojnoE4RuYvkhpM1BTwuBvFgq9vMZfVKrDPnzQhDX4DB6Wn\n4Jw16nYQ1fuVid2U9AaPJGsJ3Aa0AnbdkMOalHJKGO3rD+WJx5nH8g20xFFwEVDh\nLCnH4vjwTI7WvyXU5uVGW0KzJEnmbpjupMo+3IKSUdZyZspAc2vZby+bU18U7PO2\nfsyW7aZjQ0b2XFKbWpw+RD/HOskBImOqoL9AAzvXEaxFR3mz4OQBtUlzEiogr0/6\ngBNTbyIVSE+0hodtLxJMhi+xIFeoGCyYdpONU3mqKcYjnV4mHfLX2+Sn4hTJtaLg\n7VHyiKECgYEAwC+4RK1E/UU9nvEgaUowpmAtnk40g31ACylOmpAiHaBaJfZ2JToe\nSwd60oFZ5QEA/zoch1niUwgtj4Bn+Iv/sRD3N60DuLg5F+S+0qH7viZb5jAsIYmP\ntF7+nMd6iID+vXhXqL/ElJczGldKm+wKPV9n8KcdvHwDySvsy//WC0cCgYEA/tCR\nHbiLbMyA+stu7i5AdVWsSxmNipD8pjrI6509zerwnwvwX5nxagO3hBmev/jJgJKu\nL3ptZotR5CpcS/PxmNdRHumeQzvN5AnUefB037tQOtocPIAWwnh4a+hf4QtdXRmm\nbtSZPDBaIdEL+6XEpCfHdSktj/8nJ0aftW39rJ0CgYA7hgwiaS/26Z0ePzx0n9/V\nh/BZGYu2NfLCAjcwM/f9CQ8ituT8vrVnM5fc3udCwD7YzdzMJxCr+tpxmamaF3JI\nGyMWgWWnrpcwSU642iaoTCUmdEEW2g5CJTHiyP2wjZNYh49O2qY+B15yiBq5lC/P\ntl7w8DGLkTVy90HOJRzcPQKBgF1rY7iMvvkNZMANs0a5SEJ8PWVvIdhKXYYoCR9w\nj4cd+kelHeQ+0SY35KEWr3/cGyv7GscnUFKHA2fuK9ZgwhNx5Ti7F524oDxZaj+m\na4LW7owVCMMZhr6XYjbVg8lC3GDUlZMOeHt7kp/RP15sINK6DsMibKTdO6KHnufq\nQlV1AoGBAKeRFcw0jSB0PWEVHPDF4pCSz/vhoX/hy0OQN13QWLFhgt/rmTWOCFuJ\nSvIde8oM2pD5f8ZmxzMGkknEnrdX7zz/tE4roImep9G49BbetHGdWVu/TvvDyvXM\nzi//6WQzpjjqQxrl4q7B1tJ4A71fKN6V2u+qSdYg/t31EfE47lCQ\n-----END RSA PRIVATE KEY-----\n"} +{"cert":"-----BEGIN CERTIFICATE-----\nMIIEBjCCAvCgAwIBAgIIcoeD7FP4p0gwCwYJKoZIhvcNAQELMIGQMQswCQYDVQQG\nEwJVUzEeMBwGA1UEChMVSW50ZXJuZXQgV2lkZ2V0cywgTExDMR4wHAYDVQQLExVD\nZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzAR\nBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAMTC2V4YW1wbGUuY29tMB4XDTE1MDMy\nMzIwMTcwMloXDTE1MDMyMzIwMjIwMlowgZAxCzAJBgNVBAYTAlVTMR4wHAYDVQQK\nExVJbnRlcm5ldCBXaWRnZXRzLCBMTEMxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1\ndGhvcml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv\ncm5pYTEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQC/S+yLMwj5TEIWSujzOf3VBMCyiW+KM17fvSK6Wp14OElFs53G\nNNRVJDkIOyx66I29YqvYTO28WyLX3X8z8tS3hH8C9Bp/lG5X644hiQpl5vHSJfxa\nQdvtJZevY6lwnz9JDha1X0P7XzHeyJVC8u3Uq8i4X7t2GC23p0Jz5hEs6YBn6cQa\nui1I40U9teNUPE4ofUOt2EUx2uDiRs0+IOy5gM/GH7XDv88eWwSm/loqwYp5CdAv\n0/jnPqisqen0KXEQNFgZJZSzZy2a1GE4Ie/yc9R8udnkUd5pgIk26T4UDSpNVnDA\n5N7mH2MpcQ4l1ErTVk8WWrM+6X5UVIkWA56LAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIABjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBR8CfC68fIBAkJcjFOD\n6SDYLUTanTAfBgNVHSMEGDAWgBR8CfC68fIBAkJcjFOD6SDYLUTanTALBgkqhkiG\n9w0BAQsDggEBACZdSOd9C+ZiVGSqK113CvvQWMVxgEiprbQ+p2jJ+tmcZdAEDgDO\nEEdjPs1ygdhwJwiQL1C5Ak4Xc91aamP2dZtMlM02UVzTrjsOyC7FShq238pyaeQX\nzXyOOSnriMeuJvipLiStk1WhrbRkq1Zfy5CmX6FYd1dfuljwoDgfAY9GtCi0T6MY\n6vu9f8/S2ei4sTtqkF2/KbBq3K17WqsMmDxCYMWxBaY1z92i01lypPw3tM3k4Gkn\nJnil6RYlqg9odhazZcFXsNNywxg2S2Q2pJ1JLGDypgBpqzMQjd7zP6WOz9MTP8yK\n+V2sF/dDlAQCvl8EPvigqCCOXtL6uHYo0Fw=\n-----END CERTIFICATE-----\n","key":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAv0vsizMI+UxCFkro8zn91QTAsolvijNe370iulqdeDhJRbOd\nxjTUVSQ5CDsseuiNvWKr2EztvFsi191/M/LUt4R/AvQaf5RuV+uOIYkKZebx0iX8\nWkHb7SWXr2OpcJ8/SQ4WtV9D+18x3siVQvLt1KvIuF+7dhgtt6dCc+YRLOmAZ+nE\nGrotSONFPbXjVDxOKH1DrdhFMdrg4kbNPiDsuYDPxh+1w7/PHlsEpv5aKsGKeQnQ\nL9P45z6orKnp9ClxEDRYGSWUs2ctmtRhOCHv8nPUfLnZ5FHeaYCJNuk+FA0qTVZw\nwOTe5h9jKXEOJdRK01ZPFlqzPul+VFSJFgOeiwIDAQABAoIBAEzKaZYnUn7gwda5\nE3Iv4VlVoxUIXqIXpQojnoE4RuYvkhpM1BTwuBvFgq9vMZfVKrDPnzQhDX4DB6Wn\n4Jw16nYQ1fuVid2U9AaPJGsJ3Aa0AnbdkMOalHJKGO3rD+WJx5nH8g20xFFwEVDh\nLCnH4vjwTI7WvyXU5uVGW0KzJEnmbpjupMo+3IKSUdZyZspAc2vZby+bU18U7PO2\nfsyW7aZjQ0b2XFKbWpw+RD/HOskBImOqoL9AAzvXEaxFR3mz4OQBtUlzEiogr0/6\ngBNTbyIVSE+0hodtLxJMhi+xIFeoGCyYdpONU3mqKcYjnV4mHfLX2+Sn4hTJtaLg\n7VHyiKECgYEAwC+4RK1E/UU9nvEgaUowpmAtnk40g31ACylOmpAiHaBaJfZ2JToe\nSwd60oFZ5QEA/zoch1niUwgtj4Bn+Iv/sRD3N60DuLg5F+S+0qH7viZb5jAsIYmP\ntF7+nMd6iID+vXhXqL/ElJczGldKm+wKPV9n8KcdvHwDySvsy//WC0cCgYEA/tCR\nHbiLbMyA+stu7i5AdVWsSxmNipD8pjrI6509zerwnwvwX5nxagO3hBmev/jJgJKu\nL3ptZotR5CpcS/PxmNdRHumeQzvN5AnUefB037tQOtocPIAWwnh4a+hf4QtdXRmm\nbtSZPDBaIdEL+6XEpCfHdSktj/8nJ0aftW39rJ0CgYA7hgwiaS/26Z0ePzx0n9/V\nh/BZGYu2NfLCAjcwM/f9CQ8ituT8vrVnM5fc3udCwD7YzdzMJxCr+tpxmamaF3JI\nGyMWgWWnrpcwSU642iaoTCUmdEEW2g5CJTHiyP2wjZNYh49O2qY+B15yiBq5lC/P\ntl7w8DGLkTVy90HOJRzcPQKBgF1rY7iMvvkNZMANs0a5SEJ8PWVvIdhKXYYoCR9w\nj4cd+kelHeQ+0SY35KEWr3/cGyv7GscnUFKHA2fuK9ZgwhNx5Ti7F524oDxZaj+m\na4LW7owVCMMZhr6XYjbVg8lC3GDUlZMOeHt7kp/RP15sINK6DsMibKTdO6KHnufq\nQlV1AoGBAKeRFcw0jSB0PWEVHPDF4pCSz/vhoX/hy0OQN13QWLFhgt/rmTWOCFuJ\nSvIde8oM2pD5f8ZmxzMGkknEnrdX7zz/tE4roImep9G49BbetHGdWVu/TvvDyvXM\nzi//6WQzpjjqQxrl4q7B1tJ4A71fKN6V2u+qSdYg/t31EfE47lCQ\n-----END RSA PRIVATE KEY-----\n"} \ No newline at end of file diff --git a/helpers/testsuite/testdata/initCA/ca_csr.json b/helpers/testsuite/testdata/initCA/ca_csr.json deleted file mode 100644 index 861ec167e..000000000 --- a/helpers/testsuite/testdata/initCA/ca_csr.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "cn": "example.com", - "hosts": [ - "ca.example.com" - ], - "names": [ - { - "C": "US", - "ST": "California", - "L": "San Francisco", - "O": "Internet Widgets, LLC", - "OU": "Certificate Authority" - } - ], - "key": { - "algo": "rsa", - "size": 2048 - }, - "ca": { - "pathlen": 1, - "expiry": "1/1/2015" - } -} \ No newline at end of file diff --git a/helpers/testsuite/testing_helpers.go b/helpers/testsuite/testing_helpers.go deleted file mode 100644 index 9801b4f75..000000000 --- a/helpers/testsuite/testing_helpers.go +++ /dev/null @@ -1,361 +0,0 @@ -// These functions are designed for use in testing other parts of the code. - -package testsuite - -import ( - "bufio" - // "crypto/tls" - "encoding/json" - "errors" - "io/ioutil" - "os" - "os/exec" - "strconv" - "strings" - "time" - - "github.com/cloudflare/cfssl/csr" - // "github.com/cloudflare/cfssl/helpers/testsuite/stoppable" -) - -// CFSSLServerData is the data with which a server is initialized. These fields -// can be left empty if desired. Any empty fields passed in to StartServer will -// lead to the server being initialized with the default values defined by the -// 'cfssl serve' command. -type CFSSLServerData struct { - CA []byte - CABundle []byte - CAKey []byte - IntBundle []byte -} - -// CFSSLServer is the type returned by StartCFSSLServer. It serves as a handle -// to a running CFSSL server. -type CFSSLServer struct { - process *os.Process - tempFiles []string -} - -// StartCFSSLServer creates a local server listening on the given address and -// port number. Both the address and port number are assumed to be valid. -func StartCFSSLServer(address string, portNumber int, serverData CFSSLServerData) (*CFSSLServer, error) { - // This value is explained below. - startupTime := time.Second - - // We return this when an error occurs. - nilServer := &CFSSLServer{nil, nil} - - args := []string{"serve", "-address", address, "-port", strconv.Itoa(portNumber)} - var tempCAFile, tempCABundleFile, tempCAKeyFile, tempIntBundleFile string - var err error - var tempFiles []string - if len(serverData.CA) > 0 { - tempCAFile, err = createTempFile(serverData.CA) - tempFiles = append(tempFiles, tempCAFile) - args = append(args, "-ca") - args = append(args, tempCAFile) - } - if len(serverData.CABundle) > 0 { - tempCABundleFile, err = createTempFile(serverData.CABundle) - tempFiles = append(tempFiles, tempCABundleFile) - args = append(args, "-ca-bundle") - args = append(args, tempCABundleFile) - } - if len(serverData.CAKey) > 0 { - tempCAKeyFile, err = createTempFile(serverData.CAKey) - tempFiles = append(tempFiles, tempCAKeyFile) - args = append(args, "-ca-key") - args = append(args, tempCAKeyFile) - } - if len(serverData.IntBundle) > 0 { - tempIntBundleFile, err = createTempFile(serverData.IntBundle) - tempFiles = append(tempFiles, tempIntBundleFile) - args = append(args, "-int-bundle") - args = append(args, tempIntBundleFile) - } - // If an error occurred in the creation of any file, return an error. - if err != nil { - for _, file := range tempFiles { - os.Remove(file) - } - return nilServer, err - } - - command := exec.Command("cfssl", args...) - - stdErrPipe, err := command.StderrPipe() - if err != nil { - for _, file := range tempFiles { - os.Remove(file) - } - return nilServer, err - } - - err = command.Start() - if err != nil { - for _, file := range tempFiles { - os.Remove(file) - } - return nilServer, err - } - - // We check to see if the address given is already in use. There is no way - // to do this other than to just wait and see if an error message pops up. - // Therefore we wait for startupTime, and if we don't see an error message - // by then, we deem the server ready and return. - - errorOccurred := make(chan bool) - go func() { - scanner := bufio.NewScanner(stdErrPipe) - for scanner.Scan() { - line := scanner.Text() - if strings.Contains(line, "address already in use") { - errorOccurred <- true - } - } - }() - - select { - case <-errorOccurred: - for _, file := range tempFiles { - os.Remove(file) - } - return nilServer, errors.New( - "Error occurred on server: address " + address + ":" + - strconv.Itoa(portNumber) + " already in use.") - case <-time.After(startupTime): - return &CFSSLServer{command.Process, tempFiles}, nil - } -} - -// Kill a running CFSSL server. -func (server *CFSSLServer) Kill() error { - for _, file := range server.tempFiles { - os.Remove(file) - } - return server.process.Kill() -} - -// CreateCertificateChain creates a chain of certificates from a slice of -// requests. The first request is the root certificate and the last is the -// leaf. The chain is returned as a slice of PEM-encoded bytes. -func CreateCertificateChain(requests []csr.CertificateRequest) (certChain []byte, key []byte, err error) { - // Create the root certificate using the first request. This will be - // self-signed. - certChain = make([]byte, 0) - rootCert, prevKey, err := CreateSelfSignedCert(requests[0]) - if err != nil { - return nil, nil, err - } - certChain = append(certChain, rootCert...) - - // For each of the next requests, create a certificate signed by the - // previous certificate. - prevCert := rootCert - for _, request := range requests[1:] { - cert, key, err := SignCertificate(request, prevCert, prevKey) - if err != nil { - return nil, nil, err - } - certChain = append(certChain, byte('\n')) - certChain = append(certChain, cert...) - prevCert = cert - prevKey = key - } - - return certChain, key, nil -} - -// CreateSelfSignedCert creates a self-signed certificate from a certificate -// request. This function just calls the CLI "gencert" command. -func CreateSelfSignedCert(request csr.CertificateRequest) (encodedCert, encodedKey []byte, err error) { - // Marshall the request into JSON format and write it to a temporary file. - jsonBytes, err := json.Marshal(request) - if err != nil { - return nil, nil, err - } - tempFile, err := createTempFile(jsonBytes) - if err != nil { - os.Remove(tempFile) - return nil, nil, err - } - - // Create the certificate with the CLI tools. - command := exec.Command("cfssl", "gencert", "-initca", tempFile) - CLIOutput, err := command.CombinedOutput() - if err != nil { - os.Remove(tempFile) - return nil, nil, err - } - err = checkCLIOutput(CLIOutput) - if err != nil { - os.Remove(tempFile) - return nil, nil, err - } - - encodedCert, err = cleanCLIOutput(CLIOutput, "cert") - if err != nil { - os.Remove(tempFile) - return nil, nil, err - } - encodedKey, err = cleanCLIOutput(CLIOutput, "key") - if err != nil { - os.Remove(tempFile) - return nil, nil, err - } - - os.Remove(tempFile) - - return encodedCert, encodedKey, nil -} - -// SignCertificate uses a certificate (input as signerCert) to create a signed -// certificate for the input request. -func SignCertificate(request csr.CertificateRequest, signerCert, signerKey []byte) (encodedCert, encodedKey []byte, err error) { - // Marshall the request into JSON format and write it to a temporary file. - jsonBytes, err := json.Marshal(request) - if err != nil { - return nil, nil, err - } - tempJSONFile, err := createTempFile(jsonBytes) - if err != nil { - os.Remove(tempJSONFile) - return nil, nil, err - } - - // Create a CSR file with the CLI tools. - command := exec.Command("cfssl", "genkey", tempJSONFile) - CLIOutput, err := command.CombinedOutput() - if err != nil { - os.Remove(tempJSONFile) - return nil, nil, err - } - err = checkCLIOutput(CLIOutput) - if err != nil { - os.Remove(tempJSONFile) - return nil, nil, err - } - encodedCSR, err := cleanCLIOutput(CLIOutput, "csr") - if err != nil { - os.Remove(tempJSONFile) - return nil, nil, err - } - encodedCSRKey, err := cleanCLIOutput(CLIOutput, "key") - if err != nil { - os.Remove(tempJSONFile) - return nil, nil, err - } - - // Now we write this encoded CSR and its key to file. - tempCSRFile, err := createTempFile(encodedCSR) - if err != nil { - os.Remove(tempJSONFile) - os.Remove(tempCSRFile) - return nil, nil, err - } - - // We also need to write the signer's certficate and key to temporary files. - tempSignerCertFile, err := createTempFile(signerCert) - if err != nil { - os.Remove(tempJSONFile) - os.Remove(tempCSRFile) - os.Remove(tempSignerCertFile) - return nil, nil, err - } - tempSignerKeyFile, err := createTempFile(signerKey) - if err != nil { - os.Remove(tempJSONFile) - os.Remove(tempCSRFile) - os.Remove(tempSignerCertFile) - os.Remove(tempSignerKeyFile) - return nil, nil, err - } - - // Now we use the signer's certificate and key file along with the CSR file - // to sign a certificate for the input request. We use the CLI tools to do - // this. - command = exec.Command( - "cfssl", - "sign", - "-ca", tempSignerCertFile, - "-ca-key", tempSignerKeyFile, - "-hostname", request.CN, - tempCSRFile, - ) - CLIOutput, err = command.CombinedOutput() - err = checkCLIOutput(CLIOutput) - if err != nil { - return nil, nil, err - } - encodedCert, err = cleanCLIOutput(CLIOutput, "cert") - if err != nil { - return nil, nil, err - } - - // Clean up. - os.Remove(tempJSONFile) - os.Remove(tempCSRFile) - os.Remove(tempSignerCertFile) - os.Remove(tempSignerKeyFile) - - return encodedCert, encodedCSRKey, nil -} - -// Creates a temporary file with the given data. Returns the file name. -func createTempFile(data []byte) (fileName string, err error) { - // Avoid overwriting a file in the currect directory by choosing an unused - // file name. - baseName := "temp" - tempFileName := baseName - tryIndex := 0 - for { - if _, err := os.Stat(tempFileName); err == nil { - tempFileName = baseName + strconv.Itoa(tryIndex) - tryIndex++ - } else { - break - } - } - - readWritePermissions := os.FileMode(0664) - err = ioutil.WriteFile(tempFileName, data, readWritePermissions) - if err != nil { - return "", err - } - - return tempFileName, nil -} - -// Checks the CLI Output for failure. -func checkCLIOutput(CLIOutput []byte) error { - outputString := string(CLIOutput) - // Proper output will contain the substring "---BEGIN" somewhere - failureOccurred := !strings.Contains(outputString, "---BEGIN") - if failureOccurred { - return errors.New("Failure occurred during CLI execution: " + outputString) - } - return nil -} - -// Returns the cleaned up PEM encoding for the item specified (for example, -// 'cert' or 'key'). -func cleanCLIOutput(CLIOutput []byte, item string) (cleanedOutput []byte, err error) { - outputString := string(CLIOutput) - // The keyword will be surrounded by quotes. - itemString := "\"" + item + "\"" - // We should only search for the keyword beyond this point. - eligibleSearchIndex := strings.Index(outputString, "{") - outputString = outputString[eligibleSearchIndex:] - // Make sure the item is present in the output. - if strings.Index(outputString, itemString) == -1 { - return nil, errors.New("Item " + item + " not found in CLI Output") - } - // We add 2 for the [:"] that follows the item - startIndex := strings.Index(outputString, itemString) + len(itemString) + 2 - outputString = outputString[startIndex:] - endIndex := strings.Index(outputString, "\\n\"") - outputString = outputString[:endIndex] - outputString = strings.Replace(outputString, "\\n", "\n", -1) - - return []byte(outputString), nil -} diff --git a/helpers/testsuite/testing_helpers_test.go b/helpers/testsuite/testing_helpers_test.go deleted file mode 100644 index b29f927ae..000000000 --- a/helpers/testsuite/testing_helpers_test.go +++ /dev/null @@ -1,465 +0,0 @@ -package testsuite - -import ( - "crypto/x509" - "encoding/json" - "io/ioutil" - "math" - "math/rand" - "os" - "os/exec" - "reflect" - "strconv" - "strings" - "testing" - "time" - - // "github.com/cloudflare/cfssl/bundler" - "github.com/cloudflare/cfssl/csr" - "github.com/cloudflare/cfssl/helpers" -) - -const ( - testDataDirectory = "testdata" - initCADirectory = testDataDirectory + string(os.PathSeparator) + "initCA" - preMadeOutput = initCADirectory + string(os.PathSeparator) + "cfssl_output.pem" - csrFile = testDataDirectory + string(os.PathSeparator) + "cert_csr.json" -) - -var ( - keyRequest = csr.KeyRequest{ - Algo: "rsa", - Size: 2048, - } - CAConfig = csr.CAConfig{ - PathLength: 1, - Expiry: "1/1/2016", - } - baseRequest = csr.CertificateRequest{ - CN: "example.com", - Names: []csr.Name{ - { - C: "US", - ST: "California", - L: "San Francisco", - O: "Internet Widgets, LLC", - OU: "Certificate Authority", - }, - }, - Hosts: []string{"ca.example.com"}, - KeyRequest: &keyRequest, - } - CARequest = csr.CertificateRequest{ - CN: "example.com", - Names: []csr.Name{ - { - C: "US", - ST: "California", - L: "San Francisco", - O: "Internet Widgets, LLC", - OU: "Certificate Authority", - }, - }, - Hosts: []string{"ca.example.com"}, - KeyRequest: &keyRequest, - CA: &CAConfig, - } -) - -func TestStartCFSSLServer(t *testing.T) { - // We will test on this address and port. Be sure that these are free or - // the test will fail. - addressToTest := "127.0.0.1" - portToTest := 9775 - - CACert, CAKey, err := CreateSelfSignedCert(CARequest) - checkError(err, t) - - // Set up a test server using our CA certificate and key. - serverData := CFSSLServerData{CA: CACert, CAKey: CAKey} - server, err := StartCFSSLServer(addressToTest, portToTest, serverData) - checkError(err, t) - - // Try to start up a second server at the same address and port number. We - // should get an 'address in use' error. - _, err = StartCFSSLServer(addressToTest, portToTest, serverData) - if err == nil || !strings.Contains(err.Error(), "Error occurred on server: address") { - t.Fatal("Two servers allowed on same address and port.") - } - - // Now make a request of our server and check that no error occurred. - - // First we need a request to send to our server. We marshall the request - // into JSON format and write it to a temporary file. - jsonBytes, err := json.Marshal(baseRequest) - checkError(err, t) - tempFile, err := createTempFile(jsonBytes) - if err != nil { - os.Remove(tempFile) - panic(err) - } - - // Now we make the request and check the output. - remoteServerString := "-remote=" + addressToTest + ":" + strconv.Itoa(portToTest) - command := exec.Command( - "cfssl", "gencert", remoteServerString, "-hostname="+baseRequest.CN, tempFile) - CLIOutput, err := command.CombinedOutput() - os.Remove(tempFile) - checkError(err, t) - err = checkCLIOutput(CLIOutput) - checkError(err, t) - // The output should contain the certificate, request, and private key. - _, err = cleanCLIOutput(CLIOutput, "cert") - checkError(err, t) - _, err = cleanCLIOutput(CLIOutput, "csr") - checkError(err, t) - _, err = cleanCLIOutput(CLIOutput, "key") - checkError(err, t) - - // Finally, kill the server. - err = server.Kill() - checkError(err, t) -} - -func TestCreateCertificateChain(t *testing.T) { - - // N is the number of certificates that will be chained together. - N := 10 - - // --- TEST: Create a chain of one certificate. --- // - - encodedChainFromCode, _, err := CreateCertificateChain([]csr.CertificateRequest{CARequest}) - checkError(err, t) - - // Now compare to a pre-made certificate chain using a JSON file containing - // the same request data. - - CLIOutputFile := preMadeOutput - CLIOutput, err := ioutil.ReadFile(CLIOutputFile) - checkError(err, t) - encodedChainFromCLI, err := cleanCLIOutput(CLIOutput, "cert") - checkError(err, t) - - chainFromCode, err := helpers.ParseCertificatesPEM(encodedChainFromCode) - checkError(err, t) - chainFromCLI, err := helpers.ParseCertificatesPEM(encodedChainFromCLI) - checkError(err, t) - - if !chainsEqual(chainFromCode, chainFromCLI) { - unequalFieldSlices := checkFieldsOfChains(chainFromCode, chainFromCLI) - for i, unequalFields := range unequalFieldSlices { - if len(unequalFields) > 0 { - t.Log("The certificate chains held unequal fields for chain " + strconv.Itoa(i)) - t.Log("The following fields were unequal:") - for _, field := range unequalFields { - t.Log("\t" + field) - } - } - } - t.Fatal("Certificate chains unequal.") - } - - // --- TEST: Create a chain of N certificates. --- // - - // First we make a slice of N requests. We make each slightly different. - - cnGrabBag := []string{"example", "invalid", "test"} - topLevelDomains := []string{".com", ".net", ".org"} - subDomains := []string{"www.", "secure.", "ca.", ""} - countryGrabBag := []string{"USA", "China", "England", "Vanuatu"} - stateGrabBag := []string{"California", "Texas", "Alaska", "London"} - localityGrabBag := []string{"San Francisco", "Houston", "London", "Oslo"} - orgGrabBag := []string{"Internet Widgets, LLC", "CloudFlare, Inc."} - orgUnitGrabBag := []string{"Certificate Authority", "Systems Engineering"} - - requests := make([]csr.CertificateRequest, N) - requests[0] = CARequest - for i := 1; i < N; i++ { - requests[i] = baseRequest - - cn := randomElement(cnGrabBag) - tld := randomElement(topLevelDomains) - subDomain1 := randomElement(subDomains) - subDomain2 := randomElement(subDomains) - country := randomElement(countryGrabBag) - state := randomElement(stateGrabBag) - locality := randomElement(localityGrabBag) - org := randomElement(orgGrabBag) - orgUnit := randomElement(orgUnitGrabBag) - - requests[i].CN = cn + "." + tld - requests[i].Names = []csr.Name{ - {C: country, - ST: state, - L: locality, - O: org, - OU: orgUnit, - }, - } - hosts := []string{subDomain1 + requests[i].CN} - if subDomain2 != subDomain1 { - hosts = append(hosts, subDomain2+requests[i].CN) - } - requests[i].Hosts = hosts - } - - // Now we make a certificate chain out of these requests. - encodedCertChain, _, err := CreateCertificateChain(requests) - checkError(err, t) - - // To test this chain, we compare the data encoded in each certificate to - // each request we used to generate the chain. - chain, err := helpers.ParseCertificatesPEM(encodedCertChain) - checkError(err, t) - - if len(chain) != len(requests) { - t.Log("Length of chain: " + strconv.Itoa(len(chain))) - t.Log("Length of requests: " + strconv.Itoa(len(requests))) - t.Fatal("Length of chain not equal to length of requests.") - } - - mismatchOccurred := false - for i := 0; i < len(chain); i++ { - certEqualsRequest, unequalFields := certEqualsRequest(chain[i], requests[i]) - if !certEqualsRequest { - mismatchOccurred = true - t.Log( - "Certificate " + strconv.Itoa(i) + " and request " + - strconv.Itoa(i) + " unequal.", - ) - t.Log("Unequal fields for index " + strconv.Itoa(i) + ":") - for _, field := range unequalFields { - t.Log("\t" + field) - } - } - } - - // TODO: check that each certificate is actually signed by the previous one - - if mismatchOccurred { - t.Fatal("Unequal certificate(s) and request(s) found.") - } - - // --- TEST: Create a chain of certificates with invalid path lengths. --- // - - // Other invalid chains? -} - -func TestCreateSelfSignedCert(t *testing.T) { - - // --- TEST: Create a self-signed certificate from a CSR. --- // - - // Make the request we will use to generate the certificate. - keyRequest := csr.KeyRequest{ - Algo: "rsa", - Size: 2048, - } - CAConfig := csr.CAConfig{ - PathLength: 1, - Expiry: "1/1/2015", - } - request := csr.CertificateRequest{ - CN: "example.com", - Names: []csr.Name{ - { - C: "US", - ST: "California", - L: "San Francisco", - O: "Internet Widgets, LLC", - OU: "Certificate Authority", - }, - }, - Hosts: []string{"ca.example.com"}, - KeyRequest: &keyRequest, - CA: &CAConfig, - } - - // Generate a self-signed certificate from the request. - encodedCertFromCode, _, err := CreateSelfSignedCert(request) - checkError(err, t) - - // Now compare to a pre-made certificate made using a JSON file with the - // same request information. This JSON file is located in testdata/initCA - // and is called ca_csr.json. - - CLIOutputFile := preMadeOutput - CLIOutput, err := ioutil.ReadFile(CLIOutputFile) - checkError(err, t) - encodedCertFromCLI, err := cleanCLIOutput(CLIOutput, "cert") - checkError(err, t) - - certFromCode, err := helpers.ParseSelfSignedCertificatePEM(encodedCertFromCode) - checkError(err, t) - certFromCLI, err := helpers.ParseSelfSignedCertificatePEM(encodedCertFromCLI) - checkError(err, t) - - // Nullify any fields of the certificates which are dependent upon the time - // of the certificate's creation. - nullifyTimeDependency(certFromCode) - nullifyTimeDependency(certFromCLI) - - if !reflect.DeepEqual(certFromCode, certFromCLI) { - unequalFields := checkFields( - *certFromCode, *certFromCLI, reflect.TypeOf(*certFromCode)) - t.Log("The following fields were unequal:") - for _, field := range unequalFields { - t.Log(field) - } - t.Fatal("Certificates unequal.") - } - -} - -// Compare two x509 certificate chains. We only compare relevant data to -// determine equality. -func chainsEqual(chain1, chain2 []*x509.Certificate) bool { - if len(chain1) != len(chain2) { - return false - } - - for i := 0; i < len(chain1); i++ { - cert1 := nullifyTimeDependency(chain1[i]) - cert2 := nullifyTimeDependency(chain2[i]) - if !reflect.DeepEqual(cert1, cert2) { - return false - } - } - return true -} - -// When comparing certificates created at different times for equality, we do -// not want to worry about fields which are dependent on the time of creation. -// Thus we nullify these fields before comparing the certificates. -func nullifyTimeDependency(cert *x509.Certificate) *x509.Certificate { - cert.Raw = nil - cert.RawTBSCertificate = nil - cert.RawSubjectPublicKeyInfo = nil - cert.Signature = nil - cert.PublicKey = nil - cert.SerialNumber = nil - cert.NotBefore = time.Time{} - cert.NotAfter = time.Time{} - cert.Extensions = nil - cert.SubjectKeyId = nil - cert.AuthorityKeyId = nil - - return cert -} - -// Compares two structs and returns a list containing the names of all fields -// for which the two structs hold different values. -func checkFields(struct1, struct2 interface{}, typeOfStructs reflect.Type) []string { - v1 := reflect.ValueOf(struct1) - v2 := reflect.ValueOf(struct2) - - var unequalFields []string - for i := 0; i < v1.NumField(); i++ { - if !reflect.DeepEqual(v1.Field(i).Interface(), v2.Field(i).Interface()) { - unequalFields = append(unequalFields, typeOfStructs.Field(i).Name) - } - } - - return unequalFields -} - -// Runs checkFields on the corresponding elements of chain1 and chain2. Element -// i of the returned slice contains a slice of the fields for which certificate -// i in chain1 had different values than certificate i of chain2. -func checkFieldsOfChains(chain1, chain2 []*x509.Certificate) [][]string { - minLen := math.Min(float64(len(chain1)), float64(len(chain2))) - typeOfCert := reflect.TypeOf(*chain1[0]) - - var unequalFields [][]string - for i := 0; i < int(minLen); i++ { - unequalFields = append(unequalFields, checkFields( - *chain1[i], *chain2[i], typeOfCert)) - } - - return unequalFields -} - -// Compares a certificate to a request. Returns (true, []) if both items -// contain matching data (for the things that can match). Otherwise, returns -// (false, unequalFields) where unequalFields contains the names of all fields -// which did not match. -func certEqualsRequest(cert *x509.Certificate, request csr.CertificateRequest) (bool, []string) { - equal := true - var unequalFields []string - - if cert.Subject.CommonName != request.CN { - equal = false - unequalFields = append(unequalFields, "Common Name") - } - - nameData := make(map[string]map[string]bool) - nameData["Country"] = make(map[string]bool) - nameData["Organization"] = make(map[string]bool) - nameData["OrganizationalUnit"] = make(map[string]bool) - nameData["Locality"] = make(map[string]bool) - nameData["Province"] = make(map[string]bool) - for _, name := range request.Names { - nameData["Country"][name.C] = true - nameData["Organization"][name.O] = true - nameData["OrganizationalUnit"][name.OU] = true - nameData["Locality"][name.L] = true - nameData["Province"][name.ST] = true - } - for _, country := range cert.Subject.Country { - if _, exists := nameData["Country"][country]; !exists { - equal = false - unequalFields = append(unequalFields, "Country") - } - } - for _, organization := range cert.Subject.Organization { - if _, exists := nameData["Organization"][organization]; !exists { - equal = false - unequalFields = append(unequalFields, "Organization") - } - } - for _, organizationalUnit := range cert.Subject.OrganizationalUnit { - if _, exists := nameData["OrganizationalUnit"][organizationalUnit]; !exists { - equal = false - unequalFields = append(unequalFields, "OrganizationalUnit") - } - } - for _, locality := range cert.Subject.Locality { - if _, exists := nameData["Locality"][locality]; !exists { - equal = false - unequalFields = append(unequalFields, "Locality") - } - } - for _, province := range cert.Subject.Province { - if _, exists := nameData["Province"][province]; !exists { - equal = false - unequalFields = append(unequalFields, "Province") - } - } - - // TODO: check hosts - - if cert.BasicConstraintsValid && request.CA != nil { - if cert.MaxPathLen != request.CA.PathLength { - equal = false - unequalFields = append(unequalFields, "Max Path Length") - } - // TODO: check expiry - } - - // TODO: check isCA - - return equal, unequalFields -} - -// Returns a random element of the input slice. -func randomElement(set []string) string { - return set[rand.Intn(len(set))] -} - -// Just to clean the code up a bit. -func checkError(err error, t *testing.T) { - if err != nil { - // t.Fatal is more clean, but a panic gives more information for debugging - panic(err) - // t.Fatal(err.Error()) - } -} diff --git a/scan/connectivity_test.go b/scan/connectivity_test.go new file mode 100644 index 000000000..37fb31a43 --- /dev/null +++ b/scan/connectivity_test.go @@ -0,0 +1,57 @@ +// Tests specific to the connectivity family. + +package scan + +import ( + "net" + "net/http" + "testing" + + "github.com/cloudflare/cfssl/helpers/testsuite" +) + +func TestConnectivity(t *testing.T) { + + tcpServer := testsuite.NewTestServer( + http.Server{Addr: net.JoinHostPort(addr, portTCP)}) + err := tcpServer.Start() + if err != nil { + t.Fatal(err.Error()) + } + + defer tcpServer.Kill() + + // === TEST: test the connectivity scan's response to a TCP-only server // + + dnsLookup := Connectivity.Scanners["DNSLookup"] + tcpDial := Connectivity.Scanners["TCPDial"] + tlsDial := Connectivity.Scanners["TLSDial"] + + grade, _, err := dnsLookup.Scan(tcpServer.Addr) + if grade != Good { + t.Log("TCP server failed DNSLookup scan (grade = " + grade.String() + ")") + t.FailNow() + } + checkError(err, t) + + grade, _, err = tcpDial.Scan(tcpServer.Addr) + if grade != Good { + t.Log("TCP server failed TCPDial scan (grade = " + grade.String() + ")") + t.FailNow() + } + checkError(err, t) + + grade, _, err = tlsDial.Scan(tcpServer.Addr) + if grade != Bad { + t.Log("TCP server did not fail TLSDial scan (grade = " + grade.String() + + ", error = " + err.Error() + ")") + t.FailNow() + } +} + +func checkError(err error, t *testing.T) { + if err != nil { + t.Log(err.Error()) + t.FailNow() + } +} diff --git a/scan/scan_common_test.go b/scan/scan_common_test.go index b916f6ca0..bf676c702 100644 --- a/scan/scan_common_test.go +++ b/scan/scan_common_test.go @@ -2,7 +2,12 @@ package scan import ( "fmt" + "io/ioutil" + "net" + "net/http" "testing" + + "github.com/cloudflare/cfssl/helpers/testsuite" ) var TestingScanner = &Scanner{ @@ -30,6 +35,15 @@ var TestingFamily = &Family{ }, } +var ( + addr = "127.0.0.1" + portTLS = "7070" + portTCP = "8070" + + certFile = "testdata/cert/cert_chain.crt" + keyFile = "testdata/cert/decrypted.ssl.key" +) + func TestCommon(t *testing.T) { if TestingFamily.Scanners["TestingScanner"] != TestingScanner { t.FailNow() @@ -64,3 +78,52 @@ func TestCommon(t *testing.T) { t.FailNow() } } + +func TestDefaultFamily(t *testing.T) { + + cert, err := ioutil.ReadFile(certFile) + if err != nil { + t.Fatal(err.Error()) + } + key, err := ioutil.ReadFile(keyFile) + if err != nil { + t.Fatal(err.Error()) + } + + tlsServer := testsuite.NewTestServer(http.Server{Addr: net.JoinHostPort(addr, portTLS)}) + err = tlsServer.UseDefaultTLSConfig(cert, key) + if err != nil { + t.Fatal(err.Error()) + } + err = tlsServer.Start() + if err != nil { + t.Fatal(err.Error()) + } + + // Kill the server upon completion or failure of the tests. + defer tlsServer.Kill() + + // === TEST: perform all of the default scans against a TLS server ===== // + + results, err := Default.RunScans(tlsServer.Addr, ".", ".") + if err != nil { + t.Fatal(err.Error()) + } + for family, familyResult := range results { + // We cannot test broad scans locally. + if family == "Broad" { + continue + } + for scanner, result := range familyResult { + if result.Error != "" { + t.Fatal("An error occurred during the following scan: " + + "[Family: " + family + ", Scanner: " + scanner + + ", Grade: " + result.Grade + "]") + } + if result.Grade != Good.String() { + t.Fatal("The following scan failed: [Family: " + family + + ", Scanner: " + scanner + ", Grade: " + result.Grade + "]") + } + } + } +} diff --git a/scan/testdata/cert/cert_chain.crt b/scan/testdata/cert/cert_chain.crt new file mode 100644 index 000000000..136699c33 --- /dev/null +++ b/scan/testdata/cert/cert_chain.crt @@ -0,0 +1,70 @@ +-----BEGIN CERTIFICATE----- +MIIGTDCCBTSgAwIBAgIHBTzQSxghazANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE +BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE +aWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0YXJ0Q29tIENs +YXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMB4XDTE1MDIxODAy +NTcyM1oXDTE2MDIxOTE5NDAzMVowUDELMAkGA1UEBhMCVVMxHzAdBgNVBAMTFnNl +Y3VyZS5oYXJyeWhhcnBoYW0ubWUxIDAeBgkqhkiG9w0BCQEWEWh3aDMzQGNvcm5l +bGwuZWR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IWSkznetjqg +0XafBKw56jhIh10JopL2mTpSjhGt3BPNF8nCkUnspzN2CRzwn8owd05NQwX8uPBu +0EWsqTTPKFXR37mS3GpQ/aQhV14vQZbgSG0xex3UYFGVHe3/EE7tTEJzNnJBSYuW +9t7CyJLugRT5Pni++xGUbOnAH9n57ZbtmZ1YEhbw69McumE3rYpwMHFXeMoGGjCj +QzTYF5PRc8IWFaAbQAIvnvQRnmgrj9J763E08T8gaxLocW7S0V8x7WRV93W4AkG4 +ieBQM6pcU0ME6lyRDEiL1gEM/GQ6Nb/DljkoquEMmA7PgB1I/EcLDj+BV5B6U6jP +82LCaj0GaQIDAQABo4IC7DCCAugwCQYDVR0TBAIwADALBgNVHQ8EBAMCA6gwEwYD +VR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFLK8DJjvvpQCcKTg/2Rv7G3aCgnw +MB8GA1UdIwQYMBaAFOtCNNCYsKuf9BtrCPfMZC7vDixFMDIGA1UdEQQrMCmCFnNl +Y3VyZS5oYXJyeWhhcnBoYW0ubWWCD2hhcnJ5aGFycGhhbS5tZTCCAVYGA1UdIASC +AU0wggFJMAgGBmeBDAECATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcC +ARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjCB9wYIKwYBBQUH +AgIwgeowJxYgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBARqB +vlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBhY2NvcmRpbmcgdG8gdGhlIENs +YXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0YXJ0Q29tIENB +IHBvbGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2Ug +aW4gY29tcGxpYW5jZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4w +NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1j +cmwuY3JsMIGOBggrBgEFBQcBAQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2Nz +cC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9zZXJ2ZXIvY2EwQgYIKwYBBQUHMAKG +Nmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5jbGFzczEuc2VydmVy +LmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8wDQYJ +KoZIhvcNAQELBQADggEBADVeNvC2WIv3VljSQNS1Kd5M/CyWI1vnAlIDg381EQQF +a1rEgqCF9P7ZBEf+G1+pzt3IhJq2jcxq93RQyWW5cyc+IYQcRaPKK4zC8YWPM12r +p4r9hNTXLOZ4zFclcciPURkZZ8RTw4strq9cnYkt1T4sNR4gBdG0pMHOJt4jJhAj +dCvSmLHXVsUYNld2Tg30Nvq1vi4HaR/Okq5A2bUkD1Gph/2u6op2kapGBXGRRkOn +uYqcKKpIPQT2AtBcQiYoYPjCV/Txlgpdj2JKhVjUwmbIRhob0sBwGMQv45aRkGEi +zOOQAxdW4vOa0VX79s7ePmrnYz0pTfnWtBXC+vajIM0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIHFxU9nqs/vzANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQG +EwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERp +Z2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDcxMDE0MjA1NDE3WhcNMjIxMDE0MjA1 +NDE3WjCBjDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzAp +BgNVBAsTIlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNV +BAMTL1N0YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVy +IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj +0PREGBiEgFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo +/OenJOJApgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn6 +6+6CPAVvkvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+v +WjhwRRI/ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxD +KslIDlc5xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21T +Lwb0pwIDAQABo4IBTDCCAUgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E +BAMCAQYwHQYDVR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaA +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGkGCCsGAQUFBwEBBF0wWzAnBggrBgEFBQcw +AYYbaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL2NhMDAGCCsGAQUFBzAChiRodHRw +Oi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9jYS5jcnQwMgYDVR0fBCswKTAnoCWg +I4YhaHR0cDovL2NybC5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMEMGA1UdIAQ8MDow +OAYEVR0gADAwMC4GCCsGAQUFBwIBFiJodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9w +b2xpY3kucGRmMA0GCSqGSIb3DQEBCwUAA4ICAQCBnsOw7dxamNbdJb/ydkh4Qb6E +qgEU+G9hCCIGXwhWRZMYczNJMrpVvyLq5mNOmrFPC7bJrqYV+vEOYHNXrzthLyOG +FFOVQe2cxbmQecFOvbkWVlYAIaTG42sHKVi+RFsG2jRNZcFhHnsFnLPMyE6148lZ +wVdZGsxZvpeHReNUpW0jh7uq90sShFzHs4f7wJ5XmiHOL7fZbnFV6uE/OoFnBWif +CRnd9+RE3uCospESPCRPdbG+Q4GQ+MBS1moXDTRB6DcNoHvqC6eU3r8/Fn/DeA9w +9JHPXUfrAhZYKyOQUIqcfE5bvssaY+oODVxji6BMk8VSVHsJ4FSC1/7Pkt/UPoQp +FVh38wIJnvEUeNVmVl3HHFYTd50irdKYPBC63qi2V/YYI6bJKmbrjfP9Vhyt9uNr +y3Kh4W22ktDuCCvWC7n/gqerdq+VlTRfNt7D/mB0irnaKjEVNCXBXm9V/978J+Ez +8aplGZccQ9jnc9kiPtUp5dj45E3V8vKqzp9srSSI5Xapdg+ZcPY+6HNuVB+MadRp +ZW2One/Qnzg9B4GnVX7MOETImdoP4kXpostFuxoY/5LxCU1LJAIENV4txvT50lX2 +GBXCkxllRLWOgdyll11ift/4IO1aCOGDijGIfh498YisM1LGxytmGcxvbJERVri+ +gGpWAZ5J6dvtf0s+bA== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/scan/testdata/cert/decrypted.ssl.key b/scan/testdata/cert/decrypted.ssl.key new file mode 100644 index 000000000..fe973abf7 --- /dev/null +++ b/scan/testdata/cert/decrypted.ssl.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA6IWSkznetjqg0XafBKw56jhIh10JopL2mTpSjhGt3BPNF8nC +kUnspzN2CRzwn8owd05NQwX8uPBu0EWsqTTPKFXR37mS3GpQ/aQhV14vQZbgSG0x +ex3UYFGVHe3/EE7tTEJzNnJBSYuW9t7CyJLugRT5Pni++xGUbOnAH9n57ZbtmZ1Y +Ehbw69McumE3rYpwMHFXeMoGGjCjQzTYF5PRc8IWFaAbQAIvnvQRnmgrj9J763E0 +8T8gaxLocW7S0V8x7WRV93W4AkG4ieBQM6pcU0ME6lyRDEiL1gEM/GQ6Nb/Dljko +quEMmA7PgB1I/EcLDj+BV5B6U6jP82LCaj0GaQIDAQABAoIBAQDOIryS8NbUTp2L +lNHc3qJpVoRWHNER45/ir6XIdTvgw10rHZ70FslKYYDfC1z3C9RzsGMmbARkICLd +WSd8ymhspchtwqLD7Szn773vKnxSdWNYUhVvxD4nNaww4HEjEBWnabGiLyBaBvpK +2altD3WJ0gI0s+77vdYLyB6fHitHyLk7yd12cpv8Q85VE+aZFNbVsaDLli/h+afT +0b9pRcF4J5k+Rk4UO9ZxQ99Zxi2cwA+AU1hoZYH2E0ti7hJD/577qNZ07aIiGO65 +2i7y28QKpQT0Pcc1dhVVMVYX7i0uUNCu+lsK0GO5A7amAYbm+cDF+NMUlWXx2hFz +R32lKOXRAoGBAPRdHEB9OdD+5UNGIAT66IWUC2NZIT2bhAyJrdlrmHIP/RYFzUom +k9hjoVpDtWey5NzFDmVIFlp2LMHM/sxOYNra9ddR6YMiJtmq3OMwXJjcFjKOzq/s +xA2Tag9b9Ds4vzcAgVLcaO1fGzNOupP1ETUI+aRdpiIkL9we11xnMfmrAoGBAPOY +Gq/FJ8XHmckPJim9dOKvrX/P9uqLkZjnWEZfrdjDYRjH+CB84RV8hk0fWVJvQmnV +USpgblPxmQA143Zm/z5UePRa1fhscAepBlARg9dacSee6/aBXVqXOtn72yZcIca6 +25ZDlqMMt0OsdWuwcMrCc0cSPEngnn8PY7iUxXQ7AoGBAL9xcwDTGsix6cua1NsR +97kvEvzyhyhM/lcURi9gLD6waAco5HyDXLDa3T5qbWSgssyDVZPjth+JFed/Qt+t +SBQuFwxqEBe8WdOOGR+kc+cOBhX169fIFpDMPPtrfxwmiSf+LVNNeqb9K8I2m9Xu +2VxDe9FZzR5SW2yIfRDXiD+hAoGBALCoFnJyYK2mW3jca6jJpQLgtMqEX2UpYqWo +4PPoX4Nb7gayvteQuQuEC0HTAkwuM9Pcx8gbtudZxsPIUPWFLIZfbHiJh7NpsLVd +dx6KFTsQdA9vyWAz4SfZlOyRIhVnq3cb/DO1QIFur1l8sIswohvpeQCJ0P8izs8G +siMigTARAoGBANSp96QqJ7gNYzHN18GhBwwkCOBE4HC1GkTGlROmZG3M0N9+j7WI +MTBjf0VS9V+aRqqvIFGTv3kpk6Wp0fxyXGL8vFK3DoyrGeWr3URXlHYiAyjfa3dr +URXCnqHcaMDfgzrBZf4Hi2sTsZYximWv/v+YUGStwbWcgwJ9Eep2gixp +-----END RSA PRIVATE KEY-----