aboutsummaryrefslogtreecommitdiff
path: root/src/reclaim/oidc_helper.c
diff options
context:
space:
mode:
authorMartin Schanzenbach <mschanzenbach@posteo.de>2020-08-06 08:45:40 +0200
committerMartin Schanzenbach <mschanzenbach@posteo.de>2020-08-06 08:45:40 +0200
commit1d4f5263ae72c12a42ec166ec8b1769620baaeda (patch)
tree4c5c6051b4ae968c4b3a5c82f1d51da452e9f1d3 /src/reclaim/oidc_helper.c
parent6e764f4abd8a3f14f03a5a167af7d5cb703fd1d5 (diff)
downloadgnunet-1d4f5263ae72c12a42ec166ec8b1769620baaeda.tar.gz
gnunet-1d4f5263ae72c12a42ec166ec8b1769620baaeda.zip
reclaim: Refactoring and more standards compliance with respect to scopes
Diffstat (limited to 'src/reclaim/oidc_helper.c')
-rw-r--r--src/reclaim/oidc_helper.c313
1 files changed, 245 insertions, 68 deletions
diff --git a/src/reclaim/oidc_helper.c b/src/reclaim/oidc_helper.c
index 4da387564..cb99a749d 100644
--- a/src/reclaim/oidc_helper.c
+++ b/src/reclaim/oidc_helper.c
@@ -69,6 +69,51 @@ struct OIDC_Parameters
69 69
70GNUNET_NETWORK_STRUCT_END 70GNUNET_NETWORK_STRUCT_END
71 71
72/**
73 * Standard claims represented by the "profile" scope in OIDC
74 */
75static char OIDC_profile_claims[14][32] = {
76 "name", "family_name", "given_name", "middle_name", "nickname",
77 "preferred_username", "profile", "picture", "website", "gender", "birthdate",
78 "zoneinfo", "locale", "updated_at"
79};
80
81/**
82 * Standard claims represented by the "email" scope in OIDC
83 */
84static char OIDC_email_claims[2][16] = {
85 "email", "email_verified"
86};
87
88/**
89 * Standard claims represented by the "phone" scope in OIDC
90 */
91static char OIDC_phone_claims[2][32] = {
92 "phone_number", "phone_number_verified"
93};
94
95/**
96 * Standard claims represented by the "address" scope in OIDC
97 */
98static char OIDC_address_claims[5][32] = {
99 "street_address", "locality", "region", "postal_code", "country"
100};
101
102static enum GNUNET_GenericReturnValue
103is_claim_in_address_scope (const char *claim)
104{
105 int i;
106 for (i = 0; i < 5; i++)
107 {
108 if (0 == strcmp (claim, OIDC_address_claims[i]))
109 {
110 return GNUNET_YES;
111 }
112 }
113 return GNUNET_NO;
114}
115
116
72static char * 117static char *
73create_jwt_header (void) 118create_jwt_header (void)
74{ 119{
@@ -109,49 +154,24 @@ fix_base64 (char *str)
109 replace_char (str, '/', '_'); 154 replace_char (str, '/', '_');
110} 155}
111 156
112 157static json_t*
113/** 158generate_userinfo_json(const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key,
114 * Create a JWT from attributes 159 struct GNUNET_RECLAIM_AttributeList *attrs,
115 * 160 struct GNUNET_RECLAIM_AttestationList *attests)
116 * @param aud_key the public of the audience
117 * @param sub_key the public key of the subject
118 * @param attrs the attribute list
119 * @param expiration_time the validity of the token
120 * @param secret_key the key used to sign the JWT
121 * @return a new base64-encoded JWT string.
122 */
123char *
124OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
125 const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key,
126 struct GNUNET_RECLAIM_AttributeList *attrs,
127 struct GNUNET_RECLAIM_AttestationList *attests,
128 const struct GNUNET_TIME_Relative *expiration_time,
129 const char *nonce,
130 const char *secret_key)
131{ 161{
132 struct GNUNET_RECLAIM_AttributeListEntry *le; 162 struct GNUNET_RECLAIM_AttributeListEntry *le;
133 struct GNUNET_RECLAIM_AttestationListEntry *ale; 163 struct GNUNET_RECLAIM_AttestationListEntry *ale;
134 struct GNUNET_HashCode signature;
135 struct GNUNET_TIME_Absolute exp_time;
136 struct GNUNET_TIME_Absolute time_now;
137 char *audience;
138 char *subject; 164 char *subject;
139 char *header;
140 char *body_str;
141 char *aggr_names_str; 165 char *aggr_names_str;
142 char *aggr_sources_str; 166 char *aggr_sources_str;
143 char *source_name; 167 char *source_name;
144 char *result;
145 char *header_base64;
146 char *body_base64;
147 char *signature_target;
148 char *signature_base64;
149 char *attr_val_str; 168 char *attr_val_str;
150 char *attest_val_str; 169 char *attest_val_str;
151 json_t *body; 170 json_t *body;
152 json_t *aggr_names; 171 json_t *aggr_names;
153 json_t *aggr_sources; 172 json_t *aggr_sources;
154 json_t *aggr_sources_jwt; 173 json_t *aggr_sources_jwt;
174 json_t *addr_claim;
155 int num_attestations = 0; 175 int num_attestations = 0;
156 for (le = attrs->list_head; NULL != le; le = le->next) 176 for (le = attrs->list_head; NULL != le; le = le->next)
157 { 177 {
@@ -159,22 +179,10 @@ OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
159 num_attestations++; 179 num_attestations++;
160 } 180 }
161 181
162 // iat REQUIRED time now
163 time_now = GNUNET_TIME_absolute_get ();
164 // exp REQUIRED time expired from config
165 exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time);
166 // auth_time only if max_age
167 // nonce only if nonce
168 // OPTIONAL acr,amr,azp
169 subject = 182 subject =
170 GNUNET_STRINGS_data_to_string_alloc (sub_key, 183 GNUNET_STRINGS_data_to_string_alloc (sub_key,
171 sizeof(struct 184 sizeof(struct
172 GNUNET_CRYPTO_EcdsaPublicKey)); 185 GNUNET_CRYPTO_EcdsaPublicKey));
173 audience =
174 GNUNET_STRINGS_data_to_string_alloc (aud_key,
175 sizeof(struct
176 GNUNET_CRYPTO_EcdsaPublicKey));
177 header = create_jwt_header ();
178 body = json_object (); 186 body = json_object ();
179 aggr_names = json_object (); 187 aggr_names = json_object ();
180 aggr_sources = json_object (); 188 aggr_sources = json_object ();
@@ -185,23 +193,6 @@ OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
185 json_object_set_new (body, "iss", json_string (SERVER_ADDRESS)); 193 json_object_set_new (body, "iss", json_string (SERVER_ADDRESS));
186 // sub REQUIRED public key identity, not exceed 255 ASCII length 194 // sub REQUIRED public key identity, not exceed 255 ASCII length
187 json_object_set_new (body, "sub", json_string (subject)); 195 json_object_set_new (body, "sub", json_string (subject));
188 // aud REQUIRED public key client_id must be there
189 json_object_set_new (body, "aud", json_string (audience));
190 // iat
191 json_object_set_new (body,
192 "iat",
193 json_integer (time_now.abs_value_us / (1000 * 1000)));
194 // exp
195 json_object_set_new (body,
196 "exp",
197 json_integer (exp_time.abs_value_us / (1000 * 1000)));
198 // nbf
199 json_object_set_new (body,
200 "nbf",
201 json_integer (time_now.abs_value_us / (1000 * 1000)));
202 // nonce
203 if (NULL != nonce)
204 json_object_set_new (body, "nonce", json_string (nonce));
205 attest_val_str = NULL; 196 attest_val_str = NULL;
206 aggr_names_str = NULL; 197 aggr_names_str = NULL;
207 aggr_sources_str = NULL; 198 aggr_sources_str = NULL;
@@ -236,8 +227,28 @@ OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
236 GNUNET_RECLAIM_attribute_value_to_string (le->attribute->type, 227 GNUNET_RECLAIM_attribute_value_to_string (le->attribute->type,
237 le->attribute->data, 228 le->attribute->data,
238 le->attribute->data_size); 229 le->attribute->data_size);
239 json_object_set_new (body, le->attribute->name, 230 /**
240 json_string (attr_val_str)); 231 * There is this wierd quirk that the individual address claim(s) must be
232 * inside a JSON object of the "address" claim.
233 * FIXME: Possibly include formatted claim here
234 */
235 if (GNUNET_YES == is_claim_in_address_scope (le->attribute->name))
236 {
237 if (NULL == addr_claim)
238 {
239 addr_claim = json_object ();
240 json_object_set_new (body, "address",
241 addr_claim);
242 }
243 json_object_set_new (addr_claim, le->attribute->name,
244 json_string (attr_val_str));
245
246 }
247 else
248 {
249 json_object_set_new (body, le->attribute->name,
250 json_string (attr_val_str));
251 }
241 GNUNET_free (attr_val_str); 252 GNUNET_free (attr_val_str);
242 } 253 }
243 else 254 else
@@ -277,6 +288,102 @@ OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
277 288
278 json_decref (aggr_names); 289 json_decref (aggr_names);
279 json_decref (aggr_sources); 290 json_decref (aggr_sources);
291 return body;
292}
293
294/**
295 * Generate userinfo JSON as string
296 *
297 * @param sub_key the subject (user)
298 * @param attrs user attribute list
299 * @param attests user attribute attestation list (may be empty)
300 * @return Userinfo JSON
301 */
302char *
303OIDC_generate_userinfo (const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key,
304 struct GNUNET_RECLAIM_AttributeList *attrs,
305 struct GNUNET_RECLAIM_AttestationList *attests)
306{
307 char *body_str;
308 json_t* body = generate_userinfo_json (sub_key,
309 attrs,
310 attests);
311 body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT);
312 json_decref (body);
313 return body_str;
314}
315
316
317/**
318 * Create a JWT from attributes
319 *
320 * @param aud_key the public of the audience
321 * @param sub_key the public key of the subject
322 * @param attrs the attribute list
323 * @param expiration_time the validity of the token
324 * @param secret_key the key used to sign the JWT
325 * @return a new base64-encoded JWT string.
326 */
327char *
328OIDC_generate_id_token (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
329 const struct GNUNET_CRYPTO_EcdsaPublicKey *sub_key,
330 struct GNUNET_RECLAIM_AttributeList *attrs,
331 struct GNUNET_RECLAIM_AttestationList *attests,
332 const struct GNUNET_TIME_Relative *expiration_time,
333 const char *nonce,
334 const char *secret_key)
335{
336 struct GNUNET_HashCode signature;
337 struct GNUNET_TIME_Absolute exp_time;
338 struct GNUNET_TIME_Absolute time_now;
339 char *audience;
340 char *subject;
341 char *header;
342 char *body_str;
343 char *result;
344 char *header_base64;
345 char *body_base64;
346 char *signature_target;
347 char *signature_base64;
348 json_t *body;
349
350 body = generate_userinfo_json (sub_key,
351 attrs,
352 attests);
353 // iat REQUIRED time now
354 time_now = GNUNET_TIME_absolute_get ();
355 // exp REQUIRED time expired from config
356 exp_time = GNUNET_TIME_absolute_add (time_now, *expiration_time);
357 // auth_time only if max_age
358 // nonce only if nonce
359 // OPTIONAL acr,amr,azp
360 subject =
361 GNUNET_STRINGS_data_to_string_alloc (sub_key,
362 sizeof(struct
363 GNUNET_CRYPTO_EcdsaPublicKey));
364 audience =
365 GNUNET_STRINGS_data_to_string_alloc (aud_key,
366 sizeof(struct
367 GNUNET_CRYPTO_EcdsaPublicKey));
368 header = create_jwt_header ();
369
370 // aud REQUIRED public key client_id must be there
371 json_object_set_new (body, "aud", json_string (audience));
372 // iat
373 json_object_set_new (body,
374 "iat",
375 json_integer (time_now.abs_value_us / (1000 * 1000)));
376 // exp
377 json_object_set_new (body,
378 "exp",
379 json_integer (exp_time.abs_value_us / (1000 * 1000)));
380 // nbf
381 json_object_set_new (body,
382 "nbf",
383 json_integer (time_now.abs_value_us / (1000 * 1000)));
384 // nonce
385 if (NULL != nonce)
386 json_object_set_new (body, "nonce", json_string (nonce));
280 387
281 body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT); 388 body_str = json_dumps (body, JSON_INDENT (0) | JSON_COMPACT);
282 json_decref (body); 389 json_decref (body);
@@ -315,10 +422,6 @@ OIDC_id_token_new (const struct GNUNET_CRYPTO_EcdsaPublicKey *aud_key,
315 GNUNET_free (signature_target); 422 GNUNET_free (signature_target);
316 GNUNET_free (header); 423 GNUNET_free (header);
317 GNUNET_free (body_str); 424 GNUNET_free (body_str);
318 if (NULL != aggr_sources_str)
319 GNUNET_free (aggr_sources_str);
320 if (NULL != aggr_names_str)
321 GNUNET_free (aggr_names_str);
322 GNUNET_free (signature_base64); 425 GNUNET_free (signature_base64);
323 GNUNET_free (body_base64); 426 GNUNET_free (body_base64);
324 GNUNET_free (header_base64); 427 GNUNET_free (header_base64);
@@ -552,7 +655,7 @@ OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
552 code_challenge = ((char *) &params[1]); 655 code_challenge = ((char *) &params[1]);
553 GNUNET_free (code_verifier_hash); 656 GNUNET_free (code_verifier_hash);
554 if (0 != 657 if (0 !=
555 strncmp (expected_code_challenge, code_challenge, code_challenge_len)) 658 strncmp (expected_code_challenge, code_challenge, code_challenge_len))
556 { 659 {
557 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 660 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
558 "Invalid code verifier! Expected: %s, Got: %.*s\n", 661 "Invalid code verifier! Expected: %s, Got: %.*s\n",
@@ -575,8 +678,8 @@ OIDC_parse_authz_code (const struct GNUNET_CRYPTO_EcdsaPublicKey *audience,
575 678
576 // Ticket 679 // Ticket
577 memcpy (ticket, &params->ticket, sizeof(params->ticket)); 680 memcpy (ticket, &params->ticket, sizeof(params->ticket));
578 // Signature 681 // Signature
579 //GNUNET_CRYPTO_ecdsa_key_get_public (ecdsa_priv, &ecdsa_pub); 682 // GNUNET_CRYPTO_ecdsa_key_get_public (ecdsa_priv, &ecdsa_pub);
580 if (0 != GNUNET_memcmp (audience, &ticket->audience)) 683 if (0 != GNUNET_memcmp (audience, &ticket->audience))
581 { 684 {
582 GNUNET_free (code_payload); 685 GNUNET_free (code_payload);
@@ -675,3 +778,77 @@ OIDC_access_token_parse (const char*token,
675 return GNUNET_SYSERR; 778 return GNUNET_SYSERR;
676 return GNUNET_OK; 779 return GNUNET_OK;
677} 780}
781
782
783/**
784 * Checks if a claim is implicitly requested through standard
785 * scope(s)
786 *
787 * @param scopes the scopes which have been requested
788 * @param attr the attribute name to check
789 * @return GNUNET_YES if attribute is implcitly requested
790 */
791enum GNUNET_GenericReturnValue
792OIDC_check_scopes_for_claim_request (const char*scopes,
793 const char*attr)
794{
795 char *scope_variables;
796 char *scope_variable;
797 char delimiter[] = " ";
798 int i;
799
800 scope_variables = GNUNET_strdup (scopes);
801 scope_variable = strtok (scope_variables, delimiter);
802 while (NULL != scope_variable)
803 {
804 if (0 == strcmp ("profile", scope_variable))
805 {
806 for (i = 0; i < 14; i++)
807 {
808 if (0 == strcmp (attr, OIDC_profile_claims[i]))
809 {
810 GNUNET_free (scope_variables);
811 return GNUNET_YES;
812 }
813 }
814 }
815 else if (0 == strcmp ("address", scope_variable))
816 {
817 for (i = 0; i < 5; i++)
818 {
819 if (0 == strcmp (attr, OIDC_address_claims[i]))
820 {
821 GNUNET_free (scope_variables);
822 return GNUNET_YES;
823 }
824 }
825 }
826 else if (0 == strcmp ("email", scope_variable))
827 {
828 for (i = 0; i < 2; i++)
829 {
830 if (0 == strcmp (attr, OIDC_email_claims[i]))
831 {
832 GNUNET_free (scope_variables);
833 return GNUNET_YES;
834 }
835 }
836 }
837 else if (0 == strcmp ("phone", scope_variable))
838 {
839 for (i = 0; i < 2; i++)
840 {
841 if (0 == strcmp (attr, OIDC_phone_claims[i]))
842 {
843 GNUNET_free (scope_variables);
844 return GNUNET_YES;
845 }
846 }
847
848 }
849 scope_variable = strtok (NULL, delimiter);
850 }
851 GNUNET_free (scope_variables);
852 return GNUNET_NO;
853
854}