Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Used new CryptoModule from kotlin SDK. #280

Merged
merged 7 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ public void testHistorySingleChannel_IncludeAll_Crypto() throws PubNubException
final PubNub observer = getPubNub();
observer.getConfiguration().setCipherKey(expectedCipherKey);

assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey());
assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey()); //todo

final String expectedChannelName = random();
final int expectedMessageCount = 10;
Expand Down Expand Up @@ -348,7 +348,7 @@ public void testFetchSingleChannel_IncludeAll_Crypto() throws PubNubException {
final PubNub observer = getPubNub();
observer.getConfiguration().setCipherKey(expectedCipherKey);

assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey());
assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey()); //todo

final String expectedChannelName = random();
final int expectedMessageCount = 10;
Expand Down Expand Up @@ -379,7 +379,7 @@ public void testFetchSingleChannel_WithActions_IncludeAll_Crypto() throws PubNub
final PubNub observer = getPubNub();
observer.getConfiguration().setCipherKey(expectedCipherKey);

assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey());
assertEquals(pubNub.getConfiguration().getCipherKey(), observer.getConfiguration().getCipherKey()); //todo

final String expectedChannelName = random();
final int expectedMessageCount = 10;
Expand Down
39 changes: 34 additions & 5 deletions src/main/java/com/pubnub/api/PNConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.pubnub.api;


import com.pubnub.api.crypto.CryptoModule;
import com.pubnub.api.enums.PNHeartbeatNotificationOptions;
import com.pubnub.api.enums.PNLogVerbosity;
import com.pubnub.api.enums.PNReconnectionPolicy;
Expand Down Expand Up @@ -94,9 +95,40 @@ public class PNConfiguration {
*/
private String publishKey;
private String secretKey;
private String cipherKey;
private String authKey;


/**
* @deprecated Use {@link #cryptoModule} instead.
*/
@Deprecated
private String cipherKey;

/**
* @deprecated Use {@link #cryptoModule} instead.
*/
@Deprecated
private boolean useRandomInitializationVector;

/**
* CryptoModule is responsible for handling encryption and decryption.
* If set, all communications to and from PubNub will be encrypted.
*/
private CryptoModule cryptoModule;

public CryptoModule getCryptoModule() {
if (cryptoModule != null) {
return cryptoModule;
} else {
if (cipherKey != null && !cipherKey.isEmpty()) {
log.warning("cipherKey is deprecated. Use CryptoModule instead");
return CryptoModule.createLegacyCryptoModule(cipherKey, useRandomInitializationVector);
} else {
return null;
}
}
}

/**
* @deprecated Use {@link #getUserId()} instead.
*/
Expand All @@ -110,7 +142,7 @@ public void setUuid(@NotNull String uuid) {
this.uuid = uuid;
}

public UserId getUserId() {
public UserId getUserId() {
return new UserId(this.uuid);
}

Expand Down Expand Up @@ -210,9 +242,6 @@ public void setUserId(@NotNull UserId userId) {
private boolean dedupOnSubscribe;
@Setter
private Integer maximumMessagesCacheSize;
@Setter
private boolean useRandomInitializationVector;

@Setter
private int fileMessagePublishRetryLimit;

Expand Down
47 changes: 34 additions & 13 deletions src/main/java/com/pubnub/api/PubNub.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.pubnub.api.builder.SubscribeBuilder;
import com.pubnub.api.builder.UnsubscribeBuilder;
import com.pubnub.api.callbacks.SubscribeCallback;
import com.pubnub.api.crypto.CryptoModule;
import com.pubnub.api.crypto.CryptoModuleKt;
import com.pubnub.api.endpoints.DeleteMessages;
import com.pubnub.api.endpoints.FetchMessages;
import com.pubnub.api.endpoints.History;
Expand Down Expand Up @@ -68,8 +70,6 @@
import com.pubnub.api.managers.token_manager.TokenManager;
import com.pubnub.api.managers.token_manager.TokenParser;
import com.pubnub.api.models.consumer.access_manager.v3.PNToken;
import com.pubnub.api.vendor.Crypto;
import com.pubnub.api.vendor.FileEncryptionUtil;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -111,6 +111,10 @@ public class PubNub {

private final TokenManager tokenManager;

public CryptoModule getCryptoModule() {
return configuration.getCryptoModule();
}

public PubNub(@NotNull PNConfiguration initialConfig) {
this.configuration = initialConfig;
this.mapper = new MapperManager();
Expand Down Expand Up @@ -456,8 +460,7 @@ public String decrypt(String inputString) throws PubNubException {
if (inputString == null) {
throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build();
}

return decrypt(inputString, this.getConfiguration().getCipherKey());
return decrypt(inputString, null);
}

/**
Expand All @@ -473,16 +476,33 @@ public String decrypt(String inputString, String cipherKey) throws PubNubExcepti
if (inputString == null) {
throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build();
}
boolean dynamicIV = this.getConfiguration().isUseRandomInitializationVector();
return new Crypto(cipherKey, dynamicIV).decrypt(inputString);
CryptoModule cryptoModule = getCryptoModuleOrThrow(cipherKey);

return CryptoModuleKt.decryptString(cryptoModule, inputString);
}

private CryptoModule getCryptoModuleOrThrow(String cipherKey) throws PubNubException {
CryptoModule effectiveCryptoModule;
if (cipherKey != null) {
effectiveCryptoModule = CryptoModule.createLegacyCryptoModule(cipherKey, this.getConfiguration().isUseRandomInitializationVector());
} else {
CryptoModule cryptoModule = getCryptoModule();
if (cryptoModule != null) {
effectiveCryptoModule = cryptoModule;
} else {
throw PubNubException.builder().errormsg("Crypto module is not initialized").build();
}
}
return effectiveCryptoModule;
}

public InputStream decryptInputStream(InputStream inputStream) throws PubNubException {
return decryptInputStream(inputStream, this.getConfiguration().getCipherKey());
return decryptInputStream(inputStream, null);
}

public InputStream decryptInputStream(InputStream inputStream, String cipherKey) throws PubNubException {
return FileEncryptionUtil.decrypt(cipherKey, inputStream);
CryptoModule cryptoModule = getCryptoModuleOrThrow(cipherKey);
return cryptoModule.decryptStream(inputStream);
}

/**
Expand All @@ -497,7 +517,7 @@ public String encrypt(String inputString) throws PubNubException {
throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build();
}

return encrypt(inputString, this.getConfiguration().getCipherKey());
return encrypt(inputString, null);
}

/**
Expand All @@ -514,16 +534,17 @@ public String encrypt(String inputString, String cipherKey) throws PubNubExcepti
throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_ARGUMENTS).build();
}

boolean dynamicIV = this.getConfiguration().isUseRandomInitializationVector();
return new Crypto(cipherKey, dynamicIV).encrypt(inputString);
CryptoModule cryptoModule = getCryptoModuleOrThrow(cipherKey);
return CryptoModuleKt.encryptString(cryptoModule, inputString);
}

public InputStream encryptInputStream(InputStream inputStream) throws PubNubException {
return encryptInputStream(inputStream, this.getConfiguration().getCipherKey());
return encryptInputStream(inputStream, null);
}

public InputStream encryptInputStream(InputStream inputStream, String cipherKey) throws PubNubException {
return FileEncryptionUtil.encrypt(cipherKey, inputStream);
CryptoModule cryptoModule = getCryptoModuleOrThrow(cipherKey);
return cryptoModule.encryptStream(inputStream);
}

public int getTimestamp() {
Expand Down
140 changes: 140 additions & 0 deletions src/main/java/com/pubnub/api/crypto/CryptoModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.pubnub.api.crypto

import com.pubnub.api.crypto.cryptor.AesCbcCryptor
import com.pubnub.api.crypto.cryptor.Cryptor
import com.pubnub.api.crypto.cryptor.HeaderParser
import com.pubnub.api.crypto.cryptor.LEGACY_CRYPTOR_ID
import com.pubnub.api.crypto.cryptor.LegacyCryptor
import com.pubnub.api.crypto.cryptor.ParseResult
import com.pubnub.api.crypto.data.EncryptedData
import com.pubnub.api.crypto.data.EncryptedStreamData
import com.pubnub.api.crypto.exception.PubNubError
import com.pubnub.api.crypto.exception.PubNubException
import com.pubnub.api.vendor.Base64
import java.io.InputStream
import java.io.SequenceInputStream

class CryptoModule internal constructor(
internal val primaryCryptor: Cryptor,
internal val cryptorsForDecryptionOnly: List<Cryptor> = listOf(),
internal val headerParser: HeaderParser = HeaderParser()
) {

companion object {
@JvmStatic
fun createLegacyCryptoModule(cipherKey: String, randomIv: Boolean = true): CryptoModule {
return CryptoModule(
primaryCryptor = LegacyCryptor(cipherKey, randomIv),
cryptorsForDecryptionOnly = listOf(LegacyCryptor(cipherKey, randomIv), AesCbcCryptor(cipherKey))
)
}

@JvmStatic
fun createAesCbcCryptoModule(cipherKey: String, randomIv: Boolean = true): CryptoModule {
return CryptoModule(
primaryCryptor = AesCbcCryptor(cipherKey),
cryptorsForDecryptionOnly = listOf(AesCbcCryptor(cipherKey), LegacyCryptor(cipherKey, randomIv))
)
}

@JvmStatic
fun createNewCryptoModule(
defaultCryptor: Cryptor,
cryptorsForDecryptionOnly: List<Cryptor> = listOf()
): CryptoModule {
return CryptoModule(
primaryCryptor = defaultCryptor,
cryptorsForDecryptionOnly = listOf(defaultCryptor) + cryptorsForDecryptionOnly
)
}
}

fun encrypt(data: ByteArray): ByteArray {
val (metadata, encryptedData) = primaryCryptor.encrypt(data)

return if (primaryCryptor.id().contentEquals(LEGACY_CRYPTOR_ID)) {
encryptedData
} else {
val cryptorHeader = headerParser.createCryptorHeader(primaryCryptor.id(), metadata)
cryptorHeader + encryptedData
}
}

fun decrypt(encryptedData: ByteArray): ByteArray {
val parsedData: ParseResult<out ByteArray> = headerParser.parseDataWithHeader(encryptedData)
val decryptedData: ByteArray = when (parsedData) {
is ParseResult.NoHeader -> {
getDecryptedDataForLegacyCryptor(encryptedData)
}

is ParseResult.Success -> {
getDecryptedDataForCryptorWithHeader(parsedData)
}
}
return decryptedData
}

fun encryptStream(stream: InputStream): InputStream {
val (metadata, encryptedData) = primaryCryptor.encryptStream(stream)
return if (primaryCryptor.id().contentEquals(LEGACY_CRYPTOR_ID)) {
encryptedData
} else {
val cryptorHeader: ByteArray = headerParser.createCryptorHeader(primaryCryptor.id(), metadata)
SequenceInputStream(cryptorHeader.inputStream(), encryptedData)
}
}

fun decryptStream(encryptedData: InputStream): InputStream {
val bufferedInputStream = encryptedData.buffered()
return when (val parsedHeader = headerParser.parseDataWithHeader(bufferedInputStream)) {
ParseResult.NoHeader -> {
val decryptor = cryptorsForDecryptionOnly.first { it.id().contentEquals(LEGACY_CRYPTOR_ID) }
decryptor.decryptStream(EncryptedStreamData(stream = bufferedInputStream))
}

is ParseResult.Success -> {
val decryptor = cryptorsForDecryptionOnly.first {
it.id().contentEquals(parsedHeader.cryptoId)
}
decryptor.decryptStream(
EncryptedStreamData(
metadata = parsedHeader.cryptorData,
stream = parsedHeader.encryptedData
)
)
}
}
}

private fun getDecryptedDataForLegacyCryptor(encryptedData: ByteArray): ByteArray {
return getLegacyCryptor()?.decrypt(EncryptedData(data = encryptedData))
?: throw PubNubException("LegacyCryptor not available", PubNubError.UNKNOWN_CRYPTOR)
}

private fun getDecryptedDataForCryptorWithHeader(parsedHeader: ParseResult.Success<out ByteArray>): ByteArray {
val decryptedData: ByteArray
val cryptorId = parsedHeader.cryptoId
val cryptorData = parsedHeader.cryptorData
val pureEncryptedData = parsedHeader.encryptedData
val cryptor = getCryptorById(cryptorId)
decryptedData =
cryptor?.decrypt(EncryptedData(cryptorData, pureEncryptedData))
?: throw PubNubException(errorMessage = "No cryptor found", pubnubError = PubNubError.UNKNOWN_CRYPTOR)
return decryptedData
}

private fun getLegacyCryptor(): Cryptor? {
val idOfLegacyCryptor = ByteArray(4) { 0.toByte() }
return getCryptorById(idOfLegacyCryptor)
}

private fun getCryptorById(cryptorId: ByteArray): Cryptor? {
return cryptorsForDecryptionOnly.firstOrNull { it.id().contentEquals(cryptorId) }
}
}

internal fun CryptoModule.encryptString(inputString: String): String =
String(Base64.encode(encrypt(inputString.toByteArray()), Base64.NO_WRAP))

internal fun CryptoModule.decryptString(inputString: String): String =
decrypt(Base64.decode(inputString, Base64.NO_WRAP)).toString(Charsets.UTF_8)
Loading
Loading