-
Notifications
You must be signed in to change notification settings - Fork 839
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
BITAU-98 Add EncryptionUtils helper functions to the bridge SDK #3888
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3888 +/- ##
=======================================
Coverage 88.65% 88.65%
=======================================
Files 414 414
Lines 33945 33945
Branches 5006 5006
=======================================
Hits 30095 30095
Misses 2091 2091
Partials 1759 1759 ☔ View full report in Codecov by Sentry. |
No New Or Fixed Issues Found |
dcbc59e
to
143920d
Compare
* [SymmetricEncryptionKeyData]. | ||
*/ | ||
private fun generateCipher(): Cipher = | ||
Cipher.getInstance("AES/CBC/PKCS5PADDING") |
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.
Should this return a result as well?
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.
Or maybe have toEncryptedSharedAccountData(...)
return Result
and wrap it all in a runCatching
block. I see a few other calls that could throw in there as well.
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.
@SaintPatrck that's totally the plan 👍
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.
All the publicly available functions here are now wrapped in Result
👍
d985e90
to
6d6dea9
Compare
@@ -10,7 +10,7 @@ import kotlinx.serialization.Serializable | |||
* @param totpUri A TOTP code URI to be added to the Bitwarden app. | |||
*/ | |||
@Serializable | |||
data class AddTotpLoginItemDataJson( | |||
internal data class AddTotpLoginItemDataJson( |
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 realized when working on this PR that these are just an implementation detail of the encryption.
I love being able to use internal
😎
@@ -0,0 +1,201 @@ | |||
package com.bitwarden.bridge.util |
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.
S/O to @brian-livefront for originally writing all this code in his POC. I'm standing on the shoulders of a giant!
bridge/build.gradle.kts
Outdated
@@ -1,7 +1,7 @@ | |||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget | |||
|
|||
// For more info on versioning, see the README. | |||
val version = "0.1.0" | |||
val version = "0.2.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.
Does there need to be an entry added to CHANGELOG
for this version change? Is artifact generation happening automatically or do we need to run assemble manually for each PR with a version change?
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.
Great callout here.
- Yep, a CHANGELOG update is required here. Thanks for the callout. This builds good SDK release habits! Added: a14b78c
- Currently its all manual. My plan is to have a separate PR that includes the updated
.aar
in/app
along with corresponding/app
changes.
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.
Do we need to update the version actually right now? Could we keep just using "snapshot" builds until we're ready to "release" 0.1.0? Maybe we should not bump the version until we know where we are releasing them.
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.
Brian and I talked about this offline, and we decided to leave the version at 0.1.0 for now and keep calling every build a SNAPSHOT
build.
Then once we are in a stable place, we can bump the version to 1.0.0 and update the CHANGELOG accordingly.
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.
* | ||
* @param symmetricEncryptionKeyData Symmetric key used for encryption. | ||
* | ||
* This is intended to be used by the main Bitwarden app during a [IBridgeService.syncAccounts] call. |
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 would think these lines might need to come before all the @param in these
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.
Ah yep, who knows why my brain used that order 😆
fun generateSecretKey(): Result<SecretKey> = runCatching { | ||
val keygen = KeyGenerator.getInstance("AES") | ||
keygen.init(256, SecureRandom()) | ||
return@runCatching keygen.generateKey() |
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.
Do we need these return calls here? I think we omit these for VaultSdkSourceImpl etc that also use runCatching
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 good question.
I added these in 100% for my own readability sake. Looks like other usages of runCatching
don't use them. Removed:
|
||
class EncryptionUtilTest { | ||
|
||
private val TEST_SHARED_ACCOUNT_DATA = SharedAccountData( |
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.
Do we typically append TEST to the name of sample data instances? Also, I think we tend to place these at the bottom of a test file.
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 swear I saw TEST_
prefix elsewhere, but I must be seeing ghosts. Agreed on file location too. Updated:
This also explains why the IDE was complaining to me about the name formatting of these... it is no longer complaining!
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 see the change in the commit you posted but in the full diff this has not changed....looks like that commit is not actually included in the branch anymore.
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.
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 I messed something up rebasing this morning for sure... here's the missing commit:
} | ||
|
||
@Test | ||
fun `when KeyGenerator getInstance throws, generateSecretKey should return failure`() { |
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 believe all of these test names should first start with the name of the function being tested.
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.
True! Most were already correct, updated a few.
} | ||
|
||
@Test | ||
fun `toFingerprint should return success`() { |
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.
Seems like there should be a "when" here, like "when encryption succeeds" or something
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.
Sure thing!
mockkStatic(KeyGenerator::class) | ||
every { KeyGenerator.getInstance("AES") } throws NoSuchAlgorithmException() | ||
val secretKey = generateSecretKey() | ||
assert(secretKey.isFailure) |
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.
Can you use the assertTrue
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.
🫡
4a8f906
to
d3191a8
Compare
/** | ||
* Encrypt [SharedAccountData]. | ||
* | ||
* This is intended to be used by the main Bitwarden app during a [IBridgeService.syncAccounts] call. |
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'm a bit torn...do we need to refer to Bitwarden app
and Bitwarden Authenticator app
or would referring to the IBridgeServiceCallback
be enough? This is probably especially relevant to think through because once we add the "coroutines wrapper layer" it will actually be the SDK code that calls these and not the Authenticator app directly.
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 like leaving this breadcrumb to try and help out any future reader/editor with more context.
Re: the coroutines wrapping layer... I was imagining that layer only helping out with the Authenticator side of things (connecting to the service, registering callbacks). Here's the coroutines wrapper ticket, nothing there about helping out with the PMA side of things. To me, this makes sense, because in order to perform syncAccounts
, we need the entire data layer of the main Bitwarden app, so it makes sense this logic would be in the BItwarden app not the SDK.
Maybe I'm missing something though.
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.
We spoke offline. We'll leave references to "the Bitwarden app" but remove those to "the Bitwarden Authenticator app", mainly since we're thinking we can make the decrypt
call internal
.
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.
Thanks, Brian!
@@ -13,7 +13,7 @@ import java.time.Instant | |||
* @param accounts The list of shared accounts. | |||
*/ | |||
@Serializable | |||
data class SharedAccountDataJson( | |||
internal class SharedAccountDataJson( |
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.
Can this be an internal data class?
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.
Totally :) Nice catch:
* This is intended to be used for implementing [IBridgeService.getSymmetricEncryptionKeyData]. | ||
*/ | ||
fun generateSecretKey(): Result<SecretKey> = runCatching { | ||
val keygen = KeyGenerator.getInstance("AES") |
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.
Can we look into using KeyProperties.KEY_ALGORITHM_AES
etc. for these? When creating the Cipher we can probably use something like this from the biometrics code:
private const val CIPHER_TRANSFORMATION =
KeyProperties.KEY_ALGORITHM_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" +
KeyProperties.ENCRYPTION_PADDING_PKCS7
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 I was able to remove a few of the raw strings here.
I still hit a sticky wicket with KeyProperties.ENCRYPTION_PADDING_PKCS7
blowing up test because that algo is not available in our test environment.
I failed to mock out that constant. To my knowledge and reading the mockk docs, I don't think mockk supports mocking a static constant. It can mock top level constants and extension functions, but I was't able to make it work for this static class constant. I think the underlying reason is that static java constants don't have a getter.
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.
OK sounds good. Thanks for that context!
- rename some tests
976b19c
to
f14779e
Compare
/** | ||
* Encrypt [AddTotpLoginItemData]. | ||
* | ||
* This is intended to be used by the Bitwarden Authenticator app before requesting a new TOTP |
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.
Still a reference to Bitwarden Authenticator app
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.
👍 missed this encrypt
, good call:
class EncryptionUtilTest { | ||
|
||
@Test | ||
fun `generateSecretKey should return success when there are no internal exceptions`() { |
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.
👍
…twarden authenticator app
Thanks, @brian-livefront, @SaintPatrck, and @david-livefront ! |
🎟️ Tracking
https://livefront.atlassian.net/browse/BITAU-98
📔 Objective
The goal of this PR is to set up basic encryption/decryption logic in the Bridge SDK. This will be called by the PMA in future tickets when generating symmetric key, checking symmetric key, and ultimately implementing
BridgeService
.📸 Screenshots
⏰ Reminders before review
🦮 Reviewer guidelines
:+1:
) or similar for great changes:memo:
) or ℹ️ (:information_source:
) for notes or general info:question:
) for questions:thinking:
) or 💭 (:thought_balloon:
) for more open inquiry that's not quite a confirmedissue and could potentially benefit from discussion
:art:
) for suggestions / improvements:x:
) or:warning:
) for more significant problems or concerns needing attention:seedling:
) or ♻️ (:recycle:
) for future improvements or indications of technical debt:pick:
) for minor or nitpick changes