Packages

Gleam implementation of JOSE (JWS, JWE, JWK, JWT) and COSE (COSE_Sign, COSE_Encrypt, COSE_Mac0, CWT) standards

Current section

Files

Jump to
gose src gose@encrypted_jwk.erl
Raw

src/gose@encrypted_jwk.erl

-module(gose@encrypted_jwk).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gose/encrypted_jwk.gleam").
-export([decrypt/2, encrypt_with_key/4, encrypt_with_password/4]).
-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(
" Encrypted JWK Export/Import - [RFC 7516](https://www.rfc-editor.org/rfc/rfc7516.html)\n"
"\n"
" This module provides functions to export and import JWKs as encrypted JSON\n"
" using JWE. The plaintext JWK JSON becomes the JWE payload with `cty: \"jwk+json\"`.\n"
"\n"
" ## Example\n"
"\n"
" Key-based encryption:\n"
"\n"
" ```gleam\n"
" import gose/encrypted_jwk\n"
" import gose/jwa\n"
" import gose/jwe\n"
" import gose/jwk\n"
" import kryptos/ec\n"
"\n"
" // Generate a wrapping key and an EC key to protect\n"
" let wrapping_key = jwk.generate_enc_key(jwa.AesGcm(jwa.Aes256))\n"
" let key = jwk.generate_ec(ec.P256)\n"
"\n"
" // Export with key-based encryption\n"
" let assert Ok(encrypted) = encrypted_jwk.encrypt_with_key(\n"
" key,\n"
" alg: jwa.JweDirect,\n"
" enc: jwa.AesGcm(jwa.Aes256),\n"
" with: wrapping_key,\n"
" )\n"
"\n"
" // Import it back\n"
" let decryptor = jwe.key_decryptor(\n"
" jwa.JweDirect,\n"
" jwa.AesGcm(jwa.Aes256),\n"
" wrapping_key,\n"
" )\n"
" let assert Ok(recovered) = encrypted_jwk.decrypt(decryptor, encrypted)\n"
" ```\n"
"\n"
" Password-based encryption:\n"
"\n"
" ```gleam\n"
" import gose/encrypted_jwk\n"
" import gose/jwa\n"
" import gose/jwe\n"
" import gose/jwk\n"
" import kryptos/ec\n"
"\n"
" let key = jwk.generate_ec(ec.P256)\n"
"\n"
" // Export with password protection\n"
" let assert Ok(encrypted) = encrypted_jwk.encrypt_with_password(\n"
" key,\n"
" jwa.Pbes2Sha256Aes128Kw,\n"
" jwa.AesGcm(jwa.Aes256),\n"
" \"my-secure-password\",\n"
" )\n"
"\n"
" // Import it back using a decryptor\n"
" let decryptor = jwe.password_decryptor(\n"
" jwa.Pbes2Sha256Aes128Kw,\n"
" jwa.AesGcm(jwa.Aes256),\n"
" \"my-secure-password\",\n"
" )\n"
" let assert Ok(recovered) = encrypted_jwk.decrypt(decryptor, encrypted)\n"
" ```\n"
).
-file("src/gose/encrypted_jwk.gleam", 102).
?DOC(
" Import a JWK from encrypted JSON using a decryptor with algorithm pinning.\n"
"\n"
" Works for all algorithms. Create a decryptor with `jwe.key_decryptor`\n"
" for key-based algorithms or `jwe.password_decryptor` for PBES2.\n"
"\n"
" ## Parameters\n"
"\n"
" - `decryptor` - A decryptor created with `jwe.key_decryptor` or `jwe.password_decryptor`.\n"
" - `encrypted` - The encrypted JWE in compact format.\n"
"\n"
" ## Returns\n"
"\n"
" `Ok(Jwk)` with the decrypted and parsed key, or `Error(GoseError)` if\n"
" decryption or parsing fails.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let decryptor =\n"
" jwe.password_decryptor(\n"
" jwa.Pbes2Sha256Aes128Kw,\n"
" jwa.AesGcm(jwa.Aes256),\n"
" \"my-password\",\n"
" )\n"
" let assert Ok(key) = encrypted_jwk.decrypt(decryptor, encrypted_token)\n"
" ```\n"
).
-spec decrypt(gose@jwe:decryptor(), binary()) -> {ok, gose@jwk:jwk()} |
{error, gose:gose_error()}.
decrypt(Decryptor, Encrypted) ->
gleam@result:'try'(
gose@jwe:parse_compact(Encrypted),
fun(Parsed) ->
gleam@result:'try'(
gose@jwe:decrypt(Decryptor, Parsed),
fun(Plaintext) -> gose@jwk:from_json_bits(Plaintext) end
)
end
).
-file("src/gose/encrypted_jwk.gleam", 191).
-spec jwk_to_plaintext(gose@jwk:jwk()) -> bitstring().
jwk_to_plaintext(Key) ->
_pipe = gose@jwk:to_json(Key),
_pipe@1 = gleam@json:to_string(_pipe),
gleam_stdlib:identity(_pipe@1).
-file("src/gose/encrypted_jwk.gleam", 138).
?DOC(
" Export a JWK as encrypted JSON using a key-based algorithm.\n"
"\n"
" Supports all key-based JWE algorithms: direct symmetric (dir), AES Key Wrap,\n"
" AES-GCM Key Wrap, RSA-OAEP, and ECDH-ES. PBES2 password-based algorithms\n"
" return an error — use `encrypt_with_password` instead.\n"
"\n"
" The encryption key type must match the algorithm:\n"
" - `JweDirect`: octet key matching the content encryption key size\n"
" - `JweAesKeyWrap(AesKw, _)`: octet key (16, 24, or 32 bytes)\n"
" - `JweAesKeyWrap(AesGcmKw, _)`: octet key (16, 24, or 32 bytes)\n"
" - `JweChaCha20Kw(_)`: octet key (32 bytes)\n"
" - `JweRsa(_)`: RSA key\n"
" - `JweEcdhEs(_)`: EC or XDH key\n"
"\n"
" If the encryption key has a `kid`, it is included in the JWE header.\n"
"\n"
" ## Parameters\n"
"\n"
" - `key` - The JWK to export.\n"
" - `alg` - The JWE key encryption algorithm to use.\n"
" - `enc` - The content encryption algorithm to use.\n"
" - `encryption_key` - The key used to encrypt the JWK.\n"
"\n"
" ## Returns\n"
"\n"
" `Ok(String)` with the encrypted JWE in compact format, or\n"
" `Error(GoseError)` if serialization or encryption fails.\n"
).
-spec encrypt_with_key(
gose@jwk:jwk(),
gose@jwa:jwe_alg(),
gose@jwa:enc(),
gose@jwk:jwk()
) -> {ok, binary()} | {error, gose:gose_error()}.
encrypt_with_key(Key, Alg, Enc, Encryption_key) ->
Plaintext = jwk_to_plaintext(Key),
Kid = gleam@option:from_result(gose@jwk:kid(Encryption_key)),
_pipe = gose@jwe:encrypt_to_compact(
Alg,
Enc,
Plaintext,
Encryption_key,
Kid,
none,
{some, <<"jwk+json"/utf8>>}
),
gleam@result:map(_pipe, fun gleam@pair:first/1).
-file("src/gose/encrypted_jwk.gleam", 175).
?DOC(
" Export a JWK as encrypted JSON using PBES2 password-based encryption.\n"
"\n"
" This is the most common method for protecting stored keys with a password.\n"
" The JWK is serialized to JSON, then encrypted using the specified PBES2\n"
" algorithm and content encryption algorithm.\n"
"\n"
" ## Parameters\n"
"\n"
" - `key` - The JWK to export.\n"
" - `alg` - The PBES2 algorithm variant to use.\n"
" - `enc` - The content encryption algorithm to use.\n"
" - `password` - The password to protect the key with.\n"
"\n"
" ## Returns\n"
"\n"
" `Ok(String)` with the encrypted JWE in compact format, or\n"
" `Error(GoseError)` if serialization or encryption fails.\n"
).
-spec encrypt_with_password(
gose@jwk:jwk(),
gose@jwa:pbes2_alg(),
gose@jwa:enc(),
binary()
) -> {ok, binary()} | {error, gose:gose_error()}.
encrypt_with_password(Key, Alg, Enc, Password) ->
Plaintext = jwk_to_plaintext(Key),
Encryptor = begin
_pipe = gose@jwe:new_pbes2(Alg, Enc),
gose@jwe:with_cty(_pipe, <<"jwk+json"/utf8>>)
end,
_pipe@1 = gose@jwe:encrypt_with_password(Encryptor, Password, Plaintext),
gleam@result:'try'(_pipe@1, fun gose@jwe:serialize_compact/1).