Skip to content

Commit

Permalink
Added support for JWE and JWS
Browse files Browse the repository at this point in the history
- JWE (nested jws) encrypt + decrypt
- JWS check signature and sign
  • Loading branch information
mattebit committed Aug 1, 2023
1 parent 8c92bba commit 1186436
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 60 deletions.
36 changes: 31 additions & 5 deletions doc/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,32 @@ An important note about the body (from) section is that the input of `decode par
An useful regex to match a parameter's value could be `(?<=SAMLResponse=)[^$\n& ]*` which searches the SAMLResponse= string in the body, and matches everything that is not $ or \n or & or whitespace

#### Decoding JWTs
When decoding a jwt (by using tag type=jwt) it is possible to check the signature of that jwt by providing a public key. To do this, use the `jwt check sig` tag, assigning to it value the PEM-encoded public key to be used to check.

Note: The supported algorithms are:
When decoding a jwt (by using tag type=jwt) it is possible to check the signature of that jwt by providing a public key. To do this, use the `jwt check sig` tag with value the PEM-encoded public key to be used to check.

Note: The supported algorithms for signing are:

- RS256
- RS512

#### Decoding JWE

Note that when decrypting a JWE, a JWS is expected in the payload. Other payloads are not supproted.

To decrypt a JWE to access the JWT in its payload use these two mandatory tags

- `jwe decrypt` with the private key in PEM string format
- `jwe encrypt` with the public key in PEM string format

Note: Supported algorithms are:

- RSA_OAEP
- RSA_OAEP_256
- ECDH_ES_A128KW
- ECDH_ES_A256KW

## Tag table

In this table you can find a description of all the tags available for this Operation based on the input Module.

| input module (container) | Available tags | Required | value type | allowed values |
Expand All @@ -193,6 +212,8 @@ In this table you can find a description of all the tags available for this Oper
| | checks | | list[check Operation] | \* |
| | edits | | list[edit Operation] | \* |
| | jwt check sig | | str(PEM) | PEM-encoded public key |
| | jwe decrypt | | str(PEM) | PEM-encoded private key |
| | jwe encrypt | | str(PEM) | PEM-encoded public key |

### Recursive Decode operations

Expand Down Expand Up @@ -277,7 +298,9 @@ This type is used to edit decoded JWT tokens. This way is possible to edit, add,
- `jwt sign` Specifying a PEM-encoded private key that will be used to sign the jwt

##### Note on signing

When signing a jwt, the supported algorithms are:

- RS256
- RS512

Expand Down Expand Up @@ -486,7 +509,7 @@ The Checks tag is a list of Check elements, which can be defined with:
- `check` checks if the given string is present in the specified message section.
- `check param` specifies the name of the parameter to be checked, depending on the section choosed, the tool will search for the parameter using a pattern. (for the url, it will search for a query parameter, for the head, it will search for a head parameter)
- `check regex` specify a regex that checks the selected content by matching it.
. `use variable` (true or false) set to true if you want to specify a variable name on the following tags, to check wrt to that variable value.
. `use variable` (true or false) set to true if you want to specify a variable name on the following tags, to check wrt to that variable value.
- The actual check on the value, which are self explanatory. (if none of these are specified, the check will only check if the given parameter is present)
- `is`
- `not is`
Expand Down Expand Up @@ -526,7 +549,9 @@ In case a check operation is executed inside an operation that gives a JSON as a
If you need to do a check on an active test, you have to do a `validate` operation, which is basically an operation where you can do checks

### Examples

Check using a variable value: check that the value of the header "Host" is equal to the value of the variable "var1"

```json
"checks" : [
{
Expand All @@ -537,6 +562,7 @@ Check using a variable value: check that the value of the header "Host" is equal
}
]
```

## Preconditions

Preconditions are used in an operation of an active test to check something in the intercepted message before the execution of the message operations. If the checks in the preconditions are evaluated to false, the test is considered unsupported, not failed. Basically preconditions are a list of checks.
Expand Down Expand Up @@ -707,7 +733,7 @@ This passive test checks whether PKCE is used in an OAuth flow, checking if the
{
"test suite": {
"name": "Test Suite 01",
"description": "Only Passive Test",
"description": "Only Passive Test"
},
"tests": [
{
Expand Down Expand Up @@ -883,4 +909,4 @@ Examples: <br>
- Removed validate Operation, now just use checks inside an intercept Operation
- Removed OAuth metadata tag in test
- Added signing of decoded jwt with private key inside Edit Operation
- Added check of signature of jwt inside the decode operation
- Added check of signature of jwt inside the decode operation
5 changes: 5 additions & 0 deletions tool/src/main/java/migt/DecodeOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ public DecodeOperation(JSONObject decode_op_json) throws ParsingException {
check_jwt = true;
jwt.public_key_pem = decode_op_json.getString("jwt check sig");
break;
case "jwe decrypt":
jwt.decrypt = true;
jwt.public_key_pem_enc = decode_op_json.getString("jwe decrypt");
jwt.private_key_pem_enc = decode_op_json.getString("jwe encrypt");
break;
}
}
}
Expand Down
121 changes: 106 additions & 15 deletions tool/src/main/java/migt/JWT.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package migt;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jwt.SignedJWT;
import org.json.JSONException;
Expand All @@ -21,24 +20,35 @@ public class JWT {
public String payload;
public String signature;
public String raw;
// TODO: specify the following tags in parsing
SignedJWT parsed_jwt;

public boolean sign; // put to true if you want to sign the jwt after edit (need private key)
public String private_key_pem; // PEM the private key used to sign the jwt
public String public_key_pem; // PEM public key used to check the signature of the jwt
SignedJWT parsed_jwt;

public boolean decrypt; // put to true if the raw is a JWE, and you want to decrypt it
public String private_key_pem_enc;
public String public_key_pem_enc;
public JWEObject jwe;
EncryptingAlg e_alg;
SigningAlgs signing_alg;

/**
* Constructor that instantiate a JWT object
*/
public JWT() {
this.raw = "";
this.signature = "";
this.header = "";
this.payload = "";
this.sign = false;
this.private_key_pem = "";
this.public_key_pem = "";
raw = "";
signature = "";
header = "";
payload = "";
sign = false;
private_key_pem = "";
public_key_pem = "";
private_key_pem_enc = "";
public_key_pem_enc = "";
decrypt = false;
jwe = null;
e_alg = null;
}

/**
Expand All @@ -50,8 +60,36 @@ public JWT() {
public void parse(String raw_jwt) throws ParsingException {
this.raw = raw_jwt;

if (decrypt) {
// it is a JWE containing a JWT
try {
jwe = JWEObject.parse(raw_jwt);
e_alg = EncryptingAlg.fromString(jwe.getHeader().getAlgorithm().getName());
JWK jwk_private_enc = JWK.parseFromPEMEncodedObjects(private_key_pem_enc);

switch (e_alg) {
case RSA_OAEP:
case RSA_OAEP_256:
jwe.decrypt(new RSADecrypter(jwk_private_enc.toRSAKey()));
break;
case ECDH_ES_A128KW:
case ECDH_ES_A256KW:
jwe.decrypt(new ECDHDecrypter(jwk_private_enc.toECKey()));
break;
}

parsed_jwt = jwe.getPayload().toSignedJWT();
if (parsed_jwt == null) {
throw new ParsingException("Error, JWE payload is not a JWS");
}
} catch (ParseException | JOSEException e) {
throw new ParsingException("problem in decrypting JWE: " + e);
}
}

try {
parsed_jwt = SignedJWT.parse(raw_jwt);
if (!decrypt) // otherwise it is already parsed
parsed_jwt = SignedJWT.parse(raw_jwt);
JWSHeader header = parsed_jwt.getHeader();
signing_alg = SigningAlgs.fromString(header.getAlgorithm().getName());
} catch (ParseException e) {
Expand All @@ -68,7 +106,7 @@ public void parse(String raw_jwt) throws ParsingException {
}

/**
* Check the signature of the jwt using the public_key_pem
* Check the signature of the jws using the public_key_pem
*
* @return true if the jwt signature is valid, false otherwise
* @throws ParsingException if something goes wrong while checking signature of the jwt
Expand Down Expand Up @@ -108,7 +146,7 @@ public boolean check_sig() throws ParsingException {
* Builds a JWT token from the string values in this class
*
* @return A JWT as a string
* @throws ParsingException
* @throws ParsingException if there are problems building the jwt
*/
public String build() throws ParsingException {
String res = "";
Expand Down Expand Up @@ -167,12 +205,40 @@ public String build() throws ParsingException {
}
}

if (decrypt) {
// if the JWE has been decrypted, now it needs to be re-encrypted
if (public_key_pem_enc.length() == 0)
throw new ParsingException("Cannot enctypt jwe, public key not set");

JWEObject editedJWE = new JWEObject(
jwe.getHeader(),
new Payload(res)
);
try {
switch (e_alg) {
case RSA_OAEP:
case RSA_OAEP_256:
editedJWE.encrypt(
new RSAEncrypter(JWK.parseFromPEMEncodedObjects(public_key_pem_enc).toRSAKey()));
break;
case ECDH_ES_A128KW:
case ECDH_ES_A256KW:
editedJWE.encrypt(
new ECDHEncrypter(JWK.parseFromPEMEncodedObjects(public_key_pem_enc).toECKey()));
break;
}
} catch (JOSEException e) {
throw new ParsingException("Unable to encrypt JWE " + e);
}
res = editedJWE.serialize();
}

res = res.replaceAll("=", "");
return res;
}

/**
* All signing algs supported
* All JWS signing algs supported
*/
public enum SigningAlgs {
RS256,
Expand All @@ -189,4 +255,29 @@ public static SigningAlgs fromString(String algStr) throws ParsingException {
}
}
}

/**
* All JWE encrypting algs supported
*/
public enum EncryptingAlg {
RSA_OAEP,
RSA_OAEP_256,
ECDH_ES_A128KW,
ECDH_ES_A256KW;

public static EncryptingAlg fromString(String algStr) throws ParsingException {
switch (algStr) {
case "RSA-OAEP":
return RSA_OAEP;
case "RSA-OAEP-256":
return RSA_OAEP_256;
case "ECDH-ES+A128KW":
return ECDH_ES_A128KW;
case "ECDH-ES+A256KW":
return ECDH_ES_A256KW;
default:
throw new ParsingException("Encrypting algorithm " + algStr + " not supported");
}
}
}
}
Loading

0 comments on commit 1186436

Please sign in to comment.