7
7
-module (oauth2_client ).
8
8
-export ([get_access_token /2 , get_expiration_time /1 ,
9
9
refresh_access_token /2 ,
10
- introspect_token /1 ,
10
+ introspect_token /1 ,sign_token / 1 ,
11
11
get_oauth_provider /1 , get_oauth_provider /2 ,
12
12
get_openid_configuration /2 ,
13
13
build_openid_discovery_endpoint /3 ,
@@ -50,7 +50,7 @@ refresh_access_token(OAuthProvider, Request) ->
50
50
parse_access_token_response (Response ).
51
51
52
52
-spec introspect_token (binary ()) ->
53
- {ok , map ()} |
53
+ {ok , binary ()} |
54
54
{error , unsuccessful_access_token_response () | any ()}.
55
55
introspect_token (Token ) ->
56
56
case build_introspection_request () of
@@ -67,10 +67,56 @@ introspect_token(Token) ->
67
67
rabbit_log :debug (" Sending introspect_request URL:~p Header: ~p Body: ~p " ,
68
68
[URL , Header , Body ]),
69
69
Response = httpc :request (post , {URL , Header , Type , Body }, HTTPOptions , Options ),
70
- parse_introspect_token_response (Response );
70
+ case parse_introspect_token_response (Response ) of
71
+ {error , _ } = Error -> Error ;
72
+ {ok , _ } = Ret -> Ret
73
+ end ;
71
74
{error , _ } = Error -> Error
72
75
end .
73
76
77
+ sign_token (TokenPayload ) ->
78
+ case get_opaque_token_signing_key () of
79
+ {error , _ } = Error -> Error ;
80
+ SK ->
81
+ ct :log (" Signing with ~p " , [SK ]),
82
+ case SK # signing_key .type of
83
+ hs256 ->
84
+ {_ , Value } = sign_token_hs (TokenPayload , SK # signing_key .key , SK # signing_key .id ),
85
+ {ok , Value };
86
+ _ -> {error , not_implemented }
87
+ end
88
+ end .
89
+
90
+ sign_token_hs (Token , #{<<" kid" >> := TokenKey } = Jwk ) ->
91
+ sign_token_hs (Token , Jwk , TokenKey ).
92
+
93
+ % %sign_token_hs(Token, Jwk, TokenKey) ->
94
+ % % sign_token_hs(Token, Jwk, TokenKey, true).
95
+
96
+ sign_token_hs (Token , Jwk , TokenKey ) ->
97
+ Jws0 = #{
98
+ <<" alg" >> => <<" HS256" >>,
99
+ <<" kid" >> => TokenKey
100
+ },
101
+ Jws = maps :put (<<" kid" >>, TokenKey , Jws0 ),
102
+ sign_token (Token , Jwk , Jws ).
103
+
104
+ sign_token_rsa (Token , Jwk , TokenKey ) ->
105
+ Jws = #{
106
+ <<" alg" >> => <<" RS256" >>,
107
+ <<" kid" >> => TokenKey
108
+ },
109
+ sign_token (Token , Jwk , Jws ).
110
+
111
+ sign_token_no_kid (Token , Jwk ) ->
112
+ Signed = jose_jwt :sign (Jwk , Token ),
113
+ jose_jws :compact (Signed ).
114
+
115
+ sign_token (Token , Jwk , Jws ) ->
116
+ Signed = jose_jwt :sign (Jwk , Jws , Token ),
117
+ jose_jws :compact (Signed ).
118
+
119
+
74
120
build_introspect_request_parameters (Token , # introspect_token_request {
75
121
client_auth_method = Method ,
76
122
client_id = ClientId ,
@@ -374,6 +420,63 @@ unlock(LockId) ->
374
420
end
375
421
end .
376
422
423
+ -spec get_opaque_token_signing_key () -> {ok , signing_key ()} | {error , any ()}.
424
+ get_opaque_token_signing_key () ->
425
+ case get_env (opaque_token_signing_key ) of
426
+ undefined -> {error , missing_opaque_token_signing_key };
427
+ Map ->
428
+ parse_signing_key_configuration (Map )
429
+ end .
430
+
431
+ parse_signing_key_configuration (Map ) ->
432
+ SK0 = case maps :get (id , Map , undefined ) of
433
+ undefined -> {error , missing_signing_key_id };
434
+ Id -> # signing_key {id = Id }
435
+ end ,
436
+ case {SK0 , maps :get (type , Map , hs256 )} of
437
+ {{error , _ } = Error , _ } ->
438
+ Error ;
439
+ {_ , hs256 } ->
440
+ Sk1 = case maps :get (key , Map , undefined ) of
441
+ undefined -> {error , missing_symmetrical_key_value };
442
+ SymKey -> SK0 # signing_key {
443
+ type = hs256 ,
444
+ key = case make_jwk (#{
445
+ <<" alg" >> => <<" HS256" >>,
446
+ <<" value" >> => SymKey ,
447
+ <<" kty" >> => <<" MAC" >>,
448
+ <<" use" >> => <<" sig" >>}) of
449
+ {error , _ } = Error -> Error ;
450
+ {ok , Val } -> Val
451
+ end
452
+ }
453
+ end ,
454
+ case Sk1 # signing_key .key of
455
+ {error , _ } = Error1 -> Error1 ;
456
+ _ -> Sk1
457
+ end ;
458
+ {_ , rs256 } ->
459
+ Sk2 = case maps :get (key_pem_file , Map , undefined ) of
460
+ undefined ->
461
+ {error , missing_key_pem_file };
462
+ PrivateKey ->
463
+ case maps :get (cert_pem_file , Map , undefined ) of
464
+ undefined ->
465
+ {error , missing_cert_pem_file };
466
+ PublicKey ->
467
+ SK0 # signing_key {type = hs256 ,
468
+ private_key = PrivateKey ,
469
+ public_key = PublicKey }
470
+ end
471
+ end ,
472
+ case {Sk2 # signing_key .private_key , Sk2 # signing_key .public_key } of
473
+ {{error , _ } = Error2 , _ } -> Error2 ;
474
+ {_ , {error , _ } = Error3 } -> Error3 ;
475
+ {_ , _ } -> Sk2
476
+ end ;
477
+ {_ , _ } -> {error , unsupported_signing_type }
478
+ end .
479
+
377
480
-spec get_oauth_provider (list ()) -> {ok , oauth_provider ()} | {error , any ()}.
378
481
get_oauth_provider (ListOfRequiredAttributes ) ->
379
482
case get_env (default_oauth_provider ) of
@@ -819,3 +922,79 @@ get_env(Par, Def) ->
819
922
application :get_env (rabbitmq_auth_backend_oauth2 , Par , Def ).
820
923
set_env (Par , Val ) ->
821
924
application :set_env (rabbitmq_auth_backend_oauth2 , Par , Val ).
925
+
926
+
927
+ -include_lib (" jose/include/jose_jwk.hrl" ).
928
+
929
+ -spec make_jwk (binary () | map ()) -> {ok , #{binary () => binary ()}} | {error , term ()}.
930
+ make_jwk (Json ) when is_binary (Json ); is_list (Json ) ->
931
+ JsonMap = jose :decode (iolist_to_binary (Json )),
932
+ make_jwk (JsonMap );
933
+
934
+ make_jwk (JsonMap ) when is_map (JsonMap ) ->
935
+ case JsonMap of
936
+ #{<<" kty" >> := <<" MAC" >>, <<" value" >> := _Value } ->
937
+ {ok , mac_to_oct (JsonMap )};
938
+ #{<<" kty" >> := <<" RSA" >>, <<" n" >> := _N , <<" e" >> := _E } ->
939
+ {ok , fix_alg (JsonMap )};
940
+ #{<<" kty" >> := <<" oct" >>, <<" k" >> := _K } ->
941
+ {ok , fix_alg (JsonMap )};
942
+ #{<<" kty" >> := <<" OKP" >>, <<" crv" >> := _Crv , <<" x" >> := _X } ->
943
+ {ok , fix_alg (JsonMap )};
944
+ #{<<" kty" >> := <<" EC" >>} ->
945
+ {ok , fix_alg (JsonMap )};
946
+ #{<<" kty" >> := Kty } when Kty == <<" oct" >>;
947
+ Kty == <<" MAC" >>;
948
+ Kty == <<" RSA" >>;
949
+ Kty == <<" OKP" >>;
950
+ Kty == <<" EC" >> ->
951
+ {error , {fields_missing_for_kty , Kty }};
952
+ #{<<" kty" >> := _Kty } ->
953
+ {error , unknown_kty };
954
+ #{} ->
955
+ {error , no_kty }
956
+ end .
957
+
958
+ from_pem (Pem ) ->
959
+ case jose_jwk :from_pem (Pem ) of
960
+ # jose_jwk {} = Jwk -> {ok , Jwk };
961
+ Other ->
962
+ error_logger :warning_msg (" Error parsing jwk from pem: " , [Other ]),
963
+ {error , invalid_pem_string }
964
+ end .
965
+
966
+ from_pem_file (FileName ) ->
967
+ case filelib :is_file (FileName ) of
968
+ false ->
969
+ {error , enoent };
970
+ true ->
971
+ case jose_jwk :from_pem_file (FileName ) of
972
+ # jose_jwk {} = Jwk -> {ok , Jwk };
973
+ Other ->
974
+ error_logger :warning_msg (" Error parsing jwk from pem file: " , [Other ]),
975
+ {error , invalid_pem_file }
976
+ end
977
+ end .
978
+
979
+ mac_to_oct (#{<<" kty" >> := <<" MAC" >>, <<" value" >> := Value } = Key ) ->
980
+ OktKey = maps :merge (Key ,
981
+ #{<<" kty" >> => <<" oct" >>,
982
+ <<" k" >> => base64url :encode (Value )}),
983
+ fix_alg (OktKey ).
984
+
985
+ fix_alg (#{<<" alg" >> := Alg } = Key ) ->
986
+ Algs = uaa_algs (),
987
+ case maps :get (Alg , Algs , undefined ) of
988
+ undefined -> Key ;
989
+ Val -> Key #{<<" alg" >> := Val }
990
+ end ;
991
+ fix_alg (#{} = Key ) -> Key .
992
+
993
+ uaa_algs () ->
994
+ UaaEnv = application :get_env (rabbitmq_auth_backend_oauth2 , uaa_jwt_decoder , []),
995
+ DefaultAlgs = #{<<" HMACSHA256" >> => <<" HS256" >>,
996
+ <<" HMACSHA384" >> => <<" HS384" >>,
997
+ <<" HMACSHA512" >> => <<" HS512" >>,
998
+ <<" SHA256withRSA" >> => <<" RS256" >>,
999
+ <<" SHA512withRSA" >> => <<" RS512" >>},
1000
+ proplists :get_value (uaa_algs , UaaEnv , DefaultAlgs ).
0 commit comments