pubnub 5.2.1 → 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.
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