mirror of
https://github.com/mastodon/mastodon.git
synced 2026-07-28 12:17:28 -05:00
134 lines
3.5 KiB
Ruby
134 lines
3.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: keypairs
|
|
#
|
|
# id :bigint(8) not null, primary key
|
|
# expires_at :datetime
|
|
# local_fragment :string
|
|
# private_key :string
|
|
# public_key :string not null
|
|
# revoked :boolean default(FALSE), not null
|
|
# type :integer not null
|
|
# uri :string
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# account_id :bigint(8) not null
|
|
#
|
|
|
|
class Keypair < ApplicationRecord
|
|
include Expireable
|
|
|
|
self.inheritance_column = nil
|
|
|
|
encrypts :private_key
|
|
|
|
belongs_to :account
|
|
|
|
enum :type, {
|
|
rsa: 0,
|
|
ed25519: 1,
|
|
'ml-dsa-44': 2,
|
|
}, validate: true
|
|
|
|
validates :uri, presence: true, uniqueness: true, if: -> { account.remote? }
|
|
validates :uri, absence: true, if: -> { account.local? }
|
|
|
|
validates :local_fragment, presence: true, uniqueness: { scope: :account_id }, format: { with: /\A#[A-Z0-9-]+\Z/i }, if: -> { account.local? }
|
|
validates :local_fragment, absence: true, if: -> { account.remote? }
|
|
|
|
validates :public_key, presence: true
|
|
validates :private_key, presence: true, if: -> { account.local? }
|
|
validates :private_key, absence: true, if: -> { account.remote? }
|
|
|
|
scope :unexpired, -> { where(expires_at: nil).or(where.not(expires_at: ..Time.now.utc)) }
|
|
scope :usable, -> { unexpired.where(revoked: false) }
|
|
|
|
alias actor account
|
|
|
|
def full_uri
|
|
return ActivityPub::TagManager.instance.uri_for(account) + local_fragment if local_fragment.present?
|
|
|
|
uri
|
|
end
|
|
|
|
def keypair
|
|
@keypair ||= begin
|
|
case type
|
|
when 'rsa'
|
|
OpenSSL::PKey::RSA.new(private_key || public_key)
|
|
when 'ed25519', 'ml-dsa-44'
|
|
OpenSSL::PKey.read(private_key || public_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
def linzer_public_key
|
|
@linzer_public_key ||= begin
|
|
case type
|
|
when 'rsa'
|
|
Linzer.new_rsa_v1_5_sha256_public_key(public_key)
|
|
when 'ed25519'
|
|
Linzer.new_ed25519_public_key(public_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
def linzer_private_key
|
|
@linzer_private_key ||= begin
|
|
case type
|
|
when 'rsa'
|
|
Linzer.new_rsa_v1_5_sha256_key(private_key, full_uri)
|
|
when 'ed25519'
|
|
Linzer.new_ed25519_key(private_key, full_uri)
|
|
end
|
|
end
|
|
end
|
|
|
|
def usable?
|
|
!revoked? && !expired?
|
|
end
|
|
|
|
def self.from_keyid(uri)
|
|
return if uri.blank?
|
|
|
|
keypair = find_by(uri: uri)
|
|
return keypair unless keypair.nil?
|
|
|
|
# No keypair found, try the old way we used to store RSA keypairs
|
|
account = ActivityPub::TagManager.instance.uri_to_actor(uri)
|
|
return if account&.public_key.blank?
|
|
|
|
from_legacy_account(account, uri: uri)
|
|
end
|
|
|
|
def self.from_legacy_account(account, uri: nil)
|
|
Keypair.new(
|
|
account:,
|
|
uri: uri.presence || ActivityPub::TagManager.instance.key_uri_for(account),
|
|
public_key: account.public_key,
|
|
private_key: account.private_key,
|
|
type: :rsa
|
|
)
|
|
end
|
|
|
|
def self.from_worker_arg(account, private_key_pem_or_hash)
|
|
if private_key_pem_or_hash.is_a?(String)
|
|
account.keypairs.build(
|
|
private_key: private_key_pem_or_hash,
|
|
local_fragment: '#main-key',
|
|
type: :rsa
|
|
)
|
|
else
|
|
account.keypairs.build(
|
|
private_key: private_key_pem_or_hash['private_key'],
|
|
public_key: private_key_pem_or_hash['public_key'],
|
|
uri: private_key_pem_or_hash['uri'],
|
|
local_fragment: private_key_pem_or_hash['local_fragment'],
|
|
type: private_key_pem_or_hash['type']
|
|
)
|
|
end
|
|
end
|
|
end
|