mirror of https://github.com/mastodon/mastodon
Remove unused E2EE messaging code (#31193)
parent
2d399f5d4a
commit
5405bdd344
@ -1,18 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ActivityPub::ClaimsController < ActivityPub::BaseController
|
|
||||||
skip_before_action :authenticate_user!
|
|
||||||
|
|
||||||
before_action :require_account_signature!
|
|
||||||
before_action :set_claim_result
|
|
||||||
|
|
||||||
def create
|
|
||||||
render json: @claim_result, serializer: ActivityPub::OneTimeKeySerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_claim_result
|
|
||||||
@claim_result = ::Keys::ClaimService.new.call(@account.id, params[:id])
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,30 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::Crypto::DeliveriesController < Api::BaseController
|
|
||||||
before_action -> { doorkeeper_authorize! :crypto }
|
|
||||||
before_action :require_user!
|
|
||||||
before_action :set_current_device
|
|
||||||
|
|
||||||
def create
|
|
||||||
devices.each do |device_params|
|
|
||||||
DeliverToDeviceService.new.call(current_account, @current_device, device_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
render_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_current_device
|
|
||||||
@current_device = Device.find_by!(access_token: doorkeeper_token)
|
|
||||||
end
|
|
||||||
|
|
||||||
def resource_params
|
|
||||||
params.require(:device)
|
|
||||||
params.permit(device: [:account_id, :device_id, :type, :body, :hmac])
|
|
||||||
end
|
|
||||||
|
|
||||||
def devices
|
|
||||||
Array(resource_params[:device])
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,47 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
|
|
||||||
LIMIT = 80
|
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :crypto }
|
|
||||||
before_action :require_user!
|
|
||||||
before_action :set_current_device
|
|
||||||
|
|
||||||
before_action :set_encrypted_messages, only: :index
|
|
||||||
after_action :insert_pagination_headers, only: :index
|
|
||||||
|
|
||||||
def index
|
|
||||||
render json: @encrypted_messages, each_serializer: REST::EncryptedMessageSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def clear
|
|
||||||
@current_device.encrypted_messages.up_to(params[:up_to_id]).delete_all
|
|
||||||
render_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_current_device
|
|
||||||
@current_device = Device.find_by!(access_token: doorkeeper_token)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_encrypted_messages
|
|
||||||
@encrypted_messages = @current_device.encrypted_messages.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
|
||||||
end
|
|
||||||
|
|
||||||
def next_path
|
|
||||||
api_v1_crypto_encrypted_messages_url pagination_params(max_id: pagination_max_id) if records_continue?
|
|
||||||
end
|
|
||||||
|
|
||||||
def prev_path
|
|
||||||
api_v1_crypto_encrypted_messages_url pagination_params(min_id: pagination_since_id) unless @encrypted_messages.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def pagination_collection
|
|
||||||
@encrypted_messages
|
|
||||||
end
|
|
||||||
|
|
||||||
def records_continue?
|
|
||||||
@encrypted_messages.size == limit_param(LIMIT)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,25 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::Crypto::Keys::ClaimsController < Api::BaseController
|
|
||||||
before_action -> { doorkeeper_authorize! :crypto }
|
|
||||||
before_action :require_user!
|
|
||||||
before_action :set_claim_results
|
|
||||||
|
|
||||||
def create
|
|
||||||
render json: @claim_results, each_serializer: REST::Keys::ClaimResultSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_claim_results
|
|
||||||
@claim_results = devices.filter_map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def resource_params
|
|
||||||
params.permit(device: [:account_id, :device_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def devices
|
|
||||||
Array(resource_params[:device])
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::Crypto::Keys::CountsController < Api::BaseController
|
|
||||||
before_action -> { doorkeeper_authorize! :crypto }
|
|
||||||
before_action :require_user!
|
|
||||||
before_action :set_current_device
|
|
||||||
|
|
||||||
def show
|
|
||||||
render json: { one_time_keys: @current_device.one_time_keys.count }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_current_device
|
|
||||||
@current_device = Device.find_by!(access_token: doorkeeper_token)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,26 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::Crypto::Keys::QueriesController < Api::BaseController
|
|
||||||
before_action -> { doorkeeper_authorize! :crypto }
|
|
||||||
before_action :require_user!
|
|
||||||
before_action :set_accounts
|
|
||||||
before_action :set_query_results
|
|
||||||
|
|
||||||
def create
|
|
||||||
render json: @query_results, each_serializer: REST::Keys::QueryResultSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_accounts
|
|
||||||
@accounts = Account.where(id: account_ids).includes(:devices)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_query_results
|
|
||||||
@query_results = @accounts.filter_map { |account| ::Keys::QueryService.new.call(account) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_ids
|
|
||||||
Array(params[:id]).map(&:to_i)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,29 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::Crypto::Keys::UploadsController < Api::BaseController
|
|
||||||
before_action -> { doorkeeper_authorize! :crypto }
|
|
||||||
before_action :require_user!
|
|
||||||
|
|
||||||
def create
|
|
||||||
device = Device.find_or_initialize_by(access_token: doorkeeper_token)
|
|
||||||
|
|
||||||
device.transaction do
|
|
||||||
device.account = current_account
|
|
||||||
device.update!(resource_params[:device])
|
|
||||||
|
|
||||||
if resource_params[:one_time_keys].present? && resource_params[:one_time_keys].is_a?(Enumerable)
|
|
||||||
resource_params[:one_time_keys].each do |one_time_key_params|
|
|
||||||
device.one_time_keys.create!(one_time_key_params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: device, serializer: REST::Keys::DeviceSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def resource_params
|
|
||||||
params.permit(device: [:device_id, :name, :fingerprint_key, :identity_key], one_time_keys: [:key_id, :key, :signature])
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Vacuum::SystemKeysVacuum
|
|
||||||
def perform
|
|
||||||
vacuum_expired_system_keys!
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def vacuum_expired_system_keys!
|
|
||||||
SystemKey.expired.delete_all
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,36 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# == Schema Information
|
|
||||||
#
|
|
||||||
# Table name: devices
|
|
||||||
#
|
|
||||||
# id :bigint(8) not null, primary key
|
|
||||||
# access_token_id :bigint(8)
|
|
||||||
# account_id :bigint(8)
|
|
||||||
# device_id :string default(""), not null
|
|
||||||
# name :string default(""), not null
|
|
||||||
# fingerprint_key :text default(""), not null
|
|
||||||
# identity_key :text default(""), not null
|
|
||||||
# created_at :datetime not null
|
|
||||||
# updated_at :datetime not null
|
|
||||||
#
|
|
||||||
|
|
||||||
class Device < ApplicationRecord
|
|
||||||
belongs_to :access_token, class_name: 'Doorkeeper::AccessToken'
|
|
||||||
belongs_to :account
|
|
||||||
|
|
||||||
has_many :one_time_keys, dependent: :destroy, inverse_of: :device
|
|
||||||
has_many :encrypted_messages, dependent: :destroy, inverse_of: :device
|
|
||||||
|
|
||||||
validates :name, :fingerprint_key, :identity_key, presence: true
|
|
||||||
validates :fingerprint_key, :identity_key, ed25519_key: true
|
|
||||||
|
|
||||||
before_save :invalidate_associations, if: -> { device_id_changed? || fingerprint_key_changed? || identity_key_changed? }
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def invalidate_associations
|
|
||||||
one_time_keys.destroy_all
|
|
||||||
encrypted_messages.destroy_all
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,49 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# == Schema Information
|
|
||||||
#
|
|
||||||
# Table name: encrypted_messages
|
|
||||||
#
|
|
||||||
# id :bigint(8) not null, primary key
|
|
||||||
# device_id :bigint(8)
|
|
||||||
# from_account_id :bigint(8)
|
|
||||||
# from_device_id :string default(""), not null
|
|
||||||
# type :integer default(0), not null
|
|
||||||
# body :text default(""), not null
|
|
||||||
# digest :text default(""), not null
|
|
||||||
# message_franking :text default(""), not null
|
|
||||||
# created_at :datetime not null
|
|
||||||
# updated_at :datetime not null
|
|
||||||
#
|
|
||||||
|
|
||||||
class EncryptedMessage < ApplicationRecord
|
|
||||||
self.inheritance_column = nil
|
|
||||||
|
|
||||||
include Paginable
|
|
||||||
include Redisable
|
|
||||||
|
|
||||||
scope :up_to, ->(id) { where(arel_table[:id].lteq(id)) }
|
|
||||||
|
|
||||||
belongs_to :device
|
|
||||||
belongs_to :from_account, class_name: 'Account'
|
|
||||||
|
|
||||||
around_create Mastodon::Snowflake::Callbacks
|
|
||||||
|
|
||||||
after_commit :push_to_streaming_api
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def push_to_streaming_api
|
|
||||||
return if destroyed? || !subscribed_to_timeline?
|
|
||||||
|
|
||||||
PushEncryptedMessageWorker.perform_async(id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def subscribed_to_timeline?
|
|
||||||
redis.exists?("subscribed:#{streaming_channel}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def streaming_channel
|
|
||||||
"timeline:#{device.account_id}:#{device.device_id}"
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,19 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class MessageFranking
|
|
||||||
attr_reader :hmac, :source_account_id, :target_account_id,
|
|
||||||
:timestamp, :original_franking
|
|
||||||
|
|
||||||
def initialize(attributes = {})
|
|
||||||
@hmac = attributes[:hmac]
|
|
||||||
@source_account_id = attributes[:source_account_id]
|
|
||||||
@target_account_id = attributes[:target_account_id]
|
|
||||||
@timestamp = attributes[:timestamp]
|
|
||||||
@original_franking = attributes[:original_franking]
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_token
|
|
||||||
crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj)
|
|
||||||
crypt.encrypt_and_sign(self)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# == Schema Information
|
|
||||||
#
|
|
||||||
# Table name: one_time_keys
|
|
||||||
#
|
|
||||||
# id :bigint(8) not null, primary key
|
|
||||||
# device_id :bigint(8)
|
|
||||||
# key_id :string default(""), not null
|
|
||||||
# key :text default(""), not null
|
|
||||||
# signature :text default(""), not null
|
|
||||||
# created_at :datetime not null
|
|
||||||
# updated_at :datetime not null
|
|
||||||
#
|
|
||||||
|
|
||||||
class OneTimeKey < ApplicationRecord
|
|
||||||
belongs_to :device
|
|
||||||
|
|
||||||
validates :key_id, :key, :signature, presence: true
|
|
||||||
validates :key, ed25519_key: true
|
|
||||||
validates :signature, ed25519_signature: { message: :key, verify_key: ->(one_time_key) { one_time_key.device.fingerprint_key } }
|
|
||||||
end
|
|
@ -1,41 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# == Schema Information
|
|
||||||
#
|
|
||||||
# Table name: system_keys
|
|
||||||
#
|
|
||||||
# id :bigint(8) not null, primary key
|
|
||||||
# key :binary
|
|
||||||
# created_at :datetime not null
|
|
||||||
# updated_at :datetime not null
|
|
||||||
#
|
|
||||||
class SystemKey < ApplicationRecord
|
|
||||||
ROTATION_PERIOD = 1.week.freeze
|
|
||||||
|
|
||||||
before_validation :set_key
|
|
||||||
|
|
||||||
scope :expired, ->(now = Time.now.utc) { where(arel_table[:created_at].lt(now - (ROTATION_PERIOD * 3))) }
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def current_key
|
|
||||||
previous_key = order(id: :asc).last
|
|
||||||
|
|
||||||
if previous_key && previous_key.created_at >= ROTATION_PERIOD.ago
|
|
||||||
previous_key.key
|
|
||||||
else
|
|
||||||
create.key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_key
|
|
||||||
return if key.present?
|
|
||||||
|
|
||||||
cipher = OpenSSL::Cipher.new('AES-256-GCM')
|
|
||||||
cipher.encrypt
|
|
||||||
|
|
||||||
self.key = cipher.random_key
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,52 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ActivityPub::DeviceSerializer < ActivityPub::Serializer
|
|
||||||
context_extensions :olm
|
|
||||||
|
|
||||||
include RoutingHelper
|
|
||||||
|
|
||||||
class FingerprintKeySerializer < ActivityPub::Serializer
|
|
||||||
attributes :type, :public_key_base64
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Ed25519Key'
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_base64
|
|
||||||
object.fingerprint_key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class IdentityKeySerializer < ActivityPub::Serializer
|
|
||||||
attributes :type, :public_key_base64
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Curve25519Key'
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_base64
|
|
||||||
object.identity_key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attributes :device_id, :type, :name, :claim
|
|
||||||
|
|
||||||
has_one :fingerprint_key, serializer: FingerprintKeySerializer
|
|
||||||
has_one :identity_key, serializer: IdentityKeySerializer
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Device'
|
|
||||||
end
|
|
||||||
|
|
||||||
def claim
|
|
||||||
account_claim_url(object.account, id: object.device_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fingerprint_key
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def identity_key
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,61 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ActivityPub::EncryptedMessageSerializer < ActivityPub::Serializer
|
|
||||||
context :security
|
|
||||||
|
|
||||||
context_extensions :olm
|
|
||||||
|
|
||||||
class DeviceSerializer < ActivityPub::Serializer
|
|
||||||
attributes :type, :device_id
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Device'
|
|
||||||
end
|
|
||||||
|
|
||||||
def device_id
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DigestSerializer < ActivityPub::Serializer
|
|
||||||
attributes :type, :digest_algorithm, :digest_value
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Digest'
|
|
||||||
end
|
|
||||||
|
|
||||||
def digest_algorithm
|
|
||||||
'http://www.w3.org/2000/09/xmldsig#hmac-sha256'
|
|
||||||
end
|
|
||||||
|
|
||||||
def digest_value
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attributes :type, :message_type, :cipher_text, :message_franking
|
|
||||||
|
|
||||||
has_one :attributed_to, serializer: DeviceSerializer
|
|
||||||
has_one :to, serializer: DeviceSerializer
|
|
||||||
has_one :digest, serializer: DigestSerializer
|
|
||||||
|
|
||||||
def type
|
|
||||||
'EncryptedMessage'
|
|
||||||
end
|
|
||||||
|
|
||||||
def attributed_to
|
|
||||||
object.source_device.device_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def to
|
|
||||||
object.target_device_id
|
|
||||||
end
|
|
||||||
|
|
||||||
def message_type
|
|
||||||
object.type
|
|
||||||
end
|
|
||||||
|
|
||||||
def cipher_text
|
|
||||||
object.body
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,35 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ActivityPub::OneTimeKeySerializer < ActivityPub::Serializer
|
|
||||||
context :security
|
|
||||||
|
|
||||||
context_extensions :olm
|
|
||||||
|
|
||||||
class SignatureSerializer < ActivityPub::Serializer
|
|
||||||
attributes :type, :signature_value
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Ed25519Signature'
|
|
||||||
end
|
|
||||||
|
|
||||||
def signature_value
|
|
||||||
object.signature
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
attributes :key_id, :type, :public_key_base64
|
|
||||||
|
|
||||||
has_one :signature, serializer: SignatureSerializer
|
|
||||||
|
|
||||||
def type
|
|
||||||
'Curve25519Key'
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key_base64
|
|
||||||
object.key
|
|
||||||
end
|
|
||||||
|
|
||||||
def signature
|
|
||||||
object
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,19 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class REST::EncryptedMessageSerializer < ActiveModel::Serializer
|
|
||||||
attributes :id, :account_id, :device_id,
|
|
||||||
:type, :body, :digest, :message_franking,
|
|
||||||
:created_at
|
|
||||||
|
|
||||||
def id
|
|
||||||
object.id.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_id
|
|
||||||
object.from_account_id.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def device_id
|
|
||||||
object.from_device_id
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,9 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class REST::Keys::ClaimResultSerializer < ActiveModel::Serializer
|
|
||||||
attributes :account_id, :device_id, :key_id, :key, :signature
|
|
||||||
|
|
||||||
def account_id
|
|
||||||
object.account.id.to_s
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,6 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class REST::Keys::DeviceSerializer < ActiveModel::Serializer
|
|
||||||
attributes :device_id, :name, :identity_key,
|
|
||||||
:fingerprint_key
|
|
||||||
end
|
|
@ -1,11 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class REST::Keys::QueryResultSerializer < ActiveModel::Serializer
|
|
||||||
attributes :account_id
|
|
||||||
|
|
||||||
has_many :devices, serializer: REST::Keys::DeviceSerializer
|
|
||||||
|
|
||||||
def account_id
|
|
||||||
object.account.id.to_s
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,78 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class DeliverToDeviceService < BaseService
|
|
||||||
include Payloadable
|
|
||||||
|
|
||||||
class EncryptedMessage < ActiveModelSerializers::Model
|
|
||||||
attributes :source_account, :target_account, :source_device,
|
|
||||||
:target_device_id, :type, :body, :digest,
|
|
||||||
:message_franking
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(source_account, source_device, options = {})
|
|
||||||
@source_account = source_account
|
|
||||||
@source_device = source_device
|
|
||||||
@target_account = Account.find(options[:account_id])
|
|
||||||
@target_device_id = options[:device_id]
|
|
||||||
@body = options[:body]
|
|
||||||
@type = options[:type]
|
|
||||||
@hmac = options[:hmac]
|
|
||||||
|
|
||||||
set_message_franking!
|
|
||||||
|
|
||||||
if @target_account.local?
|
|
||||||
deliver_to_local!
|
|
||||||
else
|
|
||||||
deliver_to_remote!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_message_franking!
|
|
||||||
@message_franking = message_franking.to_token
|
|
||||||
end
|
|
||||||
|
|
||||||
def deliver_to_local!
|
|
||||||
target_device = @target_account.devices.find_by!(device_id: @target_device_id)
|
|
||||||
|
|
||||||
target_device.encrypted_messages.create!(
|
|
||||||
from_account: @source_account,
|
|
||||||
from_device_id: @source_device.device_id,
|
|
||||||
type: @type,
|
|
||||||
body: @body,
|
|
||||||
digest: @hmac,
|
|
||||||
message_franking: @message_franking
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def deliver_to_remote!
|
|
||||||
ActivityPub::DeliveryWorker.perform_async(
|
|
||||||
Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_encrypted_message(encrypted_message), ActivityPub::ActivitySerializer)),
|
|
||||||
@source_account.id,
|
|
||||||
@target_account.inbox_url
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def message_franking
|
|
||||||
MessageFranking.new(
|
|
||||||
source_account_id: @source_account.id,
|
|
||||||
target_account_id: @target_account.id,
|
|
||||||
hmac: @hmac,
|
|
||||||
timestamp: Time.now.utc
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypted_message
|
|
||||||
EncryptedMessage.new(
|
|
||||||
source_account: @source_account,
|
|
||||||
target_account: @target_account,
|
|
||||||
source_device: @source_device,
|
|
||||||
target_device_id: @target_device_id,
|
|
||||||
type: @type,
|
|
||||||
body: @body,
|
|
||||||
digest: @hmac,
|
|
||||||
message_franking: @message_franking
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,79 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Keys::ClaimService < BaseService
|
|
||||||
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
|
|
||||||
|
|
||||||
class Result < ActiveModelSerializers::Model
|
|
||||||
attributes :account, :device_id, :key_id,
|
|
||||||
:key, :signature
|
|
||||||
|
|
||||||
def initialize(account, device_id, key_attributes = {})
|
|
||||||
super(
|
|
||||||
account: account,
|
|
||||||
device_id: device_id,
|
|
||||||
key_id: key_attributes[:key_id],
|
|
||||||
key: key_attributes[:key],
|
|
||||||
signature: key_attributes[:signature],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(source_account, target_account_id, device_id)
|
|
||||||
@source_account = source_account
|
|
||||||
@target_account = Account.find(target_account_id)
|
|
||||||
@device_id = device_id
|
|
||||||
|
|
||||||
if @target_account.local?
|
|
||||||
claim_local_key!
|
|
||||||
else
|
|
||||||
claim_remote_key!
|
|
||||||
end
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def claim_local_key!
|
|
||||||
device = @target_account.devices.find_by(device_id: @device_id)
|
|
||||||
key = nil
|
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
|
||||||
key = device.one_time_keys.order(Arel.sql('random()')).first!
|
|
||||||
key.destroy!
|
|
||||||
end
|
|
||||||
|
|
||||||
@result = Result.new(@target_account, @device_id, key)
|
|
||||||
end
|
|
||||||
|
|
||||||
def claim_remote_key!
|
|
||||||
query_result = QueryService.new.call(@target_account)
|
|
||||||
device = query_result.find(@device_id)
|
|
||||||
|
|
||||||
return unless device.present? && device.valid_claim_url?
|
|
||||||
|
|
||||||
json = fetch_resource_with_post(device.claim_url)
|
|
||||||
|
|
||||||
return unless json.present? && json['publicKeyBase64'].present?
|
|
||||||
|
|
||||||
@result = Result.new(@target_account, @device_id, key_id: json['id'], key: json['publicKeyBase64'], signature: json.dig('signature', 'signatureValue'))
|
|
||||||
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e
|
|
||||||
Rails.logger.debug { "Claiming one-time key for #{@target_account.acct}:#{@device_id} failed: #{e}" }
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_resource_with_post(uri)
|
|
||||||
build_post_request(uri).perform do |response|
|
|
||||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response)
|
|
||||||
|
|
||||||
body_to_json(response.body_with_limit) if response.code == 200
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_post_request(uri)
|
|
||||||
Request.new(:post, uri).tap do |request|
|
|
||||||
request.on_behalf_of(@source_account)
|
|
||||||
request.add_headers(HEADERS)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,79 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Keys::QueryService < BaseService
|
|
||||||
include JsonLdHelper
|
|
||||||
|
|
||||||
class Result < ActiveModelSerializers::Model
|
|
||||||
attributes :account, :devices
|
|
||||||
|
|
||||||
def initialize(account, devices)
|
|
||||||
super(
|
|
||||||
account: account,
|
|
||||||
devices: devices || [],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def find(device_id)
|
|
||||||
@devices.find { |device| device.device_id == device_id }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Device < ActiveModelSerializers::Model
|
|
||||||
attributes :device_id, :name, :identity_key, :fingerprint_key
|
|
||||||
|
|
||||||
def initialize(attributes = {})
|
|
||||||
super(
|
|
||||||
device_id: attributes[:device_id],
|
|
||||||
name: attributes[:name],
|
|
||||||
identity_key: attributes[:identity_key],
|
|
||||||
fingerprint_key: attributes[:fingerprint_key],
|
|
||||||
)
|
|
||||||
@claim_url = attributes[:claim_url]
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_claim_url?
|
|
||||||
return false if @claim_url.blank?
|
|
||||||
|
|
||||||
begin
|
|
||||||
parsed_url = Addressable::URI.parse(@claim_url).normalize
|
|
||||||
rescue Addressable::URI::InvalidURIError
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(account)
|
|
||||||
@account = account
|
|
||||||
|
|
||||||
if @account.local?
|
|
||||||
query_local_devices!
|
|
||||||
else
|
|
||||||
query_remote_devices!
|
|
||||||
end
|
|
||||||
|
|
||||||
Result.new(@account, @devices)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def query_local_devices!
|
|
||||||
@devices = @account.devices.map { |device| Device.new(device) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def query_remote_devices!
|
|
||||||
return if @account.devices_url.blank?
|
|
||||||
|
|
||||||
json = fetch_resource(@account.devices_url)
|
|
||||||
|
|
||||||
return if json['items'].blank?
|
|
||||||
|
|
||||||
@devices = as_array(json['items']).map do |device|
|
|
||||||
Device.new(device_id: device['id'], name: device['name'], identity_key: device.dig('identityKey', 'publicKeyBase64'), fingerprint_key: device.dig('fingerprintKey', 'publicKeyBase64'), claim_url: device['claim'])
|
|
||||||
end
|
|
||||||
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error => e
|
|
||||||
Rails.logger.debug { "Querying devices for #{@account.acct} failed: #{e}" }
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,19 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Ed25519KeyValidator < ActiveModel::EachValidator
|
|
||||||
def validate_each(record, attribute, value)
|
|
||||||
return if value.blank?
|
|
||||||
|
|
||||||
key = Base64.decode64(value)
|
|
||||||
|
|
||||||
record.errors.add(attribute, I18n.t('crypto.errors.invalid_key')) unless verified?(key)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def verified?(key)
|
|
||||||
Ed25519.validate_key_bytes(key)
|
|
||||||
rescue ArgumentError
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,29 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Ed25519SignatureValidator < ActiveModel::EachValidator
|
|
||||||
def validate_each(record, attribute, value)
|
|
||||||
return if value.blank?
|
|
||||||
|
|
||||||
verify_key = Ed25519::VerifyKey.new(Base64.decode64(option_to_value(record, :verify_key)))
|
|
||||||
signature = Base64.decode64(value)
|
|
||||||
message = option_to_value(record, :message)
|
|
||||||
|
|
||||||
record.errors.add(attribute, I18n.t('crypto.errors.invalid_signature')) unless verified?(verify_key, signature, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def verified?(verify_key, signature, message)
|
|
||||||
verify_key.verify(signature, message)
|
|
||||||
rescue Ed25519::VerifyError, ArgumentError
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def option_to_value(record, key)
|
|
||||||
if options[key].is_a?(Proc)
|
|
||||||
options[key].call(record)
|
|
||||||
else
|
|
||||||
record.public_send(options[key])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class PushEncryptedMessageWorker
|
|
||||||
include Sidekiq::Worker
|
|
||||||
include Redisable
|
|
||||||
|
|
||||||
def perform(encrypted_message_id)
|
|
||||||
encrypted_message = EncryptedMessage.find(encrypted_message_id)
|
|
||||||
message = InlineRenderer.render(encrypted_message, nil, :encrypted_message)
|
|
||||||
timeline_id = "timeline:#{encrypted_message.device.account_id}:#{encrypted_message.device.device_id}"
|
|
||||||
|
|
||||||
redis.publish(timeline_id, Oj.dump(event: :encrypted_message, payload: message))
|
|
||||||
rescue ActiveRecord::RecordNotFound
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue