diff --git a/lib/livebook/application.ex b/lib/livebook/application.ex index 6c6a236de36..2791294553b 100644 --- a/lib/livebook/application.ex +++ b/lib/livebook/application.ex @@ -258,12 +258,12 @@ defmodule Livebook.Application do "You specified LIVEBOOK_TEAMS_NAME, but LIVEBOOK_TEAMS_KEY is missing." ) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(teams_key) + secret_key = Livebook.Teams.derive_key(teams_key) id = "team-#{name}" secrets = if encrypted_secrets do - case Livebook.Teams.decrypt(encrypted_secrets, secret_key, sign_secret) do + case Livebook.Teams.decrypt(encrypted_secrets, secret_key) do {:ok, json} -> for {name, value} <- Jason.decode!(json), do: %Livebook.Secrets.Secret{ @@ -283,7 +283,7 @@ defmodule Livebook.Application do file_systems = if encrypted_file_systems do - case Livebook.Teams.decrypt(encrypted_file_systems, secret_key, sign_secret) do + case Livebook.Teams.decrypt(encrypted_file_systems, secret_key) do {:ok, json} -> for %{"type" => type} = dumped_data <- Jason.decode!(json), do: Livebook.FileSystems.load(type, dumped_data) diff --git a/lib/livebook/hubs/personal.ex b/lib/livebook/hubs/personal.ex index 7d154c92bb8..7ca4efd3502 100644 --- a/lib/livebook/hubs/personal.ex +++ b/lib/livebook/hubs/personal.ex @@ -237,17 +237,22 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Personal do end def notebook_stamp(personal, notebook_source, metadata) do - token = Livebook.Stamping.aead_encrypt(metadata, notebook_source, personal.secret_key) - - stamp = %{"version" => 1, "token" => token} - + token = Livebook.Stamping.chapoly_encrypt(metadata, notebook_source, personal.secret_key) + stamp = %{"version" => 2, "token" => token} {:ok, stamp} end def verify_notebook_stamp(personal, notebook_source, stamp) do - %{"version" => 1, "token" => token} = stamp + case stamp do + %{"version" => 1, "token" => token} -> + Livebook.Stamping.aead_decrypt(token, notebook_source, personal.secret_key) - Livebook.Stamping.aead_decrypt(token, notebook_source, personal.secret_key) + %{"version" => 2, "token" => token} -> + Livebook.Stamping.chapoly_decrypt(token, notebook_source, personal.secret_key) + + %{"version" => _} -> + {:error, :too_recent_version} + end end def dump(personal) do diff --git a/lib/livebook/hubs/provider.ex b/lib/livebook/hubs/provider.ex index ec8f47fcf54..72ca7e3a093 100644 --- a/lib/livebook/hubs/provider.ex +++ b/lib/livebook/hubs/provider.ex @@ -100,7 +100,7 @@ defprotocol Livebook.Hubs.Provider do See `t:notebook_stamp/0` for more details. """ @spec verify_notebook_stamp(t(), iodata(), notebook_stamp()) :: - {:ok, metadata :: map()} | :error + {:ok, metadata :: map()} | {:error, :invalid | :too_recent_version} def verify_notebook_stamp(hub, notebook_source, stamp) @doc """ diff --git a/lib/livebook/hubs/team.ex b/lib/livebook/hubs/team.ex index 21c9f1b12d8..b79b85392d1 100644 --- a/lib/livebook/hubs/team.ex +++ b/lib/livebook/hubs/team.ex @@ -153,7 +153,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do # request to the Teams server (which ensures team membership). @teams_key_prefix <> teams_key = team.teams_key - token = Livebook.Stamping.aead_encrypt(metadata, notebook_source, teams_key) + token = Livebook.Stamping.chapoly_encrypt(metadata, notebook_source, teams_key) case Livebook.Teams.org_sign(team, token) do {:ok, token_signature} -> @@ -172,9 +172,9 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do @public_key_prefix <> org_public_key = team.org_public_key if Livebook.Stamping.rsa_verify?(token_signature, token, org_public_key) do - Livebook.Stamping.aead_decrypt(token, notebook_source, teams_key) + Livebook.Stamping.chapoly_decrypt(token, notebook_source, teams_key) else - :error + {:error, :invalid} end end diff --git a/lib/livebook/hubs/team_client.ex b/lib/livebook/hubs/team_client.ex index 515917eff3e..35c3b4543a8 100644 --- a/lib/livebook/hubs/team_client.ex +++ b/lib/livebook/hubs/team_client.ex @@ -14,7 +14,7 @@ defmodule Livebook.Hubs.TeamClient do defstruct [ :hub, :connection_error, - :derived_keys, + :derived_key, connected?: false, secrets: [], file_systems: [] @@ -82,7 +82,7 @@ defmodule Livebook.Hubs.TeamClient do @impl true def init(%Hubs.Team{offline: nil} = team) do - derived_keys = Teams.derive_keys(team.teams_key) + derived_key = Teams.derive_key(team.teams_key) headers = [ {"x-lb-version", to_string(Application.spec(:livebook, :vsn))}, @@ -93,18 +93,18 @@ defmodule Livebook.Hubs.TeamClient do ] {:ok, _pid} = Teams.Connection.start_link(self(), headers) - {:ok, %__MODULE__{hub: team, derived_keys: derived_keys}} + {:ok, %__MODULE__{hub: team, derived_key: derived_key}} end def init(%Hubs.Team{} = team) do - derived_keys = Teams.derive_keys(team.teams_key) + derived_key = Teams.derive_key(team.teams_key) {:ok, %__MODULE__{ hub: team, secrets: team.offline.secrets, file_systems: team.offline.file_systems, - derived_keys: derived_keys + derived_key: derived_key }} end @@ -165,8 +165,7 @@ defmodule Livebook.Hubs.TeamClient do end defp build_secret(state, %{name: name, value: value}) do - {secret_key, sign_secret} = state.derived_keys - {:ok, decrypted_value} = Teams.decrypt(value, secret_key, sign_secret) + {:ok, decrypted_value} = Teams.decrypt(value, state.derived_key) %Secrets.Secret{ name: name, @@ -189,8 +188,7 @@ defmodule Livebook.Hubs.TeamClient do end defp build_file_system(state, file_system) do - {secret_key, sign_secret} = state.derived_keys - {:ok, decrypted_value} = Teams.decrypt(file_system.value, secret_key, sign_secret) + {:ok, decrypted_value} = Teams.decrypt(file_system.value, state.derived_key) dumped_data = decrypted_value diff --git a/lib/livebook/live_markdown/import.ex b/lib/livebook/live_markdown/import.ex index 358fb558a87..87d4ab6d46d 100644 --- a/lib/livebook/live_markdown/import.ex +++ b/lib/livebook/live_markdown/import.ex @@ -603,6 +603,7 @@ defmodule Livebook.LiveMarkdown.Import do @invalid_stamp_message "invalid notebook stamp, disabling default access to secrets and remote files " @personal_stamp_context "(you are either not the author of this notebook or changed its source outside of Livebook)" @org_stamp_context "(this may happen if you made changes to the notebook source outside of Livebook)" + @too_recent_stamp_context "(the stamp has been generated using a more recent Livebook version, you need to upgrade)" defp postprocess_stamp(notebook, _notebook_source, nil), do: {notebook, []} @@ -616,12 +617,17 @@ defmodule Livebook.LiveMarkdown.Import do notebook = apply_stamp_metadata(notebook, metadata) {true, notebook, []} else - _ -> + error -> extra = - if notebook.hub_id == "personal-hub" do - @personal_stamp_context - else - @org_stamp_context + cond do + error == {:error, :too_recent_version} -> + @too_recent_stamp_context + + notebook.hub_id == "personal-hub" -> + @personal_stamp_context + + true -> + @org_stamp_context end {false, notebook, [@invalid_stamp_message <> extra]} diff --git a/lib/livebook/stamping.ex b/lib/livebook/stamping.ex index 48323e90f8a..11e9e07144a 100644 --- a/lib/livebook/stamping.ex +++ b/lib/livebook/stamping.ex @@ -4,44 +4,60 @@ defmodule Livebook.Stamping do @doc """ Performs authenticated encryption with associated data (AEAD) [1]. - Uses AES-GCM-128 [2]. Returns a single token which carries encrypted - `payload` and signature for both `payload` and `additional_data`. + 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://www.rfc-editor.org/rfc/rfc5116#section-5 + [2]: https://en.wikipedia.org/wiki/XChaCha20-Poly1305 """ - @spec aead_encrypt(term(), String.t(), String.t()) :: String.t() - def aead_encrypt(payload, additional_data, secret_key) do - {secret, sign_secret} = derive_keys(secret_key) - + @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, sign_secret) + 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_encrypt/3`. + 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 + @spec aead_decrypt(String.t(), String.t(), String.t()) :: {:ok, term()} | {:error, :invalid} def aead_decrypt(encrypted, additional_data, secret_key) do - {secret, sign_secret} = derive_keys(secret_key) + <> = derive_key(secret_key) case Plug.Crypto.MessageEncryptor.decrypt(encrypted, additional_data, secret, sign_secret) do {:ok, payload} -> - payload = Plug.Crypto.non_executable_binary_to_term(payload) - {:ok, payload} + {:ok, Plug.Crypto.non_executable_binary_to_term(payload)} :error -> - :error + {:error, :invalid} end end - defp derive_keys(secret_key) do + 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) - - {secret, sign_secret} + Plug.Crypto.KeyGenerator.generate(binary_key, "notebook signing", cache: Plug.Crypto.Keys) end @doc """ diff --git a/lib/livebook/teams.ex b/lib/livebook/teams.ex index 86e7a4bf694..2e71a4c6fbb 100644 --- a/lib/livebook/teams.ex +++ b/lib/livebook/teams.ex @@ -223,34 +223,30 @@ defmodule Livebook.Teams do @doc """ Encrypts the given value with Teams key derived keys. """ - @spec encrypt(String.t() | nil, bitstring(), bitstring()) :: String.t() - def encrypt(value, _secret, _sign_secret) when value in ["", nil], do: value + @spec encrypt(String.t() | nil, bitstring()) :: String.t() + def encrypt(value, _secret) when value in ["", nil], do: value - def encrypt(value, secret, sign_secret) do - Plug.Crypto.MessageEncryptor.encrypt(value, secret, sign_secret) + def encrypt(value, secret) do + Plug.Crypto.MessageEncryptor.encrypt(value, secret, "unused") end @doc """ Decrypts the given encrypted value with Teams key derived keys. """ - @spec decrypt(String.t() | nil, bitstring(), bitstring()) :: {:ok, String.t()} | :error - def decrypt(value, _secret, _sign_secret) when value in ["", nil], do: value + @spec decrypt(String.t() | nil, bitstring()) :: {:ok, String.t()} | :error + def decrypt(value, _secret) when value in ["", nil], do: value - def decrypt(encrypted_value, secret, sign_secret) do - Plug.Crypto.MessageEncryptor.decrypt(encrypted_value, secret, sign_secret) + def decrypt(encrypted_value, secret) do + Plug.Crypto.MessageEncryptor.decrypt(encrypted_value, secret, "unused") end @doc """ Derives the secret and sign secret from given `teams_key`. """ - @spec derive_keys(String.t()) :: {bitstring(), bitstring()} - def derive_keys(@prefix <> teams_key) do + @spec derive_key(String.t()) :: bitstring() + def derive_key(@prefix <> teams_key) do binary_key = Base.url_decode64!(teams_key, padding: false) - - <> = - Plug.Crypto.KeyGenerator.generate(binary_key, "notebook secret", cache: Plug.Crypto.Keys) - - {secret, sign_secret} + Plug.Crypto.KeyGenerator.generate(binary_key, "notebook secret", cache: Plug.Crypto.Keys) end defp add_org_errors(%Ecto.Changeset{} = changeset, errors_map) do diff --git a/lib/livebook/teams/requests.ex b/lib/livebook/teams/requests.ex index cd4bb359a82..f7fb917919a 100644 --- a/lib/livebook/teams/requests.ex +++ b/lib/livebook/teams/requests.ex @@ -50,8 +50,8 @@ defmodule Livebook.Teams.Requests do @spec create_secret(Team.t(), Secret.t()) :: {:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()} def create_secret(team, secret) do - {secret_key, sign_secret} = Teams.derive_keys(team.teams_key) - secret_value = Teams.encrypt(secret.value, secret_key, sign_secret) + secret_key = Teams.derive_key(team.teams_key) + secret_value = Teams.encrypt(secret.value, secret_key) headers = auth_headers(team) params = %{name: secret.name, value: secret_value} @@ -65,8 +65,8 @@ defmodule Livebook.Teams.Requests do @spec update_secret(Team.t(), Secret.t()) :: {:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()} def update_secret(team, secret) do - {secret_key, sign_secret} = Teams.derive_keys(team.teams_key) - secret_value = Teams.encrypt(secret.value, secret_key, sign_secret) + secret_key = Teams.derive_key(team.teams_key) + secret_value = Teams.encrypt(secret.value, secret_key) headers = auth_headers(team) params = %{name: secret.name, value: secret_value} @@ -92,7 +92,7 @@ defmodule Livebook.Teams.Requests do @spec create_file_system(Team.t(), FileSystem.t()) :: {:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()} def create_file_system(team, file_system) do - {secret_key, sign_secret} = Teams.derive_keys(team.teams_key) + secret_key = Teams.derive_key(team.teams_key) headers = auth_headers(team) type = FileSystems.type(file_system) @@ -103,7 +103,7 @@ defmodule Livebook.Teams.Requests do params = %{ name: name, type: to_string(type), - value: Teams.encrypt(json, secret_key, sign_secret) + value: Teams.encrypt(json, secret_key) } post("/api/v1/org/file-systems", params, headers) @@ -115,7 +115,7 @@ defmodule Livebook.Teams.Requests do @spec update_file_system(Team.t(), FileSystem.t()) :: {:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()} def update_file_system(team, file_system) do - {secret_key, sign_secret} = Teams.derive_keys(team.teams_key) + secret_key = Teams.derive_key(team.teams_key) headers = auth_headers(team) type = FileSystems.type(file_system) @@ -127,7 +127,7 @@ defmodule Livebook.Teams.Requests do id: file_system.external_id, name: name, type: to_string(type), - value: Teams.encrypt(json, secret_key, sign_secret) + value: Teams.encrypt(json, secret_key) } put("/api/v1/org/file-systems", params, headers) diff --git a/lib/livebook_web/live/hub/edit/team_component.ex b/lib/livebook_web/live/hub/edit/team_component.ex index d6a39671e63..ef01467d651 100644 --- a/lib/livebook_web/live/hub/edit/team_component.ex +++ b/lib/livebook_web/live/hub/edit/team_component.ex @@ -587,11 +587,11 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do end defp encrypt_to_dockerfile(socket, data) do - {secret_key, sign_secret} = Livebook.Teams.derive_keys(socket.assigns.hub.teams_key) + secret_key = Livebook.Teams.derive_key(socket.assigns.hub.teams_key) data |> Jason.encode!() - |> Livebook.Teams.encrypt(secret_key, sign_secret) + |> Livebook.Teams.encrypt(secret_key) end @zta_options for provider <- Livebook.Config.identity_providers(), diff --git a/mix.exs b/mix.exs index f3059b14471..d731ac03e41 100644 --- a/mix.exs +++ b/mix.exs @@ -94,15 +94,15 @@ defmodule Livebook.MixProject do # defp deps do [ - {:phoenix, "~> 1.7.7"}, - {:phoenix_html, "~> 3.0"}, - # {:phoenix_live_view, "~> 0.20.0"}, + {:phoenix, github: "phoenixframework/phoenix", override: true}, {:phoenix_live_view, github: "phoenixframework/phoenix_live_view", override: true}, + {:phoenix_html, "~> 3.0"}, {:phoenix_live_dashboard, "~> 0.8.0"}, {:telemetry_metrics, "~> 0.4"}, {:telemetry_poller, "~> 1.0"}, {:jason, "~> 1.0"}, {:plug_cowboy, "~> 2.0"}, + {:plug_crypto, "~> 2.0"}, {:earmark_parser, "~> 1.4"}, {:castore, "~> 1.0"}, {:ecto, "~> 3.10"}, diff --git a/mix.lock b/mix.lock index c8e40c66e57..31f34f9382c 100644 --- a/mix.lock +++ b/mix.lock @@ -25,17 +25,17 @@ "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, + "phoenix": {:git, "https://github.com/phoenixframework/phoenix.git", "4b19e58174c782b2869b28d4139eb9c82353fe54", []}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.2", "b9e33c950d1ed98494bfbde1c34c6e51c8a4214f3bea3f07ca9a510643ee1387", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "67a598441b5f583d301a77e0298719f9654887d3d8bf14e80ff0b6acf887ef90"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"}, - "phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "d75d14a7fa0469cbef40e034273bd349611262ad", []}, + "phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "1c6b997b8906b37c00122d0614febe6b995d9f95", []}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, - "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "protobuf": {:hex, :protobuf, "0.8.0", "61b27d6fd50e7b1b2eb0ee17c1f639906121f4ef965ae0994644eb4c68d4647d", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "3644ed846fd6f5e3b5c2cd617aa8344641e230edf812a45365fee7622bccd25a"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "req": {:hex, :req, "0.3.8", "e254074435c970b1d7699777f1a8466acbacab5e6ba4a264d35053bf52c03467", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a17244d18a7fbf3e9892c38c10628224f6f7974fd364392ca0d85f91e3cc8251"}, diff --git a/test/livebook/live_markdown/import_test.exs b/test/livebook/live_markdown/import_test.exs index 9d0abf469db..fec653ce8e0 100644 --- a/test/livebook/live_markdown/import_test.exs +++ b/test/livebook/live_markdown/import_test.exs @@ -1123,6 +1123,24 @@ defmodule Livebook.LiveMarkdown.ImportTest do IO.puts("hey") ``` + + """ + + {notebook, []} = Import.notebook_from_livemd(markdown) + + assert %Notebook{hub_secret_names: ["DB_PASSWORD"]} = notebook + end + + test "restores hub secret names from notebook stamp using personal hub v1 stamp" do + markdown = """ + # My Notebook + + ## Section 1 + + ```elixir + IO.puts("hey") + ``` + """ @@ -1141,7 +1159,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do IO.puts("hey") ``` - + """ {notebook, messages} = Import.notebook_from_livemd(markdown) @@ -1162,7 +1180,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do IO.puts("hey") ``` - + """ {notebook, []} = Import.notebook_from_livemd(markdown) diff --git a/test/livebook_teams/hubs/team_client_test.exs b/test/livebook_teams/hubs/team_client_test.exs index 90f8c29d48e..ced2ba150ad 100644 --- a/test/livebook_teams/hubs/team_client_test.exs +++ b/test/livebook_teams/hubs/team_client_test.exs @@ -192,8 +192,8 @@ defmodule Livebook.Hubs.TeamClientTest do hub_id: id ) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(team.teams_key) - secret_value = Livebook.Teams.encrypt(secret.value, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(team.teams_key) + secret_value = Livebook.Teams.encrypt(secret.value, secret_key) livebook_proto_secret = LivebookProto.Secret.new!(name: secret.name, value: secret_value) user_connected = @@ -223,8 +223,8 @@ defmodule Livebook.Hubs.TeamClientTest do hub_id: id ) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(team.teams_key) - secret_value = Livebook.Teams.encrypt(secret.value, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(team.teams_key) + secret_value = Livebook.Teams.encrypt(secret.value, secret_key) livebook_proto_secret = LivebookProto.Secret.new!(name: secret.name, value: secret_value) user_connected = @@ -242,7 +242,7 @@ defmodule Livebook.Hubs.TeamClientTest do assert_receive {:secret_created, ^secret} updated_secret = %{secret | value: "an updated value"} - secret_value = Livebook.Teams.encrypt(updated_secret.value, secret_key, sign_secret) + secret_value = Livebook.Teams.encrypt(updated_secret.value, secret_key) updated_livebook_proto_secret = LivebookProto.Secret.new!(name: updated_secret.name, value: secret_value) @@ -272,8 +272,8 @@ defmodule Livebook.Hubs.TeamClientTest do hub_id: id ) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(team.teams_key) - secret_value = Livebook.Teams.encrypt(secret.value, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(team.teams_key) + secret_value = Livebook.Teams.encrypt(secret.value, secret_key) livebook_proto_secret = LivebookProto.Secret.new!(name: secret.name, value: secret_value) user_connected = @@ -319,8 +319,8 @@ defmodule Livebook.Hubs.TeamClientTest do attrs = Livebook.FileSystem.dump(file_system) credentials = Jason.encode!(attrs) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(team.teams_key) - value = Livebook.Teams.encrypt(credentials, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(team.teams_key) + value = Livebook.Teams.encrypt(credentials, secret_key) livebook_proto_file_system = LivebookProto.FileSystem.new!( @@ -362,8 +362,8 @@ defmodule Livebook.Hubs.TeamClientTest do attrs = Livebook.FileSystem.dump(file_system) credentials = Jason.encode!(attrs) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(team.teams_key) - value = Livebook.Teams.encrypt(credentials, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(team.teams_key) + value = Livebook.Teams.encrypt(credentials, secret_key) livebook_proto_file_system = LivebookProto.FileSystem.new!( @@ -395,7 +395,7 @@ defmodule Livebook.Hubs.TeamClientTest do updated_attrs = Livebook.FileSystem.dump(updated_file_system) updated_credentials = Jason.encode!(updated_attrs) - updated_value = Livebook.Teams.encrypt(updated_credentials, secret_key, sign_secret) + updated_value = Livebook.Teams.encrypt(updated_credentials, secret_key) updated_livebook_proto_file_system = LivebookProto.FileSystem.new!( @@ -435,8 +435,8 @@ defmodule Livebook.Hubs.TeamClientTest do attrs = Livebook.FileSystem.dump(file_system) credentials = Jason.encode!(attrs) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(team.teams_key) - value = Livebook.Teams.encrypt(credentials, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(team.teams_key) + value = Livebook.Teams.encrypt(credentials, secret_key) livebook_proto_file_system = LivebookProto.FileSystem.new!( diff --git a/test/livebook_teams/hubs/team_test.exs b/test/livebook_teams/hubs/team_test.exs index 8274d08426a..b53294654d0 100644 --- a/test/livebook_teams/hubs/team_test.exs +++ b/test/livebook_teams/hubs/team_test.exs @@ -25,7 +25,8 @@ defmodule Livebook.Hubs.TeamTest do assert {:ok, ^metadata} = Provider.verify_notebook_stamp(team, notebook_source, stamp) - assert :error = Provider.verify_notebook_stamp(team, notebook_source <> "change\n", stamp) + assert {:error, :invalid} = + Provider.verify_notebook_stamp(team, notebook_source <> "change\n", stamp) end end end diff --git a/test/support/hub_helpers.ex b/test/support/hub_helpers.ex index 4ac382c82b3..eab1084c20d 100644 --- a/test/support/hub_helpers.ex +++ b/test/support/hub_helpers.ex @@ -3,14 +3,16 @@ defmodule Livebook.HubHelpers do import Livebook.Factory import Phoenix.LiveViewTest + @offline_hub_key "lb_tk_A9TarFeAzmX3sDwSPm5JP5qbLPnNpLpzmjVZUCHXwmI" + @offline_hub_org_public_key "lb_opk_MIIBCgKCAQEAnDlKJkOnegStai82RYoJ95mFxsD__Vn86CJtyFyGi71dxUhyY1Xta779wNZbLuK7UGFFF18jjwWcGUohoxvkxykSVaC71ULeTX03zGFMQ61za77N9F9uCbwwEcngN7y8KfCAXWK4WAx5gA_a_LvdKmzAk3emWFvCKeyABXVnafj4HCUFPPdTdARFfIS56UvqXZ27Fbzbvk1DxAKR6js9kvQJDiDUD6R1dcv_Yu-Eh4iMjBZd0i1eGD9UntmVRhTZbwP70iP-i4Dv5z0573lVNyPdIwAVon7K4n-j8l7EIYwCQRtyZCHxsqkf_3f3VAiDK4ZLovGguutLC1tYObyXaQIDAQAB" + @offline_hub_org_private_key "MIIEpAIBAAKCAQEAnDlKJkOnegStai82RYoJ95mFxsD__Vn86CJtyFyGi71dxUhyY1Xta779wNZbLuK7UGFFF18jjwWcGUohoxvkxykSVaC71ULeTX03zGFMQ61za77N9F9uCbwwEcngN7y8KfCAXWK4WAx5gA_a_LvdKmzAk3emWFvCKeyABXVnafj4HCUFPPdTdARFfIS56UvqXZ27Fbzbvk1DxAKR6js9kvQJDiDUD6R1dcv_Yu-Eh4iMjBZd0i1eGD9UntmVRhTZbwP70iP-i4Dv5z0573lVNyPdIwAVon7K4n-j8l7EIYwCQRtyZCHxsqkf_3f3VAiDK4ZLovGguutLC1tYObyXaQIDAQABAoIBAAhtzuJhpBehSPoBshvuZrtFPUKMB0PUJyEfOm0lEN1ZSkXqssFJUZYOqAJPjnvpH9ImbWPlbjW5R8LVjRsP1jgorySPl5LaGMR1jR0p4sOECEY39UTKIVXFIZLUIZTgSga5QzPGr8uQYL3YHSilujkfxQQv4HnD-aXpbL7epsXA44w7RlibwNhrNnMMYSCiRgTB8tqfuVtXAIaLfS46_v3WQqQ-al2yj3DRtxD2FmOfZzbUFkRXC9tUdCLwsNmrwhA06PYduCabZzZ8I-_j36OdCzMPSx9cVMMstDo11YP6yOIcUuloQO8iNBRrcXB7katt-PiJraJm-PVd_NPA8EECgYEAzHkJ0RaANfKUEo_FbDclURm_-uWjP-z2avwAMbjDL37S635BMHS5WuFH8YP6wTC3C7sx_RW2HFlZwhvzCko3yeeM9kvdR8OEoKcGi-Ngj6My3ks2Qy5AOoIDhPBzoI1HJU1kA5SdV81i9LTQk-D7CMHt_DxT5FwKP3L3XOMumFcCgYEAw5ebmKW77t5ksni0k2oV5l3uk0Jyg9lr_y9kkUytiosPtIxSayBag79KuR221lQVEAC5unqiGjBlCCE_doqWtXfmuEYVB43PSnsMOVbTTbk_2cR-qNmkM7407LJrvO6Zpru83HDhrjYiBucRiM_wXPJPA6Ko32fQAW9YPT07dj8CgYAwlLV7YyA1MRxzSIt8iaGpIjgV0Ye3AYMOqi8VoTNmzngokYfFjoYXjJz-SgBC9GMZO3HGEumA1M3Zq7BUCow0wXohbqb1jQOu8-A_Tle76OeGH1KWJaAHBqr1Y_fk9own1bpki2PS366aO3evGu4qB4GWw3KfOCsLJjKVdDi24wKBgQCVIJLR4AtaJNZB_SYw_0GTUysDvDXzsWJWPpw-7GekqkJfNl3gr9pTeRZP7gfpglJM0UDKnZXawetGN5Nbnm8qDTEsbsK577WM5CR902VobUXxk4--zbIUgYF4ttDOTF16csmcibSIT13CRYto9KIfO-BitTJso4pEjdCJYJZloQKBgQCJMlaq0riNXpsbkt5yFXy8_thXvCCEm_HEjdSbYB_UNhnnvvrK1X310LvXyRAY-GeLTAxjHm3fJlfccYB6yAslsBYCxLql0Yfszn9fltIBZQuisYajIzSo56p06Di7zZDXalHaCHfeGUCgDvlPt0UO4kWfSSd_o11wkLZBUnuLHA" + @offline_hub_org_name "org-number-3079" + @offline_hub %Livebook.Hubs.Team{ - id: "team-org-number-3079", - teams_key: - Livebook.Teams.Org.teams_key_prefix() <> "A9TarFeAzmX3sDwSPm5JP5qbLPnNpLpzmjVZUCHXwmI", - org_public_key: - Livebook.Hubs.Team.public_key_prefix() <> - "MIIBCgKCAQEA5v_qciaRGOZd5kgCQbhQDgFCnTnIKI5xzN4m4rVtLXMPH7RTA-K6C-e4wy2gn8zulXgSYX4vXDACSjFAG4PlFhXTPgb-v3rFLwbBrUHdaTMTyxRdK52NyNoDpYklQ7FaEU9vr3Z_-cpAQjdADOV1k45GmFe3bo4gImIfUSDYp1rRiEsYcIBt0Wc0S-vQHKSlmfcCexe254_UkvWjLW7KO790bem-PSWcBI_713oRr2mQoxXeeGKd5dSyFsIr5SZXVRWcRK3soQimCXB0ddBSXZ7d2Md3P9Ylo7TcYdBGHlwVIsrmB-P70KPHPYuAVgS9QsIiiMGXPwYVW77xNRTlcwIDAQAB", - hub_name: "org-number-3079", + id: "team-#{@offline_hub_org_name}", + teams_key: @offline_hub_key, + org_public_key: @offline_hub_org_public_key, + hub_name: @offline_hub_org_name, user_id: 0, org_id: 0, org_key_id: 0, @@ -60,6 +62,36 @@ defmodule Livebook.HubHelpers do ) end + def build_offline_team_hub(user, node) do + teams_org = build(:org, teams_key: @offline_hub_key, name: @offline_hub_org_name) + key_hash = Livebook.Teams.Org.key_hash(teams_org) + + org = erpc_call(node, :create_org, [[name: teams_org.name]]) + org_key = erpc_call(node, :create_org_key, [[org: org, key_hash: key_hash]]) + + org_key_pair = + erpc_call(node, :create_org_key_pair, [ + [ + org: org, + public_key: @offline_hub_org_public_key, + private_key: @offline_hub_org_private_key + ] + ]) + + token = erpc_call(node, :associate_user_with_org, [user, org]) + + build(:team, + id: "team-#{org.name}", + hub_name: org.name, + user_id: user.id, + org_id: org.id, + org_key_id: org_key.id, + org_public_key: org_key_pair.public_key, + session_token: token, + teams_key: teams_org.teams_key + ) + end + def assert_sidebar_hub(view, id, name, emoji \\ "🐈") do hub = element(view, hub_element_id(id)) hub_html = render(hub) @@ -85,8 +117,8 @@ defmodule Livebook.HubHelpers do def put_offline_hub_secret(secret) do hub = offline_hub() {:ok, pid} = hub_pid(hub) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(hub.teams_key) - value = Livebook.Teams.encrypt(secret.value, secret_key, sign_secret) + secret_key = Livebook.Teams.derive_key(hub.teams_key) + value = Livebook.Teams.encrypt(secret.value, secret_key) secret_created = LivebookProto.SecretCreated.new(name: secret.name, value: value) send(pid, {:event, :secret_created, secret_created}) @@ -103,11 +135,11 @@ defmodule Livebook.HubHelpers do def put_offline_hub_file_system(file_system) do hub = offline_hub() {:ok, pid} = hub_pid(hub) - {secret_key, sign_secret} = Livebook.Teams.derive_keys(hub.teams_key) + secret_key = Livebook.Teams.derive_key(hub.teams_key) %{name: name} = Livebook.FileSystem.external_metadata(file_system) attrs = Livebook.FileSystem.dump(file_system) json = Jason.encode!(attrs) - value = Livebook.Teams.encrypt(json, secret_key, sign_secret) + value = Livebook.Teams.encrypt(json, secret_key) file_system_created = LivebookProto.FileSystemCreated.new(