113 lines
4.6 KiB
Ruby
113 lines
4.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
|
|
module DiasporaFederation
|
|
module Salmon
|
|
# +EncryptedSlap+ provides class methods for generating and parsing encrypted
|
|
# Slaps. (In principle the same as {Slap}, but with encryption.)
|
|
#
|
|
# The basic encryption mechanism used here is based on the knowledge that
|
|
# asymmetrical encryption is slow and symmetrical encryption is fast. Keeping in
|
|
# mind that a message we want to de-/encrypt may greatly vary in length,
|
|
# performance considerations must play a part of this scheme.
|
|
#
|
|
# A diaspora*-flavored encrypted magic-enveloped XML message looks like the following:
|
|
#
|
|
# <?xml version="1.0" encoding="UTF-8"?>
|
|
# <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
|
|
# <encrypted_header>{encrypted_header}</encrypted_header>
|
|
# {magic_envelope with encrypted data}
|
|
# </diaspora>
|
|
#
|
|
# The encrypted header is encoded in JSON like this (when in plain text):
|
|
#
|
|
# {
|
|
# "aes_key" => "...",
|
|
# "ciphertext" => "..."
|
|
# }
|
|
#
|
|
# +aes_key+ is encrypted using the recipients public key, and contains the AES
|
|
# +key+ and +iv+ used to encrypt the +ciphertext+ also encoded as JSON.
|
|
#
|
|
# {
|
|
# "key" => "...",
|
|
# "iv" => "..."
|
|
# }
|
|
#
|
|
# +ciphertext+, once decrypted, contains the +author_id+, +aes_key+ and +iv+
|
|
# relevant to the decryption of the data in the magic_envelope and the
|
|
# verification of its signature.
|
|
#
|
|
# The decrypted cyphertext has this XML structure:
|
|
#
|
|
# <decrypted_header>
|
|
# <iv>{iv}</iv>
|
|
# <aes_key>{aes_key}</aes_key>
|
|
# <author_id>{author_id}</author_id>
|
|
# </decrypted_header>
|
|
#
|
|
# Finally, before decrypting the magic envelope payload, the signature should
|
|
# first be verified.
|
|
#
|
|
# @example Parsing a Salmon Slap
|
|
# recipient_privkey = however_you_retrieve_the_recipients_private_key()
|
|
# entity = EncryptedSlap.from_xml(slap_xml, recipient_privkey).payload
|
|
#
|
|
# @deprecated
|
|
class EncryptedSlap < Slap
|
|
# Creates a {MagicEnvelope} instance from the data within the given XML string
|
|
# containing an encrypted payload.
|
|
#
|
|
# @param [String] slap_xml encrypted Salmon xml
|
|
# @param [OpenSSL::PKey::RSA] privkey recipient private_key for decryption
|
|
#
|
|
# @return [MagicEnvelope] magic envelope instance with payload and sender
|
|
#
|
|
# @raise [ArgumentError] if any of the arguments is of the wrong type
|
|
# @raise [MissingHeader] if the +encrypted_header+ element is missing in the XML
|
|
# @raise [MissingMagicEnvelope] if the +me:env+ element is missing in the XML
|
|
def self.from_xml(slap_xml, privkey)
|
|
raise ArgumentError unless slap_xml.instance_of?(String) && privkey.instance_of?(OpenSSL::PKey::RSA)
|
|
|
|
doc = Nokogiri::XML(slap_xml)
|
|
|
|
header_elem = doc.at_xpath("d:diaspora/d:encrypted_header", Slap::NS)
|
|
raise MissingHeader if header_elem.nil?
|
|
|
|
header = header_data(header_elem.content, privkey)
|
|
sender = header[:author_id]
|
|
cipher_params = {key: Base64.decode64(header[:aes_key]), iv: Base64.decode64(header[:iv])}
|
|
|
|
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender, cipher_params)
|
|
end
|
|
|
|
# Decrypts and reads the data from the encrypted XML header
|
|
# @param [String] data base64 encoded, encrypted header data
|
|
# @param [OpenSSL::PKey::RSA] privkey private key for decryption
|
|
# @return [Hash] { iv: "...", aes_key: "...", author_id: "..." }
|
|
private_class_method def self.header_data(data, privkey)
|
|
header_elem = decrypt_header(data, privkey)
|
|
raise InvalidHeader unless header_elem.name == "decrypted_header"
|
|
|
|
iv = header_elem.at_xpath("iv").content
|
|
key = header_elem.at_xpath("aes_key").content
|
|
author_id = header_elem.at_xpath("author_id").content
|
|
|
|
{iv: iv, aes_key: key, author_id: author_id}
|
|
end
|
|
|
|
# Decrypts the xml header
|
|
# @param [String] data base64 encoded, encrypted header data
|
|
# @param [OpenSSL::PKey::RSA] privkey private key for decryption
|
|
# @return [Nokogiri::XML::Element] header xml document
|
|
private_class_method def self.decrypt_header(data, privkey)
|
|
cipher_header = JSON.parse(Base64.decode64(data))
|
|
key = JSON.parse(privkey.private_decrypt(Base64.decode64(cipher_header["aes_key"])))
|
|
|
|
xml = AES.decrypt(cipher_header["ciphertext"], Base64.decode64(key["key"]), Base64.decode64(key["iv"]))
|
|
Nokogiri::XML(xml).root
|
|
end
|
|
end
|
|
end
|
|
end
|