-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use cryptography and pyOpenSSL instead of M2Crypto #421
Use cryptography and pyOpenSSL instead of M2Crypto #421
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #421 +/- ##
===========================================
+ Coverage 47.15% 54.75% +7.59%
===========================================
Files 33 33
Lines 2059 2009 -50
Branches 331 312 -19
===========================================
+ Hits 971 1100 +129
+ Misses 993 812 -181
- Partials 95 97 +2
Continue to review full report at Codecov.
|
This PEP-8s nearly all of fedmsg except for the stuff touched by fedora-infra#421. Signed-off-by: Jeremy Cline <[email protected]>
This PEP-8s nearly all of fedmsg except for the stuff touched by fedora-infra#421. Signed-off-by: Jeremy Cline <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks reasonable to me. Note however that I lack familiarity with the cryptography and pyopenssl libraries, so I am not able to comment on whether they were used correctly or not. Thus, I recommend getting a second opinion from someone who is familiar with those APIs before merging. On the other hand, I did like the tests you wrote and they helped give me confidence in the code ☺
fedmsg/crypto/utils.py
Outdated
signer (str): The Common Name of the certificate used to sign the message. | ||
|
||
Returns: | ||
bool: True of the policy defined in the settings allows the signer to send |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True if the policy…
# whitelisted to send on this topic in our config policy. | ||
return True | ||
else: | ||
# We have a policy for this topic and $homeboy isn't on the list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha
fedmsg/crypto/utils.py
Outdated
# routing_policy for the topic of this message.. but we don't | ||
# really care. | ||
_log.info('No routing policy defined for "{t}" and routing_nitpicky ' | ||
'is False so the message is being treated as authorized.'.format(t=topic)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might make sense at warning level.
fedmsg/crypto/utils.py
Outdated
def load_remote_cert(location, cache, cache_expiry, tries=0, **config): | ||
""" Get a fresh copy from fp.o/fedmsg/crl.pem if ours is getting stale. | ||
|
||
Return the local filename. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend documenting the arguments and the return type.
(cache_expiry and time.time() - modtime > cache_expiry) | ||
): | ||
try: | ||
response = requests.get(location) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe put a timeout on this call to get()
.
except M2Crypto.RSA.RSAError as e: | ||
return fail(str(e)) | ||
_validate_signing_cert(ca_certificate, certificate, crl) | ||
except X509StoreContextError as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume this step will catch expired certificates and non-properly signed certificates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, it'll raise for that as well as revoked certificates and expired CRLs
fedmsg/tests/crypto/test_x509.py
Outdated
@@ -0,0 +1,216 @@ | |||
# This file is part of fedmsg. | |||
# Copyright (C) 2012 - 2014 Red Hat, Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2017?
self.config['routing_policy'] = {'mytopic': ['shell-app01.phx2.fedoraproject.org']} | ||
self.config['routing_nitpicky'] = True | ||
signed = self.sign({'topic': 'mytopic', 'message': 'so secure'}, **self.config) | ||
self.assertTrue(self.validate(signed, **self.config)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a test_signed_by_expired_cert
and test_signed_by_expired_ca
(bad name, but what I mean is the signing cert is signed by an expired CA) as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! I have made it so, Number One.
|
||
|
||
@skipIf(not (_m2crypto or _cryptography), "Neither M2Crypto nor Cryptography available") | ||
class X509BaseTests(TestCase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this pattern of making a superclass to get the same tests run with the different implementations. Nice!
'ca_cert_cache': os.path.join(SSLDIR, 'ca.crt'), | ||
'ca_cert_cache_expiry': 1497618475, # Stop fedmsg overwriting my CA, See Issue 420 | ||
|
||
'crl_location': "http://threebean.org/fedmsg-tests/crl.pem", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these tests work offline? If not, I see that you have a fedmsg/tests/test_certs/keys/crl.pem
that you might could use instead…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They do, but only because I set the cache expiry to be ~47 years. There's no way to have the cert not downloaded from the Internet eventually and it defaults to the Fedora CA and CRL so even if you don't provide a URL it'll happen (all of which I want to change, I just haven't decided how yet).
M2Crypto is Python 2 only and is in maintenance mode. Although pyOpenSSL is _also_ in maintenance mode, it supports Python 3 and relies on cryptography for its OpenSSL bindings. Currently the only thing cryptography is missing (that pyOpenSSL has) is certificate chain validation. Once that's available in cryptography, we can drop pyOpenSSL. Unfortunately, we still need to support M2Crypto because the current versions of cryptography and pyOpenSSL in EL7 is below 1.6 and 16.1.0 respectively. This patch uses cryptography and pyOpenSSL if available and falls back to M2Crypto. fixes fedora-infra#411 Signed-off-by: Jeremy Cline <[email protected]>
Signed-off-by: Jeremy Cline <[email protected]>
Signed-off-by: Jeremy Cline <[email protected]>
Prior to this commit, it was required that a CRL was specified. This was fine with the old M2Crypto code that didn't properly check the CRL for expiration, but the new crytography-based code will fail if the CRL is expired. This allows the definition of a CRL in the configuration to be optional so users don't have to keep a fresh CRL that they may not actually be using available. Signed-off-by: Jeremy Cline <[email protected]>
Thanks bowlofeggs for the suggestion! Signed-off-by: Jeremy Cline <[email protected]>
22a78f5
to
9ba315f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good, with some future suggestions.
|
||
Return the local filename. | ||
|
||
.. note:: This is not a public API and is subject to change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe start the name with _ then?
def _disabled_sign(*args, **kwargs): | ||
"""A fallback function that emits a warning when no crypto is being used.""" | ||
warnings.warn('Message signing is disabled because "cryptography" and ' | ||
'"pyopenssl" or "m2crypto" are not available.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add parentheses to indicate the and/or relations?
asymmetric.padding.PKCS1v15(), | ||
hashes.SHA1() | ||
hashes.SHA1(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it's fine right now, it would be great if we could start migrating to new hashes, like sha256/sha512.
Maybe add an indicator which is used before the next version, so we can switch easier in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered this, but I didn't want to make that change alongside all the other stuff that's already happening. I thought I filed an issue, but it seems like I hadn't, so I filed #438
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes perfect sense. This was just an idea now that you got me rereading the code :).
# Load our policy from the config dict. | ||
# Step 4, check that the certificate is permitted to emit messages for the | ||
# topic. | ||
common_name = crypto_certificate.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should not use the common name ideally, this is being deprecated by the CA/Browser Forum baseline requirements.
While we are not reliant on those, I think ideally we should follow their move to the Subject Alternative Name.
This does mean there may be multiple records, and we might need to check all SAN values in the authorization step.
For the time being, for compatibility, we might add the Common Name to the list of SANs for checking purposes though and issue a warning if the CN was not in any SAN values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I wanted to do this, but the M2Crypto version is only using the CN. I will definitely look into moving both of them to SAN (#439).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concrete sentence about Common Name from the CA/Browser Baselines requirements v1.4.5 (which is currently the latest stable, source https://cabforum.org/wp-content/uploads/CA-Browser-Forum-BR-1.4.5.pdf):
Certificate Field: subject:commonName (OID 2.5.4.3)
Required/Optional: Deprecated (Discouraged, but not prohibited)
routing_policy = config.get('routing_policy', {}) | ||
nitpicky = config.get('routing_nitpicky', False) | ||
return utils.validate_policy( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An idea would be a return any(...) over all the SANs.
'ca_cert_cache': os.path.join(SSLDIR, 'ca.crt'), | ||
'ca_cert_cache_expiry': 1497618475, # Stop fedmsg overwriting my CA, See Issue 420 | ||
|
||
'crl_location': "http://threebean.org/fedmsg-tests/crl.pem", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we would not depend on this website... Maybe use a file:// ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most unfortunately, the way the code currently works it doesn't support file://. I set the cache expiry to be sometime in the mid 2030s so it should never actually try to fetch from that URL. I plan to re-write all the cert fetching code in the near future.
Signed-off-by: Jeremy Cline <[email protected]>
M2Crypto is Python 2 only and is in maintenance mode. Although pyOpenSSL
is also in maintenance mode, it supports Python 3 and relies on
cryptography for its OpenSSL bindings. Currently the only thing
cryptography is missing (that pyOpenSSL has) is certificate chain
validation. Once that's available in cryptography, we can drop
pyOpenSSL.
Unfortunately, we still need to support M2Crypto because the current
versions of cryptography and pyOpenSSL in EL7 is below 1.6 and 16.1.0
respectively.
This patch uses cryptography and pyOpenSSL if available and falls back
to M2Crypto.
fixes #411
Signed-off-by: Jeremy Cline [email protected]