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 ,
@@ -50,7 +50,7 @@ refresh_access_token(OAuthProvider, Request) ->
5050 parse_access_token_response (Response ).
5151
5252-spec introspect_token (binary ()) ->
53- {ok , map ()} |
53+ {ok , binary ()} |
5454 {error , unsuccessful_access_token_response () | any ()}.
5555introspect_token (Token ) ->
5656 case build_introspection_request () of
@@ -67,10 +67,56 @@ introspect_token(Token) ->
6767 rabbit_log :debug (" Sending introspect_request URL:~p Header: ~p Body: ~p " ,
6868 [URL , Header , Body ]),
6969 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 ;
7174 {error , _ } = Error -> Error
7275 end .
7376
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+
74120build_introspect_request_parameters (Token , # introspect_token_request {
75121 client_auth_method = Method ,
76122 client_id = ClientId ,
@@ -374,6 +420,63 @@ unlock(LockId) ->
374420 end
375421 end .
376422
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+
377480-spec get_oauth_provider (list ()) -> {ok , oauth_provider ()} | {error , any ()}.
378481get_oauth_provider (ListOfRequiredAttributes ) ->
379482 case get_env (default_oauth_provider ) of
@@ -819,3 +922,79 @@ get_env(Par, Def) ->
819922 application :get_env (rabbitmq_auth_backend_oauth2 , Par , Def ).
820923set_env (Par , Val ) ->
821924 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