22#include <winpr/string.h>
23#include <winpr/crypto.h>
24#include <winpr/print.h>
25#include <freerdp/crypto/crypto.h>
27#include <freerdp/log.h>
29#define TAG CLIENT_TAG("common.oauth2")
38struct rdp_client_oauth2
41 const char* pkcsChallengeMethod;
42 enum pkcs_state pkcsstate;
47 size_t code_verifier_len;
49 size_t code_challenge_len;
52WINPR_ATTR_MALLOC(free, 1)
53static
char* rfc7636_generate_code_verifier(
size_t* plen)
58 BYTE random[32] = WINPR_C_ARRAY_INIT;
59 if (winpr_RAND(random,
sizeof(random)) < 0)
61 char* str = crypto_base64url_encode_len(random,
sizeof(random), plen);
67WINPR_ATTR_MALLOC(free, 1)
68static
char* rfc7636_generate_code_challenge(const
char* method, const
char* code_verifier,
69 size_t len,
size_t* plen)
74 if (!code_verifier || (len == 0) || !method)
77 if (strcmp(
"plain", method) == 0)
79 char* str = strndup(code_verifier, len);
86 if (strcmp(
"S256", method) == 0)
88 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
89 if (!winpr_Digest(WINPR_MD_SHA256, code_verifier, len, hash,
sizeof(hash)))
91 char* str = crypto_base64url_encode_len(hash,
sizeof(hash), plen);
100WINPR_ATTR_MALLOC(free, 1)
101static
char* rfc6749_generate_state(
size_t* plen)
105 BYTE random[32] = WINPR_C_ARRAY_INIT;
106 if (winpr_RAND(random,
sizeof(random)) < 0)
108 char* str = winpr_BinToHexString(random,
sizeof(random), FALSE);
114static void oauth2_free(rdpClientOAuth2* oauth2)
119 oauth2->valid =
false;
121 oauth2->state_len = 0;
123 oauth2->state =
nullptr;
125 oauth2->code_verifier_len = 0;
126 free(oauth2->code_verifier);
127 oauth2->code_verifier =
nullptr;
129 oauth2->code_challenge_len = 0;
130 free(oauth2->code_challenge);
131 oauth2->code_challenge =
nullptr;
134void freerdp_oauth2_free(rdpClientOAuth2* oauth2)
140rdpClientOAuth2* freerdp_oauth2_new(
void)
142 rdpClientOAuth2* oauth2 = calloc(1,
sizeof(rdpClientOAuth2));
145 oauth2->log = WLog_Get(TAG);
146 oauth2->pkcsChallengeMethod =
"S256";
147 WINPR_ASSERT(oauth2->log);
152BOOL freerdp_oauth2_reset(rdpClientOAuth2* oauth2)
154 WINPR_ASSERT(oauth2);
157 oauth2->state = rfc6749_generate_state(&oauth2->state_len);
158 if (!oauth2->state || (oauth2->state_len == 0))
161 oauth2->code_verifier = rfc7636_generate_code_verifier(&oauth2->code_verifier_len);
162 if (!oauth2->code_verifier || (oauth2->code_verifier_len == 0))
164 oauth2->code_challenge =
165 rfc7636_generate_code_challenge(oauth2->pkcsChallengeMethod, oauth2->code_verifier,
166 oauth2->code_verifier_len, &oauth2->code_challenge_len);
167 if (!oauth2->code_challenge || (oauth2->code_challenge_len == 0))
170 oauth2->pkcsstate = INITIAL_CHALLENGE;
174BOOL freerdp_oauth2_check_return_valid(rdpClientOAuth2* oauth2,
const char* response,
size_t len)
176 WINPR_ASSERT(oauth2);
179 WLog_Print(oauth2->log, WLOG_WARN,
180 "No OAuth2 request generated, but we have a response. "
181 "Discarding response.");
186 if (oauth2->state && (oauth2->state_len > 0))
188 WLog_Print(oauth2->log, WLOG_DEBUG,
189 "OAuth2 state parameter used in request, checking response for mirrored value");
191 const char* state = winpr_strnstr(response,
"state=", len);
194 WLog_Print(oauth2->log, WLOG_WARN,
195 "OAuth2 state parameter used in request, but missing in response. "
196 "Discarding response.");
199 if (strncmp(oauth2->state, &state[6], oauth2->state_len) != 0)
201 WLog_Print(oauth2->log, WLOG_WARN,
202 "OAuth2 state parameter used in request, but does not match parameter value "
203 "in response. Discarding response.");
208 WLog_Print(oauth2->log, WLOG_DEBUG,
209 "OAuth2 state parameter was not used in request, skipping response check.");
214char* freerdp_oauth2_append_state(rdpClientOAuth2* oauth2,
const char* url,
size_t len,
217 WINPR_ASSERT(oauth2);
222 if (!url || (len == 0))
225 if (strnlen(url, len + 1) > len)
228 char* safeurl =
nullptr;
229 size_t safeurllen = 0;
230 if (oauth2->pkcsChallengeMethod)
232 switch (oauth2->pkcsstate)
234 case INITIAL_CHALLENGE:
236 winpr_asprintf(&safeurl, &safeurllen,
237 "%s&state=%s&code_challenge=%s&code_challenge_method=%s", url,
238 oauth2->state, oauth2->code_challenge, oauth2->pkcsChallengeMethod);
239 oauth2->pkcsstate = TOKEN_VERIFIER;
244 winpr_asprintf(&safeurl, &safeurllen,
"%s&state=%s&code_verifier=%s", url,
245 oauth2->state, oauth2->code_verifier);
246 oauth2->pkcsstate = INVALID_STATE;
251 oauth2->log, WLOG_ERROR,
252 "Invalid pkcs state. OAuth2 call sequence of your client is wrong. Aborting.");
257 winpr_asprintf(&safeurl, &safeurllen,
"%s&state=%s", url, oauth2->state);
259 oauth2->valid =
true;
265char* freerdp_oauth2_extract_code(rdpClientOAuth2* oauth2,
const char* response,
size_t len)
267 if (!freerdp_oauth2_check_return_valid(oauth2, response, len))
270 const char* token = winpr_strnstr(response,
"code=", len);
274 const char* start = &token[5];
275 const size_t olen = WINPR_ASSERTING_INT_CAST(
size_t, start - response);
278 const size_t rlen = len - olen;
279 char* str = strndup(start, rlen);
282 char* end = winpr_strnstr(str,
"&", rlen);