voltone / x509

Elixir package for working with X.509 certificates, Certificate Signing Requests (CSRs), Certificate Revocation Lists (CRLs) and RSA/ECC key pairs
BSD 3-Clause "New" or "Revised" License
120 stars 28 forks source link

Add support for additional extensions #4

Open voltone opened 6 years ago

voltone commented 6 years ago

Such as cRLDistributionPoints, authorityInfoAccess (AIA) and certificatePolicies...

GalaxyGorilla commented 3 years ago

@voltone Have you ever figured out how to define custom extensions with Erlang's certificate support?

voltone commented 3 years ago

Some extensions are already decoded by Erlang's :public_key, such as those mentioned at the top of this ticket, I just haven't added convenience functions to X509.Certificate.Extension for them yet. You can still look them up and work with the Erlang record directly, e.g.:

iex(3)> aia = {1, 3, 6, 1, 5, 5, 7, 1, 1}
iex(4)> pem |> X509.Certificate.from_pem!() |> X509.Certificate.extension(aia)
{:Extension, {1, 3, 6, 1, 5, 5, 7, 1, 1}, false,
 [
   {:AccessDescription, {1, 3, 6, 1, 5, 5, 7, 48, 1},
    {:uniformResourceIdentifier, 'http://ocsp.starfieldtech.com/'}},
   {:AccessDescription, {1, 3, 6, 1, 5, 5, 7, 48, 2},
    {:uniformResourceIdentifier,
     'http://certificates.starfieldtech.com/repository/sfig2.crt'}}
 ]}

Same thing for encoding certificates with additional extensions.

Any unknown extensions are represented as DER binaries, which you can encode/decode manually with a suitable ASN.1 encoder.

Is there any particular extension you are interested in @GalaxyGorilla?

GalaxyGorilla commented 3 years ago

@voltone thanks for the answer! However, I meant custom extensions, e.g. not standard extensions. I want to create my own extensions for client certificates. I'll test around throughout the week and check what's possible.

Unfortunately I'm developing in Erlang, so I can't use your lib at all :(.

voltone commented 3 years ago

@voltone thanks for the answer! However, I meant custom extensions, e.g. not standard extensions. I want to create my own extensions for client certificates. I'll test around throughout the week and check what's possible.

You basically just need an OID and a blob of DER-encoded data. You can mint your own OIDs within the enterprise namespace if you have an OUI. Producing a DER version of some data structure may require defining some ASN.1 syntax and generating the encoder and decoder functions, along with a header file for the record. Or you can cheat by using the undocumented asn1rt_nif module, e.g. to wrap an arbitrary binary:

1> Hello = <<"Hello, world!">>.                      
<<"Hello, world!">>
2> asn1rt_nif:encode_ber_tlv({16, [{131072, Hello}]}).
<<48,15,128,13,72,101,108,108,111,44,32,119,111,114,108,
  100,33>>

Unfortunately I'm developing in Erlang, so I can't use your lib at all :(.

I actually have a branch somewhere that rewrites much of the code to Erlang, with the Elixir code becoming a thin wrapper and adapter to Elixir data types (e.g. DateTime). When pulled in from a Mix project it builds everything, but from Rebar3 it builds just the Erlang core. It's not quite done yet, I need some encouragement to allocate time to finish it...

GalaxyGorilla commented 3 years ago

You basically just need an OID and a blob of DER-encoded data. You can mint your own OIDs within the enterprise namespace if you have an OUI. Producing a DER version of some data structure may require defining some ASN.1 syntax and generating the encoder and decoder functions, along with a header file for the record. Or you can cheat by using the undocumented asn1rt_nif module, e.g. to wrap an arbitrary binary:

Thanks! Just being able to use a {OID, value} was exactly what I was hoping for, I did not know about asn1rt_nif :). If that really works out also using the public_key API then this would be sufficient for me. I only use it within my own SaaS system so there's no necessity for an official OUI.

I actually have a branch somewhere that rewrites much of the code to Erlang, with the Elixir code becoming a thin wrapper and adapter to Elixir data types (e.g. DateTime). When pulled in from a Mix project it builds everything, but from Rebar3 it builds just the Erlang core. It's not quite done yet, I need some encouragement to allocate time to finish it...

Sounds super cool and I think this would help the Erlang community a lot! I actually built my own little library for GRiSP as part of a larger PKI component, see https://github.com/GalaxyGorilla/grisp_cryptoauth. But that little library is not extensive for certificates at all, just tailored to our needs for GRiSP client certificates.

voltone commented 3 years ago

If that really works out also using the public_key API then this would be sufficient for me.

It should. Let's say you have a macro that defines the OID value as a tuple and you want to support a single binary value, you can just wrap that in an Extension record and use that alongside other extension in a Certificate or OTPCertificate record:

my_extension(Value) ->
  %% ASN.1 universal tag value 4: Octet String
  DER = asn1rt_nif:encode_ber_tlv({16, [{4, Value}]}),
  #'Extension'{extnID = ?my_extension_oid, extnValue = DER}.

Encoding non-binary values, such as integers, is a bit harder as encode_ber_tlv does not take care of the type-specific conversion.

GalaxyGorilla commented 3 years ago
my_extension(Value) ->
  %% ASN.1 universal tag value 4: Octet String
  DER = asn1rt_nif:encode_ber_tlv({16, [{4, Value}]}),
  #'Extension'{extnID = ?my_extension_oid, extnValue = DER}.

What does {16, [{4, Value}]} actually represent? Which is the ASN.1 tag number?

Encoding non-binary values, such as integers, is a bit harder as encode_ber_tlv does not take care of the type-specific conversion.

That's exactly what I need for e.g. device serial numbers. I found the OTP-PUB-KEY module helpful here, but it also doesn't provide support for the basic types (like Integer). But you can just use types which are directly derived from those like this:

3> 'OTP-PUB-KEY':encode('CertificateSerialNumber', 128).
{ok,<<2,2,0,128>>}
voltone commented 3 years ago

What does {16, [{4, Value}]} actually represent? Which is the ASN.1 tag number?

This is basically a Sequence (tag value 16) with a single element of type Octet String (tag value 4). I think the top-level type should always be a sequence, even if you only want to encode a single value.

GalaxyGorilla commented 3 years ago

Hmmm are you sure about the top level type being always a sequence? What I can find just always speaks about 'DER encoded'.

voltone commented 3 years ago

Hmmm are you sure about the top level type being always a sequence? What I can find just always speaks about 'DER encoded'.

I thought it was a convention among standard extensions at least, but I now noticed that SubjectKeyIdentifier is defined as a plain octet string, without a sequence. So feel free to drop the sequence :)

GalaxyGorilla commented 3 years ago

Haha, I also looked on the SubjectKeyIdentifier, that got me suspicious ;)