pubnub 5.2.2 → 5.3.0
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/.github/CODEOWNERS +2 -2
- data/.pubnub.yml +11 -4
- data/.tool-versions +1 -1
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +6 -6
- data/LICENSE +29 -0
- data/VERSION +1 -1
- data/features/step_definitions/access_steps.rb +0 -2
- data/features/step_definitions/crypto_steps.rb +99 -0
- data/features/support/cryptor.rb +58 -0
- data/features/support/hooks.rb +0 -1
- data/lib/pubnub/client.rb +30 -1
- data/lib/pubnub/error.rb +3 -0
- data/lib/pubnub/event.rb +13 -5
- data/lib/pubnub/events/add_message_action.rb +2 -2
- data/lib/pubnub/events/grant_token.rb +1 -1
- data/lib/pubnub/events/history.rb +18 -6
- data/lib/pubnub/events/publish.rb +7 -3
- data/lib/pubnub/events/remove_channel_members.rb +3 -3
- data/lib/pubnub/events/remove_channel_metadata.rb +1 -1
- data/lib/pubnub/events/remove_memberships.rb +3 -3
- data/lib/pubnub/events/remove_uuid_metadata.rb +1 -1
- data/lib/pubnub/events/set_channel_members.rb +3 -3
- data/lib/pubnub/events/set_channel_metadata.rb +2 -2
- data/lib/pubnub/events/set_memberships.rb +3 -3
- data/lib/pubnub/events/set_uuid_metadata.rb +2 -2
- data/lib/pubnub/events/signal.rb +1 -1
- data/lib/pubnub/events/subscribe.rb +5 -0
- data/lib/pubnub/formatter.rb +22 -11
- data/lib/pubnub/modules/crypto/crypto_module.rb +159 -0
- data/lib/pubnub/modules/crypto/crypto_provider.rb +31 -0
- data/lib/pubnub/modules/crypto/cryptor.rb +73 -0
- data/lib/pubnub/modules/crypto/cryptor_header.rb +251 -0
- data/lib/pubnub/modules/crypto/cryptors/aes_cbc_cryptor.rb +67 -0
- data/lib/pubnub/modules/crypto/cryptors/legacy_cryptor.rb +84 -0
- data/lib/pubnub/modules/crypto/module.rb +8 -0
- data/lib/pubnub/subscribe_event/formatter.rb +8 -8
- data/lib/pubnub/version.rb +1 -1
- data/pubnub.gemspec +2 -2
- metadata +16 -5
- data/LICENSE.txt +0 -30
- data/lib/pubnub/crypto.rb +0 -70
@@ -45,7 +45,7 @@ module Pubnub
|
|
45
45
|
membership_object
|
46
46
|
end
|
47
47
|
|
48
|
-
body = Formatter.format_message({ set: memberships },
|
48
|
+
body = Formatter.format_message({ set: memberships }, nil, false)
|
49
49
|
response = send_request(body)
|
50
50
|
|
51
51
|
envelopes = fire_callbacks(handle(response, uri))
|
@@ -86,11 +86,11 @@ module Pubnub
|
|
86
86
|
def valid_envelope(parsed_response, req_res_objects)
|
87
87
|
memberships = parsed_response['data'].map { |uuid_membership|
|
88
88
|
membership = Hash.new
|
89
|
-
uuid_membership.each{ |k,v| membership[k.to_sym] = v }
|
89
|
+
uuid_membership.each { |k, v| membership[k.to_sym] = v }
|
90
90
|
|
91
91
|
unless membership[:channel].nil?
|
92
92
|
channel_metadata = Hash.new
|
93
|
-
membership[:channel].each{ |k,v| channel_metadata[k.to_sym] = v }
|
93
|
+
membership[:channel].each { |k, v| channel_metadata[k.to_sym] = v }
|
94
94
|
channel_metadata[:updated] = Date._parse(channel_metadata[:updated]) unless channel_metadata[:updated].nil?
|
95
95
|
membership[:channel] = channel_metadata
|
96
96
|
end
|
@@ -28,7 +28,7 @@ module Pubnub
|
|
28
28
|
def fire
|
29
29
|
Pubnub.logger.debug('Pubnub::SetUuidMetadata') { "Fired event #{self.class}" }
|
30
30
|
|
31
|
-
body = Formatter.format_message(@metadata,
|
31
|
+
body = Formatter.format_message(@metadata, nil, false)
|
32
32
|
response = send_request(body)
|
33
33
|
|
34
34
|
envelopes = fire_callbacks(handle(response, uri))
|
@@ -61,7 +61,7 @@ module Pubnub
|
|
61
61
|
def valid_envelope(parsed_response, req_res_objects)
|
62
62
|
data = parsed_response['data']
|
63
63
|
metadata = Hash.new
|
64
|
-
data.each{ |k,v| metadata[k.to_sym] = v }
|
64
|
+
data.each { |k, v| metadata[k.to_sym] = v }
|
65
65
|
metadata[:updated] = Date._parse(metadata[:updated]) unless metadata[:updated].nil?
|
66
66
|
|
67
67
|
Pubnub::Envelope.new(
|
data/lib/pubnub/events/signal.rb
CHANGED
@@ -8,6 +8,11 @@ module Pubnub
|
|
8
8
|
|
9
9
|
def initialize(options, app)
|
10
10
|
@event = :subscribe
|
11
|
+
|
12
|
+
# Override crypto module if custom cipher key has been used.
|
13
|
+
random_iv = options.key?(:random_iv) ? options[:random_iv] : true
|
14
|
+
options[:crypto_module] = Crypto::CryptoModule.new_legacy(options[:cipher_key], random_iv) if options[:cipher_key]
|
15
|
+
|
11
16
|
super
|
12
17
|
app.apply_state(self)
|
13
18
|
end
|
data/lib/pubnub/formatter.rb
CHANGED
@@ -41,17 +41,28 @@ module Pubnub
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
44
|
+
# TODO: Uncomment code below when cryptor implementations will be added.
|
45
|
+
# Transforms message to json and encode it.
|
46
|
+
#
|
47
|
+
# @param message [Hash, String, Integer, Boolean] Message data which
|
48
|
+
# should be formatted.
|
49
|
+
# @param crypto [Crypto::CryptoProvider, nil] Crypto which should be used to
|
50
|
+
# encrypt message data.
|
51
|
+
# @param uri_escape [Boolean, nil] Whether formatted message should escape
|
52
|
+
# to be used as part of URI or not.
|
53
|
+
# @return [String, nil] Formatted message data.
|
54
|
+
def format_message(message, crypto = nil, uri_escape = true)
|
55
|
+
json_message = message.to_json
|
56
|
+
if crypto
|
57
|
+
encrypted_data = crypto&.encrypt(json_message)
|
58
|
+
json_message = Base64.strict_encode64(encrypted_data).to_json unless encrypted_data.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
if uri_escape
|
62
|
+
json_message = Formatter.encode(json_message) if crypto.nil?
|
63
|
+
json_message = Addressable::URI.escape(json_message).to_s unless crypto.nil?
|
53
64
|
end
|
54
|
-
|
65
|
+
json_message
|
55
66
|
end
|
56
67
|
|
57
68
|
# Quite lazy way, but good enough for current usage
|
@@ -100,7 +111,7 @@ module Pubnub
|
|
100
111
|
# Parses string to JSON
|
101
112
|
def parse_json(string)
|
102
113
|
[JSON.parse(string), nil]
|
103
|
-
rescue JSON::ParserError =>
|
114
|
+
rescue JSON::ParserError => _e
|
104
115
|
[nil, JSON::ParserError]
|
105
116
|
end
|
106
117
|
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Pubnub
|
2
|
+
module Crypto
|
3
|
+
# Crypto module for data processing.
|
4
|
+
#
|
5
|
+
# The PubNub client uses a module to encrypt and decrypt sent data in a way
|
6
|
+
# that's compatible with previous versions (if additional cryptors have been
|
7
|
+
# registered).
|
8
|
+
class CryptoModule < CryptoProvider
|
9
|
+
# AES-CBC cryptor based module.
|
10
|
+
#
|
11
|
+
# Data <i>encryption</i> and <i>decryption</i> will be done by default
|
12
|
+
# using the <i>AesCbcCryptor</i>. In addition to the <i>AesCbcCryptor</i>
|
13
|
+
# for data <i>decryption</i>, the <i>LegacyCryptor</i> will be registered
|
14
|
+
# for backward-compatibility.
|
15
|
+
#
|
16
|
+
# @param cipher_key [String] Key for data encryption and decryption.
|
17
|
+
# @param use_random_iv [Boolean] Whether random IV should be used for data
|
18
|
+
# decryption.
|
19
|
+
#
|
20
|
+
# @raise [ArgumentError] If the <i>cipher_key</i> is missing or empty.
|
21
|
+
def self.new_aes_cbc(cipher_key, use_random_iv)
|
22
|
+
if cipher_key.nil? || cipher_key.empty?
|
23
|
+
raise ArgumentError, {
|
24
|
+
message: '\'cipher_key\' is missing or empty.'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
CryptoModule.new AesCbcCryptor.new(cipher_key), [LegacyCryptor.new(cipher_key, use_random_iv)]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Legacy AES-CBC cryptor based module.
|
32
|
+
#
|
33
|
+
# Data <i>encryption</i> and <i>decryption</i> will be done by default
|
34
|
+
# using the <i>LegacyCrypto</i>. In addition to the <i>LegacyCrypto</i>
|
35
|
+
# for data <i>decryption</i>, the <i>AesCbcCryptor</i> will be registered
|
36
|
+
# for future-compatibility (which will help with gradual application
|
37
|
+
# updates).
|
38
|
+
#
|
39
|
+
# @param cipher_key [String] Key for data encryption and decryption.
|
40
|
+
# @param use_random_iv [Boolean] Whether random IV should be used for data
|
41
|
+
# decryption.
|
42
|
+
#
|
43
|
+
# @raise [ArgumentError] If the <i>cipher_key</i> is missing or empty.
|
44
|
+
def self.new_legacy(cipher_key, use_random_iv)
|
45
|
+
if cipher_key.nil? || cipher_key.empty?
|
46
|
+
raise ArgumentError, {
|
47
|
+
message: '\'cipher_key\' is missing or empty.'
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
CryptoModule.new LegacyCryptor.new(cipher_key, use_random_iv), [AesCbcCryptor.new(cipher_key)]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Create crypto module.
|
55
|
+
#
|
56
|
+
# @param default [Cryptor] Default cryptor used to encrypt and decrypt
|
57
|
+
# data.
|
58
|
+
# @param cryptors [Array<Cryptor>, nil] Additional cryptors which will be
|
59
|
+
# used to decrypt data encrypted by previously used cryptors.
|
60
|
+
def initialize(default, cryptors)
|
61
|
+
if default.nil?
|
62
|
+
raise ArgumentError, {
|
63
|
+
message: '\'default\' cryptor required for data encryption.'
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
@default = default
|
68
|
+
@cryptors = cryptors&.each_with_object({}) do |value, hash|
|
69
|
+
hash[value.identifier] = value
|
70
|
+
end || {}
|
71
|
+
super()
|
72
|
+
end
|
73
|
+
|
74
|
+
def encrypt(data)
|
75
|
+
# Encrypting provided data.
|
76
|
+
encrypted_data = default_cryptor.encrypt(data)
|
77
|
+
return nil if encrypted_data.nil?
|
78
|
+
|
79
|
+
payload = Crypto::CryptorHeader.new(default_cryptor.identifier, encrypted_data.metadata).to_s
|
80
|
+
payload << encrypted_data.metadata unless encrypted_data.metadata.nil?
|
81
|
+
payload << encrypted_data.data
|
82
|
+
end
|
83
|
+
|
84
|
+
def decrypt(data)
|
85
|
+
if data.nil? || data.empty?
|
86
|
+
puts 'Pubnub :: DECRYPTION ERROR: Empty data for decryption'
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
|
90
|
+
header = Crypto::CryptorHeader.parse(data)
|
91
|
+
return nil if header.nil?
|
92
|
+
|
93
|
+
cryptor_identifier = header.identifier || '\x00\x00\x00\x00'
|
94
|
+
cryptor = cryptor cryptor_identifier
|
95
|
+
|
96
|
+
# Check whether there is a cryptor to decrypt data or not.
|
97
|
+
if cryptor.nil?
|
98
|
+
identifier = header.identifier || 'UNKN'
|
99
|
+
raise UnknownCryptorError, {
|
100
|
+
message: "Decrypting data created by unknown cryptor. Please make sure to register
|
101
|
+
#{identifier} or update SDK."
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
encrypted_data = data[header.length..-1]
|
106
|
+
metadata = metadata encrypted_data, header.data_size
|
107
|
+
|
108
|
+
# Check whether there is still some data for processing or not.
|
109
|
+
return nil if encrypted_data.nil? || encrypted_data.empty?
|
110
|
+
|
111
|
+
cryptor.decrypt(EncryptedData.new(encrypted_data, metadata))
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Cryptor used by the module by default to encrypt data.
|
117
|
+
#
|
118
|
+
# @return [Cryptor] Default cryptor used to encrypt and decrypt data.
|
119
|
+
def default_cryptor
|
120
|
+
@default
|
121
|
+
end
|
122
|
+
|
123
|
+
# Additional cryptors that can be used to decrypt data if the
|
124
|
+
# <i>default_cryptor</i> can't.
|
125
|
+
#
|
126
|
+
# @return [Hash<String, Cryptor>] Map of Cryptor to their identifiers.
|
127
|
+
def additional_cryptors
|
128
|
+
@cryptors
|
129
|
+
end
|
130
|
+
|
131
|
+
# Extract metadata information from source data.
|
132
|
+
#
|
133
|
+
# @param data [String, nil] Encrypted data from which cryptor metadata
|
134
|
+
# should be extracted.
|
135
|
+
# @param size [Integer] Size of cryptor-defined data.
|
136
|
+
# @return [String, nil] Extracted metadata or <i>nil</i> in case if
|
137
|
+
# <i>size</i> is <b>0</b>.
|
138
|
+
def metadata(data, size)
|
139
|
+
return nil if !data || !size.positive?
|
140
|
+
|
141
|
+
data&.slice!(0..(size - 1))
|
142
|
+
end
|
143
|
+
|
144
|
+
# Find cryptor with a specified identifier.
|
145
|
+
#
|
146
|
+
# Data decryption can only be done with registered cryptors. An identifier
|
147
|
+
# in the cryptor data header is used to identify a suitable cryptor.
|
148
|
+
#
|
149
|
+
# @param identifier [String] A unicode cryptor identifier.
|
150
|
+
# @return [Cryptor, nil] Target cryptor or `nil` in case there is none
|
151
|
+
# with the specified identifier.
|
152
|
+
def cryptor(identifier)
|
153
|
+
return default_cryptor if default_cryptor.identifier == identifier
|
154
|
+
|
155
|
+
additional_cryptors.fetch(identifier, nil)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pubnub
|
4
|
+
module Crypto
|
5
|
+
# Base class which is used to implement a module that can be used to
|
6
|
+
# configure <b>PubNub</b> client or for manual data encryption and
|
7
|
+
# decryption.
|
8
|
+
class CryptoProvider
|
9
|
+
# Encrypt provided data.
|
10
|
+
#
|
11
|
+
# @param data [String] Source data for encryption.
|
12
|
+
# @return [String, nil] Encrypted data or <i>nil</i> in case of encryption
|
13
|
+
# error.
|
14
|
+
def encrypt(data)
|
15
|
+
raise NotImplementedError, 'Subclass should provide "encrypt" method implementation.'
|
16
|
+
end
|
17
|
+
|
18
|
+
# Decrypt provided data.
|
19
|
+
#
|
20
|
+
# @param data [String] Encrypted data for decryption.
|
21
|
+
# @return [String, nil] Decrypted data or <i>nil</i> in case of decryption
|
22
|
+
# error.
|
23
|
+
#
|
24
|
+
# @raise [UnknownCryptorError] If the <i>cryptor</i> for data processing is
|
25
|
+
# not registered.
|
26
|
+
def decrypt(data)
|
27
|
+
raise NotImplementedError, 'Subclass should provide "decrypt" method implementation.'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pubnub
|
4
|
+
module Crypto
|
5
|
+
# Encrypted data representation object.
|
6
|
+
#
|
7
|
+
# Objects contain both encrypted data and additional data created by cryptor
|
8
|
+
# that will be required to decrypt the data.
|
9
|
+
class EncryptedData
|
10
|
+
# Cryptor may provide here any information which will be useful when data
|
11
|
+
# should be decrypted.
|
12
|
+
#
|
13
|
+
# For example <i>metadata</i> may contain:
|
14
|
+
# * initialization vector
|
15
|
+
# * cipher key identifier
|
16
|
+
# * encrypted <i>data</i> length
|
17
|
+
#
|
18
|
+
# @return [String, nil] Cryptor-defined information.
|
19
|
+
attr_reader :metadata
|
20
|
+
|
21
|
+
# Encrypted data.
|
22
|
+
#
|
23
|
+
# @return [String] Encrypted data.
|
24
|
+
attr_reader :data
|
25
|
+
|
26
|
+
# Create encrypted data object.
|
27
|
+
#
|
28
|
+
# An object used to keep track of the results of data encryption and the
|
29
|
+
# additional data the <i>cryptor</i> needs to handle it later.
|
30
|
+
#
|
31
|
+
# @param data [String] Outcome of successful cryptor <i>encrypt</i> method
|
32
|
+
# call.
|
33
|
+
# @param metadata [String, nil] Additional information is provided by
|
34
|
+
# <i>cryptor</i> so that encrypted data can be handled later.
|
35
|
+
def initialize(data, metadata = nil)
|
36
|
+
@data = data
|
37
|
+
@metadata = metadata
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Base class which is used to implement cryptor that should be used with
|
42
|
+
# <i>CryptorProvider</i> implementation for data encryption and decryption.
|
43
|
+
class Cryptor
|
44
|
+
# Identifier will be encoded into cryptor data header and passed along
|
45
|
+
# with encrypted <i>data</i> and <i>metadata</i>.
|
46
|
+
#
|
47
|
+
# The identifier <b>must</b> be 4 bytes long.
|
48
|
+
#
|
49
|
+
# @return [String] Unique cryptor identifier.
|
50
|
+
def identifier
|
51
|
+
raise NotImplementedError, 'Subclass should provide "identifier" method implementation.'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Encrypt provided data.
|
55
|
+
#
|
56
|
+
# @param data [String] Source data for encryption.
|
57
|
+
# @return [EncryptedData, nil] Encrypted data or <i>nil</i> in case of
|
58
|
+
# encryption error.
|
59
|
+
def encrypt(data)
|
60
|
+
raise NotImplementedError, 'Subclass should provide "encrypt" method implementation.'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Decrypt provided data.
|
64
|
+
#
|
65
|
+
# @param data [EncryptedData] Encrypted data for decryption.
|
66
|
+
# @return [String, nil] Decrypted data or <i>nil</i> in case of decryption
|
67
|
+
# error.
|
68
|
+
def decrypt(data)
|
69
|
+
raise NotImplementedError, 'Subclass should provide "decrypt" method implementation.'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
module Pubnub
|
2
|
+
module Crypto
|
3
|
+
# Cryptor data header.
|
4
|
+
#
|
5
|
+
# This instance used to parse header from received data and encode into
|
6
|
+
# binary for sending.
|
7
|
+
class CryptorHeader
|
8
|
+
module Versions
|
9
|
+
# Currently used cryptor data header schema version.
|
10
|
+
CURRENT_VERSION = 1
|
11
|
+
|
12
|
+
# Base class for cryptor data schema.
|
13
|
+
class CryptorHeaderData
|
14
|
+
# Cryptor header version.
|
15
|
+
#
|
16
|
+
# @return [Integer] Cryptor header version.
|
17
|
+
def version
|
18
|
+
raise NotImplementedError, 'Subclass should provide "version" method implementation.'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Cryptor identifier.
|
22
|
+
#
|
23
|
+
# @return [String] Identifier of the cryptor which has been used to
|
24
|
+
# encrypt data.
|
25
|
+
def identifier
|
26
|
+
raise NotImplementedError, 'Subclass should provide "identifier" method implementation.'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Cryptor-defined data size.
|
30
|
+
#
|
31
|
+
# @return [Integer] Cryptor-defined data size.
|
32
|
+
def data_size
|
33
|
+
raise NotImplementedError, 'Subclass should provide "data_size" method implementation.'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# v1 cryptor header schema.
|
38
|
+
#
|
39
|
+
# This header consists of:
|
40
|
+
# * sentinel (4 bytes)
|
41
|
+
# * version (1 byte)
|
42
|
+
# * cryptor identifier (4 bytes)
|
43
|
+
# * cryptor data size (1 byte if less than 255 and 3 bytes in other cases)
|
44
|
+
# * cryptor-defined data
|
45
|
+
class CryptorHeaderV1Data < CryptorHeaderData
|
46
|
+
# Identifier of the cryptor which has been used to encrypt data.
|
47
|
+
#
|
48
|
+
# @return [String] Identifier of the cryptor which has been used to
|
49
|
+
# encrypt data.
|
50
|
+
attr_reader :identifier
|
51
|
+
|
52
|
+
# Cryptor-defined data size.
|
53
|
+
#
|
54
|
+
# @return [Integer] Cryptor-defined data size.
|
55
|
+
attr_reader :data_size
|
56
|
+
|
57
|
+
# Create cryptor header data.
|
58
|
+
#
|
59
|
+
# @param identifier [String] Identifier of the cryptor which has been
|
60
|
+
# used to encrypt data.
|
61
|
+
# @param data_size [Integer] Cryptor-defined data size.
|
62
|
+
def initialize(identifier, data_size)
|
63
|
+
@identifier = identifier
|
64
|
+
@data_size = data_size
|
65
|
+
end
|
66
|
+
|
67
|
+
def version
|
68
|
+
1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Create cryptor header.
|
74
|
+
#
|
75
|
+
# @param identifier [String] Identifier of the cryptor which has been used
|
76
|
+
# to encrypt data.
|
77
|
+
# @param metadata [String, nil] Cryptor-defined information.
|
78
|
+
def initialize(identifier = nil, metadata = nil)
|
79
|
+
@data = if identifier && identifier != '\x00\x00\x00\x00'
|
80
|
+
Versions::CryptorHeaderV1Data.new(
|
81
|
+
identifier.to_s,
|
82
|
+
metadata&.length || 0
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Parse cryptor header data to create instance.
|
88
|
+
#
|
89
|
+
# @param data [String] Data which <i>may</i> contain cryptor header
|
90
|
+
# information.
|
91
|
+
# @return [CryptorHeader, nil] Header instance or <i>nil</i> in case of
|
92
|
+
# encrypted data parse error.
|
93
|
+
#
|
94
|
+
# @raise [ArgumentError] Raise an exception if <i>data</i> is <i>nil</i>
|
95
|
+
# or empty.
|
96
|
+
# @raise [UnknownCryptorError] Raise an exception if, during cryptor
|
97
|
+
# header data parsing, an unknown cryptor header version is encountered.
|
98
|
+
def self.parse(data)
|
99
|
+
if data.nil? || data.empty?
|
100
|
+
raise ArgumentError, {
|
101
|
+
message: '\'data\' is required and should not be empty.'
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
# Data is too short to be encrypted. Assume legacy cryptor without
|
106
|
+
# header.
|
107
|
+
return CryptorHeader.new if data.length < 4 || data.unpack('A4').last != 'PNED'
|
108
|
+
|
109
|
+
# Malformed crypto header.
|
110
|
+
return nil if data.length < 10
|
111
|
+
|
112
|
+
# Unpack header bytes.
|
113
|
+
_, version, identifier, data_size = data.unpack('A4 C A4 C')
|
114
|
+
|
115
|
+
# Check whether version is within known range.
|
116
|
+
if version > current_version
|
117
|
+
raise UnknownCryptorError, {
|
118
|
+
message: 'Decrypting data created by unknown cryptor.'
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
if data_size == 255
|
123
|
+
data_size = data.unpack('A4 C A4 C n').last if data.length >= 12
|
124
|
+
return CryptorHeader.new if data.length < 12
|
125
|
+
end
|
126
|
+
|
127
|
+
header = CryptorHeader.new
|
128
|
+
header.send(
|
129
|
+
:update_header_data,
|
130
|
+
create_header_data(version.to_i, identifier.to_s, data_size.to_i)
|
131
|
+
)
|
132
|
+
header
|
133
|
+
end
|
134
|
+
|
135
|
+
# Overall header size.
|
136
|
+
#
|
137
|
+
# Full header size which includes:
|
138
|
+
# * sentinel
|
139
|
+
# * version
|
140
|
+
# * cryptor identifier
|
141
|
+
# * cryptor data size
|
142
|
+
# * cryptor-defined fields size.
|
143
|
+
def length
|
144
|
+
# Legacy payload doesn't have header.
|
145
|
+
return 0 if @data.nil?
|
146
|
+
|
147
|
+
9 + (data_size < 255 ? 1 : 3)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Crypto header version Version module.
|
151
|
+
#
|
152
|
+
# @return [Integer] One of known versions from Version module.
|
153
|
+
def version
|
154
|
+
header_data&.version || 0
|
155
|
+
end
|
156
|
+
|
157
|
+
# Identifier of the cryptor which has been used to encrypt data.
|
158
|
+
#
|
159
|
+
# @return [String, nil] Identifier of the cryptor which has been used to
|
160
|
+
# encrypt data.
|
161
|
+
def identifier
|
162
|
+
header_data&.identifier || nil
|
163
|
+
end
|
164
|
+
|
165
|
+
# Cryptor-defined information size.
|
166
|
+
#
|
167
|
+
# @return [Integer] Cryptor-defined information size.
|
168
|
+
def data_size
|
169
|
+
header_data&.data_size || 0
|
170
|
+
end
|
171
|
+
|
172
|
+
# Create cryptor header data object.
|
173
|
+
#
|
174
|
+
# @param version [Integer] Cryptor header data schema version.
|
175
|
+
# @param identifier [String] Encrypting cryptor identifier.
|
176
|
+
# @param size [Integer] Cryptor-defined data size
|
177
|
+
# @return [Versions::CryptorHeaderData] Cryptor header data.
|
178
|
+
def self.create_header_data(version, identifier, size)
|
179
|
+
Versions::CryptorHeaderV1Data.new(identifier, size) if version == 1
|
180
|
+
end
|
181
|
+
|
182
|
+
# Crypto header which is currently used to encrypt data.
|
183
|
+
#
|
184
|
+
# @return [Integer] Current cryptor header version.
|
185
|
+
def self.current_version
|
186
|
+
Versions::CURRENT_VERSION
|
187
|
+
end
|
188
|
+
|
189
|
+
# Serialize cryptor header.
|
190
|
+
#
|
191
|
+
# @return [String] Cryptor header data, which is serialized as a binary
|
192
|
+
# string.
|
193
|
+
#
|
194
|
+
# @raise [ArgumentError] Raise an exception if a <i>cryptor</i> identifier
|
195
|
+
# is not provided for a non-legacy <i>cryptor</i>.
|
196
|
+
def to_s
|
197
|
+
# We don't need to serialize header for legacy cryptor.
|
198
|
+
return '' if version.zero?
|
199
|
+
|
200
|
+
cryptor_identifier = identifier
|
201
|
+
if cryptor_identifier.nil? || cryptor_identifier.empty?
|
202
|
+
raise ArgumentError, {
|
203
|
+
message: '\'identifier\' is missing or empty.'
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
header_bytes = ['PNED', version, cryptor_identifier]
|
208
|
+
if data_size < 255
|
209
|
+
header_bytes.push(data_size)
|
210
|
+
else
|
211
|
+
header_bytes.push(255, data_size)
|
212
|
+
end
|
213
|
+
|
214
|
+
header_bytes.pack(data_size < 255 ? 'A4 C A4 C' : 'A4 C A4 C n')
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
# Versioned cryptor header data
|
220
|
+
#
|
221
|
+
# @return [Versions::CryptorHeaderData, nil] Cryptor header data.
|
222
|
+
def header_data
|
223
|
+
@data
|
224
|
+
end
|
225
|
+
|
226
|
+
# Update crypto header version.
|
227
|
+
#
|
228
|
+
# @param data [Versions::CryptorHeaderData] Header version number parsed from binary data.
|
229
|
+
def update_header_data(data)
|
230
|
+
@data = data
|
231
|
+
end
|
232
|
+
|
233
|
+
# Update crypto header version.
|
234
|
+
#
|
235
|
+
# @param value [Integer] Header version number parsed from binary data.
|
236
|
+
def update_version(value)
|
237
|
+
@version = value
|
238
|
+
end
|
239
|
+
|
240
|
+
# Update cryptor-defined data size.
|
241
|
+
#
|
242
|
+
# @param value [Integer] Cryptor-defined data size parsed from binary
|
243
|
+
# data.
|
244
|
+
def update_data_size(value)
|
245
|
+
@data_size = value
|
246
|
+
end
|
247
|
+
|
248
|
+
private_class_method :create_header_data, :current_version
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|