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

com.identityx.auth.impl.DigestRequestAuthenticator Maven / Gradle / Ivy

Go to download

Client library used for adding authentication to http messages as required by IdentityX Rest Services

There is a newer version: 5.6.0.2
Show newest version
/*
* Copyright Daon.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.identityx.auth.impl;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.SimpleTimeZone;
import java.util.UUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.identityx.auth.def.IApiKey;
import com.identityx.auth.def.ITokenKey;
import com.identityx.auth.def.IRequest;
import com.identityx.auth.def.IRequestAuthenticator;
import com.identityx.auth.support.RequestAuthenticationException;
import com.identityx.auth.util.StringInputStream;


public class DigestRequestAuthenticator extends DigestAuthenticator implements IRequestAuthenticator {

	private static final Log log = LogFactory.getLog(DigestRequestAuthenticator.class);

    @Override
    public void authenticate(IRequest request, IApiKey apiKey) throws RequestAuthenticationException, UnsupportedEncodingException {
        Date date = new Date();
        String nonce = UUID.randomUUID().toString();
        if (!(apiKey instanceof ITokenKey)) {
        	throw new IllegalArgumentException("apiKey must be of type ITokenKey");
        }
        authenticate(request, (ITokenKey)apiKey, date, nonce);
    }

    @Override
    public void authenticate(IRequest request, IApiKey apiKey, final String nonce) throws RequestAuthenticationException, UnsupportedEncodingException {    	    	
        Date date = new Date();
        if (!(apiKey instanceof ITokenKey)) {
        	throw new IllegalArgumentException("apiKey must be of type ITokenKey");
        }        
        authenticate(request, (ITokenKey)apiKey, date, nonce);
    }
    
    /*
     * Prepares the Header for Digest Authentication
     */
    public void authenticate(final IRequest request, final ITokenKey apiKey, final Date date, final String nonce) throws UnsupportedEncodingException {

    	if (request == null) throw new IllegalArgumentException("The parameter request cannot be null");
    	if (apiKey == null) throw new IllegalArgumentException("The parameter apiKey cannot be null");
    	if (nonce == null || nonce.isEmpty()) throw new IllegalArgumentException("The parameter nonce cannot be null or empty");
    	if (date == null) throw new IllegalArgumentException("The parameter date cannot be null or empty");
    	if (nonce.length() > 36) throw new IllegalArgumentException("The parameter nonce cannot be longer than 36 characters");
    	    	    	
    	SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT);
    	timestampFormat.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));
    	String timestamp = timestampFormat.format(date);
    	setCustomHeaders(request, timestamp);
    	    	
    	String authorizationHeader = buildAuthorizationHeader(request, apiKey, date, nonce);
    	
        request.getHeaders().set(AuthSettings.AUTHORIZATION_HEADER, authorizationHeader);
    }

    public String buildAuthorizationHeader(final IRequest request, final ITokenKey apiKey, final Date date, final String nonce) throws UnsupportedEncodingException {

        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        dateFormat.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));

        SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT);
        timestampFormat.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));
        String timestamp = timestampFormat.format(date);
        String dateStamp = dateFormat.format(date);
    	
        String canonicalRequestHashHex = buildCanonicalRequestHashHex(request);
        String signedHeadersString = getSignedHeadersString(request);
        
        return buildAuthorizationHeader(canonicalRequestHashHex, signedHeadersString, apiKey, dateStamp, timestamp, nonce);
    }
    
    public void setCustomHeaders(final IRequest request, final String timeStamp) {
        URI uri = request.getResourceUrl();
        
        request.getHeaders().set(DATE_HEADER, timeStamp);
        
        boolean alreadyHasContentTypeSet = false;
        
        for (String headerName : request.getHeaders().keySet()) {
        	if (headerName.equalsIgnoreCase("content-type")) {
        		alreadyHasContentTypeSet = true;
        		break;
        	}
        }
        
        if(!alreadyHasContentTypeSet){
        	request.getHeaders().set("Content-Type", "application/json");
        }
        
        if (AuthSettings.IDX_ORIGIN_HEADER != null && !AuthSettings.IDX_ORIGIN_HEADER.isEmpty()) {
        	request.getHeaders().set(AuthSettings.IDX_ORIGIN_HEADER, uri.toString());
        }
        if (AuthSettings.CUSTOM_HEADERS != null)
        {
	        for( String name : AuthSettings.CUSTOM_HEADERS.keySet() ){
	        	String value = AuthSettings.CUSTOM_HEADERS.get(name);
	          	request.getHeaders().set(name, value);
	        }
        }

    }
    
    public String buildCanonicalRequestHashHex(final IRequest request) {
    	
        /* 
		 - Timestamp
		 - Client Nonce		        
		 - HTTP Method (GET, POST)
		 - HTTP Resource Path
		 - HTTP Query String
		 - HTTP Header Variables (excluding the Authorization Header)
		 - Request Payload
        */  
    	
    	URI uri = request.getResourceUrl();
    	    	
        String method = request.getMethod().toString();
        String canonicalResourcePath = canonicalizeResourcePath(uri.getPath());
        String canonicalQueryString = canonicalizeQueryString(request);
        String canonicalHeadersString = canonicalizeHeadersString(request);
        String signedHeadersString = getSignedHeadersString(request);
        String requestPayload = getRequestPayload(request);
//        log.debug(requestPayload); // Cannot dump the whole payload as there may be PII data in here that should not be logged
        String requestPayloadHashHex = toHex(hash(requestPayload));

        String canonicalRequest =
                method + NL +
                        canonicalResourcePath + NL +
                        canonicalQueryString + NL +
                        canonicalHeadersString + NL +
                        signedHeadersString + NL +
                        requestPayloadHashHex;

        log.debug("Canonical Request: " + canonicalRequest);        
        String canonicalRequestHashHex = toHex(hash(canonicalRequest));
        
        return canonicalRequestHashHex;
    	
    }

    
    public String buildAuthorizationHeader(final String canonicalRequestHashHex, String signedHeadersString, final ITokenKey apiKey, final String dateStamp, final String timeStamp, final String nonce) 
    		throws UnsupportedEncodingException {
    	
    	//URI uri = request.getResourceUrl();

    	if (log.isTraceEnabled()) {
			log.trace(MessageFormat.format("Timestamp: {0}", new Object[] { timeStamp }));
		}
    	
        String id = apiKey.getId() + "/" + dateStamp + "/" + nonce + "/" + ID_TERMINATOR;
        String stringToSign =
                ALGORITHM + NL +
                        timeStamp + NL +
                        id + NL +
                        canonicalRequestHashHex;

        if (log.isDebugEnabled()) {
        	log.debug("String to Sign: " + stringToSign);
        }
                
        byte[] kDate = apiKey.applySignature((dateStamp + AUTHENTICATION_SCHEME).getBytes(DEFAULT_ENCODING));
        //log.debug("************************** kDate: " + toHex(kDate));
                        
        
        byte[] kNonce = sign(nonce, kDate, MacAlgorithm.HmacSHA256);
        //log.debug("************************** kNonce: " + toHex(kNonce));
        byte[] kSigning = sign(ID_TERMINATOR, kNonce, MacAlgorithm.HmacSHA256);
        //log.debug("************************** kSigning: " + toHex(kSigning));
        byte[] signature = sign(toUtf8Bytes(stringToSign), kSigning, MacAlgorithm.HmacSHA256);
        log.debug("************************** signatureHex: " + toHex(signature));
        
        String signatureHex = toHex(signature);
                
        String authorizationHeader =
                AUTHENTICATION_SCHEME + " " +
                        createNameValuePair(DIGEST_ID, id) + ", " +
                        createNameValuePair(DIGEST_SIGNED_HEADERS, signedHeadersString) + ", " +
                        createNameValuePair(DIGEST_SIGNATURE, signatureHex);

        log.debug("AUTHORIZATION HEADER is " + authorizationHeader);    
        return authorizationHeader;
    	
    }


	public static String getRequestPayload(IRequest request) {
        try {
            InputStream content = request.getBody();
            if (content == null) return "";

            if (content instanceof StringInputStream) {
                return content.toString();
            }

            if (!content.markSupported()) {
                throw new RequestAuthenticationException("Unable to read request payload to authenticate request (mark not supported).");
            }

            content.mark(-1);

            //convert InputStream into a String in one shot:
            String string;
            try {
                string = new Scanner(content, "UTF-8").useDelimiter("\\A").next();
            } catch (NoSuchElementException nsee) {
                string = "";
            }
            content.reset();

            return string;

        } catch (Exception e) {
            throw new RequestAuthenticationException("Unable to read request payload to authenticate request: " + e.getMessage(), e);
        }
    }

    protected String canonicalizeQueryString(IRequest request) {
    	String query = request.getQueryString().toString(true);
    	try {
    		query = java.net.URLDecoder.decode(query, "UTF-8");
    	}
    	catch(Exception ex) {
    		log.error("Failed to decode query string", ex);
    	}    	
    	return query; 
    }

    private String canonicalizeHeadersString(IRequest request) {
        List sortedHeaders = new ArrayList();
                
        sortedHeaders.addAll(request.getHeaders().keySet());
        Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);

        StringBuilder buffer = new StringBuilder();
        for (String header : sortedHeaders) {
 
        	if (header.equalsIgnoreCase("content-length")) {
        		if (request.getHeaders().getContentLength() == 0) {
        			continue;
        		}
        	}
        	
            buffer.append(header.toLowerCase()).append(":");
            List values = request.getHeaders().get(header);
            boolean first = true;
            if (values != null) {
                for(String value : values) {
                    if (!first) {
                        buffer.append(",");
                    }
                    buffer.append(value);
                    first = false;
                }
            }
            buffer.append(NL);
        }

        return buffer.toString();
    }

    
    // content-length header is removed by some server module if 0
    private String getSignedHeadersString(IRequest request) {
        List sortedHeaders = new ArrayList();
        sortedHeaders.addAll(request.getHeaders().keySet());
                
        Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);

        StringBuilder buffer = new StringBuilder();
        for (String header : sortedHeaders) {
        	
        	if (header.equalsIgnoreCase("content-length")) {
        		if (request.getHeaders().getContentLength() == 0) {
        			continue;
        		}
        	}
        	
            if (buffer.length() > 0) buffer.append(";");
            buffer.append(header.toLowerCase());
        }

        if (log.isTraceEnabled()) {
        	log.trace("***** all headers: " + buffer.toString());
        }
        return buffer.toString();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy