View Javadoc
1   /*
2    * Copyright 2024–2026 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.MessageDigest;
20  import java.security.SignatureException;
21  import java.util.ArrayList;
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  
28  import sun.security.krb5.Checksum;
29  import sun.security.krb5.EncryptionKey;
30  
31  /**
32   * A PAC signature verifier based on private Sun classes from Java's Kerberos implementation.
33   *
34   * <p><strong>Note:</strong> This implementation is far from ideal because it uses private classes which can break
35   * anytime. If you are running on Java 17 or newer you <em>must</em> pass
36   * {@code --add-exports=java.security.jgss/sun.security.krb5=ALL-UNNAMED} to your JVM. A better solution would be to use
37   * the <a href="https://directory.apache.org/kerby/">Apache Kerby</a> library.
38   */
39  public class PrivateSunPacSignatureVerifier extends PacSignatureVerifierBase {
40  
41  	@Override
42  	protected void verifyInternal(PacSignatureData signatureData, byte[] data, Key[] keys) throws SignatureException {
43  		SignatureType type = signatureData.getType();
44  		byte[] expectedSignature = signatureData.getSignature();
45  		List<byte[]> actualFailedSignatures = new ArrayList<>();
46  		for (int i = 0; i < keys.length; i++) {
47  			Key key = keys[i];
48  			EncryptionKey encKey = new EncryptionKey(type.getEType(), key.getEncoded());
49  			try {
50  				Checksum checksum;
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 (MessageDigest.isEqual(expectedSignature, actualSignature)) return;
59  				else actualFailedSignatures.add(actualSignature);
60  			} finally {
61  				encKey.destroy();
62  			}
63  		}
64  
65  		String actualFailedSignaturesStr = actualFailedSignatures.stream()
66  				.map(Base64.getEncoder()::encodeToString)
67  				.collect(Collectors.joining(",", "[", "]"));
68  		throw new SignatureException("Calculated signatures " + actualFailedSignaturesStr
69  				+ " do not match expected signature '"
70  				+ Base64.getEncoder().encodeToString(expectedSignature) + "'");
71  	}
72  }