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