com.identityx.auth.impl.DigestRequestAuthenticator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of daon-http-digest-auth Show documentation
Show all versions of daon-http-digest-auth Show documentation
Client library used for adding authentication to http messages as required by IdentityX Rest Services
/*
* 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