Initial commit
This commit is contained in:
commit
6c02075a57
5 changed files with 574 additions and 0 deletions
25
models/config.py
Normal file
25
models/config.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
class config:
|
||||
@classmethod
|
||||
def __init__(self):
|
||||
for config_path in (
|
||||
os.path.expanduser("~/.config/step-ca-inspector"),
|
||||
os.environ.get("STEP_CA_INSPECTOR_CONF"),
|
||||
):
|
||||
if config_path is None:
|
||||
continue
|
||||
try:
|
||||
with open(os.path.join(config_path, "config.yaml")) as ymlfile:
|
||||
cfg = yaml.load(ymlfile, Loader=yaml.FullLoader)
|
||||
break
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
print("No configuration file found")
|
||||
sys.exit(1)
|
||||
|
||||
for k, v in cfg.items():
|
||||
setattr(self, k, v)
|
156
models/ssh_cert.py
Normal file
156
models/ssh_cert.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
import base64
|
||||
import dateutil
|
||||
import json
|
||||
import mariadb
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import asymmetric, hashes, serialization
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from models.config import config
|
||||
from struct import unpack
|
||||
|
||||
|
||||
config()
|
||||
conn = mariadb.connect(
|
||||
host=config.database_host,
|
||||
user=config.database_user,
|
||||
password=config.database_password,
|
||||
database=config.database_name,
|
||||
)
|
||||
|
||||
|
||||
class list:
|
||||
certs = []
|
||||
|
||||
def __new__(cls, sort_key=None):
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT nkey FROM ssh_certs")
|
||||
|
||||
for (cert_serial,) in cur:
|
||||
cert_object = cert(cert_serial)
|
||||
cls.certs.append(cert_object)
|
||||
|
||||
cur.close()
|
||||
|
||||
if sort_key is not None:
|
||||
cls.certs.sort(key=lambda item: getattr(item, sort_key))
|
||||
|
||||
return cls.certs
|
||||
|
||||
|
||||
class cert:
|
||||
def __init__(self, serial):
|
||||
cert_raw = self.get_cert(serial)
|
||||
size = unpack(">I", cert_raw[:4])[0] + 4
|
||||
alg = cert_raw[4:size]
|
||||
|
||||
cert_pub_id = b" ".join([alg, base64.b64encode(cert_raw)])
|
||||
cert_revoked = self.get_cert_revoked(serial)
|
||||
self.load(cert_pub_id, cert_revoked, alg)
|
||||
|
||||
def load(self, cert_pub_id, cert_revoked, cert_alg):
|
||||
cert = serialization.load_ssh_public_identity(cert_pub_id)
|
||||
self.serial = cert.serial
|
||||
self.alg = cert_alg
|
||||
if cert.type == serialization.SSHCertificateType.USER:
|
||||
self.type = "User"
|
||||
self.key_id = cert.key_id
|
||||
self.principals = cert.valid_principals
|
||||
self.not_after = datetime.fromtimestamp(cert.valid_before).replace(
|
||||
tzinfo=timezone(offset=timedelta()), microsecond=0
|
||||
)
|
||||
self.not_before = datetime.fromtimestamp(cert.valid_after).replace(
|
||||
tzinfo=timezone(offset=timedelta()), microsecond=0
|
||||
)
|
||||
# TODO: Implement critical options parsing
|
||||
# cert.critical_options
|
||||
self.extensions = cert.extensions
|
||||
|
||||
(self.signing_key, self.signing_key_type, self.signing_key_hash) = (
|
||||
self.get_public_key_params(cert.signature_key())
|
||||
)
|
||||
|
||||
(self.public_key, self.public_key_type, self.public_key_hash) = (
|
||||
self.get_public_key_params(cert.public_key())
|
||||
)
|
||||
|
||||
self.public_identity = cert.public_bytes()
|
||||
|
||||
if cert_revoked is not None:
|
||||
self.revoked_at = dateutil.parser.isoparse(
|
||||
cert_revoked.get("RevokedAt")
|
||||
).replace(microsecond=0)
|
||||
else:
|
||||
self.revoked_at = None
|
||||
|
||||
now_with_tz = datetime.utcnow().replace(
|
||||
tzinfo=timezone(offset=timedelta()), microsecond=0
|
||||
)
|
||||
|
||||
if self.revoked_at is not None and self.revoked_at < now_with_tz:
|
||||
self.status = status(status.REVOKED)
|
||||
elif self.not_after < now_with_tz:
|
||||
self.status = status(status.EXPIRED)
|
||||
else:
|
||||
self.status = status(status.VALID)
|
||||
|
||||
def get_cert(self, cert_serial):
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT nvalue FROM ssh_certs WHERE nkey=?", (cert_serial,))
|
||||
if cur.rowcount > 0:
|
||||
(cert,) = cur.fetchone()
|
||||
else:
|
||||
cert = None
|
||||
|
||||
cur.close()
|
||||
return cert
|
||||
|
||||
def get_cert_revoked(self, cert_serial):
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT nvalue FROM revoked_ssh_certs WHERE nkey=?", (cert_serial,))
|
||||
if cur.rowcount > 0:
|
||||
(cert_revoked_raw,) = cur.fetchone()
|
||||
cert_revoked = json.loads(cert_revoked_raw)
|
||||
else:
|
||||
cert_revoked = None
|
||||
|
||||
cur.close()
|
||||
return cert_revoked
|
||||
|
||||
def get_public_key_params(self, public_key):
|
||||
if isinstance(public_key, asymmetric.ec.EllipticCurvePublicKey):
|
||||
key_type = "ECDSA"
|
||||
elif isinstance(public_key, asymmetric.ed25519.Ed25519PublicKey):
|
||||
key_type = "ED25519"
|
||||
elif isinstance(public_key, asymmetric.rsa.RSAPublicKey):
|
||||
key_type = "RSA"
|
||||
|
||||
key_str = public_key.public_bytes(
|
||||
serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH
|
||||
)
|
||||
|
||||
key_data = key_str.strip().split()[1]
|
||||
digest = hashes.Hash(hashes.SHA256())
|
||||
digest.update(base64.b64decode(key_data))
|
||||
hash_sha256 = digest.finalize()
|
||||
key_hash = base64.b64encode(hash_sha256)
|
||||
|
||||
return key_str, key_type, key_hash
|
||||
|
||||
|
||||
class status:
|
||||
REVOKED = 1
|
||||
EXPIRED = 2
|
||||
VALID = 3
|
||||
|
||||
def __init__(self, status):
|
||||
self.value = status
|
||||
|
||||
def __str__(self):
|
||||
if self.value == self.EXPIRED:
|
||||
return "Expired"
|
||||
elif self.value == self.REVOKED:
|
||||
return "Revoked"
|
||||
elif self.value == self.VALID:
|
||||
return "Valid"
|
||||
else:
|
||||
return "Undefined"
|
137
models/x509_cert.py
Normal file
137
models/x509_cert.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
import binascii
|
||||
import dateutil
|
||||
import json
|
||||
import mariadb
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from models.config import config
|
||||
|
||||
|
||||
config()
|
||||
conn = mariadb.connect(
|
||||
host = config.database_host,
|
||||
user = config.database_user,
|
||||
password = config.database_password,
|
||||
database = config.database_name
|
||||
)
|
||||
|
||||
|
||||
class list:
|
||||
certs = []
|
||||
|
||||
def __new__(cls, sort_key=None):
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT nkey FROM x509_certs")
|
||||
|
||||
for (cert_serial,) in cur:
|
||||
cert_object = cert(cert_serial)
|
||||
cls.certs.append(cert_object)
|
||||
|
||||
cur.close()
|
||||
|
||||
if sort_key is not None:
|
||||
cls.certs.sort(key=lambda item: getattr(item, sort_key))
|
||||
|
||||
return cls.certs
|
||||
|
||||
|
||||
|
||||
class cert:
|
||||
def __init__(self, serial):
|
||||
cert_der = self.get_cert(serial)
|
||||
cert_data = self.get_cert_data(serial)
|
||||
cert_revoked = self.get_cert_revoked(serial)
|
||||
self.load(cert_der, cert_data, cert_revoked)
|
||||
|
||||
|
||||
def load(self, cert_der, cert_data, cert_revoked):
|
||||
cert = x509.load_der_x509_certificate(cert_der)
|
||||
|
||||
self.pem = cert.public_bytes(serialization.Encoding.PEM)
|
||||
self.serial = str(cert.serial_number)
|
||||
self.sha256 = binascii.b2a_hex(cert.fingerprint(hashes.SHA256()))
|
||||
self.sha1 = binascii.b2a_hex(cert.fingerprint(hashes.SHA1()))
|
||||
self.md5 = binascii.b2a_hex(cert.fingerprint(hashes.MD5()))
|
||||
self.pub_alg = cert.public_key_algorithm_oid._name
|
||||
self.sig_alg = cert.signature_algorithm_oid._name
|
||||
self.issuer = cert.issuer.rfc4514_string()
|
||||
self.subject = cert.subject.rfc4514_string({x509.NameOID.EMAIL_ADDRESS: "E"})
|
||||
self.not_before = cert.not_valid_before_utc.replace(microsecond=0)
|
||||
self.not_after = cert.not_valid_after_utc.replace(microsecond=0)
|
||||
san_data = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
self.san_names = san_data.value.get_values_for_type(x509.GeneralName)
|
||||
self.provisioner = cert_data.get("provisioner", None)
|
||||
|
||||
if cert_revoked is not None:
|
||||
self.revoked_at = dateutil.parser.isoparse(
|
||||
cert_revoked.get("RevokedAt")
|
||||
).replace(microsecond=0)
|
||||
else:
|
||||
self.revoked_at = None
|
||||
|
||||
now_with_tz = datetime.utcnow().replace(
|
||||
tzinfo=timezone(offset=timedelta()), microsecond=0
|
||||
)
|
||||
|
||||
if self.revoked_at is not None and self.revoked_at < now_with_tz:
|
||||
self.status = status(status.REVOKED)
|
||||
elif self.not_after < now_with_tz:
|
||||
self.status = status(status.EXPIRED)
|
||||
else:
|
||||
self.status = status(status.VALID)
|
||||
|
||||
|
||||
def get_cert(self, cert_serial):
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT nvalue FROM x509_certs WHERE nkey=?", (cert_serial,))
|
||||
if cur.rowcount > 0:
|
||||
(cert,) = cur.fetchone()
|
||||
else:
|
||||
cert = None
|
||||
|
||||
cur.close()
|
||||
return cert
|
||||
|
||||
|
||||
def get_cert_data(self, cert_serial):
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT nvalue FROM x509_certs_data WHERE nkey=?", (cert_serial,))
|
||||
(cert_data_raw,) = cur.fetchone()
|
||||
cur.close()
|
||||
cert_data = json.loads(cert_data_raw)
|
||||
return cert_data
|
||||
|
||||
|
||||
def get_cert_revoked(self, cert_serial):
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"SELECT nvalue FROM revoked_x509_certs WHERE nkey=?", (cert_serial,)
|
||||
)
|
||||
if cur.rowcount > 0:
|
||||
(cert_revoked_raw,) = cur.fetchone()
|
||||
cert_revoked = json.loads(cert_revoked_raw)
|
||||
else:
|
||||
cert_revoked = None
|
||||
|
||||
cur.close()
|
||||
return cert_revoked
|
||||
|
||||
|
||||
class status:
|
||||
REVOKED = 1
|
||||
EXPIRED = 2
|
||||
VALID = 3
|
||||
|
||||
def __init__(self, status):
|
||||
self.value = status
|
||||
|
||||
def __str__(self):
|
||||
if self.value == self.EXPIRED:
|
||||
return "Expired"
|
||||
elif self.value == self.REVOKED:
|
||||
return "Revoked"
|
||||
elif self.value == self.VALID:
|
||||
return "Valid"
|
||||
else:
|
||||
return "Undefined"
|
Loading…
Add table
Add a link
Reference in a new issue