net.sf.michaelo.tomcat.pac.Pac Maven / Gradle / Ivy
/*
* Copyright 2024 Michael Osipov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.michaelo.tomcat.pac;
import java.math.BigInteger;
import java.security.Key;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* A class representing the {@code PAC Data}
* structure from MS-PAC. This implementation only parses the embedded structures which are required
* for the purpose of this component, everything else is skipped.
*
* Important: It is imperative to pass a suitable signature verifier implementation
* and the long term Kerberos keys for the principal from the keytab which were used to establish
* the security context. The simplest implementation is the {@link PrivateSunPacSignatureVerifier}
* which uses private Sun classes to perform the calculation.
*/
public class Pac {
private static final BigInteger EIGHT = BigInteger.valueOf(8L);
private static final long KERB_VALIDATION_INFO = 0x00000001L;
private static final long PAC_CLIENT_INFO = 0x0000000AL;
private static final long UPN_DNS_INFO = 0x0000000CL;
private static final long SERVER_SIGNATURE = 0x00000006L;
private static final long KDC_SIGNATURE = 0x00000007L;
protected final Log logger = LogFactory.getLog(getClass());
private KerbValidationInfo kerbValidationInfo;
private UpnDnsInfo upnDnsInfo;
private PacClientInfo pacClientInfo;
private PacSignatureData serverSignature;
private PacSignatureData kdcSignature;
private final PacSignatureVerifier signatureVerifier;
private final byte[] zeroedPacData;
/**
* Parses a PAC data object from a byte array.
*
* @param pacDataBytes
* PAC data structure encoded as bytes
* @param signatureVerifier
* a signature verifier implementation
* @throws NullPointerException
* if {@code infoBytes} is null
* @throws IllegalArgumentException
* if {@code infoBytes} is empty
* @throws NullPointerException
* if {@code signatureVerifier} is null
* @throws IllegalArgumentException
* if PAC version is not 0
* @throws IllegalArgumentException
* if an embedded {@code PAC_INFO_BUFFER} structure offset is not a multiple of 8
* @throws IllegalArgumentException
* if any embedded structure is invalid
* @throws IllegalArgumentException
* if any of the required embedded structures ({@code KERB_VALIDATION_INFO},
* {@code PAC_CLIENT_INFO}, {@code PAC_SIGNATURE_DATA} (Server Signature),
* {@code PAC_SIGNATURE_DATA} (KDC Signature)) is not present
*/
public Pac(byte[] pacDataBytes, PacSignatureVerifier signatureVerifier) {
Objects.requireNonNull(pacDataBytes, "pacDataBytes cannot be null");
if (pacDataBytes.length == 0)
throw new IllegalArgumentException("pacDataBytes cannot be empty");
PacDataBuffer buf = new PacDataBuffer(pacDataBytes);
this.signatureVerifier = Objects.requireNonNull(signatureVerifier,
"signatureVerifier cannot be null");
// Read PACTYPE structure
if (logger.isTraceEnabled())
logger.trace("Parsing PACTYPE structure...");
// cBuffers
long buffers = buf.getUnsignedInt();
// Version
long version = buf.getUnsignedInt();
if (version != 0L)
throw new IllegalArgumentException("PAC must have version 0, but has " + version);
if (logger.isTraceEnabled())
logger.trace("PAC has version " + version + " and contains " + buffers + " buffers");
// Read PAC_INFO_BUFFER structures
if (logger.isTraceEnabled())
logger.trace("Parsing " + buffers + " PAC_INFO_BUFFER structures...");
List pacInfoBuffers = new ArrayList<>();
for (long l = 0L; l < buffers; l++) {
// ulType
long type = buf.getUnsignedInt();
// cbBufferSize
long bufferSize = buf.getUnsignedInt();
// Offset
BigInteger offset = buf.getUnsignedLong();
if (!offset.mod(EIGHT).equals(BigInteger.ZERO))
throw new IllegalArgumentException(
"PAC_INFO_BUFFER offset must be multiple of 8, but is " + offset);
int pos = buf.position();
buf.position(offset.intValue());
byte[] data = new byte[(int) bufferSize];
buf.get(data);
buf.position(pos);
if (logger.isTraceEnabled())
logger.trace("PAC_INFO_BUFFER describes type " + String.format("0x%08X", type)
+ " with size " + bufferSize + " and offset " + offset + " containing data "
+ Base64.getEncoder().encodeToString(data));
pacInfoBuffers.add(new PacInfoBuffer(type, bufferSize, offset, data));
}
zeroedPacData = Arrays.copyOf(pacDataBytes, pacDataBytes.length);
for (PacInfoBuffer pacInfoBuffer : pacInfoBuffers) {
long type = pacInfoBuffer.getType();
byte[] data = pacInfoBuffer.getData();
if (type == KERB_VALIDATION_INFO) {
if (kerbValidationInfo != null) {
if (logger.isTraceEnabled())
logger.trace("Ignoring additional KERB_VALIDATION_INFO structure");
} else {
if (logger.isTraceEnabled())
logger.trace("Parsing KERB_VALIDATION_INFO structure...");
kerbValidationInfo = new KerbValidationInfo(data);
}
} else if (type == UPN_DNS_INFO) {
if (upnDnsInfo != null) {
if (logger.isTraceEnabled())
logger.trace("Ignoring additional UPN_DNS_INFO structure");
} else {
if (logger.isTraceEnabled())
logger.trace("Parsing UPN_DNS_INFO structure...");
upnDnsInfo = new UpnDnsInfo(data);
}
} else if (type == PAC_CLIENT_INFO) {
if (upnDnsInfo != null) {
if (logger.isTraceEnabled())
logger.trace("Ignoring additional PAC_CLIENT_INFO structure");
} else {
if (logger.isTraceEnabled())
logger.trace("Parsing PAC_CLIENT_INFO structure...");
pacClientInfo = new PacClientInfo(data);
}
} else if (type == SERVER_SIGNATURE) {
if (serverSignature != null) {
if (logger.isTraceEnabled())
logger.trace(
"Ignoring additional PAC_SIGNATURE_DATA (Server Signature) structure");
} else {
if (logger.isTraceEnabled())
logger.trace("Parsing PAC_SIGNATURE_DATA (Server Signature) structure...");
serverSignature = new PacSignatureData(data);
int from = pacInfoBuffer.getOffset().intValue() + 4; // sizeof(SignatureType)
int to = from + serverSignature.getType().getSize();
Arrays.fill(zeroedPacData, from, to, (byte) 0);
}
} else if (type == KDC_SIGNATURE) {
if (kdcSignature != null) {
if (logger.isTraceEnabled())
logger.trace(
"Ignoring additional PAC_SIGNATURE_DATA (KDC Signature) structure");
} else {
if (logger.isTraceEnabled())
logger.trace("Parsing PAC_SIGNATURE_DATA (KDC Signature) structure...");
kdcSignature = new PacSignatureData(data);
int from = pacInfoBuffer.getOffset().intValue() + 4; // sizeof(SignatureType)
int to = from + kdcSignature.getType().getSize();
Arrays.fill(zeroedPacData, from, to, (byte) 0);
}
} else {
if (logger.isTraceEnabled())
logger.trace(
"Ignoring unsupported structure type " + String.format("0x%08X", type)
+ " with data " + Base64.getEncoder().encodeToString(data));
}
}
if (kerbValidationInfo == null)
throw new IllegalArgumentException(
"PAC does not contain required KERB_VALIDATION_INFO structure");
if (pacClientInfo == null)
throw new IllegalArgumentException(
"PAC does not contain required PAC_CLIENT_INFO structure");
if (serverSignature == null)
throw new IllegalArgumentException(
"PAC does not contain required PAC_SIGNATURE_DATA (Server Signature) structure");
if (kdcSignature == null)
throw new IllegalArgumentException(
"PAC does not contain required PAC_SIGNATURE_DATA (KDC Signature) structure");
}
public KerbValidationInfo getKerbValidationInfo() {
return kerbValidationInfo;
}
public UpnDnsInfo getUpnDnsInfo() {
return upnDnsInfo;
}
public PacClientInfo getPacClientInfo() {
return pacClientInfo;
}
public PacSignatureData getServerSignature() {
return serverSignature;
}
public PacSignatureData getKdcSignature() {
return kdcSignature;
}
/**
* Verifies the server signature of this PAC data structure with zeroed server and KDC signature
* values with the supplied long term Kerberos keys.
*
* @param keys
* an array of long term Kerberos keys for the principal from the keytab which was
* used to establish the security context
* @throws SignatureException
* if the signature validation fails with all supplied keys
* @see PacSignatureVerifier
*/
public void verifySignature(Key[] keys) throws SignatureException {
signatureVerifier.verify(serverSignature, zeroedPacData, keys);
}
}