diff --git a/privateSBOMExchange/Makefile b/privateSBOMExchange/Makefile index 610733c..3fcf6a7 100644 --- a/privateSBOMExchange/Makefile +++ b/privateSBOMExchange/Makefile @@ -5,4 +5,7 @@ init: test: py.test tests +server: + bash runserver.sh + .PHONY: init test diff --git a/privateSBOMExchange/requirements.txt b/privateSBOMExchange/requirements.txt index 2f525e0..89af532 100644 --- a/privateSBOMExchange/requirements.txt +++ b/privateSBOMExchange/requirements.txt @@ -1,27 +1,41 @@ beartype==0.18.5 +blinker==1.8.2 boolean.py==4.0 +certifi==2024.8.30 +cffi==1.17.1 +charset-normalizer==3.4.0 click==8.1.7 +cryptography==43.0.3 defusedxml==0.7.1 distlib==0.3.8 filelock==3.16.1 +Flask==3.0.3 +idna==3.10 isodate==0.6.1 +itsdangerous==2.2.0 +Jinja2==3.1.4 lib4sbom==0.7.5 license-expression==30.3.1 +MarkupSafe==3.0.2 numpy==2.1.1 pandas==2.2.2 platformdirs==4.3.6 ply==3.11 +pycparser==2.22 pyparsing==3.1.4 python-dateutil==2.9.0.post0 pytz==2024.2 PyYAML==6.0.2 rdflib==7.0.0 +requests==2.32.3 semantic-version==2.10.0 six==1.16.0 sparse-merkle-tree==0.3.0 -spdx-tools==0.8.0a2 +spdx-tools==0.8.3 typeguard==4.0.0 tzdata==2024.1 uritools==4.0.3 +urllib3==2.2.3 virtualenv==20.26.6 +Werkzeug==3.0.4 xmltodict==0.13.0 diff --git a/privateSBOMExchange/runserver.sh b/privateSBOMExchange/runserver.sh new file mode 100644 index 0000000..f18ba77 --- /dev/null +++ b/privateSBOMExchange/runserver.sh @@ -0,0 +1,2 @@ +export PYTHONPATH=$PWD +python src/cmd/redactor.py diff --git a/privateSBOMExchange/src/cmd/client.py b/privateSBOMExchange/src/cmd/client.py new file mode 100644 index 0000000..902f89a --- /dev/null +++ b/privateSBOMExchange/src/cmd/client.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +import requests +import click +import os + +@click.group() +def cli(): + pass + +@cli.command() +@click.argument('cert', type=str) +def enroll(cert): + """Enrolls a target Petra server (defaults to localhost:5000)""" + if os.path.exists(cert): + with open(cert) as fp: + payload = fp.read() + else: + printf(f"Couldn't load {cert}. It doesn't exist!") + + # TODO: we could be cutesy and check it's an actual PEM + request = {"cert": payload} + response = requests.post("http://localhost:5000/enroll", json=request) + print(response.json()) + + +if __name__ == '__main__': + cli() + diff --git a/privateSBOMExchange/src/cmd/redactor.py b/privateSBOMExchange/src/cmd/redactor.py new file mode 100644 index 0000000..3cde0ed --- /dev/null +++ b/privateSBOMExchange/src/cmd/redactor.py @@ -0,0 +1,39 @@ +from src.lib import redactor +from flask import Flask, request, make_response, jsonify +from cryptography import x509 + +# we keep this in memory, seems to hold some global variables that we may need +# to use. + +app = Flask(__name__) + +@app.route("/") +def hello_world(): + return "
Petra: this is a JSON api, you shouldn't be looking here!
" + +""" + enroll receives an object containing only cert, and returns a success/fail + symbol depending on how things went. + + +""" +@app.route("/enroll", methods=['POST']) +def enroll(): + data = request.get_json() + if 'cert' not in data: + # This error handling could be done better + raise Exception("request is malformed!") + cert = x509.load_pem_x509_certificate(bytes(data['cert'].encode('ascii'))) + + current_role = redactor.EnrollRoles(cert) + + response_data = {"status": "ok", + "current_role": current_role} + + return make_response(jsonify(response_data), 200) + + + +if __name__ == "__main__": + print("running redactor...") + app.run(debug=True) diff --git a/privateSBOMExchange/src/lib/redactor.py b/privateSBOMExchange/src/lib/redactor.py index 9bdab6a..7988f1a 100644 --- a/privateSBOMExchange/src/lib/redactor.py +++ b/privateSBOMExchange/src/lib/redactor.py @@ -18,6 +18,7 @@ from cryptography import x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding from spdx_tools.spdx.model import Document from spdx_tools.spdx3.bump_from_spdx2 import spdx_document import os @@ -85,29 +86,29 @@ def GenerateSBOM (artifact) : #return SBOM def GetRole(key): - - - logging.debug("Inside GetRoles") - role="" - return role + + if key in Users: + return Users[key] + return None def EnrollRoles (cert): logging.debug("Inside EnrollRoles") currentRole="" - public_key=cert.public_key() + public_key=cert.public_key().public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) #TODO verify the key + #import pdb; pdb.set_trace() user = { "organizationName": cert.subject.get_attributes_for_oid(x509.OID_ORGANIZATION_NAME)[0].value, "commonName": cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value, "key":public_key } - Users.add(user) + Users[user["key"]] = user #print(cert.public_key()) currentRole=GetRole(public_key) - return currentRole + return currentRole["commonName"] def CalculateRoles (policy): logging.debug("Inside CalculateRoles") @@ -129,4 +130,4 @@ def CalculateRoles (policy): - \ No newline at end of file + diff --git a/privateSBOMExchange/tests/data/certs/github.com b/privateSBOMExchange/tests/data/certs/github.com new file mode 100644 index 0000000..c0b77ee --- /dev/null +++ b/privateSBOMExchange/tests/data/certs/github.com @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEozCCBEmgAwIBAgIQTij3hrZsGjuULNLEDrdCpTAKBggqhkjOPQQDAjCBjzEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5T +ZWN0aWdvIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMB4X +DTI0MDMwNzAwMDAwMFoXDTI1MDMwNzIzNTk1OVowFTETMBEGA1UEAxMKZ2l0aHVi +LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABARO/Ho9XdkY1qh9mAgjOUkW +mXTb05jgRulKciMVBuKB3ZHexvCdyoiCRHEMBfFXoZhWkQVMogNLo/lW215X3pGj +ggL+MIIC+jAfBgNVHSMEGDAWgBT2hQo7EYbhBH0Oqgss0u7MZHt7rjAdBgNVHQ4E +FgQUO2g/NDr1RzTK76ZOPZq9Xm56zJ8wDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB +/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEkGA1UdIARCMEAw +NAYLKwYBBAGyMQECAgcwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNv +bS9DUFMwCAYGZ4EMAQIBMIGEBggrBgEFBQcBAQR4MHYwTwYIKwYBBQUHMAKGQ2h0 +dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb0VDQ0RvbWFpblZhbGlkYXRpb25T +ZWN1cmVTZXJ2ZXJDQS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3Rp +Z28uY29tMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdwDPEVbu1S58r/OHW9lp +LpvpGnFnSrAX7KwB0lt3zsw7CAAAAY4WOvAZAAAEAwBIMEYCIQD7oNz/2oO8VGaW +WrqrsBQBzQH0hRhMLm11oeMpg1fNawIhAKWc0q7Z+mxDVYV/6ov7f/i0H/aAcHSC +Ii/QJcECraOpAHYAouMK5EXvva2bfjjtR2d3U9eCW4SU1yteGyzEuVCkR+cAAAGO +Fjrv+AAABAMARzBFAiEAyupEIVAMk0c8BVVpF0QbisfoEwy5xJQKQOe8EvMU4W8C +IGAIIuzjxBFlHpkqcsa7UZy24y/B6xZnktUw/Ne5q5hCAHcATnWjJ1yaEMM4W2zU +3z9S6x3w4I4bjWnAsfpksWKaOd8AAAGOFjrv9wAABAMASDBGAiEA+8OvQzpgRf31 +uLBsCE8ktCUfvsiRT7zWSqeXliA09TUCIQDcB7Xn97aEDMBKXIbdm5KZ9GjvRyoF +9skD5/4GneoMWzAlBgNVHREEHjAcggpnaXRodWIuY29tgg53d3cuZ2l0aHViLmNv +bTAKBggqhkjOPQQDAgNIADBFAiEAru2McPr0eNwcWNuDEY0a/rGzXRfRrm+6XfZe +SzhYZewCIBq4TUEBCgapv7xvAtRKdVdi/b4m36Uyej1ggyJsiesA +-----END CERTIFICATE----- diff --git a/privateSBOMExchange/tests/data/certs/www.purdue.edu b/privateSBOMExchange/tests/data/certs/www.purdue.edu new file mode 100644 index 0000000..d9bfcec --- /dev/null +++ b/privateSBOMExchange/tests/data/certs/www.purdue.edu @@ -0,0 +1,96 @@ +-----BEGIN CERTIFICATE----- +MIIRlzCCD/+gAwIBAgIRANn1HoLfqOdrsKHwtang+yEwDQYJKoZIhvcNAQEMBQAw +RDELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUludGVybmV0MjEhMB8GA1UEAxMYSW5D +b21tb24gUlNBIFNlcnZlciBDQSAyMB4XDTI0MTAwODAwMDAwMFoXDTI1MTAwODIz +NTk1OVowVDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0luZGlhbmExGjAYBgNVBAoT +EVB1cmR1ZSBVbml2ZXJzaXR5MRcwFQYDVQQDEw53d3cucHVyZHVlLmVkdTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALx6cq7pIodqB+CqgU0FEAoR7MEa +XU8QpWWJSdTnWFQKtqP1+UaHNvrDmFcc7wzQosB2JB1gW3Tn94qt98Ur2MjCC29Z +XY5G8fwH4H6P9I0AQ6zjdMGR8m6AYqeV1aCms2Kn9z+y5j5nph3vHZBBHBKtX7uU +of+4NcarI5uegHgM+w0C0Qb1vw603xYUhKDzTBd/nlCFe/yg/wTT6kTAyTgZfs5a +fy/LY12JgY9gYSbDN1lxLpWaR6wyoFUQwA6JuR6e30Poe4RD/yc1IETXyOQZ0d1w +sbFIi3bLBhQn1AmwRscL+pUa6B8QDmhzYhSFavDKtRm5N3T20m0TuCnO6GUCAwEA +AaOCDfIwgg3uMB8GA1UdIwQYMBaAFO9MAJKm+3YuXpXiyV+HGxnVTeLZMB0GA1Ud +DgQWBBRabR0MM9BlcYas/CsAAW+x/OKyADAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0T +AQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwSQYDVR0gBEIw +QDA0BgsrBgEEAbIxAQICZzAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28u +Y29tL0NQUzAIBgZngQwBAgIwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cDovL2NybC5z +ZWN0aWdvLmNvbS9JbkNvbW1vblJTQVNlcnZlckNBMi5jcmwwcAYIKwYBBQUHAQEE +ZDBiMDsGCCsGAQUFBzAChi9odHRwOi8vY3J0LnNlY3RpZ28uY29tL0luQ29tbW9u +UlNBU2VydmVyQ0EyLmNydDAjBggrBgEFBQcwAYYXaHR0cDovL29jc3Auc2VjdGln +by5jb20wggF8BgorBgEEAdZ5AgQCBIIBbASCAWgBZgB1AN3cyjSV1+EWBeeVMvrH +n/g9HFDf2wA6FBJ2Ciysu8gqAAABkm0tiscAAAQDAEYwRAIgcLdfJQqms1QoaKVE +nUYCLySYBBd2Uw80EFy5dWleZ4wCIEMcCx0TSpCvpkQS8p09gaVdkOsQRAYUQ7mC ++Hwngx64AHYAzPsPaoVxCWX+lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGSbS2K +gQAABAMARzBFAiEA9E9lCczUJpv3giqKn1PNv5z75qq3U/BtJTNKtbxiOA8CIG8Q +FfWAkzRYT6OYbHVDzyDA9cReecySRwM1pIcb0+A4AHUAEvFONL1TckyEBhnDjz96 +E/jntWKHiJxtMAWE6+WGJjoAAAGSbS2KUQAABAMARjBEAiB/ZBr2VqoQyH6C84R5 +rsSdA0D8iw68zjl+TP90PpRa6wIgZoWdSUPzbzv3n1zi/KjiDHRFhCmB+jSvUopH +GzsPhB4wggruBgNVHREEggrlMIIK4YIOd3d3LnB1cmR1ZS5lZHWCFGFkbWlzc2lv +bi5wdXJkdWUuZWR1ghVhZG1pc3Npb25zLnB1cmR1ZS5lZHWCIGFsc3Rhci1pbnRl +cm5hbC5jZXJpcy5wdXJkdWUuZWR1ghxhbHN0YXItcGV0cy5jZXJpcy5wdXJkdWUu +ZWR1ghFhbmdsZXJhcmNoaXZlLm9yZ4IPYW5zYy5wdXJkdWUuZWR1giBhcHByb3Zl +ZG1ldGhvZHMuY2VyaXMucHVyZHVlLmVkdYIWYXBwcy5vbmxpbmUucHVyZHVlLmVk +dYIUYXJib3JldHVtLnB1cmR1ZS5lZHWCGmJlZWtlZXBlci5jZXJpcy5wdXJkdWUu +ZWR1ghVibG9ncy5pdGFwLnB1cmR1ZS5lZHWCD2J0bnkucHVyZHVlLmVkdYITYnVz +aW5lc3MucHVyZHVlLmVkdYIdYnVzaW5lc3NwbGFuLmNlcmlzLnB1cmR1ZS5lZHWC +FWNhcHMuY2VyaXMucHVyZHVlLmVkdYIPY2FyZS5wdXJkdWUuZWR1ghBjZGV4dC5w +dXJkdWUuZWR1ghxjZWxsd2FsbC5nZW5vbWljcy5wdXJkdWUuZWR1ghtjcHNpY2Nu +ZXh1c3dvcmtzaG9wMjAyNC5vcmeCEGN5YmVyYnJpZGdlcy5vcmeCE2RhdGFtaW5l +LnB1cmR1ZS5lZHWCIWRlY2lzaW9uLXN1cHBvcnQuY2VyaXMucHVyZHVlLmVkdYIa +ZGlzY292ZXIub25saW5lLnB1cmR1ZS5lZHWCFmVjZHJlLmNlcmlzLnB1cmR1ZS5l +ZHWCD2VudG0ucHVyZHVlLmVkdYISZXNwb3J0cy5wdXJkdWUuZWR1ghNldmJhdHRl +cnlwb3J0YWwub3Jngg9leGVkLnB1cmR1ZS5lZHWCGWV4dGVuc2lvbi5lbnRtLnB1 +cmR1ZS5lZHWCFWZlZC5vbmxpbmUucHVyZHVlLmVkdYINZmlzaGF0bGFzLm9yZ4IO +Z2xvYmFsZWFzdC5vcmeCFWdsb2JhbHNvdW5kc2NhcGVzLm9yZ4IWZ29tZWV0Lml0 +YXAucHVyZHVlLmVkdYIVZ3JhZHNjaG9vbC5wdXJkdWUuZWR1gh1ncmVhdHRlYWNo +ZXIuY2VyaXMucHVyZHVlLmVkdYIPaGNkZC5wdXJkdWUuZWR1gg5oaHMucHVyZHVl +LmVkdYIMaGxiLXJlb24ub3JnghFob25vcnMucHVyZHVlLmVkdYIPaG9ydC5wdXJk +dWUuZWR1ghdob3Rob2cuY2VyaXMucHVyZHVlLmVkdYIRaS1ob3BlLnB1cmR1ZS5l +ZHWCEWluZGlhbmFnZWFydXAub3JnghVpc2ViLmNlcmlzLnB1cmR1ZS5lZHWCDWl0 +LnB1cmR1ZS5lZHWCD2l0YXAucHVyZHVlLmVkdYITa3Jhbm5lcnQucHVyZHVlLmVk +dYIPbGVhdmVhcHJpbnQudmV0ghRsZXVuZy5iaW8ucHVyZHVlLmVkdYIhbWFuZnJh +dHNlbnNvcnMucGh5c2ljcy5wdXJkdWUuZWR1ghZuYXBpcy5jZXJpcy5wdXJkdWUu +ZWR1ghJuZXcuY2NvLnB1cmR1ZS5lZHWCF25ldy5kYXRhbWluZS5wdXJkdWUuZWR1 +ghduZXcuZWR1c3RvcmUucHVyZHVlLmVkdYIVbmV3Lmhvbm9ycy5wdXJkdWUuZWR1 +ghJuZXcud3d3LnB1cmR1ZS5lZHWCE25ld3MudW5zLnB1cmR1ZS5lZHWCG25vbWVu +Y2xhdG9yLml0YXAucHVyZHVlLmVkdYIjbnVyc2VyeS1pbnNwZWN0aW9uLmNlcmlz +LnB1cmR1ZS5lZHWCDm93bC5wdXJkdWUuZWR1ghpwYXRod2F5dG9wdXJkdWUucHVy +ZHVlLmVkdYIPcGNyZC5wdXJkdWUuZWR1gg9wZXN0dHJhY2tlci5vcmeCD3BpY2Mu +cHVyZHVlLmVkdYIKcHVyZHVlLmVkdYIicHVyZHVlcGxhbnRkb2N0b3IuY2VyaXMu +cHVyZHVlLmVkdYIVcHVyZHVlcGxhbnRkb2N0b3IuY29tghJyZWNvcmR0aGVlYXJ0 +aC5vcmeCD3NtYXMucHVyZHVlLmVkdYIQc29pbGV4cGxvcmVyLm5ldIIWc29pbHNv +ZnQuYWcucHVyZHVlLmVkdYIWc3BpZGVyLmVudG0ucHVyZHVlLmVkdYINc3VjY2Vz +c25leC51c4IQc3VjY2Vzc25leHVzLm9yZ4Ifc3VydmV5cGxhbm5pbmcuY2VyaXMu +cHVyZHVlLmVkdYIcc3VzdGFpbmFibGUuY2VyaXMucHVyZHVlLmVkdYIadHdpZ2Fn +aW5nLmNlcmlzLnB1cmR1ZS5lZHWCDnZldC5wdXJkdWUuZWR1ghh3d3cuYWRtaXNz +aW9uLnB1cmR1ZS5lZHWCGXd3dy5hZG1pc3Npb25zLnB1cmR1ZS5lZHWCFXd3dy5h +bmdsZXJhcmNoaXZlLm9yZ4ITd3d3LmFuc2MucHVyZHVlLmVkdYITd3d3LmJ0bnku +cHVyZHVlLmVkdYIXd3d3LmJ1c2luZXNzLnB1cmR1ZS5lZHWCE3d3dy5jYXJlLnB1 +cmR1ZS5lZHWCFHd3dy5jZGV4dC5wdXJkdWUuZWR1gh93d3cuY3BzaWNjbmV4dXN3 +b3Jrc2hvcDIwMjQub3JnghR3d3cuY3liZXJicmlkZ2VzLm9yZ4IXd3d3LmRhdGFt +aW5lLnB1cmR1ZS5lZHWCE3d3dy5lbnRtLnB1cmR1ZS5lZHWCF3d3dy5ldmJhdHRl +cnlwb3J0YWwub3JnghN3d3cuZXhlZC5wdXJkdWUuZWR1gh13d3cuZXh0ZW5zaW9u +LmVudG0ucHVyZHVlLmVkdYIRd3d3LmZpc2hhdGxhcy5vcmeCEnd3dy5nbG9iYWxl +YXN0Lm9yZ4IZd3d3Lmdsb2JhbHNvdW5kc2NhcGVzLm9yZ4IZd3d3LmdyYWRzY2hv +b2wucHVyZHVlLmVkdYISd3d3Lmhocy5wdXJkdWUuZWR1ghB3d3cuaGxiLXJlb24u +b3JnghV3d3cuaG9ub3JzLnB1cmR1ZS5lZHWCE3d3dy5ob3J0LnB1cmR1ZS5lZHWC +FXd3dy5pLWhvcGUucHVyZHVlLmVkdYIVd3d3LmluZGlhbmFnZWFydXAub3JnghJ3 +d3cuaXJiLnB1cmR1ZS5lZHWCEXd3dy5pdC5wdXJkdWUuZWR1ghN3d3cuaXRhcC5w +dXJkdWUuZWR1ghd3d3cua3Jhbm5lcnQucHVyZHVlLmVkdYITd3d3LmxlYXZlYXBy +aW50LnZldIIed3d3LnBhdGh3YXl0b3B1cmR1ZS5wdXJkdWUuZWR1ghN3d3cucGNy +ZC5wdXJkdWUuZWR1ghN3d3cucGVzdHRyYWNrZXIub3JnghN3d3cucGljYy5wdXJk +dWUuZWR1ghl3d3cucHVyZHVlcGxhbnRkb2N0b3IuY29tghZ3d3cucmVjb3JkdGhl +ZWFydGgub3Jnghd3d3cucm9ib3RpY3MucHVyZHVlLmVkdYITd3d3LnNtYXMucHVy +ZHVlLmVkdYIUd3d3LnNvaWxleHBsb3Jlci5uZXSCG3d3dy5zdHVkZW50LW9yZ3Mu +cHVyZHVlLmVkdYIRd3d3LnN1Y2Nlc3NuZXgudXOCFHd3dy5zdWNjZXNzbmV4dXMu +b3JnghJ3d3cudmV0LnB1cmR1ZS5lZHUwDQYJKoZIhvcNAQEMBQADggGBAC/Az8zJ +iIXqdOmEelmCqHCoIVEHGrHnfdKeZrwhai5J14HxEz1JOTwfBtGRgij0sX/iC5Ql +1yWvUmfuQ4fIiebOo4MCN2VeHEdTY8bqYliTobqlA1/FuIuNjoOuRsAnnVo4WeUe +LewR7mYUtxrEUSrHC3XLNxCDuLkhDhGZgZ1MQ98NVYF1Sck54sqM78pZbLYq+Oa8 +xcDpBs+UdVj/tNLq/2QPTCEnJZq8/N6Z1NPiEm/fAzmG6LyCRPs1kWk5dH4DG8O8 +Sd/UAeuk6Rj3q7WE0saM8fsEcsQsq9yshmDfneIXdVRP6WMdEjajVFTHe9KnweLy +Lcz5DToryk0E0VTdkH28nbKkrNpBhXYP3qjz/5y/FIXVQ3Ss9/V9mhIArtINa9Kv +BSXw8QogLXLZ1J+t9zN/2MKO1oZOUOFp6GapGW70m9twoRGtguSHLKWtHjP9vnor +iKly+d2u5ghOd60+aznQchyNEnSxhVXYN1KekWQ8oRu9hSKL4nFqBBiTLg== +-----END CERTIFICATE-----