-
Notifications
You must be signed in to change notification settings - Fork 68
[PM-21862] Provide SDK with account keys #1873
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
base: main
Are you sure you want to change the base?
Conversation
…crypto initialization correctly.
Great job! No new security vulnerabilities introduced in this pull request |
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.
Nice work!
Some nits, and a question around the keychain. I don't think we need to store the signing key and the security state in the keychain. The security state is signed, by the signing key, and the signing key is encrypted by the userkey, and they are safe to store in the same way as the user private key, which - as far as I can tell - is not in the keychain.
typealias SignedPublicKey = String | ||
|
||
/// A public key in base64 encoded SPKI-DER. | ||
typealias UnsignedPublicKey = [UInt8] |
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.
Question: Should this be string? I'm not exactly sure how the conversion from response model to bytes works in swift.
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.
It should work but we could just try it when we have the server part deployed somewhere I can target and check the response. I also could try locally if it's urgent to know.
Additionally, if you have a serialized json response I could use, I can quickly check this in a test.
userId: account.profile.userId, | ||
kdfParams: account.kdf.sdkKdf, | ||
email: account.profile.email, | ||
privateKey: encryptionKeys.encryptedPrivateKey, |
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.
Nit (non-blocking): This should probably already use the accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey if available. These keys are the same, but I imagine the local state will in the future drop encryptionKeys.encryptedPrivateKey?
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.
It's already using that as when initializing the AccountEncryptionKeys
I set the encryptedPrivateKey
to the one in accountKeys
if present, and to privateKey
otherwise.
Would we want to maintain them separate for some reason or can I still reuse the same property for both private keys depending on the context?
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 think in the medium term we can drop encryptionKeys.encryptedPrivateKey
entirely, since it's the same as accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey
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 we can definitely drop in a future PR given that now we'd need to do a migration in order to move the stored encryptionKeys.encryptedPrivateKey
to accountKeys.publicKeyEncryptionKeyPair.wrappedPrivateKey
and we wouldn't gain much now.
@@ -131,6 +131,7 @@ extension DefaultKeyConnectorService: KeyConnectorService { | |||
|
|||
try await stateService.setAccountEncryptionKeys( | |||
AccountEncryptionKeys( | |||
accountKeys: nil, |
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.
Nit (not-blocking): I think we can already store privateKeyResponseModel here by constructing it from the existing private key. That would allow us to drop the encryptedPrivateKey property quicker.
@@ -8,6 +9,9 @@ enum KeychainItem: Equatable { | |||
/// The keychain item for a user's access token. | |||
case accessToken(userId: String) | |||
|
|||
/// The keychain item for a user's account security state. | |||
case accountSecurityState(userId: String) |
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.
AccountSecurityState and UserSigningKey don't need to be protected by the keychain. As far as I understand, we don't store the userPrivateKey here either. They should be stored the same way as userPrivateKey.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Changes look reasonable. My only question is around the type of public key in the state here. I'm not sure if this is intended.
"accountKeys": { | ||
"publicKeyEncryptionKeyPair": { | ||
"wrappedPrivateKey": "WRAPPED_PRIVATE_KEY", | ||
"publicKey": [1, 2, 3, 4, 5], |
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.
Question / note: This is a base64 encoded string on the server https://github.com/bitwarden/server/blob/8a39481fec87848e19cdd92d620d3e23b97901cb/src/Api/KeyManagement/Models/Requests/PublicKeyEncryptionKeyPairRequestModel.cs#L9. Is it intended to be an array of bytes 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.
Nice catch! You're right, I saw an object in the clients repo with that type and I mistakenly applied it here directly missing it was being converted from a string in the response. Fixed and removed the UnsignedPublicKey
typealias as it's no longer use.
…n UnsignedPublicKey and removed this typealias as it's not used in iOS.
/// The wrapped private key. | ||
let wrappedPrivateKey: WrappedPrivateKey | ||
|
||
/// The public key. | ||
let publicKey: String | ||
|
||
/// The signed public key. | ||
let signedPublicKey: SignedPublicKey? |
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 want to alphabetize these similar to other models?
let accountKeys = try? await stateService.getAccountEncryptionKeys() | ||
accountPrivateKeys = accountKeys?.accountKeys |
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 you think this would end up throwing in the expected use cases? Would it better to use try
instead of try?
here? If so, we could also move getting the encryption keys outside if the if
since we do that in both cases.
@@ -604,6 +604,7 @@ extension DefaultAuthRepository: AuthRepository { | |||
|
|||
try await stateService.setAccountEncryptionKeys( | |||
AccountEncryptionKeys( | |||
accountKeys: nil, |
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 possible to construct a PrivateKeysResponseModel
here with the keys we have or does the server need to do that? If I'm understanding correctly, I think this is similar to #1873 (comment).
🎟️ Tracking
PM-21862
PM-22864
📔 Objective
Implement passing AEAD (Authenticated Encryption with Associated Data) keys to the SDK crypto initialization and storing them.
Note
SDK Crypto Re-initialization from the sync profile response keys can't be done yet given we lack the crypto method necessary data to do so, therefore it's going to be done in a future PR when the SDK updates the API to allow a different way to update such keys.
⏰ 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 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