View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.wss4j.common.util;
21  
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.Test;
24  
25  import javax.security.auth.x500.X500Principal;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.IOException;
29  import java.security.InvalidAlgorithmParameterException;
30  import java.security.KeyStore;
31  import java.security.KeyStoreException;
32  import java.security.NoSuchAlgorithmException;
33  import java.security.cert.CertificateException;
34  import java.security.cert.PKIXParameters;
35  import java.security.cert.TrustAnchor;
36  import java.security.cert.X509Certificate;
37  
38  import static org.junit.jupiter.api.Assertions.assertEquals;
39  
40  class CommaDelimiterRfc2253NameTest {
41  
42  	private static final String TYPICAL_CA ="CN=Entrust Certification Authority - L1K,OU=(c) 2012 Entrust\\, Inc. - for authorized use only,OU=See www.entrust.net/legal-terms,O=Entrust\\, Inc.,C=US";
43  	private static final String QUOTES_TYPICAL_CA ="CN=Entrust Certification Authority - L1K, OU=\"(c) 2012 Entrust, Inc. - for authorized use only\", OU=See www.entrust.net/legal-terms, O=\"Entrust, Inc.\", C=US";
44  
45  	private CommaDelimiterRfc2253Name subject = new CommaDelimiterRfc2253Name();
46  
47  
48  	@Test
49  	void whenMultipleAttributesArePresentThenSpaceIsPlacedAfterComma() {
50  		String actual = new CommaDelimiterRfc2253Name().execute("CN=EOIR,OU=Some Unit,DC=Another place");
51  		assertEquals("CN=EOIR, OU=Some Unit, DC=Another place",actual);
52  	}
53  	@Test
54  	void whenRdnContainsACommaThenTheRdnIsSurroundedByDoubleQuotes() {
55  		String actual = new CommaDelimiterRfc2253Name().execute(TYPICAL_CA);
56  		assertEquals(QUOTES_TYPICAL_CA,actual);
57  	}
58  
59  	@Test
60  	void whenRdnIsInvalidThenExpectException() {
61  		Assertions.assertThrows(IllegalArgumentException.class, () -> {
62  			subject.execute("invalid");
63  		});
64  	}
65  
66  
67  	@Test
68  	void whenCallingUnescapeWithStringNoEscapesThenNoChangesAreMade() throws Exception {
69  		String input = "This is a string with (c) no escaped! sStrings $";
70  		String actual = subject.unEscapeRfc2253RdnSubPart(input);
71  		assertEquals(input,actual,"Expect that string is unchanged");
72  	}
73  
74  
75  	@Test
76  	void whenCallingUnescapeWithStringThenItUnescapesAppropiateCharacters() throws Exception {
77  		String input = "This is a string with escapes \\,\\; \\\\ and \\< then \\> \\\"Copyright Apache\\\" ";
78  		String expected = "This is a string with escapes ,; \\ and < then > \"Copyright Apache\" ";
79  		String actual = subject.unEscapeRfc2253RdnSubPart(input);
80  		assertEquals(expected,actual,"Expect that string is unescaped");
81  	}
82  
83  
84  	@Test
85  	void whenCallingUnescapeWithStringWithMultiValueRdnThenItUnescapesAppropriateCharacters() throws Exception {
86  		String input = "OU=Sales\\+CN=J. Smith\\,O=Widget Inc.\\,C=US";
87  		String expected = "OU=Sales+CN=J. Smith,O=Widget Inc.,C=US";
88  		String actual = subject.unEscapeRfc2253RdnSubPart(input);
89  		assertEquals(expected,actual,"Expect that string is unescaped");
90  	}
91  
92  	@Test
93  	public void testThatACommaDelimitedDnStringAndABackSlashEscapedDnProducesTheSameX509PrincipalUsingDefaultTruststore()
94  			throws KeyStoreException, InvalidAlgorithmParameterException, CertificateException, NoSuchAlgorithmException, IOException {
95  		KeyStore keystore = loadDefaultKeyStore();
96  		PKIXParameters params = new PKIXParameters(keystore);
97  		for (TrustAnchor ta : params.getTrustAnchors()) {
98  			X509Certificate cert = ta.getTrustedCert();
99  			assertThatTransformIsEquivalent(cert.getSubjectX500Principal().getName());
100 		}
101 	}
102 
103 	private void assertThatTransformIsEquivalent(String dnString) {
104 		// The expected value below recreates what is done in the token class by recreating the  X500Principal using getName()
105 		// even though the calling methods already used a X500Principal.getName()  to pass the value in the first place ,
106 		// this seems wasteful but I believe there is a reason for this in the wss4j code ...
107 		// Searching for different RFC 2253 parsers , this one :
108 		// https://www.codeproject.com/Articles/9788/An-RFC-2253-Compliant-Distinguished-Name-Parser
109 		// mentioned that its not possible to recreate the original binary because of the RFC allows multibyte characters  using # encoding.
110 		// Indeed w/o this additional calls to X500Principal.getName() this test will fail for one of the CA which indeed uses # encoding
111 		// because the equals uses the X500Name.canonicalDn string for comparison which if used directly from the keystore would
112 		// still contain the multibyte characters.
113 		// Since wss4j does not send multibyte characters, this tests uses of new X500Principal(dnString)
114 		// accurately reflects change usage.
115 
116 		X500Principal expected = new X500Principal(dnString);
117 		X500Principal recreatedX509principal = new X500Principal(subject.execute(dnString));
118 		assertEquals(expected, recreatedX509principal);
119 	}
120 
121 	private KeyStore loadDefaultKeyStore() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
122 		String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
123 		FileInputStream is = new FileInputStream(filename);
124 		KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
125 		String password = "changeit";
126 		keystore.load(is, password.toCharArray());
127 		return keystore;
128 	}
129 
130 
131 }