pyca / cryptography

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers.
https://cryptography.io
Other
6.6k stars 1.51k forks source link

Django DRF error DLL Load Failed while importing _rust in Python 3.11.3 venv in Windows Server 2022 with cryptography-43.0.1-cp39-abi3-win_amd64.whl #11680

Open alinganpatra20004 opened 4 days ago

alinganpatra20004 commented 4 days ago

My code is working in local environment of windows10 with venv on Python 3.10.11. However, on deploying in Windows Server 2022 with Apache2 webserver with venv on Python 3.11.3 getting following error. I have trying upgrading, installing binary. Till now issue is not resolved. Can anyone help in getting the issue resolved?

other packages in problematic environment are cffi-1.17.1 cryptography-43.0.1 pycparser-2.22

from cryptography.hazmat.primitives.asymmetric import padding\r File "C:\cbsesb\cbsenv\Lib\site-packages\cryptography\hazmat\primitives\asymmetric\padding.py", line 9, in \r from cryptography.hazmat.primitives import hashes\r File "C:\cbsesb\cbsenv\Lib\site-packages\cryptography\hazmat\primitives\hashes.py", line 9, in \r from cryptography.hazmat.bindings._rust import openssl as rust_openssl\r ImportError: DLL load failed while importing _rust: The specified module could not be found.\r Package Version


asgiref 3.7.1 certifi 2023.7.22 cffi 1.17.1 charset-normalizer 3.3.2 cryptography 43.0.1 cx-Oracle 8.3.0 defusedxml 0.7.1 dicttoxml 1.7.16 Django 4.2.1 djangorestframework 3.14.0 djangorestframework-xml 2.0.0 idna 3.6 ldap3 2.9.1 lxml 4.9.3 mod-wsgi 4.9.4 pip 24.2 psycopg2 2.9.6 pyasn1 0.6.0 pycparser 2.22 pycryptodome 3.18.0 pyOpenSSL 24.2.1 pytz 2023.3 requests 2.31.0 setuptools 74.1.2 signxml 3.2.1 soupsieve 2.5 sqlparse 0.4.4 typing_extensions 4.6.1 tzdata 2023.3 urllib3 2.2.0 xmltodict 0.12.0 Tried to update the setup tools but did not worked

python -m pip install --upgrade pip setuptools

Tried to downgrade the Python to 3.10 but did not worked.

installing Rust doesn't work.

alex commented 4 days ago

Please provide complete instructions for how you setup and installed everything on the machine where it did not work.

alinganpatra20004 commented 4 days ago

Please provide complete instructions for how you setup and installed everything on the machine where it did not work.

The server is having Python 3.11. Cryptography was installed using pip install Cryptography. Installation was smooth and successful.

The code uses backend, hash, pkcs7 packages of this library. Code is build on Django and running on Apache web server using wsgi.

alex commented 4 days ago

When I say complete instructions I mean instructions sufficient for another person to follow on a new server.

alinganpatra20004 commented 1 day ago

When I say complete instructions I mean instructions sufficient for another person to follow on a new server.

Steps to recreate the issue are mentioned below.

  1. Take a Windows Server 2022 OS and install all required C++ build tools.
  2. install Apache 2 webserver using WAMP and configure V-Host to create a web UrL.
  3. create a virturalenv for the django project in a folder. load the virtualenv.
  4. PIP install all the requirements as listed in the issue desctiption above.
  5. create a django project.
  6. inside urls.py add a url path to views.ApiCall ` urlpatterns = [

    for vkyc APIs

    path('sign/', ApiCall.as_view()),] `

  7. inside view.py run put following code. ` from cryptography.hazmat.primitives.serialization import pkcs12, Encoding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.serialization import pkcs7 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization.pkcs7 import PKCS7Options from cryptography.x509 import load_der_x509_certificate

import json import base64 import secrets

pfx_path = "C:\certificate.pfx" #a class 3 certificate pfx_password = "password"

def strip_spaces(data): if isinstance(data, dict): return {key: strip_spaces(value) for key, value in data.items()} elif isinstance(data, list): return [strip_spaces(item) for item in data] elif isinstance(data, str): return data.strip() else: return data

def load_pfx(pfx_path, pfx_password): with open(pfx_path, 'rb') as f: pfx_data = f.read()

# Load the PFX contents (private key, main certificate, additional certificates)
private_key, cert, additional_certs = pkcs12.load_key_and_certificates(
    pfx_data, 
    pfx_password.encode(),  # Password for PFX file
    backend=default_backend()
)

return private_key, cert, additional_certs

def sign_data(pfx_path, password, input_data):

Load the PFX file and extract private key and certificates

private_key, cert, additional_certs = load_pfx(pfx_path, password)

# Convert the JSON data to bytes for signing
data_to_sign = input_data.encode()

# Initialize PKCS7 builder and set data to sign
builder = pkcs7.PKCS7SignatureBuilder().set_data(data_to_sign)

# Add the signer's certificate and private key to the builder
builder = builder.add_signer(cert, private_key, hashes.SHA256())

# If there are additional certificates in the PFX file, add them to the PKCS#7 builder
if additional_certs:
    for additional_cert in additional_certs:
        builder = builder.add_certificate(additional_cert)

# Sign the data and produce the PKCS#7 signature in DER format
signed_data = builder.sign(encoding=Encoding.DER, options=[])

# Encode the signed data in Base64 for output
signed_data_base64 = base64.b64encode(signed_data)

# Return the Base64-encoded signature as a string
return signed_data_base64.decode()

def addSignature(json_data): input_data = json.dumps(json_data['inputData'], separators=(',', ':'), ensure_ascii=False)

# Sign the inputData
signature = sign_data(pfx_path, pfx_password, input_data)

# Calculate the record count
data_list = json_data['inputData']
data_count = len(data_list)

# Add the signature to the request body
json_data['signature'] = signature
json_data = json.dumps(json_data, separators=(',', ':'))
content_length = len(json_data.encode('utf-8'))

return json_data

class ApiCall(APIView):

def post(self, request, *arg, **kwarg):
    #validate inputData in request
    if (request.data.get("inputData") != ""):
        json_data = request.data
    else:
        response = {"error_msg":"FAILED", "body":{
            "failed_reason":"inputData is Mandatory"
        }}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

    r = addSignature(json_data)
    # print(r.content)

    if r.status_code == 200:
        response = json.loads(r.content)
        return Response(response, status=status.HTTP_200_OK)

    else:
        response = {"response_Code": 500, "outputData":"Internal Server Error"}
        return Response(response, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

`

  1. configure Apache with wsgi for the virutalenv as follows in v-host config. LoadFile "C:/virtualenv/Scripts/python311.dll" LoadModule wsgi_module "C:/virtualenv/Lib/site-packages/mod_wsgi/server/mod_wsgi.cp311-win_amd64.pyd" WSGIPythonHome "C:/Users/<username>/AppData/Local/Programs/Python/Python311" WSGIPythonPath "C:/virtualenvenv/Lib/site-packages" WSGIPassAuthorization On
  2. Run Apache2
  3. from postman, do POST call to the url crated in urls.py with hostname from the apache.
  4. it will fail with error code 500 and in the apache log, it will show the DLL error.

I have observred that the issue is appearing while running same code in apache web server with WSGI config. However, while calling the code mentioned here in test.py directly from the virtualenv, the code is executing without any issue from the same environment.

(virtualenv)<>path> py test.py

test.py

` from cryptography.hazmat.primitives.serialization import pkcs12, Encoding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.serialization import pkcs7 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization.pkcs7 import PKCS7Options from cryptography.x509 import load_der_x509_certificate

import json import base64 import secrets

pfx_path = "C:\certificate.pfx" pfx_password = "password"

def strip_spaces(data): if isinstance(data, dict): return {key: strip_spaces(value) for key, value in data.items()} elif isinstance(data, list): return [strip_spaces(item) for item in data] elif isinstance(data, str): return data.strip() else: return data

def load_pfx(pfx_path, pfx_password): with open(pfx_path, 'rb') as f: pfx_data = f.read()

# Load the PFX contents (private key, main certificate, additional certificates)
private_key, cert, additional_certs = pkcs12.load_key_and_certificates(
    pfx_data, 
    pfx_password.encode(),  # Password for PFX file
    backend=default_backend()
)

return private_key, cert, additional_certs

def sign_data(pfx_path, password, input_data):

Load the PFX file and extract private key and certificates

private_key, cert, additional_certs = load_pfx(pfx_path, password)

# Convert the JSON data to bytes for signing
data_to_sign = input_data.encode()

# Initialize PKCS7 builder and set data to sign
builder = pkcs7.PKCS7SignatureBuilder().set_data(data_to_sign)

# Add the signer's certificate and private key to the builder
builder = builder.add_signer(cert, private_key, hashes.SHA256())

# If there are additional certificates in the PFX file, add them to the PKCS#7 builder
if additional_certs:
    for additional_cert in additional_certs:
        builder = builder.add_certificate(additional_cert)

# Sign the data and produce the PKCS#7 signature in DER format
signed_data = builder.sign(encoding=Encoding.DER, options=[])

# Encode the signed data in Base64 for output
signed_data_base64 = base64.b64encode(signed_data)

# Return the Base64-encoded signature as a string
return signed_data_base64.decode()

def addSignature(json_data): input_data = json.dumps(json_data['inputData'], separators=(',', ':'), ensure_ascii=False)

# Sign the inputData
signature = sign_data(pfx_path, pfx_password, input_data)

# Calculate the record count
data_list = json_data['inputData']
data_count = len(data_list)

# Add the signature to the request body
json_data['signature'] = signature
json_data = json.dumps(json_data, separators=(',', ':'))
content_length = len(json_data.encode('utf-8'))

return json_data

Testing the function

def main():

Sample JSON data for testing

json_data = {
    "inputData": [
        {"field1": "value1", "field2": "value2"},
        {"field1": "value3", "field2": "value4"}
    ]
}

response = addSignature(json_data)

# Print response status and content for testin
print("Response Content:", response)

if name == "main": main()
`

My primary observation is that, in Apache, cryptography is not able to get the required DLL. but in local run, it is able to find it. We may need to put some dlls to some other location. Please help with the list of DLLs to be copied with the source location and destination locations of the DLL.

reaperhulk commented 1 day ago

Unfortunately this is far too complex for us to replicate. We need a minimal reproducer that we can do in a virtual environment on a Windows machine. Millions of people install and use this project every day and it has been our experience that the vast majority of issues like this are actually problems with the user's environment rather than bugs in our code, which is why we ask for a minimal reproducer to eliminate environment problems.

In your case if you can't reproduce this with a fresh virtualenv executing outside of apache, then this is an env issue that we can't really help with and you'd be better served debugging it as a more general Python pathing issue.

alinganpatra20004 commented 1 day ago

Please provide the list of dependant dlls for this library and where to find those dlls. This may help in resolving the issue by coping those dlls to Apache dll folder.

On Fri, 4 Oct, 2024, 07:12 Paul Kehrer, @.***> wrote:

Unfortunately this is far too complex for us to replicate. We need a minimal reproducer that we can do in a virtual environment on a Windows machine. Millions of people install and use this project every day and it has been our experience that the vast majority of issues like this are actually problems with the user's environment rather than bugs in our code, which is why we ask for a minimal reproducer to eliminate environment problems.

In your case if you can't reproduce this with a fresh virtualenv executing outside of apache, then this is an env issue that we can't really help with and you'd be better served debugging it as a more general Python pathing issue.

— Reply to this email directly, view it on GitHub https://github.com/pyca/cryptography/issues/11680#issuecomment-2392629709, or unsubscribe https://github.com/notifications/unsubscribe-auth/ANLCHFBSIOHZIHXURGRPBMTZZXXCHAVCNFSM6AAAAABPGGIKFKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGOJSGYZDSNZQHE . You are receiving this because you authored the thread.Message ID: @.***>

reaperhulk commented 1 day ago

The DLL you’re having a problem with is a core part of the package and is distributed directly inside the wheel. Again, if you cannot provide a minimal reproducer we cannot assist.