View Javadoc
1   /*
2    * Copyright 2024 Michael Osipov
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.michaelo.tomcat.pac;
17  
18  import java.security.Key;
19  import java.security.SignatureException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Base64;
23  import java.util.List;
24  import java.util.stream.Collectors;
25  
26  import net.sf.michaelo.tomcat.pac.PacSignatureData.SignatureType;
27  import sun.security.krb5.Checksum;
28  import sun.security.krb5.EncryptionKey;
29  
30  /**
31   * A PAC signature verifier based on private Sun classes from Java's Kerberos implementation.
32   * <p>
33   * <strong>Note:</strong> This implementation is far from ideal because it uses private classes
34   * which can break anytime. If you are running on Java 17 or newer you <em>must</em> pass
35   * {@code --add-exports=java.security.jgss/sun.security.krb5=ALL-UNNAMED} to your JVM. A better
36   * solution would be to use the <a href="https://directory.apache.org/kerby/">Apache Kerby</a>
37   * library.
38   */
39  public class PrivateSunPacSignatureVerifier extends PacSignatureVerifierBase {
40  
41  	@Override
42  	protected void verifyInternal(PacSignatureData signatureData, byte[] data, Key[] keys)
43  			throws SignatureException {
44  		SignatureType type = signatureData.getType();
45  		byte[] expectedSignature = signatureData.getSignature();
46  		List<byte[]> actualFailedSignatures = new ArrayList<>();
47  		for (int i = 0; i < keys.length; i++) {
48  			Key key = keys[i];
49  			EncryptionKey encKey = new EncryptionKey(type.getEType(), key.getEncoded());
50  			Checksum checksum = null;
51  			try {
52  				checksum = new Checksum(type.getValue(), data, encKey, KU_KERB_NON_KERB_CKSUM_SALT);
53  			} catch (Exception e) {
54  				throw new SignatureException("Failed to calculate signature", e);
55  			}
56  
57  			byte[] actualSignature = checksum.getBytes();
58  			if (Arrays.equals(expectedSignature, actualSignature))
59  				return;
60  			else
61  				actualFailedSignatures.add(actualSignature);
62  		}
63  
64  		String actualFailedSignaturesStr = actualFailedSignatures.stream()
65  				.map(Base64.getEncoder()::encodeToString)
66  				.collect(Collectors.joining(",", "[", "]"));
67  		throw new SignatureException("Calculated signatures " + actualFailedSignaturesStr
68  				+ " do not match expected signature '"
69  				+ Base64.getEncoder().encodeToString(expectedSignature) + "'");
70  	}
71  
72  }