Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions nextcloudappstore/certificate/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
from base64 import b64decode

import pem
from cryptography import x509 as crypto_x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from django.conf import settings # type: ignore
from OpenSSL.crypto import (
FILETYPE_PEM,
Expand All @@ -15,8 +19,6 @@
X509StoreContext,
X509StoreFlags,
load_certificate,
load_crl,
verify,
)
from rest_framework.exceptions import ValidationError

Expand All @@ -42,17 +44,16 @@ class CertificateAppIdMismatchException(ValidationError):

class CertificateValidator:
"""
See https://pyopenssl.readthedocs.io/en/stable/api/crypto.html#signing
-and-verifying-signatures
Certificate validator that supports RSA certificates with SHA512 signatures.
Uses cryptography package instead of deprecated pyopenssl functions.
"""

def __init__(self, config: CertificateConfiguration) -> None:
self.config = config

def validate_signature(self, certificate: str, signature: str, data: bytes) -> None:
"""
Tests if a value is a valid certificate using the provided hash
algorithm
Tests if a value is a valid certificate using SHA512
:param certificate: the certificate to use as string
:param signature: the signature base64 encoded string to test
:param data: the binary file content that was signed
Expand All @@ -61,8 +62,20 @@ def validate_signature(self, certificate: str, signature: str, data: bytes) -> N
"""
cert = self._to_cert(certificate)
err_msg = "Signature is invalid"

try:
verify(cert, b64decode(signature.encode()), data, self.config.digest)
# Convert OpenSSL certificate to cryptography certificate
crypto_cert = crypto_x509.load_pem_x509_certificate(
cert.to_cryptography().public_bytes(serialization.Encoding.PEM)
)

# Type check to ensure we have an RSA public key
public_key = crypto_cert.public_key()
if not isinstance(public_key, RSAPublicKey):
raise InvalidSignatureException(f"{err_msg}: Only RSA keys are supported")

# Only SHA512 is supported for RSA keys
public_key.verify(b64decode(signature.encode()), data, padding.PKCS1v15(), hashes.SHA512())
except Exception as e:
raise InvalidSignatureException(f"{err_msg}: {str(e)}")

Expand All @@ -85,9 +98,10 @@ def validate_certificate(self, certificate: str, chain: str, crl: str | None = N
cert = self._to_cert(certificate)

if crl:
parsed_crl = load_crl(FILETYPE_PEM, crl)
# Use cryptography to load CRL instead of deprecated OpenSSL load_crl
crypto_crl = crypto_x509.load_pem_x509_crl(crl.encode())
store.set_flags(X509StoreFlags.CRL_CHECK)
store.add_crl(parsed_crl)
store.add_crl(crypto_crl) # cryptography CRL is compatible with OpenSSL store

ctx = X509StoreContext(store, cert)
err_msg = "Certificate is invalid"
Expand Down
6 changes: 1 addition & 5 deletions nextcloudappstore/core/tests/e2e/test_app_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ def test_invalid_cert(self):
self.login()

def validate_error_msg(el):
msg = (
"Signature is invalid: [('rsa routines', "
"'', 'wrong signature length'), "
"('Provider routines', '', 'RSA lib')]"
)
msg = "Signature is invalid:"
self.assertTrue(el.is_displayed())
self.assertEqual(msg, el.text.strip())

Expand Down
Loading
Loading