summaryrefslogtreecommitdiffstats
path: root/src/oauth/qoauth2authorizationcodeflow.cpp
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@qt.io>2024-04-24 09:00:11 +0300
committerJuha Vuolle <juha.vuolle@qt.io>2024-05-22 14:30:41 +0300
commit682335147e5a5ee58df41c18d2e0176aea6580fe (patch)
tree16c7ab4cf12dd20354090e86d532499cba1ce3d8 /src/oauth/qoauth2authorizationcodeflow.cpp
parenta2e22926a568ceb0c11c539913b7d8e188d8b198 (diff)
Add support for PKCE
PKCE (RFC 7636) is a MUST in RFC 8252 which lays out OAuth2 best practices for native applications. PKCE mitigates the risk of authorization code intercepting. PKCE is only relevant for OAuth2 "Authorization Code" flow, which is the only OAuth2 flow Qt supports out of the box. The S256 method is set as the default. Sending out the PKCE parameters should not cause harm even if the authorization server didn't support them. [ChangeLog][QOAuth2AuthorizationCodeFlow] Added PKCE support and turned it on by default Fixes: QTBUG-124327 Change-Id: Ic0242be1b8afcd9baa3ff071989d58ddabf753a2 Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Diffstat (limited to 'src/oauth/qoauth2authorizationcodeflow.cpp')
-rw-r--r--src/oauth/qoauth2authorizationcodeflow.cpp117
1 files changed, 117 insertions, 0 deletions
diff --git a/src/oauth/qoauth2authorizationcodeflow.cpp b/src/oauth/qoauth2authorizationcodeflow.cpp
index 76d54a2..23f9f80 100644
--- a/src/oauth/qoauth2authorizationcodeflow.cpp
+++ b/src/oauth/qoauth2authorizationcodeflow.cpp
@@ -15,6 +15,8 @@
#include <qauthenticator.h>
#include <qoauthhttpserverreplyhandler.h>
+#include <QtCore/qcryptographichash.h>
+
#include <functional>
QT_BEGIN_NAMESPACE
@@ -182,6 +184,44 @@ void QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate(QNetworkReply *reply,
}
}
+/*
+ Creates and returns a new PKCE 'code_challenge', and stores the
+ underlying 'code_verifier' that was used to compute it.
+
+ The PKCE flow involves two parts:
+ 1. Authorization request: include the 'code_challenge' which
+ is computed from the 'code_verifier'.
+ 2. Access token request: include the original 'code_verifier'.
+
+ With these two parts the authorization server is able to verify
+ that the token request came from same entity as the original
+ authorization request, mitigating the risk of authorization code
+ interception attacks.
+*/
+QByteArray QOAuth2AuthorizationCodeFlowPrivate::createPKCEChallenge()
+{
+ switch (pkceMethod) {
+ case QOAuth2AuthorizationCodeFlow::PkceMethod::None:
+ pkceCodeVerifier.clear();
+ return {};
+ case QOAuth2AuthorizationCodeFlow::PkceMethod::Plain:
+ // RFC 7636 4.2, plain
+ // code_challenge = code_verifier
+ pkceCodeVerifier = generateRandomString(pkceVerifierLength);
+ return pkceCodeVerifier;
+ case QOAuth2AuthorizationCodeFlow::PkceMethod::S256:
+ // RFC 7636 4.2, S256
+ // code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
+ pkceCodeVerifier = generateRandomString(pkceVerifierLength);
+ // RFC 7636 3. Terminology:
+ // "with all trailing '=' characters omitted"
+ return QCryptographicHash::hash(pkceCodeVerifier, QCryptographicHash::Algorithm::Sha256)
+ .toBase64(QByteArray::Base64Option::Base64UrlEncoding
+ | QByteArray::Base64Option::OmitTrailingEquals);
+ };
+ Q_UNREACHABLE_RETURN({});
+}
+
/*!
Constructs a QOAuth2AuthorizationCodeFlow object with parent
object \a parent.
@@ -277,6 +317,75 @@ void QOAuth2AuthorizationCodeFlow::setAccessTokenUrl(const QUrl &accessTokenUrl)
}
/*!
+ \enum QOAuth2AuthorizationCodeFlow::PkceMethod
+ \since 6.8
+
+ List of available \l {https://datatracker.ietf.org/doc/html/rfc7636}
+ {Proof Key for Code Exchange (PKCE) methods}.
+
+ PKCE is a security measure to mitigate the risk of \l
+ {https://datatracker.ietf.org/doc/html/rfc7636#section-1}{authorization
+ code interception attacks}. As such it is relevant for OAuth2
+ "Authorization Code" flow (grant) and in particular with
+ native applications.
+
+ PKCE inserts additional parameters into authorization
+ and access token requests. With the help of these parameters the
+ authorization server is able to verify that an access token request
+ originates from the same entity that issued the authorization
+ request.
+
+ \value None PKCE is not used.
+ \value Plain The Plain PKCE method is used. Use this only if it is not
+ possible to use S256. With Plain method the
+ \l {https://datatracker.ietf.org/doc/html/rfc7636#section-4.2}{code challenge}
+ equals to the
+ \l {https://datatracker.ietf.org/doc/html/rfc7636#section-4.1}{code verifier}.
+ \value S256 The S256 PKCE method is used. This is the default and the
+ recommended method for native applications. With the S256 method
+ the \e {code challenge} is a base64url-encoded value of the
+ SHA-256 of the \e {code verifier}.
+
+ \sa setPkceMethod(), pkceMethod()
+*/
+
+/*!
+ \since 6.8
+
+ Sets the current PKCE method to \a method.
+
+ Optionally, the \a length parameter can be used to set the length
+ of the \c code_verifier. The value must be between 43 and 128 bytes.
+ The 'code verifier' itself is random-generated by the library.
+
+ \sa pkceMethod(), QOAuth2AuthorizationCodeFlow::PkceMethod
+*/
+void QOAuth2AuthorizationCodeFlow::setPkceMethod(PkceMethod method, quint8 length)
+{
+ Q_D(QOAuth2AuthorizationCodeFlow);
+ if (length < 43 || length > 128) {
+ // RFC 7636 Section 4.1, the code_verifer should be 43..128 bytes
+ qWarning("Invalid PKCE length provided, must be between 43..128. Ignoring.");
+ return;
+ }
+ d->pkceVerifierLength = length;
+ d->pkceMethod = method;
+}
+
+/*!
+ \since 6.8
+
+ Returns the current PKCE method.
+
+ \sa setPkceMethod(), QOAuth2AuthorizationCodeFlow::PkceMethod
+*/
+QOAuth2AuthorizationCodeFlow::PkceMethod QOAuth2AuthorizationCodeFlow::pkceMethod() const noexcept
+{
+ Q_D(const QOAuth2AuthorizationCodeFlow);
+ return d->pkceMethod;
+}
+
+/*!
Starts the authentication flow as described in
\l {https://tools.ietf.org/html/rfc6749#section-4.1}{The OAuth
2.0 Authorization Framework}
@@ -387,6 +496,11 @@ QUrl QOAuth2AuthorizationCodeFlow::buildAuthenticateUrl(const QMultiMap<QString,
p.insert(Key::redirectUri, callback());
p.insert(Key::scope, d->scope);
p.insert(Key::state, state);
+ if (d->pkceMethod != PkceMethod::None) {
+ p.insert(Key::codeChallenge, d->createPKCEChallenge());
+ p.insert(Key::codeChallengeMethod,
+ d->pkceMethod == PkceMethod::Plain ? u"plain"_s : u"S256"_s);
+ }
if (d->modifyParametersFunction)
d->modifyParametersFunction(Stage::RequestingAuthorization, &p);
url.setQuery(d->createQuery(p));
@@ -423,6 +537,9 @@ void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code)
parameters.insert(Key::redirectUri, QUrl::toPercentEncoding(callback()));
parameters.insert(Key::clientIdentifier, QUrl::toPercentEncoding(d->clientIdentifier));
+
+ if (d->pkceMethod != PkceMethod::None)
+ parameters.insert(Key::codeVerifier, d->pkceCodeVerifier);
if (!d->clientIdentifierSharedKey.isEmpty())
parameters.insert(Key::clientSharedSecret, d->clientIdentifierSharedKey);
if (d->modifyParametersFunction)