diff --git a/.circleci/config.yml b/.circleci/config.yml index 3c9bf24..5b78286 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -198,47 +198,8 @@ jobs: docker: - image: circleci/python:3.6 environment: - CONFIG_CLASS: accounts.config.EnvConfig + CONFIG_CLASS: accounts.config.LocalConfig MYSQL_CONNECTION_STRING: mysql://root:circle@127.0.0.1:3306/circle - JWT_PRIVATE_KEY: |- - -----BEGIN PRIVATE KEY----- - MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzgU18PnRrpbVK - LU4EewU476arjLeMAXoxQAvrufvnwAGlrvnuh+TE7z7R3KslOyP0m3bTMaNyn2la - DFz0ERR8KFA3rbUUDLG8QAUomm6X2WNbZJqFG/ORBWyEWy/bvNEbCTtF8K09v6HT - PcNzpSrzN8MK/xsIXnMTa/acftbfD3jnb1KFNWaSUKgEM9OL8NSW8PpqTf+le/+R - 0Dwhd4vNnELpobKY3tvhxjvlQmbZA6vRrOoWfWbKXuS/7B7CWXWEhXPTbcJPcKPt - zlkpJGlrcHgvTBorM/lBZw7zrmfUcJROdX6ziU1ymEENkKtLBORedWdBZU8Ivuyy - hDIzS7cdAgMBAAECggEAVReXfq0wjRMJhHdDg5Y5nIrmbG4RWFoe7ZfZzs3kXzDC - 1yLCMdPTm5N6KQu9SbHmUn8b7fOa8qwkyd4QdlZeapjFpg8/RpjZ7E5A48WJZYxU - sC9ZnH3qkTWMApYjcrvoODPBGF+GED52XOfrbje+y3sEh4L08purW2qThg4Ol8A2 - +lqh7W0DCAYV9BG4jFo0QyJRsXa88JIxVS2gSbuegDyHwRTtPq0ZH+vqUEsJti0c - G60hwcl0v9eRbJk/e9lzhKFKnf4/ReRAX9pUyyIb7za3vhYwDmjglgZ+Ax4HhkPK - SR8KjqX5hQ2nOtDmxkxEO1QF5Qm99VLlZyo55vdJYQKBgQDomtQ7jwc41JM9naZC - 1agGjJamZAtfDf/00KQILWplaDCDEyue9A1iSnu7yNLX4VOjsKhfvugARwz2pOrw - PkOYasurM+P16qNkyfyVUilC/QoOcm5UjsCy/wAViwYFG8hZGBj+jPELqPglSdr4 - m2FPTFOmtQDzB7EOk9Mzaic26QKBgQDFjz96/3OAC2dBdjQz7zXKHYPPo2gXqS76 - 3UOX2u+S43jJGEEKLdWa34qO6KrnnUsivyvEyF2/5H6n8Dc2Xj0LBdO67wyzbtFM - dI9RiQ3DqTBbebmrkSdIaTBAGu1VtSCzBKUqckkM3lLSHhJBg0XrURGdqro28nhZ - uSQ8EzzGFQKBgQCCiVlvrz3jU9Dp9E45FcR9IGrvKBgFmUq6bliPykT6cfU/qgOB - 6f6U2a4E3ZgN1QNmSp7DVNTISxdoV3cNqjOvFsgD5VQaTzqxNnXMqtZDJNR+9RMb - 2x0jlt3KOUIAne3aqh5kxF4GKCZSbtc3S6PZp8EOPmgw+3EO+EC/iuRE+QKBgHIW - uqs2UKY2b6ffMmB3mVGiX9eOX3OikW3wT7OnjMkAMmW3awAM3hl1VNgYx3HAZX6o - dgdLStChjP9A+zGblJcEA3Ulzejla1tCyO1mP5up3jJFhpLs3Ym0rVen9T2Uv1CC - sztjCoqy7ZNIKHTK8Zrmk0zBJo7K0fPGtoU2+tbNAoGAc2x7cW8y9ERRJg8FSWj0 - ADPUPzsW3Bu76U0oVEXg66d3+D9z8LBVhBYjL/+tJWADapePnye+gpqzd4kGFeV4 - q49Qz5hhZUzsUl2iOURQLUnI2g0AINiMIVL5EkZtdD+Au65+AaYL7YEsXmKcLXHX - coEomAoy102k4A6WsM2bKf4= - -----END PRIVATE KEY----- - JWT_PUBLIC_KEY: |- - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs4FNfD50a6W1Si1OBHsF - OO+mq4y3jAF6MUAL67n758ABpa757ofkxO8+0dyrJTsj9Jt20zGjcp9pWgxc9BEU - fChQN621FAyxvEAFKJpul9ljW2SahRvzkQVshFsv27zRGwk7RfCtPb+h0z3Dc6Uq - 8zfDCv8bCF5zE2v2nH7W3w94529ShTVmklCoBDPTi/DUlvD6ak3/pXv/kdA8IXeL - zZxC6aGymN7b4cY75UJm2QOr0azqFn1myl7kv+wewll1hIVz023CT3Cj7c5ZKSRp - a3B4L0waKzP5QWcO865n1HCUTnV+s4lNcphBDZCrSwTkXnVnQWVPCL7ssoQyM0u3 - HQIDAQAB - -----END PUBLIC KEY----- - image: circleci/mysql:5.7 environment: - MYSQL_ROOT_PASSWORD=circle diff --git a/Makefile b/Makefile index a453fb0..b009c87 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,11 @@ setup: @echo "to create the needed local keys and seed the database." bootstrap: + @echo "Bootstrapping KMS service ..." @docker-compose run kms make bootstrap + @echo "Bootstrapping Server service ..." @docker-compose run server make bootstrap + @echo "Bootstrapping Accounts service ..." @docker-compose run accounts make bootstrap .PHONY: setup bootstrap diff --git a/accounts/accounts/api.py b/accounts/accounts/api.py index 58a28d9..8fba6ae 100644 --- a/accounts/accounts/api.py +++ b/accounts/accounts/api.py @@ -106,7 +106,9 @@ def get_login(): ), 401, ) - return jsonify({"user": match.serialize()}) + if match: + return jsonify({"user": match.serialize()}) + return jsonify({"error": "unknown user id", "status": 401}), 401 @app.route("/api/logout", methods=["POST"]) diff --git a/accounts/accounts/config.py b/accounts/accounts/config.py index eec05fe..3c80bd1 100644 --- a/accounts/accounts/config.py +++ b/accounts/accounts/config.py @@ -7,14 +7,19 @@ class BaseConfig(object): FLASK_ADMIN_SWATCH = "flatly" -class EnvConfig(BaseConfig): +class LocalConfig(BaseConfig): SECRET_KEY = environ.get("SESSION_SECRET") SQLALCHEMY_DATABASE_URI = environ.get("MYSQL_CONNECTION_STRING") CORS_ORIGIN = environ.get("CORS_ORIGIN", "*") - JWT_PRIVATE_KEY = environ.get("JWT_PRIVATE_KEY", "") - JWT_PUBLIC_KEYS = [environ.get("JWT_PUBLIC_KEY", "")] COOKIE_DOMAIN = environ.get("COOKIE_DOMAIN") SERVER_HOST = environ.get("SERVER_HOST") + def __init__(self): + with open("private_key.pem") as f: + private_key = f.read() + with open("public_key.pem") as f: + public_key = f.read() + self.JWT_PRIVATE_KEY = private_key + self.JWT_PUBLIC_KEYS = [public_key] class SecretsManagerConfig(BaseConfig): diff --git a/accounts/lambdas/create_keys.py b/accounts/lambdas/create_keys.py new file mode 100644 index 0000000..3ba03f0 --- /dev/null +++ b/accounts/lambdas/create_keys.py @@ -0,0 +1,21 @@ +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.backends import default_backend + + +def create_key_pair(**kwargs): + key = rsa.generate_private_key( + backend=default_backend(), public_exponent=65537, **kwargs + ) + + public_key = key.public_key().public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 + ) + + pem = key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + + return {"private": pem.decode(), "public": public_key.decode()} diff --git a/accounts/lambdas/rotate_keys.py b/accounts/lambdas/rotate_keys.py index 7e97e28..ce8a10e 100644 --- a/accounts/lambdas/rotate_keys.py +++ b/accounts/lambdas/rotate_keys.py @@ -4,9 +4,7 @@ import logging import json from os import environ -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.backends import default_backend +from lambdas.create_keys import create_key_pair logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -67,24 +65,6 @@ def handler(event, context): raise ValueError("Invalid step parameter") -def create_key_pair(**kwargs): - key = rsa.generate_private_key( - backend=default_backend(), public_exponent=65537, **kwargs - ) - - public_key = key.public_key().public_bytes( - serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 - ) - - pem = key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption(), - ) - - return {"private": pem.decode(), "public": public_key.decode()} - - def create_secret(service_client, arn, token): service_client.get_secret_value(SecretId=arn, VersionStage="AWSCURRENT") try: diff --git a/accounts/scripts/bootstrap.py b/accounts/scripts/bootstrap.py index 2ab926e..af31af1 100755 --- a/accounts/scripts/bootstrap.py +++ b/accounts/scripts/bootstrap.py @@ -1,10 +1,19 @@ import yaml from passlib.hash import bcrypt -from accounts import db -from accounts.models import Account, User, AccountUserAssociation +from lambdas.create_keys import create_key_pair if __name__ == "__main__": + keypair = create_key_pair(key_size=2048) + with open("public_key.pem", "w") as f: + f.write(keypair["public"]) + + with open("private_key.pem", "w") as f: + f.write(keypair["private"]) + + from accounts import db + from accounts.models import Account, User, AccountUserAssociation + db.drop_all() db.create_all() @@ -29,4 +38,4 @@ if __name__ == "__main__": db.session.commit() - print("Successfully bootstrapped accounts database") + print("Successfully bootstrapped accounts database and created key pair") diff --git a/accounts/tests/test_api.py b/accounts/tests/test_api.py index f09b2b0..a9c8512 100644 --- a/accounts/tests/test_api.py +++ b/accounts/tests/test_api.py @@ -55,7 +55,7 @@ class TestKey(unittest.TestCase): rv = self.app.get("/api/key") assert rv.status.startswith("200") data = json.loads(rv.data) - assert data["keys"] == [environ.get("JWT_PUBLIC_KEY")] + assert data["keys"] class TestJWT(unittest.TestCase): diff --git a/docker-compose.yml b/docker-compose.yml index 5198a04..d273016 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,52 +117,13 @@ services: links: - accounts_database environment: - CONFIG_CLASS: accounts.config.EnvConfig + CONFIG_CLASS: accounts.config.LocalConfig FLASK_APP: accounts:app FLASK_ENV: development MYSQL_CONNECTION_STRING: mysql+pymysql://root:develop@accounts_database:3306/mysql CORS_ORIGIN: http://localhost:9977 SERVER_HOST: http://server:8080 SESSION_SECRET: vndJRFJTiyjfgtTF - JWT_PRIVATE_KEY: |- - -----BEGIN PRIVATE KEY----- - MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCa6AEl0RUW43YS - 6cfbYkEDoSxQV8WoEQPuwM9OPdrJjTHYwX9+J9dPhvdnuSIGca86jgYwg45xG5MC - 7arOIIGOI8rBuXWyPF/iZliWxUmzEMIsmZYFNTjezRl95ymy43ZH9JCS2pw2gTTq - Z4ln/hPkl2KMs+Two7iux52sgFRAxWYPNgqCpkbzEAL+zCHIPaZqsZjRX/nES7/y - JZWyvSSDQ/CsdeR+xssx2F1JXjQHl68dBEJJc7hAU4xNqs9aP5vaaL6PbwWOh6S7 - bzieDvankyZSrCxVBbMy8+JHXDLLQTp8Lv3y38HL/Ez0gefm2kRnGNTU19kwoFT+ - ceeuYE6RAgMBAAECggEAcZ2g2d/UnAkRXSXi1GHoVYUtP3BhJLf2LnN0mWp8wj+x - Q84IeLs4DLhtVcJP1nIjl8r7dzHGk+cpmIhBMxZcb6iI2jXwwV3O5fszFsJ1H8U2 - 5gdwJTm4EJJWFCYsS2zSIEycjVmSIdf6u8Jc4c1VQeBXA+QeEvHCT09RsmgdY7NA - zEZtpIf4EUXtQMmZdKwYVRJUJlBccAP2IR23lhAV6pS5n0pwOodnZD7lf/XAIrOm - Shy0WE+8DAxmg1DXzFLC+vw45kGJTdhHKc+ZFj86Nv2nUx0u12mb1hF3XlC66ojc - jmS0i5NbJVCqTSSXlC81V/16wCJmWIF0+O6y2EkZwQKBgQDLcgORV1QhUAz6QfiP - wR9OIWCJyxaGbaBe1FZvx8T9ofri8z/ycwxkzlEDCLYu7CZ2ZCpZLMwE5njzfmTq - ixhiVvu/fCPgo4bXRt6H/BaggBs04p8UB3ZJKl0ib67kKMXQjH2qFhuCS2Jpy3ru - REmRoukY4iAqHRdIrP6zW0y55QKBgQDC7BDIZrSbZNwkdbj+UilKEit+plBAvdan - 7CGLg4XhwiW7XYr9gHM3RKCAlUFRIsAaFClxiCvy21p3UpbO0XC5aIC5ReWYK9zh - THqwjxgDCev4FIe0md2fQCP7TP4wTYwvKjSqzI4v/XW86VYynegDBQSqeI8heuY4 - YbTBYMvHPQKBgQCy89wdiVJwZwizTTpFwNs3j3ZqXmC26FEreN17P56Qd13HKa6z - Je3d8fkikRQnnAONGjiB7jybhtsXW7OK98UAI4EYAytP2qeuTyFJPj3s+iJ0V28U - YCf03bXEp7aP7Slrc1jKNt4Fsyei5aCBW0HXQBSHlcgzIxmrDLiRrZqE3QKBgBPu - KUUkY0EkTfIYa2LtqbUeKH5ZqQkFoCYpWcC3IQBVZqBCz0xeTumOxc5/9F7Ea9n+ - x8IJB11cmmJq+mqJNbpvegH3qKMnkP0kYcMdznm5EPybtMh9lxCKcWNnmvH7a+MC - sMHqCnvTsa8wOJUSWj+8yp5Xl2L3+wQ20VGYgR2NAoGAE8/BCLHcescaA9PGr4Kx - EfVfoULTXOJp8a8j+N3C8cXhnroXGT6IYnETb34Z2J713m/5Ko/74PBWXnRvvnu4 - ATPZbyXlmtsorV5E5lRs9x6U9bRUzDEWLp1yITfaDHflYUWkrtAKDZ6GslCMbvQz - 9/CbsOaHzDf73JYos55E78E= - -----END PRIVATE KEY----- - JWT_PUBLIC_KEY: |- - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmugBJdEVFuN2EunH22JB - A6EsUFfFqBED7sDPTj3ayY0x2MF/fifXT4b3Z7kiBnGvOo4GMIOOcRuTAu2qziCB - jiPKwbl1sjxf4mZYlsVJsxDCLJmWBTU43s0ZfecpsuN2R/SQktqcNoE06meJZ/4T - 5JdijLPk8KO4rsedrIBUQMVmDzYKgqZG8xAC/swhyD2marGY0V/5xEu/8iWVsr0k - g0PwrHXkfsbLMdhdSV40B5evHQRCSXO4QFOMTarPWj+b2mi+j28Fjoeku284ng72 - p5MmUqwsVQWzMvPiR1wyy0E6fC798t/By/xM9IHn5tpEZxjU1NfZMKBU/nHnrmBO - kQIDAQAB - -----END PUBLIC KEY----- volumes: kmsdeps: diff --git a/shared/http/jwt.go b/shared/http/jwt.go index f7e9180..986fb6c 100644 --- a/shared/http/jwt.go +++ b/shared/http/jwt.go @@ -2,7 +2,6 @@ package http import ( "context" - "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -85,16 +84,11 @@ func tryParse(key []byte, tokenValue string) (*jwt.Token, error) { return nil, errors.New("jwt: no PEM block found in given key") } - parseResult, parseErr := x509.ParsePKIXPublicKey(keyBytes.Bytes) + pubKey, parseErr := x509.ParsePKCS1PublicKey(keyBytes.Bytes) if parseErr != nil { return nil, fmt.Errorf("jwt: error parsing key: %v", parseErr) } - pubKey, pubKeyOk := parseResult.(*rsa.PublicKey) - if !pubKeyOk { - return nil, errors.New("jwt: given key is not of type RSA public key") - } - token, jwtErr := jwt.ParseVerify(strings.NewReader(tokenValue), jwa.RS256, pubKey) if jwtErr != nil { return nil, fmt.Errorf("jwt: error parsing token: %v", jwtErr) diff --git a/shared/http/jwt_test.go b/shared/http/jwt_test.go index bd744c3..fe62b4e 100644 --- a/shared/http/jwt_test.go +++ b/shared/http/jwt_test.go @@ -17,45 +17,44 @@ import ( ) const publicKey = ` ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2yUfHH6SRYKvBTemrefi -Hk4L4qkcc4skl4QCaHOkfgA4VcGKG2nXysYuZK7AzNOcHQVi+e4BwN+BfIZtwEU5 -7Ogctb5eg8ksxxLjS7eSRfQIvPGfAbJ12R9OoOWcue/CdUy/YMec4R/o4+tZ45S6 -QQWIMhLqYljw+s1Runda3K8Q8lOdJ4yEZckXaZr1waNJikC7oGpT7ClAgdbvWIbo -N18G1OluRn+3WNdcN6V+vIj8c9dGs92bgTPX4cn3RmB/80BDfzeFiPMRw5xaq66F -42zXzllkTqukQPk2wmO5m9pFy0ciRve+awfgbTtZRZOEpTSWLbbpOfd4RQ5YqDWJ -mQIDAQAB ------END PUBLIC KEY----- +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAl2ifzOsh6AMRHZBe8xycvk01s3EAGT12WbgV9Z7b420Dj3NXrkns +N/jvBbtO9cjQg4WM7NPZLs+ZutRkHCtMxt7vB0kjOYetLPcGdObsVBB5k1jvwvsJ +HkcfmSsZdrV0Lz2Yxuf6ADWkxBqAY3GsS0zW0A2nIMc+41ZxqsZa3ProsKJxecRX +SSZMpZtqCGt/S83Rek4eAahllcWfZpQmoEk7usLuUl5tH2TmaW3e5lo0JNfdwcq5 +PMCa8WSZBFH3YzVttB8rbe7a7336wL2NJQFz6dswL5X1dECYpZ5TRtNgzQYa4V0W +AeICq+EzigaTxrjDHc5urHqEosz1le7O4QIDAQAB +-----END RSA PUBLIC KEY----- ` const privateKey = ` -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbJR8cfpJFgq8F -N6at5+IeTgviqRxziySXhAJoc6R+ADhVwYobadfKxi5krsDM05wdBWL57gHA34F8 -hm3ARTns6By1vl6DySzHEuNLt5JF9Ai88Z8BsnXZH06g5Zy578J1TL9gx5zhH+jj -61njlLpBBYgyEupiWPD6zVG6d1rcrxDyU50njIRlyRdpmvXBo0mKQLugalPsKUCB -1u9Yhug3XwbU6W5Gf7dY11w3pX68iPxz10az3ZuBM9fhyfdGYH/zQEN/N4WI8xHD -nFqrroXjbNfOWWROq6RA+TbCY7mb2kXLRyJG975rB+BtO1lFk4SlNJYttuk593hF -DlioNYmZAgMBAAECggEADvr6pXgBh77nN/QV8M1pJ6kuJtBooX1hgvoDMCC3neVl -9HbGehlCJxplEXzgsR/GDDXSDkO22vhsYZbO6dXRn+A+Fi5tR5T4+qLP5t0loqKL -9l6OAA+y/qSlO1p23D8Hi/0zF+qNTtZflTUBcA06rjcymDmyzAZIctyWOajvDSbK -Df0ZvKYPnwG5gjF01hPS2VJicv/O0HXLN7elq/jio1dwvLa2JjPyXhWBkHqnJBcq -ncWP9IEJQmhQ8ijNEg78uLtiNZQ4+GcXNBlwhM7JER6X/AxSxEZ/7fjZog685yUH -3iF820SnStOJQQci/RMMPOsK6cM7BiJxGp2W12EOAQKBgQD85UdCDro6zpblpAw7 -Gw82SkWGksJXuGlTX+nj3/3iIiEb4ATCvZufYXALGNtiG0tPHDMBQCKLYrbLE1pt -9uIU/IbDFPeQk8rR/b7IHu0gv3463p6r7WVhzY2/JCororKYQk4zbuk3cNYtlV76 -ojnNY1EFDLK/1nGT6QDxDA7Z5wKBgQDd1chB2qlbljRzYFwKrWXZ0COtbnEGPnUz -rLvSlAvYlZSKuB/vXkHGepxdlAjDGgX6xkKSl1TKb8UWQ9JSv0MPGBcMPukuwCAL -BOobyvd1mln6f/C7FrATkRbrG+r8RAQTwR+eknwYYOPAS/PpXm8gZvVntiahihFd -NqQtud8QfwKBgQDGV+xzWqmkxbKDmQ4erTJZGhc9XI0fz3qL8YW3O04btTjSa/hP -4/XSItGFYpFteIqwGSXHrU1qlJlY3GzoIeFfJE9tYVxpAADqgWDIA7lnHcka0s8P -eLky48xwRSTt5ES+NgKvRCWVXeIdDjHX0LQU6ff5ReRLoRyjLPOYGiTrsQKBgAmq -z1dPWCINoauFf31XoSCk2Wktbu9+uUzPMkAzA3Ek05xX+cxMp0EnBrltQhR+hdQv -36bTwXYw+L3HptrESv/VZOu7sh2/caYJSMp9RdtyJomsGamNi47Ou9jzFoJ31FWo -DOC0MYQ+dK5koPSCkQUwd3FVlsljYu5U+0Ki3v2xAoGASIMhNHOvz+Ay2otovVFN -gfRGTnepw8znHbkr10IG97BWd4VbFnHRdpYbtk8fH0UOyUVMrcY0B2/d73Rzqze3 -iZ//FXIDTtmKnVS/ZhC2w0AH8Piziy3NW3G6jRZN6+9NpOf/BIc4pfzgUJ3RqHz/ -IeONX+52k6gz1SCjPgSUlTs= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXaJ/M6yHoAxEd +kF7zHJy+TTWzcQAZPXZZuBX1ntvjbQOPc1euSew3+O8Fu071yNCDhYzs09kuz5m6 +1GQcK0zG3u8HSSM5h60s9wZ05uxUEHmTWO/C+wkeRx+ZKxl2tXQvPZjG5/oANaTE +GoBjcaxLTNbQDacgxz7jVnGqxlrc+uiwonF5xFdJJkylm2oIa39LzdF6Th4BqGWV +xZ9mlCagSTu6wu5SXm0fZOZpbd7mWjQk193Byrk8wJrxZJkEUfdjNW20Hytt7trv +ffrAvY0lAXPp2zAvlfV0QJilnlNG02DNBhrhXRYB4gKr4TOKBpPGuMMdzm6seoSi +zPWV7s7hAgMBAAECggEAYoPOxjSP4ThtoIDZZvHNAv2V3WW/HK0jHolqsGBmznmW +AXaZLGwo6NpuG5qea8n38jupUEcfXxfw/OFJKhL6Z8OSX3k1FC+1fDZW2yWNy7zU +fg02I/XXHv5EDxM+BEFYkYxQpcs2nYBJ7tcXhpzl8DDU7JaVkfxSbPVIDEf3wyP2 +k6DjYEeAj7uAsp50/32H9zhlJP/cFZaPiyFYy9/gOmDenrPVyJ/f7iQYNwYAwdbt +yfp11Wd5BpePR58+YXICE8oBtzHvB50akK6RZULC3xHVxLQQ1bSxx6vnttxw5RW+ +QRHTVWtRyWiKe/l5jMvVSUo5XCLqsL2iXfR4bz6hyQKBgQDJQVWEGHyD6JyaN/6i +5M5+O/YTbzMBgt1JAuVR2c0HYE4LpgrX4cA4kT3Bsa/Z9o0uuWVuxVab5gLsjsDu +EI4o+HJQ3pl4LF9xqrndTdybwmZAT6jv3rM/VGfaCDzXCPzVx169I+WsfkyGW7Tr +Cj4KDZyykruG/9OrpN1Aeq9a3wKBgQDAmCnQHLEPvZdezJGBc34HjZntrbW67iFB +L0waCGWydyunYmzfja1FSvlSoziZdqoq0N4+uBQFPZIlERvq0zMgIzNFvxt/WlFV +kQcBV24MNa8dtd+P7GDY8TzTfYBeXwJoi5c59sWLzSwpTlw0rI+ZvuA66eEF7xih +eWw3k1jOPwKBgQCKf8DHGEbQTEtBQlmlZkrIyqDs/PCgEJwSe8Cu1HFpqxfqokkC +CiTLiQB0BMEdAbRlPEcWtQ2GWgMXIqKY8qGyhk+9YYNCFV9VjQU9zDCOrHjLt0Zu +VNcMNR0HCfY8kb3VrM+A4GxVidFGAWR+/9xz9KwqpBoTrIjRrbJphkSZBwKBgBMs +0zTqNmLH0JNasL3/vrOH0KSOYAKdhOgVinEpFt7+6HTA4vAbDf5RKaOlppP48ZZT +t1ztPOkMqUlRe8MUhgmUF53BGj7CwkhPqS/kAYvrqGS/3+NXeIkA87pmy2oZ8YZx +J3xY6nAx3Ey8hYelCqMXEwIqmQHbPUuOaEzcOcJHAoGANw0TRha2YSbUqS3HiGR/ +Jm0lNfeLc3cYlEq0ggRDPLD11485rxVKnaKVHGPYW28OQ4jA+GCc7VZCfPV8VQXW +6b+jUnnBwu/KuYvGMee/xJv1c4MTG54mR9UrUt+R80S0OplpcYkcQnft2Bi+AZ1h +5aZTE7XIouXCYiMKPl4AMtI= -----END PRIVATE KEY----- `