/*=============================================================================# # Copyright (c) 2009-2016 Stephan Wahlbrink (WalWare.de) and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of either (per the licensee's choosing) # - the Eclipse Public License v1.0 # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v10.html, or # - the GNU Lesser General Public License v2.1 or newer # which accompanies this distribution, and is available at # http://www.gnu.org/licenses/lgpl.html # # Contributors: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.rj.server; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.security.Key; import java.util.Arrays; import javax.crypto.Cipher; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import de.walware.rj.RjException; import de.walware.rj.server.srvext.ServerAuthMethod; public final class ServerLogin implements Serializable { private static final long serialVersionUID = -596748668244272719L; private long id; private Key pubkey; private Callback[] callbacks; public ServerLogin() { } public ServerLogin(final long id, final Key pubkey, final Callback[] callbacks) { this.id = id; this.pubkey = pubkey; this.callbacks = callbacks; } public long getId() { return this.id; } /** * The callback items which should be handled by the client. * * @return an array with all callback objects * @see CallbackHandler */ public Callback[] getCallbacks() { return this.callbacks; } /** * Must be called by the client to create login data for authentification * when connecting to server via {@link Server#start(ServerLogin, String[])} * or {@link Server#connect(ServerLogin)}. * * @return the login data prepared to send to the server * @throws RjException when creating login data failed */ public ServerLogin createAnswer() throws RjException { try { Callback[] copy; if (this.callbacks != null) { copy = new Callback[this.callbacks.length]; System.arraycopy(this.callbacks, 0, copy, 0, this.callbacks.length); if (this.pubkey != null) { process(copy, Cipher.ENCRYPT_MODE, this.pubkey); } } else { copy = null; } return new ServerLogin(this.id, null, copy); } catch (final Exception e) { throw new RjException("An error occurred when creating login data.", e); } } /** * Is called by server to decrypt data. By default it is called in * {@link ServerAuthMethod#performLogin(ServerLogin)}. * * @param privateKey the key to decrypt the data * @throws RjException when processing login data failed */ public void readAnswer(final Key privateKey) throws RjException { try { if (privateKey != null) { process(this.callbacks, Cipher.DECRYPT_MODE, privateKey); } } catch (final Exception e) { throw new RjException("An error occurred when processing login data.", e); } } private void process(final Callback[] callbacks, final int mode, final Key key) throws Exception { final Cipher with = Cipher.getInstance("RSA"); with.init(mode, key); final Charset charset = Charset.forName("UTF-8"); for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof PasswordCallback) { final PasswordCallback c = (PasswordCallback) callbacks[i]; final char[] orgPassword = c.getPassword(); if (orgPassword != null) { final byte[] orgBytes; if (mode == Cipher.ENCRYPT_MODE) { orgBytes = charset.encode(CharBuffer.wrap(orgPassword)).array(); } else { orgBytes = new byte[orgPassword.length]; for (int j = 0; j < orgBytes.length; j++) { orgBytes[j] = (byte) orgPassword[j]; } } final byte[] encBytes = with.doFinal(orgBytes); final char[] encPassword; if (mode == Cipher.ENCRYPT_MODE) { encPassword = new char[encBytes.length]; for (int j = 0; j < encPassword.length; j++) { encPassword[j] = (char) encBytes[j]; } } else { encPassword = charset.decode(ByteBuffer.wrap(encBytes)).array(); } if (mode == Cipher.ENCRYPT_MODE) { final PasswordCallback copy = new PasswordCallback(c.getPrompt(), c.isEchoOn()); copy.setPassword(encPassword); callbacks[i] = copy; } else { c.clearPassword(); c.setPassword(encPassword); } Arrays.fill(orgBytes, (byte) 0); Arrays.fill(orgPassword, (char) 0); Arrays.fill(encBytes, (byte) 0); Arrays.fill(encPassword, (char) 0); } continue; } } } /** * Clear data, especially the password. */ public void clearData() { this.pubkey = null; if (this.callbacks != null) { for (int i = 0; i < this.callbacks.length; i++) { if (this.callbacks[i] instanceof PasswordCallback) { ((PasswordCallback) this.callbacks[i]).clearPassword(); } this.callbacks[i] = null; } this.callbacks = null; } } }