In a "normal" SSL (without pinning), the Handshake (the "initial conversation" that the client and the server do to connect) has several stages, and in one of them the server sends to the client its digital certificate.
The customer then checks if this certificate is reliable. For this, he sees if the certificate was issued (signed) by some certifying authority on which he trusts.
Browsers already come with a pre-installed list of trusted certification authorities (which you can manage in the settings by adding or removing certificates, for example). If the server certificate was signed by one of these authorities, it also checks if the certificate domain matches the site URL, and if all is right, the certificate is considered OK (for more information: https://stackoverflow.com/q/188266).
Other applications often have this list of trusted certificates in some configuration file (trust store, cacerts, each one calls a way).
As a curiosity (just to cite an example), the Sopt certificate (which you can see by clicking on the browser lock) is like this (I opened in Windows Chrome, the interface may vary):
It was issued to the stackexchange.com domain but it is possible to place more than one URL:
Note that the *.stackoverflow.com domain is part of the certificate, so the browser considers it to match the en.stackoverflow.com URL and is therefore valid.
And we can also see the jail:
That is, it was issued by Let’s Encrypt (which is also called the "Intermediate Authority"), which in turn was issued by the DST Root CA X3 (the "Root Authority", because above it there is no one). And both are in the list of trusted certificates that you can see in the browser settings:
Yeah, but what about pinning?
pinning SSL (or HPKP - HTTP Public Key Pinning) can be seen either as an "additional customer check", or as an bypass on the server check.
It’s basically a customer-side check. Here’s an example with code in Kotlin, I’ll put it here in a more summarized form:
val pinnedCertificate = // certificado que quero "pinnar"
val url = URL("https://www.google.com")
val httpsUrlConnection = url.openConnection() as HttpsURLConnection
httpsUrlConnection.connect()
// se o certificado do servidor é o mesmo que eu "pinnei"
if (httpsUrlConnection.serverCertificates.contains(pinnedCertificate)) {
// usa a conexão normalmente
Log.d("Pinning", "Server certificates validation successful")
} else {
Log.d("Pinning", "Server certificates validation failed")
throw SSLException("Server certificates validation failed for google.com")
}
That is, in addition to SSL Handshake that is already done normally when accessing an HTTPS site, the client still does another additional check on the server certificate.
So even if the connection is under attack man in the Middle, in which the attacker generated a false certificate (as explained in another answer), this fake certificate will not match what the customer is checking.
It is also worth remembering that this technique can be applied with only public keys instead of the whole certificate (to be more precise, what is "pinned" in this case is the public key hash). That would leave things a little "less hardcoded", because certificates can be renewed using the same key pair (if that’s good or bad, is another story).
In the case of browsers, this list hardcoded of public keys was sent in an HTTP header, along with an expiration date. Thus, the customer should use this list as long as the term is valid - the process is described here:
The server communicates the HPKP policy to the user agent via an HTTP Response header field named Public-Key-Pins (or Public-Key-Pins-Report-Only for Reporting-only purposes).*
The HPKP policy specifies hashes of the Subject public key info of one of the Certificates in the website’s authentic X.509 public key Certificate chain (and at least one backup key) in pin-sha256 Directives, and a period of time During which the user agent Shall enforce public key pinning in max-age Directive, optional includeSubDomains Directive to include all subdomains (of the Domain that sent the header) in pinning policy and optional report-Uri Directive with URL Where to send pinning Violation Reports. At least one of the public Keys of the Certificates in the Certificate chain needs to match a Pinned public key in order for the chain to be considered Valid by the user agent.
Detail that in this list could have only the hash of the Certification Authority, or intermediaries, or the server certificate. In the case of the Stack Exchange certificate, for example, the list could have only the Let’s Encrypt certificate key, or only the DST Root CA X3, or only the site itself (By placing DST Root, I am saying that any certificate issued by it would be valid, which would be less restrictive than placing only the server’s own certificate).
Of course, you can imagine the problems of having a list hardcoded on the client. Each time the domain being accessed changes certificate, you will have to update this list in the client (in case this verification is via code).
And in case the server sent the list in an HTTP header, it would be enough for someone to hack the server and make it "pin" their own fake certificates, and the clients would only accept these.
There is a more detailed discussion on the subject here.
Anyway, HPKP is obsolete and has been removed from browsers, which are currently - I don’t know if all - using CT - Certificate Transparency - which in turn, according to the OWASP, will be obsolete from 2021, when everyone is expected to use Signed Certificate Timestamps (Scts).