Search code examples
javascriptwebcrypto-apiunwrap

JavaScript WebCryptoApi: Why does this not satisfy operation specific requirements?


Setup:

  • I generate a key-encryption-key (kek, AES-GCM, 256)
  • I generate a RSA-OAEP keypair
  • I wrap the generated private key with the kek using AES-GCM-256 and a random iv, exported as pkcs8
  • I try to unwrap the key, this fails at the "import" stage

The error message shown in the console is "DOMException: Data provided to an operation does not meet requirements", I have managed to create a MWE as follows:

(async() => {
  let exportFormat = "pkcs8";


  const keyWrappingAlgorithm = {
      name: "AES-GCM",
      length: 256,
  }
  let keyPairAlgorithm = {
      name: "RSA-OAEP",
      modulusLength: 4096,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: "SHA-256",
  };


  let kek = await window.crypto.subtle.generateKey(
      keyWrappingAlgorithm, 
      true, 
      ["decrypt", "wrapKey", "unwrapKey"]
  );

  let keyPair = await crypto.subtle.generateKey(
      keyPairAlgorithm, 
      true, 
      ["encrypt", "decrypt"]
  )



  const iv = crypto.getRandomValues(new Uint8Array(96/8));



  let wrappedPrivateKey = await crypto.subtle.wrapKey(
      exportFormat,
      keyPair.privateKey,
      kek,
      {
          ...keyWrappingAlgorithm,
          iv: iv,
      }
  )

  let decryptedData = await crypto.subtle.decrypt(
      {
          ...keyWrappingAlgorithm,
          iv: iv,
      },
      kek,
      wrappedPrivateKey,
  )
  console.log("done 1", decryptedData)
  await crypto.subtle.importKey(
      exportFormat,
      decryptedData,
      keyPairAlgorithm,
      true,
      ["encrypt", "decrypt"]
  ).then(() => {
      console.log("Success 1! This was not expected.")
  }).catch((error) => console.log("error 1", error))

  let unwrappedKey = await crypto.subtle.unwrapKey(
      exportFormat,
      wrappedPrivateKey,
      kek,
      {
          ...keyWrappingAlgorithm,
          iv: iv,
      },
      keyPairAlgorithm,
      true,
      ["encrypt", "decrypt"]
  ).then(() => {
      console.log("Success 2! This was not expected.")
  }).catch((error) => console.log("error 2", error))
})()

What is going on here? How can I correct this behavior and unwrap the wrapped key?

I've been sitting at this for at least 2 hours by now to extract the MWE, but the error message doesn't give much information. I tried changing the export format, but that didn't really help. The keys all are exportable, so this shouldn't be an issue either. I also think that I added all the required key usages..


Solution

  • For RSA-OAEP a private key can only be used for decryption (encryption is done with the public key), i.e. for keyUsages ["decrypt"] has to be used and not ["encrypt", "decrypt"]. The error message ...Cannot create a key using the specified key usages... points this out.
    If you apply ["decrypt"] as keyUsages in importKey() and unwrapKey() the importing and unwrapping works:

    (async() => {
      let exportFormat = "pkcs8";
      const keyWrappingAlgorithm = {
          name: "AES-GCM",
          length: 256,
      }
      let keyPairAlgorithm = {
          name: "RSA-OAEP",
          modulusLength: 4096,
          publicExponent: new Uint8Array([1, 0, 1]),
          hash: "SHA-256",
      };
    
      let kek = await window.crypto.subtle.generateKey(
          keyWrappingAlgorithm, 
          true, 
          ["decrypt", "wrapKey", "unwrapKey"]
      );
      let keyPair = await crypto.subtle.generateKey(
          keyPairAlgorithm, 
          true, 
          ["encrypt", "decrypt"]
      )
    
      const iv = crypto.getRandomValues(new Uint8Array(96/8));
    
      let wrappedPrivateKey = await crypto.subtle.wrapKey(
          exportFormat,
          keyPair.privateKey,
          kek,
          {
              ...keyWrappingAlgorithm,
              iv: iv,
          }
      )
    
      let decryptedData = await crypto.subtle.decrypt(
          {
              ...keyWrappingAlgorithm,
              iv: iv,
          },
          kek,
          wrappedPrivateKey,
      )
      
      let decryptedKey = await crypto.subtle.importKey(
          exportFormat,
          decryptedData,
          keyPairAlgorithm,
          true,
          ["decrypt"]
      )
      console.log(decryptedKey);
      console.log(ab2hex(await crypto.subtle.exportKey('pkcs8', decryptedKey)));
    
      let unwrappedKey = await crypto.subtle.unwrapKey(
          exportFormat,
          wrappedPrivateKey,
          kek,
          {
              ...keyWrappingAlgorithm,
              iv: iv,
          },
          keyPairAlgorithm,
          true,
          ["decrypt"]
      )
      console.log(unwrappedKey);
      console.log(ab2hex(await crypto.subtle.exportKey('pkcs8', unwrappedKey)));
    })()
    
    function ab2hex(ab) { 
      return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join('');
    }