Skip to content

Commit 428517f

Browse files
kleewhoXavrax
andauthored
New crypto implementation (#155)
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 <39696234+Xavrax@users.noreply.github.com>
1 parent 5e22c81 commit 428517f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2141
-2673
lines changed

Diff for: .pubnub.yml

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
---
2-
version: v7.1.2
2+
version: v7.2.0
33
changelog:
4+
- date: 2023-10-16
5+
version: v7.2.0
6+
changes:
7+
- type: feature
8+
text: "Update the crypto module structure and add enhanced AES-CBC cryptor."
9+
- type: bug
10+
text: "Improved security of crypto implementation by increasing the cipher key entropy by a factor of two."
411
- date: 2023-05-11
512
version: v7.1.2
613
changes:
@@ -733,7 +740,7 @@ sdks:
733740
distribution-type: package
734741
distribution-repository: GitHub
735742
package-name: Go
736-
location: https://github.com/pubnub/go/releases/tag/v7.1.2
743+
location: https://github.com/pubnub/go/releases/tag/v7.2.0
737744
requires:
738745
-
739746
name: "Go"

Diff for: CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## v7.2.0
2+
October 16 2023
3+
4+
#### Added
5+
- Update the crypto module structure and add enhanced AES-CBC cryptor.
6+
7+
#### Fixed
8+
- Improved security of crypto implementation by increasing the cipher key entropy by a factor of two.
9+
110
## v7.1.2
211
May 11 2023
312

Diff for: config.go

+29-25
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package pubnub
22

33
import (
44
"fmt"
5+
"github.com/pubnub/go/v7/crypto"
56
"log"
67
"sync"
78
)
@@ -26,30 +27,33 @@ type Config struct {
2627
// UUID to be used as a device identifier.
2728
//
2829
//Deprecated: please use SetUserId/GetUserId
29-
UUID string
30-
CipherKey string // If CipherKey is passed, all communications to/from PubNub will be encrypted.
31-
Secure bool // True to use TLS
32-
ConnectTimeout int // net.Dialer.Timeout
33-
NonSubscribeRequestTimeout int // http.Client.Timeout for non-subscribe requests
34-
SubscribeRequestTimeout int // http.Client.Timeout for subscribe requests only
35-
FileUploadRequestTimeout int // http.Client.Timeout File Upload Request only
36-
HeartbeatInterval int // The frequency of the pings to the server to state that the client is active
37-
PresenceTimeout int // The time after which the server will send a timeout for the client
38-
MaximumReconnectionRetries int // The config sets how many times to retry to reconnect before giving up.
39-
MaximumLatencyDataAge int // Max time to store the latency data for telemetry
40-
FilterExpression string // Feature to subscribe with a custom filter expression.
41-
PNReconnectionPolicy ReconnectionPolicy // Reconnection policy selection
42-
Log *log.Logger // Logger instance
43-
SuppressLeaveEvents bool // When true the SDK doesn't send out the leave requests.
44-
DisablePNOtherProcessing bool // PNOther processing looks for pn_other in the JSON on the recevied message
45-
UseHTTP2 bool // HTTP2 Flag
46-
MessageQueueOverflowCount int // When the limit is exceeded by the number of messages received in a single subscribe request, a status event PNRequestMessageCountExceededCategory is fired.
47-
MaxIdleConnsPerHost int // Used to set the value of HTTP Transport's MaxIdleConnsPerHost.
48-
MaxWorkers int // Number of max workers for Publish and Grant requests
49-
UsePAMV3 bool // Use PAM version 2, Objects requets would still use PAM v3
50-
StoreTokensOnGrant bool // Will store grant v3 tokens in token manager for further use.
51-
FileMessagePublishRetryLimit int // The number of tries made in case of Publish File Message failure.
52-
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
30+
UUID string
31+
//DEPRECATED: please use CryptoModule
32+
CipherKey string // If CipherKey is passed, all communications to/from PubNub will be encrypted.
33+
Secure bool // True to use TLS
34+
ConnectTimeout int // net.Dialer.Timeout
35+
NonSubscribeRequestTimeout int // http.Client.Timeout for non-subscribe requests
36+
SubscribeRequestTimeout int // http.Client.Timeout for subscribe requests only
37+
FileUploadRequestTimeout int // http.Client.Timeout File Upload Request only
38+
HeartbeatInterval int // The frequency of the pings to the server to state that the client is active
39+
PresenceTimeout int // The time after which the server will send a timeout for the client
40+
MaximumReconnectionRetries int // The config sets how many times to retry to reconnect before giving up.
41+
MaximumLatencyDataAge int // Max time to store the latency data for telemetry
42+
FilterExpression string // Feature to subscribe with a custom filter expression.
43+
PNReconnectionPolicy ReconnectionPolicy // Reconnection policy selection
44+
Log *log.Logger // Logger instance
45+
SuppressLeaveEvents bool // When true the SDK doesn't send out the leave requests.
46+
DisablePNOtherProcessing bool // PNOther processing looks for pn_other in the JSON on the recevied message
47+
UseHTTP2 bool // HTTP2 Flag
48+
MessageQueueOverflowCount int // When the limit is exceeded by the number of messages received in a single subscribe request, a status event PNRequestMessageCountExceededCategory is fired.
49+
MaxIdleConnsPerHost int // Used to set the value of HTTP Transport's MaxIdleConnsPerHost.
50+
MaxWorkers int // Number of max workers for Publish and Grant requests
51+
UsePAMV3 bool // Use PAM version 2, Objects requets would still use PAM v3
52+
StoreTokensOnGrant bool // Will store grant v3 tokens in token manager for further use.
53+
FileMessagePublishRetryLimit int // The number of tries made in case of Publish File Message failure.
54+
//DEPRECATED: please use CryptoModule
55+
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
56+
CryptoModule crypto.CryptoModule // A cryptography module used for encryption and decryption
5357
}
5458

5559
// NewDemoConfig initiates the config with demo keys, for tests only.
@@ -90,7 +94,7 @@ func NewConfigWithUserId(userId UserId) *Config {
9094
return &c
9195
}
9296

93-
//Deprecated: Please use NewConfigWithUserId
97+
// Deprecated: Please use NewConfigWithUserId
9498
func NewConfig(uuid string) *Config {
9599
return NewConfigWithUserId(UserId(uuid))
96100
}

Diff for: crypto/aes_cbc_cryptor.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package crypto
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/sha256"
7+
"errors"
8+
"fmt"
9+
"io"
10+
)
11+
12+
type aesCbcCryptor struct {
13+
block cipher.Block
14+
}
15+
16+
func NewAesCbcCryptor(cipherKey string) (ExtendedCryptor, error) {
17+
block, e := aesCipher(cipherKey)
18+
if e != nil {
19+
return nil, e
20+
}
21+
22+
return &aesCbcCryptor{
23+
block: block,
24+
}, nil
25+
}
26+
27+
var crivId = "ACRH"
28+
29+
func (c *aesCbcCryptor) Id() string {
30+
return crivId
31+
}
32+
33+
func (c *aesCbcCryptor) Encrypt(message []byte) (*EncryptedData, error) {
34+
message = padWithPKCS7(message)
35+
iv := generateIV(aes.BlockSize)
36+
blockmode := cipher.NewCBCEncrypter(c.block, iv)
37+
38+
encryptedBytes := make([]byte, len(message))
39+
blockmode.CryptBlocks(encryptedBytes, message)
40+
41+
return &EncryptedData{
42+
Metadata: iv,
43+
Data: encryptedBytes,
44+
}, nil
45+
}
46+
47+
func (c *aesCbcCryptor) Decrypt(encryptedData *EncryptedData) (r []byte, e error) {
48+
decrypter := cipher.NewCBCDecrypter(c.block, encryptedData.Metadata)
49+
//to handle decryption errors
50+
defer func() {
51+
if rec := recover(); rec != nil {
52+
r, e = nil, fmt.Errorf("%s", rec)
53+
}
54+
}()
55+
56+
if len(encryptedData.Data)%16 != 0 {
57+
return nil, fmt.Errorf("encrypted data length should be divisible by block size")
58+
}
59+
60+
decrypted := make([]byte, len(encryptedData.Data))
61+
decrypter.CryptBlocks(decrypted, encryptedData.Data)
62+
return unpadPKCS7(decrypted)
63+
}
64+
65+
func (c *aesCbcCryptor) EncryptStream(reader io.Reader) (*EncryptedStreamData, error) {
66+
iv := generateIV(aes.BlockSize)
67+
68+
return &EncryptedStreamData{
69+
Metadata: iv,
70+
Reader: newBlockModeEncryptingReader(reader, cipher.NewCBCEncrypter(c.block, iv)),
71+
}, nil
72+
}
73+
74+
func (c *aesCbcCryptor) DecryptStream(encryptedData *EncryptedStreamData) (io.Reader, error) {
75+
if encryptedData.Metadata == nil {
76+
return nil, errors.New("missing metadata")
77+
}
78+
return newBlockModeDecryptingReader(encryptedData.Reader, cipher.NewCBCDecrypter(c.block, encryptedData.Metadata)), nil
79+
}
80+
81+
func aesCipher(cipherKey string) (cipher.Block, error) {
82+
hash := sha256.New()
83+
hash.Write([]byte(cipherKey))
84+
85+
block, err := aes.NewCipher(hash.Sum(nil))
86+
if err != nil {
87+
return nil, err
88+
}
89+
return block, nil
90+
}

Diff for: crypto/aes_cbc_cryptor_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package crypto
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"testing"
7+
"testing/quick"
8+
)
9+
10+
var defaultPropertyTestConfig = &quick.Config{
11+
MaxCount: 1000,
12+
}
13+
14+
func canDecryptEncryptStreamResult(in []byte) bool {
15+
cryptor, e := NewAesCbcCryptor("enigma")
16+
if e != nil {
17+
return false
18+
}
19+
20+
output, err := cryptor.EncryptStream(bytes.NewReader(in))
21+
if err != nil {
22+
return false
23+
}
24+
25+
encrData, err := io.ReadAll(output.Reader)
26+
27+
decrypted, err := cryptor.Decrypt(&EncryptedData{
28+
Data: encrData,
29+
Metadata: output.Metadata,
30+
})
31+
if err != nil {
32+
return false
33+
}
34+
return bytes.Equal(in, decrypted)
35+
}
36+
37+
func canDecryptStreamEncryptResult(in []byte) bool {
38+
cryptor, e := NewAesCbcCryptor("enigma")
39+
if e != nil {
40+
return false
41+
}
42+
43+
output, err := cryptor.Encrypt(in)
44+
if err != nil {
45+
println(err.Error())
46+
return false
47+
}
48+
49+
decryptingReader, err := cryptor.DecryptStream(&EncryptedStreamData{
50+
Reader: bytes.NewReader(output.Data),
51+
Metadata: output.Metadata,
52+
})
53+
if err != nil {
54+
println(err.Error())
55+
return false
56+
}
57+
58+
decrypted, err := io.ReadAll(decryptingReader)
59+
if err != nil {
60+
println(err.Error())
61+
return false
62+
}
63+
64+
return bytes.Equal(in, decrypted)
65+
}
66+
67+
func Test_AesCBC_EncryptStream(t *testing.T) {
68+
if err := quick.Check(canDecryptEncryptStreamResult, defaultPropertyTestConfig); err != nil {
69+
t.Error(err)
70+
}
71+
}
72+
73+
func Test_AesCBC_DecryptStream(t *testing.T) {
74+
if err := quick.Check(canDecryptStreamEncryptResult, defaultPropertyTestConfig); err != nil {
75+
t.Error(err)
76+
}
77+
}

Diff for: crypto/cryptor.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package crypto
2+
3+
import "io"
4+
5+
type EncryptedData struct {
6+
Metadata []byte
7+
Data []byte
8+
}
9+
10+
type Cryptor interface {
11+
Id() string
12+
Encrypt(message []byte) (*EncryptedData, error)
13+
Decrypt(encryptedData *EncryptedData) ([]byte, error)
14+
}
15+
16+
type EncryptedStreamData struct {
17+
Metadata []byte
18+
Reader io.Reader
19+
}
20+
21+
type ExtendedCryptor interface {
22+
Cryptor
23+
EncryptStream(reader io.Reader) (*EncryptedStreamData, error)
24+
DecryptStream(encryptedData *EncryptedStreamData) (io.Reader, error)
25+
}

0 commit comments

Comments
 (0)