You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by er...@apache.org on 2004/11/03 20:30:06 UTC

svn commit: rev 56516 - incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw

Author: erodriguez
Date: Wed Nov  3 11:30:05 2004
New Revision: 56516

Added:
   incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw/ChangePasswordDispatcher.java
   incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw/ChangePasswordService.java
Log:
Core of the change password service.  Tested with gnome-kerberos, Apache Kerberos server, and OpenLDAP.

Added: incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw/ChangePasswordDispatcher.java
==============================================================================
--- (empty file)
+++ incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw/ChangePasswordDispatcher.java	Wed Nov  3 11:30:05 2004
@@ -0,0 +1,81 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.kerberos.changepw;
+
+import org.apache.kerberos.changepw.io.*;
+import org.apache.kerberos.changepw.messages.*;
+import org.apache.kerberos.changepw.store.*;
+import org.apache.kerberos.crypto.*;
+import org.apache.kerberos.kdc.*;
+import org.apache.kerberos.kdc.store.*;
+
+import java.io.*;
+
+public class ChangePasswordDispatcher {
+	
+	private PrincipalStore   _bootstrap;
+	private CryptoService    _cryptoService;
+	private KdcConfiguration _config;
+	private PasswordStore    _store;
+	
+	private ChangePasswordService      _changepwService;
+	private ChangePasswordErrorService _errorService;
+	
+	public ChangePasswordDispatcher(KdcConfiguration config, BootstrapStore bootstrap, PasswordStore store) {
+		
+		_config    = config;
+		_bootstrap = bootstrap;
+		_store     = store;
+		
+		_cryptoService   = new CryptoService(_config);
+		_changepwService = new ChangePasswordService(_store, _bootstrap, _cryptoService, _config);
+	}
+	
+	public byte[] dispatch(byte[] requestBytes) throws IOException {
+		
+		byte[] reply = null;
+		
+		try {
+			ChangePasswordRequestDecoder decoder = new ChangePasswordRequestDecoder();
+			ChangePasswordRequest changepwRequest = decoder.decode(requestBytes);
+			
+			ChangePasswordReply changepwReply = _changepwService.getReplyFor(changepwRequest);
+			
+			ChangePasswordReplyEncoder encoder = new ChangePasswordReplyEncoder();
+			reply = encoder.encode(changepwReply);
+			
+		} catch (KerberosException ke) {
+			
+			System.out.println("Returning error message:  " + ke.getMessage());
+			ChangePasswordError errorMessage = _errorService.getReplyFor(ke);
+			ChangePasswordErrorEncoder errorEncoder = new ChangePasswordErrorEncoder();
+			reply = errorEncoder.encode(errorMessage);
+			
+		} catch (IOException ioe) {
+			
+			System.out.println("Returning error message:  " + ioe.getMessage());
+			ioe.printStackTrace();
+			ChangePasswordError errorMessage =
+				_errorService.getReplyFor(ChangePasswordException.KRB5_KPASSWD_MALFORMED);
+			ChangePasswordErrorEncoder errorEncoder = new ChangePasswordErrorEncoder();
+			reply = errorEncoder.encode(errorMessage);
+		}
+		
+		return reply;
+	}
+}
+

Added: incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw/ChangePasswordService.java
==============================================================================
--- (empty file)
+++ incubator/directory/kerberos/trunk/source/main/org/apache/kerberos/changepw/ChangePasswordService.java	Wed Nov  3 11:30:05 2004
@@ -0,0 +1,271 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.kerberos.changepw;
+
+import org.apache.kerberos.changepw.io.*;
+import org.apache.kerberos.changepw.messages.*;
+import org.apache.kerberos.changepw.store.*;
+import org.apache.kerberos.changepw.value.*;
+import org.apache.kerberos.crypto.*;
+import org.apache.kerberos.io.decoder.*;
+import org.apache.kerberos.io.encoder.*;
+import org.apache.kerberos.kdc.*;
+import org.apache.kerberos.kdc.store.*;
+import org.apache.kerberos.messages.*;
+import org.apache.kerberos.messages.application.*;
+import org.apache.kerberos.messages.components.*;
+import org.apache.kerberos.messages.components.Authenticator;
+import org.apache.kerberos.messages.value.*;
+
+import java.io.*;
+import java.net.*;
+
+import javax.security.auth.kerberos.*;
+
+/**
+ * Kerberos Change Password and Set Password Protocols (RFC 3244)
+ */
+public class ChangePasswordService {
+	
+	private PasswordStore    _store;
+	private PrincipalStore   _bootstrap;
+	private CryptoService    _cryptoService;
+	private KdcConfiguration _config;
+	
+	public ChangePasswordService(PasswordStore store, PrincipalStore bootstrap,
+			CryptoService cryptoService, KdcConfiguration config) {
+		
+		_store         = store;
+		_bootstrap     = bootstrap;
+		_cryptoService = cryptoService;
+		_config        = config;
+	}
+	
+	public ChangePasswordReply getReplyFor(ChangePasswordRequest request)
+			throws KerberosException, IOException {
+		
+		ApplicationRequest authHeader = request.getAuthHeader();
+		
+		Ticket ticket = authHeader.getTicket();
+		
+		Authenticator authenticator = verifyAuthHeader(authHeader, ticket);
+		
+		verifyTicket(ticket, _config.getChangepwPrincipal());
+		
+		// TODO - check ticket is for service authorized to change passwords
+		// ticket.getServerPrincipal().getName().equals(_config.getChangepwPrincipal().getName()));
+		
+		// TODO - check client principal in ticket is authorized to change password
+		
+		// get the subsession key from the Authenticator
+		EncryptionKey sessionKey = authenticator.getSubSessionKey();
+		
+		// decrypt the request's private message with the subsession key
+		EncryptedData encReqPrivPart = request.getPrivateMessage().getEncryptedPart();
+		EncKrbPrivPart privatePart;
+		try {
+			byte[] decPrivPart = _cryptoService.decrypt(sessionKey, encReqPrivPart);
+
+			EncKrbPrivPartDecoder privDecoder = new EncKrbPrivPartDecoder();
+			privatePart = privDecoder.decode(decPrivPart);
+		} catch (KerberosException ke) {
+			ke.printStackTrace();
+			throw ChangePasswordException.KRB5_KPASSWD_AUTHERROR;
+		}
+		
+		ChangePasswordData passwordData = null;
+		
+		if (request.getProtocolVersionNumber() == (short)1) {
+			// Use protocol version 0x0001, the legacy Kerberos change password protocol
+			ChangePasswordDataModifier modifier = new ChangePasswordDataModifier();
+			modifier.setNewPassword(privatePart.getUserData());
+			passwordData = modifier.getChangePasswdData();
+		} else {
+			// Use protocol version 0xFF80, the backwards-compatible MS protocol
+			ChangePasswordDataDecoder passwordDecoder = new ChangePasswordDataDecoder();
+			passwordData = passwordDecoder.decodeChangePasswordData(privatePart.getUserData());
+		}
+		
+		// usec and seq-number must be present per MS but aren't in legacy kpasswd
+		// seq-number must have same value as authenticator
+		// ignore r-address
+		
+		// generate key from password
+		String password = new String(passwordData.getNewPassword());
+		KerberosPrincipal clientPrincipal = authenticator.getClientPrincipal();
+		KerberosKey newKey = new KerberosKey(clientPrincipal, password.toCharArray(), "DES");
+		
+		// store password in database
+		String principalName = _store.changePassword(clientPrincipal, newKey.getEncoded());
+		System.out.println("Successfully modified principal named " + principalName);
+		
+		// begin building reply
+		
+		// create priv message
+		// user-data component is short result code
+		EncKrbPrivPartModifier modifier = new EncKrbPrivPartModifier();
+		byte[] resultCode = {(byte)0x00, (byte)0x00};
+		modifier.setUserData(resultCode);
+		
+		modifier.setSenderAddress(new HostAddress(InetAddress.getLocalHost()));
+		EncKrbPrivPart privPart = modifier.getEncKrbPrivPart();
+		
+		EncKrbPrivPartEncoder encoder = new EncKrbPrivPartEncoder();
+		byte[] encodedPrivPart = encoder.encode(privPart);
+		
+		EncryptedData encPrivPart = null;
+		try {
+			encPrivPart = _cryptoService.getEncryptedData(sessionKey, encodedPrivPart);
+		} catch (KerberosException ke) {
+			ke.printStackTrace();
+		}
+		PrivateMessage privateMessage = new PrivateMessage(encPrivPart);
+		
+		// Begin AP_REP generation
+		EncApRepPartModifier encApModifier = new EncApRepPartModifier();
+		encApModifier.setClientTime(authenticator.getClientTime());
+		encApModifier.setClientMicroSecond(authenticator.getClientMicroSecond());
+		encApModifier.setSequenceNumber(new Integer(authenticator.getSequenceNumber()));
+		encApModifier.setSubSessionKey(authenticator.getSubSessionKey());
+		
+		EncApRepPart repPart = encApModifier.getEncApRepPart();
+		EncApRepPartEncoder repEncoder = new EncApRepPartEncoder();
+		byte[] encodedRepPart = repEncoder.encode(repPart);
+		
+		EncryptedData encRepPart = null;
+		try {
+			encRepPart = _cryptoService.getEncryptedData(ticket.getSessionKey(), encodedRepPart);
+		} catch (KerberosException ke) {
+			ke.printStackTrace();
+		}
+		ApplicationReply appReply = new ApplicationReply(encRepPart);
+		
+		// return status message value object
+		ChangePasswordReplyModifier replyModifier = new ChangePasswordReplyModifier();
+		replyModifier.setApplicationReply(appReply);
+		replyModifier.setPrivateMessage(privateMessage);
+		
+		return replyModifier.getChangePasswordReply();
+		
+	}
+	
+	// TODO - this is a duplicate from the TGS service, with the ReplayCache disabled and ...
+	// TODO - ... changepw doesn't have the same LDAP store access
+	// RFC 1510 A.10.  KRB_AP_REQ verification
+	private Authenticator verifyAuthHeader(ApplicationRequest authHeader, Ticket ticket)
+			throws KerberosException, IOException {
+		
+		if (authHeader.getProtocolVersionNumber() != 5)
+			throw KerberosException.KRB_AP_ERR_BADVERSION;
+		if (authHeader.getMessageType() != MessageType.KRB_AP_REQ)
+			throw KerberosException.KRB_AP_ERR_MSG_TYPE;
+		if (authHeader.getTicket().getTicketVersionNumber() != 5)
+			throw KerberosException.KRB_AP_ERR_BADVERSION;
+		
+		// TODO - support multiple encryption types
+		EncryptionKey serverKey = null;
+		if (authHeader.getOption(ApOptions.USE_SESSION_KEY)) {
+			serverKey = authHeader.getTicket().getSessionKey();
+		} else {
+			KerberosPrincipal serverPrincipal = ticket.getServerPrincipal();
+			PrincipalStoreEntry serverEntry = _bootstrap.getEntry(serverPrincipal);
+			
+			if (serverEntry != null) {
+				serverKey = serverEntry.getEncryptionKey();
+			}/*
+			 else {
+				serverKey = _store.getEntry(serverPrincipal).getEncryptionKey();
+			}
+			*/
+		}
+		if (serverKey == null) {
+			// TODO - check server key version number, skvno; requires store
+			if (false)
+				throw KerberosException.KRB_AP_ERR_BADKEYVER;
+			
+			throw KerberosException.KRB_AP_ERR_NOKEY;
+		}
+		
+		try {
+			byte[] decTicketPart = _cryptoService.decrypt(serverKey, ticket.getEncPart());
+
+			EncTicketPartDecoder ticketPartDecoder = new EncTicketPartDecoder();
+			EncTicketPart encPart = ticketPartDecoder.decode(decTicketPart);
+			ticket.setEncTicketPart(encPart);
+		} catch (KerberosException ke) {
+			throw KerberosException.KRB_AP_ERR_BAD_INTEGRITY;
+		}
+		
+		Authenticator authenticator;
+		
+		try {
+			byte[] decAuthenticator = _cryptoService.decrypt(ticket.getSessionKey(), authHeader.getEncPart());
+			AuthenticatorDecoder authDecoder = new AuthenticatorDecoder();
+			authenticator = authDecoder.decode(decAuthenticator);
+		} catch (KerberosException ke) {
+			throw KerberosException.KRB_AP_ERR_BAD_INTEGRITY;
+		}
+		
+		if (!authenticator.getClientPrincipal().getName().equals(ticket.getClientPrincipal().getName())) {
+			throw KerberosException.KRB_AP_ERR_BADMATCH;
+		}
+		
+		// TODO - need to get at IP Address for sender
+		if (ticket.getClientAddresses() != null) {
+			// if (sender_address(packet) is not in decr_ticket.caddr)
+            //    then error_out(KRB_AP_ERR_BADADDR);
+		}
+        else {
+        	// if (application requires addresses) then
+            //    error_out(KRB_AP_ERR_BADADDR);
+        }
+		
+		/*
+		if(_replayCache.isReplay(authenticator.getClientTime(), authenticator.getClientPrincipal())) {
+			throw KerberosException.KRB_AP_ERR_REPEAT;
+		}
+        
+		_replayCache.save(authenticator.getClientTime(), authenticator.getClientPrincipal());
+		*/
+		
+		if (!authenticator.getClientTime().isInClockSkew(_config.getClockSkew()))
+			throw KerberosException.KRB_AP_ERR_SKEW;
+		
+		if (ticket.getStartTime() != null && !ticket.getStartTime().isInClockSkew(_config.getClockSkew()) ||
+				ticket.getFlag(TicketFlags.INVALID))
+				// it hasn't yet become valid
+                throw KerberosException.KRB_AP_ERR_TKT_NYV;
+		
+		// TODO - doesn't take into account skew
+		if (!ticket.getEndTime().greaterThan(new KerberosTime()))
+            throw KerberosException.KRB_AP_ERR_TKT_EXPIRED;
+		
+		authHeader.setOption(ApOptions.MUTUAL_REQUIRED);
+		
+		return authenticator;
+	}
+	
+	// TODO - this is a duplicate from the TGS service
+	private void verifyTicket(Ticket ticket, KerberosPrincipal serverPrincipal)
+			throws KerberosException {
+
+		if (!ticket.getRealm().equals(_config.getPrimaryRealm())
+				&& !ticket.getServerPrincipal().equals(serverPrincipal))
+			throw KerberosException.KRB_AP_ERR_NOT_US;
+	}
+}
+