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