scala-js/scala-js-dom

Some methods from dom.crypto are broken

AlexITC opened this issue ยท 7 comments

scala-js-dom 2.0.0 deprecated the dom.crypto.crypto API, suggesting to use dom.crypto package instead. Unfortunately, many methods (if not all) are broken.

On the other hand, dom.crypto.crypto and dom.webcrypto seem to work.

I'm yet to try all of the methods from these packages, I have tried some methods and all of them behave this way, it would be ideal to add test coverage for the package.

To showcase the problem, I have created a draft PR (#667), I'm happy to submit the test coverage and fix the integration but I'm not sure what's the acceptable way, I could just go linking the new packages to GlobalCrypto as a simple fix.

Thanks.

Thanks for reporting and opening the showcase PR, much appreciated. I'm just playing with this a bit in #669 to try and understand what's wrong. Besides dom.crypto.getRandomValues, what other methods are broken? Thanks.

Hmm, I have isolated the code for key generation -> encryption -> decryption, and, they are working, I'll keep testing the other methods I depend on and get back to you.

See:

    val f = for {
      key <- dom.crypto
        .subtle.generateKey(
          dom.AesKeyAlgorithm("AES-GCM", 256),
          true,
          js.Array(dom.KeyUsage.encrypt, dom.KeyUsage.decrypt)
        )
        .toFuture
        .map(_.asInstanceOf[dom.CryptoKey])

      aesIV = dom.webcrypto.getRandomValues(Array.ofDim[Byte](12).toTypedArray)
      encrypted <- dom.crypto
        .subtle.encrypt(
          AesGcmParams(
            "AES-GCM",
            aesIV,
            "".getBytes().toTypedArray.buffer,
            128
          ),
          key,
          "data".getBytes().toTypedArray
        )
        .toFuture
        .map(_.asInstanceOf[ArrayBuffer])
      decrypted <- dom.crypto
        .subtle.decrypt(
          AesGcmParams(
            "AES-GCM",
            aesIV,
            "".getBytes().toTypedArray.buffer, // this shouldn't be necessary
            128
          ),
          key,
          encrypted
        )
        .toFuture
        .map(_.asInstanceOf[ArrayBuffer])
        .map { buffer =>
          val arr = Array.ofDim[Byte](buffer.byteLength)
          TypedArrayBuffer.wrap(buffer).get(arr)
          new String(arr, "UTF-8")
        }
    } yield dom.console.log(decrypted)

    f.onComplete {
      case Success(value) => println(s"generateKey -> encrypt -> decrypt works")
      case Failure(exception) => println(s"generateKey -> encrypt -> decrypt failed: ${exception.getMessage}")
    }

Thanks, I appreciate your help ๐Ÿ‘

I have tested more methods that are working:

    val pbdkf2 = dom.Pbkdf2Params("PBKDF2", "salt".getBytes.toTypedArray.buffer, 100L, "SHA-512")
    val g = for {
      pbkdf2Key <- dom.crypto.subtle
        .importKey(
          dom.KeyFormat.raw,
          "password".getBytes.toTypedArray.buffer,
          "PBKDF2",
          false,
          js.Array(dom.KeyUsage.deriveKey, dom.KeyUsage.deriveBits)
        )
        .toFuture
      _ = println(s"pbkdf2Key: $pbkdf2Key")

      aesCtr = new dom.AesDerivedKeyParams {
        val name = "AES-GCM"
        val length = 256
      }
      _ = println(s"aesCtr: $aesCtr")
      aesKey <- dom.crypto.subtle
        .deriveKey(
          pbdkf2,
          pbkdf2Key,
          aesCtr,
          true,
          js.Array(dom.KeyUsage.encrypt, dom.KeyUsage.decrypt)
        )
        .toFuture
        .map(_.asInstanceOf[dom.CryptoKey])
      _ = println(s"aesKey: $aesKey")
    } yield ()
    g.onComplete {
      case Success(value) => println(s"aesKey works")
      case Failure(exception) => println(s"aesKey failed: ${exception.getMessage}")
    }

We should be able to gather most of these into a test file.

We should be able to gather most of these into a test file.

Sounds good. Maybe a separate CryptoTests like you did in #667 is a good idea, PR would be appreciated :)

I'm happy to prepare a PR, my only concern is, how should we run the tests given that junit doesn't support future-based tests? Thread.sleep doesn't work in scalajs.

Ah, you should check the examples in AsyncTests. Thanks!