Skip to content

Commit

Permalink
New crypto implementation (#155)
Browse files Browse the repository at this point in the history
Tests in this file have been order dependent. I broke those dependencies by introducing
few additional lines of code in each an every one. And the initHistoryOpts now requires passing pn, so maybe whoever decide to write new tests will realize that it might be nice
to create new pubnub and new config instead of reusing them across every test.

Co-authored-by: Mateusz Dahlke <[email protected]>
  • Loading branch information
kleewho and Xavrax authored Oct 16, 2023
1 parent 5e22c81 commit 428517f
Show file tree
Hide file tree
Showing 49 changed files with 2,141 additions and 2,673 deletions.
11 changes: 9 additions & 2 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
54 changes: 29 additions & 25 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pubnub

import (
"fmt"
"github.com/pubnub/go/v7/crypto"
"log"
"sync"
)
Expand All @@ -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.
Expand Down Expand Up @@ -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))
}
Expand Down
90 changes: 90 additions & 0 deletions crypto/aes_cbc_cryptor.go
Original file line number Diff line number Diff line change
@@ -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
}
77 changes: 77 additions & 0 deletions crypto/aes_cbc_cryptor_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
25 changes: 25 additions & 0 deletions crypto/cryptor.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 428517f

Please sign in to comment.