Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutual TLS / client certificate #412

Closed
odarriba opened this issue Sep 11, 2024 · 12 comments
Closed

Mutual TLS / client certificate #412

odarriba opened this issue Sep 11, 2024 · 12 comments

Comments

@odarriba
Copy link

Hello,

Thanks a lot for building Req, it's really a life saver 🚀

I'm trying to use Req to communicate with an external service that uses Mutual TLS (client certificates) as authentication.

Is it possible to use Req for this? I have been looking for documentation on Mint but no luck so far. I found a thread in ElixirForum talking about an option which is no longer in the docs or in the source code, so I'm not sure it will work anymore.

Is there any known way of using client certificates to do requests with Req?

Thanks

@wojtekmach
Copy link
Owner

I've pushed docs update to main, if passing :cacertfile is not enough, please lmk!

@odarriba
Copy link
Author

Thanks for the quick response!

As fas as I know, CA certificates are used to validate that server certificate is emitted by a trusted authority, works as authenticating the server on the client side.

However, client certificates works the opposite: they are used to authenticate the client against the server, and the server is the one that have a CA that matches the client certificate. it is like having a two-way certificate communication (here some documentation).

In other libraries (docs), it can be done passing ssl options, but I'm not sure if that can be done or how.

@wojtekmach
Copy link
Owner

wojtekmach commented Sep 11, 2024

Right, replace:

options = [ssl: [certfile: "certs/client.crt"]]
{:ok, response} = HTTPoison.post(url, [], options)

with

options = [connect_options: [transport_opts: [cacertfile: "certs.pem"]]]
Req.get!(url, options)

and you should be good to go. Transport options are documented here: https://hexdocs.pm/mint/Mint.HTTP.html#connect/4-transport-options

@sergiorjsd
Copy link

Hi!

I'm trying to make the requests as you mention but I'm getting the following error:

iex(15)> req_options = [connect_options: [transport_opts: [cacertfile: cert_path]]]
[connect_options: [transport_opts: [cacertfile: "cert.pfx"]]]

iex(16)> Req.get!(url, req_options)
[notice] TLS :client: In state :certify at ssl_handshake.erl:2174 generated CLIENT ALERT: Fatal - Unknown CA

** (Req.TransportError) TLS client: In state certify at ssl_handshake.erl:2174 generated CLIENT ALERT: Fatal - Unknown CA

    (req 0.5.6) lib/req.ex:1092: Req.request!/2
    iex:16: (file)

I don't know if .pfx is a valid certificate for that purpose. It's what I'm using in Postman and it's working properly.

I've tried to convert that .pfx to .pem with openssl if possible because of this doc:

:cacertfile - path to a file containing PEM-encoded CA certificates. See the :cacerts option for the defaults to this value.

with same result.

@wojtekmach
Copy link
Owner

is that .pfx working with httpoison that you mentioned prior?

@sergiorjsd
Copy link

It's NOT with .pfx but It's working with converted .pem.

In Req is not working with .pfx nor converted .pem

@odarriba
Copy link
Author

I think the reason is that CA certificate is used to validate the server certificate, and the client certificate is a different thing: it is sent to the server.

@wojtekmach
Copy link
Owner

what happens when in httpoison you additionally set cacertfile: ..., verify: :verify_peer? Do you get the same error?

You can disable server verification with cacertfile: ..., verify: :verify_none.

@sergiorjsd
Copy link

Responses with HTTPoison: {:ok, response} = HTTPoison.get(url, [], options)

  • options = [ssl: [certfile: cert_path]]
    Result: Success
  • options = [ssl: [cacertfile: cert_path]]
    Result:
[notice] TLS :client: In state :certify at ssl_handshake.erl:2174 generated CLIENT ALERT: Fatal - Unknown CA

** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{reason: {:tls_alert, {:unknown_ca, ~c"TLS client: In state certify at ssl_handshake.erl:2174 generated CLIENT ALERT: Fatal - Unknown CA\n"}}, id: nil}}
    (stdlib 6.0) erl_eval.erl:652: :erl_eval.expr/6
    iex:50: (file)
  • options = [ssl: [cacertfile: cert_path, verify: :verify_peer]]
    Result:
[notice] TLS :client: In state :certify at ssl_handshake.erl:2174 generated CLIENT ALERT: Fatal - Unknown CA

** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{reason: {:tls_alert, {:unknown_ca, ~c"TLS client: In state certify at ssl_handshake.erl:2174 generated CLIENT ALERT: Fatal - Unknown CA\n"}}, id: nil}}
    (stdlib 6.0) erl_eval.erl:652: :erl_eval.expr/6
    iex:50: (file)
  • options = [ssl: [cacertfile: cert_path, verify: :verify_none]]
    Result: Doesn't response with an error. But endpoint raises a 403 error. Same response as no cert provided.

@wojtekmach
Copy link
Owner

Thanks for following up. The best way to figure this out is to reproduce with Mint so we remove extra layers like Finch and Req. With Mint this should work:

{:ok, conn} =
  Mint.HTTP.connect(
    :https,
    "example.com",
    443,
    transport_opts: …
  )

My recommendation is to first of all take this snippet and make sure it works (eg without cert, to a regular host), modify it in a way that should work and when it doesn’t, ask people on ElixirForum etc, the question will have more visibility and broader community could help. Sorry I couldn’t be more helpful.

@sergiorjsd
Copy link

I've just made Req works with this params: [connect_options: [transport_opts: [certfile: "cert.pem"]]] instead of: [connect_options: [transport_opts: [cacertfile: "cert.pem"]]]

I've tried with this options just because is the name in HTTPoison and... it worked 😂

@wojtekmach
Copy link
Owner

Oh, wow, good to know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants