changed
CHANGELOG.md
|
@@ -1,5 +1,12 @@
|
1
1
|
# Changes
|
2
2
|
|
3
|
+ ## v0.5.2
|
4
|
+
|
5
|
+ ### Enhancements
|
6
|
+
|
7
|
+ * [X509.Certificate] Add `version/1`, `subject/2` and `issuer/2`
|
8
|
+ * [X509.RDNSequence] Add `get_attr/2`
|
9
|
+
|
3
10
|
## v0.5.1
|
4
11
|
|
5
12
|
### Fixes
|
changed
README.md
|
@@ -90,7 +90,7 @@ Add `x509` to your list of dependencies in `mix.exs`:
|
90
90
|
```elixir
|
91
91
|
def deps do
|
92
92
|
[
|
93
|
- {:x509, "~> 0.5.0"}
|
93
|
+ {:x509, "~> 0.5.2"}
|
94
94
|
]
|
95
95
|
end
|
96
96
|
```
|
changed
hex_metadata.config
|
@@ -7,32 +7,33 @@
|
7
7
|
[<<"lib">>,<<"lib/mix">>,<<"lib/mix/tasks">>,
|
8
8
|
<<"lib/mix/tasks/x509.gen.selfsigned.ex">>,
|
9
9
|
<<"lib/mix/tasks/x509.gen.suite.ex">>,
|
10
|
- <<"lib/mix/tasks/x509.test_server.ex">>,<<"lib/x509">>,<<"lib/x509.ex">>,
|
11
|
- <<"lib/x509/asn1">>,<<"lib/x509/asn1.ex">>,
|
10
|
+ <<"lib/mix/tasks/x509.test_server.ex">>,<<"lib/x509">>,<<"lib/x509/asn1">>,
|
12
11
|
<<"lib/x509/asn1/oid_import.ex">>,<<"lib/x509/certificate">>,
|
13
|
- <<"lib/x509/certificate.ex">>,<<"lib/x509/certificate/extension.ex">>,
|
14
12
|
<<"lib/x509/certificate/template.ex">>,
|
15
|
- <<"lib/x509/certificate/validity.ex">>,<<"lib/x509/crl">>,
|
16
|
- <<"lib/x509/crl.ex">>,<<"lib/x509/crl/entry.ex">>,
|
17
|
- <<"lib/x509/crl/extension.ex">>,<<"lib/x509/csr.ex">>,
|
18
|
- <<"lib/x509/date_time.ex">>,<<"lib/x509/private_key.ex">>,
|
19
|
- <<"lib/x509/public_key.ex">>,<<"lib/x509/rdn_sequence.ex">>,
|
20
|
- <<"lib/x509/signature_algorithm.ex">>,<<"lib/x509/test">>,
|
21
|
- <<"lib/x509/test/crl_server.ex">>,<<"lib/x509/test/server.ex">>,
|
22
|
- <<"lib/x509/test/suite.ex">>,<<"priv">>,<<"priv/cert">>,
|
23
|
- <<"priv/cert/suite">>,<<"priv/cert/suite/alternate_cacerts.pem">>,
|
24
|
- <<"priv/cert/suite/alternate_chain.pem">>,<<"priv/cert/suite/cacerts.pem">>,
|
25
|
- <<"priv/cert/suite/chain.pem">>,<<"priv/cert/suite/expired.pem">>,
|
26
|
- <<"priv/cert/suite/expired_chain.pem">>,<<"priv/cert/suite/other_key.pem">>,
|
27
|
- <<"priv/cert/suite/revoked.pem">>,<<"priv/cert/suite/revoked_chain.pem">>,
|
28
|
- <<"priv/cert/suite/selfsigned.crt">>,<<"priv/cert/suite/selfsigned.pem">>,
|
29
|
- <<"priv/cert/suite/server_key.pem">>,<<"priv/cert/suite/test.pem">>,
|
30
|
- <<"priv/cert/suite/valid.p12">>,<<"priv/cert/suite/valid.pem">>,
|
31
|
- <<"priv/cert/suite/valid_with_chain.pem">>,
|
32
|
- <<"priv/cert/suite/wildcard.pem">>,<<".formatter.exs">>,<<"mix.exs">>,
|
33
|
- <<"README.md">>,<<"LICENSE">>,<<"CHANGELOG.md">>]}.
|
13
|
+ <<"lib/x509/certificate/extension.ex">>,
|
14
|
+ <<"lib/x509/certificate/validity.ex">>,
|
15
|
+ <<"lib/x509/signature_algorithm.ex">>,<<"lib/x509/crl">>,
|
16
|
+ <<"lib/x509/crl/entry.ex">>,<<"lib/x509/crl/extension.ex">>,
|
17
|
+ <<"lib/x509/test">>,<<"lib/x509/test/crl_server.ex">>,
|
18
|
+ <<"lib/x509/test/server.ex">>,<<"lib/x509/test/suite.ex">>,
|
19
|
+ <<"lib/x509/date_time.ex">>,<<"lib/x509/asn1.ex">>,<<"lib/x509/crl.ex">>,
|
20
|
+ <<"lib/x509/csr.ex">>,<<"lib/x509/public_key.ex">>,
|
21
|
+ <<"lib/x509/certificate.ex">>,<<"lib/x509/private_key.ex">>,
|
22
|
+ <<"lib/x509/rdn_sequence.ex">>,<<"lib/x509.ex">>,<<"priv">>,<<"priv/cert">>,
|
23
|
+ <<"priv/cert/suite">>,<<"priv/cert/suite/server_key.pem">>,
|
24
|
+ <<"priv/cert/suite/other_key.pem">>,<<"priv/cert/suite/cacerts.pem">>,
|
25
|
+ <<"priv/cert/suite/alternate_cacerts.pem">>,<<"priv/cert/suite/chain.pem">>,
|
26
|
+ <<"priv/cert/suite/expired_chain.pem">>,
|
27
|
+ <<"priv/cert/suite/revoked_chain.pem">>,
|
28
|
+ <<"priv/cert/suite/alternate_chain.pem">>,<<"priv/cert/suite/valid.pem">>,
|
29
|
+ <<"priv/cert/suite/wildcard.pem">>,<<"priv/cert/suite/expired.pem">>,
|
30
|
+ <<"priv/cert/suite/revoked.pem">>,<<"priv/cert/suite/selfsigned.pem">>,
|
31
|
+ <<"priv/cert/suite/valid.p12">>,<<"priv/cert/suite/valid_with_chain.pem">>,
|
32
|
+ <<"priv/cert/suite/selfsigned.crt">>,<<"priv/cert/suite/test.pem">>,
|
33
|
+ <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,
|
34
|
+ <<"CHANGELOG.md">>]}.
|
34
35
|
{<<"licenses">>,[<<"BSD 3-Clause">>]}.
|
35
36
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/voltone/x509">>}]}.
|
36
37
|
{<<"name">>,<<"x509">>}.
|
37
38
|
{<<"requirements">>,[]}.
|
38
|
- {<<"version">>,<<"0.5.1">>}.
|
39
|
+ {<<"version">>,<<"0.5.2">>}.
|
changed
lib/x509/certificate.ex
|
@@ -139,6 +139,20 @@ defmodule X509.Certificate do
|
139
139
|
|> from_der!()
|
140
140
|
end
|
141
141
|
|
142
|
+ @doc """
|
143
|
+ Returns the Version field of a certificate.
|
144
|
+
|
145
|
+ Returns the X.509 certificate version as an atom, e.g. `:v3`.
|
146
|
+ """
|
147
|
+ @spec version(t()) :: atom()
|
148
|
+ def version(certificate(tbsCertificate: tbs)) do
|
149
|
+ tbs_certificate(tbs, :version)
|
150
|
+ end
|
151
|
+
|
152
|
+ def version(otp_certificate(tbsCertificate: tbs)) do
|
153
|
+ otp_tbs_certificate(tbs, :version)
|
154
|
+ end
|
155
|
+
|
142
156
|
@doc """
|
143
157
|
Returns the Subject field of a certificate.
|
144
158
|
"""
|
|
@@ -151,6 +165,18 @@ defmodule X509.Certificate do
|
151
165
|
otp_tbs_certificate(tbs, :subject)
|
152
166
|
end
|
153
167
|
|
168
|
+ @doc """
|
169
|
+ Returns attribute values of the Subject field of a certificate.
|
170
|
+
|
171
|
+ See also `X509.RDNSequence.get_attr/2`.
|
172
|
+ """
|
173
|
+ @spec subject(t(), binary() | :public_key.oid()) :: [String.t()]
|
174
|
+ def subject(cert, attr_type) do
|
175
|
+ cert
|
176
|
+ |> subject()
|
177
|
+ |> X509.RDNSequence.get_attr(attr_type)
|
178
|
+ end
|
179
|
+
|
154
180
|
@doc """
|
155
181
|
Returns the Issuer field of a certificate.
|
156
182
|
"""
|
|
@@ -163,6 +189,18 @@ defmodule X509.Certificate do
|
163
189
|
otp_tbs_certificate(tbs, :issuer)
|
164
190
|
end
|
165
191
|
|
192
|
+ @doc """
|
193
|
+ Returns attribute values of the Issuer field of a certificate.
|
194
|
+
|
195
|
+ See also `X509.RDNSequence.get_attr/2`.
|
196
|
+ """
|
197
|
+ @spec issuer(t(), binary() | :public_key.oid()) :: [String.t()]
|
198
|
+ def issuer(cert, attr_type) do
|
199
|
+ cert
|
200
|
+ |> issuer()
|
201
|
+ |> X509.RDNSequence.get_attr(attr_type)
|
202
|
+ end
|
203
|
+
|
166
204
|
@doc """
|
167
205
|
Returns the Validity of a certificate.
|
168
206
|
"""
|
changed
lib/x509/certificate/extension.ex
|
@@ -124,7 +124,7 @@ defmodule X509.Certificate.Extension do
|
124
124
|
{:Extension, {2, 5, 29, 37}, false,
|
125
125
|
[{1, 3, 6, 1, 5, 5, 7, 3, 1}, {1, 3, 6, 1, 5, 5, 7, 3, 2}]}
|
126
126
|
"""
|
127
|
- @spec ext_key_usage([:atom | :public_key.oid()]) :: t()
|
127
|
+ @spec ext_key_usage([atom() | :public_key.oid()]) :: t()
|
128
128
|
def ext_key_usage(list) do
|
129
129
|
extension(
|
130
130
|
extnID: oid(:"id-ce-extKeyUsage"),
|
changed
lib/x509/certificate/validity.ex
|
@@ -52,7 +52,7 @@ defmodule X509.Certificate.Validity do
|
52
52
|
value that does not reveal the exact time when the keypair was generated.
|
53
53
|
This minimizes information leakage about the state of the RNG.
|
54
54
|
"""
|
55
|
- @spec days_from_now(pos_integer(), non_neg_integer()) :: t()
|
55
|
+ @spec days_from_now(integer(), non_neg_integer()) :: t()
|
56
56
|
def days_from_now(days, backdate_seconds \\ @default_backdate_seconds) do
|
57
57
|
validity(
|
58
58
|
notBefore: X509.DateTime.new(-backdate_seconds),
|
changed
lib/x509/private_key.ex
|
@@ -24,6 +24,10 @@ defmodule X509.PrivateKey do
|
24
24
|
iex> :public_key.verify(message, :sha256, signature, public_key)
|
25
25
|
true
|
26
26
|
|
27
|
+ Note that in practice it is not a good idea to directly encrypt a message
|
28
|
+ with asymmetrical cryptography, and signatures should be calculated over
|
29
|
+ message hashes rather than raw messages. The examples above are deliberate
|
30
|
+ over-simpliciations intended to highlight the `:crypto` API calls.
|
27
31
|
"""
|
28
32
|
|
29
33
|
@typedoc "RSA or EC private key"
|
changed
lib/x509/rdn_sequence.ex
|
@@ -155,6 +155,92 @@ defmodule X509.RDNSequence do
|
155
155
|
|> Enum.join("/"))
|
156
156
|
end
|
157
157
|
|
158
|
+ @doc """
|
159
|
+ Extracts the values for the specified attributes from a `:rdnSquence` tuple.
|
160
|
+
|
161
|
+ The attribute type may be specified as an attribute name (long or short form,
|
162
|
+ as a string, or long from as an atom) or an OID tuple. Refer to the
|
163
|
+ documentation at the top of this module for a list of supported attributes.
|
164
|
+
|
165
|
+ Since an attribute may appear more than once in an RDN sequence the result is
|
166
|
+ a list of values.
|
167
|
+
|
168
|
+ ## Examples:
|
169
|
+
|
170
|
+ iex> X509.RDNSequence.new("/C=US/CN=Bob") |> X509.RDNSequence.get_attr(:countryName)
|
171
|
+ ["US"]
|
172
|
+ iex> X509.RDNSequence.new("/C=US/CN=Bob") |> X509.RDNSequence.get_attr("commonName")
|
173
|
+ ["Bob"]
|
174
|
+ iex> X509.RDNSequence.new("C=CN, givenName=麗") |> X509.RDNSequence.get_attr("GN")
|
175
|
+ ["麗"]
|
176
|
+ """
|
177
|
+ @spec get_attr(t(), binary() | atom() | :public_key.oid()) :: [String.t()]
|
178
|
+ def get_attr({:rdnSequence, sequence}, attr_type) do
|
179
|
+ oid = attr_type_to_oid(attr_type)
|
180
|
+
|
181
|
+ for {:AttributeTypeAndValue, ^oid, value} = attr <- List.flatten(sequence) do
|
182
|
+ if is_binary(value) do
|
183
|
+ # FIXME: avoid calls to undocumented functions in :public_key app
|
184
|
+ {_, _, value} = :pubkey_cert_records.transform(attr, :decode)
|
185
|
+ attr_value_to_string(value)
|
186
|
+ else
|
187
|
+ attr_value_to_string(value)
|
188
|
+ end
|
189
|
+ end
|
190
|
+ end
|
191
|
+
|
192
|
+ defp attr_type_to_oid(oid) when is_tuple(oid), do: oid
|
193
|
+
|
194
|
+ defp attr_type_to_oid(type) when type in ["countryName", "C", :countryName],
|
195
|
+ do: oid(:"id-at-countryName")
|
196
|
+
|
197
|
+ defp attr_type_to_oid(type) when type in ["organizationName", "O", :organizationName],
|
198
|
+ do: oid(:"id-at-organizationName")
|
199
|
+
|
200
|
+ defp attr_type_to_oid(type)
|
201
|
+ when type in ["organizationalUnitName", "OU", :organizationalUnitName],
|
202
|
+ do: oid(:"id-at-organizationalUnitName")
|
203
|
+
|
204
|
+ defp attr_type_to_oid(type) when type in ["dnQualifier", :dnQualifier],
|
205
|
+ do: oid(:"id-at-dnQualifier")
|
206
|
+
|
207
|
+ defp attr_type_to_oid(type)
|
208
|
+ when type in ["stateOrProvinceName", "ST", :stateOrProvinceName],
|
209
|
+ do: oid(:"id-at-stateOrProvinceName")
|
210
|
+
|
211
|
+ defp attr_type_to_oid(type) when type in ["commonName", "CN", :commonName],
|
212
|
+ do: oid(:"id-at-commonName")
|
213
|
+
|
214
|
+ defp attr_type_to_oid(type) when type in ["serialNumber", :serialNumber],
|
215
|
+ do: oid(:"id-at-serialNumber")
|
216
|
+
|
217
|
+ defp attr_type_to_oid(type) when type in ["localityName", "L", :localityName],
|
218
|
+ do: oid(:"id-at-localityName")
|
219
|
+
|
220
|
+ defp attr_type_to_oid(type) when type in ["title", :title], do: oid(:"id-at-title")
|
221
|
+ defp attr_type_to_oid(type) when type in ["name", :name], do: oid(:"id-at-name")
|
222
|
+
|
223
|
+ defp attr_type_to_oid(type) when type in ["surname", "SN", :surname],
|
224
|
+ do: oid(:"id-at-surname")
|
225
|
+
|
226
|
+ defp attr_type_to_oid(type) when type in ["givenName", "GN", :givenName],
|
227
|
+ do: oid(:"id-at-givenName")
|
228
|
+
|
229
|
+ defp attr_type_to_oid(type) when type in ["initials", :initials],
|
230
|
+ do: oid(:"id-at-initials")
|
231
|
+
|
232
|
+ defp attr_type_to_oid(type) when type in ["pseudonym", :pseudonym],
|
233
|
+ do: oid(:"id-at-pseudonym")
|
234
|
+
|
235
|
+ defp attr_type_to_oid(type) when type in ["generationQualifier", :generationQualifier],
|
236
|
+ do: oid(:"id-at-generationQualifier")
|
237
|
+
|
238
|
+ defp attr_type_to_oid(type) when type in ["domainComponent", "DC", :domainComponent],
|
239
|
+ do: oid(:"id-domainComponent")
|
240
|
+
|
241
|
+ defp attr_type_to_oid(type) when type in ["emailAddress", "E", :emailAddress],
|
242
|
+ do: oid(:"id-emailAddress")
|
243
|
+
|
158
244
|
defp attr_to_string({:AttributeTypeAndValue, _, value} = attr) when is_binary(value) do
|
159
245
|
# FIXME: avoid calls to undocumented functions in :public_key app
|
160
246
|
attr
|
changed
lib/x509/test/suite.ex
|
@@ -126,7 +126,7 @@ defmodule X509.Test.Suite do
|
126
126
|
ECC keys based on the given curve (default:
|
127
127
|
`#{inspect(@default_opts[:key_type])}`)
|
128
128
|
"""
|
129
|
- @spec new([Keyword.t()]) :: t()
|
129
|
+ @spec new(Keyword.t()) :: t()
|
130
130
|
def new(opts \\ []) do
|
131
131
|
opts = Keyword.merge(@default_opts, opts)
|
132
132
|
crl_server = Keyword.get(opts, :crl_server)
|
|
@@ -161,7 +161,7 @@ defmodule X509.Test.Suite do
|
161
161
|
|> X509.PublicKey.derive()
|
162
162
|
|> X509.Certificate.new("/O=#{__MODULE__}/CN=Intermediate CA", root_ca, root_ca_key,
|
163
163
|
template: :ca,
|
164
|
- validity: X509.Certificate.Validity.days_from_now(-1, -30 * @seconds_per_day),
|
164
|
+ validity: X509.Certificate.Validity.days_from_now(-1, 30 * @seconds_per_day),
|
165
165
|
extensions: crl_extensions(crl_server, "root_ca.crl")
|
166
166
|
)
|
167
167
|
|
|
@@ -252,7 +252,7 @@ defmodule X509.Test.Suite do
|
252
252
|
"/O=#{__MODULE__}/CN=Expired",
|
253
253
|
intermediate_ca,
|
254
254
|
intermediate_ca_key,
|
255
|
- validity: X509.Certificate.Validity.days_from_now(-1, -30 * @seconds_per_day),
|
255
|
+ validity: X509.Certificate.Validity.days_from_now(-1, 30 * @seconds_per_day),
|
256
256
|
extensions:
|
257
257
|
[
|
258
258
|
subject_alt_name:
|
changed
mix.exs
|
@@ -1,7 +1,7 @@
|
1
1
|
defmodule X509.MixProject do
|
2
2
|
use Mix.Project
|
3
3
|
|
4
|
- @version "0.5.1"
|
4
|
+ @version "0.5.2"
|
5
5
|
|
6
6
|
def project do
|
7
7
|
[
|