This is an automated email from the ASF dual-hosted git repository. rob pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
commit eaffeb59a74b16ecc1e8c3af14102d48548c4b9d Author: Rawlin Peters <rawlin_pet...@comcast.com> AuthorDate: Tue Sep 18 16:44:41 2018 -0600 Finish up DS SSL keys endpoints in TO Go Finish out the '-wip' DS sslkeys endpoints and fix up what was already there. --- lib/go-tc/deliveryservice_ssl_keys.go | 2 +- .../deliveryservice/deliveryservicesv13.go | 4 +- .../traffic_ops_golang/deliveryservice/keys.go | 220 ++++++++-------- .../deliveryservice/keys_test.go | 284 ++++++++++----------- .../traffic_ops_golang/deliveryservice/sslkeys.go | 4 +- traffic_ops/traffic_ops_golang/riaksvc/dsutil.go | 78 +----- traffic_ops/traffic_ops_golang/routes.go | 11 +- 7 files changed, 267 insertions(+), 336 deletions(-) diff --git a/lib/go-tc/deliveryservice_ssl_keys.go b/lib/go-tc/deliveryservice_ssl_keys.go index 0bfb15c..bab764a 100644 --- a/lib/go-tc/deliveryservice_ssl_keys.go +++ b/lib/go-tc/deliveryservice_ssl_keys.go @@ -44,7 +44,7 @@ type DeliveryServiceSSLKeysCertificate struct { // DeliveryServiceSSLKeys ... type DeliveryServiceSSLKeys struct { CDN string `json:"cdn,omitempty"` - DeliveryService string `json:"DeliveryService,omitempty"` + DeliveryService string `json:"deliveryservice,omitempty"` BusinessUnit string `json:"businessUnit,omitempty"` City string `json:"city,omitempty"` Organization string `json:"organization,omitempty"` diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go index 649afb1..3e2a64f 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go @@ -652,7 +652,7 @@ func updateSSLKeys(ds *tc.DeliveryServiceNullable, hostName string, tx *sql.Tx, if ds.XMLID == nil { return errors.New("delivery services has no XMLID!") } - key, ok, err := riaksvc.GetDeliveryServiceSSLKeysObjTx(*ds.XMLID, "latest", tx, cfg.RiakAuthOptions) + key, ok, err := riaksvc.GetDeliveryServiceSSLKeysObj(*ds.XMLID, riaksvc.DSSSLKeyVersionLatest, tx, cfg.RiakAuthOptions) if err != nil { return errors.New("getting SSL key: " + err.Error()) } @@ -661,7 +661,7 @@ func updateSSLKeys(ds *tc.DeliveryServiceNullable, hostName string, tx *sql.Tx, } key.DeliveryService = *ds.XMLID key.Hostname = hostName - if err := riaksvc.PutDeliveryServiceSSLKeysObjTx(key, tx, cfg.RiakAuthOptions); err != nil { + if err := riaksvc.PutDeliveryServiceSSLKeysObj(key, tx, cfg.RiakAuthOptions); err != nil { return errors.New("putting updated SSL key: " + err.Error()) } return nil diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go index aac39dc..830ec07 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go @@ -27,7 +27,6 @@ import ( "encoding/json" "encoding/pem" "errors" - "fmt" "net/http" "strings" @@ -37,6 +36,10 @@ import ( "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant" ) +const ( + PemCertEndMarker = "-----END CERTIFICATE-----" +) + // AddSSLKeys adds the given ssl keys to the given delivery service. func AddSSLKeys(w http.ResponseWriter, r *http.Request) { inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) @@ -46,7 +49,7 @@ func AddSSLKeys(w http.ResponseWriter, r *http.Request) { } defer inf.Close() if !inf.Config.RiakEnabled { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("adding Riak SSL keys for delivery service:: riak is not configured")) + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("adding SSL keys to Riak for delivery service: Riak is not configured")) return } keysObj := tc.DeliveryServiceSSLKeys{} @@ -58,22 +61,28 @@ func AddSSLKeys(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } - certChain, err := verifyAndEncodeCertificate(keysObj.Certificate.Crt, "") + certChain, err := verifyCertificate(keysObj.Certificate.Crt, "") if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("verifying certificate: "+err.Error()), nil) return } keysObj.Certificate.Crt = certChain + base64EncodeCertificate(&keysObj.Certificate) + if err := riaksvc.PutDeliveryServiceSSLKeysObj(keysObj, inf.Tx.Tx, inf.Config.RiakAuthOptions); err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, nil, errors.New("putting SSL keys in Riak for delivery service '"+keysObj.DeliveryService+"': "+err.Error())) + return + } + keysObj.Version = riaksvc.DSSSLKeyVersionLatest if err := riaksvc.PutDeliveryServiceSSLKeysObj(keysObj, inf.Tx.Tx, inf.Config.RiakAuthOptions); err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, nil, errors.New("putting Riak SSL keys for delivery service '"+keysObj.DeliveryService+"': "+err.Error())) + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, nil, errors.New("putting latest SSL keys in Riak for delivery service '"+keysObj.DeliveryService+"': "+err.Error())) return } - api.WriteRespRaw(w, r, keysObj) + api.WriteResp(w, r, "Successfully added ssl keys for "+keysObj.DeliveryService) } // GetSSLKeysByHostName fetches the ssl keys for a deliveryservice specified by the fully qualified hostname func GetSSLKeysByHostName(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"hostName"}, nil) + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"hostname"}, nil) if userErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return @@ -81,12 +90,11 @@ func GetSSLKeysByHostName(w http.ResponseWriter, r *http.Request) { defer inf.Close() if inf.Config.RiakEnabled == false { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusServiceUnavailable, errors.New("The RIAK service is unavailable"), errors.New("getting Riak SSL keys by host name: riak is not configured")) + api.HandleErr(w, r, inf.Tx.Tx, http.StatusServiceUnavailable, errors.New("the Riak service is unavailable"), errors.New("getting SSL keys from Riak by host name: Riak is not configured")) return } - version := inf.Params["version"] - hostName := inf.Params["hostName"] + hostName := inf.Params["hostname"] domainName := "" hostRegex := "" strArr := strings.Split(hostName, ".") @@ -96,7 +104,7 @@ func GetSSLKeysByHostName(w http.ResponseWriter, r *http.Request) { domainName += strArr[i] + "." } domainName += strArr[ln-1] - hostRegex = ".*\\." + strArr[1] + "\\..*" + hostRegex = `.*\.` + strArr[1] + `\..*` } // lookup the cdnID @@ -120,36 +128,28 @@ func GetSSLKeysByHostName(w http.ResponseWriter, r *http.Request) { return } - if userErr, sysErr, errCode := tenant.Check(inf.User, xmlID, inf.Tx.Tx); userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - keyObj, ok, err := riaksvc.GetDeliveryServiceSSLKeysObj(xmlID, version, inf.Tx.Tx, inf.Config.RiakAuthOptions) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting ssl keys: "+err.Error())) - return - } - if !ok { - api.WriteRespAlert(w, r, tc.InfoLevel, "no object found for the specified key") - return - } - api.WriteResp(w, r, keyObj) + getSSLKeysByXMLIDHelper(xmlID, inf, w, r) } // GetSSLKeysByXMLID fetches the deliveryservice ssl keys by the specified xmlID. func GetSSLKeysByXMLID(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"xmlID"}, nil) + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"xmlid"}, nil) if userErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } defer inf.Close() if inf.Config.RiakEnabled == false { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusServiceUnavailable, errors.New("The RIAK service is unavailable"), errors.New("getting Riak SSL keys by xml id: riak is not configured")) + api.HandleErr(w, r, inf.Tx.Tx, http.StatusServiceUnavailable, errors.New("the Riak service is unavailable"), errors.New("getting SSL keys from Riak by xml id: Riak is not configured")) return } + xmlID := inf.Params["xmlid"] + getSSLKeysByXMLIDHelper(xmlID, inf, w, r) +} + +func getSSLKeysByXMLIDHelper(xmlID string, inf *api.APIInfo, w http.ResponseWriter, r *http.Request) { version := inf.Params["version"] - xmlID := inf.Params["xmlID"] + decode := inf.Params["decode"] if userErr, sysErr, errCode := tenant.Check(inf.User, xmlID, inf.Tx.Tx); userErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return @@ -160,29 +160,65 @@ func GetSSLKeysByXMLID(w http.ResponseWriter, r *http.Request) { return } if !ok { - api.WriteRespAlert(w, r, tc.InfoLevel, "no object found for the specified key") + api.WriteRespAlertObj(w, r, tc.InfoLevel, "no object found for the specified key", struct{}{}) // empty response object because Perl return } + if decode != "" && decode != "0" { // the Perl version checked the decode string as: if ( $decode ) + err = base64DecodeCertificate(&keyObj.Certificate) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting SSL keys for XMLID '"+xmlID+"': "+err.Error())) + return + } + } api.WriteResp(w, r, keyObj) } +func base64DecodeCertificate(cert *tc.DeliveryServiceSSLKeysCertificate) error { + csrDec, err := base64.StdEncoding.DecodeString(cert.CSR) + if err != nil { + return errors.New("base64 decoding csr: " + err.Error()) + } + cert.CSR = string(csrDec) + crtDec, err := base64.StdEncoding.DecodeString(cert.Crt) + if err != nil { + return errors.New("base64 decoding crt: " + err.Error()) + } + cert.Crt = string(crtDec) + keyDec, err := base64.StdEncoding.DecodeString(cert.Key) + if err != nil { + return errors.New("base64 decoding key: " + err.Error()) + } + cert.Key = string(keyDec) + return nil +} + +func base64EncodeCertificate(cert *tc.DeliveryServiceSSLKeysCertificate) { + cert.CSR = base64.StdEncoding.EncodeToString([]byte(cert.CSR)) + cert.Crt = base64.StdEncoding.EncodeToString([]byte(cert.Crt)) + cert.Key = base64.StdEncoding.EncodeToString([]byte(cert.Key)) +} + func DeleteSSLKeys(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"name"}, nil) + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"xmlid"}, nil) if userErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } defer inf.Close() if inf.Config.RiakEnabled == false { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, userErr, errors.New("deliveryservice.DeleteSSLKeys: Riak is not configured!")) + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, userErr, errors.New("deliveryservice.DeleteSSLKeys: Riak is not configured")) + return + } + xmlID := inf.Params["xmlid"] + if userErr, sysErr, errCode := tenant.Check(inf.User, xmlID, inf.Tx.Tx); userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } - ds := tc.DeliveryServiceName(inf.Params["name"]) - if err := riaksvc.DeleteDSSSLKeys(inf.Tx.Tx, inf.Config.RiakAuthOptions, ds, inf.Params["version"]); err != nil { + if err := riaksvc.DeleteDSSSLKeys(inf.Tx.Tx, inf.Config.RiakAuthOptions, xmlID, inf.Params["version"]); err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, userErr, errors.New("deliveryservice.DeleteSSLKeys: deleting SSL keys: "+err.Error())) return } - api.WriteResp(w, r, "Successfully deleted ssl keys for "+string(ds)) + api.WriteResp(w, r, "Successfully deleted ssl keys for "+xmlID) } // returns the cdn_id found by domainname. @@ -216,86 +252,64 @@ WHERE r.pattern = $2 } // verify the server certificate chain and return the -// certificate and its chain in the proper order. Returns a verified, -// ordered, and base64 encoded certificate and CA chain. -func verifyAndEncodeCertificate(certificate string, rootCA string) (string, error) { - var pemEncodedChain string - var b64crt string +// certificate and its chain in the proper order. Returns a verified +// and ordered certificate and CA chain. +func verifyCertificate(certificate string, rootCA string) (string, error) { + // decode, verify, and order certs for storage + certs := strings.SplitAfter(certificate, PemCertEndMarker) + if len(certs) <= 1 { + return "", errors.New("no certificate chain to verify") + } - // strip newlines from encoded crt and decode it from base64. - crtArr := strings.Split(certificate, "\\n") - for i := 0; i < len(crtArr); i++ { - b64crt += crtArr[i] + // decode and verify the server certificate + block, _ := pem.Decode([]byte(certs[0])) + if block == nil { + return "", errors.New("could not decode pem-encoded server certificate") } - pemCerts := make([]byte, base64.StdEncoding.EncodedLen(len(b64crt))) - _, err := base64.StdEncoding.Decode(pemCerts, []byte(b64crt)) + cert, err := x509.ParseCertificate(block.Bytes) if err != nil { - return "", fmt.Errorf("could not base64 decode the certificate %v", err) + return "", errors.New("could not parse the server certificate: " + err.Error()) + } + if !(cert.KeyUsage&x509.KeyUsageKeyEncipherment > 0) { + return "", errors.New("no key encipherment usage for the server certificate") + } + bundle := "" + for i := 0; i < len(certs)-1; i++ { + bundle += certs[i] } - // decode, verify, and order certs for storgae - var bundle string - certs := strings.SplitAfter(string(pemCerts), "-----END CERTIFICATE-----") - if len(certs) > 1 { - // decode and verify the server certificate - block, _ := pem.Decode([]byte(certs[0])) - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return "", fmt.Errorf("could not parse the server certificate %v", err) - } - if !(cert.KeyUsage&x509.KeyUsageKeyEncipherment > 0) { - return "", fmt.Errorf("no key encipherment usage for the server certificate") - } - for i := 0; i < len(certs)-1; i++ { - bundle += certs[i] - } - - var opts x509.VerifyOptions + intermediatePool := x509.NewCertPool() + if !intermediatePool.AppendCertsFromPEM([]byte(bundle)) { + return "", errors.New("certificate CA bundle is empty") + } + opts := x509.VerifyOptions{ + Intermediates: intermediatePool, + } + if rootCA != "" { + // verify the certificate chain. rootPool := x509.NewCertPool() - if rootCA != "" { - if !rootPool.AppendCertsFromPEM([]byte(rootCA)) { - return "", fmt.Errorf("root CA certificate is empty, %v", err) - } - } - - intermediatePool := x509.NewCertPool() - if !intermediatePool.AppendCertsFromPEM([]byte(bundle)) { - return "", fmt.Errorf("certificate CA bundle is empty, %v", err) - } - - if rootCA != "" { - // verify the certificate chain. - opts = x509.VerifyOptions{ - Intermediates: intermediatePool, - Roots: rootPool, - } - } else { - opts = x509.VerifyOptions{ - Intermediates: intermediatePool, - } + if !rootPool.AppendCertsFromPEM([]byte(rootCA)) { + return "", errors.New("unable to parse root CA certificate") } + opts.Roots = rootPool + } - chain, err := cert.Verify(opts) - if err != nil { - return "", fmt.Errorf("could verify the certificate chain %v", err) - } - if len(chain) > 0 { - for _, link := range chain[0] { - // Only print non-self signed elements of the chain - if link.AuthorityKeyId != nil && !bytes.Equal(link.AuthorityKeyId, link.SubjectKeyId) { - block := &pem.Block{Type: "CERTIFICATE", Bytes: link.Raw} - pemEncodedChain += string(pem.EncodeToMemory(block)) - } - } - } else { - return "", fmt.Errorf("Can't find valid chain for cert in file in request") + chain, err := cert.Verify(opts) + if err != nil { + return "", errors.New("could not verify the certificate chain: " + err.Error()) + } + if len(chain) < 1 { + return "", errors.New("can't find valid chain for cert in file in request") + } + pemEncodedChain := "" + for _, link := range chain[0] { + // Only print non-self signed elements of the chain + if link.AuthorityKeyId != nil && !bytes.Equal(link.AuthorityKeyId, link.SubjectKeyId) { + block := &pem.Block{Type: "CERTIFICATE", Bytes: link.Raw} + pemEncodedChain += string(pem.EncodeToMemory(block)) } - } else { - return "", fmt.Errorf("ERROR: no certificate chain to verify") } - base64EncodedStr := base64.StdEncoding.EncodeToString([]byte(pemEncodedChain)) - - return base64EncodedStr, nil + return pemEncodedChain, nil } diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/keys_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/keys_test.go index 8c92aa9..14b30a3 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/keys_test.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/keys_test.go @@ -20,153 +20,143 @@ package deliveryservice */ import ( - "encoding/base64" "strings" "testing" ) const ( - BadData = "This is bad data and it is not base64 encoded" + BadData = "This is bad data and it is not pem encoded" SelfSigneCertOnly = ` -LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURrakNDQW5vQ0NRQ2Z3ZDIxOUpLcFVEQU5C -Z2txaGtpRzl3MEJBUXNGQURDQmlqRUxNQWtHQTFVRUJoTUMKVlZNeEVUQVBCZ05WQkFnTUNFTnZi -Rzl5WVdSdk1ROHdEUVlEVlFRSERBWkVaVzUyWlhJeEVEQU9CZ05WQkFvTQpCME52YldOaGMzUXhE -akFNQmdOVkJBc01CWFpwY0dWeU1SVXdFd1lEVlFRRERBeDNkM2N1ZEdWemRDNXZjbWN4CkhqQWNC -Z2txaGtpRzl3MEJDUUVXRDIxcFkydGxlVUIwWlhOMExtOXlaekFlRncweE56RXhNVFl4TmpNNU1U -TmEKRncweU56RXhNVFF4TmpNNU1UTmFNSUdLTVFzd0NRWURWUVFHRXdKVlV6RVJNQThHQTFVRUNB -d0lRMjlzYjNKaApaRzh4RHpBTkJnTlZCQWNNQmtSbGJuWmxjakVRTUE0R0ExVUVDZ3dIUTI5dFky -RnpkREVPTUF3R0ExVUVDd3dGCmRtbHdaWEl4RlRBVEJnTlZCQU1NREhkM2R5NTBaWE4wTG05eVp6 -RWVNQndHQ1NxR1NJYjNEUUVKQVJZUGJXbGoKYTJWNVFIUmxjM1F1YjNKbk1JSUJJakFOQmdrcWhr -aUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBNm02agppQU1KcExNeFpoSVVFOURyUHZuaTA2 -TmViTFJhaDFSUzgrUjNKM1BOMVhDY2d3d1VaaWZaQnBQZ2FTYXBGWGJ2ClpkamxyZlpGcGtBZmdG -UVhseUdhZTZCVStuNEN3TmxEZGdvMVhiRWM4cW9HSWRzN2FZSEVrclFadFp5aCtYRlkKMkJSdlM2 -Y2JHR0VjMngwdzVJa1hhMTM2V0NKY0x0QnpkVDdGQVZRSlZodTl4UFBuWGs4aWdUWmc2dEZ0MjdF -YgplYkhDVWVEQXJHVVJGaUZXZFhtOGRHQ1BVVkNaeFNDdnh1WGxJMTVkZGdmcDlHYkZYeENVUDVW -UjlQajZCdHFlCjdReW1GUk9zSWtscEN3NDZlYTBBODdpa1ZNYWRQQzIxVHViZmF5VUFNUTh4bDYw -aW94NVk4OWc0WEZyRWdreWQKV3hobVBXclZwVlFyUERXS2d3SURBUUFCTUEwR0NTcUdTSWIzRFFF -QkN3VUFBNElCQVFDWlpXNnJsbi9LYkdWbgpVWis3RFY5UFVCNHIyNUI5d3JSQUx5OHJRdzNVQWVQ -SFJxWmlBT1ZOV3hycjM5Njd6ZFBNYzhkMWtZY2t0K2NHCkcxUFU1QmNiVVZuUzJxRTBUa2Z2cWo2 -citPaWNrcnU2dEtmWERhcU1PRXRmTmFud1Q5dURaQ1RlT2FpU0hCengKRnBQLzlURDA1Z0VZYmQx -VzRSUnpUNi9TN3lMWTB1WWhWQUhGZGwyZTd6T2podHk0aURQRjA0ZmQrWHlaKzNXUwpJeFNnYU1y -VHAwMG1hRnRicFBuaFRheElHek1ZdG9kaTZGYVVjT21CZk1scFJHS3lrc04wWjNlSjZSNm5oTWIz -ClJHSjludUdMS3ZxUzV1OUJnZ2NEd28xcXYyazhNY2RTZzZwbEs2WG9kTHlNZEJWVEI2Szdod1N6 -MXVWcDNtWDEKSFZHRTQrb3UKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=` +-----BEGIN CERTIFICATE----- +MIIDkjCCAnoCCQCfwd219JKpUDANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMC +VVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxEDAOBgNVBAoM +B0NvbWNhc3QxDjAMBgNVBAsMBXZpcGVyMRUwEwYDVQQDDAx3d3cudGVzdC5vcmcx +HjAcBgkqhkiG9w0BCQEWD21pY2tleUB0ZXN0Lm9yZzAeFw0xNzExMTYxNjM5MTNa +Fw0yNzExMTQxNjM5MTNaMIGKMQswCQYDVQQGEwJVUzERMA8GA1UECAwIQ29sb3Jh +ZG8xDzANBgNVBAcMBkRlbnZlcjEQMA4GA1UECgwHQ29tY2FzdDEOMAwGA1UECwwF +dmlwZXIxFTATBgNVBAMMDHd3dy50ZXN0Lm9yZzEeMBwGCSqGSIb3DQEJARYPbWlj +a2V5QHRlc3Qub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6m6j +iAMJpLMxZhIUE9DrPvni06NebLRah1RS8+R3J3PN1XCcgwwUZifZBpPgaSapFXbv +ZdjlrfZFpkAfgFQXlyGae6BU+n4CwNlDdgo1XbEc8qoGIds7aYHEkrQZtZyh+XFY +2BRvS6cbGGEc2x0w5IkXa136WCJcLtBzdT7FAVQJVhu9xPPnXk8igTZg6tFt27Eb +ebHCUeDArGURFiFWdXm8dGCPUVCZxSCvxuXlI15ddgfp9GbFXxCUP5VR9Pj6Btqe +7QymFROsIklpCw46ea0A87ikVMadPC21TubfayUAMQ8xl60iox5Y89g4XFrEgkyd +WxhmPWrVpVQrPDWKgwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCZZW6rln/KbGVn +UZ+7DV9PUB4r25B9wrRALy8rQw3UAePHRqZiAOVNWxrr3967zdPMc8d1kYckt+cG +G1PU5BcbUVnS2qE0Tkfvqj6r+Oickru6tKfXDaqMOEtfNanwT9uDZCTeOaiSHBzx +FpP/9TD05gEYbd1W4RRzT6/S7yLY0uYhVAHFdl2e7zOjhty4iDPF04fd+XyZ+3WS +IxSgaMrTp00maFtbpPnhTaxIGzMYtodi6FaUcOmBfMlpRGKyksN0Z3eJ6R6nhMb3 +RGJ9nuGLKvqS5u9BggcDwo1qv2k8McdSg6plK6XodLyMdBVTB6K7hwSz1uVp3mX1 +HVGE4+ou +-----END CERTIFICATE----- +` GoodTLSKeys = ` -LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUYzRENDQThTZ0F3SUJBZ0lDRUFBd0RRWUpL -b1pJaHZjTkFRRUxCUUF3Z1l3eEN6QUpCZ05WQkFZVEFsVlQKTVJFd0R3WURWUVFJREFoRGIyeHZj -bUZrYnpFa01DSUdBMVVFQ2d3YlNYQmpaRzRnUTJWeWRHbG1hV05oZEdVZwpRWFYwYUc5eWFYUjVN -U1F3SWdZRFZRUUxEQnRKY0dOa2JpQkRaWEowYVdacFkyRjBaU0JCZFhSb2IzSnBkSGt4CkhqQWNC -Z05WQkFNTUZVbHdZMlJ1SUVsdWRHVnliV1ZrYVdGMFpTQkRRVEFlRncweE56RXhNVFl5TURVd01U -bGEKRncweU5qQXlNREl5TURVd01UbGFNSEF4Q3pBSkJnTlZCQVlUQWxWVE1SRXdEd1lEVlFRSURB -aERiMnh2Y21GawpiekVQTUEwR0ExVUVCd3dHUkdWdWRtVnlNUTh3RFFZRFZRUUtEQVpKY0dOa2Jp -QXhFakFRQmdOVkJBc01DVWx3ClkyUnVJR1JsZGpFWU1CWUdBMVVFQXd3UGQzZDNMbVY0WVcxd2JH -VXVZMjl0TUlJQklqQU5CZ2txaGtpRzl3MEIKQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBMWg5Zlh3 -SjE3UVlOTVhNNWpFZ0hlY1Z4V20rQ05QUWJpMk8wYUxtNQpsYUV1b2N0bC94bmNsRGdrT2NWNTBz -SWdVUlNuYVJYSENNYTkzemI5NXhRZWZUSXZSRi8xa1U5ZTY4bW1Gano4CmZyQWRPYU05MFRkVWYx -eXYyNlczN25UOUR4MjZDQWlTd0FMWFZkeCs0b1Bvck5IdjIweDNxUGJzKzNjRHVuQWYKL1hWWis2 -dHhuOVZzZzAwb2RIWm9mcVpibUdUdkcveWJRY0dQalJQYVdZSGFMYWZqcERCM3dQc0REdTBMMGJY -VApITlE4Vld6S3drOFVRdHpndEt4WEFFMlp3TFNkUVdzV0VLSEZGTmthT3F3ZnEyMVVZdmN0NHEy -cnE5MXREbmJhCmZxTWpOTnNxVmtYZFIyNFdjUDhtZ1YxY3NWTkx1WmhtUWFCTlo2dytoR2l0aVFJ -REFRQUJvNElCWVRDQ0FWMHcKQ1FZRFZSMFRCQUl3QURBUkJnbGdoa2dCaHZoQ0FRRUVCQU1DQmtB -d013WUpZSVpJQVliNFFnRU5CQ1lXSkU5dwpaVzVUVTB3Z1IyVnVaWEpoZEdWa0lGTmxjblpsY2lC -RFpYSjBhV1pwWTJGMFpUQWRCZ05WSFE0RUZnUVVpWmVaCjVVbXdGRjFWQ2dwd1hmM2crcTFGUzZN -d2djTUdBMVVkSXdTQnV6Q0J1SUFVSVY3aXF0Myt4WWExRngvbll6eGQKMWh4dk53MmhnWnVrZ1pn -d2daVXhDekFKQmdOVkJBWVRBbFZUTVJFd0R3WURWUVFJREFoRGIyeHZjbUZrYnpFUApNQTBHQTFV -RUJ3d0dSR1Z1ZG1WeU1TUXdJZ1lEVlFRS0RCdEpjR05rYmlCRFpYSjBhV1pwWTJGMFpTQkJkWFJv -CmIzSnBkSGt4SkRBaUJnTlZCQXNNRzBsd1kyUnVJRU5sY25ScFptbGpZWFJsSUdGMWRHaHZjbWww -ZVRFV01CUUcKQTFVRUF3d05TWEJqWkc0Z1VtOXZkQ0JEUVlJQ0VBQXdEZ1lEVlIwUEFRSC9CQVFE -QWdXZ01CTUdBMVVkSlFRTQpNQW9HQ0NzR0FRVUZCd01CTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElD -QVFCSFgrWW1PNlRMYk1UWkJ5dXdyZ2lICnV5N3JQenBYVFhTSnZVTSt1cHhTZW5ybXo4SDVmQnp5 -cExmS0UxTk9LNThScjI1T3lBZDFJcWdHMUlkKzh5Y2cKYXRldGpJMXBvYU5DWnZLUFJyNDh3amNU -VnNmZ3g3SFhXMGZ5a0kwS1QyMzlPazR1c0VNM3VxbWJrbjdBbm5mNwpkYnk4Qkt6NHA5bjF5ZTdl -SHBpcHUxQTVXeGpCUWVEeW1sUHcvQ005RTB2NUYvaGdpV3hXK2U4bDV1N2g0M2JQCjFCTk82UEpY -Si9HT05pUWg0YmNMMFI3NjN0TklLR1ZOWUhTZmZXNzF2eW9ZNnJlWGRLQk5uazkxT2dqRndIVE4K -QUxKdHI5ank1eHk5dktBVFk3Z1RoVldEaFcvdWl2OExVSUN4eFpXbkN1Y0t2aGdCMmJMV2FIeElP -cjlZeWphWApoRVZkSGdUVlhQbDVjTVhCdytVWkxuM3Z3NUVRSHVNRkFJV1IyWk5JaHJFdmphUG9u -NXFhRXZlUUNEc2RhMzl3CnJUcjNsaGV5QkdNaFRubUE0d05WK2xOU0xJbUFjMm83OGFldGUwY1Rz -SjYxOXNuR09EVW85UU8yWVovazd2UFMKbUhlSzBWclp2bkU4dWtsRkpVVERheUxvRCtTbnN4enRS -SVR2ay9FdVJheTJQMWZHMS9xWm43UVVJZytaTTRhVgpTOHZQcmZJOXJ3dThDUmd4bVRUdFg1Wk1l -ejg5Znk2RmxWRElUalZOaFJyVkljMm0zeEwwM0FTVGI2TmFua0pvCkVuK01MUXBDRlZLeVF5OU43 -MlJaTno2OEtpUEgvZnJhQWRxNG1MNU5BY1I0UC82R2c3U24rcWpuZi80bDQzeloKKzd1enFqd2hk -dFUvWUhKOWo4L2Nvdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJU -SUZJQ0FURS0tLS0tCk1JSUdCVENDQSsyZ0F3SUJBZ0lDRUFBd0RRWUpLb1pJaHZjTkFRRUxCUUF3 -Z1pVeEN6QUpCZ05WQkFZVEFsVlQKTVJFd0R3WURWUVFJREFoRGIyeHZjbUZrYnpFUE1BMEdBMVVF -Qnd3R1JHVnVkbVZ5TVNRd0lnWURWUVFLREJ0SgpjR05rYmlCRFpYSjBhV1pwWTJGMFpTQkJkWFJv -YjNKcGRIa3hKREFpQmdOVkJBc01HMGx3WTJSdUlFTmxjblJwClptbGpZWFJsSUdGMWRHaHZjbWww -ZVRFV01CUUdBMVVFQXd3TlNYQmpaRzRnVW05dmRDQkRRVEFlRncweE56RXgKTVRZeU1ETTRNVFZh -RncweU56RXhNVFF5TURNNE1UVmFNSUdNTVFzd0NRWURWUVFHRXdKVlV6RVJNQThHQTFVRQpDQXdJ -UTI5c2IzSmhaRzh4SkRBaUJnTlZCQW9NRzBsd1kyUnVJRU5sY25ScFptbGpZWFJsSUVGMWRHaHZj -bWwwCmVURWtNQ0lHQTFVRUN3d2JTWEJqWkc0Z1EyVnlkR2xtYVdOaGRHVWdRWFYwYUc5eWFYUjVN -UjR3SEFZRFZRUUQKREJWSmNHTmtiaUJKYm5SbGNtMWxaR2xoZEdVZ1EwRXdnZ0lpTUEwR0NTcUdT -SWIzRFFFQkFRVUFBNElDRHdBdwpnZ0lLQW9JQ0FRREtreFE5aGRNdFN3Zk5FQnFHUEFLenU3Q1Bo -SCt4dTNrTDRlK3JKMDlFQisrZnhHOTA1bXdNClltOHMxR0MvaTFWMWhQYk9rK3pMV1hjY1podEM0 -OUJ0dHNEQlZSbWFndDRxNmVlRDEyQXh6VkJveldqNFluRnkKWkUwNENpWmxBSU4zcU40VG5OVC9P -M2l5dm1qNThRRElGVk81MVlOU3JyN2oyZFFSTHBveVM2czg3a3B3OUE2VAoyNEwxcExrbUZ1QWdD -TE1GUEc1SFpXeVpTU3RwWE96T2M3TElUWlFYUXp1bndMYXpOOVo0QXo4ellDOWlsTzZWCnROTk5j -K1k3TXBGclRhRUZGU3NNMitSRWV4dVB0Q090VC9aRWNPd1A4ODRUNkFDY1VTVHY4NmlFL0VGT1Bn -WmgKdlRLMkQwTnphdDNaVHNjNU4ydnZOMGVabTZDT25WQXZZTndyVFdHNHYzWVV0THIvUEVvRm05 -bXRQZFNBK1RzaQpMa0dGUmp3QW9BbmhWaWVGQUZ1bFFuc3diWkFhSlJjL3hTN0JKdnRzLzNKOWk3 -bDFvcHF1MEVibTZMOGpMZUh2CnAvb3AxVEQ4TElRa2NwN0dkc1hrNExZSDZWb3BhTk9pOHlvYUVm -S1d3clhoeEJkQ2hISGxZQ2NmZWN5RDhPMzkKOGV1b0dRMHppL2VhQ3IzTUhZVTErYTIrNVRSUGR6 -SHgvbC8zVjF3eFg5aG1PR2Nyd1RDdGNZUnpSanphMlVsRQpVYWNtd0JxR3lmYTFHbi9pT3RvOXlx -dFJ2V2xQR3E5ekpOUXpOQko2aVdSN2d5YjB0ODIwanBkdG1GeU5CNVB2CkpjZUh5VnlpeC83c0JV -QjVHd01PMGlHbkxQcFk5SXg4N25ZUDNpdE4zamtxYWhUclQ3dEh1d0lEQVFBQm8yWXcKWkRBZEJn -TlZIUTRFRmdRVUlWN2lxdDMreFlhMUZ4L25ZenhkMWh4dk53MHdId1lEVlIwakJCZ3dGb0FVV2Nk -SQpCUjZ0M044dzd5VXQvVE9mWHdtdWJBZ3dFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFPQmdO -VkhROEJBZjhFCkJBTUNBWVl3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUtiZDhuS0ExaGx4Q1Rj -elRnOVBIU2RxSFZFWVdxUkQKZHpyb09uejBLY0lnSkY0bmNSYU4zbm5hNzR3SW0zbkpEL1d1d2RG -OGRWYnBENFlhZVpLaFFnbVhtWWdnVkgwbAo4SUlrZEMzcVlOVERqNHhoZzdxWkMzcHpLdU5JZzd5 -SHYzQXhyL3JiOUJ2cFVKeGJLby9hbG4vRzZQcmJOb1F4CjR4YjZ4bEs2V2NZT1JCL1VnekY2aktN -NUNCWGFVdDJRUW1XdHM3TWdub056WjRreHNZYnV3ajNyT1B6SElnUlkKWXdKdCtraDV0bHloSFBr -Z2p3VVRiTWcvTXYvb21melozK1hNQkQyWjY5ZU5kOVJIRTlYUzZBZE1LdERpcHJoZwpUTHUrOXRP -Vmx5ZFFqWmszS3NxRnY1Ny8xVHdLcnQ5SkZqWGsvUFBRYWxYb1Azb3lrb2l2aWFjZDRNeWlCbWFX -Cmk1V2J0Y3hHUDV2d2FObjI2N09HbGliZlFtUWdBMVZJZ0RxZnRyUXdlNlQvWndPS1hxRnFiNTZn -cWRXNFZkT1UKNWJHTjB1TFhlbXJpdU5Rb2xUcFZtaE51MGJOSTVXQ2lZN2MzMFhOMk01ZWJjQldE -SE9yNERPQ2crbW1YK3RKVApHaTFqdmZtQTZMT1lueVE4eEFWMDgwdVZlMzVOUTEwZTlteTZMdVhN -aVE3dWJ2SFVTNU53YVFDRnBMREl2YmhmCjJYVmM0Nk5kMFYvYm9sTTIzQVFSbHp1bzBJU3NEU2VX -U1FWTSt1UVorcExlOUc1bjJNeHFGZDg0VnU0ODlhdWMKMDJJOWNIaU5haFd5WFB0dm52TVd3SW1J -QjMwQ1loaDVCeUdXbmVEbzNkNVpqMkhBQTZGd082N0xsSWhTZTdxegpheER0Y000bk8zUUgKLS0t -LS1FTkQgQ0VSVElGSUNBVEUtLS0tLQotLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJR0Vq -Q0NBL3FnQXdJQkFnSUpBTDJWMWhuTFd1emdNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1JR1ZNUXN3Q1FZ -RApWUVFHRXdKVlV6RVJNQThHQTFVRUNBd0lRMjlzYjNKaFpHOHhEekFOQmdOVkJBY01Ca1JsYm5a -bGNqRWtNQ0lHCkExVUVDZ3diU1hCalpHNGdRMlZ5ZEdsbWFXTmhkR1VnUVhWMGFHOXlhWFI1TVNR -d0lnWURWUVFMREJ0SmNHTmsKYmlCRFpYSjBhV1pwWTJGMFpTQmhkWFJvYjNKcGRIa3hGakFVQmdO -VkJBTU1EVWx3WTJSdUlGSnZiM1FnUTBFdwpIaGNOTVRjeE1URTJNakF4T1RJMFdoY05NamN4TVRF -ME1qQXhPVEkwV2pDQmxURUxNQWtHQTFVRUJoTUNWVk14CkVUQVBCZ05WQkFnTUNFTnZiRzl5WVdS -dk1ROHdEUVlEVlFRSERBWkVaVzUyWlhJeEpEQWlCZ05WQkFvTUcwbHcKWTJSdUlFTmxjblJwWm1s -allYUmxJRUYxZEdodmNtbDBlVEVrTUNJR0ExVUVDd3diU1hCalpHNGdRMlZ5ZEdsbQphV05oZEdV -Z1lYVjBhRzl5YVhSNU1SWXdGQVlEVlFRRERBMUpjR05rYmlCU2IyOTBJRU5CTUlJQ0lqQU5CZ2tx -CmhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBMkhzTGtubTl3eDFWMzN6dHRNV24xc0ZF -SnlMTk1lZVcKaEEwZHdlS0NocURWdzVsQXlJaWQwbHFqbmpJRWl2bEFNOHpkMk5BUFpUSEZHOUJU -M3VuZHVOY0RjV3VSZ2gzMwptbHlzc0VoYXJXdEU2VTdsenc4Uk1HV2t5V1FrSjJFMFN4akUvVm1L -UWpIMy80QWs4U2hoVFpGS0VadXdlUmRnCmoyMThxVWVtc09WK0VOVHNuR1V4b0FQcHI1Y0dHbzRp -Z2ZPOXRwSTFnN1BXbmtZclZGdHdzUG95MkNLeHFoL0kKM0Y2N0VacTJ5Uk9CeTlnQmhDNUZ1Wmh1 -dmdwdHZTYWtOUXkvdys5YnVSZmZzaGI2OXdMc2JxUWF5aGpsTHFjUQpYa2NoNHk2Y0c4WWxnK2hy -cXptRzN2RzRxMDNZRno4UHVNZC91TnhlSlFBYlpKL3NYaDdqVlFOSml0Z2k2b095CmoxeFZ6TktV -cWIxTXgyNURoQnkxMjUvbDh3R0Jkc0NSVVlSNnI3ME5BWlJuZGprUUtWNGwzdFRRNCtvMnJkamQK -Qy8yU2syL2JIVmJ5dE1xNEl6KzFzT3NJVVYzTGErUitEV2NMQTZVb25wRW9jTmI4YnQ1YnU0SmhS -RHJuSndQaQo0alJhSCtLZ3hwQlRJbFozYUZuU3MwSTN3a1BPZ0EzRHJJNkxXeGd0M0JmUHVLOURQ -bWExcmM0QjFjd1NRVE5rCnl5VVkyYm9Yc25MTU1QZWJGM2Rqd2J0WUVkcUFhaXh4enpRQ1ZZbGhm -Q0VRanFjZVNkNlRKMlNkekg0STNKN3gKT3NMejFMai85MmFEZWVxNzdxL2M5RmlkbmZOZTc1ekI3 -Z1hOL2JUdGI1NVpjVFRPOFhBRXZlZ3hjcnc1Q25PMQpNbkZ3NnhEZisrVUNBd0VBQWFOak1HRXdI -UVlEVlIwT0JCWUVGRm5IU0FVZXJkemZNTzhsTGYwem4xOEpybXdJCk1COEdBMVVkSXdRWU1CYUFG -Rm5IU0FVZXJkemZNTzhsTGYwem4xOEpybXdJTUE4R0ExVWRFd0VCL3dRRk1BTUIKQWY4d0RnWURW -UjBQQVFIL0JBUURBZ0dHTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElDQVFCK2FZV1poKy9nQ21GeQpv -STU4MkFDNSs1NGM3eGVMUm10UVBtRnBBQTJZNGlUU2VHeWp2Mmc1TTlhZ1E0SXh3dWU1RGx1UWxz -RGNEc0p3CmxzWFZIeUFsQ2Y2bkRiSk9wZjVtNml4ZnZCRitRekVlWGpVRzc0aVNDV3JBcFlGbXNO -c0NybHNNL0VQSm9ncXUKN2NnR3ZId1dZTmpQenV3b1UwQVdITzlGZ0liRHozTWMxNVpsSElRMlQv -aXh4eUJmcElQYzVEOTFrNHUvWldTYgpoTzZsc00xaUVBdnY5VG9VWGtLdHRDUTBmTjM0QndZWDQ3 -QXhDNzF0U3Q5L1ZLSURJRVhqenFKSnBiSmRCR1NtCld1aVpIV3dCelZ3N2s1eStTK3dNNXZaL09N -dzB0aEwrSTUzem1reEdFZjN0TUg0UE9lS3ZYL3UxOW4wVEZrLzEKOTZjenRWbVRCNlc2N21iOXpa -OHlTZUp0RDl1WEU0NHljeGs5M2dUbmFya0lHZFRmOFhvbkNWbGtIR3BiNlRXTQo1RTdhV0NvVmJx -UVJrRjFycVB6YThoUkpHSDFFR2ttZGJrdXlVTUxtUTFGSks5Zi94RkljcmRCR3BNbHh5T29MCk1T -ZUZlYWdvU0xOcTV4emp1Y0YvNm1rQVVKOFVaQXhyU0dtYURFYVN0MS9xSlJIb3ZIY3QyYjVGV1pM -dVJ0MGcKZkRzR29LVDhJRnVhcFNzbUZFSW52RDBXS1hEU1BsVThyVHkxWkJEbENxcDNlTkg0dENm -TGFoazd2TEEwSmJmNwpVTHJ0RG5yTmZqeFVZRmNGMFJjYXVSMWRsclFRUkFzMzFRT3pYK0pPcjRn -VUc3eVhhM2o2NG54Mkc1TXBMZ1NvCk9FVWpmYWtLNzErVi9IYlF0NDc3elI0azdjUmJpQT09Ci0t -LS0tRU5EIENFUlRJRklDQVRFLS0tLS0K` - +-----BEGIN CERTIFICATE----- +MIIF3DCCA8SgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVT +MREwDwYDVQQIDAhDb2xvcmFkbzEkMCIGA1UECgwbSXBjZG4gQ2VydGlmaWNhdGUg +QXV0aG9yaXR5MSQwIgYDVQQLDBtJcGNkbiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkx +HjAcBgNVBAMMFUlwY2RuIEludGVybWVkaWF0ZSBDQTAeFw0xNzExMTYyMDUwMTla +Fw0yNjAyMDIyMDUwMTlaMHAxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhDb2xvcmFk +bzEPMA0GA1UEBwwGRGVudmVyMQ8wDQYDVQQKDAZJcGNkbiAxEjAQBgNVBAsMCUlw +Y2RuIGRldjEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA1h9fXwJ17QYNMXM5jEgHecVxWm+CNPQbi2O0aLm5 +laEuoctl/xnclDgkOcV50sIgURSnaRXHCMa93zb95xQefTIvRF/1kU9e68mmFjz8 +frAdOaM90TdUf1yv26W37nT9Dx26CAiSwALXVdx+4oPorNHv20x3qPbs+3cDunAf +/XVZ+6txn9Vsg00odHZofqZbmGTvG/ybQcGPjRPaWYHaLafjpDB3wPsDDu0L0bXT +HNQ8VWzKwk8UQtzgtKxXAE2ZwLSdQWsWEKHFFNkaOqwfq21UYvct4q2rq91tDnba +fqMjNNsqVkXdR24WcP8mgV1csVNLuZhmQaBNZ6w+hGitiQIDAQABo4IBYTCCAV0w +CQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgENBCYWJE9w +ZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUiZeZ +5UmwFF1VCgpwXf3g+q1FS6MwgcMGA1UdIwSBuzCBuIAUIV7iqt3+xYa1Fx/nYzxd +1hxvNw2hgZukgZgwgZUxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhDb2xvcmFkbzEP +MA0GA1UEBwwGRGVudmVyMSQwIgYDVQQKDBtJcGNkbiBDZXJ0aWZpY2F0ZSBBdXRo +b3JpdHkxJDAiBgNVBAsMG0lwY2RuIENlcnRpZmljYXRlIGF1dGhvcml0eTEWMBQG +A1UEAwwNSXBjZG4gUm9vdCBDQYICEAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM +MAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4ICAQBHX+YmO6TLbMTZByuwrgiH +uy7rPzpXTXSJvUM+upxSenrmz8H5fBzypLfKE1NOK58Rr25OyAd1IqgG1Id+8ycg +atetjI1poaNCZvKPRr48wjcTVsfgx7HXW0fykI0KT239Ok4usEM3uqmbkn7Annf7 +dby8BKz4p9n1ye7eHpipu1A5WxjBQeDymlPw/CM9E0v5F/hgiWxW+e8l5u7h43bP +1BNO6PJXJ/GONiQh4bcL0R763tNIKGVNYHSffW71vyoY6reXdKBNnk91OgjFwHTN +ALJtr9jy5xy9vKATY7gThVWDhW/uiv8LUICxxZWnCucKvhgB2bLWaHxIOr9YyjaX +hEVdHgTVXPl5cMXBw+UZLn3vw5EQHuMFAIWR2ZNIhrEvjaPon5qaEveQCDsda39w +rTr3lheyBGMhTnmA4wNV+lNSLImAc2o78aete0cTsJ619snGODUo9QO2YZ/k7vPS +mHeK0VrZvnE8uklFJUTDayLoD+SnsxztRITvk/EuRay2P1fG1/qZn7QUIg+ZM4aV +S8vPrfI9rwu8CRgxmTTtX5ZMez89fy6FlVDITjVNhRrVIc2m3xL03ASTb6NankJo +En+MLQpCFVKyQy9N72RZNz68KiPH/fraAdq4mL5NAcR4P/6Gg7Sn+qjnf/4l43zZ ++7uzqjwhdtU/YHJ9j8/cow== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MREwDwYDVQQIDAhDb2xvcmFkbzEPMA0GA1UEBwwGRGVudmVyMSQwIgYDVQQKDBtJ +cGNkbiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxJDAiBgNVBAsMG0lwY2RuIENlcnRp +ZmljYXRlIGF1dGhvcml0eTEWMBQGA1UEAwwNSXBjZG4gUm9vdCBDQTAeFw0xNzEx +MTYyMDM4MTVaFw0yNzExMTQyMDM4MTVaMIGMMQswCQYDVQQGEwJVUzERMA8GA1UE +CAwIQ29sb3JhZG8xJDAiBgNVBAoMG0lwY2RuIENlcnRpZmljYXRlIEF1dGhvcml0 +eTEkMCIGA1UECwwbSXBjZG4gQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR4wHAYDVQQD +DBVJcGNkbiBJbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDKkxQ9hdMtSwfNEBqGPAKzu7CPhH+xu3kL4e+rJ09EB++fxG905mwM +Ym8s1GC/i1V1hPbOk+zLWXccZhtC49BttsDBVRmagt4q6eeD12AxzVBozWj4YnFy +ZE04CiZlAIN3qN4TnNT/O3iyvmj58QDIFVO51YNSrr7j2dQRLpoyS6s87kpw9A6T +24L1pLkmFuAgCLMFPG5HZWyZSStpXOzOc7LITZQXQzunwLazN9Z4Az8zYC9ilO6V +tNNNc+Y7MpFrTaEFFSsM2+REexuPtCOtT/ZEcOwP884T6ACcUSTv86iE/EFOPgZh +vTK2D0Nzat3ZTsc5N2vvN0eZm6COnVAvYNwrTWG4v3YUtLr/PEoFm9mtPdSA+Tsi +LkGFRjwAoAnhVieFAFulQnswbZAaJRc/xS7BJvts/3J9i7l1opqu0Ebm6L8jLeHv +p/op1TD8LIQkcp7GdsXk4LYH6VopaNOi8yoaEfKWwrXhxBdChHHlYCcfecyD8O39 +8euoGQ0zi/eaCr3MHYU1+a2+5TRPdzHx/l/3V1wxX9hmOGcrwTCtcYRzRjza2UlE +UacmwBqGyfa1Gn/iOto9yqtRvWlPGq9zJNQzNBJ6iWR7gyb0t820jpdtmFyNB5Pv +JceHyVyix/7sBUB5GwMO0iGnLPpY9Ix87nYP3itN3jkqahTrT7tHuwIDAQABo2Yw +ZDAdBgNVHQ4EFgQUIV7iqt3+xYa1Fx/nYzxd1hxvNw0wHwYDVR0jBBgwFoAUWcdI +BR6t3N8w7yUt/TOfXwmubAgwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQELBQADggIBAKbd8nKA1hlxCTczTg9PHSdqHVEYWqRD +dzroOnz0KcIgJF4ncRaN3nna74wIm3nJD/WuwdF8dVbpD4YaeZKhQgmXmYggVH0l +8IIkdC3qYNTDj4xhg7qZC3pzKuNIg7yHv3Axr/rb9BvpUJxbKo/aln/G6PrbNoQx +4xb6xlK6WcYORB/UgzF6jKM5CBXaUt2QQmWts7MgnoNzZ4kxsYbuwj3rOPzHIgRY +YwJt+kh5tlyhHPkgjwUTbMg/Mv/omfzZ3+XMBD2Z69eNd9RHE9XS6AdMKtDiprhg +TLu+9tOVlydQjZk3KsqFv57/1TwKrt9JFjXk/PPQalXoP3oykoiviacd4MyiBmaW +i5WbtcxGP5vwaNn267OGlibfQmQgA1VIgDqftrQwe6T/ZwOKXqFqb56gqdW4VdOU +5bGN0uLXemriuNQolTpVmhNu0bNI5WCiY7c30XN2M5ebcBWDHOr4DOCg+mmX+tJT +Gi1jvfmA6LOYnyQ8xAV080uVe35NQ10e9my6LuXMiQ7ubvHUS5NwaQCFpLDIvbhf +2XVc46Nd0V/bolM23AQRlzuo0ISsDSeWSQVM+uQZ+pLe9G5n2MxqFd84Vu489auc +02I9cHiNahWyXPtvnvMWwImIB30CYhh5ByGWneDo3d5Zj2HAA6FwO67LlIhSe7qz +axDtcM4nO3QH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGEjCCA/qgAwIBAgIJAL2V1hnLWuzgMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzERMA8GA1UECAwIQ29sb3JhZG8xDzANBgNVBAcMBkRlbnZlcjEkMCIG +A1UECgwbSXBjZG4gQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSQwIgYDVQQLDBtJcGNk +biBDZXJ0aWZpY2F0ZSBhdXRob3JpdHkxFjAUBgNVBAMMDUlwY2RuIFJvb3QgQ0Ew +HhcNMTcxMTE2MjAxOTI0WhcNMjcxMTE0MjAxOTI0WjCBlTELMAkGA1UEBhMCVVMx +ETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxJDAiBgNVBAoMG0lw +Y2RuIENlcnRpZmljYXRlIEF1dGhvcml0eTEkMCIGA1UECwwbSXBjZG4gQ2VydGlm +aWNhdGUgYXV0aG9yaXR5MRYwFAYDVQQDDA1JcGNkbiBSb290IENBMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2HsLknm9wx1V33zttMWn1sFEJyLNMeeW +hA0dweKChqDVw5lAyIid0lqjnjIEivlAM8zd2NAPZTHFG9BT3unduNcDcWuRgh33 +mlyssEharWtE6U7lzw8RMGWkyWQkJ2E0SxjE/VmKQjH3/4Ak8ShhTZFKEZuweRdg +j218qUemsOV+ENTsnGUxoAPpr5cGGo4igfO9tpI1g7PWnkYrVFtwsPoy2CKxqh/I +3F67EZq2yROBy9gBhC5FuZhuvgptvSakNQy/w+9buRffshb69wLsbqQayhjlLqcQ +Xkch4y6cG8Ylg+hrqzmG3vG4q03YFz8PuMd/uNxeJQAbZJ/sXh7jVQNJitgi6oOy +j1xVzNKUqb1Mx25DhBy125/l8wGBdsCRUYR6r70NAZRndjkQKV4l3tTQ4+o2rdjd +C/2Sk2/bHVbytMq4Iz+1sOsIUV3La+R+DWcLA6UonpEocNb8bt5bu4JhRDrnJwPi +4jRaH+KgxpBTIlZ3aFnSs0I3wkPOgA3DrI6LWxgt3BfPuK9DPma1rc4B1cwSQTNk +yyUY2boXsnLMMPebF3djwbtYEdqAaixxzzQCVYlhfCEQjqceSd6TJ2SdzH4I3J7x +OsLz1Lj/92aDeeq77q/c9FidnfNe75zB7gXN/bTtb55ZcTTO8XAEvegxcrw5CnO1 +MnFw6xDf++UCAwEAAaNjMGEwHQYDVR0OBBYEFFnHSAUerdzfMO8lLf0zn18JrmwI +MB8GA1UdIwQYMBaAFFnHSAUerdzfMO8lLf0zn18JrmwIMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQB+aYWZh+/gCmFy +oI582AC5+54c7xeLRmtQPmFpAA2Y4iTSeGyjv2g5M9agQ4Ixwue5DluQlsDcDsJw +lsXVHyAlCf6nDbJOpf5m6ixfvBF+QzEeXjUG74iSCWrApYFmsNsCrlsM/EPJogqu +7cgGvHwWYNjPzuwoU0AWHO9FgIbDz3Mc15ZlHIQ2T/ixxyBfpIPc5D91k4u/ZWSb +hO6lsM1iEAvv9ToUXkKttCQ0fN34BwYX47AxC71tSt9/VKIDIEXjzqJJpbJdBGSm +WuiZHWwBzVw7k5y+S+wM5vZ/OMw0thL+I53zmkxGEf3tMH4POeKvX/u19n0TFk/1 +96cztVmTB6W67mb9zZ8ySeJtD9uXE44ycxk93gTnarkIGdTf8XonCVlkHGpb6TWM +5E7aWCoVbqQRkF1rqPza8hRJGH1EGkmdbkuyUMLmQ1FJK9f/xFIcrdBGpMlxyOoL +MSeFeagoSLNq5xzjucF/6mkAUJ8UZAxrSGmaDEaSt1/qJRHovHct2b5FWZLuRt0g +fDsGoKT8IFuapSsmFEInvD0WKXDSPlU8rTy1ZBDlCqp3eNH4tCfLahk7vLA0Jbf7 +ULrtDnrNfjxUYFcF0RcauR1dlrQQRAs31QOzX+JOr4gUG7yXa3j64nx2G5MpLgSo +OEUjfakK71+V/HbQt477zR4k7cRbiA== +-----END CERTIFICATE----- +` rootCA = ` -----BEGIN CERTIFICATE----- MIIGEjCCA/qgAwIBAgIJAL2V1hnLWuzgMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD @@ -209,32 +199,26 @@ OEUjfakK71+V/HbQt477zR4k7cRbiA== func TestVerifyAndEncodeCertificate(t *testing.T) { // should fail bad base64 data - dat, err := verifyAndEncodeCertificate(BadData, "") + dat, err := verifyCertificate(BadData, "") if err == nil { t.Errorf("Unexpected result, there should have been a base64 decoding failure") } // should fail, can't verify self signed cert - dat, err = verifyAndEncodeCertificate(SelfSigneCertOnly, rootCA) + dat, err = verifyCertificate(SelfSigneCertOnly, rootCA) if err == nil { t.Errorf("Unexpected result, a certificate verification error should have occured") } // should pass - dat, err = verifyAndEncodeCertificate(GoodTLSKeys, rootCA) + dat, err = verifyCertificate(GoodTLSKeys, rootCA) if err != nil { t.Errorf("Test failure: %s", err) } - pemCerts := make([]byte, base64.StdEncoding.EncodedLen(len(dat))) - _, err = base64.StdEncoding.Decode(pemCerts, []byte(dat)) - if err != nil { - t.Errorf("Test failure: bad retrun value from verifyAndEncodeCertificate(): %v", err) - } - - certs := strings.SplitAfter(string(pemCerts), "-----END CERTIFICATE-----") + certs := strings.SplitAfter(dat, PemCertEndMarker) length := len(certs) - 1 if length != 2 { - t.Errorf("Test failure: expected 2 certs from verifyAndEncodeCertificate(), got: %d ", length) + t.Errorf("Test failure: expected 2 certs from verifyCertificate(), got: %d ", length) } } diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go b/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go index d313608..16b87cf 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go @@ -75,12 +75,12 @@ func generatePutRiakKeys(req tc.DeliveryServiceSSLKeysReq, tx *sql.Tx, cfg *conf } dsSSLKeys.Certificate = tc.DeliveryServiceSSLKeysCertificate{Crt: string(crt), Key: string(key), CSR: string(csr)} } - if err := riaksvc.PutDeliveryServiceSSLKeysObjTx(dsSSLKeys, tx, cfg.RiakAuthOptions); err != nil { + if err := riaksvc.PutDeliveryServiceSSLKeysObj(dsSSLKeys, tx, cfg.RiakAuthOptions); err != nil { return errors.New("putting riak keys: " + err.Error()) } dsSSLKeys.Version = riaksvc.DSSSLKeyVersionLatest - if err := riaksvc.PutDeliveryServiceSSLKeysObjTx(dsSSLKeys, tx, cfg.RiakAuthOptions); err != nil { + if err := riaksvc.PutDeliveryServiceSSLKeysObj(dsSSLKeys, tx, cfg.RiakAuthOptions); err != nil { return errors.New("putting latest riak keys: " + err.Error()) } return nil diff --git a/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go b/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go index 24f3d57..1ad1b57 100644 --- a/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go +++ b/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go @@ -56,35 +56,6 @@ func GetDeliveryServiceSSLKeysObj(xmlID string, version string, tx *sql.Tx, auth return nil // not found } if err := json.Unmarshal(ro[0].Value, &key); err != nil { - return errors.New("unmarshalling Riak result: " + err.Error()) - } - found = true - return nil - }) - if err != nil { - return key, false, err - } - return key, found, nil -} - -func GetDeliveryServiceSSLKeysObjTx(xmlID string, version string, tx *sql.Tx, authOpts *riak.AuthOptions) (tc.DeliveryServiceSSLKeys, bool, error) { - key := tc.DeliveryServiceSSLKeys{} - if version == "" { - xmlID += "-latest" - } else { - xmlID += "-" + version - } - found := false - err := WithClusterTx(tx, authOpts, func(cluster StorageCluster) error { - // get the deliveryservice ssl keys by xmlID and version - ro, err := FetchObjectValues(xmlID, DeliveryServiceSSLKeysBucket, cluster) - if err != nil { - return err - } - if len(ro) == 0 { - return nil // not found - } - if err := json.Unmarshal(ro[0].Value, &key); err != nil { log.Errorf("failed at unmarshaling sslkey response: %s\n", err) return errors.New("unmarshalling Riak result: " + err.Error()) } @@ -118,27 +89,6 @@ func PutDeliveryServiceSSLKeysObj(key tc.DeliveryServiceSSLKeys, tx *sql.Tx, aut return err } -func PutDeliveryServiceSSLKeysObjTx(key tc.DeliveryServiceSSLKeys, tx *sql.Tx, authOpts *riak.AuthOptions) error { - keyJSON, err := json.Marshal(&key) - if err != nil { - return errors.New("marshalling key: " + err.Error()) - } - err = WithClusterTx(tx, authOpts, func(cluster StorageCluster) error { - obj := &riak.Object{ - ContentType: "text/json", - Charset: "utf-8", - ContentEncoding: "utf-8", - Key: MakeDSSSLKeyKey(key.DeliveryService, string(key.Version)), - Value: []byte(keyJSON), - } - if err = SaveObject(obj, DeliveryServiceSSLKeysBucket, cluster); err != nil { - return errors.New("saving Riak object: " + err.Error()) - } - return nil - }) - return err -} - func Ping(tx *sql.Tx, authOpts *riak.AuthOptions) (tc.RiakPingResp, error) { servers, err := GetRiakServers(tx) if err != nil { @@ -235,28 +185,14 @@ func GetBucketKey(tx *sql.Tx, authOpts *riak.AuthOptions, bucket string, key str return val, found, nil } -func DeleteDSSSLKeys(tx *sql.Tx, authOpts *riak.AuthOptions, ds tc.DeliveryServiceName, version string) error { - if version == "" { - version = "latest" - } - key := string(ds) + "-" + version - - cluster, err := GetRiakClusterTx(tx, authOpts) - if err != nil { - return errors.New("getting riak cluster: " + err.Error()) - } - if err = cluster.Start(); err != nil { - return errors.New("starting riak cluster: " + err.Error()) - } - defer func() { - if err := cluster.Stop(); err != nil { - log.Errorln("stopping Riak cluster: " + err.Error()) +func DeleteDSSSLKeys(tx *sql.Tx, authOpts *riak.AuthOptions, xmlID string, version string) error { + err := WithClusterTx(tx, authOpts, func(cluster StorageCluster) error { + if err := DeleteObject(MakeDSSSLKeyKey(xmlID, version), DeliveryServiceSSLKeysBucket, cluster); err != nil { + return errors.New("deleting SSL keys: " + err.Error()) } - }() - if err := DeleteObject(key, DeliveryServiceSSLKeysBucket, cluster); err != nil { - return errors.New("deleting SSL keys: " + err.Error()) - } - return nil + return nil + }) + return err } // GetURLSigConfigFileName returns the filename of the Apache Traffic Server URLSig config file diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go index f9c2c84..72e7d05 100644 --- a/traffic_ops/traffic_ops_golang/routes.go +++ b/traffic_ops/traffic_ops_golang/routes.go @@ -361,13 +361,6 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {1.1, http.MethodPut, `cdns/{id}/snapshot/?$`, crconfig.SnapshotHandler, auth.PrivLevelOperations, Authenticated, nil}, {1.1, http.MethodPut, `snapshot/{cdn}/?$`, crconfig.SnapshotHandler, auth.PrivLevelOperations, Authenticated, nil}, - //SSLKeys deliveryservice endpoints here that are marked marked as '-wip' need to have tenancy checks added - - {1.3, http.MethodGet, `deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, deliveryservice.GetSSLKeysByXMLID, auth.PrivLevelAdmin, Authenticated, nil}, - {1.3, http.MethodGet, `deliveryservices-wip/hostname/{hostName}/sslkeys$`, deliveryservice.GetSSLKeysByHostName, auth.PrivLevelAdmin, Authenticated, nil}, - {1.3, http.MethodPost, `deliveryservices-wip/hostname/{hostName}/sslkeys/add$`, deliveryservice.AddSSLKeys, auth.PrivLevelAdmin, Authenticated, nil}, - {1.3, http.MethodGet, `deliveryservices/xmlId/{name}/sslkeys/delete$`, deliveryservice.DeleteSSLKeys, auth.PrivLevelAdmin, Authenticated, nil}, - ////DeliveryServices {1.3, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(deliveryservice.GetTypeV13Factory()), auth.PrivLevelReadOnly, Authenticated, nil}, {1.1, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(deliveryservice.GetTypeV12Factory()), auth.PrivLevelReadOnly, Authenticated, nil}, @@ -381,6 +374,10 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {1.1, http.MethodDelete, `deliveryservices/{id}/?(\.json)?$`, api.DeleteHandler(deliveryservice.GetTypeV12Factory()), auth.PrivLevelOperations, Authenticated, nil}, {1.1, http.MethodGet, `deliveryservices/{id}/servers/eligible/?(\.json)?$`, deliveryservice.GetServersEligible, auth.PrivLevelReadOnly, Authenticated, nil}, + {1.1, http.MethodGet, `deliveryservices/xmlId/{xmlid}/sslkeys$`, deliveryservice.GetSSLKeysByXMLID, auth.PrivLevelAdmin, Authenticated, nil}, + {1.1, http.MethodGet, `deliveryservices/hostname/{hostname}/sslkeys$`, deliveryservice.GetSSLKeysByHostName, auth.PrivLevelAdmin, Authenticated, nil}, + {1.1, http.MethodPost, `deliveryservices/sslkeys/add$`, deliveryservice.AddSSLKeys, auth.PrivLevelAdmin, Authenticated, nil}, + {1.1, http.MethodGet, `deliveryservices/xmlId/{xmlid}/sslkeys/delete$`, deliveryservice.DeleteSSLKeys, auth.PrivLevelOperations, Authenticated, nil}, {1.1, http.MethodPost, `deliveryservices/sslkeys/generate/?(\.json)?$`, deliveryservice.GenerateSSLKeys, auth.PrivLevelOperations, Authenticated, nil}, {1.1, http.MethodPost, `deliveryservices/xmlId/{name}/urlkeys/copyFromXmlId/{copy-name}/?(\.json)?$`, deliveryservice.CopyURLKeys, auth.PrivLevelOperations, Authenticated, nil}, {1.1, http.MethodPost, `deliveryservices/xmlId/{name}/urlkeys/generate/?(\.json)?$`, deliveryservice.GenerateURLKeys, auth.PrivLevelOperations, Authenticated, nil},