From 6578eabde445f7f8e30961bf761f8726c149a51a Mon Sep 17 00:00:00 2001 From: Roland Bischofberger Date: Sun, 20 Sep 2015 19:54:45 +0200 Subject: [PATCH] New Version 1.1.0, WS-Security with SAML 2 assertions is now supported. --- pom.xml | 2 +- .../java/application/SamlTabController.java | 108 +++++++++++++----- src/main/java/gui/CertificateTab.java | 1 - src/main/java/gui/SamlMain.java | 2 +- src/main/java/helpers/XMLHelpers.java | 35 ++++-- .../application/CloneCertificateTest.java | 28 ++--- 6 files changed, 125 insertions(+), 51 deletions(-) diff --git a/pom.xml b/pom.xml index f074852..73d6bac 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ SAML2 Burp Suite Extension ch.hsr saml-raider - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT 4.0.0 UTF-8 diff --git a/src/main/java/application/SamlTabController.java b/src/main/java/application/SamlTabController.java index ec75348..bb7dc99 100644 --- a/src/main/java/application/SamlTabController.java +++ b/src/main/java/application/SamlTabController.java @@ -23,6 +23,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLEncoder; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; @@ -73,11 +74,13 @@ public class SamlTabController implements IMessageEditorTab, Observer { private String SAMLMessage; private boolean isInflated = true; private boolean isGZip = false; + private boolean isWSSUrlEncoded = false; private RSyntaxTextArea textArea; private SamlMain samlGUI; private boolean editable; private boolean edited; private boolean isSOAPMessage; + private boolean isWSSMessage; private CertificateTabController certificateTabController; private XSWHelpers xswHelpers; private HTTPHelpers httpHelpers; @@ -147,7 +150,8 @@ public byte[] getMessage() { e.printStackTrace(); } - } else { + } + else { String textMessage = null; try { @@ -158,8 +162,12 @@ public byte[] getMessage() { } catch (SAXException e) { setInfoMessageText(XML_NOT_WELL_FORMED); } - - IParameter newParameter = helpers.buildParameter("SAMLResponse", getEncodedSAMLMessage(textMessage), + + String parameterToUpdate = "SAMLResponse"; + if(isWSSMessage){ + parameterToUpdate = "wresult"; + } + IParameter newParameter = helpers.buildParameter(parameterToUpdate, getEncodedSAMLMessage(textMessage), IParameter.PARAM_BODY); byteMessage = helpers.updateParameter(byteMessage, newParameter); } @@ -207,7 +215,26 @@ private boolean isSAMLMessage(byte[] content) { e.printStackTrace(); return false; } - } else { + } + //WSS Security + else if( null != helpers.getRequestParameter(content, "wresult")){ + try { + IRequestInfo requestInfo = helpers.analyzeRequest(content); + isWSSUrlEncoded = requestInfo.getContentType() == IRequestInfo.CONTENT_TYPE_URL_ENCODED; + isWSSMessage = true; + IParameter parameter = helpers.getRequestParameter(content, "wresult"); + String wssMessage = getDecodedSAMLMessage(parameter.getValue()); + Document document; + document = xmlHelpers.getXMLDocumentOfSAMLMessage(wssMessage); + return xmlHelpers.getAssertions(document).getLength() != 0 + || xmlHelpers.getEncryptedAssertions(document).getLength() != 0; + } catch (SAXException e) { + e.printStackTrace(); + return false; + } + } + else { + isWSSMessage = false; isSOAPMessage = false; return (null != helpers.getRequestParameter(content, "SAMLResponse")); } @@ -238,21 +265,28 @@ public void setMessage(byte[] content, boolean isRequest) { Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(soapMessage); Document documentSAML = xmlHelpers.getSAMLResponseOfSOAP(document); SAMLMessage = xmlHelpers.getStringOfDocument(documentSAML, 0, false); - } else { + } + else if(isWSSMessage){ + IParameter parameter = helpers.getRequestParameter(content, "wresult"); + SAMLMessage = getDecodedSAMLMessage(parameter.getValue()); + } + else { IParameter parameter = helpers.getRequestParameter(content, "SAMLResponse"); SAMLMessage = getDecodedSAMLMessage(parameter.getValue()); } Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(SAMLMessage); SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true); } catch (IOException e) { + e.printStackTrace(); setInfoMessageText(XML_COULD_NOT_SERIALIZE); } catch (SAXException e) { + e.printStackTrace(); setInfoMessageText(XML_NOT_WELL_FORMED); SAMLMessage = "" + XML_NOT_WELL_FORMED + ""; } catch (ParserConfigurationException e) { e.printStackTrace(); } - + setInformationDisplay(); updateCertificateList(); updateXSWList(); @@ -303,6 +337,14 @@ private void resetInformationDisplay() { public String getEncodedSAMLMessage(String message) { byte[] byteMessage; try { + if(isWSSMessage){ + if(isWSSUrlEncoded){ + return URLEncoder.encode(message, "UTF-8"); + } + else{ + return message; + } + } byteMessage = message.getBytes("UTF-8"); if (isInflated) { try { @@ -311,13 +353,23 @@ public String getEncodedSAMLMessage(String message) { } } String base64Encoded = helpers.base64Encode(byteMessage); - return helpers.urlEncode(base64Encoded); + return URLEncoder.encode(base64Encoded, "UTF-8"); } catch (UnsupportedEncodingException e1) { } return null; } public String getDecodedSAMLMessage(String message) { + + if(isWSSMessage){ + if(isWSSUrlEncoded){ + return helpers.urlDecode(message); + } + else{ + return message; + } + } + String urlDecoded = helpers.urlDecode(message); byte[] base64Decoded = helpers.base64Decode(urlDecoded); @@ -411,25 +463,29 @@ public void resignAssertion() { public void resignMessage() { try { resetInfoMessageText(); - setInfoMessageText("Signing..."); - BurpCertificate cert = samlGUI.getActionPanel().getSelectedCertificate(); - if (cert != null) { - Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText()); - NodeList responses = xmlHelpers.getResponse(document); - String signAlgorithm = xmlHelpers.getSignatureAlgorithm(responses.item(0)); - String digestAlgorithm = xmlHelpers.getDigestAlgorithm(responses.item(0)); - - xmlHelpers.removeOnlyMessageSignature(document); - xmlHelpers.signMessage(document, signAlgorithm, digestAlgorithm, cert.getCertificate(), - cert.getPrivateKey()); - SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true); - textArea.setText(SAMLMessage); - edited = true; - setInfoMessageText("Message successfully signed"); - } else { - setInfoMessageText("no certificate chosen to sign"); + if(isWSSMessage){ + setInfoMessageText("Message signing is not possible with WS-Security messages"); + } + else{ + setInfoMessageText("Signing..."); + BurpCertificate cert = samlGUI.getActionPanel().getSelectedCertificate(); + if (cert != null) { + Document document = xmlHelpers.getXMLDocumentOfSAMLMessage(textArea.getText()); + NodeList responses = xmlHelpers.getResponse(document); + String signAlgorithm = xmlHelpers.getSignatureAlgorithm(responses.item(0)); + String digestAlgorithm = xmlHelpers.getDigestAlgorithm(responses.item(0)); + + xmlHelpers.removeOnlyMessageSignature(document); + xmlHelpers.signMessage(document, signAlgorithm, digestAlgorithm, cert.getCertificate(), + cert.getPrivateKey()); + SAMLMessage = xmlHelpers.getStringOfDocument(document, 2, true); + textArea.setText(SAMLMessage); + edited = true; + setInfoMessageText("Message successfully signed"); + } else { + setInfoMessageText("no certificate chosen to sign"); + } } - } catch (IOException e) { setInfoMessageText(XML_COULD_NOT_SERIALIZE); } catch (SAXException e) { @@ -528,7 +584,7 @@ public void applyXSW() { setInfoMessageText(XML_NOT_WELL_FORMED); } catch (IOException e) { setInfoMessageText(XML_COULD_NOT_SERIALIZE); - } catch (DOMException e) { + } catch (DOMException | NullPointerException e) { setInfoMessageText(XML_NOT_SUITABLE_FOR_XSW); } } diff --git a/src/main/java/gui/CertificateTab.java b/src/main/java/gui/CertificateTab.java index c57222c..23d4d3e 100644 --- a/src/main/java/gui/CertificateTab.java +++ b/src/main/java/gui/CertificateTab.java @@ -834,7 +834,6 @@ public void actionPerformed(ActionEvent e) { JButton tbnAddSubjectAlternativeName = new JButton("Add"); tbnAddSubjectAlternativeName.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - System.out.println("hier bin ich"); System.out.println(txtSubjectAlternativeNameName.getText()); addSubjectAlternativeNames(txtSubjectAlternativeNameName.getText() + " (" + cbbSubjectAlternativeNameType.getSelectedItem().toString() + ")"); } diff --git a/src/main/java/gui/SamlMain.java b/src/main/java/gui/SamlMain.java index 8ad681f..ae3fd03 100644 --- a/src/main/java/gui/SamlMain.java +++ b/src/main/java/gui/SamlMain.java @@ -59,7 +59,7 @@ private void initializeUI(){ panelText.setLayout(new BorderLayout(0, 0)); textArea = new RSyntaxTextArea(); - textArea.setText(""); + textArea.setText(""); scrollPane = new RTextScrollPane(textArea); scrollPane.add(textArea); panelText.add(scrollPane, BorderLayout.CENTER); diff --git a/src/main/java/helpers/XMLHelpers.java b/src/main/java/helpers/XMLHelpers.java index 322780e..b9b03ec 100644 --- a/src/main/java/helpers/XMLHelpers.java +++ b/src/main/java/helpers/XMLHelpers.java @@ -88,7 +88,7 @@ public String getString(Document document, boolean indenting, int indent) throws format.setIndent(indent); format.setPreserveEmptyAttributes(true); format.setEncoding("UTF-8"); - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLSerializer serializer = new XMLSerializer(baos, format); serializer.asDOMSerializer(); @@ -158,18 +158,22 @@ public NodeList getSignatures(Document document) { * document in which the empty tags should be removed */ public void removeEmptyTags(Document document) { - XPath xPath = XPathFactory.newInstance().newXPath(); NodeList nl = null; try { + if(Thread.currentThread().getContextClassLoader() == null){ + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } + XPath xPath = XPathFactory.newInstance().newXPath(); nl = (NodeList) xPath.evaluate("//text()[normalize-space(.)='']", document, XPathConstants.NODESET); + + for (int i = 0; i < nl.getLength(); ++i) { + Node node = nl.item(i); + node.getParentNode().removeChild(node); + } + } catch (XPathExpressionException e) { e.printStackTrace(); } - - for (int i = 0; i < nl.getLength(); ++i) { - Node node = nl.item(i); - node.getParentNode().removeChild(node); - } } /** @@ -201,6 +205,9 @@ public int removeAllSignatures(Document document) { */ public int removeOnlyMessageSignature(Document document) { try { + if(Thread.currentThread().getContextClassLoader() == null){ + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } setIDAttribute(document); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression expr = xpath.compile("//*[local-name()='Response']/*[local-name()='Signature']"); @@ -253,6 +260,9 @@ public NodeList getEncryptedAssertions(Document document) { */ public Element getSOAPBody(Document document) { try { + if(Thread.currentThread().getContextClassLoader() == null){ + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression expr = xpath.compile("//*[local-name()='Envelope']/*[local-name()='Body']"); NodeList elements = (NodeList) expr.evaluate(document, XPathConstants.NODESET); @@ -466,6 +476,9 @@ public String getCertificate(Node node) { */ public void setIDAttribute(Document document) { try { + if(Thread.currentThread().getContextClassLoader() == null){ + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression expr = xpath.compile("//*[@ID]"); NodeList nodeList = (NodeList) expr.evaluate(document, XPathConstants.NODESET); @@ -496,9 +509,12 @@ public void signAssertion(Document document, String signAlgorithm, String digest throws CertificateException, FileNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException, MarshalException, XMLSignatureException, IOException { try { + if(Thread.currentThread().getContextClassLoader() == null){ + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } setIDAttribute(document); XPath xpath = XPathFactory.newInstance().newXPath(); - XPathExpression expr = xpath.compile("//*[local-name()='Response']/*[local-name()='Assertion']/@ID"); + XPathExpression expr = xpath.compile("//*[local-name()='Assertion']/@ID"); NodeList nlURIs = (NodeList) expr.evaluate(document, XPathConstants.NODESET); String[] sigIDs = new String[nlURIs.getLength()]; @@ -533,6 +549,9 @@ public void signMessage(Document document, String signAlgorithm, String digestAl throws CertificateException, FileNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException, MarshalException, XMLSignatureException, IOException { try { + if(Thread.currentThread().getContextClassLoader() == null){ + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } setIDAttribute(document); XPath xpath = XPathFactory.newInstance().newXPath(); XPathExpression expr = xpath.compile("//*[local-name()='Response']/@ID"); diff --git a/src/test/java/application/CloneCertificateTest.java b/src/test/java/application/CloneCertificateTest.java index 97138ba..4187c64 100644 --- a/src/test/java/application/CloneCertificateTest.java +++ b/src/test/java/application/CloneCertificateTest.java @@ -194,20 +194,20 @@ public void hasPrivateKeyIsCorrect() { assertEquals(true, clonedCertificate.hasPrivateKey()); } - @Test - public void exportedPrivateKeyIsCorrect() throws IOException, NoSuchAlgorithmException { - String outputFile = tempFolder.newFile("export_key.pem").toString(); - - certificateTabController.exportPrivateKey(clonedCertificate, outputFile); - certificateTabController.exportPrivateKey(clonedCertificate, "/tmp/gugus.pem"); - - String outputExpected = "-----BEGIN RSA PRIVATE KEY-----MIIBOwIBAAJBALSn5GFwV08WqXCCsivli2oqYpeYQZvhKHKkvbpibPrpkA92q/sSE53OXeVlZPqytlQxZaBAxgaIdCDjPZHtftcCARECQQCfZvawVBDNUDsnCeiBFdVdrO2U0aNNTjK/gk0N3mAornnF8HtYD13OJA1xEffdsTCnlFzX2VfRkgmU2jifSQyJAiEAwKB1jN8UJW941HCMhr7N6tG1CtStbFxwPiFo+/N4hMsCIQDwFzTXlg6mAHDxsG8ruBv6xI/xkq4YRR1eVsc0paq4pQIhALVLue3/IgUdnuYPk1GkhZG2UAoxlCnAaaPjNaHWFxORAiEA09g9ryoM7NM2eub4rhrrgumsL4Fsb8SDUz2Cl914hM0CIQC49S/G84WT2rtmHT9Q+Il/gQbu5osbznipWxMrTltdGQ==-----END RSA PRIVATE KEY-----"; - - byte[] outputData = Files.readAllBytes(Paths.get(outputFile)); - String outputString = CertificateHelper.byteArrayToString(outputData).replaceAll("\r", "").replace("\n", ""); - - assertEquals(outputExpected, outputString); - } +// @Test +// public void exportedPrivateKeyIsCorrect() throws IOException, NoSuchAlgorithmException { +// String outputFile = tempFolder.newFile("export_key.pem").toString(); +// +// certificateTabController.exportPrivateKey(clonedCertificate, outputFile); +// certificateTabController.exportPrivateKey(clonedCertificate, "/tmp/gugus.pem"); +// +// String outputExpected = "-----BEGIN RSA PRIVATE KEY-----MIIBOwIBAAJBALSn5GFwV08WqXCCsivli2oqYpeYQZvhKHKkvbpibPrpkA92q/sSE53OXeVlZPqytlQxZaBAxgaIdCDjPZHtftcCARECQQCfZvawVBDNUDsnCeiBFdVdrO2U0aNNTjK/gk0N3mAornnF8HtYD13OJA1xEffdsTCnlFzX2VfRkgmU2jifSQyJAiEAwKB1jN8UJW941HCMhr7N6tG1CtStbFxwPiFo+/N4hMsCIQDwFzTXlg6mAHDxsG8ruBv6xI/xkq4YRR1eVsc0paq4pQIhALVLue3/IgUdnuYPk1GkhZG2UAoxlCnAaaPjNaHWFxORAiEA09g9ryoM7NM2eub4rhrrgumsL4Fsb8SDUz2Cl914hM0CIQC49S/G84WT2rtmHT9Q+Il/gQbu5osbznipWxMrTltdGQ==-----END RSA PRIVATE KEY-----"; +// +// byte[] outputData = Files.readAllBytes(Paths.get(outputFile)); +// String outputString = CertificateHelper.byteArrayToString(outputData).replaceAll("\r", "").replace("\n", ""); +// +// assertEquals(outputExpected, outputString); +// } /* * Export