/*
* Copyright (C) 2011-2012 Intel Corporation
* All rights reserved.
*/
package com.intel.mtwilson.security.http;
import java.util.ArrayList;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
/**
* This class requires the following libraries:
* org.apache.commons.lang.StringUtils from commons-lang
*
* This class defines the format of the content which is hashed to produce
* the RSA asymmetric-key signature for the "PublicKey" or "X509" authentication
* scheme. This class could probably also
* be used with other signature algorithms. The structure of the document
* to sign is different between MtWilson and PublicKey authentication schemes.
*
* This class allows the inclusion of arbitrary headers (including nonce and
* timestamp) into the signed document, giving the HTTP Agent and Web Service a
* lot of flexibility in determining just what should or shouldn't be included
* in the signed document, without duplicating information from standard
* HTTP headers into the Authorization header.
*
* Mt Wilson 2.0 clients MUST include the Date header in the request
* and cover it with the signature.
*
* @since 0.5.2
* @author jbuhacoff
*/
public class RsaSignatureInput {
public String httpMethod; // the HTTP request method, such as "GET" or "POST"; must not be null
public String url; // including query string if it is intended to be signed, such as "https://server.example.com/path/to/service?withQuery=yes"; it does not have to be absolute, but it does need to match the HTTP Request Line or an X-Original-Request or X-Original-URL header (that substitution should be done OUTSIDE of this class, so the url passed should be the one to be validated)
public String realm; // the authentication realm (must be agreed upon between server and client)
public String fingerprintBase64; // the base-64 encoded fingerprint that identifies the public key or X509 certificate that should be used to verify the signature
public String signatureAlgorithm; // the signature algorithm; currently must be "SHA256withRSA" or "RSA-SHA256"
public String[] headerNames; // headers that should be included in the signature, in order
public Map<String,String> headers; // a map that contains at least the headers referenced in headerNames; headers not referenced will be ignored
public String body; // the request body or empty string if the request does not require a body; must not be null
/**
* Generates the request document to be signed. This document represents the
* request because it includes the Http method, request URL, user-id token,
* selected HTTP headers, signature algorithm.
*
* Any parameters that are null will be represented as an empty string in the signed document.
*
* Sample output:
*
Request: POST http://example.com/some/path
Realm: Attestation
From: fingerprint
Signature-Algorithm: RSA-SHA256
X-Date: 2012-02-14T08:15:00-08:00
X-Nonce: FaaKLOOuyG7/kLVD5vQ7iw==
{"numbers":[1,2,3],"notes":"whatever the payload of the request, whether it's plain text, xml, json, or binary file in text encoding"}
*
* @return
*/
private String document() {
String preamble = String.format("Request: %s %s\nRealm: %s\nFrom: %s\nSignature-Algorithm: %s\n",
valueWithoutNewlinesOrEmptyString(httpMethod),
valueWithoutNewlinesOrEmptyString(url),
valueWithoutNewlinesOrEmptyString(realm),
valueWithoutNewlinesOrEmptyString(fingerprintBase64),
valueWithoutNewlinesOrEmptyString(signatureAlgorithm));
ArrayList<String> httpHeaderList = new ArrayList<>();
for(String headerName : headerNames) {
httpHeaderList.add( String.format("%s: %s\n", headerName, valueWithoutNewlinesOrEmptyString(headers.get(headerName))) );
}
String httpHeaders = StringUtils.join(httpHeaderList, "");
return String.format("%s%s\n%s", preamble, httpHeaders, valueOrEmptyString(body));
}
/**
* This method protects against tampering with the signed document by
* preventing the use of newlines in values that are not supposed to have
* newlines. Only the the HTTP request body is allowed to have newlines.
* All other newlines must be encoded before being used in the document.
* Any value that is null or contains a newline will be returned as an
* empty string, which will probably cause the signature verification to fail
* since it won't match the original document.
*
* @param value
* @return
*/
private String valueWithoutNewlinesOrEmptyString(String value) {
if( value == null ) { return ""; }
if( value.contains("\n") || value.contains("\r") ) { return ""; }
return value;
}
private String valueOrEmptyString(String value) {
if( value == null ) { return ""; }
return value;
}
@Override
public String toString() { return document(); }
}