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

BITAU-98 Add EncryptionUtils helper functions to the bridge SDK #3888

Merged
merged 16 commits into from
Sep 12, 2024

Conversation

ahaisting-livefront
Copy link
Collaborator

🎟️ 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

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation or informed the documentation team

🦮 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 confirmed
    issue 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

Copy link

codecov bot commented Sep 9, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.65%. Comparing base (a5726fb) to head (b33e6d1).
Report is 7 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

github-actions bot commented Sep 9, 2024

Logo
Checkmarx One – Scan Summary & Details100fb0d3-d497-4e7f-be72-c643b103d798

No New Or Fixed Issues Found

@ahaisting-livefront ahaisting-livefront changed the base branch from main to bridge-test-dependencies September 9, 2024 21:47
* [SymmetricEncryptionKeyData].
*/
private fun generateCipher(): Cipher =
Cipher.getInstance("AES/CBC/PKCS5PADDING")
Copy link
Collaborator

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?

Copy link
Contributor

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.

Copy link
Collaborator Author

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 👍

Copy link
Collaborator Author

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 👍

Base automatically changed from bridge-test-dependencies to main September 10, 2024 15:11
@ahaisting-livefront ahaisting-livefront force-pushed the encryption-utils branch 2 times, most recently from d985e90 to 6d6dea9 Compare September 10, 2024 20:05
@@ -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(
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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!

@@ -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"
Copy link
Contributor

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great callout here.

  1. Yep, a CHANGELOG update is required here. Thanks for the callout. This builds good SDK release habits! Added: a14b78c
  2. Currently its all manual. My plan is to have a separate PR that includes the updated .aar in/app along with corresponding /app changes.

Copy link
Collaborator

@brian-livefront brian-livefront Sep 10, 2024

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SaintPatrck
SaintPatrck previously approved these changes Sep 10, 2024
*
* @param symmetricEncryptionKeyData Symmetric key used for encryption.
*
* This is intended to be used by the main Bitwarden app during a [IBridgeService.syncAccounts] call.
Copy link
Collaborator

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

Copy link
Collaborator Author

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 😆

4fd92d7

fun generateSecretKey(): Result<SecretKey> = runCatching {
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256, SecureRandom())
return@runCatching keygen.generateKey()
Copy link
Collaborator

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

Copy link
Collaborator Author

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:

4a8f906


class EncryptionUtilTest {

private val TEST_SHARED_ACCOUNT_DATA = SharedAccountData(
Copy link
Collaborator

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.

Copy link
Collaborator Author

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:

0d465a8

This also explains why the IDE was complaining to me about the name formatting of these... it is no longer complaining!

Copy link
Collaborator

@brian-livefront brian-livefront Sep 11, 2024

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must have made an error while rebasing... thank you for catching this!!

Here's an update:

af046f9

Copy link
Collaborator Author

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:

af046f9

}

@Test
fun `when KeyGenerator getInstance throws, generateSecretKey should return failure`() {
Copy link
Collaborator

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.

Copy link
Collaborator Author

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`() {
Copy link
Collaborator

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

Copy link
Collaborator Author

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)
Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫡

/**
* Encrypt [SharedAccountData].
*
* This is intended to be used by the main Bitwarden app during a [IBridgeService.syncAccounts] call.
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Brian!

147c632

@@ -13,7 +13,7 @@ import java.time.Instant
* @param accounts The list of shared accounts.
*/
@Serializable
data class SharedAccountDataJson(
internal class SharedAccountDataJson(
Copy link
Collaborator

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally :) Nice catch:

5643141

* This is intended to be used for implementing [IBridgeService.getSymmetricEncryptionKeyData].
*/
fun generateSecretKey(): Result<SecretKey> = runCatching {
val keygen = KeyGenerator.getInstance("AES")
Copy link
Collaborator

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

Copy link
Collaborator Author

@ahaisting-livefront ahaisting-livefront Sep 12, 2024

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.

Copy link
Collaborator

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!

/**
* Encrypt [AddTotpLoginItemData].
*
* This is intended to be used by the Bitwarden Authenticator app before requesting a new TOTP
Copy link
Collaborator

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.

Copy link
Collaborator Author

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:

b33e6d1

class EncryptionUtilTest {

@Test
fun `generateSecretKey should return success when there are no internal exceptions`() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@ahaisting-livefront
Copy link
Collaborator Author

Thanks, @brian-livefront, @SaintPatrck, and @david-livefront !

@ahaisting-livefront ahaisting-livefront merged commit d1f21d3 into main Sep 12, 2024
9 checks passed
@ahaisting-livefront ahaisting-livefront deleted the encryption-utils branch September 12, 2024 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants