001package com.nimbusds.oauth2.sdk.assertions.saml2; 002 003 004import java.io.ByteArrayInputStream; 005import java.io.IOException; 006import java.security.Key; 007import java.security.PublicKey; 008import java.security.interfaces.RSAPublicKey; 009import java.security.interfaces.ECPublicKey; 010import javax.crypto.SecretKey; 011import javax.xml.parsers.DocumentBuilder; 012import javax.xml.parsers.DocumentBuilderFactory; 013import javax.xml.parsers.ParserConfigurationException; 014 015import com.nimbusds.oauth2.sdk.ParseException; 016import com.nimbusds.oauth2.sdk.id.Issuer; 017import net.jcip.annotations.ThreadSafe; 018import org.opensaml.DefaultBootstrap; 019import org.opensaml.saml2.core.Assertion; 020import org.opensaml.security.SAMLSignatureProfileValidator; 021import org.opensaml.xml.Configuration; 022import org.opensaml.xml.ConfigurationException; 023import org.opensaml.xml.XMLObject; 024import org.opensaml.xml.io.Unmarshaller; 025import org.opensaml.xml.io.UnmarshallerFactory; 026import org.opensaml.xml.io.UnmarshallingException; 027import org.opensaml.xml.security.credential.BasicCredential; 028import org.opensaml.xml.security.credential.UsageType; 029import org.opensaml.xml.signature.Signature; 030import org.opensaml.xml.signature.SignatureValidator; 031import org.opensaml.xml.validation.ValidationException; 032import org.w3c.dom.Document; 033import org.w3c.dom.Element; 034import org.xml.sax.InputSource; 035import org.xml.sax.SAXException; 036 037 038/** 039 * SAML 2.0 assertion validator. Supports RSA signatures and HMAC. Provides 040 * static methods for each validation step for putting together tailored 041 * assertion validation strategies. 042 */ 043@ThreadSafe 044public class SAML2AssertionValidator { 045 046 047 /** 048 * The SAML 2.0 assertion details verifier. 049 */ 050 private final SAML2AssertionDetailsVerifier detailsVerifier; 051 052 053 static { 054 try { 055 DefaultBootstrap.bootstrap(); 056 } catch (ConfigurationException e) { 057 throw new RuntimeException(e.getMessage(), e); 058 } 059 } 060 061 062 /** 063 * Creates a new SAML 2.0 assertion validator. 064 * 065 * @param detailsVerifier The SAML 2.0 assertion details verifier. Must 066 * not be {@code null}. 067 */ 068 public SAML2AssertionValidator(final SAML2AssertionDetailsVerifier detailsVerifier) { 069 if (detailsVerifier == null) { 070 throw new IllegalArgumentException("The SAML 2.0 assertion details verifier must not be null"); 071 } 072 this.detailsVerifier = detailsVerifier; 073 } 074 075 076 /** 077 * Gets the SAML 2.0 assertion details verifier. 078 * 079 * @return The SAML 2.0 assertion details verifier. 080 */ 081 public SAML2AssertionDetailsVerifier getDetailsVerifier() { 082 return detailsVerifier; 083 } 084 085 086 /** 087 * Parses a SAML 2.0 assertion from the specified XML string. 088 * 089 * @param xml The XML string. Must not be {@code null}. 090 * 091 * @return The SAML 2.0 assertion. 092 * 093 * @throws ParseException If parsing of the assertion failed. 094 */ 095 public static Assertion parse(final String xml) 096 throws ParseException { 097 098 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 099 documentBuilderFactory.setNamespaceAware(true); 100 101 XMLObject xmlObject; 102 103 try { 104 DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder(); 105 106 Document document = docBuilder.parse(new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8")))); 107 Element element = document.getDocumentElement(); 108 109 UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); 110 Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element); 111 xmlObject = unmarshaller.unmarshall(element); 112 113 } catch (ParserConfigurationException | IOException | SAXException | UnmarshallingException e) { 114 throw new ParseException("SAML 2.0 assertion parsing failed: " + e.getMessage(), e); 115 } 116 117 if (! (xmlObject instanceof Assertion)) { 118 throw new ParseException("Top-level XML element not a SAML 2.0 assertion"); 119 } 120 121 return (Assertion)xmlObject; 122 } 123 124 125 /** 126 * Verifies the specified XML signature (HMAC, RSA or EC) with the 127 * provided key. 128 * 129 * @param signature The XML signature. Must not be {@code null}. 130 * @param key The key to verify the signature. Should be an 131 * {@link SecretKey} instance for HMAC, 132 * {@link RSAPublicKey} for RSA signatures or 133 * {@link ECPublicKey} for EC signatures. Must not be 134 * {@code null}. 135 * 136 * @throws BadSAML2AssertionException If the key type doesn't match the 137 * signature, or the signature is 138 * invalid. 139 */ 140 public static void verifySignature(final Signature signature, final Key key) 141 throws BadSAML2AssertionException { 142 143 SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); 144 try { 145 profileValidator.validate(signature); 146 } catch (ValidationException e) { 147 throw new BadSAML2AssertionException("Invalid SAML 2.0 signature format: " + e.getMessage(), e); 148 } 149 150 BasicCredential credential = new BasicCredential(); 151 if (key instanceof SecretKey) { 152 credential.setSecretKey((SecretKey)key); 153 } else if (key instanceof PublicKey) { 154 credential.setPublicKey((PublicKey)key); 155 credential.setUsageType(UsageType.SIGNING); 156 } else { 157 throw new BadSAML2AssertionException("Unsupported key type: " + key.getAlgorithm()); 158 } 159 160 SignatureValidator signatureValidator = new SignatureValidator(credential); 161 try { 162 signatureValidator.validate(signature); 163 } catch (ValidationException e) { 164 throw new BadSAML2AssertionException("Bad SAML 2.0 signature: " + e.getMessage(), e); 165 } 166 } 167 168 169 /** 170 * Validates the specified SAML 2.0 assertion. 171 * 172 * @param assertion The SAML 2.0 assertion XML. Must not be 173 * {@code null}. 174 * @param key The key to verify the signature. Should be an 175 * {@link SecretKey} instance for HMAC, 176 * {@link RSAPublicKey} for RSA signatures or 177 * {@link ECPublicKey} for EC signatures. Must not be 178 * {@code null}. 179 * 180 * @return The validated SAML 2.0 assertion. 181 * 182 * @throws BadSAML2AssertionException If the assertion is invalid. 183 */ 184 public Assertion validate(final Assertion assertion, 185 final Issuer expectedIssuer, 186 final Key key) 187 throws BadSAML2AssertionException { 188 189 final SAML2AssertionDetails assertionDetails; 190 191 try { 192 assertionDetails = SAML2AssertionDetails.parse(assertion); 193 } catch (ParseException e) { 194 throw new BadSAML2AssertionException("Invalid SAML 2.0 assertion: " + e.getMessage(), e); 195 } 196 197 // Check the audience and time window details 198 detailsVerifier.verify(assertionDetails); 199 200 // Check the issuer 201 if (! expectedIssuer.equals(assertionDetails.getIssuer())) { 202 throw new BadSAML2AssertionException("Unexpected issuer: " + assertionDetails.getIssuer()); 203 } 204 205 // Verify the signature 206 verifySignature(assertion.getSignature(), key); 207 208 return assertion; // OK 209 } 210 211 212 /** 213 * Validates the specified SAML 2.0 assertion. 214 * 215 * @param xml The SAML 2.0 assertion XML. Must not be {@code null}. 216 * @param key The key to verify the signature. Should be an 217 * {@link SecretKey} instance for HMAC, {@link RSAPublicKey} 218 * for RSA signatures or {@link ECPublicKey} for EC 219 * signatures. Must not be {@code null}. 220 * 221 * @return The validated SAML 2.0 assertion. 222 * 223 * @throws BadSAML2AssertionException If the assertion is invalid. 224 */ 225 public Assertion validate(final String xml, 226 final Issuer expectedIssuer, 227 final Key key) 228 throws BadSAML2AssertionException { 229 230 // Parse string to XML, then to SAML 2.0 assertion object 231 final Assertion assertion; 232 233 try { 234 assertion = parse(xml); 235 } catch (ParseException e) { 236 throw new BadSAML2AssertionException("Invalid SAML 2.0 assertion: " + e.getMessage(), e); 237 } 238 239 return validate(assertion, expectedIssuer, key); 240 } 241}