Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON::LD::API.toRdf, when given a Hash as input, may mutate it #54

Closed
ClearlyClaire opened this issue May 10, 2022 · 5 comments
Closed
Assignees
Labels

Comments

@ClearlyClaire
Copy link

When given a hash with an embedded @context, it can end up removing @context from the nested hash.

This seems to be because of

context = context.parse(input.delete('@context'), base: @options[:base])

@gkellogg
Copy link
Member

Yes, but I believe this is removing it from a copy of the original input, unless you have a use case that illustrates an actual issue created because of that. That said, the code would probably work using input['@context'] instead of input.delete('@context').

@ClearlyClaire
Copy link
Author

Yes, I did run into this issue with an actual use case. Mastodon signs (some of) its activities by using Linked-Data Signatures, so it uses JSON::LD::API.toRdf to normalize its activities to be signed.

Because not all consumers handle JSON-LD, we ensure that our payloads can be processed as plain JSON, so we attach the signature (computed on the normalized object) to the original hash object.

A recent PR aiming to fix a bug in Mastodon introduces nested contexts, which strips a @context from the input object and cause its meaning to change and the signature to be wrong: mastodon/mastodon#18354

The signature is basically computed like this:

def canonicalize(json)
  graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
  graph.dump(:normalize)
end

def hash(obj)
  Digest::SHA256.hexdigest(canonicalize(obj))
end

def sign!(creator, sign_with: nil)
  options = {
    'type'    => 'RsaSignature2017',
    'creator' => [ActivityPub::TagManager.instance.uri_for(creator), '#main-key'].join,
    'created' => Time.now.utc.iso8601,
  }

  options_hash  = hash(options.without('type', 'id', 'signatureValue').merge('@context' => CONTEXT))
  document_hash = hash(@json.without('signature'))
  to_be_signed  = options_hash + document_hash
  keypair       = sign_with.present? ? OpenSSL::PKey::RSA.new(sign_with) : creator.keypair

  signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), to_be_signed))

  @json.merge('signature' => options.merge('signatureValue' => signature))
end

I can easily work around this issue by building my object differently or doing a deep_dup on the input, but this was fairly surprising behavior.

@gkellogg gkellogg self-assigned this May 10, 2022
@gkellogg gkellogg added the bug label May 10, 2022
@gkellogg
Copy link
Member

I'll get an update out shortly with a fix. Thanks for the use case.

@gkellogg
Copy link
Member

I pushed a fix to the develop branch. If that solves your issues, I'll push a new release out.

@ClearlyClaire
Copy link
Author

It does solve it! Thank you for the quick fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants