jwtk / jjwt

Java JWT: JSON Web Token for Java and Android
Apache License 2.0
10.23k stars 1.32k forks source link

SignatureAlgorithm.ES512 has wrong min length requirements #530

Closed aamarfii closed 4 years ago

aamarfii commented 4 years ago

Current 521 Should be 512

lhazlewood commented 4 years ago

Hi there! Thanks for trying to help JJWT be bug free, but JJWT is correct and doing the right thing.

Per RFC 7518, Section 6.2.2.1 (bold emphasis is mine):

The "d" (ECC private key) parameter contains the Elliptic Curve private key value. It is represented as the base64url encoding of the octet string representation of the private key value, as defined in Section 2.3.7 of SEC1 [SEC1]. The length of this octet string MUST be ceiling(log-base-2(n)/8) octets (where n is the order of the curve).

The NIST P-521 named curve has an order (n) length of 521 bits (and not 512 bits; this is not a typo, and the numbers are not accidentally reversed). In Java, the Sun JCE provider implementation represents NIST P-521 as the JCE string literal name secp521r1.

When you properly generate an EllipticCurve keypair in Java suitable for JWT's EC with SHA-512 hashes, you do so using the secp521r1 (aka NIST P-521) name as follows:

//try/catch exception handling removed for brevity:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("SHA512withECDSA");
ECGenParameterSpec spec = new ECGenParameterSpec("secp521r1");
kpg.initialize(spec);
KeyPair kp = kpg.generateKeyPair();

Then you can verify that the order is indeed 521 bits and not 512 bits:

assert 521 == kp.getPrivate().getParams().getOrder().bitLength();

And this order bitLength is what JJWT will enforce when trying to use a private key to sign JWT's using any of the ES* algorithms because the RFC mandates minimum key length requirements (i.e. it's not optional).

This of course means that you can't use just any private key that you like for JWT's ES* algorithms. You must use a well-formed, valid key per the JWA RFC's constraints. And of course, you don't have to worry about this either, because JJWT provides this logic with proper parameters, arguments, and a secure number generator just by calling:

Keys.keyPairFor(SignatureAlgorithm.ES512); //or ES256 or ES384

Finally, because the JJWT test suites ensure 100% code coverage for all code, you can see that JJWT is doing the correct RFC-defined Elliptic Curve key assertions here:

https://github.com/jwtk/jjwt/blob/master/impl/src/test/groovy/io/jsonwebtoken/security/KeysImplTest.groovy#L89-L107 :

KeyPair pair = Keys.keyPairFor(alg);
assertNotNull pair

int len = alg.minKeyLength
String asn1oid = "secp${len}r1"
String suffix = len == 256 ? ", X9.62 prime${len}v1" : '' //the JDK only adds this extra suffix to the secp256r1 curve name and not secp384r1 or secp521r1 curve names
String jdkParamName = "$asn1oid [NIST P-${len}${suffix}]" as String

PublicKey pub = pair.getPublic()
assert pub instanceof ECPublicKey
assertEquals "EC", pub.algorithm
assertEquals jdkParamName, pub.params.name
assertEquals alg.minKeyLength, pub.params.order.bitLength()

PrivateKey priv = pair.getPrivate()
assert priv instanceof ECPrivateKey
assertEquals "EC", priv.algorithm
assertEquals jdkParamName, priv.params.name
assertEquals alg.minKeyLength, priv.params.order.bitLength()