From 0f0fe2ba1b772e6964241c0631683b306fff23c0 Mon Sep 17 00:00:00 2001
From: Stuart McLaren <stuart.mclaren@hp.com>
Date: Thu, 9 Feb 2012 18:10:42 +0000
Subject: [PATCH] New -k/--insecure command line option

Fix for bug 929591.

Change glance to require server certificate validation
by default when using https. The standard system
CA file will be used if available (and an alternative was not
provided).

The --insecure option can be used by clients to skip server
certificate validation if appropriate.

* This change will impact Nova clients accessing glance over https.
  If the standard CA file is not suitable they will need to provide
  a CA file or else create an 'insecure' glance client.
* Accesses to a https registry server must now perform server
  certificate validation.
* If the package which provides the standard
  system CA file is installed then that file will be used by default.
  It probably makes sense for the glance package to have a
  dependency on whichever package provides the default CA bundle.
  (In Ubuntu this is 'ca-certificates')

Change-Id: I7c83361ba0881559ec77d4baf10dfeb5b8e32185
---
 bin/glance                          |  10 ++-
 doc/source/glance.rst               |   6 +-
 doc/source/man/glance.rst           |  13 ++--
 glance/common/client.py             |  33 ++++++---
 glance/tests/functional/test_ssl.py | 107 +++++++++++++++++++++++++++-
 glance/tests/var/ca.crt             |  35 +++++++++
 glance/tests/var/certificate.crt    |  44 +++++++-----
 glance/tests/var/privatekey.key     |  74 ++++++++++++-------
 8 files changed, 265 insertions(+), 57 deletions(-)
 create mode 100644 glance/tests/var/ca.crt

diff --git a/bin/glance b/bin/glance
index fd44f24886..8aefea5334 100755
--- a/bin/glance
+++ b/bin/glance
@@ -754,7 +754,7 @@ def get_client(options):
                                 use_ssl=use_ssl,
                                 auth_tok=options.auth_token or \
                                         os.getenv('OS_TOKEN'),
-                                creds=creds)
+                                creds=creds, insecure=options.insecure)
 
 
 def create_options(parser):
@@ -781,6 +781,12 @@ def create_options(parser):
                            "(http/https) of the glance server, for example "
                            "-U https://localhost:" + str(DEFAULT_PORT) +
                            "/v1 Default: None")
+    parser.add_option('-k', '--insecure', dest="insecure",
+                      default=False, action="store_true",
+                      help="Explicitly allow glance to perform \"insecure\" "
+                      "SSL (https) requests. The server's certificate will "
+                      "not be verified against any certificate authorities. "
+                      "This option should be used with caution.")
     parser.add_option('-A', '--auth_token', dest="auth_token",
                       metavar="TOKEN", default=None,
                       help="Authentication token to use to identify the "
@@ -810,7 +816,7 @@ def create_options(parser):
                       help="Sort results by this image attribute.")
     parser.add_option('--sort_dir', dest="sort_dir", metavar="[desc|asc]",
                       help="Sort results in this direction.")
-    parser.add_option('-f', '--force', dest="force", metavar="FORCE",
+    parser.add_option('-f', '--force', dest="force",
                       default=False, action="store_true",
                       help="Prevent select actions from requesting "
                            "user confirmation")
diff --git a/doc/source/glance.rst b/doc/source/glance.rst
index 1a86f4c9e7..e105eb48e6 100644
--- a/doc/source/glance.rst
+++ b/doc/source/glance.rst
@@ -100,6 +100,10 @@ a brief help message, like so::
                           specify the hostname, port and protocol (http/https)
                           of the glance server, for example -U
                           https://localhost:9292/v1 Default: None
+    -k, --insecure        Explicitly allow glance to perform insecure SSL
+                          requests. The server certificate will not be
+                          verified against any certificate authorities.
+                          This option should be used with caution.
     --limit=LIMIT         Page size to use while requesting image metadata
     --marker=MARKER       Image index after which to begin pagination
     --sort_key=KEY        Sort results by this image attribute.
@@ -153,7 +157,7 @@ Important Information about Uploading Images
 Before we go over the commands for adding an image to Glance, it is
 important to understand that Glance **does not currently inspect** the image
 files you add to it. In other words, **Glance only understands what you tell it,
-via attributes and custom properties**. 
+via attributes and custom properties**.
 
 If the file extension of the file you upload to Glance ends in '.vhd', Glance
 **does not** know that the image you are uploading has a disk format of ``vhd``.
diff --git a/doc/source/man/glance.rst b/doc/source/man/glance.rst
index 16ecdc11bf..ae174f9a36 100644
--- a/doc/source/man/glance.rst
+++ b/doc/source/man/glance.rst
@@ -78,10 +78,10 @@ OPTIONS
 
   **-v, --verbose**
         Print more verbose output
- 
+
   **-d, --debug**
         Print more verbose output
- 
+
   **-H ADDRESS, --host=ADDRESS**
         Address of Glance API host. Default: 0.0.0.0
 
@@ -90,10 +90,15 @@ OPTIONS
 
   **-U URL, --url=URL**
         URL of Glance service. This option can be used to specify the hostname,
-        port and protocol (http/https) of the glance server, for example 
-        -U https://localhost:9292/v1 
+        port and protocol (http/https) of the glance server, for example
+        -U https://localhost:9292/v1
         Default: None
 
+  **-k, --insecure**
+        Explicitly allow glance to perform insecure SSL (https) requests.
+        The server certificate will not be verified against any certificate
+        authorities. This option should be used with caution.
+
   **-A TOKEN, --auth_token=TOKEN**
         Authentication token to use to identify the client to the glance server
 
diff --git a/glance/common/client.py b/glance/common/client.py
index 9a3c3bfbef..9e08c15aa5 100644
--- a/glance/common/client.py
+++ b/glance/common/client.py
@@ -148,13 +148,14 @@ class HTTPSClientAuthConnection(httplib.HTTPSConnection):
     """
 
     def __init__(self, host, port, key_file, cert_file,
-                 ca_file, timeout=None):
+                 ca_file, timeout=None, insecure=False):
         httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file,
                                          cert_file=cert_file)
         self.key_file = key_file
         self.cert_file = cert_file
         self.ca_file = ca_file
         self.timeout = timeout
+        self.insecure = insecure
 
     def connect(self):
         """
@@ -170,14 +171,14 @@ class HTTPSClientAuthConnection(httplib.HTTPSConnection):
         if self._tunnel_host:
             self.sock = sock
             self._tunnel()
-        # If there's no CA File, don't force Server Certificate Check
-        if self.ca_file:
+        # Check CA file unless 'insecure' is specificed
+        if self.insecure is True:
+            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
+                                        cert_reqs=ssl.CERT_NONE)
+        else:
             self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
                                         ca_certs=self.ca_file,
                                         cert_reqs=ssl.CERT_REQUIRED)
-        else:
-            self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file,
-                                        cert_reqs=ssl.CERT_NONE)
 
 
 class BaseClient(object):
@@ -186,6 +187,12 @@ class BaseClient(object):
 
     DEFAULT_PORT = 80
     DEFAULT_DOC_ROOT = None
+    # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
+    # Suse, FreeBSD/OpenBSD
+    DEFAULT_CA_FILE_PATH = '/etc/ssl/certs/ca-certificates.crt:'\
+        '/etc/pki/tls/certs/ca-bundle.crt:'\
+        '/etc/ssl/ca-bundle.pem:'\
+        '/etc/ssl/cert.pem'
 
     OK_RESPONSE_CODES = (
         httplib.OK,
@@ -203,8 +210,8 @@ class BaseClient(object):
     )
 
     def __init__(self, host, port=None, use_ssl=False, auth_tok=None,
-                 creds=None, doc_root=None,
-                 key_file=None, cert_file=None, ca_file=None):
+                 creds=None, doc_root=None, key_file=None,
+                 cert_file=None, ca_file=None, insecure=False):
         """
         Creates a new client to some service.
 
@@ -231,6 +238,8 @@ class BaseClient(object):
                         If use_ssl is True, and this param is None (the
                         default), then an environ variable
                         GLANCE_CLIENT_CA_FILE is looked for.
+        :param insecure: Optional. If set then the server's certificate
+                         will not be verified.
         """
         self.host = host
         self.port = port or self.DEFAULT_PORT
@@ -286,7 +295,15 @@ class BaseClient(object):
                 msg = _("The CA file you specified %s does not "
                         "exist") % ca_file
                 raise exception.ClientConnectionError(msg)
+
+            if ca_file is None:
+                for ca in self.DEFAULT_CA_FILE_PATH.split(":"):
+                    if os.path.exists(ca):
+                        ca_file = ca
+                        break
+
             self.connect_kwargs['ca_file'] = ca_file
+            self.connect_kwargs['insecure'] = insecure
 
     def set_auth_token(self, auth_tok):
         """
diff --git a/glance/tests/functional/test_ssl.py b/glance/tests/functional/test_ssl.py
index b0fdf622d6..4c94d1cf9f 100644
--- a/glance/tests/functional/test_ssl.py
+++ b/glance/tests/functional/test_ssl.py
@@ -40,6 +40,8 @@ import os
 import tempfile
 import unittest
 
+from glance import client as glance_client
+from glance.common import exception
 from glance.common import utils
 from glance.store.location import get_location_from_uri
 from glance.tests import functional
@@ -62,6 +64,12 @@ class TestSSL(functional.FunctionalTest):
         self.inited = False
         self.disabled = True
 
+        # Test key/cert/CA file created as per:
+        #   http://blog.didierstevens.com/2008/12/30/
+        #     howto-make-your-own-cert-with-openssl/
+        # Note that for these tests certificate.crt had to
+        # be created with 'Common Name' set to 0.0.0.0
+
         self.key_file = os.path.join(TEST_VAR_DIR, 'privatekey.key')
         if not os.path.exists(self.key_file):
             self.disabled_message = "Could not find private key file"
@@ -69,11 +77,17 @@ class TestSSL(functional.FunctionalTest):
             return
 
         self.cert_file = os.path.join(TEST_VAR_DIR, 'certificate.crt')
-        if not os.path.exists(self.key_file):
+        if not os.path.exists(self.cert_file):
             self.disabled_message = "Could not find certificate file"
             self.inited = True
             return
 
+        self.ca_file = os.path.join(TEST_VAR_DIR, 'ca.crt')
+        if not os.path.exists(self.ca_file):
+            self.disabled_message = "Could not find CA file"
+            self.inited = True
+            return
+
         self.inited = True
         self.disabled = False
 
@@ -1230,3 +1244,94 @@ class TestSSL(functional.FunctionalTest):
         self.assertEqual(response.status, 404)
 
         self.stop_servers()
+
+    @skip_if_disabled
+    def test_certificate_validation(self):
+        """
+        Check SSL client cerificate verification
+        """
+        self.cleanup()
+        self.start_servers(**self.__dict__.copy())
+
+        # 0. GET /images
+        # Verify no public images
+        path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
+        https = httplib2.Http(disable_ssl_certificate_validation=True)
+        response, content = https.request(path, 'GET')
+        self.assertEqual(response.status, 200)
+        self.assertEqual(content, '{"images": []}')
+
+        # 1. POST /images with public image named Image1
+        headers = {'Content-Type': 'application/octet-stream',
+                   'X-Image-Meta-Name': 'Image1',
+                   'X-Image-Meta-Status': 'active',
+                   'X-Image-Meta-Container-Format': 'ovf',
+                   'X-Image-Meta-Disk-Format': 'vdi',
+                   'X-Image-Meta-Size': '19',
+                   'X-Image-Meta-Is-Public': 'True'}
+        path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
+        https = httplib2.Http(disable_ssl_certificate_validation=True)
+        response, content = https.request(path, 'POST', headers=headers)
+        self.assertEqual(response.status, 201)
+        data = json.loads(content)
+
+        image_id = data['image']['id']
+
+        # 2. Attempt to delete the image *without* CA file
+        path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
+        secure_cli = glance_client.Client(host="0.0.0.0", port=self.api_port,
+                                          use_ssl=True, insecure=False)
+        try:
+            secure_cli.delete_image(image_id)
+            self.fail("Client with no CA file deleted image %s" % image_id)
+        except exception.ClientConnectionError, e:
+            pass
+
+        # 3. Delete the image with a secure client *with* CA file
+        secure_cli2 = glance_client.Client(host="0.0.0.0", port=self.api_port,
+                                           use_ssl=True, ca_file=self.ca_file,
+                                           insecure=False)
+        try:
+            secure_cli2.delete_image(image_id)
+        except exception.ClientConnectionError, e:
+            self.fail("Secure client failed to delete image %s" % image_id)
+
+        # Verify image is deleted
+        path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
+        https = httplib2.Http(disable_ssl_certificate_validation=True)
+        response, content = https.request(path, 'GET')
+        self.assertEqual(response.status, 200)
+        self.assertEqual(content, '{"images": []}')
+
+        # 4. POST another image
+        headers = {'Content-Type': 'application/octet-stream',
+                   'X-Image-Meta-Name': 'Image1',
+                   'X-Image-Meta-Status': 'active',
+                   'X-Image-Meta-Container-Format': 'ovf',
+                   'X-Image-Meta-Disk-Format': 'vdi',
+                   'X-Image-Meta-Size': '19',
+                   'X-Image-Meta-Is-Public': 'True'}
+        path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
+        https = httplib2.Http(disable_ssl_certificate_validation=True)
+        response, content = https.request(path, 'POST', headers=headers)
+        self.assertEqual(response.status, 201)
+        data = json.loads(content)
+
+        image_id = data['image']['id']
+
+        # 5. Delete the image with an insecure client
+        insecure_cli = glance_client.Client(host="0.0.0.0", port=self.api_port,
+                                            use_ssl=True, insecure=True)
+        try:
+            insecure_cli.delete_image(image_id)
+        except exception.ClientConnectionError, e:
+            self.fail("Insecure client failed to delete image")
+
+        # Verify image is deleted
+        path = "https://%s:%d/v1/images" % ("0.0.0.0", self.api_port)
+        https = httplib2.Http(disable_ssl_certificate_validation=True)
+        response, content = https.request(path, 'GET')
+        self.assertEqual(response.status, 200)
+        self.assertEqual(content, '{"images": []}')
+
+        self.stop_servers()
diff --git a/glance/tests/var/ca.crt b/glance/tests/var/ca.crt
new file mode 100644
index 0000000000..9d66ca6270
--- /dev/null
+++ b/glance/tests/var/ca.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+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
+WoRMgEwjGJWqzhJZUYpUAQ==
+-----END CERTIFICATE-----
diff --git a/glance/tests/var/certificate.crt b/glance/tests/var/certificate.crt
index 9bb6dd1d0f..3c1aa6363b 100644
--- a/glance/tests/var/certificate.crt
+++ b/glance/tests/var/certificate.crt
@@ -1,18 +1,30 @@
 -----BEGIN CERTIFICATE-----
-MIIC4DCCAcigAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl
-c3RDQTAeFw0xMTA3MjExNTA1NDZaFw0xMjA3MjAxNTA1NDZaMCMxEDAOBgNVBAMT
-B2FobWFkcGMxDzANBgNVBAoTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEP
-ADCCAQoCggEBAO9zpczf+W4DoK2z8oFbsZfbvz1y/yQOnrQYvb1zv1IieT+QA+Ti
-N64N/sgR/cR7YEIXDnhij8yE1JTWMk1W6g4m7TGacUMXD/WAcsTM7kRol/FVksdn
-F51qxCYqWUPQ3xiTfBg2SJWvJCUGowvz06xh8JeOEXLbALC5xrzrM3hclpdbrKYE
-oe8kikI/K0TKpu52VJJrTBGPHMsw+eIqL2Ix5pWHh7DPfjBiiG7khsJxN7xSqLbX
-LrhDi24nTM9pndaqABkmPYQ9qd11SoAUB82QAAGj8A7iR/DnAzAfJl1usvQp+Me6
-sR3TPY27zifBbD04tiROi1swM/1xRH7qOpkCAwEAAaMvMC0wCQYDVR0TBAIwADAL
-BgNVHQ8EBAMCBSAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQEFBQAD
-ggEBAIJvnQjkEDFvLT7NiyFrO938BuxdQH2mX2N7Fz86myZLcGpr5NCdLvT9tD9f
-6KqrR8e839pYVPZY80cBpGTmRmzW3xLsmGCFHPHt4p1tkqSP1R5iLzKDe8jawHhD
-sch8P9URRhW9ZgBzA4xiv9FnIxZ70uDr04uX/sR/j41HGBS8YW6dJvr9Y2SpGqSS
-rR2btnNZ945dau6CPLRNd9Fls3Qjx03PnsmZ5ikSuV0pT1sPQmhhw7rBYV/b2ff+
-z/4cRtZrR00NVc74IEXLoujIjUUpFC83in10PKQmAvKYTeTdXns48eC4Cwqe8eaM
-N0YtxqQvSTsUo6vPM28NR99Fbow=
+MIIFLjCCAxYCAQEwDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCQVUxEzARBgNV
+BAgTClNvbWUtU3RhdGUxFTATBgNVBAoTDE9wZW5zdGFjayBDQTESMBAGA1UECxMJ
+R2xhbmNlIENBMRIwEAYDVQQDEwlHbGFuY2UgQ0EwHhcNMTIwMjA5MTcxMDUzWhcN
+MjIwMjA2MTcxMDUzWjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
+ZTESMBAGA1UEChMJT3BlbnN0YWNrMQ8wDQYDVQQLEwZHbGFuY2UxEDAOBgNVBAMT
+BzAuMC4wLjAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXpUkQN6pu
+avo+gz3o1K4krVdPl1m7NjNJDyD/+ZH0EGNcEN7iag1qPE7JsjqGPNZsQK1dMoXb
+Sz+OSi9qvNeJnBcfwUx5qTAtwyAb9AxGkwuMafIU+lWbsclo+dPGsja01ywbXTCZ
+bF32iqnpOMYhfxWUdoQYiBkhxxhW9eMPKLS/KkP8/bx+Vaa2XJiAebqkd9nrksAA
+BeGc9mlafYBEmiChPdJEPw+1ePA4QVq9aPepDsqAKtGN8JLpmoC3BdxQQTbbwL3Q
+8fTXK4tCNUaVk4AbDy/McFq6y0ocQoBPJjihOY35mWG/OLtcI99yPOpWGnps/5aG
+/64DDJ2D67Fnaj6gKHV+6TXFO8KZxlnxtgtiZDJBZkneTBt9ArSOv+l6NBsumRz0
+iEJ4o4H1S2TSMnprAvX7WnGtc6Xi9gXahYcDHEelwwYzqAiTBv6hxSp4MZ2dNXa+
+KzOitC7ZbV2qsg0au0wjfE/oSQ3NvsvUr8nOmfutJTvHRAwbC1v4G/tuAsO7O0w2
+0u2B3u+pG06m5+rnEqp+rB9hmukRYTfgEFRRsVIvpFl/cwvPXKRcX03UIMx+lLr9
+Ft+ep7YooBhY3wY2kwCxD4lRYNmbwsCIVywZt40f/4ad98TkufR9NhsfycxGeqbr
+mTMFlZ8TTlmP82iohekKCOvoyEuTIWL2+wIDAQABMA0GCSqGSIb3DQEBBQUAA4IC
+AQBMUBgV0R+Qltf4Du7u/8IFmGAoKR/mktB7R1gRRAqsvecUt7kIwBexGdavGg1y
+0pU0+lgUZjJ20N1SlPD8gkNHfXE1fL6fmMjWz4dtYJjzRVhpufHPeBW4tl8DgHPN
+rBGAYQ+drDSXaEjiPQifuzKx8WS+DGA3ki4co5mPjVnVH1xvLIdFsk89z3b3YD1k
+yCJ/a9K36x6Z/c67JK7s6MWtrdRF9+MVnRKJ2PK4xznd1kBz16V+RA466wBDdARY
+vFbtkafbEqOb96QTonIZB7+fAldKDPZYnwPqasreLmaGOaM8sxtlPYAJ5bjDONbc
+AaXG8BMRQyO4FyH237otDKlxPyHOFV66BaffF5S8OlwIMiZoIvq+IcTZOdtDUSW2
+KHNLfe5QEDZdKjWCBrfqAfvNuG13m03WqfmcMHl3o/KiPJlx8l9Z4QEzZ9xcyQGL
+cncgeHM9wJtzi2cD/rTDNFsx/gxvoyutRmno7I3NRbKmpsXF4StZioU3USRspB07
+hYXOVnG3pS+PjVby7ThT3gvFHSocguOsxClx1epdUJAmJUbmM7NmOp5WVBVtMtC2
+Su4NG/xJciXitKzw+btb7C7RjO6OEqv/1X/oBDzKBWQAwxUC+lqmnM7W6oqWJFEM
+YfTLnrjs7Hj6ThMGcEnfvc46dWK3dz0RjsQzUxugPuEkLA==
 -----END CERTIFICATE-----
diff --git a/glance/tests/var/privatekey.key b/glance/tests/var/privatekey.key
index 9da0a6c858..b63df3d29d 100644
--- a/glance/tests/var/privatekey.key
+++ b/glance/tests/var/privatekey.key
@@ -1,27 +1,51 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+MIIJKAIBAAKCAgEA16VJEDeqbmr6PoM96NSuJK1XT5dZuzYzSQ8g//mR9BBjXBDe
+4moNajxOybI6hjzWbECtXTKF20s/jkovarzXiZwXH8FMeakwLcMgG/QMRpMLjGny
+FPpVm7HJaPnTxrI2tNcsG10wmWxd9oqp6TjGIX8VlHaEGIgZIccYVvXjDyi0vypD
+/P28flWmtlyYgHm6pHfZ65LAAAXhnPZpWn2ARJogoT3SRD8PtXjwOEFavWj3qQ7K
+gCrRjfCS6ZqAtwXcUEE228C90PH01yuLQjVGlZOAGw8vzHBaustKHEKATyY4oTmN
++Zlhvzi7XCPfcjzqVhp6bP+Whv+uAwydg+uxZ2o+oCh1fuk1xTvCmcZZ8bYLYmQy
+QWZJ3kwbfQK0jr/pejQbLpkc9IhCeKOB9Utk0jJ6awL1+1pxrXOl4vYF2oWHAxxH
+pcMGM6gIkwb+ocUqeDGdnTV2viszorQu2W1dqrINGrtMI3xP6EkNzb7L1K/Jzpn7
+rSU7x0QMGwtb+Bv7bgLDuztMNtLtgd7vqRtOpufq5xKqfqwfYZrpEWE34BBUUbFS
+L6RZf3MLz1ykXF9N1CDMfpS6/Rbfnqe2KKAYWN8GNpMAsQ+JUWDZm8LAiFcsGbeN
+H/+GnffE5Ln0fTYbH8nMRnqm65kzBZWfE05Zj/NoqIXpCgjr6MhLkyFi9vsCAwEA
+AQKCAgAA96baQcWr9SLmQOR4NOwLEhQAMWefpWCZhU3amB4FgEVR1mmJjnw868RW
+t0v36jH0Dl44us9K6o2Ab+jCi9JTtbWM2Osk6JNkwSlVtsSPVH2KxbbmTTExH50N
+sYE3tPj12rlB7isXpRrOzlRwzWZmJBHOtrFlAsdKFYCQc03vdXlKGkBv1BuSXYP/
+8W5ltSYXMspxehkOZvhaIejbFREMPbzDvGlDER1a7Q320qQ7kUr7ISvbY1XJUzj1
+f1HwgEA6w/AhED5Jv6wfgvx+8Yo9hYnflTPbsO1XRS4x7kJxGHTMlFuEsSF1ICYH
+Bcos0wUiGcBO2N6uAFuhe98BBn+nOwAPZYWwGkmVuK2psm2mXAHx94GT/XqgK/1r
+VWGSoOV7Fhjauc2Nv8/vJU18DXT3OY5hc4iXVeEBkuZwRb/NVUtnFoHxVO/Mp5Fh
+/W5KZaLWVrLghzvSQ/KUIM0k4lfKDZpY9ZpOdNgWDyZY8tNrXumUZZimzWdXZ9vR
+dBssmd8qEKs1AHGFnMDt56IjLGou6j0qnWsLdR1e/WEFsYzGXLVHCv6vXRNkbjqh
+WFw5nA+2Dw1YAsy+YkTfgx2pOe+exM/wxsVPa7tG9oZ374dywUi1k6VoHw5dkmJw
+1hbXqSLZtx2N51G+SpGmNAV4vLUF0y3dy2wnrzFkFT4uxh1w8QKCAQEA+h6LwHTK
+hgcJx6CQQ6zYRqXo4wdvMooY1FcqJOq7LvJUA2CX5OOLs8qN1TyFrOCuAUTurOrM
+ABlQ0FpsIaP8TOGz72dHe2eLB+dD6Bqjn10sEFMn54zWd/w9ympQrO9jb5X3ViTh
+sCcdYyXVS9Hz8nzbbIF+DaKlxF2Hh71uRDxXpMPxRcGbOIuKZXUj6RkTIulzqT6o
+uawlegWxch05QSgzq/1ASxtjTzo4iuDCAii3N45xqxnB+fV9NXEt4R2oOGquBRPJ
+LxKcOnaQKBD0YNX4muTq+zPlv/kOb8/ys2WGWDUrNkpyJXqhTve4KONjqM7+iL/U
+4WdJuiCjonzk/QKCAQEA3Lc+kNq35FNLxMcnCVcUgkmiCWZ4dyGZZPdqjOPww1+n
+bbudGPzY1nxOvE60dZM4or/tm6qlXYfb2UU3+OOJrK9s297EQybZ8DTZu2GHyitc
+NSFV3Gl4cgvKdbieGKkk9X2dV9xSNesNvX9lJEnQxuwHDTeo8ubLHtV88Ml1xokn
+7W+IFiyEuUIL4e5/fadbrI3EwMrbCF4+9VcfABx4PTNMzdc8LsncCMXE+jFX8AWp
+TsT2JezTe5o2WpvBoKMAYhJQNQiaWATn00pDVY/70H1vK3ljomAa1IUdOr/AhAF7
+3jL0MYMgXSHzXZOKAtc7yf+QfFWF1Ls8+sen1clJVwKCAQEAp59rB0r+Iz56RmgL
+5t7ifs5XujbURemY5E2aN+18DuVmenD0uvfoO1DnJt4NtCNLWhxpXEdq+jH9H/VJ
+fG4a+ydT4IC1vjVRTrWlo9qeh4H4suQX3S1c2kKY4pvHf25blH/Lp9bFzbkZD8Ze
+IRcOxxb4MsrBwL+dGnGYD9dbG63ZCtoqSxaKQSX7VS1hKKmeUopj8ivFBdIht5oz
+JogBQ/J+Vqg9u1gagRFCrYgdXTcOOtRix0lW336vL+6u0ax/fXe5MjvlW3+8Zc3p
+pIBgVrlvh9ccx8crFTIDg9m4DJRgqaLQV+0ifI2np3WK3RQvSQWYPetZ7sm69ltD
+bvUGvQKCAQAz5CEhjUqOs8asjOXwnDiGKSmfbCgGWi/mPQUf+rcwN9z1P5a/uTKB
+utgIDbj/q401Nkp2vrgCNV7KxitSqKxFnTjKuKUL5KZ4gvRtyZBTR751/1BgcauP
+pJYE91K0GZBG5zGG5pWtd4XTd5Af5/rdycAeq2ddNEWtCiRFuBeohbaNbBtimzTZ
+GV4R0DDJKf+zoeEQMqEsZnwG0mTHceoS+WylOGU92teQeG7HI7K5C5uymTwFzpgq
+ByegRd5QFgKRDB0vWsZuyzh1xI/wHdnmOpdYcUGre0zTijhFB7ALWQ32P6SJv3ps
+av78kSNxZ4j3BM7DbJf6W8sKasZazOghAoIBAHekpBcLq9gRv2+NfLYxWN2sTZVB
+1ldwioG7rWvk5YQR2akukecI3NRjtC5gG2vverawG852Y4+oLfgRMHxgp0qNStwX
+juTykzPkCwZn8AyR+avC3mkrtJyM3IigcYOu4/UoaRDFa0xvCC1EfumpnKXIpHag
+miSQZf2sVbgqb3/LWvHIg/ceOP9oGJve87/HVfQtBoLaIe5RXCWkqB7mcI/exvTS
+8ShaW6v2Fe5Bzdvawj7sbsVYRWe93Aq2tmIgSX320D2RVepb6mjD4nr0IUaM3Yed
+TFT7e2ikWXyDLLgVkDTU4Qe8fr3ZKGfanCIDzvgNw6H1gRi+2WQgOmjilMQ=
 -----END RSA PRIVATE KEY-----