-
-
Notifications
You must be signed in to change notification settings - Fork 31
Support intermediate CAs #30
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #30 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 3 3
Lines 255 275 +20
Branches 14 18 +4
=====================================
+ Hits 255 275 +20
Continue to review full report at Codecov.
|
njsmith
left a comment
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.
(Also needs tests of course.)
|
|
||
| name = _name(u"Testing CA #" + random_text()) | ||
| self._certificate = ( | ||
| _cert_builder_common(name, name, self._private_key.public_key()) |
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 think here you want self._private_key, not sign_key, because this fills in the parts of the cert that say "this is the key that I use to sign things"?
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.
@njsmith Thanks! That was the issue. I'll make sure to add tests, yes, thanks for the reminder.
It does not seem to work, though. Test with:
import trustme
ca = trustme.CA()
ca = ca.create_child_ca()
server_cert = ca.issue_server_cert(u"test-host.example.org")
server_cert.private_key_and_cert_chain_pem.write_to_path("server.pem")
When commenting the `ca = ca.create_child_ca()` line, `openssl verify
-untrusted server.pem server.pem` says:
server.pem: O = "trustme v0.4.0+dev", OU = Testing server cert #XukT8k24PBXQBSqv
error 20 at 0 depth lookup:unable to get local issuer certificate
But with the `create_child_ca` line, we get:
server.pem: O = "trustme v0.4.0+dev", OU = Testing CA #D2vixR6eI2g5N2k5
error 19 at 1 depth lookup:self signed certificate in certificate chain
I have no idea what I'm doing: what's the problem? I did try to make
sure to only self-sign the root CA and not including it in the
server.pem file.
4c6acc1 to
d884652
Compare
|
(I've pushed my progress here, but I still need to dig in the test failures, so it's not worth reviewing as the last commit will contain obvious mistakes.) |
|
So I don't really know how to debug this. The certificate chain seems to be valid. To check, use this new test script: Running Yay! However the test client from the end-to-end test still says: Any advice on a general strategy to debug this test failure? |
|
@pquentin I'm afraid I do not know of any general strategy. If we really get stuck on something, my general strategy will probably be "beg Alex Gaynor or Paul Kehrer for help". In this case though I tried doing some general poking around to make sure things looked basically reasonable, and noticed something odd. When I run your script, I get out (Actually I didn't try the and I noticed that the random name that trustme uses for the CA seemed to match, which seemed weird since the server cert is supposed to be issued by the intermediate CA, not the root CA...) ...and actually now I'm looking at the test script again, and it doesn't look quite right :-). It throws away the root CA object, and write out the intermediate CA as import trustme
root_ca = trustme.CA()
child_ca = root_ca.create_child_ca()
server_cert = child_ca.issue_server_cert(u"test-host.example.org")
root_ca.cert_pem.write_to_path("ca.pem")
server_cert.private_key_and_cert_chain_pem.write_to_path("server.pem")Now I get: Annoyingly, So it looks like the intermediate CA cert is self-signed, instead of being signed by the root CA? |
|
Thank you @njsmith, this is incredibly useful, as always! Thanks for sharing your process, too: I now realize that I should have tried those things. |
It was causing verification to fail in tests.
See https://tools.ietf.org/html/rfc5280#section-4.2.1.3: > Conforming CAs MUST include this extension in certificates that > contain public keys that are used to validate digital signatures on > other public key certificates or CRLs. When present, conforming CAs > SHOULD mark this extension as critical. > The keyCertSign bit is asserted when the subject public key is > used for verifying signatures on public key certificates. If the > keyCertSign bit is asserted, then the cA bit in the basic > constraints extension (Section 4.2.1.9) MUST also be asserted.
|
@njsmith So the intermediate CA was signed by the root CA, but the issuer name was incorrectly set to the intermediate CA subject name. It turns out that this was the actual issue in the tests, which are now fixed! I also added a commit that adds required extensions for CAs, as noted in https://security.stackexchange.com/a/176602/182464 (I created an account there to upvote the answer.) However I still get this with the fixed t.py script: I'll have to find out why. |
trustme/__init__.py
Outdated
| data_encipherment=False, | ||
| key_agreement=False, | ||
| key_cert_sign=True, | ||
| crl_sign=False, |
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.
Did a bit of reading on this, and found: "This extension MUST be present and MUST be marked critical. Bit positions for keyCertSign and cRLSign MUST be set. If the Private Key is used for signing OCSP responses, then the digitalSignature bit MUST be set." – https://cabforum.org/baseline-requirements-certificate-contents/
So I think we should have crl_sign=True?
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.
Thanks, done. (The openssl issue remains, I still need to look into it.)
|
Pasting from private chat: ughhh I get: Or even better, let's add |
njsmith
left a comment
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.
Okay since this seems to actually be working now, let me do a more nitpicky review!
The actual code looks fine to me.
-
Needs a news entry. Actually, probably needs 2 news entries: one for the intermediate cert support, and one for the new keyUsage stuff.
-
Do you want to include a convenient way to create a server cert with a missing intermediate? (Could be a separate PR, but IIUC that's what you actually want but it seems... actually kind of non-trivial with trustme's API currently. Maybe
broken_server_cert = working_server_cert.with_intermediates_missing()?)
tests/test_trustme.py
Outdated
| def test_intermediate(): | ||
| ca = CA() | ||
| ca = ca.create_child_ca() | ||
| ca.issue_server_cert(u"test-host.example.org") |
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 test feels a bit trivial... maybe that's OK, it's just a smoke test? But maybe we should at least check a few basic attributes, like that server_cert's chain has two entries, the second one matches the child ca's chain, that sort of thing?
|
(And thank you for your patience working through all this nonsense!) |
|
Thank you, @njsmith, for your patience and for clearing all those roadblocks for me.
I added more checks, made sure to decrease the path length, and added the news entries. Can you improve the wording of the docstring and news entries? And anything else you'd like to improve. |
|
(Oh and I'd prefer to leave |
|
Looks good to me!
makes sense |
|
Maybe this comment will avoid me losing half an hour the next time I try to understand why With Homebrew, you need to explicitly install it with |
I tried to address #3.
It does not seem to work, though. Test with:
When commenting the
ca = ca.create_child_ca()line,openssl verify -untrusted server.pem server.pemsays:But with the
create_child_caline, we get:I have no idea what I'm doing: what's the problem? I did try to make
sure to only self-sign the root CA and not including it in the
server.pem file.