This commit is contained in:
Martin Treurnicht 2016-06-28 12:12:40 -07:00
parent c60deebb64
commit 61510dfca5
9 changed files with 292 additions and 221 deletions

View File

@ -307,11 +307,6 @@
<lineRate>100</lineRate> <lineRate>100</lineRate>
<branchRate>90</branchRate> <branchRate>90</branchRate>
</regex> </regex>
<regex>
<pattern>io.jsonwebtoken.impl.crypto.ECDSA</pattern>
<lineRate>80</lineRate>
<branchRate>60</branchRate>
</regex>
</regexes> </regexes>
</check> </check>
</configuration> </configuration>

View File

@ -1,177 +0,0 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.JOSEException;
/**
* Elliptic Curve Digital Signature Algorithm (ECDSA) functions and utilities.
*/
public class ECDSA {
/**
* Returns the expected signature byte array length (R + S parts) for
* the specified ECDSA algorithm.
*
* @param alg The ECDSA algorithm. Must be supported and not
* {@code null}.
*
* @return The expected byte array length for the signature.
*
* @throws JOSEException If the algorithm is not supported.
*/
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg)
throws JOSEException {
switch (alg) {
case ES256: return 64;
case ES384: return 96;
case ES512: return 132;
default:
throw new JOSEException("unsupported algorithm: " + alg.name());
}
}
/**
* Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
* R + S format expected by ECDSA JWS.
*
* @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
* @param outputLength The expected length of the ECDSA JWS signature.
*
* @return The ECDSA JWS encoded signature.
*
* @throws JOSEException If the ASN.1/DER signature format is invalid.
*/
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength)
throws JOSEException {
if (derSignature.length < 8 || derSignature[0] != 48) {
throw new JOSEException("Invalid ECDSA signature format");
}
int offset;
if (derSignature[1] > 0) {
offset = 2;
} else if (derSignature[1] == (byte) 0x81) {
offset = 3;
} else {
throw new JOSEException("Invalid ECDSA signature format");
}
byte rLength = derSignature[offset + 1];
int i;
for (i = rLength; (i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0); i--) {
// do nothing
}
byte sLength = derSignature[offset + 2 + rLength + 1];
int j;
for (j = sLength; (j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0); j--) {
// do nothing
}
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 JOSEException("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;
}
/**
* Transcodes the ECDSA JWS signature into ASN.1/DER format for use by
* the JCA verifier.
*
* @param jwsSignature The JWS signature, consisting of the
* concatenated R and S values. Must not be
* {@code null}.
*
* @return The ASN.1/DER encoded signature.
*
* @throws JOSEException If the ECDSA JWS signature format is invalid.
*/
public static byte[] transcodeSignatureToDER(byte[] jwsSignature)
throws JOSEException {
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;
}
int len = 2 + j + 2 + l;
if (len > 255) {
throw new JOSEException("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;
}
/**
* Prevents public instantiation.
*/
private ECDSA() {}
}

View File

@ -15,9 +15,6 @@
*/ */
package io.jsonwebtoken.impl.crypto; package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import java.security.Key; import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
@ -25,6 +22,10 @@ import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
/** /**
* ElliptiCurve crypto provider. * ElliptiCurve crypto provider.
* *
@ -135,4 +136,166 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e); throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e);
} }
} }
/**
* Returns the expected signature byte array length (R + S parts) for
* the specified ECDSA algorithm.
*
* @param alg The ECDSA algorithm. Must be supported and not
* {@code null}.
*
* @return The expected byte array length for the signature.
*
* @throws JwtException If the algorithm is not supported.
*/
public static int getSignatureByteArrayLength(final SignatureAlgorithm alg)
throws JwtException {
switch (alg) {
case ES256: return 64;
case ES384: return 96;
case ES512: return 132;
default:
throw new JwtException("Unsupported Algorithm: " + alg.name());
}
}
/**
* Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
* R + S format expected by ECDSA JWS.
*
* @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
* @param outputLength The expected length of the ECDSA JWS signature.
*
* @return The ECDSA JWS encoded signature.
*
* @throws JwtException If the ASN.1/DER signature format is invalid.
*/
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength)
throws JwtException {
if (derSignature.length < 8 || derSignature[0] != 48) {
throw new JwtException("Invalid ECDSA signature format");
}
int offset;
if (derSignature[1] > 0) {
offset = 2;
} else if (derSignature[1] == (byte) 0x81) {
offset = 3;
} else {
throw new JwtException("Invalid ECDSA signature format");
}
byte rLength = derSignature[offset + 1];
int i = rLength;
while ((i > 0)
&& (derSignature[(offset + 2 + rLength) - i] == 0))
i--;
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;
}
/**
* Transcodes the ECDSA JWS signature into ASN.1/DER format for use by
* the JCA verifier.
*
* @param jwsSignature The JWS signature, consisting of the
* concatenated R and S values. Must not be
* {@code null}.
*
* @return The ASN.1/DER encoded signature.
*
* @throws JwtException If the ECDSA JWS signature format is invalid.
*/
public static byte[] transcodeSignatureToDER(byte[] jwsSignature)
throws JwtException {
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;
}
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

@ -40,7 +40,9 @@ public class EllipticCurveSignatureValidator extends EllipticCurveProvider imple
Signature sig = createSignatureInstance(); Signature sig = createSignatureInstance();
PublicKey publicKey = (PublicKey) key; PublicKey publicKey = (PublicKey) key;
try { try {
byte[] derSignature = ECDSA.transcodeSignatureToDER(signature); int expectedSize = getSignatureByteArrayLength(alg);
//if the expected size is not valid for JOSE, fall back to ASN.1 DER signature
byte[] derSignature = expectedSize != signature.length && signature[0] == 0x30 ? signature : EllipticCurveProvider.transcodeSignatureToDER(signature);
return doVerify(sig, publicKey, data, derSignature); return doVerify(sig, publicKey, data, derSignature);
} catch (Exception e) { } catch (Exception e) {
String msg = "Unable to verify Elliptic Curve signature using configured ECPublicKey. " + e.getMessage(); String msg = "Unable to verify Elliptic Curve signature using configured ECPublicKey. " + e.getMessage();

View File

@ -21,9 +21,9 @@ import java.security.PrivateKey;
import java.security.Signature; import java.security.Signature;
import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPrivateKey;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.JOSEException;
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer { public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
@ -44,16 +44,16 @@ public class EllipticCurveSigner extends EllipticCurveProvider implements Signer
throw new SignatureException("Invalid Elliptic Curve PrivateKey. " + e.getMessage(), e); throw new SignatureException("Invalid Elliptic Curve PrivateKey. " + e.getMessage(), e);
} catch (java.security.SignatureException e) { } catch (java.security.SignatureException e) {
throw new SignatureException("Unable to calculate signature using Elliptic Curve PrivateKey. " + e.getMessage(), e); throw new SignatureException("Unable to calculate signature using Elliptic Curve PrivateKey. " + e.getMessage(), e);
} catch (JOSEException e) { } catch (JwtException e) {
throw new SignatureException("Unable to convert signature to JOSE format. " + e.getMessage(), e); throw new SignatureException("Unable to convert signature to JOSE format. " + e.getMessage(), e);
} }
} }
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JOSEException { protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
PrivateKey privateKey = (PrivateKey)key; PrivateKey privateKey = (PrivateKey)key;
Signature sig = createSignatureInstance(); Signature sig = createSignatureInstance();
sig.initSign(privateKey); sig.initSign(privateKey);
sig.update(data); sig.update(data);
return ECDSA.transcodeSignatureToConcat(sig.sign(), ECDSA.getSignatureByteArrayLength(alg)); return transcodeSignatureToConcat(sig.sign(), getSignatureByteArrayLength(alg));
} }
} }

View File

@ -15,16 +15,16 @@
*/ */
package io.jsonwebtoken.impl.crypto; package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.RuntimeEnvironment;
import java.security.Key; import java.security.Key;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Signature; import java.security.Signature;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.RuntimeEnvironment;
abstract class SignatureProvider { abstract class SignatureProvider {
/** /**

View File

@ -1,22 +0,0 @@
package io.jsonwebtoken.lang;
/**
* Javascript Object Signing and Encryption (JOSE) exception.
*/
public class JOSEException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Creates a new JOSE exception with the specified message.
*
* @param message The exception message.
*/
public JOSEException(final String message) {
super(message);
}
}

View File

@ -15,6 +15,7 @@
*/ */
package io.jsonwebtoken.impl.crypto package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException import io.jsonwebtoken.SignatureException
import io.jsonwebtoken.impl.TextCodec import io.jsonwebtoken.impl.TextCodec
@ -76,4 +77,113 @@ class EllipticCurveSignatureValidatorTest {
//Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4 //Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4
verifier("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn") verifier("eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn")
} }
@Test
void legacySignatureCompatTest() {
def withoutSignature = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30"
def keypair = EllipticCurveProvider.generateKeyPair()
def signature = Signature.getInstance(SignatureAlgorithm.ES512.jcaName, "BC")
def data = withoutSignature.getBytes("US-ASCII")
signature.initSign(keypair.private)
signature.update(data)
def signed = signature.sign()
assert new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, keypair.public).isValid(data, signed)
}
@Test
void invalidAlgorithmTest() {
def invalidAlgorithm = SignatureAlgorithm.HS256
try {
EllipticCurveProvider.getSignatureByteArrayLength(invalidAlgorithm)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Unsupported Algorithm: ' + invalidAlgorithm.name()
}
}
@Test
void invalidECDSASignatureFormatTest() {
try {
def signature = new byte[257]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
EllipticCurveProvider.transcodeSignatureToDER(signature)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
}
}
@Test
void invalidDERSignatureToJoseFormatTest() {
def verify = { signature ->
try {
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
}
}
def signature = new byte[257]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
//invalid type
signature[0] = 34
verify(signature)
def shortSignature = new byte[7]
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(shortSignature)
verify(shortSignature)
signature[0] = 48
// signature[1] = 0x81
signature[1] = -10
verify(signature)
}
@Test
void edgeCaseSignatureLengthTest() {
def signature = new byte[1]
EllipticCurveProvider.transcodeSignatureToDER(signature)
}
@Test
void edgeCaseSignatureToConcatLengthTest() {
try {
def signature = TextCodec.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
}
}
@Test
void edgeCaseSignatureToConcatInvalidSignatureTest() {
try {
def signature = TextCodec.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
}
}
@Test
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
try {
def signature = TextCodec.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
}
}
@Test
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
try {
def signature = TextCodec.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
assertEquals e.message, 'Invalid ECDSA signature format'
}
}
} }

View File

@ -15,16 +15,16 @@
*/ */
package io.jsonwebtoken.impl.crypto package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException import io.jsonwebtoken.SignatureException
import io.jsonwebtoken.lang.JOSEException import org.junit.Test
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
import org.junit.Test
import static org.junit.Assert.* import static org.junit.Assert.*
class EllipticCurveSignerTest { class EllipticCurveSignerTest {
@ -88,11 +88,11 @@ class EllipticCurveSignerTest {
PrivateKey privateKey = kp.getPrivate(); PrivateKey privateKey = kp.getPrivate();
String msg = 'foo' String msg = 'foo'
final JOSEException ex = new JOSEException(msg) final JwtException ex = new JwtException(msg)
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) { def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
@Override @Override
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JOSEException { protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException, JwtException {
throw ex throw ex
} }
} }