Packages
livebook
0.16.3
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/stamping.ex
defmodule Livebook.Stamping do
# Cryptographic functions used to implement notebook stamping.
@doc """
Performs authenticated encryption with associated data (AEAD) [1].
Uses XChaCha20-Poly1305 [2]. Returns a single token which carries
encrypted `payload` and signature for both `payload` and
`additional_data`.
[1]: https://en.wikipedia.org/wiki/Authenticated_encryption#Authenticated_encryption_with_associated_data_(AEAD)
[2]: https://en.wikipedia.org/wiki/XChaCha20-Poly1305
"""
@spec chapoly_encrypt(term(), String.t(), String.t()) :: String.t()
def chapoly_encrypt(payload, additional_data, secret_key) do
secret = derive_key(secret_key)
payload = :erlang.term_to_binary(payload)
Plug.Crypto.MessageEncryptor.encrypt(payload, additional_data, secret, "unused")
end
@doc """
Decrypts and verifies data obtained from `chapoly_encrypt/3`.
"""
@spec chapoly_decrypt(String.t(), String.t(), String.t()) :: {:ok, term()} | {:error, :invalid}
def chapoly_decrypt(encrypted, additional_data, secret_key) do
secret = derive_key(secret_key)
case Plug.Crypto.MessageEncryptor.decrypt(encrypted, additional_data, secret, "unused") do
{:ok, payload} ->
{:ok, Plug.Crypto.non_executable_binary_to_term(payload)}
:error ->
{:error, :invalid}
end
end
@doc """
Decrypts and verifies data obtained from AEAD using AES-GCM-128 [1].
Earlier Livebook versions implemented AEAD using AES-GCM-128 and
this function can be used to decrypt that data.
[1]: https://www.rfc-editor.org/rfc/rfc5116#section-5
"""
@spec aead_decrypt(String.t(), String.t(), String.t()) :: {:ok, term()} | {:error, :invalid}
def aead_decrypt(encrypted, additional_data, secret_key) do
<<secret::16-bytes, sign_secret::16-bytes>> = derive_key(secret_key)
case Plug.Crypto.MessageEncryptor.decrypt(encrypted, additional_data, secret, sign_secret) do
{:ok, payload} ->
{:ok, Plug.Crypto.non_executable_binary_to_term(payload)}
:error ->
{:error, :invalid}
end
end
defp derive_key(secret_key) do
binary_key = Base.url_decode64!(secret_key, padding: false)
Plug.Crypto.KeyGenerator.generate(binary_key, "notebook signing", cache: Plug.Crypto.Keys)
end
@doc """
Verifies RSA `signature` of the given `payload` using the public key.
"""
@spec rsa_verify?(String.t(), String.t(), String.t()) :: boolean()
def rsa_verify?(signature, payload, public_key) do
der_key = Base.url_decode64!(public_key, padding: false)
with {:ok, raw_key} <- safe_der_decode(der_key),
{:ok, raw_signature} <- Base.url_decode64(signature, padding: false) do
:public_key.verify(payload, :sha256, raw_signature, raw_key)
else
_ -> false
end
end
defp safe_der_decode(der_key) do
try do
{:ok, :public_key.der_decode(:RSAPublicKey, der_key)}
rescue
_ -> :error
end
end
end