90 lines
2.5 KiB
Elixir
90 lines
2.5 KiB
Elixir
defmodule Age.X25519 do
|
|
@moduledoc """
|
|
Internal helper functions for the X25519 identity format.
|
|
"""
|
|
|
|
@type public_key :: binary()
|
|
@type private_key :: binary()
|
|
|
|
@doc """
|
|
Generates a new public/private keypair.
|
|
"""
|
|
@spec generate_keypair() :: {public_key(), private_key()}
|
|
def generate_keypair() do
|
|
:libsodium_crypto_box.keypair()
|
|
end
|
|
|
|
@doc """
|
|
Encodes the public key as a Bech32 string of format age1...
|
|
"""
|
|
@spec encode_public_key(public_key()) :: binary()
|
|
def encode_public_key(pub) do
|
|
Bech32.encode("age", pub)
|
|
end
|
|
|
|
@doc """
|
|
Decodes the public key from a Bech32 string of format age1...
|
|
"""
|
|
@spec decode_public_key(binary()) :: public_key()
|
|
def decode_public_key(pub) do
|
|
{:ok, "age", res} = Bech32.decode(pub)
|
|
|
|
res
|
|
end
|
|
|
|
@doc """
|
|
Encodes the private key as a Bech32 string of format AGE-SECRET-KEY-1...
|
|
"""
|
|
@spec encode_private_key(private_key()) :: binary()
|
|
def encode_private_key(priv) do
|
|
Bech32.encode("AGE-SECRET-KEY-", priv)
|
|
end
|
|
|
|
@doc """
|
|
Decodes the private key from a Bech32 string of format AGE-SECRET-KEY-1...
|
|
"""
|
|
@spec decode_private_key(binary()) :: private_key()
|
|
def decode_private_key(priv) do
|
|
{:ok, "age-secret-key-", res} = Bech32.decode(priv)
|
|
|
|
res
|
|
end
|
|
|
|
@doc """
|
|
Generates the stanza for file encryption from the given recipient and file key.
|
|
"""
|
|
@spec stanza(public_key(), binary()) :: {binary(), binary()}
|
|
def stanza(recipient, file_key) do
|
|
{eph_share, eph_secret} = generate_keypair()
|
|
|
|
shared_secret = :crypto.compute_key(:ecdh, recipient, eph_secret, :x25519)
|
|
|
|
wrap_key = derive_wrap_key(shared_secret, eph_share, recipient)
|
|
raw_body = :libsodium_crypto_aead_chacha20poly1305.ietf_encrypt(file_key, <<0::96>>, wrap_key)
|
|
|
|
32 = byte_size(raw_body)
|
|
|
|
header = "X25519 " <> (eph_share |> Base.encode64(padding: false))
|
|
body = raw_body |> Base.encode64(padding: false)
|
|
|
|
{header, body}
|
|
end
|
|
|
|
@doc """
|
|
X25519(secret, base) to derive a public key from a private key.
|
|
"""
|
|
@spec derive_public_key(private_key()) :: public_key()
|
|
def derive_public_key(private_key) do
|
|
:libsodium_crypto_scalarmult_curve25519.base(private_key)
|
|
end
|
|
|
|
@doc """
|
|
Helper function to derive the wrap key for the file key.
|
|
"""
|
|
@spec derive_wrap_key(binary(), binary(), public_key()) :: binary()
|
|
def derive_wrap_key(shared_secret, ephemeral_share, recipient) do
|
|
info = "age-encryption.org/v1/X25519"
|
|
salt = ephemeral_share <> recipient
|
|
HKDF.derive(:sha256, shared_secret, 32, salt, info)
|
|
end
|
|
end
|