/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.embedded.ssh.internal;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.codehaus.jackson.annotate.JsonIgnore;
import de.rcenvironment.core.embedded.ssh.api.SshAccount;
/**
* Default {@link SshAccount} implementation.
*
* @author Sebastian Holtappels
* @author Robert Mischke
* @author Brigitte Boden (added public key authentication)
*/
public class SshAccountImpl implements SshAccount {
private String loginName;
private String password;
private String passwordHash;
private String publicKey;
@JsonIgnore
private PublicKey publicKeyObj;
private String role = "";
// allow to disable/suspend accounts without deleting them
private boolean enabled = true;
public SshAccountImpl() {} // for JSON serialization
public SshAccountImpl(String username, String password, String passwordHash, String publicKey, String role) {
this.loginName = username;
this.password = password;
this.passwordHash = passwordHash;
this.publicKey = publicKey;
this.role = role;
parsePublicKey();
}
/**
* Method to validate the SshUser.
*
* @param roles - List of roles
* @param log the log instance to send validation failures to (as warnings)
* @return true if valid, else false
*/
public boolean validate(List<SshAccountRole> roles, Log log) {
boolean isValid = true;
boolean noMatchingRole = true;
// every user has a name (that is not the empty String)
if (loginName == null || loginName.isEmpty()) {
log.warn("Found a user without username");
isValid = false;
}
// warn on deprecated clear-text passwords, but do not fail the validation
if (isValid && password != null) {
log.warn("SSH user \"" + loginName + "\" has an insecure clear-text password. "
+ "Refer to the RCE User Guide on how to change it to a secure format.");
}
// every user has a password or a public key
if ((password == null || password.isEmpty())
&& (passwordHash == null || passwordHash.isEmpty())
&& (publicKey == null || publicKey.isEmpty())) {
log.warn("User \"" + loginName + "\" does not have a password, password hash, or public key");
isValid = false;
}
if (password != null && passwordHash != null) {
log.warn("User \"" + loginName + "\" has both a clear-text and a hashed password at the same time");
isValid = false;
}
// ensure public key is valid (can be parsed to public key object)
if (publicKey != null && !publicKey.isEmpty() && publicKeyObj == null) {
log.warn("SSH User \"" + loginName + "\" has an invalid public key (only RSA keys are valid)");
isValid = false;
}
// ensure role is not null
if (role == null) {
log.warn("Changed role for user \"" + loginName + "\" from null to empty string");
role = "";
}
// role of user exist
for (SshAccountRole curRole : roles) {
if (role.equals(curRole.getRoleName())) {
noMatchingRole = false;
break;
}
}
if (noMatchingRole) {
log.warn("Non-existing role \"" + role + "\" configured for user \"" + loginName
+ "\". Default permissions (\"help\", \"exit\", \"version\") will be used.");
}
return isValid;
}
@Override
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getPasswordHash() {
return passwordHash;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String getPublicKey() {
return publicKey;
}
/**
* Sets the string representation of the public key and parses it to a key object.
*
* @param publicKey the string representation of the public key
*/
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
parsePublicKey();
}
@JsonIgnore
@Override
public PublicKey getPublicKeyObj() {
return publicKeyObj;
}
@Override
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
/**
* {@inheritDoc}
*
* @see de.rcenvironment.core.embedded.ssh.api.SshAccount#isEnabled()
*/
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
private void parsePublicKey() {
if (publicKey != null && !publicKey.isEmpty()) {
try {
// Parse known key string to a PublicKey object.
byte[] encKey = Base64.decodeBase64(publicKey.split(" ")[1]);
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(encKey));
byte[] header = readElement(inputStream);
String pubKeyFormat = new String(header);
if (pubKeyFormat.equals("ssh-rsa")) {
byte[] publicExponent = readElement(inputStream);
byte[] modulus = readElement(inputStream);
KeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKeyObj = kf.generatePublic(spec);
}
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException | ArrayIndexOutOfBoundsException e) {
// No valid public key
publicKeyObj = null;
}
}
}
// Helper method for parsing public key string
private static byte[] readElement(DataInput dataInput) throws IOException {
int len = dataInput.readInt();
byte[] buf = new byte[len];
dataInput.readFully(buf);
return buf;
}
}