summaryrefslogtreecommitdiff
path: root/src/plop_httputil.erl
blob: 67e48baae29e3f00c14bf25c7d31da3c5a4e1259 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
%%% Copyright (c) 2014-2015, NORDUnet A/S.
%%% See LICENSE for licensing information.
%%%

-module(plop_httputil).
-export([request/4]).
-include_lib("hackney/include/hackney_lib.hrl").
-include_lib("public_key/include/public_key.hrl").

get_auth_header(Headers) ->
    case hackney_headers:get_value("X-Catlfish-Auth", Headers) of
        undefined ->
            undefined;
        Result when is_binary(Result) ->
            lager:debug("received auth header: ~p", [Result]),
            binary_to_list(Result)
    end.

add_auth(Method, Path, Headers, Data) ->
    AuthHeader = http_auth:create_auth(Method, Path, Data),
    lager:debug("sent auth header: ~p", [AuthHeader]),
    [{"X-Catlfish-Auth", AuthHeader} | Headers].

get_cert_hostname(Cert) ->
    TBSCert = Cert#'OTPCertificate'.tbsCertificate,
    Subject = TBSCert#'OTPTBSCertificate'.subject,
    {rdnSequence, RDNs} = Subject,
    CNs = lists:map(fun([#'AttributeTypeAndValue'{type=?'id-at-commonName',
                                                  value=Value}]) ->
                            Value;
                       (_) ->
                            none
                    end, RDNs),
    case [CN || CN <- CNs, CN /= none] of
        [{utf8String, BinCN} | _] when is_binary(BinCN) ->
            binary_to_list(BinCN);
        [CN | _] ->
            CN;
        [] ->
            ""
    end.

verify_fun(_,{bad_cert, _} = Reason, _) ->
    {fail, Reason};
verify_fun(_,{extension, _}, UserState) ->
    {unknown, UserState};
verify_fun(_, valid, UserState) ->
    {valid, UserState};
verify_fun(Cert, valid_peer, UserState) ->
    CertHostname = get_cert_hostname(Cert),
    case lists:keyfind(check_hostname, 1, UserState) of
        {check_hostname, CertHostname} ->
            {valid, UserState};
        {check_hostname, ExpectedHostname} ->
            lager:info("Expected hostname ~p but certificate has hostname ~p",
                       [ExpectedHostname, CertHostname]),
            {fail, "Hostname does not match"};
        false ->
            {valid, UserState}
    end.

read_and_verify_cacertfile(Filename) ->
    {ok, PemBin} = file:read_file(Filename),
    [KeyPem] = public_key:pem_decode(PemBin),
    {'Certificate', Der, _} = KeyPem,
    CalculatedHash = crypto:hash(sha256, Der),
    CorrectHash = application:get_env(plop, https_cacert_fingerprint, none),
    CorrectHash = CalculatedHash,
    Der.

request(DebugTag, URL, Headers, <<>>) ->
    request(DebugTag, URL, Headers, <<>>, get);
request(DebugTag, URL, Headers, RequestBody) ->
    request(DebugTag, URL, Headers, RequestBody, post).

request(DebugTag, URL, Headers, RequestBody, Method) ->
    Starttime = os:timestamp(),
    ParsedURL = hackney_url:parse_url(URL),
    CACertFile = application:get_env(plop, https_cacertfile, none),
    CACert = read_and_verify_cacertfile(CACertFile),
    MethodString = case Method of
                       get -> "GET";
                       post -> "POST"
                   end,
    #hackney_url{path = Path, host = Host} = ParsedURL,
    lager:debug("~s: sending http request to ~p",
                [DebugTag, URL]),
    case hackney:connect(ParsedURL,
                         [{ssl_options, [{cacerts, [CACert]},
                                         {verify, verify_peer},
                                         {verify_fun, {fun verify_fun/3,
                                                       [{check_hostname, Host}]}}
                                        ]}]) of
        {ok, ConnRef} ->
            lager:debug("~s: connected to ~p",
                        [DebugTag, URL]),
            {ok, StatusCode, RespHeaders, ClientRef} =
                hackney:send_request(ConnRef,
                                     {Method, Path,
                                      add_auth(MethodString, Path, Headers,
                                               RequestBody),
                                      RequestBody}),
            lager:debug("~s: received headers for ~p: ~p",
                        [DebugTag, URL, RespHeaders]),
            {ok, Body} = hackney:body(ClientRef),
            Stoptime = os:timestamp(),
            hackney:close(ClientRef),
            lager:debug("~s: received body for ~p: status ~p, time ~p",
                        [DebugTag, URL, StatusCode, timer:now_diff(Stoptime, Starttime)]),
            StatusLine = {none, StatusCode, none},
            AuthHeader = get_auth_header(hackney_headers:new(RespHeaders)),
            {http_auth:verify_auth(AuthHeader, "REPLY",
                                   binary_to_list(Path), Body),
             StatusLine, RespHeaders, Body};
        {error, Error} ->
            {error, Error}
    end.