-
Notifications
You must be signed in to change notification settings - Fork 15
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
CryptoModule #117
CryptoModule #117
Conversation
@@ -21,4 +21,15 @@ class CipherKey { | |||
factory CipherKey.fromList(List<int> key) { | |||
return CipherKey._(key); | |||
} | |||
|
|||
@override | |||
bool operator ==(dynamic other) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jguz-pubnub for comparison and to distinguish explicitly provided keyset/using keyset v/s default. Till the time we support cipherKey at configuration level.
|
||
AesCbcCryptor(this.cipherKey); | ||
@override | ||
List<int> decrypt(EncryptedData encryptedData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a nitpick from developer to developer, so you don't have to fix it 🙂 You can enable code formatting on save. I know it from Android Studio through:
Preferences
-> Languages & Frameworks
-> Flutter
-> Format code on save
I'm almost sure a similar option may also be available for Visual Studio you use. Moreover, the whole code structure doesn't wrap if you add some trailing commas at the end and save a source file. For example, something like this:
@override
List<int> decrypt(EncryptedData encryptedData) {
var encrypter = crypto.Encrypter(
crypto.AES(_getKey(), mode: crypto.AESMode.cbc),
);
return encrypter.decryptBytes(
crypto.Encrypted(Uint8List.fromList(encryptedData.data.toList())),
iv: crypto.IV(Uint8List.fromList(encryptedData.metadata!.toList())),
);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting it up. this will look elegant.
Thanks!
pubnub/lib/src/crypto/crypto.dart
Outdated
if (encrypted.metadata == null) return encrypted.data; | ||
|
||
var header = | ||
CryptorHeader.from(defaultCryptor.identifier, encrypted.metadata!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to access .metadata
using an exclamation mark here? The same goes for header!
below
EDIT: Regarding metadata, perhaps you could declare it as non-optional List<int>
in the EncryptedData
class. The empty list might indicate there's no metadata and you could avoid handling such an optional variable every time you want to access it. Let's leave it as it is now if this would require significant effort or may break your unit/contract tests
pubnub/lib/src/crypto/crypto.dart
Outdated
} else { | ||
return crypto.Key(Uint8List.fromList(cipherKey.data)); | ||
} | ||
factory CryptoModule.aescbcCryptoModule(CipherKey cipherKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: typo in method's name (could be camel-cased aesCbcCryptoModule
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah! naming!
pubnub/lib/src/crypto/crypto.dart
Outdated
/// Decrypts [input] based on the [key] and [configuration]. | ||
/// | ||
/// If [configuration] is `null`, then [CryptoModule.defaultConfiguration] is used. | ||
List<ICryptor> _getAllCryptor() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: _getAllCryptors
pubnub/lib/src/crypto/crypto.dart
Outdated
var iv = crypto.IV.fromUtf8('0123456789012345'); | ||
return encrypter.encrypt(input, iv: iv).base64; | ||
} | ||
var allCryptors = _getAllCryptor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps you could simplify instantiating local cryptor
variable in this way:
var cryptor = allCryptors.firstWhere(
(element) => element.identifier == (header?.identifier ?? ''),
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch
if (encryptedData.length >= 5) { | ||
version = encryptedData[4]; | ||
} else { | ||
throw CryptoException('decryption error'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you want to provide more detailed messages that clearly indicate what's missing? For example, missing sentinel bytes, version byte, etc. Let's leave it as it is if you think it brings no value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be helpful.
WIP: update along with tests in next commit.
|
||
/// Decrypts [input] with [key] based on [configuration]. | ||
/// | ||
/// If [configuration] is `null`, then [CryptoModule.defaultConfiguration] is used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like those documentation comments! 👏
* Added more information in exception string for reason fo failure. * naming convention fix for CryptoModule factory. * code formatting.
@@ -12,7 +12,7 @@ class CryptoException extends PubNubException { | |||
} | |||
|
|||
/// @nodoc | |||
abstract class ICryptoModule { | |||
abstract class ILegacyCryptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Android Studio shows me this class is never used. Is it true? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah! We do not need it. Earlier I was thinking to have a different interface for LegacyCryptor. But then I was able to figure out that we can unify the signature and have only one interface that's ICryptor
.
removing it.
|
||
var header = | ||
CryptorHeader.from(defaultCryptor.identifier, encrypted.metadata); | ||
var headerData = List<int>.filled(header!.length, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that header
is not null here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, This is encryption code. Before attempting CryptoHeader.from(...), Here is what we did:
var encrypted = defaultCryptor.encrypt(data);
if (encrypted.metadata.isEmpty) return encrypted.data;
Which means If the default cryptor is not giving metadata(that means legacy one) we return from there only.
If we haven't return that means It's not legacy cryptor and If it's not legacy cryptor then we must have header.[going forward we won't ever support cryptor which does not conforms our header requirement] So header! will never throw.
I think I know the root of confusion:
from(..) has this if (id == LEGACY_IDENTIFIER) return null;
This was there for testing purpose(If CryptoHeader.from() called with legacy id then it should work as expected), while consuming this method in encrypt method. We are sure that execution will reach to Header.from(...) only when we have non-legacy cryptor.
I think I can remove if (id == LEGACY_IDENTIFIER) return null;
condition. Because from method will be called only when we deal with non legacy cryptor.
var cryptor = _getCryptor(header); | ||
var headerLength = header != null ? header.length : 0; | ||
var metadata = headerLength > 0 | ||
? data.sublist((headerLength - header!.metadataLength), headerLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same, are you sure that header
is not null here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, Because we already performed check :
var headerLength = header != null ? header.length : 0;
we go with sublist only when we have headerLength>0 that
means header is not null.
@@ -104,10 +106,14 @@ class ChannelHistory { | |||
_cursor = result.endTimetoken; | |||
_messages.addAll(await Future.wait(result.messages.map((message) async { | |||
if (_keyset.cipherKey != null) { | |||
message['message'] = await _core.parser.decode(_core.crypto | |||
.decrypt(_keyset.cipherKey!, message['message'] as String)); | |||
message['message'] = _keyset.cipherKey == |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain to me what's the logic here? Perhaps the idea is the same for other changes below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibility 1: User has provided a custom cipherKey(or a keyset or one of the keyset from KeysetStore via using param) while calling history.
In this case _keyset(current one from param).cipherKey == _core.keysets.defaultKeyset.cipherKey
=> false
That means we need to use custom cipherKey - and so we perform encryptWithKey
Possibility 2: User hasn't provided key which means _keyset.cipherKey is already defaulted to defaultKeyset's cipherkey
In this case _keyset.cipherKey == _core.keysets.defaultKeyset.cipherKey
=> true
That means we can use our decrypt()
methods of cryptoModule because that cryptoModule is already initialised with defaultKeyset's cipherKey.
…ecryption, * refactoring to fix broken format issue with decrypted hostory messages, * files encryption/ decryption conditions refined
@pubnub-release-bot release dart as v4.3.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
🚀 Release successfully completed 🚀 |
fix: Improve security of crypto implementation.
Improved security of crypto implementation by adding enhanced AES-CBC cryptor.
feat: Add crypto module
Add crypto module that allows configure SDK to encrypt and decrypt messages.