pubnub 5.2.1 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.github/workflows/commands-handler.yml +18 -2
  4. data/.github/workflows/run-tests.yml +27 -17
  5. data/.github/workflows/run-validations.yml +12 -2
  6. data/.pubnub.yml +20 -4
  7. data/.tool-versions +1 -1
  8. data/CHANGELOG.md +15 -0
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +6 -6
  11. data/LICENSE +29 -0
  12. data/README.md +1 -1
  13. data/VERSION +1 -1
  14. data/features/step_definitions/access_steps.rb +0 -2
  15. data/features/step_definitions/crypto_steps.rb +99 -0
  16. data/features/support/cryptor.rb +58 -0
  17. data/features/support/hooks.rb +0 -1
  18. data/lib/pubnub/client.rb +30 -1
  19. data/lib/pubnub/error.rb +3 -0
  20. data/lib/pubnub/event.rb +13 -5
  21. data/lib/pubnub/events/add_message_action.rb +2 -2
  22. data/lib/pubnub/events/get_message_actions.rb +1 -1
  23. data/lib/pubnub/events/grant_token.rb +1 -1
  24. data/lib/pubnub/events/history.rb +18 -6
  25. data/lib/pubnub/events/publish.rb +7 -3
  26. data/lib/pubnub/events/remove_channel_members.rb +3 -3
  27. data/lib/pubnub/events/remove_channel_metadata.rb +1 -1
  28. data/lib/pubnub/events/remove_memberships.rb +3 -3
  29. data/lib/pubnub/events/remove_uuid_metadata.rb +1 -1
  30. data/lib/pubnub/events/set_channel_members.rb +3 -3
  31. data/lib/pubnub/events/set_channel_metadata.rb +2 -2
  32. data/lib/pubnub/events/set_memberships.rb +3 -3
  33. data/lib/pubnub/events/set_uuid_metadata.rb +2 -2
  34. data/lib/pubnub/events/signal.rb +1 -1
  35. data/lib/pubnub/events/subscribe.rb +5 -0
  36. data/lib/pubnub/formatter.rb +22 -11
  37. data/lib/pubnub/modules/crypto/crypto_module.rb +159 -0
  38. data/lib/pubnub/modules/crypto/crypto_provider.rb +31 -0
  39. data/lib/pubnub/modules/crypto/cryptor.rb +73 -0
  40. data/lib/pubnub/modules/crypto/cryptor_header.rb +251 -0
  41. data/lib/pubnub/modules/crypto/cryptors/aes_cbc_cryptor.rb +67 -0
  42. data/lib/pubnub/modules/crypto/cryptors/legacy_cryptor.rb +84 -0
  43. data/lib/pubnub/modules/crypto/module.rb +8 -0
  44. data/lib/pubnub/subscribe_event/formatter.rb +8 -8
  45. data/lib/pubnub/version.rb +1 -1
  46. data/pubnub.gemspec +2 -2
  47. metadata +16 -5
  48. data/LICENSE.txt +0 -30
  49. data/lib/pubnub/crypto.rb +0 -70
@@ -8,6 +8,11 @@ module Pubnub
8
8
  def initialize(options, app)
9
9
  @event = :history
10
10
  @telemetry_name = :l_hist
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
  end
13
18
 
@@ -63,23 +68,30 @@ module Pubnub
63
68
 
64
69
  def decrypt_history(message, crypto)
65
70
  if @include_token || @include_meta
66
- message['message'] = JSON.parse(crypto.decrypt(message['message']), quirks_mode: true)
71
+ encrypted_message = Base64.decode64(message['message'])
72
+ message['message'] = JSON.parse(crypto.decrypt(encrypted_message), quirks_mode: true)
67
73
 
68
74
  message
69
75
  else
70
- JSON.parse(crypto.decrypt(message), quirks_mode: true)
76
+ encrypted_message = Base64.decode64(message)
77
+ JSON.parse(crypto.decrypt(encrypted_message), quirks_mode: true)
71
78
  end
72
79
  end
73
80
 
74
81
  def valid_envelope(parsed_response, req_res_objects)
75
82
  messages = parsed_response[0]
76
83
 
77
- if (@cipher_key || @app.env[:cipher_key] || @cipher_key_selector || @app.env[:cipher_key_selector]) && messages
78
- cipher_key = compute_cipher_key(parsed_response)
79
- random_iv = compute_random_iv(parsed_response)
80
- crypto = Crypto.new(cipher_key, random_iv)
84
+ # TODO: Uncomment code below when cryptor implementations will be added.
85
+ if crypto_module && messages
86
+ crypto = crypto_module
81
87
  messages = messages.map { |message| decrypt_history(message, crypto) }
82
88
  end
89
+ # if (@cipher_key || @app.env[:cipher_key] || @cipher_key_selector || @app.env[:cipher_key_selector]) && messages
90
+ # cipher_key = compute_cipher_key(parsed_response)
91
+ # random_iv = compute_random_iv(parsed_response)
92
+ # crypto = Crypto.new(cipher_key, random_iv)
93
+ # messages = messages.map { |message| decrypt_history(message, crypto) }
94
+ # end
83
95
 
84
96
  start = parsed_response[1]
85
97
  finish = parsed_response[2]
@@ -10,6 +10,11 @@ module Pubnub
10
10
  def initialize(options, app)
11
11
  @event = :publish
12
12
  @telemetry_name = :l_pub
13
+
14
+ # Override crypto module if custom cipher key has been used.
15
+ random_iv = options.key?(:random_iv) ? options[:random_iv] : true
16
+ options[:crypto_module] = Crypto::CryptoModule.new_legacy(options[:cipher_key], random_iv) if options[:cipher_key]
17
+
13
18
  super
14
19
  @sequence_number = sequence_number!
15
20
  @origination_time_token = @app.generate_ortt
@@ -25,9 +30,8 @@ module Pubnub
25
30
 
26
31
  def fire
27
32
  Pubnub.logger.debug('Pubnub::Publish') { "Fired event #{self.class}" }
28
-
29
33
  if @compressed
30
- compressed_body = Formatter.format_message(@message, @cipher_key, @random_iv, false)
34
+ compressed_body = Formatter.format_message(@message, @crypto_module, false)
31
35
  response = send_request(compressed_body)
32
36
  else
33
37
  response = send_request
@@ -72,7 +76,7 @@ module Pubnub
72
76
  '0',
73
77
  Formatter.format_channel(@channel, true),
74
78
  '0',
75
- Formatter.format_message(@message, @cipher_key, @random_iv)
79
+ Formatter.format_message(@message, @crypto_module)
76
80
  ]
77
81
 
78
82
  rpath.pop if @compressed
@@ -42,7 +42,7 @@ module Pubnub
42
42
  { uuid: { id: member } }
43
43
  end
44
44
 
45
- body = Formatter.format_message({ delete: members }, "", @random_iv, false)
45
+ body = Formatter.format_message({ delete: members }, nil, false)
46
46
  response = send_request(body)
47
47
 
48
48
  envelopes = fire_callbacks(handle(response, uri))
@@ -83,11 +83,11 @@ module Pubnub
83
83
  def valid_envelope(parsed_response, req_res_objects)
84
84
  members = parsed_response['data'].map { |channel_member|
85
85
  member = Hash.new
86
- channel_member.each{ |k,v| member[k.to_sym] = v }
86
+ channel_member.each { |k, v| member[k.to_sym] = v }
87
87
 
88
88
  unless member[:uuid].nil?
89
89
  uuid_metadata = Hash.new
90
- member[:uuid].each{ |k,v| uuid_metadata[k.to_sym] = v }
90
+ member[:uuid].each { |k, v| uuid_metadata[k.to_sym] = v }
91
91
  uuid_metadata[:updated] = Date._parse(uuid_metadata[:updated]) unless uuid_metadata[:updated].nil?
92
92
  member[:uuid] = uuid_metadata
93
93
  end
@@ -17,7 +17,7 @@ module Pubnub
17
17
  def fire
18
18
  Pubnub.logger.debug('Pubnub::RemoveChannelMetadata') { "Fired event #{self.class}" }
19
19
 
20
- body = Formatter.format_message(@data, "", @random_iv, false)
20
+ body = Formatter.format_message(@data, nil, false)
21
21
  response = send_request(body)
22
22
 
23
23
  envelopes = fire_callbacks(handle(response, uri))
@@ -42,7 +42,7 @@ module Pubnub
42
42
  { channel: { id: membership } }
43
43
  end
44
44
 
45
- body = Formatter.format_message({ delete: memberships }, "", @random_iv, false)
45
+ body = Formatter.format_message({ delete: memberships }, nil, false)
46
46
  response = send_request(body)
47
47
 
48
48
  envelopes = fire_callbacks(handle(response, uri))
@@ -83,11 +83,11 @@ module Pubnub
83
83
  def valid_envelope(parsed_response, req_res_objects)
84
84
  memberships = parsed_response['data'].map { |uuid_membership|
85
85
  membership = Hash.new
86
- uuid_membership.each{ |k,v| membership[k.to_sym] = v }
86
+ uuid_membership.each { |k, v| membership[k.to_sym] = v }
87
87
 
88
88
  unless membership[:channel].nil?
89
89
  channel_metadata = Hash.new
90
- membership[:channel].each{ |k,v| channel_metadata[k.to_sym] = v }
90
+ membership[:channel].each { |k, v| channel_metadata[k.to_sym] = v }
91
91
  channel_metadata[:updated] = Date._parse(channel_metadata[:updated]) unless channel_metadata[:updated].nil?
92
92
  membership[:channel] = channel_metadata
93
93
  end
@@ -17,7 +17,7 @@ module Pubnub
17
17
  def fire
18
18
  Pubnub.logger.debug('Pubnub::RemoveUuidMetadata') { "Fired event #{self.class}" }
19
19
 
20
- body = Formatter.format_message(@data, "", @random_iv, false)
20
+ body = Formatter.format_message(@data, nil, false)
21
21
  response = send_request(body)
22
22
 
23
23
  envelopes = fire_callbacks(handle(response, uri))
@@ -45,7 +45,7 @@ module Pubnub
45
45
  member_object
46
46
  end
47
47
 
48
- body = Formatter.format_message({ set: members }, "", @random_iv, false)
48
+ body = Formatter.format_message({ set: members }, 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
  members = parsed_response['data'].map { |channel_member|
88
88
  member = Hash.new
89
- channel_member.each{ |k,v| member[k.to_sym] = v }
89
+ channel_member.each { |k, v| member[k.to_sym] = v }
90
90
 
91
91
  unless member[:uuid].nil?
92
92
  uuid_metadata = Hash.new
93
- member[:uuid].each{ |k,v| uuid_metadata[k.to_sym] = v }
93
+ member[:uuid].each { |k, v| uuid_metadata[k.to_sym] = v }
94
94
  uuid_metadata[:updated] = Date._parse(uuid_metadata[:updated]) unless uuid_metadata[:updated].nil?
95
95
  member[:uuid] = uuid_metadata
96
96
  end
@@ -27,7 +27,7 @@ module Pubnub
27
27
  def fire
28
28
  Pubnub.logger.debug('Pubnub::SetChannelMetadata') { "Fired event #{self.class}" }
29
29
 
30
- body = Formatter.format_message(@metadata, "", @random_iv, false)
30
+ body = Formatter.format_message(@metadata, nil, false)
31
31
  response = send_request(body)
32
32
 
33
33
  envelopes = fire_callbacks(handle(response, uri))
@@ -60,7 +60,7 @@ module Pubnub
60
60
  def valid_envelope(parsed_response, req_res_objects)
61
61
  data = parsed_response['data']
62
62
  metadata = Hash.new
63
- data.each{ |k,v| metadata[k.to_sym] = v }
63
+ data.each { |k, v| metadata[k.to_sym] = v }
64
64
  metadata[:updated] = Date._parse(metadata[:updated]) unless metadata[:updated].nil?
65
65
 
66
66
  Pubnub::Envelope.new(
@@ -45,7 +45,7 @@ module Pubnub
45
45
  membership_object
46
46
  end
47
47
 
48
- body = Formatter.format_message({ set: memberships }, "", @random_iv, false)
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, "", @random_iv, false)
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(
@@ -39,7 +39,7 @@ module Pubnub
39
39
  '0',
40
40
  Formatter.format_channel(@channel, true),
41
41
  '0',
42
- Formatter.format_message(@message, @cipher_key, @random_iv)
42
+ Formatter.format_message(@message, @crypto_module)
43
43
  ].join('/')
44
44
  end
45
45
 
@@ -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
@@ -41,17 +41,28 @@ module Pubnub
41
41
  end
42
42
  end
43
43
 
44
- # Transforms message to json and encode it
45
- def format_message(message, cipher_key = "", use_random_iv = false, uri_escape = true)
46
- if cipher_key && !cipher_key.empty?
47
- pc = Pubnub::Crypto.new(cipher_key, use_random_iv)
48
- message = pc.encrypt(message).to_json
49
- message = Addressable::URI.escape(message) if uri_escape
50
- else
51
- message = message.to_json
52
- message = Formatter.encode(message) if uri_escape
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
- message
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 => _error
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