class SelfSDK::SignatureGraph
Public Class Methods
new(history)
click to toggle source
# File lib/signature_graph.rb, line 100 def initialize(history) @root = nil @keys = Hash.new @devices = Hash.new @signatures = Hash.new @operations = Array.new @recovery_key = nil history.each do |operation| execute(operation) end end
Public Instance Methods
execute(operation)
click to toggle source
# File lib/signature_graph.rb, line 125 def execute(operation) op = Operation.new(operation) raise "operation sequence is out of order" if op.sequence != @operations.length if op.sequence > 0 if @signatures[op.previous] != op.sequence - 1 raise "operation previous signature does not match" end if @operations[op.sequence - 1].timestamp >= op.timestamp raise "operation timestamp occurs before previous operation" end sk = @keys[op.signing_key] raise "operation specifies a signing key that does not exist" if sk.nil? if sk.revoked? && op.timestamp > sk.revoked raise "operation was signed by a key that was revoked at the time of signing" end if sk.type == KEY_TYPE_RECOVERY && op.revokes(op.signing_key) != true raise "account recovery operation does not revoke the current active recovery key" end end execute_actions(op) sk = @keys[op.signing_key] raise "operation specifies a signing key that does not exist" if sk.nil? if op.timestamp < sk.created || sk.revoked? && op.timestamp > sk.revoked raise "operation was signed with a key that was revoked" end sig = Base64.urlsafe_decode64(op.jws[:signature]) sk.public_key.verify(sig, "#{op.jws[:protected]}.#{op.jws[:payload]}") has_valid_key = false @keys.each do |kid, k| has_valid_key = true unless k.revoked? end raise "signature graph does not contain any active or valid keys" unless has_valid_key raise "signature graph does not contain a valid recovery key" if @recovery_key.nil? raise "signature graph does not contain a valid recovery key" if @recovery_key.revoked? @operations.push(op) @signatures[op.jws[:signature]] = op.sequence end
key_by_device(did)
click to toggle source
# File lib/signature_graph.rb, line 119 def key_by_device(did) k = @devices[did] raise "key not found" if k.nil? k end
key_by_id(kid)
click to toggle source
# File lib/signature_graph.rb, line 113 def key_by_id(kid) k = @keys[kid] raise "key not found" if k.nil? k end
Private Instance Methods
add(operation, action)
click to toggle source
# File lib/signature_graph.rb, line 216 def add(operation, action) if @keys[action[:kid]].nil? != true raise "operation contains a key with a duplicate identifier" end k = Key.new(action) case action[:type] when KEY_TYPE_DEVICE dk = @devices[action[:did]] unless dk.nil? raise "operation contains more than one active key for a device" unless dk.revoked? end when KEY_TYPE_RECOVERY unless @recovery_key.nil? raise "operation contains more than one active recovery key" unless @recovery_key.revoked? end @recovery_key = k end @keys[k.kid] = k @devices[k.did] = k if operation.sequence == 0 && operation.signing_key == action[:kid] @root = k return end parent = @keys[operation.signing_key] raise "operation specifies a signing key that does not exist" if parent.nil? k.incoming.push(parent) parent.outgoing.push(k) end
execute_actions(op)
click to toggle source
# File lib/signature_graph.rb, line 182 def execute_actions(op) op.actions.each do |action| raise "operation action does not provide a key identifier" if action[:kid].nil? if action[:type] != KEY_TYPE_DEVICE && action[:type] != KEY_TYPE_RECOVERY raise "operation action does not provide a valid type" end if action[:action] != ACTION_ADD && action[:action] != ACTION_REVOKE raise "operation action does not provide a valid action" end if action[:action] == ACTION_ADD && action[:key].nil? raise "operation action does not provide a valid public key" end if action[:action] == ACTION_ADD && action[:type] == KEY_TYPE_DEVICE && action[:did].nil? raise "operation action does not provide a valid device id" end if action[:from] < 0 raise "operation action does not provide a valid timestamp for the action to take effect from" end case action[:action] when ACTION_ADD action[:from] = op.timestamp add(op, action) when ACTION_REVOKE revoke(op, action) end end end
revoke(operation, action)
click to toggle source
# File lib/signature_graph.rb, line 253 def revoke(operation, action) k = @keys[action[:kid]] raise "operation tries to revoke a key that does not exist" if k.nil? raise "root operation cannot revoke keys" if operation.sequence < 1 raise "operation tries to revoke a key that has already been revoked" if k.revoked? k.revoke(action[:from]) sk = @keys[operation.signing_key] raise "operation specifies a signing key that does not exist" if sk.nil? # if this is an account recovery, nuke all existing keys if sk.type == KEY_TYPE_RECOVERY @root.revoke(action[:from]) @root.child_keys.each do |ck| ck.revoke(action[:from]) unless ck.revoked? end return end k.child_keys.each do |ck| ck.revoke(action[:from]) unless ck.created < action[:from] end end