summaryrefslogtreecommitdiff
path: root/share/man/ENVOY.md
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-27 12:30:45 -0600
committermo khan <mo@mokhan.ca>2025-05-27 12:30:45 -0600
commit527ca756cbe44016596fbfa27a090b2f330316bb (patch)
tree1a56a80c03741b71d793aa968a2dd0ba21b2ee17 /share/man/ENVOY.md
parentc522506bb06ae36492dee4be50b565b25c430c72 (diff)
docs: add an example of sending/receiving messages with signatures
Diffstat (limited to 'share/man/ENVOY.md')
-rw-r--r--share/man/ENVOY.md87
1 files changed, 78 insertions, 9 deletions
diff --git a/share/man/ENVOY.md b/share/man/ENVOY.md
index 7db50cd..9fb0701 100644
--- a/share/man/ENVOY.md
+++ b/share/man/ENVOY.md
@@ -189,7 +189,7 @@ own private key.
#!/bin/env ruby
require 'openssl'
-class Player
+class Person
attr_reader :name, :public_key
def initialize(name, private_key = OpenSSL::PKey::RSA.new(2048))
@@ -198,22 +198,22 @@ class Player
@public_key = private_key.public_key
end
- def send_to(player, plaintext)
- ciphertext = player.public_key.public_encrypt(plaintext)
- player.receive_from(self, ciphertext)
+ def send_to(person, plaintext)
+ ciphertext = person.public_key.public_encrypt(plaintext)
+ person.receive_from(self, ciphertext)
end
- def receive_from(player, ciphertext)
+ def receive_from(person, ciphertext)
plaintext = @private_key.private_decrypt(ciphertext)
- puts "#{player.name}: #{plaintext}\n"
+ puts "#{person.name}: #{plaintext}\n"
end
end
-clifford = Player.new("clifford")
-reginald = Player.new("reginald")
+clifford = Person.new("clifford")
+reginald = Person.new("reginald")
clifford.send_to(reginald, "What time is it?")
-reginald.send_to(clifford, "Time to go live!")
+reginald.send_to(clifford, "Time to go live! Who sent this?")
```
#### Authenticity
@@ -227,6 +227,14 @@ then they can decrypt the signature using the public key of the sender. If
signature can be decrypted without an error then we can trust that the message
did in fact originate from the sender. This authenticates the message.
+In the previous code example each party was able to ensure that the message that
+they delivered to the intended recipient could only be read by that recipient.
+However, the recipient could not guarantee that they message that they received
+actually came from the party that claims to have sent it. If an attacker could
+eavesdrop on the conversation, they could intercept the message and rewrite it
+before delivering it. This might cause confusion between the two parties and an
+attacker could then coerce one of the parties into a specific action.
+
#### Integrity
When a recipient receives a message from a sender the recipient also needs to
@@ -235,6 +243,67 @@ an encrypted hash then the recipient can compute a hash of the plaintext message
and compare it with the hash in the encrypted signature. This ensures that the
message hasn't been tampered with.
+In the following code example the two actors perform a public key exchange with
+each other before they start to communicate with each other. This allows them to
+verify that the message that they receive did in fact originate from the person
+that they think it originated from. It also allows them to ensure that the
+message hasn't been altered in transit by appending a signature.
+
+```ruby
+#!/bin/env ruby
+require 'openssl'
+
+class Person
+ attr_reader :name, :public_key
+
+ def initialize(name, private_key = OpenSSL::PKey::RSA.new(2048))
+ @name = name
+ @private_key = private_key
+ @public_key = private_key.public_key
+ @friends = {}
+ end
+
+ def add_friend(friend)
+ @friends[friend.name] = friend.public_key
+ end
+
+ def send_to(person, plaintext)
+ signature = @private_key.private_encrypt(Digest::SHA1.hexdigest(plaintext))
+ person.receive([self.name, plaintext, signature])
+ end
+
+ def receive(message)
+ raise "This message cannot be trusted" unless valid?(message)
+
+ name, plaintext, _ = message
+ puts "#{name}: #{plaintext}\n"
+ end
+
+ private
+
+ def valid?(message)
+ header, body, signature = message
+ public_key = @friends[header]
+
+ # verify that we know the sender
+ return false if public_key.nil?
+
+ # verify that the message hasn't been altered in transit
+ Digest::SHA1.hexdigest(body) == public_key.public_decrypt(signature)
+ end
+end
+
+clifford = Person.new("clifford")
+reginald = Person.new("reginald")
+
+# public key exchange
+clifford.add_friend(reginald)
+reginald.add_friend(clifford)
+
+clifford.send_to(reginald, "What time is it?")
+reginald.send_to(clifford, "It's still time to go live!")
+```
+
In order for us to be able to trust JSON Web Tokens we need public/private key
pairs that we can use to validate the authenticity and integrity of the token.
It is also possible to encrypt the JWT body but this isn't necessary and this is