VYPR
Moderate severityOSV Advisory· Published Dec 6, 2023· Updated Aug 2, 2024

CVE-2023-26154

CVE-2023-26154

Description

Versions of the package pubnub before 7.4.0; all versions of the package com.pubnub:pubnub; versions of the package pubnub before 6.19.0; all versions of the package github.com/pubnub/go; versions of the package github.com/pubnub/go/v7 before 7.2.0; versions of the package pubnub before 7.3.0; versions of the package pubnub/pubnub before 6.1.0; versions of the package pubnub before 5.3.0; versions of the package pubnub before 0.4.0; versions of the package pubnub/c-core before 4.5.0; versions of the package com.pubnub:pubnub-kotlin before 7.7.0; versions of the package pubnub/swift before 6.2.0; versions of the package pubnub before 5.2.0; versions of the package pubnub before 4.3.0 are vulnerable to Insufficient Entropy via the getKey function, due to inefficient implementation of the AES-256-CBC cryptographic algorithm. The provided encrypt function is less secure when hex encoding and trimming are applied, leaving half of the bits in the key always the same for every encoded message or file. Note: In order to exploit this vulnerability, the attacker needs to invest resources in preparing the attack and brute-force the encryption.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
pubnubnpm
< 7.4.07.4.0
com.pubnub:pubnub-kotlinMaven
< 7.7.07.7.0
com.pubnub:pubnubMaven
<= 4.6.5
github.com/pubnub/go/v7Go
< 7.2.07.2.0
github.com/pubnub/goGo
< 0.0.0-20231016150651-428517fef5b90.0.0-20231016150651-428517fef5b9
github.com/pubnub/go/v6Go
< 6.1.1-0.20231016150651-428517fef5b96.1.1-0.20231016150651-428517fef5b9
github.com/pubnub/go/v5Go
< 5.0.4-0.20231016150651-428517fef5b95.0.4-0.20231016150651-428517fef5b9
PubnubNuGet
< 6.19.06.19.0
github.com/pubnub/swiftSwiftURL
< 6.2.06.2.0
pubnubRubyGems
< 5.3.05.3.0
pubnubcrates.io
< 0.4.00.4.0
pubnub/pubnubPackagist
< 6.1.06.1.0
pubnubPub
< 4.3.04.3.0
pubnubPyPI
< 7.3.07.3.0

Affected products

1

Patches

2
428517fef5b9

New crypto implementation (#155)

https://github.com/pubnub/goLukasz KlichOct 16, 2023via ghsa
49 files changed · +2141 2673
  • CHANGELOG.md+9 0 modified
    @@ -1,3 +1,12 @@
    +## v7.2.0
    +October 16 2023
    +
    +#### Added
    +- Update the crypto module structure and add enhanced AES-CBC cryptor.
    +
    +#### Fixed
    +- Improved security of crypto implementation by increasing the cipher key entropy by a factor of two.
    +
     ## v7.1.2
     May 11 2023
     
    
  • config.go+29 25 modified
    @@ -2,6 +2,7 @@ package pubnub
     
     import (
     	"fmt"
    +	"github.com/pubnub/go/v7/crypto"
     	"log"
     	"sync"
     )
    @@ -26,30 +27,33 @@ type Config struct {
     	// UUID to be used as a device identifier.
     	//
     	//Deprecated: please use SetUserId/GetUserId
    -	UUID                          string
    -	CipherKey                     string             // If CipherKey is passed, all communications to/from PubNub will be encrypted.
    -	Secure                        bool               // True to use TLS
    -	ConnectTimeout                int                // net.Dialer.Timeout
    -	NonSubscribeRequestTimeout    int                // http.Client.Timeout for non-subscribe requests
    -	SubscribeRequestTimeout       int                // http.Client.Timeout for subscribe requests only
    -	FileUploadRequestTimeout      int                // http.Client.Timeout File Upload Request only
    -	HeartbeatInterval             int                // The frequency of the pings to the server to state that the client is active
    -	PresenceTimeout               int                // The time after which the server will send a timeout for the client
    -	MaximumReconnectionRetries    int                // The config sets how many times to retry to reconnect before giving up.
    -	MaximumLatencyDataAge         int                // Max time to store the latency data for telemetry
    -	FilterExpression              string             // Feature to subscribe with a custom filter expression.
    -	PNReconnectionPolicy          ReconnectionPolicy // Reconnection policy selection
    -	Log                           *log.Logger        // Logger instance
    -	SuppressLeaveEvents           bool               // When true the SDK doesn't send out the leave requests.
    -	DisablePNOtherProcessing      bool               // PNOther processing looks for pn_other in the JSON on the recevied message
    -	UseHTTP2                      bool               // HTTP2 Flag
    -	MessageQueueOverflowCount     int                // When the limit is exceeded by the number of messages received in a single subscribe request, a status event PNRequestMessageCountExceededCategory is fired.
    -	MaxIdleConnsPerHost           int                // Used to set the value of HTTP Transport's MaxIdleConnsPerHost.
    -	MaxWorkers                    int                // Number of max workers for Publish and Grant requests
    -	UsePAMV3                      bool               // Use PAM version 2, Objects requets would still use PAM v3
    -	StoreTokensOnGrant            bool               // Will store grant v3 tokens in token manager for further use.
    -	FileMessagePublishRetryLimit  int                // The number of tries made in case of Publish File Message failure.
    -	UseRandomInitializationVector bool               // When true the IV will be random for all requests and not just file upload. When false the IV will be hardcoded for all requests except File Upload
    +	UUID string
    +	//DEPRECATED: please use CryptoModule
    +	CipherKey                    string             // If CipherKey is passed, all communications to/from PubNub will be encrypted.
    +	Secure                       bool               // True to use TLS
    +	ConnectTimeout               int                // net.Dialer.Timeout
    +	NonSubscribeRequestTimeout   int                // http.Client.Timeout for non-subscribe requests
    +	SubscribeRequestTimeout      int                // http.Client.Timeout for subscribe requests only
    +	FileUploadRequestTimeout     int                // http.Client.Timeout File Upload Request only
    +	HeartbeatInterval            int                // The frequency of the pings to the server to state that the client is active
    +	PresenceTimeout              int                // The time after which the server will send a timeout for the client
    +	MaximumReconnectionRetries   int                // The config sets how many times to retry to reconnect before giving up.
    +	MaximumLatencyDataAge        int                // Max time to store the latency data for telemetry
    +	FilterExpression             string             // Feature to subscribe with a custom filter expression.
    +	PNReconnectionPolicy         ReconnectionPolicy // Reconnection policy selection
    +	Log                          *log.Logger        // Logger instance
    +	SuppressLeaveEvents          bool               // When true the SDK doesn't send out the leave requests.
    +	DisablePNOtherProcessing     bool               // PNOther processing looks for pn_other in the JSON on the recevied message
    +	UseHTTP2                     bool               // HTTP2 Flag
    +	MessageQueueOverflowCount    int                // When the limit is exceeded by the number of messages received in a single subscribe request, a status event PNRequestMessageCountExceededCategory is fired.
    +	MaxIdleConnsPerHost          int                // Used to set the value of HTTP Transport's MaxIdleConnsPerHost.
    +	MaxWorkers                   int                // Number of max workers for Publish and Grant requests
    +	UsePAMV3                     bool               // Use PAM version 2, Objects requets would still use PAM v3
    +	StoreTokensOnGrant           bool               // Will store grant v3 tokens in token manager for further use.
    +	FileMessagePublishRetryLimit int                // The number of tries made in case of Publish File Message failure.
    +	//DEPRECATED: please use CryptoModule
    +	UseRandomInitializationVector bool                // When true the IV will be random for all requests and not just file upload. When false the IV will be hardcoded for all requests except File Upload
    +	CryptoModule                  crypto.CryptoModule // A cryptography module used for encryption and decryption
     }
     
     // NewDemoConfig initiates the config with demo keys, for tests only.
    @@ -90,7 +94,7 @@ func NewConfigWithUserId(userId UserId) *Config {
     	return &c
     }
     
    -//Deprecated: Please use NewConfigWithUserId
    +// Deprecated: Please use NewConfigWithUserId
     func NewConfig(uuid string) *Config {
     	return NewConfigWithUserId(UserId(uuid))
     }
    
  • crypto/aes_cbc_cryptor.go+90 0 added
    @@ -0,0 +1,90 @@
    +package crypto
    +
    +import (
    +	"crypto/aes"
    +	"crypto/cipher"
    +	"crypto/sha256"
    +	"errors"
    +	"fmt"
    +	"io"
    +)
    +
    +type aesCbcCryptor struct {
    +	block cipher.Block
    +}
    +
    +func NewAesCbcCryptor(cipherKey string) (ExtendedCryptor, error) {
    +	block, e := aesCipher(cipherKey)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return &aesCbcCryptor{
    +		block: block,
    +	}, nil
    +}
    +
    +var crivId = "ACRH"
    +
    +func (c *aesCbcCryptor) Id() string {
    +	return crivId
    +}
    +
    +func (c *aesCbcCryptor) Encrypt(message []byte) (*EncryptedData, error) {
    +	message = padWithPKCS7(message)
    +	iv := generateIV(aes.BlockSize)
    +	blockmode := cipher.NewCBCEncrypter(c.block, iv)
    +
    +	encryptedBytes := make([]byte, len(message))
    +	blockmode.CryptBlocks(encryptedBytes, message)
    +
    +	return &EncryptedData{
    +		Metadata: iv,
    +		Data:     encryptedBytes,
    +	}, nil
    +}
    +
    +func (c *aesCbcCryptor) Decrypt(encryptedData *EncryptedData) (r []byte, e error) {
    +	decrypter := cipher.NewCBCDecrypter(c.block, encryptedData.Metadata)
    +	//to handle decryption errors
    +	defer func() {
    +		if rec := recover(); rec != nil {
    +			r, e = nil, fmt.Errorf("%s", rec)
    +		}
    +	}()
    +
    +	if len(encryptedData.Data)%16 != 0 {
    +		return nil, fmt.Errorf("encrypted data length should be divisible by block size")
    +	}
    +
    +	decrypted := make([]byte, len(encryptedData.Data))
    +	decrypter.CryptBlocks(decrypted, encryptedData.Data)
    +	return unpadPKCS7(decrypted)
    +}
    +
    +func (c *aesCbcCryptor) EncryptStream(reader io.Reader) (*EncryptedStreamData, error) {
    +	iv := generateIV(aes.BlockSize)
    +
    +	return &EncryptedStreamData{
    +		Metadata: iv,
    +		Reader:   newBlockModeEncryptingReader(reader, cipher.NewCBCEncrypter(c.block, iv)),
    +	}, nil
    +}
    +
    +func (c *aesCbcCryptor) DecryptStream(encryptedData *EncryptedStreamData) (io.Reader, error) {
    +	if encryptedData.Metadata == nil {
    +		return nil, errors.New("missing metadata")
    +	}
    +	return newBlockModeDecryptingReader(encryptedData.Reader, cipher.NewCBCDecrypter(c.block, encryptedData.Metadata)), nil
    +}
    +
    +func aesCipher(cipherKey string) (cipher.Block, error) {
    +	hash := sha256.New()
    +	hash.Write([]byte(cipherKey))
    +
    +	block, err := aes.NewCipher(hash.Sum(nil))
    +	if err != nil {
    +		return nil, err
    +	}
    +	return block, nil
    +}
    
  • crypto/aes_cbc_cryptor_test.go+77 0 added
    @@ -0,0 +1,77 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"io"
    +	"testing"
    +	"testing/quick"
    +)
    +
    +var defaultPropertyTestConfig = &quick.Config{
    +	MaxCount: 1000,
    +}
    +
    +func canDecryptEncryptStreamResult(in []byte) bool {
    +	cryptor, e := NewAesCbcCryptor("enigma")
    +	if e != nil {
    +		return false
    +	}
    +
    +	output, err := cryptor.EncryptStream(bytes.NewReader(in))
    +	if err != nil {
    +		return false
    +	}
    +
    +	encrData, err := io.ReadAll(output.Reader)
    +
    +	decrypted, err := cryptor.Decrypt(&EncryptedData{
    +		Data:     encrData,
    +		Metadata: output.Metadata,
    +	})
    +	if err != nil {
    +		return false
    +	}
    +	return bytes.Equal(in, decrypted)
    +}
    +
    +func canDecryptStreamEncryptResult(in []byte) bool {
    +	cryptor, e := NewAesCbcCryptor("enigma")
    +	if e != nil {
    +		return false
    +	}
    +
    +	output, err := cryptor.Encrypt(in)
    +	if err != nil {
    +		println(err.Error())
    +		return false
    +	}
    +
    +	decryptingReader, err := cryptor.DecryptStream(&EncryptedStreamData{
    +		Reader:   bytes.NewReader(output.Data),
    +		Metadata: output.Metadata,
    +	})
    +	if err != nil {
    +		println(err.Error())
    +		return false
    +	}
    +
    +	decrypted, err := io.ReadAll(decryptingReader)
    +	if err != nil {
    +		println(err.Error())
    +		return false
    +	}
    +
    +	return bytes.Equal(in, decrypted)
    +}
    +
    +func Test_AesCBC_EncryptStream(t *testing.T) {
    +	if err := quick.Check(canDecryptEncryptStreamResult, defaultPropertyTestConfig); err != nil {
    +		t.Error(err)
    +	}
    +}
    +
    +func Test_AesCBC_DecryptStream(t *testing.T) {
    +	if err := quick.Check(canDecryptStreamEncryptResult, defaultPropertyTestConfig); err != nil {
    +		t.Error(err)
    +	}
    +}
    
  • crypto/cryptor.go+25 0 added
    @@ -0,0 +1,25 @@
    +package crypto
    +
    +import "io"
    +
    +type EncryptedData struct {
    +	Metadata []byte
    +	Data     []byte
    +}
    +
    +type Cryptor interface {
    +	Id() string
    +	Encrypt(message []byte) (*EncryptedData, error)
    +	Decrypt(encryptedData *EncryptedData) ([]byte, error)
    +}
    +
    +type EncryptedStreamData struct {
    +	Metadata []byte
    +	Reader   io.Reader
    +}
    +
    +type ExtendedCryptor interface {
    +	Cryptor
    +	EncryptStream(reader io.Reader) (*EncryptedStreamData, error)
    +	DecryptStream(encryptedData *EncryptedStreamData) (io.Reader, error)
    +}
    
  • crypto/cryptor_header.go+174 0 added
    @@ -0,0 +1,174 @@
    +package crypto
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"encoding/binary"
    +	"fmt"
    +	"io"
    +	math "math"
    +	"strconv"
    +)
    +
    +const versionPosition = 4
    +const versionV1 = 1
    +const cryptorIdPosition = 5
    +const cryptorIdLength = 4
    +const sizePosition = 9
    +const shortSizeLength = 1
    +const longSizeLength = 3
    +const longSizeIndicator = 0xFF
    +const maxShortSize = 254
    +const sentinelLength = 4
    +
    +var sentinel = [sentinelLength]byte{0x50, 0x4E, 0x45, 0x44}
    +
    +func headerV1(cryptorId string, metadata []byte) ([]byte, error) {
    +	cryptorDataSize := len(metadata)
    +	var cryptorDataBytesSize int
    +
    +	if cryptorDataSize < longSizeIndicator {
    +		cryptorDataBytesSize = shortSizeLength
    +	} else if cryptorDataSize < math.MaxUint16 {
    +		cryptorDataBytesSize = longSizeLength
    +	} else {
    +		return nil, fmt.Errorf("size of cryptor metadata too large %d", cryptorDataSize)
    +	}
    +	r := make([]byte, 0, len(sentinel)+1+cryptorIdLength+cryptorDataBytesSize+cryptorDataSize)
    +
    +	buffer := bytes.NewBuffer(r)
    +	_, e := buffer.Write(sentinel[:])
    +	if e != nil {
    +		return nil, e
    +	}
    +	e = buffer.WriteByte(versionV1)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	_, e = buffer.Write([]byte(cryptorId))
    +	if e != nil {
    +		return nil, e
    +	}
    +	if cryptorDataBytesSize == shortSizeLength {
    +		e = buffer.WriteByte(byte(cryptorDataSize))
    +		if e != nil {
    +			return nil, e
    +		}
    +	} else {
    +		e = buffer.WriteByte(longSizeIndicator)
    +		if e != nil {
    +			return nil, e
    +		}
    +		sizeBytes := make([]byte, 2)
    +		binary.BigEndian.PutUint16(sizeBytes, uint16(cryptorDataSize))
    +		_, e = buffer.Write(sizeBytes)
    +		if e != nil {
    +			return nil, e
    +		}
    +	}
    +	_, e = buffer.Write(metadata)
    +	if e != nil {
    +		return nil, e
    +	}
    +	return buffer.Bytes(), nil
    +}
    +
    +func peekHeaderCryptorId(data []byte) (cryptorId *string, e error) {
    +	if len(data) < len(sentinel) || !bytes.Equal(data[:len(sentinel)], sentinel[:]) {
    +		return &legacyId, nil
    +	}
    +
    +	if data[versionPosition] != versionV1 {
    +		return nil, unsupportedHeaderVersion(int(data[versionPosition]))
    +	}
    +
    +	id := string(data[cryptorIdPosition : cryptorIdPosition+cryptorIdLength])
    +	return &id, nil
    +}
    +
    +func parseHeader(data []byte) (cryptorId *string, encrData *EncryptedData, e error) {
    +	id, err := peekHeaderCryptorId(data)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	if (*id) == legacyId {
    +		return id, &EncryptedData{Data: data, Metadata: nil}, nil
    +	}
    +	var headerSize int64
    +	position := int64(sizePosition)
    +	if data[sizePosition] == longSizeIndicator {
    +		position += longSizeLength
    +
    +		headerSize, err = strconv.ParseInt(string(data[sizePosition:sizePosition+longSizeLength]), 10, 32)
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +	} else {
    +		position += shortSizeLength
    +		headerSize = int64(data[sizePosition])
    +	}
    +
    +	metadata := data[position : position+headerSize]
    +	position += int64(len(metadata))
    +
    +	if int64(len(data)) < position {
    +		return nil, nil, fmt.Errorf("decryption error: %w", e)
    +	}
    +
    +	return id, &EncryptedData{Data: data[position:], Metadata: metadata}, nil
    +}
    +
    +func parseHeaderStream(bufData *bufio.Reader) (cryptorId *string, encrypted *EncryptedStreamData, e error) {
    +	peeked, err := bufData.Peek(sentinelLength + 1 + cryptorIdLength + longSizeLength)
    +	if err != nil {
    +		return nil, nil, fmt.Errorf("decryption error: %w", err)
    +	}
    +
    +	id, err := peekHeaderCryptorId(peeked)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	if (*id) == legacyId {
    +		return id, &EncryptedStreamData{
    +			Reader:   bufData,
    +			Metadata: nil,
    +		}, nil
    +	}
    +
    +	var metadataSize int64
    +	position := int64(sizePosition)
    +	if peeked[sizePosition] == longSizeIndicator {
    +		position += longSizeLength
    +		var e error
    +		metadataSize, e = strconv.ParseInt(string(peeked[sizePosition:sizePosition+longSizeLength]), 10, 32)
    +		if e != nil {
    +			return nil, nil, e
    +		}
    +	} else {
    +		position += shortSizeLength
    +		metadataSize = int64(peeked[sizePosition])
    +	}
    +	if metadataSize > 254 {
    +		_, e := bufData.Discard(sentinelLength + 1 + cryptorIdLength + longSizeLength)
    +		if e != nil {
    +			return nil, nil, e
    +		}
    +	} else {
    +		_, e := bufData.Discard(sentinelLength + 1 + cryptorIdLength + shortSizeLength)
    +		if e != nil {
    +			return nil, nil, e
    +		}
    +	}
    +	m := make([]byte, metadataSize)
    +	_, e = io.ReadFull(bufData, m)
    +	if e != nil {
    +		return nil, nil, e
    +	}
    +
    +	return id, &EncryptedStreamData{
    +		Reader:   bufData,
    +		Metadata: m,
    +	}, nil
    +}
    
  • crypto/cryptor_header_test.go+29 0 added
    @@ -0,0 +1,29 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"github.com/stretchr/testify/assert"
    +	"math"
    +	"testing"
    +)
    +
    +func TestCryptorHeader_CreateHeaderWithLargeMetadata(t *testing.T) {
    +	metadata := make([]byte, 512)
    +	header, _ := headerV1("abcd", metadata)
    +	cryptorDataSize := header[sentinelLength+1+cryptorIdLength+longSizeLength-2 : sentinelLength+1+cryptorIdLength+longSizeLength]
    +	assert.True(t, bytes.Equal([]byte{0xff, 0x02, 0x00}, cryptorDataSize))
    +}
    +
    +func TestCryptorHeader_CreateHeaderWithSmallMetadata(t *testing.T) {
    +	metadata := make([]byte, 128)
    +	header, _ := headerV1("abcd", metadata)
    +	assert.Equal(t, byte(0x80), header[sizePosition])
    +}
    +
    +func TestCryptorHeader_WithTooLargeMetadata(t *testing.T) {
    +	metadata := make([]byte, math.MaxUint16+255)
    +	_, e := headerV1("abcd", metadata)
    +	if e == nil {
    +		assert.Fail(t, "expected error")
    +	}
    +}
    
  • crypto/decrypting_reader.go+134 0 added
    @@ -0,0 +1,134 @@
    +package crypto
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"crypto/cipher"
    +	"errors"
    +	"io"
    +)
    +
    +func newBlockModeDecryptingReader(r io.Reader, mode cipher.BlockMode) io.Reader {
    +	return &blockModeDecryptingReader{
    +		r:         bufio.NewReader(r),
    +		blockMode: mode,
    +		buffer:    bytes.NewBuffer(nil),
    +		err:       nil,
    +	}
    +}
    +
    +type blockModeDecryptingReader struct {
    +	r         *bufio.Reader
    +	blockMode cipher.BlockMode
    +	buffer    *bytes.Buffer
    +	err       error
    +}
    +
    +func (decryptingReader *blockModeDecryptingReader) readNextBlock() ([]byte, error) {
    +	reader := decryptingReader.r
    +
    +	output := make([]byte, decryptingReader.blockMode.BlockSize())
    +	sizeOfCurrentlyRead, readErr := io.ReadFull(reader, output)
    +	if readErr != nil && readErr != io.EOF {
    +		return nil, readErr
    +	}
    +
    +	if sizeOfCurrentlyRead == 0 && readErr == io.EOF {
    +		return nil, io.EOF
    +	}
    +
    +	if _, e := reader.Peek(1); e == io.EOF {
    +		return output[:sizeOfCurrentlyRead], io.EOF
    +	}
    +
    +	return output, nil
    +}
    +
    +func (decryptingReader *blockModeDecryptingReader) decryptUntilPFull(p []byte) (n int, err error) {
    +	var copied int
    +	var block []byte
    +	var e error
    +	alreadyWrote := 0
    +	for alreadyWrote < len(p) {
    +		block, e = decryptingReader.readNextBlock()
    +		decryptingReader.err = e
    +
    +		if errors.Is(e, io.ErrUnexpectedEOF) {
    +			return alreadyWrote, errors.New("encrypted data length is not a multiple of the block size")
    +		}
    +
    +		if e != nil && e != io.EOF {
    +			return alreadyWrote, e
    +		}
    +
    +		decryptingReader.blockMode.CryptBlocks(block, block)
    +
    +		if bytes.Equal(block, bytes.Repeat([]byte{byte(decryptingReader.blockMode.BlockSize())}, len(block))) {
    +			break
    +		} else if e == io.EOF {
    +			unpadded, unpadErr := unpadPKCS7(block)
    +			if len(unpadded) > 0 {
    +				block = unpadded
    +				copied = copy(p[alreadyWrote:], block)
    +				alreadyWrote += copied
    +			}
    +
    +			if unpadErr != nil {
    +				return alreadyWrote, unpadErr
    +			}
    +			if copied < len(block) {
    +				decryptingReader.buffer.Write(block[copied:])
    +			}
    +			break
    +		} else {
    +			copied = copy(p[alreadyWrote:], block)
    +			alreadyWrote += copied
    +			if copied < len(block) {
    +				decryptingReader.buffer.Write(block[copied:])
    +			}
    +		}
    +	}
    +
    +	return alreadyWrote, nil
    +}
    +
    +func readFromBufferUntilEmpty(buffer *bytes.Buffer, p []byte) int {
    +	buffered := buffer.Next(len(p))
    +	if len(buffered) == 0 {
    +		return 0
    +	}
    +	return copy(p, buffered)
    +}
    +
    +func (decryptingReader *blockModeDecryptingReader) readFromBufferUntilEmpty(p []byte) (n int, err error) {
    +	buffered := decryptingReader.buffer.Next(len(p))
    +	var alreadyWrote int
    +	if len(buffered) > 0 {
    +		alreadyWrote = copy(p, buffered)
    +	}
    +
    +	if decryptingReader.err != nil {
    +		return alreadyWrote, decryptingReader.err
    +	}
    +
    +	return alreadyWrote, nil
    +}
    +
    +func (decryptingReader *blockModeDecryptingReader) Read(p []byte) (n int, err error) {
    +	if len(p) == 0 {
    +		return 0, errors.New("cannot read into empty buffer")
    +	}
    +
    +	if (decryptingReader.err != nil) && decryptingReader.buffer.Len() == 0 {
    +		return 0, decryptingReader.err
    +	}
    +
    +	alreadyWrote := readFromBufferUntilEmpty(decryptingReader.buffer, p)
    +
    +	if decryptingReader.err != nil {
    +		return alreadyWrote, nil
    +	}
    +
    +	n, e := decryptingReader.decryptUntilPFull(p[alreadyWrote:])
    +	return alreadyWrote + n, e
    +}
    
  • crypto/decrypting_reader_test.go+53 0 added
    @@ -0,0 +1,53 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"io"
    +	"testing"
    +	"testing/quick"
    +)
    +
    +type DoNothingBlockMode struct {
    +}
    +
    +func (d *DoNothingBlockMode) BlockSize() int {
    +	return 16
    +}
    +
    +func (d *DoNothingBlockMode) CryptBlocks(dst, src []byte) {
    +	copy(dst, src)
    +}
    +
    +func canReadDifferentSizeOfChunks(in []byte, bufferSize uint8) bool {
    +	inPadded := padWithPKCS7VarBlock(in, 16)
    +	if bufferSize == 0 {
    +		return true
    +	}
    +	decryptingReader := newBlockModeDecryptingReader(bytes.NewReader(inPadded), &DoNothingBlockMode{})
    +	readDataBuffer := bytes.NewBuffer(nil)
    +	buffer := make([]byte, bufferSize)
    +	numberOfReadBytes := 0
    +
    +	var e error
    +	var readBytes int
    +
    +	for e == nil {
    +		readBytes, e = decryptingReader.Read(buffer)
    +		numberOfReadBytes += readBytes
    +		readDataBuffer.Write(buffer[:readBytes])
    +	}
    +
    +	if e != nil && e != io.EOF {
    +		return false
    +	}
    +
    +	out := readDataBuffer.Bytes()[:numberOfReadBytes]
    +
    +	return bytes.Equal(in, out)
    +}
    +
    +func Test_DecryptingReader_ReadDifferentSizeOfBuffers(t *testing.T) {
    +	if err := quick.Check(canReadDifferentSizeOfChunks, defaultPropertyTestConfig); err != nil {
    +		t.Error(err)
    +	}
    +}
    
  • crypto/default_extended_cryptor.go+55 0 added
    @@ -0,0 +1,55 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"io"
    +)
    +
    +type defaultExtendedCryptor struct {
    +	Cryptor
    +}
    +
    +func liftToExtendedCryptor(cryptor Cryptor) ExtendedCryptor {
    +	if extendedCryptor, ok := cryptor.(ExtendedCryptor); ok {
    +		return extendedCryptor
    +	} else {
    +		return newDefaultExtendedCryptor(cryptor)
    +	}
    +}
    +
    +func newDefaultExtendedCryptor(cryptor Cryptor) ExtendedCryptor {
    +	return &defaultExtendedCryptor{
    +		cryptor,
    +	}
    +}
    +
    +func (c *defaultExtendedCryptor) EncryptStream(input io.Reader) (*EncryptedStreamData, error) {
    +	inputBytes, e := io.ReadAll(input)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	encryptedData, e := c.Cryptor.Encrypt(inputBytes)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return &EncryptedStreamData{
    +		Reader:   bytes.NewReader(encryptedData.Data),
    +		Metadata: encryptedData.Metadata,
    +	}, nil
    +}
    +
    +func (c *defaultExtendedCryptor) DecryptStream(input *EncryptedStreamData) (io.Reader, error) {
    +	inputBytes, e := io.ReadAll(input.Reader)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	decryptedData, e := c.Cryptor.Decrypt(&EncryptedData{Data: inputBytes, Metadata: input.Metadata})
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return bytes.NewReader(decryptedData), nil
    +}
    
  • crypto/encrypting_reader.go+86 0 added
    @@ -0,0 +1,86 @@
    +package crypto
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"crypto/cipher"
    +	"errors"
    +	"io"
    +)
    +
    +func newBlockModeEncryptingReader(r io.Reader, mode cipher.BlockMode) io.Reader {
    +	return &blockModeEncryptingReader{
    +		r:         bufio.NewReader(r),
    +		blockMode: mode,
    +		buffer:    bytes.NewBuffer(nil),
    +		err:       nil,
    +	}
    +}
    +
    +type blockModeEncryptingReader struct {
    +	r         *bufio.Reader
    +	blockMode cipher.BlockMode
    +	buffer    *bytes.Buffer
    +	err       error
    +}
    +
    +func (encryptingReader *blockModeEncryptingReader) readNextBlockPadded() (read []byte, err error) {
    +	output := make([]byte, encryptingReader.blockMode.BlockSize())
    +	sizeOfCurrentlyRead, readErr := io.ReadFull(encryptingReader.r, output)
    +	if readErr != nil && readErr != io.EOF && !errors.Is(readErr, io.ErrUnexpectedEOF) {
    +		return nil, readErr
    +	}
    +
    +	if sizeOfCurrentlyRead == 0 && readErr == io.EOF {
    +		return padWithPKCS7VarBlock(nil, encryptingReader.blockMode.BlockSize()), io.EOF
    +	}
    +
    +	if errors.Is(readErr, io.ErrUnexpectedEOF) {
    +		return padWithPKCS7VarBlock(output[:sizeOfCurrentlyRead], encryptingReader.blockMode.BlockSize()), io.EOF
    +	}
    +
    +	return output, nil
    +}
    +
    +func (encryptingReader *blockModeEncryptingReader) encryptUntilPFull(p []byte) (int, error) {
    +	var alreadyWrote int
    +	for alreadyWrote <= len(p) {
    +		block, e := encryptingReader.readNextBlockPadded()
    +		if e != nil && e != io.EOF {
    +			return alreadyWrote, e
    +		}
    +		encryptingReader.err = e
    +
    +		encryptingReader.blockMode.CryptBlocks(block, block)
    +		copied := copy(p[alreadyWrote:], block)
    +		alreadyWrote += copied
    +		if copied < len(block) {
    +			encryptingReader.buffer.Write(block[copied:])
    +		}
    +		if e == io.EOF {
    +			break
    +		}
    +	}
    +
    +	return alreadyWrote, nil
    +}
    +
    +func (encryptingReader *blockModeEncryptingReader) Read(p []byte) (n int, err error) {
    +	if len(p) == 0 {
    +		return 0, errors.New("cannot read into empty buffer")
    +	}
    +
    +	if encryptingReader.err != nil && encryptingReader.buffer.Len() == 0 {
    +		return 0, encryptingReader.err
    +	}
    +
    +	alreadyWrote := readFromBufferUntilEmpty(encryptingReader.buffer, p)
    +
    +	if encryptingReader.err != nil {
    +		return alreadyWrote, nil
    +	}
    +
    +	n, e := encryptingReader.encryptUntilPFull(p[alreadyWrote:])
    +
    +	return alreadyWrote + n, e
    +}
    
  • crypto/encrypting_reader_test.go+42 0 added
    @@ -0,0 +1,42 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"io"
    +	"testing"
    +	"testing/quick"
    +)
    +
    +func encryptingReaderCanReadDifferentSizeOfChunks(in []byte, bufferSize uint8) bool {
    +	inPadded := padWithPKCS7VarBlock(in, 16)
    +	if bufferSize == 0 {
    +		return true
    +	}
    +	encryptingReader := newBlockModeEncryptingReader(bytes.NewReader(in), &DoNothingBlockMode{})
    +	readDataBuffer := bytes.NewBuffer(nil)
    +	buffer := make([]byte, bufferSize)
    +	numberOfReadBytes := 0
    +
    +	var e error
    +	var readBytes int
    +
    +	for e == nil {
    +		readBytes, e = encryptingReader.Read(buffer)
    +		numberOfReadBytes += readBytes
    +		readDataBuffer.Write(buffer[:readBytes])
    +	}
    +
    +	if e != nil && e != io.EOF {
    +		return false
    +	}
    +
    +	out := readDataBuffer.Bytes()[:numberOfReadBytes]
    +
    +	return bytes.Equal(inPadded, out)
    +}
    +
    +func Test_EncryptingReader_ReadDifferentSizeOfBuffers(t *testing.T) {
    +	if err := quick.Check(encryptingReaderCanReadDifferentSizeOfChunks, defaultPropertyTestConfig); err != nil {
    +		t.Error(err)
    +	}
    +}
    
  • crypto/legacy_cryptor.go+150 0 added
    @@ -0,0 +1,150 @@
    +package crypto
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"crypto/aes"
    +	"crypto/cipher"
    +	"crypto/sha256"
    +	"encoding/hex"
    +	"errors"
    +	"fmt"
    +	"io"
    +)
    +
    +// 16 byte constant legacy IV
    +var valIV = "0123456789012345"
    +
    +type legacyCryptor struct {
    +	block    cipher.Block
    +	randomIv bool
    +}
    +
    +func NewLegacyCryptor(cipherKey string, useRandomIV bool) (ExtendedCryptor, error) {
    +	block, e := legacyAesCipher(cipherKey)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return &legacyCryptor{
    +		block:    block,
    +		randomIv: useRandomIV,
    +	}, nil
    +}
    +
    +var legacyId = string([]byte{0x00, 0x00, 0x00, 0x00})
    +
    +func (c *legacyCryptor) Id() string {
    +	return legacyId
    +}
    +
    +func (c *legacyCryptor) Encrypt(message []byte) (*EncryptedData, error) {
    +	message = padWithPKCS7(message)
    +	iv := make([]byte, aes.BlockSize)
    +	if c.randomIv {
    +		iv = generateIV(aes.BlockSize)
    +	} else {
    +		iv = []byte(valIV)
    +	}
    +	blockmode := cipher.NewCBCEncrypter(c.block, iv)
    +
    +	encryptedBytes := make([]byte, len(message))
    +	blockmode.CryptBlocks(encryptedBytes, message)
    +
    +	if c.randomIv {
    +		return &EncryptedData{Data: append(iv, encryptedBytes...), Metadata: nil}, nil
    +	}
    +	return &EncryptedData{Data: encryptedBytes, Metadata: nil}, nil
    +}
    +
    +func (c *legacyCryptor) Decrypt(encryptedData *EncryptedData) (r []byte, e error) {
    +	iv := make([]byte, aes.BlockSize)
    +	data := encryptedData.Data
    +	if c.randomIv {
    +		iv = data[:aes.BlockSize]
    +		data = data[aes.BlockSize:]
    +	} else {
    +		iv = []byte(valIV)
    +	}
    +
    +	if len(data)%16 != 0 {
    +		return nil, fmt.Errorf("length of data to decrypt should be divisible by block size")
    +	}
    +
    +	decrypter := cipher.NewCBCDecrypter(c.block, iv)
    +	//to handle decryption errors
    +	defer func() {
    +		if rec := recover(); rec != nil {
    +			r, e = nil, fmt.Errorf("decrypt error: %s", rec)
    +		}
    +	}()
    +
    +	decrypted := make([]byte, len(data))
    +	decrypter.CryptBlocks(decrypted, data)
    +	val, err := unpadPKCS7(decrypted)
    +	if err != nil {
    +		return nil, fmt.Errorf("decrypt error: %s", err)
    +	}
    +
    +	return val, nil
    +}
    +
    +func (c *legacyCryptor) EncryptStream(reader io.Reader) (*EncryptedStreamData, error) {
    +	iv := generateIV(aes.BlockSize)
    +
    +	return &EncryptedStreamData{
    +		Metadata: nil,
    +		Reader:   io.MultiReader(bytes.NewReader(iv), newBlockModeEncryptingReader(reader, cipher.NewCBCEncrypter(c.block, iv))),
    +	}, nil
    +}
    +
    +func (c *legacyCryptor) DecryptStream(encryptedData *EncryptedStreamData) (io.Reader, error) {
    +	iv := make([]byte, aes.BlockSize)
    +	_, err := io.ReadFull(encryptedData.Reader, iv)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	bufReader := bufio.NewReader(encryptedData.Reader)
    +	peeked, e := bufReader.Peek(1)
    +	if len(peeked) == 0 {
    +		return nil, errors.New("can't decrypt empty data")
    +	}
    +
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return newBlockModeDecryptingReader(encryptedData.Reader, cipher.NewCBCDecrypter(c.block, iv)), nil
    +}
    +
    +// EncryptCipherKey DEPRECATED
    +// EncryptCipherKey creates the 256 bit hex of the cipher key
    +//
    +// It accepts the following parameters:
    +// cipherKey: cipher key to use to decrypt.
    +//
    +// returns the 256 bit hex of the cipher key.
    +func EncryptCipherKey(cipherKey string) []byte {
    +	hash := sha256.New()
    +	hash.Write([]byte(cipherKey))
    +
    +	sha256String := hash.Sum(nil)[:16]
    +	return []byte(hex.EncodeToString(sha256String))
    +}
    +
    +// legacyAesCipher returns the cipher block
    +//
    +// It accepts the following parameters:
    +// cipherKey: cipher key.
    +//
    +// returns the cipher block,
    +// error if any.
    +func legacyAesCipher(cipherKey string) (cipher.Block, error) {
    +	key := EncryptCipherKey(cipherKey)
    +	block, err := aes.NewCipher(key)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return block, nil
    +}
    
  • crypto/legacy_cryptor_test.go+72 0 added
    @@ -0,0 +1,72 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"io"
    +	"testing"
    +	"testing/quick"
    +)
    +
    +func legacyCanDecryptEncryptStreamResult(in []byte) bool {
    +	cryptor, e := NewLegacyCryptor("enigma", true)
    +	if e != nil {
    +		return false
    +	}
    +	output, err := cryptor.EncryptStream(bytes.NewReader(in))
    +	if err != nil {
    +		return false
    +	}
    +
    +	encr, err := io.ReadAll(output.Reader)
    +
    +	decrypted, err := cryptor.Decrypt(&EncryptedData{
    +		Data:     encr,
    +		Metadata: output.Metadata,
    +	})
    +	if err != nil {
    +		return false
    +	}
    +	return bytes.Equal(in, decrypted)
    +}
    +
    +func legacyCanDecryptStreamEncryptResult(in []byte) bool {
    +	cryptor, e := NewLegacyCryptor("enigma", true)
    +	if e != nil {
    +		return false
    +	}
    +
    +	output, err := cryptor.Encrypt(in)
    +	if err != nil {
    +		println(err.Error())
    +		return false
    +	}
    +
    +	decryptingReader, err := cryptor.DecryptStream(&EncryptedStreamData{
    +		Reader:   bytes.NewReader(output.Data),
    +		Metadata: output.Metadata,
    +	})
    +	if err != nil {
    +		println(err.Error())
    +		return false
    +	}
    +
    +	decrypted, err := io.ReadAll(decryptingReader)
    +	if err != nil {
    +		println(err.Error())
    +		return false
    +	}
    +
    +	return bytes.Equal(in, decrypted)
    +}
    +
    +func Test_Legacy_EncryptStream(t *testing.T) {
    +	if err := quick.Check(legacyCanDecryptEncryptStreamResult, defaultPropertyTestConfig); err != nil {
    +		t.Error(err)
    +	}
    +}
    +
    +func Test_Legacy_DecryptStream(t *testing.T) {
    +	if err := quick.Check(legacyCanDecryptStreamEncryptResult, defaultPropertyTestConfig); err != nil {
    +		t.Error(err)
    +	}
    +}
    
  • crypto/module.go+174 0 added
    @@ -0,0 +1,174 @@
    +package crypto
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"errors"
    +	"fmt"
    +	"io"
    +)
    +
    +// CryptoModule is an interface for encrypting and decrypting data.
    +type CryptoModule interface {
    +	Encrypt(input []byte) ([]byte, error)
    +	Decrypt(input []byte) ([]byte, error)
    +	EncryptStream(input io.Reader) (io.Reader, error)
    +	DecryptStream(input io.Reader) (io.Reader, error)
    +}
    +
    +type module struct {
    +	encryptor  ExtendedCryptor
    +	decryptors map[string]ExtendedCryptor
    +}
    +
    +func NewLegacyCryptoModule(cipherKey string, randomIv bool) (CryptoModule, error) {
    +	legacy, e := NewLegacyCryptor(cipherKey, randomIv)
    +	if e != nil {
    +		return nil, e
    +	}
    +	aesCbc, e := NewAesCbcCryptor(cipherKey)
    +
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return NewCryptoModule(legacy, []Cryptor{aesCbc}), nil
    +}
    +
    +func NewAesCbcCryptoModule(cipherKey string, randomIv bool) (CryptoModule, error) {
    +	aesCbc, e := NewAesCbcCryptor(cipherKey)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	legacy, e := NewLegacyCryptor(cipherKey, randomIv)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return NewCryptoModule(aesCbc, []Cryptor{legacy}), nil
    +}
    +
    +func NewCryptoModule(defaultCryptor Cryptor, decryptors []Cryptor) CryptoModule {
    +
    +	decryptorsMap := make(map[string]ExtendedCryptor, len(decryptors)+1)
    +	for _, d := range decryptors {
    +		decryptorsMap[d.Id()] = liftToExtendedCryptor(d)
    +	}
    +
    +	encryptor := liftToExtendedCryptor(defaultCryptor)
    +	decryptorsMap[encryptor.Id()] = encryptor
    +
    +	return &module{
    +		encryptor:  encryptor,
    +		decryptors: decryptorsMap,
    +	}
    +}
    +
    +func (m *module) Encrypt(message []byte) ([]byte, error) {
    +	if len(message) == 0 {
    +		return nil, errors.New("encryption error: can't encrypt empty data")
    +	}
    +	encryptedData, e := m.encryptor.Encrypt(message)
    +	if e != nil {
    +		return nil, fmt.Errorf("encryption error: %s", e.Error())
    +	}
    +
    +	if m.encryptor.Id() == legacyId {
    +		return encryptedData.Data, nil
    +	}
    +
    +	header, e := headerV1(m.encryptor.Id(), encryptedData.Metadata)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	return append(header, encryptedData.Data...), nil
    +}
    +
    +func (m *module) Decrypt(data []byte) ([]byte, error) {
    +	if len(data) == 0 {
    +		return nil, errors.New("decryption error: can't decrypt empty data")
    +	}
    +
    +	id, encryptedData, e := parseHeader(data)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	decryptor := m.decryptors[*id]
    +
    +	if decryptor == nil {
    +		return nil, fmt.Errorf("unknown crypto error: unknown cryptor id %s", *id)
    +	}
    +
    +	if len(encryptedData.Data) == 0 {
    +		return nil, errors.New("decryption error: can't decrypt empty data")
    +	}
    +
    +	var r []byte
    +	if r, e = m.decryptors[*id].Decrypt(encryptedData); e != nil {
    +		return nil, fmt.Errorf("decryption error: %s", e.Error())
    +	}
    +
    +	return r, nil
    +}
    +
    +func (m *module) EncryptStream(input io.Reader) (io.Reader, error) {
    +	bufferedReader := bufio.NewReader(input)
    +	peekedBytes, e := bufferedReader.Peek(1)
    +	if len(peekedBytes) == 0 {
    +		return nil, errors.New("encryption error: can't encrypt empty data")
    +	}
    +
    +	encryptedStreamData, e := m.encryptor.EncryptStream(bufferedReader)
    +	if e != nil {
    +		return nil, e
    +	}
    +	if m.encryptor.Id() == legacyId {
    +		return encryptedStreamData.Reader, nil
    +	}
    +	header, e := headerV1(m.encryptor.Id(), encryptedStreamData.Metadata)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	headerReader := bytes.NewReader(header)
    +
    +	return io.MultiReader(headerReader, encryptedStreamData.Reader), nil
    +}
    +
    +func (m *module) DecryptStream(input io.Reader) (io.Reader, error) {
    +	bufData := bufio.NewReader(input)
    +
    +	id, encryptedStreamData, e := parseHeaderStream(bufData)
    +	if e != nil {
    +		return nil, e
    +	}
    +
    +	peeked, e := bufData.Peek(1)
    +	if e != nil {
    +		return nil, fmt.Errorf("decryption error: %w", e)
    +	}
    +
    +	if len(peeked) == 0 {
    +		return nil, errors.New("decryption error: can't decrypt empty data")
    +	}
    +
    +	decryptor := m.decryptors[*id]
    +
    +	if decryptor == nil {
    +		return nil, fmt.Errorf("unknown crypto error: unknown cryptor id %s", *id)
    +	}
    +
    +	if e != nil {
    +		return nil, fmt.Errorf("decryption error: %s", e.Error())
    +	}
    +
    +	var r io.Reader
    +	if r, e = m.decryptors[*id].DecryptStream(encryptedStreamData); e != nil {
    +		return nil, fmt.Errorf("decryption error: %s", e.Error())
    +	}
    +
    +	return r, nil
    +}
    
  • crypto/module_test.go+22 0 added
    @@ -0,0 +1,22 @@
    +package crypto
    +
    +import (
    +	"encoding/base64"
    +	"testing"
    +)
    +
    +func TestToProduceData(t *testing.T) {
    +	cipherKey := "myCipherKey"
    +	textToEncrypt := "Hello world encrypted with "
    +
    +	legacyModuleStaticIv, _ := NewLegacyCryptoModule(cipherKey, false)
    +	legacyModuleRandomIv, _ := NewLegacyCryptoModule(cipherKey, true)
    +	aesCbcModule, _ := NewAesCbcCryptoModule(cipherKey, true)
    +	r1, _ := legacyModuleStaticIv.Encrypt([]byte(textToEncrypt + "legacyModuleStaticIv"))
    +	r2, _ := legacyModuleRandomIv.Encrypt([]byte(textToEncrypt + "legacyModuleRandomIv"))
    +	r3, _ := aesCbcModule.Encrypt([]byte(textToEncrypt + "aesCbcModule"))
    +
    +	println(base64.StdEncoding.EncodeToString(r1))
    +	println(base64.StdEncoding.EncodeToString(r2))
    +	println(base64.StdEncoding.EncodeToString(r3))
    +}
    
  • crypto/utils.go+61 0 added
    @@ -0,0 +1,61 @@
    +package crypto
    +
    +import (
    +	"bytes"
    +	"crypto/rand"
    +	"fmt"
    +)
    +
    +func generateIV(blocksize int) []byte {
    +	iv := make([]byte, blocksize)
    +	if _, err := rand.Read(iv); err != nil {
    +		panic(err)
    +	}
    +	return iv
    +}
    +
    +func padWithPKCS7VarBlock(data []byte, blocklen int) []byte {
    +	padlen := 1
    +	for ((len(data) + padlen) % blocklen) != 0 {
    +		padlen = padlen + 1
    +	}
    +
    +	pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
    +	return append(data, pad...)
    +}
    +
    +// padWithPKCS7 pads the data as per the PKCS7 standard
    +// It accepts the following parameters:
    +// data: data to pad as byte array.
    +// returns the padded data as byte array.
    +func padWithPKCS7(data []byte) []byte {
    +	return padWithPKCS7VarBlock(data, 16)
    +}
    +
    +// unpadPKCS7 unpads the data as per the PKCS7 standard
    +// It accepts the following parameters:
    +// data: data to unpad as byte array.
    +// returns the unpadded data as byte array.
    +func unpadPKCS7(data []byte) ([]byte, error) {
    +	blocklen := 16
    +	if len(data)%blocklen != 0 || len(data) == 0 {
    +		return nil, fmt.Errorf("invalid data len %d", len(data))
    +	}
    +	padlen := int(data[len(data)-1])
    +	if padlen > blocklen || padlen == 0 {
    +		return nil, fmt.Errorf("padding is invalid")
    +	}
    +	// check padding
    +	pad := data[len(data)-padlen:]
    +	for i := 0; i < padlen; i++ {
    +		if pad[i] != byte(padlen) {
    +			return nil, fmt.Errorf("padding is invalid")
    +		}
    +	}
    +
    +	return data[:len(data)-padlen], nil
    +}
    +
    +func unsupportedHeaderVersion(version int) error {
    +	return fmt.Errorf("unknown crypto error: unsupported crypto header version %d", version)
    +}
    
  • fetch_request.go+2 2 modified
    @@ -278,7 +278,7 @@ func (o *fetchOpts) parseMessageActions(actions interface{}) map[string]PNHistor
     	return resp
     }
     
    -//{"status": 200, "error": false, "error_message": "", "channels": {"ch1":[{"message_type": "", "message": {"text": "hey"}, "timetoken": "15959610984115342", "meta": "", "uuid": "db9c5e39-7c95-40f5-8d71-125765b6f561"}]}}
    +// {"status": 200, "error": false, "error_message": "", "channels": {"ch1":[{"message_type": "", "message": {"text": "hey"}, "timetoken": "15959610984115342", "meta": "", "uuid": "db9c5e39-7c95-40f5-8d71-125765b6f561"}]}}
     func (o *fetchOpts) fetchMessages(channels map[string]interface{}) map[string][]FetchResponseItem {
     	messages := make(map[string][]FetchResponseItem, len(channels))
     
    @@ -290,7 +290,7 @@ func (o *fetchOpts) fetchMessages(channels map[string]interface{}) map[string][]
     
     			for _, val := range histResponseMap {
     				if histResponse, ok3 := val.(map[string]interface{}); ok3 {
    -					msg, _ := parseCipherInterface(histResponse["message"], o.pubnub.Config)
    +					msg, _ := parseCipherInterface(histResponse["message"], o.pubnub.Config, o.pubnub.getCryptoModule())
     
     					histItem := FetchResponseItem{
     						Message:   msg,
    
  • files_download_file.go+17 19 modified
    @@ -2,12 +2,10 @@ package pubnub
     
     import (
     	"fmt"
    +	"github.com/pubnub/go/v7/crypto"
     	"io"
     	"net/http"
     	"net/url"
    -	"strconv"
    -
    -	"github.com/pubnub/go/v7/utils"
     )
     
     var emptyDownloadFileResponse *PNDownloadFileResponse
    @@ -93,32 +91,32 @@ func (b *downloadFileBuilder) Execute() (*PNDownloadFileResponse, StatusResponse
     		stat.StatusCode = resp.StatusCode
     		return nil, stat, err
     	}
    -	contentLenEnc, err := strconv.ParseInt(string(resp.Header.Get("Content-Length")), 10, 64)
    -	if err != nil {
    -		b.opts.pubnub.Config.Log.Printf("err in parsing content length %s", err)
    -		return nil, stat, err
    -	}
     
     	var respDL *PNDownloadFileResponse
    -	if b.opts.CipherKey != "" {
    -		r, w := io.Pipe()
    -		utils.DecryptFile(b.opts.CipherKey, contentLenEnc, resp.Body, w)
    +	if b.opts.CipherKey == "" && b.opts.pubnub.getCryptoModule() == nil {
     		respDL = &PNDownloadFileResponse{
    -			File: r,
    +			File: resp.Body,
    +		}
    +	} else {
    +		var e error
    +		cryptoModule := b.opts.pubnub.getCryptoModule()
    +		if b.opts.CipherKey != "" {
    +			cryptoModule, e = crypto.NewLegacyCryptoModule(b.opts.CipherKey, true)
    +			if e != nil {
    +				return nil, stat, e
    +			}
     		}
     
    -	} else if b.opts.pubnub.Config.CipherKey != "" {
    -		r, w := io.Pipe()
    -		utils.DecryptFile(b.opts.pubnub.Config.CipherKey, contentLenEnc, resp.Body, w)
    +		r, e := cryptoModule.DecryptStream(resp.Body)
    +		if e != nil {
    +			return nil, stat, e
    +		}
     		respDL = &PNDownloadFileResponse{
     			File: r,
     		}
     
    -	} else {
    -		respDL = &PNDownloadFileResponse{
    -			File: resp.Body,
    -		}
     	}
    +
     	return respDL, stat, nil
     }
     
    
  • files_send_file_to_s3.go+19 6 modified
    @@ -3,6 +3,7 @@ package pubnub
     import (
     	"bytes"
     	"encoding/json"
    +	"github.com/pubnub/go/v7/crypto"
     	"io"
     	"io/ioutil"
     	"mime/multipart"
    @@ -11,7 +12,6 @@ import (
     	"os"
     
     	"github.com/pubnub/go/v7/pnerr"
    -	"github.com/pubnub/go/v7/utils"
     )
     
     var emptySendFileToS3Response *PNSendFileToS3Response
    @@ -131,17 +131,30 @@ func (o *sendFileToS3Opts) buildBodyMultipartFileUpload() (bytes.Buffer, *multip
     		return bytes.Buffer{}, writer, s, errFilePart
     	}
     
    -	if o.CipherKey != "" {
    -		utils.EncryptFile(o.CipherKey, []byte{}, filePart, o.File)
    -	} else if o.pubnub.Config.CipherKey != "" {
    -		utils.EncryptFile(o.pubnub.Config.CipherKey, []byte{}, filePart, o.File)
    -	} else {
    +	if o.CipherKey == "" && o.pubnub.getCryptoModule() == nil {
     		_, errIOCopy := io.Copy(filePart, o.File)
     
     		if errIOCopy != nil {
     			o.pubnub.Config.Log.Printf("ERROR: io Copy error: %s\n", errIOCopy.Error())
     			return bytes.Buffer{}, writer, s, errIOCopy
     		}
    +	} else {
    +		var e error
    +		cryptoModule := o.pubnub.getCryptoModule()
    +
    +		if o.CipherKey != "" {
    +			cryptoModule, e = crypto.NewLegacyCryptoModule(o.CipherKey, true)
    +			if e != nil {
    +				o.pubnub.Config.Log.Printf("ERROR: %s\n", e.Error())
    +				return bytes.Buffer{}, writer, s, e
    +			}
    +		}
    +
    +		e = encryptStreamAndCopyTo(cryptoModule, o.File, filePart)
    +		if e != nil {
    +			o.pubnub.Config.Log.Printf("ERROR: %s\n", e.Error())
    +			return bytes.Buffer{}, writer, s, e
    +		}
     	}
     
     	errWriterClose := writer.Close()
    
  • fire_request.go+20 14 modified
    @@ -157,15 +157,18 @@ func (o *fireOpts) buildPath() (string, error) {
     	var message []byte
     	var err error
     
    -	if cipherKey := o.pubnub.Config.CipherKey; cipherKey != "" {
    -		msg := utils.EncryptString(cipherKey, string(message), o.pubnub.Config.UseRandomInitializationVector)
    -
    -		o.Message = []byte(msg)
    -	}
    -
    -	message, err = utils.ValueAsString(o.Message)
    -	if err != nil {
    -		return "", err
    +	if o.pubnub.getCryptoModule() != nil {
    +		var msg string
    +		if msg, err = serializeEncryptAndSerialize(o.pubnub.getCryptoModule(), o.Message, o.Serialize); err != nil {
    +			o.pubnub.Config.Log.Printf("error in serializing: %v\n", err)
    +			return "", err
    +		}
    +		message = []byte(msg)
    +	} else {
    +		message, err = utils.ValueAsString(o.Message)
    +		if err != nil {
    +			return "", err
    +		}
     	}
     
     	return fmt.Sprintf(publishGetPath,
    @@ -212,7 +215,7 @@ func (o *fireOpts) buildBody() ([]byte, error) {
     			if err != nil {
     				return []byte{}, err
     			}
    -			msg = []byte(m)
    +			msg = m
     		} else {
     			if s, ok := o.Message.(string); ok {
     				msg = []byte(s)
    @@ -222,13 +225,16 @@ func (o *fireOpts) buildBody() ([]byte, error) {
     			}
     		}
     
    -		if cipherKey := o.pubnub.Config.CipherKey; cipherKey != "" {
    -			enc := utils.EncryptString(cipherKey, string(msg), o.pubnub.Config.UseRandomInitializationVector)
    -			msg, err := utils.ValueAsString(enc)
    +		if o.pubnub.getCryptoModule() != nil {
    +			enc, err := encryptString(o.pubnub.getCryptoModule(), string(msg))
    +			if err != nil {
    +				return []byte{}, err
    +			}
    +			m, err := utils.ValueAsString(enc)
     			if err != nil {
     				return []byte{}, err
     			}
    -			return []byte(msg), nil
    +			return m, nil
     		}
     		return msg, nil
     	}
    
  • fire_request_test.go+1 1 modified
    @@ -308,7 +308,7 @@ func TestFireGetAllParameters(t *testing.T) {
     }
     func TestFireGetAllParametersCipher(t *testing.T) {
     	message := "test"
    -	AssertSuccessFireGetAllParameters(t, "%22c3dSanMrRnc4ZnNNT1BEaGFnZmd1QT09%22", message, "enigma")
    +	AssertSuccessFireGetAllParameters(t, "%22%2B3AfkVAl8saHsXJdtOhRVQ%3D%3D%22", message, "enigma")
     }
     
     func TestFirePostAllParameters(t *testing.T) {
    
  • go.mod+1 0 modified
    @@ -6,6 +6,7 @@ require (
     	github.com/brianolson/cbor_go v1.0.0
     	github.com/cucumber/godog v0.12.0
     	github.com/google/uuid v1.3.0
    +	github.com/joho/godotenv v1.5.1 // indirect
     	github.com/stretchr/testify v1.7.0
     	golang.org/x/net v0.8.0
     	golang.org/x/text v0.8.0 // indirect
    
  • go.sum+2 0 modified
    @@ -112,6 +112,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
     github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
     github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
     github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
    +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
    +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
     github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
     github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
     github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
    
  • history_request.go+2 2 modified
    @@ -224,7 +224,7 @@ func getHistoryItemsWithoutTimetoken(historyResponseRaw []byte, o *historyOpts,
     
     	for i, v := range historyResponseItems {
     		o.pubnub.Config.Log.Println(v)
    -		items[i].Message, _ = parseCipherInterface(v, o.pubnub.Config)
    +		items[i].Message, _ = parseCipherInterface(v, o.pubnub.Config, o.pubnub.getCryptoModule())
     	}
     	return items, nil
     }
    @@ -237,7 +237,7 @@ func getHistoryItemsWithTimetoken(historyResponseItems []HistoryResponseItem, o
     	for i, v := range historyResponseItems {
     		if v.Message != nil {
     			o.pubnub.Config.Log.Println(v.Message)
    -			items[i].Message, _ = parseCipherInterface(v.Message, o.pubnub.Config)
    +			items[i].Message, _ = parseCipherInterface(v.Message, o.pubnub.Config, o.pubnub.getCryptoModule())
     
     			o.pubnub.Config.Log.Println(v.Timetoken)
     			items[i].Timetoken = v.Timetoken
    
  • history_request_test.go+50 26 modified
    @@ -15,7 +15,7 @@ var (
     	fakeResponseState = StatusResponse{}
     )
     
    -func initHistoryOpts() *historyOpts {
    +func (pn *PubNub) initHistoryOpts() *historyOpts {
     	opts := newHistoryOpts(pubnub, pubnub.ctx)
     	opts.Channel = "ch"
     	opts.Start = int64(100000)
    @@ -25,6 +25,7 @@ func initHistoryOpts() *historyOpts {
     	opts.Reverse = false
     	opts.Count = 3
     	opts.IncludeTimetoken = true
    +	opts.pubnub = pn
     	return opts
     }
     
    @@ -201,7 +202,9 @@ func TestHistoryResponseParsingStringMessages(t *testing.T) {
     
     	jsonString := []byte(`[["hey-1","hey-two","hey-1","hey-1","hey-1","hey0","hey1","hey2","hey3","hey4","hey5","hey6","hey7","hey8","hey9","hey10","hey0","hey1","hey2","hey3","hey4","hey5","hey6","hey7","hey8","hey9","hey10","hey0","hey1","hey2","hey3","hey4","hey5","hey6","hey7","hey8","hey9","hey10"],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -216,8 +219,9 @@ func TestHistoryResponseParsingStringWithTimetoken(t *testing.T) {
     	assert := assert.New(t)
     
     	jsonString := []byte(`[[{"timetoken":15232761410327866,"message":"hey-1"},{"timetoken":15232761410327866,"message":"hey-2"},{"timetoken":15232761410327866,"message":"hey-3"}],15232761410327866,15232761410327866]`)
    +	pn := NewPubNubDemo()
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(15232761410327866), resp.StartTimetoken)
    @@ -236,8 +240,9 @@ func TestHistoryResponseParsingInt(t *testing.T) {
     	assert := assert.New(t)
     
     	jsonString := []byte(`[[1,2,3,4,5,6,7],14991775432719844,14991868111600528]`)
    +	pn := NewPubNubDemo()
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -252,7 +257,8 @@ func TestHistoryResponseParsingInt1(t *testing.T) {
     	int1 := int(1)
     	jsonString := []byte(fmt.Sprintf("[[%d],14991775432719844,14991868111600528]", int1))
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -266,19 +272,21 @@ func TestHistoryResponseParsingSlice(t *testing.T) {
     	assert := assert.New(t)
     
     	jsonString := []byte(`[[[1,2,3],["one","two","three"]],14991775432719844,14991868111600528]`)
    +	pn := NewPubNubDemo()
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
     }
     
     func TestHistoryResponseParsingMap(t *testing.T) {
     	assert := assert.New(t)
    -	pnconfig.CipherKey = ""
    +
     	jsonString := []byte(`[[{"one":1,"two":2},{"three":3,"four":4}],14991775432719844,14991868111600528]`)
    +	pn := NewPubNubDemo()
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -291,13 +299,14 @@ func TestHistoryResponseParsingMap(t *testing.T) {
     
     func TestHistoryPNOther(t *testing.T) {
     	assert := assert.New(t)
    -	pnconfig.CipherKey = "testCipher"
    -	pnconfig.UseRandomInitializationVector = false
    +	pn := NewPubNubDemo()
    +	pn.Config.CipherKey = "testCipher"
    +	pn.Config.UseRandomInitializationVector = false
     
     	int64Val := int64(14991775432719844)
     	jsonString := []byte(`[[{"pn_other":"ven1bo79fk88nq5EIcnw/N9RmGzLeeWMnsabr1UL3iw="},1,"a",1.1,false,14991775432719844],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -331,11 +340,13 @@ func TestHistoryPNOther(t *testing.T) {
     
     func TestHistoryPNOtherYay(t *testing.T) {
     	assert := assert.New(t)
    -	pnconfig.CipherKey = "enigma"
    +	pn := NewPubNubDemo()
    +	pn.Config.UseRandomInitializationVector = false
    +	pn.Config.CipherKey = "enigma"
     	int64Val := int64(14991775432719844)
     	jsonString := []byte(`[[{"pn_other":"Wi24KS4pcTzvyuGOHubiXg=="},1,"a",1.1,false,14991775432719844],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -369,7 +380,8 @@ func TestHistoryResponseParsingSliceInMapWithTimetoken(t *testing.T) {
     
     	jsonString := []byte(`[[{"message":[1,2,3,["one","two","three"]],"timetoken":1111}],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -385,7 +397,8 @@ func TestHistoryResponseParsingMapInSlice(t *testing.T) {
     
     	jsonString := []byte(`[[[{"one":"two","three":[5,6]}]],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(14991775432719844), resp.StartTimetoken)
    @@ -400,12 +413,14 @@ func TestHistoryResponseParsingMapInSlice(t *testing.T) {
     
     func TestHistoryEncrypt(t *testing.T) {
     	assert := assert.New(t)
    +	pnconfig := NewDemoConfig()
     	pnconfig.CipherKey = "testCipher"
    -	pubnub = NewPubNub(pnconfig)
    +	pnconfig.UseRandomInitializationVector = false
    +	pubnub := NewPubNub(pnconfig)
     
     	jsonString := []byte(`[["MnwzPGdVgz2osQCIQJviGg=="],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pubnub.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	messages := resp.Messages
    @@ -415,11 +430,13 @@ func TestHistoryEncrypt(t *testing.T) {
     
     func TestHistoryEncryptSlice(t *testing.T) {
     	assert := assert.New(t)
    -	pnconfig.CipherKey = "testCipher"
    +	pn := NewPubNubDemo()
    +	pn.Config.CipherKey = "testCipher"
    +	pn.Config.UseRandomInitializationVector = false
     
     	jsonString := []byte(`[["gwkdY8qcv60GM/PslArWQsdXrQ6LwJD2HoaEfy0CjMc="],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	messages := resp.Messages
    @@ -436,11 +453,13 @@ func TestHistoryEncryptSlice(t *testing.T) {
     
     func TestHistoryEncryptMap(t *testing.T) {
     	assert := assert.New(t)
    -	pnconfig.CipherKey = "testCipher"
    +	pn := NewPubNubDemo()
    +	pn.Config.CipherKey = "testCipher"
    +	pn.Config.UseRandomInitializationVector = false
     
     	jsonString := []byte(`[["wIC13nvJcI4vBtWNFVUu0YDiqREr9kavB88xeyWTweDS363Yl84RCWqOHWTol4aY"],14991775432719844,14991868111600528]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	messages := resp.Messages
    @@ -464,7 +483,8 @@ func TestHistoryResponseMeta(t *testing.T) {
     
     	jsonString := []byte(`[[{"message":"my-message","meta":{"m1":"n1","m2":"n2"}}],15699986472636251,15699986472636251]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(15699986472636251), resp.StartTimetoken)
    @@ -482,7 +502,8 @@ func TestHistoryResponseMetaAndTT(t *testing.T) {
     
     	jsonString := []byte(`[[{"message":"my-message","meta":{"m1":"n1","m2":"n2"},"timetoken":15699986472636251}],15699986472636251,15699986472636251]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Nil(err)
     
     	assert.Equal(int64(15699986472636251), resp.StartTimetoken)
    @@ -501,7 +522,8 @@ func TestHistoryResponseError(t *testing.T) {
     
     	jsonString := []byte(`s`)
     
    -	_, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	_, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Equal("pubnub/parsing: Error unmarshalling response: {s}", err.Error())
     }
     
    @@ -510,7 +532,8 @@ func TestHistoryResponseStartTTError(t *testing.T) {
     
     	jsonString := []byte(`[[{"message":[1,2,3,["one","two","three"]],"timetoken":1111}],"s","a"]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Equal(int64(0), resp.StartTimetoken)
     	assert.Equal(int64(0), resp.EndTimetoken)
     	assert.Nil(err)
    @@ -522,7 +545,8 @@ func TestHistoryResponseEndTTError(t *testing.T) {
     
     	jsonString := []byte(`[[{"message":[1,2,3,["one","two","three"]],"timetoken":1111}],121324,"a"]`)
     
    -	resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
    +	pn := NewPubNubDemo()
    +	resp, _, err := newHistoryResponse(jsonString, pn.initHistoryOpts(), fakeResponseState)
     	assert.Equal(int64(121324), resp.StartTimetoken)
     	assert.Equal(int64(0), resp.EndTimetoken)
     	assert.Nil(err)
    
  • publish_file_message.go+2 2 modified
    @@ -201,7 +201,7 @@ func (o *publishFileMessageOpts) buildPath() (string, error) {
     		}
     	}
     
    -	if cipherKey := o.pubnub.Config.CipherKey; cipherKey != "" {
    +	if o.pubnub.getCryptoModule() != nil {
     		var msg string
     		var p *publishBuilder
     		if o.context() != nil {
    @@ -211,7 +211,7 @@ func (o *publishFileMessageOpts) buildPath() (string, error) {
     		}
     		p.opts.Message = o.Message
     
    -		msg, errJSONMarshal := p.opts.encryptProcessing(cipherKey)
    +		msg, errJSONMarshal := p.opts.encryptProcessing()
     		if errJSONMarshal != nil {
     			return "", errJSONMarshal
     		}
    
  • publish_request.go+9 9 modified
    @@ -198,13 +198,13 @@ func (o *publishOpts) validate() error {
     	return nil
     }
     
    -func (o *publishOpts) encryptProcessing(cipherKey string) (string, error) {
    +func (o *publishOpts) encryptProcessing() (string, error) {
     	var msg string
     	var errJSONMarshal error
     
     	o.pubnub.Config.Log.Println("EncryptString: encrypting", fmt.Sprintf("%s", o.Message))
     	if o.pubnub.Config.DisablePNOtherProcessing {
    -		if msg, errJSONMarshal = utils.SerializeEncryptAndSerialize(o.Message, cipherKey, o.Serialize, o.pubnub.Config.UseRandomInitializationVector); errJSONMarshal != nil {
    +		if msg, errJSONMarshal = serializeEncryptAndSerialize(o.pubnub.getCryptoModule(), o.Message, o.Serialize); errJSONMarshal != nil {
     			o.pubnub.Config.Log.Printf("error in serializing: %v\n", errJSONMarshal)
     			return "", errJSONMarshal
     		}
    @@ -218,7 +218,7 @@ func (o *publishOpts) encryptProcessing(cipherKey string) (string, error) {
     
     			if ok {
     				o.pubnub.Config.Log.Println(ok, msgPart)
    -				encMsg, errJSONMarshal := utils.SerializeAndEncrypt(msgPart, cipherKey, o.Serialize, o.pubnub.Config.UseRandomInitializationVector)
    +				encMsg, errJSONMarshal := serializeAndEncrypt(o.pubnub.getCryptoModule(), msgPart, o.Serialize)
     				if errJSONMarshal != nil {
     					o.pubnub.Config.Log.Printf("error in serializing: %v\n", errJSONMarshal)
     					return "", errJSONMarshal
    @@ -231,14 +231,14 @@ func (o *publishOpts) encryptProcessing(cipherKey string) (string, error) {
     				}
     				msg = string(jsonEncBytes)
     			} else {
    -				if msg, errJSONMarshal = utils.SerializeEncryptAndSerialize(o.Message, cipherKey, o.Serialize, o.pubnub.Config.UseRandomInitializationVector); errJSONMarshal != nil {
    +				if msg, errJSONMarshal = serializeEncryptAndSerialize(o.pubnub.getCryptoModule(), o.Message, o.Serialize); errJSONMarshal != nil {
     					o.pubnub.Config.Log.Printf("error in serializing: %v\n", errJSONMarshal)
     					return "", errJSONMarshal
     				}
     			}
     			break
     		default:
    -			if msg, errJSONMarshal = utils.SerializeEncryptAndSerialize(o.Message, cipherKey, o.Serialize, o.pubnub.Config.UseRandomInitializationVector); errJSONMarshal != nil {
    +			if msg, errJSONMarshal = serializeEncryptAndSerialize(o.pubnub.getCryptoModule(), o.Message, o.Serialize); errJSONMarshal != nil {
     				o.pubnub.Config.Log.Printf("error in serializing: %v\n", errJSONMarshal)
     				return "", errJSONMarshal
     			}
    @@ -261,8 +261,8 @@ func (o *publishOpts) buildPath() (string, error) {
     	var msg string
     	var errJSONMarshal error
     
    -	if cipherKey := o.pubnub.Config.CipherKey; cipherKey != "" {
    -		if msg, errJSONMarshal = o.encryptProcessing(cipherKey); errJSONMarshal != nil {
    +	if o.pubnub.getCryptoModule() != nil {
    +		if msg, errJSONMarshal = o.encryptProcessing(); errJSONMarshal != nil {
     			return "", errJSONMarshal
     		}
     
    @@ -336,8 +336,8 @@ func (o *publishOpts) buildQuery() (*url.Values, error) {
     
     func (o *publishOpts) buildBody() ([]byte, error) {
     	if o.UsePost {
    -		if cipherKey := o.pubnub.Config.CipherKey; cipherKey != "" {
    -			msg, errJSONMarshal := o.encryptProcessing(cipherKey)
    +		if o.pubnub.getCryptoModule() != nil {
    +			msg, errJSONMarshal := o.encryptProcessing()
     			if errJSONMarshal != nil {
     				return []byte{}, errJSONMarshal
     			}
    
  • publish_request_test.go+12 10 modified
    @@ -191,10 +191,10 @@ func AssertSuccessPublishGetMeta(t *testing.T, expectedString string, message in
     	assert.Nil(err1)
     }
     
    -func AssertSuccessPublishPost(t *testing.T, expectedBody string, message interface{}) {
    +func AssertSuccessPublishPost(t *testing.T, pn *PubNub, expectedBody string, message interface{}) {
     	assert := assert.New(t)
     
    -	opts := newPublishOpts(pubnub, pubnub.ctx)
    +	opts := newPublishOpts(pn, pn.ctx)
     	opts.Channel = "ch"
     	opts.Message = message
     	opts.UsePost = true
    @@ -206,7 +206,7 @@ func AssertSuccessPublishPost(t *testing.T, expectedBody string, message interfa
     		Path: path,
     	}
     	h.AssertPathsEqual(t,
    -		"/publish/pub_key/sub_key/0/ch/0",
    +		"/publish/demo/demo/0/ch/0",
     		u.EscapedPath(), []int{})
     
     	body, err := opts.buildBody()
    @@ -270,6 +270,8 @@ func TestPublishMixedGet(t *testing.T) {
     }
     
     func TestPublishMixedPost(t *testing.T) {
    +	pn := NewPubNub(NewDemoConfig())
    +
     	type msg struct {
     		One   string `json:"one"`
     		Two   string `json:"two"`
    @@ -282,16 +284,16 @@ func TestPublishMixedPost(t *testing.T) {
     	msgMap["two"] = "hey2"
     	msgMap["three"] = "hey3"
     
    -	AssertSuccessPublishPost(t, "12", 12)
    -	AssertSuccessPublishPost(t, "\"hey\"", "hey")
    -	AssertSuccessPublishPost(t, "true", true)
    -	AssertSuccessPublishPost(t, "[\"hey1\",\"hey2\",\"hey3\"]",
    +	AssertSuccessPublishPost(t, pn, "12", 12)
    +	AssertSuccessPublishPost(t, pn, "\"hey\"", "hey")
    +	AssertSuccessPublishPost(t, pn, "true", true)
    +	AssertSuccessPublishPost(t, pn, "[\"hey1\",\"hey2\",\"hey3\"]",
     		[]string{"hey1", "hey2", "hey3"})
    -	AssertSuccessPublishPost(t, "[1,2,3]", []int{1, 2, 3})
    -	AssertSuccessPublishPost(t,
    +	AssertSuccessPublishPost(t, pn, "[1,2,3]", []int{1, 2, 3})
    +	AssertSuccessPublishPost(t, pn,
     		"{\"one\":\"hey1\",\"two\":\"hey2\",\"three\":\"hey3\"}",
     		msgStruct)
    -	AssertSuccessPublishPost(t,
    +	AssertSuccessPublishPost(t, pn,
     		"{\"one\":\"hey1\",\"three\":\"hey3\",\"two\":\"hey2\"}",
     		msgMap)
     }
    
  • pubnub.go+32 2 modified
    @@ -2,6 +2,7 @@ package pubnub
     
     import (
     	"fmt"
    +	"github.com/pubnub/go/v7/crypto"
     	"io/ioutil"
     	"log"
     	"net/http"
    @@ -14,7 +15,7 @@ import (
     // Default constants
     const (
     	// Version :the version of the SDK
    -	Version = "7.1.2"
    +	Version = "7.2.0"
     	// MaxSequence for publish messages
     	MaxSequence = 65535
     )
    @@ -74,6 +75,26 @@ type PubNub struct {
     	ctx                  Context
     	cancel               func()
     	tokenManager         *TokenManager
    +	previousCipherKey    string
    +	previousIvFlag       bool
    +}
    +
    +// TODO this needs to be tested
    +func (pn *PubNub) getCryptoModule() crypto.CryptoModule {
    +	pn.Lock()
    +	defer pn.Unlock()
    +	if pn.previousCipherKey == pn.Config.CipherKey && pn.previousIvFlag == pn.Config.UseRandomInitializationVector {
    +		return pn.Config.CryptoModule
    +	}
    +
    +	if pn.Config != nil && pn.Config.CipherKey != "" {
    +		pn.Config.CryptoModule, _ = crypto.NewLegacyCryptoModule(pn.Config.CipherKey, pn.Config.UseRandomInitializationVector)
    +		return pn.Config.CryptoModule
    +	} else if pn.Config != nil && pn.Config.CipherKey == "" {
    +		pn.Config.CryptoModule = nil
    +		return pn.Config.CryptoModule
    +	}
    +	return nil
     }
     
     // Publish is used to send a message to all subscribers of a channel.
    @@ -752,16 +773,25 @@ func NewPubNub(pnconf *Config) *PubNub {
     	if pnconf.Log == nil {
     		pnconf.Log = log.New(ioutil.Discard, "", log.Ldate|log.Ltime|log.Lshortfile)
     	}
    -	pnconf.Log.Println(fmt.Sprintf("PubNub Go v4 SDK: %s\npnconf: %v\n%s\n%s\n%s", Version, pnconf, runtime.Version(), runtime.GOARCH, runtime.GOOS))
    +	pnconf.Log.Println(fmt.Sprintf("PubNub Go v7 SDK: %s\npnconf: %v\n%s\n%s\n%s", Version, pnconf, runtime.Version(), runtime.GOARCH, runtime.GOOS))
     
     	utils.CheckUUID(pnconf.UUID)
     	pn := &PubNub{
     		Config:              pnconf,
     		nextPublishSequence: 0,
     		ctx:                 ctx,
     		cancel:              cancel,
    +		previousIvFlag:      pnconf.UseRandomInitializationVector,
    +		previousCipherKey:   pnconf.CipherKey,
     	}
     
    +	if pnconf.CipherKey != "" {
    +		var e error
    +		pn.Config.CryptoModule, e = crypto.NewLegacyCryptoModule(pnconf.CipherKey, pnconf.UseRandomInitializationVector)
    +		if e != nil {
    +			panic(e)
    +		}
    +	}
     	pn.subscriptionManager = newSubscriptionManager(pn, ctx)
     	pn.heartbeatManager = newHeartbeatManager(pn, ctx)
     	pn.telemetryManager = newTelemetryManager(pnconf.MaximumLatencyDataAge, ctx)
    
  • pubnub_test.go+27 0 modified
    @@ -1,6 +1,7 @@
     package pubnub
     
     import (
    +	"github.com/pubnub/go/v7/crypto"
     	"testing"
     
     	"github.com/stretchr/testify/assert"
    @@ -20,6 +21,32 @@ func TestInitializer(t *testing.T) {
     	assert.Equal("my_secret_key", pubnub.Config.SecretKey)
     }
     
    +func TestCryptoModuleChangesWhenCipherKeyChanges(t *testing.T) {
    +	pubnub := NewPubNubDemo()
    +
    +	cryptoModule, _ := crypto.NewAesCbcCryptoModule("my_cipher_key", true)
    +	pubnub.Config.CryptoModule = cryptoModule
    +	a := assert.New(t)
    +
    +	a.Equal(cryptoModule, pubnub.getCryptoModule())
    +
    +	pubnub.Config.CipherKey = "new_cipher_key"
    +	a.NotEqual(cryptoModule, pubnub.getCryptoModule())
    +}
    +
    +func TestCryptoModuleChangesIfIvFlagChanges(t *testing.T) {
    +	pubnub := NewPubNubDemo()
    +
    +	cryptoModule, _ := crypto.NewAesCbcCryptoModule("my_cipher_key", true)
    +	pubnub.Config.CryptoModule = cryptoModule
    +	a := assert.New(t)
    +
    +	a.Equal(cryptoModule, pubnub.getCryptoModule())
    +
    +	pubnub.Config.UseRandomInitializationVector = false
    +	a.NotEqual(cryptoModule, pubnub.getCryptoModule())
    +}
    +
     func TestDemoInitializer(t *testing.T) {
     	demo := NewPubNubDemo()
     
    
  • .pubnub.yml+9 2 modified
    @@ -1,6 +1,13 @@
     ---
    -version: v7.1.2
    +version: v7.2.0
     changelog:
    +  - date: 2023-10-16
    +    version: v7.2.0
    +    changes:
    +      - type: feature
    +        text: "Update the crypto module structure and add enhanced AES-CBC cryptor."
    +      - type: bug
    +        text: "Improved security of crypto implementation by increasing the cipher key entropy by a factor of two."
       - date: 2023-05-11
         version: v7.1.2
         changes:
    @@ -733,7 +740,7 @@ sdks:
                 distribution-type: package
                 distribution-repository: GitHub
                 package-name: Go
    -            location: https://github.com/pubnub/go/releases/tag/v7.1.2
    +            location: https://github.com/pubnub/go/releases/tag/v7.2.0
                 requires:
                   -
                     name: "Go"
    
  • subscription_manager.go+7 8 modified
    @@ -3,14 +3,13 @@ package pubnub
     import (
     	"encoding/json"
     	"errors"
    +	"github.com/pubnub/go/v7/crypto"
     	"net/http"
     	"reflect"
     	"strconv"
     	"strings"
     	"sync"
     	"time"
    -
    -	"github.com/pubnub/go/v7/utils"
     )
     
     // SubscriptionManager Events:
    @@ -662,7 +661,7 @@ func processNonPresencePayload(m *SubscriptionManager, payload subscribeMessage,
     		m.listenerManager.announceMessageActionsEvent(pnMessageActionsEvent)
     	case PNMessageTypeFile:
     		var err error
    -		messagePayload, err = parseCipherInterface(payload.Payload, m.pubnub.Config)
    +		messagePayload, err = parseCipherInterface(payload.Payload, m.pubnub.Config, m.pubnub.getCryptoModule())
     		if err != nil {
     			pnStatus := &PNStatus{
     				Category:         PNBadRequestCategory,
    @@ -681,7 +680,7 @@ func processNonPresencePayload(m *SubscriptionManager, payload subscribeMessage,
     		m.listenerManager.announceFile(pnFilesEvent)
     	default:
     		var err error
    -		messagePayload, err = parseCipherInterface(payload.Payload, m.pubnub.Config)
    +		messagePayload, err = parseCipherInterface(payload.Payload, m.pubnub.Config, m.pubnub.getCryptoModule())
     		if err != nil {
     			pnStatus := &PNStatus{
     				Category:         PNBadRequestCategory,
    @@ -959,8 +958,8 @@ func createPNMessageResult(messagePayload interface{}, actualCh, subscribedCh, c
     // cipherKey: cipher key to use to decrypt.
     //
     // returns the decrypted data as interface and error.
    -func parseCipherInterface(data interface{}, pnConf *Config) (interface{}, error) {
    -	if pnConf.CipherKey != "" {
    +func parseCipherInterface(data interface{}, pnConf *Config, module crypto.CryptoModule) (interface{}, error) {
    +	if module != nil {
     		pnConf.Log.Println("reflect.TypeOf(data).Kind()", reflect.TypeOf(data).Kind(), data)
     		switch v := data.(type) {
     		case map[string]interface{}:
    @@ -970,7 +969,7 @@ func parseCipherInterface(data interface{}, pnConf *Config) (interface{}, error)
     				msg, ok := v["pn_other"].(string)
     				if ok {
     					pnConf.Log.Println("v[pn_other]", v["pn_other"], v, msg)
    -					decrypted, errDecryption := utils.DecryptString(pnConf.CipherKey, msg, pnConf.UseRandomInitializationVector)
    +					decrypted, errDecryption := decryptString(module, msg)
     					if errDecryption != nil {
     						pnConf.Log.Println(errDecryption, msg)
     						return v, errDecryption
    @@ -993,7 +992,7 @@ func parseCipherInterface(data interface{}, pnConf *Config) (interface{}, error)
     			return v, nil
     		case string:
     			var intf interface{}
    -			decrypted, errDecryption := utils.DecryptString(pnConf.CipherKey, data.(string), pnConf.UseRandomInitializationVector)
    +			decrypted, errDecryption := decryptString(module, data.(string))
     			if errDecryption != nil {
     				pnConf.Log.Println(errDecryption, intf)
     				intf = data
    
  • subscription_manager_test.go+18 18 modified
    @@ -22,7 +22,7 @@ func TestParseCipherInterfaceCipherWithCipher(t *testing.T) {
     	pn.Config.CipherKey = "enigma"
     	pn.Config.UseRandomInitializationVector = false
     
    -	intf, err := parseCipherInterface(s, pn.Config)
    +	intf, err := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Nil(err)
     	assert.Equal("yay!", intf.(string))
    @@ -35,7 +35,7 @@ func TestParseCipherInterfacePlainWithCipher(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = "enigma"
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Equal("yay!", intf.(string))
     }
    @@ -47,7 +47,7 @@ func TestParseCipherInterfaceCipherWithDiffCipher(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = "test"
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Equal("Wi24KS4pcTzvyuGOHubiXg==", intf.(string))
     
    @@ -60,7 +60,7 @@ func TestParseCipherInterfacePlainWithDiffCipher(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = "test"
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Equal("yay!", intf.(string))
     }
    @@ -72,7 +72,7 @@ func TestParseCipherInterfaceCipherWithoutCipher(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = ""
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Equal("Wi24KS4pcTzvyuGOHubiXg==", intf.(string))
     }
    @@ -84,7 +84,7 @@ func TestParseCipherInterfacePlainWithoutCipher(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = ""
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Equal("yay!", intf.(string))
     }
    @@ -99,7 +99,7 @@ func TestParseCipherInterfacePlainWithCipherStruct(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = "enigma"
     
    -	intf, err := parseCipherInterface(s, pn.Config)
    +	intf, err := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Nil(err)
     	if msg, ok := intf.(customStruct); !ok {
    @@ -121,7 +121,7 @@ func TestParseCipherInterfacePlainWithoutCipherStruct(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = ""
     
    -	intf, err := parseCipherInterface(s, pn.Config)
    +	intf, err := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	assert.Nil(err)
     	if msg, ok := intf.(customStruct); !ok {
    @@ -148,7 +148,7 @@ func TestParseCipherInterfacePlainWithCipherMapPNOther(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = "enigma"
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("12345", msg["not_other"])
    @@ -174,7 +174,7 @@ func TestParseCipherInterfacePlainWithoutCipherMapPNOther(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = ""
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("12345", msg["not_other"])
    @@ -196,7 +196,7 @@ func TestParseCipherInterfaceCipherWithoutCipherStringPNOther(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = ""
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("1234", msg["not_other"])
    @@ -219,7 +219,7 @@ func TestParseCipherInterfaceCipherWithCipherStringPNOther(t *testing.T) {
     	pn.Config.CipherKey = "enigma"
     	pn.Config.UseRandomInitializationVector = false
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("1234", msg["not_other"])
    @@ -238,7 +238,7 @@ func TestParseCipherInterfaceCipherWithoutCipherStruct(t *testing.T) {
     	pn.Config.CipherKey = "enigma"
     	pn.Config.UseRandomInitializationVector = false
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     	msg := intf.(map[string]interface{})
     	assert.Equal("hi!", msg["Foo"])
     
    @@ -256,7 +256,7 @@ func TestParseCipherInterfaceCipherWithCipherStructPNOther(t *testing.T) {
     	pn.Config.CipherKey = "enigma"
     	pn.Config.UseRandomInitializationVector = false
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("1234", msg["not_other"])
    @@ -278,7 +278,7 @@ func TestParseCipherInterfaceCipherWithOtherCipherStructPNOther(t *testing.T) {
     	pn := NewPubNub(NewDemoConfig())
     	pn.Config.CipherKey = "test"
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("1234", msg["not_other"])
    @@ -298,7 +298,7 @@ func TestParseCipherInterfaceCipherWithCipherStructPNOtherDisable(t *testing.T)
     	pn.Config.UseRandomInitializationVector = false
     	pn.Config.CipherKey = "enigma"
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.(map[string]interface{})
     	assert.Equal("1234", msg["not_other"])
    @@ -314,7 +314,7 @@ func TestParseCipherInterfaceCipherWithIntSlice(t *testing.T) {
     	pn.Config.DisablePNOtherProcessing = true
     	pn.Config.CipherKey = ""
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     
     	msg := intf.([]int)
     	assert.Equal(1, msg[0])
    @@ -329,7 +329,7 @@ func TestParseCipherInterfaceCipherWithoutCipherStruct2(t *testing.T) {
     	pn.Config.CipherKey = "enigma"
     	pn.Config.UseRandomInitializationVector = false
     
    -	intf, _ := parseCipherInterface(s, pn.Config)
    +	intf, _ := parseCipherInterface(s, pn.Config, pn.getCryptoModule())
     	msg := intf.(map[string]interface{})
     	assert.Equal("12345", msg["not_other"])
     	if msgOther, ok := msg["pn_other"].(map[string]interface{}); !ok {
    
  • tests/contract/contract_test.go+5 5 modified
    @@ -13,8 +13,8 @@ var tagsFilter string
     var format string
     
     func TestMain(m *testing.M) {
    -	flag.StringVar(&path, "path", "", "Path to feature files")
    -	flag.StringVar(&tagsFilter, "tagsFilter", "~@skip && ~@na=go && ~@beta", "Tags filter")
    +	flag.StringVar(&path, "path", "../../../sdk-specifications/features", "Path to feature files")
    +	flag.StringVar(&tagsFilter, "tagsFilter", "~@skip && ~@na=go && @beta && @featureSet=cryptoModule", "Tags filter")
     	flag.StringVar(&format, "format", "pretty", "Output formatter")
     	flag.Parse()
     	if path == "" {
    @@ -28,9 +28,9 @@ func TestFeatures(t *testing.T) {
     	suite := godog.TestSuite{
     		ScenarioInitializer: InitializeScenario,
     		Options: &godog.Options{
    -			Format: format,
    -			Paths:  []string{path},
    -			Tags:   tagsFilter,
    +			Format:   format,
    +			Paths:    []string{path},
    +			Tags:     tagsFilter,
     			TestingT: t, // Testing instance that will run subtests.
     		},
     	}
    
  • tests/contract/crypto_state_test.go+52 0 added
    @@ -0,0 +1,52 @@
    +package contract
    +
    +import (
    +	"context"
    +	"errors"
    +	"github.com/pubnub/go/v7/crypto"
    +	"io"
    +	"os"
    +)
    +
    +type cryptoStateKey struct{}
    +
    +type cryptoState struct {
    +	cryptorNames        []string
    +	cryptorName         string
    +	cipherKey           string
    +	cryptoFeaturePath   string
    +	randomIv            bool
    +	result              []byte
    +	resultReader        io.Reader
    +	err                 error
    +	cryptoModule        crypto.CryptoModule
    +	legacyCodeCipherKey string
    +	legacyCodeRandomIv  bool
    +}
    +
    +func (c *cryptoState) getCryptoModule() (crypto.CryptoModule, error) {
    +
    +	if len(c.cryptorNames) > 0 && c.cryptorNames[0] == "legacy" {
    +		return crypto.NewLegacyCryptoModule(c.cipherKey, c.randomIv)
    +	} else if len(c.cryptorNames) > 0 && c.cryptorNames[0] == "acrh" {
    +		return crypto.NewAesCbcCryptoModule(c.cipherKey, c.randomIv)
    +	} else if c.cryptorName != "" {
    +		cryptor, e := createCryptor(c.cryptorName, c.cipherKey, c.randomIv)
    +		c.cryptoModule = crypto.NewCryptoModule(cryptor, []crypto.Cryptor{})
    +		return c.cryptoModule, e
    +	} else {
    +		return nil, errors.New("I don't know how to create this crypto module")
    +	}
    +}
    +
    +func (c *cryptoState) openAssetFile(filename string) (io.ReadCloser, error) {
    +	return os.Open(c.cryptoFeaturePath + "/assets/" + filename)
    +}
    +
    +func getCryptoState(ctx context.Context) *cryptoState {
    +	return ctx.Value(cryptoStateKey{}).(*cryptoState)
    +}
    +
    +func newCryptoState(cryptoFeaturePath string) *cryptoState {
    +	return &cryptoState{cryptoFeaturePath: cryptoFeaturePath, randomIv: false}
    +}
    
  • tests/contract/crypto_steps_test.go+243 0 added
    @@ -0,0 +1,243 @@
    +package contract
    +
    +import (
    +	"bufio"
    +	"bytes"
    +	"context"
    +	"encoding/base64"
    +	"errors"
    +	"fmt"
    +	"github.com/pubnub/go/v7/crypto"
    +	"github.com/pubnub/go/v7/utils"
    +	"io"
    +	"os"
    +	"strings"
    +)
    +
    +func cryptor(ctx context.Context, cryptor string) error {
    +	cryptoState := getCryptoState(ctx)
    +	cryptoState.cryptorName = cryptor
    +	return nil
    +}
    +
    +func cryptoModuleWithDefaultAndAdditionalLegacyCryptors(ctx context.Context, cryptor1 string, cryptor2 string) error {
    +	cryptoState := getCryptoState(ctx)
    +	cryptoState.cryptorNames = append(cryptoState.cryptorNames, cryptor1, cryptor2)
    +	return nil
    +}
    +
    +func decryptedFileContentEqualToFileContent(ctx context.Context, filename string) error {
    +	cryptoState := getCryptoState(ctx)
    +
    +	file, e := os.Open(cryptoState.cryptoFeaturePath + "/assets/" + filename)
    +	if e != nil {
    +		return e
    +	}
    +
    +	fileContent, e := io.ReadAll(file)
    +	if e != nil {
    +		return e
    +	}
    +
    +	if cryptoState.result != nil {
    +		if !bytes.Equal(cryptoState.result, fileContent) {
    +			return errors.New("decrypted file content not equal to file content")
    +		}
    +	} else if cryptoState.resultReader != nil {
    +		resultContent, e := io.ReadAll(cryptoState.resultReader)
    +		if e != nil {
    +			return e
    +		}
    +		if !bytes.Equal(resultContent, fileContent) {
    +			return errors.New("decrypted file content not equal to file content")
    +		}
    +	} else {
    +		return errors.New("no result")
    +	}
    +	return nil
    +}
    +
    +func legacyCodeWithCipherKeyAndConstantVector(ctx context.Context, cipherKey string) error {
    +	cryptoState := getCryptoState(ctx)
    +	cryptoState.legacyCodeCipherKey = cipherKey
    +	cryptoState.legacyCodeRandomIv = false
    +	return nil
    +}
    +
    +func legacyCodeWithCipherKeyAndRandomVector(ctx context.Context, cipherKey string) error {
    +	cryptoState := getCryptoState(ctx)
    +	cryptoState.legacyCodeCipherKey = cipherKey
    +	cryptoState.legacyCodeRandomIv = true
    +	return nil
    +}
    +
    +func iDecryptFileAs(ctx context.Context, filename string, decryptionType string) error {
    +
    +	cryptoState := getCryptoState(ctx)
    +
    +	module, e := cryptoState.getCryptoModule()
    +	if e != nil {
    +		return e
    +	}
    +	file, e := os.Open(cryptoState.cryptoFeaturePath + "/assets/" + filename)
    +	if e != nil {
    +		return e
    +	}
    +
    +	if decryptionType == "stream" {
    +		cryptoState.resultReader, cryptoState.err = module.DecryptStream(bufio.NewReader(file))
    +	} else {
    +		fileContent, e := io.ReadAll(file)
    +		if e != nil {
    +			return e
    +		}
    +		cryptoState.result, cryptoState.err = module.Decrypt(fileContent)
    +	}
    +	return nil
    +}
    +
    +func iDecryptFile(ctx context.Context, filename string) error {
    +
    +	cryptoState := getCryptoState(ctx)
    +
    +	module, e := cryptoState.getCryptoModule()
    +	if e != nil {
    +		return e
    +	}
    +	file, e := cryptoState.openAssetFile(filename)
    +	if e != nil {
    +		return e
    +	}
    +
    +	_, e = module.DecryptStream(file)
    +	if e != nil {
    +		cryptoState.err = e
    +	}
    +	return nil
    +}
    +
    +func createCryptor(name string, cipherKey string, randomIv bool) (crypto.Cryptor, error) {
    +	if name == "acrh" {
    +		return crypto.NewAesCbcCryptor(cipherKey)
    +	} else if name == "legacy" {
    +		return crypto.NewLegacyCryptor(cipherKey, randomIv)
    +	} else {
    +		return nil, fmt.Errorf("unknown crypto algorithm %s", name)
    +	}
    +}
    +
    +func iEncryptFileAs(ctx context.Context, filename string, encryptionType string) error {
    +	cryptoState := getCryptoState(ctx)
    +	module, e := cryptoState.getCryptoModule()
    +	if e != nil {
    +		return e
    +	}
    +	file, e := cryptoState.openAssetFile(filename)
    +	if e != nil {
    +		return e
    +	}
    +	if encryptionType == "stream" {
    +		cryptoState.resultReader, cryptoState.err = module.EncryptStream(bufio.NewReader(file))
    +	} else {
    +		content, e := io.ReadAll(file)
    +		if e != nil {
    +			return e
    +		}
    +		cryptoState.result, cryptoState.err = module.Encrypt(content)
    +
    +	}
    +	return nil
    +}
    +
    +func iReceiveDecryptionError(ctx context.Context) error {
    +	cryptoState := getCryptoState(ctx)
    +	if cryptoState.err != nil {
    +		if strings.HasPrefix(cryptoState.err.Error(), "decryption error") {
    +			return nil
    +		} else {
    +			return cryptoState.err
    +		}
    +	} else {
    +		return errors.New("expected error")
    +	}
    +}
    +
    +func iReceiveSuccess(ctx context.Context) error {
    +	cryptoState := getCryptoState(ctx)
    +	if cryptoState.err != nil {
    +		return cryptoState.err
    +	}
    +	return nil
    +}
    +
    +func iReceiveUnknownCryptoError(ctx context.Context) error {
    +	cryptoState := getCryptoState(ctx)
    +	if cryptoState.err != nil {
    +		if strings.Contains(cryptoState.err.Error(), "unknown crypto error") {
    +			return nil
    +		} else {
    +			return cryptoState.err
    +		}
    +	} else {
    +		return errors.New("expected error")
    +	}
    +}
    +
    +func withCipherKey(ctx context.Context, cipherKey string) error {
    +	cryptoState := getCryptoState(ctx)
    +	cryptoState.cipherKey = cipherKey
    +	return nil
    +}
    +
    +func randomIv(iv string) bool {
    +	if iv == "constant" {
    +		return false
    +	} else if iv == "random" {
    +		return true
    +	} else {
    +		return false
    +	}
    +}
    +
    +func withVector(ctx context.Context, iv string) error {
    +	cryptoState := getCryptoState(ctx)
    +	cryptoState.randomIv = randomIv(iv)
    +	return nil
    +}
    +
    +func successfullyDecryptAnEncryptedFileWithLegacyCode(ctx context.Context) error {
    +	cryptoState := getCryptoState(ctx)
    +	if cryptoState.result != nil {
    +		_, e := utils.DecryptString(cryptoState.legacyCodeCipherKey, base64.StdEncoding.EncodeToString(cryptoState.result), cryptoState.legacyCodeRandomIv)
    +		return e
    +	} else if cryptoState.resultReader != nil {
    +		buffer := &closerBuffer{bytes.NewBuffer(nil)}
    +
    +		utils.DecryptFile(cryptoState.legacyCodeCipherKey, 0, cryptoState.resultReader, buffer)
    +		_, e := io.ReadAll(buffer)
    +		return e
    +	} else {
    +		return errors.New("no result")
    +	}
    +}
    +
    +func iReceiveEncryptionError(ctx context.Context) error {
    +	cryptoState := getCryptoState(ctx)
    +	if cryptoState.err != nil {
    +		if strings.HasPrefix(cryptoState.err.Error(), "encryption error") {
    +			return nil
    +		} else {
    +			return cryptoState.err
    +		}
    +	} else {
    +		return errors.New("expected error")
    +	}
    +}
    +
    +type closerBuffer struct {
    +	*bytes.Buffer
    +}
    +
    +func (c *closerBuffer) Close() error {
    +	return nil
    +}
    
  • tests/contract/steps_mapping_test.go+18 0 modified
    @@ -55,4 +55,22 @@ func MapSteps(ctx *godog.ScenarioContext) {
     	ctx.Step(`^the error detail message is not empty$`, theErrorDetailMessageIsNotEmpty)
     	ctx.Step(`^the result is successful$`, theResultIsSuccessful)
     	ctx.Step(`^the token string \'(.*)\'$`, theTokenString)
    +
    +	ctx.Step(`^Decrypted file content equal to the \'(.*)\' file content$`, decryptedFileContentEqualToFileContent)
    +	ctx.Step(`^I decrypt \'(.*)\' file as \'(.*)\'$`, iDecryptFileAs)
    +	ctx.Step(`^I decrypt \'(.*)\' file$`, iDecryptFile)
    +	ctx.Step(`^I encrypt \'(.*)\' file as \'(.*)\'$`, iEncryptFileAs)
    +	ctx.Step(`^I receive \'decryption error\'$`, iReceiveDecryptionError)
    +	ctx.Step(`^I receive \'success\'$`, iReceiveSuccess)
    +	ctx.Step(`^I receive \'unknown cryptor error\'$`, iReceiveUnknownCryptoError)
    +	ctx.Step(`^I receive \'encryption error\'$`, iReceiveEncryptionError)
    +	ctx.Step(`^with \'(.*)\' cipher key$`, withCipherKey)
    +	ctx.Step(`^with \'(.*)\' vector$`, withVector)
    +
    +	ctx.Step(`^Crypto module with \'(.*)\' cryptor$`, cryptor)
    +	ctx.Step(`^Crypto module with default \'(.*)\' and additional \'(.*)\' cryptors$`, cryptoModuleWithDefaultAndAdditionalLegacyCryptors)
    +	ctx.Step(`^Crypto module with \'(.*)\' cryptor$`, cryptor)
    +	ctx.Step(`^Legacy code with \'(.*)\' cipher key and \'constant\' vector$`, legacyCodeWithCipherKeyAndConstantVector)
    +	ctx.Step(`^Legacy code with \'(.*)\' cipher key and \'random\' vector$`, legacyCodeWithCipherKeyAndRandomVector)
    +	ctx.Step(`^Successfully decrypt an encrypted file with legacy code$`, successfullyDecryptAnEncryptedFileWithLegacyCode)
     }
    
  • tests/contract/utils_test.go+3 0 modified
    @@ -46,6 +46,7 @@ func before(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
     
     	newCtx = context.WithValue(newCtx, commonStateKey{}, commonState)
     	newCtx = context.WithValue(newCtx, accessStateKey{}, accessState)
    +	newCtx = context.WithValue(newCtx, cryptoStateKey{}, newCryptoState(contractTestConfig.featurePath+"/encryption"))
     
     	if !contractTestConfig.serverMock {
     		return newCtx, nil
    @@ -116,6 +117,7 @@ type contractTestConfig struct {
     	serverMock   bool
     	hostPort     string
     	secure       bool
    +	featurePath  string
     }
     
     func newContractTestConfig() (contractTestConfig, error) {
    @@ -135,6 +137,7 @@ func newContractTestConfig() (contractTestConfig, error) {
     		hostPort:     getenvWithDefault("HOST_PORT", "localhost:8090"),
     		serverMock:   serverMock,
     		secure:       secure,
    +		featurePath:  path,
     	}, err
     }
     
    
  • tests/e2e/files_test.go+0 34 modified
    @@ -289,40 +289,6 @@ func FileUploadCommon(t *testing.T, useCipher bool, customCipher string, filepat
     
     }
     
    -func TestFileEncryptionDecryption(t *testing.T) {
    -	assert := assert.New(t)
    -	filepathInput := "file_upload_test.txt"
    -	filepathOutput := "file_upload_test_output.txt"
    -	filepathSampleOutput := "file_upload_sample_encrypted.txt"
    -	filepathOutputDec := "file_upload_dec_output.txt"
    -
    -	out, _ := os.Create(filepathOutput)
    -	file, err := os.Open(filepathInput)
    -	if err != nil {
    -		panic(err)
    -	}
    -	utils.EncryptFile("enigma", []byte{133, 126, 158, 123, 43, 95, 96, 90, 215, 178, 17, 73, 166, 130, 79, 156}, out, file)
    -	fileText, _ := ioutil.ReadFile(filepathOutput)
    -
    -	fileTextSample, _ := ioutil.ReadFile(filepathSampleOutput)
    -	assert.Equal(string(fileTextSample), string(fileText))
    -
    -	outDec, _ := os.Open(filepathSampleOutput)
    -	fi, _ := outDec.Stat()
    -	contentLenEnc := fi.Size()
    -	defer outDec.Close()
    -
    -	fileDec, _ := os.Create(filepathOutputDec)
    -	defer fileDec.Close()
    -	r, w := io.Pipe()
    -	utils.DecryptFile("enigma", contentLenEnc, outDec, w)
    -	io.Copy(fileDec, r)
    -	fileTextDec, _ := ioutil.ReadFile(filepathOutputDec)
    -	fileTextIn, _ := ioutil.ReadFile(filepathInput)
    -	assert.Equal(fileTextIn, fileTextDec)
    -
    -}
    -
     func unpadPKCS7(data []byte) ([]byte, error) {
     	blocklen := 16
     	if len(data)%blocklen != 0 || len(data) == 0 {
    
  • tests/e2e/file_upload_dec_output.txt+0 352 removed
    @@ -1,352 +0,0 @@
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -File upload test
    -
    -Junk text start
    -==============================
    -
    -
    -Oh to talking improve produce in limited offices fifteen an. Wicket branch to answer do we. Place are decay men hours tiled. If or of ye throwing friendly required. Marianne interest in exertion as. Offering my branched confined oh dashwood. 
    -
    -Their could can widen ten she any. As so we smart those money in. Am wrote up whole so tears sense oh. Absolute required of reserved in offering no. How sense found our those gay again taken the. Improved property reserved disposal do offering me. 
    -
    -Same an quit most an. Admitting an mr disposing sportsmen. Tried on cause no spoil arise plate. Longer valley get esteem use led six. Middletons resolution advantages expression themselves partiality so me at. West none hope if sing oh sent tell is. 
    -
    -==============================
    -==============================
    -
    -END
    \ No newline at end of file
    
  • tests/e2e/file_upload_sample_encrypted.txt+0 0 removed
  • tests/e2e/file_upload_test_output.txt+0 0 modified
  • tests/e2e/helper.go+8 0 modified
    @@ -5,11 +5,13 @@ import (
     	"encoding/binary"
     	"errors"
     	"fmt"
    +	"github.com/joho/godotenv"
     	"io"
     	"math/rand"
     	"net"
     	"net/http"
     	"os"
    +	"testing"
     	"time"
     
     	pubnub "github.com/pubnub/go/v7"
    @@ -42,6 +44,8 @@ func seedRand() {
     }
     
     func init() {
    +	godotenv.Load("../../.env")
    +
     	seedRand()
     	config = pubnub.NewConfigWithUserId(pubnub.UserId(pubnub.GenerateUUID()))
     	config.PublishKey = os.Getenv("PUBLISH_KEY")
    @@ -96,6 +100,10 @@ func logInTest(format string, a ...interface{}) (n int, err error) {
     	return 0, nil
     }
     
    +func checkForAsserted(t *testing.T, maxTime, intervalTime time.Duration, fun func() error) {
    +
    +}
    +
     func checkFor(assert *assert.Assertions, maxTime, intervalTime time.Duration, fun func() error) {
     	maxTimeout := time.NewTimer(maxTime)
     	interval := time.NewTicker(intervalTime)
    
  • tests/e2e/objectsV2_test.go+115 1843 modified
    @@ -1,1920 +1,192 @@
     package e2e
     
     import (
    -	"fmt"
    +	pubnub "github.com/pubnub/go/v7"
    +	"github.com/stretchr/testify/assert"
     	"log"
     	"os"
    -	"sync"
    +	"reflect"
     	"testing"
    -	"time"
    -
    -	pubnub "github.com/pubnub/go/v7"
    -	"github.com/stretchr/testify/assert"
     )
     
    -func ActivateWithPAMV2() *pubnub.PubNub {
    -	pn := pubnub.NewPubNub(pamConfigCopy())
    -	return pn
    -}
    -
    -func RunGrantV2(pn *pubnub.PubNub, users, channels []string, read, write, manage, del, create, createPattern bool) []string {
    -	authkey := randomized("authkey")
    -
    -	res, _, _ := pn.Grant().
    -		Read(true).Write(true).Manage(true).
    -		Get(true).Update(true).Join(true).
    -		UUIDs(append(users, channels...)).AuthKeys([]string{authkey}).
    -		Execute()
    -
    -	if res != nil {
    -		return []string{authkey}
    -	}
    -	return []string{}
    -}
    -
    -func SetPNV2(pn, pn2 *pubnub.PubNub, tokens []string) {
    -	pn.Config.SubscribeKey = pn2.Config.SubscribeKey
    -	pn.Config.PublishKey = pn2.Config.PublishKey
    -	pn.Config.SecretKey = ""
    -	pn.Config.Origin = pn2.Config.Origin
    -	pn.Config.Secure = pn2.Config.Secure
    -
    -	pn.Config.AuthKey = tokens[0]
    -}
    -
    -func TestObjectsV2CreateUpdateGetDeleteUUID(t *testing.T) {
    -	ObjectsCreateUpdateGetDeleteUUIDCommon(t, false, false)
    -}
    -
    -func TestObjectsV2CreateUpdateGetDeleteUUIDWithPAM(t *testing.T) {
    -	ObjectsCreateUpdateGetDeleteUUIDCommon(t, true, false)
    -}
    -
    -// func TestObjectsV2CreateUpdateGetDeleteUUIDWithPAMWithoutSecKey(t *testing.T) {
    -// 	ObjectsCreateUpdateGetDeleteUUIDCommon(t, true, true)
    -// }
    -
    -func ObjectsCreateUpdateGetDeleteUUIDCommon(t *testing.T, withPAM, runWithoutSecretKey bool) {
    -
    -	assert := assert.New(t)
    +func TestObjectsV2ChannelMetadataSetUpdateGetRemove(t *testing.T) {
    +	a := assert.New(t)
     
     	pn := pubnub.NewPubNub(configCopy())
     
    -	id := randomized("testuser")
    -	if withPAM {
    -		pn2 := ActivateWithPAMV2()
    -		if runWithoutSecretKey {
    -			tokens := RunGrantV2(pn2, []string{id}, []string{}, true, true, true, true, true, true)
    -			SetPNV2(pn, pn2, tokens)
    -		} else {
    -			pn = pn2
    -			RunGrantV2(pn, []string{id}, []string{}, true, true, false, true, true, false)
    -		}
    -
    -	}
    -
    -	if enableDebuggingInTests {
    -		pn.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
    -	}
    -
    -	name := randomized("name")
    -	extid := "extid"
    -	purl := "profileurl"
    -	email := "email"
    -
    -	custom := make(map[string]interface{})
    -	custom["a"] = "b"
    -	custom["c"] = "d"
    -
    -	incl := []pubnub.PNUUIDMetadataInclude{
    -		pubnub.PNUUIDMetadataIncludeCustom,
    -	}
    -
    -	res, st, err := pn.SetUUIDMetadata().Include(incl).UUID(id).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(err)
    -	assert.Equal(200, st.StatusCode)
    -	if res != nil {
    -		assert.Equal(id, res.Data.ID)
    -		assert.Equal(name, res.Data.Name)
    -		assert.Equal(extid, res.Data.ExternalID)
    -		assert.Equal(purl, res.Data.ProfileURL)
    -		assert.Equal(email, res.Data.Email)
    -		// assert.NotNil(res.Data.Created)
    -		assert.NotNil(res.Data.Updated)
    -		assert.NotNil(res.Data.ETag)
    -		assert.Equal("b", res.Data.Custom["a"])
    -		assert.Equal("d", res.Data.Custom["c"])
    -	}
    -
    -	email = "email2"
    -
    -	res2, st2, err2 := pn.SetUUIDMetadata().Include(incl).UUID(id).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(err2)
    -	assert.Equal(200, st2.StatusCode)
    -	if res2 != nil {
    -		assert.Equal(id, res2.Data.ID)
    -		assert.Equal(name, res2.Data.Name)
    -		assert.Equal(extid, res2.Data.ExternalID)
    -		assert.Equal(purl, res2.Data.ProfileURL)
    -		assert.Equal(email, res2.Data.Email)
    -		// assert.Equal(res.Data.Created, res2.Data.Created)
    -		assert.NotNil(res2.Data.Updated)
    -		assert.NotNil(res2.Data.ETag)
    -		assert.Equal("b", res2.Data.Custom["a"])
    -		assert.Equal("d", res2.Data.Custom["c"])
    -	}
    -
    -	res3, st3, err3 := pn.GetUUIDMetadata().UUID(id).Include(incl).Execute()
    -	assert.Nil(err3)
    -	assert.Equal(200, st3.StatusCode)
    -	if res3 != nil {
    -		assert.Equal(id, res3.Data.ID)
    -		assert.Equal(name, res3.Data.Name)
    -		assert.Equal(extid, res3.Data.ExternalID)
    -		assert.Equal(purl, res3.Data.ProfileURL)
    -		assert.Equal(email, res3.Data.Email)
    -		// assert.Equal(res.Data.Created, res3.Data.Created)
    -		if res2 != nil {
    -			assert.Equal(res2.Data.Updated, res3.Data.Updated)
    -			assert.Equal(res2.Data.ETag, res3.Data.ETag)
    -		}
    -		assert.Equal("b", res3.Data.Custom["a"])
    -		assert.Equal("d", res3.Data.Custom["c"])
    -	}
    -
    -	//getusers
    -	sort := []string{"updated:desc"}
    -	if withPAM {
    -		res6, st6, err6 := pn.GetAllUUIDMetadata().Include(incl).Sort(sort).Limit(100).Count(true).Execute()
    -		assert.Nil(err6)
    -		assert.Equal(200, st6.StatusCode)
    -		assert.True(res6.TotalCount > 0)
    -		found := false
    -		for i := range res6.Data {
    -			if res6.Data[i].ID == id {
    -				assert.Equal(name, res6.Data[i].Name)
    -				assert.Equal(extid, res6.Data[i].ExternalID)
    -				assert.Equal(purl, res6.Data[i].ProfileURL)
    -				assert.Equal(email, res6.Data[i].Email)
    -				// assert.Equal(res.Data.Created, res6.Data[i].Created)
    -				if res2 != nil {
    -					assert.Equal(res2.Data.Updated, res6.Data[i].Updated)
    -					assert.Equal(res2.Data.ETag, res6.Data[i].ETag)
    -				}
    -				assert.Equal("b", res6.Data[i].Custom["a"])
    -				assert.Equal("d", res6.Data[i].Custom["c"])
    -				found = true
    -			}
    -		}
    -		assert.True(found)
    -
    -		res6F, st6F, err6F := pn.GetAllUUIDMetadata().Include(incl).Limit(100).Filter("name == '" + name + "'").Count(true).Execute()
    -		assert.Nil(err6F)
    -		assert.Equal(200, st6F.StatusCode)
    -		assert.True(res6F.TotalCount > 0)
    -		foundF := false
    -		for i := range res6F.Data {
    -			//fmt.Println(res6F.Data[i], id)
    -			if res6F.Data[i].ID == id {
    -				assert.Equal(name, res6F.Data[i].Name)
    -				assert.Equal(extid, res6F.Data[i].ExternalID)
    -				assert.Equal(purl, res6F.Data[i].ProfileURL)
    -				assert.Equal(email, res6F.Data[i].Email)
    -				// assert.Equal(res.Data.Created, res6F.Data[i].Created)
    -				assert.Equal(res2.Data.Updated, res6F.Data[i].Updated)
    -				assert.Equal(res2.Data.ETag, res6F.Data[i].ETag)
    -				assert.Equal("b", res6F.Data[i].Custom["a"])
    -				assert.Equal("d", res6F.Data[i].Custom["c"])
    -				foundF = true
    -			}
    -		}
    -		assert.True(foundF)
    -	}
    -
    -	//delete
    -	res5, st5, err5 := pn.RemoveUUIDMetadata().UUID(id).Execute()
    -	assert.Nil(err5)
    -	assert.Equal(200, st5.StatusCode)
    -	if res5 != nil {
    -		assert.Nil(res5.Data)
    -	}
    -
    -	//getuser
    -	res4, st4, err4 := pn.GetUUIDMetadata().Include(incl).UUID(id).Execute()
    -	assert.NotNil(err4)
    -	if res5 != nil {
    -		assert.Nil(res4)
    -	}
    -	assert.Equal(404, st4.StatusCode)
    -
    -}
    -
    -func TestObjectsV2CreateUpdateGetDeleteChannel(t *testing.T) {
    -	ObjectsCreateUpdateGetDeleteChannelCommon(t, false, false)
    -}
    -
    -func TestObjectsV2CreateUpdateGetDeleteChannelWithPAM(t *testing.T) {
    -	ObjectsCreateUpdateGetDeleteChannelCommon(t, true, false)
    -}
    -
    -// func TestObjectsV2CreateUpdateGetDeleteChannelWithPAMWithoutSecKey(t *testing.T) {
    -// 	ObjectsCreateUpdateGetDeleteChannelCommon(t, true, true)
    -// }
    -
    -func ObjectsCreateUpdateGetDeleteChannelCommon(t *testing.T, withPAM, runWithoutSecretKey bool) {
    -	assert := assert.New(t)
    -
    -	pn := pubnub.NewPubNub(configCopy())
     	id := randomized("testchannel")
    -
    -	if withPAM {
    -		pn2 := ActivateWithPAMV2()
    -		if runWithoutSecretKey {
    -			tokens := RunGrantV2(pn2, []string{}, []string{id}, true, true, false, true, true, true)
    -			SetPNV2(pn, pn2, tokens)
    -		} else {
    -			pn = pn2
    -			RunGrantV2(pn, []string{}, []string{id}, true, true, false, true, true, false)
    -		}
    -
    -	}
     	if enableDebuggingInTests {
     		pn.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
     	}
     
     	name := randomized("name")
     	desc := "desc"
    -
    -	custom := make(map[string]interface{})
    -	custom["a"] = "b"
    -	custom["c"] = "d"
    +	custom := map[string]interface{}{"a": "b", "c": "d"}
     
     	incl := []pubnub.PNChannelMetadataInclude{
     		pubnub.PNChannelMetadataIncludeCustom,
     	}
     
    +	defer removeChannelMetadata(a, pn, id)
     	res, st, err := pn.SetChannelMetadata().Include(incl).Channel(id).Name(name).Description(desc).Custom(custom).Execute()
    -	assert.Nil(err)
    -	assert.Equal(200, st.StatusCode)
    +
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
     	if res != nil {
    -		assert.Equal(id, res.Data.ID)
    -		assert.Equal(name, res.Data.Name)
    -		assert.Equal(desc, res.Data.Description)
    -		// assert.NotNil(res.Data.Created)
    -		assert.NotNil(res.Data.Updated)
    -		assert.NotNil(res.Data.ETag)
    -		assert.Equal("b", res.Data.Custom["a"])
    -		assert.Equal("d", res.Data.Custom["c"])
    +		a.Equal(id, res.Data.ID)
    +		a.Equal(name, res.Data.Name)
    +		a.Equal(desc, res.Data.Description)
    +		a.NotNil(res.Data.Updated)
    +		a.NotNil(res.Data.ETag)
    +		a.True(reflect.DeepEqual(custom, res.Data.Custom))
     	}
     
     	desc = "desc2"
     
    -	res2, st2, err2 := pn.SetChannelMetadata().Include(incl).Channel(id).Name(name).Description(desc).Custom(custom).Execute()
    -	assert.Nil(err2)
    -	assert.Equal(200, st2.StatusCode)
    -	if res2 != nil {
    -		assert.Equal(id, res2.Data.ID)
    -		assert.Equal(name, res2.Data.Name)
    -		assert.Equal(desc, res2.Data.Description)
    -		// assert.Equal(res.Data.Created, res2.Data.Created)
    -		assert.NotNil(res2.Data.Updated)
    -		assert.NotNil(res2.Data.ETag)
    -		assert.Equal("b", res2.Data.Custom["a"])
    -		assert.Equal("d", res2.Data.Custom["c"])
    -	}
    -
    -	res3, st3, err3 := pn.GetChannelMetadata().Include(incl).Channel(id).Execute()
    -	assert.Nil(err3)
    -	assert.Equal(200, st3.StatusCode)
    -	if res3 != nil {
    -		assert.Equal(id, res3.Data.ID)
    -		assert.Equal(name, res3.Data.Name)
    -		assert.Equal(desc, res3.Data.Description)
    -		// assert.Equal(res.Data.Created, res3.Data.Created)
    -		assert.Equal(res2.Data.Updated, res3.Data.Updated)
    -		assert.Equal(res2.Data.ETag, res3.Data.ETag)
    -		assert.Equal("b", res3.Data.Custom["a"])
    -		assert.Equal("d", res3.Data.Custom["c"])
    -	}
    -
    -	sort := []string{"updated:desc"}
    -	//getusers
    -	if withPAM {
    -		res6, st6, err6 := pn.GetAllChannelMetadata().Include(incl).Sort(sort).Limit(100).Count(true).Execute()
    -		assert.Nil(err6)
    -		assert.Equal(200, st6.StatusCode)
    -		found := false
    -		if res6 != nil {
    -			assert.True(res6.TotalCount > 0)
    -
    -			for i := range res6.Data {
    -				if res6.Data[i].ID == id {
    -					assert.Equal(name, res6.Data[i].Name)
    -					assert.Equal(desc, res6.Data[i].Description)
    -					// assert.Equal(res.Data.Created, res6.Data[i].Created)
    -					assert.Equal(res2.Data.Updated, res6.Data[i].Updated)
    -					assert.Equal(res2.Data.ETag, res6.Data[i].ETag)
    -					assert.Equal("b", res6.Data[i].Custom["a"])
    -					assert.Equal("d", res6.Data[i].Custom["c"])
    -					found = true
    -				}
    -			}
    -		}
    -		assert.True(found)
    -
    -		res6F, st6F, err6F := pn.GetAllChannelMetadata().Include(incl).Limit(100).Filter("name like '" + name + "*'").Count(true).Execute()
    -		assert.Nil(err6F)
    -		assert.Equal(200, st6F.StatusCode)
    -		foundF := false
    -		if res6F != nil {
    -			assert.True(res6F.TotalCount > 0)
    -
    -			for i := range res6F.Data {
    -				if res6F.Data[i].ID == id {
    -					assert.Equal(name, res6F.Data[i].Name)
    -					assert.Equal(desc, res6F.Data[i].Description)
    -					// assert.Equal(res.Data.Created, res6F.Data[i].Created)
    -					assert.Equal(res2.Data.Updated, res6F.Data[i].Updated)
    -					assert.Equal(res2.Data.ETag, res6F.Data[i].ETag)
    -					assert.Equal("b", res6F.Data[i].Custom["a"])
    -					assert.Equal("d", res6F.Data[i].Custom["c"])
    -					foundF = true
    -				}
    -			}
    -		}
    -		assert.True(foundF)
    -
    -	}
    -
    -	//delete
    -	res5, st5, err5 := pn.RemoveChannelMetadata().Channel(id).Execute()
    -	assert.Nil(err5)
    -	assert.Equal(200, st5.StatusCode)
    -	if res5 != nil {
    -		assert.Nil(res5.Data)
    -	}
    -
    -	//getuser
    -	res4, st4, err4 := pn.GetChannelMetadata().Include(incl).Channel(id).Execute()
    -	assert.NotNil(err4)
    -	if res4 != nil {
    -		assert.Nil(res4)
    +	res, st, err = pn.SetChannelMetadata().Include(incl).Channel(id).Name(name).Description(desc).Custom(custom).Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
    +	if res != nil {
    +		a.Equal(id, res.Data.ID)
    +		a.Equal(desc, res.Data.Description)
     	}
    -	assert.Equal(404, st4.StatusCode)
     
    -}
    +	_, st, err = pn.GetChannelMetadata().Include(incl).Channel(id).Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
     
    -func TestObjectsV2SetRemoveMembershipsV2(t *testing.T) {
    -	ObjectsSetRemoveMembershipsCommonV2(t, false, false)
     }
     
    -func TestObjectsV2SetRemoveMembershipsV2WithPAM(t *testing.T) {
    -	ObjectsSetRemoveMembershipsCommonV2(t, true, false)
    -}
    -
    -// PASSES after adding PAM checks for Update Members
    -// func TestObjectsV2SetRemoveMembershipsV2WithPAMWithoutSecKey(t *testing.T) {
    -// 	ObjectsSetRemoveMembershipsCommonV2(t, true, true)
    -// }
    -
    -func ObjectsSetRemoveMembershipsCommonV2(t *testing.T, withPAM, runWithoutSecretKey bool) {
    -	assert := assert.New(t)
    -
    -	limit := 100
    -	count := true
    -
    -	pn := pubnub.NewPubNub(configCopy())
    -
    -	userid := randomized("testuser1")
    -	channelid := randomized("testchannel")
    -
    -	if withPAM {
    -		pn2 := ActivateWithPAMV2()
    -		if runWithoutSecretKey {
    -			tokens := RunGrantV2(pn2, []string{userid}, []string{channelid}, true, true, true, true, true, true)
    -			SetPNV2(pn, pn2, tokens)
    -		} else {
    -			pn = pn2
    -			RunGrantV2(pn, []string{userid}, []string{channelid}, true, true, true, true, true, false)
    -		}
    -
    -	}
    -	if enableDebuggingInTests {
    -		pn.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
    -	}
    -
    -	name := randomized("name")
    -	extid := "extid"
    -	purl := "profileurl"
    -	email := "email"
    -
    -	custom := make(map[string]interface{})
    -	custom["a"] = "b"
    -	custom["c"] = "d"
    -
    -	inclUUID := []pubnub.PNUUIDMetadataInclude{
    -		pubnub.PNUUIDMetadataIncludeCustom,
    -	}
    -
    -	res, st, err := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(err)
    -	assert.Equal(200, st.StatusCode)
    +func removeChannelMetadata(a *assert.Assertions, pn *pubnub.PubNub, id string) {
    +	res, st, err := pn.RemoveChannelMetadata().Channel(id).Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
     	if res != nil {
    -		assert.Equal(userid, res.Data.ID)
    -		assert.Equal(name, res.Data.Name)
    -		assert.Equal(extid, res.Data.ExternalID)
    -		assert.Equal(purl, res.Data.ProfileURL)
    -		assert.Equal(email, res.Data.Email)
    -		// assert.NotNil(res.Data.Created)
    -		assert.NotNil(res.Data.Updated)
    -		assert.NotNil(res.Data.ETag)
    -		assert.Equal("b", res.Data.Custom["a"])
    -		assert.Equal("d", res.Data.Custom["c"])
    +		a.Nil(res.Data)
     	}
    -
    -	desc := "desc"
    -	custom2 := make(map[string]interface{})
    -	custom2["a1"] = "b1"
    -	custom2["c1"] = "d1"
    -
    -	inclChannel := []pubnub.PNChannelMetadataInclude{
    -		pubnub.PNChannelMetadataIncludeCustom,
    -	}
    -
    -	res2, st2, err2 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid).Name(name).Description(desc).Custom(custom2).Execute()
    -	assert.Nil(err2)
    -	assert.Equal(200, st2.StatusCode)
    -	//fmt.Println("res2-->", res2)
    -	if res2 != nil {
    -		assert.Equal(channelid, res2.Data.ID)
    -		assert.Equal(name, res2.Data.Name)
    -		assert.Equal(desc, res2.Data.Description)
    -		// assert.NotNil(res2.Data.Created)
    -		assert.NotNil(res2.Data.Updated)
    -		assert.NotNil(res2.Data.ETag)
    -		assert.Equal("b1", res2.Data.Custom["a1"])
    -		assert.Equal("d1", res2.Data.Custom["c1"])
    -	}
    -
    -	userid2 := randomized("testuser2")
    -
    -	_, st3, err3 := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid2).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(err3)
    -	assert.Equal(200, st3.StatusCode)
    -
    -	channelid2 := randomized("testchannel")
    -
    -	_, st4, err4 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid2).Name(name).Description(desc).Custom(custom2).Execute()
    -	assert.Nil(err4)
    -	assert.Equal(200, st4.StatusCode)
    -
    -	userid3 := randomized("testuser3")
    -
    -	_, stuser3, erruser3 := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid3).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(erruser3)
    -	assert.Equal(200, stuser3.StatusCode)
    -
    -	channelid3 := randomized("testchannel")
    -
    -	_, stchannel3, errchannel33 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid3).Name(name).Description(desc).Custom(custom2).Execute()
    -	assert.Nil(errchannel33)
    -	assert.Equal(200, stchannel3.StatusCode)
    -
    -	inclSm := []pubnub.PNChannelMembersInclude{
    -		pubnub.PNChannelMembersIncludeUUIDCustom,
    -		pubnub.PNChannelMembersIncludeUUID,
    -		pubnub.PNChannelMembersIncludeCustom,
    -	}
    -
    -	custom3 := make(map[string]interface{})
    -	custom3["a3"] = "b3"
    -	custom3["c3"] = "d3"
    -
    -	uuid := pubnub.PNChannelMembersUUID{
    -		ID: userid,
    -	}
    -
    -	in := pubnub.PNChannelMembersSet{
    -		UUID:   uuid,
    -		Custom: custom3,
    -	}
    -	uuid3 := pubnub.PNChannelMembersUUID{
    -		ID: userid3,
    -	}
    -
    -	inUser3 := pubnub.PNChannelMembersSet{
    -		UUID:   uuid3,
    -		Custom: custom3,
    -	}
    -
    -	inArr := []pubnub.PNChannelMembersSet{
    -		in,
    -		inUser3,
    -	}
    -
    -	//Add Channel Memberships
    -	sortSetChannelMembers := []string{"uuid.id:desc"}
    -	sort := []string{"updated:desc"}
    -
    -	resAdd, stAdd, errAdd := pn.SetChannelMembers().Channel(channelid).Sort(sortSetChannelMembers).Set(inArr).Include(inclSm).Limit(limit).Count(count).Execute()
    -	assert.Nil(errAdd)
    -	assert.Equal(200, stAdd.StatusCode)
    -	if errAdd == nil {
    -		sortMembers1 := false
    -		sortMembers2 := false
    -
    -		found := false
    -		assert.True(resAdd.TotalCount > 0)
    -		//fmt.Println("resAdd-->", resAdd)
    -
    -		for i := range resAdd.Data {
    -			if resAdd.Data[i].UUID.ID == userid {
    -				found = true
    -				assert.Equal(custom3["a3"], resAdd.Data[i].Custom["a3"])
    -				assert.Equal(custom3["c3"], resAdd.Data[i].Custom["c3"])
    -				assert.Equal(userid, resAdd.Data[i].UUID.ID)
    -				assert.Equal(name, resAdd.Data[i].UUID.Name)
    -				assert.Equal(extid, resAdd.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resAdd.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resAdd.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resAdd.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resAdd.Data[i].UUID.Custom["c"])
    -			}
    -		}
    -		if (resAdd.Data != nil) && (len(resAdd.Data) > 1) {
    -			sortMembers1 = (resAdd.Data[1].UUID.ID == userid)
    -			sortMembers2 = (resAdd.Data[0].UUID.ID == userid3)
    -			assert.True(sortMembers1)
    -			assert.True(sortMembers2)
    -		} else {
    -			assert.Fail("Sort ", "resAdd.Data null or ", len(resAdd.Data))
    -		}
    -
    -		assert.True(found)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMembers->", errAdd.Error())
    -		}
    -	}
    -
    -	//Update Channel Memberships
    -	if !withPAM {
    -
    -		custom4 := make(map[string]interface{})
    -		custom4["a2"] = "b2"
    -		custom4["c2"] = "d2"
    -
    -		up := pubnub.PNChannelMembersSet{
    -			UUID:   uuid,
    -			Custom: custom4,
    -		}
    -
    -		upArr := []pubnub.PNChannelMembersSet{
    -			up,
    -		}
    -
    -		resUp, stUp, errUp := pn.SetChannelMembers().Channel(channelid).Sort(sort).Set(upArr).Include(inclSm).Limit(limit).Count(count).Execute()
    -		assert.Nil(errUp)
    -		assert.Equal(200, stUp.StatusCode)
    -		if errUp == nil {
    -			assert.True(resUp.TotalCount > 0)
    -			foundUp := false
    -			for i := range resUp.Data {
    -				if resUp.Data[i].UUID.ID == userid {
    -					foundUp = true
    -					assert.Equal("b2", resUp.Data[i].Custom["a2"])
    -					assert.Equal("d2", resUp.Data[i].Custom["c2"])
    -					//assert.Equal(userid, resAdd.Data[i].UUID.ID)
    -					assert.Equal(name, resAdd.Data[i].UUID.Name)
    -					assert.Equal(extid, resAdd.Data[i].UUID.ExternalID)
    -					assert.Equal(purl, resAdd.Data[i].UUID.ProfileURL)
    -					assert.Equal(email, resAdd.Data[i].UUID.Email)
    -					assert.Equal(custom["a"], resAdd.Data[i].UUID.Custom["a"])
    -					assert.Equal(custom["c"], resAdd.Data[i].UUID.Custom["c"])
    -
    -				}
    -			}
    -			assert.True(foundUp)
    -		} else {
    -			if enableDebuggingInTests {
    -
    -				fmt.Println("ManageMembers->", errUp.Error())
    -			}
    -		}
    -	}
    -	//Get Channel Memberships
    -
    -	inclMemberships := []pubnub.PNMembershipsInclude{
    -		pubnub.PNMembershipsIncludeCustom,
    -		pubnub.PNMembershipsIncludeChannel,
    -		pubnub.PNMembershipsIncludeChannelCustom,
    -	}
    -
    -	//fmt.Println("GetMemberships ====>")
    -
    -	resGetMem, stGetMem, errGetMem := pn.GetMemberships().UUID(userid).Include(inclMemberships).Sort(sort).Limit(limit).Count(count).Execute()
    -	foundGetMem := false
    -	assert.Nil(errGetMem)
    -	if errGetMem == nil {
    -		for i := range resGetMem.Data {
    -			if resGetMem.Data[i].Channel.ID == channelid {
    -				foundGetMem = true
    -				assert.Equal(name, resGetMem.Data[i].Channel.Name)
    -				assert.Equal(desc, resGetMem.Data[i].Channel.Description)
    -				assert.Equal("b1", resGetMem.Data[i].Channel.Custom["a1"])
    -				assert.Equal("d1", resGetMem.Data[i].Channel.Custom["c1"])
    -				if withPAM {
    -					assert.Equal("b3", resGetMem.Data[i].Custom["a3"])
    -					assert.Equal("d3", resGetMem.Data[i].Custom["c3"])
    -				} else {
    -					assert.Equal("b2", resGetMem.Data[i].Custom["a2"])
    -					assert.Equal("d2", resGetMem.Data[i].Custom["c2"])
    -				}
    -			}
    -		}
    -		assert.Equal(200, stGetMem.StatusCode)
    -		assert.True(foundGetMem)
    -	} else {
    -		if enableDebuggingInTests {
    -			fmt.Println("GetMemberships->", errGetMem.Error())
    -		}
    -	}
    -
    -	//filterExp := fmt.Sprintf("custom.c3 == '%s' || custom.c2 == '%s'", "d3", "d2")
    -	filterExp := fmt.Sprintf("channel.name == '%s'", name)
    -
    -	//fmt.Println("GetMemberships ====>", filterExp)
    -
    -	resGetMemF, stGetMemF, errGetMemF := pn.GetMemberships().UUID(userid).Include(inclMemberships).Filter(filterExp).Limit(limit).Count(count).Execute()
    -	foundGetMemF := false
    -	assert.Nil(errGetMemF)
    -	if errGetMemF == nil {
    -		for i := range resGetMemF.Data {
    -			if resGetMemF.Data[i].Channel.ID == channelid {
    -				foundGetMemF = true
    -				assert.Equal(name, resGetMemF.Data[i].Channel.Name)
    -				assert.Equal(desc, resGetMemF.Data[i].Channel.Description)
    -				assert.Equal("b1", resGetMemF.Data[i].Channel.Custom["a1"])
    -				assert.Equal("d1", resGetMemF.Data[i].Channel.Custom["c1"])
    -				if withPAM {
    -					assert.Equal("b3", resGetMemF.Data[i].Custom["a3"])
    -					assert.Equal("d3", resGetMemF.Data[i].Custom["c3"])
    -				} else {
    -					assert.Equal("b2", resGetMemF.Data[i].Custom["a2"])
    -					assert.Equal("d2", resGetMemF.Data[i].Custom["c2"])
    -				}
    -			}
    -		}
    -		assert.Equal(200, stGetMemF.StatusCode)
    -		assert.True(foundGetMemF)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("GetMemberships->", errGetMemF.Error())
    -		}
    -	}
    -
    -	//Remove Channel Memberships
    -	re := pubnub.PNChannelMembersRemove{
    -		UUID: uuid,
    -	}
    -
    -	reArr := []pubnub.PNChannelMembersRemove{
    -		re,
    -	}
    -	resRem, stRem, errRem := pn.RemoveChannelMembers().Channel(channelid).Remove(reArr).Include(inclSm).Limit(limit).Count(count).Execute()
    -	assert.Nil(errRem)
    -	assert.Equal(200, stRem.StatusCode)
    -	//fmt.Println("====>stRem.StatusCode", stRem.StatusCode)
    -	if errRem == nil {
    -
    -		foundRem := false
    -		for i := range resRem.Data {
    -			if resRem.Data[i].UUID.ID == userid {
    -				foundRem = true
    -				assert.Equal("b2", resRem.Data[i].Custom["a2"])
    -				assert.Equal("d2", resRem.Data[i].Custom["c2"])
    -				assert.Equal(userid, resRem.Data[i].UUID.ID)
    -				assert.Equal(name, resRem.Data[i].UUID.Name)
    -				assert.Equal(extid, resRem.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resRem.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resRem.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resRem.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resRem.Data[i].UUID.Custom["c"])
    -
    -			}
    -		}
    -		assert.False(foundRem)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMembers->", errRem.Error())
    -		}
    -	}
    -
    -	channel2 := pubnub.PNMembershipsChannel{
    -		ID: channelid2,
    -	}
    -
    -	inMem := pubnub.PNMembershipsSet{
    -		Channel: channel2,
    -		Custom:  custom3,
    -	}
    -
    -	channel3 := pubnub.PNMembershipsChannel{
    -		ID: channelid3,
    -	}
    -
    -	inMemChannel3 := pubnub.PNMembershipsSet{
    -		Channel: channel3,
    -		Custom:  custom3,
    -	}
    -
    -	inArrMem := []pubnub.PNMembershipsSet{
    -		inMem,
    -		inMemChannel3,
    -	}
    -
    -	//Add user memberships
    -	resManageMemAdd, stManageMemAdd, errManageMemAdd := pn.SetMemberships().UUID(userid2).Set(inArrMem).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resManageMemAdd -->", resManageMemAdd)
    -	assert.Nil(errManageMemAdd)
    -	assert.Equal(200, stManageMemAdd.StatusCode)
    -	if errManageMemAdd == nil {
    -		foundManageMembers := false
    -		for i := range resManageMemAdd.Data {
    -			if resManageMemAdd.Data[i].Channel.ID == channelid2 {
    -				assert.Equal(channelid2, resManageMemAdd.Data[i].Channel.ID)
    -				assert.Equal(name, resManageMemAdd.Data[i].Channel.Name)
    -				assert.Equal(desc, resManageMemAdd.Data[i].Channel.Description)
    -				assert.Equal(custom2["a1"], resManageMemAdd.Data[i].Channel.Custom["a1"])
    -				assert.Equal(custom2["c1"], resManageMemAdd.Data[i].Channel.Custom["c1"])
    -				assert.Equal(custom3["a3"], resManageMemAdd.Data[i].Custom["a3"])
    -				assert.Equal(custom3["c3"], resManageMemAdd.Data[i].Custom["c3"])
    -				foundManageMembers = true
    -			}
    -		}
    -		assert.True(foundManageMembers)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMemberships->", errManageMemAdd.Error())
    -		}
    -	}
    -
    -	// //Update user memberships
    -
    -	custom5 := make(map[string]interface{})
    -	custom5["a5"] = "b5"
    -	custom5["c5"] = "d5"
    -
    -	upMem := pubnub.PNMembershipsSet{
    -		Channel: channel2,
    -		Custom:  custom5,
    -	}
    -
    -	upArrMem := []pubnub.PNMembershipsSet{
    -		upMem,
    -	}
    -	sortMemberships1 := false
    -	sortMemberships2 := false
    -
    -	resManageMemUp, stManageMemUp, errManageMemUp := pn.SetMemberships().UUID(userid2).Sort(sort).Set(upArrMem).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resManageMemUp -->", resManageMemUp)
    -	assert.Nil(errManageMemUp)
    -	assert.Equal(200, stManageMemUp.StatusCode)
    -	if errManageMemUp == nil {
    -		foundManageMembersUp := false
    -
    -		for i := range resManageMemUp.Data {
    -			//fmt.Println("resManageMemUp.Data[i].ID == channelid2-->", resRem.Data[i].UUID.ID, channelid2)
    -			if resManageMemUp.Data[i].Channel.ID == channelid2 {
    -				assert.Equal(channelid2, resManageMemUp.Data[i].Channel.ID)
    -				assert.Equal(name, resManageMemUp.Data[i].Channel.Name)
    -				assert.Equal(desc, resManageMemUp.Data[i].Channel.Description)
    -				assert.Equal(custom2["a1"], resManageMemAdd.Data[i].Channel.Custom["a1"])
    -				assert.Equal(custom2["c1"], resManageMemAdd.Data[i].Channel.Custom["c1"])
    -				assert.Equal(custom5["a5"], resManageMemUp.Data[i].Custom["a5"])
    -				assert.Equal(custom5["c5"], resManageMemUp.Data[i].Custom["c5"])
    -				foundManageMembersUp = true
    -			}
    -		}
    -		if (resManageMemUp.Data != nil) && (len(resManageMemUp.Data) > 1) {
    -			sortMemberships1 = (resManageMemUp.Data[0].Channel.ID == channelid2)
    -			sortMemberships2 = (resManageMemUp.Data[1].Channel.ID == channelid3)
    -			assert.True(sortMemberships1)
    -			assert.True(sortMemberships2)
    -		} else {
    -			assert.Fail("Sort ", "resManageMemUp.Data null or ", len(resManageMemUp.Data))
    -		}
    -
    -		assert.True(foundManageMembersUp)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMemberships->", errManageMemUp.Error())
    -		}
    -	}
    -
    -	// //Get members
    -	resGetMembers, stGetMembers, errGetMembers := pn.GetChannelMembers().Channel(channelid2).Include(inclSm).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resGetMembers -->", resGetMembers)
    -	assert.Nil(errGetMembers)
    -	assert.Equal(200, stGetMembers.StatusCode)
    -	if errGetMembers == nil {
    -		foundGetMembers := false
    -		for i := range resGetMembers.Data {
    -			if resGetMembers.Data[i].UUID.ID == userid2 {
    -				foundGetMembers = true
    -				assert.Equal(name, resGetMembers.Data[i].UUID.Name)
    -				assert.Equal(extid, resGetMembers.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resGetMembers.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resGetMembers.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resGetMembers.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resGetMembers.Data[i].UUID.Custom["c"])
    -				assert.Equal(custom5["a5"], resGetMembers.Data[i].Custom["a5"])
    -				assert.Equal(custom5["c5"], resGetMembers.Data[i].Custom["c5"])
    -			}
    -		}
    -
    -		assert.True(foundGetMembers)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("GetMembers->", errGetMembers.Error())
    -		}
    -	}
    -
    -	//filterExp2 := fmt.Sprintf("custom.a5 == '%s' || custom.c5 == '%s'", custom5["a5"], custom5["c5"])
    -	filterExp2 := fmt.Sprintf("uuid.name == '%s'", name)
    -	//fmt.Println("GetMembers ====>", filterExp2)
    -
    -	resGetMembersF, stGetMembersF, errGetMembersF := pn.GetChannelMembers().Channel(channelid2).Include(inclSm).Filter(filterExp2).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resGetMembers -->", resGetMembersF)
    -	assert.Nil(errGetMembersF)
    -	assert.Equal(200, stGetMembersF.StatusCode)
    -	if errGetMembersF == nil {
    -		foundGetMembersF := false
    -
    -		for i := range resGetMembersF.Data {
    -			if resGetMembersF.Data[i].UUID.ID == userid2 {
    -				foundGetMembersF = true
    -				assert.Equal(name, resGetMembersF.Data[i].UUID.Name)
    -				assert.Equal(extid, resGetMembersF.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resGetMembersF.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resGetMembersF.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resGetMembersF.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resGetMembersF.Data[i].UUID.Custom["c"])
    -				assert.Equal(custom5["a5"], resGetMembersF.Data[i].Custom["a5"])
    -				assert.Equal(custom5["c5"], resGetMembersF.Data[i].Custom["c5"])
    -			}
    -		}
    -		assert.True(foundGetMembersF)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("GetMembers->", errGetMembersF.Error())
    -		}
    -	}
    -
    -	// //Remove user memberships
    -
    -	reMem := pubnub.PNMembershipsRemove{
    -		Channel: channel2,
    -	}
    -
    -	reArrMem := []pubnub.PNMembershipsRemove{
    -		reMem,
    -	}
    -	resManageMemRem, stManageMemRem, errManageMemRem := pn.RemoveMemberships().UUID(userid2).Sort(sort).Remove(reArrMem).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	assert.Nil(errManageMemRem)
    -	assert.Equal(200, stManageMemRem.StatusCode)
    -	if errManageMemRem == nil {
    -
    -		foundManageMemRem := false
    -		for i := range resManageMemRem.Data {
    -			if resManageMemRem.Data[i].Channel.ID == channelid2 {
    -				foundManageMemRem = true
    -			}
    -		}
    -		assert.False(foundManageMemRem)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMemberships->", errManageMemRem.Error())
    -		}
    -	}
    -
    -	//delete
    -	res5, st5, err5 := pn.RemoveUUIDMetadata().UUID(userid).Execute()
    -	assert.Nil(err5)
    -	assert.Equal(200, st5.StatusCode)
    -
    -	assert.Nil(res5.Data)
    -
    -	//delete
    -	res6, st6, err6 := pn.RemoveChannelMetadata().Channel(channelid).Execute()
    -	assert.Nil(err6)
    -	assert.Equal(200, st6.StatusCode)
    -	assert.Nil(res6.Data)
    -
    -	//delete
    -	res52, st52, err52 := pn.RemoveUUIDMetadata().UUID(userid2).Execute()
    -	assert.Nil(err52)
    -	assert.Equal(200, st52.StatusCode)
    -	if res52 != nil {
    -		assert.Nil(res52.Data)
    -	}
    -
    -	//delete
    -	res62, st62, err62 := pn.RemoveChannelMetadata().Channel(channelid2).Execute()
    -	assert.Nil(err62)
    -	assert.Equal(200, st62.StatusCode)
    -	if res62 != nil {
    -		assert.Nil(res62.Data)
    -	}
    -
    -}
    -
    -func TestObjectsV2MembershipsV2(t *testing.T) {
    -	ObjectsMembershipsCommonV2(t, false, false)
     }
     
    -func TestObjectsV2MembershipsV2WithPAM(t *testing.T) {
    -	ObjectsMembershipsCommonV2(t, true, false)
    -}
    -
    -// PASSES after adding PAM checks for Update Members
    -// func TestObjectsV2MembershipsV2WithPAMWithoutSecKey(t *testing.T) {
    -// 	ObjectsMembershipsCommonV2(t, true, true)
    -// }
    -
    -func ObjectsMembershipsCommonV2(t *testing.T, withPAM, runWithoutSecretKey bool) {
    -	assert := assert.New(t)
    -
    -	limit := 100
    -	count := true
    +func TestObjectsV2UUIDMetadataSetUpdateGetRemove(t *testing.T) {
    +	a := assert.New(t)
     
     	pn := pubnub.NewPubNub(configCopy())
     
    -	userid := randomized("testuser1")
    -	channelid := randomized("testchannel1")
    -
    -	if withPAM {
    -		pn2 := ActivateWithPAMV2()
    -		if runWithoutSecretKey {
    -			tokens := RunGrantV2(pn2, []string{userid}, []string{channelid}, true, true, true, true, true, true)
    -			SetPNV2(pn, pn2, tokens)
    -		} else {
    -			pn = pn2
    -			RunGrantV2(pn, []string{userid}, []string{channelid}, true, true, true, true, true, false)
    -		}
    -
    -	}
    +	id := randomized("testuuid")
     	if enableDebuggingInTests {
     		pn.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
     	}
     
     	name := randomized("name")
    -	extid := "extid"
    -	purl := "profileurl"
    -	email := "email"
    +	email := "go@pubnub.com"
    +	custom := map[string]interface{}{"a": "b", "c": "d"}
     
    -	custom := make(map[string]interface{})
    -	custom["a"] = "b"
    -	custom["c"] = "d"
    -
    -	inclUUID := []pubnub.PNUUIDMetadataInclude{
    +	incl := []pubnub.PNUUIDMetadataInclude{
     		pubnub.PNUUIDMetadataIncludeCustom,
     	}
     
    -	res, st, err := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(err)
    -	assert.Equal(200, st.StatusCode)
    -	if res != nil {
    -		assert.Equal(userid, res.Data.ID)
    -		assert.Equal(name, res.Data.Name)
    -		assert.Equal(extid, res.Data.ExternalID)
    -		assert.Equal(purl, res.Data.ProfileURL)
    -		assert.Equal(email, res.Data.Email)
    -		// assert.NotNil(res.Data.Created)
    -		assert.NotNil(res.Data.Updated)
    -		assert.NotNil(res.Data.ETag)
    -		assert.Equal("b", res.Data.Custom["a"])
    -		assert.Equal("d", res.Data.Custom["c"])
    -	}
    -
    -	desc := "desc"
    -	custom2 := make(map[string]interface{})
    -	custom2["a1"] = "b1"
    -	custom2["c1"] = "d1"
    -
    -	inclChannel := []pubnub.PNChannelMetadataInclude{
    -		pubnub.PNChannelMetadataIncludeCustom,
    -	}
    -
    -	res2, st2, err2 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid).Name(name).Description(desc).Custom(custom2).Execute()
    -	assert.Nil(err2)
    -	assert.Equal(200, st2.StatusCode)
    -	//fmt.Println("res2-->", res2)
    -	if res2 != nil {
    -		assert.Equal(channelid, res2.Data.ID)
    -		assert.Equal(name, res2.Data.Name)
    -		assert.Equal(desc, res2.Data.Description)
    -		// assert.NotNil(res2.Data.Created)
    -		assert.NotNil(res2.Data.Updated)
    -		assert.NotNil(res2.Data.ETag)
    -		assert.Equal("b1", res2.Data.Custom["a1"])
    -		assert.Equal("d1", res2.Data.Custom["c1"])
    -	}
    -
    -	userid2 := randomized("testuser2")
    -
    -	_, st3, err3 := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid2).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(err3)
    -	assert.Equal(200, st3.StatusCode)
    -
    -	channelid2 := randomized("testchannel2")
    -
    -	_, st4, err4 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid2).Name(name).Description(desc).Custom(custom2).Execute()
    -	assert.Nil(err4)
    -	assert.Equal(200, st4.StatusCode)
    -
    -	userid3 := randomized("testuser3")
    -
    -	_, stuser3, erruser3 := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid3).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(custom).Execute()
    -	assert.Nil(erruser3)
    -	assert.Equal(200, stuser3.StatusCode)
    -
    -	channelid3 := randomized("testchannel3")
    -
    -	_, stchannel3, errchannel3 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid3).Name(name).Description(desc).Custom(custom2).Execute()
    -	assert.Nil(errchannel3)
    -	assert.Equal(200, stchannel3.StatusCode)
    -
    -	inclSm := []pubnub.PNChannelMembersInclude{
    -		pubnub.PNChannelMembersIncludeUUIDCustom,
    -		pubnub.PNChannelMembersIncludeUUID,
    -		pubnub.PNChannelMembersIncludeCustom,
    -	}
    -
    -	custom3 := make(map[string]interface{})
    -	custom3["a3"] = "b3"
    -	custom3["c3"] = "d3"
    -
    -	uuid := pubnub.PNChannelMembersUUID{
    -		ID: userid,
    -	}
    -
    -	in := pubnub.PNChannelMembersSet{
    -		UUID:   uuid,
    -		Custom: custom3,
    -	}
    -	uuid3 := pubnub.PNChannelMembersUUID{
    -		ID: userid3,
    -	}
    -
    -	inUser3 := pubnub.PNChannelMembersSet{
    -		UUID:   uuid3,
    -		Custom: custom3,
    -	}
    -
    -	inArr := []pubnub.PNChannelMembersSet{
    -		in,
    -		inUser3,
    -	}
    -
    -	//Add Channel Memberships
    -	sortManageChannelMembers := []string{"uuid.id:desc"}
    -	sort := []string{"updated:desc"}
    -
    -	resAdd, stAdd, errAdd := pn.ManageChannelMembers().Channel(channelid).Sort(sortManageChannelMembers).Set(inArr).Remove([]pubnub.PNChannelMembersRemove{}).Include(inclSm).Limit(limit).Count(count).Execute()
    -	assert.Nil(errAdd)
    -	assert.Equal(200, stAdd.StatusCode)
    -	if errAdd == nil {
    -		sortMembers1 := false
    -		sortMembers2 := false
    -
    -		found := false
    -		assert.True(resAdd.TotalCount > 0)
    -		//fmt.Println("resAdd-->", resAdd)
    -
    -		for i := range resAdd.Data {
    -			if resAdd.Data[i].UUID.ID == userid {
    -				found = true
    -				assert.Equal(custom3["a3"], resAdd.Data[i].Custom["a3"])
    -				assert.Equal(custom3["c3"], resAdd.Data[i].Custom["c3"])
    -				assert.Equal(userid, resAdd.Data[i].UUID.ID)
    -				assert.Equal(name, resAdd.Data[i].UUID.Name)
    -				assert.Equal(extid, resAdd.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resAdd.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resAdd.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resAdd.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resAdd.Data[i].UUID.Custom["c"])
    -			}
    -		}
    -		if (resAdd.Data != nil) && (len(resAdd.Data) > 1) {
    -			sortMembers1 = (resAdd.Data[1].UUID.ID == userid)
    -			sortMembers2 = (resAdd.Data[0].UUID.ID == userid3)
    -			assert.True(sortMembers1)
    -			assert.True(sortMembers2)
    -		} else {
    -			assert.Fail("Sort ", "resAdd.Data null or ", len(resAdd.Data))
    -		}
    -
    -		assert.True(found)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMembers->", errAdd.Error())
    -		}
    -	}
    -
    -	//Update Channel Memberships
    -	if !withPAM {
    -
    -		custom4 := make(map[string]interface{})
    -		custom4["a2"] = "b2"
    -		custom4["c2"] = "d2"
    -
    -		up := pubnub.PNChannelMembersSet{
    -			UUID:   uuid,
    -			Custom: custom4,
    -		}
    -
    -		upArr := []pubnub.PNChannelMembersSet{
    -			up,
    -		}
    -
    -		resUp, stUp, errUp := pn.ManageChannelMembers().Channel(channelid).Sort(sort).Set(upArr).Remove([]pubnub.PNChannelMembersRemove{}).Include(inclSm).Limit(limit).Count(count).Execute()
    -		assert.Nil(errUp)
    -		assert.Equal(200, stUp.StatusCode)
    -		if errUp == nil {
    -			assert.True(resUp.TotalCount > 0)
    -			foundUp := false
    -			for i := range resUp.Data {
    -				if resUp.Data[i].UUID.ID == userid {
    -					foundUp = true
    -					assert.Equal("b2", resUp.Data[i].Custom["a2"])
    -					assert.Equal("d2", resUp.Data[i].Custom["c2"])
    -					//assert.Equal(userid, resAdd.Data[i].UUID.ID)
    -					assert.Equal(name, resAdd.Data[i].UUID.Name)
    -					assert.Equal(extid, resAdd.Data[i].UUID.ExternalID)
    -					assert.Equal(purl, resAdd.Data[i].UUID.ProfileURL)
    -					assert.Equal(email, resAdd.Data[i].UUID.Email)
    -					assert.Equal(custom["a"], resAdd.Data[i].UUID.Custom["a"])
    -					assert.Equal(custom["c"], resAdd.Data[i].UUID.Custom["c"])
    -
    -				}
    -			}
    -			assert.True(foundUp)
    -		} else {
    -			if enableDebuggingInTests {
    -
    -				fmt.Println("ManageMembers->", errUp.Error())
    -			}
    -		}
    -	}
    -	//Get Channel Memberships
    -
    -	inclMemberships := []pubnub.PNMembershipsInclude{
    -		pubnub.PNMembershipsIncludeCustom,
    -		pubnub.PNMembershipsIncludeChannel,
    -		pubnub.PNMembershipsIncludeChannelCustom,
    -	}
    -
    -	//fmt.Println("GetMemberships ====>")
    -
    -	resGetMem, stGetMem, errGetMem := pn.GetMemberships().UUID(userid).Include(inclMemberships).Sort(sort).Limit(limit).Count(count).Execute()
    -	foundGetMem := false
    -	assert.Nil(errGetMem)
    -	if errGetMem == nil {
    -		for i := range resGetMem.Data {
    -			if resGetMem.Data[i].Channel.ID == channelid {
    -				foundGetMem = true
    -				assert.Equal(name, resGetMem.Data[i].Channel.Name)
    -				assert.Equal(desc, resGetMem.Data[i].Channel.Description)
    -				assert.Equal("b1", resGetMem.Data[i].Channel.Custom["a1"])
    -				assert.Equal("d1", resGetMem.Data[i].Channel.Custom["c1"])
    -				if withPAM {
    -					assert.Equal("b3", resGetMem.Data[i].Custom["a3"])
    -					assert.Equal("d3", resGetMem.Data[i].Custom["c3"])
    -				} else {
    -					assert.Equal("b2", resGetMem.Data[i].Custom["a2"])
    -					assert.Equal("d2", resGetMem.Data[i].Custom["c2"])
    -				}
    -			}
    -		}
    -		assert.Equal(200, stGetMem.StatusCode)
    -		assert.True(foundGetMem)
    -	} else {
    -		if enableDebuggingInTests {
    -			fmt.Println("GetMemberships->", errGetMem.Error())
    -		}
    -	}
    -
    -	//filterExp := fmt.Sprintf("custom.c3 == '%s' || custom.c2 == '%s'", "d3", "d2")
    -	filterExp := fmt.Sprintf("channel.name == '%s'", name)
    -
    -	//fmt.Println("GetMemberships ====>", filterExp)
    -
    -	resGetMemF, stGetMemF, errGetMemF := pn.GetMemberships().UUID(userid).Include(inclMemberships).Filter(filterExp).Limit(limit).Count(count).Execute()
    -	foundGetMemF := false
    -	assert.Nil(errGetMemF)
    -	if errGetMemF == nil {
    -		for i := range resGetMemF.Data {
    -			if resGetMemF.Data[i].Channel.ID == channelid {
    -				foundGetMemF = true
    -				assert.Equal(name, resGetMemF.Data[i].Channel.Name)
    -				assert.Equal(desc, resGetMemF.Data[i].Channel.Description)
    -				assert.Equal("b1", resGetMemF.Data[i].Channel.Custom["a1"])
    -				assert.Equal("d1", resGetMemF.Data[i].Channel.Custom["c1"])
    -				if withPAM {
    -					assert.Equal("b3", resGetMemF.Data[i].Custom["a3"])
    -					assert.Equal("d3", resGetMemF.Data[i].Custom["c3"])
    -				} else {
    -					assert.Equal("b2", resGetMemF.Data[i].Custom["a2"])
    -					assert.Equal("d2", resGetMemF.Data[i].Custom["c2"])
    -				}
    -			}
    -		}
    -		assert.Equal(200, stGetMemF.StatusCode)
    -		assert.True(foundGetMemF)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("GetMemberships->", errGetMemF.Error())
    -		}
    -	}
    -
    -	//Remove Channel Memberships
    -	re := pubnub.PNChannelMembersRemove{
    -		UUID: uuid,
    -	}
    -
    -	reArr := []pubnub.PNChannelMembersRemove{
    -		re,
    -	}
    -	resRem, stRem, errRem := pn.ManageChannelMembers().Channel(channelid).Set([]pubnub.PNChannelMembersSet{}).Remove(reArr).Include(inclSm).Limit(limit).Count(count).Execute()
    -	assert.Nil(errRem)
    -	assert.Equal(200, stRem.StatusCode)
    -	if errRem == nil {
    +	defer removeUUIDMetadata(a, pn, id)
    +	res, st, err := pn.SetUUIDMetadata().Include(incl).UUID(id).Name(name).Email(email).Custom(custom).Execute()
     
    -		foundRem := false
    -		for i := range resRem.Data {
    -			if resRem.Data[i].UUID.ID == userid {
    -				foundRem = true
    -				assert.Equal("b2", resRem.Data[i].Custom["a2"])
    -				assert.Equal("d2", resRem.Data[i].Custom["c2"])
    -				assert.Equal(userid, resRem.Data[i].UUID.ID)
    -				assert.Equal(name, resRem.Data[i].UUID.Name)
    -				assert.Equal(extid, resRem.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resRem.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resRem.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resRem.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resRem.Data[i].UUID.Custom["c"])
    -
    -			}
    -		}
    -		assert.False(foundRem)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMembers->", errRem.Error())
    -		}
    -	}
    -
    -	channel2 := pubnub.PNMembershipsChannel{
    -		ID: channelid2,
    -	}
    -
    -	inMem := pubnub.PNMembershipsSet{
    -		Channel: channel2,
    -		Custom:  custom3,
    -	}
    -
    -	channel3 := pubnub.PNMembershipsChannel{
    -		ID: channelid3,
    -	}
    -
    -	inMemChannel3 := pubnub.PNMembershipsSet{
    -		Channel: channel3,
    -		Custom:  custom3,
    -	}
    -
    -	inArrMem := []pubnub.PNMembershipsSet{
    -		inMem,
    -		inMemChannel3,
    -	}
    -
    -	//Add user memberships
    -	resManageMemAdd, stManageMemAdd, errManageMemAdd := pn.ManageMemberships().UUID(userid2).Set(inArrMem).Remove([]pubnub.PNMembershipsRemove{}).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resManageMemAdd -->", resManageMemAdd)
    -	assert.Nil(errManageMemAdd)
    -	assert.Equal(200, stManageMemAdd.StatusCode)
    -	if errManageMemAdd == nil {
    -		foundManageMembers := false
    -		for i := range resManageMemAdd.Data {
    -			if resManageMemAdd.Data[i].Channel.ID == channelid2 {
    -				assert.Equal(channelid2, resManageMemAdd.Data[i].Channel.ID)
    -				assert.Equal(name, resManageMemAdd.Data[i].Channel.Name)
    -				assert.Equal(desc, resManageMemAdd.Data[i].Channel.Description)
    -				assert.Equal(custom2["a1"], resManageMemAdd.Data[i].Channel.Custom["a1"])
    -				assert.Equal(custom2["c1"], resManageMemAdd.Data[i].Channel.Custom["c1"])
    -				assert.Equal(custom3["a3"], resManageMemAdd.Data[i].Custom["a3"])
    -				assert.Equal(custom3["c3"], resManageMemAdd.Data[i].Custom["c3"])
    -				foundManageMembers = true
    -			}
    -		}
    -		assert.True(foundManageMembers)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMemberships->", errManageMemAdd.Error())
    -		}
    -	}
    -
    -	// //Update user memberships
    -
    -	custom5 := make(map[string]interface{})
    -	custom5["a5"] = "b5"
    -	custom5["c5"] = "d5"
    -
    -	upMem := pubnub.PNMembershipsSet{
    -		Channel: channel2,
    -		Custom:  custom5,
    -	}
    -
    -	upArrMem := []pubnub.PNMembershipsSet{
    -		upMem,
    -	}
    -	sortMemberships1 := false
    -	sortMemberships2 := false
    -
    -	resManageMemUp, stManageMemUp, errManageMemUp := pn.ManageMemberships().UUID(userid2).Sort(sort).Set(upArrMem).Remove([]pubnub.PNMembershipsRemove{}).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resManageMemUp -->", resManageMemUp)
    -	assert.Nil(errManageMemUp)
    -	assert.Equal(200, stManageMemUp.StatusCode)
    -	if errManageMemUp == nil {
    -		foundManageMembersUp := false
    -
    -		for i := range resManageMemUp.Data {
    -			//fmt.Println("resManageMemUp.Data[i].ID == channelid2-->", resRem.Data[i].UUID.ID, channelid2)
    -			if resManageMemUp.Data[i].Channel.ID == channelid2 {
    -				assert.Equal(channelid2, resManageMemUp.Data[i].Channel.ID)
    -				assert.Equal(name, resManageMemUp.Data[i].Channel.Name)
    -				assert.Equal(desc, resManageMemUp.Data[i].Channel.Description)
    -				assert.Equal(custom2["a1"], resManageMemAdd.Data[i].Channel.Custom["a1"])
    -				assert.Equal(custom2["c1"], resManageMemAdd.Data[i].Channel.Custom["c1"])
    -				assert.Equal(custom5["a5"], resManageMemUp.Data[i].Custom["a5"])
    -				assert.Equal(custom5["c5"], resManageMemUp.Data[i].Custom["c5"])
    -				foundManageMembersUp = true
    -			}
    -		}
    -		if (resManageMemUp.Data != nil) && (len(resManageMemUp.Data) > 1) {
    -			sortMemberships1 = (resManageMemUp.Data[0].Channel.ID == channelid2)
    -			sortMemberships2 = (resManageMemUp.Data[1].Channel.ID == channelid3)
    -			assert.True(sortMemberships1)
    -			assert.True(sortMemberships2)
    -		} else {
    -			assert.Fail("Sort ", "resManageMemUp.Data null or ", len(resManageMemUp.Data))
    -		}
    -
    -		assert.True(foundManageMembersUp)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("ManageMemberships->", errManageMemUp.Error())
    -		}
    -	}
    -
    -	// //Get members
    -	resGetMembers, stGetMembers, errGetMembers := pn.GetChannelMembers().Channel(channelid2).Include(inclSm).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resGetMembers -->", resGetMembers)
    -	assert.Nil(errGetMembers)
    -	assert.Equal(200, stGetMembers.StatusCode)
    -	if errGetMembers == nil {
    -		foundGetMembers := false
    -		for i := range resGetMembers.Data {
    -			if resGetMembers.Data[i].UUID.ID == userid2 {
    -				foundGetMembers = true
    -				assert.Equal(name, resGetMembers.Data[i].UUID.Name)
    -				assert.Equal(extid, resGetMembers.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resGetMembers.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resGetMembers.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resGetMembers.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resGetMembers.Data[i].UUID.Custom["c"])
    -				assert.Equal(custom5["a5"], resGetMembers.Data[i].Custom["a5"])
    -				assert.Equal(custom5["c5"], resGetMembers.Data[i].Custom["c5"])
    -			}
    -		}
    -
    -		assert.True(foundGetMembers)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("GetMembers->", errGetMembers.Error())
    -		}
    -	}
    -
    -	//filterExp2 := fmt.Sprintf("custom.a5 == '%s' || custom.c5 == '%s'", custom5["a5"], custom5["c5"])
    -	filterExp2 := fmt.Sprintf("uuid.name == '%s'", name)
    -	//fmt.Println("GetMembers ====>", filterExp2)
    -
    -	resGetMembersF, stGetMembersF, errGetMembersF := pn.GetChannelMembers().Channel(channelid2).Include(inclSm).Filter(filterExp2).Limit(limit).Count(count).Execute()
    -	//fmt.Println("resGetMembers -->", resGetMembersF)
    -	assert.Nil(errGetMembersF)
    -	assert.Equal(200, stGetMembersF.StatusCode)
    -	if errGetMembersF == nil {
    -		foundGetMembersF := false
    -
    -		for i := range resGetMembersF.Data {
    -			if resGetMembersF.Data[i].UUID.ID == userid2 {
    -				foundGetMembersF = true
    -				assert.Equal(name, resGetMembersF.Data[i].UUID.Name)
    -				assert.Equal(extid, resGetMembersF.Data[i].UUID.ExternalID)
    -				assert.Equal(purl, resGetMembersF.Data[i].UUID.ProfileURL)
    -				assert.Equal(email, resGetMembersF.Data[i].UUID.Email)
    -				assert.Equal(custom["a"], resGetMembersF.Data[i].UUID.Custom["a"])
    -				assert.Equal(custom["c"], resGetMembersF.Data[i].UUID.Custom["c"])
    -				assert.Equal(custom5["a5"], resGetMembersF.Data[i].Custom["a5"])
    -				assert.Equal(custom5["c5"], resGetMembersF.Data[i].Custom["c5"])
    -			}
    -		}
    -		assert.True(foundGetMembersF)
    -	} else {
    -		if enableDebuggingInTests {
    -
    -			fmt.Println("GetMembers->", errGetMembersF.Error())
    -		}
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
    +	if res != nil {
    +		a.Equal(id, res.Data.ID)
    +		a.Equal(name, res.Data.Name)
    +		a.Equal(email, res.Data.Email)
    +		a.NotNil(res.Data.Updated)
    +		a.NotNil(res.Data.ETag)
    +		a.True(reflect.DeepEqual(custom, res.Data.Custom))
     	}
     
    -	// //Remove user memberships
    +	email = "gosdk@pubnub.com"
     
    -	reMem := pubnub.PNMembershipsRemove{
    -		Channel: channel2,
    +	res, st, err = pn.SetUUIDMetadata().Include(incl).UUID(id).Name(name).Email(email).Custom(custom).Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
    +	if res != nil {
    +		a.Equal(id, res.Data.ID)
    +		a.Equal(email, res.Data.Email)
     	}
     
    -	reArrMem := []pubnub.PNMembershipsRemove{
    -		reMem,
    -	}
    -	resManageMemRem, stManageMemRem, errManageMemRem := pn.ManageMemberships().UUID(userid2).Sort(sort).Set([]pubnub.PNMembershipsSet{}).Remove(reArrMem).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	assert.Nil(errManageMemRem)
    -	assert.Equal(200, stManageMemRem.StatusCode)
    -	if errManageMemRem == nil {
    +	_, st, err = pn.GetUUIDMetadata().Include(incl).UUID(id).Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
     
    -		foundManageMemRem := false
    -		for i := range resManageMemRem.Data {
    -			if resManageMemRem.Data[i].Channel.ID == channelid2 {
    -				foundManageMemRem = true
    -			}
    -		}
    -		assert.False(foundManageMemRem)
    -	} else {
    -		if enableDebuggingInTests {
    +}
     
    -			fmt.Println("ManageMemberships->", errManageMemRem.Error())
    -		}
    +func removeUUIDMetadata(a *assert.Assertions, pn *pubnub.PubNub, id string) {
    +	res, st, err := pn.RemoveUUIDMetadata().UUID(id).Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
    +	if res != nil {
    +		a.Nil(res.Data)
     	}
    +}
     
    -	//delete
    -	res5, st5, err5 := pn.RemoveUUIDMetadata().UUID(userid).Execute()
    -	assert.Nil(err5)
    -	assert.Equal(200, st5.StatusCode)
    +func TestObjectsV2MembersAddRemove(t *testing.T) {
    +	a := assert.New(t)
     
    -	assert.Nil(res5.Data)
    +	pn := pubnub.NewPubNub(configCopy())
    +	channelid := randomized("channel")
    +	userid := randomized("uuid")
    +	inc := []pubnub.PNChannelMembersInclude{pubnub.PNChannelMembersIncludeUUID}
     
    -	//delete
    -	res6, st6, err6 := pn.RemoveChannelMetadata().Channel(channelid).Execute()
    -	assert.Nil(err6)
    -	assert.Equal(200, st6.StatusCode)
    -	assert.Nil(res6.Data)
    +	defer removeChannelMembers(a, pn, channelid, userid)
     
    -	//delete
    -	res52, st52, err52 := pn.RemoveUUIDMetadata().UUID(userid2).Execute()
    -	assert.Nil(err52)
    -	assert.Equal(200, st52.StatusCode)
    -	if res52 != nil {
    -		assert.Nil(res52.Data)
    -	}
    -
    -	//delete
    -	res62, st62, err62 := pn.RemoveChannelMetadata().Channel(channelid2).Execute()
    -	assert.Nil(err62)
    -	assert.Equal(200, st62.StatusCode)
    -	if res62 != nil {
    -		assert.Nil(res62.Data)
    +	res, st, err := pn.
    +		SetChannelMembers().
    +		Channel(channelid).
    +		Set([]pubnub.PNChannelMembersSet{{UUID: pubnub.PNChannelMembersUUID{ID: userid}}}).
    +		Include(inc).
    +		Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
    +	if err == nil {
    +		a.True(len(res.Data) > 0)
     	}
     
     }
     
    -func TestObjectsV2ListenersV2(t *testing.T) {
    -	ObjectsListenersCommonV2(t, false, false)
    -}
    -
    -func TestObjectsV2ListenersV2WithPAM(t *testing.T) {
    -	ObjectsListenersCommonV2(t, true, false)
    +func removeChannelMembers(a *assert.Assertions, pn *pubnub.PubNub, channelid string, userid string) {
    +	_, st, err := pn.
    +		RemoveChannelMembers().
    +		Channel(channelid).
    +		Remove([]pubnub.PNChannelMembersRemove{{UUID: pubnub.PNChannelMembersUUID{ID: userid}}}).
    +		Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
     }
     
    -// func TestObjectsV2ListenersV2WithPAMWithoutSecKey(t *testing.T) {
    -// 	ObjectsListenersCommonV2(t, true, true)
    -// }
    -
    -func ObjectsListenersCommonV2(t *testing.T, withPAM, runWithoutSecretKey bool) {
    -	//Create channel names for Channel and User
    -	eventWaitTime := 2
    -	assert := assert.New(t)
    -
    -	limit := 100
    -	count := true
    +func TestObjectsV2MembershipAddRemove(t *testing.T) {
    +	a := assert.New(t)
     
     	pn := pubnub.NewPubNub(configCopy())
    -	pnSub := pubnub.NewPubNub(configCopy())
    -
    -	userid := randomized("testlistuser")
    -	channelid := randomized("testlistchannel")
    -	if withPAM {
    -		pn2 := ActivateWithPAMV2()
    -		if runWithoutSecretKey {
    -			if enableDebuggingInTests {
    -				pn2.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
    -			}
    -			tokens := RunGrantV2(pn2, []string{userid}, []string{channelid}, true, true, true, true, true, true)
    -			SetPNV2(pn, pn2, tokens)
    -			SetPNV2(pnSub, pn2, tokens)
    -			//You have to use Grant v2 to subscribe
    -			pnSub.Config.AuthKey = "authKey"
    -			pn2.Grant().
    -				Read(true).Write(true).Manage(true).
    -				Channels([]string{userid, channelid}).
    -				AuthKeys([]string{pnSub.Config.AuthKey}).
    -				Execute()
    -		} else {
    -			pn = pn2
    -			pnSub = pn2
    -			if enableDebuggingInTests {
    -				pn.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
    -			}
    -			RunGrantV2(pn, []string{userid}, []string{channelid}, true, true, true, true, true, false)
    -		}
    -	}
    -	if enableDebuggingInTests {
    -		pn.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
    -		pnSub.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
    -	}
    -
    -	//Subscribe to the channel names
    -
    -	listener := pubnub.NewListener()
    -
    -	var mut sync.RWMutex
    -
    -	addUserToChannel := false
    -	addUserToChannel2 := false
    -	updateUserMem := false
    -	updateUser := false
    -	updateChannel := false
    -	removeUserFromChannel := false
    -	deleteUser := false
    -	deleteChannel := false
    -
    -	doneConnected := make(chan bool)
    -	exitListener := make(chan bool)
    -
    -	go func() {
    -	ExitLabel:
    -		for {
    -			//fmt.Println("Running =--->")
    -			select {
    -
    -			case status := <-listener.Status:
    -				switch status.Category {
    -				case pubnub.PNConnectedCategory:
    -					doneConnected <- true
    -				default:
    -					if enableDebuggingInTests {
    -
    -						fmt.Println(" --- status: ", status)
    -					}
    -				}
    -
    -			case userEvent := <-listener.UUIDEvent:
    -				if enableDebuggingInTests {
    -
    -					fmt.Println(" --- UserEvent: ")
    -					fmt.Println(fmt.Sprintf("%s", userEvent))
    -					fmt.Println(fmt.Sprintf("userEvent.Channel: %s", userEvent.Channel))
    -					fmt.Println(fmt.Sprintf("userEvent.SubscribedChannel: %s", userEvent.SubscribedChannel))
    -					fmt.Println(fmt.Sprintf("userEvent.Event: %s", userEvent.Event))
    -					fmt.Println(fmt.Sprintf("userEvent.UUID: %s", userEvent.UUID))
    -					fmt.Println(fmt.Sprintf("userEvent.Description: %s", userEvent.Description))
    -					fmt.Println(fmt.Sprintf("userEvent.Timestamp: %s", userEvent.Timestamp))
    -					fmt.Println(fmt.Sprintf("userEvent.Name: %s", userEvent.Name))
    -					fmt.Println(fmt.Sprintf("userEvent.ExternalID: %s", userEvent.ExternalID))
    -					fmt.Println(fmt.Sprintf("userEvent.ProfileURL: %s", userEvent.ProfileURL))
    -					fmt.Println(fmt.Sprintf("userEvent.Email: %s", userEvent.Email))
    -					// fmt.Println(fmt.Sprintf("userEvent.Created: %s", userEvent.Created))
    -					fmt.Println(fmt.Sprintf("userEvent.Updated: %s", userEvent.Updated))
    -					fmt.Println(fmt.Sprintf("userEvent.ETag: %s", userEvent.ETag))
    -					fmt.Println(fmt.Sprintf("userEvent.Custom: %v", userEvent.Custom))
    -				}
    -
    -				if (userEvent.Event == pubnub.PNObjectsEventRemove) && (userEvent.UUID == userid) {
    -					mut.Lock()
    -					deleteUser = true
    -					mut.Unlock()
    -				}
    -				if (userEvent.Event == pubnub.PNObjectsEventSet) && (userEvent.UUID == userid) {
    -					mut.Lock()
    -					updateUser = true
    -					mut.Unlock()
    -				}
    -			case channelEvent := <-listener.ChannelEvent:
    -
    -				if enableDebuggingInTests {
    -
    -					fmt.Println(" --- ChannelEvent: ")
    -					fmt.Println(fmt.Sprintf("%s", channelEvent))
    -					fmt.Println(fmt.Sprintf("channelEvent.SubscribedChannel: %s", channelEvent.SubscribedChannel))
    -					fmt.Println(fmt.Sprintf("channelEvent.Event: %s", channelEvent.Event))
    -					fmt.Println(fmt.Sprintf("channelEvent.ChannelID: %s", channelEvent.ChannelID))
    -					fmt.Println(fmt.Sprintf("channelEvent.Channel: %s", channelEvent.Channel))
    -					fmt.Println(fmt.Sprintf("channelEvent.Description: %s", channelEvent.Description))
    -					fmt.Println(fmt.Sprintf("channelEvent.Timestamp: %s", channelEvent.Timestamp))
    -					// fmt.Println(fmt.Sprintf("channelEvent.Created: %s", channelEvent.Created))
    -					fmt.Println(fmt.Sprintf("channelEvent.Updated: %s", channelEvent.Updated))
    -					fmt.Println(fmt.Sprintf("channelEvent.ETag: %s", channelEvent.ETag))
    -					fmt.Println(fmt.Sprintf("channelEvent.Custom: %v", channelEvent.Custom))
    -				}
    -				if (channelEvent.Event == pubnub.PNObjectsEventRemove) && (channelEvent.ChannelID == channelid) {
    -					mut.Lock()
    -					deleteChannel = true
    -					mut.Unlock()
    -				}
    -				if (channelEvent.Event == pubnub.PNObjectsEventSet) && (channelEvent.ChannelID == channelid) {
    -					mut.Lock()
    -					updateChannel = true
    -					mut.Unlock()
    -				}
    -
    -			case membershipEvent := <-listener.MembershipEvent:
    -				if enableDebuggingInTests {
    -
    -					fmt.Println(" --- MembershipEvent: ")
    -					fmt.Println(fmt.Sprintf("%s", membershipEvent))
    -					fmt.Println(fmt.Sprintf("membershipEvent.SubscribedChannel: %s", membershipEvent.SubscribedChannel))
    -					fmt.Println(fmt.Sprintf("membershipEvent.Event: %s", membershipEvent.Event))
    -					fmt.Println(fmt.Sprintf("membershipEvent.Channel: %s", membershipEvent.Channel))
    -					fmt.Println(fmt.Sprintf("membershipEvent.UUID: %s", membershipEvent.UUID))
    -					fmt.Println(fmt.Sprintf("membershipEvent.ChannelID: %s", membershipEvent.ChannelID))
    -					fmt.Println(fmt.Sprintf("membershipEvent.Description: %s", membershipEvent.Description))
    -					fmt.Println(fmt.Sprintf("membershipEvent.Timestamp: %s", membershipEvent.Timestamp))
    -					fmt.Println(fmt.Sprintf("membershipEvent.Custom: %v", membershipEvent.Custom))
    -				}
    -				if (membershipEvent.Event == pubnub.PNObjectsEventSet) && (membershipEvent.ChannelID == channelid) && (membershipEvent.UUID == userid) && ((membershipEvent.Channel == channelid) || (membershipEvent.Channel == userid)) {
    -					mut.Lock()
    -					addUserToChannel = true
    -					mut.Unlock()
    -				}
    -				if (membershipEvent.Event == pubnub.PNObjectsEventSet) && (membershipEvent.ChannelID == channelid) && (membershipEvent.UUID == userid) && ((membershipEvent.Channel == channelid) || (membershipEvent.Channel == userid)) {
    -					mut.Lock()
    -					addUserToChannel2 = true
    -					mut.Unlock()
    -				}
    -				if (membershipEvent.Event == pubnub.PNObjectsEventSet) && (membershipEvent.ChannelID == channelid) && (membershipEvent.UUID == userid) && ((membershipEvent.Channel == channelid) || (membershipEvent.Channel == userid)) {
    -					mut.Lock()
    -					updateUserMem = true
    -					mut.Unlock()
    -				}
    -				if (membershipEvent.Event == pubnub.PNObjectsEventSet) && (membershipEvent.ChannelID == channelid) && (membershipEvent.UUID == userid) && ((membershipEvent.Channel == channelid) || (membershipEvent.Channel == userid)) {
    -					mut.Lock()
    -					updateUserMem = true
    -					mut.Unlock()
    -				}
    -				if (membershipEvent.Event == pubnub.PNObjectsEventRemove) && (membershipEvent.ChannelID == channelid) && (membershipEvent.UUID == userid) && ((membershipEvent.Channel == channelid) || (membershipEvent.Channel == userid)) {
    -					mut.Lock()
    -					removeUserFromChannel = true
    -					mut.Unlock()
    -				}
    -			case <-exitListener:
    -				break ExitLabel
    -
    -			}
    -
    -			//fmt.Println("=>>>>>>>>>>>>> restart")
    -
    -		}
    -
    -	}()
    -
    -	pnSub.AddListener(listener)
    -
    -	pnSub.Subscribe().Channels([]string{userid, channelid}).Execute()
    -	tic := time.NewTicker(time.Duration(eventWaitTime) * time.Second)
    -	select {
    -	case <-doneConnected:
    -	case <-tic.C:
    -		tic.Stop()
    -		assert.Fail("timeout")
    -	}
    -
    -	name := "name"
    -	extid := "extid"
    -	purl := "profileurl"
    -	email := "email"
    -	desc := "desc"
    -
    -	customUser := make(map[string]interface{})
    -	customUser["au"] = "bu"
    -	customUser["cu"] = "du"
    -
    -	inclUUID := []pubnub.PNUUIDMetadataInclude{
    -		pubnub.PNUUIDMetadataIncludeCustom,
    -	}
    -
    -	//Create User
    -	_, st, err := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(customUser).Execute()
    -	assert.Nil(err)
    -	assert.Equal(200, st.StatusCode)
    -
    -	//Create Channel
    -	customChannel := make(map[string]interface{})
    -	customChannel["as"] = "bs"
    -	customChannel["cs"] = "ds"
    -
    -	inclChannel := []pubnub.PNChannelMetadataInclude{
    -		pubnub.PNChannelMetadataIncludeCustom,
    -	}
    -
    -	_, st4, err4 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid).Name(name).Description(desc).Custom(customChannel).Execute()
    -	assert.Nil(err4)
    -	assert.Equal(200, st4.StatusCode)
    -
    -	time.Sleep(1 * time.Second)
    -
    -	//Update User
    -	email = "email2"
    -	//fmt.Println("SetUUIDMetadata, Update ===> ", userid)
    -
    -	_, st2, err2 := pn.SetUUIDMetadata().Include(inclUUID).UUID(userid).Name(name).ExternalID(extid).ProfileURL(purl).Email(email).Custom(customUser).Execute()
    -	assert.Nil(err2)
    -	assert.Equal(200, st2.StatusCode)
    +	channelid := randomized("channel")
    +	userid := randomized("uuid")
    +	inc := []pubnub.PNMembershipsInclude{pubnub.PNMembershipsIncludeChannel}
     
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    -	assert.True(updateUser)
    -	mut.Unlock()
    +	defer removeMemberships(a, pn, channelid, userid)
     
    -	desc = "desc2"
    -
    -	//fmt.Println("SetChannelMetadata, Update ===> ", channelid)
    -
    -	//Update Channel
    -	_, st3, err3 := pn.SetChannelMetadata().Include(inclChannel).Channel(channelid).Name(name).Description(desc).Custom(customChannel).Execute()
    -	assert.Nil(err3)
    -	assert.Equal(200, st3.StatusCode)
    -
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    -	assert.True(updateChannel)
    -	mut.Unlock()
    -
    -	//Add user to channel
    -	inclSm := []pubnub.PNChannelMembersInclude{
    -		pubnub.PNChannelMembersIncludeCustom,
    -		pubnub.PNChannelMembersIncludeUUID,
    -		pubnub.PNChannelMembersIncludeUUIDCustom,
    -	}
    -
    -	if enableDebuggingInTests {
    -
    -		fmt.Println("inclSm===>", inclSm)
    -		for k, value := range inclSm {
    -			fmt.Println("inclSm===>", k, value)
    -		}
    -	}
    -
    -	custom3 := make(map[string]interface{})
    -	custom3["a3"] = "b3"
    -	custom3["c3"] = "d3"
    -
    -	uuid := pubnub.PNChannelMembersUUID{
    -		ID: userid,
    -	}
    -
    -	in := pubnub.PNChannelMembersSet{
    -		UUID:   uuid,
    -		Custom: custom3,
    -	}
    -
    -	inArr := []pubnub.PNChannelMembersSet{
    -		in,
    -	}
    -
    -	_, stAdd, errAdd := pn.ManageChannelMembers().Channel(channelid).Set(inArr).Remove([]pubnub.PNChannelMembersRemove{}).Include(inclSm).Limit(limit).Count(count).Execute()
    -	assert.Nil(errAdd)
    -	if enableDebuggingInTests {
    -
    -		if errAdd != nil {
    -			fmt.Println("ManageMembers-->", errAdd)
    -		}
    -	}
    -	assert.Equal(200, stAdd.StatusCode)
    -
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    -	assert.True(addUserToChannel && addUserToChannel2)
    -	mut.Unlock()
    -
    -	//Update user membership
    -
    -	//Read event
    -
    -	custom5 := make(map[string]interface{})
    -	custom5["a5"] = "b5"
    -	custom5["c5"] = "d5"
    -
    -	channel := pubnub.PNMembershipsChannel{
    -		ID: channelid,
    -	}
    -
    -	upMem := pubnub.PNMembershipsSet{
    -		Channel: channel,
    -		Custom:  custom5,
    -	}
    -
    -	upArrMem := []pubnub.PNMembershipsSet{
    -		upMem,
    -	}
    -
    -	inclMemberships := []pubnub.PNMembershipsInclude{
    -		pubnub.PNMembershipsIncludeCustom,
    -		pubnub.PNMembershipsIncludeChannel,
    -		pubnub.PNMembershipsIncludeChannelCustom,
    -	}
    -
    -	resManageMemUp, stManageMemUp, errManageMemUp := pn.ManageMemberships().UUID(userid).Set(upArrMem).Remove([]pubnub.PNMembershipsRemove{}).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -
    -	assert.Nil(errManageMemUp)
    -	if enableDebuggingInTests {
    -
    -		fmt.Println("resManageMemUp -->", resManageMemUp)
    -		if errManageMemUp != nil {
    -			fmt.Println("ManageMemberships-->", errManageMemUp)
    -		}
    -	}
    -	assert.Equal(200, stManageMemUp.StatusCode)
    -
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    -	assert.True(updateUserMem)
    -	mut.Unlock()
    -
    -	//Remove user from channel
    -	reMem := pubnub.PNMembershipsRemove{
    -		Channel: channel,
    -	}
    -
    -	reArrMem := []pubnub.PNMembershipsRemove{
    -		reMem,
    -	}
    -	_, stManageMemRem, errManageMemRem := pn.ManageMemberships().UUID(userid).Set([]pubnub.PNMembershipsSet{}).Remove(reArrMem).Include(inclMemberships).Limit(limit).Count(count).Execute()
    -	assert.Nil(errManageMemRem)
    -	if enableDebuggingInTests {
    -
    -		if errManageMemRem != nil {
    -			fmt.Println("ManageMemberships-->", errManageMemRem)
    -		}
    +	res, st, err := pn.
    +		SetMemberships().
    +		UUID(userid).
    +		Set([]pubnub.PNMembershipsSet{{Channel: pubnub.PNMembershipsChannel{ID: channelid}}}).
    +		Include(inc).
    +		Execute()
    +	a.Nil(err)
    +	a.Equal(200, st.StatusCode)
    +	if err == nil {
    +		a.True(len(res.Data) > 0)
     	}
    -	assert.Equal(200, stManageMemRem.StatusCode)
     
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    -	assert.True(removeUserFromChannel)
    -	mut.Unlock()
    -
    -	//Delete user
    -	res52, st52, err52 := pn.RemoveUUIDMetadata().UUID(userid).Execute()
    -	assert.Nil(err52)
    -	assert.Equal(200, st52.StatusCode)
    -	assert.Nil(res52.Data)
    -
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    -	assert.True(deleteUser)
    -	mut.Unlock()
    -
    -	//Delete Channel
    -	res62, st62, err62 := pn.RemoveChannelMetadata().Channel(channelid).Execute()
    -	assert.Nil(err62)
    -	assert.Equal(200, st62.StatusCode)
    -	assert.Nil(res62.Data)
    -
    -	time.Sleep(1 * time.Second)
    -	mut.Lock()
    
    ... [truncated]
    
  • tests/e2e/publish_test.go+5 1 modified
    @@ -2,6 +2,7 @@ package e2e
     
     import (
     	"fmt"
    +	"os"
     	"strings"
     	"testing"
     	"time"
    @@ -182,7 +183,10 @@ func TestPublishServerError(t *testing.T) {
     		ResponseStatusCode: 403,
     	})
     
    -	pn := pubnub.NewPubNub(configCopy())
    +	config := pubnub.NewConfigWithUserId(pubnub.UserId(pubnub.GenerateUUID()))
    +	config.PublishKey = os.Getenv("PUBLISH_KEY")
    +	config.SubscribeKey = os.Getenv("SUBSCRIBE_KEY")
    +	pn := pubnub.NewPubNub(config)
     	pn.SetClient(interceptor.GetClient())
     
     	_, _, err := pn.Publish().Channel("ch").Message("hey").Execute()
    
  • utils/crypto.go+41 292 modified
    @@ -2,24 +2,17 @@ package utils
     
     import (
     	"bytes"
    -	"crypto/aes"
    -	"crypto/cipher"
     	"crypto/hmac"
    -	"crypto/rand"
     	"crypto/sha256"
     	"encoding/base64"
    -	"encoding/hex"
    -	"errors"
     	"fmt"
    +	"github.com/pubnub/go/v7/crypto"
     	"io"
     	"os"
     	"strconv"
     	"strings"
     )
     
    -// 16 byte IV
    -var valIV = "0123456789012345"
    -
     // EncryptString creates the base64 encoded encrypted string using the
     // cipherKey.
     // It accepts the following parameters:
    @@ -29,33 +22,15 @@ var valIV = "0123456789012345"
     //
     // returns the base64 encoded encrypted string.
     func EncryptString(cipherKey string, message string, useRandomInitializationVector bool) string {
    -	block, _ := aesCipher(cipherKey)
    -
    -	message = encodeNonASCIIChars(message)
    -	value := []byte(message)
    -	value = padWithPKCS7(value)
    -	iv := make([]byte, aes.BlockSize)
    -	if useRandomInitializationVector {
    -		iv = generateIV(aes.BlockSize)
    -	} else {
    -		iv = []byte(valIV)
    +	cryptoModule, e := crypto.NewLegacyCryptoModule(cipherKey, useRandomInitializationVector)
    +	if e != nil {
    +		panic(e)
     	}
    -	blockmode := cipher.NewCBCEncrypter(block, iv)
    -
    -	cipherBytes := make([]byte, len(value))
    -	blockmode.CryptBlocks(cipherBytes, value)
    -	if useRandomInitializationVector {
    -		return base64.StdEncoding.EncodeToString(append(iv, cipherBytes...))
    +	encryptedData, e := cryptoModule.Encrypt([]byte(encodeNonASCIIChars(message)))
    +	if e != nil {
    +		panic(e)
     	}
    -	return base64.StdEncoding.EncodeToString(cipherBytes)
    -}
    -
    -type A struct {
    -	I         string
    -	Interface *B
    -}
    -type B struct {
    -	Value string
    +	return base64.StdEncoding.EncodeToString(encryptedData)
     }
     
     // DecryptString decodes encrypted string using the cipherKey
    @@ -67,74 +42,18 @@ type B struct {
     //
     // returns the unencoded encrypted string,
     // error if any.
    -func DecryptString(cipherKey string, message string, useRandomInitializationVector bool) (
    -	retVal interface{}, err error) {
    -	if message == "" {
    -		return "**decrypt error***", errors.New("message is empty")
    -	}
    -
    -	block, aesErr := aesCipher(cipherKey)
    -	if aesErr != nil {
    -		return "***decrypt error***", fmt.Errorf("decrypt error aes cipher: %s", aesErr)
    -	}
    -
    +func DecryptString(cipherKey string, message string, useRandomInitializationVector bool) (retVal interface{}, err error) {
     	value, decodeErr := base64.StdEncoding.DecodeString(message)
     	if decodeErr != nil {
     		return "***decrypt error***", fmt.Errorf("decrypt error on decode: %s", decodeErr)
     	}
    -	iv := make([]byte, aes.BlockSize)
    -	if useRandomInitializationVector {
    -		iv = value[:16]
    -		value = value[16:]
    -	} else {
    -		iv = []byte(valIV)
    -	}
    -
    -	decrypter := cipher.NewCBCDecrypter(block, iv)
    -	//to handle decryption errors
    -	defer func() {
    -		if r := recover(); r != nil {
    -			retVal, err = "***decrypt error***", fmt.Errorf("decrypt error: %s", r)
    -		}
    -	}()
    -	decrypted := make([]byte, len(value))
    -	decrypter.CryptBlocks(decrypted, value)
    -	val, err := unpadPKCS7(decrypted)
    -	if err != nil {
    -		return "***decrypt error***", fmt.Errorf("decrypt error: %s", err)
    -	}
     
    -	return fmt.Sprintf("%s", string(val)), nil
    -}
    -
    -// aesCipher returns the cipher block
    -//
    -// It accepts the following parameters:
    -// cipherKey: cipher key.
    -//
    -// returns the cipher block,
    -// error if any.
    -func aesCipher(cipherKey string) (cipher.Block, error) {
    -	key := EncryptCipherKey(cipherKey)
    -	block, err := aes.NewCipher(key)
    -	if err != nil {
    -		return nil, err
    +	cryptoModule, e := crypto.NewLegacyCryptoModule(cipherKey, useRandomInitializationVector)
    +	if e != nil {
    +		return nil, e
     	}
    -	return block, nil
    -}
    -
    -// EncryptCipherKey creates the 256 bit hex of the cipher key
    -//
    -// It accepts the following parameters:
    -// cipherKey: cipher key to use to decrypt.
    -//
    -// returns the 256 bit hex of the cipher key.
    -func EncryptCipherKey(cipherKey string) []byte {
    -	hash := sha256.New()
    -	hash.Write([]byte(cipherKey))
    -
    -	sha256String := hash.Sum(nil)[:16]
    -	return []byte(hex.EncodeToString(sha256String))
    +	val, e := cryptoModule.Decrypt(value)
    +	return fmt.Sprintf("%s", string(val)), e
     }
     
     // encodeNonAsciiChars creates unicode string of the non-ascii chars.
    @@ -178,214 +97,44 @@ func GetHmacSha256(secretKey string, input string) string {
     	return signature
     }
     
    -// padWithPKCS7 pads the data as per the PKCS7 standard
    -// It accepts the following parameters:
    -// data: data to pad as byte array.
    -// returns the padded data as byte array.
    -func padWithPKCS7(data []byte) []byte {
    -	blocklen := 16
    -	padlen := 1
    -	for ((len(data) + padlen) % blocklen) != 0 {
    -		padlen = padlen + 1
    -	}
    -
    -	pad := bytes.Repeat([]byte{byte(padlen)}, padlen)
    -	return append(data, pad...)
    -}
    -
    -// unpadPKCS7 unpads the data as per the PKCS7 standard
    -// It accepts the following parameters:
    -// data: data to unpad as byte array.
    -// returns the unpadded data as byte array.
    -func unpadPKCS7(data []byte) ([]byte, error) {
    -	blocklen := 16
    -	if len(data)%blocklen != 0 || len(data) == 0 {
    -		return nil, fmt.Errorf("invalid data len %d", len(data))
    -	}
    -	padlen := int(data[len(data)-1])
    -	if padlen > blocklen || padlen == 0 {
    -		return nil, fmt.Errorf("padding is invalid")
    +func EncryptFile(cipherKey string, _ []byte, filePart io.Writer, file *os.File) {
    +	cryptor, e := crypto.NewLegacyCryptoModule(cipherKey, true)
    +	if e != nil {
    +		panic(e)
     	}
    -	// check padding
    -	pad := data[len(data)-padlen:]
    -	for i := 0; i < padlen; i++ {
    -		if pad[i] != byte(padlen) {
    -			return nil, fmt.Errorf("padding is invalid")
    -		}
    +	r, e := cryptor.EncryptStream(file)
    +	if e != nil {
    +		panic(e)
     	}
    -
    -	return data[:len(data)-padlen], nil
    -}
    -
    -func generateIV(blocksize int) []byte {
    -	iv := make([]byte, aes.BlockSize)
    -	if _, err := rand.Read(iv); err != nil {
    -		panic(err)
    +	_, e = io.Copy(filePart, r)
    +	if e != nil {
    +		panic(e)
     	}
    -	return iv
     }
     
    -func EncryptFile(cipherKey string, iv []byte, filePart io.Writer, file *os.File) {
    -	key := EncryptCipherKey(cipherKey)
    -	block, err := aes.NewCipher(key)
    -	if err != nil {
    -		panic(err)
    -	}
    -	if bytes.Equal(iv, []byte{}) {
    -		iv = generateIV(aes.BlockSize)
    +func DecryptFile(cipherKey string, _ int64, reader io.Reader, w io.WriteCloser) {
    +	cryptoModule, e := crypto.NewLegacyCryptoModule(cipherKey, true)
    +	if e != nil {
    +		panic(e)
     	}
    -	_, e := filePart.Write(iv)
    +	encryptedReader, e := cryptoModule.DecryptStream(reader)
     	if e != nil {
     		panic(e)
     	}
    -	blockSize := 16
    -	bufferSize := 16
    -	p := make([]byte, bufferSize)
    -
    -	mode := cipher.NewCBCEncrypter(block, iv)
    -	cryptoRan := false
    -	fii, _ := file.Stat()
    -	contentLenIn := fii.Size()
    -	var contentRead int64
    -
    -	for {
    -		n2, err2 := io.ReadFull(file, p)
    -		contentRead += int64(n2)
    -		if err2 != nil {
    -			if err2 == io.EOF {
    -				ciphertext := make([]byte, blockSize)
    -				copy(ciphertext[:n2], p[:n2])
    -				break
    -			}
    -
    -			if err2 == io.ErrUnexpectedEOF {
    -				if !cryptoRan {
    -					text := make([]byte, blockSize)
    -					ciphertext := make([]byte, blockSize)
    -					copy(text[:n2], p[:n2])
    -					pad := bytes.Repeat([]byte{byte(blockSize - n2)}, blockSize-n2)
    -					copy(text[n2:], pad)
    -					mode.CryptBlocks(ciphertext, text)
    -					filePart.Write(ciphertext)
    -				} else {
    -					text := make([]byte, blockSize)
    -					ciphertext := make([]byte, blockSize)
    -					copy(text[:n2], p[:n2])
    -					pad := bytes.Repeat([]byte{byte(blockSize - n2)}, blockSize-n2)
    -					copy(text[n2:], pad)
    -					mode.CryptBlocks(ciphertext, text)
    -					filePart.Write(ciphertext)
    -
    -				}
    -
    -			}
    -			break
    -		}
    -
    -		ciphertext := make([]byte, blockSize)
    -		cryptoRan = true
    -		if contentRead >= contentLenIn {
    -			pad := bytes.Repeat([]byte{byte(blockSize - n2)}, blockSize-n2)
    -			copy(p[n2:], pad)
    -		}
    -
    -		mode.CryptBlocks(ciphertext, p)
    -		filePart.Write(ciphertext)
    -
    +	_, e = io.Copy(w, encryptedReader)
    +	if e != nil {
    +		panic(e)
     	}
    +	e = w.Close()
     }
     
    -func DecryptFile(cipherKey string, contentLenEnc int64, reader io.Reader, w io.WriteCloser) {
    -	key := EncryptCipherKey(cipherKey)
    -	block, err := aes.NewCipher(key)
    -	if err != nil {
    -		panic(err)
    -	}
    -	blockSize := 16
    -	bufferSize := 16
    -	p := make([]byte, bufferSize)
    -	ivBuff := make([]byte, blockSize)
    -	emptyByteVar := make([]byte, blockSize)
    -
    -	iv2 := make([]byte, blockSize)
    -	count := 0
    -
    -	var mode cipher.BlockMode
    -
    -	cryptoRan := false
    -	var contentDownloaded int64
    -
    -	go func() {
    -	ExitReadLabel:
    -		for {
    -			n2, err2 := io.ReadFull(reader, p)
    -			if err2 != nil {
    -				if err2 == io.EOF {
    -					ciphertext := make([]byte, blockSize)
    -					copy(ciphertext, p[:n2])
    -					ciphertext, _ = unpadPKCS7(ciphertext)
    -					w.Write(ciphertext)
    -					w.Close()
    -					break ExitReadLabel
    -				}
    -
    -				if err2 == io.ErrUnexpectedEOF {
    -					if bytes.Equal(iv2, emptyByteVar) {
    -						copy(iv2, ivBuff[0:blockSize])
    -						mode = cipher.NewCBCDecrypter(block, iv2)
    -					}
    -					if !cryptoRan {
    -						text := make([]byte, blockSize)
    -						ciphertext := make([]byte, blockSize)
    -						copy(text, p[:n2])
    -						mode.CryptBlocks(ciphertext, text)
    -						ciphertext, _ = unpadPKCS7(ciphertext)
    -						w.Write(ciphertext)
    -
    -						w.Close()
    -						break ExitReadLabel
    -					} else {
    -						ciphertext := make([]byte, blockSize)
    -						copy(ciphertext, p[:n2])
    -						ciphertext, _ = unpadPKCS7(ciphertext)
    -						w.Write(ciphertext)
    -						w.Close()
    -						break ExitReadLabel
    -					}
    -
    -				}
    -				break ExitReadLabel
    -			} else {
    -				contentDownloaded += int64(n2)
    -				if count < blockSize/bufferSize {
    -					if err != nil {
    -						panic(err)
    -					}
    -					copy(ivBuff[bufferSize*count:], p)
    -				} else {
    -
    -					if bytes.Equal(iv2, emptyByteVar) {
    -						copy(iv2, ivBuff[0:blockSize])
    -						mode = cipher.NewCBCDecrypter(block, iv2)
    -					}
    -
    -					ciphertext := make([]byte, blockSize)
    -
    -					text := make([]byte, blockSize)
    -					copy(text, p[:n2])
    -
    -					mode.CryptBlocks(ciphertext, p)
    -					cryptoRan = true
    -					if contentDownloaded >= contentLenEnc {
    -						ciphertext, _ = unpadPKCS7(ciphertext)
    -						w.Write(ciphertext)
    -					} else {
    -						w.Write(ciphertext)
    -					}
    -				}
    -			}
    -			count++
    +// EncryptCipherKey creates the 256 bit hex of the cipher key
    +//
    +// It accepts the following parameters:
    +// cipherKey: cipher key to use to decrypt.
    +//
    +// returns the 256 bit hex of the cipher key.
     
    -		}
    -	}()
    +func EncryptCipherKey(cipherKey string) []byte {
    +	return crypto.EncryptCipherKey(cipherKey)
     }
    
  • utils/crypto_test.go+19 0 modified
    @@ -489,3 +489,22 @@ func CreateLoggerForTests() *log.Logger {
     	}
     	return infoLogger
     }
    +
    +func TestDecryptStuffFromPython(t *testing.T) {
    +	cipherKey := "myCipherKey"
    +	s1 := "KGc+SNJD7mIveY+KNIL/L9ZzAjC0dCJCju+HXRwSW2k="
    +	s2 := "PXjHv0L05kgj0mqIE9s7n4LDPrLtjnfamMoHyiMoL0R1uzSMsYp7dDfqEWrnoaqS"
    +	s3 := "UE5FRAFBQ1JIEHvl3cY3RYsHnbKm6VR51XG/Y7HodnkumKHxo+mrsxbIjZvFpVuILQ0oZysVwjNsDNMKiMfZteoJ8P1/mvPmbuQKLErBzS2l7vEohCwbmAJODPR2yNhJGB8989reTZ7Y7Q=="
    +
    +	d1, _ := DecryptString(cipherKey, s1, false)
    +	d2, _ := DecryptString(cipherKey, s2, true)
    +	d3, _ := DecryptString(cipherKey, s3, true)
    +
    +	r1 := d1.(string)
    +	r2 := d2.(string)
    +	r3 := d3.(string)
    +
    +	fmt.Printf("%s: => %d\n", r1, len(r1))
    +	fmt.Printf("%s: => %d\n", r2, len(r2))
    +	fmt.Printf("%s: => %d\n", r3, len(r3))
    +}
    
  • utils.go+120 0 added
    @@ -0,0 +1,120 @@
    +package pubnub
    +
    +import (
    +	"bytes"
    +	"encoding/base64"
    +	"encoding/json"
    +	"fmt"
    +	"github.com/pubnub/go/v7/crypto"
    +	"github.com/pubnub/go/v7/pnerr"
    +	"io"
    +	"strconv"
    +)
    +
    +// encodeNonAsciiChars creates unicode string of the non-ascii chars.
    +// It accepts the following parameters:
    +// message: to parse.
    +//
    +// returns the encoded string.
    +func encodeNonASCIIChars(message string) string {
    +	runeOfMessage := []rune(message)
    +	lenOfRune := len(runeOfMessage)
    +	encodedString := bytes.NewBuffer(make([]byte, 0, lenOfRune))
    +	for i := 0; i < lenOfRune; i++ {
    +		intOfRune := uint16(runeOfMessage[i])
    +		if intOfRune > 127 {
    +			hexOfRune := strconv.FormatUint(uint64(intOfRune), 16)
    +			dataLen := len(hexOfRune)
    +			paddingNum := 4 - dataLen
    +			encodedString.WriteString(`\u`)
    +			for i := 0; i < paddingNum; i++ {
    +				encodedString.WriteString("0")
    +			}
    +			encodedString.WriteString(hexOfRune)
    +		} else {
    +			encodedString.WriteString(string(runeOfMessage[i]))
    +		}
    +	}
    +	return encodedString.String()
    +}
    +
    +func encryptString(module crypto.CryptoModule, message string) (string, error) {
    +	encryptedData, e := module.Encrypt([]byte(encodeNonASCIIChars(message)))
    +	if e != nil {
    +		return "", e
    +	}
    +	return base64.StdEncoding.EncodeToString(encryptedData), nil
    +}
    +
    +func serializeEncryptAndSerialize(cryptoModule crypto.CryptoModule, msg interface{}, serialize bool) (string, error) {
    +	var encrypted string
    +	var err error
    +
    +	if serialize {
    +		jsonSerialized, errJSONMarshal := json.Marshal(msg)
    +		if errJSONMarshal != nil {
    +			return "", errJSONMarshal
    +		}
    +		encrypted, err = encryptString(cryptoModule, string(jsonSerialized))
    +
    +	} else {
    +		if serializedMsg, ok := msg.(string); ok {
    +			encrypted, err = encryptString(cryptoModule, string(serializedMsg))
    +		} else {
    +			return "", pnerr.NewBuildRequestError("Message is not JSON serialized.")
    +		}
    +	}
    +	if err != nil {
    +		return "", err
    +	}
    +	jsonSerialized, errJSONMarshal := json.Marshal(encrypted)
    +	if errJSONMarshal != nil {
    +		return "", errJSONMarshal
    +	}
    +	return string(jsonSerialized), nil
    +}
    +
    +func serializeAndEncrypt(cryptoModule crypto.CryptoModule, msg interface{}, serialize bool) (string, error) {
    +	var encrypted string
    +	var err error
    +	if serialize {
    +		jsonSerialized, errJSONMarshal := json.Marshal(msg)
    +		if errJSONMarshal != nil {
    +			return "", errJSONMarshal
    +		}
    +		encrypted, err = encryptString(cryptoModule, string(jsonSerialized))
    +	} else {
    +		if serializedMsg, ok := msg.(string); ok {
    +			encrypted, err = encryptString(cryptoModule, serializedMsg)
    +		} else {
    +			return "", pnerr.NewBuildRequestError("Message is not JSON serialized.")
    +		}
    +	}
    +	if err != nil {
    +		return "", err
    +	}
    +
    +	return encrypted, nil
    +}
    +
    +func encryptStreamAndCopyTo(module crypto.CryptoModule, reader io.Reader, writer io.Writer) error {
    +	encryptedStream, e := module.EncryptStream(reader)
    +	if e != nil {
    +		return e
    +	}
    +	_, e = io.Copy(writer, encryptedStream)
    +	if e != nil {
    +		return e
    +	}
    +	return nil
    +}
    +
    +func decryptString(cryptoModule crypto.CryptoModule, message string) (retVal interface{}, err error) {
    +	value, decodeErr := base64.StdEncoding.DecodeString(message)
    +	if decodeErr != nil {
    +		return "***decrypt error***", fmt.Errorf("decrypt error on decode: %s", decodeErr)
    +	}
    +
    +	val, e := cryptoModule.Decrypt(value)
    +	return fmt.Sprintf("%s", string(val)), e
    +}
    
fb6cd0417cbb

feat/CryptoModule (#339)

https://github.com/pubnub/javascriptMohit TejaniOct 16, 2023via ghsa
65 files changed · +4275 374
  • CHANGELOG.md+9 0 modified
    @@ -1,3 +1,12 @@
    +## v7.4.0
    +October 16 2023
    +
    +#### Added
    +- Add crypto module that allows configure SDK to encrypt and decrypt messages.
    +
    +#### Fixed
    +- Improved security of crypto implementation by adding enhanced AES-CBC cryptor.
    +
     ## v7.3.3
     September 11 2023
     
    
  • dist/web/pubnub.js+715 112 modified
    @@ -636,7 +636,7 @@
                 this.sdkFamily = setup.sdkFamily;
                 this.partnerId = setup.partnerId;
                 this.setAuthKey(setup.authKey);
    -            this.setCipherKey(setup.cipherKey);
    +            this.cryptoModule = setup.cryptoModule;
                 this.setFilterExpression(setup.filterExpression);
                 if (typeof setup.origin !== 'string' && !Array.isArray(setup.origin) && setup.origin !== undefined) {
                     throw new Error('Origin must be either undefined, a string or a list of strings.');
    @@ -695,6 +695,7 @@
                     }
                     this.setUUID(setup.uuid);
                 }
    +            this.setCipherKey(setup.cipherKey, setup);
             }
             // exposed setters
             default_1.prototype.getAuthKey = function () {
    @@ -704,8 +705,15 @@
                 this.authKey = val;
                 return this;
             };
    -        default_1.prototype.setCipherKey = function (val) {
    +        default_1.prototype.setCipherKey = function (val, setup, modules) {
    +            var _a;
                 this.cipherKey = val;
    +            if (this.cipherKey) {
    +                this.cryptoModule =
    +                    (_a = setup.cryptoModule) !== null && _a !== void 0 ? _a : setup.initCryptoModule({ cipherKey: this.cipherKey, useRandomIVs: this.useRandomIVs });
    +                if (modules)
    +                    modules.cryptoModule = this.cryptoModule;
    +            }
                 return this;
             };
             default_1.prototype.getUUID = function () {
    @@ -783,7 +791,7 @@
                 return this;
             };
             default_1.prototype.getVersion = function () {
    -            return '7.3.3';
    +            return '7.4.0';
             };
             default_1.prototype._addPnsdkSuffix = function (name, suffix) {
                 this._PNSDKSuffix[name] = suffix;
    @@ -841,6 +849,45 @@
             }
             return data;
         }
    +    function encode$1(input) {
    +        var base64 = '';
    +        var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    +        var bytes = new Uint8Array(input);
    +        var byteLength = bytes.byteLength;
    +        var byteRemainder = byteLength % 3;
    +        var mainLength = byteLength - byteRemainder;
    +        var a, b, c, d;
    +        var chunk;
    +        // Main loop deals with bytes in chunks of 3
    +        for (var i = 0; i < mainLength; i = i + 3) {
    +            // Combine the three bytes into a single integer
    +            chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
    +            // Use bitmasks to extract 6-bit segments from the triplet
    +            a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
    +            b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12
    +            c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6
    +            d = chunk & 63; // 63       = 2^6 - 1
    +            // Convert the raw binary segments to the appropriate ASCII encoding
    +            base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
    +        }
    +        // Deal with the remaining bytes and padding
    +        if (byteRemainder == 1) {
    +            chunk = bytes[mainLength];
    +            a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
    +            // Set the 4 least significant bits to zero
    +            b = (chunk & 3) << 4; // 3   = 2^2 - 1
    +            base64 += encodings[a] + encodings[b] + '==';
    +        }
    +        else if (byteRemainder == 2) {
    +            chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
    +            a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
    +            b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4
    +            // Set the 2 least significant bits to zero
    +            c = (chunk & 15) << 2; // 15    = 2^4 - 1
    +            base64 += encodings[a] + encodings[b] + encodings[c] + '=';
    +        }
    +        return base64;
    +    }
     
         /*eslint-disable */
         /*
    @@ -1764,11 +1811,20 @@
             });
             return { promise: promise, reject: failureResolve, fulfill: successResolve };
         }
    +    function stringToArrayBuffer(str) {
    +        var buf = new ArrayBuffer(str.length * 2);
    +        var bufView = new Uint16Array(buf);
    +        for (var i = 0, strLen = str.length; i < strLen; i++) {
    +            bufView[i] = str.charCodeAt(i);
    +        }
    +        return buf;
    +    }
         var utils$5 = {
             signPamFromParams: signPamFromParams,
             endsWith: endsWith,
             createPromise: createPromise,
             encodeString: encodeString,
    +        stringToArrayBuffer: stringToArrayBuffer,
         };
     
         /*       */
    @@ -1795,7 +1851,7 @@
     
         var default_1$7 = /** @class */ (function () {
             function default_1(_a) {
    -            var subscribeEndpoint = _a.subscribeEndpoint, leaveEndpoint = _a.leaveEndpoint, heartbeatEndpoint = _a.heartbeatEndpoint, setStateEndpoint = _a.setStateEndpoint, timeEndpoint = _a.timeEndpoint, getFileUrl = _a.getFileUrl, config = _a.config, crypto = _a.crypto, listenerManager = _a.listenerManager;
    +            var subscribeEndpoint = _a.subscribeEndpoint, leaveEndpoint = _a.leaveEndpoint, heartbeatEndpoint = _a.heartbeatEndpoint, setStateEndpoint = _a.setStateEndpoint, timeEndpoint = _a.timeEndpoint, getFileUrl = _a.getFileUrl, config = _a.config, crypto = _a.crypto, listenerManager = _a.listenerManager, cryptoModule = _a.cryptoModule;
                 this._listenerManager = listenerManager;
                 this._config = config;
                 this._leaveEndpoint = leaveEndpoint;
    @@ -1804,6 +1860,7 @@
                 this._subscribeEndpoint = subscribeEndpoint;
                 this._getFileUrl = getFileUrl;
                 this._crypto = crypto;
    +            this._cryptoModule = cryptoModule;
                 this._channels = {};
                 this._presenceChannels = {};
                 this._heartbeatChannels = {};
    @@ -1819,6 +1876,8 @@
                 this._isOnline = true;
                 this._reconnectionManager = new default_1$9({ timeEndpoint: timeEndpoint });
                 this._dedupingManager = new default_1$8({ config: config });
    +            if (this._cryptoModule)
    +                this._decoder = new TextDecoder();
             }
             default_1.prototype.adaptStateChange = function (args, callback) {
                 var _this = this;
    @@ -2286,9 +2345,20 @@
                         announce.timetoken = publishMetaData.publishTimetoken;
                         announce.publisher = message.issuingClientId;
                         var msgPayload = message.payload;
    -                    if (_this._config.cipherKey) {
    -                        var decryptedPayload = _this._crypto.decrypt(message.payload);
    -                        if (typeof decryptedPayload === 'object' && decryptedPayload !== null) {
    +                    if (_this._cryptoModule) {
    +                        var decryptedPayload = void 0;
    +                        try {
    +                            var decryptedData = _this._cryptoModule.decrypt(message.payload);
    +                            decryptedPayload =
    +                                decryptedData instanceof ArrayBuffer ? JSON.parse(_this._decoder.decode(decryptedData)) : decryptedData;
    +                        }
    +                        catch (e) {
    +                            decryptedPayload = null;
    +                            if (console && console.log) {
    +                                console.log('decryption error', e.message);
    +                            }
    +                        }
    +                        if (decryptedPayload !== null) {
                                 msgPayload = decryptedPayload;
                             }
                         }
    @@ -2322,8 +2392,26 @@
                         if (message.userMetadata) {
                             announce.userMetadata = message.userMetadata;
                         }
    -                    if (_this._config.cipherKey) {
    -                        announce.message = _this._crypto.decrypt(message.payload);
    +                    if (_this._cryptoModule) {
    +                        var decryptedPayload = void 0;
    +                        try {
    +                            var decryptedData = _this._cryptoModule.decrypt(message.payload);
    +                            decryptedPayload =
    +                                decryptedData instanceof ArrayBuffer ? JSON.parse(_this._decoder.decode(decryptedData)) : decryptedData;
    +                        }
    +                        catch (e) {
    +                            decryptedPayload = null;
    +                            // eslint-disable-next-line
    +                            if (console && console.log) {
    +                                console.log('decryption error', e.message); //eslint-disable-line
    +                            }
    +                        }
    +                        if (decryptedPayload != null) {
    +                            announce.message = decryptedPayload;
    +                        }
    +                        else {
    +                            announce.message = message.payload;
    +                        }
                         }
                         else {
                             announce.message = message.payload;
    @@ -4622,11 +4710,11 @@
         };
     
         /**       */
    -    var preparePayload = function (_a, payload) {
    -        var crypto = _a.crypto, config = _a.config;
    +    var preparePayload = function (modules, payload) {
             var stringifiedPayload = JSON.stringify(payload);
    -        if (config.cipherKey) {
    -            stringifiedPayload = crypto.encrypt(stringifiedPayload);
    +        if (modules.cryptoModule) {
    +            var encrypted = modules.cryptoModule.encrypt(stringifiedPayload);
    +            stringifiedPayload = typeof encrypted === 'string' ? encrypted : encode$1(encrypted);
                 stringifiedPayload = JSON.stringify(stringifiedPayload);
             }
             return stringifiedPayload || '';
    @@ -4681,13 +4769,13 @@
     
         var sendFile = function (_a) {
             var _this = this;
    -        var generateUploadUrl = _a.generateUploadUrl, publishFile = _a.publishFile, _b = _a.modules, PubNubFile = _b.PubNubFile, config = _b.config, cryptography = _b.cryptography, networking = _b.networking;
    +        var generateUploadUrl = _a.generateUploadUrl, publishFile = _a.publishFile, _b = _a.modules, PubNubFile = _b.PubNubFile, config = _b.config, cryptography = _b.cryptography, cryptoModule = _b.cryptoModule, networking = _b.networking;
             return function (_a) {
                 var channel = _a.channel, input = _a.file, message = _a.message, cipherKey = _a.cipherKey, meta = _a.meta, ttl = _a.ttl, storeInHistory = _a.storeInHistory;
                 return __awaiter(_this, void 0, void 0, function () {
    -                var file, _b, _c, url, formFields, _d, id, name, formFieldsWithMimeType, result, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, e_1, errorBody, reason, retries, wasSuccessful, publishResult;
    -                return __generator(this, function (_s) {
    -                    switch (_s.label) {
    +                var file, _b, _c, url, formFields, _d, id, name, _e, formFieldsWithMimeType, result, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, e_1, errorBody, reason, retries, wasSuccessful, publishResult;
    +                return __generator(this, function (_t) {
    +                    switch (_t.label) {
                             case 0:
                                 if (!channel) {
                                     throw new PubNubError('Validation failed, check status for details', createValidationError("channel can't be empty"));
    @@ -4698,13 +4786,21 @@
                                 file = PubNubFile.create(input);
                                 return [4 /*yield*/, generateUploadUrl({ channel: channel, name: file.name })];
                             case 1:
    -                            _b = _s.sent(), _c = _b.file_upload_request, url = _c.url, formFields = _c.form_fields, _d = _b.data, id = _d.id, name = _d.name;
    -                            if (!(PubNubFile.supportsEncryptFile && (cipherKey !== null && cipherKey !== void 0 ? cipherKey : config.cipherKey))) return [3 /*break*/, 3];
    -                            return [4 /*yield*/, cryptography.encryptFile(cipherKey !== null && cipherKey !== void 0 ? cipherKey : config.cipherKey, file, PubNubFile)];
    +                            _b = _t.sent(), _c = _b.file_upload_request, url = _c.url, formFields = _c.form_fields, _d = _b.data, id = _d.id, name = _d.name;
    +                            if (!(PubNubFile.supportsEncryptFile && (cipherKey || cryptoModule))) return [3 /*break*/, 6];
    +                            if (!(cipherKey == null)) return [3 /*break*/, 3];
    +                            return [4 /*yield*/, cryptoModule.encryptFile(file, PubNubFile)];
                             case 2:
    -                            file = _s.sent();
    -                            _s.label = 3;
    -                        case 3:
    +                            _e = _t.sent();
    +                            return [3 /*break*/, 5];
    +                        case 3: return [4 /*yield*/, cryptography.encryptFile(cipherKey, file, PubNubFile)];
    +                        case 4:
    +                            _e = _t.sent();
    +                            _t.label = 5;
    +                        case 5:
    +                            file = _e;
    +                            _t.label = 6;
    +                        case 6:
                                 formFieldsWithMimeType = formFields;
                                 if (file.mimeType) {
                                     formFieldsWithMimeType = formFields.map(function (entry) {
    @@ -4713,48 +4809,48 @@
                                         return entry;
                                     });
                                 }
    -                            _s.label = 4;
    -                        case 4:
    -                            _s.trys.push([4, 18, , 19]);
    -                            if (!(PubNubFile.supportsFileUri && input.uri)) return [3 /*break*/, 7];
    -                            _f = (_e = networking).POSTFILE;
    -                            _g = [url, formFieldsWithMimeType];
    -                            return [4 /*yield*/, file.toFileUri()];
    -                        case 5: return [4 /*yield*/, _f.apply(_e, _g.concat([_s.sent()]))];
    -                        case 6:
    -                            result = _s.sent();
    -                            return [3 /*break*/, 17];
    +                            _t.label = 7;
                             case 7:
    -                            if (!PubNubFile.supportsFile) return [3 /*break*/, 10];
    -                            _j = (_h = networking).POSTFILE;
    -                            _k = [url, formFieldsWithMimeType];
    -                            return [4 /*yield*/, file.toFile()];
    -                        case 8: return [4 /*yield*/, _j.apply(_h, _k.concat([_s.sent()]))];
    +                            _t.trys.push([7, 21, , 22]);
    +                            if (!(PubNubFile.supportsFileUri && input.uri)) return [3 /*break*/, 10];
    +                            _g = (_f = networking).POSTFILE;
    +                            _h = [url, formFieldsWithMimeType];
    +                            return [4 /*yield*/, file.toFileUri()];
    +                        case 8: return [4 /*yield*/, _g.apply(_f, _h.concat([_t.sent()]))];
                             case 9:
    -                            result = _s.sent();
    -                            return [3 /*break*/, 17];
    +                            result = _t.sent();
    +                            return [3 /*break*/, 20];
                             case 10:
    -                            if (!PubNubFile.supportsBuffer) return [3 /*break*/, 13];
    -                            _m = (_l = networking).POSTFILE;
    -                            _o = [url, formFieldsWithMimeType];
    -                            return [4 /*yield*/, file.toBuffer()];
    -                        case 11: return [4 /*yield*/, _m.apply(_l, _o.concat([_s.sent()]))];
    +                            if (!PubNubFile.supportsFile) return [3 /*break*/, 13];
    +                            _k = (_j = networking).POSTFILE;
    +                            _l = [url, formFieldsWithMimeType];
    +                            return [4 /*yield*/, file.toFile()];
    +                        case 11: return [4 /*yield*/, _k.apply(_j, _l.concat([_t.sent()]))];
                             case 12:
    -                            result = _s.sent();
    -                            return [3 /*break*/, 17];
    +                            result = _t.sent();
    +                            return [3 /*break*/, 20];
                             case 13:
    -                            if (!PubNubFile.supportsBlob) return [3 /*break*/, 16];
    -                            _q = (_p = networking).POSTFILE;
    -                            _r = [url, formFieldsWithMimeType];
    -                            return [4 /*yield*/, file.toBlob()];
    -                        case 14: return [4 /*yield*/, _q.apply(_p, _r.concat([_s.sent()]))];
    +                            if (!PubNubFile.supportsBuffer) return [3 /*break*/, 16];
    +                            _o = (_m = networking).POSTFILE;
    +                            _p = [url, formFieldsWithMimeType];
    +                            return [4 /*yield*/, file.toBuffer()];
    +                        case 14: return [4 /*yield*/, _o.apply(_m, _p.concat([_t.sent()]))];
                             case 15:
    -                            result = _s.sent();
    -                            return [3 /*break*/, 17];
    -                        case 16: throw new Error('Unsupported environment');
    -                        case 17: return [3 /*break*/, 19];
    +                            result = _t.sent();
    +                            return [3 /*break*/, 20];
    +                        case 16:
    +                            if (!PubNubFile.supportsBlob) return [3 /*break*/, 19];
    +                            _r = (_q = networking).POSTFILE;
    +                            _s = [url, formFieldsWithMimeType];
    +                            return [4 /*yield*/, file.toBlob()];
    +                        case 17: return [4 /*yield*/, _r.apply(_q, _s.concat([_t.sent()]))];
                             case 18:
    -                            e_1 = _s.sent();
    +                            result = _t.sent();
    +                            return [3 /*break*/, 20];
    +                        case 19: throw new Error('Unsupported environment');
    +                        case 20: return [3 /*break*/, 22];
    +                        case 21:
    +                            e_1 = _t.sent();
                                 if (e_1.response && typeof e_1.response.text === 'string') {
                                     errorBody = e_1.response.text;
                                     reason = /<Message>(.*)<\/Message>/gi.exec(errorBody);
    @@ -4763,16 +4859,16 @@
                                 else {
                                     throw new PubNubError('Upload to bucket failed.', e_1);
                                 }
    -                        case 19:
    +                        case 22:
                                 if (result.status !== 204) {
                                     throw new PubNubError('Upload to bucket was unsuccessful', result);
                                 }
                                 retries = config.fileUploadPublishRetryLimit;
                                 wasSuccessful = false;
                                 publishResult = { timetoken: '0' };
    -                            _s.label = 20;
    -                        case 20:
    -                            _s.trys.push([20, 22, , 23]);
    +                            _t.label = 23;
    +                        case 23:
    +                            _t.trys.push([23, 25, , 26]);
                                 return [4 /*yield*/, publishFile({
                                         channel: channel,
                                         message: message,
    @@ -4782,19 +4878,19 @@
                                         storeInHistory: storeInHistory,
                                         ttl: ttl,
                                     })];
    -                        case 21:
    +                        case 24:
                                 /* eslint-disable-next-line no-await-in-loop */
    -                            publishResult = _s.sent();
    +                            publishResult = _t.sent();
                                 wasSuccessful = true;
    -                            return [3 /*break*/, 23];
    -                        case 22:
    -                            _s.sent();
    +                            return [3 /*break*/, 26];
    +                        case 25:
    +                            _t.sent();
                                 retries -= 1;
    -                            return [3 /*break*/, 23];
    -                        case 23:
    -                            if (!wasSuccessful && retries > 0) return [3 /*break*/, 20];
    -                            _s.label = 24;
    -                        case 24:
    +                            return [3 /*break*/, 26];
    +                        case 26:
    +                            if (!wasSuccessful && retries > 0) return [3 /*break*/, 23];
    +                            _t.label = 27;
    +                        case 27:
                                 if (!wasSuccessful) {
                                     throw new PubNubError('Publish failed. You may want to execute that operation manually using pubnub.publishFile', {
                                         channel: channel,
    @@ -4861,7 +4957,7 @@
             return "".concat(networking.getStandardOrigin()).concat(url);
         });
     
    -    /**       */
    +    // Download_file.js
         var endpoint$g = {
             getOperation: function () { return OPERATIONS.PNDownloadFileOperation; },
             validateParams: function (_, params) {
    @@ -4889,20 +4985,28 @@
             forceBuffered: function () { return true; },
             prepareParams: function () { return ({}); },
             handleResponse: function (_a, res, params) {
    -            var PubNubFile = _a.PubNubFile, config = _a.config, cryptography = _a.cryptography;
    +            var PubNubFile = _a.PubNubFile, config = _a.config, cryptography = _a.cryptography, cryptoModule = _a.cryptoModule;
                 return __awaiter(void 0, void 0, void 0, function () {
    -                var body;
    -                var _b, _c, _d;
    +                var body, _b;
    +                var _c, _d;
                     return __generator(this, function (_e) {
                         switch (_e.label) {
                             case 0:
                                 body = res.response.body;
    -                            if (!(PubNubFile.supportsEncryptFile && ((_b = params.cipherKey) !== null && _b !== void 0 ? _b : config.cipherKey))) return [3 /*break*/, 2];
    -                            return [4 /*yield*/, cryptography.decrypt((_c = params.cipherKey) !== null && _c !== void 0 ? _c : config.cipherKey, body)];
    +                            if (!(PubNubFile.supportsEncryptFile && (params.cipherKey || cryptoModule))) return [3 /*break*/, 5];
    +                            if (!(params.cipherKey == null)) return [3 /*break*/, 2];
    +                            return [4 /*yield*/, cryptoModule.decryptFile(PubNubFile.create({ data: body, name: params.name }), PubNubFile)];
                             case 1:
    -                            body = _e.sent();
    -                            _e.label = 2;
    -                        case 2: return [2 /*return*/, PubNubFile.create({
    +                            _b = (_e.sent()).data;
    +                            return [3 /*break*/, 4];
    +                        case 2: return [4 /*yield*/, cryptography.decrypt((_c = params.cipherKey) !== null && _c !== void 0 ? _c : config.cipherKey, body)];
    +                        case 3:
    +                            _b = _e.sent();
    +                            _e.label = 4;
    +                        case 4:
    +                            body = _b;
    +                            _e.label = 5;
    +                        case 5: return [2 /*return*/, PubNubFile.create({
                                     data: body,
                                     name: (_d = res.response.name) !== null && _d !== void 0 ? _d : params.name,
                                     mimeType: res.response.type,
    @@ -5996,13 +6100,13 @@
     
         /*       */
         function prepareMessagePayload$1(modules, messagePayload) {
    -        var crypto = modules.crypto, config = modules.config;
             var stringifiedPayload = JSON.stringify(messagePayload);
    -        if (config.cipherKey) {
    -            stringifiedPayload = crypto.encrypt(stringifiedPayload);
    +        if (modules.cryptoModule) {
    +            var encrypted = modules.cryptoModule.encrypt(stringifiedPayload);
    +            stringifiedPayload = typeof encrypted === 'string' ? encrypted : encode$1(encrypted);
                 stringifiedPayload = JSON.stringify(stringifiedPayload);
             }
    -        return stringifiedPayload;
    +        return stringifiedPayload || '';
         }
         function getOperation$7() {
             return OPERATIONS.PNPublishOperation;
    @@ -6135,13 +6239,16 @@
     
         /*       */
         function __processMessage$1(modules, message) {
    -        var config = modules.config, crypto = modules.crypto;
    -        if (!config.cipherKey)
    +        if (!modules.cryptoModule)
                 return message;
             try {
    -            return crypto.decrypt(message);
    +            var decryptedData = modules.cryptoModule.decrypt(message);
    +            var decryptedPayload = decryptedData instanceof ArrayBuffer ? JSON.parse(new TextDecoder().decode(decryptedData)) : decryptedData;
    +            return decryptedPayload;
             }
             catch (e) {
    +            if (console && console.log)
    +                console.log('decryption error', e.message);
                 return message;
             }
         }
    @@ -6331,13 +6438,16 @@
     
         /*       */
         function __processMessage(modules, message) {
    -        var config = modules.config, crypto = modules.crypto;
    -        if (!config.cipherKey)
    +        if (!modules.cryptoModule)
                 return message;
             try {
    -            return crypto.decrypt(message);
    +            var decryptedData = modules.cryptoModule.decrypt(message);
    +            var decryptedPayload = decryptedData instanceof ArrayBuffer ? JSON.parse(new TextDecoder().decode(decryptedData)) : decryptedData;
    +            return decryptedPayload;
             }
             catch (e) {
    +            if (console && console.log)
    +                console.log('decryption error', e.message);
                 return message;
             }
         }
    @@ -7375,6 +7485,7 @@
                     maximumSamplesCount: 60000,
                 });
                 this._telemetryManager = telemetryManager;
    +            var cryptoModule = this._config.cryptoModule;
                 var modules = {
                     config: config,
                     networking: networking,
    @@ -7383,10 +7494,23 @@
                     tokenManager: tokenManager,
                     telemetryManager: telemetryManager,
                     PubNubFile: setup.PubNubFile,
    +                cryptoModule: cryptoModule,
                 };
                 this.File = setup.PubNubFile;
    -            this.encryptFile = function (key, file) { return cryptography.encryptFile(key, file, _this.File); };
    -            this.decryptFile = function (key, file) { return cryptography.decryptFile(key, file, _this.File); };
    +            this.encryptFile = function (key, file) {
    +                if (arguments.length == 1 && typeof key != 'string' && modules.cryptoModule) {
    +                    file = key;
    +                    return modules.cryptoModule.encryptFile(file, this.File);
    +                }
    +                return cryptography.encryptFile(key, file, this.File);
    +            };
    +            this.decryptFile = function (key, file) {
    +                if (arguments.length == 1 && typeof key != 'string' && modules.cryptoModule) {
    +                    file = key;
    +                    return modules.cryptoModule.decryptFile(file, this.File);
    +                }
    +                return cryptography.decryptFile(key, file, this.File);
    +            };
                 var timeEndpoint = endpointCreator.bind(this, modules, timeEndpointConfig);
                 var leaveEndpoint = endpointCreator.bind(this, modules, presenceLeaveEndpointConfig);
                 var heartbeatEndpoint = endpointCreator.bind(this, modules, presenceHeartbeatEndpointConfig);
    @@ -7417,6 +7541,7 @@
                         config: modules.config,
                         listenerManager: listenerManager,
                         getFileUrl: function (params) { return getFileUrlFunction(modules, params); },
    +                    cryptoModule: modules.cryptoModule,
                     });
                     this.subscribe = subscriptionManager_1.adaptSubscribeChange.bind(subscriptionManager_1);
                     this.unsubscribe = subscriptionManager_1.adaptUnsubscribeChange.bind(subscriptionManager_1);
    @@ -7706,18 +7831,35 @@
                 this.stop = this.destroy; // --------
                 // --- deprecated  ------------------
                 // mount crypto
    -            this.encrypt = crypto.encrypt.bind(crypto);
    -            this.decrypt = crypto.decrypt.bind(crypto);
    +            this.encrypt = function (data, key) {
    +                if (typeof key === 'undefined' && modules.cryptoModule) {
    +                    var encrypted = modules.cryptoModule.encrypt(data);
    +                    return typeof encrypted === 'string' ? encrypted : encode$1(encrypted);
    +                }
    +                else {
    +                    return crypto.encrypt(data, key);
    +                }
    +            };
    +            this.decrypt = function (data, key) {
    +                if (typeof key === 'undefined' && cryptoModule) {
    +                    var decrypted = modules.cryptoModule.decrypt(data);
    +                    return decrypted instanceof ArrayBuffer ? encode$1(decrypted) : decrypted;
    +                }
    +                else {
    +                    return crypto.decrypt(data, key);
    +                }
    +            };
                 /* config */
                 this.getAuthKey = modules.config.getAuthKey.bind(modules.config);
                 this.setAuthKey = modules.config.setAuthKey.bind(modules.config);
    -            this.setCipherKey = modules.config.setCipherKey.bind(modules.config);
                 this.getUUID = modules.config.getUUID.bind(modules.config);
                 this.setUUID = modules.config.setUUID.bind(modules.config);
                 this.getUserId = modules.config.getUserId.bind(modules.config);
                 this.setUserId = modules.config.setUserId.bind(modules.config);
                 this.getFilterExpression = modules.config.getFilterExpression.bind(modules.config);
                 this.setFilterExpression = modules.config.setFilterExpression.bind(modules.config);
    +            // this.setCipherKey = modules.config.setCipherKey.bind(modules.config);
    +            this.setCipherKey = function (key) { return modules.config.setCipherKey(key, setup, modules); };
                 this.setHeartbeatInterval = modules.config.setHeartbeatInterval.bind(modules.config);
                 if (networking.hasModule('proxy')) {
                     this.setProxy = function (proxy) {
    @@ -12534,10 +12676,13 @@
                     var bKey, abPlaindata, abCipherdata;
                     return __generator(this, function (_a) {
                         switch (_a.label) {
    -                        case 0: return [4 /*yield*/, this.getKey(key)];
    +                        case 0:
    +                            if (file.data.byteLength <= 0)
    +                                throw new Error('encryption error. empty content');
    +                            return [4 /*yield*/, this.getKey(key)];
                             case 1:
                                 bKey = _a.sent();
    -                            return [4 /*yield*/, file.toArrayBuffer()];
    +                            return [4 /*yield*/, file.data.arrayBuffer()];
                             case 2:
                                 abPlaindata = _a.sent();
                                 return [4 /*yield*/, this.encryptArrayBuffer(bKey, abPlaindata)];
    @@ -12560,7 +12705,7 @@
                             case 0: return [4 /*yield*/, this.getKey(key)];
                             case 1:
                                 bKey = _a.sent();
    -                            return [4 /*yield*/, file.toArrayBuffer()];
    +                            return [4 /*yield*/, file.data.arrayBuffer()];
                             case 2:
                                 abCipherdata = _a.sent();
                                 return [4 /*yield*/, this.decryptArrayBuffer(bKey, abCipherdata)];
    @@ -12576,15 +12721,16 @@
             };
             WebCryptography.prototype.getKey = function (key) {
                 return __awaiter(this, void 0, void 0, function () {
    -                var bKey, abHash, abKey;
    +                var digest, hashHex, abKey;
                     return __generator(this, function (_a) {
                         switch (_a.label) {
    -                        case 0:
    -                            bKey = Buffer.from(key);
    -                            return [4 /*yield*/, crypto.subtle.digest('SHA-256', bKey.buffer)];
    +                        case 0: return [4 /*yield*/, crypto.subtle.digest('SHA-256', WebCryptography.encoder.encode(key))];
                             case 1:
    -                            abHash = _a.sent();
    -                            abKey = Buffer.from(Buffer.from(abHash).toString('hex').slice(0, 32), 'utf8').buffer;
    +                            digest = _a.sent();
    +                            hashHex = Array.from(new Uint8Array(digest))
    +                                .map(function (b) { return b.toString(16).padStart(2, '0'); })
    +                                .join('');
    +                            abKey = WebCryptography.encoder.encode(hashHex.slice(0, 32)).buffer;
                                 return [2 /*return*/, crypto.subtle.importKey('raw', abKey, 'AES-CBC', true, ['encrypt', 'decrypt'])];
                         }
                     });
    @@ -12607,10 +12753,18 @@
             };
             WebCryptography.prototype.decryptArrayBuffer = function (key, ciphertext) {
                 return __awaiter(this, void 0, void 0, function () {
    -                var abIv;
    +                var abIv, data;
                     return __generator(this, function (_a) {
    -                    abIv = ciphertext.slice(0, 16);
    -                    return [2 /*return*/, crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, ciphertext.slice(16))];
    +                    switch (_a.label) {
    +                        case 0:
    +                            abIv = ciphertext.slice(0, 16);
    +                            if (ciphertext.slice(WebCryptography.IV_LENGTH).byteLength <= 0)
    +                                throw new Error('decryption error: empty content');
    +                            return [4 /*yield*/, crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, ciphertext.slice(WebCryptography.IV_LENGTH))];
    +                        case 1:
    +                            data = _a.sent();
    +                            return [2 /*return*/, data];
    +                    }
                     });
                 });
             };
    @@ -12621,12 +12775,12 @@
                         switch (_a.label) {
                             case 0:
                                 abIv = crypto.getRandomValues(new Uint8Array(16));
    -                            abPlaintext = Buffer.from(plaintext).buffer;
    +                            abPlaintext = WebCryptography.encoder.encode(plaintext).buffer;
                                 return [4 /*yield*/, crypto.subtle.encrypt({ name: 'AES-CBC', iv: abIv }, key, abPlaintext)];
                             case 1:
                                 abPayload = _a.sent();
                                 ciphertext = concatArrayBuffer(abIv.buffer, abPayload);
    -                            return [2 /*return*/, Buffer.from(ciphertext).toString('utf8')];
    +                            return [2 /*return*/, WebCryptography.decoder.decode(ciphertext)];
                         }
                     });
                 });
    @@ -12637,18 +12791,20 @@
                     return __generator(this, function (_a) {
                         switch (_a.label) {
                             case 0:
    -                            abCiphertext = Buffer.from(ciphertext);
    +                            abCiphertext = WebCryptography.encoder.encode(ciphertext).buffer;
                                 abIv = abCiphertext.slice(0, 16);
                                 abPayload = abCiphertext.slice(16);
                                 return [4 /*yield*/, crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, abPayload)];
                             case 1:
                                 abPlaintext = _a.sent();
    -                            return [2 /*return*/, Buffer.from(abPlaintext).toString('utf8')];
    +                            return [2 /*return*/, WebCryptography.decoder.decode(abPlaintext)];
                         }
                     });
                 });
             };
             WebCryptography.IV_LENGTH = 16;
    +        WebCryptography.encoder = new TextEncoder();
    +        WebCryptography.decoder = new TextDecoder();
             return WebCryptography;
         }());
     
    @@ -12764,6 +12920,443 @@
             _a.supportsFileUri = false,
             _a);
     
    +    var LegacyCryptor = /** @class */ (function () {
    +        function LegacyCryptor(config) {
    +            this.config = config;
    +            this.cryptor = new default_1$a({ config: config });
    +            this.fileCryptor = new WebCryptography();
    +        }
    +        Object.defineProperty(LegacyCryptor.prototype, "identifier", {
    +            get: function () {
    +                return '';
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        LegacyCryptor.prototype.encrypt = function (data) {
    +            var stringData = typeof data === 'string' ? data : new TextDecoder().decode(data);
    +            return {
    +                data: this.cryptor.encrypt(stringData),
    +                metadata: null,
    +            };
    +        };
    +        LegacyCryptor.prototype.decrypt = function (encryptedData) {
    +            var data = typeof encryptedData.data === 'string' ? encryptedData.data : encode$1(encryptedData.data);
    +            return this.cryptor.decrypt(data);
    +        };
    +        LegacyCryptor.prototype.encryptFile = function (file, File) {
    +            var _a;
    +            return __awaiter(this, void 0, void 0, function () {
    +                return __generator(this, function (_b) {
    +                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +                    //@ts-ignore: can not detect cipherKey from old Config
    +                    return [2 /*return*/, this.fileCryptor.encryptFile((_a = this.config) === null || _a === void 0 ? void 0 : _a.cipherKey, file, File)];
    +                });
    +            });
    +        };
    +        LegacyCryptor.prototype.decryptFile = function (file, File) {
    +            return __awaiter(this, void 0, void 0, function () {
    +                return __generator(this, function (_a) {
    +                    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +                    //@ts-ignore: can not detect cipherKey from old Config
    +                    return [2 /*return*/, this.fileCryptor.decryptFile(this.config.cipherKey, file, File)];
    +                });
    +            });
    +        };
    +        return LegacyCryptor;
    +    }());
    +
    +    var AesCbcCryptor = /** @class */ (function () {
    +        function AesCbcCryptor(configuration) {
    +            this.cipherKey = configuration.cipherKey;
    +            this.CryptoJS = hmacSha256;
    +            this.encryptedKey = this.CryptoJS.SHA256(this.cipherKey);
    +        }
    +        Object.defineProperty(AesCbcCryptor.prototype, "algo", {
    +            get: function () {
    +                return 'AES-CBC';
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        Object.defineProperty(AesCbcCryptor.prototype, "identifier", {
    +            get: function () {
    +                return 'ACRH';
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        AesCbcCryptor.prototype.getIv = function () {
    +            return crypto.getRandomValues(new Uint8Array(AesCbcCryptor.BLOCK_SIZE));
    +        };
    +        AesCbcCryptor.prototype.getKey = function () {
    +            return __awaiter(this, void 0, void 0, function () {
    +                var bKey, abHash;
    +                return __generator(this, function (_a) {
    +                    switch (_a.label) {
    +                        case 0:
    +                            bKey = AesCbcCryptor.encoder.encode(this.cipherKey);
    +                            return [4 /*yield*/, crypto.subtle.digest('SHA-256', bKey.buffer)];
    +                        case 1:
    +                            abHash = _a.sent();
    +                            return [2 /*return*/, crypto.subtle.importKey('raw', abHash, this.algo, true, ['encrypt', 'decrypt'])];
    +                    }
    +                });
    +            });
    +        };
    +        AesCbcCryptor.prototype.encrypt = function (data) {
    +            var stringData = typeof data === 'string' ? data : AesCbcCryptor.decoder.decode(data);
    +            if (stringData.length === 0)
    +                throw new Error('encryption error. empty content');
    +            var abIv = this.getIv();
    +            return {
    +                metadata: abIv,
    +                data: decode$1(this.CryptoJS.AES.encrypt(data, this.encryptedKey, {
    +                    iv: this.bufferToWordArray(abIv),
    +                    mode: this.CryptoJS.mode.CBC,
    +                }).ciphertext.toString(this.CryptoJS.enc.Base64)),
    +            };
    +        };
    +        AesCbcCryptor.prototype.decrypt = function (encryptedData) {
    +            var iv = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.metadata));
    +            var data = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.data));
    +            return AesCbcCryptor.encoder.encode(this.CryptoJS.AES.decrypt({ ciphertext: data }, this.encryptedKey, {
    +                iv: iv,
    +                mode: this.CryptoJS.mode.CBC,
    +            }).toString(this.CryptoJS.enc.Utf8)).buffer;
    +        };
    +        AesCbcCryptor.prototype.encryptFileData = function (data) {
    +            return __awaiter(this, void 0, void 0, function () {
    +                var key, iv;
    +                var _a;
    +                return __generator(this, function (_b) {
    +                    switch (_b.label) {
    +                        case 0: return [4 /*yield*/, this.getKey()];
    +                        case 1:
    +                            key = _b.sent();
    +                            iv = this.getIv();
    +                            _a = {};
    +                            return [4 /*yield*/, crypto.subtle.encrypt({ name: this.algo, iv: iv }, key, data)];
    +                        case 2: return [2 /*return*/, (_a.data = _b.sent(),
    +                                _a.metadata = iv,
    +                                _a)];
    +                    }
    +                });
    +            });
    +        };
    +        AesCbcCryptor.prototype.decryptFileData = function (encryptedData) {
    +            return __awaiter(this, void 0, void 0, function () {
    +                var key;
    +                return __generator(this, function (_a) {
    +                    switch (_a.label) {
    +                        case 0: return [4 /*yield*/, this.getKey()];
    +                        case 1:
    +                            key = _a.sent();
    +                            return [2 /*return*/, crypto.subtle.decrypt({ name: this.algo, iv: encryptedData.metadata }, key, encryptedData.data)];
    +                    }
    +                });
    +            });
    +        };
    +        AesCbcCryptor.prototype.bufferToWordArray = function (b) {
    +            var wa = [];
    +            var i;
    +            for (i = 0; i < b.length; i += 1) {
    +                wa[(i / 4) | 0] |= b[i] << (24 - 8 * i);
    +            }
    +            return this.CryptoJS.lib.WordArray.create(wa, b.length);
    +        };
    +        AesCbcCryptor.BLOCK_SIZE = 16;
    +        AesCbcCryptor.encoder = new TextEncoder();
    +        AesCbcCryptor.decoder = new TextDecoder();
    +        return AesCbcCryptor;
    +    }());
    +
    +    var CryptoModule = /** @class */ (function () {
    +        function CryptoModule(cryptoModuleConfiguration) {
    +            var _a;
    +            this.defaultCryptor = cryptoModuleConfiguration.default;
    +            this.cryptors = (_a = cryptoModuleConfiguration.cryptors) !== null && _a !== void 0 ? _a : [];
    +        }
    +        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +        //@ts-ignore: type detection issue with old Config type assignment
    +        CryptoModule.legacyCryptoModule = function (config) {
    +            var _a;
    +            return new this({
    +                default: new LegacyCryptor({
    +                    cipherKey: config.cipherKey,
    +                    useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +                }),
    +                cryptors: [new AesCbcCryptor({ cipherKey: config.cipherKey })],
    +            });
    +        };
    +        CryptoModule.aesCbcCryptoModule = function (config) {
    +            var _a;
    +            return new this({
    +                default: new AesCbcCryptor({ cipherKey: config.cipherKey }),
    +                cryptors: [
    +                    new LegacyCryptor({
    +                        cipherKey: config.cipherKey,
    +                        useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +                    }),
    +                ],
    +            });
    +        };
    +        CryptoModule.withDefaultCryptor = function (defaultCryptor) {
    +            return new this({ default: defaultCryptor });
    +        };
    +        CryptoModule.prototype.getAllCryptors = function () {
    +            return __spreadArray([this.defaultCryptor], __read(this.cryptors), false);
    +        };
    +        CryptoModule.prototype.encrypt = function (data) {
    +            var encrypted = this.defaultCryptor.encrypt(data);
    +            if (!encrypted.metadata)
    +                return encrypted.data;
    +            var headerData = this.getHeaderData(encrypted);
    +            return this.concatArrayBuffer(headerData, encrypted.data);
    +        };
    +        CryptoModule.prototype.decrypt = function (data) {
    +            var encryptedData = typeof data === 'string' ? decode$1(data) : data;
    +            var header = CryptorHeader.tryParse(encryptedData);
    +            var cryptor = this.getCryptor(header);
    +            var metadata = header.length > 0
    +                ? encryptedData.slice(header.length - header.metadataLength, header.length)
    +                : null;
    +            if (encryptedData.slice(header.length).byteLength <= 0)
    +                throw new Error('decryption error. empty content');
    +            return cryptor.decrypt({
    +                data: encryptedData.slice(header.length),
    +                metadata: metadata,
    +            });
    +        };
    +        CryptoModule.prototype.encryptFile = function (file, File) {
    +            return __awaiter(this, void 0, void 0, function () {
    +                var fileData, encrypted;
    +                return __generator(this, function (_a) {
    +                    switch (_a.label) {
    +                        case 0:
    +                            if (this.defaultCryptor.identifier === CryptorHeader.LEGACY_IDENTIFIER)
    +                                return [2 /*return*/, this.defaultCryptor.encryptFile(file, File)];
    +                            fileData = this.getFileData(file.data);
    +                            return [4 /*yield*/, this.defaultCryptor.encryptFileData(fileData)];
    +                        case 1:
    +                            encrypted = _a.sent();
    +                            return [2 /*return*/, File.create({
    +                                    name: file.name,
    +                                    mimeType: 'application/octet-stream',
    +                                    data: this.concatArrayBuffer(this.getHeaderData(encrypted), encrypted.data),
    +                                })];
    +                    }
    +                });
    +            });
    +        };
    +        CryptoModule.prototype.decryptFile = function (file, File) {
    +            return __awaiter(this, void 0, void 0, function () {
    +                var data, header, cryptor, fileData, metadata, _a, _b;
    +                var _c;
    +                return __generator(this, function (_d) {
    +                    switch (_d.label) {
    +                        case 0: return [4 /*yield*/, file.data.arrayBuffer()];
    +                        case 1:
    +                            data = _d.sent();
    +                            header = CryptorHeader.tryParse(data);
    +                            cryptor = this.getCryptor(header);
    +                            if ((cryptor === null || cryptor === void 0 ? void 0 : cryptor.identifier) === CryptoModule.LEGACY_IDENTIFIER) {
    +                                return [2 /*return*/, cryptor.decryptFile(file, File)];
    +                            }
    +                            fileData = this.getFileData(data);
    +                            metadata = fileData.slice(header.length - header.metadataLength, header.length);
    +                            _b = (_a = File).create;
    +                            _c = {
    +                                name: file.name
    +                            };
    +                            return [4 /*yield*/, this.defaultCryptor.decryptFileData({
    +                                    data: data.slice(header.length),
    +                                    metadata: metadata,
    +                                })];
    +                        case 2: return [2 /*return*/, _b.apply(_a, [(_c.data = _d.sent(),
    +                                    _c)])];
    +                    }
    +                });
    +            });
    +        };
    +        CryptoModule.prototype.getCryptor = function (header) {
    +            if (header === '') {
    +                var cryptor = this.getAllCryptors().find(function (c) { return c.identifier === ''; });
    +                if (cryptor)
    +                    return cryptor;
    +                throw new Error('unknown cryptor error');
    +            }
    +            else if (header instanceof CryptorHeaderV1) {
    +                return this.getCryptorFromId(header.identifier);
    +            }
    +        };
    +        CryptoModule.prototype.getCryptorFromId = function (id) {
    +            var cryptor = this.getAllCryptors().find(function (c) { return id === c.identifier; });
    +            if (cryptor) {
    +                return cryptor;
    +            }
    +            throw Error('unknown cryptor error');
    +        };
    +        CryptoModule.prototype.concatArrayBuffer = function (ab1, ab2) {
    +            var tmp = new Uint8Array(ab1.byteLength + ab2.byteLength);
    +            tmp.set(new Uint8Array(ab1), 0);
    +            tmp.set(new Uint8Array(ab2), ab1.byteLength);
    +            return tmp.buffer;
    +        };
    +        CryptoModule.prototype.getHeaderData = function (encrypted) {
    +            if (!encrypted.metadata)
    +                return;
    +            var header = CryptorHeader.from(this.defaultCryptor.identifier, encrypted.metadata);
    +            var headerData = new Uint8Array(header.length);
    +            var pos = 0;
    +            headerData.set(header.data, pos);
    +            pos += header.length - encrypted.metadata.byteLength;
    +            headerData.set(new Uint8Array(encrypted.metadata), pos);
    +            return headerData.buffer;
    +        };
    +        CryptoModule.prototype.getFileData = function (input) {
    +            if (input instanceof ArrayBuffer) {
    +                return input;
    +            }
    +            if (typeof input === 'string') {
    +                return CryptoModule.encoder.encode(input);
    +            }
    +            throw new Error('Cannot decrypt/encrypt file. In browsers file decryption supports only string or ArrayBuffer');
    +        };
    +        CryptoModule.LEGACY_IDENTIFIER = '';
    +        CryptoModule.encoder = new TextEncoder();
    +        CryptoModule.decoder = new TextDecoder();
    +        return CryptoModule;
    +    }());
    +    // CryptorHeader Utility
    +    var CryptorHeader = /** @class */ (function () {
    +        function CryptorHeader() {
    +        }
    +        CryptorHeader.from = function (id, metadata) {
    +            if (id === CryptorHeader.LEGACY_IDENTIFIER)
    +                return;
    +            return new CryptorHeaderV1(id, metadata.byteLength);
    +        };
    +        CryptorHeader.tryParse = function (data) {
    +            var encryptedData = new Uint8Array(data);
    +            var sentinel = '';
    +            var version = null;
    +            if (encryptedData.byteLength >= 4) {
    +                sentinel = encryptedData.slice(0, 4);
    +                if (this.decoder.decode(sentinel) !== CryptorHeader.SENTINEL)
    +                    return '';
    +            }
    +            if (encryptedData.byteLength >= 5) {
    +                version = encryptedData[4];
    +            }
    +            else {
    +                throw new Error('decryption error. invalid header version');
    +            }
    +            if (version > CryptorHeader.MAX_VERSION)
    +                throw new Error('unknown cryptor error');
    +            var identifier = '';
    +            var pos = 5 + CryptorHeader.IDENTIFIER_LENGTH;
    +            if (encryptedData.byteLength >= pos) {
    +                identifier = encryptedData.slice(5, pos);
    +            }
    +            else {
    +                throw new Error('decryption error. invalid crypto identifier');
    +            }
    +            var metadataLength = null;
    +            if (encryptedData.byteLength >= pos + 1) {
    +                metadataLength = encryptedData[pos];
    +            }
    +            else {
    +                throw new Error('decryption error. invalid metadata length');
    +            }
    +            pos += 1;
    +            if (metadataLength === 255 && encryptedData.byteLength >= pos + 2) {
    +                metadataLength = new Uint16Array(encryptedData.slice(pos, pos + 2)).reduce(function (acc, val) { return (acc << 8) + val; }, 0);
    +                pos += 2;
    +            }
    +            return new CryptorHeaderV1(this.decoder.decode(identifier), metadataLength);
    +        };
    +        CryptorHeader.SENTINEL = 'PNED';
    +        CryptorHeader.LEGACY_IDENTIFIER = '';
    +        CryptorHeader.IDENTIFIER_LENGTH = 4;
    +        CryptorHeader.VERSION = 1;
    +        CryptorHeader.MAX_VERSION = 1;
    +        CryptorHeader.decoder = new TextDecoder();
    +        return CryptorHeader;
    +    }());
    +    // v1 CryptorHeader
    +    var CryptorHeaderV1 = /** @class */ (function () {
    +        function CryptorHeaderV1(id, metadataLength) {
    +            this._identifier = id;
    +            this._metadataLength = metadataLength;
    +        }
    +        Object.defineProperty(CryptorHeaderV1.prototype, "identifier", {
    +            get: function () {
    +                return this._identifier;
    +            },
    +            set: function (value) {
    +                this._identifier = value;
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        Object.defineProperty(CryptorHeaderV1.prototype, "metadataLength", {
    +            get: function () {
    +                return this._metadataLength;
    +            },
    +            set: function (value) {
    +                this._metadataLength = value;
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        Object.defineProperty(CryptorHeaderV1.prototype, "version", {
    +            get: function () {
    +                return CryptorHeader.VERSION;
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        Object.defineProperty(CryptorHeaderV1.prototype, "length", {
    +            get: function () {
    +                return (CryptorHeader.SENTINEL.length +
    +                    1 +
    +                    CryptorHeader.IDENTIFIER_LENGTH +
    +                    (this.metadataLength < 255 ? 1 : 3) +
    +                    this.metadataLength);
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        Object.defineProperty(CryptorHeaderV1.prototype, "data", {
    +            get: function () {
    +                var pos = 0;
    +                var header = new Uint8Array(this.length);
    +                var encoder = new TextEncoder();
    +                header.set(encoder.encode(CryptorHeader.SENTINEL));
    +                pos += CryptorHeader.SENTINEL.length;
    +                header[pos] = this.version;
    +                pos++;
    +                if (this.identifier)
    +                    header.set(encoder.encode(this.identifier), pos);
    +                pos += CryptorHeader.IDENTIFIER_LENGTH;
    +                var metadataLength = this.metadataLength;
    +                if (metadataLength < 255) {
    +                    header[pos] = metadataLength;
    +                }
    +                else {
    +                    header.set([255, metadataLength >> 8, metadataLength & 0xff], pos);
    +                }
    +                return header;
    +            },
    +            enumerable: false,
    +            configurable: true
    +        });
    +        CryptorHeaderV1.IDENTIFIER_LENGTH = 4;
    +        CryptorHeaderV1.SENTINEL = 'PNED';
    +        return CryptorHeaderV1;
    +    }());
    +
         /* eslint no-bitwise: ["error", { "allow": ["~", "&", ">>"] }] */
         function sendBeacon(url) {
             if (navigator && navigator.sendBeacon) {
    @@ -12792,6 +13385,15 @@
                 setup.cbor = new default_1$1(function (arrayBuffer) { return stringifyBufferKeys(CborReader.decode(arrayBuffer)); }, decode$1);
                 setup.PubNubFile = PubNubFile;
                 setup.cryptography = new WebCryptography();
    +            setup.initCryptoModule = function (cryptoConfiguration) {
    +                return new CryptoModule({
    +                    default: new LegacyCryptor({
    +                        cipherKey: cryptoConfiguration.cipherKey,
    +                        useRandomIVs: cryptoConfiguration.useRandomIVs,
    +                    }),
    +                    cryptors: [new AesCbcCryptor({ cipherKey: cryptoConfiguration.cipherKey })],
    +                });
    +            };
                 _this = _super.call(this, setup) || this;
                 if (listenToBrowserNetworkEvents) {
                     // mount network events.
    @@ -12804,6 +13406,7 @@
                 }
                 return _this;
             }
    +        default_1.CryptoModule = CryptoModule;
             return default_1;
         }(default_1$3));
     
    
  • dist/web/pubnub.min.js+2 2 modified
  • lib/core/components/base64_codec.js+41 1 modified
    @@ -1,6 +1,6 @@
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
    -exports.decode = void 0;
    +exports.encode = exports.decode = void 0;
     var BASE64_CHARMAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
     /**
      * Decode a Base64 encoded string.
    @@ -48,3 +48,43 @@ function decode(paddedInput) {
         return data;
     }
     exports.decode = decode;
    +function encode(input) {
    +    var base64 = '';
    +    var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    +    var bytes = new Uint8Array(input);
    +    var byteLength = bytes.byteLength;
    +    var byteRemainder = byteLength % 3;
    +    var mainLength = byteLength - byteRemainder;
    +    var a, b, c, d;
    +    var chunk;
    +    // Main loop deals with bytes in chunks of 3
    +    for (var i = 0; i < mainLength; i = i + 3) {
    +        // Combine the three bytes into a single integer
    +        chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
    +        // Use bitmasks to extract 6-bit segments from the triplet
    +        a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
    +        b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12
    +        c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6
    +        d = chunk & 63; // 63       = 2^6 - 1
    +        // Convert the raw binary segments to the appropriate ASCII encoding
    +        base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
    +    }
    +    // Deal with the remaining bytes and padding
    +    if (byteRemainder == 1) {
    +        chunk = bytes[mainLength];
    +        a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
    +        // Set the 4 least significant bits to zero
    +        b = (chunk & 3) << 4; // 3   = 2^2 - 1
    +        base64 += encodings[a] + encodings[b] + '==';
    +    }
    +    else if (byteRemainder == 2) {
    +        chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
    +        a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
    +        b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4
    +        // Set the 2 least significant bits to zero
    +        c = (chunk & 15) << 2; // 15    = 2^4 - 1
    +        base64 += encodings[a] + encodings[b] + encodings[c] + '=';
    +    }
    +    return base64;
    +}
    +exports.encode = encode;
    
  • lib/core/components/config.js+11 3 modified
    @@ -22,7 +22,7 @@ var default_1 = /** @class */ (function () {
             this.sdkFamily = setup.sdkFamily;
             this.partnerId = setup.partnerId;
             this.setAuthKey(setup.authKey);
    -        this.setCipherKey(setup.cipherKey);
    +        this.cryptoModule = setup.cryptoModule;
             this.setFilterExpression(setup.filterExpression);
             if (typeof setup.origin !== 'string' && !Array.isArray(setup.origin) && setup.origin !== undefined) {
                 throw new Error('Origin must be either undefined, a string or a list of strings.');
    @@ -81,6 +81,7 @@ var default_1 = /** @class */ (function () {
                 }
                 this.setUUID(setup.uuid);
             }
    +        this.setCipherKey(setup.cipherKey, setup);
         }
         // exposed setters
         default_1.prototype.getAuthKey = function () {
    @@ -90,8 +91,15 @@ var default_1 = /** @class */ (function () {
             this.authKey = val;
             return this;
         };
    -    default_1.prototype.setCipherKey = function (val) {
    +    default_1.prototype.setCipherKey = function (val, setup, modules) {
    +        var _a;
             this.cipherKey = val;
    +        if (this.cipherKey) {
    +            this.cryptoModule =
    +                (_a = setup.cryptoModule) !== null && _a !== void 0 ? _a : setup.initCryptoModule({ cipherKey: this.cipherKey, useRandomIVs: this.useRandomIVs });
    +            if (modules)
    +                modules.cryptoModule = this.cryptoModule;
    +        }
             return this;
         };
         default_1.prototype.getUUID = function () {
    @@ -169,7 +177,7 @@ var default_1 = /** @class */ (function () {
             return this;
         };
         default_1.prototype.getVersion = function () {
    -        return '7.3.3';
    +        return '7.4.0';
         };
         default_1.prototype._addPnsdkSuffix = function (name, suffix) {
             this._PNSDKSuffix[name] = suffix;
    
  • lib/core/components/subscription_manager.js+38 6 modified
    @@ -31,7 +31,7 @@ var utils_1 = __importDefault(require("../utils"));
     var categories_1 = __importDefault(require("../constants/categories"));
     var default_1 = /** @class */ (function () {
         function default_1(_a) {
    -        var subscribeEndpoint = _a.subscribeEndpoint, leaveEndpoint = _a.leaveEndpoint, heartbeatEndpoint = _a.heartbeatEndpoint, setStateEndpoint = _a.setStateEndpoint, timeEndpoint = _a.timeEndpoint, getFileUrl = _a.getFileUrl, config = _a.config, crypto = _a.crypto, listenerManager = _a.listenerManager;
    +        var subscribeEndpoint = _a.subscribeEndpoint, leaveEndpoint = _a.leaveEndpoint, heartbeatEndpoint = _a.heartbeatEndpoint, setStateEndpoint = _a.setStateEndpoint, timeEndpoint = _a.timeEndpoint, getFileUrl = _a.getFileUrl, config = _a.config, crypto = _a.crypto, listenerManager = _a.listenerManager, cryptoModule = _a.cryptoModule;
             this._listenerManager = listenerManager;
             this._config = config;
             this._leaveEndpoint = leaveEndpoint;
    @@ -40,6 +40,7 @@ var default_1 = /** @class */ (function () {
             this._subscribeEndpoint = subscribeEndpoint;
             this._getFileUrl = getFileUrl;
             this._crypto = crypto;
    +        this._cryptoModule = cryptoModule;
             this._channels = {};
             this._presenceChannels = {};
             this._heartbeatChannels = {};
    @@ -55,6 +56,8 @@ var default_1 = /** @class */ (function () {
             this._isOnline = true;
             this._reconnectionManager = new reconnection_manager_1.default({ timeEndpoint: timeEndpoint });
             this._dedupingManager = new deduping_manager_1.default({ config: config });
    +        if (this._cryptoModule)
    +            this._decoder = new TextDecoder();
         }
         default_1.prototype.adaptStateChange = function (args, callback) {
             var _this = this;
    @@ -522,9 +525,20 @@ var default_1 = /** @class */ (function () {
                     announce.timetoken = publishMetaData.publishTimetoken;
                     announce.publisher = message.issuingClientId;
                     var msgPayload = message.payload;
    -                if (_this._config.cipherKey) {
    -                    var decryptedPayload = _this._crypto.decrypt(message.payload);
    -                    if (typeof decryptedPayload === 'object' && decryptedPayload !== null) {
    +                if (_this._cryptoModule) {
    +                    var decryptedPayload = void 0;
    +                    try {
    +                        var decryptedData = _this._cryptoModule.decrypt(message.payload);
    +                        decryptedPayload =
    +                            decryptedData instanceof ArrayBuffer ? JSON.parse(_this._decoder.decode(decryptedData)) : decryptedData;
    +                    }
    +                    catch (e) {
    +                        decryptedPayload = null;
    +                        if (console && console.log) {
    +                            console.log('decryption error', e.message);
    +                        }
    +                    }
    +                    if (decryptedPayload !== null) {
                             msgPayload = decryptedPayload;
                         }
                     }
    @@ -558,8 +572,26 @@ var default_1 = /** @class */ (function () {
                     if (message.userMetadata) {
                         announce.userMetadata = message.userMetadata;
                     }
    -                if (_this._config.cipherKey) {
    -                    announce.message = _this._crypto.decrypt(message.payload);
    +                if (_this._cryptoModule) {
    +                    var decryptedPayload = void 0;
    +                    try {
    +                        var decryptedData = _this._cryptoModule.decrypt(message.payload);
    +                        decryptedPayload =
    +                            decryptedData instanceof ArrayBuffer ? JSON.parse(_this._decoder.decode(decryptedData)) : decryptedData;
    +                    }
    +                    catch (e) {
    +                        decryptedPayload = null;
    +                        // eslint-disable-next-line
    +                        if (console && console.log) {
    +                            console.log('decryption error', e.message); //eslint-disable-line
    +                        }
    +                    }
    +                    if (decryptedPayload != null) {
    +                        announce.message = decryptedPayload;
    +                    }
    +                    else {
    +                        announce.message = message.payload;
    +                    }
                     }
                     else {
                         announce.message = message.payload;
    
  • lib/core/endpoints/fetch_messages.js+6 3 modified
    @@ -8,13 +8,16 @@ exports.handleResponse = exports.prepareParams = exports.isAuthSupported = expor
     var operations_1 = __importDefault(require("../constants/operations"));
     var utils_1 = __importDefault(require("../utils"));
     function __processMessage(modules, message) {
    -    var config = modules.config, crypto = modules.crypto;
    -    if (!config.cipherKey)
    +    if (!modules.cryptoModule)
             return message;
         try {
    -        return crypto.decrypt(message);
    +        var decryptedData = modules.cryptoModule.decrypt(message);
    +        var decryptedPayload = decryptedData instanceof ArrayBuffer ? JSON.parse(new TextDecoder().decode(decryptedData)) : decryptedData;
    +        return decryptedPayload;
         }
         catch (e) {
    +        if (console && console.log)
    +            console.log('decryption error', e.message);
             return message;
         }
     }
    
  • lib/core/endpoints/file_upload/download_file.js+18 9 modified
    @@ -1,5 +1,5 @@
     "use strict";
    -/**       */
    +// Download_file.js
     var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
         function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
         return new (P || (P = Promise))(function (resolve, reject) {
    @@ -40,6 +40,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
         return (mod && mod.__esModule) ? mod : { "default": mod };
     };
     Object.defineProperty(exports, "__esModule", { value: true });
    +/**       */
     var operations_1 = __importDefault(require("../../constants/operations"));
     var utils_1 = __importDefault(require("../../utils"));
     var endpoint = {
    @@ -69,20 +70,28 @@ var endpoint = {
         forceBuffered: function () { return true; },
         prepareParams: function () { return ({}); },
         handleResponse: function (_a, res, params) {
    -        var PubNubFile = _a.PubNubFile, config = _a.config, cryptography = _a.cryptography;
    +        var PubNubFile = _a.PubNubFile, config = _a.config, cryptography = _a.cryptography, cryptoModule = _a.cryptoModule;
             return __awaiter(void 0, void 0, void 0, function () {
    -            var body;
    -            var _b, _c, _d;
    +            var body, _b;
    +            var _c, _d;
                 return __generator(this, function (_e) {
                     switch (_e.label) {
                         case 0:
                             body = res.response.body;
    -                        if (!(PubNubFile.supportsEncryptFile && ((_b = params.cipherKey) !== null && _b !== void 0 ? _b : config.cipherKey))) return [3 /*break*/, 2];
    -                        return [4 /*yield*/, cryptography.decrypt((_c = params.cipherKey) !== null && _c !== void 0 ? _c : config.cipherKey, body)];
    +                        if (!(PubNubFile.supportsEncryptFile && (params.cipherKey || cryptoModule))) return [3 /*break*/, 5];
    +                        if (!(params.cipherKey == null)) return [3 /*break*/, 2];
    +                        return [4 /*yield*/, cryptoModule.decryptFile(PubNubFile.create({ data: body, name: params.name }), PubNubFile)];
                         case 1:
    -                        body = _e.sent();
    -                        _e.label = 2;
    -                    case 2: return [2 /*return*/, PubNubFile.create({
    +                        _b = (_e.sent()).data;
    +                        return [3 /*break*/, 4];
    +                    case 2: return [4 /*yield*/, cryptography.decrypt((_c = params.cipherKey) !== null && _c !== void 0 ? _c : config.cipherKey, body)];
    +                    case 3:
    +                        _b = _e.sent();
    +                        _e.label = 4;
    +                    case 4:
    +                        body = _b;
    +                        _e.label = 5;
    +                    case 5: return [2 /*return*/, PubNubFile.create({
                                 data: body,
                                 name: (_d = res.response.name) !== null && _d !== void 0 ? _d : params.name,
                                 mimeType: res.response.type,
    
  • lib/core/endpoints/file_upload/publish_file.js+5 4 modified
    @@ -6,11 +6,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
     Object.defineProperty(exports, "__esModule", { value: true });
     var operations_1 = __importDefault(require("../../constants/operations"));
     var utils_1 = __importDefault(require("../../utils"));
    -var preparePayload = function (_a, payload) {
    -    var crypto = _a.crypto, config = _a.config;
    +var base64_codec_1 = require("../../components/base64_codec");
    +var preparePayload = function (modules, payload) {
         var stringifiedPayload = JSON.stringify(payload);
    -    if (config.cipherKey) {
    -        stringifiedPayload = crypto.encrypt(stringifiedPayload);
    +    if (modules.cryptoModule) {
    +        var encrypted = modules.cryptoModule.encrypt(stringifiedPayload);
    +        stringifiedPayload = typeof encrypted === 'string' ? encrypted : (0, base64_codec_1.encode)(encrypted);
             stringifiedPayload = JSON.stringify(stringifiedPayload);
         }
         return stringifiedPayload || '';
    
  • lib/core/endpoints/file_upload/send_file.js+68 60 modified
    @@ -39,13 +39,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
     var endpoint_1 = require("../../components/endpoint");
     var sendFile = function (_a) {
         var _this = this;
    -    var generateUploadUrl = _a.generateUploadUrl, publishFile = _a.publishFile, _b = _a.modules, PubNubFile = _b.PubNubFile, config = _b.config, cryptography = _b.cryptography, networking = _b.networking;
    +    var generateUploadUrl = _a.generateUploadUrl, publishFile = _a.publishFile, _b = _a.modules, PubNubFile = _b.PubNubFile, config = _b.config, cryptography = _b.cryptography, cryptoModule = _b.cryptoModule, networking = _b.networking;
         return function (_a) {
             var channel = _a.channel, input = _a.file, message = _a.message, cipherKey = _a.cipherKey, meta = _a.meta, ttl = _a.ttl, storeInHistory = _a.storeInHistory;
             return __awaiter(_this, void 0, void 0, function () {
    -            var file, _b, _c, url, formFields, _d, id, name, formFieldsWithMimeType, result, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, e_1, errorBody, reason, retries, wasSuccessful, publishResult, e_2;
    -            return __generator(this, function (_s) {
    -                switch (_s.label) {
    +            var file, _b, _c, url, formFields, _d, id, name, _e, formFieldsWithMimeType, result, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, e_1, errorBody, reason, retries, wasSuccessful, publishResult, e_2;
    +            return __generator(this, function (_t) {
    +                switch (_t.label) {
                         case 0:
                             if (!channel) {
                                 throw new endpoint_1.PubNubError('Validation failed, check status for details', (0, endpoint_1.createValidationError)("channel can't be empty"));
    @@ -56,13 +56,21 @@ var sendFile = function (_a) {
                             file = PubNubFile.create(input);
                             return [4 /*yield*/, generateUploadUrl({ channel: channel, name: file.name })];
                         case 1:
    -                        _b = _s.sent(), _c = _b.file_upload_request, url = _c.url, formFields = _c.form_fields, _d = _b.data, id = _d.id, name = _d.name;
    -                        if (!(PubNubFile.supportsEncryptFile && (cipherKey !== null && cipherKey !== void 0 ? cipherKey : config.cipherKey))) return [3 /*break*/, 3];
    -                        return [4 /*yield*/, cryptography.encryptFile(cipherKey !== null && cipherKey !== void 0 ? cipherKey : config.cipherKey, file, PubNubFile)];
    +                        _b = _t.sent(), _c = _b.file_upload_request, url = _c.url, formFields = _c.form_fields, _d = _b.data, id = _d.id, name = _d.name;
    +                        if (!(PubNubFile.supportsEncryptFile && (cipherKey || cryptoModule))) return [3 /*break*/, 6];
    +                        if (!(cipherKey == null)) return [3 /*break*/, 3];
    +                        return [4 /*yield*/, cryptoModule.encryptFile(file, PubNubFile)];
                         case 2:
    -                        file = _s.sent();
    -                        _s.label = 3;
    -                    case 3:
    +                        _e = _t.sent();
    +                        return [3 /*break*/, 5];
    +                    case 3: return [4 /*yield*/, cryptography.encryptFile(cipherKey, file, PubNubFile)];
    +                    case 4:
    +                        _e = _t.sent();
    +                        _t.label = 5;
    +                    case 5:
    +                        file = _e;
    +                        _t.label = 6;
    +                    case 6:
                             formFieldsWithMimeType = formFields;
                             if (file.mimeType) {
                                 formFieldsWithMimeType = formFields.map(function (entry) {
    @@ -71,48 +79,48 @@ var sendFile = function (_a) {
                                     return entry;
                                 });
                             }
    -                        _s.label = 4;
    -                    case 4:
    -                        _s.trys.push([4, 18, , 19]);
    -                        if (!(PubNubFile.supportsFileUri && input.uri)) return [3 /*break*/, 7];
    -                        _f = (_e = networking).POSTFILE;
    -                        _g = [url, formFieldsWithMimeType];
    -                        return [4 /*yield*/, file.toFileUri()];
    -                    case 5: return [4 /*yield*/, _f.apply(_e, _g.concat([_s.sent()]))];
    -                    case 6:
    -                        result = _s.sent();
    -                        return [3 /*break*/, 17];
    +                        _t.label = 7;
                         case 7:
    -                        if (!PubNubFile.supportsFile) return [3 /*break*/, 10];
    -                        _j = (_h = networking).POSTFILE;
    -                        _k = [url, formFieldsWithMimeType];
    -                        return [4 /*yield*/, file.toFile()];
    -                    case 8: return [4 /*yield*/, _j.apply(_h, _k.concat([_s.sent()]))];
    +                        _t.trys.push([7, 21, , 22]);
    +                        if (!(PubNubFile.supportsFileUri && input.uri)) return [3 /*break*/, 10];
    +                        _g = (_f = networking).POSTFILE;
    +                        _h = [url, formFieldsWithMimeType];
    +                        return [4 /*yield*/, file.toFileUri()];
    +                    case 8: return [4 /*yield*/, _g.apply(_f, _h.concat([_t.sent()]))];
                         case 9:
    -                        result = _s.sent();
    -                        return [3 /*break*/, 17];
    +                        result = _t.sent();
    +                        return [3 /*break*/, 20];
                         case 10:
    -                        if (!PubNubFile.supportsBuffer) return [3 /*break*/, 13];
    -                        _m = (_l = networking).POSTFILE;
    -                        _o = [url, formFieldsWithMimeType];
    -                        return [4 /*yield*/, file.toBuffer()];
    -                    case 11: return [4 /*yield*/, _m.apply(_l, _o.concat([_s.sent()]))];
    +                        if (!PubNubFile.supportsFile) return [3 /*break*/, 13];
    +                        _k = (_j = networking).POSTFILE;
    +                        _l = [url, formFieldsWithMimeType];
    +                        return [4 /*yield*/, file.toFile()];
    +                    case 11: return [4 /*yield*/, _k.apply(_j, _l.concat([_t.sent()]))];
                         case 12:
    -                        result = _s.sent();
    -                        return [3 /*break*/, 17];
    +                        result = _t.sent();
    +                        return [3 /*break*/, 20];
                         case 13:
    -                        if (!PubNubFile.supportsBlob) return [3 /*break*/, 16];
    -                        _q = (_p = networking).POSTFILE;
    -                        _r = [url, formFieldsWithMimeType];
    -                        return [4 /*yield*/, file.toBlob()];
    -                    case 14: return [4 /*yield*/, _q.apply(_p, _r.concat([_s.sent()]))];
    +                        if (!PubNubFile.supportsBuffer) return [3 /*break*/, 16];
    +                        _o = (_m = networking).POSTFILE;
    +                        _p = [url, formFieldsWithMimeType];
    +                        return [4 /*yield*/, file.toBuffer()];
    +                    case 14: return [4 /*yield*/, _o.apply(_m, _p.concat([_t.sent()]))];
                         case 15:
    -                        result = _s.sent();
    -                        return [3 /*break*/, 17];
    -                    case 16: throw new Error('Unsupported environment');
    -                    case 17: return [3 /*break*/, 19];
    +                        result = _t.sent();
    +                        return [3 /*break*/, 20];
    +                    case 16:
    +                        if (!PubNubFile.supportsBlob) return [3 /*break*/, 19];
    +                        _r = (_q = networking).POSTFILE;
    +                        _s = [url, formFieldsWithMimeType];
    +                        return [4 /*yield*/, file.toBlob()];
    +                    case 17: return [4 /*yield*/, _r.apply(_q, _s.concat([_t.sent()]))];
                         case 18:
    -                        e_1 = _s.sent();
    +                        result = _t.sent();
    +                        return [3 /*break*/, 20];
    +                    case 19: throw new Error('Unsupported environment');
    +                    case 20: return [3 /*break*/, 22];
    +                    case 21:
    +                        e_1 = _t.sent();
                             if (e_1.response && typeof e_1.response.text === 'string') {
                                 errorBody = e_1.response.text;
                                 reason = /<Message>(.*)<\/Message>/gi.exec(errorBody);
    @@ -121,17 +129,17 @@ var sendFile = function (_a) {
                             else {
                                 throw new endpoint_1.PubNubError('Upload to bucket failed.', e_1);
                             }
    -                        return [3 /*break*/, 19];
    -                    case 19:
    +                        return [3 /*break*/, 22];
    +                    case 22:
                             if (result.status !== 204) {
                                 throw new endpoint_1.PubNubError('Upload to bucket was unsuccessful', result);
                             }
                             retries = config.fileUploadPublishRetryLimit;
                             wasSuccessful = false;
                             publishResult = { timetoken: '0' };
    -                        _s.label = 20;
    -                    case 20:
    -                        _s.trys.push([20, 22, , 23]);
    +                        _t.label = 23;
    +                    case 23:
    +                        _t.trys.push([23, 25, , 26]);
                             return [4 /*yield*/, publishFile({
                                     channel: channel,
                                     message: message,
    @@ -141,19 +149,19 @@ var sendFile = function (_a) {
                                     storeInHistory: storeInHistory,
                                     ttl: ttl,
                                 })];
    -                    case 21:
    +                    case 24:
                             /* eslint-disable-next-line no-await-in-loop */
    -                        publishResult = _s.sent();
    +                        publishResult = _t.sent();
                             wasSuccessful = true;
    -                        return [3 /*break*/, 23];
    -                    case 22:
    -                        e_2 = _s.sent();
    +                        return [3 /*break*/, 26];
    +                    case 25:
    +                        e_2 = _t.sent();
                             retries -= 1;
    -                        return [3 /*break*/, 23];
    -                    case 23:
    -                        if (!wasSuccessful && retries > 0) return [3 /*break*/, 20];
    -                        _s.label = 24;
    -                    case 24:
    +                        return [3 /*break*/, 26];
    +                    case 26:
    +                        if (!wasSuccessful && retries > 0) return [3 /*break*/, 23];
    +                        _t.label = 27;
    +                    case 27:
                             if (!wasSuccessful) {
                                 throw new endpoint_1.PubNubError('Publish failed. You may want to execute that operation manually using pubnub.publishFile', {
                                     channel: channel,
    
  • lib/core/endpoints/history/get_history.js+6 3 modified
    @@ -8,13 +8,16 @@ exports.handleResponse = exports.prepareParams = exports.isAuthSupported = expor
     var operations_1 = __importDefault(require("../../constants/operations"));
     var utils_1 = __importDefault(require("../../utils"));
     function __processMessage(modules, message) {
    -    var config = modules.config, crypto = modules.crypto;
    -    if (!config.cipherKey)
    +    if (!modules.cryptoModule)
             return message;
         try {
    -        return crypto.decrypt(message);
    +        var decryptedData = modules.cryptoModule.decrypt(message);
    +        var decryptedPayload = decryptedData instanceof ArrayBuffer ? JSON.parse(new TextDecoder().decode(decryptedData)) : decryptedData;
    +        return decryptedPayload;
         }
         catch (e) {
    +        if (console && console.log)
    +            console.log('decryption error', e.message);
             return message;
         }
     }
    
  • lib/core/endpoints/publish.js+5 4 modified
    @@ -7,14 +7,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
     exports.handleResponse = exports.prepareParams = exports.postPayload = exports.isAuthSupported = exports.getRequestTimeout = exports.postURL = exports.getURL = exports.usePost = exports.validateParams = exports.getOperation = void 0;
     var operations_1 = __importDefault(require("../constants/operations"));
     var utils_1 = __importDefault(require("../utils"));
    +var base64_codec_1 = require("../components/base64_codec");
     function prepareMessagePayload(modules, messagePayload) {
    -    var crypto = modules.crypto, config = modules.config;
         var stringifiedPayload = JSON.stringify(messagePayload);
    -    if (config.cipherKey) {
    -        stringifiedPayload = crypto.encrypt(stringifiedPayload);
    +    if (modules.cryptoModule) {
    +        var encrypted = modules.cryptoModule.encrypt(stringifiedPayload);
    +        stringifiedPayload = typeof encrypted === 'string' ? encrypted : (0, base64_codec_1.encode)(encrypted);
             stringifiedPayload = JSON.stringify(stringifiedPayload);
         }
    -    return stringifiedPayload;
    +    return stringifiedPayload || '';
     }
     function getOperation() {
         return operations_1.default.PNPublishOperation;
    
  • lib/core/pubnub-common.js+38 5 modified
    @@ -60,6 +60,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
     Object.defineProperty(exports, "__esModule", { value: true });
     var config_1 = __importDefault(require("./components/config"));
     var index_1 = __importDefault(require("./components/cryptography/index"));
    +var base64_codec_1 = require("./components/base64_codec");
     var subscription_manager_1 = __importDefault(require("./components/subscription_manager"));
     var telemetry_manager_1 = __importDefault(require("./components/telemetry_manager"));
     var push_payload_1 = __importDefault(require("./components/push_payload"));
    @@ -141,6 +142,7 @@ var default_1 = /** @class */ (function () {
                 maximumSamplesCount: 60000,
             });
             this._telemetryManager = telemetryManager;
    +        var cryptoModule = this._config.cryptoModule;
             var modules = {
                 config: config,
                 networking: networking,
    @@ -149,10 +151,23 @@ var default_1 = /** @class */ (function () {
                 tokenManager: tokenManager,
                 telemetryManager: telemetryManager,
                 PubNubFile: setup.PubNubFile,
    +            cryptoModule: cryptoModule,
             };
             this.File = setup.PubNubFile;
    -        this.encryptFile = function (key, file) { return cryptography.encryptFile(key, file, _this.File); };
    -        this.decryptFile = function (key, file) { return cryptography.decryptFile(key, file, _this.File); };
    +        this.encryptFile = function (key, file) {
    +            if (arguments.length == 1 && typeof key != 'string' && modules.cryptoModule) {
    +                file = key;
    +                return modules.cryptoModule.encryptFile(file, this.File);
    +            }
    +            return cryptography.encryptFile(key, file, this.File);
    +        };
    +        this.decryptFile = function (key, file) {
    +            if (arguments.length == 1 && typeof key != 'string' && modules.cryptoModule) {
    +                file = key;
    +                return modules.cryptoModule.decryptFile(file, this.File);
    +            }
    +            return cryptography.decryptFile(key, file, this.File);
    +        };
             var timeEndpoint = endpoint_1.default.bind(this, modules, timeEndpointConfig);
             var leaveEndpoint = endpoint_1.default.bind(this, modules, presenceLeaveEndpointConfig);
             var heartbeatEndpoint = endpoint_1.default.bind(this, modules, presenceHeartbeatEndpointConfig);
    @@ -183,6 +198,7 @@ var default_1 = /** @class */ (function () {
                     config: modules.config,
                     listenerManager: listenerManager,
                     getFileUrl: function (params) { return (0, get_file_url_1.default)(modules, params); },
    +                cryptoModule: modules.cryptoModule,
                 });
                 this.subscribe = subscriptionManager_1.adaptSubscribeChange.bind(subscriptionManager_1);
                 this.unsubscribe = subscriptionManager_1.adaptUnsubscribeChange.bind(subscriptionManager_1);
    @@ -472,18 +488,35 @@ var default_1 = /** @class */ (function () {
             this.stop = this.destroy; // --------
             // --- deprecated  ------------------
             // mount crypto
    -        this.encrypt = crypto.encrypt.bind(crypto);
    -        this.decrypt = crypto.decrypt.bind(crypto);
    +        this.encrypt = function (data, key) {
    +            if (typeof key === 'undefined' && modules.cryptoModule) {
    +                var encrypted = modules.cryptoModule.encrypt(data);
    +                return typeof encrypted === 'string' ? encrypted : (0, base64_codec_1.encode)(encrypted);
    +            }
    +            else {
    +                return crypto.encrypt(data, key);
    +            }
    +        };
    +        this.decrypt = function (data, key) {
    +            if (typeof key === 'undefined' && cryptoModule) {
    +                var decrypted = modules.cryptoModule.decrypt(data);
    +                return decrypted instanceof ArrayBuffer ? (0, base64_codec_1.encode)(decrypted) : decrypted;
    +            }
    +            else {
    +                return crypto.decrypt(data, key);
    +            }
    +        };
             /* config */
             this.getAuthKey = modules.config.getAuthKey.bind(modules.config);
             this.setAuthKey = modules.config.setAuthKey.bind(modules.config);
    -        this.setCipherKey = modules.config.setCipherKey.bind(modules.config);
             this.getUUID = modules.config.getUUID.bind(modules.config);
             this.setUUID = modules.config.setUUID.bind(modules.config);
             this.getUserId = modules.config.getUserId.bind(modules.config);
             this.setUserId = modules.config.setUserId.bind(modules.config);
             this.getFilterExpression = modules.config.getFilterExpression.bind(modules.config);
             this.setFilterExpression = modules.config.setFilterExpression.bind(modules.config);
    +        // this.setCipherKey = modules.config.setCipherKey.bind(modules.config);
    +        this.setCipherKey = function (key) { return modules.config.setCipherKey(key, setup, modules); };
             this.setHeartbeatInterval = modules.config.setHeartbeatInterval.bind(modules.config);
             if (networking.hasModule('proxy')) {
                 this.setProxy = function (proxy) {
    
  • lib/core/utils.js+9 0 modified
    @@ -26,9 +26,18 @@ function createPromise() {
         });
         return { promise: promise, reject: failureResolve, fulfill: successResolve };
     }
    +function stringToArrayBuffer(str) {
    +    var buf = new ArrayBuffer(str.length * 2);
    +    var bufView = new Uint16Array(buf);
    +    for (var i = 0, strLen = str.length; i < strLen; i++) {
    +        bufView[i] = str.charCodeAt(i);
    +    }
    +    return buf;
    +}
     module.exports = {
         signPamFromParams: signPamFromParams,
         endsWith: endsWith,
         createPromise: createPromise,
         encodeString: encodeString,
    +    stringToArrayBuffer: stringToArrayBuffer,
     };
    
  • lib/crypto/modules/NodeCryptoModule/aesCbcCryptor.js+146 0 added
    @@ -0,0 +1,146 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +var stream_1 = require("stream");
    +var crypto_1 = require("crypto");
    +var AesCbcCryptor = /** @class */ (function () {
    +    function AesCbcCryptor(configuration) {
    +        this.cipherKey = configuration.cipherKey;
    +    }
    +    Object.defineProperty(AesCbcCryptor.prototype, "algo", {
    +        get: function () {
    +            return 'aes-256-cbc';
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(AesCbcCryptor.prototype, "identifier", {
    +        get: function () {
    +            return 'ACRH';
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    AesCbcCryptor.prototype.getIv = function () {
    +        return (0, crypto_1.randomBytes)(AesCbcCryptor.BLOCK_SIZE);
    +    };
    +    AesCbcCryptor.prototype.getKey = function () {
    +        var sha = (0, crypto_1.createHash)('sha256');
    +        sha.update(Buffer.from(this.cipherKey, 'utf8'));
    +        return Buffer.from(sha.digest());
    +    };
    +    AesCbcCryptor.prototype.encrypt = function (data) {
    +        var iv = this.getIv();
    +        var key = this.getKey();
    +        var plainData = typeof data === 'string' ? new TextEncoder().encode(data) : data;
    +        var bPlain = Buffer.from(plainData);
    +        if (bPlain.byteLength === 0)
    +            throw new Error('encryption error. empty content');
    +        var aes = (0, crypto_1.createCipheriv)(this.algo, key, iv);
    +        return {
    +            metadata: iv,
    +            data: Buffer.concat([aes.update(bPlain), aes.final()]),
    +        };
    +    };
    +    AesCbcCryptor.prototype.decrypt = function (encryptedData) {
    +        var data = typeof encryptedData.data === 'string' ? new TextEncoder().encode(encryptedData.data) : encryptedData.data;
    +        if (data.byteLength <= 0)
    +            throw new Error('decryption error: empty content');
    +        var aes = (0, crypto_1.createDecipheriv)(this.algo, this.getKey(), encryptedData.metadata);
    +        return Uint8Array.from(Buffer.concat([aes.update(data), aes.final()])).buffer;
    +    };
    +    AesCbcCryptor.prototype.encryptStream = function (stream) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var output, bIv, aes;
    +            return __generator(this, function (_a) {
    +                output = new stream_1.PassThrough();
    +                bIv = this.getIv();
    +                if (stream.readable === false)
    +                    throw new Error('encryption error. empty stream');
    +                aes = (0, crypto_1.createCipheriv)(this.algo, this.getKey(), bIv);
    +                stream.pipe(aes).pipe(output);
    +                return [2 /*return*/, {
    +                        stream: output,
    +                        metadata: bIv,
    +                        metadataLength: AesCbcCryptor.BLOCK_SIZE,
    +                    }];
    +            });
    +        });
    +    };
    +    AesCbcCryptor.prototype.decryptStream = function (encryptedStream) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var decryptedStream, bIv, aes, onReadable;
    +            var _this = this;
    +            return __generator(this, function (_a) {
    +                decryptedStream = new stream_1.PassThrough();
    +                bIv = Buffer.alloc(0);
    +                aes = null;
    +                onReadable = function () {
    +                    var data = encryptedStream.stream.read();
    +                    while (data !== null) {
    +                        if (data) {
    +                            var bChunk = Buffer.from(data);
    +                            var sliceLen = encryptedStream.metadataLength - bIv.byteLength;
    +                            if (bChunk.byteLength < sliceLen) {
    +                                bIv = Buffer.concat([bIv, bChunk]);
    +                            }
    +                            else {
    +                                bIv = Buffer.concat([bIv, bChunk.slice(0, sliceLen)]);
    +                                aes = (0, crypto_1.createDecipheriv)(_this.algo, _this.getKey(), bIv);
    +                                aes.pipe(decryptedStream);
    +                                aes.write(bChunk.slice(sliceLen));
    +                            }
    +                        }
    +                        data = encryptedStream.stream.read();
    +                    }
    +                };
    +                encryptedStream.stream.on('readable', onReadable);
    +                encryptedStream.stream.on('end', function () {
    +                    if (aes) {
    +                        aes.end();
    +                    }
    +                    decryptedStream.end();
    +                });
    +                return [2 /*return*/, decryptedStream];
    +            });
    +        });
    +    };
    +    AesCbcCryptor.BLOCK_SIZE = 16;
    +    return AesCbcCryptor;
    +}());
    +exports.default = AesCbcCryptor;
    
  • lib/crypto/modules/NodeCryptoModule/ICryptor.js+2 0 added
    @@ -0,0 +1,2 @@
    +"use strict";
    +Object.defineProperty(exports, "__esModule", { value: true });
    
  • lib/crypto/modules/NodeCryptoModule/ILegacyCryptor.js+2 0 added
    @@ -0,0 +1,2 @@
    +"use strict";
    +Object.defineProperty(exports, "__esModule", { value: true });
    
  • lib/crypto/modules/NodeCryptoModule/legacyCryptor.js+86 0 added
    @@ -0,0 +1,86 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +var index_1 = __importDefault(require("../../../core/components/cryptography/index"));
    +var base64_codec_1 = require("../../../core/components/base64_codec");
    +var node_1 = __importDefault(require("../node"));
    +var LegacyCryptor = /** @class */ (function () {
    +    function LegacyCryptor(config) {
    +        this.config = config;
    +        this.cryptor = new index_1.default({ config: config });
    +        this.fileCryptor = new node_1.default();
    +    }
    +    Object.defineProperty(LegacyCryptor.prototype, "identifier", {
    +        get: function () {
    +            return '';
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    LegacyCryptor.prototype.encrypt = function (data) {
    +        if (data.length === 0)
    +            throw new Error('encryption error. empty content');
    +        return {
    +            data: this.cryptor.encrypt(data),
    +            metadata: null,
    +        };
    +    };
    +    LegacyCryptor.prototype.decrypt = function (encryptedData) {
    +        var data = typeof encryptedData.data === 'string' ? encryptedData.data : (0, base64_codec_1.encode)(encryptedData.data);
    +        return this.cryptor.decrypt(data);
    +    };
    +    LegacyCryptor.prototype.encryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            return __generator(this, function (_a) {
    +                return [2 /*return*/, this.fileCryptor.encryptFile(this.config.cipherKey, file, File)];
    +            });
    +        });
    +    };
    +    LegacyCryptor.prototype.decryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            return __generator(this, function (_a) {
    +                return [2 /*return*/, this.fileCryptor.decryptFile(this.config.cipherKey, file, File)];
    +            });
    +        });
    +    };
    +    return LegacyCryptor;
    +}());
    +exports.default = LegacyCryptor;
    
  • lib/crypto/modules/NodeCryptoModule/nodeCryptoModule.js+446 0 added
    @@ -0,0 +1,446 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +var __read = (this && this.__read) || function (o, n) {
    +    var m = typeof Symbol === "function" && o[Symbol.iterator];
    +    if (!m) return o;
    +    var i = m.call(o), r, ar = [], e;
    +    try {
    +        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    +    }
    +    catch (error) { e = { error: error }; }
    +    finally {
    +        try {
    +            if (r && !r.done && (m = i["return"])) m.call(i);
    +        }
    +        finally { if (e) throw e.error; }
    +    }
    +    return ar;
    +};
    +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    +    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
    +        if (ar || !(i in from)) {
    +            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
    +            ar[i] = from[i];
    +        }
    +    }
    +    return to.concat(ar || Array.prototype.slice.call(from));
    +};
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +exports.CryptoModule = exports.AesCbcCryptor = exports.LegacyCryptor = void 0;
    +var stream_1 = require("stream");
    +var base64_codec_1 = require("../../../core/components/base64_codec");
    +var legacyCryptor_1 = __importDefault(require("./legacyCryptor"));
    +exports.LegacyCryptor = legacyCryptor_1.default;
    +var aesCbcCryptor_1 = __importDefault(require("./aesCbcCryptor"));
    +exports.AesCbcCryptor = aesCbcCryptor_1.default;
    +var CryptoModule = /** @class */ (function () {
    +    function CryptoModule(cryptoModuleConfiguration) {
    +        var _a;
    +        this.defaultCryptor = cryptoModuleConfiguration.default;
    +        this.cryptors = (_a = cryptoModuleConfiguration.cryptors) !== null && _a !== void 0 ? _a : [];
    +    }
    +    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +    // @ts-ignore: type detection issue with old Config type assignment
    +    CryptoModule.legacyCryptoModule = function (config) {
    +        var _a;
    +        return new this({
    +            default: new legacyCryptor_1.default({
    +                cipherKey: config.cipherKey,
    +                useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +            }),
    +            cryptors: [new aesCbcCryptor_1.default({ cipherKey: config.cipherKey })],
    +        });
    +    };
    +    CryptoModule.aesCbcCryptoModule = function (config) {
    +        var _a;
    +        return new this({
    +            default: new aesCbcCryptor_1.default({ cipherKey: config.cipherKey }),
    +            cryptors: [
    +                new legacyCryptor_1.default({
    +                    cipherKey: config.cipherKey,
    +                    useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +                }),
    +            ],
    +        });
    +    };
    +    CryptoModule.withDefaultCryptor = function (defaultCryptor) {
    +        return new this({ default: defaultCryptor });
    +    };
    +    CryptoModule.prototype.getAllCryptors = function () {
    +        return __spreadArray([this.defaultCryptor], __read(this.cryptors), false);
    +    };
    +    CryptoModule.prototype.getLegacyCryptor = function () {
    +        return this.getAllCryptors().find(function (c) { return c.identifier === ''; });
    +    };
    +    CryptoModule.prototype.encrypt = function (data) {
    +        var encrypted = this.defaultCryptor.encrypt(data);
    +        if (!encrypted.metadata)
    +            return encrypted.data;
    +        var header = CryptorHeader.from(this.defaultCryptor.identifier, encrypted.metadata);
    +        var headerData = new Uint8Array(header.length);
    +        var pos = 0;
    +        headerData.set(header.data, pos);
    +        pos = header.length - encrypted.metadata.length;
    +        headerData.set(encrypted.metadata, pos);
    +        return Buffer.concat([headerData, Buffer.from(encrypted.data)]);
    +    };
    +    CryptoModule.prototype.decrypt = function (data) {
    +        var encryptedData = Buffer.from(typeof data === 'string' ? (0, base64_codec_1.decode)(data) : data);
    +        var header = CryptorHeader.tryParse(encryptedData);
    +        var cryptor = this.getCryptor(header);
    +        var metadata = header.length > 0
    +            ? encryptedData.slice(header.length - header.metadataLength, header.length)
    +            : null;
    +        if (encryptedData.slice(header.length).byteLength <= 0)
    +            throw new Error('decryption error. empty content');
    +        return cryptor.decrypt({
    +            data: encryptedData.slice(header.length),
    +            metadata: metadata,
    +        });
    +    };
    +    CryptoModule.prototype.encryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var encryptedStream, header, payload, pos, output;
    +            return __generator(this, function (_a) {
    +                switch (_a.label) {
    +                    case 0:
    +                        /**
    +                         * Files handled differently in case of Legacy cryptor.
    +                         * (as long as we support legacy need to check on intsance type)
    +                         */
    +                        if (this.defaultCryptor.identifier === CryptorHeader.LEGACY_IDENTIFIER)
    +                            return [2 /*return*/, this.defaultCryptor.encryptFile(file, File)];
    +                        if (file.data instanceof Buffer) {
    +                            return [2 /*return*/, File.create({
    +                                    name: file.name,
    +                                    mimeType: 'application/octet-stream',
    +                                    data: Buffer.from(this.encrypt(file.data)),
    +                                })];
    +                        }
    +                        if (!(file.data instanceof stream_1.Readable)) return [3 /*break*/, 2];
    +                        if (file.contentLength === 0)
    +                            throw new Error('encryption error. empty content');
    +                        return [4 /*yield*/, this.defaultCryptor.encryptStream(file.data)];
    +                    case 1:
    +                        encryptedStream = _a.sent();
    +                        header = CryptorHeader.from(this.defaultCryptor.identifier, encryptedStream.metadata);
    +                        payload = new Uint8Array(header.length);
    +                        pos = 0;
    +                        payload.set(header.data, pos);
    +                        pos += header.length;
    +                        if (encryptedStream.metadata) {
    +                            pos -= encryptedStream.metadata.length;
    +                            payload.set(encryptedStream.metadata, pos);
    +                        }
    +                        output = new stream_1.PassThrough();
    +                        output.write(payload);
    +                        encryptedStream.stream.pipe(output);
    +                        return [2 /*return*/, File.create({
    +                                name: file.name,
    +                                mimeType: 'application/octet-stream',
    +                                stream: output,
    +                            })];
    +                    case 2: return [2 /*return*/];
    +                }
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.decryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var header, cryptor, stream_2;
    +            var _this = this;
    +            return __generator(this, function (_a) {
    +                if ((file === null || file === void 0 ? void 0 : file.data) instanceof Buffer) {
    +                    header = CryptorHeader.tryParse(file.data);
    +                    cryptor = this.getCryptor(header);
    +                    /**
    +                     * If It's legacyone then redirect it.
    +                     * (as long as we support legacy need to check on instance type)
    +                     */
    +                    if ((cryptor === null || cryptor === void 0 ? void 0 : cryptor.identifier) === CryptoModule.LEGACY_IDENTIFIER)
    +                        return [2 /*return*/, cryptor.decryptFile(file, File)];
    +                    return [2 /*return*/, File.create({
    +                            name: file.name,
    +                            data: Buffer.from(this.decrypt(file === null || file === void 0 ? void 0 : file.data)),
    +                        })];
    +                }
    +                if (file.data instanceof stream_1.Readable) {
    +                    stream_2 = file.data;
    +                    return [2 /*return*/, new Promise(function (resolve) {
    +                            stream_2.on('readable', function () { return resolve(_this.onStreamReadable(stream_2, file, File)); });
    +                        })];
    +                }
    +                return [2 /*return*/];
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.onStreamReadable = function (stream, file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var magicBytes, versionByte, identifier, cryptor, headerSize, _a, _b;
    +            var _c;
    +            return __generator(this, function (_d) {
    +                switch (_d.label) {
    +                    case 0:
    +                        stream.removeAllListeners('readable');
    +                        magicBytes = stream.read(4);
    +                        if (!CryptorHeader.isSentinel(magicBytes)) {
    +                            if (magicBytes === null)
    +                                throw new Error('decryption error. empty content');
    +                            stream.unshift(magicBytes);
    +                            return [2 /*return*/, this.decryptLegacyFileStream(stream, file, File)];
    +                        }
    +                        versionByte = stream.read(1);
    +                        CryptorHeader.validateVersion(versionByte[0]);
    +                        identifier = stream.read(4);
    +                        cryptor = this.getCryptorFromId(CryptorHeader.tryGetIdentifier(identifier));
    +                        headerSize = CryptorHeader.tryGetMetadataSizeFromStream(stream);
    +                        if (file.contentLength <= CryptorHeader.MIN_HEADER_LEGTH + headerSize)
    +                            throw new Error('decryption error. empty content');
    +                        _b = (_a = File).create;
    +                        _c = {
    +                            name: file.name,
    +                            mimeType: 'application/octet-stream'
    +                        };
    +                        return [4 /*yield*/, cryptor.decryptStream({ stream: stream, metadataLength: headerSize })];
    +                    case 1: return [2 /*return*/, _b.apply(_a, [(_c.stream = _d.sent(),
    +                                _c)])];
    +                }
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.decryptLegacyFileStream = function (stream, file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var cryptor;
    +            return __generator(this, function (_a) {
    +                if (file.contentLength <= 16)
    +                    throw new Error('decryption error: empty content');
    +                cryptor = this.getLegacyCryptor();
    +                if (cryptor) {
    +                    return [2 /*return*/, cryptor.decryptFile(File.create({
    +                            name: file.name,
    +                            stream: stream,
    +                        }), File)];
    +                }
    +                else {
    +                    throw new Error('unknown cryptor error');
    +                }
    +                return [2 /*return*/];
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.getCryptor = function (header) {
    +        if (header === '') {
    +            var cryptor = this.getAllCryptors().find(function (c) { return c.identifier === ''; });
    +            if (cryptor)
    +                return cryptor;
    +            throw new Error('unknown cryptor error');
    +        }
    +        else if (header instanceof CryptorHeaderV1) {
    +            return this.getCryptorFromId(header.identifier);
    +        }
    +    };
    +    CryptoModule.prototype.getCryptorFromId = function (id) {
    +        var cryptor = this.getAllCryptors().find(function (c) { return id === c.identifier; });
    +        if (cryptor) {
    +            return cryptor;
    +        }
    +        throw new Error('unknown cryptor error');
    +    };
    +    CryptoModule.LEGACY_IDENTIFIER = '';
    +    return CryptoModule;
    +}());
    +exports.CryptoModule = CryptoModule;
    +// CryptorHeader Utility
    +var CryptorHeader = /** @class */ (function () {
    +    function CryptorHeader() {
    +    }
    +    CryptorHeader.from = function (id, metadata) {
    +        if (id === CryptorHeader.LEGACY_IDENTIFIER)
    +            return;
    +        return new CryptorHeaderV1(id, metadata.length);
    +    };
    +    CryptorHeader.isSentinel = function (bytes) {
    +        if (bytes && bytes.byteLength >= 4) {
    +            if (bytes.toString('utf8') == CryptorHeader.SENTINEL)
    +                return true;
    +        }
    +    };
    +    CryptorHeader.validateVersion = function (data) {
    +        if (data && data > CryptorHeader.MAX_VERSION)
    +            throw new Error('decryption error. invalid header version');
    +        return data;
    +    };
    +    CryptorHeader.tryGetIdentifier = function (data) {
    +        if (data.byteLength < 4) {
    +            throw new Error('unknown cryptor error. decryption failed');
    +        }
    +        else {
    +            return data.toString('utf8');
    +        }
    +    };
    +    CryptorHeader.tryGetMetadataSizeFromStream = function (stream) {
    +        var sizeBuf = stream.read(1);
    +        if (sizeBuf && sizeBuf[0] < 255) {
    +            return sizeBuf[0];
    +        }
    +        if (sizeBuf[0] === 255) {
    +            var nextBuf = stream.read(2);
    +            if (nextBuf.length >= 2) {
    +                return new Uint16Array([nextBuf[0], nextBuf[1]]).reduce(function (acc, val) { return (acc << 8) + val; }, 0);
    +            }
    +        }
    +        throw new Error('decryption error. Invalid metadata size');
    +    };
    +    CryptorHeader.tryParse = function (encryptedData) {
    +        var sentinel = '';
    +        var version = null;
    +        if (encryptedData.length >= 4) {
    +            sentinel = encryptedData.slice(0, 4);
    +            if (sentinel.toString('utf8') !== CryptorHeader.SENTINEL)
    +                return '';
    +        }
    +        if (encryptedData.length >= 5) {
    +            version = encryptedData[4];
    +        }
    +        else {
    +            throw new Error('decryption error. invalid header version');
    +        }
    +        if (version > CryptorHeader.MAX_VERSION)
    +            throw new Error('unknown cryptor error');
    +        var identifier;
    +        var pos = 5 + CryptorHeader.IDENTIFIER_LENGTH;
    +        if (encryptedData.length >= pos) {
    +            identifier = encryptedData.slice(5, pos);
    +        }
    +        else {
    +            throw new Error('decryption error. invalid crypto identifier');
    +        }
    +        var metadataLength = null;
    +        if (encryptedData.length >= pos + 1) {
    +            metadataLength = encryptedData[pos];
    +        }
    +        else {
    +            throw new Error('decryption error. invalid metadata length');
    +        }
    +        pos += 1;
    +        if (metadataLength === 255 && encryptedData.length >= pos + 2) {
    +            metadataLength = new Uint16Array(encryptedData.slice(pos, pos + 2)).reduce(function (acc, val) { return (acc << 8) + val; }, 0);
    +            pos += 2;
    +        }
    +        return new CryptorHeaderV1(identifier.toString('utf8'), metadataLength);
    +    };
    +    CryptorHeader.SENTINEL = 'PNED';
    +    CryptorHeader.LEGACY_IDENTIFIER = '';
    +    CryptorHeader.IDENTIFIER_LENGTH = 4;
    +    CryptorHeader.VERSION = 1;
    +    CryptorHeader.MAX_VERSION = 1;
    +    CryptorHeader.MIN_HEADER_LEGTH = 10;
    +    return CryptorHeader;
    +}());
    +// v1 CryptorHeader
    +var CryptorHeaderV1 = /** @class */ (function () {
    +    function CryptorHeaderV1(id, metadataLength) {
    +        this._identifier = id;
    +        this._metadataLength = metadataLength;
    +    }
    +    Object.defineProperty(CryptorHeaderV1.prototype, "identifier", {
    +        get: function () {
    +            return this._identifier;
    +        },
    +        set: function (value) {
    +            this._identifier = value;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "metadataLength", {
    +        get: function () {
    +            return this._metadataLength;
    +        },
    +        set: function (value) {
    +            this._metadataLength = value;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "version", {
    +        get: function () {
    +            return CryptorHeader.VERSION;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "length", {
    +        get: function () {
    +            return (CryptorHeader.SENTINEL.length +
    +                1 +
    +                CryptorHeader.IDENTIFIER_LENGTH +
    +                (this.metadataLength < 255 ? 1 : 3) +
    +                this.metadataLength);
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "data", {
    +        get: function () {
    +            var pos = 0;
    +            var header = new Uint8Array(this.length);
    +            header.set(Buffer.from(CryptorHeader.SENTINEL));
    +            pos += CryptorHeader.SENTINEL.length;
    +            header[pos] = this.version;
    +            pos++;
    +            if (this.identifier)
    +                header.set(Buffer.from(this.identifier), pos);
    +            pos += CryptorHeader.IDENTIFIER_LENGTH;
    +            var metadataLength = this.metadataLength;
    +            if (metadataLength < 255) {
    +                header[pos] = metadataLength;
    +            }
    +            else {
    +                header.set([255, metadataLength >> 8, metadataLength & 0xff], pos);
    +            }
    +            return header;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    return CryptorHeaderV1;
    +}());
    
  • lib/crypto/modules/NodeCryptoModule/NodeCryptoModule.js+446 0 added
    @@ -0,0 +1,446 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +var __read = (this && this.__read) || function (o, n) {
    +    var m = typeof Symbol === "function" && o[Symbol.iterator];
    +    if (!m) return o;
    +    var i = m.call(o), r, ar = [], e;
    +    try {
    +        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    +    }
    +    catch (error) { e = { error: error }; }
    +    finally {
    +        try {
    +            if (r && !r.done && (m = i["return"])) m.call(i);
    +        }
    +        finally { if (e) throw e.error; }
    +    }
    +    return ar;
    +};
    +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    +    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
    +        if (ar || !(i in from)) {
    +            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
    +            ar[i] = from[i];
    +        }
    +    }
    +    return to.concat(ar || Array.prototype.slice.call(from));
    +};
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +exports.CryptoModule = exports.AesCbcCryptor = exports.LegacyCryptor = void 0;
    +var stream_1 = require("stream");
    +var base64_codec_1 = require("../../../core/components/base64_codec");
    +var legacyCryptor_1 = __importDefault(require("./legacyCryptor"));
    +exports.LegacyCryptor = legacyCryptor_1.default;
    +var aesCbcCryptor_1 = __importDefault(require("./aesCbcCryptor"));
    +exports.AesCbcCryptor = aesCbcCryptor_1.default;
    +var CryptoModule = /** @class */ (function () {
    +    function CryptoModule(cryptoModuleConfiguration) {
    +        var _a;
    +        this.defaultCryptor = cryptoModuleConfiguration.default;
    +        this.cryptors = (_a = cryptoModuleConfiguration.cryptors) !== null && _a !== void 0 ? _a : [];
    +    }
    +    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +    // @ts-ignore: type detection issue with old Config type assignment
    +    CryptoModule.legacyCryptoModule = function (config) {
    +        var _a;
    +        return new this({
    +            default: new legacyCryptor_1.default({
    +                cipherKey: config.cipherKey,
    +                useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +            }),
    +            cryptors: [new aesCbcCryptor_1.default({ cipherKey: config.cipherKey })],
    +        });
    +    };
    +    CryptoModule.aesCbcCryptoModule = function (config) {
    +        var _a;
    +        return new this({
    +            default: new aesCbcCryptor_1.default({ cipherKey: config.cipherKey }),
    +            cryptors: [
    +                new legacyCryptor_1.default({
    +                    cipherKey: config.cipherKey,
    +                    useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +                }),
    +            ],
    +        });
    +    };
    +    CryptoModule.withDefaultCryptor = function (defaultCryptor) {
    +        return new this({ default: defaultCryptor });
    +    };
    +    CryptoModule.prototype.getAllCryptors = function () {
    +        return __spreadArray([this.defaultCryptor], __read(this.cryptors), false);
    +    };
    +    CryptoModule.prototype.getLegacyCryptor = function () {
    +        return this.getAllCryptors().find(function (c) { return c.identifier === ''; });
    +    };
    +    CryptoModule.prototype.encrypt = function (data) {
    +        var encrypted = this.defaultCryptor.encrypt(data);
    +        if (!encrypted.metadata)
    +            return encrypted.data;
    +        var header = CryptorHeader.from(this.defaultCryptor.identifier, encrypted.metadata);
    +        var headerData = new Uint8Array(header.length);
    +        var pos = 0;
    +        headerData.set(header.data, pos);
    +        pos = header.length - encrypted.metadata.length;
    +        headerData.set(encrypted.metadata, pos);
    +        return Buffer.concat([headerData, Buffer.from(encrypted.data)]);
    +    };
    +    CryptoModule.prototype.decrypt = function (data) {
    +        var encryptedData = Buffer.from(typeof data === 'string' ? (0, base64_codec_1.decode)(data) : data);
    +        var header = CryptorHeader.tryParse(encryptedData);
    +        var cryptor = this.getCryptor(header);
    +        var metadata = header.length > 0
    +            ? encryptedData.slice(header.length - header.metadataLength, header.length)
    +            : null;
    +        if (encryptedData.slice(header.length).byteLength <= 0)
    +            throw new Error('decryption error. empty content');
    +        return cryptor.decrypt({
    +            data: encryptedData.slice(header.length),
    +            metadata: metadata,
    +        });
    +    };
    +    CryptoModule.prototype.encryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var encryptedStream, header, payload, pos, output;
    +            return __generator(this, function (_a) {
    +                switch (_a.label) {
    +                    case 0:
    +                        /**
    +                         * Files handled differently in case of Legacy cryptor.
    +                         * (as long as we support legacy need to check on intsance type)
    +                         */
    +                        if (this.defaultCryptor.identifier === CryptorHeader.LEGACY_IDENTIFIER)
    +                            return [2 /*return*/, this.defaultCryptor.encryptFile(file, File)];
    +                        if (file.data instanceof Buffer) {
    +                            return [2 /*return*/, File.create({
    +                                    name: file.name,
    +                                    mimeType: 'application/octet-stream',
    +                                    data: Buffer.from(this.encrypt(file.data)),
    +                                })];
    +                        }
    +                        if (!(file.data instanceof stream_1.Readable)) return [3 /*break*/, 2];
    +                        if (file.contentLength === 0)
    +                            throw new Error('encryption error. empty content');
    +                        return [4 /*yield*/, this.defaultCryptor.encryptStream(file.data)];
    +                    case 1:
    +                        encryptedStream = _a.sent();
    +                        header = CryptorHeader.from(this.defaultCryptor.identifier, encryptedStream.metadata);
    +                        payload = new Uint8Array(header.length);
    +                        pos = 0;
    +                        payload.set(header.data, pos);
    +                        pos += header.length;
    +                        if (encryptedStream.metadata) {
    +                            pos -= encryptedStream.metadata.length;
    +                            payload.set(encryptedStream.metadata, pos);
    +                        }
    +                        output = new stream_1.PassThrough();
    +                        output.write(payload);
    +                        encryptedStream.stream.pipe(output);
    +                        return [2 /*return*/, File.create({
    +                                name: file.name,
    +                                mimeType: 'application/octet-stream',
    +                                stream: output,
    +                            })];
    +                    case 2: return [2 /*return*/];
    +                }
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.decryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var header, cryptor, stream_2;
    +            var _this = this;
    +            return __generator(this, function (_a) {
    +                if ((file === null || file === void 0 ? void 0 : file.data) instanceof Buffer) {
    +                    header = CryptorHeader.tryParse(file.data);
    +                    cryptor = this.getCryptor(header);
    +                    /**
    +                     * If It's legacyone then redirect it.
    +                     * (as long as we support legacy need to check on instance type)
    +                     */
    +                    if ((cryptor === null || cryptor === void 0 ? void 0 : cryptor.identifier) === CryptoModule.LEGACY_IDENTIFIER)
    +                        return [2 /*return*/, cryptor.decryptFile(file, File)];
    +                    return [2 /*return*/, File.create({
    +                            name: file.name,
    +                            data: Buffer.from(this.decrypt(file === null || file === void 0 ? void 0 : file.data)),
    +                        })];
    +                }
    +                if (file.data instanceof stream_1.Readable) {
    +                    stream_2 = file.data;
    +                    return [2 /*return*/, new Promise(function (resolve) {
    +                            stream_2.on('readable', function () { return resolve(_this.onStreamReadable(stream_2, file, File)); });
    +                        })];
    +                }
    +                return [2 /*return*/];
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.onStreamReadable = function (stream, file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var magicBytes, versionByte, identifier, cryptor, headerSize, _a, _b;
    +            var _c;
    +            return __generator(this, function (_d) {
    +                switch (_d.label) {
    +                    case 0:
    +                        stream.removeAllListeners('readable');
    +                        magicBytes = stream.read(4);
    +                        if (!CryptorHeader.isSentinel(magicBytes)) {
    +                            if (magicBytes === null)
    +                                throw new Error('decryption error. empty content');
    +                            stream.unshift(magicBytes);
    +                            return [2 /*return*/, this.decryptLegacyFileStream(stream, file, File)];
    +                        }
    +                        versionByte = stream.read(1);
    +                        CryptorHeader.validateVersion(versionByte[0]);
    +                        identifier = stream.read(4);
    +                        cryptor = this.getCryptorFromId(CryptorHeader.tryGetIdentifier(identifier));
    +                        headerSize = CryptorHeader.tryGetMetadataSizeFromStream(stream);
    +                        if (file.contentLength <= CryptorHeader.MIN_HEADER_LEGTH + headerSize)
    +                            throw new Error('decryption error. empty content');
    +                        _b = (_a = File).create;
    +                        _c = {
    +                            name: file.name,
    +                            mimeType: 'application/octet-stream'
    +                        };
    +                        return [4 /*yield*/, cryptor.decryptStream({ stream: stream, metadataLength: headerSize })];
    +                    case 1: return [2 /*return*/, _b.apply(_a, [(_c.stream = _d.sent(),
    +                                _c)])];
    +                }
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.decryptLegacyFileStream = function (stream, file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var cryptor;
    +            return __generator(this, function (_a) {
    +                if (file.contentLength <= 16)
    +                    throw new Error('decryption error: empty content');
    +                cryptor = this.getLegacyCryptor();
    +                if (cryptor) {
    +                    return [2 /*return*/, cryptor.decryptFile(File.create({
    +                            name: file.name,
    +                            stream: stream,
    +                        }), File)];
    +                }
    +                else {
    +                    throw new Error('unknown cryptor error');
    +                }
    +                return [2 /*return*/];
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.getCryptor = function (header) {
    +        if (header === '') {
    +            var cryptor = this.getAllCryptors().find(function (c) { return c.identifier === ''; });
    +            if (cryptor)
    +                return cryptor;
    +            throw new Error('unknown cryptor error');
    +        }
    +        else if (header instanceof CryptorHeaderV1) {
    +            return this.getCryptorFromId(header.identifier);
    +        }
    +    };
    +    CryptoModule.prototype.getCryptorFromId = function (id) {
    +        var cryptor = this.getAllCryptors().find(function (c) { return id === c.identifier; });
    +        if (cryptor) {
    +            return cryptor;
    +        }
    +        throw new Error('unknown cryptor error');
    +    };
    +    CryptoModule.LEGACY_IDENTIFIER = '';
    +    return CryptoModule;
    +}());
    +exports.CryptoModule = CryptoModule;
    +// CryptorHeader Utility
    +var CryptorHeader = /** @class */ (function () {
    +    function CryptorHeader() {
    +    }
    +    CryptorHeader.from = function (id, metadata) {
    +        if (id === CryptorHeader.LEGACY_IDENTIFIER)
    +            return;
    +        return new CryptorHeaderV1(id, metadata.length);
    +    };
    +    CryptorHeader.isSentinel = function (bytes) {
    +        if (bytes && bytes.byteLength >= 4) {
    +            if (bytes.toString('utf8') == CryptorHeader.SENTINEL)
    +                return true;
    +        }
    +    };
    +    CryptorHeader.validateVersion = function (data) {
    +        if (data && data > CryptorHeader.MAX_VERSION)
    +            throw new Error('decryption error. invalid header version');
    +        return data;
    +    };
    +    CryptorHeader.tryGetIdentifier = function (data) {
    +        if (data.byteLength < 4) {
    +            throw new Error('unknown cryptor error. decryption failed');
    +        }
    +        else {
    +            return data.toString('utf8');
    +        }
    +    };
    +    CryptorHeader.tryGetMetadataSizeFromStream = function (stream) {
    +        var sizeBuf = stream.read(1);
    +        if (sizeBuf && sizeBuf[0] < 255) {
    +            return sizeBuf[0];
    +        }
    +        if (sizeBuf[0] === 255) {
    +            var nextBuf = stream.read(2);
    +            if (nextBuf.length >= 2) {
    +                return new Uint16Array([nextBuf[0], nextBuf[1]]).reduce(function (acc, val) { return (acc << 8) + val; }, 0);
    +            }
    +        }
    +        throw new Error('decryption error. Invalid metadata size');
    +    };
    +    CryptorHeader.tryParse = function (encryptedData) {
    +        var sentinel = '';
    +        var version = null;
    +        if (encryptedData.length >= 4) {
    +            sentinel = encryptedData.slice(0, 4);
    +            if (sentinel.toString('utf8') !== CryptorHeader.SENTINEL)
    +                return '';
    +        }
    +        if (encryptedData.length >= 5) {
    +            version = encryptedData[4];
    +        }
    +        else {
    +            throw new Error('decryption error. invalid header version');
    +        }
    +        if (version > CryptorHeader.MAX_VERSION)
    +            throw new Error('unknown cryptor error');
    +        var identifier;
    +        var pos = 5 + CryptorHeader.IDENTIFIER_LENGTH;
    +        if (encryptedData.length >= pos) {
    +            identifier = encryptedData.slice(5, pos);
    +        }
    +        else {
    +            throw new Error('decryption error. invalid crypto identifier');
    +        }
    +        var metadataLength = null;
    +        if (encryptedData.length >= pos + 1) {
    +            metadataLength = encryptedData[pos];
    +        }
    +        else {
    +            throw new Error('decryption error. invalid metadata length');
    +        }
    +        pos += 1;
    +        if (metadataLength === 255 && encryptedData.length >= pos + 2) {
    +            metadataLength = new Uint16Array(encryptedData.slice(pos, pos + 2)).reduce(function (acc, val) { return (acc << 8) + val; }, 0);
    +            pos += 2;
    +        }
    +        return new CryptorHeaderV1(identifier.toString('utf8'), metadataLength);
    +    };
    +    CryptorHeader.SENTINEL = 'PNED';
    +    CryptorHeader.LEGACY_IDENTIFIER = '';
    +    CryptorHeader.IDENTIFIER_LENGTH = 4;
    +    CryptorHeader.VERSION = 1;
    +    CryptorHeader.MAX_VERSION = 1;
    +    CryptorHeader.MIN_HEADER_LEGTH = 10;
    +    return CryptorHeader;
    +}());
    +// v1 CryptorHeader
    +var CryptorHeaderV1 = /** @class */ (function () {
    +    function CryptorHeaderV1(id, metadataLength) {
    +        this._identifier = id;
    +        this._metadataLength = metadataLength;
    +    }
    +    Object.defineProperty(CryptorHeaderV1.prototype, "identifier", {
    +        get: function () {
    +            return this._identifier;
    +        },
    +        set: function (value) {
    +            this._identifier = value;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "metadataLength", {
    +        get: function () {
    +            return this._metadataLength;
    +        },
    +        set: function (value) {
    +            this._metadataLength = value;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "version", {
    +        get: function () {
    +            return CryptorHeader.VERSION;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "length", {
    +        get: function () {
    +            return (CryptorHeader.SENTINEL.length +
    +                1 +
    +                CryptorHeader.IDENTIFIER_LENGTH +
    +                (this.metadataLength < 255 ? 1 : 3) +
    +                this.metadataLength);
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "data", {
    +        get: function () {
    +            var pos = 0;
    +            var header = new Uint8Array(this.length);
    +            header.set(Buffer.from(CryptorHeader.SENTINEL));
    +            pos += CryptorHeader.SENTINEL.length;
    +            header[pos] = this.version;
    +            pos++;
    +            if (this.identifier)
    +                header.set(Buffer.from(this.identifier), pos);
    +            pos += CryptorHeader.IDENTIFIER_LENGTH;
    +            var metadataLength = this.metadataLength;
    +            if (metadataLength < 255) {
    +                header[pos] = metadataLength;
    +            }
    +            else {
    +                header.set([255, metadataLength >> 8, metadataLength & 0xff], pos);
    +            }
    +            return header;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    return CryptorHeaderV1;
    +}());
    
  • lib/crypto/modules/node.js+26 6 modified
    @@ -94,6 +94,8 @@ var NodeCryptography = /** @class */ (function () {
                         case 0:
                             bKey = this.getKey(key);
                             if (!(file.data instanceof Buffer)) return [3 /*break*/, 2];
    +                        if (file.data.byteLength <= 0)
    +                            throw new Error('encryption error. empty content');
                             _b = (_a = File).create;
                             _e = {
                                 name: file.name,
    @@ -104,6 +106,8 @@ var NodeCryptography = /** @class */ (function () {
                                     _e)])];
                         case 2:
                             if (!(file.data instanceof stream_1.Readable)) return [3 /*break*/, 4];
    +                        if (file.contentLength === 0)
    +                            throw new Error('encryption error. empty content');
                             _d = (_c = File).create;
                             _f = {
                                 name: file.name,
    @@ -176,16 +180,32 @@ var NodeCryptography = /** @class */ (function () {
         NodeCryptography.prototype.decryptBuffer = function (key, ciphertext) {
             var bIv = ciphertext.slice(0, NodeCryptography.IV_LENGTH);
             var bCiphertext = ciphertext.slice(NodeCryptography.IV_LENGTH);
    +        if (bCiphertext.byteLength <= 0)
    +            throw new Error('decryption error: empty content');
             var aes = (0, crypto_1.createDecipheriv)(this.algo, key, bIv);
             return Buffer.concat([aes.update(bCiphertext), aes.final()]);
         };
         NodeCryptography.prototype.encryptStream = function (key, stream) {
    -        var output = new stream_1.PassThrough();
    -        var bIv = this.getIv();
    -        var aes = (0, crypto_1.createCipheriv)(this.algo, key, bIv);
    -        output.write(bIv);
    -        stream.pipe(aes).pipe(output);
    -        return output;
    +        return __awaiter(this, void 0, void 0, function () {
    +            var bIv, aes, inited;
    +            return __generator(this, function (_a) {
    +                bIv = this.getIv();
    +                aes = (0, crypto_1.createCipheriv)('aes-256-cbc', key, bIv).setAutoPadding(true);
    +                inited = false;
    +                return [2 /*return*/, stream.pipe(aes).pipe(new stream_1.Transform({
    +                        transform: function (chunk, _, cb) {
    +                            if (!inited) {
    +                                inited = true;
    +                                this.push(Buffer.concat([bIv, chunk]));
    +                            }
    +                            else {
    +                                this.push(chunk);
    +                            }
    +                            cb();
    +                        },
    +                    }))];
    +            });
    +        });
         };
         NodeCryptography.prototype.decryptStream = function (key, stream) {
             var _this = this;
    
  • lib/crypto/modules/WebCryptoModule/aesCbcCryptor.js+148 0 added
    @@ -0,0 +1,148 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +var hmac_sha256_1 = __importDefault(require("../../../core/components/cryptography/hmac-sha256"));
    +var base64_codec_1 = require("../../../core/components/base64_codec");
    +var AesCbcCryptor = /** @class */ (function () {
    +    function AesCbcCryptor(configuration) {
    +        this.cipherKey = configuration.cipherKey;
    +        this.CryptoJS = hmac_sha256_1.default;
    +        this.encryptedKey = this.CryptoJS.SHA256(this.cipherKey);
    +    }
    +    Object.defineProperty(AesCbcCryptor.prototype, "algo", {
    +        get: function () {
    +            return 'AES-CBC';
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(AesCbcCryptor.prototype, "identifier", {
    +        get: function () {
    +            return 'ACRH';
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    AesCbcCryptor.prototype.getIv = function () {
    +        return crypto.getRandomValues(new Uint8Array(AesCbcCryptor.BLOCK_SIZE));
    +    };
    +    AesCbcCryptor.prototype.getKey = function () {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var bKey, abHash;
    +            return __generator(this, function (_a) {
    +                switch (_a.label) {
    +                    case 0:
    +                        bKey = AesCbcCryptor.encoder.encode(this.cipherKey);
    +                        return [4 /*yield*/, crypto.subtle.digest('SHA-256', bKey.buffer)];
    +                    case 1:
    +                        abHash = _a.sent();
    +                        return [2 /*return*/, crypto.subtle.importKey('raw', abHash, this.algo, true, ['encrypt', 'decrypt'])];
    +                }
    +            });
    +        });
    +    };
    +    AesCbcCryptor.prototype.encrypt = function (data) {
    +        var stringData = typeof data === 'string' ? data : AesCbcCryptor.decoder.decode(data);
    +        if (stringData.length === 0)
    +            throw new Error('encryption error. empty content');
    +        var abIv = this.getIv();
    +        return {
    +            metadata: abIv,
    +            data: (0, base64_codec_1.decode)(this.CryptoJS.AES.encrypt(data, this.encryptedKey, {
    +                iv: this.bufferToWordArray(abIv),
    +                mode: this.CryptoJS.mode.CBC,
    +            }).ciphertext.toString(this.CryptoJS.enc.Base64)),
    +        };
    +    };
    +    AesCbcCryptor.prototype.decrypt = function (encryptedData) {
    +        var iv = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.metadata));
    +        var data = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.data));
    +        return AesCbcCryptor.encoder.encode(this.CryptoJS.AES.decrypt({ ciphertext: data }, this.encryptedKey, {
    +            iv: iv,
    +            mode: this.CryptoJS.mode.CBC,
    +        }).toString(this.CryptoJS.enc.Utf8)).buffer;
    +    };
    +    AesCbcCryptor.prototype.encryptFileData = function (data) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var key, iv;
    +            var _a;
    +            return __generator(this, function (_b) {
    +                switch (_b.label) {
    +                    case 0: return [4 /*yield*/, this.getKey()];
    +                    case 1:
    +                        key = _b.sent();
    +                        iv = this.getIv();
    +                        _a = {};
    +                        return [4 /*yield*/, crypto.subtle.encrypt({ name: this.algo, iv: iv }, key, data)];
    +                    case 2: return [2 /*return*/, (_a.data = _b.sent(),
    +                            _a.metadata = iv,
    +                            _a)];
    +                }
    +            });
    +        });
    +    };
    +    AesCbcCryptor.prototype.decryptFileData = function (encryptedData) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var key;
    +            return __generator(this, function (_a) {
    +                switch (_a.label) {
    +                    case 0: return [4 /*yield*/, this.getKey()];
    +                    case 1:
    +                        key = _a.sent();
    +                        return [2 /*return*/, crypto.subtle.decrypt({ name: this.algo, iv: encryptedData.metadata }, key, encryptedData.data)];
    +                }
    +            });
    +        });
    +    };
    +    AesCbcCryptor.prototype.bufferToWordArray = function (b) {
    +        var wa = [];
    +        var i;
    +        for (i = 0; i < b.length; i += 1) {
    +            wa[(i / 4) | 0] |= b[i] << (24 - 8 * i);
    +        }
    +        return this.CryptoJS.lib.WordArray.create(wa, b.length);
    +    };
    +    AesCbcCryptor.BLOCK_SIZE = 16;
    +    AesCbcCryptor.encoder = new TextEncoder();
    +    AesCbcCryptor.decoder = new TextDecoder();
    +    return AesCbcCryptor;
    +}());
    +exports.default = AesCbcCryptor;
    
  • lib/crypto/modules/WebCryptoModule/ICryptor.js+2 0 added
    @@ -0,0 +1,2 @@
    +"use strict";
    +Object.defineProperty(exports, "__esModule", { value: true });
    
  • lib/crypto/modules/WebCryptoModule/ILegacyCryptor.js+2 0 added
    @@ -0,0 +1,2 @@
    +"use strict";
    +Object.defineProperty(exports, "__esModule", { value: true });
    
  • lib/crypto/modules/WebCryptoModule/legacyCryptor.js+90 0 added
    @@ -0,0 +1,90 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +var index_1 = __importDefault(require("../../../core/components/cryptography/index"));
    +var web_1 = __importDefault(require("../web"));
    +var base64_codec_1 = require("../../../core/components/base64_codec");
    +var LegacyCryptor = /** @class */ (function () {
    +    function LegacyCryptor(config) {
    +        this.config = config;
    +        this.cryptor = new index_1.default({ config: config });
    +        this.fileCryptor = new web_1.default();
    +    }
    +    Object.defineProperty(LegacyCryptor.prototype, "identifier", {
    +        get: function () {
    +            return '';
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    LegacyCryptor.prototype.encrypt = function (data) {
    +        var stringData = typeof data === 'string' ? data : new TextDecoder().decode(data);
    +        return {
    +            data: this.cryptor.encrypt(stringData),
    +            metadata: null,
    +        };
    +    };
    +    LegacyCryptor.prototype.decrypt = function (encryptedData) {
    +        var data = typeof encryptedData.data === 'string' ? encryptedData.data : (0, base64_codec_1.encode)(encryptedData.data);
    +        return this.cryptor.decrypt(data);
    +    };
    +    LegacyCryptor.prototype.encryptFile = function (file, File) {
    +        var _a;
    +        return __awaiter(this, void 0, void 0, function () {
    +            return __generator(this, function (_b) {
    +                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +                //@ts-ignore: can not detect cipherKey from old Config
    +                return [2 /*return*/, this.fileCryptor.encryptFile((_a = this.config) === null || _a === void 0 ? void 0 : _a.cipherKey, file, File)];
    +            });
    +        });
    +    };
    +    LegacyCryptor.prototype.decryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            return __generator(this, function (_a) {
    +                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +                //@ts-ignore: can not detect cipherKey from old Config
    +                return [2 /*return*/, this.fileCryptor.decryptFile(this.config.cipherKey, file, File)];
    +            });
    +        });
    +    };
    +    return LegacyCryptor;
    +}());
    +exports.default = LegacyCryptor;
    
  • lib/crypto/modules/WebCryptoModule/webCryptoModule.js+358 0 added
    @@ -0,0 +1,358 @@
    +"use strict";
    +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    +    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    +    return new (P || (P = Promise))(function (resolve, reject) {
    +        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
    +        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
    +        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
    +        step((generator = generator.apply(thisArg, _arguments || [])).next());
    +    });
    +};
    +var __generator = (this && this.__generator) || function (thisArg, body) {
    +    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    +    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    +    function verb(n) { return function (v) { return step([n, v]); }; }
    +    function step(op) {
    +        if (f) throw new TypeError("Generator is already executing.");
    +        while (_) try {
    +            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
    +            if (y = 0, t) op = [op[0] & 2, t.value];
    +            switch (op[0]) {
    +                case 0: case 1: t = op; break;
    +                case 4: _.label++; return { value: op[1], done: false };
    +                case 5: _.label++; y = op[1]; op = [0]; continue;
    +                case 7: op = _.ops.pop(); _.trys.pop(); continue;
    +                default:
    +                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
    +                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
    +                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
    +                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
    +                    if (t[2]) _.ops.pop();
    +                    _.trys.pop(); continue;
    +            }
    +            op = body.call(thisArg, _);
    +        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
    +        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    +    }
    +};
    +var __read = (this && this.__read) || function (o, n) {
    +    var m = typeof Symbol === "function" && o[Symbol.iterator];
    +    if (!m) return o;
    +    var i = m.call(o), r, ar = [], e;
    +    try {
    +        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    +    }
    +    catch (error) { e = { error: error }; }
    +    finally {
    +        try {
    +            if (r && !r.done && (m = i["return"])) m.call(i);
    +        }
    +        finally { if (e) throw e.error; }
    +    }
    +    return ar;
    +};
    +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    +    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
    +        if (ar || !(i in from)) {
    +            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
    +            ar[i] = from[i];
    +        }
    +    }
    +    return to.concat(ar || Array.prototype.slice.call(from));
    +};
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
    +Object.defineProperty(exports, "__esModule", { value: true });
    +exports.CryptoModule = exports.AesCbcCryptor = exports.LegacyCryptor = void 0;
    +var legacyCryptor_1 = __importDefault(require("./legacyCryptor"));
    +exports.LegacyCryptor = legacyCryptor_1.default;
    +var aesCbcCryptor_1 = __importDefault(require("./aesCbcCryptor"));
    +exports.AesCbcCryptor = aesCbcCryptor_1.default;
    +var base64_codec_1 = require("../../../core/components/base64_codec");
    +var CryptoModule = /** @class */ (function () {
    +    function CryptoModule(cryptoModuleConfiguration) {
    +        var _a;
    +        this.defaultCryptor = cryptoModuleConfiguration.default;
    +        this.cryptors = (_a = cryptoModuleConfiguration.cryptors) !== null && _a !== void 0 ? _a : [];
    +    }
    +    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +    //@ts-ignore: type detection issue with old Config type assignment
    +    CryptoModule.legacyCryptoModule = function (config) {
    +        var _a;
    +        return new this({
    +            default: new legacyCryptor_1.default({
    +                cipherKey: config.cipherKey,
    +                useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +            }),
    +            cryptors: [new aesCbcCryptor_1.default({ cipherKey: config.cipherKey })],
    +        });
    +    };
    +    CryptoModule.aesCbcCryptoModule = function (config) {
    +        var _a;
    +        return new this({
    +            default: new aesCbcCryptor_1.default({ cipherKey: config.cipherKey }),
    +            cryptors: [
    +                new legacyCryptor_1.default({
    +                    cipherKey: config.cipherKey,
    +                    useRandomIVs: (_a = config.useRandomIVs) !== null && _a !== void 0 ? _a : true,
    +                }),
    +            ],
    +        });
    +    };
    +    CryptoModule.withDefaultCryptor = function (defaultCryptor) {
    +        return new this({ default: defaultCryptor });
    +    };
    +    CryptoModule.prototype.getAllCryptors = function () {
    +        return __spreadArray([this.defaultCryptor], __read(this.cryptors), false);
    +    };
    +    CryptoModule.prototype.encrypt = function (data) {
    +        var encrypted = this.defaultCryptor.encrypt(data);
    +        if (!encrypted.metadata)
    +            return encrypted.data;
    +        var headerData = this.getHeaderData(encrypted);
    +        return this.concatArrayBuffer(headerData, encrypted.data);
    +    };
    +    CryptoModule.prototype.decrypt = function (data) {
    +        var encryptedData = typeof data === 'string' ? (0, base64_codec_1.decode)(data) : data;
    +        var header = CryptorHeader.tryParse(encryptedData);
    +        var cryptor = this.getCryptor(header);
    +        var metadata = header.length > 0
    +            ? encryptedData.slice(header.length - header.metadataLength, header.length)
    +            : null;
    +        if (encryptedData.slice(header.length).byteLength <= 0)
    +            throw new Error('decryption error. empty content');
    +        return cryptor.decrypt({
    +            data: encryptedData.slice(header.length),
    +            metadata: metadata,
    +        });
    +    };
    +    CryptoModule.prototype.encryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var fileData, encrypted;
    +            return __generator(this, function (_a) {
    +                switch (_a.label) {
    +                    case 0:
    +                        if (this.defaultCryptor.identifier === CryptorHeader.LEGACY_IDENTIFIER)
    +                            return [2 /*return*/, this.defaultCryptor.encryptFile(file, File)];
    +                        fileData = this.getFileData(file.data);
    +                        return [4 /*yield*/, this.defaultCryptor.encryptFileData(fileData)];
    +                    case 1:
    +                        encrypted = _a.sent();
    +                        return [2 /*return*/, File.create({
    +                                name: file.name,
    +                                mimeType: 'application/octet-stream',
    +                                data: this.concatArrayBuffer(this.getHeaderData(encrypted), encrypted.data),
    +                            })];
    +                }
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.decryptFile = function (file, File) {
    +        return __awaiter(this, void 0, void 0, function () {
    +            var data, header, cryptor, fileData, metadata, _a, _b;
    +            var _c;
    +            return __generator(this, function (_d) {
    +                switch (_d.label) {
    +                    case 0: return [4 /*yield*/, file.data.arrayBuffer()];
    +                    case 1:
    +                        data = _d.sent();
    +                        header = CryptorHeader.tryParse(data);
    +                        cryptor = this.getCryptor(header);
    +                        if ((cryptor === null || cryptor === void 0 ? void 0 : cryptor.identifier) === CryptoModule.LEGACY_IDENTIFIER) {
    +                            return [2 /*return*/, cryptor.decryptFile(file, File)];
    +                        }
    +                        fileData = this.getFileData(data);
    +                        metadata = fileData.slice(header.length - header.metadataLength, header.length);
    +                        _b = (_a = File).create;
    +                        _c = {
    +                            name: file.name
    +                        };
    +                        return [4 /*yield*/, this.defaultCryptor.decryptFileData({
    +                                data: data.slice(header.length),
    +                                metadata: metadata,
    +                            })];
    +                    case 2: return [2 /*return*/, _b.apply(_a, [(_c.data = _d.sent(),
    +                                _c)])];
    +                }
    +            });
    +        });
    +    };
    +    CryptoModule.prototype.getCryptor = function (header) {
    +        if (header === '') {
    +            var cryptor = this.getAllCryptors().find(function (c) { return c.identifier === ''; });
    +            if (cryptor)
    +                return cryptor;
    +            throw new Error('unknown cryptor error');
    +        }
    +        else if (header instanceof CryptorHeaderV1) {
    +            return this.getCryptorFromId(header.identifier);
    +        }
    +    };
    +    CryptoModule.prototype.getCryptorFromId = function (id) {
    +        var cryptor = this.getAllCryptors().find(function (c) { return id === c.identifier; });
    +        if (cryptor) {
    +            return cryptor;
    +        }
    +        throw Error('unknown cryptor error');
    +    };
    +    CryptoModule.prototype.concatArrayBuffer = function (ab1, ab2) {
    +        var tmp = new Uint8Array(ab1.byteLength + ab2.byteLength);
    +        tmp.set(new Uint8Array(ab1), 0);
    +        tmp.set(new Uint8Array(ab2), ab1.byteLength);
    +        return tmp.buffer;
    +    };
    +    CryptoModule.prototype.getHeaderData = function (encrypted) {
    +        if (!encrypted.metadata)
    +            return;
    +        var header = CryptorHeader.from(this.defaultCryptor.identifier, encrypted.metadata);
    +        var headerData = new Uint8Array(header.length);
    +        var pos = 0;
    +        headerData.set(header.data, pos);
    +        pos += header.length - encrypted.metadata.byteLength;
    +        headerData.set(new Uint8Array(encrypted.metadata), pos);
    +        return headerData.buffer;
    +    };
    +    CryptoModule.prototype.getFileData = function (input) {
    +        if (input instanceof ArrayBuffer) {
    +            return input;
    +        }
    +        if (typeof input === 'string') {
    +            return CryptoModule.encoder.encode(input);
    +        }
    +        throw new Error('Cannot decrypt/encrypt file. In browsers file decryption supports only string or ArrayBuffer');
    +    };
    +    CryptoModule.LEGACY_IDENTIFIER = '';
    +    CryptoModule.encoder = new TextEncoder();
    +    CryptoModule.decoder = new TextDecoder();
    +    return CryptoModule;
    +}());
    +exports.CryptoModule = CryptoModule;
    +// CryptorHeader Utility
    +var CryptorHeader = /** @class */ (function () {
    +    function CryptorHeader() {
    +    }
    +    CryptorHeader.from = function (id, metadata) {
    +        if (id === CryptorHeader.LEGACY_IDENTIFIER)
    +            return;
    +        return new CryptorHeaderV1(id, metadata.byteLength);
    +    };
    +    CryptorHeader.tryParse = function (data) {
    +        var encryptedData = new Uint8Array(data);
    +        var sentinel = '';
    +        var version = null;
    +        if (encryptedData.byteLength >= 4) {
    +            sentinel = encryptedData.slice(0, 4);
    +            if (this.decoder.decode(sentinel) !== CryptorHeader.SENTINEL)
    +                return '';
    +        }
    +        if (encryptedData.byteLength >= 5) {
    +            version = encryptedData[4];
    +        }
    +        else {
    +            throw new Error('decryption error. invalid header version');
    +        }
    +        if (version > CryptorHeader.MAX_VERSION)
    +            throw new Error('unknown cryptor error');
    +        var identifier = '';
    +        var pos = 5 + CryptorHeader.IDENTIFIER_LENGTH;
    +        if (encryptedData.byteLength >= pos) {
    +            identifier = encryptedData.slice(5, pos);
    +        }
    +        else {
    +            throw new Error('decryption error. invalid crypto identifier');
    +        }
    +        var metadataLength = null;
    +        if (encryptedData.byteLength >= pos + 1) {
    +            metadataLength = encryptedData[pos];
    +        }
    +        else {
    +            throw new Error('decryption error. invalid metadata length');
    +        }
    +        pos += 1;
    +        if (metadataLength === 255 && encryptedData.byteLength >= pos + 2) {
    +            metadataLength = new Uint16Array(encryptedData.slice(pos, pos + 2)).reduce(function (acc, val) { return (acc << 8) + val; }, 0);
    +            pos += 2;
    +        }
    +        return new CryptorHeaderV1(this.decoder.decode(identifier), metadataLength);
    +    };
    +    CryptorHeader.SENTINEL = 'PNED';
    +    CryptorHeader.LEGACY_IDENTIFIER = '';
    +    CryptorHeader.IDENTIFIER_LENGTH = 4;
    +    CryptorHeader.VERSION = 1;
    +    CryptorHeader.MAX_VERSION = 1;
    +    CryptorHeader.decoder = new TextDecoder();
    +    return CryptorHeader;
    +}());
    +// v1 CryptorHeader
    +var CryptorHeaderV1 = /** @class */ (function () {
    +    function CryptorHeaderV1(id, metadataLength) {
    +        this._identifier = id;
    +        this._metadataLength = metadataLength;
    +    }
    +    Object.defineProperty(CryptorHeaderV1.prototype, "identifier", {
    +        get: function () {
    +            return this._identifier;
    +        },
    +        set: function (value) {
    +            this._identifier = value;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "metadataLength", {
    +        get: function () {
    +            return this._metadataLength;
    +        },
    +        set: function (value) {
    +            this._metadataLength = value;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "version", {
    +        get: function () {
    +            return CryptorHeader.VERSION;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "length", {
    +        get: function () {
    +            return (CryptorHeader.SENTINEL.length +
    +                1 +
    +                CryptorHeader.IDENTIFIER_LENGTH +
    +                (this.metadataLength < 255 ? 1 : 3) +
    +                this.metadataLength);
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    Object.defineProperty(CryptorHeaderV1.prototype, "data", {
    +        get: function () {
    +            var pos = 0;
    +            var header = new Uint8Array(this.length);
    +            var encoder = new TextEncoder();
    +            header.set(encoder.encode(CryptorHeader.SENTINEL));
    +            pos += CryptorHeader.SENTINEL.length;
    +            header[pos] = this.version;
    +            pos++;
    +            if (this.identifier)
    +                header.set(encoder.encode(this.identifier), pos);
    +            pos += CryptorHeader.IDENTIFIER_LENGTH;
    +            var metadataLength = this.metadataLength;
    +            if (metadataLength < 255) {
    +                header[pos] = metadataLength;
    +            }
    +            else {
    +                header.set([255, metadataLength >> 8, metadataLength & 0xff], pos);
    +            }
    +            return header;
    +        },
    +        enumerable: false,
    +        configurable: true
    +    });
    +    CryptorHeaderV1.IDENTIFIER_LENGTH = 4;
    +    CryptorHeaderV1.SENTINEL = 'PNED';
    +    return CryptorHeaderV1;
    +}());
    
  • lib/crypto/modules/web.js+30 16 modified
    @@ -96,10 +96,13 @@ var WebCryptography = /** @class */ (function () {
                 var bKey, abPlaindata, abCipherdata;
                 return __generator(this, function (_a) {
                     switch (_a.label) {
    -                    case 0: return [4 /*yield*/, this.getKey(key)];
    +                    case 0:
    +                        if (file.data.byteLength <= 0)
    +                            throw new Error('encryption error. empty content');
    +                        return [4 /*yield*/, this.getKey(key)];
                         case 1:
                             bKey = _a.sent();
    -                        return [4 /*yield*/, file.toArrayBuffer()];
    +                        return [4 /*yield*/, file.data.arrayBuffer()];
                         case 2:
                             abPlaindata = _a.sent();
                             return [4 /*yield*/, this.encryptArrayBuffer(bKey, abPlaindata)];
    @@ -122,7 +125,7 @@ var WebCryptography = /** @class */ (function () {
                         case 0: return [4 /*yield*/, this.getKey(key)];
                         case 1:
                             bKey = _a.sent();
    -                        return [4 /*yield*/, file.toArrayBuffer()];
    +                        return [4 /*yield*/, file.data.arrayBuffer()];
                         case 2:
                             abCipherdata = _a.sent();
                             return [4 /*yield*/, this.decryptArrayBuffer(bKey, abCipherdata)];
    @@ -138,15 +141,16 @@ var WebCryptography = /** @class */ (function () {
         };
         WebCryptography.prototype.getKey = function (key) {
             return __awaiter(this, void 0, void 0, function () {
    -            var bKey, abHash, abKey;
    +            var digest, hashHex, abKey;
                 return __generator(this, function (_a) {
                     switch (_a.label) {
    -                    case 0:
    -                        bKey = Buffer.from(key);
    -                        return [4 /*yield*/, crypto.subtle.digest('SHA-256', bKey.buffer)];
    +                    case 0: return [4 /*yield*/, crypto.subtle.digest('SHA-256', WebCryptography.encoder.encode(key))];
                         case 1:
    -                        abHash = _a.sent();
    -                        abKey = Buffer.from(Buffer.from(abHash).toString('hex').slice(0, 32), 'utf8').buffer;
    +                        digest = _a.sent();
    +                        hashHex = Array.from(new Uint8Array(digest))
    +                            .map(function (b) { return b.toString(16).padStart(2, '0'); })
    +                            .join('');
    +                        abKey = WebCryptography.encoder.encode(hashHex.slice(0, 32)).buffer;
                             return [2 /*return*/, crypto.subtle.importKey('raw', abKey, 'AES-CBC', true, ['encrypt', 'decrypt'])];
                     }
                 });
    @@ -169,10 +173,18 @@ var WebCryptography = /** @class */ (function () {
         };
         WebCryptography.prototype.decryptArrayBuffer = function (key, ciphertext) {
             return __awaiter(this, void 0, void 0, function () {
    -            var abIv;
    +            var abIv, data;
                 return __generator(this, function (_a) {
    -                abIv = ciphertext.slice(0, 16);
    -                return [2 /*return*/, crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, ciphertext.slice(16))];
    +                switch (_a.label) {
    +                    case 0:
    +                        abIv = ciphertext.slice(0, 16);
    +                        if (ciphertext.slice(WebCryptography.IV_LENGTH).byteLength <= 0)
    +                            throw new Error('decryption error: empty content');
    +                        return [4 /*yield*/, crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, ciphertext.slice(WebCryptography.IV_LENGTH))];
    +                    case 1:
    +                        data = _a.sent();
    +                        return [2 /*return*/, data];
    +                }
                 });
             });
         };
    @@ -183,12 +195,12 @@ var WebCryptography = /** @class */ (function () {
                     switch (_a.label) {
                         case 0:
                             abIv = crypto.getRandomValues(new Uint8Array(16));
    -                        abPlaintext = Buffer.from(plaintext).buffer;
    +                        abPlaintext = WebCryptography.encoder.encode(plaintext).buffer;
                             return [4 /*yield*/, crypto.subtle.encrypt({ name: 'AES-CBC', iv: abIv }, key, abPlaintext)];
                         case 1:
                             abPayload = _a.sent();
                             ciphertext = concatArrayBuffer(abIv.buffer, abPayload);
    -                        return [2 /*return*/, Buffer.from(ciphertext).toString('utf8')];
    +                        return [2 /*return*/, WebCryptography.decoder.decode(ciphertext)];
                     }
                 });
             });
    @@ -199,18 +211,20 @@ var WebCryptography = /** @class */ (function () {
                 return __generator(this, function (_a) {
                     switch (_a.label) {
                         case 0:
    -                        abCiphertext = Buffer.from(ciphertext);
    +                        abCiphertext = WebCryptography.encoder.encode(ciphertext).buffer;
                             abIv = abCiphertext.slice(0, 16);
                             abPayload = abCiphertext.slice(16);
                             return [4 /*yield*/, crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, abPayload)];
                         case 1:
                             abPlaintext = _a.sent();
    -                        return [2 /*return*/, Buffer.from(abPlaintext).toString('utf8')];
    +                        return [2 /*return*/, WebCryptography.decoder.decode(abPlaintext)];
                     }
                 });
             });
         };
         WebCryptography.IV_LENGTH = 16;
    +    WebCryptography.encoder = new TextEncoder();
    +    WebCryptography.decoder = new TextDecoder();
         return WebCryptography;
     }());
     exports.default = WebCryptography;
    
  • lib/file/modules/node.js+6 3 modified
    @@ -1,5 +1,4 @@
     "use strict";
    -/**       */
     var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
         function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
         return new (P || (P = Promise))(function (resolve, reject) {
    @@ -36,19 +35,23 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
             if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
         }
     };
    +var __importDefault = (this && this.__importDefault) || function (mod) {
    +    return (mod && mod.__esModule) ? mod : { "default": mod };
    +};
     var _a;
     Object.defineProperty(exports, "__esModule", { value: true });
     var stream_1 = require("stream");
    -var fs_1 = require("fs");
    +var fs_1 = __importDefault(require("fs"));
     var path_1 = require("path");
     var PubNubFile = (_a = /** @class */ (function () {
             function PubNubFile(_a) {
                 var stream = _a.stream, data = _a.data, encoding = _a.encoding, name = _a.name, mimeType = _a.mimeType;
                 if (stream instanceof stream_1.Readable) {
                     this.data = stream;
    -                if (stream instanceof fs_1.ReadStream) {
    +                if (stream instanceof fs_1.default.ReadStream) {
                         // $FlowFixMe: incomplete flow node definitions
                         this.name = (0, path_1.basename)(stream.path);
    +                    this.contentLength = fs_1.default.statSync(stream.path).size;
                     }
                 }
                 else if (data instanceof Buffer) {
    
  • lib/node/index.js+38 25 modified
    @@ -17,6 +17,7 @@ var __extends = (this && this.__extends) || (function () {
     var __importDefault = (this && this.__importDefault) || function (mod) {
         return (mod && mod.__esModule) ? mod : { "default": mod };
     };
    +var _a;
     var cbor_sync_1 = __importDefault(require("cbor-sync"));
     var pubnub_common_1 = __importDefault(require("../core/pubnub-common"));
     var networking_1 = __importDefault(require("../networking"));
    @@ -26,29 +27,41 @@ var web_node_1 = require("../networking/modules/web-node");
     var node_1 = require("../networking/modules/node");
     var node_2 = __importDefault(require("../crypto/modules/node"));
     var node_3 = __importDefault(require("../file/modules/node"));
    -module.exports = /** @class */ (function (_super) {
    -    __extends(class_1, _super);
    -    function class_1(setup) {
    -        var _this = this;
    -        setup.cbor = new common_1.default(function (buffer) { return cbor_sync_1.default.decode(Buffer.from(buffer)); }, base64_codec_1.decode);
    -        setup.networking = new networking_1.default({
    -            keepAlive: node_1.keepAlive,
    -            del: web_node_1.del,
    -            get: web_node_1.get,
    -            post: web_node_1.post,
    -            patch: web_node_1.patch,
    -            proxy: node_1.proxy,
    -            getfile: web_node_1.getfile,
    -            postfile: web_node_1.postfile,
    -        });
    -        setup.sdkFamily = 'Nodejs';
    -        setup.PubNubFile = node_3.default;
    -        setup.cryptography = new node_2.default();
    -        if (!('ssl' in setup)) {
    -            setup.ssl = true;
    +var nodeCryptoModule_1 = require("../crypto/modules/NodeCryptoModule/nodeCryptoModule");
    +module.exports = (_a = /** @class */ (function (_super) {
    +        __extends(class_1, _super);
    +        function class_1(setup) {
    +            var _this = this;
    +            setup.cbor = new common_1.default(function (buffer) { return cbor_sync_1.default.decode(Buffer.from(buffer)); }, base64_codec_1.decode);
    +            setup.networking = new networking_1.default({
    +                keepAlive: node_1.keepAlive,
    +                del: web_node_1.del,
    +                get: web_node_1.get,
    +                post: web_node_1.post,
    +                patch: web_node_1.patch,
    +                proxy: node_1.proxy,
    +                getfile: web_node_1.getfile,
    +                postfile: web_node_1.postfile,
    +            });
    +            setup.sdkFamily = 'Nodejs';
    +            setup.PubNubFile = node_3.default;
    +            setup.cryptography = new node_2.default();
    +            setup.initCryptoModule = function (cryptoConfiguration) {
    +                return new nodeCryptoModule_1.CryptoModule({
    +                    default: new nodeCryptoModule_1.LegacyCryptor({
    +                        cipherKey: cryptoConfiguration.cipherKey,
    +                        useRandomIVs: cryptoConfiguration.useRandomIVs,
    +                    }),
    +                    cryptors: [new nodeCryptoModule_1.AesCbcCryptor({ cipherKey: cryptoConfiguration.cipherKey })],
    +                });
    +            };
    +            if (!('ssl' in setup)) {
    +                setup.ssl = true;
    +            }
    +            _this = _super.call(this, setup) || this;
    +            return _this;
             }
    -        _this = _super.call(this, setup) || this;
    -        return _this;
    -    }
    -    return class_1;
    -}(pubnub_common_1.default));
    +        return class_1;
    +    }(pubnub_common_1.default)),
    +    _a.CryptoModule = nodeCryptoModule_1.CryptoModule,
    +    _a);
    
  • lib/web/index.js+11 0 modified
    @@ -29,6 +29,7 @@ var common_1 = __importDefault(require("../cbor/common"));
     var web_node_1 = require("../networking/modules/web-node");
     var web_1 = __importDefault(require("../crypto/modules/web"));
     var web_2 = __importDefault(require("../file/modules/web"));
    +var webCryptoModule_1 = require("../crypto/modules/WebCryptoModule/webCryptoModule");
     function sendBeacon(url) {
         if (navigator && navigator.sendBeacon) {
             navigator.sendBeacon(url);
    @@ -56,6 +57,15 @@ var default_1 = /** @class */ (function (_super) {
             setup.cbor = new common_1.default(function (arrayBuffer) { return (0, stringify_buffer_keys_1.stringifyBufferKeys)(cbor_js_1.default.decode(arrayBuffer)); }, base64_codec_1.decode);
             setup.PubNubFile = web_2.default;
             setup.cryptography = new web_1.default();
    +        setup.initCryptoModule = function (cryptoConfiguration) {
    +            return new webCryptoModule_1.CryptoModule({
    +                default: new webCryptoModule_1.LegacyCryptor({
    +                    cipherKey: cryptoConfiguration.cipherKey,
    +                    useRandomIVs: cryptoConfiguration.useRandomIVs,
    +                }),
    +                cryptors: [new webCryptoModule_1.AesCbcCryptor({ cipherKey: cryptoConfiguration.cipherKey })],
    +            });
    +        };
             _this = _super.call(this, setup) || this;
             if (listenToBrowserNetworkEvents) {
                 // mount network events.
    @@ -68,6 +78,7 @@ var default_1 = /** @class */ (function (_super) {
             }
             return _this;
         }
    +    default_1.CryptoModule = webCryptoModule_1.CryptoModule;
         return default_1;
     }(pubnub_common_1.default));
     exports.default = default_1;
    
  • LICENSE+25 23 modified
    @@ -1,27 +1,29 @@
    -PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks
    -Copyright (c) 2013 PubNub Inc.
    -http://www.pubnub.com/
    -http://www.pubnub.com/terms
    +PubNub Software Development Kit License Agreement
    +Copyright © 2023 PubNub Inc. All rights reserved.
     
    -Permission is hereby granted, free of charge, to any person obtaining a copy
    -of this software and associated documentation files (the "Software"), to deal
    -in the Software without restriction, including without limitation the rights
    -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    -copies of the Software, and to permit persons to whom the Software is
    -furnished to do so, subject to the following conditions:
    +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 permission notice shall be included in
    -all copies or substantial portions of the Software.
    +The above copyright notice and this license shall be included
    +in or with all copies or substantial portions of the software.
     
    -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 THE
    -AUTHORS OR COPYRIGHT HOLDERS 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.
    +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.
     
    -PubNub Real-time Cloud-Hosted Push API and Push Notification Client Frameworks
    -Copyright (c) 2013 PubNub Inc.
    -http://www.pubnub.com/
    -http://www.pubnub.com/terms
    +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
    
  • package.json+2 2 modified
    @@ -1,6 +1,6 @@
     {
       "name": "pubnub",
    -  "version": "7.3.3",
    +  "version": "7.4.0",
       "author": "PubNub <support@pubnub.com>",
       "description": "Publish & Subscribe Real-time Messaging with PubNub",
       "scripts": {
    @@ -102,4 +102,4 @@
       "engine": {
         "node": ">=0.8"
       }
    -}
    +}
    \ No newline at end of file
    
  • .pubnub.yml+10 3 modified
    @@ -1,5 +1,12 @@
     ---
     changelog:
    +  - date: 2023-10-16
    +    version: v7.4.0
    +    changes:
    +      - type: feature
    +        text: "Add crypto module that allows configure SDK to encrypt and decrypt messages."
    +      - type: bug
    +        text: "Improved security of crypto implementation by adding enhanced AES-CBC cryptor."
       - date: 2023-09-11
         version: v7.3.3
         changes:
    @@ -895,7 +902,7 @@ supported-platforms:
           - 'Ubuntu 14.04 and up'
           - 'Windows 7 and up'
         version: 'Pubnub Javascript for Node'
    -version: '7.3.3'
    +version: '7.4.0'
     sdks:
       - full-name: PubNub Javascript SDK
         short-name: Javascript
    @@ -911,7 +918,7 @@ sdks:
               - distribution-type: source
                 distribution-repository: GitHub release
                 package-name: pubnub.js
    -            location: https://github.com/pubnub/javascript/archive/refs/tags/v7.3.3.zip
    +            location: https://github.com/pubnub/javascript/archive/refs/tags/v7.4.0.zip
                 requires:
                   - name: 'agentkeepalive'
                     min-version: '3.5.2'
    @@ -1582,7 +1589,7 @@ sdks:
               - distribution-type: library
                 distribution-repository: GitHub release
                 package-name: pubnub.js
    -            location: https://github.com/pubnub/javascript/releases/download/v7.3.3/pubnub.7.3.3.js
    +            location: https://github.com/pubnub/javascript/releases/download/v7.4.0/pubnub.7.4.0.js
                 requires:
                   - name: 'agentkeepalive'
                     min-version: '3.5.2'
    
  • README.md+2 2 modified
    @@ -28,8 +28,8 @@ Watch [Getting Started with PubNub JS SDK](https://app.dashcam.io/replay/64ee0d2
          npm install pubnub
          ```
        * or download one of our builds from our CDN: 
    -     * https://cdn.pubnub.com/sdk/javascript/pubnub.7.3.3.js
    -     * https://cdn.pubnub.com/sdk/javascript/pubnub.7.3.3.min.js
    +     * https://cdn.pubnub.com/sdk/javascript/pubnub.7.4.0.js
    +     * https://cdn.pubnub.com/sdk/javascript/pubnub.7.4.0.min.js
     
     2. Configure your keys:
     
    
  • src/core/components/base64_codec.ts+52 0 modified
    @@ -53,3 +53,55 @@ export function decode(paddedInput: string): ArrayBuffer {
     
       return data;
     }
    +
    +export function encode(input: ArrayBuffer): string {
    +  let base64 = '';
    +  const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    +
    +  const bytes = new Uint8Array(input);
    +  const byteLength = bytes.byteLength;
    +  const byteRemainder = byteLength % 3;
    +  const mainLength = byteLength - byteRemainder;
    +
    +  let a, b, c, d;
    +  let chunk;
    +
    +  // Main loop deals with bytes in chunks of 3
    +  for (let i = 0; i < mainLength; i = i + 3) {
    +    // Combine the three bytes into a single integer
    +    chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
    +
    +    // Use bitmasks to extract 6-bit segments from the triplet
    +    a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
    +    b = (chunk & 258048) >> 12; // 258048   = (2^6 - 1) << 12
    +    c = (chunk & 4032) >> 6; // 4032     = (2^6 - 1) << 6
    +    d = chunk & 63; // 63       = 2^6 - 1
    +
    +    // Convert the raw binary segments to the appropriate ASCII encoding
    +    base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
    +  }
    +
    +  // Deal with the remaining bytes and padding
    +  if (byteRemainder == 1) {
    +    chunk = bytes[mainLength];
    +
    +    a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
    +
    +    // Set the 4 least significant bits to zero
    +    b = (chunk & 3) << 4; // 3   = 2^2 - 1
    +
    +    base64 += encodings[a] + encodings[b] + '==';
    +  } else if (byteRemainder == 2) {
    +    chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
    +
    +    a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
    +    b = (chunk & 1008) >> 4; // 1008  = (2^6 - 1) << 4
    +
    +    // Set the 2 least significant bits to zero
    +    c = (chunk & 15) << 2; // 15    = 2^4 - 1
    +
    +    base64 += encodings[a] + encodings[b] + encodings[c] + '=';
    +  }
    +
    +  return base64;
    +}
    
  • src/core/components/config.js+15 4 modified
    @@ -129,7 +129,7 @@ export default class {
       maximumCacheSize;
     
       /*
    -    support customp encryption and decryption functions.
    +    support custom encryption and decryption functions.
       */
       customEncrypt; // function to support custome encryption of messages
     
    @@ -142,6 +142,11 @@ export default class {
       useRandomIVs;
       enableSubscribeBeta;
     
    +  /*
    +    set cryptoModule to encrypt/decrypt messages and files.
    +  */
    +  cryptoModule;
    +
       constructor({ setup }) {
         this._PNSDKSuffix = {};
     
    @@ -153,7 +158,7 @@ export default class {
         this.sdkFamily = setup.sdkFamily;
         this.partnerId = setup.partnerId;
         this.setAuthKey(setup.authKey);
    -    this.setCipherKey(setup.cipherKey);
    +    this.cryptoModule = setup.cryptoModule;
     
         this.setFilterExpression(setup.filterExpression);
     
    @@ -227,6 +232,7 @@ export default class {
     
           this.setUUID(setup.uuid);
         }
    +    this.setCipherKey(setup.cipherKey, setup);
       }
     
       // exposed setters
    @@ -239,8 +245,13 @@ export default class {
         return this;
       }
     
    -  setCipherKey(val) {
    +  setCipherKey(val, setup, modules) {
         this.cipherKey = val;
    +    if (this.cipherKey) {
    +      this.cryptoModule =
    +        setup.cryptoModule ?? setup.initCryptoModule({ cipherKey: this.cipherKey, useRandomIVs: this.useRandomIVs });
    +      if (modules) modules.cryptoModule = this.cryptoModule;
    +    }
         return this;
       }
     
    @@ -339,7 +350,7 @@ export default class {
       }
     
       getVersion() {
    -    return '7.3.3';
    +    return '7.4.0';
       }
     
       _addPnsdkSuffix(name, suffix) {
    
  • src/core/components/cryptography/index.js+0 1 modified
    @@ -26,7 +26,6 @@ export default class {
     
       constructor({ config }) {
         this._config = config;
    -
         this._iv = '0123456789012345';
     
         this._allowedKeyEncodings = ['hex', 'utf8', 'base64', 'binary'];
    
  • src/core/components/subscription_manager.js+39 6 modified
    @@ -58,6 +58,10 @@ export default class {
       _pendingChannelGroupSubscriptions;
       //
     
    +  _cryptoModule;
    +
    +  _decoder;
    +
       _dedupingManager;
     
       constructor({
    @@ -70,6 +74,7 @@ export default class {
         config,
         crypto,
         listenerManager,
    +    cryptoModule,
       }) {
         this._listenerManager = listenerManager;
         this._config = config;
    @@ -81,6 +86,7 @@ export default class {
         this._getFileUrl = getFileUrl;
     
         this._crypto = crypto;
    +    this._cryptoModule = cryptoModule;
     
         this._channels = {};
         this._presenceChannels = {};
    @@ -104,6 +110,8 @@ export default class {
     
         this._reconnectionManager = new ReconnectionManager({ timeEndpoint });
         this._dedupingManager = new DedupingManager({ config });
    +
    +    if (this._cryptoModule) this._decoder = new TextDecoder();
       }
     
       adaptStateChange(args, callback) {
    @@ -683,10 +691,19 @@ export default class {
     
             let msgPayload = message.payload;
     
    -        if (this._config.cipherKey) {
    -          const decryptedPayload = this._crypto.decrypt(message.payload);
    -
    -          if (typeof decryptedPayload === 'object' && decryptedPayload !== null) {
    +        if (this._cryptoModule) {
    +          let decryptedPayload;
    +          try {
    +            const decryptedData = this._cryptoModule.decrypt(message.payload);
    +            decryptedPayload =
    +              decryptedData instanceof ArrayBuffer ? JSON.parse(this._decoder.decode(decryptedData)) : decryptedData;
    +          } catch (e) {
    +            decryptedPayload = null;
    +            if (console && console.log) {
    +              console.log('decryption error', e.message);
    +            }
    +          }
    +          if (decryptedPayload !== null) {
                 msgPayload = decryptedPayload;
               }
             }
    @@ -727,8 +744,24 @@ export default class {
               announce.userMetadata = message.userMetadata;
             }
     
    -        if (this._config.cipherKey) {
    -          announce.message = this._crypto.decrypt(message.payload);
    +        if (this._cryptoModule) {
    +          let decryptedPayload;
    +          try {
    +            const decryptedData = this._cryptoModule.decrypt(message.payload);
    +            decryptedPayload =
    +              decryptedData instanceof ArrayBuffer ? JSON.parse(this._decoder.decode(decryptedData)) : decryptedData;
    +          } catch (e) {
    +            decryptedPayload = null;
    +            // eslint-disable-next-line
    +            if (console && console.log) {
    +              console.log('decryption error', e.message); //eslint-disable-line
    +            }
    +          }
    +          if (decryptedPayload != null) {
    +            announce.message = decryptedPayload;
    +          } else {
    +            announce.message = message.payload;
    +          }
             } else {
               announce.message = message.payload;
             }
    
  • src/core/endpoints/fetch_messages.js+6 4 modified
    @@ -11,12 +11,14 @@ import operationConstants from '../constants/operations';
     import utils from '../utils';
     
     function __processMessage(modules, message) {
    -  const { config, crypto } = modules;
    -  if (!config.cipherKey) return message;
    -
    +  if (!modules.cryptoModule) return message;
       try {
    -    return crypto.decrypt(message);
    +    const decryptedData = modules.cryptoModule.decrypt(message);
    +    const decryptedPayload =
    +      decryptedData instanceof ArrayBuffer ? JSON.parse(new TextDecoder().decode(decryptedData)) : decryptedData;
    +    return decryptedPayload;
       } catch (e) {
    +    if (console && console.log) console.log('decryption error', e.message);
         return message;
       }
     }
    
  • src/core/endpoints/file_upload/download_file.js+8 4 modified
    @@ -1,3 +1,5 @@
    +// Download_file.js
    +
     /**       */
     
     import operationConstants from '../../constants/operations';
    @@ -34,11 +36,13 @@ const endpoint = {
     
       prepareParams: () => ({}),
     
    -  handleResponse: async ({ PubNubFile, config, cryptography }, res, params) => {
    +  handleResponse: async ({ PubNubFile, config, cryptography, cryptoModule }, res, params) => {
         let { body } = res.response;
    -
    -    if (PubNubFile.supportsEncryptFile && (params.cipherKey ?? config.cipherKey)) {
    -      body = await cryptography.decrypt(params.cipherKey ?? config.cipherKey, body);
    +    if (PubNubFile.supportsEncryptFile && (params.cipherKey || cryptoModule)) {
    +      body =
    +        params.cipherKey == null
    +          ? (await cryptoModule.decryptFile(PubNubFile.create({ data: body, name: params.name }), PubNubFile)).data
    +          : await cryptography.decrypt(params.cipherKey ?? config.cipherKey, body);
         }
     
         return PubNubFile.create({
    
  • src/core/endpoints/file_upload/publish_file.js+5 6 modified
    @@ -1,17 +1,16 @@
     /**       */
     
     import operationConstants from '../../constants/operations';
    -
     import utils from '../../utils';
    +import { encode } from '../../components/base64_codec';
     
    -const preparePayload = ({ crypto, config }, payload) => {
    +const preparePayload = (modules, payload) => {
       let stringifiedPayload = JSON.stringify(payload);
    -
    -  if (config.cipherKey) {
    -    stringifiedPayload = crypto.encrypt(stringifiedPayload);
    +  if (modules.cryptoModule) {
    +    const encrypted = modules.cryptoModule.encrypt(stringifiedPayload);
    +    stringifiedPayload = typeof encrypted === 'string' ? encrypted : encode(encrypted);
         stringifiedPayload = JSON.stringify(stringifiedPayload);
       }
    -
       return stringifiedPayload || '';
     };
     
    
  • src/core/endpoints/file_upload/send_file.js+6 3 modified
    @@ -3,7 +3,7 @@ import { PubNubError, createValidationError } from '../../components/endpoint';
     const sendFile = function ({
       generateUploadUrl,
       publishFile,
    -  modules: { PubNubFile, config, cryptography, networking },
    +  modules: { PubNubFile, config, cryptography, cryptoModule, networking },
     }) {
       return async ({ channel, file: input, message, cipherKey, meta, ttl, storeInHistory }) => {
         if (!channel) {
    @@ -27,8 +27,11 @@ const sendFile = function ({
           data: { id, name },
         } = await generateUploadUrl({ channel, name: file.name });
     
    -    if (PubNubFile.supportsEncryptFile && (cipherKey ?? config.cipherKey)) {
    -      file = await cryptography.encryptFile(cipherKey ?? config.cipherKey, file, PubNubFile);
    +    if (PubNubFile.supportsEncryptFile && (cipherKey || cryptoModule)) {
    +      file =
    +        cipherKey == null
    +          ? await cryptoModule.encryptFile(file, PubNubFile)
    +          : await cryptography.encryptFile(cipherKey, file, PubNubFile);
         }
     
         let formFieldsWithMimeType = formFields;
    
  • src/core/endpoints/history/get_history.js+6 4 modified
    @@ -5,12 +5,14 @@ import operationConstants from '../../constants/operations';
     import utils from '../../utils';
     
     function __processMessage(modules, message) {
    -  const { config, crypto } = modules;
    -  if (!config.cipherKey) return message;
    -
    +  if (!modules.cryptoModule) return message;
       try {
    -    return crypto.decrypt(message);
    +    const decryptedData = modules.cryptoModule.decrypt(message);
    +    const decryptedPayload =
    +      decryptedData instanceof ArrayBuffer ? JSON.parse(new TextDecoder().decode(decryptedData)) : decryptedData;
    +    return decryptedPayload;
       } catch (e) {
    +    if (console && console.log) console.log('decryption error', e.message);
         return message;
       }
     }
    
  • src/core/endpoints/publish.js+5 5 modified
    @@ -3,17 +3,17 @@
     import { PublishResponse, PublishArguments, ModulesInject } from '../flow_interfaces';
     import operationConstants from '../constants/operations';
     import utils from '../utils';
    +import { encode } from '../components/base64_codec';
     
     function prepareMessagePayload(modules, messagePayload) {
    -  const { crypto, config } = modules;
       let stringifiedPayload = JSON.stringify(messagePayload);
     
    -  if (config.cipherKey) {
    -    stringifiedPayload = crypto.encrypt(stringifiedPayload);
    +  if (modules.cryptoModule) {
    +    const encrypted = modules.cryptoModule.encrypt(stringifiedPayload);
    +    stringifiedPayload = typeof encrypted === 'string' ? encrypted : encode(encrypted);
         stringifiedPayload = JSON.stringify(stringifiedPayload);
       }
    -
    -  return stringifiedPayload;
    +  return stringifiedPayload || '';
     }
     
     export function getOperation() {
    
  • src/core/pubnub-common.js+36 6 modified
    @@ -1,5 +1,6 @@
     import Config from './components/config';
     import Crypto from './components/cryptography/index';
    +import { encode } from './components/base64_codec';
     import SubscriptionManager from './components/subscription_manager';
     import TelemetryManager from './components/telemetry_manager';
     import NotificationsPayload from './components/push_payload';
    @@ -291,6 +292,7 @@ export default class {
         });
     
         this._telemetryManager = telemetryManager;
    +    const cryptoModule = this._config.cryptoModule;
     
         const modules = {
           config,
    @@ -300,12 +302,25 @@ export default class {
           tokenManager,
           telemetryManager,
           PubNubFile: setup.PubNubFile,
    +      cryptoModule: cryptoModule,
         };
     
         this.File = setup.PubNubFile;
     
    -    this.encryptFile = (key, file) => cryptography.encryptFile(key, file, this.File);
    -    this.decryptFile = (key, file) => cryptography.decryptFile(key, file, this.File);
    +    this.encryptFile = function (key, file) {
    +      if (arguments.length == 1 && typeof key != 'string' && modules.cryptoModule) {
    +        file = key;
    +        return modules.cryptoModule.encryptFile(file, this.File);
    +      }
    +      return cryptography.encryptFile(key, file, this.File);
    +    };
    +    this.decryptFile = function (key, file) {
    +      if (arguments.length == 1 && typeof key != 'string' && modules.cryptoModule) {
    +        file = key;
    +        return modules.cryptoModule.decryptFile(file, this.File);
    +      }
    +      return cryptography.decryptFile(key, file, this.File);
    +    };
     
         const timeEndpoint = endpointCreator.bind(this, modules, timeEndpointConfig);
         const leaveEndpoint = endpointCreator.bind(this, modules, presenceLeaveEndpointConfig);
    @@ -341,6 +356,7 @@ export default class {
             config: modules.config,
             listenerManager,
             getFileUrl: (params) => getFileUrlFunction(modules, params),
    +        cryptoModule: modules.cryptoModule,
           });
     
           this.subscribe = subscriptionManager.adaptSubscribeChange.bind(subscriptionManager);
    @@ -664,20 +680,34 @@ export default class {
         // --- deprecated  ------------------
     
         // mount crypto
    -    this.encrypt = crypto.encrypt.bind(crypto);
    -    this.decrypt = crypto.decrypt.bind(crypto);
    +    this.encrypt = function (data, key) {
    +      if (typeof key === 'undefined' && modules.cryptoModule) {
    +        const encrypted = modules.cryptoModule.encrypt(data);
    +        return typeof encrypted === 'string' ? encrypted : encode(encrypted);
    +      } else {
    +        return crypto.encrypt(data, key);
    +      }
    +    };
    +    this.decrypt = function (data, key) {
    +      if (typeof key === 'undefined' && cryptoModule) {
    +        const decrypted = modules.cryptoModule.decrypt(data);
    +        return decrypted instanceof ArrayBuffer ? encode(decrypted) : decrypted;
    +      } else {
    +        return crypto.decrypt(data, key);
    +      }
    +    };
     
         /* config */
         this.getAuthKey = modules.config.getAuthKey.bind(modules.config);
         this.setAuthKey = modules.config.setAuthKey.bind(modules.config);
    -    this.setCipherKey = modules.config.setCipherKey.bind(modules.config);
         this.getUUID = modules.config.getUUID.bind(modules.config);
         this.setUUID = modules.config.setUUID.bind(modules.config);
         this.getUserId = modules.config.getUserId.bind(modules.config);
         this.setUserId = modules.config.setUserId.bind(modules.config);
         this.getFilterExpression = modules.config.getFilterExpression.bind(modules.config);
         this.setFilterExpression = modules.config.setFilterExpression.bind(modules.config);
    -
    +    // this.setCipherKey = modules.config.setCipherKey.bind(modules.config);
    +    this.setCipherKey = (key) => modules.config.setCipherKey(key, setup, modules);
         this.setHeartbeatInterval = modules.config.setHeartbeatInterval.bind(modules.config);
     
         if (networking.hasModule('proxy')) {
    
  • src/core/utils.js+10 0 modified
    @@ -32,9 +32,19 @@ function createPromise() {
       return { promise, reject: failureResolve, fulfill: successResolve };
     }
     
    +function stringToArrayBuffer(str) {
    +  var buf = new ArrayBuffer(str.length * 2);
    +  var bufView = new Uint16Array(buf);
    +  for (var i = 0, strLen = str.length; i < strLen; i++) {
    +    bufView[i] = str.charCodeAt(i);
    +  }
    +  return buf;
    +}
    +
     module.exports = {
       signPamFromParams,
       endsWith,
       createPromise,
       encodeString,
    +  stringToArrayBuffer,
     };
    
  • src/crypto/modules/NodeCryptoModule/aesCbcCryptor.ts+97 0 added
    @@ -0,0 +1,97 @@
    +import { PassThrough } from 'stream';
    +import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';
    +import { ICryptor, EncryptedDataType, EncryptedStream } from './ICryptor';
    +
    +export default class AesCbcCryptor implements ICryptor {
    +  static BLOCK_SIZE = 16;
    +
    +  cipherKey: string;
    +  constructor(configuration: { cipherKey: string }) {
    +    this.cipherKey = configuration.cipherKey;
    +  }
    +
    +  get algo() {
    +    return 'aes-256-cbc';
    +  }
    +
    +  get identifier() {
    +    return 'ACRH';
    +  }
    +
    +  getIv() {
    +    return randomBytes(AesCbcCryptor.BLOCK_SIZE);
    +  }
    +
    +  getKey() {
    +    const sha = createHash('sha256');
    +    sha.update(Buffer.from(this.cipherKey, 'utf8'));
    +    return Buffer.from(sha.digest());
    +  }
    +
    +  encrypt(data: ArrayBuffer | string) {
    +    const iv = this.getIv();
    +    const key = this.getKey();
    +    const plainData = typeof data === 'string' ? new TextEncoder().encode(data) : data;
    +    const bPlain = Buffer.from(plainData);
    +    if (bPlain.byteLength === 0) throw new Error('encryption error. empty content');
    +    const aes = createCipheriv(this.algo, key, iv);
    +
    +    return {
    +      metadata: iv,
    +      data: Buffer.concat([aes.update(bPlain), aes.final()]),
    +    };
    +  }
    +
    +  decrypt(encryptedData: EncryptedDataType) {
    +    const data =
    +      typeof encryptedData.data === 'string' ? new TextEncoder().encode(encryptedData.data) : encryptedData.data;
    +    if (data.byteLength <= 0) throw new Error('decryption error: empty content');
    +    const aes = createDecipheriv(this.algo, this.getKey(), encryptedData.metadata);
    +    return Uint8Array.from(Buffer.concat([aes.update(data), aes.final()])).buffer;
    +  }
    +
    +  async encryptStream(stream: NodeJS.ReadableStream) {
    +    const output = new PassThrough();
    +    const bIv = this.getIv();
    +    if (stream.readable === false) throw new Error('encryption error. empty stream');
    +    const aes = createCipheriv(this.algo, this.getKey(), bIv);
    +    stream.pipe(aes).pipe(output);
    +    return {
    +      stream: output,
    +      metadata: bIv,
    +      metadataLength: AesCbcCryptor.BLOCK_SIZE,
    +    };
    +  }
    +
    +  async decryptStream(encryptedStream: EncryptedStream) {
    +    const decryptedStream = new PassThrough();
    +    let bIv = Buffer.alloc(0);
    +    let aes: any = null;
    +    const onReadable = () => {
    +      let data = encryptedStream.stream.read();
    +      while (data !== null) {
    +        if (data) {
    +          const bChunk = Buffer.from(data);
    +          const sliceLen = encryptedStream.metadataLength - bIv.byteLength;
    +          if (bChunk.byteLength < sliceLen) {
    +            bIv = Buffer.concat([bIv, bChunk]);
    +          } else {
    +            bIv = Buffer.concat([bIv, bChunk.slice(0, sliceLen)]);
    +            aes = createDecipheriv(this.algo, this.getKey(), bIv);
    +            aes.pipe(decryptedStream);
    +            aes.write(bChunk.slice(sliceLen));
    +          }
    +        }
    +        data = encryptedStream.stream.read();
    +      }
    +    };
    +    encryptedStream.stream.on('readable', onReadable);
    +    encryptedStream.stream.on('end', () => {
    +      if (aes) {
    +        aes.end();
    +      }
    +      decryptedStream.end();
    +    });
    +    return decryptedStream;
    +  }
    +}
    
  • src/crypto/modules/NodeCryptoModule/ICryptor.ts+19 0 added
    @@ -0,0 +1,19 @@
    +export type EncryptedDataType = {
    +  data: Buffer | string;
    +  metadata: Buffer | null;
    +};
    +
    +export type EncryptedStream = {
    +  stream: NodeJS.ReadableStream;
    +  metadataLength: number;
    +  metadata?: Buffer | undefined;
    +};
    +
    +export interface ICryptor {
    +  get identifier(): string;
    +  encrypt(data: BufferSource | string): EncryptedDataType;
    +  decrypt(data: EncryptedDataType): ArrayBuffer;
    +
    +  encryptStream(stream: NodeJS.ReadableStream): Promise<EncryptedStream>;
    +  decryptStream(encryptedStream: EncryptedStream): Promise<NodeJS.ReadableStream>;
    +}
    
  • src/crypto/modules/NodeCryptoModule/ILegacyCryptor.ts+26 0 added
    @@ -0,0 +1,26 @@
    +import { EncryptedDataType } from './ICryptor';
    +
    +export type PubNubFileType = {
    +  stream: NodeJS.ReadStream;
    +  data: NodeJS.ReadStream | Buffer;
    +  name: string;
    +  mimeType: string;
    +  contentLength: number;
    +
    +  create(config: any): PubNubFileType;
    +
    +  toBuffer(): Buffer;
    +  toArrayBuffer(): ArrayBuffer;
    +  toString(): string;
    +  toStream(): NodeJS.ReadStream;
    +};
    +
    +export interface ILegacyCryptor<T extends PubNubFileType> {
    +  get identifier(): string;
    +
    +  encrypt(data: string | ArrayBuffer): EncryptedDataType;
    +  decrypt(data: EncryptedDataType): BufferSource | string;
    +
    +  encryptFile(file: T, File: T): Promise<T>;
    +  decryptFile(file: T, File: T): Promise<T>;
    +}
    
  • src/crypto/modules/NodeCryptoModule/legacyCryptor.ts+41 0 added
    @@ -0,0 +1,41 @@
    +import Crypto from '../../../core/components/cryptography/index';
    +import { encode } from '../../../core/components/base64_codec';
    +import FileCryptor from '../node';
    +import { EncryptedDataType } from './ICryptor';
    +import { ILegacyCryptor, PubNubFileType } from './ILegacyCryptor';
    +
    +export default class LegacyCryptor implements ILegacyCryptor<PubNubFileType> {
    +  config;
    +
    +  cryptor;
    +  fileCryptor;
    +
    +  constructor(config: any) {
    +    this.config = config;
    +    this.cryptor = new Crypto({ config });
    +    this.fileCryptor = new FileCryptor();
    +  }
    +  get identifier() {
    +    return '';
    +  }
    +  encrypt(data: string) {
    +    if (data.length === 0) throw new Error('encryption error. empty content');
    +    return {
    +      data: this.cryptor.encrypt(data),
    +      metadata: null,
    +    };
    +  }
    +
    +  decrypt(encryptedData: EncryptedDataType) {
    +    const data = typeof encryptedData.data === 'string' ? encryptedData.data : encode(encryptedData.data);
    +    return this.cryptor.decrypt(data);
    +  }
    +
    +  async encryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    return this.fileCryptor.encryptFile(this.config.cipherKey, file, File);
    +  }
    +
    +  async decryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    return this.fileCryptor.decryptFile(this.config.cipherKey, file, File);
    +  }
    +}
    
  • src/crypto/modules/NodeCryptoModule/nodeCryptoModule.ts+350 0 added
    @@ -0,0 +1,350 @@
    +import { Readable, PassThrough } from 'stream';
    +import { decode } from '../../../core/components/base64_codec';
    +import LegacyCryptor from './legacyCryptor';
    +import AesCbcCryptor from './aesCbcCryptor';
    +import { ICryptor } from './ICryptor';
    +import { ILegacyCryptor, PubNubFileType } from './ILegacyCryptor';
    +
    +export { LegacyCryptor, AesCbcCryptor };
    +
    +type CryptorType = ICryptor | ILegacyCryptor<PubNubFileType>;
    +
    +type CryptoModuleConfiguration = {
    +  default: CryptorType;
    +  cryptors?: Array<CryptorType>;
    +};
    +
    +export class CryptoModule {
    +  static LEGACY_IDENTIFIER = '';
    +
    +  defaultCryptor: CryptorType;
    +  cryptors: Array<CryptorType>;
    +
    +  constructor(cryptoModuleConfiguration: CryptoModuleConfiguration) {
    +    this.defaultCryptor = cryptoModuleConfiguration.default;
    +    this.cryptors = cryptoModuleConfiguration.cryptors ?? [];
    +  }
    +
    +  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +  // @ts-ignore: type detection issue with old Config type assignment
    +  static legacyCryptoModule(config) {
    +    return new this({
    +      default: new LegacyCryptor({
    +        cipherKey: config.cipherKey,
    +        useRandomIVs: config.useRandomIVs ?? true,
    +      }),
    +      cryptors: [new AesCbcCryptor({ cipherKey: config.cipherKey })],
    +    });
    +  }
    +
    +  static aesCbcCryptoModule(config: any) {
    +    return new this({
    +      default: new AesCbcCryptor({ cipherKey: config.cipherKey }),
    +      cryptors: [
    +        new LegacyCryptor({
    +          cipherKey: config.cipherKey,
    +          useRandomIVs: config.useRandomIVs ?? true,
    +        }),
    +      ],
    +    });
    +  }
    +  static withDefaultCryptor(defaultCryptor: CryptorType) {
    +    return new this({ default: defaultCryptor });
    +  }
    +
    +  private getAllCryptors() {
    +    return [this.defaultCryptor, ...this.cryptors];
    +  }
    +
    +  private getLegacyCryptor() {
    +    return this.getAllCryptors().find((c) => c.identifier === '');
    +  }
    +
    +  encrypt(data: ArrayBuffer | string) {
    +    const encrypted = this.defaultCryptor.encrypt(data);
    +    if (!encrypted.metadata) return encrypted.data;
    +
    +    const header = CryptorHeader.from(this.defaultCryptor.identifier, encrypted.metadata);
    +
    +    const headerData = new Uint8Array(header!.length);
    +    let pos = 0;
    +    headerData.set(header!.data, pos);
    +    pos = header!.length - encrypted.metadata.length;
    +    headerData.set(encrypted.metadata, pos);
    +    return Buffer.concat([headerData, Buffer.from(encrypted.data)]);
    +  }
    +
    +  decrypt(data: ArrayBuffer | string) {
    +    const encryptedData = Buffer.from(typeof data === 'string' ? decode(data) : data);
    +    const header = CryptorHeader.tryParse(encryptedData);
    +    const cryptor = this.getCryptor(header);
    +    const metadata =
    +      header.length > 0
    +        ? encryptedData.slice(header.length - (header as CryptorHeaderV1).metadataLength, header.length)
    +        : null;
    +    if (encryptedData.slice(header.length).byteLength <= 0) throw new Error('decryption error. empty content');
    +    return cryptor!.decrypt({
    +      data: encryptedData.slice(header.length),
    +      metadata: metadata,
    +    });
    +  }
    +
    +  async encryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    /**
    +     * Files handled differently in case of Legacy cryptor.
    +     * (as long as we support legacy need to check on intsance type)
    +     */
    +    if (this.defaultCryptor.identifier === CryptorHeader.LEGACY_IDENTIFIER)
    +      return (this.defaultCryptor as ILegacyCryptor<PubNubFileType>).encryptFile(file, File);
    +    if (file.data instanceof Buffer) {
    +      return File.create({
    +        name: file.name,
    +        mimeType: 'application/octet-stream',
    +        data: Buffer.from(this.encrypt(file.data!) as Buffer),
    +      });
    +    }
    +    if (file.data instanceof Readable) {
    +      if (file.contentLength === 0) throw new Error('encryption error. empty content');
    +      const encryptedStream = await (this.defaultCryptor as ICryptor).encryptStream(file.data);
    +      const header = CryptorHeader.from(this.defaultCryptor.identifier, encryptedStream.metadata!);
    +      const payload = new Uint8Array(header!.length);
    +      let pos = 0;
    +      payload.set(header!.data, pos);
    +      pos += header!.length;
    +      if (encryptedStream.metadata) {
    +        pos -= encryptedStream.metadata.length;
    +        payload.set(encryptedStream.metadata, pos);
    +      }
    +      const output = new PassThrough();
    +      output.write(payload);
    +      encryptedStream.stream.pipe(output);
    +      return File.create({
    +        name: file.name,
    +        mimeType: 'application/octet-stream',
    +        stream: output,
    +      });
    +    }
    +  }
    +
    +  async decryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    if (file?.data instanceof Buffer) {
    +      const header = CryptorHeader.tryParse(file.data);
    +      const cryptor = this.getCryptor(header);
    +      /**
    +       * If It's legacyone then redirect it.
    +       * (as long as we support legacy need to check on instance type)
    +       */
    +      if (cryptor?.identifier === CryptoModule.LEGACY_IDENTIFIER)
    +        return (cryptor as ILegacyCryptor<PubNubFileType>).decryptFile(file, File);
    +      return File.create({
    +        name: file.name,
    +        data: Buffer.from(this.decrypt(file?.data) as ArrayBuffer),
    +      });
    +    }
    +
    +    if (file.data instanceof Readable) {
    +      const stream = file.data;
    +      return new Promise((resolve) => {
    +        stream.on('readable', () => resolve(this.onStreamReadable(stream, file, File)));
    +      });
    +    }
    +  }
    +
    +  private async onStreamReadable(stream: NodeJS.ReadableStream, file: PubNubFileType, File: PubNubFileType) {
    +    stream.removeAllListeners('readable');
    +    const magicBytes = stream.read(4);
    +    if (!CryptorHeader.isSentinel(magicBytes as Buffer)) {
    +      if (magicBytes === null) throw new Error('decryption error. empty content');
    +      stream.unshift(magicBytes);
    +      return this.decryptLegacyFileStream(stream, file, File);
    +    }
    +    const versionByte = stream.read(1);
    +    CryptorHeader.validateVersion(versionByte[0] as number);
    +    const identifier = stream.read(4);
    +    const cryptor = this.getCryptorFromId(CryptorHeader.tryGetIdentifier(identifier as Buffer));
    +    const headerSize = CryptorHeader.tryGetMetadataSizeFromStream(stream);
    +    if (file.contentLength <= CryptorHeader.MIN_HEADER_LEGTH + headerSize)
    +      throw new Error('decryption error. empty content');
    +    return File.create({
    +      name: file.name,
    +      mimeType: 'application/octet-stream',
    +      stream: await (cryptor as ICryptor).decryptStream({ stream: stream, metadataLength: headerSize as number }),
    +    });
    +  }
    +
    +  private async decryptLegacyFileStream(stream: NodeJS.ReadableStream, file: PubNubFileType, File: PubNubFileType) {
    +    if (file.contentLength <= 16) throw new Error('decryption error: empty content');
    +    const cryptor = this.getLegacyCryptor();
    +    if (cryptor) {
    +      return (cryptor as ILegacyCryptor<PubNubFileType>).decryptFile(
    +        File.create({
    +          name: file.name,
    +          stream: stream,
    +        }),
    +        File,
    +      );
    +    } else {
    +      throw new Error('unknown cryptor error');
    +    }
    +  }
    +
    +  private getCryptor(header: CryptorHeader) {
    +    if (header === '') {
    +      const cryptor = this.getAllCryptors().find((c) => c.identifier === '');
    +      if (cryptor) return cryptor;
    +      throw new Error('unknown cryptor error');
    +    } else if (header instanceof CryptorHeaderV1) {
    +      return this.getCryptorFromId(header.identifier);
    +    }
    +  }
    +
    +  private getCryptorFromId(id: string) {
    +    const cryptor = this.getAllCryptors().find((c) => id === c.identifier);
    +    if (cryptor) {
    +      return cryptor;
    +    }
    +    throw new Error('unknown cryptor error');
    +  }
    +}
    +
    +// CryptorHeader Utility
    +class CryptorHeader {
    +  static SENTINEL = 'PNED';
    +  static LEGACY_IDENTIFIER = '';
    +  static IDENTIFIER_LENGTH = 4;
    +  static VERSION = 1;
    +  static MAX_VERSION = 1;
    +  static MIN_HEADER_LEGTH = 10;
    +
    +  static from(id: string, metadata: Buffer) {
    +    if (id === CryptorHeader.LEGACY_IDENTIFIER) return;
    +    return new CryptorHeaderV1(id, metadata.length);
    +  }
    +
    +  static isSentinel(bytes: Buffer) {
    +    if (bytes && bytes.byteLength >= 4) {
    +      if (bytes.toString('utf8') == CryptorHeader.SENTINEL) return true;
    +    }
    +  }
    +
    +  static validateVersion(data: number) {
    +    if (data && data > CryptorHeader.MAX_VERSION) throw new Error('decryption error. invalid header version');
    +    return data;
    +  }
    +
    +  static tryGetIdentifier(data: Buffer) {
    +    if (data.byteLength < 4) {
    +      throw new Error('unknown cryptor error. decryption failed');
    +    } else {
    +      return data.toString('utf8');
    +    }
    +  }
    +
    +  static tryGetMetadataSizeFromStream(stream: NodeJS.ReadableStream) {
    +    const sizeBuf = stream.read(1);
    +    if (sizeBuf && (sizeBuf[0] as number) < 255) {
    +      return sizeBuf[0] as number;
    +    }
    +    if ((sizeBuf[0] as number) === 255) {
    +      const nextBuf = stream.read(2);
    +      if (nextBuf.length >= 2) {
    +        return new Uint16Array([nextBuf[0] as number, nextBuf[1] as number]).reduce((acc, val) => (acc << 8) + val, 0);
    +      }
    +    }
    +    throw new Error('decryption error. Invalid metadata size');
    +  }
    +  static tryParse(encryptedData: Buffer) {
    +    let sentinel: any = '';
    +    let version = null;
    +    if (encryptedData.length >= 4) {
    +      sentinel = encryptedData.slice(0, 4);
    +      if (sentinel.toString('utf8') !== CryptorHeader.SENTINEL) return '';
    +    }
    +
    +    if (encryptedData.length >= 5) {
    +      version = encryptedData[4];
    +    } else {
    +      throw new Error('decryption error. invalid header version');
    +    }
    +    if (version > CryptorHeader.MAX_VERSION) throw new Error('unknown cryptor error');
    +
    +    let identifier: Buffer;
    +    let pos = 5 + CryptorHeader.IDENTIFIER_LENGTH;
    +    if (encryptedData.length >= pos) {
    +      identifier = encryptedData.slice(5, pos);
    +    } else {
    +      throw new Error('decryption error. invalid crypto identifier');
    +    }
    +    let metadataLength = null;
    +    if (encryptedData.length >= pos + 1) {
    +      metadataLength = encryptedData[pos];
    +    } else {
    +      throw new Error('decryption error. invalid metadata length');
    +    }
    +    pos += 1;
    +    if (metadataLength === 255 && encryptedData.length >= pos + 2) {
    +      metadataLength = new Uint16Array(encryptedData.slice(pos, pos + 2)).reduce((acc, val) => (acc << 8) + val, 0);
    +      pos += 2;
    +    }
    +    return new CryptorHeaderV1(identifier.toString('utf8'), metadataLength);
    +  }
    +}
    +
    +// v1 CryptorHeader
    +class CryptorHeaderV1 {
    +  _identifier;
    +  _metadataLength;
    +
    +  constructor(id: string, metadataLength: number) {
    +    this._identifier = id;
    +    this._metadataLength = metadataLength;
    +  }
    +
    +  get identifier() {
    +    return this._identifier;
    +  }
    +
    +  set identifier(value) {
    +    this._identifier = value;
    +  }
    +
    +  get metadataLength() {
    +    return this._metadataLength;
    +  }
    +
    +  set metadataLength(value) {
    +    this._metadataLength = value;
    +  }
    +
    +  get version() {
    +    return CryptorHeader.VERSION;
    +  }
    +
    +  get length() {
    +    return (
    +      CryptorHeader.SENTINEL.length +
    +      1 +
    +      CryptorHeader.IDENTIFIER_LENGTH +
    +      (this.metadataLength < 255 ? 1 : 3) +
    +      this.metadataLength
    +    );
    +  }
    +
    +  get data() {
    +    let pos = 0;
    +    const header = new Uint8Array(this.length);
    +    header.set(Buffer.from(CryptorHeader.SENTINEL));
    +    pos += CryptorHeader.SENTINEL.length;
    +    header[pos] = this.version;
    +    pos++;
    +    if (this.identifier) header.set(Buffer.from(this.identifier), pos);
    +    pos += CryptorHeader.IDENTIFIER_LENGTH;
    +    const metadataLength = this.metadataLength;
    +    if (metadataLength < 255) {
    +      header[pos] = metadataLength;
    +    } else {
    +      header.set([255, metadataLength >> 8, metadataLength & 0xff], pos);
    +    }
    +    return header;
    +  }
    +}
    
  • src/crypto/modules/node.js+20 11 modified
    @@ -1,5 +1,5 @@
     /**       */
    -import { Readable, PassThrough } from 'stream';
    +import { Readable, PassThrough, Transform } from 'stream';
     import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto';
     
     export default class NodeCryptography {
    @@ -41,13 +41,15 @@ export default class NodeCryptography {
         const bKey = this.getKey(key);
     
         if (file.data instanceof Buffer) {
    +      if (file.data.byteLength <= 0) throw new Error('encryption error. empty content');
           return File.create({
             name: file.name,
             mimeType: 'application/octet-stream',
             data: await this.encryptBuffer(bKey, file.data),
           });
         }
         if (file.data instanceof Readable) {
    +      if (file.contentLength === 0) throw new Error('encryption error. empty content');
           return File.create({
             name: file.name,
             mimeType: 'application/octet-stream',
    @@ -118,22 +120,29 @@ export default class NodeCryptography {
       decryptBuffer(key, ciphertext) {
         const bIv = ciphertext.slice(0, NodeCryptography.IV_LENGTH);
         const bCiphertext = ciphertext.slice(NodeCryptography.IV_LENGTH);
    -
    +    if (bCiphertext.byteLength <= 0) throw new Error('decryption error: empty content');
         const aes = createDecipheriv(this.algo, key, bIv);
     
         return Buffer.concat([aes.update(bCiphertext), aes.final()]);
       }
     
    -  encryptStream(key, stream) {
    -    const output = new PassThrough();
    +  async encryptStream(key, stream) {
         const bIv = this.getIv();
    -
    -    const aes = createCipheriv(this.algo, key, bIv);
    -
    -    output.write(bIv);
    -    stream.pipe(aes).pipe(output);
    -
    -    return output;
    +    const aes = createCipheriv('aes-256-cbc', key, bIv).setAutoPadding(true);
    +    let inited = false;
    +    return stream.pipe(aes).pipe(
    +      new Transform({
    +        transform(chunk, _, cb) {
    +          if (!inited) {
    +            inited = true;
    +            this.push(Buffer.concat([bIv, chunk]));
    +          } else {
    +            this.push(chunk);
    +          }
    +          cb();
    +        },
    +      }),
    +    );
       }
     
       decryptStream(key, stream) {
    
  • src/crypto/modules/WebCryptoModule/aesCbcCryptor.ts+86 0 added
    @@ -0,0 +1,86 @@
    +import { ICryptor, EncryptedDataType } from './ICryptor';
    +import cryptoJS from '../../../core/components/cryptography/hmac-sha256';
    +import { decode } from '../../../core/components/base64_codec';
    +
    +export default class AesCbcCryptor implements ICryptor {
    +  static BLOCK_SIZE = 16;
    +  static encoder = new TextEncoder();
    +  static decoder = new TextDecoder();
    +
    +  cipherKey: string;
    +  encryptedKey: any;
    +  CryptoJS: any;
    +
    +  constructor(configuration: { cipherKey: string }) {
    +    this.cipherKey = configuration.cipherKey;
    +    this.CryptoJS = cryptoJS;
    +    this.encryptedKey = this.CryptoJS.SHA256(this.cipherKey);
    +  }
    +
    +  get algo() {
    +    return 'AES-CBC';
    +  }
    +
    +  get identifier() {
    +    return 'ACRH';
    +  }
    +
    +  private getIv() {
    +    return crypto.getRandomValues(new Uint8Array(AesCbcCryptor.BLOCK_SIZE));
    +  }
    +
    +  private async getKey() {
    +    const bKey = AesCbcCryptor.encoder.encode(this.cipherKey);
    +    const abHash = await crypto.subtle.digest('SHA-256', bKey.buffer);
    +    return crypto.subtle.importKey('raw', abHash, this.algo, true, ['encrypt', 'decrypt']);
    +  }
    +
    +  encrypt(data: ArrayBuffer | string) {
    +    const stringData = typeof data === 'string' ? data : AesCbcCryptor.decoder.decode(data);
    +    if (stringData.length === 0) throw new Error('encryption error. empty content');
    +    const abIv = this.getIv();
    +    return {
    +      metadata: abIv,
    +      data: decode(
    +        this.CryptoJS.AES.encrypt(data, this.encryptedKey, {
    +          iv: this.bufferToWordArray(abIv),
    +          mode: this.CryptoJS.mode.CBC,
    +        }).ciphertext.toString(this.CryptoJS.enc.Base64),
    +      ),
    +    };
    +  }
    +
    +  decrypt(encryptedData: EncryptedDataType) {
    +    const iv = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.metadata!));
    +    const data = this.bufferToWordArray(new Uint8ClampedArray(encryptedData.data));
    +    return AesCbcCryptor.encoder.encode(
    +      this.CryptoJS.AES.decrypt({ ciphertext: data }, this.encryptedKey, {
    +        iv,
    +        mode: this.CryptoJS.mode.CBC,
    +      }).toString(this.CryptoJS.enc.Utf8),
    +    ).buffer;
    +  }
    +
    +  async encryptFileData(data: ArrayBuffer): Promise<EncryptedDataType> {
    +    const key = await this.getKey();
    +    const iv = this.getIv();
    +    return {
    +      data: await crypto.subtle.encrypt({ name: this.algo, iv: iv }, key, data),
    +      metadata: iv,
    +    };
    +  }
    +
    +  async decryptFileData(encryptedData: EncryptedDataType): Promise<ArrayBuffer> {
    +    const key = await this.getKey();
    +    return crypto.subtle.decrypt({ name: this.algo, iv: encryptedData.metadata! }, key, encryptedData.data);
    +  }
    +
    +  private bufferToWordArray(b: any) {
    +    const wa: any[] = [];
    +    let i;
    +    for (i = 0; i < b.length; i += 1) {
    +      wa[(i / 4) | 0] |= b[i] << (24 - 8 * i);
    +    }
    +    return this.CryptoJS.lib.WordArray.create(wa, b.length);
    +  }
    +}
    
  • src/crypto/modules/WebCryptoModule/ICryptor.ts+13 0 added
    @@ -0,0 +1,13 @@
    +export type EncryptedDataType = {
    +  data: ArrayBuffer;
    +  metadata: ArrayBuffer | null;
    +};
    +
    +export interface ICryptor {
    +  get identifier(): string;
    +  encrypt(data: ArrayBuffer | string): EncryptedDataType;
    +  decrypt(data: EncryptedDataType): ArrayBuffer;
    +
    +  encryptFileData(data: ArrayBuffer): Promise<EncryptedDataType>;
    +  decryptFileData(data: EncryptedDataType): Promise<ArrayBuffer>;
    +}
    
  • src/crypto/modules/WebCryptoModule/ILegacyCryptor.ts+24 0 added
    @@ -0,0 +1,24 @@
    +import { EncryptedDataType } from './ICryptor';
    +
    +export type PubNubFileType = {
    +  data: File | Blob;
    +  name: string;
    +  mimeType: string;
    +
    +  create(config: any): PubNubFileType;
    +
    +  toArrayBuffer(): ArrayBuffer;
    +  toBlob(): Blob;
    +  toString(): string;
    +  toFile(): File;
    +};
    +
    +export interface ILegacyCryptor<T extends PubNubFileType> {
    +  get identifier(): string;
    +
    +  encrypt(data: ArrayBuffer | string): EncryptedDataType;
    +  decrypt(data: EncryptedDataType): ArrayBuffer | string;
    +
    +  encryptFile(file: T, File: T): Promise<T>;
    +  decryptFile(file: T, File: T): Promise<T>;
    +}
    
  • src/crypto/modules/WebCryptoModule/legacyCryptor.ts+46 0 added
    @@ -0,0 +1,46 @@
    +import Crypto from '../../../core/components/cryptography/index';
    +import FileCryptor from '../web';
    +import { EncryptedDataType } from './ICryptor';
    +import { ILegacyCryptor, PubNubFileType } from './ILegacyCryptor';
    +import { encode } from '../../../core/components/base64_codec';
    +
    +export default class LegacyCryptor implements ILegacyCryptor<PubNubFileType> {
    +  config;
    +
    +  cryptor;
    +  fileCryptor;
    +
    +  constructor(config: any) {
    +    this.config = config;
    +    this.cryptor = new Crypto({ config });
    +    this.fileCryptor = new FileCryptor();
    +  }
    +
    +  get identifier() {
    +    return '';
    +  }
    +  encrypt(data: ArrayBuffer | string) {
    +    const stringData = typeof data === 'string' ? data : new TextDecoder().decode(data);
    +    return {
    +      data: this.cryptor.encrypt(stringData),
    +      metadata: null,
    +    };
    +  }
    +
    +  decrypt(encryptedData: EncryptedDataType) {
    +    const data = typeof encryptedData.data === 'string' ? encryptedData.data : encode(encryptedData.data);
    +    return this.cryptor.decrypt(data);
    +  }
    +
    +  async encryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +    //@ts-ignore: can not detect cipherKey from old Config
    +    return this.fileCryptor.encryptFile(this.config?.cipherKey, file, File);
    +  }
    +
    +  async decryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +    //@ts-ignore: can not detect cipherKey from old Config
    +    return this.fileCryptor.decryptFile(this.config.cipherKey, file, File);
    +  }
    +}
    
  • src/crypto/modules/WebCryptoModule/webCryptoModule.ts+273 0 added
    @@ -0,0 +1,273 @@
    +import LegacyCryptor from './legacyCryptor';
    +import AesCbcCryptor from './aesCbcCryptor';
    +import { EncryptedDataType, ICryptor } from './ICryptor';
    +import { ILegacyCryptor, PubNubFileType } from './ILegacyCryptor';
    +import { decode } from '../../../core/components/base64_codec';
    +
    +export { LegacyCryptor, AesCbcCryptor };
    +
    +type CryptorType = ICryptor | ILegacyCryptor<PubNubFileType>;
    +
    +type CryptoModuleConfiguration = {
    +  default: CryptorType;
    +  cryptors?: Array<CryptorType>;
    +};
    +
    +export class CryptoModule {
    +  static LEGACY_IDENTIFIER = '';
    +  static encoder = new TextEncoder();
    +  static decoder = new TextDecoder();
    +  defaultCryptor: CryptorType;
    +  cryptors: Array<CryptorType>;
    +
    +  constructor(cryptoModuleConfiguration: CryptoModuleConfiguration) {
    +    this.defaultCryptor = cryptoModuleConfiguration.default;
    +    this.cryptors = cryptoModuleConfiguration.cryptors ?? [];
    +  }
    +
    +  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    +  //@ts-ignore: type detection issue with old Config type assignment
    +  static legacyCryptoModule(config) {
    +    return new this({
    +      default: new LegacyCryptor({
    +        cipherKey: config.cipherKey,
    +        useRandomIVs: config.useRandomIVs ?? true,
    +      }),
    +      cryptors: [new AesCbcCryptor({ cipherKey: config.cipherKey })],
    +    });
    +  }
    +
    +  static aesCbcCryptoModule(config: any) {
    +    return new this({
    +      default: new AesCbcCryptor({ cipherKey: config.cipherKey }),
    +      cryptors: [
    +        new LegacyCryptor({
    +          cipherKey: config.cipherKey,
    +          useRandomIVs: config.useRandomIVs ?? true,
    +        }),
    +      ],
    +    });
    +  }
    +
    +  static withDefaultCryptor(defaultCryptor: CryptorType) {
    +    return new this({ default: defaultCryptor });
    +  }
    +
    +  private getAllCryptors() {
    +    return [this.defaultCryptor, ...this.cryptors];
    +  }
    +
    +  encrypt(data: ArrayBuffer | string) {
    +    const encrypted = (this.defaultCryptor as ICryptor).encrypt(data);
    +    if (!encrypted.metadata) return encrypted.data;
    +    const headerData = this.getHeaderData(encrypted);
    +    return this.concatArrayBuffer(headerData!, encrypted.data);
    +  }
    +
    +  decrypt(data: ArrayBuffer | string) {
    +    const encryptedData = typeof data === 'string' ? decode(data) : data;
    +    const header = CryptorHeader.tryParse(encryptedData);
    +    const cryptor = this.getCryptor(header);
    +    const metadata =
    +      header.length > 0
    +        ? encryptedData.slice(header.length - (header as CryptorHeaderV1).metadataLength, header.length)
    +        : null;
    +    if (encryptedData.slice(header.length).byteLength <= 0) throw new Error('decryption error. empty content');
    +    return cryptor!.decrypt({
    +      data: encryptedData.slice(header.length),
    +      metadata: metadata,
    +    });
    +  }
    +
    +  async encryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    if (this.defaultCryptor.identifier === CryptorHeader.LEGACY_IDENTIFIER)
    +      return (this.defaultCryptor as ILegacyCryptor<PubNubFileType>).encryptFile(file, File);
    +    const fileData = this.getFileData(file.data);
    +    const encrypted = await (this.defaultCryptor as ICryptor).encryptFileData(fileData);
    +    return File.create({
    +      name: file.name,
    +      mimeType: 'application/octet-stream',
    +      data: this.concatArrayBuffer(this.getHeaderData(encrypted)!, encrypted.data),
    +    });
    +  }
    +
    +  async decryptFile(file: PubNubFileType, File: PubNubFileType) {
    +    const data = await file.data.arrayBuffer();
    +    const header = CryptorHeader.tryParse(data);
    +    const cryptor = this.getCryptor(header);
    +    if (cryptor?.identifier === CryptoModule.LEGACY_IDENTIFIER) {
    +      return (cryptor as ILegacyCryptor<PubNubFileType>).decryptFile(file, File);
    +    }
    +    const fileData = this.getFileData(data);
    +    const metadata = fileData.slice(header.length - (header as CryptorHeaderV1).metadataLength, header.length);
    +    return File.create({
    +      name: file.name,
    +      data: await (this.defaultCryptor as ICryptor).decryptFileData({
    +        data: data.slice(header.length),
    +        metadata: metadata,
    +      }),
    +    });
    +  }
    +
    +  private getCryptor(header: string | CryptorHeaderV1) {
    +    if (header === '') {
    +      const cryptor = this.getAllCryptors().find((c) => c.identifier === '');
    +      if (cryptor) return cryptor;
    +      throw new Error('unknown cryptor error');
    +    } else if (header instanceof CryptorHeaderV1) {
    +      return this.getCryptorFromId(header.identifier);
    +    }
    +  }
    +
    +  private getCryptorFromId(id: string) {
    +    const cryptor = this.getAllCryptors().find((c) => id === c.identifier);
    +    if (cryptor) {
    +      return cryptor;
    +    }
    +    throw Error('unknown cryptor error');
    +  }
    +
    +  private concatArrayBuffer(ab1: ArrayBuffer, ab2: ArrayBuffer) {
    +    const tmp = new Uint8Array(ab1.byteLength + ab2.byteLength);
    +
    +    tmp.set(new Uint8Array(ab1), 0);
    +    tmp.set(new Uint8Array(ab2), ab1.byteLength);
    +
    +    return tmp.buffer;
    +  }
    +
    +  private getHeaderData(encrypted: EncryptedDataType) {
    +    if (!encrypted.metadata) return;
    +    const header = CryptorHeader.from(this.defaultCryptor.identifier, encrypted.metadata);
    +    const headerData = new Uint8Array(header!.length);
    +    let pos = 0;
    +    headerData.set(header!.data, pos);
    +    pos += header!.length - encrypted.metadata.byteLength;
    +    headerData.set(new Uint8Array(encrypted.metadata), pos);
    +    return headerData.buffer;
    +  }
    +
    +  private getFileData(input: any) {
    +    if (input instanceof ArrayBuffer) {
    +      return input;
    +    }
    +    if (typeof input === 'string') {
    +      return CryptoModule.encoder.encode(input);
    +    }
    +    throw new Error('Cannot decrypt/encrypt file. In browsers file decryption supports only string or ArrayBuffer');
    +  }
    +}
    +
    +// CryptorHeader Utility
    +class CryptorHeader {
    +  static SENTINEL = 'PNED';
    +  static LEGACY_IDENTIFIER = '';
    +  static IDENTIFIER_LENGTH = 4;
    +  static VERSION = 1;
    +  static MAX_VERSION = 1;
    +  static decoder = new TextDecoder();
    +
    +  static from(id: string, metadata: ArrayBuffer) {
    +    if (id === CryptorHeader.LEGACY_IDENTIFIER) return;
    +    return new CryptorHeaderV1(id, metadata.byteLength);
    +  }
    +
    +  static tryParse(data: ArrayBuffer) {
    +    const encryptedData = new Uint8Array(data);
    +    let sentinel: any = '';
    +    let version = null;
    +    if (encryptedData.byteLength >= 4) {
    +      sentinel = encryptedData.slice(0, 4);
    +      if (this.decoder.decode(sentinel) !== CryptorHeader.SENTINEL) return '';
    +    }
    +    if (encryptedData.byteLength >= 5) {
    +      version = (encryptedData as Uint8Array)[4] as number;
    +    } else {
    +      throw new Error('decryption error. invalid header version');
    +    }
    +    if (version > CryptorHeader.MAX_VERSION) throw new Error('unknown cryptor error');
    +
    +    let identifier: any = '';
    +    let pos = 5 + CryptorHeader.IDENTIFIER_LENGTH;
    +    if (encryptedData.byteLength >= pos) {
    +      identifier = encryptedData.slice(5, pos);
    +    } else {
    +      throw new Error('decryption error. invalid crypto identifier');
    +    }
    +    let metadataLength = null;
    +    if (encryptedData.byteLength >= pos + 1) {
    +      metadataLength = (encryptedData as Uint8Array)[pos];
    +    } else {
    +      throw new Error('decryption error. invalid metadata length');
    +    }
    +    pos += 1;
    +    if (metadataLength === 255 && encryptedData.byteLength >= pos + 2) {
    +      metadataLength = new Uint16Array(encryptedData.slice(pos, pos + 2)).reduce((acc, val) => (acc << 8) + val, 0);
    +      pos += 2;
    +    }
    +    return new CryptorHeaderV1(this.decoder.decode(identifier), metadataLength);
    +  }
    +}
    +
    +// v1 CryptorHeader
    +class CryptorHeaderV1 {
    +  static IDENTIFIER_LENGTH = 4;
    +  static SENTINEL = 'PNED';
    +
    +  _identifier;
    +  _metadataLength;
    +
    +  constructor(id: string, metadataLength: number) {
    +    this._identifier = id;
    +    this._metadataLength = metadataLength;
    +  }
    +
    +  get identifier() {
    +    return this._identifier;
    +  }
    +
    +  set identifier(value) {
    +    this._identifier = value;
    +  }
    +
    +  get metadataLength() {
    +    return this._metadataLength;
    +  }
    +
    +  set metadataLength(value) {
    +    this._metadataLength = value;
    +  }
    +
    +  get version() {
    +    return CryptorHeader.VERSION;
    +  }
    +
    +  get length() {
    +    return (
    +      CryptorHeader.SENTINEL.length +
    +      1 +
    +      CryptorHeader.IDENTIFIER_LENGTH +
    +      (this.metadataLength < 255 ? 1 : 3) +
    +      this.metadataLength
    +    );
    +  }
    +
    +  get data() {
    +    let pos = 0;
    +    const header = new Uint8Array(this.length);
    +    const encoder = new TextEncoder();
    +    header.set(encoder.encode(CryptorHeader.SENTINEL));
    +    pos += CryptorHeader.SENTINEL.length;
    +    header[pos] = this.version;
    +    pos++;
    +    if (this.identifier) header.set(encoder.encode(this.identifier), pos);
    +    pos += CryptorHeader.IDENTIFIER_LENGTH;
    +    const metadataLength = this.metadataLength;
    +    if (metadataLength < 255) {
    +      header[pos] = metadataLength;
    +    } else {
    +      header.set([255, metadataLength >> 8, metadataLength & 0xff], pos);
    +    }
    +    return header;
    +  }
    +}
    
  • src/crypto/modules/web.js+21 15 modified
    @@ -11,6 +11,8 @@ function concatArrayBuffer(ab1, ab2) {
     
     export default class WebCryptography {
       static IV_LENGTH = 16;
    +  static encoder = new TextEncoder();
    +  static decoder = new TextDecoder();
     
       get algo() {
         return 'aes-256-cbc';
    @@ -30,7 +32,6 @@ export default class WebCryptography {
     
       async decrypt(key, input) {
         const cKey = await this.getKey(key);
    -
         if (input instanceof ArrayBuffer) {
           return this.decryptArrayBuffer(cKey, input);
         }
    @@ -41,9 +42,10 @@ export default class WebCryptography {
       }
     
       async encryptFile(key, file, File) {
    +    if (file.data.byteLength <= 0) throw new Error('encryption error. empty content');
         const bKey = await this.getKey(key);
     
    -    const abPlaindata = await file.toArrayBuffer();
    +    const abPlaindata = await file.data.arrayBuffer();
     
         const abCipherdata = await this.encryptArrayBuffer(bKey, abPlaindata);
     
    @@ -57,8 +59,7 @@ export default class WebCryptography {
       async decryptFile(key, file, File) {
         const bKey = await this.getKey(key);
     
    -    const abCipherdata = await file.toArrayBuffer();
    -
    +    const abCipherdata = await file.data.arrayBuffer();
         const abPlaindata = await this.decryptArrayBuffer(bKey, abCipherdata);
     
         return File.create({
    @@ -68,11 +69,11 @@ export default class WebCryptography {
       }
     
       async getKey(key) {
    -    const bKey = Buffer.from(key);
    -    const abHash = await crypto.subtle.digest('SHA-256', bKey.buffer);
    -
    -    const abKey = Buffer.from(Buffer.from(abHash).toString('hex').slice(0, 32), 'utf8').buffer;
    -
    +    const digest = await crypto.subtle.digest('SHA-256', WebCryptography.encoder.encode(key));
    +    const hashHex = Array.from(new Uint8Array(digest))
    +      .map((b) => b.toString(16).padStart(2, '0'))
    +      .join('');
    +    const abKey = WebCryptography.encoder.encode(hashHex.slice(0, 32)).buffer;
         return crypto.subtle.importKey('raw', abKey, 'AES-CBC', true, ['encrypt', 'decrypt']);
       }
     
    @@ -84,28 +85,33 @@ export default class WebCryptography {
     
       async decryptArrayBuffer(key, ciphertext) {
         const abIv = ciphertext.slice(0, 16);
    -
    -    return crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, ciphertext.slice(16));
    +    if (ciphertext.slice(WebCryptography.IV_LENGTH).byteLength <= 0) throw new Error('decryption error: empty content');
    +    const data = await crypto.subtle.decrypt(
    +      { name: 'AES-CBC', iv: abIv },
    +      key,
    +      ciphertext.slice(WebCryptography.IV_LENGTH),
    +    );
    +    return data;
       }
     
       async encryptString(key, plaintext) {
         const abIv = crypto.getRandomValues(new Uint8Array(16));
     
    -    const abPlaintext = Buffer.from(plaintext).buffer;
    +    const abPlaintext = WebCryptography.encoder.encode(plaintext).buffer;
         const abPayload = await crypto.subtle.encrypt({ name: 'AES-CBC', iv: abIv }, key, abPlaintext);
     
         const ciphertext = concatArrayBuffer(abIv.buffer, abPayload);
     
    -    return Buffer.from(ciphertext).toString('utf8');
    +    return WebCryptography.decoder.decode(ciphertext);
       }
     
       async decryptString(key, ciphertext) {
    -    const abCiphertext = Buffer.from(ciphertext);
    +    const abCiphertext = WebCryptography.encoder.encode(ciphertext).buffer;
         const abIv = abCiphertext.slice(0, 16);
         const abPayload = abCiphertext.slice(16);
     
         const abPlaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, abPayload);
     
    -    return Buffer.from(abPlaintext).toString('utf8');
    +    return WebCryptography.decoder.decode(abPlaintext);
       }
     }
    
  • src/file/modules/node.js+5 6 modified
    @@ -1,11 +1,7 @@
    -/**       */
    -
     import { Readable, PassThrough } from 'stream';
    -import { ReadStream } from 'fs';
    +import fs from 'fs';
     import { basename } from 'path';
     
    -import { IFile, FileClass } from '..';
    -
     const PubNubFile = class PubNubFile {
       static supportsBlob = false;
     
    @@ -29,6 +25,8 @@ const PubNubFile = class PubNubFile {
     
       mimeType;
     
    +  contentLength;
    +
       static create(config) {
         return new this(config);
       }
    @@ -37,9 +35,10 @@ const PubNubFile = class PubNubFile {
         if (stream instanceof Readable) {
           this.data = stream;
     
    -      if (stream instanceof ReadStream) {
    +      if (stream instanceof fs.ReadStream) {
             // $FlowFixMe: incomplete flow node definitions
             this.name = basename(stream.path);
    +        this.contentLength = fs.statSync(stream.path).size;
           }
         } else if (data instanceof Buffer) {
           this.data = Buffer.from(data);
    
  • src/node/index.ts+12 0 modified
    @@ -8,8 +8,10 @@ import { keepAlive, proxy } from '../networking/modules/node';
     
     import NodeCryptography from '../crypto/modules/node';
     import PubNubFile from '../file/modules/node';
    +import { CryptoModule, LegacyCryptor, AesCbcCryptor } from '../crypto/modules/NodeCryptoModule/nodeCryptoModule';
     
     export = class extends PubNubCore {
    +  static CryptoModule = CryptoModule;
       constructor(setup: any) {
         setup.cbor = new Cbor((buffer: ArrayBuffer) => CborReader.decode(Buffer.from(buffer)), decode);
         setup.networking = new Networking({
    @@ -27,6 +29,16 @@ export = class extends PubNubCore {
         setup.PubNubFile = PubNubFile;
         setup.cryptography = new NodeCryptography();
     
    +    setup.initCryptoModule = (cryptoConfiguration: any) => {
    +      return new CryptoModule({
    +        default: new LegacyCryptor({
    +          cipherKey: cryptoConfiguration.cipherKey,
    +          useRandomIVs: cryptoConfiguration.useRandomIVs,
    +        }),
    +        cryptors: [new AesCbcCryptor({ cipherKey: cryptoConfiguration.cipherKey })],
    +      });
    +    };
    +
         if (!('ssl' in setup)) {
           setup.ssl = true;
         }
    
  • src/web/index.js+12 1 modified
    @@ -11,6 +11,7 @@ import { del, get, post, patch, getfile, postfile } from '../networking/modules/
     
     import WebCryptography from '../crypto/modules/web';
     import PubNubFile from '../file/modules/web';
    +import { CryptoModule, LegacyCryptor, AesCbcCryptor } from '../crypto/modules/WebCryptoModule/webCryptoModule';
     
     function sendBeacon(url) {
       if (navigator && navigator.sendBeacon) {
    @@ -21,10 +22,10 @@ function sendBeacon(url) {
     }
     
     export default class extends PubNubCore {
    +  static CryptoModule = CryptoModule;
       constructor(setup) {
         // extract config.
         const { listenToBrowserNetworkEvents = true } = setup;
    -
         setup.sdkFamily = 'Web';
         setup.networking = new Networking({
           del,
    @@ -40,6 +41,16 @@ export default class extends PubNubCore {
         setup.PubNubFile = PubNubFile;
         setup.cryptography = new WebCryptography();
     
    +    setup.initCryptoModule = (cryptoConfiguration) => {
    +      return new CryptoModule({
    +        default: new LegacyCryptor({
    +          cipherKey: cryptoConfiguration.cipherKey,
    +          useRandomIVs: cryptoConfiguration.useRandomIVs,
    +        }),
    +        cryptors: [new AesCbcCryptor({ cipherKey: cryptoConfiguration.cipherKey })],
    +      });
    +    };
    +
         super(setup);
     
         if (listenToBrowserNetworkEvents) {
    
  • test/contract/steps/cryptoModule/cryptoModule.ts+178 0 added
    @@ -0,0 +1,178 @@
    +import { Given, When, Then, Before } from '@cucumber/cucumber';
    +import { expect } from 'chai';
    +import fs from 'fs';
    +
    +import {
    +  CryptoModule,
    +  AesCbcCryptor,
    +  LegacyCryptor,
    +} from '../../../../lib/crypto/modules/NodeCryptoModule/nodeCryptoModule.js';
    +
    +Before(function () {
    +  this.useRandomIVs = true;
    +});
    +
    +Given('Crypto module with {string} cryptor', function (cryptorIdentifier: string) {
    +  this.cryptorIdentifier = cryptorIdentifier;
    +});
    +
    +Given(
    +  'Crypto module with default {string} and additional {string} cryptors',
    +  function (defaultCryptorId: string, additionalCryptorId: string) {
    +    this.defaultCryptorId = defaultCryptorId;
    +    this.additionalCryptorId = additionalCryptorId;
    +  },
    +);
    +
    +Given('with {string} cipher key', function (cipherKey: string) {
    +  this.cipherKey = cipherKey;
    +});
    +
    +Given('with {string} vector', function (vector: string) {
    +  if (vector === 'constant') this.useRandomIVs = false;
    +  this._initCryptor = (id: string) => {
    +    return id === 'legacy'
    +      ? new LegacyCryptor({ cipherKey: this.cipherKey, useRandomIVs: this.useRandomIVs })
    +      : new AesCbcCryptor({ cipherKey: this.cipherKey });
    +  };
    +});
    +
    +When('I decrypt {string} file', async function (fileName: string) {
    +  if (this.cryptorIdentifier === 'acrh') {
    +    const cryptor = new AesCbcCryptor({ cipherKey: this.cipherKey });
    +    this.cryptoModule = CryptoModule.withDefaultCryptor(cryptor);
    +  }
    +  const pubnub = await this.getPubnub({ subscribeKey: 'key' });
    +  const fileData = fs.readFileSync(this.getFilePath(fileName));
    +  const file = pubnub.File.create({
    +    name: fileName,
    +    mimeType: 'text/plain',
    +    data: fileData,
    +  });
    +  try {
    +    const result = await this.cryptoModule.decryptFile(file, pubnub.File);
    +  } catch (e: any) {
    +    this.errorMessage = e?.message;
    +  }
    +});
    +
    +When('I decrypt {string} file as {string}', async function (fileName: string, format: string) {
    +  if (this.defaultCryptorId && this.additionalCryptorId) {
    +    this.cryptoModule = new CryptoModule({
    +      default: this._initCryptor(this.defaultCryptorId),
    +      cryptors: [this._initCryptor(this.additionalCryptorId)],
    +    });
    +  } else {
    +    this.cryptoModule = CryptoModule.withDefaultCryptor(this._initCryptor(this.cryptorIdentifier));
    +  }
    +
    +  const pubnub = await this.getPubnub({ subscribeKey: 'key' });
    +
    +  if (format === 'binary') {
    +    this.isBinary = true;
    +    if (!this.useRandomIVs) return;
    +    let encrypteFile = pubnub.File.create({
    +      name: fileName,
    +      data: fs.readFileSync(this.getFilePath(fileName)),
    +    });
    +    try {
    +      this.binaryFileResult = await this.cryptoModule.decryptFile(encrypteFile, pubnub.File);
    +    } catch (e: any) {
    +      this.errorMessage = e?.message;
    +    }
    +  } else if (format === 'stream') {
    +    this.isStream = true;
    +    const filestream = fs.createReadStream(this.getFilePath(fileName));
    +    this.file = pubnub.File.create({
    +      name: fileName,
    +      stream: filestream,
    +    });
    +    try {
    +      this.streamFileResult = await this.cryptoModule.decryptFile(this.file, pubnub.File);
    +    } catch (e: any) {
    +      this.errorMessage = e?.message;
    +    }
    +  }
    +});
    +
    +Then('Decrypted file content equal to the {string} file content', async function (sourceFile: string) {
    +  if (this.isBinary && !this.useRandomIVs) return;
    +  if (this.isStream) {
    +    const fileStream = await this.streamFileResult.toStream();
    +    const tempFilePath = `${__dirname}/${this.file.name}`;
    +    const outputStream = fs.createWriteStream(tempFilePath);
    +    const expected = fs.readFileSync(this.getFilePath(sourceFile));
    +    fileStream.pipe(outputStream);
    +    return new Promise((resolve) => {
    +      outputStream.on('finish', () => {
    +        try {
    +          const actual = fs.readFileSync(tempFilePath);
    +          expect(Buffer.compare(actual, expected.slice(0, actual.length)) === 0).to.be.true;
    +        } finally {
    +          fs.unlink(tempFilePath, () => {});
    +        }
    +        resolve(0);
    +      });
    +    });
    +  }
    +  expect(this.binaryFileResult.data.equals(fs.readFileSync(this.getFilePath(sourceFile)))).to.be.true;
    +});
    +
    +Then('I receive {string}', async function (result: string) {
    +  if ((this.isBinaryFile || this.isBinary) && !this.useRandomIVs) return;
    +  if (result === 'success') {
    +    expect(this.errorMessage).to.be.undefined;
    +  } else {
    +    expect(this.errorMessage).to.have.string(result);
    +  }
    +});
    +
    +Given('Legacy code with {string} cipher key and {string} vector', function (cipherKey: string, vector: string) {
    +  const cryptor = new LegacyCryptor({ cipherKey: cipherKey, useRandomIVs: vector === 'random' ? true : false });
    +  this.cryptoModule = CryptoModule.withDefaultCryptor(cryptor);
    +});
    +
    +When('I encrypt {string} file as {string}', async function (fileName: string, format: string) {
    +  this.pubnub = await this.getPubnub({ subscribeKey: 'key' });
    +  this.fileDataBuffer = fs.readFileSync(this.getFilePath(fileName));
    +  if (format === 'stream') {
    +    this.file = this.pubnub.File.create({
    +      name: fileName,
    +      mimeType: 'application/octet-stream',
    +      stream: fs.createReadStream(this.getFilePath(fileName)),
    +    });
    +    this.isStream = true;
    +  } else {
    +    this.file = this.pubnub.File.create({
    +      name: fileName,
    +      mimeType: 'application/octet-stream',
    +      data: this.fileDataBuffer,
    +    });
    +    this.isBinaryFile = true;
    +  }
    +  if (!this.cryptoModule) {
    +    this.cryptoModule = CryptoModule.withDefaultCryptor(this._initCryptor(this.cryptorIdentifier));
    +  }
    +  try {
    +    this.encryptedFile = await this.cryptoModule.encryptFile(this.file, this.pubnub.File);
    +  } catch (e: any) {
    +    this.errorMessage = e?.message;
    +  }
    +});
    +
    +Then('Successfully decrypt an encrypted file with legacy code', async function () {
    +  const decryptedFile = await this.cryptoModule.decryptFile(this.encryptedFile, this.pubnub.File);
    +  if (this.isStream) {
    +    const fileStream = await decryptedFile.toStream();
    +    const tempFilePath = `${__dirname}/${this.file.name}`;
    +    const outputStream = fs.createWriteStream(tempFilePath);
    +    fileStream.pipe(outputStream);
    +    outputStream.on('end', () => {
    +      const actualFileBuffer = fs.readFileSync(tempFilePath);
    +      expect(actualFileBuffer).to.equalBytes(this.fileDataBuffer);
    +      fs.unlink(tempFilePath, () => {});
    +    });
    +  } else {
    +    expect(decryptedFile.data.toString('utf8')).to.equal(this.fileDataBuffer.toString('utf8'));
    +  }
    +});
    
  • test/contract/utils.ts+4 0 modified
    @@ -7,3 +7,7 @@ export function loadFixtureFile(persona) {
       );
       return JSON.parse(fileData);
     }
    +
    +export function getFilePath(filename) {
    +  return `${process.cwd()}/dist/contract/contract/features/encryption/assets/${filename}`;
    +}
    
  • test/contract/world.ts+4 1 modified
    @@ -4,7 +4,7 @@ import {
       World
     } from '@cucumber/cucumber';
     import PubNub from '../../lib/node/index.js';
    -import { loadFixtureFile } from './utils';
    +import { loadFixtureFile, getFilePath } from './utils';
     import * as http from 'http';
     
     interface State {
    @@ -134,6 +134,9 @@ class PubnubWorld extends World{
         }
         return this.fileFixtures[name];
       }
    +  getFilePath(filename) {
    +    return getFilePath(filename);
    +  }
     }
     
     setWorldConstructor(PubnubWorld);
    
  • tsconfig.json+7 5 modified
    @@ -1,18 +1,20 @@
     {
       "$schema": "https://json.schemastore.org/tsconfig",
    -
       "compilerOptions": {
         "target": "ES5",
         "module": "CommonJS",
         "moduleResolution": "Node",
         "esModuleInterop": true,
    -
         "allowJs": true,
         "noEmitOnError": true,
         "strict": true,
         "outDir": "./lib",
         "downlevelIteration": true
       },
    -  "include": ["src/**/*"],
    -  "exclude": ["node_modules"]
    -}
    +  "include": [
    +    "src/**/*"
    +  ],
    +  "exclude": [
    +    "node_modules"
    +  ]
    +}
    \ No newline at end of file
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

22

News mentions

0

No linked articles in our index yet.