-
Notifications
You must be signed in to change notification settings - Fork 142
Support EC Signed OIDC Jwt tokens #7785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0992f8b
2d22189
02e99fc
6e5c91b
5155d12
4b771ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
import java.nio.charset.StandardCharsets; | ||
import java.security.GeneralSecurityException; | ||
import java.security.NoSuchAlgorithmException; | ||
import java.security.SignatureException; | ||
import java.security.PublicKey; | ||
import java.security.Signature; | ||
import java.time.Instant; | ||
|
@@ -113,12 +114,57 @@ public String getKeyIdentifier() { | |
return kid; | ||
} | ||
|
||
private static byte[] transcodeJWTECDSASignatureToDER(byte[] jwsSignature) throws SignatureException { | ||
int rawLen = jwsSignature.length / 2; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [non-blocking] It would be better if the code rejected Better still would be if the code rejected |
||
|
||
// Find the start of R (skip leading zeros) | ||
int rStart = 0; | ||
while (rStart < rawLen && jwsSignature[rStart] == 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This produces the wrong sequence if the initial non-padding octet is >= 0x80. Under DER encoding, an integer starting with an octet >= 0x80 results in that value accepted as a negative number (2s-complement). Under these circumstances, a proceeding 0x00 octet is needed to ensure the DER encoded integer is understood as a positive number. As a special case, if the R octet sequence has no 0x00 padding and the first octet is >= 0x80 then an additional 0x00 octet (not present in The same problem exists for the code calculating the start of S: the DER encoded integer is treated as negative if the initial octet is >= 0x80. |
||
rStart++; | ||
} | ||
int rLen = rawLen - rStart; | ||
|
||
// Find the start of S (skip leading zeros) | ||
int sStart = rawLen; | ||
while (sStart < jwsSignature.length && jwsSignature[sStart] == 0) { | ||
sStart++; | ||
} | ||
int sLen = rawLen - (sStart - rawLen); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [non-blocking] This is equivalent to:
Personally I find the above form easier to understand, but tastes vary, so I offer this only as an alternative. |
||
|
||
int totalLen = 2 + 2 + rLen + 2 + sLen; // SEQUENCE + INTEGER(R) + INTEGER(S) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This underestimates the amount of memory needed if (2 + rLen + 2 + sLen) >= 0x80. This is because DER encoding a length n where 0x80 <= n <= 0xff requires two bytes: 0x81, n. |
||
int offset = 0; | ||
byte[] der = new byte[totalLen]; | ||
|
||
der[offset++] = 0x30; // SEQUENCE | ||
der[offset++] = (byte) (totalLen - 2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Encoding the length as a single octet assumes that the number of octets needed to DER-encoded the two integers (i.e., |
||
|
||
// INTEGER R | ||
der[offset++] = 0x02; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [non-blocking] It would be helpful if the meaning of the 0x02 value was explained, similar to the 0x30 above. |
||
der[offset++] = (byte) rLen; | ||
System.arraycopy(jwsSignature, rawLen - rLen, der, offset, rLen); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [non-blocking] Suggest: System.arraycopy(jwsSignature, rStart, der, offset, rLen). |
||
offset += rLen; | ||
|
||
// INTEGER S | ||
der[offset++] = 0x02; | ||
der[offset++] = (byte) sLen; | ||
System.arraycopy(jwsSignature, jwsSignature.length - sLen, der, offset, sLen); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [non-blocking] Suggest:
|
||
|
||
return der; | ||
} | ||
|
||
public boolean isSignedBy(PublicKey key) { | ||
try { | ||
Signature signature = getSignature(); | ||
signature.initVerify(key); | ||
signature.update(unsignedToken); | ||
return signature.verify(this.signature); | ||
byte[] sig = this.signature; | ||
if (alg.startsWith("ES")) { | ||
sig = transcodeJWTECDSASignatureToDER(sig); | ||
} | ||
return signature.verify(sig); | ||
} catch (SignatureException e) { | ||
LOGGER.warn("Problem verifying signature: {}", e.toString()); | ||
return false; | ||
} catch (GeneralSecurityException e) { | ||
LOGGER.warn("Problem verifying signature: {}", e.toString()); | ||
return false; | ||
|
@@ -129,6 +175,12 @@ private Signature getSignature() throws GeneralSecurityException { | |
switch (alg) { | ||
case "RS256": | ||
return Signature.getInstance("SHA256withRSA"); | ||
case "ES256": | ||
return Signature.getInstance("SHA256withECDSA"); | ||
case "ES384": | ||
return Signature.getInstance("SHA384withECDSA"); | ||
case "ES512": | ||
return Signature.getInstance("SHA512withECDSA"); | ||
default: | ||
throw new NoSuchAlgorithmException("Unknown JWT alg " + alg); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the complexity of this method, it would be reasonable to provide some unit-tests (tests targeting this specific method) to verify correct behaviour for example byte sequences for each of the supported algorithms.