aruba / pyclearpass

Python SDK for ClearPass
https://developer.arubanetworks.com/aruba-cppm/docs/getting-started-with-pyclearpass
MIT License
10 stars 4 forks source link

_send_request mangles binary API responses #2

Open gcoxmoz opened 1 week ago

gcoxmoz commented 1 week ago

common.py's _send_request method does a thing at the end, return response.text if you didn't get a JSON response: https://github.com/aruba/pyclearpass/blob/b48a554e62442ad329044b7c7cfd27fc51ea9240/pyclearpass/common.py#L110-L113

However... https://github.com/aruba/pyclearpass/blob/b48a554e62442ad329044b7c7cfd27fc51ea9240/pyclearpass/api_certificateauthority.py#L75 new_certificate_by_cert_id_export exports certs. Now, if you're getting a textualized version of a PEM, that's all fine, but if you're exporting PKCS12 (the only reasonable way to get a private key back out) that's a bytes-blob. Getting back response.text is a stringified "error corrected" mangling of response.content, which is pretty much irrecoverable/irreversible.

    cert_id = 1234
    p12_pass = 'SomeJunkPassword1234'
    data = {}
    # Works fine, because text certs:
    #data['export_format'] = 'pem'
    # Fails, because binary:
    data['export_format'] = 'p12'
    data['export_password'] = p12_pass
    data['export_password2'] = p12_pass
    data['include_chain'] = True

    cert_export = ApiCertificateAuthority.new_certificate_by_cert_id_export(login, cert_id=str(cert_id), body=data)

I tried changing _send_request to return response.content and got back a usable PKCS12/.p12 "file", but that was just spot-testing one function call, I'm sure it would otherwise wreck the API by changing 'everything' to bytes.

jazzyj123 commented 6 days ago

When you made the change to response.content - did you validate that the returned .p12 data actually worked in the certificate manager? I ask because when I attempt in a lab what you have suggested, I cannot do anything with the byte data. I also tried directly on the APIExplorer with different outputs and it fails there as well. I am able to download the certificate from ClearPass Guest. Reading the documentation states 'The response is file data and does not contain JSON', which is fine, just usable text. Thx.

gcoxmoz commented 6 days ago

When you made the change to response.content - did you validate that the returned .p12 data actually worked in the certificate manager?

Yes. I made a quick-and-dirty hack since I figured this was no quick fix in the API. This stripped-down override form of _send_request works:

    p12_pass = 'THEPASSWORD'
    import requests  # pylint: disable=import-outside-toplevel
    response = requests.post(
        url=login.server + f'/certificate/{cert_id}/export',
        json=data,
        headers={"Authorization": "Bearer " + login.api_token},
        verify=True,
    )
    cert_export = response.content

    with open(f'/tmp/testfile-{p12_pass}.txt', 'wb') as f:
        f.write(cert_export)
        f.close()

Then, running openssl pkcs12 -noenc -in /tmp/testfile-THEPASSWORD.txt -passin pass:THEPASSWORD gets me back the key and certs (and I can run it through pkcs12.load_key_and_certificates(cert_export, p12_pass.encode()) on the python side.

Sticking with the API...

    cert_export = ApiCertificateAuthority.new_certificate_by_cert_id_export(login,
        cert_id=str(cert_id),
        body=data)
    with open(f'/tmp/testfile-{p12_pass}.txt', 'w') as f:
        f.write(cert_export)
        f.close()

... results (looking up the same user/cert) in a testfile that is 2x the size due to corruption, and that openssl can't deal with: 008CAD0202000000:error:0680007B:asn1 encoding routines:ASN1_get_object:header too long:crypto/asn1/asn1_lib.c:105:

jazzyj123 commented 6 days ago

Ok. I think the problem originates from the API not returning in JSON. Most content does, this seems to be an exception. I have an idea of an approach on how to fix properly and look for other similar issues but it will take some time and I will need to do a bit of testing before implementing the change. Will take a while :). Good job you have suitable documented workaround. Thanks for the detail and the support so far.


From: gcoxmoz @.> Sent: Tuesday, June 25, 2024 4:18:43 PM To: aruba/pyclearpass @.> Cc: jazzyj123 @.>; Assign @.> Subject: Re: [aruba/pyclearpass] _send_request mangles binary API responses (Issue #2)

When you made the change to response.content - did you validate that the returned .p12 data actually worked in the certificate manager?

Yes. I made a quick-and-dirty hack since I figured this was no quick fix in the API. This stripped-down override form of _send_request works:

p12_pass = 'THEPASSWORD'
import requests  # pylint: disable=import-outside-toplevel
response = requests.post(
    url=login.server + f'/certificate/{cert_id}/export',
    json=data,
    headers={"Authorization": "Bearer " + login.api_token},
    verify=True,
)
cert_export = response.content

with open(f'/tmp/testfile-{p12_pass}.txt', 'wb') as f:
    f.write(cert_export)
    f.close()

Then, running openssl pkcs12 -noenc -in /tmp/testfile-THEPASSWORD.txt -passin pass:THEPASSWORD gets me back the key and certs (and I can run it through pkcs12.load_key_and_certificates(cert_export, p12_pass.encode()) on the python side.

Sticking with the API...

cert_export = ApiCertificateAuthority.new_certificate_by_cert_id_export(login,
    cert_id=str(cert_id),
    body=data)
with open(f'/tmp/testfile-{p12_pass}.txt', 'w') as f:
    f.write(cert_export)
    f.close()

... results (looking up the same user/cert) in a testfile that is 2x the size due to corruption, and that openssl can't deal with: 008CAD0202000000:error:0680007B:asn1 encoding routines:ASN1_get_object:header too long:crypto/asn1/asn1_lib.c:105:

— Reply to this email directly, view it on GitHubhttps://github.com/aruba/pyclearpass/issues/2#issuecomment-2189240288, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ASKTXT6PIXOA4TWT5WCPYEDZJGC5HAVCNFSM6AAAAABJZY736OVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBZGI2DAMRYHA. You are receiving this because you were assigned.Message ID: @.***>