Packages

Cryptography library for Gleam targeting Erlang and JavaScript

Current section

Files

Jump to
kryptos src kryptos@x509@csr.erl
Raw

src/kryptos@x509@csr.erl

-module(kryptos@x509@csr).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/kryptos/x509/csr.gleam").
-export([new/0, with_subject/2, with_dns_name/2, with_email/2, with_ip/2, to_der/1, version/1, subject/1, public_key/1, signature_algorithm/1, subject_alt_names/1, extensions/1, attributes/1, from_der_unverified/1, from_der/1, sign_with_ecdsa/3, sign_with_rsa/3, sign_with_eddsa/2, to_pem/1, from_pem/1, from_pem_unverified/1]).
-export_type([built/0, parsed/0, csr/1, csr_error/0, builder/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" X.509 Certificate Signing Request (CSR) generation.\n"
"\n"
" This module provides a builder for creating PKCS#10 Certificate Signing\n"
" Requests. CSRs are used to request certificates from a Certificate Authority.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/result\n"
" import kryptos/ec\n"
" import kryptos/hash\n"
" import kryptos/x509\n"
" import kryptos/x509/csr\n"
"\n"
" let #(private_key, _) = ec.generate_key_pair(ec.P256)\n"
"\n"
" let subject = x509.name([\n"
" x509.cn(\"example.com\"),\n"
" x509.organization(\"Acme Inc\"),\n"
" x509.country(\"US\"),\n"
" ])\n"
"\n"
" let assert Ok(builder) =\n"
" csr.new()\n"
" |> csr.with_subject(subject)\n"
" |> csr.with_dns_name(\"example.com\")\n"
" |> result.try(csr.with_dns_name(_, \"www.example.com\"))\n"
"\n"
" let assert Ok(my_csr) = csr.sign_with_ecdsa(builder, private_key, hash.Sha256)\n"
"\n"
" let pem = csr.to_pem(my_csr)\n"
" ```\n"
).
-type built() :: any().
-type parsed() :: any().
-opaque csr(AADQ) :: {built_csr, bitstring()} |
{parsed_csr,
bitstring(),
integer(),
kryptos@x509:name(),
kryptos@x509:public_key(),
kryptos@x509:signature_algorithm(),
list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()}),
list({kryptos@x509:oid(), bitstring()})} |
{gleam_phantom, AADQ}.
-type csr_error() :: invalid_pem |
invalid_structure |
{unsupported_signature_algorithm, kryptos@x509:oid()} |
{unsupported_key_type, kryptos@x509:oid()} |
signature_verification_failed |
{unsupported_version, integer()}.
-opaque builder() :: {builder, kryptos@x509:name(), kryptos@x509:extensions()}.
-file("src/kryptos/x509/csr.gleam", 108).
?DOC(
" Creates a new CSR builder with an empty subject and no extensions.\n"
"\n"
" Use the `with_*` functions to configure the builder, then call\n"
" `sign_with_ecdsa` or `sign_with_rsa` to generate the signed CSR.\n"
"\n"
" ## Returns\n"
" A new Builder ready for configuration.\n"
).
-spec new() -> builder().
new() ->
{builder, kryptos@x509:name([]), {extensions, []}}.
-file("src/kryptos/x509/csr.gleam", 125).
?DOC(
" Sets the distinguished name subject for the CSR.\n"
"\n"
" The subject identifies who the certificate will be issued to.\n"
"\n"
" ## Parameters\n"
" - `builder`: The CSR builder\n"
" - `subject`: A distinguished name created with `x509.name`\n"
"\n"
" ## Returns\n"
" The updated builder.\n"
).
-spec with_subject(builder(), kryptos@x509:name()) -> builder().
with_subject(Builder, Subject) ->
{builder, Subject, erlang:element(3, Builder)}.
-file("src/kryptos/x509/csr.gleam", 142).
?DOC(
" Adds a DNS name to the Subject Alternative Names extension.\n"
"\n"
" SANs allow a certificate to be valid for multiple hostnames. Modern\n"
" browsers require the domain to appear in the SAN extension, not just\n"
" the Common Name.\n"
"\n"
" ## Parameters\n"
" - `builder`: The CSR builder\n"
" - `name`: A DNS hostname (e.g., \"example.com\" or \"*.example.com\")\n"
"\n"
" ## Returns\n"
" - `Ok(Builder)` with the updated builder\n"
" - `Error(Nil)` if the DNS name contains non-ASCII characters\n"
).
-spec with_dns_name(builder(), binary()) -> {ok, builder()} | {error, nil}.
with_dns_name(Builder, Name) ->
gleam@bool:guard(
not kryptos@internal@utils:is_ascii(Name),
{error, nil},
fun() ->
{extensions, Sans} = erlang:element(3, Builder),
{ok,
{builder,
erlang:element(2, Builder),
{extensions, [{dns_name, Name} | Sans]}}}
end
).
-file("src/kryptos/x509/csr.gleam", 168).
?DOC(
" Adds an email address to the Subject Alternative Names extension.\n"
"\n"
" Used for S/MIME certificates where the certificate should be valid\n"
" for a specific email address.\n"
"\n"
" ## Parameters\n"
" - `builder`: The CSR builder\n"
" - `email`: An email address (e.g., \"user@example.com\")\n"
"\n"
" ## Returns\n"
" - `Ok(Builder)` with the updated builder\n"
" - `Error(Nil)` if the email contains non-ASCII characters\n"
).
-spec with_email(builder(), binary()) -> {ok, builder()} | {error, nil}.
with_email(Builder, Email) ->
gleam@bool:guard(
not kryptos@internal@utils:is_ascii(Email),
{error, nil},
fun() ->
{extensions, Sans} = erlang:element(3, Builder),
{ok,
{builder,
erlang:element(2, Builder),
{extensions, [{email, Email} | Sans]}}}
end
).
-file("src/kryptos/x509/csr.gleam", 192).
?DOC(
" Adds an IP address to the Subject Alternative Names extension.\n"
"\n"
" Allows the certificate to be valid when accessed by IP address instead\n"
" of hostname.\n"
"\n"
" ## Parameters\n"
" - `builder`: The CSR builder\n"
" - `ip`: An IPv4 address (e.g., \"192.168.1.1\") or IPv6 address\n"
" (e.g., \"2001:db8::1\", \"::1\")\n"
"\n"
" ## Returns\n"
" - `Ok(Builder)` with the updated builder\n"
" - `Error(Nil)` if the IP address cannot be parsed\n"
).
-spec with_ip(builder(), binary()) -> {ok, builder()} | {error, nil}.
with_ip(Builder, Ip) ->
gleam@result:'try'(
kryptos@internal@utils:parse_ip(Ip),
fun(Parsed) ->
{extensions, Sans} = erlang:element(3, Builder),
{ok,
{builder,
erlang:element(2, Builder),
{extensions, [{ip_address, Parsed} | Sans]}}}
end
).
-file("src/kryptos/x509/csr.gleam", 311).
?DOC(
" Exports the CSR as DER-encoded bytes.\n"
"\n"
" DER (Distinguished Encoding Rules) is a binary format commonly used\n"
" for programmatic certificate handling.\n"
"\n"
" ## Parameters\n"
" - `csr`: The signed CSR (either built or parsed)\n"
"\n"
" ## Returns\n"
" The raw DER-encoded CSR bytes.\n"
).
-spec to_der(csr(any())) -> bitstring().
to_der(Csr) ->
case Csr of
{built_csr, Der} ->
Der;
{parsed_csr, Der@1, _, _, _, _, _, _, _} ->
Der@1
end.
-file("src/kryptos/x509/csr.gleam", 456).
?DOC(
" Returns the version of a parsed CSR.\n"
"\n"
" PKCS#10 v1 CSRs always have version 0.\n"
"\n"
" ## Parameters\n"
" - `csr`: A parsed CSR\n"
"\n"
" ## Returns\n"
" The version number (always 0 for PKCS#10 v1).\n"
).
-spec version(csr(parsed())) -> integer().
version(Csr) ->
Version@1 = case Csr of
{parsed_csr, _, Version, _, _, _, _, _, _} -> Version;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"version"/utf8>>,
line => 457,
value => _assert_fail,
start => 14463,
'end' => 14503,
pattern_start => 14474,
pattern_end => 14497})
end,
Version@1.
-file("src/kryptos/x509/csr.gleam", 470).
?DOC(
" Returns the subject (distinguished name) of a parsed CSR.\n"
"\n"
" The subject identifies who the certificate is being requested for.\n"
"\n"
" ## Parameters\n"
" - `csr`: A parsed CSR\n"
"\n"
" ## Returns\n"
" The subject as a distinguished name.\n"
).
-spec subject(csr(parsed())) -> kryptos@x509:name().
subject(Csr) ->
Subject@1 = case Csr of
{parsed_csr, _, _, Subject, _, _, _, _, _} -> Subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"subject"/utf8>>,
line => 471,
value => _assert_fail,
start => 14812,
'end' => 14852,
pattern_start => 14823,
pattern_end => 14846})
end,
Subject@1.
-file("src/kryptos/x509/csr.gleam", 484).
?DOC(
" Returns the public key embedded in a parsed CSR.\n"
"\n"
" This is the key that the requester wants certified.\n"
"\n"
" ## Parameters\n"
" - `csr`: A parsed CSR\n"
"\n"
" ## Returns\n"
" The subject's public key (RSA, EC, or EdDSA).\n"
).
-spec public_key(csr(parsed())) -> kryptos@x509:public_key().
public_key(Csr) ->
Public_key@1 = case Csr of
{parsed_csr, _, _, _, Public_key, _, _, _, _} -> Public_key;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"public_key"/utf8>>,
line => 485,
value => _assert_fail,
start => 15154,
'end' => 15197,
pattern_start => 15165,
pattern_end => 15191})
end,
Public_key@1.
-file("src/kryptos/x509/csr.gleam", 496).
?DOC(
" Returns the signature algorithm used to sign the CSR.\n"
"\n"
" ## Parameters\n"
" - `csr`: A parsed CSR\n"
"\n"
" ## Returns\n"
" The signature algorithm identifier.\n"
).
-spec signature_algorithm(csr(parsed())) -> kryptos@x509:signature_algorithm().
signature_algorithm(Csr) ->
Signature_algorithm@1 = case Csr of
{parsed_csr, _, _, _, _, Signature_algorithm, _, _, _} -> Signature_algorithm;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"signature_algorithm"/utf8>>,
line => 497,
value => _assert_fail,
start => 15455,
'end' => 15507,
pattern_start => 15466,
pattern_end => 15501})
end,
Signature_algorithm@1.
-file("src/kryptos/x509/csr.gleam", 509).
?DOC(
" Returns the Subject Alternative Names from the CSR.\n"
"\n"
" ## Parameters\n"
" - `csr`: A parsed CSR\n"
"\n"
" ## Returns\n"
" List of SANs (DNS names, emails, IPs), or empty list if no SAN extension\n"
" was requested.\n"
).
-spec subject_alt_names(csr(parsed())) -> list(kryptos@x509:subject_alt_name()).
subject_alt_names(Csr) ->
Subject_alt_names@1 = case Csr of
{parsed_csr, _, _, _, _, _, Subject_alt_names, _, _} -> Subject_alt_names;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"subject_alt_names"/utf8>>,
line => 510,
value => _assert_fail,
start => 15828,
'end' => 15878,
pattern_start => 15839,
pattern_end => 15872})
end,
Subject_alt_names@1.
-file("src/kryptos/x509/csr.gleam", 519).
?DOC(
" Returns any extensions beyond SANs as raw (OID, critical, value) tuples.\n"
"\n"
" This allows access to extensions that kryptos doesn't have typed\n"
" representations for. The Bool indicates whether the extension was\n"
" marked as critical per RFC 5280.\n"
).
-spec extensions(csr(parsed())) -> list({kryptos@x509:oid(),
boolean(),
bitstring()}).
extensions(Csr) ->
Extensions@1 = case Csr of
{parsed_csr, _, _, _, _, _, _, Extensions, _} -> Extensions;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"extensions"/utf8>>,
line => 520,
value => _assert_fail,
start => 16236,
'end' => 16279,
pattern_start => 16247,
pattern_end => 16273})
end,
Extensions@1.
-file("src/kryptos/x509/csr.gleam", 528).
?DOC(
" Returns any non-extension attributes as raw (OID, value) pairs.\n"
"\n"
" Most CSRs only have the extensionRequest attribute, so this is\n"
" typically empty.\n"
).
-spec attributes(csr(parsed())) -> list({kryptos@x509:oid(), bitstring()}).
attributes(Csr) ->
Attributes@1 = case Csr of
{parsed_csr, _, _, _, _, _, _, _, Attributes} -> Attributes;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"attributes"/utf8>>,
line => 529,
value => _assert_fail,
start => 16527,
'end' => 16570,
pattern_start => 16538,
pattern_end => 16564})
end,
Attributes@1.
-file("src/kryptos/x509/csr.gleam", 540).
-spec parse_version(bitstring()) -> {ok, integer()} | {error, csr_error()}.
parse_version(Bytes) ->
case Bytes of
<<0>> ->
{ok, 0};
<<V>> ->
{error, {unsupported_version, V}};
_ ->
{error, invalid_structure}
end.
-file("src/kryptos/x509/csr.gleam", 604).
-spec parse_single_attribute(bitstring()) -> {ok,
{kryptos@x509:oid(), bitstring()}} |
{error, nil}.
parse_single_attribute(Bytes) ->
gleam@result:'try'(
kryptos@internal@der:parse_oid(Bytes),
fun(_use0) ->
{Oid_components, After_oid} = _use0,
gleam@result:'try'(
kryptos@internal@der:parse_set(After_oid),
fun(_use0@1) ->
{Value, Remaining} = _use0@1,
gleam@bool:guard(
Remaining /= <<>>,
{error, nil},
fun() -> {ok, {{oid, Oid_components}, Value}} end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 625).
-spec parse_extensions(
bitstring(),
list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()})
) -> {ok,
{list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()})}} |
{error, nil}.
parse_extensions(Bytes, Sans, Exts) ->
case Bytes of
<<>> ->
{ok, {Sans, lists:reverse(Exts)}};
_ ->
gleam@result:'try'(
kryptos@internal@der:parse_sequence(Bytes),
fun(_use0) ->
{Ext_bytes, Rest} = _use0,
gleam@result:'try'(
kryptos@internal@x509:parse_single_extension(Ext_bytes),
fun(_use0@1) ->
{Oid, Is_critical, Value} = _use0@1,
case Oid of
{oid, [2, 5, 29, 17]} ->
gleam@result:'try'(
kryptos@internal@x509:parse_san_extension(
Value,
false
),
fun(New_sans) ->
parse_extensions(
Rest,
lists:append(Sans, New_sans),
Exts
)
end
);
_ ->
parse_extensions(
Rest,
Sans,
[{Oid, Is_critical, Value} | Exts]
)
end
end
)
end
)
end.
-file("src/kryptos/x509/csr.gleam", 611).
-spec parse_extension_request(bitstring()) -> {ok,
{list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()})}} |
{error, nil}.
parse_extension_request(Bytes) ->
gleam@bool:guard(
erlang:byte_size(Bytes) =:= 0,
{ok, {[], []}},
fun() ->
gleam@result:'try'(
kryptos@internal@der:parse_sequence(Bytes),
fun(_use0) ->
{Exts_content, _} = _use0,
parse_extensions(Exts_content, [], [])
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 569).
-spec parse_attributes_content(
bitstring(),
list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()}),
list({kryptos@x509:oid(), bitstring()})
) -> {ok,
{list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()}),
list({kryptos@x509:oid(), bitstring()})}} |
{error, nil}.
parse_attributes_content(Bytes, Sans, Exts, Attrs) ->
case Bytes of
<<>> ->
{ok, {Sans, lists:reverse(Exts), lists:reverse(Attrs)}};
_ ->
gleam@result:'try'(
kryptos@internal@der:parse_sequence(Bytes),
fun(_use0) ->
{Attr_bytes, Rest} = _use0,
gleam@result:'try'(
parse_single_attribute(Attr_bytes),
fun(_use0@1) ->
{Oid, Value} = _use0@1,
case Oid of
{oid, [1, 2, 840, 113549, 1, 9, 14]} ->
gleam@result:'try'(
parse_extension_request(Value),
fun(_use0@2) ->
{New_sans, New_exts} = _use0@2,
parse_attributes_content(
Rest,
lists:append(Sans, New_sans),
lists:append(Exts, New_exts),
Attrs
)
end
);
_ ->
parse_attributes_content(
Rest,
Sans,
Exts,
[{Oid, Value} | Attrs]
)
end
end
)
end
)
end.
-file("src/kryptos/x509/csr.gleam", 548).
-spec parse_attributes(bitstring()) -> {ok,
{list(kryptos@x509:subject_alt_name()),
list({kryptos@x509:oid(), boolean(), bitstring()}),
list({kryptos@x509:oid(), bitstring()})}} |
{error, nil}.
parse_attributes(Bytes) ->
case Bytes of
<<16#a0, _/bitstring>> ->
case kryptos@internal@der:parse_context_tag(Bytes, 0) of
{ok, {Attrs_content, _}} ->
parse_attributes_content(Attrs_content, [], [], []);
{error, _} ->
{error, nil}
end;
_ ->
{ok, {[], [], []}}
end.
-file("src/kryptos/x509/csr.gleam", 363).
?DOC(
" Parse a DER-encoded CSR without verifying the signature.\n"
"\n"
" Useful for debugging malformed or partially valid CSRs.\n"
" The parsed fields may not be trustworthy since the signature\n"
" was not verified.\n"
).
-spec from_der_unverified(bitstring()) -> {ok, csr(parsed())} |
{error, csr_error()}.
from_der_unverified(Der) ->
gleam@result:'try'(
begin
_pipe = kryptos@internal@der:parse_sequence(Der),
gleam@result:replace_error(_pipe, invalid_structure)
end,
fun(_use0) ->
{Csr_content, Remaining} = _use0,
gleam@bool:guard(
erlang:byte_size(Remaining) /= 0,
{error, invalid_structure},
fun() ->
gleam@result:'try'(
begin
_pipe@1 = kryptos@internal@x509:parse_sequence_with_header(
Csr_content
),
gleam@result:replace_error(
_pipe@1,
invalid_structure
)
end,
fun(_use0@1) ->
{Cert_req_info_bytes, After_info} = _use0@1,
gleam@result:'try'(
begin
_pipe@2 = kryptos@internal@der:parse_sequence(
Cert_req_info_bytes
),
gleam@result:replace_error(
_pipe@2,
invalid_structure
)
end,
fun(_use0@2) ->
{Cert_req_info_content, _} = _use0@2,
gleam@result:'try'(
begin
_pipe@3 = kryptos@internal@der:parse_integer(
Cert_req_info_content
),
gleam@result:replace_error(
_pipe@3,
invalid_structure
)
end,
fun(_use0@3) ->
{Version_bytes, After_version} = _use0@3,
gleam@result:'try'(
parse_version(Version_bytes),
fun(Version) ->
gleam@result:'try'(
begin
_pipe@4 = kryptos@internal@der:parse_sequence(
After_version
),
gleam@result:replace_error(
_pipe@4,
invalid_structure
)
end,
fun(_use0@4) ->
{Subject_bytes,
After_subject} = _use0@4,
gleam@result:'try'(
begin
_pipe@5 = kryptos@internal@x509:parse_name(
Subject_bytes
),
gleam@result:replace_error(
_pipe@5,
invalid_structure
)
end,
fun(Subject) ->
gleam@result:'try'(
begin
_pipe@6 = kryptos@internal@x509:parse_sequence_with_header(
After_subject
),
gleam@result:replace_error(
_pipe@6,
invalid_structure
)
end,
fun(
_use0@5
) ->
{Spki_bytes,
After_spki} = _use0@5,
gleam@result:'try'(
begin
_pipe@7 = kryptos@internal@x509:parse_public_key(
Spki_bytes
),
gleam@result:map_error(
_pipe@7,
fun(
Oid
) ->
case Oid of
{oid,
[]} ->
invalid_structure;
_ ->
{unsupported_key_type,
Oid}
end
end
)
end,
fun(
Public_key
) ->
gleam@result:'try'(
begin
_pipe@8 = parse_attributes(
After_spki
),
gleam@result:replace_error(
_pipe@8,
invalid_structure
)
end,
fun(
_use0@6
) ->
{Subject_alt_names,
Extensions,
Attributes} = _use0@6,
gleam@result:'try'(
begin
_pipe@9 = kryptos@internal@der:parse_sequence(
After_info
),
gleam@result:replace_error(
_pipe@9,
invalid_structure
)
end,
fun(
_use0@7
) ->
{Sig_alg_bytes,
After_sig_alg} = _use0@7,
gleam@result:'try'(
begin
_pipe@10 = kryptos@internal@x509:parse_signature_algorithm(
Sig_alg_bytes
),
gleam@result:map_error(
_pipe@10,
fun(Field@0) -> {unsupported_signature_algorithm, Field@0} end
)
end,
fun(
Signature_algorithm
) ->
gleam@result:'try'(
begin
_pipe@11 = kryptos@internal@der:parse_bit_string(
After_sig_alg
),
gleam@result:replace_error(
_pipe@11,
invalid_structure
)
end,
fun(
_use0@8
) ->
{_,
_} = _use0@8,
{ok,
{parsed_csr,
Der,
Version,
Subject,
Public_key,
Signature_algorithm,
Subject_alt_names,
Extensions,
Attributes}}
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 654).
-spec verify_signature(csr(parsed())) -> {ok, nil} | {error, csr_error()}.
verify_signature(Csr) ->
{Der@1, Public_key@1, Signature_algorithm@1} = case Csr of
{parsed_csr, Der, _, _, Public_key, Signature_algorithm, _, _, _} -> {
Der,
Public_key,
Signature_algorithm};
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"kryptos/x509/csr"/utf8>>,
function => <<"verify_signature"/utf8>>,
line => 655,
value => _assert_fail,
start => 19958,
'end' => 20029,
pattern_start => 19969,
pattern_end => 20023})
end,
gleam@result:'try'(
begin
_pipe = kryptos@internal@der:parse_sequence(Der@1),
gleam@result:replace_error(_pipe, invalid_structure)
end,
fun(_use0) ->
{Csr_content, _} = _use0,
gleam@result:'try'(
begin
_pipe@1 = kryptos@internal@x509:parse_sequence_with_header(
Csr_content
),
gleam@result:replace_error(_pipe@1, invalid_structure)
end,
fun(_use0@1) ->
{Cert_req_info_bytes, After_info} = _use0@1,
gleam@result:'try'(
begin
_pipe@2 = kryptos@internal@der:parse_sequence(
After_info
),
gleam@result:replace_error(
_pipe@2,
invalid_structure
)
end,
fun(_use0@2) ->
{_, After_sig_alg} = _use0@2,
gleam@result:'try'(
begin
_pipe@3 = kryptos@internal@der:parse_bit_string(
After_sig_alg
),
gleam@result:replace_error(
_pipe@3,
invalid_structure
)
end,
fun(_use0@3) ->
{Signature, _} = _use0@3,
Verified = kryptos@internal@x509:verify_signature(
Public_key@1,
Cert_req_info_bytes,
Signature,
Signature_algorithm@1
),
case Verified of
true ->
{ok, nil};
false ->
{error,
signature_verification_failed}
end
end
)
end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 343).
?DOC(" Parse a DER-encoded CSR and verify its signature.\n").
-spec from_der(bitstring()) -> {ok, csr(parsed())} | {error, csr_error()}.
from_der(Der) ->
gleam@result:'try'(
from_der_unverified(Der),
fun(Parsed) ->
gleam@result:'try'(
verify_signature(Parsed),
fun(_) -> {ok, Parsed} end
)
end
).
-file("src/kryptos/x509/csr.gleam", 693).
-spec encode_csr(bitstring(), kryptos@internal@x509:sig_alg_info(), bitstring()) -> {ok,
bitstring()} |
{error, nil}.
encode_csr(Cert_request_info, Sig_alg, Signature) ->
gleam@result:'try'(
kryptos@internal@x509:encode_algorithm_identifier(Sig_alg),
fun(Sig_alg_der) ->
gleam@result:'try'(
kryptos@internal@der:encode_bit_string(Signature),
fun(Sig_bits) ->
kryptos@internal@der:encode_sequence(
gleam_stdlib:bit_array_concat(
[Cert_request_info, Sig_alg_der, Sig_bits]
)
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 740).
-spec encode_extensions(kryptos@x509:extensions()) -> {ok, bitstring()} |
{error, nil}.
encode_extensions(Extensions) ->
{extensions, Sans} = Extensions,
_pipe = kryptos@internal@x509:encode_san_extension(Sans, false),
gleam@result:'try'(_pipe, fun kryptos@internal@der:encode_sequence/1).
-file("src/kryptos/x509/csr.gleam", 727).
-spec encode_extension_request(kryptos@x509:extensions()) -> {ok, bitstring()} |
{error, nil}.
encode_extension_request(Extensions) ->
{oid, Ext_req_components} = {oid, [1, 2, 840, 113549, 1, 9, 14]},
gleam@result:'try'(
kryptos@internal@der:encode_oid(Ext_req_components),
fun(Oid_encoded) -> _pipe = Extensions,
_pipe@1 = encode_extensions(_pipe),
_pipe@2 = gleam@result:'try'(
_pipe@1,
fun kryptos@internal@der:encode_set/1
),
_pipe@3 = gleam@result:map(
_pipe@2,
fun(Set_encoded) ->
gleam_stdlib:bit_array_concat([Oid_encoded, Set_encoded])
end
),
gleam@result:'try'(
_pipe@3,
fun kryptos@internal@der:encode_sequence/1
) end
).
-file("src/kryptos/x509/csr.gleam", 717).
-spec encode_attributes(kryptos@x509:extensions()) -> {ok, bitstring()} |
{error, nil}.
encode_attributes(Extensions) ->
case gleam@list:is_empty(erlang:element(2, Extensions)) of
true ->
kryptos@internal@der:encode_context_tag(0, <<>>);
false ->
_pipe = Extensions,
_pipe@1 = encode_extension_request(_pipe),
gleam@result:'try'(
_pipe@1,
fun(_capture) ->
kryptos@internal@der:encode_context_tag(0, _capture)
end
)
end.
-file("src/kryptos/x509/csr.gleam", 707).
-spec encode_certification_request_info(builder(), bitstring()) -> {ok,
bitstring()} |
{error, nil}.
encode_certification_request_info(Builder, Spki) ->
gleam@result:'try'(
kryptos@internal@der:encode_integer(<<0>>),
fun(Version) ->
gleam@result:'try'(
kryptos@internal@x509:encode_name(erlang:element(2, Builder)),
fun(Subject) ->
gleam@result:'try'(
encode_attributes(erlang:element(3, Builder)),
fun(Attributes) ->
kryptos@internal@der:encode_sequence(
gleam_stdlib:bit_array_concat(
[Version, Subject, Spki, Attributes]
)
)
end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 222).
?DOC(
" Signs the CSR with an ECDSA private key.\n"
"\n"
" The public key is derived from the private key and included in the CSR.\n"
" The resulting CSR can be submitted to a Certificate Authority.\n"
"\n"
" ## Parameters\n"
" - `builder`: The configured CSR builder\n"
" - `key`: An EC private key from `ec.generate_key_pair`\n"
" - `hash`: The hash algorithm for signing. Recommended: `Sha256` for P-256,\n"
" `Sha384` for P-384, `Sha512` for P-521. Note: `Sha1` is supported for\n"
" legacy compatibility but is cryptographically weak and should be avoided.\n"
"\n"
" ## Returns\n"
" - `Ok(Csr(Built))` containing the signed CSR\n"
" - `Error(Nil)` if the hash algorithm is not supported or the public key\n"
" cannot be encoded\n"
).
-spec sign_with_ecdsa(
builder(),
kryptos@ec:private_key(),
kryptos@hash:hash_algorithm()
) -> {ok, csr(built())} | {error, nil}.
sign_with_ecdsa(Builder, Key, Hash) ->
gleam@result:'try'(
kryptos@internal@x509:ecdsa_sig_alg_info(Hash),
fun(Sig_alg) ->
Public_key = kryptos_ffi:ec_public_key_from_private(Key),
gleam@result:'try'(
kryptos_ffi:ec_export_public_key_der(Public_key),
fun(Spki) ->
gleam@result:'try'(
encode_certification_request_info(Builder, Spki),
fun(Cert_request_info) ->
Signature = kryptos_ffi:ecdsa_sign(
Key,
Cert_request_info,
Hash
),
gleam@result:'try'(
encode_csr(
Cert_request_info,
Sig_alg,
Signature
),
fun(Csr_der) -> {ok, {built_csr, Csr_der}} end
)
end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 256).
?DOC(
" Signs the CSR with an RSA private key using PKCS#1 v1.5 padding.\n"
"\n"
" The public key is derived from the private key and included in the CSR.\n"
" The resulting CSR can be submitted to a Certificate Authority.\n"
"\n"
" ## Parameters\n"
" - `builder`: The configured CSR builder\n"
" - `key`: An RSA private key from `rsa.generate_key_pair`\n"
" - `hash`: The hash algorithm for signing. Recommended: `Sha256` for 2048-bit\n"
" keys, `Sha384` or `Sha512` for 3072-bit or larger keys. Note: `Sha1` is\n"
" supported for legacy compatibility but is cryptographically weak and\n"
" should be avoided.\n"
"\n"
" ## Returns\n"
" - `Ok(Csr(Built))` containing the signed CSR\n"
" - `Error(Nil)` if the hash algorithm is not supported or the public key\n"
" cannot be encoded\n"
).
-spec sign_with_rsa(
builder(),
kryptos@rsa:private_key(),
kryptos@hash:hash_algorithm()
) -> {ok, csr(built())} | {error, nil}.
sign_with_rsa(Builder, Key, Hash) ->
gleam@result:'try'(
kryptos@internal@x509:rsa_sig_alg_info(Hash),
fun(Sig_alg) ->
Public_key = kryptos_ffi:rsa_public_key_from_private(Key),
gleam@result:'try'(
kryptos_ffi:rsa_export_public_key_der(Public_key, spki),
fun(Spki) ->
gleam@result:'try'(
encode_certification_request_info(Builder, Spki),
fun(Cert_request_info) ->
Signature = kryptos_ffi:rsa_sign(
Key,
Cert_request_info,
Hash,
pkcs1v15
),
gleam@result:'try'(
encode_csr(
Cert_request_info,
Sig_alg,
Signature
),
fun(Csr_der) -> {ok, {built_csr, Csr_der}} end
)
end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 285).
?DOC(
" Signs the CSR with an EdDSA private key (Ed25519 or Ed448).\n"
"\n"
" **Note**: Support for EdDSA is limited with browsers and certificate\n"
" authorities.\n"
"\n"
" ## Parameters\n"
" - `builder`: The configured CSR builder\n"
" - `key`: An EdDSA private key from `eddsa.generate_key_pair`\n"
"\n"
" ## Returns\n"
" - `Ok(Csr(Built))` containing the signed CSR\n"
" - `Error(Nil)` if the public key cannot be encoded\n"
).
-spec sign_with_eddsa(builder(), kryptos@eddsa:private_key()) -> {ok,
csr(built())} |
{error, nil}.
sign_with_eddsa(Builder, Key) ->
Sig_alg = kryptos@internal@x509:eddsa_sig_alg_info(
kryptos_ffi:eddsa_private_key_curve(Key)
),
Public_key = kryptos_ffi:eddsa_public_key_from_private(Key),
gleam@result:'try'(
kryptos_ffi:eddsa_export_public_key_der(Public_key),
fun(Spki) ->
gleam@result:'try'(
encode_certification_request_info(Builder, Spki),
fun(Cert_request_info) ->
Signature = kryptos_ffi:eddsa_sign(Key, Cert_request_info),
gleam@result:'try'(
encode_csr(Cert_request_info, Sig_alg, Signature),
fun(Csr_der) -> {ok, {built_csr, Csr_der}} end
)
end
)
end
).
-file("src/kryptos/x509/csr.gleam", 329).
?DOC(
" Exports the CSR as a PEM-encoded string.\n"
"\n"
" PEM (Privacy-Enhanced Mail) is a Base64-encoded format with header and\n"
" footer lines. This is the format typically required when submitting\n"
" a CSR to a Certificate Authority.\n"
"\n"
" ## Parameters\n"
" - `csr`: The signed CSR (either built or parsed)\n"
"\n"
" ## Returns\n"
" A PEM-encoded string with `-----BEGIN CERTIFICATE REQUEST-----` headers.\n"
).
-spec to_pem(csr(any())) -> binary().
to_pem(Csr) ->
kryptos@internal@x509:encode_pem(
to_der(Csr),
<<"-----BEGIN CERTIFICATE REQUEST-----"/utf8>>,
<<"-----END CERTIFICATE REQUEST-----"/utf8>>
).
-file("src/kryptos/x509/csr.gleam", 533).
-spec decode_csr_pem(binary()) -> {ok, bitstring()} | {error, nil}.
decode_csr_pem(Pem) ->
case kryptos@internal@x509:decode_pem(
Pem,
<<"-----BEGIN CERTIFICATE REQUEST-----"/utf8>>,
<<"-----END CERTIFICATE REQUEST-----"/utf8>>
) of
{ok, Der} ->
{ok, Der};
{error, _} ->
kryptos@internal@x509:decode_pem(
Pem,
<<"-----BEGIN NEW CERTIFICATE REQUEST-----"/utf8>>,
<<"-----END NEW CERTIFICATE REQUEST-----"/utf8>>
)
end.
-file("src/kryptos/x509/csr.gleam", 337).
?DOC(
" Parse a PEM-encoded CSR and verify its signature.\n"
"\n"
" Returns an error if the PEM is invalid, the structure is malformed,\n"
" or the signature doesn't verify against the embedded public key.\n"
).
-spec from_pem(binary()) -> {ok, csr(parsed())} | {error, csr_error()}.
from_pem(Pem) ->
gleam@result:'try'(
begin
_pipe = decode_csr_pem(Pem),
gleam@result:replace_error(_pipe, invalid_pem)
end,
fun(Der) -> from_der(Der) end
).
-file("src/kryptos/x509/csr.gleam", 353).
?DOC(
" Parse a PEM-encoded CSR without verifying the signature.\n"
"\n"
" Useful for debugging malformed or partially valid CSRs.\n"
" The parsed fields may not be trustworthy.\n"
).
-spec from_pem_unverified(binary()) -> {ok, csr(parsed())} |
{error, csr_error()}.
from_pem_unverified(Pem) ->
gleam@result:'try'(
begin
_pipe = decode_csr_pem(Pem),
gleam@result:replace_error(_pipe, invalid_pem)
end,
fun(Der) -> from_der_unverified(Der) end
).