83 lines
3.9 KiB
Python
83 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import bcrypt
|
|
import datetime
|
|
import hashlib
|
|
import os
|
|
|
|
from cryptography import x509
|
|
from cryptography.exceptions import InvalidSignature
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
|
|
|
|
from .paths import ACME_CRON, CERT_PUB_FILE, CERT_KEY_FILE, PKG_SIG_FILE
|
|
|
|
def verify_signature(file, signature):
|
|
# Verifies ECDSA HMAC SHA512 signature of a file
|
|
with open(PKG_SIG_FILE, 'rb') as f:
|
|
pub_key = serialization.load_pem_public_key(f.read(), default_backend())
|
|
pub_key.verify(signature, file, ec.ECDSA(hashes.SHA512()))
|
|
|
|
def verify_hash(file, expected_hash):
|
|
# Verifies SHA512 hash of a file against expected hash
|
|
sha512 = hashlib.sha512()
|
|
with open(file, 'rb') as f:
|
|
while True:
|
|
data = f.read(65536)
|
|
if not data:
|
|
break
|
|
sha512.update(data)
|
|
if sha512.hexdigest() != expected_hash:
|
|
raise InvalidSignature(file)
|
|
|
|
def create_selfsigned_cert(domain):
|
|
# Create selfsigned certificate with wildcard alternative subject name
|
|
private_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
|
|
public_key = private_key.public_key()
|
|
subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, domain)])
|
|
now = datetime.datetime.utcnow()
|
|
cert = x509.CertificateBuilder() \
|
|
.subject_name(subject) \
|
|
.issuer_name(subject) \
|
|
.public_key(public_key) \
|
|
.serial_number(x509.random_serial_number()) \
|
|
.not_valid_before(now) \
|
|
.not_valid_after(now + datetime.timedelta(days=7305)) \
|
|
.add_extension(x509.SubjectAlternativeName((x509.DNSName(domain), x509.DNSName('*.{}'.format(domain)))), critical=False) \
|
|
.add_extension(x509.SubjectKeyIdentifier.from_public_key(public_key), critical=False) \
|
|
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(public_key), critical=False) \
|
|
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True) \
|
|
.add_extension(x509.KeyUsage(digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False), critical=True) \
|
|
.add_extension(x509.ExtendedKeyUsage((ExtendedKeyUsageOID.SERVER_AUTH, ExtendedKeyUsageOID.CLIENT_AUTH)), critical=False) \
|
|
.sign(private_key, hashes.SHA256(), default_backend())
|
|
with open(CERT_PUB_FILE, 'wb') as f:
|
|
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
|
with open(CERT_KEY_FILE, 'wb') as f:
|
|
f.write(private_key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()))
|
|
os.chmod(CERT_KEY_FILE, 0o640)
|
|
|
|
def get_cert_info():
|
|
# Gather certificate data important for setup-host
|
|
with open(CERT_PUB_FILE, 'rb') as f:
|
|
cert = x509.load_pem_x509_certificate(f.read(), default_backend())
|
|
data = {'subject': cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
|
|
'issuer': cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value,
|
|
'expires': '{} UTC'.format(cert.not_valid_after),
|
|
'method': 'manual'}
|
|
if os.access(ACME_CRON, os.X_OK):
|
|
data['method'] = 'automatic'
|
|
# Naive method of inferring if the cert is selfsigned
|
|
# Good enough as reputable CAs will never have the same subject and issuer CN
|
|
# and the 'method' field is used just to populate a GUI element and not for any real cryptography
|
|
elif data['subject'] == data['issuer']:
|
|
data['method'] = 'selfsigned'
|
|
return data
|
|
|
|
def adminpwd_hash(password):
|
|
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
|
|
|
def adminpwd_verify(password, pwhash):
|
|
return bcrypt.checkpw(password.encode(), pwhash.encode())
|