【问题标题】:x509 public key to JWK in web-workers environment网络工作者环境中 JWK 的 x509 公钥
【发布时间】:2023-03-09 01:00:01
【问题描述】:

我想在 “Cloudflare workers” 环境中验证 “firebase JWT 令牌”

问题是firebase-auth 不提供标准/.well-known/jwks.json,而是提供x806 public key certificate(pem) format

我正在使用“Webcrypto API”来完成加密工作,这就是我要做的

// Get CryptoKey
const key = await crypto.subtle.importKey(
  "jwk", // it's possible to change this format if the pem can be changed to other standards
  jwk, //  ?? Here is the missing piece
  { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
  false,
  ["verify"]
);

// Verify
const success = await crypto.subtle.verify(
  "RSASSA-PKCS1-v1_5",
  key,
  signature, // JWT signature
  data // JWT payload
);

我在 Github 上尝试了几个包,我发现的所有库要么不起作用,要么使用在 CF 环境中不起作用的 nodejs API(例如缓冲区)

谁能告诉我怎么做

  • 将 Firebase 公钥转换为 JWK 或
  • 将公钥转换为importKey可以接受的其他标准("raw" | "pkcs8" | "spki")

注意:我们处于“CF Workers”环境中,所以所有“nodejs”api 都不起作用

谢谢

【问题讨论】:

    标签: javascript firebase web-standards webcrypto-api cloudflare-workers


    【解决方案1】:

    我不确定 CF 工作人员有什么可用的,但这可能是开始的:

    const forge = require('node-forge')
    const NodeRSA = require('node-rsa')
    const {createHash} = require('crypto')
    const base64url = require('base64url')
    
    const getCertificateDer = certPem => {
        return forge.util.encode64(
            forge.asn1
                .toDer(forge.pki.certificateToAsn1(forge.pki.certificateFromPem(certPem)))
                .getBytes(),
        )
    }
    
    const getModulusExponent = certPem => {
        const nodeRsa = new NodeRSA()
        nodeRsa.importKey(certPem)
    
        const {n: modulus, e: exponent} = nodeRsa.exportKey('components-public')
    
        return {
            modulus,
            exponent,
        }
    }
    
    const getCertThumbprint = certDer => {
        const derBinaryStr = Buffer.from(certDer).toString('binary')
    
        const shasum = createHash('sha1')
        shasum.update(derBinaryStr)
    
        return shasum.digest('base64')
    }
    
    const getCertThumbprintEncoded = certDer => base64url.encode(getCertThumbprint(certDer))
    
    const certPem = "<your pem certificate>"
    const {modulus, exponent} = getModulusExponent(certPem)
    const certDer = getCertificateDer(certPem)
    const thumbprintEncoded = getCertThumbprintEncoded(certDer)
    
    const jwksInfo = {
        alg: 'RSA256',
        kty: 'RSA',
        use: 'sig',
        x5c: [certDer],
        e: String(exponent),
        n: modulus.toString('base64'),
        kid: thumbprintEncoded,
        x5t: thumbprintEncoded,
    }
    

    由于您无法使用 Buffer 并且可能无法使用节点的加密库,因此您必须找到 getCertThumbprint 函数的替代品。但它所做的只是创建一个 certDer 的 sha1 散列并用 base64 对其进行编码,所以这可能并不难。

    更新:这可以替代getCertThumbprint。我做了一些测试,它似乎返回了与上面相同的值,但我没有用它来验证 JWT。

    const sha1 = require('sha1')
    
    const getCertThumbprint = certDer => btoa(sha1(certDer))
    

    【讨论】:

      【解决方案2】:

      这里的关键(可以这么说)是 PEM 格式的私钥基于 PKCS #8 二进制格式。 “PEM”格式意味着底层二进制数据已经过base64编码并添加了--- BEGIN PRIVATE KEY ---之类的cmets。 WebCrypto 可以理解 PKCS #8 二进制格式,但不处理 PEM。幸运的是,手动解码 PEM 并不难。

      这里有一些代码,来自一个真实的生产 Cloudflare Worker。

      let pem = "[your PEM string here]";
      
      // Parse PEM base64 format into binary bytes.
      // The first line removes comments and newlines to form one continuous
      // base64 string, the second line decodes that to a Uint8Array.
      let b64 = pem.split('\n').filter(line => !line.startsWith("--")).join("");
      let bytes = new Uint8Array([...atob(b64)].map(c => c.charCodeAt(0)));
      
      // Import key using WebCrypto API.
      let key = await crypto.subtle.importKey("pkcs8", bytes,
          { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
          false, ["verify"]);
      

      请注意,PEM 用于包装许多不同的格式。 PKCS #8 对于私钥很常见。 SPKI 对于公钥很常见(WebCrypto 也支持)。证书是另一种格式,我认为 WebCrypto 无法直接读取。

      【讨论】:

        猜你喜欢
        • 2020-05-17
        • 1970-01-01
        • 2022-01-01
        • 2015-10-19
        • 2015-03-16
        • 2019-02-01
        • 1970-01-01
        • 2016-02-21
        相关资源
        最近更新 更多