blob: a1fd5c09842b1e3ea92cead8dfb86aa4cb78e4f8 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.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 org.eclipse.statet.rj.RjException;
import org.eclipse.statet.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;
}
}
}