1%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*- 2%% vim: ts=4 sw=4 ft=erlang noet 3%%%------------------------------------------------------------------- 4%%% @author Andrew Bennett <potatosaladx@gmail.com> 5%%% @copyright 2014-2015, Andrew Bennett 6%%% @doc JSON Web Signature (JWS) 7%%% See RFC 7515: https://tools.ietf.org/html/rfc7515 8%%% See RFC 7797: https://tools.ietf.org/html/rfc7797 9%%% @end 10%%% Created : 21 Jul 2015 by Andrew Bennett <potatosaladx@gmail.com> 11%%%------------------------------------------------------------------- 12-module(jose_jws). 13 14-include("jose_jwk.hrl"). 15-include("jose_jws.hrl"). 16 17-callback from_map(Fields) -> State 18 when 19 Fields :: map(), 20 State :: any(). 21-callback to_map(State, Fields) -> Map 22 when 23 State :: any(), 24 Fields :: map(), 25 Map :: map(). 26 27%% Decode API 28-export([from/1]). 29-export([from_binary/1]). 30-export([from_file/1]). 31-export([from_map/1]). 32%% Encode API 33-export([to_binary/1]). 34-export([to_file/2]). 35-export([to_map/1]). 36%% API 37-export([compact/1]). 38-export([expand/1]). 39-export([generate_key/1]). 40-export([merge/2]). 41-export([peek/1]). 42-export([peek_payload/1]). 43-export([peek_protected/1]). 44-export([peek_signature/1]). 45-export([sign/3]). 46-export([sign/4]). 47-export([signing_input/2]). 48-export([signing_input/3]). 49-export([verify/2]). 50-export([verify_strict/3]). 51 52-define(ALG_ECDSA_MODULE, jose_jws_alg_ecdsa). 53-define(ALG_EDDSA_MODULE, jose_jws_alg_eddsa). 54-define(ALG_HMAC_MODULE, jose_jws_alg_hmac). 55-define(ALG_NONE_MODULE, jose_jws_alg_none). 56-define(ALG_POLY1305_MODULE, jose_jws_alg_poly1305). 57-define(ALG_RSA_PKCS1_V1_5_MODULE, jose_jws_alg_rsa_pkcs1_v1_5). 58-define(ALG_RSA_PSS_MODULE, jose_jws_alg_rsa_pss). 59 60%%==================================================================== 61%% Decode API functions 62%%==================================================================== 63 64from(List) when is_list(List) -> 65 [from(Element) || Element <- List]; 66from({Modules, Map}) when is_map(Modules) andalso is_map(Map) -> 67 from_map({Modules, Map}); 68from({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) -> 69 from_binary({Modules, Binary}); 70from(JWS=#jose_jws{}) -> 71 JWS; 72from(Other) when is_map(Other) orelse is_binary(Other) -> 73 from({#{}, Other}). 74 75from_binary(List) when is_list(List) -> 76 [from_binary(Element) || Element <- List]; 77from_binary({Modules, Binary}) when is_map(Modules) andalso is_binary(Binary) -> 78 from_map({Modules, jose:decode(Binary)}); 79from_binary(Binary) when is_binary(Binary) -> 80 from_binary({#{}, Binary}). 81 82from_file({Modules, File}) when is_map(Modules) andalso (is_binary(File) orelse is_list(File)) -> 83 case file:read_file(File) of 84 {ok, Binary} -> 85 from_binary({Modules, Binary}); 86 ReadError -> 87 ReadError 88 end; 89from_file(File) when is_binary(File) orelse is_list(File) -> 90 from_file({#{}, File}). 91 92from_map(List) when is_list(List) -> 93 [from_map(Element) || Element <- List]; 94from_map(Map) when is_map(Map) -> 95 from_map({#{}, Map}); 96from_map({Modules, Map}) when is_map(Modules) andalso is_map(Map) -> 97 from_map({#jose_jws{}, Modules, Map}); 98from_map({JWS, Modules = #{ alg := Module }, Map=#{ <<"alg">> := _ }}) -> 99 {ALG, Fields} = Module:from_map(Map), 100 from_map({JWS#jose_jws{ alg = {Module, ALG} }, maps:remove(alg, Modules), Fields}); 101from_map({JWS, Modules, Map=#{ <<"b64">> := B64 }}) -> 102 from_map({JWS#jose_jws{ b64 = B64 }, Modules, maps:remove(<<"b64">>, Map)}); 103from_map({JWS, Modules, Map=#{ <<"alg">> := << "Ed25519", _/binary >> }}) -> 104 from_map({JWS, Modules#{ alg => ?ALG_EDDSA_MODULE }, Map}); 105from_map({JWS, Modules, Map=#{ <<"alg">> := << "Ed448", _/binary >> }}) -> 106 from_map({JWS, Modules#{ alg => ?ALG_EDDSA_MODULE }, Map}); 107from_map({JWS, Modules, Map=#{ <<"alg">> := << "EdDSA", _/binary >> }}) -> 108 from_map({JWS, Modules#{ alg => ?ALG_EDDSA_MODULE }, Map}); 109from_map({JWS, Modules, Map=#{ <<"alg">> := << "ES", _/binary >> }}) -> 110 from_map({JWS, Modules#{ alg => ?ALG_ECDSA_MODULE }, Map}); 111from_map({JWS, Modules, Map=#{ <<"alg">> := << "HS", _/binary >> }}) -> 112 from_map({JWS, Modules#{ alg => ?ALG_HMAC_MODULE }, Map}); 113from_map({JWS, Modules, Map=#{ <<"alg">> := << "Poly1305" >> }}) -> 114 from_map({JWS, Modules#{ alg => ?ALG_POLY1305_MODULE }, Map}); 115from_map({JWS, Modules, Map=#{ <<"alg">> := << "PS", _/binary >> }}) -> 116 from_map({JWS, Modules#{ alg => ?ALG_RSA_PSS_MODULE }, Map}); 117from_map({JWS, Modules, Map=#{ <<"alg">> := << "RS", _/binary >> }}) -> 118 from_map({JWS, Modules#{ alg => ?ALG_RSA_PKCS1_V1_5_MODULE }, Map}); 119from_map({JWS, Modules, Map=#{ <<"alg">> := << "none" >> }}) -> 120 from_map({JWS, Modules#{ alg => ?ALG_NONE_MODULE }, Map}); 121from_map({#jose_jws{ alg = undefined }, _Modules, _Map}) -> 122 {error, {missing_required_keys, [<<"alg">>]}}; 123from_map({JWS, _Modules, Fields}) -> 124 JWS#jose_jws{ fields = Fields }. 125 126%%==================================================================== 127%% Encode API functions 128%%==================================================================== 129 130to_binary(List) when is_list(List) -> 131 [to_binary(Element) || Element <- List]; 132to_binary(JWS=#jose_jws{}) -> 133 {Modules, Map} = to_map(JWS), 134 {Modules, jose:encode(Map)}; 135to_binary(Other) -> 136 to_binary(from(Other)). 137 138to_file(File, JWS=#jose_jws{}) when is_binary(File) orelse is_list(File) -> 139 {Modules, Binary} = to_binary(JWS), 140 case file:write_file(File, Binary) of 141 ok -> 142 {Modules, File}; 143 WriteError -> 144 WriteError 145 end; 146to_file(File, Other) when is_binary(File) orelse is_list(File) -> 147 to_file(File, from(Other)). 148 149to_map(List) when is_list(List) -> 150 [to_map(Element) || Element <- List]; 151to_map(JWS=#jose_jws{fields=Fields}) -> 152 record_to_map(JWS, #{}, Fields); 153to_map(Other) -> 154 to_map(from(Other)). 155 156%%==================================================================== 157%% API functions 158%%==================================================================== 159 160compact({Modules, #{ 161 <<"payload">> := Payload, 162 <<"signatures">> := Signatures }}) when is_list(Signatures) -> 163 {Modules, [do_compact(Map#{ <<"payload">> => Payload }) || Map <- Signatures]}; 164compact({Modules, Map}) when is_map(Map) -> 165 {Modules, do_compact(Map)}; 166compact({Modules, List}) when is_list(List) -> 167 {Modules, [do_compact(Map) || Map <- List]}; 168compact(Map) when is_map(Map) -> 169 compact({#{}, Map}); 170compact(List) when is_list(List) -> 171 compact({#{}, List}); 172compact(BadArg) -> 173 erlang:error({badarg, [BadArg]}). 174 175expand({Modules, Binary}) when is_binary(Binary) -> 176 {Modules, do_expand(Binary)}; 177expand({Modules, List}) when is_list(List) -> 178 Expanded = [do_expand(Binary) || Binary <- List], 179 Eligible = lists:foldl(fun 180 (_, false) -> 181 false; 182 (#{ <<"payload">> := Payload }, undefined) when is_binary(Payload) -> 183 Payload; 184 (#{ <<"payload">> := Payload }, Payload) when is_binary(Payload) -> 185 Payload; 186 (_, _) -> 187 false 188 end, undefined, Expanded), 189 case Eligible of 190 _ when Eligible =:= false orelse Eligible =:= undefined -> 191 {Modules, Expanded}; 192 Payload -> 193 Signatures = [maps:remove(<<"payload">>, Map) || Map <- Expanded], 194 {Modules, #{ 195 <<"payload">> => Payload, 196 <<"signatures">> => Signatures 197 }} 198 end; 199expand(Binary) when is_binary(Binary) -> 200 expand({#{}, Binary}); 201expand(List) when is_list(List) -> 202 expand({#{}, List}). 203 204generate_key(List) when is_list(List) -> 205 [generate_key(Element) || Element <- List]; 206generate_key(#jose_jws{alg={Module, ALG}, fields=Fields}) -> 207 Module:generate_key(ALG, Fields); 208generate_key(Other) -> 209 generate_key(from(Other)). 210 211merge(LeftJWS=#jose_jws{}, RightMap) when is_map(RightMap) -> 212 {Modules, LeftMap} = to_map(LeftJWS), 213 from_map({Modules, maps:merge(LeftMap, RightMap)}); 214merge(LeftOther, RightJWS=#jose_jws{}) -> 215 merge(LeftOther, element(2, to_map(RightJWS))); 216merge(LeftOther, RightMap) when is_map(RightMap) -> 217 merge(from(LeftOther), RightMap). 218 219peek(Signed) -> 220 peek_payload(Signed). 221 222peek_payload({_Modules, Signed}) when is_binary(Signed) or is_map(Signed) -> 223 peek_payload(Signed); 224peek_payload(SignedBinary) when is_binary(SignedBinary) -> 225 peek_payload(expand(SignedBinary)); 226peek_payload(#{ <<"payload">> := Payload }) -> 227 jose_jwa_base64url:decode(Payload). 228 229peek_protected({_Modules, Signed}) when is_binary(Signed) or is_map(Signed) -> 230 peek_protected(Signed); 231peek_protected(SignedBinary) when is_binary(SignedBinary) -> 232 peek_protected(expand(SignedBinary)); 233peek_protected(#{ <<"protected">> := Protected }) -> 234 jose_jwa_base64url:decode(Protected). 235 236peek_signature({_Modules, Signed}) when is_binary(Signed) or is_map(Signed) -> 237 peek_signature(Signed); 238peek_signature(SignedBinary) when is_binary(SignedBinary) -> 239 peek_signature(expand(SignedBinary)); 240peek_signature(#{ <<"signature">> := Signature }) -> 241 jose_jwa_base64url:decode(Signature). 242 243sign(KeyList, PlainText, SignerList) 244 when is_list(KeyList) 245 andalso is_list(SignerList) 246 andalso length(KeyList) =:= length(SignerList) -> 247 HeaderList = [#{} || _ <- SignerList], 248 sign(KeyList, PlainText, HeaderList, SignerList); 249sign(KeyList, PlainText, SignerList) 250 when is_list(KeyList) 251 andalso is_list(SignerList) 252 andalso length(KeyList) =/= length(SignerList) -> 253 erlang:error({badarg, [KeyList, PlainText, SignerList]}); 254sign(KeyOrKeyList, PlainText, JWS=#jose_jws{}) -> 255 sign(KeyOrKeyList, PlainText, #{}, JWS); 256sign(KeyOrKeyList, PlainText, Other) -> 257 sign(KeyOrKeyList, PlainText, from(Other)). 258 259sign(KeyList, PlainText, Header, Signer=#jose_jws{}) 260 when is_list(KeyList) 261 andalso is_binary(PlainText) 262 andalso is_map(Header) -> 263 HeaderList = [Header || _ <- KeyList], 264 SignerList = [Signer || _ <- KeyList], 265 sign(KeyList, PlainText, HeaderList, SignerList); 266sign(KeyList, PlainText, Header, SignerList) 267 when is_list(KeyList) 268 andalso is_binary(PlainText) 269 andalso is_map(Header) 270 andalso is_list(SignerList) 271 andalso length(KeyList) =:= length(SignerList) -> 272 HeaderList = [Header || _ <- KeyList], 273 sign(KeyList, PlainText, HeaderList, SignerList); 274sign(KeyList, PlainText, HeaderList, Signer=#jose_jws{}) 275 when is_list(KeyList) 276 andalso is_binary(PlainText) 277 andalso is_list(HeaderList) 278 andalso length(KeyList) =:= length(HeaderList) -> 279 SignerList = [Signer || _ <- KeyList], 280 sign(KeyList, PlainText, HeaderList, SignerList); 281sign(KeyList, PlainText, HeaderList, SignerList) 282 when is_list(KeyList) 283 andalso is_binary(PlainText) 284 andalso is_list(HeaderList) 285 andalso is_list(SignerList) 286 andalso length(KeyList) =:= length(SignerList) 287 andalso length(KeyList) =:= length(HeaderList) -> 288 Keys = jose_jwk:from(KeyList), 289 Signers = from(SignerList), 290 Payload = jose_jwa_base64url:encode(PlainText), 291 Signatures = map_signatures(Keys, PlainText, HeaderList, Signers, []), 292 {#{}, #{ 293 <<"payload">> => Payload, 294 <<"signatures">> => Signatures 295 }}; 296sign(Key=#jose_jwk{}, PlainText, Header, JWS=#jose_jws{alg={ALGModule, ALG}}) 297 when is_binary(PlainText) 298 andalso is_map(Header) -> 299 _ = code:ensure_loaded(ALGModule), 300 NewALG = case erlang:function_exported(ALGModule, presign, 2) of 301 false -> 302 ALG; 303 true -> 304 ALGModule:presign(Key, ALG) 305 end, 306 NewJWS = JWS#jose_jws{alg={ALGModule, NewALG}}, 307 {Modules, ProtectedBinary} = to_binary(NewJWS), 308 Protected = jose_jwa_base64url:encode(ProtectedBinary), 309 Payload = jose_jwa_base64url:encode(PlainText), 310 SigningInput = signing_input(PlainText, Protected, NewJWS), 311 Signature = jose_jwa_base64url:encode(ALGModule:sign(Key, SigningInput, NewALG)), 312 {Modules, maps:put(<<"payload">>, Payload, signature_to_map(Protected, Header, Key, Signature))}; 313sign(Key=none, PlainText, Header, JWS=#jose_jws{alg={ALGModule, ALG}}) 314 when is_binary(PlainText) 315 andalso is_map(Header) -> 316 _ = code:ensure_loaded(ALGModule), 317 NewALG = case erlang:function_exported(ALGModule, presign, 2) of 318 false -> 319 ALG; 320 true -> 321 ALGModule:presign(Key, ALG) 322 end, 323 NewJWS = JWS#jose_jws{alg={ALGModule, NewALG}}, 324 {Modules, ProtectedBinary} = to_binary(NewJWS), 325 Protected = jose_jwa_base64url:encode(ProtectedBinary), 326 Payload = jose_jwa_base64url:encode(PlainText), 327 SigningInput = signing_input(PlainText, Protected, NewJWS), 328 Signature = jose_jwa_base64url:encode(ALGModule:sign(Key, SigningInput, NewALG)), 329 {Modules, maps:put(<<"payload">>, Payload, signature_to_map(Protected, Header, Key, Signature))}; 330sign(KeyList, PlainText, HeaderList, SignerList) 331 when (is_list(KeyList) 332 andalso is_list(HeaderList) 333 andalso length(KeyList) =/= length(HeaderList)) 334 orelse (is_list(KeyList) 335 andalso is_list(SignerList) 336 andalso length(KeyList) =/= length(SignerList)) 337 orelse (is_list(HeaderList) 338 andalso is_list(SignerList) 339 andalso length(HeaderList) =/= length(SignerList)) 340 orelse (is_list(HeaderList) 341 andalso not is_list(KeyList) 342 andalso not is_list(SignerList)) -> 343 erlang:error({badarg, [KeyList, PlainText, HeaderList, SignerList]}); 344sign(KeyOrKeyList, PlainText, Header, Other) 345 when is_binary(PlainText) 346 andalso is_map(Header) -> 347 sign(jose_jwk:from(KeyOrKeyList), PlainText, Header, from(Other)). 348 349%% See https://tools.ietf.org/html/rfc7797 350signing_input(Payload, JWS=#jose_jws{}) -> 351 {_, ProtectedBinary} = to_binary(JWS), 352 Protected = jose_jwa_base64url:encode(ProtectedBinary), 353 signing_input(Payload, Protected, JWS); 354signing_input(Payload, Other) -> 355 signing_input(Payload, from(Other)). 356 357signing_input(PlainText, Protected, #jose_jws{b64=B64}) 358 when (B64 =:= true 359 orelse B64 =:= undefined) -> 360 Payload = jose_jwa_base64url:encode(PlainText), 361 << Protected/binary, $., Payload/binary >>; 362signing_input(Payload, Protected, #jose_jws{b64=false}) -> 363 << Protected/binary, $., Payload/binary >>. 364 365verify(Key, SignedMap) when is_map(SignedMap) -> 366 verify(Key, {#{}, SignedMap}); 367verify(Key, SignedBinary) when is_binary(SignedBinary) -> 368 verify(Key, expand(SignedBinary)); 369verify(Key, {Modules, SignedBinary}) when is_binary(SignedBinary) -> 370 verify(Key, expand({Modules, SignedBinary})); 371verify(Key, {Modules, #{ 372 <<"payload">> := Payload, 373 <<"protected">> := Protected, 374 <<"signature">> := EncodedSignature}}) -> 375 JWS = #jose_jws{alg={ALGModule, ALG}} = from_binary({Modules, jose_jwa_base64url:decode(Protected)}), 376 Signature = jose_jwa_base64url:decode(EncodedSignature), 377 PlainText = jose_jwa_base64url:decode(Payload), 378 SigningInput = signing_input(PlainText, Protected, JWS), 379 {ALGModule:verify(Key, SigningInput, Signature, ALG), PlainText, JWS}; 380verify(Keys = [_ | _], {Modules, Signed=#{ 381 <<"payload">> := _Payload, 382 <<"signatures">> := EncodedSignatures}}) 383 when is_list(EncodedSignatures) -> 384 [begin 385 {Key, verify(Key, {Modules, Signed})} 386 end || Key <- Keys]; 387verify(Key, {Modules, #{ 388 <<"payload">> := Payload, 389 <<"signatures">> := EncodedSignatures}}) 390 when is_list(EncodedSignatures) -> 391 [begin 392 verify(Key, {Modules, maps:put(<<"payload">>, Payload, EncodedSignature)}) 393 end || EncodedSignature <- EncodedSignatures]. 394 395verify_strict(Key, Allow, SignedMap) when is_map(SignedMap) -> 396 verify_strict(Key, Allow, {#{}, SignedMap}); 397verify_strict(Key, Allow, SignedBinary) when is_binary(SignedBinary) -> 398 verify_strict(Key, Allow, expand(SignedBinary)); 399verify_strict(Key, Allow, {Modules, SignedBinary}) when is_binary(SignedBinary) -> 400 verify_strict(Key, Allow, expand({Modules, SignedBinary})); 401verify_strict(Key, Allow, {Modules, #{ 402 <<"payload">> := Payload, 403 <<"protected">> := Protected, 404 <<"signature">> := EncodedSignature}}) -> 405 ProtectedMap = jose:decode(jose_jwa_base64url:decode(Protected)), 406 Signature = jose_jwa_base64url:decode(EncodedSignature), 407 PlainText = jose_jwa_base64url:decode(Payload), 408 case ProtectedMap of 409 #{ <<"alg">> := Algorithm } -> 410 case lists:member(Algorithm, Allow) of 411 false -> 412 {false, PlainText, ProtectedMap}; 413 true -> 414 JWS = #jose_jws{alg={ALGModule, ALG}} = from_map({Modules, ProtectedMap}), 415 SigningInput = signing_input(PlainText, Protected, JWS), 416 {ALGModule:verify(Key, SigningInput, Signature, ALG), PlainText, JWS} 417 end; 418 _ -> 419 {false, PlainText, ProtectedMap} 420 end; 421verify_strict(Keys = [_ | _], Allow, {Modules, Signed=#{ 422 <<"payload">> := _Payload, 423 <<"signatures">> := EncodedSignatures}}) 424 when is_list(EncodedSignatures) -> 425 [begin 426 {Key, verify_strict(Key, Allow, {Modules, Signed})} 427 end || Key <- Keys]; 428verify_strict(Key, Allow, {Modules, #{ 429 <<"payload">> := Payload, 430 <<"signatures">> := EncodedSignatures}}) 431 when is_list(EncodedSignatures) -> 432 [begin 433 verify_strict(Key, Allow, {Modules, maps:put(<<"payload">>, Payload, EncodedSignature)}) 434 end || EncodedSignature <- EncodedSignatures]. 435 436%%%------------------------------------------------------------------- 437%%% Internal functions 438%%%------------------------------------------------------------------- 439 440%% @private 441do_compact(#{ 442 <<"payload">> := Payload, 443 <<"protected">> := Protected, 444 <<"signature">> := Signature}) -> 445 << 446 Protected/binary, $., 447 Payload/binary, $., 448 Signature/binary 449 >>; 450do_compact(BadArg) -> 451 erlang:error({badarg, [BadArg]}). 452 453%% @private 454do_expand(Binary) when is_binary(Binary) -> 455 case binary:split(Binary, <<".">>, [global]) of 456 [Protected, Payload, Signature] -> 457 #{ 458 <<"payload">> => Payload, 459 <<"protected">> => Protected, 460 <<"signature">> => Signature 461 }; 462 _ -> 463 erlang:error({badarg, [Binary]}) 464 end; 465do_expand(BadArg) -> 466 erlang:error({badarg, [BadArg]}). 467 468%% @private 469map_signatures([Key | Keys], PlainText, [Header | Headers], [Signer=#jose_jws{alg={ALGModule, ALG}} | Signers], Acc) -> 470 _ = code:ensure_loaded(ALGModule), 471 NewALG = case erlang:function_exported(ALGModule, presign, 2) of 472 false -> 473 ALG; 474 true -> 475 ALGModule:presign(Key, ALG) 476 end, 477 NewSigner = Signer#jose_jws{alg={ALGModule, NewALG}}, 478 {_Modules, ProtectedBinary} = to_binary(NewSigner), 479 Protected = jose_jwa_base64url:encode(ProtectedBinary), 480 SigningInput = signing_input(PlainText, Protected, NewSigner), 481 Signature = jose_jwa_base64url:encode(ALGModule:sign(Key, SigningInput, NewALG)), 482 map_signatures(Keys, PlainText, Headers, Signers, [signature_to_map(Protected, Header, Key, Signature) | Acc]); 483map_signatures([], _PlainText, [], [], Acc) -> 484 lists:reverse(Acc). 485 486%% @private 487record_to_map(JWS=#jose_jws{alg={Module, ALG}}, Modules, Fields0) -> 488 Fields1 = Module:to_map(ALG, Fields0), 489 record_to_map(JWS#jose_jws{alg=undefined}, Modules#{ alg => Module }, Fields1); 490record_to_map(JWS=#jose_jws{b64=B64}, Modules, Fields0) when is_boolean(B64) -> 491 Fields1 = Fields0#{ <<"b64">> => B64 }, 492 record_to_map(JWS#jose_jws{b64=undefined}, Modules, Fields1); 493record_to_map(_JWS, Modules, Fields) -> 494 {Modules, Fields}. 495 496%% @private 497signature_to_map(Protected, Header, #jose_jwk{fields=Fields}, Signature) -> 498 signature_to_map(Protected, Header, Fields, Signature); 499signature_to_map(Protected, Header, #{ <<"kid">> := KID }, Signature) -> 500 #{ 501 <<"protected">> => Protected, 502 <<"header">> => maps:put(<<"kid">>, KID, Header), 503 <<"signature">> => Signature 504 }; 505signature_to_map(Protected, Header, _Fields, Signature) -> 506 case maps:size(Header) of 507 0 -> 508 #{ 509 <<"protected">> => Protected, 510 <<"signature">> => Signature 511 }; 512 _ -> 513 #{ 514 <<"protected">> => Protected, 515 <<"header">> => Header, 516 <<"signature">> => Signature 517 } 518 end. 519