terminalwire 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/terminalwire/cache.rb +113 -0
- data/lib/terminalwire/client/entitlement.rb +7 -19
- data/lib/terminalwire/client/server_license_verification.rb +74 -0
- data/lib/terminalwire/client.rb +19 -15
- data/lib/terminalwire/version.rb +1 -1
- data/lib/terminalwire.rb +7 -0
- metadata +19 -5
- data/lib/terminalwire/authority.rb +0 -44
- data/lib/terminalwire/licensing.rb +0 -187
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ab3f5780e6c3f7f544936130b288676ad60b29ef05944842771bcb3ca51192a
|
4
|
+
data.tar.gz: 4bd9a47eb0541ca81dd53cc19f6bf5ca02e388f22048b1fceaf7ac0f910e557d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e37f803cf8777f66aa6f8f19cd72fb0ca778e06b1f39a7762b8e3ed74b494dee210f025af65063b6d60321d9d6e9c20fa00b81dcd61b1ca7efd9193a6bf5e310
|
7
|
+
data.tar.gz: 25b6c73db710026062340f779bee11738952bd86efb80bef53f4d92cdef9599e917e85a6d979ee559fa422c3f73b4f8ffd5d7e9d87d81f26a531bab8603c1e68
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "msgpack"
|
3
|
+
require "base64"
|
4
|
+
require "time"
|
5
|
+
require "fileutils"
|
6
|
+
|
7
|
+
module Terminalwire::Cache
|
8
|
+
module File
|
9
|
+
# Hoist the File class to avoid conflicts with the standard library.
|
10
|
+
File = ::File
|
11
|
+
|
12
|
+
class Store
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
def initialize(path:)
|
16
|
+
@path = Pathname.new(path).expand_path
|
17
|
+
FileUtils.mkdir_p(@path) unless @path.directory?
|
18
|
+
end
|
19
|
+
|
20
|
+
def entry(key)
|
21
|
+
Entry.new(path: @path.join(Entry.key_path(key)))
|
22
|
+
end
|
23
|
+
alias :[] :entry
|
24
|
+
|
25
|
+
def evict
|
26
|
+
each(&:evict)
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
each(&:destroy)
|
31
|
+
end
|
32
|
+
|
33
|
+
def each
|
34
|
+
@path.each_child do |path|
|
35
|
+
yield Entry.new(path:)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Entry
|
41
|
+
VERSION = "1.0"
|
42
|
+
|
43
|
+
def self.key_path(value)
|
44
|
+
Base64.urlsafe_encode64(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_accessor :value, :expires
|
48
|
+
|
49
|
+
def initialize(path:)
|
50
|
+
@path = path
|
51
|
+
deserialize if persisted?
|
52
|
+
end
|
53
|
+
|
54
|
+
def nil?
|
55
|
+
@value.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
def present?
|
59
|
+
not nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
def persisted?
|
63
|
+
File.exist? @path
|
64
|
+
end
|
65
|
+
|
66
|
+
def expired?(time: Time.now)
|
67
|
+
@expires && @expires < time.utc
|
68
|
+
end
|
69
|
+
|
70
|
+
def fresh?(...)
|
71
|
+
not expired?(...)
|
72
|
+
end
|
73
|
+
|
74
|
+
def hit?
|
75
|
+
persisted? and fresh?
|
76
|
+
end
|
77
|
+
|
78
|
+
def miss?
|
79
|
+
not hit?
|
80
|
+
end
|
81
|
+
|
82
|
+
def save
|
83
|
+
File.write @path, serialize
|
84
|
+
end
|
85
|
+
|
86
|
+
def evict
|
87
|
+
destroy if expired?
|
88
|
+
end
|
89
|
+
|
90
|
+
def deserialize
|
91
|
+
case MessagePack.unpack(File.read(@path), symbolize_keys: true)
|
92
|
+
in { value:, expires:, version: VERSION }
|
93
|
+
@value = value
|
94
|
+
@expires = Time.parse(expires).utc if expires
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def destroy
|
99
|
+
File.delete(@path)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def serialize
|
105
|
+
MessagePack.pack(
|
106
|
+
value: @value,
|
107
|
+
expires: @expires&.utc&.iso8601,
|
108
|
+
version: VERSION
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -167,7 +167,7 @@ module Terminalwire::Client
|
|
167
167
|
end
|
168
168
|
|
169
169
|
class RootPolicy < Policy
|
170
|
-
|
170
|
+
AUTHORITY = "terminalwire.com".freeze
|
171
171
|
|
172
172
|
# Ensure the binary stubs are executable. This increases the
|
173
173
|
# file mode entitlement so that stubs created in ./bin are executable.
|
@@ -175,7 +175,7 @@ module Terminalwire::Client
|
|
175
175
|
|
176
176
|
def initialize(*, **, &)
|
177
177
|
# Make damn sure the authority is set to Terminalwire.
|
178
|
-
super(*, authority:
|
178
|
+
super(*, authority: AUTHORITY, **, &)
|
179
179
|
|
180
180
|
# Now setup special permitted paths.
|
181
181
|
@paths.permit root_path
|
@@ -202,24 +202,12 @@ module Terminalwire::Client
|
|
202
202
|
end
|
203
203
|
end
|
204
204
|
|
205
|
-
def self.
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
when RootPolicy::HOST
|
210
|
-
RootPolicy.new
|
211
|
-
else
|
212
|
-
Policy.new authority: url_authority(url)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def self.url_authority(url)
|
217
|
-
# I had to lift this from URI::HTTP because `ws://` doesn't
|
218
|
-
# have an authority method.
|
219
|
-
if url.port == url.default_port
|
220
|
-
url.host
|
205
|
+
def self.resolve(*, authority:, **, &)
|
206
|
+
case authority
|
207
|
+
when RootPolicy::AUTHORITY
|
208
|
+
RootPolicy.new(*, **, &)
|
221
209
|
else
|
222
|
-
|
210
|
+
Policy.new *, authority:, **, &
|
223
211
|
end
|
224
212
|
end
|
225
213
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "async/http/internet"
|
2
|
+
require "base64"
|
3
|
+
require "uri"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Terminalwire
|
7
|
+
module Client
|
8
|
+
class ServerLicenseVerification
|
9
|
+
include Logging
|
10
|
+
|
11
|
+
def initialize(url:)
|
12
|
+
@url = URI(url)
|
13
|
+
@internet = Async::HTTP::Internet.new
|
14
|
+
@cache_store = Terminalwire::Cache::File::Store.new(path: "~/.terminalwire/cache/licenses/verifications")
|
15
|
+
end
|
16
|
+
|
17
|
+
def key
|
18
|
+
Base64.urlsafe_encode64 @url
|
19
|
+
end
|
20
|
+
|
21
|
+
def cache = @cache_store.entry key
|
22
|
+
|
23
|
+
def payload
|
24
|
+
if cache.miss?
|
25
|
+
logger.debug "Stale verification. Requesting new verification."
|
26
|
+
request do |response|
|
27
|
+
# Set the expiry on the file cache for the header.
|
28
|
+
if max_age = response.headers["cache-control"].max_age
|
29
|
+
logger.debug "Caching for #{max_age}"
|
30
|
+
cache.expires = Time.now + max_age
|
31
|
+
end
|
32
|
+
|
33
|
+
# Process based on the response code.
|
34
|
+
case response.status
|
35
|
+
in 200
|
36
|
+
logger.debug "License for #{@url} found."
|
37
|
+
data = self.class.unpack response.read
|
38
|
+
cache.value = data
|
39
|
+
return data
|
40
|
+
in 404
|
41
|
+
logger.debug "License for #{@url} not found."
|
42
|
+
return self.class.unpack response.read
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
return cache.value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def message
|
51
|
+
payload.dig(:shell, :output)
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def verification_url
|
57
|
+
Terminalwire.url
|
58
|
+
.path("/licenses/verifications", key)
|
59
|
+
end
|
60
|
+
|
61
|
+
def request(&)
|
62
|
+
logger.debug "Requesting license verification from #{verification_url}."
|
63
|
+
response = @internet.get verification_url, {
|
64
|
+
"Accept" => "application/x-msgpack",
|
65
|
+
"User-Agent" => "Terminalwire/#{Terminalwire::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})",
|
66
|
+
}, &
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.unpack(pack)
|
70
|
+
MessagePack.unpack(pack, symbolize_keys: true)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/terminalwire/client.rb
CHANGED
@@ -9,13 +9,17 @@ module Terminalwire
|
|
9
9
|
|
10
10
|
include Logging
|
11
11
|
|
12
|
-
attr_reader :adapter, :
|
12
|
+
attr_reader :adapter, :resources, :endpoint
|
13
|
+
attr_accessor :entitlement
|
13
14
|
|
14
|
-
def initialize(adapter, arguments: ARGV, program_name: $0,
|
15
|
-
@
|
15
|
+
def initialize(adapter, arguments: ARGV, program_name: $0, endpoint:)
|
16
|
+
@endpoint = endpoint
|
16
17
|
@adapter = adapter
|
17
18
|
@program_arguments = arguments
|
18
19
|
@program_name = program_name
|
20
|
+
@entitlement = Entitlement.resolve(authority: @endpoint.authority)
|
21
|
+
|
22
|
+
yield self if block_given?
|
19
23
|
|
20
24
|
@resources = Resource::Handler.new do |it|
|
21
25
|
it << Resource::STDOUT.new("stdout", @adapter, entitlement:)
|
@@ -27,7 +31,17 @@ module Terminalwire
|
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
34
|
+
def verify_license
|
35
|
+
# Connect to the Terminalwire license server to verify the URL endpoint
|
36
|
+
# and displays a message to the user, if any are present.
|
37
|
+
$stdout.print Terminalwire::Client::ServerLicenseVerification.new(url: @endpoint.to_url).message
|
38
|
+
rescue
|
39
|
+
$stderr.puts "Failed to verify server license."
|
40
|
+
end
|
41
|
+
|
30
42
|
def connect
|
43
|
+
verify_license
|
44
|
+
|
31
45
|
@adapter.write(
|
32
46
|
event: "initialization",
|
33
47
|
protocol: { version: VERSION },
|
@@ -53,16 +67,7 @@ module Terminalwire
|
|
53
67
|
end
|
54
68
|
end
|
55
69
|
|
56
|
-
|
57
|
-
def self.authority(url)
|
58
|
-
if url.port == url.default_port
|
59
|
-
url.host
|
60
|
-
else
|
61
|
-
"#{url.host}:#{url.port}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.websocket(url:, arguments: ARGV, entitlement: nil)
|
70
|
+
def self.websocket(url:, arguments: ARGV, &configuration)
|
66
71
|
url = URI(url)
|
67
72
|
|
68
73
|
Async do |task|
|
@@ -74,8 +79,7 @@ module Terminalwire
|
|
74
79
|
Async::WebSocket::Client.connect(endpoint) do |adapter|
|
75
80
|
transport = Terminalwire::Transport::WebSocket.new(adapter)
|
76
81
|
adapter = Terminalwire::Adapter::Socket.new(transport)
|
77
|
-
|
78
|
-
Terminalwire::Client::Handler.new(adapter, arguments:, entitlement:).connect
|
82
|
+
Terminalwire::Client::Handler.new(adapter, arguments:, endpoint:, &configuration).connect
|
79
83
|
end
|
80
84
|
end
|
81
85
|
end
|
data/lib/terminalwire/version.rb
CHANGED
data/lib/terminalwire.rb
CHANGED
@@ -10,15 +10,22 @@ require 'async'
|
|
10
10
|
require 'async/http/endpoint'
|
11
11
|
require 'async/websocket/client'
|
12
12
|
require 'async/websocket/adapters/rack'
|
13
|
+
require 'uri-builder'
|
13
14
|
|
14
15
|
module Terminalwire
|
15
16
|
class Error < StandardError; end
|
16
17
|
|
18
|
+
# Zeitwerk loader for the Terminalwire gem.
|
17
19
|
Loader = Zeitwerk::Loader.for_gem.tap do |loader|
|
18
20
|
loader.ignore("#{__dir__}/generators")
|
19
21
|
loader.setup
|
20
22
|
end
|
21
23
|
|
24
|
+
# Used by Terminalwire client to connect to Terminalire.com for license
|
25
|
+
# validations, etc.
|
26
|
+
TERMINALWIRE_URL = "https://terminalwire.com".freeze
|
27
|
+
def self.url = URI.build(TERMINALWIRE_URL)
|
28
|
+
|
22
29
|
module Resource
|
23
30
|
class Base
|
24
31
|
attr_reader :name, :adapter
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terminalwire
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Gessler
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-websocket
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '2.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: uri-builder
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.1.9
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.1.9
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: rake
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,12 +175,12 @@ files:
|
|
161
175
|
- lib/generators/terminalwire/install/templates/main_terminal.rb
|
162
176
|
- lib/terminalwire.rb
|
163
177
|
- lib/terminalwire/adapter.rb
|
164
|
-
- lib/terminalwire/
|
178
|
+
- lib/terminalwire/cache.rb
|
165
179
|
- lib/terminalwire/client.rb
|
166
180
|
- lib/terminalwire/client/entitlement.rb
|
167
181
|
- lib/terminalwire/client/exec.rb
|
168
182
|
- lib/terminalwire/client/resource.rb
|
169
|
-
- lib/terminalwire/
|
183
|
+
- lib/terminalwire/client/server_license_verification.rb
|
170
184
|
- lib/terminalwire/logging.rb
|
171
185
|
- lib/terminalwire/rails.rb
|
172
186
|
- lib/terminalwire/server.rb
|
@@ -199,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
213
|
- !ruby/object:Gem::Version
|
200
214
|
version: '0'
|
201
215
|
requirements: []
|
202
|
-
rubygems_version: 3.5.
|
216
|
+
rubygems_version: 3.5.3
|
203
217
|
signing_key:
|
204
218
|
specification_version: 4
|
205
219
|
summary: Ship a CLI for your web app. No API required.
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require "uri"
|
2
|
-
require "base64"
|
3
|
-
|
4
|
-
# Resolves domains into authorities, which are is used for access
|
5
|
-
# identity control in Terminalwire.
|
6
|
-
class Terminalwire::Authority
|
7
|
-
# Used to seperate path keys in the URL.
|
8
|
-
PATH_SEPERATOR = "/".freeze
|
9
|
-
|
10
|
-
# Used to demark a URL string as authorative.
|
11
|
-
SCHEME = "terminalwire://".freeze
|
12
|
-
|
13
|
-
def initialize(url:)
|
14
|
-
@url = URI(url)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Extracted from HTTP. This is so we can
|
18
|
-
def domain
|
19
|
-
if @url.port == @url.default_port
|
20
|
-
@url.host
|
21
|
-
else
|
22
|
-
"#{url.host}:#{url.port}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Make sure there's always a / at the end of the path.
|
27
|
-
def path
|
28
|
-
path_keys.join(PATH_SEPERATOR).prepend(PATH_SEPERATOR)
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_s
|
32
|
-
[SCHEME, domain, path].join
|
33
|
-
end
|
34
|
-
|
35
|
-
def key
|
36
|
-
Base64.urlsafe_encode64(to_s)
|
37
|
-
end
|
38
|
-
|
39
|
-
protected
|
40
|
-
|
41
|
-
def path_keys
|
42
|
-
@url.path.scan(/[^\/]+/)
|
43
|
-
end
|
44
|
-
end
|
@@ -1,187 +0,0 @@
|
|
1
|
-
require "msgpack"
|
2
|
-
require "openssl"
|
3
|
-
require "base64"
|
4
|
-
require "uri"
|
5
|
-
|
6
|
-
module Terminalwire::Licensing
|
7
|
-
PRIVATE_KEY_LENGTH = 2048
|
8
|
-
|
9
|
-
def self.generate_private_key
|
10
|
-
OpenSSL::PKey::RSA.new(PRIVATE_KEY_LENGTH)
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.generate_private_pem
|
14
|
-
generate_private_key.to_pem
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.time
|
18
|
-
Time.now.utc
|
19
|
-
end
|
20
|
-
|
21
|
-
# Handles encoding data into a license key with prefixes that can be packed and unpacked.
|
22
|
-
module Key
|
23
|
-
# Mix into classes that need to generate or read keys
|
24
|
-
module Serialization
|
25
|
-
# This is called when the module is included in a class
|
26
|
-
def self.included(base)
|
27
|
-
# Extend the class with the class methods when the module is included
|
28
|
-
base.extend(ClassMethods)
|
29
|
-
end
|
30
|
-
|
31
|
-
def serialize(...)
|
32
|
-
self.class.serialize(...)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Define the class methods that will be available on the including class
|
36
|
-
module ClassMethods
|
37
|
-
def serializer
|
38
|
-
Key::Serializer.new(prefix: self::PREFIX)
|
39
|
-
end
|
40
|
-
|
41
|
-
def serialize(...)
|
42
|
-
serializer.serialize(...)
|
43
|
-
end
|
44
|
-
|
45
|
-
def deserialize(...)
|
46
|
-
serializer.deserialize(...)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
class Serializer
|
52
|
-
attr_reader :prefix
|
53
|
-
|
54
|
-
def initialize(prefix:)
|
55
|
-
@prefix = prefix
|
56
|
-
end
|
57
|
-
|
58
|
-
def serialize(data)
|
59
|
-
prepend_prefix Base64.urlsafe_encode64 MessagePack.pack data
|
60
|
-
end
|
61
|
-
|
62
|
-
def deserialize(data)
|
63
|
-
MessagePack.unpack Base64.urlsafe_decode64 unshift_prefix data
|
64
|
-
end
|
65
|
-
|
66
|
-
protected
|
67
|
-
|
68
|
-
def prepend_prefix(key)
|
69
|
-
[prefix, key].join
|
70
|
-
end
|
71
|
-
|
72
|
-
def unshift_prefix(key)
|
73
|
-
head, prefix, tail = key.partition(@prefix)
|
74
|
-
# Check if partition successfully split the string with the correct prefix
|
75
|
-
raise RuntimeError, "Expected prefix #{@prefix.inspect} on #{key.inspect}" if prefix.empty?
|
76
|
-
tail
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# This code all runs on Terminalwire servers.
|
82
|
-
module Issuer
|
83
|
-
# Generates license keys that developers use to activate their software.
|
84
|
-
class ServerKeyGenerator
|
85
|
-
include Key::Serialization
|
86
|
-
|
87
|
-
PREFIX = "server_key_".freeze
|
88
|
-
|
89
|
-
VERSION = "1.0".freeze
|
90
|
-
|
91
|
-
def initialize(public_key:, license_url:, generated_at: Terminalwire::Licensing.time)
|
92
|
-
@public_key = public_key
|
93
|
-
@license_url = URI(license_url)
|
94
|
-
@generated_at = generated_at
|
95
|
-
end
|
96
|
-
|
97
|
-
def to_h
|
98
|
-
{
|
99
|
-
version: VERSION,
|
100
|
-
generated_at: @generated_at.iso8601,
|
101
|
-
public_key: @public_key.to_pem,
|
102
|
-
license_url: @license_url.to_s
|
103
|
-
}
|
104
|
-
end
|
105
|
-
|
106
|
-
def server_key
|
107
|
-
serialize to_h
|
108
|
-
end
|
109
|
-
alias :to_s :server_key
|
110
|
-
end
|
111
|
-
|
112
|
-
class ClientKeyVerifier
|
113
|
-
# Time variance the server will tolerate from the client.
|
114
|
-
DRIFT_SECONDS = 600 # 600 seconds, or 10 minutes.
|
115
|
-
|
116
|
-
# This means the server will tolerate a 10 minute drift in the generated_at time.
|
117
|
-
def self.drift
|
118
|
-
now = Terminalwire::Licensing.time
|
119
|
-
(now - DRIFT_SECONDS)...(now + DRIFT_SECONDS)
|
120
|
-
end
|
121
|
-
|
122
|
-
def initialize(client_key:, private_key:, drift: self.class.drift)
|
123
|
-
@data = Server::ClientKeyGenerator.deserialize client_key
|
124
|
-
@private_key = private_key
|
125
|
-
@drift = drift
|
126
|
-
end
|
127
|
-
|
128
|
-
def server_attestation
|
129
|
-
@server_attestation ||= decrypt @data.fetch("server_attestation")
|
130
|
-
end
|
131
|
-
|
132
|
-
def decrypt(data)
|
133
|
-
MessagePack.unpack @private_key.private_decrypt Base64.urlsafe_decode64 data
|
134
|
-
end
|
135
|
-
|
136
|
-
def generated_at
|
137
|
-
@generated_at ||= Time.parse(server_attestation.fetch("generated_at"))
|
138
|
-
end
|
139
|
-
|
140
|
-
def valid?
|
141
|
-
@drift.include? generated_at
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# Those code runs on customer servers
|
147
|
-
module Server
|
148
|
-
class ClientKeyGenerator
|
149
|
-
VERSION = "1.0".freeze
|
150
|
-
|
151
|
-
include Key::Serialization
|
152
|
-
PREFIX = "client_key_".freeze
|
153
|
-
|
154
|
-
def initialize(server_key:, generated_at: Terminalwire::Licensing.time)
|
155
|
-
@data = Issuer::ServerKeyGenerator.deserialize server_key
|
156
|
-
@license_url = URI(@data.fetch("license_url"))
|
157
|
-
@generated_at = generated_at
|
158
|
-
end
|
159
|
-
|
160
|
-
def to_h
|
161
|
-
{
|
162
|
-
version: VERSION,
|
163
|
-
license_url: @license_url.to_s,
|
164
|
-
server_attestation: attest(
|
165
|
-
version: VERSION,
|
166
|
-
generated_at: @generated_at.iso8601,
|
167
|
-
)
|
168
|
-
}
|
169
|
-
end
|
170
|
-
|
171
|
-
def client_key
|
172
|
-
serialize to_h
|
173
|
-
end
|
174
|
-
alias :to_s :client_key
|
175
|
-
|
176
|
-
protected
|
177
|
-
|
178
|
-
def attest(data)
|
179
|
-
Base64.urlsafe_encode64 public_key.public_encrypt MessagePack.pack data
|
180
|
-
end
|
181
|
-
|
182
|
-
def public_key
|
183
|
-
@public_key ||= OpenSSL::PKey::RSA.new(@data.fetch("public_key"))
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|