Skip to content

Commit 53dfed1

Browse files
committed
Fix signature verification on RedirectBinding, add security measures for DSig signature validation
1 parent 762b0b9 commit 53dfed1

File tree

5 files changed

+100
-1
lines changed

5 files changed

+100
-1
lines changed

gamsaml20/src/main/java/com/genexus/saml20/RedirectBinding.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.bouncycastle.crypto.Signer;
77
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
88
import org.bouncycastle.crypto.signers.RSADigestSigner;
9+
import org.bouncycastle.jcajce.provider.asymmetric.RSA;
910
import org.bouncycastle.util.encoders.Base64;
1011
import org.w3c.dom.Document;
1112

@@ -14,6 +15,7 @@
1415
import java.net.URLDecoder;
1516
import java.net.URLEncoder;
1617
import java.nio.charset.StandardCharsets;
18+
import java.security.cert.X509Certificate;
1719
import java.text.MessageFormat;
1820
import java.util.HashMap;
1921
import java.util.Map;
@@ -100,6 +102,36 @@ public String getLoginAttribute(String name) {
100102

101103
// EXTERNAL OBJECT PUBLIC METHODS - END
102104

105+
private boolean VerifySignature_internal(String certPath, String certPass, String certAlias) {
106+
logger.trace("VerifySignature_internal");
107+
108+
byte[] signature = Encoding.decodeParameter(this.redirectMessage.get("Signature"));
109+
110+
String signedMessage;
111+
if (this.redirectMessage.containsKey("RelayState")) {
112+
signedMessage = MessageFormat.format("SAMLResponse={0}", this.redirectMessage.get("SAMLResponse"));
113+
signedMessage += MessageFormat.format("&RelayState={0}", this.redirectMessage.get("RelayState"));
114+
signedMessage += MessageFormat.format("&SigAlg={0}", this.redirectMessage.get("SigAlg"));
115+
} else {
116+
signedMessage = MessageFormat.format("SAMLResponse={0}", this.redirectMessage.get("SAMLResponse"));
117+
signedMessage += MessageFormat.format("&SigAlg={0}", this.redirectMessage.get("SigAlg"));
118+
}
119+
120+
byte[] query = signedMessage.getBytes(StandardCharsets.UTF_8);
121+
122+
X509Certificate cert = Keys.loadCertificate(certPath, certPass, certAlias);
123+
124+
try (InputStream inputStream = new ByteArrayInputStream(query)) {
125+
String sigalg = URLDecoder.decode(this.redirectMessage.get("SigAlg"), StandardCharsets.UTF_8.name());
126+
RSADigestSigner signer = new RSADigestSigner(Hash.getDigest(Hash.getHashFromSigAlg(sigalg)));
127+
setUpSigner(signer, inputStream, Keys.getAsymmetricKeyParameter(cert), false);
128+
return signer.verifySignature(signature);
129+
} catch (Exception e) {
130+
logger.error("VerifySignature_internal", e);
131+
return false;
132+
}
133+
}
134+
103135
private static Map<String, String> parseRedirect(String request) {
104136
logger.trace("parseRedirect");
105137
Map<String, String> result = new HashMap<>();

gamsaml20/src/main/java/com/genexus/saml20/utils/DSig.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
import javax.xml.xpath.XPathFactory;
1515
import java.security.cert.X509Certificate;
1616
import java.text.MessageFormat;
17+
import java.util.ArrayList;
18+
import java.util.Arrays;
19+
import java.util.List;
1720

1821
public class DSig {
1922

@@ -26,7 +29,10 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin
2629
NodeList nodes = findElementsByPath(xmlDoc, "//*[@ID]");
2730

2831
NodeList signatures = xmlDoc.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE);
29-
32+
//check the message is signed - security measure
33+
if(signatures.getLength() == 0){
34+
return false;
35+
}
3036
for (int i = 0; i < signatures.getLength(); i++) {
3137
Element signedElement = findNodeById(nodes, getSignatureID((Element) signatures.item(i)));
3238
if (signedElement == null) {
@@ -35,6 +41,10 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin
3541
signedElement.setIdAttribute("ID", true);
3642
try {
3743
XMLSignature signature = new XMLSignature((Element) signatures.item(i), "");
44+
//verifies the signature algorithm is one expected - security meassure
45+
if (!verifySignatureAlgorithm((Element) signatures.item(i))) {
46+
return false;
47+
}
3848
if (!signature.checkSignatureValue(cert)) {
3949
return false;
4050
}
@@ -46,6 +56,21 @@ public static boolean validateSignatures(Document xmlDoc, String certPath, Strin
4656
return true;
4757
}
4858

59+
private static boolean verifySignatureAlgorithm(Element elem) {
60+
logger.trace("verifySignatureAlgorithm");
61+
NodeList signatureMethod = elem.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATUREMETHOD);
62+
String signatureAlgorithm = signatureMethod.item(0).getAttributes().getNamedItem(Constants._ATT_ALGORITHM).getNodeValue();
63+
logger.debug(MessageFormat.format("verifySignatureAlgorithm - algorithm: {0}", signatureAlgorithm));
64+
String[] algorithm = signatureAlgorithm.split("#");
65+
List<String> validAlgorithms = Arrays.asList("rsa-sha1", "rsa-sha256", "rsa-sha512");
66+
for (String alg : validAlgorithms) {
67+
if (algorithm[1].trim().equals(alg)) {
68+
return true;
69+
}
70+
}
71+
logger.error(MessageFormat.format("verifySignatureAlgorithm - Invalid Signature algorithm {0}", algorithm[1]));
72+
return false;
73+
}
4974

5075
private static String getSignatureID(Element signatureElement) {
5176
return signatureElement.getElementsByTagNameNS(Constants.SignatureSpecNS, Constants._TAG_REFERENCE).item(0).getAttributes().getNamedItem(Constants._ATT_URI).getNodeValue();

gamsaml20/src/main/java/com/genexus/saml20/utils/Encoding.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,16 @@ public static String documentToString(Document doc) {
7373
return null;
7474
}
7575
}
76+
77+
public static byte[] decodeParameter(String parm) {
78+
logger.trace("decodeParameter");
79+
try {
80+
String base64 = URLDecoder.decode(parm, StandardCharsets.UTF_8.name());
81+
return Base64.decode(base64);
82+
} catch (Exception e) {
83+
logger.error("decodeParameter", e);
84+
return null;
85+
}
86+
87+
}
7688
}

gamsaml20/src/main/java/com/genexus/saml20/utils/Hash.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,19 @@ public static Digest getDigest(Hash hash) {
7272
return null;
7373
}
7474
}
75+
76+
public static Hash getHashFromSigAlg(String sigAlg) {
77+
logger.trace("getHashFromSigAlg");
78+
switch (sigAlg.trim()) {
79+
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha1":
80+
return SHA1;
81+
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256":
82+
return SHA256;
83+
case "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512":
84+
return SHA512;
85+
default:
86+
logger.error(MessageFormat.format("getHashFromSigAlg - not implemented signature algorithm: {0}", sigAlg));
87+
return null;
88+
}
89+
}
7590
}

gamsaml20/src/main/java/com/genexus/saml20/utils/Keys.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import org.bouncycastle.asn1.ASN1InputStream;
66
import org.bouncycastle.asn1.ASN1Sequence;
77
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
8+
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
89
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
910
import org.bouncycastle.crypto.util.PrivateKeyFactory;
11+
import org.bouncycastle.crypto.util.PublicKeyFactory;
1012
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
1113
import org.bouncycastle.util.encoders.Base64;
1214

@@ -18,6 +20,7 @@
1820
import java.nio.file.Path;
1921
import java.security.KeyStore;
2022
import java.security.PrivateKey;
23+
import java.security.PublicKey;
2124
import java.security.cert.X509Certificate;
2225
import java.text.MessageFormat;
2326

@@ -33,6 +36,18 @@ public static X509Certificate loadCertificate(String path, String alias, String
3336
return isBase64(path) ? loadCertificateFromBase64(path) : loadCertificateFromJKS(path, alias, password);
3437
}
3538

39+
public static AsymmetricKeyParameter getAsymmetricKeyParameter(X509Certificate cert) {
40+
logger.trace("getAsymmetricKeyParameter");
41+
try {
42+
PublicKey publicKey = cert.getPublicKey();
43+
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
44+
return PublicKeyFactory.createKey(subjectPublicKeyInfo);
45+
} catch (Exception e) {
46+
logger.error("getAsymmetricKeyParameter", e);
47+
return null;
48+
}
49+
}
50+
3651
private static String getCertPath(String path) {
3752
//boolean isAbsolute = new File(path).isAbsolute();
3853

0 commit comments

Comments
 (0)