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