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
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
elixir: '1.17'
otp: '27.0'
lint: lint
- pair:
elixir: '1.18'
Comment thread
voltone marked this conversation as resolved.
otp: '28.0'
steps:
- uses: actions/checkout@v2

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ Requires Erlang/OTP 20.1 or later.
Development and public release of this package were made possible by
[Bluecode](https://bluecode.com/).

## OTP 28 support

Due to internal changes in the Erlang/OTP `public_key` application in OTP 28,
please beware that:

* When using OTP >= 28, please use OTP 28.0.1 or later; this package is not
compatible with the initial OTP 28.0 release (more specifically: it requires
`public_key` version 1.18.1, it will not work with 1.18)
* When compiled with OTP 27 or earlier, this package will not run on OTP 28
or later

## Usage

### As a Certificate Authority (CA)
Expand Down
97 changes: 56 additions & 41 deletions lib/x509/asn1.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,66 @@ defmodule X509.ASN1 do
@moduledoc false

require Record

alias X509.ASN1.OIDImport
alias X509.Util

if Util.app_version(:public_key) >= [1, 18] do
@additional_records [
tbs_cert_list_signature: :TBSCertList_signature,
certificate_list_algorithm_identifier: :CertificateList_algorithmIdentifier
]
else
@additional_records []
end

# Records to import from :public_key's HRL files, and their snake-case names
@records [
# RSA keys
rsa_private_key: :RSAPrivateKey,
rsa_public_key: :RSAPublicKey,
# RSA keys
rsa_private_key: :RSAPrivateKey,
rsa_public_key: :RSAPublicKey,

# EC keys
ec_private_key: :ECPrivateKey,
ec_point: :ECPoint,
# EC keys
ec_private_key: :ECPrivateKey,
ec_point: :ECPoint,

# PrivateKeyInfo and SPKI
private_key_info: :PrivateKeyInfo,
private_key_info_private_key_algorithm: :PrivateKeyInfo_privateKeyAlgorithm,
otp_subject_public_key_info: :OTPSubjectPublicKeyInfo,
subject_public_key_info: :SubjectPublicKeyInfo,
public_key_algorithm: :PublicKeyAlgorithm,
algorithm_identifier: :AlgorithmIdentifier,
# PrivateKeyInfo and SPKI
private_key_info: :PrivateKeyInfo,
private_key_info_private_key_algorithm: :PrivateKeyInfo_privateKeyAlgorithm,
otp_subject_public_key_info: :OTPSubjectPublicKeyInfo,
subject_public_key_info: :SubjectPublicKeyInfo,
public_key_algorithm: :PublicKeyAlgorithm,
algorithm_identifier: :AlgorithmIdentifier,

# Names (RDNs)
attribute_type_and_value: :AttributeTypeAndValue,
# Names (RDNs)
attribute_type_and_value: :AttributeTypeAndValue,

# CSRs
certification_request: :CertificationRequest,
certification_request_info: :CertificationRequestInfo,
certification_request_subject_pk_info: :CertificationRequestInfo_subjectPKInfo,
certification_request_subject_pk_info_algorithm:
:CertificationRequestInfo_subjectPKInfo_algorithm,
certification_request_signature_algorithm: :CertificationRequest_signatureAlgorithm,
certification_request_attribute: :"AttributePKCS-10",
# CSRs
certification_request: :CertificationRequest,
certification_request_info: :CertificationRequestInfo,
certification_request_subject_pk_info: :CertificationRequestInfo_subjectPKInfo,
certification_request_subject_pk_info_algorithm:
:CertificationRequestInfo_subjectPKInfo_algorithm,
certification_request_signature_algorithm: :CertificationRequest_signatureAlgorithm,
certification_request_attribute: :"AttributePKCS-10",

# Certificates
certificate: :Certificate,
otp_certificate: :OTPCertificate,
tbs_certificate: :TBSCertificate,
otp_tbs_certificate: :OTPTBSCertificate,
signature_algorithm: :SignatureAlgorithm,
validity: :Validity,
extension: :Extension,
basic_constraints: :BasicConstraints,
authority_key_identifier: :AuthorityKeyIdentifier,
access_description: :AccessDescription,
# Certificates
certificate: :Certificate,
otp_certificate: :OTPCertificate,
tbs_certificate: :TBSCertificate,
otp_tbs_certificate: :OTPTBSCertificate,
signature_algorithm: :SignatureAlgorithm,
validity: :Validity,
extension: :Extension,
basic_constraints: :BasicConstraints,
authority_key_identifier: :AuthorityKeyIdentifier,
access_description: :AccessDescription,

# CRLs
certificate_list: :CertificateList,
tbs_cert_list: :TBSCertList,
tbs_cert_list_revoked_certificate: :TBSCertList_revokedCertificates_SEQOF
]
# CRLs
certificate_list: :CertificateList,
tbs_cert_list: :TBSCertList,
tbs_cert_list_revoked_certificate: :TBSCertList_revokedCertificates_SEQOF
] ++ @additional_records

# The :ECPoint record is the only ASN.1 record defined in public_key.hrl;
# all other records are in either OTP-PUB-KEY.hrl or PKCS-FRAME.hrl
Expand Down Expand Up @@ -81,8 +92,12 @@ defmodule X509.ASN1 do
def null, do: open_type(<<5, 0>>)

# OIDs taken from :public_key's header files
@oids OIDImport.from_lib("public_key/include/OTP-PUB-KEY.hrl") ++
OIDImport.from_lib("public_key/include/PKCS-FRAME.hrl")
if Util.app_version(:public_key) >= [1, 18] do
@oids OIDImport.from_lib("public_key/include/public_key.hrl")
else
@oids OIDImport.from_lib("public_key/include/OTP-PUB-KEY.hrl") ++
OIDImport.from_lib("public_key/include/PKCS-FRAME.hrl")
end

# OIDs defined as a macro, so they may be used in pattern matching
defmacro oid(name) do
Expand Down
6 changes: 3 additions & 3 deletions lib/x509/certificate/extension.ex
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ defmodule X509.Certificate.Extension do
@doc false
# Intended for internal use only
def to_der(list) when is_list(list) do
:public_key.der_encode(:OTPExtensions, Enum.map(list, &encode/1))
:public_key.der_encode(:Extensions, Enum.map(list, &encode/1))
end

def to_der(extension() = ext) do
Expand All @@ -443,8 +443,8 @@ defmodule X509.Certificate.Extension do
# Intended for internal use only
def from_der!(der, type \\ :Extension)

def from_der!(der, :OTPExtensions) do
:public_key.der_decode(:OTPExtensions, der)
def from_der!(der, :Extensions) do
:public_key.der_decode(:Extensions, der)
|> Enum.map(&decode/1)
end

Expand Down
31 changes: 27 additions & 4 deletions lib/x509/crl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule X509.CRL do

alias X509.{Certificate, SignatureAlgorithm}
alias X509.CRL.{Entry, Extension}
alias X509.Util

@typedoc """
`:CertificateList` record, as used in Erlang's `:public_key` module
Expand Down Expand Up @@ -57,7 +58,7 @@ defmodule X509.CRL do
@spec new([Entry.t()], Certificate.t(), X509.PrivateKey.t(), Keyword.t()) :: t()
def new(revoked, issuer, issuer_key, opts \\ []) do
hash = Keyword.get(opts, :hash, :sha256)
algorithm = SignatureAlgorithm.new(hash, issuer_key, :AlgorithmIdentifier)
{algorithm1, algorithm2} = signature_algorithms(hash, issuer_key)

this_update =
opts
Expand Down Expand Up @@ -87,8 +88,8 @@ defmodule X509.CRL do
tbs =
tbs_cert_list(
version: :v2,
signature: algorithm,
issuer: issuer |> Certificate.subject() |> :pubkey_cert_records.transform(:encode),
signature: algorithm1,
issuer: issuer_subject(issuer),
thisUpdate: this_update,
nextUpdate: next_update,
revokedCertificates: revoked_certificates(revoked),
Expand All @@ -99,11 +100,33 @@ defmodule X509.CRL do

certificate_list(
tbsCertList: tbs,
signatureAlgorithm: algorithm,
signatureAlgorithm: algorithm2,
signature: :public_key.sign(tbs_der, hash, issuer_key)
)
end

if Util.app_version(:public_key) >= [1, 18] do
defp signature_algorithms(hash, issuer_key) do
{
SignatureAlgorithm.new(hash, issuer_key, :TBSCertList_signature),
SignatureAlgorithm.new(hash, issuer_key, :CertificateList_algorithmIdentifier)
}
end

defp issuer_subject(cert) do
cert |> Certificate.subject()
end
else
defp signature_algorithms(hash, issuer_key) do
algorithm = SignatureAlgorithm.new(hash, issuer_key, :AlgorithmIdentifier)
{algorithm, algorithm}
end

defp issuer_subject(cert) do
cert |> Certificate.subject() |> :pubkey_cert_records.transform(:encode)
end
end

@doc """
Verifies whether a CRL matches the given issuer certificate and has a valid
signature.
Expand Down
2 changes: 1 addition & 1 deletion lib/x509/csr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ defmodule X509.CSR do

case attribute do
certification_request_attribute(values: [asn1_OPENTYPE: der]) ->
Extension.from_der!(der, :OTPExtensions)
Extension.from_der!(der, :Extensions)

_ ->
[]
Expand Down
24 changes: 20 additions & 4 deletions lib/x509/public_key.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule X509.PublicKey do
import X509.ASN1

alias X509.Util

@moduledoc """
Functions for deriving, reading and writing RSA and EC public keys.
"""
Expand Down Expand Up @@ -57,8 +59,7 @@ defmodule X509.PublicKey do
algorithm:
algorithm_identifier(
algorithm: oid(:rsaEncryption),
# NULL, DER encoded
parameters: <<5, 0>>
parameters: maybe_encode_parameters(:NULL)
),
subjectPublicKey: :public_key.der_encode(:RSAPublicKey, public_key)
)
Expand All @@ -69,7 +70,7 @@ defmodule X509.PublicKey do
algorithm:
algorithm_identifier(
algorithm: oid(:"id-ecPublicKey"),
parameters: :public_key.der_encode(:EcpkParameters, parameters)
parameters: maybe_encode_parameters(parameters)
),
subjectPublicKey: public_key
)
Expand Down Expand Up @@ -119,6 +120,17 @@ defmodule X509.PublicKey do
)
end

if Util.app_version(:public_key) >= [1, 18] do
defp maybe_encode_parameters(:NULL), do: :NULL
defp maybe_encode_parameters(parameters), do: parameters
else
defp maybe_encode_parameters(:NULL), do: <<5, 0>>

defp maybe_encode_parameters(parameters) do
:public_key.der_encode(:EcpkParameters, parameters)
end
end

@doc """
Extracts a public key from a SubjectPublicKeyInfo style container.

Expand All @@ -130,8 +142,12 @@ defmodule X509.PublicKey do
algorithm_identifier(algorithm: oid(:rsaEncryption)) ->
:public_key.der_decode(:RSAPublicKey, public_key)

algorithm_identifier(algorithm: oid(:"id-ecPublicKey"), parameters: parameters) ->
algorithm_identifier(algorithm: oid(:"id-ecPublicKey"), parameters: parameters)
when is_binary(parameters) ->
{ec_point(point: public_key), :public_key.der_decode(:EcpkParameters, parameters)}

algorithm_identifier(algorithm: oid(:"id-ecPublicKey"), parameters: parameters) ->
{ec_point(point: public_key), parameters}
end
end

Expand Down
36 changes: 6 additions & 30 deletions lib/x509/rdn_sequence.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,35 +79,6 @@ defmodule X509.RDNSequence do
attribute types, when attribute values exceed the maximum length
('upper bound' in the RFC) or when values cannot be coerced into the
expected ASN.1 type.

## Examples:

iex> X509.RDNSequence.new("/C=US/CN=Bob")
{:rdnSequence,
[
[{:AttributeTypeAndValue, {2, 5, 4, 6}, <<19, 2, 85, 83>>}],
[{:AttributeTypeAndValue, {2, 5, 4, 3}, <<12, 3, 66, 111, 98>>}]
]}

iex> X509.RDNSequence.new("C=CN, givenName=麗")
{:rdnSequence,
[
[{:AttributeTypeAndValue, {2, 5, 4, 6}, <<19, 2, 67, 78>>}],
[{:AttributeTypeAndValue, {2, 5, 4, 42}, <<12, 3, 233, 186, 151>>}]
]}

iex> X509.RDNSequence.new(commonName: "Elixir")
{:rdnSequence,
[
[{:AttributeTypeAndValue, {2, 5, 4, 3}, <<12, 6, 69, 108, 105, 120, 105, 114>>}]
]}

iex> X509.RDNSequence.new(language: "Elixir")
** (FunctionClauseError) no function clause matching in X509.RDNSequence.new_attr/1

iex> X509.RDNSequence.new("C=!!")
** (ArgumentError) unsupported character(s) in `PrintableString` attribute

"""
@spec new(String.t() | attr_list(), :plain | :otp) :: t()
def new(rdn, type \\ :plain)
Expand Down Expand Up @@ -178,7 +149,7 @@ defmodule X509.RDNSequence do
def get_attr({:rdnSequence, sequence}, attr_type) do
oid = attr_type_to_oid(attr_type)

for {:AttributeTypeAndValue, ^oid, value} = attr <- List.flatten(sequence) do
for {_, ^oid, value} = attr <- List.flatten(sequence) do
if is_binary(value) do
# FIXME: avoid calls to undocumented functions in :public_key app
{_, _, value} = :pubkey_cert_records.transform(attr, :decode)
Expand Down Expand Up @@ -252,6 +223,10 @@ defmodule X509.RDNSequence do
attr_oid_to_string(oid) <> "=" <> attr_value_to_string(value)
end

defp attr_to_string({:SingleAttribute, oid, value}) do
attr_oid_to_string(oid) <> "=" <> attr_value_to_string(value)
end

defp attr_oid_to_string(oid(:"id-at-countryName")), do: "C"
defp attr_oid_to_string(oid(:"id-at-organizationName")), do: "O"
defp attr_oid_to_string(oid(:"id-at-organizationalUnitName")), do: "OU"
Expand All @@ -277,6 +252,7 @@ defmodule X509.RDNSequence do
|> Enum.join(".")
end

defp attr_value_to_string({:correct, value}), do: List.to_string(value)
defp attr_value_to_string({:utf8String, value}), do: value
defp attr_value_to_string({:printableString, value}), do: List.to_string(value)
defp attr_value_to_string({:ia5String, value}), do: List.to_string(value)
Expand Down
Loading
Loading