Wed, 19 Aug 2020 14:42:55 -0400
8236645: JDK 8u231 introduces a regression with incompatible handling of XML messages
Reviewed-by: sgehwolf
1.1 --- a/src/share/classes/com/sun/org/apache/xml/internal/security/utils/XMLUtils.java Mon Oct 31 13:57:28 2016 -0400 1.2 +++ b/src/share/classes/com/sun/org/apache/xml/internal/security/utils/XMLUtils.java Wed Aug 19 14:42:55 2020 -0400 1.3 @@ -55,6 +55,9 @@ 1.4 */ 1.5 public final class XMLUtils { 1.6 1.7 + private static boolean lineFeedOnly = 1.8 + AccessController.doPrivileged( 1.9 + (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("com.sun.org.apache.xml.internal.security.lineFeedOnly")); 1.10 private static boolean ignoreLineBreaks = 1.11 AccessController.doPrivileged( 1.12 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("com.sun.org.apache.xml.internal.security.ignoreLineBreaks")); 1.13 @@ -66,6 +69,7 @@ 1.14 1.15 private static final com.sun.org.slf4j.internal.Logger LOG = 1.16 com.sun.org.slf4j.internal.LoggerFactory.getLogger(XMLUtils.class); 1.17 + private static final java.util.Base64.Encoder LF_ENCODER = Base64.getMimeEncoder(76, new byte[] {'\n'}); 1.18 1.19 1.20 /** 1.21 @@ -448,6 +452,9 @@ 1.22 if (ignoreLineBreaks) { 1.23 return Base64.getEncoder().encodeToString(bytes); 1.24 } 1.25 + if (lineFeedOnly) { 1.26 + return LF_ENCODER.encodeToString(bytes); 1.27 + } 1.28 return Base64.getMimeEncoder().encodeToString(bytes); 1.29 } 1.30
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/test/javax/xml/crypto/dsig/LineFeedOnlyTest.java Wed Aug 19 14:42:55 2020 -0400 2.3 @@ -0,0 +1,214 @@ 2.4 +/* 2.5 + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. 2.6 + * Copyright (c) 2020 Red Hat, Inc. 2.7 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2.8 + * 2.9 + * This code is free software; you can redistribute it and/or modify it 2.10 + * under the terms of the GNU General Public License version 2 only, as 2.11 + * published by the Free Software Foundation. 2.12 + * 2.13 + * This code is distributed in the hope that it will be useful, but WITHOUT 2.14 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 2.15 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 2.16 + * version 2 for more details (a copy is included in the LICENSE file that 2.17 + * accompanied this code). 2.18 + * 2.19 + * You should have received a copy of the GNU General Public License version 2.20 + * 2 along with this work; if not, write to the Free Software Foundation, 2.21 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2.22 + * 2.23 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2.24 + * or visit www.oracle.com if you need additional information or have any 2.25 + * questions. 2.26 + */ 2.27 + 2.28 +import java.io.File; 2.29 +import java.io.FileInputStream; 2.30 +import java.io.StringWriter; 2.31 +import java.security.Key; 2.32 +import java.security.KeyStore; 2.33 +import java.security.cert.Certificate; 2.34 +import java.util.Base64; 2.35 +import java.util.Collections; 2.36 + 2.37 +import javax.xml.crypto.Data; 2.38 +import javax.xml.crypto.OctetStreamData; 2.39 +import javax.xml.crypto.URIDereferencer; 2.40 +import javax.xml.crypto.URIReference; 2.41 +import javax.xml.crypto.URIReferenceException; 2.42 +import javax.xml.crypto.XMLCryptoContext; 2.43 +import javax.xml.crypto.dsig.CanonicalizationMethod; 2.44 +import javax.xml.crypto.dsig.DigestMethod; 2.45 +import javax.xml.crypto.dsig.Reference; 2.46 +import javax.xml.crypto.dsig.SignatureMethod; 2.47 +import javax.xml.crypto.dsig.SignedInfo; 2.48 +import javax.xml.crypto.dsig.XMLSignature; 2.49 +import javax.xml.crypto.dsig.XMLSignatureFactory; 2.50 +import javax.xml.crypto.dsig.dom.DOMSignContext; 2.51 +import javax.xml.crypto.dsig.keyinfo.KeyInfo; 2.52 +import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; 2.53 +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; 2.54 +import javax.xml.parsers.DocumentBuilder; 2.55 +import javax.xml.parsers.DocumentBuilderFactory; 2.56 +import javax.xml.transform.OutputKeys; 2.57 +import javax.xml.transform.Transformer; 2.58 +import javax.xml.transform.TransformerFactory; 2.59 +import javax.xml.transform.dom.DOMSource; 2.60 +import javax.xml.transform.stream.StreamResult; 2.61 + 2.62 +import org.w3c.dom.Document; 2.63 +import org.w3c.dom.Node; 2.64 +import org.w3c.dom.NodeList; 2.65 + 2.66 +/* @test 2.67 + * @bug 8236645 2.68 + * @summary Test "lineFeedOnly" property prevents CR in Base64 encoded output 2.69 + * @run main/othervm/timeout=300 -Dcom.sun.org.apache.xml.internal.security.lineFeedOnly=false LineFeedOnlyTest 2.70 + * @run main/othervm/timeout=300 -Dcom.sun.org.apache.xml.internal.security.lineFeedOnly=true LineFeedOnlyTest 2.71 + * @run main/othervm/timeout=300 -Dcom.sun.org.apache.xml.internal.security.lineFeedOnly=true 2.72 + * -Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true LineFeedOnlyTest 2.73 + * @run main/othervm/timeout=300 LineFeedOnlyTest 2.74 + */ 2.75 +public class LineFeedOnlyTest { 2.76 + 2.77 + private final static String DIR = System.getProperty("test.src", "."); 2.78 + private final static String DATA_DIR = 2.79 + DIR + System.getProperty("file.separator") + "data"; 2.80 + private final static String KEYSTORE = 2.81 + DATA_DIR + System.getProperty("file.separator") + "certs" + 2.82 + System.getProperty("file.separator") + "test.jks"; 2.83 + private final static String STYLESHEET = 2.84 + "http://www.w3.org/TR/xml-stylesheet"; 2.85 + 2.86 + private static XMLSignatureFactory fac; 2.87 + private static KeyInfoFactory kifac; 2.88 + private static DocumentBuilder db; 2.89 + private static CanonicalizationMethod withoutComments; 2.90 + private static SignatureMethod dsaSha1; 2.91 + private static DigestMethod sha1; 2.92 + private static Key signingKey; 2.93 + private static Certificate signingCert; 2.94 + private static KeyStore ks; 2.95 + private static URIDereferencer httpUd; 2.96 + 2.97 + // Much of this test is derived from GenerationTests. We use a separate file in order to test 2.98 + // when the system property "com.sun.org.apache.xml.internal.security.lineFeedOnly" is enabled/disabled. 2.99 + public static void main(String[] args) throws Exception { 2.100 + boolean lineFeedOnly = Boolean.getBoolean("com.sun.org.apache.xml.internal.security.lineFeedOnly"); 2.101 + boolean ignoreLineBreaks = Boolean.getBoolean("com.sun.org.apache.xml.internal.security.ignoreLineBreaks"); 2.102 + 2.103 + setup(); 2.104 + test_create_signature_line_endings(lineFeedOnly, ignoreLineBreaks); 2.105 + } 2.106 + 2.107 + private static void setup() throws Exception { 2.108 + fac = XMLSignatureFactory.getInstance(); 2.109 + kifac = fac.getKeyInfoFactory(); 2.110 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 2.111 + dbf.setNamespaceAware(true); 2.112 + db = dbf.newDocumentBuilder(); 2.113 + 2.114 + // get key & self-signed certificate from keystore 2.115 + FileInputStream fis = new FileInputStream(KEYSTORE); 2.116 + ks = KeyStore.getInstance("JKS"); 2.117 + ks.load(fis, "changeit".toCharArray()); 2.118 + signingKey = ks.getKey("user", "changeit".toCharArray()); 2.119 + signingCert = ks.getCertificate("user"); 2.120 + 2.121 + // create common objects 2.122 + withoutComments = fac.newCanonicalizationMethod 2.123 + (CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null); 2.124 + dsaSha1 = fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null); 2.125 + sha1 = fac.newDigestMethod(DigestMethod.SHA1, null); 2.126 + 2.127 + httpUd = new HttpURIDereferencer(); 2.128 + } 2.129 + 2.130 + private static void test_create_signature_line_endings(boolean lineFeedOnly, 2.131 + boolean ignoreLineBreaks) throws Exception { 2.132 + System.out.println("* Generating signature-line-endings.xml"); 2.133 + System.out.println("* com.sun.org.apache.xml.internal.security.lineFeedOnly is " 2.134 + + String.valueOf(lineFeedOnly)); 2.135 + System.out.println("* com.sun.org.apache.xml.internal.security.ignoreLineBreaks is " 2.136 + + String.valueOf(ignoreLineBreaks)); 2.137 + 2.138 + // create reference 2.139 + Reference ref = fac.newReference(STYLESHEET, sha1); 2.140 + 2.141 + // create SignedInfo 2.142 + SignedInfo si = fac.newSignedInfo(withoutComments, dsaSha1, 2.143 + Collections.singletonList(ref)); 2.144 + 2.145 + Document doc = db.newDocument(); 2.146 + 2.147 + // create XMLSignature 2.148 + KeyInfo crt = kifac.newKeyInfo(Collections.singletonList 2.149 + (kifac.newX509Data(Collections.singletonList(signingCert)))); 2.150 + XMLSignature sig = fac.newXMLSignature(si, crt); 2.151 + 2.152 + DOMSignContext dsc = new DOMSignContext(signingKey, doc); 2.153 + dsc.setURIDereferencer(httpUd); 2.154 + 2.155 + sig.sign(dsc); 2.156 + 2.157 + NodeList list = doc.getElementsByTagName("X509Certificate"); 2.158 + if (list.getLength() != 1) { 2.159 + throw new Exception("Expected exactly one X509Certificate tag"); 2.160 + } 2.161 + Node item = list.item(0); 2.162 + StringWriter writer = new StringWriter(); 2.163 + Transformer tr = TransformerFactory.newInstance().newTransformer(); 2.164 + tr.setOutputProperty(OutputKeys.METHOD, "xml"); 2.165 + tr.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); 2.166 + tr.transform(new DOMSource(item.getFirstChild()), new StreamResult(writer)); 2.167 + String actual = writer.toString(); 2.168 + 2.169 + String expected; 2.170 + if (ignoreLineBreaks) { 2.171 + expected = Base64.getEncoder().encodeToString(signingCert.getEncoded()); 2.172 + } else if (lineFeedOnly) { 2.173 + expected = Base64.getMimeEncoder(76, new byte[] {'\n'}).encodeToString(signingCert.getEncoded()); 2.174 + } else { 2.175 + expected = Base64.getMimeEncoder().encodeToString(signingCert.getEncoded()); 2.176 + } 2.177 + 2.178 + if (!expected.equals(actual)) { 2.179 + if (ignoreLineBreaks && actual.contains("\n")) { 2.180 + throw new Exception("ignoreLineBreaks did not take precedence over lineFeedOnly"); 2.181 + } else if (lineFeedOnly && actual.contains("\r\n")) { 2.182 + throw new Exception("Expected LF only, but found CRLF"); 2.183 + } else if (!lineFeedOnly && !actual.contains("\r\n")) { 2.184 + throw new Exception("Expected CRLF, but found LF only"); 2.185 + } 2.186 + throw new Exception("Unexpected output in encoded certificate"); 2.187 + } 2.188 + } 2.189 + 2.190 + /** 2.191 + * This URIDereferencer returns locally cached copies of http content to 2.192 + * avoid test failures due to network glitches, etc. 2.193 + */ 2.194 + private static class HttpURIDereferencer implements URIDereferencer { 2.195 + private URIDereferencer defaultUd; 2.196 + 2.197 + HttpURIDereferencer() { 2.198 + defaultUd = XMLSignatureFactory.getInstance().getURIDereferencer(); 2.199 + } 2.200 + 2.201 + public Data dereference(final URIReference ref, XMLCryptoContext ctx) 2.202 + throws URIReferenceException { 2.203 + String uri = ref.getURI(); 2.204 + if (uri.equals(STYLESHEET)) { 2.205 + try { 2.206 + FileInputStream fis = new FileInputStream(new File 2.207 + (DATA_DIR, uri.substring(uri.lastIndexOf('/')))); 2.208 + return new OctetStreamData(fis,ref.getURI(),ref.getType()); 2.209 + } catch (Exception e) { throw new URIReferenceException(e); } 2.210 + } 2.211 + 2.212 + // fallback on builtin deref 2.213 + return defaultUd.dereference(ref, ctx); 2.214 + } 2.215 + } 2.216 + 2.217 +}