该问题是由于密钥导入不正确造成的。
发布的密钥是 SEC1 格式的 PEM 编码私钥。在getKey()
密钥以 JWK 格式传递,指定原始私钥d
。 PEM 编码的 SEC1 密钥用作以下值d
。这是不正确的,因为原始私钥与 SEC1 密钥不同,而只是包含在其中。
要解决此问题,必须正确导入密钥。jsrsasign还支持导入 SEC1 格式的 PEM 编码密钥,但它还需要 EC 参数 s。例如here https://kjur.github.io/jsrsasign/api/symbols/KEYUTIL.html#.getKey. For prime256v1 aka secp256r1这是:
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
这些可以被创建,例如使用 OpenSSL 作为密钥生成过程的一部分:
openssl ecparam -name secp256r1 -genkey
至此,固定的 JavaScript 代码为:
var tNow = KJUR.jws.IntDate.get('now');
var tEnd = KJUR.jws.IntDate.get('now + 1day');
var teamId = 'SEARCHADS.xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
var keyId = 'xxxxxx-xxxx-xxxx-xxxxxxxxxxx';
var privateKey = `-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIK1vV4iLOPym9KvJJU5hd6CMEp+DTt8QI7NPBdJSf+VDoAoGCCqGSM49
AwEHoUQDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0xk0H/TFo6gfT23ish58blPNh
YrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
-----END EC PRIVATE KEY-----`;
var oHeader = {
"alg": "ES256",
"kid": keyId
}
var oPayload = {
"iss": teamId,
"iat": tNow,
"exp": tEnd,
"aud": "https://appleid.apple.com",
"sub": "clientId"
}
var sHeader = JSON.stringify(oHeader);
var sPayload = JSON.stringify(oPayload);
var sKey = KEYUTIL.getKey(privateKey);
var sResult = KJUR.jws.JWS.sign('ES256', sHeader, sPayload, sKey);
document.getElementById("jwt").innerHTML = sResult;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script>
<p style="font-family:'Courier New', monospace;" id="jwt"></p>
使用此代码生成的 JWT 可以成功验证https://jwt.io/ https://jwt.io/使用以下公钥(与上面的私钥关联):
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0
xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
-----END PUBLIC KEY-----
当然,正如评论中提到的,私钥也可以转换为 PKCS#8 格式(例如使用 OpenSSL)。导入同样可以使用getKey()
(或者也可以KEYUTIL.getKeyFromPlainPrivatePKCS8PEM()
):
var tNow = KJUR.jws.IntDate.get('now');
var tEnd = KJUR.jws.IntDate.get('now + 1day');
var teamId = 'SEARCHADS.xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
var keyId = 'xxxxxx-xxxx-xxxx-xxxxxxxxxxx';
var privateKey = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgrW9XiIs4/Kb0q8kl
TmF3oIwSn4NO3xAjs08F0lJ/5UOhRANCAAQykdP4c0ozvOOHHSNkMfLNCWRstXTG
TQf9MWjqB9PbeKyHnxuU82FisUjnVD9zO+QDAK0tnP/qzWf8zxoD0vVW
-----END PRIVATE KEY-----`;
var oHeader = {
"alg": "ES256",
"kid": keyId
}
var oPayload = {
"iss": teamId,
"iat": tNow,
"exp": tEnd,
"aud": "https://appleid.apple.com",
"sub": "clientId"
}
var sHeader = JSON.stringify(oHeader);
var sPayload = JSON.stringify(oPayload);
var sKey = KEYUTIL.getKey(privateKey);
//var sKey = KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(privateKey); // works also
var sResult = KJUR.jws.JWS.sign('ES256', sHeader, sPayload, sKey);
document.getElementById("jwt").innerHTML = sResult;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script>
<p style="font-family:'Courier New', monospace;" id="jwt"></p>
如果密钥导入为 JWK,则x
and y
除了原始私钥之外,还必须指定原始公钥的坐标d
。使用 ASN.1 解析器最容易确定这些值,例如https://lapo.it/asn1js/ https://lapo.it/asn1js/。此外,密钥类型(kty
) 必须指定,曲线标识符的关键字是crv
:
var tNow = KJUR.jws.IntDate.get('now');
var tEnd = KJUR.jws.IntDate.get('now + 1day');
var teamId = 'SEARCHADS.xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
var keyId = 'xxxxxx-xxxx-xxxx-xxxxxxxxxxx';
var privateKey = `rW9XiIs4_Kb0q8klTmF3oIwSn4NO3xAjs08F0lJ_5UM`;
var publicKeyX = `MpHT-HNKM7zjhx0jZDHyzQlkbLV0xk0H_TFo6gfT23g`;
var publicKeyY = `rIefG5TzYWKxSOdUP3M75AMArS2c_-rNZ_zPGgPS9VY`;
var oHeader = {
"alg": "ES256",
"kid": keyId
}
var oPayload = {
"iss": teamId,
"iat": tNow,
"exp": tEnd,
"aud": "https://appleid.apple.com",
"sub": "clientId"
}
var sHeader = JSON.stringify(oHeader);
var sPayload = JSON.stringify(oPayload);
var sKey = KEYUTIL.getKey({kty: "EC", d: privateKey, x: publicKeyX, y: publicKeyY, crv: 'prime256v1'});
var sResult = KJUR.jws.JWS.sign('ES256', sHeader, sPayload, sKey);
document.getElementById("jwt").innerHTML = sResult;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script>
<p style="font-family:'Courier New', monospace;" id="jwt"></p>
这些代码生成的 JWT 可以在上成功验证https://jwt.io/ https://jwt.io/使用上面的公钥。