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