class WopiDiscovery < ApplicationRecord require 'base64' has_many :wopi_apps, class_name: 'WopiApp', foreign_key: 'wopi_discovery_id', dependent: :destroy validates :expires, :proof_key_mod, :proof_key_exp, :proof_key_old_mod, :proof_key_old_exp, presence: true # Verifies if proof from headers, X-WOPI-Proof/X-WOPI-OldProof was encrypted # with this discovery public key (two key possible old/new) def verify_proof(token, timestamp, signed_proof, signed_proof_old, url) token_length = [token.length].pack('>N').bytes timestamp_bytes = [timestamp.to_i].pack('>Q').bytes.reverse timestamp_length = [timestamp_bytes.length].pack('>N').bytes url_length = [url.length].pack('>N').bytes expected_proof = token_length + token.bytes + url_length + url.upcase.bytes + timestamp_length + timestamp_bytes key = generate_key(proof_key_mod, proof_key_exp) old_key = generate_key(proof_key_old_mod, proof_key_old_exp) # Try all possible combiniations try_verification(expected_proof, signed_proof, key) || try_verification(expected_proof, signed_proof_old, key) || try_verification(expected_proof, signed_proof, old_key) end # Generates a public key from given modulus and exponent def generate_key(modulus, exponent) mod = Base64.decode64(modulus).unpack('H*').first.to_i(16) exp = Base64.decode64(exponent).unpack('H*').first.to_i(16) seq = OpenSSL::ASN1::Sequence.new([OpenSSL::ASN1::Integer.new(mod), OpenSSL::ASN1::Integer.new(exp)]) OpenSSL::PKey::RSA.new(seq.to_der) end # Verify if decrypting signed_proof with public_key equals to expected_proof def try_verification(expected_proof, signed_proof_b64, public_key) signed_proof = Base64.decode64(signed_proof_b64) public_key.verify(OpenSSL::Digest::SHA256.new, signed_proof, expected_proof.pack('c*')) end end