From 2cbcec7d67b99d73817ed025b7510adbaa12afa4 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Wed, 2 Jul 2025 22:06:20 -0400 Subject: [PATCH 01/13] [RTOP-24] Created credentials.py and added in instalation the required cryptography python library --- background_installer.sh | 6 +++--- requirements.txt | 1 + webserver/credentials.py | 39 +++++++++++++++++++++++++++++++++++++++ webserver/webserver.py | 11 ++++++++++- 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 webserver/credentials.py diff --git a/background_installer.sh b/background_installer.sh index dae1784..7e901d6 100755 --- a/background_installer.sh +++ b/background_installer.sh @@ -92,9 +92,9 @@ function install_py_deps { python3 -m venv "$VENV_DIR" "$VENV_DIR/bin/python3" -m pip install --upgrade pip if [ "$1" == "neuron" ]; then - "$VENV_DIR/bin/python3" -m pip install flask==2.2.5 werkzeug==2.2.2 flask-login==0.6.2 pyserial pymodbus==2.5.3 + "$VENV_DIR/bin/python3" -m pip install flask==2.2.5 werkzeug==2.2.2 flask-login==0.6.2 pyserial pymodbus==2.5.3 cryptography else - "$VENV_DIR/bin/python3" -m pip install flask==2.3.3 werkzeug==2.3.7 flask-login==0.6.2 pyserial pymodbus==2.5.3 + "$VENV_DIR/bin/python3" -m pip install flask==2.3.3 werkzeug==2.3.7 flask-login==0.6.2 pyserial pymodbus==2.5.3 cryptography fi python3 -m pip install pymodbus==2.5.3 } @@ -278,7 +278,7 @@ if [ "$1" == "win" ]; then #Setting up venv python3 -m venv "$VENV_DIR" "$VENV_DIR/bin/python3" get-pip3.py - "$VENV_DIR/bin/python3" -m pip install flask==2.3.3 werkzeug==2.3.7 flask-login==0.6.2 pyserial pymodbus==2.5.3 + "$VENV_DIR/bin/python3" -m pip install flask==2.3.3 werkzeug==2.3.7 flask-login==0.6.2 pyserial pymodbus==2.5.3 cryptography echo "" echo "[MATIEC COMPILER]" diff --git a/requirements.txt b/requirements.txt index 06cfa7e..84daaaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ Flask==1.0.2 Flask-Login==0.4.1 pyserial==3.4 pymodbus==2.2.0 +cryptography diff --git a/webserver/credentials.py b/webserver/credentials.py new file mode 100644 index 0000000..101c353 --- /dev/null +++ b/webserver/credentials.py @@ -0,0 +1,39 @@ +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes + +key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, +) + +with open("/home/lucas/Documents/secrets/key.pem", "wb") as f: + f.write(key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), + )) + +csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ + # Provide various details about who we are. + x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), + x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), + x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), + ])).add_extension( + x509.SubjectAlternativeName([ + # Describe what sites we want this certificate for. + x509.DNSName("mysite.com"), + x509.DNSName("www.mysite.com"), + x509.DNSName("subdomain.mysite.com"), + ]), + critical=False, + # Sign the CSR with our private key. +).sign(key, hashes.SHA256()) + +# Write our CSR out to disk. +with open("/home/lucas/Documents/secrets/csr.pem", "wb") as f: + f.write(csr.public_bytes(serialization.Encoding.PEM)) \ No newline at end of file diff --git a/webserver/webserver.py b/webserver/webserver.py index b3defec..6ba679e 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -14,6 +14,7 @@ import sys import ctypes import socket import mimetypes +import ssl import flask import flask_login @@ -2527,7 +2528,15 @@ if __name__ == '__main__': configure_runtime() monitor.parse_st(openplc_runtime.project_file) - app.run(debug=False, host='0.0.0.0', threaded=True, port=8080) + try: + context = ('/home/lucas/Documents/secrets/csr.pem', '/home/lucas/Documents/secrets/key.pem') + app.run(debug=False, host='0.0.0.0', threaded=True, port=8080, ssl_context=context) + # TODO handle file error + except FileNotFoundError: + print("Could not find SSL credintails!") + except ssl.SSLError: + print("SSL credentials FAIL!") + except Error as e: print("error connecting to the database" + str(e)) From a1644cded941961f5e9e9baf2694c44639a3632b Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Wed, 2 Jul 2025 22:45:35 -0400 Subject: [PATCH 02/13] [RTOP-24] Fix credential generation for Flask --- webserver/credentials.py | 139 ++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 31 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index 101c353..88c9656 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -1,39 +1,116 @@ -from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +import datetime +import ipaddress +import os -key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, -) +# key = rsa.generate_private_key( +# public_exponent=65537, +# key_size=4096, +# backend=default_backend() +# ) -with open("/home/lucas/Documents/secrets/key.pem", "wb") as f: - f.write(key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), - )) +# with open("/home/lucas/Documents/secrets/key.pem", "wb") as f: +# f.write(key.private_bytes( +# encoding=serialization.Encoding.PEM, +# format=serialization.PrivateFormat.TraditionalOpenSSL, +# encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), +# )) -csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ - # Provide various details about who we are. - x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), - x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), - x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), - ])).add_extension( - x509.SubjectAlternativeName([ - # Describe what sites we want this certificate for. - x509.DNSName("mysite.com"), - x509.DNSName("www.mysite.com"), - x509.DNSName("subdomain.mysite.com"), - ]), - critical=False, - # Sign the CSR with our private key. -).sign(key, hashes.SHA256()) +# csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ +# # Provide various details about who we are. +# x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), +# x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), +# x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), +# x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), +# x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), +# ])).add_extension( +# x509.SubjectAlternativeName([ +# # Describe what sites we want this certificate for. +# x509.DNSName("mysite.com"), +# x509.DNSName("www.mysite.com"), +# x509.DNSName("subdomain.mysite.com"), +# ]), +# critical=False, +# # Sign the CSR with our private key. +# ).sign(key, hashes.SHA256()) -# Write our CSR out to disk. -with open("/home/lucas/Documents/secrets/csr.pem", "wb") as f: - f.write(csr.public_bytes(serialization.Encoding.PEM)) \ No newline at end of file +# # Write our CSR out to disk. +# with open("/home/lucas/Documents/secrets/csr.pem", "wb") as f: +# f.write(csr.public_bytes(serialization.Encoding.PEM)) + +def generate_self_signed_cert(hostname, ip_addresses=None, cert_file="cert.pem", key_file="key.pem"): + """ + Generates a self-signed TLS certificate and private key. + + Args: + hostname (str): The common name (CN) for the certificate. + ip_addresses (list, optional): A list of IP addresses to include in the SAN extension. + cert_file (str): The filename for the certificate (PEM format). + key_file (str): The filename for the private key (PEM format). + """ + print(f"Generating self-signed certificate for {hostname}...") + + # Generate our key + key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) + + # Subject and Issuer + subject = issuer = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"), + x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Development Company"), + x509.NameAttribute(NameOID.COMMON_NAME, hostname), + ]) + + # Subject Alternative Names (SAN) + alt_names = [x509.DNSName(hostname)] + if ip_addresses: + for addr in ip_addresses: + alt_names.append(x509.IPAddress(ipaddress.ip_address(addr))) + + san_extension = x509.SubjectAlternativeName(alt_names) + + # Certificate validity + now = datetime.datetime.utcnow() + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now) + .not_valid_after(now + datetime.timedelta(days=365)) # Valid for 1 year + .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) + .add_extension(san_extension, critical=False) + .sign(key, hashes.SHA256(), default_backend()) + ) + + # Write our certificate and key to disk + with open(cert_file, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + with open(key_file, "wb") as f: + f.write(key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + )) + print(f"Certificate saved to {cert_file}") + print(f"Private key saved to {key_file}") + +if __name__ == '__main__': + CERT_FILE = "secrets/cert.pem" + KEY_FILE = "secrets/key.pem" + HOSTNAME = "localhost" # Or your local IP address, e.g., "127.0.0.1" + + # Generate certificate if it doesn't exist + if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): + generate_self_signed_cert(HOSTNAME, ip_addresses=["127.0.0.1"], cert_file=CERT_FILE, key_file=KEY_FILE) From c6e1db47f2f88abd1e490a8923fb3afb95868170 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Wed, 2 Jul 2025 23:22:32 -0400 Subject: [PATCH 03/13] [RTOP-24] Class implementation --- webserver/credentials.py | 104 +++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index 88c9656..568006d 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -42,8 +42,7 @@ import os # # Write our CSR out to disk. # with open("/home/lucas/Documents/secrets/csr.pem", "wb") as f: # f.write(csr.public_bytes(serialization.Encoding.PEM)) - -def generate_self_signed_cert(hostname, ip_addresses=None, cert_file="cert.pem", key_file="key.pem"): +class CertGen(): """ Generates a self-signed TLS certificate and private key. @@ -53,58 +52,67 @@ def generate_self_signed_cert(hostname, ip_addresses=None, cert_file="cert.pem", cert_file (str): The filename for the certificate (PEM format). key_file (str): The filename for the private key (PEM format). """ - print(f"Generating self-signed certificate for {hostname}...") + def __init__(self, hostname, ip_addresses=None): + self.hostname = hostname + self.ip_addresses = ip_addresses - # Generate our key - key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=default_backend() - ) + # Certificate validity + self.now = datetime.datetime.utcnow() + # Subject and Issuer + self.subject = self.issuer = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"BR"), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"openplc"), + x509.NameAttribute(NameOID.LOCALITY_NAME, u"openplc"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Autonomy"), + x509.NameAttribute(NameOID.COMMON_NAME, hostname), + ]) - # Subject and Issuer - subject = issuer = x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Development Company"), - x509.NameAttribute(NameOID.COMMON_NAME, hostname), - ]) + # Subject Alternative Names (SAN) + self.alt_names = [x509.DNSName(hostname)] + if ip_addresses: + for addr in ip_addresses: + self.alt_names.append(x509.IPAddress(ipaddress.ip_address(addr))) - # Subject Alternative Names (SAN) - alt_names = [x509.DNSName(hostname)] - if ip_addresses: - for addr in ip_addresses: - alt_names.append(x509.IPAddress(ipaddress.ip_address(addr))) + self.san_extension = x509.SubjectAlternativeName(alt_names) + + def generate_key(self): + # Generate our key + self.key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend() + ) - san_extension = x509.SubjectAlternativeName(alt_names) + def generate_self_signed_cert(self, cert_file="cert.pem", key_file="key.pem"): + print(f"Generating self-signed certificate for {self.hostname}...") - # Certificate validity - now = datetime.datetime.utcnow() - cert = ( - x509.CertificateBuilder() - .subject_name(subject) - .issuer_name(issuer) - .public_key(key.public_key()) - .serial_number(x509.random_serial_number()) - .not_valid_before(now) - .not_valid_after(now + datetime.timedelta(days=365)) # Valid for 1 year - .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) - .add_extension(san_extension, critical=False) - .sign(key, hashes.SHA256(), default_backend()) - ) + self.generate_key() + + cert = ( + x509.CertificateBuilder() + .subject_name(self.subject) + .issuer_name(self.issuer) + .public_key(self.key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(self.now) + .not_valid_after(self.now + datetime.timedelta(days=365)) # Valid for 1 year + .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) + .add_extension(self.san_extension, critical=False) + .sign(self.key, hashes.SHA256(), default_backend()) + ) + + # Write our certificate and key to disk + with open(cert_file, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + with open(key_file, "wb") as f: + f.write(self.key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + )) + print(f"Certificate saved to {cert_file}") + print(f"Private key saved to {key_file}") - # Write our certificate and key to disk - with open(cert_file, "wb") as f: - f.write(cert.public_bytes(serialization.Encoding.PEM)) - with open(key_file, "wb") as f: - f.write(key.private_bytes( - serialization.Encoding.PEM, - serialization.PrivateFormat.PKCS8, - serialization.NoEncryption() - )) - print(f"Certificate saved to {cert_file}") - print(f"Private key saved to {key_file}") if __name__ == '__main__': CERT_FILE = "secrets/cert.pem" From 5cab6bd2db2ba52a9f3cb57f24d1c6dda60b0171 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 3 Jul 2025 09:13:56 -0400 Subject: [PATCH 04/13] [RTOP-24] Error handling of credentials file not found or wrong --- webserver/webserver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/webserver/webserver.py b/webserver/webserver.py index 6ba679e..9e89d23 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -2529,13 +2529,13 @@ if __name__ == '__main__': monitor.parse_st(openplc_runtime.project_file) try: - context = ('/home/lucas/Documents/secrets/csr.pem', '/home/lucas/Documents/secrets/key.pem') + context = ('/home/lucas/Documents/secrets/cert2.pem', '/home/lucas/Documents/secrets/key2.pem') app.run(debug=False, host='0.0.0.0', threaded=True, port=8080, ssl_context=context) # TODO handle file error - except FileNotFoundError: - print("Could not find SSL credintails!") - except ssl.SSLError: - print("SSL credentials FAIL!") + except FileNotFoundError as e: + print(f"Could not find SSL credentials! {e}") + except ssl.SSLError as e: + print(f"SSL credentials FAIL! {e}") except Error as e: From b675f3a164c6a9d1f09fb0d86b158070e0ee6716 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Mon, 7 Jul 2025 21:59:38 -0400 Subject: [PATCH 05/13] [RTOP-24] Certificate expiration function validation --- webserver/credentials.py | 56 +++++++++++++++++++++++++++++++++++----- webserver/webserver.py | 28 ++++++++++++++++---- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index 568006d..f5bbf60 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -73,7 +73,7 @@ class CertGen(): for addr in ip_addresses: self.alt_names.append(x509.IPAddress(ipaddress.ip_address(addr))) - self.san_extension = x509.SubjectAlternativeName(alt_names) + self.san_extension = x509.SubjectAlternativeName(self.alt_names) def generate_key(self): # Generate our key @@ -102,9 +102,9 @@ class CertGen(): ) # Write our certificate and key to disk - with open(cert_file, "wb") as f: + with open(cert_file, "wb+") as f: f.write(cert.public_bytes(serialization.Encoding.PEM)) - with open(key_file, "wb") as f: + with open(key_file, "wb+") as f: f.write(self.key.private_bytes( serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, @@ -113,12 +113,54 @@ class CertGen(): print(f"Certificate saved to {cert_file}") print(f"Private key saved to {key_file}") + def is_certificate_valid(self, cert_file): + """ + Checks if a certificate is valid (not expired and not yet valid). + + Args: + cert_file (str): The path to the certificate file (PEM format). + + Returns: + bool: True if the certificate is currently valid, False otherwise. + """ + if not os.path.exists(cert_file): + print(f"Certificate file not found: {cert_file}") + return False + + try: + with open(cert_file, "rb") as f: + cert_data = f.read() + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) + + now = datetime.datetime.utcnow() + + if now < cert.not_valid_before: + print(f"Certificate is not yet valid. Valid from: {cert.not_valid_before}") + return False + if now > cert.not_valid_after: + print(f"Certificate has expired. Expired on: {cert.not_valid_after}") + return False + + print(f"Certificate is valid. Expires on: {cert.not_valid_after}") + return True + + except Exception as e: + print(f"Error loading or parsing certificate: {e}") + return False if __name__ == '__main__': - CERT_FILE = "secrets/cert.pem" - KEY_FILE = "secrets/key.pem" - HOSTNAME = "localhost" # Or your local IP address, e.g., "127.0.0.1" + # TODO define best path to store credentials + CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" + KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" + HOSTNAME = "localhost" + cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) # Generate certificate if it doesn't exist if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): - generate_self_signed_cert(HOSTNAME, ip_addresses=["127.0.0.1"], cert_file=CERT_FILE, key_file=KEY_FILE) + cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) + # Verify expiration date + elif cert_gen.is_certificate_valid(CERT_FILE): + print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) + # Credentials already created + else: + print("Credentials already generated!") diff --git a/webserver/webserver.py b/webserver/webserver.py index 9e89d23..d3ceadd 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -16,8 +16,9 @@ import socket import mimetypes import ssl -import flask +import flask import flask_login +from credentials import CertGen app = flask.Flask(__name__) app.secret_key = str(os.urandom(16)) @@ -26,6 +27,11 @@ login_manager.init_app(app) openplc_runtime = openplc.runtime() +# TODO define best path to store credentials +CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" +KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" +HOSTNAME = "localhost" + class User(flask_login.UserMixin): pass @@ -2496,8 +2502,8 @@ def main(): if __name__ == '__main__': #Load information about current program on the openplc_runtime object - file = open("active_program", "r") - st_file = file.read() + with open("active_program", "r") as file: + st_file = file.read() st_file = st_file.replace('\r','').replace('\n','') database = "openplc.db" @@ -2527,9 +2533,21 @@ if __name__ == '__main__': time.sleep(1) configure_runtime() monitor.parse_st(openplc_runtime.project_file) - + try: - context = ('/home/lucas/Documents/secrets/cert2.pem', '/home/lucas/Documents/secrets/key2.pem') + # CertGen class is used to generate SSL certificates and verify their validity + cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) + # Generate certificate if it doesn't exist + if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): + cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) + # Verify expiration date + elif cert_gen.is_certificate_valid(CERT_FILE): + print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) + # Credentials already created + else: + print("Credentials already generated!") + + context = ('/etc/ssl/certs/certOPENPLC.pem', '/etc/ssl/private/keyOPENPLC.pem') app.run(debug=False, host='0.0.0.0', threaded=True, port=8080, ssl_context=context) # TODO handle file error except FileNotFoundError as e: From 95877b528400d7a451378b4c028d74ee255ea84c Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 09:28:16 -0400 Subject: [PATCH 06/13] [RTOP-24] Leave only COMMON_NAME for NameAttribute --- webserver/credentials.py | 45 +++++------------------ webserver/webserver.py | 78 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index f5bbf60..038a271 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -8,40 +8,7 @@ import datetime import ipaddress import os -# key = rsa.generate_private_key( -# public_exponent=65537, -# key_size=4096, -# backend=default_backend() -# ) -# with open("/home/lucas/Documents/secrets/key.pem", "wb") as f: -# f.write(key.private_bytes( -# encoding=serialization.Encoding.PEM, -# format=serialization.PrivateFormat.TraditionalOpenSSL, -# encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), -# )) - -# csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ -# # Provide various details about who we are. -# x509.NameAttribute(NameOID.COUNTRY_NAME, "US"), -# x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"), -# x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"), -# x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Company"), -# x509.NameAttribute(NameOID.COMMON_NAME, "mysite.com"), -# ])).add_extension( -# x509.SubjectAlternativeName([ -# # Describe what sites we want this certificate for. -# x509.DNSName("mysite.com"), -# x509.DNSName("www.mysite.com"), -# x509.DNSName("subdomain.mysite.com"), -# ]), -# critical=False, -# # Sign the CSR with our private key. -# ).sign(key, hashes.SHA256()) - -# # Write our CSR out to disk. -# with open("/home/lucas/Documents/secrets/csr.pem", "wb") as f: -# f.write(csr.public_bytes(serialization.Encoding.PEM)) class CertGen(): """ Generates a self-signed TLS certificate and private key. @@ -60,10 +27,10 @@ class CertGen(): self.now = datetime.datetime.utcnow() # Subject and Issuer self.subject = self.issuer = x509.Name([ - x509.NameAttribute(NameOID.COUNTRY_NAME, u"BR"), - x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"openplc"), - x509.NameAttribute(NameOID.LOCALITY_NAME, u"openplc"), - x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Autonomy"), + # x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), # TODO get device country + # x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"openplc"), + # x509.NameAttribute(NameOID.LOCALITY_NAME, u"openplc"), + # x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Autonomy"), x509.NameAttribute(NameOID.COMMON_NAME, hostname), ]) @@ -113,6 +80,10 @@ class CertGen(): print(f"Certificate saved to {cert_file}") print(f"Private key saved to {key_file}") + # TODO update certificate on cliente before expiration + # TODO add a function to update the certificate on the client before expiration + # TODO add a function to check if the certificate is valid (not expired and not yet valid) + # TODO add a function to check if the certificate is already generated def is_certificate_valid(self, cert_file): """ Checks if a certificate is valid (not expired and not yet valid). diff --git a/webserver/webserver.py b/webserver/webserver.py index 814ee0b..da10270 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -18,8 +18,8 @@ import ssl import flask import flask_login -from restapi import restapi_bp, register_callback_get, register_callback_post from credentials import CertGen +from restapi import restapi_bp, register_callback_get, register_callback_post app = flask.Flask(__name__) app.secret_key = str(os.urandom(16)) @@ -28,6 +28,82 @@ login_manager.init_app(app) openplc_runtime = openplc.runtime() +# TODO define best path to store credentials +CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" +KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" +HOSTNAME = "localhost" + +def restapi_callback_get(argument: str, data: dict) -> dict: + """ + This is the central callback function that handles the logic + based on the 'argument' from the URL and 'data' from the request. + """ + # TODO logging debug level + print(f"GET | [{__name__}] Received argument: {argument}, data: {data}") + + if argument == "start-plc": + openplc_runtime.start_runtime() + return {"status": "runtime started"} + + elif argument == "stop-plc": + openplc_runtime.stop_runtime() + return {"status": "runtime stop"} + + elif argument == "runtime-logs": + logs = openplc_runtime.logs() + return {"runtime-logs": logs} + + elif argument == "compilation-status": + status = openplc_runtime.is_compiling + return {"is-compiling": status} + + elif argument == "compilation-logs": + logs = openplc_runtime.compilation_status() + return {"compilation-logs": logs} + + elif argument == "status": + return {"current_status": "operational", "details": data} + + elif argument == "ping": + return {"status": "pong"} + else: + return {"error": "Unknown argument"} + +# file upload POST handler +def restapi_callback_post(argument: str, data: dict) -> dict: + # TODO logging debug level + print(f"POST | [{__name__}] Received argument: {argument}, data: {data}") + + if argument == "upload-file": + try: + # TODO validate filename, content and size + st_file = flask.request.files['file'] + print(st_file.filename) + st_file.save(f"st_files/{st_file.filename}") + return {"UploadFile": "Success"} + + except: + return {"UploadFile": "Fail"} + + elif argument == "compile-program": + if (openplc_runtime.status() == "Compiling"): + return {"RuntimeStatus": "Compiling"} + + try: + # TODO return compilation result and validate filename + # st_file = flask.request.args.get('file') + st_file = flask.request.files['file'] + # print(f"st_files/{st_file.filename}") + openplc_runtime.compile_program(f"{st_file.filename}") + return {"CompilationStatus": "Program Compiled"} + + except Exception as e: + return {"CompilationStatus": e} + + else: + return {"PostError": "Unknown argument"} + + class User(flask_login.UserMixin): pass From a8ddd94025c8dfcf34717e76826dbbfec0f46286 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 10:38:50 -0400 Subject: [PATCH 07/13] [RTOP-23] PR review fix --- .gitignore | 3 +++ webserver/restapi.py | 4 ++-- webserver/webserver.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 33f669c..8bf317a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,8 +33,11 @@ webserver/core/glue_generator webserver/core/openplc webserver/iec2c webserver/st_optimizer +webserver/active_program utils/libmodbus_src/src/.libs/* utils/libmodbus_src/tests/.libs/* +webserver/scripts/openplc_driver +webserver/scripts/openplc_platform # Runtime directories .venv \ No newline at end of file diff --git a/webserver/restapi.py b/webserver/restapi.py index e4bd113..339d7f3 100644 --- a/webserver/restapi.py +++ b/webserver/restapi.py @@ -32,7 +32,7 @@ def restapi_plc_get(command): result = _handler_callback_get(command, data) return jsonify(result), 200 except Exception as e: - print(f"Error in restapi_start_plc: {e}") + print(f"Error in restapi_plc_get: {e}") return jsonify({"error": str(e)}), 500 @restapi_bp.route("/", methods=["POST"]) @@ -47,5 +47,5 @@ def restapi_plc_post(command): result = _handler_callback_post(command, data) return jsonify(result), 200 except Exception as e: - print(f"Error in restapi_start_plc_post: {e}") + print(f"Error in restapi_plc_post: {e}") return jsonify({"error": str(e)}), 500 diff --git a/webserver/webserver.py b/webserver/webserver.py index c46ddcf..cfeb1b7 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -924,7 +924,7 @@ def upload_program(): if (prog_file.filename == ''): return draw_blank_page() + "

Error

You need to select a file to be uploaded!

Use the back-arrow on your browser to return

" - # TODO colocar em outra funçao + # TODO realocate to another function filename = str(random.randint(1,1000000)) + ".st" prog_file.save(os.path.join('st_files', filename)) From 62e03a33d80bb27928d6cd75706a8a58d53b1f67 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 10:38:50 -0400 Subject: [PATCH 08/13] [RTOP-23] PR review fix --- .gitignore | 3 +++ webserver/restapi.py | 4 ++-- webserver/webserver.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 33f669c..8bf317a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,8 +33,11 @@ webserver/core/glue_generator webserver/core/openplc webserver/iec2c webserver/st_optimizer +webserver/active_program utils/libmodbus_src/src/.libs/* utils/libmodbus_src/tests/.libs/* +webserver/scripts/openplc_driver +webserver/scripts/openplc_platform # Runtime directories .venv \ No newline at end of file diff --git a/webserver/restapi.py b/webserver/restapi.py index e4bd113..339d7f3 100644 --- a/webserver/restapi.py +++ b/webserver/restapi.py @@ -32,7 +32,7 @@ def restapi_plc_get(command): result = _handler_callback_get(command, data) return jsonify(result), 200 except Exception as e: - print(f"Error in restapi_start_plc: {e}") + print(f"Error in restapi_plc_get: {e}") return jsonify({"error": str(e)}), 500 @restapi_bp.route("/", methods=["POST"]) @@ -47,5 +47,5 @@ def restapi_plc_post(command): result = _handler_callback_post(command, data) return jsonify(result), 200 except Exception as e: - print(f"Error in restapi_start_plc_post: {e}") + print(f"Error in restapi_plc_post: {e}") return jsonify({"error": str(e)}), 500 diff --git a/webserver/webserver.py b/webserver/webserver.py index da10270..e209896 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -931,7 +931,7 @@ def upload_program(): if (prog_file.filename == ''): return draw_blank_page() + "

Error

You need to select a file to be uploaded!

Use the back-arrow on your browser to return

" - # TODO colocar em outra funçao + # TODO realocate to another function filename = str(random.randint(1,1000000)) + ".st" prog_file.save(os.path.join('st_files', filename)) From 4067ce939f8d8f409ac528e8321969e0e693141a Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 12:46:56 -0400 Subject: [PATCH 09/13] [RTOP-24] Including threads for running RestAPI and webserver on diferent ports --- webserver/credentials.py | 36 +++++++++++------------ webserver/webserver.py | 62 ++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index 038a271..e00609b 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -105,33 +105,33 @@ class CertGen(): now = datetime.datetime.utcnow() - if now < cert.not_valid_before: + if now < cert.not_valid_before_utc: print(f"Certificate is not yet valid. Valid from: {cert.not_valid_before}") return False - if now > cert.not_valid_after: + if now > cert.not_valid_after_utc: print(f"Certificate has expired. Expired on: {cert.not_valid_after}") return False - print(f"Certificate is valid. Expires on: {cert.not_valid_after}") + print(f"Certificate is valid. Expires on: {cert.not_valid_after_utc}") return True except Exception as e: print(f"Error loading or parsing certificate: {e}") return False -if __name__ == '__main__': - # TODO define best path to store credentials - CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" - KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" - HOSTNAME = "localhost" +# if __name__ == '__main__': +# # TODO define best path to store credentials +# CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" +# KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" +# HOSTNAME = "localhost" - cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) - # Generate certificate if it doesn't exist - if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): - cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) - # Verify expiration date - elif cert_gen.is_certificate_valid(CERT_FILE): - print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) - # Credentials already created - else: - print("Credentials already generated!") +# cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) +# # Generate certificate if it doesn't exist +# if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): +# cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) +# # Verify expiration date +# elif cert_gen.is_certificate_valid(CERT_FILE): +# print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) +# # Credentials already created +# else: +# print("Credentials already generated!") diff --git a/webserver/webserver.py b/webserver/webserver.py index e209896..f7632df 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -15,9 +15,11 @@ import ctypes import socket import mimetypes import ssl +import threading import flask import flask_login + from credentials import CertGen from restapi import restapi_bp, register_callback_get, register_callback_post @@ -28,6 +30,8 @@ login_manager.init_app(app) openplc_runtime = openplc.runtime() +app_restapi = flask.Flask(__name__) + # TODO define best path to store credentials CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" @@ -2572,13 +2576,35 @@ def escape(s, quote=True): #---------------------------------------------------------------------------- def main(): print("Starting the web interface...") - -if __name__ == '__main__': - # rest api register - app.register_blueprint(restapi_bp, url_prefix='/api') + +def run_https(): + # rest api register + app_restapi.register_blueprint(restapi_bp, url_prefix='/api') register_callback_get(restapi_callback_get) register_callback_post(restapi_callback_post) + try: + # CertGen class is used to generate SSL certificates and verify their validity + cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) + # Generate certificate if it doesn't exist + if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): + cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) + # Verify expiration date + elif cert_gen.is_certificate_valid(CERT_FILE): + print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) + # Credentials already created + else: + print("Credentials already generated!") + + context = ('/etc/ssl/certs/certOPENPLC.pem', '/etc/ssl/private/keyOPENPLC.pem') + app_restapi.run(debug=False, host='0.0.0.0', threaded=True, port=8443, ssl_context=context) + # TODO handle file error + except FileNotFoundError as e: + print(f"Could not find SSL credentials! {e}") + except ssl.SSLError as e: + print(f"SSL credentials FAIL! {e}") + +def run_http(): #Load information about current program on the openplc_runtime object with open("active_program", "r") as file: st_file = file.read() @@ -2612,29 +2638,15 @@ if __name__ == '__main__': configure_runtime() monitor.parse_st(openplc_runtime.project_file) - try: - # CertGen class is used to generate SSL certificates and verify their validity - cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) - # Generate certificate if it doesn't exist - if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): - cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) - # Verify expiration date - elif cert_gen.is_certificate_valid(CERT_FILE): - print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) - # Credentials already created - else: - print("Credentials already generated!") - - context = ('/etc/ssl/certs/certOPENPLC.pem', '/etc/ssl/private/keyOPENPLC.pem') - app.run(debug=False, host='0.0.0.0', threaded=True, port=8080, ssl_context=context) - # TODO handle file error - except FileNotFoundError as e: - print(f"Could not find SSL credentials! {e}") - except ssl.SSLError as e: - print(f"SSL credentials FAIL! {e}") - + app.run(debug=False, host='0.0.0.0', threaded=True, port=8080) except Error as e: print("error connecting to the database" + str(e)) else: print("error connecting to the database") + + +if __name__ == '__main__': + # Running webserver and RestAPI in separate threads + threading.Thread(target=run_http).start() + threading.Thread(target=run_https).start() From fbd3afe00a7ca3aee38eafc27df824c6e245861e Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 14:35:27 -0400 Subject: [PATCH 10/13] [RTOP-24] Minor fix --- webserver/credentials.py | 3 --- webserver/webserver.py | 27 +++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index e00609b..6f147ac 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -80,10 +80,7 @@ class CertGen(): print(f"Certificate saved to {cert_file}") print(f"Private key saved to {key_file}") - # TODO update certificate on cliente before expiration # TODO add a function to update the certificate on the client before expiration - # TODO add a function to check if the certificate is valid (not expired and not yet valid) - # TODO add a function to check if the certificate is already generated def is_certificate_valid(self, cert_file): """ Checks if a certificate is valid (not expired and not yet valid). diff --git a/webserver/webserver.py b/webserver/webserver.py index f7632df..b2fff65 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -2595,9 +2595,19 @@ def run_https(): # Credentials already created else: print("Credentials already generated!") - - context = ('/etc/ssl/certs/certOPENPLC.pem', '/etc/ssl/private/keyOPENPLC.pem') - app_restapi.run(debug=False, host='0.0.0.0', threaded=True, port=8443, ssl_context=context) + + try: + context = ('/etc/ssl/certs/certOPENPLC.pem', '/etc/ssl/private/keyOPENPLC.pem') + app_restapi.run(debug=False, host='0.0.0.0', threaded=True, port=8443, ssl_context=context) + except KeyboardInterrupt as e: + print(f"Exiting OpenPLC Webserver...{e}") + openplc_runtime.stop_runtime() + except Exception as e: + print(f"An error occurred: {e}") + openplc_runtime.stop_runtime() + except: + print("An unexpected error occurred.") + # TODO handle file error except FileNotFoundError as e: print(f"Could not find SSL credentials! {e}") @@ -2638,7 +2648,16 @@ def run_http(): configure_runtime() monitor.parse_st(openplc_runtime.project_file) - app.run(debug=False, host='0.0.0.0', threaded=True, port=8080) + try: + app.run(debug=False, host='0.0.0.0', threaded=True, port=8080) + except KeyboardInterrupt as e: + print(f"Exiting OpenPLC Webserver...{e}") + openplc_runtime.stop_runtime() + except Exception as e: + print(f"An error occurred: {e}") + openplc_runtime.stop_runtime() + except: + print("An unexpected error occurred.") except Error as e: print("error connecting to the database" + str(e)) From fed07e1505aeb998644e8c870967e589b5db1fe3 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 14:46:51 -0400 Subject: [PATCH 11/13] [RTOP-24] Certificate files path variables --- webserver/webserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webserver/webserver.py b/webserver/webserver.py index b2fff65..786de09 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -2597,7 +2597,7 @@ def run_https(): print("Credentials already generated!") try: - context = ('/etc/ssl/certs/certOPENPLC.pem', '/etc/ssl/private/keyOPENPLC.pem') + context = (CERT_FILE, KEY_FILE) app_restapi.run(debug=False, host='0.0.0.0', threaded=True, port=8443, ssl_context=context) except KeyboardInterrupt as e: print(f"Exiting OpenPLC Webserver...{e}") From 57968b198f810b93228a13dab6f179871aeb0c5a Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 14:52:17 -0400 Subject: [PATCH 12/13] Deleted file uploaded on RestAPI --- webserver/st_files/Dockerfile | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 webserver/st_files/Dockerfile diff --git a/webserver/st_files/Dockerfile b/webserver/st_files/Dockerfile deleted file mode 100644 index a6d8cb4..0000000 --- a/webserver/st_files/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM debian:bullseye-20240722 - -COPY . /workdir - -WORKDIR /workdir - -RUN mkdir /docker_persistent - -VOLUME /docker_persistent - -# setup docker -RUN ./install.sh docker \ - && touch /docker_persistent/mbconfig.cfg \ - && touch /docker_persistent/persistent.file \ - && mkdir /docker_persistent/st_files \ - && cp /workdir/webserver/openplc.db /docker_persistent/openplc.db \ - && mv /workdir/webserver/openplc.db /workdir/webserver/openplc_default.db \ - && cp /workdir/webserver/dnp3.cfg /docker_persistent/dnp3.cfg \ - && mv /workdir/webserver/dnp3.cfg /workdir/webserver/dnp3_default.cfg \ - && cp -r /workdir/webserver/st_files/ /docker_persistent/st_files/ \ - && mv /workdir/webserver/st_files /workdir/webserver/st_files_default \ - && cp /workdir/webserver/active_program /docker_persistent/active_program \ - && mv /workdir/webserver/active_program /workdir/webserver/active_program_default \ - && ln -s /docker_persistent/mbconfig.cfg /workdir/webserver/mbconfig.cfg \ - && ln -s /docker_persistent/persistent.file /workdir/webserver/persistent.file \ - && ln -s /docker_persistent/openplc.db /workdir/webserver/openplc.db \ - && ln -s /docker_persistent/dnp3.cfg /workdir/webserver/dnp3.cfg \ - && ln -s /docker_persistent/st_files /workdir/webserver/st_files \ - && ln -s /docker_persistent/active_program /workdir/webserver/active_program - -ENTRYPOINT ["./start_openplc.sh"] From 5426dcb95b5c0439c42bf0624374799494727205 Mon Sep 17 00:00:00 2001 From: lucasbutzke Date: Thu, 10 Jul 2025 20:54:56 -0400 Subject: [PATCH 13/13] [RTOP-24] Removing commented lines --- webserver/credentials.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/webserver/credentials.py b/webserver/credentials.py index 6f147ac..6fac7d1 100644 --- a/webserver/credentials.py +++ b/webserver/credentials.py @@ -115,20 +115,3 @@ class CertGen(): except Exception as e: print(f"Error loading or parsing certificate: {e}") return False - -# if __name__ == '__main__': -# # TODO define best path to store credentials -# CERT_FILE = "/etc/ssl/certs/certOPENPLC.pem" -# KEY_FILE = "/etc/ssl/private/keyOPENPLC.pem" -# HOSTNAME = "localhost" - -# cert_gen = CertGen(hostname=HOSTNAME, ip_addresses=["127.0.0.1"]) -# # Generate certificate if it doesn't exist -# if not os.path.exists(CERT_FILE) or not os.path.exists(KEY_FILE): -# cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE) -# # Verify expiration date -# elif cert_gen.is_certificate_valid(CERT_FILE): -# print(cert_gen.generate_self_signed_cert(cert_file=CERT_FILE, key_file=KEY_FILE)) -# # Credentials already created -# else: -# print("Credentials already generated!")