/***********************************************************************
*
* This file is part of WebScarab, an Open Web Application Security
* Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2011 FedICT
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package org.owasp.webscarab.plugin.openid;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.owasp.webscarab.httpclient.HTTPClient;
import org.owasp.webscarab.model.HttpUrl;
import org.owasp.webscarab.model.NamedValue;
import org.owasp.webscarab.model.Request;
import org.owasp.webscarab.model.Response;
import org.owasp.webscarab.util.Encoding;
/**
*
* @author Frank Cornelis
*/
public class OpenIdHTTPClient implements HTTPClient {
private final HTTPClient httpClient;
private final OpenIdProxyConfig openIdProxyConfig;
public OpenIdHTTPClient(HTTPClient httpClient, OpenIdProxyConfig openIdProxyConfig) {
this.httpClient = httpClient;
this.openIdProxyConfig = openIdProxyConfig;
}
@Override
public Response fetchResponse(Request request) throws IOException {
if (false == this.openIdProxyConfig.doSomething()) {
Response response = this.httpClient.fetchResponse(request);
return response;
}
String openIdProxyHeader = "";
if (this.openIdProxyConfig.doCorruptSignature()) {
openIdProxyHeader += corruptSignature(request);
}
if (this.openIdProxyConfig.doRemoveSignature()) {
openIdProxyHeader += removeSignature(request);
}
if (this.openIdProxyConfig.doRemoveRequestedAttribute()) {
openIdProxyHeader += removeRequestedAttribute(request);
}
if (this.openIdProxyConfig.doAppendAttribute()) {
openIdProxyHeader += appendAttribute(request);
}
if (this.openIdProxyConfig.doRemoveRequestAssociationHandle()) {
openIdProxyHeader += removeRequestAssociationHandle(request);
}
if (this.openIdProxyConfig.doRemoveResponseAssociationHandle()) {
openIdProxyHeader += removeResponseAssociationHandle(request);
}
if (false == openIdProxyHeader.isEmpty()) {
request.addHeader("X-OpenIDProxy", openIdProxyHeader);
}
Response response = this.httpClient.fetchResponse(request);
return response;
}
private NamedValue[] getParameters(Request request) {
String method = request.getMethod();
if ("GET".equals(method)) {
HttpUrl httpUrl = request.getURL();
String query = httpUrl.getQuery();
if (null == query) {
return null;
}
NamedValue[] values = NamedValue.splitNamedValues(query, "&", "=");
return values;
} else if ("POST".equals(method)) {
byte[] requestContent = request.getContent();
if (null == requestContent) {
return null;
}
if (0 == requestContent.length) {
return null;
}
String body = new String(requestContent);
NamedValue[] values = NamedValue.splitNamedValues(
body, "&", "=");
return values;
} else {
return null;
}
}
private String removeSignature(Request request) {
NamedValue[] values = getParameters(request);
if (null == values) {
return "";
}
boolean removedSignature = false;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
if ("openid.sig".equals(name)) {
values[i] = null;
removedSignature = true;
}
if ("openid.signed".equals(name)) {
values[i] = null;
removedSignature = true;
}
}
if (false == removedSignature) {
return "";
}
updateParameters(values, request);
return "remove signature;";
}
private void updateParameters(NamedValue[] values, Request request) {
updateParameters(values, null, request);
}
private void updateParameters(NamedValue[] values, List additionalParameters, Request request) {
String method = request.getMethod();
if ("GET".equals(method)) {
try {
HttpUrl httpUrl = request.getURL();
setNewUrl(httpUrl, values, additionalParameters, request);
} catch (MalformedURLException ex) {
Logger.getLogger(OpenIdHTTPClient.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
// POST
updateRequestPostParameters(values, additionalParameters, request);
}
}
private String corruptSignature(Request request) {
NamedValue[] values = getParameters(request);
if (null == values) {
return "";
}
boolean corruptedSignature = false;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if ("openid.sig".equals(name)) {
byte[] decodedSignature = Base64.decode(value);
decodedSignature[0]++;
String corruptEncodedSignature = new String(Base64.encode(decodedSignature));
values[i] = new NamedValue(name, corruptEncodedSignature);
corruptedSignature = true;
break;
}
}
if (false == corruptedSignature) {
return "";
}
updateParameters(values, request);
return "corrupt signature;";
}
private void updateRequestPostParameters(NamedValue[] values, List additionalParameters, Request request) {
StringBuilder stringBuffer = new StringBuilder();
for (int i = 0; i < values.length; i++) {
if (null == values[i]) {
continue;
}
if (stringBuffer.length() > 1) {
stringBuffer.append("&");
}
stringBuffer.append(values[i].getName());
stringBuffer.append("=");
stringBuffer.append(values[i].getValue());
}
if (null != additionalParameters) {
Iterator additionalAttributesIter = additionalParameters.iterator();
while (additionalAttributesIter.hasNext()) {
NamedValue namedValue = (NamedValue) additionalAttributesIter.next();
if (stringBuffer.length() > 1) {
stringBuffer.append("&");
}
stringBuffer.append(namedValue.getName());
stringBuffer.append("=");
stringBuffer.append(namedValue.getValue());
}
}
byte[] content = stringBuffer.toString().getBytes();
request.setContent(content);
}
private void setNewUrl(HttpUrl httpUrl, NamedValue[] values, Request request) throws MalformedURLException {
setNewUrl(httpUrl, values, null, request);
}
private void setNewUrl(HttpUrl httpUrl, NamedValue[] values, List additionalAttributes, Request request) throws MalformedURLException {
StringBuilder stringBuffer = new StringBuilder("?");
for (int i = 0; i < values.length; i++) {
if (null == values[i]) {
continue;
}
if (stringBuffer.length() > 1) {
stringBuffer.append("&");
}
stringBuffer.append(values[i].getName());
stringBuffer.append("=");
stringBuffer.append(values[i].getValue());
}
if (null != additionalAttributes) {
Iterator additionalAttributesIter = additionalAttributes.iterator();
while (additionalAttributesIter.hasNext()) {
NamedValue namedValue = (NamedValue) additionalAttributesIter.next();
stringBuffer.append("&");
stringBuffer.append(namedValue.getName());
stringBuffer.append("=");
stringBuffer.append(namedValue.getValue());
}
}
request.setURL(new HttpUrl(httpUrl.getSHPP() + stringBuffer.toString()));
}
private String removeRequestAssociationHandle(Request request) {
NamedValue[] values = getParameters(request);
if (null == values) {
return "";
}
// check if OpenID request
boolean openIdRequest = false;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if ("openid.mode".equals(name)) {
if ("checkid_setup".equals(value)) {
openIdRequest = true;
}
break;
}
}
if (false == openIdRequest) {
return "";
}
// remove assoc_handle
boolean assocHandleRemoved = false;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
if ("openid.assoc_handle".equals(name)) {
values[i] = null;
assocHandleRemoved = true;
break;
}
}
if (false == assocHandleRemoved) {
return "";
}
// construct altered response
updateParameters(values, request);
return "removed request assoc_handle;";
}
private String removeResponseAssociationHandle(Request request) {
NamedValue[] values = getParameters(request);
if (null == values) {
return "";
}
// check if OpenID response
boolean openIdResponse = false;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if ("openid.mode".equals(name)) {
if ("id_res".equals(value)) {
openIdResponse = true;
}
break;
}
}
if (false == openIdResponse) {
return "";
}
// remove assoc_handle
boolean assocHandleRemoved = false;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
if ("openid.assoc_handle".equals(name)) {
values[i] = null;
assocHandleRemoved = true;
break;
}
}
if (false == assocHandleRemoved) {
return "";
}
// construct altered response
updateParameters(values, request);
return "removed response assoc_handle;";
}
private String removeRequestedAttribute(Request request) {
NamedValue[] values = getParameters(request);
if (null == values) {
return "";
}
// locate the AX alias
String axAlias = null;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if (name.startsWith("openid.ns.")) {
if ("http://openid.net/srv/ax/1.0".equals(value)) {
axAlias = name.substring("openid.ns.".length());
break;
}
}
}
if (null == axAlias) {
return "";
}
// get set of required AX aliases
Set requiredAttributeAliases = new HashSet();
int requiredIdx = -1;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if (name.equals("openid." + axAlias + ".required")) {
String[] aliases = value.split(",");
requiredAttributeAliases.addAll(Arrays.asList(aliases));
requiredIdx = i;
break;
}
}
// get set of optional AX aliases
Set optionalAttributeAliases = new HashSet();
int optionalIdx = -1;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if (name.equals("openid." + axAlias + ".if_available")) {
String[] aliases = value.split(",");
optionalAttributeAliases.addAll(Arrays.asList(aliases));
optionalIdx = i;
break;
}
}
// remove the attribute
String attributeAlias = null;
String attributeType = this.openIdProxyConfig.getRemoveAttributeType();
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if (name.startsWith("openid." + axAlias + ".type.")) {
if (value.equals(attributeType)) {
attributeAlias = name.substring(("openid." + axAlias + ".type.").length());
values[i] = null; // remove it
break;
}
}
}
if (null == attributeAlias) {
return "";
}
// remove all references to the attribute alias
requiredAttributeAliases.remove(attributeAlias);
Iterator requiredIter = requiredAttributeAliases.iterator();
String requiredValue = "";
while (requiredIter.hasNext()) {
requiredValue += (String) requiredIter.next();
if (requiredIter.hasNext()) {
requiredValue += ",";
}
}
values[requiredIdx] = new NamedValue(values[requiredIdx].getName(), requiredValue);
optionalAttributeAliases.remove(attributeAlias);
Iterator optionalIter = optionalAttributeAliases.iterator();
String optionalValue = "";
while (optionalIter.hasNext()) {
optionalValue += (String) optionalIter.next();
if (optionalIter.hasNext()) {
optionalValue += ",";
}
}
values[optionalIdx] = new NamedValue(values[optionalIdx].getName(), optionalValue);
updateParameters(values, request);
return "removed attribute request;";
}
private String appendAttribute(Request request) {
NamedValue[] values = getParameters(request);
if (null == values) {
return "";
}
// check if openid response
boolean response = false;
for (int idx = 0; idx < values.length; idx++) {
String name = values[idx].getName();
String value = Encoding.urlDecode(values[idx].getValue());
if ("openid.mode".equals(name)) {
if ("id_res".equals(value)) {
response = true;
break;
}
}
}
if (false == response) {
return "";
}
// check if AX response is present
String axAlias = null;
for (int i = 0; i < values.length; i++) {
String name = values[i].getName();
String value = Encoding.urlDecode(values[i].getValue());
if (name.startsWith("openid.ns.")) {
if ("http://openid.net/srv/ax/1.0".equals(value)) {
axAlias = name.substring("openid.ns.".length());
break;
}
}
}
List additionalParameters = new LinkedList();
if (null == axAlias) {
axAlias = "ax";
additionalParameters.add(new NamedValue("openid.ns." + axAlias, "http://openid.net/srv/ax/1.0"));
additionalParameters.add(new NamedValue("openid." + axAlias + ".mode", "fetch_response"));
}
String attributeAlias = this.openIdProxyConfig.getAppendAttributeAlias();
String attributeType = this.openIdProxyConfig.getAppendAttributeType();
String attributeValue = this.openIdProxyConfig.getAppendAttributeValue();
additionalParameters.add(new NamedValue("openid." + axAlias + ".type." + attributeAlias, Encoding.urlEncode(attributeType)));
additionalParameters.add(new NamedValue("openid." + axAlias + ".value." + attributeAlias, Encoding.urlEncode(attributeValue)));
updateParameters(values, additionalParameters, request);
return "add attribute response;";
}
}