Merge pull request #65 from SuperTux88/refactor-relayable-signatures

Refactor relayable signatures
This commit is contained in:
cmrd Senya 2017-06-03 11:57:54 +03:00
commit 7a28db7d76
No known key found for this signature in database
GPG key ID: 5FCC5BA680E67BFE
13 changed files with 62 additions and 143 deletions

View file

@ -16,7 +16,6 @@ See also: [Relayable][relayable]
| `text` | [Markdown][markdown] (65535) | The comment text. |
| `created_at` | [Timestamp][timestamp] | The create timestamp of the comment. |
| `author_signature` | [Signature][signature] | The signature from the author of the comment. |
| `parent_author_signature` | [Signature][signature] | The signature from the parent entity author. |
## Optional Properties
@ -26,8 +25,6 @@ See also: [Relayable][relayable]
## Examples
### From author
~~~xml
<comment>
<author>alice@example.org</author>
@ -36,21 +33,6 @@ See also: [Relayable][relayable]
<parent_guid>c3893bf029e7013487753131731751e9</parent_guid>
<text>this is a very informative comment</text>
<author_signature>cGIsxB5hU/94+rmgIg/Z+OUvXVYcY/kMOvc267ybpk1pT44P1JiWfnI26F1Mta62UjzIW/SjeAO0RIsJRguaISLpXX/d5DJCMpePAZaZiagUbdgH/w4L++fXiPxBKkSm+PB4txxmHGN8FHjwEUJFHJ1m3VfU4w2JC8+IBU93eag=</author_signature>
<parent_author_signature/>
</comment>
~~~
### From parent author
~~~xml
<comment>
<author>alice@example.org</author>
<guid>5c241a3029f8013487763131731751e9</guid>
<created_at>2016-07-12T00:49:06Z</created_at>
<parent_guid>c3893bf029e7013487753131731751e9</parent_guid>
<text>this is a very informative comment</text>
<author_signature>cGIsxB5hU/94+rmgIg/Z+OUvXVYcY/kMOvc267ybpk1pT44P1JiWfnI26F1Mta62UjzIW/SjeAO0RIsJRguaISLpXX/d5DJCMpePAZaZiagUbdgH/w4L++fXiPxBKkSm+PB4txxmHGN8FHjwEUJFHJ1m3VfU4w2JC8+IBU93eag=</author_signature>
<parent_author_signature>uzjxUSqR8DQBSBa6abY7R/s9DVzT6UAgTctRcUu5rV5o0iXJD2MR6kp6bsVH3nMbbNvOjwAtrdfz3SVHT2gD8M5PmoFagxK7m5T1c9FB0i+wknoAah0Si0c2sP/BPLnkQ83DgLjF+JZCzMX4sWKvYfyaMjnih1MtQILGyuiwA0E=</parent_author_signature>
</comment>
~~~

View file

@ -21,12 +21,9 @@ See also: [Relayable][relayable]
| `parent_type` | [Type][type] | The entity type of the parent. |
| `positive` | [Boolean][boolean] | `true` if it is a like, `false` if it is a dislike. |
| `author_signature` | [Signature][signature] | The signature from the author of the like. |
| `parent_author_signature` | [Signature][signature] | The signature from the parent entity author. |
## Examples
### From author
~~~xml
<like>
<positive>true</positive>
@ -35,21 +32,6 @@ See also: [Relayable][relayable]
<parent_guid>947a854029f7013487753131731751e9</parent_guid>
<author>alice@example.org</author>
<author_signature>gk8e+K7XRjVRblv8B8PVOf7BpURbf5HrXO5rmq8D/AkPO7lA0+Akwouu5JGKAHIhPR3dfXVp0o6bIDD+e8gtMYRdDd5IHRfBGNk3WsQecnbhmesHy40Qca/dCQcdcXd5aeWHJKeyUrSAvS55U6VUpk/DK/4IIEZfnr0T9+jM8I0=</author_signature>
<parent_author_signature/>
</like>
~~~
### From parent author
~~~xml
<like>
<positive>true</positive>
<guid>947a88f029f7013487753131731751e9</guid>
<parent_type>Post</parent_type>
<parent_guid>947a854029f7013487753131731751e9</parent_guid>
<author>alice@example.org</author>
<author_signature>gk8e+K7XRjVRblv8B8PVOf7BpURbf5HrXO5rmq8D/AkPO7lA0+Akwouu5JGKAHIhPR3dfXVp0o6bIDD+e8gtMYRdDd5IHRfBGNk3WsQecnbhmesHy40Qca/dCQcdcXd5aeWHJKeyUrSAvS55U6VUpk/DK/4IIEZfnr0T9+jM8I0=</author_signature>
<parent_author_signature>0oAjHO8uIn2Z3Gcmo1KF8su0c7bqI6MzTRq5JagGaZVkFVU8WlNqtwamu6xlmpcAoClGpI5xvbnHzyw5YA8NS8KmUy8BUpg67Mq4QsHHBtueNxHuhgRjszN2V0S8BUKFHGJnnvXmZ/P6YGOOomDgp9I/7zIOownvIm5wj2MotWw=</parent_author_signature>
</like>
~~~

View file

@ -15,12 +15,9 @@ See also: [Relayable][relayable]
| `parent_guid` | [GUID][guid] | The GUID of the [Poll][poll]. |
| `poll_answer_guid` | [GUID][guid] | The GUID of the [PollAnswer][poll_answer]. |
| `author_signature` | [Signature][signature] | The signature from the author of the poll participation. |
| `parent_author_signature` | [Signature][signature] | The signature from the author of the [Poll][poll]. |
## Examples
### From author
~~~xml
<poll_participation>
<guid>f1eb866029f7013487753131731751e9</guid>
@ -28,20 +25,6 @@ See also: [Relayable][relayable]
<author>alice@example.org</author>
<poll_answer_guid>2a22db2029e9013487753131731751e9</poll_answer_guid>
<author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature>
<parent_author_signature/>
</poll_participation>
~~~
### From parent author
~~~xml
<poll_participation>
<guid>f1eb866029f7013487753131731751e9</guid>
<parent_guid>2a22d6c029e9013487753131731751e9</parent_guid>
<author>alice@example.org</author>
<poll_answer_guid>2a22db2029e9013487753131731751e9</poll_answer_guid>
<author_signature>dT6KbT7kp0bE+s3//ZErxO1wvVIqtD0lY67i81+dO43B4D2m5kjCdzW240eWt/jZmcHIsdxXf4WHNdrb6ZDnamA8I1FUVnLjHA9xexBITQsSLXrcV88UdammSmmOxl1Ac4VUXqFpdavm6a7/MwOJ7+JHP8TbUO9siN+hMfgUbtY=</author_signature>
<parent_author_signature>gWasNPpSnMcKBIMWyzfoVO6sr8eRYkhUqy3PIkkh53n/ki+DM9mnh3ayotI0+6un9aq1N3XkS7Vn05ZD3+nHVby6i21XkYgPnbD8pWYuBBj7VGPyahT70BUs/vSvY8KX8V3wYfsPsaiAgJsAFg2UHYdY3r4/oWdIIbBZc21O3zk=</parent_author_signature>
</poll_participation>
~~~

View file

@ -21,24 +21,25 @@ All relayables have the following properties:
| `guid` | [GUID][guid] | The GUID of the relayable. |
| `parent_guid` | [GUID][guid] | The GUID of the parent entity. |
| `author_signature` | [Signature][signature] | The signature from the author of the relayable. |
| `parent_author_signature` | [Signature][signature] | The signature from the parent entity author. |
## Relaying
The author of the relayable sends the entity to the parent author. The author must include the `author_signature`. The
`parent_author_signature` may be empty or missing.
If the author is not the same as the parent author, the author of the relayable sends the entity to the parent author
and the author must include the `author_signature`.
The parent author then must add the `parent_author_signature` and send the entity to all the recipients of the parent
entity.
The parent author then must envelop it in a new [Magic Envelope][magicsig] and send the entity to all the recipients
of the parent entity. If the author and the parent author are on the same server, the author must sign the
`author_signature` and the parent author needs to sign the Magic Envelope.
If someone other then the parent author receives an relayable without a valid `parent_author_signature`, it must be
ignored. If the `author_signature` is missing or invalid, it also must be ignored.
If someone other then the parent author receives a relayable without a valid Magic Envelope signed from
the parent author, it must be ignored. If the author is not the same as the parent author and the `author_signature`
is missing or invalid, it also must be ignored. If the author is the same as the parent author, the `author_signature`
can be missing, because a valid signature in the Magic Envelope from the author is enough in that case.
## Signatures
The string to sign is built with the content of all properties (except the `author_signature` and
`parent_author_signature` itself), concatenated using `;` as separator in the same order as they appear in the XML. The
order in the XML is not specified.
The string to sign is built with the content of all properties (except the `author_signature` itself),
concatenated using `;` as separator in the same order as they appear in the XML. The order in the XML is not specified.
This ensures that relayables even work, if the parent author or another recipient does not know all properties of the
relayable entity (e.g. older version of diaspora\*).

View file

@ -28,16 +28,15 @@ module DiasporaFederation
# @return [String] parent guid
#
# @!attribute [r] author_signature
# Contains a signature of the entity using the private key of the author of a post itself
# Contains a signature of the entity using the private key of the author of a relayable itself.
# The presence of this signature is mandatory. Without it the entity won't be accepted by
# a target pod.
# @return [String] author signature
#
# @!attribute [r] parent_author_signature
# Contains a signature of the entity using the private key of the author of a parent post
# This signature is required only when federating from upstream (parent) post author to
# downstream subscribers. This is the case when the parent author has to resend a relayable
# received from one of their subscribers to all others.
# Contains a signature of the entity using the private key of the author of a parent post.
# @deprecated This signature isn't required anymore, because we can check the signature from
# the parent author in the MagicEnvelope.
# @return [String] parent author signature
#
# @!attribute [r] parent
@ -71,18 +70,17 @@ module DiasporaFederation
super(data)
end
# Verifies the signatures (+author_signature+ and +parent_author_signature+ if needed).
# Verifies the +author_signature+ if needed.
# @see DiasporaFederation::Entities::Signable#verify_signature
#
# @raise [SignatureVerificationFailed] if the signature is not valid
# @raise [PublicKeyNotFound] if no public key is found
def verify_signatures
verify_signature(author, :author_signature)
# This happens only on downstream federation.
verify_signature(parent.author, :parent_author_signature) unless parent.local
def verify_signature
super(author, :author_signature) unless author == parent.author
end
def sender_valid?(sender)
sender == author || sender == parent.author
(sender == author && parent.local) || sender == parent.author
end
# @return [String] string representation of this object
@ -136,7 +134,7 @@ module DiasporaFederation
def enriched_properties
super.merge(additional_data).tap do |hash|
hash[:author_signature] = author_signature || sign_with_author
hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
hash.delete(:parent_author_signature)
end
end
@ -144,7 +142,9 @@ module DiasporaFederation
#
# @return [Hash] sorted xml elements
def xml_elements
data = super
data = super.tap do |hash|
hash[:parent_author_signature] = parent_author_signature || sign_with_parent_author_if_available.to_s
end
order = signature_order + %i(author_signature parent_author_signature)
order.map {|element| [element, data[element] || ""] }.to_h
end
@ -178,7 +178,7 @@ module DiasporaFederation
additional_data = properties_hash.reject {|key, _| class_props.has_key?(key) }
fetch_parent(properties_hash)
new(properties_hash, property_order, additional_data).tap(&:verify_signatures)
new(properties_hash, property_order, additional_data).tap(&:verify_signature)
end
private

View file

@ -46,8 +46,7 @@
"author": { "type": "string" },
"guid": { "$ref": "#/definitions/guid" },
"parent_guid": { "$ref": "#/definitions/guid" },
"author_signature": { "$ref": "#/definitions/signature" },
"parent_author_signature": { "$ref": "#/definitions/signature" }
"author_signature": { "$ref": "#/definitions/signature" }
},
"required": [
"author", "guid", "parent_guid"

View file

@ -224,7 +224,6 @@ XML
before do
expect_callback(:fetch_public_key, author).and_return(author_key.public_key)
expect_callback(:fetch_public_key, parent.author).and_return(parent_key.public_key)
expect_callback(:fetch_related_entity, "Post", parent_guid).and_return(parent)
end

View file

@ -32,7 +32,6 @@ XML
"guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"text": "#{data[:text]}",
"created_at": "#{data[:created_at].iso8601}"
},

View file

@ -32,7 +32,6 @@ XML
"guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"parent_type": "#{parent.entity_type}",
"positive": #{data[:positive]}
},

View file

@ -30,7 +30,6 @@ XML
"guid": "#{data[:guid]}",
"parent_guid": "#{parent.guid}",
"author_signature": "#{data[:author_signature]}",
"parent_author_signature": "#{data[:parent_author_signature]}",
"poll_answer_guid": "#{data[:poll_answer_guid]}"
},
"property_order": [

View file

@ -25,16 +25,15 @@ module DiasporaFederation
end
end
describe "#verify_signatures" do
describe "#verify_signature" do
it "doesn't raise anything if correct signatures were passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, signature_data)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signatures }.not_to raise_error
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end
it "doesn't raise anything if correct signatures with new property were passed" do
@ -46,10 +45,9 @@ module DiasporaFederation
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order, "new_property" => new_property).verify_signatures
Entities::SomeRelayable.new(hash, signature_order, "new_property" => new_property).verify_signature
}.not_to raise_error
end
@ -57,7 +55,7 @@ module DiasporaFederation
expect_callback(:fetch_public_key, anything).and_return(nil)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::PublicKeyNotFound
end
@ -67,66 +65,47 @@ module DiasporaFederation
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "doesn't raise when no author signature was passed, but the author is also the parent author" do
hash[:author_signature] = nil
hash[:parent] = Fabricate(:related_entity, author: author, local: false)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.not_to raise_error
end
it "raises when bad author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, "bad signed string")
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
Entities::SomeRelayable.new(hash, signature_order).verify_signature
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "raises when no public key for parent author was fetched" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(nil)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::PublicKeyNotFound
end
it "raises when no parent author signature was passed" do
it "doesn't raise when no parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = nil
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end
it "raises when bad parent author signature was passed" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = sign_with_key(parent_pkey, "bad signed string")
hash[:parent] = remote_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect {
Entities::SomeRelayable.new(hash, signature_order).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed
end
it "doesn't raise if parent_author_signature isn't set but we're on upstream federation" do
it "doesn't raise when no parent author signature was passed and we're on upstream federation" do
hash[:author_signature] = sign_with_key(author_pkey, signature_data)
hash[:parent_author_signature] = nil
hash[:parent] = local_parent
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signatures }.not_to raise_error
expect { Entities::SomeRelayable.new(hash, signature_order).verify_signature }.not_to raise_error
end
end
@ -228,7 +207,6 @@ XML
before do
expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent)
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
end
let(:new_signature_data) { "#{author};#{guid};#{parent_guid};#{new_property};#{property}" }
@ -329,25 +307,21 @@ XML
)
end
it "computes correct signatures for the entity with new unknown elements" do
it "computes correct author_signature for the entity with new unknown elements" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(parent_pkey)
property_order = [:author, :guid, :parent_guid, "new_property", :property]
signature_data_with_new_property = "#{author};#{guid};#{parent_guid};#{new_property};#{property}"
json_hash = Entities::SomeRelayable.new(hash, property_order, "new_property" => new_property).to_json
author_signature = json_hash[:entity_data][:author_signature]
parent_author_signature = json_hash[:entity_data][:parent_author_signature]
expect(verify_signature(author_pkey, author_signature, signature_data_with_new_property)).to be_truthy
expect(verify_signature(parent_pkey, parent_author_signature, signature_data_with_new_property)).to be_truthy
end
it "doesn't change signatures if they are already set" do
it "doesn't change author_signature if it is already set" do
json = Entities::SomeRelayable.new(hash_with_fake_signatures).to_json.to_json
expect(json).to include_json(entity_data: {author_signature: "aa"})
expect(json).to include_json(entity_data: {parent_author_signature: "bb"})
end
it "raises when author_signature not set and key isn't supplied" do
@ -358,12 +332,11 @@ XML
}.to raise_error Entities::Relayable::AuthorPrivateKeyNotFound
end
it "doesn't set parent_author_signature if key isn't supplied" do
it "doesn't contain the parent_author_signature" do
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, local_parent.author).and_return(nil)
json = Entities::SomeRelayable.new(hash).to_json.to_json
expect(json).to include_json(entity_data: {parent_author_signature: ""})
json = Entities::SomeRelayable.new(hash).to_json
expect(json[:entity_data]).not_to include(:parent_author_signature)
end
end
@ -374,7 +347,6 @@ XML
before do
expect_callback(:fetch_related_entity, "Parent", parent_guid).and_return(remote_parent)
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
end
context "when properties are sorted and there is an unknown property" do
@ -475,9 +447,7 @@ XML
context "fetch parent" do
before do
expect_callback(:fetch_public_key, author).and_return(author_pkey.public_key)
expect_callback(:fetch_public_key, remote_parent.author).and_return(parent_pkey.public_key)
expect_callback(:fetch_private_key, author).and_return(author_pkey)
expect_callback(:fetch_private_key, remote_parent.author).and_return(parent_pkey)
end
let(:entity) { Entities::SomeRelayable.new(hash) }
@ -508,12 +478,18 @@ XML
end
describe "#sender_valid?" do
it "allows author" do
it "allows author if the parent is local" do
entity = Entities::SomeRelayable.new(hash)
expect(entity.sender_valid?(author)).to be_truthy
end
it "does not allow the author if the parent is not local" do
entity = Entities::SomeRelayable.new(hash.merge(parent: remote_parent))
expect(entity.sender_valid?(author)).to be_falsey
end
it "allows parent author" do
entity = Entities::SomeRelayable.new(hash)

View file

@ -14,7 +14,7 @@ end
# signature methods
def add_signatures(hash, klass=described_class)
properties = klass.new(hash).send(:enriched_properties)
properties = klass.new(hash).send(:xml_elements)
hash[:author_signature] = properties[:author_signature]
hash[:parent_author_signature] = properties[:parent_author_signature]
end

View file

@ -1,4 +1,5 @@
def entity_hash_from(hash)
hash.delete(:parent_author_signature)
hash.map {|key, value|
if [String, TrueClass, FalseClass, Integer, NilClass].any? {|c| value.is_a? c }
[key, value]
@ -136,10 +137,9 @@ shared_examples "a JSON Entity" do
it "contains JSON properties for each of the entity properties with the entity_data property" do
entity_data = entity_hash_from(data)
entity_data.delete(:parent)
nested_elements = entity_data.select {|_key, value| value.is_a?(Array) || value.is_a?(Hash) }
entity_data.reject! {|_key, value| value.is_a?(Array) || value.is_a?(Hash) }
nested_elements, simple_props = entity_data.partition {|_key, value| value.is_a?(Array) || value.is_a?(Hash) }
expect(to_json_output).to include_json(entity_data: entity_data)
expect(to_json_output).to include_json(entity_data: simple_props.to_h)
nested_elements.each {|key, value|
type = described_class.class_props[key]
if value.is_a?(Array)