Skip to content
Merged
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
8 changes: 8 additions & 0 deletions lib/x509/signature_algorithm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule X509.SignatureAlgorithm do

alias X509.Util

@edwards_curves [oid(:"id-Ed25519"), oid(:"id-Ed448")]

# Returns a signature algorithm record for the given public key type and hash
# algorithm; this is essentially the reverse of
# `:public_key.pkix_sign_types/1`
Expand All @@ -15,6 +17,11 @@ defmodule X509.SignatureAlgorithm do
new(hash, algorithm, type)
end

def new(hash, ec_private_key(parameters: {:namedCurve, curve}), type)
when curve in @edwards_curves do
new(hash, {:eddsa, curve}, type)
end

def new(hash, rsa_private_key(), type) do
new(hash, :rsa, type)
end
Expand Down Expand Up @@ -81,6 +88,7 @@ defmodule X509.SignatureAlgorithm do
defp algorithm(:sha384, :ecdsa), do: {oid(:"ecdsa-with-SHA384"), :asn1_NOVALUE}
defp algorithm(:sha512, :rsa), do: {oid(:sha512WithRSAEncryption), null()}
defp algorithm(:sha512, :ecdsa), do: {oid(:"ecdsa-with-SHA512"), :asn1_NOVALUE}
defp algorithm(_, {:eddsa, curve}), do: {curve, :asn1_NOVALUE}

defp algorithm(hash, :rsa) do
raise ArgumentError, "Unsupported hashing algorithm for RSA signing: #{inspect(hash)}"
Expand Down
1 change: 1 addition & 0 deletions test/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ Generating PEM output:
```bash
openssl req -new -key rsa.pem -days 365 -x509 -subj "/C=US/ST=NT/L=Springfield/O=ACME Inc." -out selfsigned_rsa.pem
openssl req -new -key prime256v1.pem -days 365 -x509 -subj "/C=US/ST=NT/L=Springfield/O=ACME Inc." -out selfsigned_prime256v1.pem
openssl req -new -key ed25519.pem -days 365 -x509 -subj "/C=US/ST=NT/L=Springfield/O=ACME Inc." -out selfsigned_ed25519.pem
```
11 changes: 11 additions & 0 deletions test/data/selfsigned_ed25519.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBnTCCAU+gAwIBAgIUeEEI1nx0VCdQZpYoyu3TVWuRJEQwBQYDK2VwMEQxCzAJ
BgNVBAYTAlVTMQswCQYDVQQIDAJOVDEUMBIGA1UEBwwLU3ByaW5nZmllbGQxEjAQ
BgNVBAoMCUFDTUUgSW5jLjAeFw0yNTA2MjYwMTMzMDFaFw0yNjA2MjYwMTMzMDFa
MEQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOVDEUMBIGA1UEBwwLU3ByaW5nZmll
bGQxEjAQBgNVBAoMCUFDTUUgSW5jLjAqMAUGAytlcAMhAC+GmWDNmLW56fepwflW
0gruk9GOn8NuXHwMH3orNzRNo1MwUTAdBgNVHQ4EFgQUpMRJar2ZQODRQeO6oTs5
LNyci4swHwYDVR0jBBgwFoAUpMRJar2ZQODRQeO6oTs5LNyci4swDwYDVR0TAQH/
BAUwAwEB/zAFBgMrZXADQQDOCrEui+5Na4Az6wLFUyAOTZtUbFfGqaLuf2e11zwy
zqJTjVgIEtGGkq0sXy38aQThOp39F86Kvusl+dbPA8AD
-----END CERTIFICATE-----
112 changes: 112 additions & 0 deletions test/integration/openssl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@ defmodule X509.OpenSSLTest do
assert openssl_out =~ "DNS:acme.com, DNS:www.acme.com"
end

test "OpenSSL can read certificates (EdDSA)" do
file =
X509.PrivateKey.new_ec(:ed25519)
|> X509.Certificate.self_signed(
"/C=US/ST=NT/L=Springfield/O=ACME Inc.",
extensions: [
subject_alt_name:
X509.Certificate.Extension.subject_alt_name(["acme.com", "www.acme.com"])
]
)
|> X509.Certificate.to_pem()
|> write_tmp()

openssl_out = openssl(["x509", "-in", file, "-text", "-noout"])
assert openssl_out =~ ~r(Subject: C ?= ?US, ST ?= ?NT, L ?= ?Springfield, O ?= ?ACME Inc.)
assert openssl_out =~ "Public Key Algorithm: ED25519"
assert openssl_out =~ "Signature Algorithm: ED25519"
assert openssl_out =~ "DNS:acme.com, DNS:www.acme.com"
end

test "OpenSSL can read CRLs (RSA)" do
ca_key = X509.PrivateKey.new_rsa(512)
ca = X509.Certificate.self_signed(ca_key, "/CN=My Root CA", template: :root_ca)
Expand Down Expand Up @@ -212,6 +232,42 @@ defmodule X509.OpenSSLTest do
assert openssl_out =~ "Serial Number: FF"
assert openssl_out =~ "Key Compromise"
end

test "OpenSSL can read CRLs (EdDSA)" do
ca_key = X509.PrivateKey.new_ec(:ed25519)
ca = X509.Certificate.self_signed(ca_key, "/CN=My Root CA", template: :root_ca)

cert =
X509.PrivateKey.new_ec(:ed25519)
|> X509.PublicKey.derive()
|> X509.Certificate.new("/CN=Sample", ca, ca_key,
serial: 0xFF,
extensions: [
crl_distribution_points:
X509.Certificate.Extension.crl_distribution_points(["http://localhost/test.crl"])
]
)

entry =
X509.CRL.Entry.new(cert, DateTime.utc_now(), [
X509.CRL.Extension.reason_code(:keyCompromise)
])

file =
entry
|> List.wrap()
|> X509.CRL.new(ca, ca_key)
|> X509.CRL.to_pem()
|> write_tmp()

openssl_out = openssl(["crl", "-in", file, "-text", "-noout"])
assert openssl_out =~ "Certificate Revocation List (CRL)"
assert openssl_out =~ "Signature Algorithm: ED25519"
assert openssl_out =~ ~r(Issuer: /?CN ?= ?My Root CA)
assert openssl_out =~ "X509v3 Authority Key Identifier:"
assert openssl_out =~ "Serial Number: FF"
assert openssl_out =~ "Key Compromise"
end
end

describe "DER encode" do
Expand Down Expand Up @@ -333,6 +389,26 @@ defmodule X509.OpenSSLTest do
assert openssl_out =~ "DNS:acme.com, DNS:www.acme.com"
end

test "OpenSSL can read certificates (EdDSA)" do
file =
X509.PrivateKey.new_ec(:ed25519)
|> X509.Certificate.self_signed(
"/C=US/ST=NT/L=Springfield/O=ACME Inc.",
extensions: [
subject_alt_name:
X509.Certificate.Extension.subject_alt_name(["acme.com", "www.acme.com"])
]
)
|> X509.Certificate.to_der()
|> write_tmp()

openssl_out = openssl(["x509", "-in", file, "-inform", "der", "-text", "-noout"])
assert openssl_out =~ ~r(Subject: C ?= ?US, ST ?= ?NT, L ?= ?Springfield, O ?= ?ACME Inc.)
assert openssl_out =~ "Public Key Algorithm: ED25519"
assert openssl_out =~ "Signature Algorithm: ED25519"
assert openssl_out =~ "DNS:acme.com, DNS:www.acme.com"
end

test "OpenSSL can read CRLs (RSA)" do
ca_key = X509.PrivateKey.new_rsa(512)
ca = X509.Certificate.self_signed(ca_key, "/CN=My Root CA", template: :root_ca)
Expand Down Expand Up @@ -404,6 +480,42 @@ defmodule X509.OpenSSLTest do
assert openssl_out =~ "Serial Number: FF"
assert openssl_out =~ "Key Compromise"
end

test "OpenSSL can read CRLs (EdDSA)" do
ca_key = X509.PrivateKey.new_ec(:ed25519)
ca = X509.Certificate.self_signed(ca_key, "/CN=My Root CA", template: :root_ca)

cert =
X509.PrivateKey.new_ec(:ed25519)
|> X509.PublicKey.derive()
|> X509.Certificate.new("/CN=Sample", ca, ca_key,
serial: 0xFF,
extensions: [
crl_distribution_points:
X509.Certificate.Extension.crl_distribution_points(["http://localhost/test.crl"])
]
)

entry =
X509.CRL.Entry.new(cert, DateTime.utc_now(), [
X509.CRL.Extension.reason_code(:keyCompromise)
])

file =
entry
|> List.wrap()
|> X509.CRL.new(ca, ca_key)
|> X509.CRL.to_der()
|> write_tmp()

openssl_out = openssl(["crl", "-in", file, "-inform", "der", "-text", "-noout"])
assert openssl_out =~ "Certificate Revocation List (CRL)"
assert openssl_out =~ "Signature Algorithm: ED25519"
assert openssl_out =~ ~r(Issuer: /?CN ?= ?My Root CA)
assert openssl_out =~ "X509v3 Authority Key Identifier:"
assert openssl_out =~ "Serial Number: FF"
assert openssl_out =~ "Key Compromise"
end
end

defp openssl(args) do
Expand Down
78 changes: 78 additions & 0 deletions test/x509/certificate_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule X509.CertificateTest do
[
rsa_key: X509.PrivateKey.new_rsa(512),
ec_key: X509.PrivateKey.new_ec(:secp256r1),
eddsa_key: X509.PrivateKey.new_ec(:ed25519),
selfsigned_rsa:
"test/data/selfsigned_rsa.pem"
|> File.read!()
Expand All @@ -23,6 +24,14 @@ defmodule X509.CertificateTest do
selfsigned_ecdsa_key:
"test/data/prime256v1.pem"
|> File.read!()
|> X509.PrivateKey.from_pem!(),
selfsigned_eddsa:
"test/data/selfsigned_ed25519.pem"
|> File.read!()
|> X509.Certificate.from_pem!(),
selfsigned_eddsa_key:
"test/data/ed25519.pem"
|> File.read!()
|> X509.PrivateKey.from_pem!()
]
end
Expand Down Expand Up @@ -192,6 +201,75 @@ defmodule X509.CertificateTest do
end
end

describe "EdDSA" do
test :new, context do
cert =
context.eddsa_key
|> X509.PublicKey.derive()
|> X509.Certificate.new(
"/C=US/ST=NT/L=Springfield/O=ACME Inc./CN=Example",
context.selfsigned_eddsa,
context.selfsigned_eddsa_key
)

assert match?(otp_certificate(), cert)
refute :public_key.pkix_is_self_signed(cert)
assert :public_key.pkix_is_issuer(cert, context.selfsigned_eddsa)
assert {:ok, _} = :public_key.pkix_path_validation(context.selfsigned_eddsa, [cert], [])

assert cert
|> X509.Certificate.to_der()
|> :public_key.pkix_verify(X509.PublicKey.derive(context.selfsigned_eddsa_key))
end

test "intermediate", context do
root_key = X509.PrivateKey.new_ec(:ed25519)

root =
root_key
|> X509.Certificate.self_signed(
"/C=US/ST=NT/L=Springfield/O=ACME Inc./CN=Intermediate CA",
template: :root_ca
)

intermediate_key = X509.PrivateKey.new_ec(:ed25519)

intermediate =
intermediate_key
|> X509.PublicKey.derive()
|> X509.Certificate.new(
"/C=US/ST=NT/L=Springfield/O=ACME Inc./CN=Intermediate CA",
root,
root_key,
template: :ca
)

cert =
context.eddsa_key
|> X509.PublicKey.derive()
|> X509.Certificate.new(
"/C=US/ST=NT/L=Springfield/O=ACME Inc./CN=Example",
intermediate,
intermediate_key
)

assert {:ok, _} = :public_key.pkix_path_validation(root, [intermediate, cert], [])
end

test :self_signed, context do
cert =
context.eddsa_key
|> X509.Certificate.self_signed("/C=US/ST=NT/L=Springfield/O=ACME Inc.")

assert match?(otp_certificate(), cert)
assert :public_key.pkix_is_self_signed(cert)

assert cert
|> X509.Certificate.to_der()
|> :public_key.pkix_verify(X509.PublicKey.derive(context.eddsa_key))
end
end

test :version, context do
assert :v3 == X509.Certificate.version(context.selfsigned_rsa)
end
Expand Down