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.math.BigInteger;
19  import java.security.Key;
20  import java.security.SignatureException;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Base64;
24  import java.util.List;
25  import java.util.Objects;
26  
27  import org.apache.juli.logging.Log;
28  import org.apache.juli.logging.LogFactory;
29  
30  /**
31   * A class representing the <a href=
32   * "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/166d8064-c863-41e1-9c23-edaaa5f36962">{@code PAC Data}</a>
33   * structure from MS-PAC. This implementation only parses the embedded structures which are required
34   * for the purpose of this component, everything else is skipped.
35   * <p>
36   * <strong>Important:</strong> It is imperative to pass a suitable signature verifier implementation
37   * and the long term Kerberos keys for the principal from the keytab which were used to establish
38   * the security context. The simplest implementation is the {@link PrivateSunPacSignatureVerifier}
39   * which uses private Sun classes to perform the calculation.
40   */
41  public class Pac {
42  
43  	private static final BigInteger EIGHT = BigInteger.valueOf(8L);
44  
45  	private static final long KERB_VALIDATION_INFO = 0x00000001L;
46  	private static final long PAC_CLIENT_INFO = 0x0000000AL;
47  	private static final long UPN_DNS_INFO = 0x0000000CL;
48  	private static final long SERVER_SIGNATURE = 0x00000006L;
49  	private static final long KDC_SIGNATURE = 0x00000007L;
50  
51  	protected final Log logger = LogFactory.getLog(getClass());
52  
53  	private KerbValidationInfo kerbValidationInfo;
54  	private UpnDnsInfo upnDnsInfo;
55  	private PacClientInfo pacClientInfo;
56  	private PacSignatureData serverSignature;
57  	private PacSignatureData kdcSignature;
58  
59  	private final PacSignatureVerifier signatureVerifier;
60  	private final byte[] zeroedPacData;
61  
62  	/**
63  	 * Parses a PAC data object from a byte array.
64  	 *
65  	 * @param pacDataBytes
66  	 *            PAC data structure encoded as bytes
67  	 * @param signatureVerifier
68  	 *            a signature verifier implementation
69  	 * @throws NullPointerException
70  	 *             if {@code infoBytes} is null
71  	 * @throws IllegalArgumentException
72  	 *             if {@code infoBytes} is empty
73  	 * @throws NullPointerException
74  	 *             if {@code signatureVerifier} is null
75  	 * @throws IllegalArgumentException
76  	 *             if PAC version is not 0
77  	 * @throws IllegalArgumentException
78  	 *             if an embedded {@code PAC_INFO_BUFFER} structure offset is not a multiple of 8
79  	 * @throws IllegalArgumentException
80  	 *             if any embedded structure is invalid
81  	 * @throws IllegalArgumentException
82  	 *             if any of the required embedded structures ({@code KERB_VALIDATION_INFO},
83  	 *             {@code PAC_CLIENT_INFO}, {@code PAC_SIGNATURE_DATA} (Server Signature),
84  	 *             {@code PAC_SIGNATURE_DATA} (KDC Signature)) is not present
85  	 */
86  	public Pac(byte[] pacDataBytes, PacSignatureVerifier signatureVerifier) {
87  		Objects.requireNonNull(pacDataBytes, "pacDataBytes cannot be null");
88  		if (pacDataBytes.length == 0)
89  			throw new IllegalArgumentException("pacDataBytes cannot be empty");
90  
91  		PacDataBuffer buf = new PacDataBuffer(pacDataBytes);
92  		this.signatureVerifier = Objects.requireNonNull(signatureVerifier,
93  				"signatureVerifier cannot be null");
94  
95  		// Read PACTYPE structure
96  		if (logger.isTraceEnabled())
97  			logger.trace("Parsing PACTYPE structure...");
98  		// cBuffers
99  		long buffers = buf.getUnsignedInt();
100 		// Version
101 		long version = buf.getUnsignedInt();
102 		if (version != 0L)
103 			throw new IllegalArgumentException("PAC must have version 0, but has " + version);
104 
105 		if (logger.isTraceEnabled())
106 			logger.trace("PAC has version " + version + " and contains " + buffers + " buffers");
107 
108 		// Read PAC_INFO_BUFFER structures
109 		if (logger.isTraceEnabled())
110 			logger.trace("Parsing " + buffers + " PAC_INFO_BUFFER structures...");
111 		List<PacInfoBuffer> pacInfoBuffers = new ArrayList<>();
112 		for (long l = 0L; l < buffers; l++) {
113 			// ulType
114 			long type = buf.getUnsignedInt();
115 			// cbBufferSize
116 			long bufferSize = buf.getUnsignedInt();
117 			// Offset
118 			BigInteger offset = buf.getUnsignedLong();
119 			if (!offset.mod(EIGHT).equals(BigInteger.ZERO))
120 				throw new IllegalArgumentException(
121 						"PAC_INFO_BUFFER offset must be multiple of 8, but is " + offset);
122 			int pos = buf.position();
123 			buf.position(offset.intValue());
124 			byte[] data = new byte[(int) bufferSize];
125 			buf.get(data);
126 			buf.position(pos);
127 			if (logger.isTraceEnabled())
128 				logger.trace("PAC_INFO_BUFFER describes type " + String.format("0x%08X", type)
129 						+ " with size " + bufferSize + " and offset " + offset + " containing data "
130 						+ Base64.getEncoder().encodeToString(data));
131 
132 			pacInfoBuffers.add(new PacInfoBuffer(type, bufferSize, offset, data));
133 		}
134 
135 		zeroedPacData = Arrays.copyOf(pacDataBytes, pacDataBytes.length);
136 
137 		for (PacInfoBuffer pacInfoBuffer : pacInfoBuffers) {
138 			long type = pacInfoBuffer.getType();
139 			byte[] data = pacInfoBuffer.getData();
140 			if (type == KERB_VALIDATION_INFO) {
141 				if (kerbValidationInfo != null) {
142 					if (logger.isTraceEnabled())
143 						logger.trace("Ignoring additional KERB_VALIDATION_INFO structure");
144 				} else {
145 					if (logger.isTraceEnabled())
146 						logger.trace("Parsing KERB_VALIDATION_INFO structure...");
147 					kerbValidationInfo = new KerbValidationInfo(data);
148 				}
149 			} else if (type == UPN_DNS_INFO) {
150 				if (upnDnsInfo != null) {
151 					if (logger.isTraceEnabled())
152 						logger.trace("Ignoring additional UPN_DNS_INFO structure");
153 				} else {
154 					if (logger.isTraceEnabled())
155 						logger.trace("Parsing UPN_DNS_INFO structure...");
156 					upnDnsInfo = new UpnDnsInfo(data);
157 				}
158 			} else if (type == PAC_CLIENT_INFO) {
159 				if (upnDnsInfo != null) {
160 					if (logger.isTraceEnabled())
161 						logger.trace("Ignoring additional PAC_CLIENT_INFO structure");
162 				} else {
163 					if (logger.isTraceEnabled())
164 						logger.trace("Parsing PAC_CLIENT_INFO structure...");
165 					pacClientInfo = new PacClientInfo(data);
166 				}
167 			} else if (type == SERVER_SIGNATURE) {
168 				if (serverSignature != null) {
169 					if (logger.isTraceEnabled())
170 						logger.trace(
171 								"Ignoring additional PAC_SIGNATURE_DATA (Server Signature) structure");
172 				} else {
173 					if (logger.isTraceEnabled())
174 						logger.trace("Parsing PAC_SIGNATURE_DATA (Server Signature) structure...");
175 					serverSignature = new PacSignatureData(data);
176 					int from = pacInfoBuffer.getOffset().intValue() + 4; // sizeof(SignatureType)
177 					int to = from + serverSignature.getType().getSize();
178 					Arrays.fill(zeroedPacData, from, to, (byte) 0);
179 				}
180 			} else if (type == KDC_SIGNATURE) {
181 				if (kdcSignature != null) {
182 					if (logger.isTraceEnabled())
183 						logger.trace(
184 								"Ignoring additional PAC_SIGNATURE_DATA (KDC Signature) structure");
185 				} else {
186 					if (logger.isTraceEnabled())
187 						logger.trace("Parsing PAC_SIGNATURE_DATA (KDC Signature) structure...");
188 					kdcSignature = new PacSignatureData(data);
189 					int from = pacInfoBuffer.getOffset().intValue() + 4; // sizeof(SignatureType)
190 					int to = from + kdcSignature.getType().getSize();
191 					Arrays.fill(zeroedPacData, from, to, (byte) 0);
192 				}
193 			} else {
194 				if (logger.isTraceEnabled())
195 					logger.trace(
196 							"Ignoring unsupported structure type " + String.format("0x%08X", type)
197 									+ " with data " + Base64.getEncoder().encodeToString(data));
198 			}
199 		}
200 
201 		if (kerbValidationInfo == null)
202 			throw new IllegalArgumentException(
203 					"PAC does not contain required KERB_VALIDATION_INFO structure");
204 
205 		if (pacClientInfo == null)
206 			throw new IllegalArgumentException(
207 					"PAC does not contain required PAC_CLIENT_INFO structure");
208 
209 		if (serverSignature == null)
210 			throw new IllegalArgumentException(
211 					"PAC does not contain required PAC_SIGNATURE_DATA (Server Signature) structure");
212 
213 		if (kdcSignature == null)
214 			throw new IllegalArgumentException(
215 					"PAC does not contain required PAC_SIGNATURE_DATA (KDC Signature) structure");
216 	}
217 
218 	public KerbValidationInfo getKerbValidationInfo() {
219 		return kerbValidationInfo;
220 	}
221 
222 	public UpnDnsInfo getUpnDnsInfo() {
223 		return upnDnsInfo;
224 	}
225 
226 	public PacClientInfo getPacClientInfo() {
227 		return pacClientInfo;
228 	}
229 
230 	public PacSignatureData getServerSignature() {
231 		return serverSignature;
232 	}
233 
234 	public PacSignatureData getKdcSignature() {
235 		return kdcSignature;
236 	}
237 
238 	/**
239 	 * Verifies the server signature of this PAC data structure with zeroed server and KDC signature
240 	 * values with the supplied long term Kerberos keys.
241 	 *
242 	 * @param keys
243 	 *            an array of long term Kerberos keys for the principal from the keytab which was
244 	 *            used to establish the security context
245 	 * @throws SignatureException
246 	 *             if the signature validation fails with all supplied keys
247 	 * @see PacSignatureVerifier
248 	 */
249 	public void verifySignature(Key[] keys) throws SignatureException {
250 		signatureVerifier.verify(serverSignature, zeroedPacData, keys);
251 	}
252 
253 }