Skip to content

Commit

Permalink
Add crypto module (#151)
Browse files Browse the repository at this point in the history
feat(crypto): add crypto module

Add crypto module that allows to configure SDK to encrypt and decrypt messages.

fix(crypto): fix legacy cryptor

Improved security of crypto implementation by adding enhanced AES-CBC cryptor.
---------

Co-authored-by: Michał Dobrzański <[email protected]>
  • Loading branch information
parfeon and MikeDobrzan authored Oct 16, 2023
1 parent c6166a5 commit 312e266
Show file tree
Hide file tree
Showing 42 changed files with 1,021 additions and 175 deletions.
15 changes: 11 additions & 4 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
---
version: "5.2.2"
version: "5.3.0"
changelog:
- date: 2023-10-16
version: v5.3.0
changes:
- type: feature
text: "Add crypto module that allows to configure SDK to encrypt and decrypt messages."
- type: bug
text: "Improved security of crypto implementation by adding enhanced AES-CBC cryptor."
- date: 2023-03-14
version: v5.2.2
changes:
Expand Down Expand Up @@ -663,7 +670,7 @@ sdks:
- x86-64
- distribution-type: package
distribution-repository: RubyGems
package-name: pubnub-5.2.2.gem
package-name: pubnub-5.3.0.gem
location: https://rubygems.org/gems/pubnub
requires:
- name: addressable
Expand Down Expand Up @@ -768,8 +775,8 @@ sdks:
- x86-64
- distribution-type: library
distribution-repository: GitHub release
package-name: pubnub-5.2.2.gem
location: https://github.com/pubnub/ruby/releases/download/v5.2.2/pubnub-5.2.2.gem
package-name: pubnub-5.3.0.gem
location: https://github.com/pubnub/ruby/releases/download/v5.3.0/pubnub-5.3.0.gem
requires:
- name: addressable
min-version: 2.0.0
Expand Down
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby jruby-9.3.8.0
ruby 3.2.2
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## v5.3.0
October 16 2023

#### Added
- Add crypto module that allows to configure SDK to encrypt and decrypt messages.

#### Fixed
- Improved security of crypto implementation by adding enhanced AES-CBC cryptor.

## v5.2.2
March 14 2023

Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ end

group :development, :test do
gem 'awesome_print'
gem 'pry'
gem 'pry', '>= 0.14.2'
gem 'pry-rescue'
gem 'pry-stack_explorer'
end
12 changes: 6 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
pubnub (5.2.2)
pubnub (5.3.0)
addressable (>= 2.0.0)
concurrent-ruby (~> 1.1.5)
concurrent-ruby-edge (~> 0.5.0)
Expand Down Expand Up @@ -100,8 +100,8 @@ GEM
dry-equalizer (~> 0.2)
dry-initializer (~> 3.0)
dry-schema (~> 1.5)
ffi (1.13.1)
ffi (1.13.1-java)
ffi (1.16.2)
ffi (1.16.2-java)
hashdiff (1.0.1)
httpclient (2.8.3)
interception (0.5)
Expand All @@ -115,10 +115,10 @@ GEM
parallel (1.19.2)
parser (2.7.1.4)
ast (~> 2.4.1)
pry (0.13.1)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry (0.13.1-java)
pry (0.14.2-java)
coderay (~> 1.1)
method_source (~> 1.0)
spoon (~> 0.0)
Expand Down Expand Up @@ -187,7 +187,7 @@ DEPENDENCIES
awesome_print
codacy-coverage
cucumber
pry
pry (>= 0.14.2)
pry-rescue
pry-stack_explorer
pubnub!
Expand Down
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
PubNub Software Development Kit License Agreement
Copyright © 2023 PubNub Inc. All rights reserved.

Subject to the terms and conditions of the license, you are hereby granted
a non-exclusive, worldwide, royalty-free license to (a) copy and modify
the software in source code or binary form for use with the software services
and interfaces provided by PubNub, and (b) redistribute unmodified copies
of the software to third parties. The software may not be incorporated in
or used to provide any product or service competitive with the products
and services of PubNub.

The above copyright notice and this license shall be included
in or with all copies or substantial portions of the software.

This license does not grant you permission to use the trade names, trademarks,
service marks, or product names of PubNub, except as required for reasonable
and customary use in describing the origin of the software and reproducing
the content of this license.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL PUBNUB OR THE AUTHORS OR COPYRIGHT HOLDERS OF THE SOFTWARE BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

https://www.pubnub.com/
https://www.pubnub.com/terms
30 changes: 0 additions & 30 deletions LICENSE.txt

This file was deleted.

2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.2.2
5.3.0
2 changes: 0 additions & 2 deletions features/step_definitions/access_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,3 @@
Then('the error detail message is not empty') do
expect(parse_error_body(@global_state[:last_call_res])["error"]["message"].empty?).to eq false
end


99 changes: 99 additions & 0 deletions features/step_definitions/crypto_steps.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true
#
require 'pubnub'

Given(/^Crypto module with '([^']*)' cryptor$/) do |cryptor_id|
@cryptor_ids = [cryptor_id]
end

Given(/^Crypto module with default '([^']*)' and additional '([^']*)' cryptors$/) do |cryptor_id1, cryptor_id2|
@cryptor_ids = [cryptor_id1, cryptor_id2]
end

Given(/^Legacy code with '([^']*)' cipher key and '(random|constant|-)' vector$/) do |cipher_key, use_random_iv|
use_random_iv = use_random_iv != 'constant'
@legacy_cryptor = Cryptor.new cipher_key, use_random_iv
end

Then(/^with '([^']*)' cipher key$/) do |cipher_key|
@cipher_key = cipher_key
end

Then(/^with '(random|constant|-)' vector$/) do |use_random_iv|
@use_random_iv = use_random_iv != 'constant'
end

When(/^I encrypt '([^']*)' file as '([^']*)'$/) do |file_name, _|
@source_file_name = file_name
@source_file_content = File.binread "sdk-specifications/features/encryption/assets/#{file_name}"
@encrypted_content = crypto_module.encrypt @source_file_content
if file_name.include? 'empty'
@encrypt_status = 'encryption error' if @encrypted_content.nil? && @encrypt_status.nil?
@encrypt_status = 'success' if !@encrypted_content.nil? && @encrypt_status.nil?
else
expect(@encrypted_content).not_to eq nil
end
end

When(/^I decrypt '([^']*)' file$/) do |file_name|
file_content = File.binread "sdk-specifications/features/encryption/assets/#{file_name}"

begin
@decrypted_content = crypto_module.decrypt file_content
rescue Pubnub::UnknownCryptorError
@decrypt_status = 'unknown cryptor error'
end
@decrypt_status = 'decryption error' if @decrypted_content.nil? && @decrypt_status.nil?
@decrypt_status = 'success' if !@decrypted_content.nil? && @decrypt_status.nil?
end

When(/^I decrypt '([^']*)' file as '([^']*)'$/) do |file_name, _|
file_content = File.binread "sdk-specifications/features/encryption/assets/#{file_name}"

begin
@decrypted_content = crypto_module.decrypt file_content
rescue Pubnub::UnknownCryptorError
@decrypt_status = 'unknown cryptor error'
end
@decrypt_status = 'decryption error' if @decrypted_content.nil? && @decrypt_status.nil?
@decrypt_status = 'success' if !@decrypted_content.nil? && @decrypt_status.nil?
end

Then(/^Decrypted file content equal to the '([^']*)' file content$/) do |file_name|
file_content = File.binread "sdk-specifications/features/encryption/assets/#{file_name}"
expect(@decrypted_content).not_to eq nil
expect(@decrypted_content).to eq file_content
end

Then('Successfully decrypt an encrypted file with legacy code') do
expect(@legacy_cryptor).not_to eq nil
base64_encoded = Base64.strict_encode64(@encrypted_content)
decrypted_content = @legacy_cryptor.decrypt(base64_encoded)
expect(decrypted_content).not_to eq nil
expect(decrypted_content).to eq @source_file_content
end

Then(/^I receive '([^']*)'$/) do |outcome|
expect(@encrypt_status || @decrypt_status).not_to eq nil
expect(@encrypt_status || @decrypt_status).to eq outcome
end

# Crypto module
#
# @return [Pubnub::Crypto::CryptoModule] Crypto module instance.
def crypto_module
cryptors = []
@cryptor_ids.each do |cryptor_id|
cryptor = if cryptor_id == 'acrh'
Pubnub::Crypto::AesCbcCryptor.new @cipher_key
elsif cryptor_id == 'legacy'
Pubnub::Crypto::LegacyCryptor.new @cipher_key, @use_random_iv
end
cryptors.push(cryptor) unless cryptor.nil?
end

raise ArgumentError, "No crypto identifiers specified: #{@cryptor_ids}" if cryptors.empty?

default_cryptor = cryptors.shift
Pubnub::Crypto::CryptoModule.new default_cryptor, cryptors unless default_cryptor.nil?
end
58 changes: 58 additions & 0 deletions features/support/cryptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Internal Crypto class used for message encryption and decryption
class Cryptor
def initialize(cipher_key, use_random_iv)
@alg = 'AES-256-CBC'
sha256_key = Digest::SHA256.hexdigest(cipher_key.to_s)
@key = sha256_key.slice(0, 32)
@using_random_iv = use_random_iv
@iv = @using_random_iv == true ? random_iv : '0123456789012345'
end

def encrypt(message)
aes = OpenSSL::Cipher.new(@alg)
aes.encrypt
aes.key = @key
aes.iv = @iv

json_message = message.to_json
cipher = @using_random_iv == true ? @iv : ''
cipher << aes.update(json_message)
cipher << aes.final

Base64.strict_encode64(cipher)
end

def decrypt(cipher_text)
undecoded_text = Base64.decode64(cipher_text)
iv = @iv

if cipher_text.length > 16 && @using_random_iv == true
iv = undecoded_text.slice!(0..15)
end

decode_cipher = OpenSSL::Cipher.new(@alg).decrypt
decode_cipher.key = @key
decode_cipher.iv = iv

plain_text = decryption(undecoded_text, decode_cipher)

plain_text
end

private

def decryption(cipher_text, decode_cipher)
plain_text = decode_cipher.update(cipher_text)
plain_text << decode_cipher.final
rescue StandardError => e
puts "Pubnub :: DECRYPTION ERROR: #{e}"
'"DECRYPTION ERROR"'
end

private

def random_iv
random_bytes = Random.new.bytes(16).unpack('NnnnnN')
format('%08x%04x%04x', *random_bytes)
end
end
1 change: 0 additions & 1 deletion features/support/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
@pn_configuration = {}

when_mock_server_used {
puts "Using mock"
expect(ENV['SERVER_HOST']).not_to be_nil
expect(ENV['SERVER_PORT']).not_to be_nil
@pn_configuration = {
Expand Down
Loading

0 comments on commit 312e266

Please sign in to comment.