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: AES CBC encryption Migration #35641

Merged
merged 16 commits into from
Jan 24, 2025

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 migrates GeoConfig's api_token, and ConnectionSettings's password, client_secret, and last_token_aes. For values that are AES ECB encrypted, this migration reencrypts it with AES CBC. For values that are not encrypted, this migration encrypts it with AES CBC. In both cases, the prefix "$aes-cbc$" is prepended to the encrypted value and saved.

Feature Flag

no FF

Safety Assurance

Safety story

locally tested and will test on staging.

Automated test coverage

There exists tests that reencrypt_ecb_to_cbc_mode decrypts to the same plaintext value.

QA Plan

no QA

Migrations

  • The migrations in this code can be safely applied first independently of the code
    The migration in this PR is related to this past PR and should not be merged until that PR is deployed.

Rollback instructions

  • This PR can be reverted after deploy with no further considerations

Labels & Review

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

Values that can't be decrypted are already  broken so there is no use in continuing to store it.
This will "clear" those values be overriding it with an empty string during migration.
@dimagimon dimagimon added the reindex/migration Reindex or migration will be required during or before deploy label Jan 21, 2025
@Jtang-1 Jtang-1 marked this pull request as ready for review January 21, 2025 07:24
@Jtang-1 Jtang-1 requested a review from kaapstorm as a code owner January 21, 2025 07:24
@Jtang-1 Jtang-1 requested review from jingcheng16, esoergel, a team, AddisonDunn, minhaminha and Robert-Costello and removed request for esoergel, a team, AddisonDunn, minhaminha and Robert-Costello January 21, 2025 07:24
@Jtang-1
Copy link
Contributor Author

Jtang-1 commented Jan 21, 2025

This PR is dependent on #35642 and should not be merged until after that PR is deployed.

Comment on lines 33 to 60
def reencrypted_password_with_cbc(connection):
if connection.password == '':
return ''
elif connection.password.startswith(f'${ALGO_AES}$'):
return reencrypt_ecb_to_cbc_mode(connection.password, f'${ALGO_AES}$')
else:
ciphertext = b64_aes_cbc_encrypt(connection.password)
return f'${ALGO_AES_CBC}${ciphertext}'


def reencrypted_client_secret_with_cbc(connection):
if connection.client_secret == '':
return ''
elif connection.client_secret.startswith(f'${ALGO_AES}$'):
return reencrypt_ecb_to_cbc_mode(connection.client_secret, f'${ALGO_AES}$')
else:
ciphertext = b64_aes_cbc_encrypt(connection.client_secret)
return f'${ALGO_AES_CBC}${ciphertext}'


def reencrypted_last_token_with_cbc(connection):
if connection.last_token_aes == '':
return ''
elif connection.last_token_aes.startswith(f'${ALGO_AES}$'):
prefix = f'${ALGO_AES}$'
else:
prefix = None
return reencrypt_ecb_to_cbc_mode(connection.last_token_aes, prefix)
Copy link
Contributor

Choose a reason for hiding this comment

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

This three functions can be one function, the logic is the same, and you can pass connection.password, connection.client_secret, connection.last_token_aes to it.

Copy link
Contributor

Choose a reason for hiding this comment

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

This can be further simplified after you bring some logic to reencrypt_ecb_to_cbc_mode in #35642

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I combined the function for password and client_secret here 903d12f. But last_token_aes needs to be handled differently. The difference being that the stored value is always encrypted but may or may not have the aes-cbc prefix. For password and client_secret, if the value does not contain the prefix, then it is not encrypted. To keep these two lines of logic cleanly separated, I would prefer to keep the functions separate.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the change. What do you think of combining the logic, and have a new parameter called reencrypt_only, reencrypt_only is True for last_token_aes.

Copy link
Contributor Author

@Jtang-1 Jtang-1 Jan 23, 2025

Choose a reason for hiding this comment

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

hmm, like this? I have mixed feelings about it, but don't have a strong preference. I can make this change

def _reencrypt_or_encrypt_value_with_cbc(value, reencrypt_only=False):
    if value == '':
        return ''
    if value.startswith(f'${ALGO_AES_CBC}$'):
        return value

    try:
        if value.startswith(f'${ALGO_AES}$'):
            return reencrypt_ecb_to_cbc_mode(value, f'${ALGO_AES}$')
        else:
            if reencrypt_only:
                return reencrypt_ecb_to_cbc_mode(value)
            else:
                ciphertext = b64_aes_cbc_encrypt(value)
                return f'${ALGO_AES_CBC}${ciphertext}'
    except AesEcbDecryptionError:
        return ''

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 43 to 45
else:
ciphertext = b64_aes_cbc_encrypt(connection.password)
return f'${ALGO_AES_CBC}${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 these _reencrypted_... functions also check for .startswith(f'${ALGO_AES_CBC}$') and skip re-encrypting the value if that returns true?

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 should. Added this commit to check if the value already contains a aes-cbc prefix and just returns the value if it does. ee61d0f

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 to me

@Jtang-1 Jtang-1 merged commit 15b438e into master Jan 24, 2025
14 checks passed
@Jtang-1 Jtang-1 deleted the jt/cbc-password-migration branch January 24, 2025 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
reindex/migration Reindex or migration will be required during or before deploy
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants