diff --git a/core/src/main/java/org/codelibs/saml2/core/util/SchemaFactory.java b/core/src/main/java/org/codelibs/saml2/core/util/SchemaFactory.java index b95b755..8497f83 100644 --- a/core/src/main/java/org/codelibs/saml2/core/util/SchemaFactory.java +++ b/core/src/main/java/org/codelibs/saml2/core/util/SchemaFactory.java @@ -5,6 +5,8 @@ import java.util.Locale; import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import org.slf4j.Logger; @@ -36,6 +38,17 @@ private SchemaFactory() { /** URL of the SAML 2.0 protocol schema. */ public static final URL SAML_SCHEMA_PROTOCOL_2_0 = SchemaFactory.class.getResource("/schemas/saml-schema-protocol-2.0.xsd"); + /** + * URL of the XML Encryption 1.1 schema, which is added to every schema set built here. + * + *

Nothing in the SAML schemas imports this namespace, but {@code xenc:EncryptionMethod} + * ends in a wildcard whose {@code processContents} is {@code strict}, so any element carried + * there has to be declared somewhere in the schema set or the document is invalid. An IdP + * encrypting with XML Encryption 1.1 puts {@code } in exactly that position, and + * without this schema the whole response is refused before it is decrypted.

+ */ + private static final URL XML_ENCRYPTION_SCHEMA_1_1 = SchemaFactory.class.getResource("/schemas/xenc-schema-11.xsd"); + /** * Loads a {@link Schema} from the given URL, resolving referenced schemas and DTDs locally. * @@ -66,6 +79,7 @@ public LSInput resolveResource(final String type, final String namespaceURI, fin case "urn:oasis:names:tc:SAML:metadata:algsupport" -> getLocalResource("sstc-saml-metadata-algsupport-v1.0.xsd"); case "urn:oasis:names:tc:SAML:metadata:ui" -> getLocalResource("sstc-saml-metadata-ui-v1.0.xsd"); case "http://www.w3.org/2001/04/xmlenc#" -> getLocalResource("xenc-schema.xsd"); + case "http://www.w3.org/2009/xmlenc11#" -> getLocalResource("xenc-schema-11.xsd"); case "http://www.w3.org/XML/1998/namespace" -> getLocalResource("xml.xsd"); case "http://www.w3.org/2000/09/xmldsig#" -> getLocalResource("xmldsig-core-schema.xsd"); default -> null; @@ -108,6 +122,12 @@ public LSInput getLocalResource(final String name) throws Exception { return lsInput; } }); - return factory.newSchema(schemaUrl); + if (XML_ENCRYPTION_SCHEMA_1_1 == null) { + // the schema is packaged next to the others; carry on without it rather than fail + LOGGER.warn("xenc-schema-11.xsd is missing, so XML Encryption 1.1 elements will not validate"); + return factory.newSchema(schemaUrl); + } + return factory.newSchema(new Source[] { new StreamSource(schemaUrl.toExternalForm()), + new StreamSource(XML_ENCRYPTION_SCHEMA_1_1.toExternalForm()) }); } } diff --git a/core/src/main/resources/schemas/xenc-schema-11.xsd b/core/src/main/resources/schemas/xenc-schema-11.xsd new file mode 100644 index 0000000..0550c3b --- /dev/null +++ b/core/src/main/resources/schemas/xenc-schema-11.xsd @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/test/java/org/codelibs/saml2/core/test/util/UtilsTest.java b/core/src/test/java/org/codelibs/saml2/core/test/util/UtilsTest.java index ac7d2b4..967d23d 100644 --- a/core/src/test/java/org/codelibs/saml2/core/test/util/UtilsTest.java +++ b/core/src/test/java/org/codelibs/saml2/core/test/util/UtilsTest.java @@ -156,6 +156,31 @@ public void testLoadXML() throws URISyntaxException, IOException { * * @see org.codelibs.saml2.core.core.util.Util#validateXML */ + /** + * Tests the ValidateXML method + * Case: Validates a response encrypted with XML Encryption 1.1 + * + * @throws Exception + * + * @see org.codelibs.saml2.core.core.util.Util#validateXML + */ + @Test + public void testValidateXMLEncryptionMethodFromXmlEnc11() throws Exception { + // An IdP encrypting with XML Encryption 1.1 puts inside + // , whose wildcard is strict: unless the 1.1 schema is part of the + // schema set, the whole response is invalid and is refused before it is ever decrypted. + String response = + new String(Base64.decodeBase64(Util.getFileAsString("data/responses/valid_encrypted_assertion_xmlenc11.xml.base64"))); + assertThat(response, containsString("xenc11:MGF")); + + assertTrue(Util.validateXML(Util.loadXML(response), SchemaFactory.SAML_SCHEMA_PROTOCOL_2_0)); + + // and the namespace is known rather than waved through: an element that does not exist in + // it is still refused, which is what tells this apart from switching validation off + String bogus = response.replace("xenc11:MGF", "xenc11:NoSuchElement"); + assertFalse(Util.validateXML(Util.loadXML(bogus), SchemaFactory.SAML_SCHEMA_PROTOCOL_2_0)); + } + @Test public void testValidateXMLBadFormat() throws Exception { String metadataUnloaded = ""; diff --git a/core/src/test/resources/data/responses/valid_encrypted_assertion_xmlenc11.xml.base64 b/core/src/test/resources/data/responses/valid_encrypted_assertion_xmlenc11.xml.base64 new file mode 100644 index 0000000..fb3cd82 --- /dev/null +++ b/core/src/test/resources/data/responses/valid_encrypted_assertion_xmlenc11.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfNWY0NjgyNDk2MDkwNDBjNmEzNTFhYzFiZTBlOWZjNjA1MzNmZjA5ZDNkIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wMy0yOVQxMjowMTo1N1oiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjgwODAvamF2YS1zYW1sLWpzcHNhbXBsZS9hY3MuanNwIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOX2JlNjBiOGNhZjhlOWQxOWI3YTM1NTFiMjQ0ZjExNmM5NDdmZjI0N2QiPg0KICAgIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogICAgPHNhbWxwOlN0YXR1cz4NCiAgICAgICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICAgIDwvc2FtbHA6U3RhdHVzPg0KICAgIA0KPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwOS94bWxlbmMxMSNhZXMyNTYtZ2NtIi8+PGRzaWc6S2V5SW5mbyB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXk+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjcnNhLW9hZXAiPjxkc2lnOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3NoYTI1NiIvPjx4ZW5jMTE6TUdGIHhtbG5zOnhlbmMxMT0iaHR0cDovL3d3dy53My5vcmcvMjAwOS94bWxlbmMxMSMiIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwOS94bWxlbmMxMSNtZ2Yxc2hhMjU2Ii8+PC94ZW5jOkVuY3J5cHRpb25NZXRob2Q+PHhlbmM6Q2lwaGVyRGF0YT48eGVuYzpDaXBoZXJWYWx1ZT5lWURRZ1cwK2IrNUp3Z3lwUmZRSWVKa0JxcXpZSDBvSjQ5eDBhQ0ttem91UlEzK1IrelRTMEZxdDM1N0tqRkUrcmNjM0ViMWJ6Y3NEM3doTlVXQmVrMzJlbDVLWit0eklxMUl1RWtQNG0wOTNjOUhlWE1GSVY1VzZkQjl2Z1JTbXVXeUZRemp3d0k2VXY3ME1PQ2lnUGVUZmp4Qk1MVlBLYjRLSzFLS0hkOUE9PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWRLZXk+PC9kc2lnOktleUluZm8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+KytvZDArZzhDV2tCOEE3R3NTb004aVQ2a0xkK2JBTE1adnNhWnRVTFM4cXdKdGNCV29ycTF1VjFvR093dzVnb2s3NEdnQzFpbUhXd2ZYRTdDUklBZkxFU1F3N2VrZkZBZ09ESGtVcTVaM3d0OFIzSUgxMHVDaUg0MHVDQjJiT2hoeFpqSUtUR0oxMlEreFZYRUZXYXZEdlp4alhRSkM1a2RPMTFDVUhicnNiUTE5MG0xdkROUUpjTDRBV3FtWFloa1JLeGhKS3k3V3Rpc2dCR2NWZWdyWlY4dmlvZGN2SHRXVXBrZ0JGa2ZVSlVwZ0hIVlJhK0xMMnJLRGNNTFNGSmVtdjhGc1A4ME8waEZ1NlRWa2t5S3dTL3lja3dTQ2ZpTGFnRUsvdHFtd0M0cU0xd2l4OEdTQWRmaWZQODNuT1JMbEdZNkowd2RZdGVuYi9pWStnd213UDdib3Q1b0JpZm10ZTYySjcvNk9CbHlua3c3QkR4dU1rMFVyZ1h5Nk5JVnFaUHFNTzY1RzZtV2haS0E1ZTF3K3c0MFprZGkyQ2E1cTg4Tm9FOEJCVVBnNysycjZxczM0VlVpQ0Q0QWRvNEJqOWNaOTd4QVVzYTlPMHVEbmR5UWV1RFVNM2dkaGk5RzRXNmIwL3VNOUJKZm10VmN5VUZBb0xRMU10QmtZamNic2JwS0FGbUlQVXNta1JsNlkvSWl6eGNXQmhYOUQrR3U3bmFvTWdEOVcrL09yNU5Fczlob0Fnb3N5MGR0K1kxZCtLUHhFd0ppS0VJcTdqT211cDZWbVMxektqZ25PUmwvRDRJNWkwQjFJY1VRbWdTS3JrL25EWG1panZNVUpZOFN5MEhhMk96NVNkMFhDRFdUamN4cnAzRkQwVmxNTnV1UTBOQXprMmI4QVhXejY5VllESUV6SmZ4OVZFaVlOd3NOdWNlVGtzUVk5K1c3dThGbk5HQjdkL05sZ0NPQkxid3JJRHNXZ2tkN083NW4rdmJKWmsza2FHd1dpbWcrNHpCckt2TFh1UkIrdjBGUGl2bUk0c1Z0WENOZFhQekVXb1RpTE5ic29ZWkkvNE12SnlxdnA5cTZVanF1R0hIcWVCVUtqa0lHM3NWWmpiVCtNckZWOFArRXFiSVMyZkJOOEF3dDI5Ri9paHFQdUVtL1REWTRvcmFIMm5vajlPWnlQRktWNzNGM0VQV3JxaXFzZkdmcEVLRjQrZFJRUXhXK01ERkFZQlZMZmlPSDFEYzZYblY4SVlUNHhsbjY3SmV4SGZEdUVtK3hUcHVGTi9ENGpQZDFWWTQydlovVGZCQURkQmU0QTQyeHdWMTF5YnZDcVBJbHJuNWlhenNiVFN5OFJUaUkvSi9KbVFmNVg1eVlJMVpnUWZoQnB0UzlIUk9JWnFZVm5KWjlsL2xxTUFGQzhuWkMyV3hiM0pyT1Y3NjZINE1aUkFGWTdlaDBqSkJrMFRsemdaMncxdXE4TkVhUVlsNnZjYVAwUVc1TEEzTDdvR0tSTHB6clQzbEZiOHdEUmxxeTNnL3hDdFpYQ1hQWm5qTTJ4di8zRy8zV2R0NlFkZ0xPQmRQSU5uRlljb1VRQlJjOE9Lc3NGWEl3eTdBTmVkZ3NTbGhtWnljZks2WUNRaXBGRDRRcmRYSklXTmwzRVRXR3BJdnlaN1hmcXFlcUxINnZ3OWRjWDEvRi9KVUJjWWpHelpMRldvbVhXeDBLZDBUTTNIMlVubWhJdW0zU3kwY2ZXcSszTVBwamJUQUQ5Y3FSMnQ2OGhOcU9sazYxelJRS3J2N1Y0WXF1S1dVdUVBYVVXS1cydVFwTlBKcjJqWE5mNWdvSGhXTzZvK014ZUFNVkoyQ0J6T0VSd2tqVmI4NkNMallUUVQ0bU1uaW52eUhER3BhSkxvbk04YmpueHBjOFRRTXczaXVXT0UrZEkwemZrbDRRRzFieWFHMDQ0ampBaFFWVEpUOTRhZXF1VCtVYkZXNkZxRDRYckd1Nkp1SGZkK2xBZHdOSjdBeHROaUk1ZmtWUlVtQ0lEWWhXVHVxQTRMOHF6QVlZdmVEUk5rRCtsL0xGem9ZZm5nYnplVU9vVGVWRE5EbDVUYi8wdXlVM1BPWVlzMVFVN3B2VnlKUnZ3OEUweHNtaEx2R3NYalJER2E1NzZmektnMXlha25CMlhONVFJUk5yZldmOWt5WmdJcFp2cnFwQXRjNU4reEZHS1g2M2pYV2pzU0Q1WDduNE0zaEhvekpXbjU4OEluOVp2OUl6akNuTWN2c0FDKzhoYWFIVko5Si8wWXIzTk9JTGY2VHhpcUVLNDNSS2JHWGRtY3cyUUpwUGlUa2RPQXRHWTA1bTlOSi9lQ1hyQ3dpVTNONDRCa2sxRFV6WDdrQStaSjF4RnlrbzhGa1hJcFhkWEphK05aM1VsTEdYUlRIdUY3dG8wSlR0VE4xRGVIWDE3MlhUbGhjSlJCbHMrUXUxTytVUjV0WTZmNDY4aEJ5V1hQUE83bkJXVVBxT2dDWWhZVlhaU3JEUXYzVmpDcnIyU2h6dEZkMlQwN0ltSm5UNGRWMlhrREk0VUE5bitzcDN4UmIrOWZEMHhzd0VFZWI0M0tBdnpjeG9xNWRTN1h1dVBxeVVpVXE3TC95VWFSSlY0MmFXOTBMN3NHcUM2dFI0ZS9yR3VsSm9GbDJpRTA1dnlPai9hKzB1QnlMOTNZTkErT3lEaE5QQnVIVjFkcngvK3RVLzVtQnFCdCs4VHVsMmVTZUpZVWVSTjNNTnI2b2wvM1BBakUrT0h3Vk96ZHU4OGd5MFkrMTM1YzJXTWIyZmN3dVdYZXVMVzZIZTVlaUNDbUNoWWpkUkhlZ3R5Wi80S3Q3dHNUV2dveWJNc2kvR3h3bjIxSjlLOVFVTExaMmlmV3kzaFNJbGpUZjVOZXF4NlNQWE1UWmNQenZxT2xvMFBZUUJIdExObmRRQStKbzJCVzVvdENtQmt2WWRRQWUwMDc4SlhpNW9saHhRVHlkQkI0WGlFbjl5YWJnZmRMa1FpY2pydUpTZTBISzZhUGpjS1RTWXVNcHVCZzJmbHlDTXV6YzJBY1p0UjhxTHZzUEl0WXFFWG00dXFRaTZxbjExVzZlcjUyYm9HM0liODJOZzV0bVZUUjZxak5hdEM3Zk9tK0RrQ3BlcWVWT01uUmYvbmtTSGxmQW9mK2hEWDV0a2ZvRUlHdngvRjRheVJmc0tJWnR0MHBLd3VicGFSUHZFOXZIS3JxeElLQVFDL3BTaXQ3QTJtcEtXcUN6YmJyY1UyeUVHcXYzbSs5dzdoSlhGNlcrZElmMU92Q2xjaXBtMTJDY2Jua1ZkQUlJdkcvTTF2aWFFZE9ZeGEzdVpVK0JYNHBZRjNZbUNsaUE1VUJlVHpabmNsdTJqQ2lzMHBTM0J5K3lHd0Jtc2pYSGhyMTh6SXlJZVBwTGZiNlgxVHB4MTRhUWxoa2NocEhiMHUreUQxa3pFeXY2U29Mdm03TUFZcHcxcXNZbmd4bnExbzdDN0hCUTNobEw4UEQ4M3dxMnV4SlVJU2IrS292Y0JQbmhWRHhLNTZ4N1BzUFl3YWU4RmkyVDcvenZTVTlFUy9wNHhTdTM2d2dLSGF1TE9MWERKeGlPNG13QmJmTlh2cGFkOXJrZnNLWUNqbmgwZFFXNjNubG1RMDhvWkFVT3NUWXRBZHc5MUhObHROMG1tMEdaODdSSC9xSTJPMFFMdk8yWWRIV1R2cG1QbTNnM1FxK29TSnF2czI4WGRLaWFicGtXVyt5c28rZ3JSYWNmb1VPU2xYSTBUdjFVSnpYekhEUjJxbkp4cmtSM25rV1FFZ3RWK28zbDlYOVladUo4aTRwMWZGTVA2TTU0SHRodWV3bFBBSDNiVDJaeGV1cUM3bmFyRndnZnVtZ0hOL1hob2J4ZHZlRjJGTW9jVklWcUt5ZmN3dnM5Y1JlRWp6clR5VzQ3RVpoaUZEL1V0Qy9wOFlLdTdkb1FPSENCeFd3b2VzcUJNbWQ1NHByZDJicklnOWxhRzFuNzZJbkdSQ05wL2VaZENPQ05nZ0w0RkxHRjAxMXlVbVBLejJOQTNVMTNRTFRyQXhLVzZkeXF1V0s4bk5RVW9BOG9WKy9WVjFja1BhTGZ2V0FnNnMzekJUUGdJZzB4RE01SHNNSzVlcFY1WGltUDwveGVuYzpDaXBoZXJWYWx1ZT4NCiAgIDwveGVuYzpDaXBoZXJEYXRhPg0KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file