FreeRDP
Loading...
Searching...
No Matches
oauth2.c
1
20#include "oauth2.h"
21
22#include <winpr/string.h>
23#include <winpr/crypto.h>
24#include <winpr/print.h>
25#include <freerdp/crypto/crypto.h>
26
27#include <freerdp/log.h>
28
29#define TAG CLIENT_TAG("common.oauth2")
30
31enum pkcs_state
32{
33 INVALID_STATE = 0,
34 INITIAL_CHALLENGE,
35 TOKEN_VERIFIER
36};
37
38struct rdp_client_oauth2
39{
40 wLog* log;
41 const char* pkcsChallengeMethod;
42 enum pkcs_state pkcsstate;
43 bool valid;
44 char* state;
45 size_t state_len;
46 char* code_verifier;
47 size_t code_verifier_len;
48 char* code_challenge;
49 size_t code_challenge_len;
50};
51
52WINPR_ATTR_MALLOC(free, 1)
53static char* rfc7636_generate_code_verifier(size_t* plen)
54{
55 WINPR_ASSERT(plen);
56 *plen = 0;
57
58 BYTE random[32] = WINPR_C_ARRAY_INIT;
59 if (winpr_RAND(random, sizeof(random)) < 0)
60 return nullptr;
61 char* str = crypto_base64url_encode_len(random, sizeof(random), plen);
62 if (!str)
63 return nullptr;
64 return str;
65}
66
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)
70{
71 WINPR_ASSERT(plen);
72 *plen = 0;
73
74 if (!code_verifier || (len == 0) || !method)
75 return nullptr;
76
77 if (strcmp("plain", method) == 0)
78 {
79 char* str = strndup(code_verifier, len);
80 if (!str)
81 return nullptr;
82 *plen = len;
83 return str;
84 }
85
86 if (strcmp("S256", method) == 0)
87 {
88 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
89 if (!winpr_Digest(WINPR_MD_SHA256, code_verifier, len, hash, sizeof(hash)))
90 return nullptr;
91 char* str = crypto_base64url_encode_len(hash, sizeof(hash), plen);
92 if (!str)
93 return nullptr;
94 return str;
95 }
96
97 return nullptr;
98}
99
100WINPR_ATTR_MALLOC(free, 1)
101static char* rfc6749_generate_state(size_t* plen)
102{
103 WINPR_ASSERT(plen);
104 *plen = 0;
105 BYTE random[32] = WINPR_C_ARRAY_INIT;
106 if (winpr_RAND(random, sizeof(random)) < 0)
107 return nullptr;
108 char* str = winpr_BinToHexString(random, sizeof(random), FALSE);
109 if (str)
110 *plen = strlen(str);
111 return str;
112}
113
114static void oauth2_free(rdpClientOAuth2* oauth2)
115{
116 if (!oauth2)
117 return;
118
119 oauth2->valid = false;
120
121 oauth2->state_len = 0;
122 free(oauth2->state);
123 oauth2->state = nullptr;
124
125 oauth2->code_verifier_len = 0;
126 free(oauth2->code_verifier);
127 oauth2->code_verifier = nullptr;
128
129 oauth2->code_challenge_len = 0;
130 free(oauth2->code_challenge);
131 oauth2->code_challenge = nullptr;
132}
133
134void freerdp_oauth2_free(rdpClientOAuth2* oauth2)
135{
136 oauth2_free(oauth2);
137 free(oauth2);
138}
139
140rdpClientOAuth2* freerdp_oauth2_new(void)
141{
142 rdpClientOAuth2* oauth2 = calloc(1, sizeof(rdpClientOAuth2));
143 if (!oauth2)
144 return nullptr;
145 oauth2->log = WLog_Get(TAG);
146 oauth2->pkcsChallengeMethod = "S256"; // alternatively "plain"
147 WINPR_ASSERT(oauth2->log);
148
149 return oauth2;
150}
151
152BOOL freerdp_oauth2_reset(rdpClientOAuth2* oauth2)
153{
154 WINPR_ASSERT(oauth2);
155 oauth2_free(oauth2);
156
157 oauth2->state = rfc6749_generate_state(&oauth2->state_len);
158 if (!oauth2->state || (oauth2->state_len == 0))
159 return FALSE;
160
161 oauth2->code_verifier = rfc7636_generate_code_verifier(&oauth2->code_verifier_len);
162 if (!oauth2->code_verifier || (oauth2->code_verifier_len == 0))
163 return FALSE;
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))
168 return FALSE;
169
170 oauth2->pkcsstate = INITIAL_CHALLENGE;
171 return TRUE;
172}
173
174BOOL freerdp_oauth2_check_return_valid(rdpClientOAuth2* oauth2, const char* response, size_t len)
175{
176 WINPR_ASSERT(oauth2);
177 if (!oauth2->valid)
178 {
179 WLog_Print(oauth2->log, WLOG_WARN,
180 "No OAuth2 request generated, but we have a response. "
181 "Discarding response.");
182 return FALSE;
183 }
184
185 // We did use a state parameter, so check it is there in the response.
186 if (oauth2->state && (oauth2->state_len > 0))
187 {
188 WLog_Print(oauth2->log, WLOG_DEBUG,
189 "OAuth2 state parameter used in request, checking response for mirrored value");
190 // Check if there is a state argument and it must match the one in the request.
191 const char* state = winpr_strnstr(response, "state=", len);
192 if (!state)
193 {
194 WLog_Print(oauth2->log, WLOG_WARN,
195 "OAuth2 state parameter used in request, but missing in response. "
196 "Discarding response.");
197 return FALSE;
198 }
199 if (strncmp(oauth2->state, &state[6], oauth2->state_len) != 0)
200 {
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.");
204 return FALSE;
205 }
206 }
207 else
208 WLog_Print(oauth2->log, WLOG_DEBUG,
209 "OAuth2 state parameter was not used in request, skipping response check.");
210
211 return TRUE;
212}
213
214char* freerdp_oauth2_append_state(rdpClientOAuth2* oauth2, const char* url, size_t len,
215 size_t* plen)
216{
217 WINPR_ASSERT(oauth2);
218
219 if (plen)
220 *plen = 0;
221
222 if (!url || (len == 0))
223 return nullptr;
224
225 if (strnlen(url, len + 1) > len)
226 return nullptr;
227
228 char* safeurl = nullptr;
229 size_t safeurllen = 0;
230 if (oauth2->pkcsChallengeMethod)
231 {
232 switch (oauth2->pkcsstate)
233 {
234 case INITIAL_CHALLENGE:
235 {
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;
240 }
241 break;
242 case TOKEN_VERIFIER:
243 {
244 winpr_asprintf(&safeurl, &safeurllen, "%s&state=%s&code_verifier=%s", url,
245 oauth2->state, oauth2->code_verifier);
246 oauth2->pkcsstate = INVALID_STATE;
247 }
248 break;
249 default:
250 WLog_Print(
251 oauth2->log, WLOG_ERROR,
252 "Invalid pkcs state. OAuth2 call sequence of your client is wrong. Aborting.");
253 return nullptr;
254 }
255 }
256 else
257 winpr_asprintf(&safeurl, &safeurllen, "%s&state=%s", url, oauth2->state);
258
259 oauth2->valid = true;
260 if (plen)
261 *plen = safeurllen;
262 return safeurl;
263}
264
265char* freerdp_oauth2_extract_code(rdpClientOAuth2* oauth2, const char* response, size_t len)
266{
267 if (!freerdp_oauth2_check_return_valid(oauth2, response, len))
268 return nullptr;
269
270 const char* token = winpr_strnstr(response, "code=", len);
271 if (!token)
272 return nullptr;
273
274 const char* start = &token[5];
275 const size_t olen = WINPR_ASSERTING_INT_CAST(size_t, start - response);
276 if (olen > len)
277 return nullptr;
278 const size_t rlen = len - olen;
279 char* str = strndup(start, rlen);
280 if (!str)
281 return nullptr;
282 char* end = winpr_strnstr(str, "&", rlen);
283 if (end)
284 *end = '\0';
285 return str;
286}