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

Geoconfig and API settings models: AES CBC encryption read and write #35642

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

Jtang-1
Copy link
Contributor

@Jtang-1 Jtang-1 commented Jan 21, 2025

Product Description

N/A

Technical Summary

Continuation of the work initiated by this PR
Jira Ticket

Introduces a solution for these code scan alerts:
https://github.com/dimagi/commcare-hq/security/code-scanning/295
https://github.com/dimagi/commcare-hq/security/code-scanning/296

This PR updates GeoConfig's api_token, and ConnectionSettings's password, client_secret, and last_token_aes. The model continues supporting reads for both AES ECB and AES CBC encryption while writes only with CBC encryption.

Feature Flag

no FF

Safety Assurance

Safety story

locally tested. This change will not affect any existing data and existing data will be read the same way.

Automated test coverage

There exists tests that the encryption and decryption with CBC results in the expected plaintext.

QA Plan

no QA

Migrations

There is no migration in this PR. But there is a follow up PR that does a migration related to the changes in this PR.
Related migration

Rollback instructions

This PR will start writing data with AES CBC encryption while also adding support to read AES CBC encrypted data. If this PR is reverted, the data encrypted with EBC will fail to be decrypted. To rollback this PR, data written with AES CBC encryption will need to be reencrypted with EBC.

Labels & Review

  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@Jtang-1 Jtang-1 requested review from jingcheng16, a team, AddisonDunn, minhaminha, Robert-Costello and esoergel and removed request for a team and AddisonDunn January 21, 2025 07:26
@Jtang-1 Jtang-1 marked this pull request as ready for review January 21, 2025 07:26
@Jtang-1 Jtang-1 requested a review from kaapstorm as a code owner January 21, 2025 07:26
Copy link
Contributor

@jingcheng16 jingcheng16 left a comment

Choose a reason for hiding this comment

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

Looks good! Thank you!

try:
plaintext = b64_aes_decrypt(ciphertext)
except UnicodeDecodeError:
raise AesEcbDecryptionError("Failed to decrypt the AES-ECB-encrypted text.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, in what case we might fail to decrypt it?

Copy link
Contributor Author

@Jtang-1 Jtang-1 Jan 21, 2025

Choose a reason for hiding this comment

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

I'm not sure why. On staging (and only staging, not prod) ConnectionSettings contains encrypted passwords that after being decrypted, raises a UnicodeDecodeError when attempting to decode it (which happens within b4_aes_decrypt. But since it's already unusable (so probably hasn't been used), I'm raising this error so I can set the new value to '' during the migration.

Copy link
Contributor

Choose a reason for hiding this comment

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

Did you print those encrypted passwords, are they start with the ECB prefix? I'm worried something goes wrong when we encrypt the ConnectionSettings pwd.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the existing passwords that were failing do start with the aes prefix.

I'm worried something goes wrong when we encrypt the ConnectionSettings pwd.

I'm not following - what's the concern you have?

corehq/apps/geospatial/const.py Outdated Show resolved Hide resolved

new_ciphertext = b64_aes_cbc_encrypt(b64_aes_decrypt(ciphertext))
try:
plaintext = b64_aes_decrypt(ciphertext)
Copy link
Contributor

Choose a reason for hiding this comment

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

If there is no existing_prefix, then it means the text is not encrypted, so we should not decrypt it right? Can this try except block moved to the first if block, when text starts with existing_prefix?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there is no existing_prefix, then it means the text is not encrypted, so we should not decrypt it right?

No that's not the case. The expectation is that the encrypted_text passed to reencrypt_ecb_to_cbc_mode is encrypted. The existing_prefix is only to handle situations where the encrypted text is prefixed such that the prefix can be stripped.

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 we don't need existing_prefix at all? Shouldn't this function be able to take in any text, determine if and how is encrypted then do whatever needs to return a cbc encrypted string?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a case that the text is encrypted without any prefix?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a case that the text is encrypted without any prefix?

Yes, it depends on the model.

If we remove the existing_prefix, then we would need to handle stripping prefix on the higher level function that calls reencrypt_ecb_to_cbc_mode and pass only the encrypted text. Is that what you're suggesting?

return json.loads(plaintext)
if self.last_token_aes.startswith(f'${ALGO_AES_CBC}$'):
ciphertext = self.client_secret.split('$', 2)[2]
return b64_aes_cbc_decrypt(ciphertext)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the result of b64_aes_cbc_decrypt() be passed to json.loads() similar to what is done with the result of b64_aes_decrypt() below?

Is there a test that covers this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it does, thanks for catching that and also for reminding me to add tests.. it caught a couple other bugs as well. bc3d6a6

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.

3 participants