1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.michaelo.tomcat.realm;
17
18 import java.security.Key;
19 import java.security.Principal;
20 import java.security.SignatureException;
21 import java.util.Arrays;
22 import java.util.Base64;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31
32 import javax.security.auth.Subject;
33 import javax.security.auth.kerberos.KerberosPrincipal;
34 import javax.security.auth.kerberos.KeyTab;
35 import javax.security.auth.login.LoginContext;
36 import javax.security.auth.login.LoginException;
37
38 import com.sun.security.jgss.AuthorizationDataEntry;
39 import com.sun.security.jgss.ExtendedGSSContext;
40 import com.sun.security.jgss.InquireType;
41
42 import net.sf.michaelo.tomcat.authenticator.SpnegoAuthenticator;
43 import net.sf.michaelo.tomcat.pac.GroupMembership;
44 import net.sf.michaelo.tomcat.pac.KerbValidationInfo;
45 import net.sf.michaelo.tomcat.pac.Pac;
46 import net.sf.michaelo.tomcat.pac.PrivateSunPacSignatureVerifier;
47 import net.sf.michaelo.tomcat.pac.asn1.AdIfRelevantAsn1Parser;
48
49 import org.apache.catalina.authenticator.SSLAuthenticator;
50 import org.ietf.jgss.GSSContext;
51 import org.ietf.jgss.GSSCredential;
52 import org.ietf.jgss.GSSException;
53 import org.ietf.jgss.GSSName;
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public class PacDataActiveDirectoryRealm extends ActiveDirectoryRealmBase {
88
89 private static final long USER_ACCOUNT_DISABLED = 0x00000001L;
90 private static final long USER_NORMAL_ACCOUNT = 0x00000010L;
91 private static final long USER_WORKSTATION_TRUST_ACCOUNT = 0x00000080L;
92
93 protected String loginEntryName;
94 protected boolean prependRoleFormat;
95 protected boolean addAdditionalAttributes;
96
97
98
99
100
101
102 public void setLoginEntryName(String loginEntryName) {
103 this.loginEntryName = loginEntryName;
104 }
105
106
107
108
109
110
111 public void setPrependRoleFormat(boolean prependRoleFormat) {
112 this.prependRoleFormat = prependRoleFormat;
113 }
114
115
116
117
118
119
120 public void setAddAdditionalAttributes(boolean addAdditionalAttributes) {
121 this.addAdditionalAttributes = addAdditionalAttributes;
122 }
123
124 protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential, GSSContext gssContext) {
125 if (gssName.isAnonymous()) return new ActiveDirectoryPrincipal(gssName, Sid.ANONYMOUS_SID, gssCredential);
126
127 if (gssContext instanceof ExtendedGSSContext) {
128 ExtendedGSSContext extGssContext = (ExtendedGSSContext) gssContext;
129
130 AuthorizationDataEntry[] adEntries = null;
131 try {
132 adEntries = (AuthorizationDataEntry[]) extGssContext.inquireSecContext(InquireType.KRB5_GET_AUTHZ_DATA);
133 } catch (GSSException e) {
134 logger.warn(sm.getString("krb5AuthzDataRealmBase.inquireSecurityContextFailed"), e);
135 }
136
137 if (adEntries == null) {
138 if (logger.isDebugEnabled())
139 logger.debug(sm.getString("krb5AuthzDataRealmBase.noDataProvided", gssName));
140 return null;
141 }
142
143 Optional<AuthorizationDataEntry> pacDataEntry = Optional.empty();
144 try {
145 pacDataEntry = Arrays.stream(adEntries)
146 .filter(adEntry -> adEntry.getType() == AdIfRelevantAsn1Parser.AD_IF_RELEVANT)
147 .map(adEntry -> AdIfRelevantAsn1Parser.parse(adEntry.getData()))
148 .flatMap(List::stream)
149 .filter(adEntry -> adEntry.getType() == AdIfRelevantAsn1Parser.AD_WIN2K_PAC)
150 .findFirst();
151 } catch (Exception e) {
152 String adEntriesStr = Arrays.stream(adEntries)
153 .map(adEntry ->
154 adEntry.getType() + " " + Base64.getEncoder().encodeToString(adEntry.getData()))
155 .collect(Collectors.joining(",", "[", "]"));
156 logger.warn(sm.getString("pacDataActiveDirectoryRealm.incorrectlyEncodedData", adEntriesStr), e);
157
158 return null;
159 }
160
161 if (pacDataEntry.isPresent()) {
162 byte[] pacData = pacDataEntry.get().getData();
163
164 Pac pac = null;
165 try {
166 pac = new Pac(pacData, new PrivateSunPacSignatureVerifier());
167 } catch (Exception e) {
168 logger.warn(
169 sm.getString(
170 "pacDataActiveDirectoryRealm.incorrectlyEncodedData",
171 Base64.getEncoder().encodeToString(pacData)),
172 e);
173
174 return null;
175 }
176
177 Key[] keys = getKeys();
178 try {
179 pac.verifySignature(keys);
180 } catch (SignatureException e) {
181 logger.warn(sm.getString("pacDataActiveDirectoryRealm.signatureVerificationFailed"), e);
182 return null;
183 }
184
185 KerbValidationInfo kerbValidationInfo = pac.getKerbValidationInfo();
186 long userAccountControl = kerbValidationInfo.getUserAccountControl();
187
188 if ((userAccountControl & USER_ACCOUNT_DISABLED) != 0L) {
189 logger.warn(sm.getString("activeDirectoryRealm.userFoundButDisabled", gssName));
190 return null;
191 }
192
193 if ((userAccountControl & USER_NORMAL_ACCOUNT) == 0L
194 && (userAccountControl & USER_WORKSTATION_TRUST_ACCOUNT) == 0L) {
195 logger.warn(sm.getString("activeDirectoryRealm.userFoundButNotSupported", gssName));
196 return null;
197 }
198
199 long userId = kerbValidationInfo.getUserId();
200 Sid sid = null;
201 if (userId == 0L) {
202 sid = kerbValidationInfo.getExtraSids().get(0).getSid();
203 } else {
204 sid = kerbValidationInfo.getLogonDomainId().append(userId);
205 }
206 Collection<Sid> groups = new HashSet<>();
207
208 Sid primaryGroupSid =
209 kerbValidationInfo.getLogonDomainId().append(kerbValidationInfo.getPrimaryGroupId());
210 groups.add(primaryGroupSid);
211 for (GroupMembership membership : kerbValidationInfo.getGroupIds()) {
212 groups.add(kerbValidationInfo.getLogonDomainId().append(membership.getRelativeId()));
213 }
214 if (kerbValidationInfo.getExtraSids() != null) {
215 long n = userId == 0L ? 1L : 0L;
216 groups.addAll(kerbValidationInfo.getExtraSids().stream()
217 .skip(n)
218 .map(extraSid -> extraSid.getSid())
219 .collect(Collectors.toList()));
220 }
221 if (kerbValidationInfo.getResourceGroupDomainSid() != null) {
222 groups.addAll(kerbValidationInfo.getResourceGroupIds().stream()
223 .map(resourceGroupId -> kerbValidationInfo
224 .getResourceGroupDomainSid()
225 .append(resourceGroupId.getRelativeId()))
226 .collect(Collectors.toList()));
227 }
228
229 Map<String, Object> additionalAttributesMap = null;
230 if (addAdditionalAttributes) {
231 additionalAttributesMap = new HashMap<String, Object>();
232 additionalAttributesMap.put("sAMAccountName", kerbValidationInfo.getEffectiveName());
233 additionalAttributesMap.put("displayName", kerbValidationInfo.getFullName());
234 additionalAttributesMap.put(
235 "msDS-PrincipalName",
236 kerbValidationInfo.getLogonDomainName() + "\\" + kerbValidationInfo.getEffectiveName());
237 if (pac.getUpnDnsInfo() != null) {
238 additionalAttributesMap.put(
239 "userPrincipalName", pac.getUpnDnsInfo().getUpn());
240 }
241 }
242
243 String roleFormatPrefix = prependRoleFormat ? "sid:" : "";
244 List<String> roles = groups.stream()
245 .map(String::valueOf)
246 .map(group -> roleFormatPrefix + group)
247 .collect(Collectors.toList());
248
249 if (logger.isTraceEnabled())
250 logger.trace(sm.getString("activeDirectoryRealm.foundRoles", roles.size(), gssName, roles));
251 else if (logger.isDebugEnabled())
252 logger.debug(sm.getString("activeDirectoryRealm.foundRolesCount", roles.size(), gssName));
253
254 return new ActiveDirectoryPrincipal(gssName, sid, roles, gssCredential, additionalAttributesMap);
255 } else {
256 if (logger.isDebugEnabled())
257 logger.debug(sm.getString("pacDataActiveDirectoryRealm.noDataProvided", gssName));
258 }
259 } else {
260 logger.error(sm.getString("krb5AuthzDataRealmBase.incompatibleSecurityContextType"));
261 }
262
263 return null;
264 }
265
266 protected Key[] getKeys() {
267 LoginContext lc = null;
268 try {
269 lc = new LoginContext(loginEntryName);
270 lc.login();
271 Subject subject = lc.getSubject();
272 Set<KerberosPrincipal> principals = subject.getPrincipals(KerberosPrincipal.class);
273 KerberosPrincipal principal = principals.iterator().next();
274 Set<KeyTab> privateCredentials = subject.getPrivateCredentials(KeyTab.class);
275 KeyTab keyTab = privateCredentials.iterator().next();
276 return keyTab.getKeys(principal);
277 } catch (LoginException | SecurityException e) {
278 throw new IllegalStateException("Failed to load Kerberos keys for login entry '" + loginEntryName + "'", e);
279 } finally {
280 if (lc != null) {
281 try {
282 lc.logout();
283 } catch (LoginException e) {
284 ;
285 }
286 }
287 }
288 }
289 }