/*********************************************************************** * * 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.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.crypto.spec.DHParameterSpec; import org.openid4java.association.Association; import org.openid4java.association.AssociationSessionType; import org.openid4java.association.DiffieHellmanSession; import org.openid4java.message.AssociationRequest; import org.openid4java.message.AssociationResponse; import org.openid4java.message.ParameterList; import org.owasp.webscarab.httpclient.HTTPClientFactory; import org.owasp.webscarab.model.ConversationID; import org.owasp.webscarab.model.ConversationModel; import org.owasp.webscarab.model.FilteredConversationModel; import org.owasp.webscarab.model.FrameworkModel; 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.plugin.AbstractPluginModel; import org.owasp.webscarab.util.Encoding; /** * * @author Frank Cornelis */ public class OpenIdModel extends AbstractPluginModel { private Logger _logger = Logger.getLogger(getClass().getName()); private final FrameworkModel model; private final ConversationModel openIdConversationModel; public OpenIdModel(FrameworkModel model) { this.model = model; this.openIdConversationModel = new FilteredConversationModel(model, model.getConversationModel()) { @Override public boolean shouldFilter(ConversationID id) { return !isOpenIDMessage(id); } }; } public void setOpenIDMessage(ConversationID id, String namespace) { this.model.setConversationProperty(id, "OPENID", namespace); } public boolean isOpenIDMessage(ConversationID id) { return this.model.getConversationProperty(id, "OPENID") != null; } public ConversationModel getOpenIDConversationModel() { return this.openIdConversationModel; } public void setOpenIDMessageType(ConversationID id, String messageType) { this.model.setConversationProperty(id, "OPENID_MODE", messageType); } public String getReadableOpenIDMessageType(ConversationID id) { String openIdMode = this.model.getConversationProperty(id, "OPENID_MODE"); if (null == openIdMode) { return "Unknown"; } if ("checkid_setup".equals(openIdMode)) { return "Request"; } if ("id_res".equals(openIdMode)) { return "Response"; } return "Unknown"; } public List getParameters(ConversationID id) { List parameters = new LinkedList(); Request request = this.model.getRequest(id); NamedValue[] values = null; String method = request.getMethod(); if ("GET".equals(method)) { HttpUrl url = request.getURL(); String query = url.getQuery(); if (null != query) { values = NamedValue.splitNamedValues(query, "&", "="); } } else if ("POST".equals(method)) { byte[] requestContent = request.getContent(); if (requestContent != null && requestContent.length > 0) { String body = new String(requestContent); values = NamedValue.splitNamedValues( body, "&", "="); } } if (null != values) { for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.startsWith("openid.")) { NamedValue parameter = new NamedValue(name, value); parameters.add(parameter); } } } return parameters; } public List getAXFetchRequestAttributes(ConversationID id) { List attributes = new LinkedList(); Request request = this.model.getRequest(id); String method = request.getMethod(); NamedValue[] values = null; if ("GET".equals(method)) { HttpUrl url = request.getURL(); String query = url.getQuery(); if (null != query) { values = NamedValue.splitNamedValues(query, "&", "="); } } else if ("POST".equals(method)) { byte[] requestContent = request.getContent(); if (requestContent != null && requestContent.length > 0) { String body = new String(requestContent); values = NamedValue.splitNamedValues( body, "&", "="); } } if (null != values) { // first locate the AX extension String alias = 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)) { alias = name.substring("openid.ns.".length()); break; } } } if (null == alias) { return attributes; } _logger.info("AX alias: " + alias); // check the AX mode boolean isFetchRequest = false; for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.equals("openid." + alias + ".mode")) { if ("fetch_request".equals(value)) { isFetchRequest = true; break; } } } if (false == isFetchRequest) { return attributes; } // required aliases Set requiredAliases = new HashSet(); for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.equals("openid." + alias + ".required")) { String[] aliases = value.split(","); requiredAliases.addAll(Arrays.asList(aliases)); break; } } // optional aliases Set optionalAliases = new HashSet(); for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.equals("openid." + alias + ".if_available")) { String[] aliases = value.split(","); optionalAliases.addAll(Arrays.asList(aliases)); break; } } // get the fetch request attributes for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.startsWith("openid." + alias + ".type.")) { String attributeAlias = name.substring(("openid." + alias + ".type.").length()); boolean requiredAttribute = requiredAliases.contains(attributeAlias); boolean optionalAttribute = optionalAliases.contains(attributeAlias); AXFetchRequestAttribute attribute = new AXFetchRequestAttribute(value, attributeAlias, requiredAttribute, optionalAttribute); attributes.add(attribute); } } } return attributes; } public List getAXFetchResponseAttributes(ConversationID id) { List attributes = new LinkedList(); Request request = this.model.getRequest(id); String method = request.getMethod(); NamedValue[] values = null; if ("GET".equals(method)) { HttpUrl url = request.getURL(); String query = url.getQuery(); if (null != query) { values = NamedValue.splitNamedValues(query, "&", "="); } } else if ("POST".equals(method)) { byte[] requestContent = request.getContent(); if (requestContent != null && requestContent.length > 0) { String body = new String(requestContent); values = NamedValue.splitNamedValues( body, "&", "="); } } if (null != values) { // first locate the AX extension String alias = 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)) { alias = name.substring("openid.ns.".length()); break; } } } if (null == alias) { return attributes; } _logger.info("AX alias: " + alias); // check the AX mode boolean isFetchResponse = false; for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.equals("openid." + alias + ".mode")) { if ("fetch_response".equals(value)) { isFetchResponse = true; break; } } } if (false == isFetchResponse) { return attributes; } // signed aliases Set signedAliases = new HashSet(); for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.equals("openid.signed")) { String[] aliases = value.split(","); signedAliases.addAll(Arrays.asList(aliases)); break; } } // get the fetch response attributes Map attributeMap = new HashMap(); for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.startsWith("openid." + alias + ".type.")) { String attributeAlias = name.substring(("openid." + alias + ".type.").length()); AXFetchResponseAttribute attribute = (AXFetchResponseAttribute) attributeMap.get(attributeAlias); if (null == attribute) { attribute = new AXFetchResponseAttribute(attributeAlias); attributeMap.put(attributeAlias, attribute); } attribute.setAttributeType(value); } else if (name.startsWith("openid." + alias + ".value.")) { String attributeAlias = name.substring(("openid." + alias + ".value.").length()); AXFetchResponseAttribute attribute = (AXFetchResponseAttribute) attributeMap.get(attributeAlias); if (null == attribute) { attribute = new AXFetchResponseAttribute(attributeAlias); attributeMap.put(attributeAlias, attribute); } attribute.setValue(value); } } attributes.addAll(attributeMap.values()); // check attribute signing Iterator attributeIterator = attributes.iterator(); while (attributeIterator.hasNext()) { AXFetchResponseAttribute attribute = (AXFetchResponseAttribute) attributeIterator.next(); if (signedAliases.contains(alias + ".type." + attribute.getAlias()) && signedAliases.contains(alias + ".value." + attribute.getAlias())) { attribute.setSigned(true); } } } return attributes; } public PAPEResponse getPAPEResponse(ConversationID id) { Request request = this.model.getRequest(id); String method = request.getMethod(); NamedValue[] values = null; if ("GET".equals(method)) { HttpUrl url = request.getURL(); String query = url.getQuery(); if (null != query) { values = NamedValue.splitNamedValues(query, "&", "="); } } else if ("POST".equals(method)) { byte[] requestContent = request.getContent(); if (requestContent != null && requestContent.length > 0) { String body = new String(requestContent); values = NamedValue.splitNamedValues( body, "&", "="); } } if (null == values) { return null; } // first locate the PAPE extension String alias = 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://specs.openid.net/extensions/pape/1.0".equals(value)) { alias = name.substring("openid.ns.".length()); break; } } } if (null == alias) { return null; } // signed aliases Set signedAliases = new HashSet(); for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.equals("openid.signed")) { String[] aliases = value.split(","); signedAliases.addAll(Arrays.asList(aliases)); break; } } PAPEResponse papeResponse = new PAPEResponse(); boolean signed = true; for (int i = 0; i < values.length; i++) { String name = values[i].getName(); String value = Encoding.urlDecode(values[i].getValue()); if (name.startsWith("openid." + alias)) { String expectedSignedAlias = name.substring("openid.".length()); if (false == signedAliases.contains(expectedSignedAlias)) { signed = false; } } if (name.equals("openid." + alias + ".auth_time")) { papeResponse.setAuthenticationTime(value); } else if (name.equals("openid." + alias + ".auth_policies")) { String[] authPolicies = value.split(" "); Set authPoliciesSet = new HashSet(Arrays.asList(authPolicies)); if (authPoliciesSet.contains("http://schemas.openid.net/pape/policies/2007/06/phishing-resistant")) { papeResponse.setPhishingResistant(true); } if (authPoliciesSet.contains("http://schemas.openid.net/pape/policies/2007/06/multi-factor")) { papeResponse.setMultiFactor(true); } if (authPoliciesSet.contains("http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical")) { papeResponse.setMultiFactorPhysical(true); } } } if (false == signedAliases.contains("ns." + alias)) { signed = false; } papeResponse.setSigned(signed); return papeResponse; } public Association establishAssociation(String opUrl, AssociationSessionType associationSessionType) throws Exception { DiffieHellmanSession dhSession; if (null != associationSessionType.getHAlgorithm()) { // Diffie-Hellman DHParameterSpec dhParameterSpec = DiffieHellmanSession.getDefaultParameter(); dhSession = DiffieHellmanSession.create(associationSessionType, dhParameterSpec); } else { dhSession = null; } AssociationRequest associationRequest = AssociationRequest.createAssociationRequest(associationSessionType, dhSession); Request request = new Request(); request.setMethod("POST"); request.setURL(new HttpUrl(opUrl)); request.setHeader("Content-Type", "application/x-www-form-urlencoded"); StringBuilder body = new StringBuilder(); Map parameters = associationRequest.getParameterMap(); Set parameterEntries = parameters.entrySet(); Iterator parameterIterator = parameterEntries.iterator(); while (parameterIterator.hasNext()) { if (0 != body.length()) { body.append("&"); } Map.Entry parameterEntry = (Map.Entry) parameterIterator.next(); body.append(parameterEntry.getKey()); body.append("="); body.append(Encoding.urlEncode((String)parameterEntry.getValue())); } request.setHeader("Content-Length", Integer.toString(body.length())); request.setContent(body.toString().getBytes()); Response response = HTTPClientFactory.getInstance().fetchResponse(request); if (false == "200".equals(response.getStatus())) { throw new RuntimeException("invalid status return code: " + response.getStatus()); } byte[] responseContent = response.getContent(); ParameterList responseParameterList = ParameterList.createFromKeyValueForm(new String(responseContent)); AssociationResponse associationResponse = AssociationResponse.createAssociationResponse(responseParameterList); Association association = associationResponse.getAssociation(dhSession); return association; } public boolean isOpenIDRequestMessage(ConversationID id) { String openIdMode = this.model.getConversationProperty(id, "OPENID_MODE"); if (null == openIdMode) { return false; } if ("checkid_setup".equals(openIdMode)) { return true; } return false; } public String getOPUrl(ConversationID id) { if (false == isOpenIDRequestMessage(id)) { return null; } HttpUrl httpUrl = this.model.getRequestUrl(id); return httpUrl.getSHPP(); } }