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