blob: 7be5e37596af717de1d5f9a33f968d440a31ba71 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.changepw.protocol;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import javax.security.auth.kerberos.KerberosPrincipal;
import org.apache.directory.junit.tools.Concurrent;
import org.apache.directory.junit.tools.ConcurrentJunitRunner;
import org.apache.directory.server.changepw.ChangePasswordServer;
import org.apache.directory.server.changepw.io.ChangePasswordDataEncoder;
import org.apache.directory.server.changepw.messages.ChangePasswordError;
import org.apache.directory.server.changepw.messages.ChangePasswordRequest;
import org.apache.directory.server.changepw.value.ChangePasswordData;
import org.apache.directory.server.changepw.value.ChangePasswordDataModifier;
import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
import org.apache.directory.server.kerberos.shared.messages.application.PrivateMessage;
import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
import org.apache.directory.server.kerberos.shared.store.TicketFactory;
import org.apache.directory.shared.kerberos.KerberosMessageType;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.codec.options.ApOptions;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.codec.types.PrincipalNameType;
import org.apache.directory.shared.kerberos.components.EncKrbPrivPart;
import org.apache.directory.shared.kerberos.components.EncryptedData;
import org.apache.directory.shared.kerberos.components.EncryptionKey;
import org.apache.directory.shared.kerberos.components.HostAddress;
import org.apache.directory.shared.kerberos.components.PrincipalName;
import org.apache.directory.shared.kerberos.exceptions.KerberosException;
import org.apache.directory.shared.kerberos.messages.ApReq;
import org.apache.directory.shared.kerberos.messages.Authenticator;
import org.apache.directory.shared.kerberos.messages.KrbError;
import org.apache.directory.shared.kerberos.messages.Ticket;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.DummySession;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests the ChangePasswordProtocolHandler.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
@RunWith(ConcurrentJunitRunner.class)
@Concurrent()
public class ChangepwProtocolHandlerTest
{
/**
* The Change Password SUCCESS result code.
*/
// Never used...
//private static final byte[] SUCCESS = new byte[]
// { ( byte ) 0x00, ( byte ) 0x00 };
private static ChangePasswordServer config;
private static PrincipalStore store;
private static ChangePasswordProtocolHandler handler;
private static final CipherTextHandler cipherTextHandler = new CipherTextHandler();
/**
* Creates a new instance of ChangepwProtocolHandlerTest.
*/
@BeforeClass
public static void setup()
{
config = new ChangePasswordServer();
store = new MapPrincipalStoreImpl();
handler = new ChangePasswordProtocolHandler( config, store );
}
/**
* Tests the protocol version number, which must be '1'.
*/
@Test
public void testProtocolVersionNumber()
{
ChPwdDummySession session = new ChPwdDummySession();
ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 2, null, null );
handler.messageReceived( session, message );
ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
KrbError error = reply.getKrbError();
assertEquals( "Protocol version unsupported", 6, error.getErrorCode() );
}
/**
* Tests when a service ticket is missing that the request is rejected with
* the correct error message.
*/
@Test
public void testMissingTicket()
{
ChPwdDummySession session = new ChPwdDummySession();
ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 1, null, null );
handler.messageReceived( session, message );
ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
KrbError error = reply.getKrbError();
assertEquals( "Request failed due to an error in authentication processing", 3, error.getErrorCode() );
}
/**
* Tests when the INITIAL flag is missing that the request is rejected with
* the correct error message.
*
* @throws Exception
*/
@Test
@Ignore( "test started to fail after changes done to kerberos-codec, should be verified after completing the codec work" )
public void testInitialFlagRequired() throws Exception
{
ChPwdDummySession session = new ChPwdDummySession();
KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
KerberosPrincipal serverPrincipal = new KerberosPrincipal( "kadmin/changepw@EXAMPLE.COM" );
String serverPassword = "secret";
TicketFactory ticketFactory = new TicketFactory();
EncryptionKey serverKey = ticketFactory.getServerKey( serverPrincipal, serverPassword );
Ticket serviceTicket = ticketFactory.getTicket( clientPrincipal, serverPrincipal, serverKey );
EncryptionKey subSessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
ApOptions apOptions = new ApOptions();
Authenticator authenticator = new Authenticator();
authenticator.setVersionNumber( 5 );
authenticator.setCRealm( "EXAMPLE.COM" );
authenticator.setCName( getPrincipalName( "hnelson" ) );
authenticator.setCTime( new KerberosTime() );
authenticator.setCusec( 0 );
authenticator.setSubKey( subSessionKey );
EncryptedData encryptedAuthenticator = cipherTextHandler.seal( serviceTicket.getEncTicketPart().getKey(), authenticator
, KeyUsage.AP_REQ_AUTHNT_SESS_KEY );
ApReq apReq = new ApReq();
apReq.setOption( apOptions );
apReq.setTicket( serviceTicket );
apReq.setAuthenticator( encryptedAuthenticator );
String newPassword = "secretsecret";
PrivateMessage priv = getChangePasswordPrivateMessage( newPassword, subSessionKey );
ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 1, apReq, priv );
handler.messageReceived( session, message );
ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
KrbError error = reply.getKrbError();
assertEquals( "Initial flag required", 7, error.getErrorCode() );
//ChangePasswordReply reply = ( ChangePasswordReply ) session.getMessage();
//processChangePasswordReply( reply, serviceTicket.getSessionKey(), subSessionKey );
}
/**
* TODO : Check if this method is important or not. It was called in
* the testInitialFlagRequired() method above, but this call has been commented
* private void processChangePasswordReply( ChangePasswordReply reply, EncryptionKey sessionKey,
* EncryptionKey subSessionKey ) throws Exception
* {
* PrivateMessage privateMessage = reply.getPrivateMessage();
* <p/>
* EncryptedData encPrivPart = privateMessage.getEncryptedPart();
* <p/>
* EncKrbPrivPart privPart;
* <p/>
* try
* {
* privPart = ( EncKrbPrivPart ) cipherTextHandler.unseal( EncKrbPrivPart.class, subSessionKey, encPrivPart,
* KeyUsage.NUMBER13 );
* }
* catch ( KerberosException ke )
* {
* return;
* }
* <p/>
* // Verify result code.
* byte[] resultCode = privPart.getUserData();
* <p/>
* assertTrue( "Password change returned SUCCESS (0x00 0x00).", Arrays.equals( SUCCESS, resultCode ) );
* }
*/
@Test
public void testSetPassword() throws Exception
{
ChPwdDummySession session = new ChPwdDummySession();
KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
KerberosPrincipal serverPrincipal = new KerberosPrincipal( "kadmin/changepw@EXAMPLE.COM" );
String serverPassword = "secret";
TicketFactory ticketFactory = new TicketFactory();
EncryptionKey serverKey = ticketFactory.getServerKey( serverPrincipal, serverPassword );
Ticket serviceTicket = ticketFactory.getTicket( clientPrincipal, serverPrincipal, serverKey );
EncryptionKey subSessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
ApOptions apOptions = new ApOptions();
Authenticator authenticator = new Authenticator();
authenticator.setVersionNumber( 5 );
authenticator.setCRealm( "EXAMPLE.COM" );
authenticator.setCName( getPrincipalName( "hnelson" ) );
authenticator.setCTime( new KerberosTime() );
authenticator.setCusec( 0 );
EncryptedData encryptedAuthenticator = cipherTextHandler.seal( serverKey, authenticator,
KeyUsage.AP_REQ_AUTHNT_SESS_KEY );
ApReq apReq = new ApReq();
apReq.setOption( apOptions );
apReq.setTicket( serviceTicket );
apReq.setAuthenticator( encryptedAuthenticator );
String newPassword = "secretsecret";
PrivateMessage priv = getSetPasswordPrivateMessage( newPassword, subSessionKey, getPrincipalName( "hnelson" ) );
ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 0xFF80, apReq, priv );
handler.messageReceived( session, message );
ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
KrbError error = reply.getKrbError();
assertEquals( "Protocol version unsupported", 6, error.getErrorCode() );
}
/*
* Legacy kpasswd (Change Password) version. User data is the password bytes.
*/
private PrivateMessage getChangePasswordPrivateMessage( String newPassword, EncryptionKey subSessionKey )
throws UnsupportedEncodingException, KerberosException, UnknownHostException
{
// Make private message part.
EncKrbPrivPart encReqPrivPart = new EncKrbPrivPart();
encReqPrivPart.setUserData( newPassword.getBytes( "UTF-8" ) );
encReqPrivPart.setSenderAddress( new HostAddress( InetAddress.getLocalHost() ) );
// Seal private message part.
EncryptedData encryptedPrivPart = cipherTextHandler.seal( subSessionKey, encReqPrivPart, KeyUsage.KRB_PRIV_ENC_PART_CHOSEN_KEY );
// Make private message with private message part.
PrivateMessage privateMessage = new PrivateMessage();
privateMessage.setProtocolVersionNumber( 5 );
privateMessage.setMessageType( KerberosMessageType.ENC_PRIV_PART );
privateMessage.setEncryptedPart( encryptedPrivPart );
return privateMessage;
}
/*
* Set/Change Password version. User data is an encoding of the new password and the target principal.
*/
private PrivateMessage getSetPasswordPrivateMessage( String newPassword, EncryptionKey subSessionKey,
PrincipalName targetPrincipalName ) throws UnsupportedEncodingException, KerberosException,
UnknownHostException, IOException
{
// Make private message part.
EncKrbPrivPart encReqPrivPart = new EncKrbPrivPart();
ChangePasswordDataModifier dataModifier = new ChangePasswordDataModifier();
dataModifier.setNewPassword( newPassword.getBytes() );
dataModifier.setTargetName( targetPrincipalName );
dataModifier.setTargetRealm( "EXAMPLE.COM" );
ChangePasswordData data = dataModifier.getChangePasswdData();
ChangePasswordDataEncoder encoder = new ChangePasswordDataEncoder();
byte[] dataBytes = encoder.encode( data );
encReqPrivPart.setUserData( dataBytes );
encReqPrivPart.setSenderAddress( new HostAddress( InetAddress.getLocalHost() ) );
// Seal private message part.
EncryptedData encryptedPrivPart = cipherTextHandler.seal( subSessionKey, encReqPrivPart, KeyUsage.KRB_PRIV_ENC_PART_CHOSEN_KEY );
// Make private message with private message part.
PrivateMessage privateMessage = new PrivateMessage();
privateMessage.setProtocolVersionNumber( 5 );
privateMessage.setMessageType( KerberosMessageType.ENC_PRIV_PART );
privateMessage.setEncryptedPart( encryptedPrivPart );
return privateMessage;
}
private PrincipalName getPrincipalName( String name )
{
PrincipalName principalName = new PrincipalName();
principalName.addName( name );
principalName.setNameType( PrincipalNameType.KRB_NT_PRINCIPAL );
return principalName;
}
private static class ChPwdDummySession extends DummySession
{
Object message;
private Object getMessage()
{
return message;
}
public SocketAddress getRemoteAddress()
{
return new InetSocketAddress( 10464 );
}
public int getScheduledWriteRequests()
{
return 0;
}
public WriteFuture write(Object message)
{
this.message = message;
return null;
}
}
}