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; + } +} +
