Skip to content

[ca] Separate RootCA signing cert from root certs#2038

Merged
aaronlehmann merged 2 commits into
moby:masterfrom
cyli:separate-root-certs-from-signing-certs
Mar 18, 2017
Merged

[ca] Separate RootCA signing cert from root certs#2038
aaronlehmann merged 2 commits into
moby:masterfrom
cyli:separate-root-certs-from-signing-certs

Conversation

@cyli
Copy link
Copy Markdown
Contributor

@cyli cyli commented Mar 14, 2017

Since in theory, we could be signing with an intermediate certificate, which should not be used as a trust root.

This is useful both for supporting offline roots, and for supporting swarm root CA rotation (in which the root we trust is still the old root, but we will be signing with a cross-signed intermediate, which can easily be switched to the new root.

For more context on how this will be used in terms of root CA rotation, please see #2037. (or just the diff)

I made it a requirement that the signing cert has to chain up through the intermediates to the root pool, so that during root CA rotation the signing cert will actually be the cross-signed intermediate. This doesn't have to be the case (the signing cert could be a root instead) but I thought having direct relationship between the signing cert and the root certs would be easier to validate (can use Certificate.Verify.

Otherwise, the relationship to the root is indirect ; it has to have the same public key/subject as the first intermediate, and the first intermediate has to chain up to the root. But because it doesn't chain up to the root itself, we'd have to do certificate verification (expiry, maybe path len check) ourselves. Maybe this would be more flexible though, since we'd only be manually checking the signing cert and not the whole chain?

@cyli cyli force-pushed the separate-root-certs-from-signing-certs branch from fb78408 to 7c78bfa Compare March 14, 2017 23:42
@cyli cyli force-pushed the separate-root-certs-from-signing-certs branch from 7c78bfa to eea8904 Compare March 14, 2017 23:53
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 14, 2017

Codecov Report

Merging #2038 into master will increase coverage by 0.76%.
The diff coverage is 79.5%.

@@            Coverage Diff             @@
##           master    #2038      +/-   ##
==========================================
+ Coverage   53.17%   53.93%   +0.76%     
==========================================
  Files         111      109       -2     
  Lines       19656    19122     -534     
==========================================
- Hits        10452    10314     -138     
+ Misses       7928     7563     -365     
+ Partials     1276     1245      -31

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fff0ebe...5454ec0. Read the comment docs.

@cyli cyli changed the title [ca] Separate RootCA signing cert from root certs WIP: [ca] Separate RootCA signing cert from root certs Mar 15, 2017
@cyli
Copy link
Copy Markdown
Contributor Author

cyli commented Mar 15, 2017

(getting intermittent integration test failures - trying to track those down)

@cyli
Copy link
Copy Markdown
Contributor Author

cyli commented Mar 15, 2017

Update: The test failures do not seem related - https://circleci.com/gh/docker/swarmkit/6536. PTAL cc @diogomonica?

@cyli cyli changed the title WIP: [ca] Separate RootCA signing cert from root certs [ca] Separate RootCA signing cert from root certs Mar 15, 2017
Comment thread ca/certificates.go Outdated
// CanSign ensures that the signer has all the necessary elements needed to operate
func (rca *RootCA) CanSign() bool {
if rca.Cert == nil || rca.Pool == nil || rca.Signer == nil {
if rca.Pool == nil || rca.Signer == nil || rca.Signer.Cert == nil || rca.Signer.Signer == nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it's better to check len(rca.Signer.Cert) == 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks!

@cyli cyli force-pushed the separate-root-certs-from-signing-certs branch from eea8904 to b40e7bd Compare March 16, 2017 01:13
Copy link
Copy Markdown
Contributor

@diogomonica diogomonica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments. LGTM.

Comment thread ca/config.go
defer s.mu.Unlock()

rootCA, err := NewRootCA(cert, key, certExpiry, nil)
signingCert := cert
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on what is happening here?

Comment thread ca/certificates.go Outdated
if err != nil {
return RootCA{}, err
if localSigner != nil && len(parsedIntermediates) > 0 {
// If a signer is provided and there are intermediates, then either the first intermediate would BE the signer CA
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

capital BE?

Comment thread ca/certificates.go
return nil, err
}

signer, err := local.NewSigner(priv, parsedCerts[0], cfsigner.DefaultSigAlgo(priv), SigningPolicy(certExpiry))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should refactor newLocalSigner to also receive priv, cert in this order? (instead of cert, priv)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment thread ca/certificates.go
// If the key was loaded from disk unencrypted, but there is a passphrase set,
// ensure it is encrypted, so it doesn't hit raft in plain-text
// we don't have to check for nil, because if we couldn't pem-decode the bytes, then parsing above would have failed
keyBlock, _ := pem.Decode(keyBytes)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't wait to remove all of this env passphrase stuff. I'm sorry I added it in the first place. :(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all, could have been useful depending on which way we decided to go for secrets encryption.

@cyli cyli force-pushed the separate-root-certs-from-signing-certs branch from b40e7bd to a12d15d Compare March 16, 2017 18:28
@diogomonica
Copy link
Copy Markdown
Contributor

@aaronlehmann review please ✌️

Comment thread ca/certificates.go
case len(keyBytes) > 0 && len(certBytes) > 0: // possibly valid signer
default:
return nil, errors.New("must provide both a signing key and a signing cert, or neither")
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest moving this check out of the call to newLocalSigner, so we can avoid the return nil, nil case. By convention, a function that returns a pointer and an error will generally return a valid pointer if the error is nil. Violating this isn't the end of the world, but changing newLocalSigner to a function that we only call when we expect it to return a LocalSigner or an error would let us stick to this convention.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could move this to a separate function like possibleValidSigner if you expect to have multiple call sites for newLocalSigner in the future.

Comment thread ca/certificates.go Outdated
Intermediates: intermediatePool,
}
if _, err := parsedCerts[0].Verify(opts); err != nil {
return nil, errors.Wrap(err, "error while validating Signing CA Certificate against roots and intermediates")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is Signing CA Certificate capitalized?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was starting to think of it as a proper noun. :) Fixing.

Comment thread ca/config.go Outdated
// If we have no signing key, then we shouldn't pass a signing cert either (because we don't want a local
// signer at all)
signingCert := cert
if key == nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if len(key) == 0

Comment thread ca/certificates.go
if err != nil {
return nil, errors.Wrap(err, "invalid signing CA cert")
}
if err := validateSignatureAlgorithm(parsedCerts[0]); err != nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe ParseCertificatesPEM can return an empty slice if you pass in whitespace only. So a bounds check is necessary here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks

@aaronlehmann
Copy link
Copy Markdown
Collaborator

Non-blocking: I notice there's a pattern of calling CanSign to check if it's safe to use rca.Signer.cryptoSigner and rca.Signer.parsedCert. I wonder if it would be clearer/safer to replace CanSign with some kind of signer accessor that returns rca.Signer if and only if all the conditions for signing are satisfied (or an error if they are not).

@diogomonica
Copy link
Copy Markdown
Contributor

@aaronlehmann there are situation where not being able to sign is fine though.

@aaronlehmann
Copy link
Copy Markdown
Collaborator

Yeah, not saying the error should be bubbled up. Just saying that structuring the check of whether we can sign like this might prevent accidental uses of localSigner when it's not initialized.

… which could

be an intermediate CA.

Signed-off-by: cyli <ying.li@docker.com>
@cyli
Copy link
Copy Markdown
Contributor Author

cyli commented Mar 17, 2017

@aaronlehmann It will probably help prevent panics if we try to refer to RootCA.Signer.Key. Will update - this might mean a bunch of extra test changes in the diff, though.

@cyli cyli force-pushed the separate-root-certs-from-signing-certs branch 3 times, most recently from c22bbe3 to cba18fd Compare March 18, 2017 01:54
…signer is defined.

Signed-off-by: cyli <ying.li@docker.com>
@cyli cyli force-pushed the separate-root-certs-from-signing-certs branch from cba18fd to 5454ec0 Compare March 18, 2017 02:13
@aaronlehmann
Copy link
Copy Markdown
Collaborator

LGTM

@aaronlehmann aaronlehmann merged commit daa6aff into moby:master Mar 18, 2017
@cyli cyli deleted the separate-root-certs-from-signing-certs branch March 20, 2017 17:49
@cyli cyli restored the separate-root-certs-from-signing-certs branch March 20, 2017 22:35
@cyli cyli deleted the separate-root-certs-from-signing-certs branch March 20, 2017 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants