Current section
Files
Jump to
Current section
Files
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)
hbody = [@format, # format
@version, # version
mode, # mode: 1 (attached) or 2 (detached)
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