Current section

Files

Jump to
saltpack lib saltpack sign.ex
Raw

lib/saltpack/sign.ex

defmodule Saltpack.Sign do
@moduledoc false
@format "saltpack"
@version [1, 0]
defp mode_constant(1), do: "saltpack attached signature\0"
defp mode_constant(2), do: "saltpack detached signature\0"
defp pack_header(our_public, mode) when mode == 1 or mode == 2 do
nonce = :crypto.strong_rand_bytes(32)
# format
hbody =
[
@format,
# version
@version,
# mode: 1 (attached) or 2 (detached)
mode,
our_public |> Msgpax.Bin.new(),
nonce |> Msgpax.Bin.new()
]
|> Msgpax.pack!()
|> IO.iodata_to_binary()
hhash = :crypto.hash(:sha512, hbody)
{hbody |> Msgpax.Bin.new() |> Msgpax.pack!() |> IO.iodata_to_binary(), hhash}
end
def create_message(payload, private, mode, nil),
do: create_message(payload, private, mode, Kcl.derive_public_key(private, :sign))
def create_message(payload, private, 2, public) do
{header, header_hash} = pack_header(public, 2)
header <> detached_signature(payload, header_hash, private, public)
end
def create_message(payload, private, 1, public) do
{header, header_hash} = pack_header(public, 1)
header <> pack_payload(payload, header_hash, private, public, 0, [])
end
defp detached_signature(text, hash, private, public) do
(mode_constant(2) <> :crypto.hash(:sha512, hash <> text))
|> Kcl.sign(private, public)
|> Msgpax.Bin.new()
|> Msgpax.pack!()
|> IO.iodata_to_binary()
end
defp nonce(n), do: n |> :binary.encode_unsigned() |> pad(8)
defp pad(s, n) when byte_size(s) == n, do: s
defp pad(s, n) when byte_size(s) < n, do: pad(<<0>> <> s, n)
defp pack_payload(
<<m::binary-size(1024), rest::binary>>,
header_hash,
private,
public,
nonce_count,
acc
) do
pack_payload(rest, header_hash, private, public, nonce_count + 1, [
code_payload(m, header_hash, private, public, nonce(nonce_count)) | acc
])
end
defp pack_payload(final, header_hash, private, public, nonce_count, acc) do
(acc |> Enum.reverse() |> Enum.join()) <>
code_payload(final, header_hash, private, public, nonce(nonce_count)) <>
code_payload("", header_hash, private, public, nonce(nonce_count + 1))
end
defp code_payload(payload, header_hash, private, public, nonce) do
payload_hash = :crypto.hash(:sha512, header_hash <> nonce <> payload)
[Kcl.sign(mode_constant(1) <> payload_hash, private, public), payload]
|> Enum.map(fn p -> Msgpax.Bin.new(p) end)
|> Msgpax.pack!()
|> IO.iodata_to_binary()
end
def open_message(message, nil) do
message
|> header_verify(1)
|> payloads_open(0, [])
end
def open_message(message, plaintext) do
message
|> header_verify(2)
|> verify_msg_match(plaintext)
end
defp verify_msg_match({signature, hhash, opub}, text) do
if Kcl.valid_signature?(
signature |> Msgpax.unpack!(),
mode_constant(2) <> :crypto.hash(:sha512, hhash <> text),
opub
),
do: opub,
else: sign_error()
end
defp payloads_open(_finished_payloads, _n, ["" | acc]), do: acc |> Enum.reverse() |> Enum.join()
defp payloads_open({payloads, hhash, pub}, nonce_count, acc) do
nonce = nonce(nonce_count)
{[tsig, data], rest} = Msgpax.unpack_slice!(payloads)
if not Kcl.valid_signature?(
tsig,
mode_constant(1) <> :crypto.hash(:sha512, hhash <> nonce <> data),
pub
),
do: sign_error()
payloads_open({rest, hhash, pub}, nonce_count + 1, [data | acc])
end
defp header_verify(packet, emode) do
{contents, rest} = Msgpax.unpack_slice!(packet)
hhash = :crypto.hash(:sha512, contents)
[format, version, mode, opub, _nonce] = Msgpax.unpack!(contents)
if format != @format or version != @version or mode != emode, do: sign_error()
{rest, hhash, opub}
end
defp sign_error, do: raise("Improper or incomplete message.")
end