This commit is contained in:
Looly 2023-07-18 02:43:55 +08:00
parent 119d742e3f
commit 808d48a9fb
5 changed files with 282 additions and 2 deletions

View File

@ -59,6 +59,18 @@
<version>2.0.25</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-gson</artifactId>
<version>0.11.5</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -70,16 +70,38 @@ public class AsymmetricJWTSigner implements JWTSigner {
@Override
public String sign(final String headerBase64, final String payloadBase64) {
return Base64.encodeUrlSafe(sign.sign(StrUtil.format("{}.{}", headerBase64, payloadBase64)));
final String dataStr = StrUtil.format("{}.{}", headerBase64, payloadBase64);
return Base64.encodeUrlSafe(sign(ByteUtil.toBytes(dataStr, charset)));
}
/**
* 签名字符串数据
*
* @param data 数据
* @return 签名
*/
protected byte[] sign(byte[] data) {
return sign.sign(data);
}
@Override
public boolean verify(final String headerBase64, final String payloadBase64, final String signBase64) {
return sign.verify(
return verify(
ByteUtil.toBytes(StrUtil.format("{}.{}", headerBase64, payloadBase64), charset),
Base64.decode(signBase64));
}
/**
* 验签数据
*
* @param data 数据
* @param signed 签名
* @return 是否通过
*/
protected boolean verify(byte[] data, byte[] signed) {
return sign.verify(data, signed);
}
@Override
public String getAlgorithm() {
return this.sign.getSignature().getAlgorithm();

View File

@ -0,0 +1,197 @@
package org.dromara.hutool.json.jwt.signers;
import org.dromara.hutool.json.jwt.JWTException;
import java.security.Key;
import java.security.KeyPair;
/**
* 椭圆曲线Elliptic Curve的JWT签名器<br>
* 按照https://datatracker.ietf.org/doc/html/rfc7518#section-3.4,<br>
* Elliptic Curve Digital Signature Algorithm (ECDSA)算法签名需要转换DER格式为pair (R, S)
*
* @author looly
* @since 5.8.21
*/
public class EllipticCurveJWTSigner extends AsymmetricJWTSigner {
/**
* 构造
*
* @param algorithm 算法
* @param key 密钥
*/
public EllipticCurveJWTSigner(final String algorithm, final Key key) {
super(algorithm, key);
}
/**
* 构造
*
* @param algorithm 算法
* @param keyPair 密钥对
*/
public EllipticCurveJWTSigner(final String algorithm, final KeyPair keyPair) {
super(algorithm, keyPair);
}
@Override
protected byte[] sign(final byte[] data) {
// https://datatracker.ietf.org/doc/html/rfc7518#section-3.4
return derToConcat(super.sign(data), getSignatureByteArrayLength(getAlgorithm()));
}
@Override
protected boolean verify(final byte[] data, final byte[] signed) {
// https://datatracker.ietf.org/doc/html/rfc7518#section-3.4
return super.verify(data, concatToDER(signed));
}
/**
* 获取签名长度
*
* @param alg 算法
* @return 长度
* @throws JWTException JWT异常
*/
private static int getSignatureByteArrayLength(final String alg) throws JWTException {
switch (alg) {
case "ES256":
case "SHA256withECDSA":
return 64;
case "ES384":
case "SHA384withECDSA":
return 96;
case "ES512":
case "SHA512withECDSA":
return 132;
default:
throw new JWTException("Unsupported Algorithm: {}", alg);
}
}
/**
* DER格式转换为pair (R, S)
*
* @param derSignature DER格式签名
* @param outputLength 算法签名长度
* @return pair (R, S)
* @throws JWTException JWT异常
*/
private static byte[] derToConcat(final byte[] derSignature, final int outputLength) throws JWTException {
if (derSignature.length < 8 || derSignature[0] != 48) {
throw new JWTException("Invalid ECDSA signature format");
}
final int offset;
if (derSignature[1] > 0) {
offset = 2;
} else if (derSignature[1] == (byte) 0x81) {
offset = 3;
} else {
throw new JWTException("Invalid ECDSA signature format");
}
final byte rLength = derSignature[offset + 1];
int i = rLength;
while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) {
i--;
}
final byte sLength = derSignature[offset + 2 + rLength + 1];
int j = sLength;
while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) {
j--;
}
int rawLen = Math.max(i, j);
rawLen = Math.max(rawLen, outputLength / 2);
if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
|| (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
|| derSignature[offset] != 2
|| derSignature[offset + 2 + rLength] != 2) {
throw new JWTException("Invalid ECDSA signature format");
}
final byte[] concatSignature = new byte[2 * rawLen];
System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);
return concatSignature;
}
/**
* pair (R, S)转换为DER格式
*
* @param jwsSignature JWT签名
* @return DER格式签名
*/
private static byte[] concatToDER(final byte[] jwsSignature) {
final int rawLen = jwsSignature.length / 2;
int i = rawLen;
while ((i > 0) && (jwsSignature[rawLen - i] == 0)) {
i--;
}
int j = i;
if (jwsSignature[rawLen - i] < 0) {
j += 1;
}
int k = rawLen;
while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) {
k--;
}
int l = k;
if (jwsSignature[2 * rawLen - k] < 0) {
l += 1;
}
final int len = 2 + j + 2 + l;
if (len > 255) {
throw new JWTException("Invalid ECDSA signature format");
}
int offset;
final byte[] derSignature;
if (len < 128) {
derSignature = new byte[2 + 2 + j + 2 + l];
offset = 1;
} else {
derSignature = new byte[3 + 2 + j + 2 + l];
derSignature[1] = (byte) 0x81;
offset = 2;
}
derSignature[0] = 48;
derSignature[offset++] = (byte) len;
derSignature[offset++] = 2;
derSignature[offset++] = (byte) j;
System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i);
offset += j;
derSignature[offset++] = 2;
derSignature[offset++] = (byte) l;
System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);
return derSignature;
}
}

View File

@ -13,6 +13,7 @@
package org.dromara.hutool.json.jwt.signers;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.regex.ReUtil;
import java.security.Key;
import java.security.KeyPair;
@ -261,6 +262,12 @@ public class JWTSignerUtil {
if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) {
return none();
}
// issue3205@Github
if(ReUtil.isMatch("es\\d{3}", algorithmId.toLowerCase())){
return new EllipticCurveJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), keyPair);
}
return new AsymmetricJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), keyPair);
}
@ -278,6 +285,11 @@ public class JWTSignerUtil {
return NoneJWTSigner.NONE;
}
if (key instanceof PrivateKey || key instanceof PublicKey) {
// issue3205@Github
if(ReUtil.isMatch("ES\\d{3}", algorithmId)){
return new EllipticCurveJWTSigner(algorithmId, key);
}
return new AsymmetricJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key);
}
return new HMacJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key);

View File

@ -0,0 +1,37 @@
package org.dromara.hutool.json.jwt;
import io.jsonwebtoken.Jwts;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.crypto.KeyUtil;
import org.dromara.hutool.json.jwt.signers.AlgorithmUtil;
import org.dromara.hutool.json.jwt.signers.JWTSigner;
import org.dromara.hutool.json.jwt.signers.JWTSignerUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.security.KeyPair;
/**
*https://github.com/dromara/hutool/issues/3205
*/
public class Issue3205Test {
@Test
public void es256Test() {
final String id = "es256";
final KeyPair keyPair = KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id));
final JWTSigner signer = JWTSignerUtil.createSigner(id, keyPair);
final JWT jwt = JWT.of()
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setExpiresAt(DateUtil.tomorrow())
.setSigner(signer);
final String token = jwt.sign();
final boolean signed = Jwts.parserBuilder().setSigningKey(keyPair.getPublic()).build().isSigned(token);
Assertions.assertTrue(signed);
}
}