PKCE of OAuth 2.0 extension protocol

preface

Before reading this article, you need to understand the relevant contents of OAuth 2.0 licensing agreement. You can refer to my Last article   OAuth 2.0 adventure [1].

The full name of PKCE is Proof Key for Code Exchange, which was released in 2015. It is an extended protocol of OAuth 2.0 core, so it can be used in combination with existing authorization modes, such as Authorization Code + PKCE, which is also a best practice. PKCE was originally created for mobile device applications and local applications, It is mainly to reduce the authorization code interception attack of public clients.

In the latest OAuth 2.1 specification (Draft), it is recommended that all clients use PKCE, not just public clients, and the Implicit and Password modes are removed. What about clients using these two modes before? Yes, you can try to use the Authorization Code + PKCE authorization mode now. Then why does PKCE have this magic? In fact, its principle is that the client provides a self created certificate to the authorization server. The authorization server uses it to verify the client and issue the access_token to the real client instead of forgery.

Client type

As mentioned above, PKCE is mainly to reduce the authorization code interception attack of public clients, so it is necessary to introduce the next two types of clients.

OAuth 2.0 core specification defines two types of clients, confidential and public. The way to distinguish these two types is to judge whether the client has the ability to maintain its own confidentiality credentials client_secret.

•confidential
For an ordinary web site, although users can access the front-end page, the data comes from the back-end api service of the server. The front-end only obtains the authorization code and exchanges the code for access_ The step of token is completed in the back-end api. Because it is an internal server, the client has the ability to maintain password or key information. This is a confidential client.
•public
The client itself does not have the ability to save key information, such as desktop software, mobile App and single page program (SPA), because these applications are published, in fact, they are not safe. Malicious attackers can view the key of the client through decompilation and other means. This is a public client.

In OAuth 2.0 Authorization Code mode, when the client obtains the access_token from the authorization server through the Authorization Code, it also needs to carry the client_secret in the request, and the authorization server verifies it to ensure access_ The token is issued to the legitimate client. For the public client, there is a risk of key disclosure, so the Authorization Code mode of conventional OAuth 2.0 cannot be used, so the client cannot be used_ The secret scenario derives the Implicit mode, which is unsafe from the beginning. After a period of time, PKCE extension protocol was introduced to solve the authorization security problem of public clients.

Authorization code interception attack

The above is the complete process of OAuth 2.0 authorization code mode. The authorization code interception attack occurs in step C in the figure, that is, when the authorization server returns the authorization code to the client, why is step C unsafe in so many steps? In the OAuth 2.0 core specification, it is required that the antithorize endpoint and token endpoint of the authorization server must be protected by TLS (secure transport layer protocol). However, when the authorization server returns to the callback address of the client with the authorization code, it may not be protected by TLS. Malicious programs can intercept the authorization code in this process. After getting the code, The next step is to exchange the access token access to the authorization server through code_ Token. For confidential clients, request access_ The key client of the client needs to be carried with the token_ Secret, and the key is saved on the back-end server, so it is useless for malicious programs to get the authorization code through interception. For public clients (mobile App, desktop application), they have no ability to protect the client_secret, because you can get the client through decompilation and other means_ Secret, you can exchange the authorization code for access_token. At this stage, malicious applications can request the resource server with the token.

State parameter. In the OAuth 2.0 core protocol, in the step of exchanging code for token, it is recommended to use the state parameter to associate the request and response, which can prevent cross Site Request Forgery CSRF attack, but state can not prevent the above authorization code interception attack, because the request and response are not forged, Instead, the authorization code of the response is intercepted by a malicious program.

PKCE protocol process

PKCE protocol itself is an extension of OAuth 2.0. It is basically the same as the previous authorization code process. The difference is that when requesting the authorize endpoint of the authorization server, additional authorization is required   code_challenge   and   code_challenge_method   Parameter. Additional parameters are required when requesting token endpoint   code_verifier   Finally, the authorization server will compare and verify these three parameters and issue tokens after passing.

code_verifier

For each OAuth authorization request, the client will first create a code verifier code_verifier, which is a random string encrypted with high entropy. It uses URI unreserved characters and ranges   [A-Z] / [a-z] / [0-9] / "-" / ". /" / "/" ~ ", because non reserved characters do not need to be URL encoded during transmission, and_ The minimum length of the verifier is 43 and the maximum is 128, code_ If the verifier has enough entropy, it is difficult to guess.

code_ The extended Bakos normal form (ABNF) of verifier is as follows:

code-verifier = 43*128unreservedunreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"ALPHA = %x41-5A / %x61-7ADIGIT = %x30-39

Simply put, in   [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"   Within the range, a random string of 43-128 bits is generated.

javascript example

// Required: Node.js crypto module// https://nodejs.org/api/crypto.html#crypto_cryptofunction base64URLEncode(str) {    return str.toString('base64')        .replace(/\+/g, '-')        .replace(/\//g, '_')        .replace(/=/g, '');}var verifier = base64URLEncode(crypto.randomBytes(32));

java example

// Required: Apache Commons Codec// https://commons.apache.org/proper/commons-codec/// Import the Base64 class.// import org.apache.commons.codec.binary.Base64;SecureRandom sr = new SecureRandom();byte[] code = new byte[32];sr.nextBytes(code);String verifier = Base64.getUrlEncoder().withoutPadding().encodeToString(code); 

c# example

public static string randomDataBase64url(int length){    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();    byte[] bytes = new byte[length];    rng.GetBytes(bytes);    return base64urlencodeNoPadding(bytes);}public static string base64urlencodeNoPadding(byte[] buffer){    string base64 = Convert.ToBase64String(buffer);    base64 = base64.Replace("+", "-");    base64 = base64.Replace("/", "_");    base64 = base64.Replace("=", "");    return base64;}string code_verifier = randomDataBase64url(32);

code_challenge_method

Right code_ The method that validator converts. This parameter will be passed to the authorization server, and the authorization server will remember this parameter and compare it when issuing tokens,   code_challenge == code_challenge_method(code_verifier)  , If consistent, the token is issued.

code_ challenge_ The method can be set to plain (original value) or S256 (sha256 hash).

code_challenge

Use code_challenge_method to code_ The verifier is converted to get the code_challenge, you can convert it in the following way

•plain
code_challenge = code_verifier
•S256
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

The client should first consider using S256 for conversion. If it is not supported, use plain. At this time, code_challenge and code_ The values of verifier are equal.

javascript example

// Required: Node.js crypto module// https://nodejs.org/api/crypto.html#crypto_cryptofunction sha256(buffer) {    return crypto.createHash('sha256').update(buffer).digest();}var challenge = base64URLEncode(sha256(verifier));

java example

// Dependency: Apache Commons Codec// https://commons.apache.org/proper/commons-codec/// Import the Base64 class.// import org.apache.commons.codec.binary.Base64;byte[] bytes = verifier.getBytes("US-ASCII");MessageDigest md = MessageDigest.getInstance("SHA-256");md.update(bytes, 0, bytes.length);byte[] digest = md.digest();String challenge = Base64.encodeBase64URLSafeString(digest);

C# example

public static string base64urlencodeNoPadding(byte[] buffer){    string base64 = Convert.ToBase64String(buffer);    base64 = base64.Replace("+", "-");    base64 = base64.Replace("/", "_");    base64 = base64.Replace("=", "");    return base64;}string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));

Principle analysis

We mentioned the authorization code interception attack above, which means that in the whole authorization process, you can apply for a token from the authorization server by intercepting the authorization code called back from the authorization server to the client, because the client is public, even if there is a key client_secret is also in vain. After the malicious program gets the access token, it can openly request the resource server.

How does PKCE do it? Since the fixed client_secret is not secure, so a random key (code_verifier) is generated for each request. When the authorization endpoint of the authorization server is requested for the first time, the code is carried_ Challenge and code_challenge_method, that is, code_ Verifier the converted value and conversion method, and then the authorization server needs to cache these two parameters. When the token endpoint is requested for the second time, it carries the original value (code_verifier) of the generated random key, and then the authorization server uses the following method for verification:

•plain
code_challenge = code_verifier
•S256
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
The token is issued only after passing. How should the two requests to the authorization server authorize endpoint and token endpoint be associated? Just pass the authorization code, so even if the malicious program intercepts the authorization code, there is no code_ The verifier cannot obtain the access token. Of course, PKCE can also be used on the confidential client, that is, the client_secret + code_verifier double key.

Finally, let's look at an example of request parameters:

GET /oauth2/authorize https://www.authorization-server.com/oauth2/authorize?response_type=code&client_id=s6BhdRkqt3&scope=user&state=8b815ab1d177f5c8e &redirect_uri=https://www.client.com/callback&code_challenge_method=S256 &code_challenge=FWOeBX6Qw_krhUE2M0lOIH3jcxaZzfs5J4jtai5hOX4

POST /oauth2/token  Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencodedhttps://www.authorization-server.com/oauth2/token?grant_type=authorization_code&code=d8c2afe6ecca004eb4bd7024&redirect_uri=https://www.client.com/callback&code_verifier=2D9RWc5iTdtejle7GTMzQ9Mg15InNmqk3GZL-Hg5Iz0

The following uses Postman to demonstrate the authorization process using PKCE mode

References

https://www.rfc-editor.org/rfc/rfc6749
https://www.rfc-editor.org/rfc/rfc7636.html
https://oauth.net/2/pkce
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-04

Posted by cyclefiend2000 on Tue, 23 Nov 2021 12:38:32 -0800