1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.michaelo.tomcat.pac;
17
18 import java.io.BufferedReader;
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.io.UncheckedIOException;
22 import java.nio.charset.StandardCharsets;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.security.SignatureException;
27 import java.util.ArrayList;
28 import java.util.Base64;
29 import java.util.List;
30 import java.util.Scanner;
31 import java.util.concurrent.atomic.AtomicInteger;
32
33 import javax.security.auth.Subject;
34 import javax.security.auth.kerberos.KerberosKey;
35 import javax.security.auth.kerberos.KerberosPrincipal;
36 import javax.security.auth.kerberos.KeyTab;
37 import javax.security.auth.login.LoginContext;
38 import javax.security.auth.login.LoginException;
39
40 import com.sun.security.jgss.AuthorizationDataEntry;
41
42 import net.sf.michaelo.tomcat.authenticator.SpnegoAuthenticator;
43 import net.sf.michaelo.tomcat.pac.asn1.AdIfRelevantAsn1Parser;
44 import net.sf.michaelo.tomcat.realm.Krb5AuthzDataDumpingActiveDirectoryRealm;
45 import net.sf.michaelo.tomcat.realm.Sid;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class Krb5AuthzDataDumpPrinter {
68
69 private static final AtomicInteger PAC_ID_GENERATOR = new AtomicInteger();
70
71 private static void dumpFile(Path file, String format, KerberosKey[] keys) throws IOException, SignatureException {
72 System.err.printf("Processing file '%s'%n", file);
73 List<AuthorizationDataEntry> adEntries = new ArrayList<>();
74 try (Scanner scanner = new Scanner(file)) {
75 while (scanner.hasNext()) {
76 int type = scanner.nextInt();
77 byte[] data = Base64.getDecoder().decode(scanner.next());
78 adEntries.add(new AuthorizationDataEntry(type, data));
79 }
80 }
81
82 for (AuthorizationDataEntry adEntry : adEntries) {
83 int type = adEntry.getType();
84 byte[] data = adEntry.getData();
85 switch (type) {
86 case AdIfRelevantAsn1Parser.AD_IF_RELEVANT:
87 List<AuthorizationDataEntry> adIfRelevantEntries = AdIfRelevantAsn1Parser.parse(data);
88 for (AuthorizationDataEntry adIfRelevantEntry : adIfRelevantEntries) {
89 int adIfRelevantType = adIfRelevantEntry.getType();
90 byte[] adIfRelevantTypeData = adIfRelevantEntry.getData();
91 switch (adIfRelevantType) {
92 case AdIfRelevantAsn1Parser.AD_WIN2K_PAC:
93 int pacId = PAC_ID_GENERATOR.incrementAndGet();
94
95 Pac pac = new Pac(adIfRelevantTypeData, new PrivateSunPacSignatureVerifier());
96
97 if (keys != null) {
98 if (format.equals("listing")) System.out.print("Verifying PAC server signature...");
99 pac.verifySignature(keys);
100 if (format.equals("listing")) System.out.println("PASSED");
101 }
102
103 KerbValidationInfo kerbValidationInfo = pac.getKerbValidationInfo();
104 String effectiveName = kerbValidationInfo.getEffectiveName();
105 String fullName = kerbValidationInfo.getFullName();
106 String logonScript = kerbValidationInfo.getLogonScript();
107 String profilePath = kerbValidationInfo.getProfilePath();
108 String homeDirectory = kerbValidationInfo.getHomeDirectory();
109 String homeDirectoryDrive = kerbValidationInfo.getHomeDirectoryDrive();
110 long userId = kerbValidationInfo.getUserId();
111 long primaryGroupId = kerbValidationInfo.getPrimaryGroupId();
112 List<GroupMembership> groupIds = kerbValidationInfo.getGroupIds();
113 long userFlags = kerbValidationInfo.getUserFlags();
114 String logonServer = kerbValidationInfo.getLogonServer();
115 String logonDomainName = kerbValidationInfo.getLogonDomainName();
116 Sid logonDomainId = kerbValidationInfo.getLogonDomainId();
117 long userAccountControl = kerbValidationInfo.getUserAccountControl();
118 List<KerbSidAndAttributes> extraSids = kerbValidationInfo.getExtraSids();
119 Sid resourceGroupDomainSid = kerbValidationInfo.getResourceGroupDomainSid();
120 List<GroupMembership> resourceGroupIds = kerbValidationInfo.getResourceGroupIds();
121 switch (format) {
122 case "listing":
123 System.out.println("KerbValidationInfo:");
124 System.out.println(" effectiveName: " + effectiveName);
125 System.out.println(" fullName: " + fullName);
126 System.out.println(" logonScript: " + logonScript);
127 System.out.println(" profilePath: " + profilePath);
128 System.out.println(" homeDirectory: " + homeDirectory);
129 System.out.println(" homeDirectoryDrive: " + homeDirectoryDrive);
130 System.out.println(" userId: " + userId);
131 System.out.println(" primaryGroupId: " + primaryGroupId);
132 System.out.println(" groupIds (" + groupIds.size() + "):");
133 for (GroupMembership groupId : groupIds) {
134 System.out.println(" - " + groupId + " ("
135 + logonDomainId.append(groupId.getRelativeId()) + ")");
136 }
137 System.out.printf(" userFlags: 0x%08X%n", userFlags);
138 System.out.println(" logonServer: " + logonServer);
139 System.out.println(" logonDomainName: " + logonDomainName);
140 System.out.println(" logonDomainId: " + logonDomainId);
141 System.out.printf(" userAccountControl: 0x%08X%n", userAccountControl);
142 if (extraSids != null) {
143 System.out.println(" extraSids (" + extraSids.size() + "):");
144 for (KerbSidAndAttributes extraSid : extraSids) {
145 System.out.println(" - " + extraSid);
146 }
147 }
148 if (resourceGroupDomainSid != null) {
149 System.out.println(" resourceGroupDomainSid: " + resourceGroupDomainSid);
150 System.out.println(" resourceGroupIds (" + resourceGroupIds.size() + "):");
151 for (GroupMembership resourceGroupId : resourceGroupIds) {
152 System.out.println(" - " + resourceGroupId + " ("
153 + resourceGroupDomainSid.append(resourceGroupId.getRelativeId())
154 + ")");
155 }
156 }
157 break;
158 case "sql":
159 System.out.printf(
160 "insert into kerb_validation_info(pacId, effectiveName, fullName, logonScript, profilePath, homeDirectory, homeDirectoryDrive, userId, primaryGroupId, userFlags, logonServer, logonDomainName, logonDomainId, userAccountControl, resourceGroupDomainSid)"
161 + " values(%d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s', '%s', %d, %s);%n",
162 pacId,
163 effectiveName,
164 fullName,
165 logonScript,
166 profilePath,
167 homeDirectory,
168 homeDirectoryDrive,
169 userId,
170 primaryGroupId,
171 userFlags,
172 logonServer,
173 logonDomainName,
174 logonDomainId,
175 userAccountControl,
176 nullSafe(resourceGroupDomainSid));
177 for (GroupMembership groupId : groupIds) {
178 System.out.printf(
179 "insert into group_ids(pacId, relativeId, attributes) values(%d, %d, %d);%n",
180 pacId, groupId.getRelativeId(), groupId.getAttributes());
181 }
182 if (extraSids != null) {
183 for (KerbSidAndAttributes extraSid : extraSids) {
184 System.out.printf(
185 "insert into extra_sids(pacId, sid, attributes) values(%d, '%s', %d);%n",
186 pacId, extraSid.getSid(), extraSid.getAttributes());
187 }
188 }
189 if (resourceGroupDomainSid != null) {
190 for (GroupMembership resourceGroupId : resourceGroupIds) {
191 System.out.printf(
192 "insert into resource_group_ids(pacId, relativeId, attributes) values(%d, %d, %d);%n",
193 pacId,
194 resourceGroupId.getRelativeId(),
195 resourceGroupId.getAttributes());
196 }
197 }
198 break;
199 }
200 UpnDnsInfo upnDnsInfo = pac.getUpnDnsInfo();
201 if (upnDnsInfo != null) {
202 String upn = upnDnsInfo.getUpn();
203 String dnsDomainName = upnDnsInfo.getDnsDomainName();
204 long flags = upnDnsInfo.getFlags();
205 String samName = upnDnsInfo.getSamName();
206 Sid sid = upnDnsInfo.getSid();
207 switch (format) {
208 case "listing":
209 System.out.println("UpnDnsInfo:");
210 System.out.println(" upn: " + upn);
211 System.out.println(" dnsDomainName: " + dnsDomainName);
212 System.out.printf(" flags: 0x%08X%n", flags);
213 if (samName != null) {
214 System.out.println(" samName: " + samName);
215 System.out.println(" sid: " + sid);
216 }
217 break;
218 case "sql":
219 System.out.printf(
220 "insert into upn_dns_info(pacId, upn, dnsDomainName, flags, samName, sid)"
221 + " values(%d, '%s', '%s', %d, %s, %s);%n",
222 pacId, upn, dnsDomainName, flags, nullSafe(samName), nullSafe(sid));
223 break;
224 }
225 }
226 PacClientInfo pacClientInfo = pac.getPacClientInfo();
227 switch (format) {
228 case "listing":
229 System.out.println("PacClientInfo:");
230 System.out.println(" name: " + pacClientInfo.getName());
231 break;
232 }
233 PacSignatureData serverSignature = pac.getServerSignature();
234 PacSignatureData kdcSignature = pac.getKdcSignature();
235 switch (format) {
236 case "listing":
237 System.out.println("ServerSignature:");
238 System.out.println(" type: " + serverSignature.getType());
239 System.out.println(" signature: "
240 + Base64.getEncoder().encodeToString(serverSignature.getSignature()));
241 System.out.println("KdcSignature:");
242 System.out.println(" type: " + kdcSignature.getType());
243 System.out.println(" signature: "
244 + Base64.getEncoder().encodeToString(kdcSignature.getSignature()));
245 break;
246 }
247 break;
248 default:
249 System.err.println(
250 "Ignoring unsupported authorization data (AD-IF-RELEVANT) entry type "
251 + adIfRelevantType + " with data "
252 + Base64.getEncoder().encodeToString(adIfRelevantTypeData));
253 break;
254 }
255 }
256 break;
257 default:
258 System.err.println("Ignoring unsupported authorization data entry type " + type + " with data "
259 + Base64.getEncoder().encodeToString(data));
260 break;
261 }
262 }
263 }
264
265 private static String nullSafe(Object obj) {
266 return obj != null ? "'" + obj + "'" : "NULL";
267 }
268
269 public static void main(String[] args) throws IOException, SignatureException {
270 if (args.length == 0) {
271 System.err.println("No arguments provided");
272 System.exit(1);
273 }
274
275 int positionalArgs = 0;
276 String formatValue = "listing";
277 String verifySignatureValue = null;
278 boolean breakLoop = false;
279 while (positionalArgs < args.length && !breakLoop) {
280 switch (args[positionalArgs]) {
281 case "--format":
282 positionalArgs++;
283 if (positionalArgs > args.length - 1)
284 throw new IllegalArgumentException("Missing option value for '--format'");
285 formatValue = args[positionalArgs++];
286 break;
287 case "--verify-signature":
288 positionalArgs++;
289 if (positionalArgs > args.length - 1)
290 throw new IllegalArgumentException("Missing option value for '--verify-signature'");
291 verifySignatureValue = args[positionalArgs++];
292 break;
293 case "--":
294 positionalArgs++;
295 breakLoop = true;
296 break;
297 default:
298 breakLoop = true;
299 break;
300 }
301 }
302
303 if (!formatValue.equals("listing") && !formatValue.equals("sql"))
304 throw new IllegalArgumentException("Unsupported format value: " + formatValue);
305
306 KerberosKey[] keysValue = null;
307 if (verifySignatureValue != null) {
308 String loginEntryName = verifySignatureValue;
309 LoginContext lc = null;
310 try {
311 lc = new LoginContext(loginEntryName);
312 lc.login();
313 Subject subject = lc.getSubject();
314 KerberosPrincipal principal = subject.getPrincipals(KerberosPrincipal.class)
315 .iterator()
316 .next();
317 KeyTab keyTab =
318 subject.getPrivateCredentials(KeyTab.class).iterator().next();
319 keysValue = keyTab.getKeys(principal);
320 } catch (LoginException | SecurityException e) {
321 throw new IllegalStateException(
322 "Failed to load Kerberos keys for login entry '" + loginEntryName + "'", e);
323 } finally {
324 if (lc != null) {
325 try {
326 lc.logout();
327 } catch (LoginException e) {
328 ;
329 }
330 }
331 }
332 }
333 final KerberosKey[] keys = keysValue;
334
335 final String format = formatValue;
336 if (format.equals("sql")) {
337 System.out.println("BEGIN TRANSACTION;");
338 try (BufferedReader r = new BufferedReader(new InputStreamReader(
339 Krb5AuthzDataDumpPrinter.class.getResourceAsStream("/net/sf/michaelo/tomcat/pac/create-tables.sql"),
340 StandardCharsets.UTF_8))) {
341 r.lines().forEach(line -> System.out.println(line));
342 }
343 }
344
345 for (int i = positionalArgs; i < args.length; i++) {
346 Path path = Paths.get(args[i]);
347 if (Files.notExists(path)) {
348 System.err.printf("Ignoring non-existing path '%s'%n", path);
349 continue;
350 }
351 if (Files.isRegularFile(path)) {
352 dumpFile(path, format, keys);
353 } else if (Files.isDirectory(path)) {
354 try (var stream = Files.walk(path)) {
355 stream.filter(Files::isRegularFile).forEach(file -> {
356 try {
357 dumpFile(file, format, keys);
358 } catch (IOException e) {
359 throw new UncheckedIOException(e);
360 } catch (SignatureException e) {
361 throw new RuntimeException(e);
362 }
363 });
364 }
365 } else {
366 System.err.printf("Ignoring unsupported path '%s'%n", path);
367 continue;
368 }
369 }
370
371 if (format.equals("sql")) {
372 System.out.println("COMMIT;");
373 }
374 }
375 }