/usr/lib/ruby/vendor_ruby/diaspora_federation/salmon/encrypted_slap.rb is in ruby-diaspora-federation 0.1.4-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | 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 Generating an encrypted Salmon Slap
# author_id = "author@pod.example.tld"
# author_privkey = however_you_retrieve_the_authors_private_key(author_id)
# recipient_pubkey = however_you_retrieve_the_recipients_public_key()
# entity = YourEntity.new(attr: "val")
#
# slap_xml = EncryptedSlap.prepare(author_id, author_privkey, entity).generate_xml(recipient_pubkey)
#
# @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
# the author of the slap
# @param [String] value the author diaspora* ID
attr_writer :author_id
# the key and iv if it is an encrypted slap
# @param [Hash] value hash containing the key and iv
attr_writer :cipher_params
# the prepared encrypted magic envelope xml
# @param [Nokogiri::XML::Element] value magic envelope xml
attr_writer :magic_envelope_xml
# 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::Document.parse(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
# Creates an encrypted Salmon Slap.
#
# @param [String] author_id diaspora* ID of the author
# @param [OpenSSL::PKey::RSA] privkey sender private key for signing the magic envelope
# @param [Entity] entity payload
# @return [EncryptedSlap] encrypted Slap instance
# @raise [ArgumentError] if any of the arguments is of the wrong type
def self.prepare(author_id, privkey, entity)
raise ArgumentError unless author_id.instance_of?(String) &&
privkey.instance_of?(OpenSSL::PKey::RSA) &&
entity.is_a?(Entity)
EncryptedSlap.new.tap do |slap|
slap.author_id = author_id
magic_envelope = MagicEnvelope.new(entity)
slap.cipher_params = magic_envelope.encrypt!
slap.magic_envelope_xml = magic_envelope.envelop(privkey)
end
end
# Creates an encrypted Salmon Slap XML string.
#
# @param [OpenSSL::PKey::RSA] pubkey recipient public key for encrypting the AES key
# @return [String] Salmon XML string
# @raise [ArgumentError] if any of the arguments is of the wrong type
def generate_xml(pubkey)
raise ArgumentError unless pubkey.instance_of?(OpenSSL::PKey::RSA)
Slap.build_xml do |xml|
xml.encrypted_header(encrypted_header(@author_id, @cipher_params, pubkey))
xml.parent << @magic_envelope_xml
end
end
private
# 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::Document.parse(xml).root
end
# Encrypt the header xml with an AES cipher and encrypt the cipher params
# with the recipients public_key.
# @param [String] author_id diaspora_handle
# @param [Hash] envelope_key envelope cipher params
# @param [OpenSSL::PKey::RSA] pubkey recipient public_key
# @return [String] encrypted base64 encoded header
def encrypted_header(author_id, envelope_key, pubkey)
data = header_xml(author_id, strict_base64_encode(envelope_key))
header_key = AES.generate_key_and_iv
ciphertext = AES.encrypt(data, header_key[:key], header_key[:iv])
json_key = JSON.generate(strict_base64_encode(header_key))
encrypted_key = Base64.strict_encode64(pubkey.public_encrypt(json_key))
json_header = JSON.generate(aes_key: encrypted_key, ciphertext: ciphertext)
Base64.strict_encode64(json_header)
end
# Generate the header xml string, including the author, aes_key and iv
# @param [String] author_id diaspora_handle of the author
# @param [Hash] envelope_key { key: "...", iv: "..." } (values in base64)
# @return [String] header XML string
def header_xml(author_id, envelope_key)
@header_xml ||= Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.decrypted_header {
xml.iv(envelope_key[:iv])
xml.aes_key(envelope_key[:key])
xml.author_id(author_id)
}
}.to_xml.strip
end
# @param [Hash] hash { key: "...", iv: "..." }
# @return [Hash] encoded hash: { key: "...", iv: "..." }
def strict_base64_encode(hash)
hash.map {|k, v| [k, Base64.strict_encode64(v)] }.to_h
end
end
end
end
|