/* * © 2016 AgNO3 Gmbh & Co. KG * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package jcifs.tests; import java.io.IOException; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.kerberos.KeyTab; import org.ietf.jgss.GSSException; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jcifs.CIFSContext; import jcifs.CIFSException; import jcifs.ResolverType; import jcifs.SmbResource; import jcifs.SmbTreeHandle; import jcifs.smb.JAASAuthenticator; import jcifs.smb.Kerb5Authenticator; import jcifs.smb.SmbException; import jcifs.smb.SmbFile; import jcifs.smb.SmbSessionInternal; import jcifs.smb.SmbTreeHandleInternal; import jcifs.smb.SmbUnsupportedOperationException; import sun.security.jgss.krb5.Krb5Util; import sun.security.krb5.Asn1Exception; import sun.security.krb5.Credentials; import sun.security.krb5.EncryptionKey; import sun.security.krb5.KrbAsReqBuilder; import sun.security.krb5.KrbException; import sun.security.krb5.PrincipalName; import sun.security.krb5.RealmException; import sun.security.krb5.internal.KerberosTime; /** * @author mbechler * */ @SuppressWarnings ( { "javadoc", "restriction" } ) @RunWith ( Parameterized.class ) public class KerberosTest extends BaseCIFSTest { private static final Logger log = LoggerFactory.getLogger(KerberosTest.class); /** * @param properties */ public KerberosTest ( String name, Map<String, String> properties ) { super(name, properties); } @Parameters ( name = "{0}" ) public static Collection<Object> configs () { return getConfigs("smb1", "smb2", "smb30", "smb31", "forceSpnegoIntegrity"); } @Before public void setup () { Assume.assumeTrue("Skip kerberos auth", getProperties().get("test.skip.kerberos") == null); } @Test public void testKRB () throws Exception { Assume.assumeTrue(getContext().getConfig().getResolveOrder().contains(ResolverType.RESOLVER_DNS)); Subject s = getInitiatorSubject(getTestUser(), getTestUserPassword(), getTestUserDomainRequired(), null); CIFSContext ctx = getContext().withCredentials(new Kerb5Authenticator(s, getTestUserDomainRequired(), getTestUser(), getTestUserPassword())); try ( SmbResource f = new SmbFile(getTestShareURL(), ctx) ) { f.exists(); } catch ( SmbUnsupportedOperationException e ) { Assume.assumeTrue("Using short names", false); } } @Test public void testJAAS () throws CIFSException, MalformedURLException { Assume.assumeTrue(getContext().getConfig().getResolveOrder().contains(ResolverType.RESOLVER_DNS)); CIFSContext ctx = getContext().withCredentials(new JAASAuthenticator(getTestUserDomainRequired(), getTestUser(), getTestUserPassword())); try ( SmbResource f = new SmbFile(getTestShareURL(), ctx) ) { f.exists(); } catch ( SmbUnsupportedOperationException e ) { Assume.assumeTrue("Using short names", false); } } @Test public void testFallback () throws Exception { Subject s = getInitiatorSubject(getTestUser(), getTestUserPassword(), getTestUserDomainRequired(), null); Kerb5Authenticator auth = new Kerb5Authenticator(s, getTestUserDomainRequired(), getTestUser(), getTestUserPassword()); auth.setForceFallback(true); CIFSContext ctx = getContext().withCredentials(auth); try ( SmbResource f = new SmbFile(getTestShareURL(), ctx) ) { f.exists(); } catch ( SmbUnsupportedOperationException e ) { Assume.assumeTrue("Using short names", false); } } @Test public void testReauthenticate () throws Exception { Assume.assumeTrue(getContext().getConfig().getResolveOrder().contains(ResolverType.RESOLVER_DNS)); Subject s = getInitiatorSubject(getTestUser(), getTestUserPassword(), getTestUserDomainRequired(), null); Kerb5Authenticator creds = new RefreshableKerb5Authenticator(s, getTestUserDomainRequired(), getTestUser(), getTestUserPassword()); CIFSContext ctx = getContext().withCredentials(creds); try ( SmbFile f = new SmbFile(getTestShareURL(), ctx); SmbTreeHandleInternal th = (SmbTreeHandleInternal) f.getTreeHandle(); SmbSessionInternal session = (SmbSessionInternal) th.getSession() ) { Assume.assumeTrue("Not SMB2", th.isSMB2()); f.exists(); session.reauthenticate(); f.exists(); } } @Test public void testSessionExpiration () throws Exception { Assume.assumeTrue(getContext().getConfig().getResolveOrder().contains(ResolverType.RESOLVER_DNS)); long start = System.currentTimeMillis() / 1000 * 1000; // this is not too great as it depends on timing/clockskew // first we need to obtain a ticket, therefor need valid credentials // then we need to wait until the ticket is expired int wait = 10 * 1000; long princExp = start + ( wait / 2 ); Subject s = getInitiatorSubject(getTestUser(), getTestUserPassword(), getTestUserDomainRequired(), princExp); Kerb5Authenticator creds = new RefreshableKerb5Authenticator(s, getTestUserDomainRequired(), getTestUser(), getTestUserPassword()); CIFSContext ctx = getContext().withCredentials(creds); try ( SmbFile f = new SmbFile(getTestShareURL(), ctx) ) { try ( SmbTreeHandle th = f.getTreeHandle() ) { Assume.assumeTrue("Not SMB2", th.isSMB2()); } f.exists(); Thread.sleep(wait); try ( SmbResource r = f.resolve("test") ) { r.exists(); } } catch ( SmbUnsupportedOperationException e ) { Assume.assumeTrue("Using short names", false); } catch ( SmbException e ) { if ( ! ( e.getCause() instanceof GSSException ) ) { throw e; } log.error("Kerberos problem", e); Assume.assumeTrue("Kerberos problem, clockskew?", false); } } public static Subject getInitiatorSubject ( KeyTab keytab, final KerberosPrincipal principal ) throws Asn1Exception, KrbException, IOException { KerberosTicket ticket = getKerberosTicket(keytab, principal); Set<Object> privCreds = new HashSet<>(); privCreds.add(ticket); return new Subject(false, new HashSet<>(Arrays.asList((Principal) principal)), Collections.EMPTY_SET, privCreds); } private static KerberosTicket getKerberosTicket ( KeyTab keytab, final KerberosPrincipal principal ) throws Asn1Exception, KrbException, IOException { PrincipalName principalName = convertPrincipal(principal); EncryptionKey[] keys = Krb5Util.keysFromJavaxKeyTab(keytab, principalName); if ( keys == null || keys.length == 0 ) { throw new KrbException("Could not find any keys in keytab for " + principalName); //$NON-NLS-1$ } KrbAsReqBuilder builder = new KrbAsReqBuilder(principalName, keytab); Credentials creds = builder.action().getCreds(); builder.destroy(); return Krb5Util.credsToTicket(creds); } public static Subject getInitiatorSubject ( KerberosPrincipal principal, String password, Long expire ) throws Exception { KerberosTicket ticket = getKerberosTicket(principal, password, expire); Set<Object> privCreds = new HashSet<>(); privCreds.add(ticket); return new Subject(false, new HashSet<>(Arrays.asList((Principal) principal)), Collections.EMPTY_SET, privCreds); } private static KerberosTicket getKerberosTicket ( KerberosPrincipal principal, String password, Long expire ) throws Exception { PrincipalName principalName = convertPrincipal(principal); KrbAsReqBuilder builder = new KrbAsReqBuilder(principalName, password != null ? password.toCharArray() : new char[0]); if ( expire != null ) { System.out.println("Request expires " + expire); KerberosTime till = new KerberosTime(expire); Field tillF = builder.getClass().getDeclaredField("till"); tillF.setAccessible(true); tillF.set(builder, till); } Credentials creds = builder.action().getCreds(); builder.destroy(); KerberosTicket ticket = Krb5Util.credsToTicket(creds); System.out.println("Ends " + ticket.getEndTime().getTime()); return ticket; } /** * @param principal * @return * @throws RealmException */ protected static PrincipalName convertPrincipal ( KerberosPrincipal principal ) throws RealmException { PrincipalName principalName = new PrincipalName( principal.getName() + PrincipalName.NAME_REALM_SEPARATOR + principal.getRealm(), PrincipalName.KRB_NT_PRINCIPAL); return principalName; } public static Subject getInitiatorSubject ( String userName, String password, String realm, Long expire ) throws Exception { KerberosPrincipal principal = new KerberosPrincipal(String.format("%s@%s", userName, realm), KerberosPrincipal.KRB_NT_PRINCIPAL); return getInitiatorSubject(principal, password, expire); } public final class RefreshableKerb5Authenticator extends Kerb5Authenticator { /** * */ private static final long serialVersionUID = -4979600496889213143L; public RefreshableKerb5Authenticator ( Subject subject, String domain, String username, String password ) { super(subject, domain, username, password); } @Override public void refresh () throws CIFSException { try { System.out.println("Refreshing"); setSubject(getInitiatorSubject(getTestUser(), getTestUserPassword(), getTestUserDomainRequired(), null)); System.out.println("Refreshed"); } catch ( Exception e ) { throw new CIFSException("Failed to refresh credentials", e); } } @Override public Kerb5Authenticator clone () { Kerb5Authenticator auth = new RefreshableKerb5Authenticator(getSubject(), getUserDomain(), getUser(), getPassword()); cloneInternal(auth, this); return auth; } } }