Module: Faria::Launchpad::Packet

Defined in:
lib/faria/launchpad/packet.rb

Defined Under Namespace

Classes: ExpiredSignature, MismatchedRequestURL, MissingRemoteKey

Constant Summary collapse

VERSION =
"v0.2"

Class Method Summary collapse

Class Method Details

.add_api_url(packet, url) ⇒ Object



85
86
87
88
# File 'lib/faria/launchpad/packet.rb', line 85

def self.add_api_url(packet, url)
  packet["api_url"] = Addressable::URI.parse(url).normalize.to_s
  packet
end

.add_expires(packet, expires_in) ⇒ Object



95
96
97
98
# File 'lib/faria/launchpad/packet.rb', line 95

def self.add_expires(packet, expires_in)
  packet[:exp] = Time.now.utc.to_i + expires_in
  packet
end

.add_issued_at(packet) ⇒ Object



90
91
92
93
# File 'lib/faria/launchpad/packet.rb', line 90

def self.add_issued_at(packet)
  packet[:iat] = Time.now.utc.to_i
  packet
end

.add_issuer(packet, issuer) ⇒ Object



80
81
82
83
# File 'lib/faria/launchpad/packet.rb', line 80

def self.add_issuer(packet, issuer)
  packet[:iss] = issuer
  packet
end

.add_source(packet, source) ⇒ Object



75
76
77
78
# File 'lib/faria/launchpad/packet.rb', line 75

def self.add_source(packet, source)
  packet[:faria_source] = source
  packet
end

.decrypt(raw_data, options = {}, local_key:, remote_key:) ⇒ Object

for cases where you known in advance the remote key to use (such as LaunchPad clients which will only be receiving messages from LaunchPad and therefore will only use it’s public key for verifying signatures



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/faria/launchpad/packet.rb', line 45

def self.decrypt(raw_data, options = {}, local_key:, remote_key: )
  _version, jwe = raw_data.split(";", 2)
  jwt = JWE.decrypt(jwe, local_key)
  arr = JWT.decode(jwt, remote_key, true, { :algorithm => 'RS512' })
  payload, _header = arr

  # validate_expiration will be handled by JWT decode
  validate_url!(payload, options[:actual_url])

  payload["data"]
end

.decrypt_variable_key(raw_data, options = {}, local_key:, remote_key_func:) ⇒ Object

for cases where the signature key is not known in advance and must be determined by source information embedded in the JWT header



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/faria/launchpad/packet.rb', line 59

def self.decrypt_variable_key(raw_data, options = {}, local_key:, remote_key_func: )
  _version, jwe = raw_data.split(";", 2)
  jwt = JWE.decrypt(jwe, local_key)
  header, payload = JWT::Decode.new(jwt, nil, false, {}).decode_segments[0..1]
  remote_key = remote_key_func.call(header, payload)
  fail(MissingRemoteKey) if remote_key.nil?

  arr = JWT.decode(jwt, remote_key, true, { :algorithm => 'RS512' })
  payload, _header = arr

  # validate_expiration will be handled by JWT decode
  validate_url!(payload, options[:actual_url])

  payload["data"]
end

.encrypt(data, options = {}, local_key:, remote_key:) ⇒ Object

encrypting is done with LaunchPad public key signing is done with local private key



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/faria/launchpad/packet.rb', line 29

def self.encrypt(data, options = {}, local_key:, remote_key: )
  packet = { "data" => data}
  packet = add_issued_at(packet)
  packet = add_expires(packet, options[:expires_in]) if options[:expires_in]
  packet = add_api_url(packet, options[:api_url]) if options[:api_url]
  # packet = add_issuer(packet, options[:issuer])
  packet = add_source(packet, options[:source]) if options[:source]

  payload = JWT.encode(packet, local_key, 'RS512')
  "#{VERSION};" + JWE.encrypt(payload, remote_key) # public
end

.validate_expiration!(payload) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/faria/launchpad/packet.rb', line 109

def self.validate_expiration!(payload)
  return unless payload.include?('exp')
  leeway = 0

  valid_until = (Time.now.utc.to_i - leeway)
  if payload['exp'].to_i < valid_until
    diff = valid_until - payload['exp'].to_i
    error = ExpiredSignature.new("Signature expired", diff)
    fail(error)
  end
end

.validate_url!(payload, actual_url) ⇒ Object



100
101
102
103
104
105
106
107
# File 'lib/faria/launchpad/packet.rb', line 100

def self.validate_url!(payload, actual_url)
  return unless payload.include?('api_url')

  normalized_url = Addressable::URI.parse(actual_url).normalize.to_s
  if payload['api_url'] != normalized_url
    fail(MismatchedRequestURL)
  end
end