Skip to content
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

Adding support for chained intermediate CAs in pki backend #1694

Merged
merged 33 commits into from
Sep 28, 2016

Conversation

chrishoffman
Copy link
Contributor

The current pki backend does not have the ability to provide the full trust chain for the certificate authority. The changes that were made were all additive and should maintain backwards compatibility for everyone who does not provide this information to their backend.

This pull request includes a rewrite portions of the PEM parser to support reading, storing, and verifying the entire trust chain. In doing so, the parser is a little more prescriptive about how the PEM bundles should be constructed. Previously, the parser used some assumptions to populate the certificate bundle regardless of order. Now, certificates are required to be provided in order with the leaf certificate first. This follows what the tls.Certificate package does. As a benefit to this change, some of the code relating to incorrect assumptions the parser was making have been removed.

This PR relates to this original thread.

@jefferai jefferai added this to the 0.6.2 milestone Aug 5, 2016
@chrishoffman chrishoffman changed the title Adding support for chained CAs in pki backend Adding support for chained intermediate CAs in pki backend Aug 8, 2016
@jefferai
Copy link
Member

Hi @chrishoffman ,

It doesn't seem to me like this needs to be separate endpoints. Is there any reason we don't simply accept a chain in the normal CA upload endpoint?

@chrishoffman
Copy link
Contributor Author

I think you are right, we don't need the endpoints. It works in both places but I thought it was still required when you generated your private key using the /pki/intermediate/generate endpoint. It looks like you can provide the full certificate chain when uploading the signed cert with /pki/intermediate/set-signed or providing the full cert with key with /pki/config/ca.

I'll remove the /pki/config/chain endpoints.

@jefferai
Copy link
Member

It looks like you can provide the full certificate chain when uploading the signed cert with /pki/intermediate/set-signed or providing the full cert with key with /pki/config/ca.

I think it doesn't now, but ought to be :-D Hence the point of this PR.

@chrishoffman
Copy link
Contributor Author

The endpoints are out. I need add some documentation to the two upload endpoints but will wait to see if there are any major changes that need to be made.

func fetchCAInfo(req *logical.Request) (*caInfoBundle, error) {
caBundle, err := fetchCABundle(req)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch CA bundle: %v", err)}
Copy link
Member

Choose a reason for hiding this comment

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

Just pass this through; the error might be Internal or User, and we don't want to lose that. The error message here isn't adding much.

Copy link
Member

Choose a reason for hiding this comment

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

Ping^ :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was waiting for more feedback since this was a simple one :), I knew there would be more.

@jefferai jefferai self-assigned this Sep 13, 2016
result.IssuingCA = creationInfo.SigningBundle.Certificate
certPath := creationInfo.SigningBundle.GetCertificatePath()
result.IssuingCABytes = certPath[0].Bytes
result.IssuingCA = certPath[0].Certificate
Copy link
Member

Choose a reason for hiding this comment

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

For safety, can you ensure that the length of certPath is at least one here? Better to return an error than panic.

@jefferai
Copy link
Member

Overall this is looking pretty good. Most of my comments are cleanup, a few potential behavioral changes.

@chrishoffman
Copy link
Contributor Author

I updated the DER format logic to return the CA chain certificate when there is only one certificate in the chain. If we decide to keep the issuing CA out of the chain, this will still add one level to the DER format and unlimited for PEM.

@jefferai What do you think about the prepending the issuing CA to the chain? I'm not sure what the expectation should be.

@jefferai
Copy link
Member

Weeeeeeeeeeeeeeeel...

Before Apache gained support for just reading appended PEM files containing the full bundle, it had SSLCertificateChainFile, which:

This directive sets the optional all-in-one file where you can assemble the certificates of Certification Authorities (CA) which form the certificate chain of the server certificate. This starts with the issuing CA certificate of the server certificate and can range up to the root CA certificate. Such a file is simply the concatenation of the various PEM-encoded CA Certificate files, usually in certificate chain order.

Of course, that's deprecated.

Then you have things like the Java keystore which form chains internally based on key relationships but you only add in one cert at a time.

Generally, non-Java services these days seem to trend towards full chains specified in the file, starting with the actual server cert (and maybe with the key elsewhere). Although if you have the root CA in the file, Qualsys will ding you.

I think, maybe, the right thing to do is the following. This would work across formats, with pem_bundle modifying certificate in the obvious way (cert + full chain). Note that this doesn't necessarily need to correspond to the internal representation; this is my suggestion for what is output across the wire.

certificate: The actual certificate
issuing_ca: The issuing CA
ca_chain: An array of CA certs, including the issuing CA

Having it be an array makes it harder to parse on the command line, but still very easy to deal with via the native JSON API; makes it simple to reason about what to do in the DER case; and anyone not wanting the immediate CA can just start indexing at 1 instead of 0.

And, I think the 99% case generally will be satisfied by certificate with the pem_bundle output format anyways.

I'm totally open for debate here, let me know what you think.

@chrishoffman
Copy link
Contributor Author

Let's go with your suggestion. I can't disagree with anything you said and also believe that the pem_bundle is the majority use case. I'll try and finish this up today.

@chrishoffman
Copy link
Contributor Author

I feel good about the current state and now that the major changes have stabilized, I may look at adding a few more tests. Let's let it burn in for a few days.

@jefferai
Copy link
Member

Sounds good. We're looking to close 0.6.2 soonish so I'll give a ping in a few days.

@stepanstipl
Copy link

Hi, this looks great, thanks, pretty much exactly what I was missing in Vault.

I have one suggestion:

  • add option to retrieve ca certificate with a complete chain - /ca(/pem) endpoints, possibly add smth. like ?chain=true

@jefferai
Copy link
Member

jefferai commented Sep 23, 2016 via email

@chrishoffman
Copy link
Contributor Author

This also probably only makes sense as a concatenated pem bundle. We can't really support the chain with der certs due to the limitation of the format.

@jefferai Should we try and get this in for 0.6.2? How do you like /ca_chain or /chain? Should I just ignore the der format and just return pem from the base path?

I did review the tests and we seem reasonably covered. I may add a few minor tweaks.

@stepanstipl
Copy link

@jefferai Ah, I was just looking at /pki/certs/?list=true endpoint, but special endpoint works equally well of course (what about /ca/chain in that case?).

@jefferai
Copy link
Member

@chrishoffman If you can get it in for 0.6.2 that'd be neat! I think ca_chain for explicitness sounds good, and yeah, I think ignore the DER format.

@jefferai
Copy link
Member

@stepanstipl That's handled waaaay up in the chain; it's that way because we weren't sure if all software would be happy with using the HTTP LIST verb :-)

But down at the level of backend endpoints GET calls don't have parameters.

@chrishoffman
Copy link
Contributor Author

I settled on /ca/chain for now and is ready to be reviewed.

logical.ReadOperation: b.pathFetchReadCAChain,
},

HelpSynopsis: pathFetchHelpSyn,
Copy link
Member

Choose a reason for hiding this comment

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

The text for this probably needs some changes to accommodate the new info.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I need to add the ca_chain to a few other documents too.

errutil.InternalError{Err: fmt.Sprintf("Error fetching CA certificate: %s", err)}
}

caChain := caInfo.GetCAChain()
Copy link
Member

Choose a reason for hiding this comment

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

Should this return nil if there is no certs? Then you can easily fast-path that with a nil, nil response which will 204.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It may make sense. This is only used in two places right now and should be pretty easy to transition to this way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Returning nil here bypasses the Response where we want to provide raw output (this is currently a raw endpoint). When I returned nil here, it fast-path to a default response of 404 with an empty error array. I think we should just keep it the way it is.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, you're right -- a GET will return a 404 on no data returned, unlike a POST or DELETE.

Keeping the way it is sounds fine but if there's no data we should return 204, not 200.

certs = append(certs, pem.EncodeToMemory(&block)...)
}

response := &logical.Response{
Copy link
Member

Choose a reason for hiding this comment

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

This tweaks me a little in a consistency sense.

ca returns a Vault response; ca/pem returns a raw response.
crl returns a Vault response; crl/pem returns a raw response.

It feels like ca/chain should return a Vault response and ca/chain/pem should return a raw response. Although in that scenario it's probably better to just use ca_chain and ca_chain/pem.

The reason for having both is so that the Vault CLI (and API clients, generally) can do a read against one of the endpoints and understand how to parse it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both ca and ca/pam return a raw response, ca being in DER format. I think you were thinking about cert/ca which returns a Vault response. Same for crl. The reason I didn't store the ca_chain by a special serial in the cert store was because it expects the bytes of a single certificate so there was no easy way to store the slice of []byte. I also can't support the DER format since it could be multiple certificates and that is why I left the /pem off the end.

You do bring up a valid point about API clients, which I didn't fully consider. I'll think about the best way to support the cert/ca_chain endpoint which would be more suitable for API clients.

Copy link
Member

Choose a reason for hiding this comment

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

I think you were thinking about cert/ca which returns a Vault response.
I was! Mea culpa. So this seems fine as-is, but it would be nice to support cert/ca_chain. It could just build up the chain rather than attempt to fetch one from the backend, unless this is difficult for a reason I'm not thinking of right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was able to merge the fetching of the ca chain into the standard fetch method. It is a little different in how this is generated vs. the rest of the certs but I think it is ok.

Copy link
Member

Choose a reason for hiding this comment

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

Neet-o!

@jefferai
Copy link
Member

I know I'm requesting more, but this is close...I promise!

@jefferai
Copy link
Member

Looking pretty good to me. Want to give it a couple of days to bake in your mind, or merge?

@chrishoffman
Copy link
Contributor Author

I had a thought about the cert/ca_chain endpoint. Do you think we should be returning this as a list of certificates under ca_chain and maybe the pem bundle in certificate? This would provide parity with what is returned when issuing/signing a certificate. It may even make sense to return the chain on the cert/ca endpoint only and not provide a separate cert/ca_bundle endpoint. What do you think?

The raw endpoints can stay as they are.

@chrishoffman
Copy link
Contributor Author

...and maybe we should return the chain for all certificates.

@jefferai
Copy link
Member

I think this should be kept simple for now. Fetching is not generally (so far as I know) a common operation, it's mostly a nicety. The CA chain has the full chain, including the CA cert set in the backend, so it's really simple to just make two queries if you really need to -- and chances are if you already have the serial of the certificate, given that certificates are public, you only need one or the other.

@jefferai
Copy link
Member

OK. Let me know when you want me to pull the trigger!

@chrishoffman
Copy link
Contributor Author

I think it is ready to go.

@jefferai jefferai merged commit 10c8024 into hashicorp:master Sep 28, 2016
@jefferai
Copy link
Member

💥

@chrishoffman
Copy link
Contributor Author

Woohoo!

@jefferai
Copy link
Member

Great job and many thanks! It is also now officially in the changelog as a FEATURE of 0.6.2.

@chrishoffman
Copy link
Contributor Author

chrishoffman commented Sep 28, 2016

Thanks! There may be a small tweak that is needed for the changelog. I've built full chains using internally generated certificates since you can set-signed without the key. Here is a gist that shows building a 4-level CA chain. https://gist.github.com/chrishoffman/acc60cf577e1e79f56beb63747466d3c

@jefferai
Copy link
Member

Oh neato, so set-signed also handles chains? I didn't realize that. Will adjust!

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