bikram990 / PyScep

A Python SCEP client and server
MIT License
8 stars 6 forks source link

Identity? #4

Closed macnotes closed 2 years ago

macnotes commented 2 years ago

This is so helpful. Thank you!

Client.SigningRequest.generate_csr offers a shortcut that allows a private key to be generated on the fly.

However, client.enrol requires an identity. How is this parameter obtained if the private key is generated via the generate_csr shortcut?

res = client.enrol( csr=csr, identity=identity, identity_private_key=identity_private_key, identifier=my_ca_cert_thumbprint # An optional identifier how CA Server identifies the CA )

bikram990 commented 2 years ago

Identity is the identity of your client, you can use an existing certificate or generate self signed certificate.

macnotes commented 2 years ago

Thank you for your assistance. This is very nice of you.

I don't know SCEP very well but I am learning from your code. I had it in my head that the client generates a private key then the client generates a CSR based on the private key. Then the client sends the CSR to the SCEP server and a certificate is returned. Finally, the certificate and private key can be combined into a .p12 to create an "identity". Based on your comment, my understanding is likely wrong.

I am trying your project against my NDES server. I am following the instructions as I understand them, but NDES returns a 500 error.

import logging # Requirements of the examples in the readme from scep import Client from scep.Client import PKIStatus # To call the challenge page... import requests as requests import re # To load env vars from os import getenv

challenge_url = getenv('challenge_url') challenge_user = getenv('challenge_user') challenge_pass = getenv('challenge_pass') scep_url = getenv('scep_url')

logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)

def get_ndes_challenge_password(): logging.info('GET NDES challenge page') response = requests.get(url=challenge_url, auth=(challenge_user, challenge_pass), timeout=10) html_string = response.text html_status_code = response.status_code logging.debug(html_string) logging.debug(html_status_code) assert html_status_code == 200 assert html_string

Extract dynamic challenge

regex = r".\*challenge password is: \<B> ([A-Za-z0-9_-]{43}) \</B>.\*"
try:
    my_dynamic_challenge = re.search(regex, html_string).group(1)
except AttributeError:
    logging.error("dynamic_challenge not found")
    my_dynamic_challenge = ''
logging.info(f"Retrieved a dynamic_challenge: \"{my_dynamic_challenge}\"")
# extract ca cert thumbprint
regex = r".\*CA certificate is: \<B> ([A-F0-9]{40}) \</B>.\*"
try:
    my_ca_cert_thumbprint = re.search(regex, html_string).group(1)
except AttributeError:
    logging.error("ca_cert_thumbprint not found")
    my_ca_cert_thumbprint = ''
logging.info(f"Retrieved a ca_cert_thumbprint: \"{my_ca_cert_thumbprint}\"")
assert my_dynamic_challenge
assert my_ca_cert_thumbprint
return my_dynamic_challenge, my_ca_cert_thumbprint

def main(): (dynamic_challenge, ca_cert_thumbprint) = get_ndes_challenge_password()

Generate self-signed

identity, identity_private_key = Client.SigningRequest.generate_self_signed(
    cn=u'PyScep-test',
    key_usage={u'digital_signature', u'key_encipherment'}
)
# Signing Request
# def generate_csr(cls, cn, key_usage, password=None, private_key=None):
csr, private_key = Client.SigningRequest.generate_csr(
    cn=u'PyScep-test',
    key_usage={u'digital_signature', u'key_encipherment'},
    password=challenge_pass,
    private_key=identity_private_key
)
# Instantiate a Client
client = Client.Client(scep_url)
# Enroll the cert
# def enrol(self, csr, identity, identity_private_key, identifier=None):
# , identifier=ca_cert_thumbprint  # An optional identifier how CA Server identifies the CA
res = client.enrol(
    csr=csr,
    identity=identity,
    identity_private_key=identity_private_key
)
if res.status == PKIStatus.FAILURE:
    print(res.fail_info)
elif res.status == PKIStatus.PENDING:
    print(res.transaction_id)
else:
    print(res.certificate)

if name == "main": main()

The regex to get the challenge works well. The 500 happens on the enrol.

DEBUG:Starting new HTTPS connection (1): ...:443 DEBUG:https://...:443 "GET ...?operation=GetCACaps&message= HTTP/1.1" 200 None DEBUG:Server Capabilities are SHA-256, POSTPKIOperation, SHA-512 DEBUG:Starting new HTTPS connection (1): ...:443 DEBUG:https://...:443 "GET ...?operation=GetCACert&message= HTTP/1.1" 200 2428 DEBUG:Received response with RA certificates DEBUG:2 certificate(s) attached to signedData DEBUG:Message Type : 19 DEBUG:Transaction ID : 6bf62cf... DEBUG:Sender Nonce : b'ta2M...qhAw==' DEBUG:Starting new HTTPS connection (1): ...:443 DEBUG:https://...:443 "POST ...?operation=PKIOperation&message= HTTP/1.1" 500 1719 Traceback (most recent call last): File "/Users/admin/PycharmProjects/scep/ndes.py", line 84, in main() File "/Users/admin/PycharmProjects/scep/ndes.py", line 70, in main res = client.enrol( File "/Users/admin/PycharmProjects/scep/venv/lib/python3.10/site-packages/scep/Client/client.py", line 123, in enrol return self._pki_operation(identity=identity, identity_private_key=identity_private_key, envelope=envelope, message_type=MessageType.PKCSReq, cacaps=cacaps, ca_certs=ca_certs, transaction_id=transaction_id) File "/Users/admin/PycharmProjects/scep/venv/lib/python3.10/site-packages/scep/Client/client.py", line 145, in _pki_operation res = self.pki_operation(data=pki_msg.dump(), cacaps=cacaps) File "/Users/admin/PycharmProjects/scep/venv/lib/python3.10/site-packages/scep/Client/client.py", line 183, in pki_operation raise ValueError('Got invalid status code for PKIOperation: {}'.format(res.status_code)) ValueError: Got invalid status code for PKIOperation: 500

bikram990 commented 2 years ago

@macnotes Your understanding is correct, the only thing you were missing was that all the communication is protected using a certificate and the self-signed certificate is for that purpose. You could use an existing certificate as well to protect your communication to your SCEP server.

500 is Server Internal Error, Could you please check the server logs?

I've tested this package only with EJBCA as I don't have access to an NDES server to try it. Your code posed above looks good to me.

bikram990 commented 2 years ago

Closing due to no activity