Skip to content

Commit 84ef706

Browse files
authored
Merge pull request #3 from scrogson/merge_rsa
generate_rsa
2 parents 8b0f1d1 + c85bc83 commit 84ef706

File tree

16 files changed

+531
-425
lines changed

16 files changed

+531
-425
lines changed

README.md

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,50 +7,46 @@
77
## Features
88

99
In its current state, this library only supports signing JWTs using the `RS512`
10-
algo with a DER encoded RSA private key.
10+
algo with a DER or PEM encoded RSA private key.
1111

1212
## Installation
1313

1414
```elixir
1515
def deps do
1616
[
17-
{:no_way_jose, "~> 0.1.0"}
17+
{:no_way_jose, "~> 0.2.0"}
1818
]
1919
end
2020
```
2121

2222
## Generating a key
2323

24-
In order to sign a JWT an RSA private key must be provided. The key must be DER
25-
encoded.
24+
In order to sign a JWT an RSA private key must be provided.
2625

27-
### Generate an RSA public/private key pair
26+
### In code
2827

29-
```
30-
ssh-keygen -m PEM -t rsa -b 4096 -f private.pem
31-
# Don't add passphrase
32-
```
28+
NoWayJose allows generating an RSA private key directly in code by specifying
29+
the number of bits and an encoding format (PEM or DER):
3330

34-
### Convert the PEM to DER
35-
36-
```
37-
openssl rsa -in private.pem -outform DER -out private.der
31+
```elixir
32+
# PEM encoded RSA private key
33+
NoWayJose.generate_rsa(4096, :pem)
3834
```
3935

40-
Optionally, you can extract the DER data from a PEM encoded private key in code
41-
using the following:
42-
4336
```elixir
44-
{:ok, key} = File.read("private.pem")
45-
[{:RSAPrivateKey, der, _}] = :public_key.pem_decode(key)
37+
# DER encoded RSA private key
38+
NoWayJose.generate_rsa(4096, :der)
4639
```
4740

4841
## Basic usage
4942

5043
```elixir
51-
# Get the private signing key
44+
# Read a private signing key from a file
5245
{:ok, key} = File.read("private.der")
5346

47+
# Or generate a new one in code
48+
key = NoWayJose.generate_rsa(4096, :der)
49+
5450
# Build your claims
5551
claims = %{
5652
"exp" => 1571065163,
@@ -66,7 +62,7 @@ claims = %{
6662
}
6763

6864
# Sign the claims into a JWT
69-
{:ok, token} = NoWayJose.sign(claims, key)
65+
{:ok, token} = NoWayJose.sign(claims, alg: :rs512, format: :der, key: key)
7066
```
7167

7268
## Documentation

lib/no_way_jose.ex

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,45 @@ defmodule NoWayJose do
44
a signing key.
55
"""
66

7+
require Logger
8+
79
@typedoc """
810
A map containing the claims to be encoded. Map keys must be strings.
911
"""
1012
@type claims :: %{binary() => term()}
1113

1214
@typedoc """
13-
DER encoded RSA private key.
15+
Algorithm used in JWT signing.
16+
"""
17+
@type alg :: :rs512
1418

15-
## Generating a key
19+
@typedoc """
20+
The format of the provided key.
21+
"""
22+
@type key_format :: :der | :pem
1623

17-
ssh-keygen -m PEM -t rsa -b 4096 -f private.pem
24+
@typedoc """
25+
Key Identifier – Acts as an alias for the key
26+
"""
27+
@type kid :: nil | binary()
28+
29+
@type signing_option ::
30+
{:alg, alg()}
31+
| {:format, key_format()}
32+
| {:key, key()}
33+
| {:kid, kid()}
1834

19-
Make sure not to set a passphrase.
35+
@type signing_options :: [signing_option()]
36+
37+
@typedoc """
38+
RSA private key.
2039
40+
The key can be either DER or PEM encoded.
2141
22-
## Convert the PEM to DER
42+
## Generating a key
2343
24-
openssl rsa -in private.pem -outform DER -out private.der
44+
der = NoWayJose.generate_rsa(4096, :der)
45+
pem = NoWayJose.generate_rsa(4096, :pem)
2546
2647
Optionally, you can extract the DER data from a PEM encoded private key in code
2748
using the following:
@@ -41,9 +62,9 @@ defmodule NoWayJose do
4162
4263
Returns a JWT on success and raises an error on error.
4364
"""
44-
@spec sign!(claims(), key()) :: token() | no_return()
45-
def sign!(claims, key) do
46-
case sign(claims, key) do
65+
@spec sign!(claims(), key() | signing_options()) :: token() | no_return()
66+
def sign!(claims, opts) do
67+
case sign(claims, opts) do
4768
{:ok, token} -> token
4869
{:error, error} -> raise error
4970
end
@@ -74,8 +95,51 @@ defmodule NoWayJose do
7495
# Sign the claims into a JWT
7596
{:ok, token} = NoWayJose.sign(claims, key)
7697
"""
77-
@spec sign(claims(), key()) :: {:ok, token()} | {:error, term()}
78-
def sign(claims, key) do
79-
NoWayJose.Native.sign(claims, key)
98+
@spec sign(claims(), signing_options()) :: {:ok, token()} | {:error, term()}
99+
def sign(claims, key) when is_binary(key) do
100+
Logger.warn(
101+
"Passing a binary key to sign/2 is deprecated. Please pass a list of signing options."
102+
)
103+
104+
opts = [alg: :rs512, format: :der, key: key]
105+
NoWayJose.Native.sign(claims, struct(NoWayJose.Signer, opts))
106+
end
107+
108+
@doc """
109+
Generates a signed JWT from the given claims and signing options.
110+
111+
## Example
112+
113+
# Get the private signing key
114+
{:ok, key} = File.read("private.pem")
115+
116+
# Build your claims
117+
claims = %{
118+
"exp" => 1571065163,
119+
"iat" => 1571061563,
120+
"iss" => "example.com",
121+
"jti" => "a3a31258-2450-490b-86ed-2b8e67f91e20",
122+
"nbf" => 1571061563,
123+
"scopes" => [
124+
"posts.r+w",
125+
"comments.r+w"
126+
],
127+
"sub" => "4d3796ca-19e0-40e6-97fe-060c0b7e3ce3"
128+
}
129+
130+
# Sign the claims into a JWT
131+
{:ok, token} = NoWayJose.sign(claims, alg: :rs512, key: key, format: :pem, kid: "1")
132+
"""
133+
@spec sign(claims(), signing_options()) :: {:ok, token()} | {:error, term()}
134+
def sign(claims, opts) when is_list(opts) do
135+
NoWayJose.Native.sign(claims, struct(NoWayJose.Signer, opts))
136+
end
137+
138+
@doc """
139+
Generates an RSA private key based on the given bit size and format.
140+
"""
141+
@spec generate_rsa(integer(), key_format()) :: binary()
142+
def(generate_rsa(bits, format)) do
143+
NoWayJose.Native.generate_rsa(bits, format)
80144
end
81145
end

lib/no_way_jose/native.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ defmodule NoWayJose.Native do
77

88
def sign(_claims, _signer), do: nif_error()
99

10+
def generate_rsa(_bits, _output), do: nif_error()
11+
1012
defp nif_error, do: :erlang.nif_error(:nif_not_loaded)
1113
end

lib/no_way_jose/signer.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule NoWayJose.Signer do
2+
@moduledoc false
3+
4+
defstruct alg: :rs512,
5+
key: nil,
6+
format: :der,
7+
kid: nil
8+
9+
@type t :: %__MODULE__{
10+
alg: NoWayJose.alg(),
11+
key: NoWayJose.key(),
12+
format: NoWayJose.key_format(),
13+
kid: NoWayJose.kid()
14+
}
15+
end

mix.exs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule NoWayJose.MixProject do
55
Rust NIF for signing JWTs
66
"""
77

8-
@version "0.1.0"
8+
@version "0.2.0"
99

1010
def project do
1111
[
@@ -20,20 +20,20 @@ defmodule NoWayJose.MixProject do
2020
"https://github.com/scrogson/no-way-jose/blob/v#{@version}/%{path}#L%{line}"
2121
],
2222
elixir: "~> 1.8",
23-
elixirc_paths: elixirc_paths(Mix.env()),
2423
name: "NoWayJose",
2524
package: [
2625
exclude_patterns: [
2726
~r/\W\.DS_Store$/,
28-
~r/target/,
29-
~r/nowayjose_testutils/
27+
~r/target/
3028
],
3129
files: ["lib", "native", "mix.exs", "README.md", "LICENSE"],
3230
licenses: ["Apache-2.0"],
3331
links: %{"GitHub" => "https://github.com/scrogson/no-way-jose"},
3432
maintainers: ["Sonny Scroggin"]
3533
],
36-
rustler_crates: rustler_crates(Mix.env()),
34+
rustler_crates: [
35+
nowayjose: []
36+
],
3737
start_permanent: Mix.env() == :prod,
3838
version: @version
3939
]
@@ -54,20 +54,4 @@ defmodule NoWayJose.MixProject do
5454
{:jason, "~> 1.0", only: [:dev, :test]}
5555
]
5656
end
57-
58-
defp elixirc_paths(:test), do: ["lib", "test/support"]
59-
defp elixirc_paths(_), do: ["lib"]
60-
61-
defp rustler_crates(:test) do
62-
[
63-
nowayjose: [],
64-
nowayjose_testutils: []
65-
]
66-
end
67-
68-
defp rustler_crates(_) do
69-
[
70-
nowayjose: []
71-
]
72-
end
7357
end

0 commit comments

Comments
 (0)