2020-03-06 14:42:19 +00:00
|
|
|
require "spec_helper"
|
|
|
|
|
|
|
|
RSpec.describe Pigeon::Message do
|
|
|
|
before(:each) do
|
|
|
|
Pigeon::Storage.reset
|
2020-03-24 12:39:32 +00:00
|
|
|
Pigeon::LocalIdentity.reset
|
2020-03-06 14:42:19 +00:00
|
|
|
end
|
|
|
|
|
2020-03-11 13:43:54 +00:00
|
|
|
def create_draft(params)
|
2020-03-06 14:42:19 +00:00
|
|
|
draft = Pigeon::Draft.create(kind: "unit_test")
|
2020-03-11 13:43:54 +00:00
|
|
|
params.each { |(k, v)| draft[k] = v }
|
2020-03-06 14:42:19 +00:00
|
|
|
draft
|
|
|
|
end
|
|
|
|
|
2020-03-11 13:43:54 +00:00
|
|
|
def create_message(params)
|
|
|
|
draft = create_draft(params)
|
2020-04-07 13:58:53 +00:00
|
|
|
draft.publish
|
2020-03-11 13:43:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
let(:draft) do
|
|
|
|
hash = Pigeon::Storage.current.set_blob(File.read("./logo.png"))
|
|
|
|
create_draft({ "a" => "bar",
|
|
|
|
"b" => hash })
|
|
|
|
end
|
|
|
|
|
2020-04-11 14:42:43 +00:00
|
|
|
let(:templated_message) { create_message({ "a" => "b" }) }
|
|
|
|
|
2020-03-12 12:34:22 +00:00
|
|
|
let (:template) do
|
2020-04-11 14:42:43 +00:00
|
|
|
Pigeon::MessageSerializer.new(templated_message)
|
2020-03-12 12:34:22 +00:00
|
|
|
end
|
|
|
|
|
2020-03-07 15:38:34 +00:00
|
|
|
it "discards a draft after signing" do
|
|
|
|
expect(draft.internal_id).to eq(Pigeon::Draft.current.internal_id)
|
2020-04-07 13:58:53 +00:00
|
|
|
draft.publish
|
2020-03-14 02:59:13 +00:00
|
|
|
expect { Pigeon::Draft.current }.to raise_error("NO DRAFT FOUND")
|
2020-03-07 15:38:34 +00:00
|
|
|
end
|
|
|
|
|
2020-03-06 14:42:19 +00:00
|
|
|
it "creates a single message" do
|
2020-04-07 13:58:53 +00:00
|
|
|
message = draft.publish
|
|
|
|
expect(message.author.multihash).to eq(Pigeon::LocalIdentity.current.multihash)
|
2020-03-06 14:42:19 +00:00
|
|
|
expect(message.body).to eq(draft.body)
|
|
|
|
expect(message.depth).to eq(0)
|
|
|
|
expect(message.kind).to eq("unit_test")
|
2020-04-12 14:03:44 +00:00
|
|
|
expect(message.prev).to eq(Pigeon::NOTHING)
|
2020-03-14 03:02:45 +00:00
|
|
|
expect(message.signature.include?(Pigeon::SIG_FOOTER)).to eq(true)
|
2020-03-06 14:42:19 +00:00
|
|
|
expect(message.signature.length).to be > 99
|
|
|
|
actual = message.render
|
|
|
|
expected = [
|
|
|
|
"author __AUTHOR__",
|
|
|
|
"kind unit_test",
|
|
|
|
"prev NONE",
|
|
|
|
"depth 0",
|
2020-04-14 12:33:41 +00:00
|
|
|
"lipmaa 0",
|
2020-03-06 14:42:19 +00:00
|
|
|
"",
|
|
|
|
"a:\"bar\"",
|
2020-04-06 01:01:59 +00:00
|
|
|
"b:&CHHABX8Q9D9Q0BY2BBZ6FA7SMAFNE9GGMSDTZVZZC9TK2N9F15QG.sha256",
|
2020-03-06 14:42:19 +00:00
|
|
|
"",
|
2020-03-09 13:02:17 +00:00
|
|
|
"signature __SIGNATURE__",
|
2020-03-06 14:42:19 +00:00
|
|
|
].join("\n")
|
2020-04-06 12:22:24 +00:00
|
|
|
.gsub("__AUTHOR__", message.author.multihash)
|
2020-03-06 14:42:19 +00:00
|
|
|
.gsub("__SIGNATURE__", message.signature)
|
|
|
|
expect(actual).to eq(expected)
|
|
|
|
end
|
|
|
|
|
2020-03-08 16:18:05 +00:00
|
|
|
it "creates a chain of messages" do
|
|
|
|
all = []
|
2020-03-29 15:26:41 +00:00
|
|
|
0.upto(4) do |expected_depth|
|
2020-03-08 16:18:05 +00:00
|
|
|
draft1 = Pigeon::Draft.create(kind: "unit_test")
|
2020-03-29 15:26:41 +00:00
|
|
|
draft1["description"] = "Message number #{expected_depth}"
|
2020-04-07 13:58:53 +00:00
|
|
|
message = draft1.publish
|
2020-03-08 16:18:05 +00:00
|
|
|
all.push(message)
|
|
|
|
expect(message.depth).to eq(expected_depth)
|
2020-03-29 15:26:41 +00:00
|
|
|
if expected_depth == 0
|
2020-04-12 14:03:44 +00:00
|
|
|
expect(message.prev).to eq(Pigeon::NOTHING)
|
2020-03-29 15:26:41 +00:00
|
|
|
else
|
|
|
|
expect(message.prev).to eq(all[expected_depth - 1].multihash)
|
2020-03-08 16:18:05 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-03-09 13:56:45 +00:00
|
|
|
|
2020-03-11 13:43:54 +00:00
|
|
|
it "verifies accuracy of hash chain" do
|
|
|
|
m1 = create_message({ "a" => "b" })
|
|
|
|
m2 = create_message({ "c" => "d" })
|
|
|
|
m3 = create_message({ "e" => "f" })
|
|
|
|
m4 = create_message({ "g" => "h" })
|
2020-03-31 12:09:42 +00:00
|
|
|
|
2020-04-12 14:03:44 +00:00
|
|
|
expect(m1.prev).to eq(Pigeon::NOTHING)
|
2020-03-11 13:43:54 +00:00
|
|
|
expect(m2.prev).to be
|
|
|
|
expect(m2.prev).to eq(m1.multihash)
|
|
|
|
expect(m3.prev).to eq(m2.multihash)
|
|
|
|
expect(m3.prev).to be
|
|
|
|
expect(m4.prev).to eq(m3.multihash)
|
|
|
|
expect(m4.prev).to be
|
|
|
|
end
|
|
|
|
|
2020-04-11 14:53:22 +00:00
|
|
|
it "verifies accuracy of Lipmaa links"
|
2020-03-11 13:43:54 +00:00
|
|
|
it "verifies accuracy of signatures" do
|
2020-03-13 12:39:44 +00:00
|
|
|
# === Initial setup
|
2020-03-24 12:39:32 +00:00
|
|
|
Pigeon::LocalIdentity.current
|
2020-03-11 13:43:54 +00:00
|
|
|
secret = Pigeon::Storage.current.get_config(Pigeon::SEED_CONFIG_KEY)
|
2020-04-11 14:42:43 +00:00
|
|
|
message = templated_message
|
2020-03-12 13:07:13 +00:00
|
|
|
plaintext = template.render_without_signature
|
2020-03-13 12:39:44 +00:00
|
|
|
|
|
|
|
# Make fake pairs of data for cross-checking
|
2020-03-24 12:39:32 +00:00
|
|
|
key1 = Pigeon::LocalIdentity.current.instance_variable_get(:@signing_key)
|
2020-03-13 12:39:44 +00:00
|
|
|
key2 = Ed25519::SigningKey.new(secret)
|
|
|
|
|
|
|
|
sig1 = key1.sign(plaintext)
|
|
|
|
sig2 = key2.sign(plaintext)
|
|
|
|
|
|
|
|
expect(key1.seed).to eq(key2.seed)
|
|
|
|
expect(sig1).to eq(sig2)
|
2020-03-30 13:49:29 +00:00
|
|
|
combinations = [[key1, sig1], [key1, sig2], [key2, sig1], [key2, sig2]]
|
|
|
|
combinations.map { |(key, sig)| key.verify_key.verify(sig, plaintext) }
|
2020-03-13 12:39:44 +00:00
|
|
|
|
2020-04-06 00:38:37 +00:00
|
|
|
sig1_b64 = Pigeon::Helpers.b32_encode(sig1) + Pigeon::SIG_FOOTER
|
|
|
|
sig2_b64 = Pigeon::Helpers.b32_encode(sig2) + Pigeon::SIG_FOOTER
|
2020-03-13 12:39:44 +00:00
|
|
|
expect(message.signature).to eq(sig1_b64)
|
|
|
|
expect(message.signature).to eq(sig2_b64)
|
2020-03-11 13:43:54 +00:00
|
|
|
end
|
2020-04-03 10:29:19 +00:00
|
|
|
|
|
|
|
it "crashes on forged fields" do
|
|
|
|
m = "Expected field `depth` to equal 0, got: 10"
|
2020-04-09 12:30:32 +00:00
|
|
|
expect do
|
|
|
|
Pigeon::Parser.parse([
|
|
|
|
[:AUTHOR, "@DYdgK1KUInVtG3lS45hA1HZ-jTuvfLKsxDpXPFCve04=.ed25519"],
|
|
|
|
[:KIND, "invalid"],
|
|
|
|
[:PREV, "NONE"],
|
|
|
|
[:DEPTH, 10],
|
2020-04-12 14:03:44 +00:00
|
|
|
[:LIPMAA, Pigeon::Helpers.lipmaa(10)],
|
2020-04-09 12:30:32 +00:00
|
|
|
[:HEADER_END],
|
|
|
|
[:BODY_ENTRY, "duplicate", "This key is a duplicate."],
|
|
|
|
[:SIGNATURE, "DN7yPTE-m433ND3jBL4oM23XGxBKafjq0Dp9ArBQa_TIGU7DmCxTumieuPBN-NKxlx_0N7-c5zjLb5XXVHYPCQ==.sig.ed25519"],
|
|
|
|
[:MESSAGE_END],
|
|
|
|
]).first.save!
|
|
|
|
end.to raise_error(Pigeon::Message::VerificationError, m)
|
2020-04-03 10:29:19 +00:00
|
|
|
end
|
2020-04-06 12:52:32 +00:00
|
|
|
|
|
|
|
# Every ASCII character that is not a letter:
|
|
|
|
WHITESPACE = (0..32).to_a.map(&:chr).push(127.chr)
|
|
|
|
|
|
|
|
it "does not allow whitespace in `kind` attributes" do
|
|
|
|
WHITESPACE.map do |n|
|
|
|
|
kind = SecureRandom.alphanumeric(8)
|
|
|
|
kind[rand(0...8)] = n
|
|
|
|
draft = Pigeon::Draft.create(kind: kind)
|
|
|
|
draft["body"] = "empty"
|
2020-04-09 12:31:31 +00:00
|
|
|
boom = ->() { Pigeon::Lexer.tokenize(draft.publish.render) }
|
2020-04-07 13:13:28 +00:00
|
|
|
expect(boom).to raise_error(Pigeon::Lexer::LexError)
|
2020-04-06 12:52:32 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not allow whitespace in key names" do
|
|
|
|
WHITESPACE.map do |n|
|
|
|
|
draft = Pigeon::Draft.create(kind: "unit_test")
|
2020-04-07 13:13:28 +00:00
|
|
|
key = SecureRandom.alphanumeric(8)
|
|
|
|
key[rand(0...8)] = n
|
|
|
|
draft[key] = "should crash"
|
2020-04-09 12:30:32 +00:00
|
|
|
boom = ->() { Pigeon::Lexer.tokenize(draft.publish.render) }
|
2020-04-07 13:13:28 +00:00
|
|
|
expect(boom).to raise_error(Pigeon::Lexer::LexError)
|
2020-04-06 12:52:32 +00:00
|
|
|
end
|
|
|
|
end
|
2020-03-06 14:42:19 +00:00
|
|
|
end
|