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
[