|
26 | 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27 | 27 | */
|
28 | 28 |
|
29 |
| -:- module(jwt_io, [jwt_encode/3, jwt_decode/3, jwt_decode_head/2]). |
| 29 | +:- module(jwt_io, [jwt_encode/3, jwt_decode/3, jwt_decode_head/2, setup_jwks/1]). |
30 | 30 | /** <module> Json Web Tokens implementation
|
31 | 31 |
|
32 | 32 | Generates and verifies Json Web Tokens.
|
|
70 | 70 | :- use_foreign_library(foreign(jwt_io)).
|
71 | 71 |
|
72 | 72 | :- use_module(library(http/json)).
|
| 73 | +:- use_module(library(http/http_client)). |
| 74 | +:- use_module(library(http/http_json)). |
73 | 75 | :- use_module(library(settings)).
|
74 | 76 |
|
75 | 77 | :- setting(keys, list(dict), [], 'Signing keys').
|
|
292 | 294 | get_key_file(File, Key) :-
|
293 | 295 | read_file_to_string(File, KeyStr, []),
|
294 | 296 | atom_string(Key, KeyStr).
|
| 297 | + |
| 298 | + |
| 299 | +wrap_key(PubKeyString, PadStart, PadWidth, StrLength, Acc, Output) :- |
| 300 | + NewPadStart is PadStart + PadWidth, |
| 301 | + ( NewPadStart < StrLength |
| 302 | + -> sub_string(PubKeyString, PadStart, PadWidth, _, SubString), |
| 303 | + format(string(NewAcc), "~s~s~n", [Acc, SubString]), |
| 304 | + wrap_key(PubKeyString, NewPadStart, PadWidth, StrLength, NewAcc, Output) |
| 305 | + ; Output = Acc |
| 306 | + ). |
| 307 | + |
| 308 | +/* convert_jwt_to_key(+Key, -SettingsKey) is semidet. |
| 309 | + * |
| 310 | + * Converts a key from a JWKS endpoint to a key that is usable in the settings |
| 311 | + */ |
| 312 | +convert_jwk_to_key(Key, SettingsKey) :- |
| 313 | + Key.use = "sig", % key should be skipped if it is not sig |
| 314 | + Key.kty = "RSA", % only support RSA for the time being |
| 315 | + memberchk(PubKeyString, Key.x5c), |
| 316 | + string_length(PubKeyString, StrLength), |
| 317 | + wrap_key(PubKeyString, 0, 64, StrLength, "", WrappedKey), |
| 318 | + format(string(PubKeyFormatted), |
| 319 | + "-----BEGIN PUBLIC KEY-----~n~s-----END PUBLIC KEY-----", |
| 320 | + [WrappedKey]), |
| 321 | + tmp_file_stream(text, File, Stream), |
| 322 | + write(Stream, PubKeyFormatted), |
| 323 | + close(Stream), |
| 324 | + SettingsKey = _{kid: Key.kid, |
| 325 | + type: "RSA", |
| 326 | + algorithm: Key.alg, |
| 327 | + public_key: File}. |
| 328 | + |
| 329 | +/* setup_jwks(+Endpoint) is nondet. |
| 330 | + * |
| 331 | + * Sets up a JWKS endpoint to use and set the key settings accordingly. |
| 332 | + * Only supports RSA for now and be wary that it overwrites the existing |
| 333 | + * configuration. |
| 334 | + * |
| 335 | + * Another limitation so far is that this is quite inefficient, as the pubkey |
| 336 | + * is being written to a temporary file and then read again by the kid |
| 337 | + * predicate. This module should probably be refactored entirely. |
| 338 | + * |
| 339 | + * It also assumes the auth0 JWKS structure. |
| 340 | + */ |
| 341 | +setup_jwks(Endpoint) :- |
| 342 | + http_get(Endpoint, Data, [json_object(dict)]), |
| 343 | + maplist(convert_jwk_to_key, Data.keys, Keys), |
| 344 | + set_setting(jwt_io:keys, Keys). |
0 commit comments