KJUR jws jsrsasign:无法在 JWT.io 上验证 ES256 令牌
KJUR jws jsrsasign: Cannot validate ES256 token on JWT.io
我们正在尝试使用 KJUR jws 库为 Apple Search Ads 制作 JWT 令牌。我们正在使用 Apple 的 API 文档:
我们正在生成私钥(prime256v1曲线):
openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem
接下来我们从私钥生成一个 public 密钥:
openssl ec -in private-key.pem -pubout -out public-key.pem
接下来我们设置 header 和负载:
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 PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----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({d: privateKey, curve: 'prime256v1'});
var sResult = KJUR.jws.JWS.sign('ES256', sHeader, sPayload, sKey);
接下来我们尝试在 jwt.io 上验证 JWT 令牌(它已生成令牌)但无法验证。 Apple 搜索广告还会抛出 invalid_client 消息。我错过了什么?有人知道我在这里做错了什么吗?
亲切的问候,
杰克·夸克曼
问题是由于密钥导入不正确造成的。
发布的密钥是 SEC1 格式的 PEM 编码私钥。在 getKey()
中,密钥以 JWK 格式传递,指定原始私钥 d
。 PEM 编码的 SEC1 密钥用作 d
的值。这是不正确的,因为原始私钥与 SEC1 密钥不同,而只是包含在其中。
要解决此问题,必须正确导入密钥。 jsrsasign也支持导入SEC1格式的PEM编码密钥,但是还需要EC参数,s。例如here。对于 prime256v1 又名 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>
可以使用以下 public 密钥(与上述私钥关联)在 https://jwt.io/ 上成功验证使用此代码生成的 JWT:
-----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 导入,除了原始私钥 d
之外,还必须指定原始 public 密钥的 x
和 y
坐标.使用 ASN.1 解析器(例如 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>
可以使用上面的 public 密钥在 https://jwt.io/ 上成功验证由这些代码生成的 JWT。
我们正在尝试使用 KJUR jws 库为 Apple Search Ads 制作 JWT 令牌。我们正在使用 Apple 的 API 文档:
我们正在生成私钥(prime256v1曲线):
openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem
接下来我们从私钥生成一个 public 密钥:
openssl ec -in private-key.pem -pubout -out public-key.pem
接下来我们设置 header 和负载:
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 PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----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({d: privateKey, curve: 'prime256v1'});
var sResult = KJUR.jws.JWS.sign('ES256', sHeader, sPayload, sKey);
接下来我们尝试在 jwt.io 上验证 JWT 令牌(它已生成令牌)但无法验证。 Apple 搜索广告还会抛出 invalid_client 消息。我错过了什么?有人知道我在这里做错了什么吗?
亲切的问候,
杰克·夸克曼
问题是由于密钥导入不正确造成的。
发布的密钥是 SEC1 格式的 PEM 编码私钥。在 getKey()
中,密钥以 JWK 格式传递,指定原始私钥 d
。 PEM 编码的 SEC1 密钥用作 d
的值。这是不正确的,因为原始私钥与 SEC1 密钥不同,而只是包含在其中。
要解决此问题,必须正确导入密钥。 jsrsasign也支持导入SEC1格式的PEM编码密钥,但是还需要EC参数,s。例如here。对于 prime256v1 又名 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>
可以使用以下 public 密钥(与上述私钥关联)在 https://jwt.io/ 上成功验证使用此代码生成的 JWT:
-----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 导入,除了原始私钥 d
之外,还必须指定原始 public 密钥的 x
和 y
坐标.使用 ASN.1 解析器(例如 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>
可以使用上面的 public 密钥在 https://jwt.io/ 上成功验证由这些代码生成的 JWT。