oauth.erl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. -module(oauth).
  2. -export([get/3, get/5, get/6, post/3, post/5, post/6, put/6, put/7, uri/2, header/1,
  3. sign/6, params_decode/1, token/1, token_secret/1, verify/6]).
  4. -export([plaintext_signature/2, hmac_sha1_signature/5,
  5. hmac_sha1_signature/3, rsa_sha1_signature/4, rsa_sha1_signature/2,
  6. signature_base_string/3, params_encode/1, signature/5]).
  7. -export([plaintext_verify/3, hmac_sha1_verify/6, hmac_sha1_verify/4,
  8. rsa_sha1_verify/5, rsa_sha1_verify/3]).
  9. -export([header_params_encode/1, header_params_decode/1]).
  10. -include_lib("public_key/include/public_key.hrl").
  11. -if(?OTP_RELEASE >= 22).
  12. -define(HMAC_SHA1(Key, Data), crypto:mac(hmac, sha, Key, Data)).
  13. -else.
  14. -define(HMAC_SHA1(Key, Data), crypto:hmac(sha, Key, Data)).
  15. -endif.
  16. get(URL, ExtraParams, Consumer) ->
  17. get(URL, ExtraParams, Consumer, "", "").
  18. get(URL, ExtraParams, Consumer, Token, TokenSecret) ->
  19. get(URL, ExtraParams, Consumer, Token, TokenSecret, []).
  20. get(URL, ExtraParams, Consumer, Token, TokenSecret, HttpcOptions) ->
  21. SignedParams = sign("GET", URL, ExtraParams, Consumer, Token, TokenSecret),
  22. http_request(get, {uri(URL, SignedParams), []}, HttpcOptions).
  23. post(URL, ExtraParams, Consumer) ->
  24. post(URL, ExtraParams, Consumer, "", "").
  25. post(URL, ExtraParams, Consumer, Token, TokenSecret) ->
  26. post(URL, ExtraParams, Consumer, Token, TokenSecret, []).
  27. post(URL, ExtraParams, Consumer, Token, TokenSecret, HttpcOptions) ->
  28. SignedParams = sign("POST", URL, ExtraParams, Consumer, Token, TokenSecret),
  29. http_request(post, {URL, [], "application/x-www-form-urlencoded", uri_string:compose_query(SignedParams)}, HttpcOptions).
  30. put(URL, ExtraParams, {ContentType, Body}, Consumer, Token, TokenSecret) ->
  31. put(URL, ExtraParams, {ContentType, Body}, Consumer, Token, TokenSecret, []).
  32. put(URL, ExtraParams, {ContentType, Body}, Consumer, Token, TokenSecret, HttpcOptions) ->
  33. SignedParams = sign("PUT", URL, ExtraParams, Consumer, Token, TokenSecret),
  34. http_request(put, {uri(URL, SignedParams), [], ContentType, Body}, HttpcOptions).
  35. uri(Base, []) ->
  36. Base;
  37. uri(Base, Params) ->
  38. lists:concat([Base, "?", uri_string:compose_query(Params)]).
  39. header(Params) ->
  40. {"Authorization", "OAuth " ++ header_params_encode(Params)}.
  41. token(Params) ->
  42. proplists:get_value("oauth_token", Params).
  43. token_secret(Params) ->
  44. proplists:get_value("oauth_token_secret", Params).
  45. consumer_key(_Consumer={Key, _, _}) ->
  46. Key.
  47. consumer_secret(_Consumer={_, Secret, _}) ->
  48. Secret.
  49. signature_method(_Consumer={_, _, Method}) ->
  50. Method.
  51. sign(HttpMethod, URL, Params, Consumer, Token, TokenSecret) ->
  52. SignatureParams = signature_params(Consumer, Params, Token),
  53. Signature = signature(HttpMethod, URL, SignatureParams, Consumer, TokenSecret),
  54. [{"oauth_signature", Signature} | SignatureParams].
  55. signature_params(Consumer, Params, "") ->
  56. signature_params(Consumer, Params);
  57. signature_params(Consumer, Params, Token) ->
  58. signature_params(Consumer, [{"oauth_token", Token} | Params]).
  59. signature_params(Consumer, Params) ->
  60. Timestamp = unix_timestamp(),
  61. Nonce = base64:encode_to_string(crypto:strong_rand_bytes(32)), % cf. ruby-oauth
  62. [ {"oauth_version", "1.0"}
  63. , {"oauth_nonce", Nonce}
  64. , {"oauth_timestamp", integer_to_list(Timestamp)}
  65. , {"oauth_signature_method", signature_method_string(Consumer)}
  66. , {"oauth_consumer_key", consumer_key(Consumer)}
  67. | Params
  68. ].
  69. verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret) ->
  70. case signature_method(Consumer) of
  71. plaintext ->
  72. plaintext_verify(Signature, Consumer, TokenSecret);
  73. hmac_sha1 ->
  74. hmac_sha1_verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret);
  75. rsa_sha1 ->
  76. rsa_sha1_verify(Signature, HttpMethod, URL, Params, Consumer)
  77. end.
  78. signature(HttpMethod, URL, Params, Consumer, TokenSecret) ->
  79. case signature_method(Consumer) of
  80. plaintext ->
  81. plaintext_signature(Consumer, TokenSecret);
  82. hmac_sha1 ->
  83. hmac_sha1_signature(HttpMethod, URL, Params, Consumer, TokenSecret);
  84. rsa_sha1 ->
  85. rsa_sha1_signature(HttpMethod, URL, Params, Consumer)
  86. end.
  87. signature_method_string(Consumer) ->
  88. case signature_method(Consumer) of
  89. plaintext ->
  90. "PLAINTEXT";
  91. hmac_sha1 ->
  92. "HMAC-SHA1";
  93. rsa_sha1 ->
  94. "RSA-SHA1"
  95. end.
  96. plaintext_signature(Consumer, TokenSecret) ->
  97. uri_join([consumer_secret(Consumer), TokenSecret]).
  98. plaintext_verify(Signature, Consumer, TokenSecret) ->
  99. verify_in_constant_time(Signature, plaintext_signature(Consumer, TokenSecret)).
  100. hmac_sha1_signature(HttpMethod, URL, Params, Consumer, TokenSecret) ->
  101. BaseString = signature_base_string(HttpMethod, URL, Params),
  102. hmac_sha1_signature(BaseString, Consumer, TokenSecret).
  103. hmac_sha1_signature(BaseString, Consumer, TokenSecret) ->
  104. Key = uri_join([consumer_secret(Consumer), TokenSecret]),
  105. base64:encode_to_string(?HMAC_SHA1(Key, BaseString)).
  106. hmac_sha1_verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret) ->
  107. verify_in_constant_time(Signature, hmac_sha1_signature(HttpMethod, URL, Params, Consumer, TokenSecret)).
  108. hmac_sha1_verify(Signature, BaseString, Consumer, TokenSecret) ->
  109. verify_in_constant_time(Signature, hmac_sha1_signature(BaseString, Consumer, TokenSecret)).
  110. rsa_sha1_signature(HttpMethod, URL, Params, Consumer) ->
  111. BaseString = signature_base_string(HttpMethod, URL, Params),
  112. rsa_sha1_signature(BaseString, Consumer).
  113. rsa_sha1_signature(BaseString, Consumer) ->
  114. Key = read_private_key(consumer_secret(Consumer)),
  115. base64:encode_to_string(public_key:sign(list_to_binary(BaseString), sha, Key)).
  116. rsa_sha1_verify(Signature, HttpMethod, URL, Params, Consumer) ->
  117. BaseString = signature_base_string(HttpMethod, URL, Params),
  118. rsa_sha1_verify(Signature, BaseString, Consumer).
  119. rsa_sha1_verify(Signature, BaseString, Consumer) when is_binary(BaseString) ->
  120. Key = read_cert_key(consumer_secret(Consumer)),
  121. public_key:verify(BaseString, sha, base64:decode(Signature), Key);
  122. rsa_sha1_verify(Signature, BaseString, Consumer) when is_list(BaseString) ->
  123. rsa_sha1_verify(Signature, list_to_binary(BaseString), Consumer).
  124. verify_in_constant_time(<<X/binary>>, <<Y/binary>>) ->
  125. verify_in_constant_time(binary_to_list(X), binary_to_list(Y));
  126. verify_in_constant_time(X, Y) when is_list(X) and is_list(Y) ->
  127. case length(X) == length(Y) of
  128. true ->
  129. verify_in_constant_time(X, Y, 0);
  130. false ->
  131. false
  132. end.
  133. verify_in_constant_time([X | RestX], [Y | RestY], Result) ->
  134. verify_in_constant_time(RestX, RestY, (X bxor Y) bor Result);
  135. verify_in_constant_time([], [], Result) ->
  136. Result == 0.
  137. signature_base_string(HttpMethod, URL, Params) ->
  138. uri_join([HttpMethod, base_string_uri(URL), params_encode(Params)]).
  139. params_encode(Params) ->
  140. % cf. http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
  141. Encoded = [{uri_encode(K), uri_encode(V)} || {K, V} <- Params],
  142. Sorted = lists:sort(Encoded),
  143. Concatenated = [lists:concat([K, "=", V]) || {K, V} <- Sorted],
  144. string:join(Concatenated, "&").
  145. params_decode(_Response={{_, _, _}, _, Body}) ->
  146. uri_string:dissect_query(Body).
  147. http_request(Method, Request, Options) ->
  148. httpc:request(Method, Request, [{autoredirect, false}], Options).
  149. -define(unix_epoch, 62167219200).
  150. unix_timestamp() ->
  151. calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - ?unix_epoch.
  152. read_cert_key(Path) when is_list(Path) ->
  153. {ok, Contents} = file:read_file(Path),
  154. [{'Certificate', DerCert, not_encrypted}] = public_key:pem_decode(Contents),
  155. read_cert_key(public_key:pkix_decode_cert(DerCert, otp));
  156. read_cert_key(#'OTPCertificate'{tbsCertificate=Cert}) ->
  157. read_cert_key(Cert);
  158. read_cert_key(#'OTPTBSCertificate'{subjectPublicKeyInfo=Info}) ->
  159. read_cert_key(Info);
  160. read_cert_key(#'OTPSubjectPublicKeyInfo'{subjectPublicKey=Key}) ->
  161. Key.
  162. read_private_key(Path) ->
  163. {ok, Contents} = file:read_file(Path),
  164. [Info] = public_key:pem_decode(Contents),
  165. public_key:pem_entry_decode(Info).
  166. header_params_encode(Params) ->
  167. intercalate(", ", [lists:concat([uri_encode(K), "=\"", uri_encode(V), "\""]) || {K, V} <- Params]).
  168. header_params_decode(String) ->
  169. [header_param_decode(Param) || Param <- re:split(String, ",\\s*", [{return, list}]), Param =/= ""].
  170. header_param_decode(Param) ->
  171. [Key, QuotedValue] = string:tokens(Param, "="),
  172. Value = string:substr(QuotedValue, 2, length(QuotedValue) - 2),
  173. {uri_decode(Key), uri_decode(Value)}.
  174. base_string_uri(Str) ->
  175. % https://tools.ietf.org/html/rfc5849#section-3.4.1.2
  176. Map1 = uri_string:parse(Str),
  177. Scheme = string:to_lower(maps:get(scheme, Map1)),
  178. Host = string:to_lower(maps:get(host, Map1)),
  179. Map2 = maps:put(scheme, Scheme, Map1),
  180. Map3 = maps:put(host, Host, Map2),
  181. Map4 = maps:remove(query, Map3),
  182. Map5 = without_default_port(Scheme, Map4),
  183. uri_string:recompose(Map5).
  184. without_default_port("http", #{ port := 80 } = Map) ->
  185. maps:remove(port, Map);
  186. without_default_port("https", #{ port := 443 } = Map) ->
  187. maps:remove(port, Map);
  188. without_default_port(_Scheme, Map) ->
  189. Map.
  190. uri_join(Values) ->
  191. uri_join(Values, "&").
  192. uri_join(Values, Separator) ->
  193. string:join(lists:map(fun uri_encode/1, Values), Separator).
  194. intercalate(Sep, Xs) ->
  195. lists:concat(intersperse(Sep, Xs)).
  196. intersperse(_, []) ->
  197. [];
  198. intersperse(_, [X]) ->
  199. [X];
  200. intersperse(Sep, [X | Xs]) ->
  201. [X, Sep | intersperse(Sep, Xs)].
  202. uri_encode(Term) when is_integer(Term) ->
  203. integer_to_list(Term);
  204. uri_encode(Term) when is_atom(Term) ->
  205. uri_encode(atom_to_list(Term));
  206. uri_encode(Term) when is_binary(Term) ->
  207. uri_encode(binary_to_list(Term));
  208. uri_encode(Term) when is_list(Term) ->
  209. uri_encode(lists:reverse(Term, []), []).
  210. -define(is_alphanum(C), C >= $A, C =< $Z; C >= $a, C =< $z; C >= $0, C =< $9).
  211. uri_encode([X | T], Acc) when ?is_alphanum(X); X =:= $-; X =:= $_; X =:= $.; X =:= $~ ->
  212. uri_encode(T, [X | Acc]);
  213. uri_encode([X | T], Acc) ->
  214. NewAcc = [$%, dec2hex(X bsr 4), dec2hex(X band 16#0f) | Acc],
  215. uri_encode(T, NewAcc);
  216. uri_encode([], Acc) ->
  217. Acc.
  218. uri_decode(Str) when is_list(Str) ->
  219. uri_decode(Str, []).
  220. uri_decode([$%, A, B | T], Acc) ->
  221. uri_decode(T, [(hex2dec(A) bsl 4) + hex2dec(B) | Acc]);
  222. uri_decode([X | T], Acc) ->
  223. uri_decode(T, [X | Acc]);
  224. uri_decode([], Acc) ->
  225. lists:reverse(Acc, []).
  226. -compile({inline, [{dec2hex, 1}, {hex2dec, 1}]}).
  227. dec2hex(N) when N >= 10 andalso N =< 15 ->
  228. N + $A - 10;
  229. dec2hex(N) when N >= 0 andalso N =< 9 ->
  230. N + $0.
  231. hex2dec(C) when C >= $A andalso C =< $F ->
  232. C - $A + 10;
  233. hex2dec(C) when C >= $0 andalso C =< $9 ->
  234. C - $0.