Skip to content

Commit

Permalink
Fix DPoP with JWK Set (#346)
Browse files Browse the repository at this point in the history
* Fix DPoP with JWK Set

* fix(dpop): support keys without "use" header (#347)

---------

Co-authored-by: Paul Swartz <paul@paulswartz.net>
  • Loading branch information
maennchen and paulswartz committed Apr 30, 2024
1 parent a9a9077 commit 1704f0b
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 18 deletions.
12 changes: 1 addition & 11 deletions src/oidcc_auth_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -383,18 +383,8 @@ dpop_proof(Method, Endpoint, Claims0, #oidcc_client_context{
<<"nbf">> => os:system_time(seconds) - MaxClockSkew
},
Jwt = jose_jwt:from(Claims),
{_, PublicJwk} = jose_jwk:to_public_map(ClientJwks),

case
oidcc_jwt_util:sign(Jwt, ClientJwks, SigningAlgSupported, #{
<<"typ">> => <<"dpop+jwt">>, <<"jwk">> => PublicJwk
})
of
{ok, SignedRequestObject} ->
{ok, SignedRequestObject};
{error, no_supported_alg_or_key} ->
error
end;
oidcc_jwt_util:sign_dpop(Jwt, ClientJwks, SigningAlgSupported);
dpop_proof(_Method, _Endpoint, _Claims, _ClientContext) ->
error.

Expand Down
13 changes: 8 additions & 5 deletions src/oidcc_authorization.erl
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,18 @@ when
maybe_append_dpop_jkt(
QueryParams,
#oidcc_client_context{
client_jwks = #jose_jwk{},
client_jwks = #jose_jwk{} = ClientJwks,
provider_configuration = #oidcc_provider_configuration{
dpop_signing_alg_values_supported = [_ | _]
}
} = ClientContext
}
) ->
#oidcc_client_context{client_jwks = ClientJwks} = ClientContext,
Thumbprint = jose_jwk:thumbprint(ClientJwks),
[{"dpop_jkt", Thumbprint} | QueryParams];
case oidcc_jwt_util:thumbprint(ClientJwks) of
{ok, Thumbprint} ->
[{<<"dpop_jkt">>, Thumbprint} | QueryParams];
error ->
QueryParams
end;
maybe_append_dpop_jkt(QueryParams, _ClientContext) ->
QueryParams.

Expand Down
27 changes: 27 additions & 0 deletions src/oidcc_jwt_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
-export([refresh_jwks_fun/1]).
-export([sign/3]).
-export([sign/4]).
-export([sign_dpop/3]).
-export([thumbprint/1]).
-export([verify_claims/2]).
-export([verify_not_none_alg/1]).
-export([verify_signature/3]).
Expand Down Expand Up @@ -376,6 +378,7 @@ verify_decrypted_token(Jwt, SigningAlgs, Jwe, Jwks) ->
{error, Reason} ->
{error, Reason}
end.

%% @private
-spec encrypt(
Jwt :: binary(),
Expand Down Expand Up @@ -432,6 +435,30 @@ encrypt(Jwt, Jwk, [Algorithm | _RestAlgorithms] = SupportedAlgorithms, Supported
error -> encrypt(Jwt, Jwk, SupportedAlgorithms, SupportedEncValues, RestEncValues)
end.

%% @private
-spec thumbprint(Jwk :: jose_jwk:key()) -> {ok, binary()} | error.
thumbprint(Jwk) ->
evaluate_for_all_keys(Jwk, fun
(#jose_jwk{fields = #{<<"use">> := Use}}) when Use =/= <<"sig">> ->
error;
(Key) ->
{ok, jose_jwk:thumbprint(Key)}
end).

%% @private
-spec sign_dpop(Jwt :: #jose_jwt{}, Jwk :: jose_jwk:key(), SigningAlgSupported :: [binary()]) ->
{ok, binary()} | {error, no_supported_alg_or_key}.
sign_dpop(Jwt, Jwk, SigningAlgSupported) ->
evaluate_for_all_keys(Jwk, fun
(#jose_jwk{fields = #{<<"use">> := Use}}) when Use =/= <<"sig">> ->
error;
(Key) ->
{_, PublicJwk} = jose_jwk:to_public_map(Key),
sign(Jwt, Key, SigningAlgSupported, #{
<<"typ">> => <<"dpop+jwt">>, <<"jwk">> => PublicJwk
})
end).

%% @private
-spec evaluate_for_all_keys(Jwk :: jose_jwk:key(), fun((jose_jwk:key()) -> {ok, Result} | error)) ->
{ok, Result} | error
Expand Down
5 changes: 3 additions & 2 deletions test/oidcc_userinfo_test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -620,16 +620,17 @@ dpop_proof_test() ->
dpop_signing_alg_values_supported = [<<"RS256">>]
},
Jwks = jose_jwk:from_pem_file(PrivDir ++ "/test/fixtures/jwk.pem"),

ClientJwk = Jwks#jose_jwk{
fields = #{<<"kid">> => <<"private_kid">>, <<"use">> => <<"sig">>}
},

ClientJwks = #jose_jwk{keys = {jose_jwk_set, [ClientJwk]}},

ClientId = <<"client_id">>,
ClientSecret = <<"client_secret">>,

ClientContext = oidcc_client_context:from_manual(
Configuration, Jwks, ClientId, ClientSecret, #{client_jwks => ClientJwk}
Configuration, Jwks, ClientId, ClientSecret, #{client_jwks => ClientJwks}
),

HttpBody = <<"{\"name\":\"joe\", \"sub\":\"123456\"}">>,
Expand Down

0 comments on commit 1704f0b

Please sign in to comment.