Current section
Files
Jump to
Current section
Files
src/kryptos@x509.erl
-module(kryptos@x509).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/kryptos/x509.gleam").
-export([name/1, encode_attribute_value/1, utf8_string/1, printable_string/1, ia5_string/1, attribute_value_to_string/1, name_to_string/1, cn/1, organization/1, organizational_unit/1, country/1, state/1, locality/1, email_address/1]).
-export_type([oid/0, name/0, rdn/0, attribute_value/0, subject_alt_name/0, extensions/0, public_key/0, signature_algorithm/0, basic_constraints/0, key_usage/0, extended_key_usage/0, validity/0, authority_key_identifier/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 and CSR types and utilities.\n"
"\n"
" This module provides foundational types and helpers for working with X.509\n"
" public key infrastructure. It includes:\n"
"\n"
" - **Distinguished Names**: Type-safe construction of subject/issuer names\n"
" - **Extensions**: Subject Alternative Names, Basic Constraints, Key Usage, etc.\n"
" - **Public Keys**: Unified representation for RSA, ECDSA, EdDSA, and XDH keys\n"
" - **Signature Algorithms**: Common signing algorithms for certificates and CSRs\n"
" - **Certificate Fields**: Validity periods, authority key identifiers\n"
"\n"
" Use this module with:\n"
" - `x509/csr` for creating and parsing Certificate Signing Requests\n"
" - `x509/certificate` for parsing and working with X.509 certificates\n"
"\n"
" ## Distinguished Names\n"
"\n"
" Distinguished names (DNs) identify subjects and issuers in X.509 structures.\n"
" They consist of attribute-value pairs like Common Name (CN), Organization (O),\n"
" and Country (C). Helper functions provide type-safe construction with correct\n"
" ASN.1 string encodings:\n"
"\n"
" ```gleam\n"
" import kryptos/x509\n"
"\n"
" let subject = x509.name([\n"
" x509.cn(\"example.com\"),\n"
" x509.organization(\"Acme Inc\"),\n"
" x509.organizational_unit(\"Engineering\"),\n"
" x509.locality(\"San Francisco\"),\n"
" x509.state(\"California\"),\n"
" x509.country(\"US\"),\n"
" ])\n"
" ```\n"
"\n"
" ## Extensions\n"
"\n"
" Subject Alternative Names (SANs) specify additional identities:\n"
"\n"
" ```gleam\n"
" import kryptos/x509.{DnsName, Email, IpAddress, Uri}\n"
"\n"
" let extensions = x509.Extensions([\n"
" DnsName(\"www.example.com\"),\n"
" DnsName(\"api.example.com\"),\n"
" Email(\"admin@example.com\"),\n"
" Uri(\"https://example.com/cps\"),\n"
" ])\n"
" ```\n"
).
-type oid() :: {oid, list(integer())}.
-type name() :: {name, list(rdn())}.
-type rdn() :: {rdn, list({oid(), attribute_value()})}.
-opaque attribute_value() :: {utf8_string, binary()} |
{printable_string, binary()} |
{ia5_string, binary()}.
-type subject_alt_name() :: {dns_name, binary()} |
{ip_address, bitstring()} |
{email, binary()} |
{uri, binary()} |
{directory_name, name()} |
{registered_id, oid()} |
{other_name, oid(), bitstring()} |
{unknown, integer(), bitstring()}.
-type extensions() :: {extensions, list(subject_alt_name())}.
-type public_key() :: {ec_public_key, kryptos@ec:public_key()} |
{rsa_public_key, kryptos@rsa:public_key()} |
{ed_public_key, kryptos@eddsa:public_key()} |
{xdh_public_key, kryptos@xdh:public_key()}.
-type signature_algorithm() :: rsa_sha1 |
rsa_sha256 |
rsa_sha384 |
rsa_sha512 |
ecdsa_sha1 |
ecdsa_sha256 |
ecdsa_sha384 |
ecdsa_sha512 |
ed25519 |
ed448.
-type basic_constraints() :: {basic_constraints,
boolean(),
gleam@option:option(integer())}.
-type key_usage() :: digital_signature |
non_repudiation |
key_encipherment |
data_encipherment |
key_agreement |
key_cert_sign |
crl_sign |
encipher_only |
decipher_only.
-type extended_key_usage() :: server_auth |
client_auth |
code_signing |
email_protection |
ocsp_signing.
-type validity() :: {validity,
gleam@time@timestamp:timestamp(),
gleam@time@timestamp:timestamp()}.
-type authority_key_identifier() :: {authority_key_identifier,
gleam@option:option(bitstring()),
gleam@option:option(list(subject_alt_name())),
gleam@option:option(bitstring())}.
-file("src/kryptos/x509.gleam", 232).
?DOC(
" Builds a distinguished name from a list of attribute-value pairs.\n"
"\n"
" Creates a Name with each attribute in its own Relative Distinguished Name\n"
" (RDN). This produces the standard X.509 format where attributes are\n"
" displayed as \"CN = x, O = y\".\n"
"\n"
" ## Parameters\n"
" - `attributes`: A list of OID and value tuples (use helper functions like\n"
" `cn`, `organization`, `country`, etc.)\n"
"\n"
" ## Returns\n"
" A Name suitable for use as a CSR subject.\n"
).
-spec name(list({oid(), attribute_value()})) -> name().
name(Attributes) ->
{name, gleam@list:map(Attributes, fun(Attr) -> {rdn, [Attr]} end)}.
-file("src/kryptos/x509.gleam", 336).
?DOC(false).
-spec encode_attribute_value(attribute_value()) -> {ok, bitstring()} |
{error, nil}.
encode_attribute_value(Value) ->
case Value of
{utf8_string, S} ->
kryptos@internal@der:encode_utf8_string(S);
{printable_string, S@1} ->
kryptos@internal@der:encode_printable_string(S@1);
{ia5_string, S@2} ->
kryptos@internal@der:encode_ia5_string(S@2)
end.
-file("src/kryptos/x509.gleam", 345).
?DOC(false).
-spec utf8_string(binary()) -> attribute_value().
utf8_string(Value) ->
{utf8_string, Value}.
-file("src/kryptos/x509.gleam", 350).
?DOC(false).
-spec printable_string(binary()) -> attribute_value().
printable_string(Value) ->
{printable_string, Value}.
-file("src/kryptos/x509.gleam", 355).
?DOC(false).
-spec ia5_string(binary()) -> attribute_value().
ia5_string(Value) ->
{ia5_string, Value}.
-file("src/kryptos/x509.gleam", 363).
?DOC(
" Extracts the string value from an AttributeValue.\n"
"\n"
" Returns the underlying string regardless of encoding type\n"
" (UTF8String, PrintableString, or IA5String).\n"
).
-spec attribute_value_to_string(attribute_value()) -> binary().
attribute_value_to_string(Value) ->
case Value of
{utf8_string, S} ->
S;
{printable_string, S@1} ->
S@1;
{ia5_string, S@2} ->
S@2
end.
-file("src/kryptos/x509.gleam", 390).
-spec oid_to_abbrev(oid()) -> binary().
oid_to_abbrev(Oid) ->
case Oid of
{oid, [2, 5, 4, 3]} ->
<<"CN"/utf8>>;
{oid, [2, 5, 4, 6]} ->
<<"C"/utf8>>;
{oid, [2, 5, 4, 7]} ->
<<"L"/utf8>>;
{oid, [2, 5, 4, 8]} ->
<<"ST"/utf8>>;
{oid, [2, 5, 4, 10]} ->
<<"O"/utf8>>;
{oid, [2, 5, 4, 11]} ->
<<"OU"/utf8>>;
{oid, [1, 2, 840, 113549, 1, 9, 1]} ->
<<"emailAddress"/utf8>>;
{oid, Components} ->
gleam@string:join(
gleam@list:map(Components, fun erlang:integer_to_binary/1),
<<"."/utf8>>
)
end.
-file("src/kryptos/x509.gleam", 377).
?DOC(
" Converts a distinguished name to a human-readable string.\n"
"\n"
" Formats the name in OpenSSL style: \"CN=example.com, O=Acme Inc, C=US\"\n"
"\n"
" Known OIDs are displayed with their standard abbreviations (CN, O, OU, C, ST, L).\n"
" Unknown OIDs are displayed in dotted-decimal notation (e.g., \"1.2.3.4=value\").\n"
).
-spec name_to_string(name()) -> binary().
name_to_string(Name) ->
{name, Rdns} = Name,
_pipe = Rdns,
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Rdn) ->
{rdn, Attributes} = Rdn,
gleam@list:map(
Attributes,
fun(Attr) ->
{Oid, Value} = Attr,
<<<<(oid_to_abbrev(Oid))/binary, "="/utf8>>/binary,
(attribute_value_to_string(Value))/binary>>
end
)
end
),
gleam@string:join(_pipe@1, <<", "/utf8>>).
-file("src/kryptos/x509.gleam", 247).
?DOC(
" Creates a Common Name (CN) attribute.\n"
"\n"
" The Common Name typically contains the primary identifier for the subject,\n"
" such as a domain name for server certificates or a person's name for\n"
" client certificates.\n"
"\n"
" ## Parameters\n"
" - `value`: The common name value (e.g., \"example.com\")\n"
"\n"
" ## Returns\n"
" A Common Name attribute tuple.\n"
).
-spec cn(binary()) -> {oid(), attribute_value()}.
cn(Value) ->
{{oid, [2, 5, 4, 3]}, {utf8_string, Value}}.
-file("src/kryptos/x509.gleam", 260).
?DOC(
" Creates an Organization (O) attribute.\n"
"\n"
" Identifies the organization or company name associated with the certificate.\n"
"\n"
" ## Parameters\n"
" - `value`: The organization name (e.g., \"Acme Inc\")\n"
"\n"
" ## Returns\n"
" An Organization attribute tuple.\n"
).
-spec organization(binary()) -> {oid(), attribute_value()}.
organization(Value) ->
{{oid, [2, 5, 4, 10]}, {utf8_string, Value}}.
-file("src/kryptos/x509.gleam", 274).
?DOC(
" Creates an Organizational Unit (OU) attribute.\n"
"\n"
" Identifies a subdivision within an organization, such as a department\n"
" or team name.\n"
"\n"
" ## Parameters\n"
" - `value`: The organizational unit name (e.g., \"Engineering\")\n"
"\n"
" ## Returns\n"
" An Organizational Unit attribute tuple.\n"
).
-spec organizational_unit(binary()) -> {oid(), attribute_value()}.
organizational_unit(Value) ->
{{oid, [2, 5, 4, 11]}, {utf8_string, Value}}.
-file("src/kryptos/x509.gleam", 291).
?DOC(
" Creates a Country (C) attribute.\n"
"\n"
" Uses PrintableString encoding as required by X.520.\n"
"\n"
" **Important:** The value must be a two-letter uppercase ISO 3166-1 alpha-2\n"
" country code (e.g., \"US\", \"GB\", \"DE\"). Non-ASCII or incorrectly formatted\n"
" values will produce non-compliant DER that may be rejected by CAs and clients.\n"
"\n"
" ## Parameters\n"
" - `value`: The two-letter country code (e.g., \"US\", \"GB\", \"DE\")\n"
"\n"
" ## Returns\n"
" A Country attribute tuple.\n"
).
-spec country(binary()) -> {oid(), attribute_value()}.
country(Value) ->
{{oid, [2, 5, 4, 6]}, {printable_string, Value}}.
-file("src/kryptos/x509.gleam", 302).
?DOC(
" Creates a State or Province (ST) attribute.\n"
"\n"
" ## Parameters\n"
" - `value`: The state or province name (e.g., \"California\")\n"
"\n"
" ## Returns\n"
" A State/Province attribute tuple.\n"
).
-spec state(binary()) -> {oid(), attribute_value()}.
state(Value) ->
{{oid, [2, 5, 4, 8]}, {utf8_string, Value}}.
-file("src/kryptos/x509.gleam", 313).
?DOC(
" Creates a Locality (L) attribute.\n"
"\n"
" ## Parameters\n"
" - `value`: The city or locality name (e.g., \"San Francisco\")\n"
"\n"
" ## Returns\n"
" A Locality attribute tuple.\n"
).
-spec locality(binary()) -> {oid(), attribute_value()}.
locality(Value) ->
{{oid, [2, 5, 4, 7]}, {utf8_string, Value}}.
-file("src/kryptos/x509.gleam", 331).
?DOC(
" Creates an Email Address attribute.\n"
"\n"
" Note: emailAddress in the DN is deprecated; prefer using\n"
" Subject Alternative Names via `csr.with_email` instead.\n"
"\n"
" **Important:** The value must contain only ASCII characters.\n"
" Non-ASCII values will produce non-compliant DER that may be rejected\n"
" by CAs and clients.\n"
"\n"
" ## Parameters\n"
" - `value`: The email address (e.g., \"admin@example.com\")\n"
"\n"
" ## Returns\n"
" An Email Address attribute tuple.\n"
).
-spec email_address(binary()) -> {oid(), attribute_value()}.
email_address(Value) ->
{{oid, [1, 2, 840, 113549, 1, 9, 1]}, {ia5_string, Value}}.