diff options
author | Martin Schanzenbach <mschanzenbach@posteo.de> | 2020-08-06 08:45:40 +0200 |
---|---|---|
committer | Martin Schanzenbach <mschanzenbach@posteo.de> | 2020-08-06 08:45:40 +0200 |
commit | 1d4f5263ae72c12a42ec166ec8b1769620baaeda (patch) | |
tree | 4c5c6051b4ae968c4b3a5c82f1d51da452e9f1d3 /src/reclaim/oidc_helper.c | |
parent | 6e764f4abd8a3f14f03a5a167af7d5cb703fd1d5 (diff) | |
download | gnunet-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.c | 313 |
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 | ||
70 | GNUNET_NETWORK_STRUCT_END | 70 | GNUNET_NETWORK_STRUCT_END |
71 | 71 | ||
72 | /** | ||
73 | * Standard claims represented by the "profile" scope in OIDC | ||
74 | */ | ||
75 | static 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 | */ | ||
84 | static char OIDC_email_claims[2][16] = { | ||
85 | "email", "email_verified" | ||
86 | }; | ||
87 | |||
88 | /** | ||
89 | * Standard claims represented by the "phone" scope in OIDC | ||
90 | */ | ||
91 | static 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 | */ | ||
98 | static char OIDC_address_claims[5][32] = { | ||
99 | "street_address", "locality", "region", "postal_code", "country" | ||
100 | }; | ||
101 | |||
102 | static enum GNUNET_GenericReturnValue | ||
103 | is_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 | |||
72 | static char * | 117 | static char * |
73 | create_jwt_header (void) | 118 | create_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 | 157 | static json_t* | |
113 | /** | 158 | generate_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 | */ | ||
123 | char * | ||
124 | OIDC_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 | */ | ||
302 | char * | ||
303 | OIDC_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 | */ | ||
327 | char * | ||
328 | OIDC_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 *) ¶ms[1]); | 655 | code_challenge = ((char *) ¶ms[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, ¶ms->ticket, sizeof(params->ticket)); | 680 | memcpy (ticket, ¶ms->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 | */ | ||
791 | enum GNUNET_GenericReturnValue | ||
792 | OIDC_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 | } | ||