Closed commonsguy closed 4 years ago
Does this help?
@Test public void upgradeCases() throws Exception {
// Create a couple CAs.
HeldCertificate redCaCert = new HeldCertificate.Builder()
.serialNumber("100")
.build();
HeldCertificate blueCaCert = new HeldCertificate.Builder()
.serialNumber("200")
.build();
// Create a private cert and get the red CA to sign it.
HeldCertificate myCertIssuedByRed = new HeldCertificate.Builder()
.serialNumber("300")
.issuedBy(redCaCert)
.build();
CertificatePinner pinner = new CertificatePinner.Builder()
.add("example.com", CertificatePinner.pin(myCertIssuedByRed.certificate))
.add("example.com", CertificatePinner.pin(redCaCert.certificate))
.build();
// Confirm this all checks out.
pinner.check("example.com",
Arrays.<Certificate>asList(myCertIssuedByRed.certificate, redCaCert.certificate));
// What if the red CA is loses trust by the browsers?
// We can get the blue CA to sign our old certificate.
HeldCertificate myCertIssuedByBlue = new HeldCertificate.Builder()
.keyPair(myCertIssuedByRed.keyPair)
.serialNumber("400")
.issuedBy(blueCaCert)
.build();
pinner.check("example.com",
Arrays.<Certificate>asList(myCertIssuedByBlue.certificate, blueCaCert.certificate));
// What if our cert is compromised? We can get a new cert signed by our old CA.
HeldCertificate newCertIssuedByRed = new HeldCertificate.Builder()
.serialNumber("500")
.issuedBy(redCaCert)
.build();
pinner.check("example.com",
Arrays.<Certificate>asList(newCertIssuedByRed.certificate, redCaCert.certificate));
}
This issue is with respect to improving the documentation for CertificatePinner
. I am sorry, but I do not see how that code snippet will help.
It’s just an example of why you’d want to use multiple pins.
Just to be clear: by documentation, I am referring to this page, this page, or perhaps a new page. My interpretation of your comments is that you are proposing adding this block of code, sans explanation, to one of those pages.
However, HeldCertificate
is not part the public API, and so few people reading the documentation will have heard of it. Also, I am not certain that many people will recognize your middle scenario ("We can get the blue CA to sign our old certificate").
As a test case, your block of code is great! As documentation, on its own, I do not believe that it will help much. But, that's just me.
The code above is intended to convince you that pinning all certs in the chain is useful in practice.
There is nothing wrong with using your sample pin configuration, so long as the developer realizes what is going on here and understands that it means that fraudulent certs issued by the CA will bypass the pin. I doubt that developers reading the documentation will realize that this is the behavior, that's all.
Got it. My argument is that more pins aren’t superfluous as you suggest, but functional because they support more kinds of forward migrations.
I think we should expand the https://github.com/square/okhttp/wiki/HTTPS documentation to show what an example client setup for using certificate pinning with regular certificate rotation would look like. Maybe a page for here's how you can deploy a client pinning against Let's Encrypt certificates that rotate every 3 months.
Thanks for your awesome work on Okhttp! It is one of the most used library in our teams.
I think this issue should be discussed some more. It is my understanding that the recommended approach on de docs is not a good security advice but I may be wrong and would like to revive this issue because of that.
By pinning all the certificates printed in logcat we are pinning the whole certificate chain. Since CertificatePinner
checks if any cert on the chain is pinned, that makes any certificate issued by the same root as valid on your configuration.
Say my cert chain is:
And I pin them all. With that, any other chain like:
Will ALSO pass validation since I've pinned the root. Is that correct? If that is the case, then I agree the docs are misleading to say the least and wouldn't recommend this as security best practice.
Hopefully this can be peer reviewed by other here too. Thanks
Suppose your device trusts 300 root certificates. The practical benefit of certificate pinning is to defend against attacks by the 299 root certificates that your device trusts.
If the root you’ve chosen to sign your certificate is not trustworthy, you should choose another root. Otherwise users with browsers and other user agents that don’t pin are vulnerable.
The reason I recommend pinning the root certificate is that it allows you to gracefully recover from a lost private key on your own certificate. Your in-the-wild devices will trust a new certificate that you get issued as long as you use the same CA.
If the root you’ve chosen to sign your certificate is not trustworthy, you should choose another root.
I suspect that few developers have the ability to determine whether a particular root certificate authority is or is not trustworthy. Also, "trustworthy" itself is somewhat nebulous. For example, if the attacker is able to get a fraudulent certificate via apparently legitimate means (e.g., law enforcement requests), is that certificate authority now somehow not trustworthy?
IMHO, a better framing is in terms of the abilities of an attacker:
If an attacker can obtain a fraudulent certificate, but only from an authority of their choosing (e.g., one where they have successfully bribed the right people), then pinning the root certificate of your CA may be acceptable. It comes down to the number of potential attackers who might try pulling off this sort of attack.
If the attacker can obtain a fraudulent certificate from a wide range of authorities ("nation-state level adversary"), pinning at the root CA level will be less effective.
Or, in matrix form:
Weak Attackers | Strong Attackers | |
---|---|---|
Few Attackers | pin the root certificate | pin the site certificate |
Many Attackers | pin the site certificate | abandon hope |
Most apps should in the few/weak attackers quadrant, so pinning the root certificate should be fine (exemplary, even). For any individual project, it's a matter of coming up with your "threat vectors" and deciding which quadrant you are in, and whether you are in position to deal with the challenges of pinning the site certificate.
From what I understand, and again, that might be wrong, all you need to have a certificate that passes pin validation, as is now recommended, is any certificate from the same root which is relatively easy to achieve. You just buy a new certificate from the same issuer and then you can bypass pinning. As you mentioned, there are 300 or some root certificates that are embedded in Android. It is feasible to buy one from each of them but even then that is not necessary. Most apps connect to known apis. You just check which root you need by looking at the api endpoint and find how to get a new certificate from the same root.
You don't even need to buy a level 2 certificate. You can buy a very cheap one from some intermediate. It just needs to: 1. Be a valid certificate (that is why I'm saying to buy one) and 2. Be from the same root.
I understand that narrowing it down from 300 roots to one is better than nothing but I still think that to recommend this in the docs is not recommending a good practice IMHO but I might be wrong. I'm no expert.
Some way to solve this issue would be to let us pass an implementation of check, or have an option to change the algorithm applied to one that would mandate validating all instead of any certificate in the chain.
Em sex, 20 de set de 2019 20:33, Mark Murphy notifications@github.com escreveu:
If the root you’ve chosen to sign your certificate is not trustworthy, you should choose another root.
I suspect that few developers have the ability to determine whether a particular root certificate authority is or is not trustworthy. Also, "trustworthy" itself is somewhat nebulous. For example, if the attacker is able to get a fraudulent certificate via apparently legitimate means (e.g., law enforcement requests), is that certificate authority now somehow not trustworthy?
IMHO, a better framing is in terms of the abilities of an attacker:
-
If an attacker can obtain a fraudulent certificate, but only from an authority of their choosing (e.g., one where they have successfully bribed the right people), then pinning the root certificate of your CA may be acceptable. It comes down to the number of potential attackers who might try pulling off this sort of attack.
If the attacker can obtain a fraudulent certificate from a wide range of authorities ("nation-state level adversary"), pinning at the root CA level will be less effective.
Or, in matrix form: Weak Attackers Strong Attackers Few Attackers pin the root certificate pin the site certificate Many Attackers pin the site certificate abandon hope
Most apps should in the few/weak attackers quadrant, so pinning the root certificate should be fine (exemplary, even). For any individual project, it's a matter of coming up with your "threat vectors" and deciding which quadrant you are in, and whether you are in position to deal with the challenges of pinning the site certificate.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/square/okhttp/issues/3471?email_source=notifications&email_token=AAC34QSO2BQLDPHLNGG44L3QKVMVHA5CNFSM4DTZFKR2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7IETEI#issuecomment-533744017, or mute the thread https://github.com/notifications/unsubscribe-auth/AAC34QQP4E5HMU632PBF5TLQKVMVHANCNFSM4DTZFKRQ .
I stand by defending against the dangers of bricking an app due to a compromised private key. This isn't academic; Heartbleed compromised many private keys.
The advice offered is intended to help busy software developers improve the security of their software. Anyone who is sufficiently motivated is going to have a security and operations team setting policy and those people can make appropriate security vs. operations trade-offs.
@victorolinasc the attacker needs a certificate signed by the same CA, but also that has a SubjectAltName of the victim’s domain. Root CAs don't typically sign certificates without proof of domain ownership.
We should probably focus on concise specific advise for small companies without security teams. And assume the Squares and Twitters of the world have their well informed security teams with informed policies, just support them via the APIs.
The bigger risk for the former is only pinning against something temporary or revokable. Hence the OR of multiple pins.
Regarding which root CAs to trust. You do that when you choose who to pay for certs from. And should have more than 1.
Going to close as won't fix. This topic isn't one size fits all, you should understand what you are doing enough that you don't need to follow along or worse copy and paste from a open source website.
I came here to write up this documentation, then realised that there are lots of external blogs on best practices. We don't need one more.
@swankjesse re-open if you disagree
My in progress draft
Discuss your security needs/requirements with your Security Team. For any large companies, especially those with external user data, the policies provided by your security team should define what you should be doing to secure clients.
val hostname = "publicobject.com";
val certificatePinner = CertificatePinner.Builder()
.add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
val request = Request.Builder()
.url("https://" + hostname)
.build();
client.newCall(request).execute();
The JavaDocs for
CertificatePinner
recommends creating pins for every certificate in the chain, as reported by the "Peer certificate chain" output in LogCat when a pin fails. So, at the moment, the docs have:Near as I can tell,
CertificatePinner
implements a logical OR when there are multiple pins set for any given hostname. So long as any certificate in the chain matches a pin for that hostname, the certificate is accepted.Hence, from a security standpoint, the code in the question is the same as:
or even:
The recommended advice is not pinning on the specific certificate for that server, but rather for every certificate based on the root CA (in this case, AddTrust's), since you're setting up a pin on that root CA certificate. There is nothing wrong with pinning on the root CA, but the code's superfluous pins makes it appear that you are pinning on the server's certificate, not the root CA's.
You might want to modify the docs to:
add()
is logically OR'd with other pins for that hostnamesha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=
)sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=
) and noting the effective differenceadd()
calls (e.g., for migrating to a new certificate)If I am misunderstanding the nature of
add()
andCertificatePinner
, I sincerely apologize.