Search code examples
.net-corejwtcasjwe

Decode with net core encrypted JWT issued by Apereo CAS


Apereo CAS Single-Sign-On issued the following JWT.

eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.ZXlKNmFYQWlPaUpFUlVZaUxDSmhiR2NpT2lKa2FYSWlMQ0psYm1NaU9pSkJNVEk0UTBKRExVaFRNalUySWl3aWRIbHdJam9pU2xkVUluMC4uREJXMFNUa19OSUVrMmFCVElJNHVnUS5qX3l4a1BVOHNzQ2tCUkdrdjN2RGI0QjFyVDRHWEhzRUJtTlJvTVR0d0hvMjc0NF96cHIzaW1JUk15ek1nU0RIcF9xcXFIQVVnNUNvM3JuM0FTQWUzS1VaMmFsR1B1SmIzT3UxN0hGR2t6a0RDS29CamdHdU9LVHowMlpfVWpYcm1hZXg0RDVyOUt1blNZYWxTWVBDTFU3TncyZEZVWHBwWnlrb3pQdXl6RFFadjZVMDlQaXgxa1gtLW9obUpFVUZJWlN6Vi1DQUs1RTRsSmtWU3ZrZ1gxSmVBMkVjNlY4aFJUSzFIMXRmb2t6Y09nNlJXNElWN05JNVJUQW8ycmlIVlVnLV94czZIY2JFcFZxR0hlVW5QNmF0Nl9zLTFnbGMzTTFzeHBJcmxBN3FEblhPUGt1ZjFmbnpwcEpFVmZWVTV2LWpOUDZQQ0ZnMjA2S2pjeDBYYlRVX0hWN1NyUUhyWUx2Y0gwVWlWVjljZGNOWmJ0Q2hjQjkwX2M2YXVRVmZjeEJZZjE1c2V0M2g3MzA0T0w5RmM5MG5fbXNyV3RIMnVYcGxfUEEwc29IMnJMM0xVV3YyS1E0WE5lR0V5eXpqRXBZWTU3VzJMazZreWQ2UU13RV9ndy5TenQ3WGFqbTBnSy11bk05djl5azdB.V50nzzET85j2FAMRGCLqN1sLXZ8WZrfH0G5__WL6UwvrjAZbvj9tjXAnwcIoBeyFU-zvIsjom520-p2JCNoqEg

I know that it is an encrypted JWT using JWE standard. The encryption key is the following.

9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8

The signing secret is the following.

9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O89O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8

The java code used to correctly decode this JWT is the following.

    public Assertion validate(String ticket) throws TicketValidationException {
        try {
            System.out.println("ticket="+ticket);
            
            final Key key = new AesKey(signingKey.getBytes(StandardCharsets.UTF_8));
            
            final JsonWebSignature jws = new JsonWebSignature();
            jws.setCompactSerialization(ticket);
            jws.setKey(key);
            if (!jws.verifySignature()) {
                throw new TicketValidationException("JWT verification failed");
            }
            
            final byte[] decodedBytes = Base64.decodeBase64(jws.getEncodedPayload().getBytes(StandardCharsets.UTF_8));
            final String decodedPayload = new String(decodedBytes, StandardCharsets.UTF_8);
            
            final JsonWebEncryption jwe = new JsonWebEncryption();
            final JsonWebKey jsonWebKey = JsonWebKey.Factory
                    .newJwk("\n" + "{\"kty\":\"oct\",\n" + " \"k\":\"" + encryptionKey + "\"\n" + "}");
            
            jwe.setCompactSerialization(decodedPayload);
            jwe.setKey(new AesKey(jsonWebKey.getKey().getEncoded()));
            System.out.println("JWT ---> "+jwe.getPlaintextString());
            
            JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE); 
            JSONObject json = (JSONObject) parser.parse(jwe.getPlaintextString());

            return new AssertionImpl(json.getAsString("sub"));
            
        } catch (JoseException | TicketValidationException ex) {
            logger.error(Arrays.toString(ex.getStackTrace()));
        } catch (ParseException ex) {
            java.util.logging.Logger.getLogger(CustomJWTValidator.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

I am trying to decode the same JWT in net core 2.2. The code is the following.

    var encryptionKey = "9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8";
    var jsonWebKey = "\n" + "{\"kty\":\"oct\",\n" + " \"k\":\"" + encryptionKey + "\"\n" + "}";
    var jwkc = new JsonWebKey(jsonWebKey);
    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = false,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O89O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8")),
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = false,
            TokenDecryptionKey = jwkc,
        };
    });

I deliberately disabled the signature validation and any other type of validation. Nevertheless, on JWT validation, I have the following error.

System.ArgumentException: IDX12723: Unable to decode the payload 'ZXlKNmF...5azdB' as Base64Url encoded string. jwtEncodedString: ''. ---> Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: e. Path '', line 0, position 0.

It seems that the payload is not correctly decoded. A lot of googling gave me no results so far.


Solution

  • I was able to fully load, verify the signature and decrypt the token. The problem comes from the keys you received: the encrypton key is already base64url safe encoded when the signature one is not.

    The corresponding JWK are (signature and encryption respectively):

    {"kty":"oct","k":"OU8yMlZkN1FKdTNtQk5oT3k4dndaYVNIMVVQZGllV0FqNGY5c2kycS1PODlPMjJWZDdRSnUzbUJOaE95OHZ3WmFTSDFVUGRpZVdBajRmOXNpMnEtTzg"}

    and

    {"kty":"oct","k":"9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8"}

    EDIT: for the record, I used web-token/jwt-framework (PHP library) and the following script:

    <?php
    
    use Base64Url\Base64Url;
    use Jose\Component\Core\AlgorithmManager;
    use Jose\Component\Core\JWK;
    use Jose\Component\Encryption\Algorithm\ContentEncryption\A128CBCHS256;
    use Jose\Component\Encryption\Algorithm\KeyEncryption\Dir;
    use Jose\Component\Encryption\Compression\CompressionMethodManager;
    use Jose\Component\Encryption\Compression\Deflate;
    use Jose\Component\Encryption\JWEDecrypter;
    use Jose\Component\Encryption\Serializer\CompactSerializer as JweSerializer;
    use Jose\Component\Signature\Algorithm\HS512;
    use Jose\Component\Signature\JWSVerifier;
    use Jose\Component\Signature\Serializer\CompactSerializer as JwsSerializer;
    
    require_once 'vendor/autoload.php';
    
    $token = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.ZXlKNmFYQWlPaUpFUlVZaUxDSmhiR2NpT2lKa2FYSWlMQ0psYm1NaU9pSkJNVEk0UTBKRExVaFRNalUySWl3aWRIbHdJam9pU2xkVUluMC4uREJXMFNUa19OSUVrMmFCVElJNHVnUS5qX3l4a1BVOHNzQ2tCUkdrdjN2RGI0QjFyVDRHWEhzRUJtTlJvTVR0d0hvMjc0NF96cHIzaW1JUk15ek1nU0RIcF9xcXFIQVVnNUNvM3JuM0FTQWUzS1VaMmFsR1B1SmIzT3UxN0hGR2t6a0RDS29CamdHdU9LVHowMlpfVWpYcm1hZXg0RDVyOUt1blNZYWxTWVBDTFU3TncyZEZVWHBwWnlrb3pQdXl6RFFadjZVMDlQaXgxa1gtLW9obUpFVUZJWlN6Vi1DQUs1RTRsSmtWU3ZrZ1gxSmVBMkVjNlY4aFJUSzFIMXRmb2t6Y09nNlJXNElWN05JNVJUQW8ycmlIVlVnLV94czZIY2JFcFZxR0hlVW5QNmF0Nl9zLTFnbGMzTTFzeHBJcmxBN3FEblhPUGt1ZjFmbnpwcEpFVmZWVTV2LWpOUDZQQ0ZnMjA2S2pjeDBYYlRVX0hWN1NyUUhyWUx2Y0gwVWlWVjljZGNOWmJ0Q2hjQjkwX2M2YXVRVmZjeEJZZjE1c2V0M2g3MzA0T0w5RmM5MG5fbXNyV3RIMnVYcGxfUEEwc29IMnJMM0xVV3YyS1E0WE5lR0V5eXpqRXBZWTU3VzJMazZreWQ2UU13RV9ndy5TenQ3WGFqbTBnSy11bk05djl5azdB.V50nzzET85j2FAMRGCLqN1sLXZ8WZrfH0G5__WL6UwvrjAZbvj9tjXAnwcIoBeyFU-zvIsjom520-p2JCNoqEg';
    $signatureKey = new JWK([
        'kty' => 'oct',
        'k' => Base64Url::encode('9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O89O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8'),
    ]);
    $encryptionKey = new JWK([
        'kty' => 'oct',
        'k' => '9O22Vd7QJu3mBNhOy8vwZaSH1UPdieWAj4f9si2q-O8',
    ]);
    
    $jwsSerializer = new JwsSerializer();
    $jws = $jwsSerializer->unserialize($token);
    dump($jws);
    
    $jwsVerifier = new JWSVerifier(new AlgorithmManager([
            new HS512()
    ]));
    $signatureIsVerified = $jwsVerifier->verifyWithKey($jws, $signatureKey, 0);
    if (!$signatureIsVerified) {
        exit('Invalid signature');
    }
    dump('The signature is valid');
    
    $nestedToken = $jws->getPayload();
    
    $jweSerializer = new JweSerializer();
    $jwe = $jweSerializer->unserialize($nestedToken);
    dump($jwe);
    
    $jweDecrypter = new JWEDecrypter(
        new AlgorithmManager([new Dir()]),
        new AlgorithmManager([new A128CBCHS256()]),
        new CompressionMethodManager([new Deflate()])
    );
    $decryptionSuccess = $jweDecrypter->decryptUsingKey($jwe, $encryptionKey, 0);
    if (!$decryptionSuccess) {
        exit('Unable to decrypt the token');
    }
    dump('The token has been decrypted');
    dump($jwe->getPayload());
    
    dump(json_encode($signatureKey), json_encode($encryptionKey));