View Javadoc
1   /**
2    * Waffle (https://github.com/dblock/waffle)
3    *
4    * Copyright (c) 2010 - 2015 Application Security, Inc.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Eclipse Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/epl-v10.html
10   *
11   * Contributors:
12   *     Application Security, Inc.
13   */
14  package waffle.windows.auth.impl;
15  
16  import java.net.InetAddress;
17  import java.net.UnknownHostException;
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.concurrent.TimeUnit;
21  
22  import waffle.windows.auth.IWindowsAccount;
23  import waffle.windows.auth.IWindowsAuthProvider;
24  import waffle.windows.auth.IWindowsComputer;
25  import waffle.windows.auth.IWindowsCredentialsHandle;
26  import waffle.windows.auth.IWindowsDomain;
27  import waffle.windows.auth.IWindowsIdentity;
28  import waffle.windows.auth.IWindowsSecurityContext;
29  
30  import com.google.common.cache.Cache;
31  import com.google.common.cache.CacheBuilder;
32  import com.sun.jna.platform.win32.Advapi32;
33  import com.sun.jna.platform.win32.Kernel32;
34  import com.sun.jna.platform.win32.Netapi32Util;
35  import com.sun.jna.platform.win32.Netapi32Util.DomainTrust;
36  import com.sun.jna.platform.win32.Secur32;
37  import com.sun.jna.platform.win32.Sspi;
38  import com.sun.jna.platform.win32.Sspi.CtxtHandle;
39  import com.sun.jna.platform.win32.Sspi.SecBufferDesc;
40  import com.sun.jna.platform.win32.Win32Exception;
41  import com.sun.jna.platform.win32.WinBase;
42  import com.sun.jna.platform.win32.WinError;
43  import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
44  import com.sun.jna.ptr.IntByReference;
45  
46  /**
47   * Windows Auth Provider.
48   * 
49   * @author dblock[at]dblock[dot]org
50   */
51  public class WindowsAuthProviderImpl implements IWindowsAuthProvider {
52  
53      /** The continue contexts. */
54      private final Cache<String, CtxtHandle> continueContexts;
55  
56      /**
57       * Instantiates a new windows auth provider impl.
58       */
59      public WindowsAuthProviderImpl() {
60          this(30);
61      }
62  
63      /**
64       * A Windows authentication provider.
65       * 
66       * @param continueContextsTimeout
67       *            Timeout for security contexts in seconds.
68       */
69      public WindowsAuthProviderImpl(final int continueContextsTimeout) {
70          this.continueContexts = CacheBuilder.newBuilder().expireAfterWrite(continueContextsTimeout, TimeUnit.SECONDS)
71                  .build();
72      }
73  
74      /* (non-Javadoc)
75       * @see waffle.windows.auth.IWindowsAuthProvider#acceptSecurityToken(java.lang.String, byte[], java.lang.String)
76       */
77      @Override
78      public IWindowsSecurityContext acceptSecurityToken(final String connectionId, final byte[] token,
79              final String securityPackage) {
80  
81          if (token == null || token.length == 0) {
82              this.continueContexts.asMap().remove(connectionId);
83              throw new Win32Exception(WinError.SEC_E_INVALID_TOKEN);
84          }
85  
86          final IWindowsCredentialsHandle serverCredential = new WindowsCredentialsHandleImpl(null,
87                  Sspi.SECPKG_CRED_INBOUND, securityPackage);
88          serverCredential.initialize();
89  
90          WindowsSecurityContextImpl sc;
91  
92          int rc;
93          int tokenSize = Sspi.MAX_TOKEN_SIZE;
94  
95          CtxtHandle continueContext;
96          SecBufferDesc pbServerToken;
97          SecBufferDesc pbClientToken;
98          final IntByReference pfClientContextAttr = new IntByReference();
99          final CtxtHandle phNewServerContext = new CtxtHandle();
100         do {
101             pbServerToken = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, tokenSize);
102             pbClientToken = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, token);
103 
104             continueContext = this.continueContexts.asMap().get(connectionId);
105 
106             rc = Secur32.INSTANCE.AcceptSecurityContext(serverCredential.getHandle(), continueContext, pbClientToken,
107                     Sspi.ISC_REQ_CONNECTION, Sspi.SECURITY_NATIVE_DREP, phNewServerContext, pbServerToken,
108                     pfClientContextAttr, null);
109 
110             sc = new WindowsSecurityContextImpl();
111             sc.setCredentialsHandle(serverCredential.getHandle());
112             sc.setSecurityPackage(securityPackage);
113             sc.setSecurityContext(phNewServerContext);
114 
115             switch (rc) {
116                 case WinError.SEC_E_BUFFER_TOO_SMALL:
117                     tokenSize += Sspi.MAX_TOKEN_SIZE;
118                     sc.dispose();
119                     WindowsSecurityContextImpl.dispose(continueContext);
120                     break;
121                 case WinError.SEC_E_OK:
122                     // the security context received from the client was accepted
123                     this.continueContexts.asMap().remove(connectionId);
124                     // if an output token was generated by the function, it must be sent to the client process
125                     if (pbServerToken.pBuffers != null && pbServerToken.cBuffers == 1
126                             && pbServerToken.pBuffers[0].cbBuffer > 0) {
127                         sc.setToken(pbServerToken.getBytes() == null ? new byte[0] : pbServerToken.getBytes().clone());
128                     }
129                     sc.setContinue(false);
130                     break;
131                 case WinError.SEC_I_CONTINUE_NEEDED:
132                     // the server must send the output token to the client and wait for a returned token
133                     this.continueContexts.put(connectionId, phNewServerContext);
134                     sc.setToken(pbServerToken.getBytes() == null ? new byte[0] : pbServerToken.getBytes().clone());
135                     sc.setContinue(true);
136                     break;
137                 default:
138                     sc.dispose();
139                     WindowsSecurityContextImpl.dispose(continueContext);
140                     this.continueContexts.asMap().remove(connectionId);
141                     throw new Win32Exception(rc);
142             }
143         } while (rc == WinError.SEC_E_BUFFER_TOO_SMALL);
144 
145         return sc;
146     }
147 
148     /* (non-Javadoc)
149      * @see waffle.windows.auth.IWindowsAuthProvider#getCurrentComputer()
150      */
151     @Override
152     public IWindowsComputer getCurrentComputer() {
153         try {
154             return new WindowsComputerImpl(InetAddress.getLocalHost().getHostName());
155         } catch (final UnknownHostException e) {
156             throw new RuntimeException(e);
157         }
158     }
159 
160     /* (non-Javadoc)
161      * @see waffle.windows.auth.IWindowsAuthProvider#getDomains()
162      */
163     @Override
164     public IWindowsDomain[] getDomains() {
165         final List<IWindowsDomain> domains = new ArrayList<IWindowsDomain>();
166         final DomainTrust[] trusts = Netapi32Util.getDomainTrusts();
167         for (final DomainTrust trust : trusts) {
168             domains.add(new WindowsDomainImpl(trust));
169         }
170         return domains.toArray(new IWindowsDomain[0]);
171     }
172 
173     /* (non-Javadoc)
174      * @see waffle.windows.auth.IWindowsAuthProvider#logonDomainUser(java.lang.String, java.lang.String, java.lang.String)
175      */
176     @Override
177     public IWindowsIdentity logonDomainUser(final String username, final String domain, final String password) {
178         return this.logonDomainUserEx(username, domain, password, WinBase.LOGON32_LOGON_NETWORK,
179                 WinBase.LOGON32_PROVIDER_DEFAULT);
180     }
181 
182     /* (non-Javadoc)
183      * @see waffle.windows.auth.IWindowsAuthProvider#logonDomainUserEx(java.lang.String, java.lang.String, java.lang.String, int, int)
184      */
185     @Override
186     public IWindowsIdentity logonDomainUserEx(final String username, final String domain, final String password,
187             final int logonType, final int logonProvider) {
188         final HANDLEByReference phUser = new HANDLEByReference();
189         if (!Advapi32.INSTANCE.LogonUser(username, domain, password, logonType, logonProvider, phUser)) {
190             throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
191         }
192         return new WindowsIdentityImpl(phUser.getValue());
193     }
194 
195     /* (non-Javadoc)
196      * @see waffle.windows.auth.IWindowsAuthProvider#logonUser(java.lang.String, java.lang.String)
197      */
198     @Override
199     public IWindowsIdentity logonUser(final String username, final String password) {
200         // username@domain UPN format is natively supported by the
201         // Windows LogonUser API process domain\\username format
202         final String[] userNameDomain = username.split("\\\\", 2);
203         if (userNameDomain.length == 2) {
204             return this.logonDomainUser(userNameDomain[1], userNameDomain[0], password);
205         }
206         return this.logonDomainUser(username, null, password);
207     }
208 
209     /* (non-Javadoc)
210      * @see waffle.windows.auth.IWindowsAuthProvider#lookupAccount(java.lang.String)
211      */
212     @Override
213     public IWindowsAccount lookupAccount(final String username) {
214         return new WindowsAccountImpl(username);
215     }
216 
217     /* (non-Javadoc)
218      * @see waffle.windows.auth.IWindowsAuthProvider#resetSecurityToken(java.lang.String)
219      */
220     @Override
221     public void resetSecurityToken(final String connectionId) {
222         this.continueContexts.asMap().remove(connectionId);
223     }
224 
225     /**
226      * Number of elements in the continue contexts map.
227      * 
228      * @return Number of elements in the hash map.
229      */
230     public int getContinueContextsSize() {
231         return this.continueContexts.asMap().size();
232     }
233 }