I'm relatively new to Erlang and want to write a small server that uses the client certificate to identify the client. The client should be able to use any public/private key pair without it being part of a certificate chain. I looked at the SSL examples from the OTP source code and used make_certs.erl to create the key pairs. I can connect using the created client certificate, but not using a self signed certificate.
How can I get the client certificate without validating it?
My sample code is currently:
-module(simple_server).
-export([start/0]).
keep_alive() ->
receive
Any -> io:format("Listening socket: ~p~n",[Any])
end.
start() ->
ssl:start(),
spawn(fun() ->
start_parallel_server(3333),
keep_alive()
end).
start_parallel_server(Port) ->
case ssl:listen(Port, [
binary,
{packet, 0},
{reuseaddr, true},
{active, true},
{certfile,"../etc/server/cert.pem"},
{keyfile,"../etc/server/key.pem"},
{cacertfile,"../etc/server/cacerts.pem"},
{verify,verify_peer},
{fail_if_no_peer_cert,true}
]) of
{ok,Listen} ->
spawn(fun() -> par_connect(Listen) end);
{error,Reason} ->
io:format("error ~p~n",[Reason])
end.
par_connect(Listen) ->
case ssl:transport_accept(Listen) of
{ok,Socket} ->
spawn(fun() -> par_connect(Listen) end),
ssl:ssl_accept(Socket),
print_cert(Socket),
get_request(Socket,[]);
{error,Reason} ->
io:format("Listening stopped (~p)~n",[Reason])
end.
print_cert(Socket) ->
case ssl:peercert(Socket) of
{ok,Cert} ->
io:format("Certificate: ~p~n",[Cert]);
{error,Reason} ->
io:format("Certificate error ~p~n",[Reason])
end.
get_request(Socket,L) ->
receive
{ssl, Socket, Bin} ->
io:format("Server received: ~p~n",[Bin]),
get_request(Socket,L);
{ssl_closed, Socket} ->
io:format("Socket did disconnect~n");
Reason ->
io:format("Client error: ~p~n",[Reason])
end.
You need to supply a custom path verification function to ssl:listen that allows self-signed certificates.
The verify_fun option (see the docs) lets you specify a function that is called when a certification verification error is encountered.
We can take the default implementation (given in the docs) and make sure the selfsigned_peer case returns success:
{verify_fun, {fun(_, {bad_cert, selfsigned_peer}, UserState) ->
{valid, UserState}; %% Allow self-signed certificates
(_,{bad_cert, _} = Reason, _) ->
{fail, Reason};
(_,{extension, _}, UserState) ->
{unknown, UserState};
(_, valid, UserState) ->
{valid, UserState};
(_, valid_peer, UserState) ->
{valid, UserState}
end, []}}
Related
Calling my hyper based API now ported to HTTPS, with Python's requests I'm getting
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)' on every request.
As per the docs for tokio_rustls:
Certificate has to be DER-encoded X.509
PrivateKey has to be DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format.
The keys I used in my PKEY and CERT variables are my certbot generated .pem keys converted to .der format using those commands:
openssl x509 -outform der -in /etc/letsencrypt/live/mysite.com/cert.pem -out /etc/letsencrypt/live/mysite.com/cert.der
openssl pkcs8 -topk8 -inform PEM -outform DER -in /etc/letsencrypt/live/mysite.com/privkey.pem -out /etc/letsencrypt/live/mysite.com/privkey.der -nocrypt
And loaded up with include_bytes!() macro.
Well it compiles, polls... and just throws this error on every request Bad connection: cannot decrypt peer's message whilst the caller gets the SSLError mentioned in the beginning.
Here is the script used for the API:
fn tls_acceptor_impl(cert_der: &[u8], key_der: &[u8]) -> tokio_rustls::TlsAcceptor {
let key = PrivateKey(cert_der.into());
let cert = Certificate(key_der.into());
Arc::new(
ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(vec![cert], key)
.unwrap(),
)
.into()
}
fn tls_acceptor() -> tokio_rustls::TlsAcceptor {
tls_acceptor_impl(PKEY, CERT)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(...);
let mut listener = tls_listener::builder(tls_acceptor())
.max_handshakes(10)
.listen(AddrIncoming::bind(&addr).unwrap());
let (tx, mut rx) = mpsc::channel::<tokio_rustls::TlsAcceptor>(1);
let http = Http::new();
loop {
tokio::select! {
conn = listener.accept() => {
match conn.expect("Tls listener stream should be infinite") {
Ok(conn) => {
let http = http.clone();
// let tx = tx.clone();
// let counter = counter.clone();
tokio::spawn(async move {
// let svc = service_fn(move |request| handle_request(tx.clone(), counter.clone(), request));
if let Err(err) = http.serve_connection(conn, service_fn(my_query_handler)).await {
eprintln!("Application error: {}", err);
}
});
},
Err(e) => {
eprintln!("Bad connection: {}", e);
}
}
},
message = rx.recv() => {
// Certificate is loaded on another task; we don't want to block the listener loop
let acceptor = message.expect("Channel should not be closed");
}
}
}
How can I make any sense of the errors, when the certificate keys work on Web (as they are the apache2 server's keys)? I've tried various other encodings, that are against the docs, and all fail in the same way.
I'm not familiar enough with rust, but I know that proper configuration of a TLS server (no matter which language) requires the server certificate, the key matching the certificate and all intermediate CA needed to build the trust chain from server certificate to the root CA on the clients machine. These intermediate CA are not provided in your code. That's why the Python code fails to validate the certificate.
What you need is probably something like this:
ServerConfig::builder()
....
.with_single_cert(vec![cert, intermediate_cert], key)
Where intermediate_cert is the internal representation of the Let’s Encrypt R3 CA, which you can find here.
I added a new module (-behavior(gen_mod)) inside ejabberd and I was able to open a ssl connection to a server and send a message directly using the ssl connection(ssl:send() function). However, I am unable to receive response after this.
I tried using the receive option in erlang to catch messages, and still no luck.
I tried changing the module to be a stateful module by using the gen_server behavior and I still don't observe any handle_info calls with any message.
I could not get any response using ssl:recv/2 either.
Am I missing something? How do I asynchronously receive the response from the ssl socket in erlang?
Any pointers would be really appreciated. Thank you!
code: Adding only relevant parts of the code.
sendPacketToServer() ->
case ssl:connect(Gateway, Port, Options, ?SSL_TIMEOUT) of
{ok, Socket} ->
ssl:controlling_process(Socket, self()),
Packet = .....,
Result = ssl:send(Socket, Packet),
receiveMessage(),
ssl:close(Socket),
?INFO_MSG("~n~n~n~n Successfully sent payload to the server, result: ~p for the user: ~p", [Result, Username]);
{error, Reason} = Err ->
?ERROR_MSG("Unable to connect to the server: ~s for the user: ~p", [ssl:format_error(Reason), Username]),
Err
end
...
....
receiveMessage() ->
receive ->
{ssl, Msg, Data} -> % incoming msg from SSL, send it to process
......
{ssl_closed, Msg} -> % incoming msg from SSL, send it to process
.....
{ssl_error, Msg} -> % incoming msg from SSL, send it to process
.....
{ssl_passive, Msg} -> % incoming msg from SSL, send it to process
....
end.
Added the following code for gen_server: (when doing the following, i do not close the socket immediately, but it still does not work).
start(Host, Opts) ->
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
gen_mod:stop_child(?MODULE, Host).
init([ServerHost|_]) ->
Opts = gen_mod:get_module_opts(ServerHost, ?MODULE),
start(ServerHost, Opts),
{ok, #state{host = ServerHost}}.
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, State) ->
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
I was able to overcome the problem by using a gen_server module, since it has explicit callbacks for any messages.
I sent a restfull api request by http client, but i get below error:
{error,{failed_connect,[{to_address,{"https://example.com",443}},
{inet,[inet],{tls_alert,"record overflow"}}]}}
i found that SSL peer verification made this problem. how can i disable it?
my code:
test() ->
inets:start(),
ssl:start(),
RequestBody = "",
Request = {"https://example.com", [{"X-API-CODE",""}, {"Accept","application/json"}, {"access-token",""}], "application/json", RequestBody},
{ok, {_, _, ResponseBody}} = httpc:request(post, Request, [], []),
io:format("~st", [ResponseBody]).
Although disabling verification is not a good idea, but it's possible by using {ssl, [{verify, verify_none}]} in the options.
Example:
httpc:request(get, {"https://revoked.badssl.com/", []}, [{ssl, [{verify, verify_none}]}], []).
How can I specify my custom host in send.exs file if my app which has receive.exs is hosted somewhere? I have one elixir app with send.exs and another app with receive.exs which is Phoenix app and is hosted.
send.exs
{:ok, connection} = AMQP.Connection.open
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, "hello")
AMQP.Basic.publish(channel, "", "hello", msg)
IO.puts " [x] Sent everything"
AMQP.Connection.close(connection)
receive.exs
...
{:ok, connection} = AMQP.Connection.open
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, "hello")
AMQP.Basic.consume(channel, "hello", nil, no_ack: true)
IO.puts " [*] Waiting for messages. To exit press CTRL+C, CTRL+C"
...
There are a few different forms of Connection.open.
The first, which you're using, takes no arguments, and uses some default settings (localhost, "guest" username, "guest" password, etc.).
The second takes a Keyword list of options, including host, port, virtual_host, and others. You may use Connection.open(host: your_host). Any settings which you don't provide will use the default setting.
The third form takes a well-formed RabbitMQ URI as a String, which is formed using a combination of host, port, username, password, and virtual host. Note that some of these values are optional in the URI.
I have the following certificate hierarchy:
Root-->CA-->3 leaf certificates
The entire chain has both serverAuth and clientAuth as extended key usages explicitly defined.
In my go code, I create a tls.Config object like so:
func parseCert(certFile, keyFile string) (cert tls.Certificate, err error) {
certPEMBlock , err := ioutil.ReadFile(certFile)
if err != nil {
return
}
var certDERBlock *pem.Block
for {
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
}
}
// Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
cpy := make([][]byte, len(cert.Certificate))
copy(cpy, cert.Certificate)
var j = 0
for i := len(cpy)-1; i >=0; i-- {
cert.Certificate[j] = cert.Certificate[i]
j++
}
keyData, err := ioutil.ReadFile(keyFile)
if err != nil {
return
}
block, _ := pem.Decode(keyData)
if err != nil {
return
}
ecdsaKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return
}
cert.PrivateKey = ecdsaKey
return
}
// configure and create a tls.Config instance using the provided cert, key, and ca cert files.
func configureTLS(certFile, keyFile, caCertFile string) (tlsConfig *tls.Config, err error) {
c, err := parseCert(certFile, keyFile)
if err != nil {
return
}
ciphers := []uint16 {
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
certPool := x509.NewCertPool()
buf, err := ioutil.ReadFile(caCertFile)
if nil != err {
log.Println("failed to load ca cert")
log.Fatal(seelog.Errorf("failed to load ca cert.\n%s", err))
}
if !certPool.AppendCertsFromPEM(buf) {
log.Fatalln("Failed to parse truststore")
}
tlsConfig = &tls.Config {
CipherSuites: ciphers,
ClientAuth: tls.RequireAndVerifyClientCert,
PreferServerCipherSuites: true,
RootCAs: certPool,
ClientCAs: certPool,
Certificates: []tls.Certificate{c},
}
return
}
certFile is the certificate chain file and keyFile is the private key file. caCertFile is the truststore and consists of just the root certificate
So basically, here is what I expect to have inside of my tls.Config object that comes out of this function:
RootCAs: Just the root certificate from caCertFile
ClientCAs: Again, just the root certificate from caCertFile, same as RootCAs
Certificates: A single certificate chain, containing all of the certificates in certFile, ordered to be leaf first.
Now, I have 3 pieces here. A server, a relay, and a client. The client connects directly to the relay, which in turn forwards the request to the server. All three pieces use the same configuration code, of course using different certs/keys. The caCertFile is the same between all 3 pieces.
Now, if I stand up the server and the relay and connect to the relay from my browser, all goes well, so I can assume that the connection between relay and server is fine. The issue comes about when I try to connect my client to the relay. When I do so, the TLS handshake fails and the following error is returned:
x509: certificate signed by unknown authority
On the relay side of things, I get the following error:
http: TLS handshake error from : remote error: bad certificate
I am really at a loss here. I obviously have something setup incorrectly, but I am not sure what. It's really weird that it works from the browser (meaning that the config is correct from relay to server), but it doesn't work with the same config from my client.
Update:
So if I add InsecureSkipVerify: true to my tls.Config object on both the relay and the client, the errors change to:
on the client: remote error: bad certificate
and on the relay: http: TLS handshake error from : tls: client didn't provide a certificate
So it looks like the client is rejecting the certificate on from the server (the relay) due to it being invalid for some reason and thus never sending its certificate to the server (the relay).
I really wish go had better logging. I can't even hook into this process to see what, exactly, is going on.
When you say
Need to flip the array because openssl gives it to us in the opposite format than golang tls expects.
I have used certificates generated by openssl and had no problem opening them with:
tls.LoadX509KeyPair(cert, key)
Anyway, the error message bad certificate is due to the server not managing to match the client-provided certificate against its RootCAs. I have also had this problem in Go using self-signed certificats and the only work-around I've found is to install the caCertFile into the machines system certs, and use x509.SystemCertPool() instead of x509.NewCertPool().
Maybe someone else will have another solution?
Beside what beldin0 suggested.
I have tried another way to do this.
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(crt)
client := &http.Client{
//some config
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
},
},
}
Here, the variable "crt" is the content in your certificate.
Basically, you just add it into your code(or read as a config file).
Then everything would be fine.