Difference between get_ca_certificates and get_certificate - python

I'm using python to load a pfx certificate.
pfx = crypto.load_pkcs12(pfx, self.nfe_a1_password)
cert = pfx.get_certificate()
end = datetime.strptime(cert.get_notAfter(), '%Y%m%d%H%M%SZ')
subj = cert.get_subject()
http://www.pyopenssl.org/en/stable/api/crypto.html#OpenSSL.crypto.PKCS12.get_ca_certificates
I got one interesting certificate that the method get_certificate() returns None and if I call get_ca_certificate it returns the certificate.
What does it mean if the pfx do not return the certificate? The certificate issuer sent me a wrong one?

Related

Website authentication using smart cards' certificate and public key

So I have a credit card looking like smart card with a chip. This card logins on a website after the card is inserted into the card reader.
Now I have to write a program in python which can read the card and login on that website. After research on internet I found out that I need to extract :
Certificate and
Public key (since private key cannot be extracted)
from the card and then use these 2 things to create a HTTPs connection (example here) . So far I am able to extract certificate in pem format. But i cant find a way to extract key in pem format till now. I used PyKCS11 to read the card. Below is my code:
from asn1crypto import pem, x509
from PyKCS11 import *
import binascii
pkcs11 = PyKCS11Lib()
pkcs11.load(r'C:\Windows\System32\XXXX.dll')
print(pkcs11.getSlotList(tokenPresent=False))
slot = pkcs11.getSlotList(tokenPresent=False)[0]
print(pkcs11.getTokenInfo(slot))
session = pkcs11.openSession(0, CKF_SERIAL_SESSION | CKF_RW_SESSION)
session.login('123456')
result = []
result_pem = []
# find public key and print modulus
pubKey = session.findObjects([(CKA_CLASS, CKO_PUBLIC_KEY)])[0]
modulus = session.getAttributeValue(pubKey, [CKA_MODULUS])[0]
print("\nmodulus: {}".format(binascii.hexlify(bytearray(modulus))))
#find certificates
certs = session.findObjects([(CKA_CLASS, CKO_CERTIFICATE)])
for cert in certs:
cka_value, cka_id = session.getAttributeValue(cert, [CKA_VALUE, CKA_ID])
cert_der = bytes(cka_value)
cert = x509.Certificate.load(cert_der)
# Write out a PEM encoded value
cert_pem = pem.armor('CERTIFICATE', cert_der)
result.append(cert)
result_pem.append(cert_pem)
with open('cert.pem','wb') as f:
f.write(cert_pem)
print(result)
So here are my questions:
1. Is my approach right?
If yes, then how to extract public key in pem format?
How this smart card authentication actually works on client side and server side?
Public key extraxction
If you already have exported the certificate, it is probably easier to extract the public key from there, instead of from the smartcard. You can use openssl for that:
openssl x509 -in cert.pem -pubkey -out pubkey.pem -noout
Authentication
What you are trying to achieve is to open a TLS connection with mutual authentication using a client certificate. If you do this, the private key of your client certificate signs parts of the handshake to authenticate itself towards the server.
Extracting the certificate and the public key from the smartcard won't help you here. You need to find a library, which allows you to use your private key straight from your PKCS#11 token.

Pyopenssl to verify the file signature

I want to verify the downloaded file's signature and cert using pyopenssl, but the documentation is not clear and Google is of no help.
I have a root CA cert in user's machine, now when user download the file then I will send a certificate and signature along with it. First I need to verify the certificate with rootCA on machine then I need to verify the signature with file
In openssl I can use following to verify the ca cert
openssl verify -CAfile <root_pem> <cert_pem>
and following to verify the file
openssl dgst <algo> -verify <cert_pub_key> -signature <signature> <file>
I am looking for equivalent way to do it using python, most preferably pyopenssl
I'm still learning about OpenSSL in general, let alone PyOpenSSL. Having said that, I was able to verify a file (your second command) in PyOpenSSL with the following:
from OpenSSL.crypto import load_publickey, FILETYPE_PEM, verify, X509
with open(file_to_verify, 'rb') as f:
file_data = f.read()
with open(signature_filename, 'rb') as f:
signature = f.read()
with open(public_key_filename) as f:
public_key_data = f.read()
# load in the publickey file, in my case, I had a .pem file.
# If the file starts with
# "-----BEGIN PUBLIC KEY-----"
# then it is of the PEM type. The only other FILETYPE is
# "FILETYPE_ASN1".
pkey = load_publickey(FILETYPE_PEM, public_key_data)
# the verify() function expects that the public key is
# wrapped in an X.509 certificate
x509 = X509()
x509.set_pubkey(pkey)
# perform the actual verification. We need the X509 object,
# the signature to verify, the file to verify, and the
# algorithm used when signing.
verify(x509, signature, file_data, 'sha256')
The verify() function will return None in the event that verification is successful (i.e. it does nothing) or it will raise an Exception if something went wrong.

Verify signed message with M2Crypto for a self signed certificate

I am trying to replicate this openssl command with M2Crypto
openssl smime -verify -in local_files/auth_data.pem.pk7 -inform PEM -certfile certificate.crt -noverify
My code looks like this:
smime = M2Crypto.SMIME.SMIME()
x509_store = M2Crypto.X509.X509_Store()
x509_store.load_info(ca_file)
smime.set_x509_store(x509_store)
x509_stack = M2Crypto.X509.X509_Stack()
x509_cert = M2Crypto.X509.load_cert(cert_file)
x509_stack.push(x509_cert)
smime.set_x509_stack(x509_stack)
p7 = M2Crypto.SMIME.load_pkcs7_bio(M2Crypto.BIO.MemoryBuffer(cipher_text))
decrypted_data = smime.verify(p7)
But I get this error in the last line:
PKCS7_Error: certificate verify error
I can't make M2Crypto to behave like openssl with '-noverify' flag.
I tried loading the same certificate in the X509_Store but it was the same result.
I took me a while but I found the answer in this source code https://pythonhosted.org/pysmime/pysmime.core-pysrc.html#verify
This is the code to allow a self signed certificate:
decrypted_data = smime.verify(p7, flags=M2Crypto.SMIME.PKCS7_NOVERIFY)

M2Crypto return a X509 object from a function

I did this function that takes a pkcs7 envelop and the issuer public key and return the certificate.
def get_cert_from_pkcs7(pkcs7, cert_parent):
"""
Take a pkcs7 and return a certificate.
#type pkcs7: string
#param pkcs7: The base64 of the PKCS7 envelop as
-----BEGIN PKCS7-----
base64 of the pkcs7 envelop
-----END PKCS7-----
#type cert_parent : string
#param cert_parent : Issuer certificate file path
#rtype : M2Crypto.X509
#return : The certificate
"""
sm_obj = SMIME.SMIME()
x509 = X509.load_cert(cert_parent) # public key cert used by the remote
# client when signing the message
sk = X509.X509_Stack()
sk.push(x509)
sm_obj.set_x509_stack(sk)
st = X509.X509_Store()
st.load_info(cert_parent) # Public cert for the CA which signed
# the above certificate
sm_obj.set_x509_store(st)
buf = BIO.MemoryBuffer(pkcs7)
p7 = SMIME.load_pkcs7_bio(buf)
signers = p7.get0_signers(sk)
certificat = signers[0]
return certificat
The problem is that certificat is a C object binded with Python and when the function returns the C object is garbage collected so the _ptr doesn't exist and an access to the certificate returns a Segmentation fault.
Is it possible to return my certificate without any error (copy/clone)?

Disable SSL certificate validation in Python

I'm writing a script that connects to a bunch of URLs over HTTPS, downloads their SSL certificate and extracts the CN. Everything works except when I stumble on a site with an invalid SSL certificate. I absolutely do not care if the certificate is valid or not. I just want the CN but Python stubbornly refuses to extract the certificate information if the certificate is not validated. Is there any way to get around this profoundly stupid behavior? Oh, I'm using the built-in socket and ssl libraries only. I don't want to use third-party libraries like M2Crypto or pyOpenSSL because I'm trying to keep the script as portable as possible.
Here's the relevant code:
file = open("list.txt", "r")
for x in file:
server = socket.getaddrinfo(x.rstrip(), "443")[0][4][0]
sslsocket = socket.socket()
sslsocket.connect((server, 443))
sslsocket = ssl.wrap_socket(sslsocket, cert_reqs=ssl.CERT_REQUIRED, ca_certs="cacerts.txt")
certificate = sslsocket.getpeercert()`
The ssl.get_server_certificate can do it:
import ssl
ssl.get_server_certificate(("www.sefaz.ce.gov.br",443))
I think function doc string is more clear than python doc site:
"""Retrieve the certificate from the server at the specified address,
and return it as a PEM-encoded string.
If 'ca_certs' is specified, validate the server cert against it.
If 'ssl_version' is specified, use it in the connection attempt."""
So you can extract common name from binary DER certificate searching for common name object identifier:
def get_commonname(host,port=443):
oid='\x06\x03U\x04\x03' # Object Identifier 2.5.4.3 (COMMON NAME)
pem=ssl.get_server_certificate((host,port))
der=ssl.PEM_cert_to_DER_cert(pem)
i=der.find(oid) # find first common name (certificate authority)
if i!=-1:
i=der.find(oid,i+1) # skip and find second common name
if i!=-1:
begin=i+len(oid)+2
end=begin+ord(der[begin-1])
return der[begin:end]
return None
Ok. I cleaned up olivecoder's code to solve the problem that it assumes there will always be three CNs in the certificate chain (root, intermediate, server) and I condensed it. This is the final code I will be using.
cert = ssl.get_server_certificate(("www.google.com", 443)) #Retrieve SSL server certificate
cert = ssl.PEM_cert_to_DER_cert(cert) #Convert certificate to DER format
begin = cert.rfind('\x06\x03\x55\x04\x03') + 7 #Find the last occurence of this byte string indicating the CN, add 7 bytes to startpoint to account for length of byte string and padding
end = begin + ord(cert[begin - 1]) #Set endpoint to startpoint + the length of the CN
print cert[begin:end] #Retrieve the CN from the DER encoded certificate

Categories