Skip to content

feat(#448): support encrypting submission payload#766

Draft
garethbowen wants to merge 16 commits intomainfrom
448-submission-encryption
Draft

feat(#448): support encrypting submission payload#766
garethbowen wants to merge 16 commits intomainfrom
448-submission-encryption

Conversation

@garethbowen
Copy link
Copy Markdown
Collaborator

Closes #448

I have verified this PR works in these browsers (latest versions):

  • Chrome
  • Firefox
  • Safari (macOS)
  • Safari (iOS)
  • Chrome for Android
  • Not applicable

What else has been done to verify that this works as intended?

Minimal testing. I'd really like to add an e2e test one day that actually verifies that the submission can be decrypted, but that requires actually starting up backend. If we end up in the same repo as frontend then there already is a framework to do this so postponing it until we figure out where we're going to go!

Why is this the best possible solution? Were any other approaches considered?

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

Do we need any specific form for testing your changes? If so, please attach one.

No specific form but it only works in central (not demo) and only if you set up encryption as laid out in the test plan.

What's changed

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 2, 2026

🦋 Changeset detected

Latest commit: 0b4a7c1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@getodk/xforms-engine Minor
@getodk/scenario Minor
@getodk/common Minor
@getodk/web-forms Minor
@getodk/xpath Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

// );
// el3.textContent = this.signature;
// manifest.appendChild(el3);
// }
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This part is still waiting on a decision. I suspect it's going to get removed completely because it's a) optional, and b) not implemented in central.

@garethbowen garethbowen marked this pull request as draft April 2, 2026 04:54
@garethbowen
Copy link
Copy Markdown
Collaborator Author

@latin-panda This is ready for review. I'm still waiting on a couple of answers and it'll need a fair amount of QA testing, but as I'm AFK for the next week I thought I'd send it over for review in the meantime. Thanks!

@garethbowen garethbowen requested a review from latin-panda April 2, 2026 04:54
Copy link
Copy Markdown
Collaborator

@latin-panda latin-panda left a comment

Choose a reason for hiding this comment

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

Really interesting! I left some comments below


## Implementation

The symmetric encryption parts of the spec are implemnted using CryptoJS because the particularly algorithm required by the spec is not supported by Subtle Crypto, and we use CryptoJS elsewhere.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The symmetric encryption parts of the spec are implemnted using CryptoJS because the particularly algorithm required by the spec is not supported by Subtle Crypto, and we use CryptoJS elsewhere.
The symmetric encryption parts of the spec are implemented using CryptoJS because the particular algorithm required by the spec is not supported by Subtle Crypto, and we use CryptoJS elsewhere.

symmetricKey: Uint8Array<ArrayBuffer>,
seed: Seed
): Promise<File> => {
const content = await getBlobData(attachment);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm curious about the amount of memory WF is allocating to submissions overall, including attachments and raw data, since it loads the submission into memory again. It'd be good to check how large forms perform on low-end devices, as part of the test plan.

const bytes = [];
for (let i = 0; i < buffer.length; i += 4) {
bytes.push(
(buffer[i]! << 24) | (buffer[i + 1]! << 16) | (buffer[i + 2]! << 8) | buffer[i + 3]!
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This increases by 4, and it will go out of bounds. However, the bitwise operation treats undefined as 0. It would be better to still check that buffer[i], buffer[i + 1], etc., are defined to make it more robust.

readonly instanceId: string,
readonly symmetricKey: Uint8Array<ArrayBuffer>
) {
const key = CryptoJS.lib.WordArray.create(symmetricKey);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to use this function here too?

Suggested change
const key = CryptoJS.lib.WordArray.create(symmetricKey);
const key = arrayBufferToWordArray(symmetricKey);

Comment on lines +63 to +69
const el = document.createElementNS(ODK_SUBMISSIONS_NAMESPACE_URI, 'base64EncryptedKey');
el.textContent = this.base64EncryptedKey;
manifest.appendChild(el);

const el2 = document.createElementNS(ODK_SUBMISSIONS_NAMESPACE_URI, 'encryptedXmlFile');
el2.textContent = ENCRYPTED_SUBMISSION_ATTACHMENT_NAME;
manifest.appendChild(el2);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const el = document.createElementNS(ODK_SUBMISSIONS_NAMESPACE_URI, 'base64EncryptedKey');
el.textContent = this.base64EncryptedKey;
manifest.appendChild(el);
const el2 = document.createElementNS(ODK_SUBMISSIONS_NAMESPACE_URI, 'encryptedXmlFile');
el2.textContent = ENCRYPTED_SUBMISSION_ATTACHMENT_NAME;
manifest.appendChild(el2);
const keyEl = document.createElementNS(ODK_SUBMISSIONS_NAMESPACE_URI, 'base64EncryptedKey');
el.textContent = this.base64EncryptedKey;
manifest.appendChild(el);
const xmlFileEl = document.createElementNS(ODK_SUBMISSIONS_NAMESPACE_URI, 'encryptedXmlFile');
el2.textContent = ENCRYPTED_SUBMISSION_ATTACHMENT_NAME;
manifest.appendChild(el2);

import {
ODK_SUBMISSIONS_NAMESPACE_URI,
OPENROSA_XFORMS_NAMESPACE_URI,
} from '@getodk/common/constants/xmlns.ts';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How about moving this to /instance-state? It seems like this file is handling a lot of DOM element work, and currently parse/model isn't importing from client-reactivity. What do you think?


class Seed {
readonly ivSeedArray;
counter = 0;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think it can be private

Suggested change
counter = 0;
private counter = 0;

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.

Add submission data encryption and display encryption status

2 participants