1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2007-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22
23-module(ssl_config).
24
25-include("ssl_internal.hrl").
26-include("ssl_connection.hrl").
27-include_lib("public_key/include/public_key.hrl").
28
29-export([init/2]).
30
31init(#{erl_dist := ErlDist,
32       key := Key,
33       keyfile := KeyFile,
34       password := Password,
35       dh := DH,
36       dhfile := DHFile} = SslOpts, Role) ->
37
38    init_manager_name(ErlDist),
39
40    {ok, #{pem_cache := PemCache} = Config}
41	= init_certificates(SslOpts, Role),
42    PrivateKey =
43	init_private_key(PemCache, Key, KeyFile, Password, Role),
44    DHParams = init_diffie_hellman(PemCache, DH, DHFile, Role),
45    {ok, Config#{private_key => PrivateKey, dh_params => DHParams}}.
46
47init_manager_name(false) ->
48    put(ssl_manager, ssl_manager:name(normal)),
49    put(ssl_pem_cache, ssl_pem_cache:name(normal));
50init_manager_name(true) ->
51    put(ssl_manager, ssl_manager:name(dist)),
52    put(ssl_pem_cache, ssl_pem_cache:name(dist)).
53
54init_certificates(#{cacerts := CaCerts,
55                    cacertfile := CACertFile,
56                    certfile := CertFile,
57                    cert := Cert,
58                    crl_cache := CRLCache
59                   }, Role) ->
60    {ok, Config} =
61	try
62	    Certs = case CaCerts of
63			undefined ->
64			    CACertFile;
65			_ ->
66			    {der, CaCerts}
67		    end,
68	    {ok,_} = ssl_manager:connection_init(Certs, Role, CRLCache)
69	catch
70	    _:Reason ->
71		file_error(CACertFile, {cacertfile, Reason})
72	end,
73    init_certificates(Cert, Config, CertFile, Role).
74
75init_certificates(undefined, Config, <<>>, _) ->
76    {ok, Config#{own_certificate => undefined}};
77
78init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, client) ->
79    try
80	%% Ignoring potential proxy-certificates see:
81	%% http://dev.globus.org/wiki/Security/ProxyFileFormat
82	[OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache),
83	{ok, Config#{own_certificate => OwnCert}}
84    catch _Error:_Reason  ->
85	    {ok, Config#{own_certificate => undefined}}
86    end;
87
88init_certificates(undefined, #{pem_cache := PemCache} = Config, CertFile, server) ->
89    try
90	[OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCache),
91	{ok, Config#{own_certificate => OwnCert}}
92    catch
93	_:Reason ->
94	    file_error(CertFile, {certfile, Reason})
95    end;
96init_certificates(Cert, Config, _, _) ->
97    {ok, Config#{own_certificate => Cert}}.
98init_private_key(_, #{algorithm := Alg} = Key, _, _Password, _Client) when Alg == ecdsa;
99                                                                           Alg == rsa;
100                                                                           Alg == dss ->
101    case maps:is_key(engine, Key) andalso maps:is_key(key_id, Key) of
102        true ->
103            Key;
104        false ->
105            throw({key, {invalid_key_id, Key}})
106    end;
107init_private_key(_, undefined, <<>>, _Password, _Client) ->
108    undefined;
109init_private_key(DbHandle, undefined, KeyFile, Password, _) ->
110    try
111	{ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle),
112	[PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List,
113				  PKey =:= 'RSAPrivateKey' orelse
114				      PKey =:= 'DSAPrivateKey' orelse
115				      PKey =:= 'ECPrivateKey' orelse
116				      PKey =:= 'PrivateKeyInfo'
117		     ],
118	private_key(public_key:pem_entry_decode(PemEntry, Password))
119    catch
120	_:Reason ->
121	    file_error(KeyFile, {keyfile, Reason})
122    end;
123
124init_private_key(_,{Asn1Type, PrivateKey},_,_,_) ->
125    private_key(init_private_key(Asn1Type, PrivateKey)).
126
127init_private_key(Asn1Type, PrivateKey) ->
128    public_key:der_decode(Asn1Type, PrivateKey).
129
130private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
131				 #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'},
132			     privateKey = Key}) ->
133    public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key));
134
135private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
136				 #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'},
137			     privateKey = Key}) ->
138    public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key));
139private_key(#'PrivateKeyInfo'{privateKeyAlgorithm =
140                                  #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-ecPublicKey',
141                                                                        parameters =  {asn1_OPENTYPE, Parameters}},
142                              privateKey = Key}) ->
143    ECKey = public_key:der_decode('ECPrivateKey',  iolist_to_binary(Key)),
144    ECParameters = public_key:der_decode('EcpkParameters', Parameters),
145    ECKey#'ECPrivateKey'{parameters = ECParameters};
146private_key(Key) ->
147    Key.
148
149-spec(file_error(_,_) -> no_return()).
150file_error(File, Throw) ->
151    case Throw of
152	{Opt,{badmatch, {error, {badmatch, Error}}}} ->
153	    throw({options, {Opt, binary_to_list(File), Error}});
154	{Opt, {badmatch, Error}} ->
155	    throw({options, {Opt, binary_to_list(File), Error}});
156	_ ->
157	    throw(Throw)
158    end.
159
160init_diffie_hellman(_,Params, _,_) when is_binary(Params)->
161    public_key:der_decode('DHParameter', Params);
162init_diffie_hellman(_,_,_, client) ->
163    undefined;
164init_diffie_hellman(_,_,undefined, _) ->
165    ?DEFAULT_DIFFIE_HELLMAN_PARAMS;
166init_diffie_hellman(DbHandle,_, DHParamFile, server) ->
167    try
168	{ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle),
169	case [Entry || Entry = {'DHParameter', _ , _} <- List] of
170	    [Entry] ->
171		public_key:pem_entry_decode(Entry);
172	    [] ->
173		?DEFAULT_DIFFIE_HELLMAN_PARAMS
174	end
175    catch
176	_:Reason ->
177	    file_error(DHParamFile, {dhfile, Reason})
178    end.
179