Skip to content

Conversation

@eerhardt
Copy link
Member

@eerhardt eerhardt commented Jun 17, 2022

This ports the C# implementation of AES from https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/security/cryptography/rijndaelmanagedtransform.cs, marks the APIs as supported on Browser, and enables AES tests on Browser WASM.

Contributes to #40074

The AES SubtleCrypto support will come in a separate PR.

It is easiest to review the commits separately. I copied the reference source, get it formatted and compiling. Then enable new functionality and refactor in separate commits to make it easier to review.

@ghost
Copy link

ghost commented Jun 17, 2022

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented Jun 17, 2022

Tagging subscribers to this area: @dotnet/area-system-security, @vcsjones
See info in area-owners.md if you want to be subscribed.

Issue Details

This ports the C# implementation of AES from https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/security/cryptography/rijndaelmanagedtransform.cs, marks the APIs as supported on Browser, and enables AES tests on Browser WASM.

Contributes to #40074

The AES SubtleCrypto support will come in a separate PR.

(While in draft, skip the first two commits - those are from a separate PR. Will be rebased out once that PR is merged.)

It is easiest to review the first 4 commits separately. They copy the reference source, get it formatted and compiling.

Author: eerhardt
Assignees: eerhardt
Labels:

area-System.Security, new-api-needs-documentation

Milestone: -

@eerhardt eerhardt force-pushed the AddAes branch 3 times, most recently from 800ecd5 to 74ddd51 Compare June 21, 2022 23:05
@vcsjones
Copy link
Member

remember that last block of output data from the previous block, and then use it as the IV for the next block. SubtleCrypto let’s us specify the key, IV, and data.

Okay. That makes sense.

@eerhardt
Copy link
Member Author

eerhardt commented Jun 24, 2022

remember that last block of output data from the previous block, and then use it as the IV for the next block. SubtleCrypto let’s us specify the key, IV, and data.

I just tested this out quickly, and it appears to work (on the encryption side). Here's roughly what it would look like to append 2 full blocks together:

function getMessage() {
  const message = "a".repeat(32);
  const enc = new TextEncoder();
  return enc.encode(message);
}

/*
Encrypt the plainText twice in two separate blocks.
*/
async function encryptMessage(key: CryptoKey) {
  const plainText = getMessage();
  iv = window.crypto.getRandomValues(new Uint8Array(16));

  ciphertext = await window.crypto.subtle.encrypt(
    {
      name: "AES-CBC",
      iv
    },
    key,
    plainText
  );

  // since the message is a whole block and we are using PKCS7, we are guaranteed the whole last block is all padding (all values are `0x10`)
  const ciphertextNoPadding = new Uint8Array(ciphertext).slice(0, ciphertext.byteLength - 16);

  // store the last block of the encrypted message as the IV for the next block
  const nextIv = ciphertextNoPadding.subarray(ciphertextNoPadding.length - 16, ciphertextNoPadding.length);

  // encrypt the same message again
  const ciphertext2 = await window.crypto.subtle.encrypt(
    {
      name: "AES-CBC",
      iv: nextIv
    },
    key,
    plainText
  );

  ciphertext = new ArrayBuffer(ciphertextNoPadding.byteLength + ciphertext2.byteLength);
  const ciphertextBytes = new Uint8Array(ciphertext);

  ciphertextBytes.set(ciphertextNoPadding);
  ciphertextBytes.set(new Uint8Array(ciphertext2), ciphertextNoPadding.byteLength);
}

However, I have no idea how to make this work on the "decrypt" side. Going that way, I think we will have to buffer, return 0 for each intermediate block, and then return the whole thing in TransformFinal.

Update: I think I just thought of a way and am testing it now. When decrypting, we have the "last block" of cipher text that would have been used to encrypt the padding block. We can use that "last block" as the IV, and we already have the key. So all we need to do is encrypt a block of 0x10 bytes (the padding) and append that to the cipher text blocks, now we should have a full message we can decrypt, with valid PKCS7 padding.

Update2: I got it working as described above, here is the rough code:

async function decryptMessage(key: CryptoKey) {
  const ciphertextBytes = new Uint8Array(ciphertext);

  const firstBlock = ciphertextBytes.slice(0, 16);
  const secondBlock = ciphertextBytes.slice(16, ciphertextBytes.length);

  const fullPaddingBlock = new Uint8Array([0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,]);
  const fullPaddingBlockEncrypted: ArrayBuffer = await window.crypto.subtle.encrypt(
    {
      name: "AES-CBC",
      iv: firstBlock
    },
    key,
    fullPaddingBlock
  );

  const firstBlockWithPadding = new Uint8Array(firstBlock.length + 16);
  firstBlockWithPadding.set(firstBlock);
  firstBlockWithPadding.set(new Uint8Array(fullPaddingBlockEncrypted).slice(0, 16), firstBlock.length);

  const decrypted1: ArrayBuffer = await window.crypto.subtle.decrypt(
    {
      name: "AES-CBC",
      iv
    },
    key,
    firstBlockWithPadding
  );

  const decrypted2: ArrayBuffer = await window.crypto.subtle.decrypt(
    {
      name: "AES-CBC",
      iv: firstBlock
    },
    key,
    secondBlock
  );

  const decrypted = new Uint8Array(decrypted1.byteLength + decrypted2.byteLength);
  decrypted.set(new Uint8Array(decrypted1));
  decrypted.set(new Uint8Array(decrypted2), decrypted1.byteLength);
}

eerhardt added 4 commits June 24, 2022 15:55
Encrypt and Decrypt index arrays are constant values.
Use CopyTo in more places.
Instead, reuse the UniversalCrypto infrastructure to do padding.
@eerhardt
Copy link
Member Author

After chatting with @bartonjs, he instructed me on how to handle PaddingMode.None on top of SubtleCrypto. This means we can reuse all the "UniversalCrypto" infrastructure, and do all the padding in managed code (like we do for all the other backends).

With this knowledge, I can completely simplify the managed implementation by cutting out the "depad buffer" and the padding logic.

I believe this PR is ready for review. Once this goes in, I can send a new PR for the SubtleCrypto side of things.

- clean up unnecessary changes
Remove UnsupportedOSPlatform("browser") from CipherMode and the SymmetricAlgorithm base class. These create too many false positives and aren't technically accurate since other implementations may support these on browser.
@eerhardt eerhardt merged commit 6a02d5d into dotnet:main Jun 30, 2022
@eerhardt eerhardt deleted the AddAes branch June 30, 2022 00:52
@ghost ghost locked as resolved and limited conversation to collaborators Jul 30, 2022
@bartonjs bartonjs added the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Aug 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Security needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants