Verifying a digital signature in Python, with optional CRL - python

I want to be able to sign some data, and later verify the signature; I also want to use a CRL.
Right now, I'm using a small helper executable (written in C) to do all of this; I would prefer to do the entire thing in Python, since that's what the rest of the system is written in.
In the helper executable, I
Create a store
Load both the system root CA and our root CA into a store
Load the CRL using:
crl = d2i_X509_CRL_fp(crl, NULL);
crl = PEM_read_X509_CRL(crl, NULL, NULL, NULL);
X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
X509_STORE_set1_param(store, param);
X509_VERIFY_PARAM_free(param);
X509_STORE_add_crl(store, crl);
Read the certificate I want to verify against
Verify said certificate against the store I set up before
And then verify the signature against the certificate
I see that Python3.4 has an ability to load a CRL in OpenSSL.crypto, but then I don't see how I'd go about using it.
I hate to think I'm missing something obvious, but of course that's possible.
So... am I? If so, what?
Added: It looks like I have an outdated version of pyopenssl -- version 16.1.0 added a add_crl method to the X509Store class, so that may solve my problem. (And looks like, the obvious thing I missed was checking the source repository!)

Related

ssl.SSLError: [SSL] PEM lib (_ssl.c:3833) in python ssl library

so I was working with verify a X509 certificate to my IoT Hub in order to send/receive message from my application. However it keeps throwing out the ssl.SSLError: [SSL] PEM lib (_ssl.c:3833) error.
I have the correct certificate, private key and the pass phrase. So I went over to the python github to check out what does the error means, and the line 3833 in the _ssl.c file means
r = SSL_CTX_use_certificate_chain_file(self->ctx,
PyBytes_AS_STRING(certfile_bytes));
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state);
if (r != 1) {
if (pw_info.error) {
ERR_clear_error();
/* the password callback has already set the error information */
}
else if (errno != 0) {
ERR_clear_error();
PyErr_SetFromErrno(PyExc_OSError);
}
else {
_setSSLError(NULL, 0, __FILE__, __LINE__); <--- THIS IS LINE 3833
}
goto error;
}
Does that means my certificate is wrong? My certificate currently is the location like C:/Certificate/MyCertName.pfx
Thank you for reading and any helps is appreciated!
Source of the _ssl.c in python 3.7.4: https://github.com/python/cpython/blob/v3.7.4/Modules/_ssl.c
This means that SSL_CTX_use_certificate_chain_file (which is a part of OpenSSL) returned an error code and neither of the two typical cases describe the situation, so Python's code cannot tell you more about it.
So the only lead is to check what exactly that function receives and read its documentation (and source code if that's not enough) to try to figure out why it fails. If that's not enough, you'll have to run the process under a C debugger to check the same in vivo.
My (blind) guess is that the certificate file might be of an incorrect/unsupported format/cipher, or not contain the correct certificate chain as required by the TLS standard. Specifically, the documentation says:
SSL_CTX_use_certificate_chain_file() loads a certificate chain from
file into ctx. The certificates must be in PEM format and must be
sorted starting with the subject's certificate (actual client or
server certificate), followed by intermediate CA certificates if
applicable, and ending at the highest level (root) CA.

How to validate / verify an X509 Certificate chain of trust in Python?

I am working on implementing a web application that utilizes an API. During a response, the API server sends over a link to an X509 certificate (in PEM format, composed of a signing certificate and one or more intermediate certificates to a root CA certificate ) that I must download and use to do further verification.
Before using the certificate, I need to ensure that all certificates in the chain combine to create a chain of trust to a trusted root CA certificate (to detect and avoid any malicious requests). I am having a hard time doing this in python and my research into the subject is not yielding anything useful.
The certificate is easily grabbed and loaded using requests and M2Crypto
import requests
from M2Crypto import RSA, X509
mypem = requests.get('https://server.com/my_certificate.pem')
cert = X509.load_cert_string(str(mypem.text), X509.FORMAT_PEM)
However, validating the certificate chain is a problem. It is not feasible for me to write the certificate to disk in order to use a command line utility like openssl through something like subprocess, so it must be done through python. I also do not have any open connections and so using a connection based validation solution (like is mentioned in this answer / thread: https://stackoverflow.com/a/1088224/4984533) will not work either.
On another thread about this problem (at https://stackoverflow.com/a/4427081) abbot explains that m2crypto is incapable of doing this validation and says that he has written an extension to allow validation (using the module m2ext) but his patch never seems work, always returning false even though I know it's valid:
from m2ext import SSL
ctx = SSL.Context()
ctx.load_verify_locations(capath='/etc/ssl/certs/') # I have run c_rehash in this directory to generate a list of cert files with signature based names
if not ctx.validate_certificate(cert): # always happens
print('Invalid certificate!')
There's also this answer on a similar thread here https://stackoverflow.com/a/9007764/4984533 in which John Matthews claims to have a patch written which will do it, but unfortunately the patch link is now dead -- and anyway there is a comment on that thread stating that the patch did not work with openssl 0.9.8e.
All answers relating to validating a certificates chain of trust in python seem to either link to the dead patch or go back to m2ext.
Is there a simple, straightforward way to go about validating my certificates chain of trust in Python?
While the response of Avi Das is valid for the trivial case of verifying a single trust anchor with a single leaf certificate, it places trust in the intermediate certificate. That means that in the case where the intermediate is sent, as well as the client certificate, the entire chain is trusted.
Do not do this. The code found in pyOpenSSL's tests are flawed!
I found this thread on Python's cryptography-dev mailing lists (which links back to this answer): https://mail.python.org/pipermail/cryptography-dev/2016-August/000676.html
We see this code makes no distinction between the root_cert and the
intermediate. If we look at the documentation, add_cert itself adds a
trusted cert (maybe add_trusted_cert would be a better name?).
It includes examples on why this is a terrible idea. I can not stress this enough: verifying your chain by trusting the intermediates is similar to not performing any check at all.
Having said that, how do you verify a certificate chain in Python? The best alternative I found is https://github.com/wbond/certvalidator, which seems to do the job.
There are also some flawed alternatives:
https://github.com/alex/x509-validator, which explicitly states that it is not safe to use.
https://github.com/pyca/cryptography/issues/1660#issuecomment-75075319, which does some sort of checking, but I'm not sure it works as intended
This is the current state for some respectable Python cryptography libraries:
https://github.com/pyca/pyopenssl/pull/473 discusses a pull request to incorporate chain verification in pyopenssl, which has not been merged yet.
https://github.com/pyca/cryptography/issues/2381 is an issue requesting a similar feature to the cryptography module, but it is still unresolved
Both threads seem stale at this time.
I know this does not match the question's case, but: if you are building something using certificate validation in TLS sockets, just use the modules already available in Python. Never re-invent the wheel, especially regarding cryptography. Crypto is hard; the only easy thing about it is messing it up.
I looked into pyopenssl library and found this for certificate chain validation. The following example is from their tests and seem to do what you want, which is validating chain of trust to a trusted root certificate. Here are the relevant docs for X509Store and X509StoreContext
from OpenSSL.crypto import load_certificate, load_privatekey
from OpenSSL.crypto import X509Store, X509StoreContext
from six import u, b, binary_type, PY3
root_cert_pem = b("""-----BEGIN CERTIFICATE-----
MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE
BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU
ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2
NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM
MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U
ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL
urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy
2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF
1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE
FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn
VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE
BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS
b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB
AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi
hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY
w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn
-----END CERTIFICATE-----
""")
intermediate_cert_pem = b("""-----BEGIN CERTIFICATE-----
MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw
WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw
DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw
ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh
dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT
MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK
FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT
21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID
AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX
QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+
9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF
9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT
-----END CERTIFICATE-----
""")
untrusted_cert_pem = b("""-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw
ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV
BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1
iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4
+kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm
biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw
UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN
3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6
x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA==
-----END CERTIFICATE-----
""")
root_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem)
untrusted_cert = load_certificate(FILETYPE_PEM, untrusted_cert_pem)
store = X509Store()
store.add_cert(root_cert)
store.add_cert(intermediate_cert)
store_ctx = X509StoreContext(store, untrusted_cert)
print(store_ctx.verify_certificate())
Used the excellent explanation on https://duo.com/labs/research/chain-of-fools to adapt the previous solution (from avi_das), and added some comments in code. Basically we need to only add certificates to the store when they are trusted (e.g. root certificate) or verified/trusted by another (e.g. intermediate certificate). You cannot add all certificates to the store in one go, as you need to verify each certificate along the chain with the correct certificates in the store at that moment. The code comments below are less abstract.
import OpenSSL
from six import u, b, binary_type, PY3
root_cert_pem = b("""-----BEGIN CERTIFICATE-----
MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE
BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU
ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2
NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM
MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U
ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL
urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy
2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF
1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE
FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn
VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE
BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS
b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB
AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi
hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY
w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn
-----END CERTIFICATE-----
""")
intermediate_cert_pem = b("""-----BEGIN CERTIFICATE-----
MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw
WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw
DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw
ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh
dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT
MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK
FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT
21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID
AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX
QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+
9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF
9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT
-----END CERTIFICATE-----
""")
untrusted_cert_pem = b("""-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw
ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV
BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT
CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh
biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1
iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4
+kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm
biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw
UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN
3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6
x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA==
-----END CERTIFICATE-----
""")
# load certificates
root_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, root_cert_pem)
intermediate_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, intermediate_cert_pem)
untrusted_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, untrusted_cert_pem)
# Trust the root certificate
store = OpenSSL.crypto.X509Store()
store.add_cert(root_cert)
# only add intermediate if it can be verified by the root
store_ctx = OpenSSL.crypto.X509StoreContext(store, intermediate_cert)
print(store_ctx.verify_certificate())
store.add_cert(intermediate_cert)
# now that root and intermediate are trused, you can verify the end certificate using the store
store_ctx = OpenSSL.crypto.X509StoreContext(store, untrusted_cert)
print(store_ctx.verify_certificate())
Awesome answer by Paul-Armand and the chain of fools article. This works for me. Note: I need to set the validation date of the store appropriately.
However I found a way to avoid adding the untrusted certificate as trusted. I checked the pyopenssl code (https://github.com/pyca/pyopenssl/blob/main/src/OpenSSL/crypto.py) and found there's a "chain" parameter on X509StoreContext, so I tried to just put the intermediate certificate in there like so:
# .... paste Paul-Armand's example here ....
store = OpenSSL.crypto.X509Store()
store.add_cert(root_cert)
# Set time to reuse certs from Paul-Armand's example
validation_date = datetime.datetime.strptime("2016-01-01", "%Y-%m-%d")
store.set_time(validation_date)
# This is the change, added param [intermediate_cert]
store_ctx = OpenSSL.crypto.X509StoreContext(store, untrusted_cert, [intermediate_cert])
print("Untrusted chain example")
try:
store_ctx.verify_certificate()
print("Verify - OK")
except OpenSSL.crypto.X509StoreContextError:
print("Verify failed - bad")
It all validates properly for me and is closer to what I use on the command line with OpenSSL (-untrusted intermediate.pem). Sure wish I figured out pyopenssl in January 2019, would have saved me years of using the "certvalidate" python alternative. I revisited because I'm moving to EdDSA25519 keys and certvalidate does not support them.

File object in memory using Python

I'm not sure how to word this exactly but I have a script that downloads an SSL certificate from a web server to check it's expiration date.
To do this, I need to download the CA certificates. Currently I write them to a temporary file in the /tmp directory and read it back later but I am sure there must be a way to do this without writing to disk.
Here's the portion that's downloading the certificates
CA_FILE = '/tmp/ca_certs.txt'
root_cert = urllib.urlopen('https://www.cacert.org/certs/root.txt')
class3_cert = urllib.urlopen('https://www.cacert.org/certs/class3.txt')
temp_file = open(CA_FILE, 'w')
temp_file.write(root_cert.read())
temp_file.write(class3_cert.read())
temp_file.close()
EDIT
Here's the portion that uses the file to get the certificate
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = ssl.wrap_socket(s, ca_certs=CA_FILE, cert_reqs=ssl.CERT_REQUIRED)
ssl_sock.connect(('mail.google.com', 443))
date = ssl_sock.getpeercert()['notAfter']
the response from urllib is a file object. just use those wherever you are using the actual files instead. This is assuming that the code that consumes the file objects doesn't need to write to them of course.
Wow, don't do this. You're hitting cacert's site every time? That's INCREDIBLY rude and needlessly eats their resources. It's also ridiculously bad security practice. You're supposed to get the root certificate once and validate that it's the correct root cert and not some forgery, otherwise you can't rely on the validity of certificates signed by it.
Cache their root cert, or better yet, install it with the rest of the root certificates on your system like you're supposed to.
In the following line:
temp_file.write(root_cert.read())
you are actually reading the certificate into memory, and writing it out again. That line is equivalent to:
filedata = root_cert.read()
temp_file.write(filedata)
Now filedata is a variable containing the bytes of the root certificate, that you can use in any way you like (including not writing it to temp_file and doing something else with it instead).

How to do PGP in Python (generate keys, encrypt/decrypt)

I'm making a program in Python to be distributed to windows users via an installer.
The program needs to be able to download a file every day encrypted with the user's public key and then decrypt it.
So I need to find a Python library that will let me generate public and private PGP keys, and also decrypt files encrypted with the public key.
Is this something pyCrypto will do (documentation is nebulous)? Are there other pure Python libraries? How about a standalone command line tool in any language?
All I saw so far was GNUPG but installing that on Windows does stuff to the registry and throws dll's everywhere, and then I have to worry about whether the user already has this installed, how to backup their existing keyrings, etc. I'd rather just have a python library or command line tool and mange the keys myself.
Update: pyME might work but it doesn't seem to be compatible with Python 2.4 which I have to use.
You don't need PyCrypto or PyMe, fine though those packages may be - you will have all kinds of problems building under Windows. Instead, why not avoid the rabbit-holes and do what I did? Use gnupg 1.4.9. You don't need to do a full installation on end-user machines - just gpg.exe and iconv.dll from the distribution are sufficient, and you just need to have them somewhere in the path or accessed from your Python code using a full pathname. No changes to the registry are needed, and everything (executables and data files) can be confined to a single folder if you want.
There's a module GPG.py which was originally written by Andrew Kuchling, improved by Richard Jones and improved further by Steve Traugott. It's available here, but as-is it's not suitable for Windows because it uses os.fork(). Although originally part of PyCrypto, it is completely independent of the other parts of PyCrypto and needs only gpg.exe/iconv.dll in order to work.
I have a version (gnupg.py) derived from Traugott's GPG.py, which uses the subprocess module. It works fine under Windows, at least for my purposes - I use it to do the following:
Key management - generation, listing, export etc.
Import keys from an external source (e.g. public keys received from a partner company)
Encrypt and decrypt data
Sign and verify signatures
The module I've got is not ideal to show right now, because it includes some other stuff which shouldn't be there - which means I can't release it as-is at the moment. At some point, perhaps in the next couple of weeks, I hope to be able to tidy it up, add some more unit tests (I don't have any unit tests for sign/verify, for example) and release it (either under the original PyCrypto licence or a similar commercial-friendly license). If you can't wait, go with Traugott's module and modify it yourself - it wasn't too much work to make it work with the subprocess module.
This approach was a lot less painful than the others (e.g. SWIG-based solutions, or solutions which require building with MinGW/MSYS), which I considered and experimented with. I've used the same (gpg.exe/iconv.dll) approach with systems written in other languages, e.g. C#, with equally painless results.
P.S. It works with Python 2.4 as well as Python 2.5 and later. Not tested with other versions, though I don't foresee any problems.
After a LOT of digging, I found a package that worked for me. Although it is said to support the generation of keys, I didn't test it. However I did manage to decrypt a message that was encrypted using a GPG public key. The advantage of this package is that it does not require a GPG executable file on the machine, and is a Python based implementation of the OpenPGP (rather than a wrapper around the executable).
I created the private and public keys using GPG4win and kleopatra for windows
See my code below.
import pgpy
emsg = pgpy.PGPMessage.from_file(<path to the file from the client that was encrypted using your public key>)
key,_ = pgpy.PGPKey.from_file(<path to your private key>)
with key.unlock(<your private key passpharase>):
print (key.decrypt(emsg).message)
Although the question is very old. I hope this helps future users.
PyCrypto supports PGP - albeit you should test it to make sure that it works to your specifications.
Although documentation is hard to come by, if you look through Util/test.py (the module test script), you can find a rudimentary example of their PGP support:
if verbose: print ' PGP mode:',
obj1=ciph.new(password, ciph.MODE_PGP, IV)
obj2=ciph.new(password, ciph.MODE_PGP, IV)
start=time.time()
ciphertext=obj1.encrypt(str)
plaintext=obj2.decrypt(ciphertext)
end=time.time()
if (plaintext!=str):
die('Error in resulting plaintext from PGP mode')
print_timing(256, end-start, verbose)
del obj1, obj2
Futhermore, PublicKey/pubkey.py provides for the following relevant methods:
def encrypt(self, plaintext, K)
def decrypt(self, ciphertext):
def sign(self, M, K):
def verify (self, M, signature):
def can_sign (self):
"""can_sign() : bool
Return a Boolean value recording whether this algorithm can
generate signatures. (This does not imply that this
particular key object has the private information required to
to generate a signature.)
"""
return 1
PyMe does claim full compatibility with Python 2.4, and I quote:
The latest version of PyMe (as of this
writing) is v0.8.0. Its binary
distribution for Debian was compiled
with SWIG v1.3.33 and GCC v4.2.3 for
GPGME v1.1.6 and Python v2.3.5,
v2.4.4, and v2.5.2 (provided in
'unstable' distribution at the time).
Its binary distribution for Windows
was compiled with SWIG v1.3.29 and
MinGW v4.1 for GPGME v1.1.6 and Python
v2.5.2 (although the same binary get
installed and works fine in v2.4.2 as
well).
I'm not sure why you say "it doesn't seem to be compatible with Python 2.4 which I have to use" -- specifics please?
And yes it does exist as a semi-Pythonic (SWIGd) wrapper on GPGME -- that's a popular way to develop Python extensions once you have a C library that basically does the job.
PyPgp has a much simpler approach -- that's why it's a single, simple Python script: basically it does nothing more than "shell out" to command-line PGP commands. For example, decryption is just:
def decrypt(data):
"Decrypt a string - if you have the right key."
pw,pr = os.popen2('pgpv -f')
pw.write(data)
pw.close()
ptext = pr.read()
return ptext
i.e., write the encrypted cyphertext to the standard input of pgpv -f, read pgpv's standard output as the decrypted plaintext.
PyPgp is also a very old project, though its simplicity means that making it work with modern Python (e.g., subprocess instead of now-deprecated os.popen2) would not be hard. But you still do need PGP installed, or PyPgp won't do anything;-).
M2Crypto has PGP module, but I have actually never tried to use it. If you try it, and it works, please let me know (I am the current M2Crypto maintainer). Some links:
Module sources
Demo Script
unit tests
Update: The PGP module does not provide ways to generate keys, but presumably these could be created with the lower level RSA, DSA etc. modules. I don't know PGP insides, so you'd have to dig up the details. Also, if you know how to generate these using openssl command line commands, it should be reasonably easy to convert that to M2Crypto calls.
As other have noted, PyMe is the canonical solution for this, since it's based on GpgME, which is part of the GnuPG ecosystem.
For Windows, I strongly recommend to use Gpg4win as the GnuPG distribution, for two reasons:
It's based on GnuPG 2, which, among other things, includes gpg2.exe, which can (finally, I might add :) start gpg-agent.exe on-demand (gpg v1.x can't).
And secondly, it's the only official Windows build by the GnuPG developers. E.g. it's entirely cross-compiled from Linux to Windows, so not a iota of non-free software was used in preparing it (quite important for a security suite :).
To sign with only the exported public key file without a keyring.
With PGPy 0.5.2 (pure Python GPG RFC implementation):
key_fpath = './recipient-PUB.gpg'
rsa_pub, _ = pgpy.PGPKey.from_file(rkey_fpath)
rkey = rsa_pub.subkeys.values()[0]
text_message = pgpy.PGPMessage.new('my msg')
encrypted_message = rkey.encrypt(text_message)
print encrypted_message.__bytes__()
With gpg 1.10.0 (gpgme Python bindings - former PyME):
rkey_fpath = './recipient-PUB.gpg'
cg = gpg.Context()
rkey = list(cg.keylist(source = rkey_fpath))
ciphertext, result, sign_result = cg.encrypt('my msg', recipients=rkey, sign=False, always_trust=True)
print ciphertext
A simple benchmark in a for loop shows me that for this simple operation on my system PGPy is ~3x time faster than gpgme Python bindings (please do not take this statement as X is faster than Y: I will invite you to test in your environment).
Here's a full script that will:
Attempt to decrypt all the files in a given folder that were encrypted with your public key.
Write the new files to a specified folder.
Move the encrypted files to a specified folder.
The script also has everything you need to create and store your own private and public keys, check out the "First time set up" section below.
The idea is that you can schedule this script to run as often as you like, and it'll automatically decrypt data found and store it for you.
I hope this helps someone, this was a tricky project to figure out.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~ Introduction, change log and table of contents
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Purpose: This script is used to decrypt files that are passed to us from ICF.
#
# Change date Changed by Description
# 2022-10-03 Ryan Bradley Initial draft
# 2022-10-12 Ryan Bradley Cleaned up some comments and table of contents.
#
# Table of Contents
# [1.0] Hard-coded variables
# [1.1] Load packages and custom functions
# [1.3] First time set up
# [1.4] Define custom functions
# [2.0] Load keys and decrypt files
#
# Sources used to create this script, and for further reading:
# https://github.com/SecurityInnovation/PGPy/
# https://stackoverflow.com/questions/1020320/how-to-do-pgp-in-python-generate-keys-encrypt-decrypt
# https://pypi.org/project/PGPy/
# https://betterprogramming.pub/creating-a-pgp-encryption-tool-with-python-19bae51b7fd
# https://pgpy.readthedocs.io/en/latest/examples.html
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~ [1.1] Load packages
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import glob
import pgpy
import shutil
import io
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~ [1.2] Hard-coded variables
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define the paths to public and private keys
path_public_key = r'YOUR PATH HERE'
path_private_key = r'YOUR PATH HERE'
# Define paths to files you want to try decrypting
path_original_files = r'YOUR PATH HERE'
path_decrypted_files = r'YOUR PATH HERE'
path_encrypted_files= r'YOUR PATH HERE'
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~ [1.3] First time set up
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# IMPORTANT WARNINGS!!!!
# - Do NOT share your private key with anyone else.
# - You MUST have the associated private key that is is generated along with a public key
# if you want to be able to decrypt anything that is encryped with that public key. Do
# not overwrite the existing keys unless you will never need any of the previously
# encryped data.
# - Do not generate new public and private keys unless you have a good reason to.
#
# The following steps will walk you through how to create and write public and private keys to
# a network location. Be very careful where you store this information. Anyone with access
# to your private key can decrypt anything that was encryped with your public key.
#
# These steps only need to be performed one time when the script is first being
# created. They are commented out intentionally, as they shouldn't need to be performed
# every time the script is ran.
#
# Here's the a link to the documentation on this topic:
# https://pgpy.readthedocs.io/en/latest/examples.html
# # Load the extra things we need to define a new key
# from pgpy.constants import PubKeyAlgorithm, KeyFlags, HashAlgorithm, SymmetricKeyAlgorithm, CompressionAlgorithm
# # Gerate a new a primary key. For this example, we'll use RSA, but it could be DSA or ECDSA as well
# key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
# # Define a new user
# uid = pgpy.PGPUID.new('SA_CODA_Admin', comment='Customer Strategy and Data Analytics service account.', email='CustomerDataAnalytics#cmsenergy.com')
# # Add the new user id to the key, and define all the key preferences.
# key.add_uid(uid, usage={KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage},
# hashes=[HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512, HashAlgorithm.SHA224],
# ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.AES192, SymmetricKeyAlgorithm.AES128],
# compression=[CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZ2, CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed]
# , is_compressed = True)
# # Write the ASCII armored public key to a network location.
# text_file = open(path_public_key, 'w')
# text_file.write(str(key.pubkey))
# text_file.close()
# # Write the ASCII armored private key to a network location.
# text_file = open(path_private_key, 'w')
# text_file.write(str(key))
# text_file.close()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~ [1.4] Define custom functions
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def file_encrypt(path_original_file, path_encrypted_file, key_public):
"""
A function that encrypts the content of a file at the given path and
creates an ecryped version file at the new location using the specified
public key.
"""
# Create a PGP file, compressed with ZIP DEFLATE by default unless otherwise specified
pgp_file = pgpy.PGPMessage.new(path_original_file, file=True)
# Encrypt the data with the public key
encrypted_data = key_public.encrypt(pgp_file)
# Write the encryped data to the encrypted destination
text_file = open(path_encrypted_file, 'w')
text_file.write(str(encrypted_data))
text_file.close()
def file_decrypt(path_encrypted_file, path_decrypted_file, key_private):
"""
A function that decrypts the content of a file at path path and
creates a decrypted file at the new location using the given
private key.
"""
# Load a previously encryped message from a file
pgp_file = pgpy.PGPMessage.from_file(path_encrypted_file)
# Decrypt the data with the given private key
decrypted_data = key_private.decrypt(pgp_file).message
# Read in the bytes of the decrypted data
toread = io.BytesIO()
toread.write(bytes(decrypted_data))
toread.seek(0) # reset the pointer
# Write the data to the location
with open(path_decrypted_file, 'wb') as f:
shutil.copyfileobj(toread, f)
f.close()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#~~ [2.0] Load keys and decrypt files
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Load your pre-generated public key from the network
key_public, _ = pgpy.PGPKey.from_file(path_public_key)
# Load your pre-generated public key from the network
key_private, _ = pgpy.PGPKey.from_file(path_private_key)
# Find and process any encrypted files in the landing folder
for file in glob.glob(path_original_files + '\*.pgp'):
# Get the path to the file we need to decrypt
path_encrypted_file = str(file)
# Extract the file name
parts = path_encrypted_file.split('\\')
str_file_name = parts[len(parts)-1]
str_clean_file_name = str_file_name[:-4]
# Extract the file exension
str_extension = str_clean_file_name.split('.')
str_extension = str_extension[len(str_extension) - 1]
# Create the path to the new decryped file, dropping the ".pgp" extension
path_decrypted_file = path_decrypted_files + '\\' + str_clean_file_name
# Create the path to the place we'll store the encryped file
path_archived_encrypted_file = path_encrypted_files + '\\' + str_file_name
# Decrypt the file
try:
file_decrypt(path_encrypted_file, path_decrypted_file, key_private)
# Move the encryped file to its new location
shutil.move(path_encrypted_file, path_archived_encrypted_file)
except:
print('DECRYPTION ERROR!')
print(f'Unable to decrypt {path_encrypted_file}')
# Just for reference, here's how you would call the function to encrypt a file:
# file_encrypt(path_original_file, path_encrypted_file, key_public)

Checking folder/file ntfs permissions using python

As the question title might suggest, I would very much like to know of the way to check the ntfs permissions of the given file or folder (hint: those are the ones you see in the "security" tab). Basically, what I need is to take a path to a file or directory (on a local machine, or, preferrably, on a share on a remote machine) and get the list of users/groups and the corresponding permissions for this file/folder. Ultimately, the application is going to traverse a directory tree, reading permissions for each object and processing them accordingly.
Now, I can think of a number of ways to do that:
parse cacls.exe output -- easily done, BUT, unless im missing something, cacls.exe only gives the permissions in the form of R|W|C|F (read/write/change/full), which is insufficient (I need to get the permissions like "List folder contents", extended permissions too)
xcacls.exe or xcacls.vbs output -- yes, they give me all the permissions I need, but they work dreadfully slow, it takes xcacls.vbs about ONE SECOND to get permissions on a local system file. Such speed is unacceptable
win32security (it wraps around winapi, right?) -- I am sure it can be handled like this, but I'd rather not reinvent the wheel
Is there anything else I am missing here?
Unless you fancy rolling your own, win32security is the way to go. There's the beginnings of an example here:
http://timgolden.me.uk/python/win32_how_do_i/get-the-owner-of-a-file.html
If you want to live slightly dangerously (!) my in-progress winsys package is designed to do exactly what you're after. You can get an MSI of the dev version here:
http://timgolden.me.uk/python/downloads/WinSys-0.4.win32-py2.6.msi
or you can just checkout the svn trunk:
svn co http://winsys.googlecode.com/svn/trunk winsys
To do what you describe (guessing slightly at the exact requirements) you could do this:
import codecs
from winsys import fs
base = "c:/temp"
with codecs.open ("permissions.log", "wb", encoding="utf8") as log:
for f in fs.flat (base):
log.write ("\n" + f.filepath.relative_to (base) + "\n")
for ace in f.security ().dacl:
access_flags = fs.FILE_ACCESS.names_from_value (ace.access)
log.write (u" %s => %s\n" % (ace.trustee, ", ".join (access_flags)))
TJG

Categories