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

add ecdsa support for native client #69

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

- drop support for Elixir < v1.16
- [allow](https://github.com/sasa1977/site_encrypt/pull/63) all `Logger.levels()` in `:log_level` option
- [support](https://github.com/sasa1977/site_encrypt/pull/69) ECDSA in the native ACME client
- [default](https://github.com/sasa1977/site_encrypt/pull/69) to ECDSA in the native ACME client

## 0.6.0

Expand Down
19 changes: 18 additions & 1 deletion lib/site_encrypt.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ defmodule SiteEncrypt do
client: :native | :certbot,
backup: String.t() | nil,
callback: module,
key_type: :rsa | :ecdsa,
key_size: pos_integer,
elliptic_curve: String.t(),
periodic_offset: Certification.Periodic.offset()
}

Expand Down Expand Up @@ -118,10 +120,25 @@ defmodule SiteEncrypt do
default: :info,
doc: "Logger level for info messages."
],
key_type: [
type: {:in, [:rsa, :ecdsa]},
default: :ecdsa,
doc: """
Specifies the algorithm to be used for the key.

- `:rsa` - Use RSA algorithm for key generation. This option is widely supported and ensures compatibility with a broad range of clients and servers.
- `:ecdsa` - Use ECDSA for key generation. This option provides better performance and security with smaller key sizes compared to RSA. This is the default option.
"""
],
key_size: [
type: :pos_integer,
default: 4096,
doc: "The size used for generating private keys."
doc: "The size used for generating private RSA keys."
],
elliptic_curve: [
type: {:in, ["P-256", "P-384"]},
default: "P-256",
Copy link
Contributor Author

@ruslandoga ruslandoga Jun 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P-256 is fast: benchmark

doc: "The curve used for generating private ECDSA keys."
],
mode: [
type: {:in, [:auto, :manual]},
Expand Down
10 changes: 9 additions & 1 deletion lib/site_encrypt/acme/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule SiteEncrypt.Acme.Client do
@spec new_account(SiteEncrypt.id(), API.session_opts()) :: API.session()
def new_account(id, session_opts \\ []) do
config = SiteEncrypt.Registry.config(id)
account_key = JOSE.JWK.generate_key({:rsa, config.key_size})
account_key = generate_key(config)
session = start_session(SiteEncrypt.directory_url(config), account_key, session_opts)
{:ok, session} = API.new_account(session, config.emails)
session
Expand Down Expand Up @@ -47,6 +47,14 @@ defmodule SiteEncrypt.Acme.Client do
{%{privkey: Crypto.private_key_to_pem(private_key), cert: cert, chain: chain}, session}
end

defp generate_key(%{key_type: :rsa, key_size: key_size}) do
JOSE.JWK.generate_key({:rsa, key_size})
end

defp generate_key(%{key_type: :ecdsa, elliptic_curve: curve}) do
JOSE.JWK.generate_key({:ec, curve})
end

defp start_session(directory_url, account_key, session_opts) do
{:ok, session} = API.new_session(directory_url, account_key, session_opts)
session
Expand Down
12 changes: 11 additions & 1 deletion lib/site_encrypt/acme/client/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ defmodule SiteEncrypt.Acme.Client.API do
defp jws_body(session, url, id_field, payload) do
protected =
Map.merge(
%{"alg" => "RS256", "nonce" => session.nonce, "url" => url},
%{"alg" => jwk_to_alg(session.account_key), "nonce" => session.nonce, "url" => url},
id_map(id_field, session)
)

Expand All @@ -269,6 +269,16 @@ defmodule SiteEncrypt.Acme.Client.API do

defp id_map(:kid, session), do: %{"kid" => session.kid}

defp jwk_to_alg(jwk) do
{_modules, public_map} = JOSE.JWK.to_public_map(jwk)

case public_map do
%{"kty" => "RSA"} -> "RS256"
%{"kty" => "EC", "crv" => "P-256"} -> "ES256"
%{"kty" => "EC", "crv" => "P-384"} -> "ES384"
end
end

defp http_request(session, verb, url, opts \\ []) do
opts =
opts
Expand Down
2 changes: 1 addition & 1 deletion test/pebble_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule SiteEncrypt.PebbleTest do
use ExUnit.Case, async: true
use ExUnit.Case, async: false
import SiteEncrypt.Phoenix.Test
alias __MODULE__.TestEndpoint

Expand Down
Loading