Skip to content

Commit ce149df

Browse files
1. Added support for unwrapping the key via an HSM when decrypting the SAML assertion.
2. Added 2 optional dependencies to integrate this library with the Azure Key Vault. 3. Modified the decryptAssertion() method in the SamlResponse class in order to check whether an HSM has been configured or not. If yes, it will call the HSM in order to unwrap the key. 4. Modified the settings validations in the Saml2Settings class in order to validate the following scenarios: a. If the library is configured with both a private key and an HSM, an error (use_either_hsm_or_private_key) is thrown. b. If the library is configured to use an HSM, it does not need to check for the SP certificate. 5. Added a new function decryptUsingHsm() in the Util class and moved the validation on the encrypted data to a new function in order to be used both from the decryptUsingHsm() and decryptElement() functions. 6. Added respective tests for the Saml2Settings class and added also the config.hsm.properties file in order to test the scenario when you have the library configured with both the private key and the HSM. 7. Added a new abstract class HSM, which must be used whenever a new HSM needs to be integrated with this library. 8. Added a new class AzureKeyVault which extends the abstract class and implement its abstract methods.
1 parent 2c01106 commit ce149df

File tree

11 files changed

+442
-89
lines changed

11 files changed

+442
-89
lines changed

.nvd-suppressions.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.2.xsd">
3-
3+
<suppress>
4+
<notes></notes>
5+
<filePath regex="true">.*\bmsal4j-1.6.1\.jar</filePath>
6+
<cpe>cpe:/a:http_authentication_library_project:http_authentication_library</cpe>
7+
</suppress>
8+
<suppress>
9+
<notes></notes>
10+
<filePath regex="true">.*\boauth2-oidc-sdk-6.14\.jar</filePath>
11+
<cpe>cpe:/a:openid:openid</cpe>
12+
</suppress>
413
</suppressions>

core/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@
6767
<artifactId>commons-codec</artifactId>
6868
<version>1.15</version>
6969
</dependency>
70+
71+
<!-- Azure Key Vault -->
72+
<dependency>
73+
<groupId>com.azure</groupId>
74+
<artifactId>azure-security-keyvault-keys</artifactId>
75+
<version>4.2.1</version>
76+
<optional>true</optional>
77+
</dependency>
78+
<dependency>
79+
<groupId>com.azure</groupId>
80+
<artifactId>azure-identity</artifactId>
81+
<version>1.0.9</version>
82+
<optional>true</optional>
83+
</dependency>
7084
</dependencies>
7185

7286
<build>

core/src/main/java/com/onelogin/saml2/authn/SamlResponse.java

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.util.Objects;
1111
import javax.xml.parsers.ParserConfigurationException;
1212
import javax.xml.xpath.XPathExpressionException;
13+
14+
import com.onelogin.saml2.model.hsm.HSM;
1315
import org.joda.time.DateTime;
1416
import org.joda.time.Instant;
1517
import org.slf4j.Logger;
@@ -78,7 +80,7 @@ public class SamlResponse {
7880

7981
/**
8082
* After validation, if it fails this property has the cause of the problem
81-
*/
83+
*/
8284
private Exception validationException;
8385

8486
/**
@@ -156,7 +158,7 @@ public void loadXmlFromBase64(String responseStr) throws ParserConfigurationExce
156158

157159
NodeList encryptedAssertionNodes = samlResponseDocument.getElementsByTagNameNS(Constants.NS_SAML,"EncryptedAssertion");
158160

159-
if (encryptedAssertionNodes.getLength() != 0) {
161+
if (encryptedAssertionNodes.getLength() != 0) {
160162
decryptedDocument = Util.copyDocument(samlResponseDocument);
161163
encrypted = true;
162164
decryptedDocument = this.decryptAssertion(decryptedDocument);
@@ -566,20 +568,20 @@ public String getNameIdSPNameQualifier() throws Exception {
566568
* @throws XPathExpressionException
567569
* @throws ValidationError
568570
*
569-
*/
571+
*/
570572
public HashMap<String, List<String>> getAttributes() throws XPathExpressionException, ValidationError {
571573
HashMap<String, List<String>> attributes = new HashMap<String, List<String>>();
572574

573575
NodeList nodes = this.queryAssertion("/saml:AttributeStatement/saml:Attribute");
574-
576+
575577
if (nodes.getLength() != 0) {
576578
for (int i = 0; i < nodes.getLength(); i++) {
577579
NamedNodeMap attrName = nodes.item(i).getAttributes();
578580
String attName = attrName.getNamedItem("Name").getNodeValue();
579581
if (attributes.containsKey(attName) && !settings.isAllowRepeatAttributeName()) {
580582
throw new ValidationError("Found an Attribute element with duplicated Name", ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND);
581583
}
582-
584+
583585
NodeList childrens = nodes.item(i).getChildNodes();
584586

585587
List<String> attrValues = null;
@@ -605,7 +607,7 @@ public HashMap<String, List<String>> getAttributes() throws XPathExpressionExcep
605607

606608
/**
607609
* Returns the ResponseStatus object
608-
*
610+
*
609611
* @return
610612
*/
611613
public SamlResponseStatus getResponseStatus() {
@@ -639,7 +641,7 @@ public void checkStatus() throws ValidationError {
639641
*
640642
* @throws IllegalArgumentException
641643
* if the response not contain status or if Unexpected XPath error
642-
* @throws ValidationError
644+
* @throws ValidationError
643645
*/
644646
public static SamlResponseStatus getStatus(Document dom) throws ValidationError {
645647
String statusXpath = "/samlp:Response/samlp:Status";
@@ -682,7 +684,7 @@ public Boolean checkOneAuthnStatement() throws XPathExpressionException {
682684
* Gets the audiences.
683685
*
684686
* @return the audiences of the response
685-
*
687+
*
686688
* @throws XPathExpressionException
687689
*/
688690
public List<String> getAudiences() throws XPathExpressionException {
@@ -706,8 +708,8 @@ public List<String> getAudiences() throws XPathExpressionException {
706708
*
707709
* @return the issuers of the assertion/response
708710
*
709-
* @throws XPathExpressionException
710-
* @throws ValidationError
711+
* @throws XPathExpressionException
712+
* @throws ValidationError
711713
*/
712714
public List<String> getIssuers() throws XPathExpressionException, ValidationError {
713715
List<String> issuers = new ArrayList<String>();
@@ -763,7 +765,7 @@ public DateTime getSessionNotOnOrAfter() throws XPathExpressionException {
763765
*
764766
* @return the SessionIndex value
765767
*
766-
* @throws XPathExpressionException
768+
* @throws XPathExpressionException
767769
*/
768770
public String getSessionIndex() throws XPathExpressionException {
769771
String sessionIndex = null;
@@ -852,7 +854,7 @@ public ArrayList<String> processSignedElements() throws XPathExpressionException
852854

853855
String responseTag = "{" + Constants.NS_SAMLP + "}Response";
854856
String assertionTag = "{" + Constants.NS_SAML + "}Assertion";
855-
857+
856858
if (!signedElement.equals(responseTag) && !signedElement.equals(assertionTag)) {
857859
throw new ValidationError("Invalid Signature Element " + signedElement + " SAML Response rejected", ValidationError.WRONG_SIGNED_ELEMENT);
858860
}
@@ -862,13 +864,13 @@ public ArrayList<String> processSignedElements() throws XPathExpressionException
862864
if (idNode == null || idNode.getNodeValue() == null || idNode.getNodeValue().isEmpty()) {
863865
throw new ValidationError("Signed Element must contain an ID. SAML Response rejected", ValidationError.ID_NOT_FOUND_IN_SIGNED_ELEMENT);
864866
}
865-
866-
String idValue = idNode.getNodeValue();
867+
868+
String idValue = idNode.getNodeValue();
867869
if (verifiedIds.contains(idValue)) {
868870
throw new ValidationError("Duplicated ID. SAML Response rejected", ValidationError.DUPLICATED_ID_IN_SIGNED_ELEMENTS);
869871
}
870872
verifiedIds.add(idValue);
871-
873+
872874
NodeList refNodes = Util.query(null, "ds:SignedInfo/ds:Reference", signNode);
873875
if (refNodes.getLength() == 1) {
874876
Node refNode = refNodes.item(0);
@@ -878,7 +880,7 @@ public ArrayList<String> processSignedElements() throws XPathExpressionException
878880
if (!sei.equals(idValue)) {
879881
throw new ValidationError("Found an invalid Signed Element. SAML Response rejected", ValidationError.INVALID_SIGNED_ELEMENT);
880882
}
881-
883+
882884
if (verifiedSeis.contains(sei)) {
883885
throw new ValidationError("Duplicated Reference URI. SAML Response rejected", ValidationError.DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS);
884886
}
@@ -958,7 +960,7 @@ public boolean validateSignedElements(ArrayList<String> signedElements) throws X
958960
*
959961
* @return true if still valid
960962
*
961-
* @throws ValidationError
963+
* @throws ValidationError
962964
*/
963965
public boolean validateTimestamps() throws ValidationError {
964966
NodeList timestampNodes = samlResponseDocument.getElementsByTagNameNS("*", "Conditions");
@@ -1026,7 +1028,7 @@ public Exception getValidationException() {
10261028
* Xpath Expression
10271029
*
10281030
* @return the queried node
1029-
* @throws XPathExpressionException
1031+
* @throws XPathExpressionException
10301032
*
10311033
*/
10321034
private NodeList queryAssertion(String assertionXpath) throws XPathExpressionException {
@@ -1075,7 +1077,7 @@ private NodeList queryAssertion(String assertionXpath) throws XPathExpressionExc
10751077
*
10761078
* @param nameQuery
10771079
* Xpath Expression
1078-
* @param context
1080+
* @param context
10791081
* The context node
10801082
*
10811083
* @return DOMNodeList The queried nodes
@@ -1094,13 +1096,13 @@ private NodeList query(String nameQuery, Node context) throws XPathExpressionExc
10941096

10951097
/**
10961098
* Decrypt assertion.
1097-
*
1099+
*
10981100
* @param dom
10991101
* Encrypted assertion
11001102
*
11011103
* @return Decrypted Assertion.
11021104
*
1103-
* @throws XPathExpressionException
1105+
* @throws XPathExpressionException
11041106
* @throws IOException
11051107
* @throws SAXException
11061108
* @throws ParserConfigurationException
@@ -1110,7 +1112,9 @@ private NodeList query(String nameQuery, Node context) throws XPathExpressionExc
11101112
private Document decryptAssertion(Document dom) throws XPathExpressionException, ParserConfigurationException, SAXException, IOException, SettingsException, ValidationError {
11111113
PrivateKey key = settings.getSPkey();
11121114

1113-
if (key == null) {
1115+
HSM hsm = this.settings.getHsm();
1116+
1117+
if (hsm == null && key == null) {
11141118
throw new SettingsException("No private key available for decrypt, check settings", SettingsException.PRIVATE_KEY_NOT_FOUND);
11151119
}
11161120

@@ -1119,7 +1123,13 @@ private Document decryptAssertion(Document dom) throws XPathExpressionException,
11191123
throw new ValidationError("No /samlp:Response/saml:EncryptedAssertion/xenc:EncryptedData element found", ValidationError.MISSING_ENCRYPTED_ELEMENT);
11201124
}
11211125
Element encryptedData = (Element) encryptedDataNodes.item(0);
1122-
Util.decryptElement(encryptedData, key);
1126+
1127+
if (hsm != null) {
1128+
Util.decryptUsingHsm(encryptedData, hsm);
1129+
} else {
1130+
Util.decryptElement(encryptedData, key);
1131+
}
1132+
11231133

11241134
// We need to Remove the saml:EncryptedAssertion Node
11251135
NodeList AssertionDataNodes = Util.query(dom, "/samlp:Response/saml:EncryptedAssertion/saml:Assertion");
@@ -1138,7 +1148,7 @@ private Document decryptAssertion(Document dom) throws XPathExpressionException,
11381148
}
11391149

11401150
/**
1141-
* @return the SAMLResponse XML, If the Assertion of the SAMLResponse was encrypted,
1151+
* @return the SAMLResponse XML, If the Assertion of the SAMLResponse was encrypted,
11421152
* returns the XML with the assertion decrypted
11431153
*/
11441154
public String getSAMLResponseXml() {
@@ -1148,11 +1158,11 @@ public String getSAMLResponseXml() {
11481158
} else {
11491159
xml = samlResponseString;
11501160
}
1151-
return xml;
1161+
return xml;
11521162
}
11531163

11541164
/**
1155-
* @return the SAMLResponse Document, If the Assertion of the SAMLResponse was encrypted,
1165+
* @return the SAMLResponse Document, If the Assertion of the SAMLResponse was encrypted,
11561166
* returns the Document with the assertion decrypted
11571167
*/
11581168
protected Document getSAMLResponseDocument() {

0 commit comments

Comments
 (0)