All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.sdk.cloudplatform.security.AuthTokenDecoder Maven / Gradle / Ivy

/*
 * Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.
 */

package com.sap.cloud.sdk.cloudplatform.security;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.net.HttpHeaders;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.sap.cloud.sdk.cloudplatform.CloudPlatform;
import com.sap.cloud.sdk.cloudplatform.CloudPlatformAccessor;
import com.sap.cloud.sdk.cloudplatform.ScpCfCloudPlatform;
import com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException;
import com.sap.cloud.sdk.cloudplatform.exception.ShouldNotHappenException;
import com.sap.cloud.sdk.cloudplatform.security.exception.AuthTokenAccessException;

class AuthTokenDecoder
{
    private static final String BEARER_PREFIX = "Bearer ";

    private int getNumberOfAuthHeaders( final HttpServletRequest request )
    {
        @Nullable
        final Enumeration headers = request.getHeaders(HttpHeaders.AUTHORIZATION);

        int numHeaders = 0;
        if( headers != null ) {
            while( headers.hasMoreElements() ) {
                headers.nextElement();
                ++numHeaders;
            }
        }

        return numHeaders;
    }

    private ScpCfCloudPlatform getCloudPlatform()
    {
        final CloudPlatform cloudPlatform = CloudPlatformAccessor.getCloudPlatform();

        if( !(cloudPlatform instanceof ScpCfCloudPlatform) ) {
            throw new ShouldNotHappenException(
                "The current Cloud platform is not an instance of "
                    + ScpCfCloudPlatform.class.getSimpleName()
                    + ". Please make sure to specify a dependency to com.sap.cloud.s4hana.cloudplatform:core-scp-cf.");
        }

        return (ScpCfCloudPlatform) cloudPlatform;
    }

    @Nonnull
    private RSAPublicKey getVerificationPublicKey( @Nonnull final String encodedJwt )
        throws AuthTokenAccessException
    {
        final JsonObject xsuaaServiceCredentials;
        try {
            final DecodedJWT decodedJWT = JWT.decode(encodedJwt); // just decode, no verify
            xsuaaServiceCredentials = getCloudPlatform().getXsuaaServiceCredentials(decodedJWT);
        }
        catch( final CloudPlatformException | JWTDecodeException e ) {
            throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
        }

        @Nullable
        final JsonElement verificationKey = xsuaaServiceCredentials.get("verificationkey");

        if( verificationKey == null ) {
            throw new AuthTokenAccessException(
                "Failed to verify JWT bearer: no verification key found in XSUAA service credentials.");
        }

        final String publicKey =
            verificationKey.getAsString().replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace(
                "-----END PUBLIC KEY-----",
                "");

        try {
            final KeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
            return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec);
        }
        catch( final NoSuchAlgorithmException | InvalidKeySpecException e ) {
            throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
        }
    }

    @Nonnull
    private
        Algorithm
        getVerificationAlgorithm( @Nonnull final RSAPublicKey verificationKey, @Nonnull final String tokenValue )
            throws AuthTokenAccessException
    {
        try {
            @Nullable
            final String algorithmIdentifier = JWT.decode(tokenValue).getAlgorithm(); // just decode, no verify

            if( algorithmIdentifier == null ) {
                throw new AuthTokenAccessException(
                    "Failed to verify JWT bearer: no algorithm specified in token header.");
            }

            // limit the supported algorithms to RSA with different hash lengths to avoid the risks described at:
            // https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/

            switch( algorithmIdentifier ) {
                case "RS256":
                    return Algorithm.RSA256(verificationKey, null);
                case "RS384":
                    return Algorithm.RSA384(verificationKey, null);
                case "RS512":
                    return Algorithm.RSA512(verificationKey, null);
                default:
                    throw new AuthTokenAccessException(
                        "Failed to verify JWT bearer: algorithm '" + algorithmIdentifier + "' not supported.");
            }
        }
        catch( final JWTDecodeException | IllegalArgumentException e ) {
            throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
        }
    }

    @Nonnull
    AuthToken decode( @Nonnull final String encodedJwt, @Nullable final String refreshToken )
        throws AuthTokenAccessException
    {
        final RSAPublicKey verificationKey = getVerificationPublicKey(encodedJwt);
        final Algorithm verificationAlgorithm = getVerificationAlgorithm(verificationKey, encodedJwt);

        try {
            final JWTVerifier verifier = JWT.require(verificationAlgorithm).build();
            DecodedJWT jwt;
            try {
                jwt = verifier.verify(encodedJwt);
            }
            catch( final TokenExpiredException e ) {
                jwt = Optional.ofNullable(refreshToken).map(token -> {
                    final String refreshedEncodedJwt = new RefreshJwtTokenCommand(encodedJwt, refreshToken).run();
                    return verifier.verify(refreshedEncodedJwt);
                }).orElseThrow(() -> e);
            }

            return new AuthToken(jwt);
        }
        catch( final IllegalArgumentException | JWTVerificationException e ) {
            throw new AuthTokenAccessException("Failed to verify JWT bearer.", e);
        }
    }

    @Nonnull
    Optional decode( @Nonnull final HttpServletRequest request )
        throws AuthTokenAccessException
    {
        @Nullable
        final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

        if( authorizationHeader == null ) {
            return Optional.empty();
        }

        if( getNumberOfAuthHeaders(request) != 1 ) {
            throw new AuthTokenAccessException(
                "Failed to decode JWT bearer: multiple '"
                    + HttpHeaders.AUTHORIZATION
                    + "' headers present in request.");
        }

        if( !authorizationHeader.startsWith(BEARER_PREFIX) ) {
            throw new AuthTokenAccessException(
                "Failed to decode JWT bearer: no JWT bearer present in '"
                    + HttpHeaders.AUTHORIZATION
                    + "' header of request.");
        }

        final String tokenValue = authorizationHeader.substring(BEARER_PREFIX.length());
        return Optional.of(decode(tokenValue, null));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy