Add support for importing Ed25519 public keys

This commit is contained in:
Claire
2026-06-18 20:01:41 +02:00
parent 2a894970ac
commit 3be52d7968
4 changed files with 77 additions and 4 deletions

View File

@@ -22,7 +22,7 @@ class ActivityPub::LinkedDataSignature
keypair = Keypair.from_keyid(creator_uri)
keypair = ActivityPub::FetchRemoteKeyService.new.call(creator_uri) if keypair&.public_key.blank?
return if keypair.nil? || !keypair.usable?
return if keypair.nil? || !keypair.usable? || keypair.type != 'rsa'
options_hash = hash(@json['signature'].without('type', 'id', 'signatureValue').merge('@context' => CONTEXT))
document_hash = hash(@json.without('signature'))

View File

@@ -25,7 +25,10 @@ class Keypair < ApplicationRecord
belongs_to :account
enum :type, { rsa: 0 }
enum :type, {
rsa: 0,
ed25519: 1,
}, validate: true
attr_accessor :require_private_key
@@ -46,6 +49,8 @@ class Keypair < ApplicationRecord
case type
when 'rsa'
OpenSSL::PKey::RSA.new(private_key || public_key)
when 'ed25519'
OpenSSL::PKey.read(private_key || public_key)
end
end
end

View File

@@ -341,6 +341,14 @@ class ActivityPub::ProcessAccountService < BaseService
case tag
when :'rsa-pub'
[:rsa, OpenSSL::PKey::RSA.new(key).to_pem]
when :'ed25519-pub'
asn1 = OpenSSL::ASN1::Sequence(
[
OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('ED25519')]),
OpenSSL::ASN1::BitString(key),
]
)
[:ed25519, OpenSSL::PKey.read(asn1.to_der).public_to_pem]
end
rescue ArgumentError
nil

View File

@@ -110,7 +110,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do
stub_request(:get, public_key_id).to_return(body: key_json.merge({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }).to_json, headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the nil' do
it 'returns nil' do
expect(keypair).to be_nil
end
end
@@ -127,6 +127,13 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do
publicKeyMultibase: 'z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK',
}
end
let(:ed25519_key_pem) do
<<~TEXT
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEALm/M42cB3HkUiODQsXRcweM6TByfzEHGO9ND274JcOY=
-----END PUBLIC KEY-----
TEXT
end
let(:rsa_key_id) { 'https://example.com/alice#rsa-key' }
let(:actor_rsa_key) { rsa_multikey }
@@ -218,10 +225,63 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do
stub_request(:get, rsa_key_id).to_return(body: rsa_multikey.merge({ '@context': ['https://www.w3.org/ns/cid/v1'] }).to_json, headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the nil' do
it 'returns nil' do
expect(keypair).to be_nil
end
end
context 'with an Ed25519 key' do
let(:keypair) { subject.call(ed25519_key_id) }
context 'when the key is a sub-object from the actor' do
before do
stub_request(:get, ed25519_key_id).to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the expected account' do
expect(keypair.account.uri).to eq 'https://example.com/alice'
expect(keypair)
.to have_attributes(
uri: ed25519_key_id,
type: 'ed25519',
public_key: ed25519_key_pem
)
end
end
context 'when the key is a separate document' do
let(:ed25519_key_id) { 'https://example.com/alice-public-key.json' }
let(:actor_ed25519_key) { ed25519_key_id }
before do
stub_request(:get, ed25519_key_id).to_return(body: ed25519_multikey.merge({ '@context': ['https://www.w3.org/ns/cid/v1'] }).to_json, headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the expected account' do
expect(keypair.account.uri).to eq 'https://example.com/alice'
expect(keypair)
.to have_attributes(
uri: ed25519_key_id,
type: 'ed25519',
public_key: ed25519_key_pem
)
end
end
context 'when the key and owner do not match' do
let(:ed25519_key_id) { 'https://example.com/fake-public-key.json' }
let(:actor_ed25519_key) { 'https://example.com/alice-public-key.json' }
before do
stub_request(:get, ed25519_key_id).to_return(body: ed25519_multikey.merge({ '@context': ['https://www.w3.org/ns/cid/v1'] }).to_json, headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns nil' do
expect(keypair).to be_nil
end
end
end
end
end
end