-
-
Notifications
You must be signed in to change notification settings - Fork 113
Expand file tree
/
Copy pathlicense_file.rb
More file actions
139 lines (106 loc) · 4.02 KB
/
license_file.rb
File metadata and controls
139 lines (106 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true
module Keygen
module EE
class InvalidLicenseFileError < StandardError; end
class ExpiredLicenseFileError < StandardError; end
class LicenseFile
DEFAULT_PATH = '/etc/keygen/ee.lic'.freeze
HEADER_RE = /\A-----BEGIN LICENSE FILE-----\n/.freeze
FOOTER_RE = /-----END LICENSE FILE-----\n*\z/.freeze
class << self
def current = @current ||= self.new
def reset! = @current = nil if Rails.env.test?
end
def initialize(data = nil)
@data = data&.with_indifferent_access
end
def license = data['data']
def entitlements = data['included']&.filter { _1['type'] == 'entitlements' } || []
def environment = data['included']&.find { _1['type'] == 'environments' }
def product = data['included']&.find { _1['type'] == 'products' }
def policy = data['included']&.find { _1['type'] == 'policies' }
def present? = data.present? rescue false
def issued = Time.parse(data['meta']['issued'])
def expiry = data['meta']['expiry'].present? ? Time.parse(data['meta']['expiry']) : nil
def expires? = expiry.present?
def expiring? = expires? && expiry > Time.current && expiry < 30.days.from_now
def expired? = expires? && expiry < Time.current
def desync? = issued > Time.current
def valid?
unless environment.nil? || (environment in attributes: { code: /#{Rails.env}/i => code })
raise InvalidLicenseFileError, "environment does not match (expected #{Rails.env.inspect} got #{code.inspect.downcase})"
end
raise InvalidLicenseFileError, 'system clock is desynchronized' if
desync?
raise ExpiredLicenseFileError, 'license file is expired' if
expired? && expiry < 30.days.ago
!expired?
end
private
def data = @data ||= load!
def import!(path: DEFAULT_PATH, enc: nil)
return Base64.strict_decode64(enc) if
enc.present?
path = if (p = Pathname.new(path)) && p.relative?
Rails.root.join(p)
else
path
end
File.read(path)
rescue => e
raise InvalidLicenseFileError, "license file is missing or invalid: #{e}"
end
def parse!(cert)
dec = Base64.decode64(
cert.gsub(HEADER_RE, '')
.gsub(FOOTER_RE, ''),
)
JSON.parse(dec)
rescue => e
raise InvalidLicenseFileError, "license file is malformed: #{e}"
end
def verify!(data)
ed = Ed25519::VerifyKey.new(PUBLIC_KEY)
enc = data['enc']
sig = data['sig']
raise InvalidLicenseFileError, 'license file signature is invalid' unless
ed.verify(Base64.strict_decode64(sig), "license/#{enc}")
nil
rescue => e
raise InvalidLicenseFileError, "failed to verify license file: #{e}"
end
def decrypt!(data, key:)
raise InvalidLicenseFileError, 'license key is missing' unless
key.present?
aes = OpenSSL::Cipher::AES256.new(:GCM)
aes.decrypt
enc = data['enc']
ciphertext, iv, tag = enc.split('.')
.map { Base64.strict_decode64(_1) }
aes.key = OpenSSL::Digest::SHA256.digest(key)
aes.iv = iv
aes.auth_tag = tag
aes.auth_data = ''
aes.update(ciphertext) + aes.final
rescue => e
raise InvalidLicenseFileError, "failed to decrypt license file: #{e}"
end
def decode!(s)
JSON.parse(s)
rescue => e
raise InvalidLicenseFileError, "failed to decode license file: #{e}"
end
def load!
path = ENV['KEYGEN_LICENSE_FILE_PATH']
enc = ENV['KEYGEN_LICENSE_FILE']
key = ENV['KEYGEN_LICENSE_KEY']
kwargs = { path:, enc: }.compact
cert = import!(**kwargs)
lic = parse!(cert)
verify!(lic)
s = decrypt!(lic, key:)
decode!(s)
end
end
end
end