在公钥分发到PEM中时,如何使用Web Crypto验证ES256 JWT令牌?

时间:2019-05-29 09:29:38

标签: web cryptography jwt

我需要在Cloudflare Worker中验证ES256 JWT令牌。据我了解,他们不在那儿运行Node.js,我必须使所有东西都与Web Crypto API一起工作(也可以在浏览器中以window.crypto.subtle的形式使用)。但是,我希望将公钥作为PEM文件接收,并且我认为导入公钥时遇到问题。

我一直在尝试修改现有的仅支持HS256的开源JWT实现以支持ES256。

除了尝试使用实际的令牌和密钥以及在浏览器和OpenSSL中生成的密钥之外,我还尝试使用JWT.io网站上的工作示例(使用node-jose将其转换为JWK格式之后),因为它应该正确验证。但是我没有收到任何错误,我在浏览器中运行的代码只是告诉我该令牌无效。

这是我用来将密钥从JWT.io转换为JWK的Node REPL会话:

> const jose = require('node-jose')
> const publicKey = `-----BEGIN PUBLIC KEY-----
... MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
... q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
... -----END PUBLIC KEY-----`
> const keyStore = jose.JWK.createKeyStore()
> keyStore.add(publicKey, 'pem')
> keyStore.toJSON()
{
  keys: [
    {
      kty: 'EC',
      kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
      crv: 'P-256',
      x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
      y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
    }
  ]
}
>

这是基于webcrypto-jwt软件包中的代码的验证失败:

function utf8ToUint8Array(str) {
    // Adapted from https://chromium.googlesource.com/chromium/blink/+/master/LayoutTests/crypto/subtle/hmac/sign-verify.html
    var Base64URL = {
        stringify: function (a) {
        var base64string = btoa(String.fromCharCode.apply(0, a));
        return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
        },
        parse: function (s) {
        s = s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '');
        return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0); }));
        }
    };
    str = btoa(unescape(encodeURIComponent(str)));
    return Base64URL.parse(str);
}

var cryptoSubtle = (crypto && crypto.subtle) ||
(crypto && crypto.webkitSubtle) ||
(window.msCrypto && window.msCrypto.Subtle);

// Token from JWT.io
var tokenParts = [
    'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9', 
    'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0',
    'tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA'
];

// Public key from JWT.io converted in Node using node-jose
var publicKey = {
    kty: 'EC',
    kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
    crv: 'P-256',
    x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
    y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
};

var importAlgorithm = {
    name: 'ECDSA',
    namedCurve: 'P-256',
    hash: 'SHA-256',
};

cryptoSubtle.importKey(
    "jwk",
    publicKey,
    importAlgorithm,
    false,
    ["verify"]
).then(function (key) {
    var partialToken = tokenParts.slice(0,2).join('.');
    var signaturePart = tokenParts[2];

    cryptoSubtle.verify(
        importAlgorithm,
        key,
        utf8ToUint8Array(signaturePart),
        utf8ToUint8Array(partialToken)
    ).then(function (ok) {
        if (ok) {
            console.log("I think it's valid");
        } else {
            console.log("I think it isn't valid");
        }
    }).catch(function (err) {
        console.log("error verifying", err);
    });
}).catch(function(err) {
    console.log("error importing", err);
});

由于我从JWT.io复制了有效的密钥和有效的令牌,因此我期望代码记录“我认为这是有效的”而不会出现错误。实际上,它没有显示任何错误,但是最终出现在“我认为它无效”分支中。

1 个答案:

答案 0 :(得分:0)

在这里How to verify a signed JWT with SubtleCrypto of the Web Crypto API?

回答

因此,如果我理解正确,那么问题就在于开放源码上游所包含的base64编码在其中一种方向上无法正常工作,因为它使用了浏览器的btoa。改用https://github.com/swansontec/rfc4648.js可以。