/**
* Copyright 2013 Sean Kavanagh - sean.p.kavanagh6@gmail.com
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 com.keybox.manage.util;
import com.jcraft.jsch.*;
import com.keybox.common.util.AppConfig;
import com.keybox.manage.db.*;
import com.keybox.manage.model.*;
import com.keybox.manage.task.SecureShellTask;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SSH utility class used to create public/private key for system and distribute authorized key files
*/
public class SSHUtil {
public static final String PRIVATE_KEY = "privateKey";
public static final String PUBLIC_KEY = "publicKey";
private static Logger log = LoggerFactory.getLogger(SSHUtil.class);
public static final boolean keyManagementEnabled = "true".equals(AppConfig.getProperty("keyManagementEnabled"));
//system path to public/private key
public static final String KEY_PATH = SSHUtil.class.getClassLoader().getResource(".").getPath() + "keydb";
//key type - rsa or dsa
public static final String KEY_TYPE = AppConfig.getProperty("sshKeyType");
public static final int KEY_LENGTH = StringUtils.isNumeric(AppConfig.getProperty("sshKeyLength")) ? Integer.parseInt(AppConfig.getProperty("sshKeyLength")) : 2048;
//private key name
public static final String PVT_KEY = KEY_PATH + "/id_" + KEY_TYPE;
//public key name
public static final String PUB_KEY = PVT_KEY + ".pub";
public static final int SERVER_ALIVE_INTERVAL = StringUtils.isNumeric(AppConfig.getProperty("serverAliveInterval")) ? Integer.parseInt(AppConfig.getProperty("serverAliveInterval")) * 1000 : 60 * 1000;
public static final int SESSION_TIMEOUT = 60000;
public static final int CHANNEL_TIMEOUT = 60000;
private SSHUtil() {
}
/**
* returns the system's public key
*
* @return system's public key
*/
public static String getPublicKey() {
String publicKey = PUB_KEY;
//check to see if pub/pvt are defined in properties
if (StringUtils.isNotEmpty(AppConfig.getProperty(PRIVATE_KEY)) && StringUtils.isNotEmpty(AppConfig.getProperty(PUBLIC_KEY))) {
publicKey = AppConfig.getProperty(PUBLIC_KEY);
}
//read pvt ssh key
File file = new File(publicKey);
try {
publicKey = FileUtils.readFileToString(file);
} catch (Exception ex) {
log.error(ex.toString(), ex);
}
return publicKey;
}
/**
* returns the system's public key
*
* @return system's public key
*/
public static String getPrivateKey() {
String privateKey = PVT_KEY;
//check to see if pub/pvt are defined in properties
if (StringUtils.isNotEmpty(AppConfig.getProperty(PRIVATE_KEY)) && StringUtils.isNotEmpty(AppConfig.getProperty(PUBLIC_KEY))) {
privateKey = AppConfig.getProperty(PRIVATE_KEY);
}
//read pvt ssh key
File file = new File(privateKey);
try {
privateKey = FileUtils.readFileToString(file);
} catch (Exception ex) {
log.error(ex.toString(), ex);
}
return privateKey;
}
/**
* generates system's public/private key par and returns passphrase
*
* @return passphrase for system generated key
*/
public static String keyGen() {
//get passphrase cmd from properties file
Map<String, String> replaceMap = new HashMap<>();
replaceMap.put("randomPassphrase", UUID.randomUUID().toString());
String passphrase = AppConfig.getProperty("defaultSSHPassphrase", replaceMap);
AppConfig.updateProperty("defaultSSHPassphrase", "${randomPassphrase}");
return keyGen(passphrase);
}
/**
* delete SSH keys
*/
public static void deleteGenSSHKeys() {
deletePvtGenSSHKey();
//delete public key
try {
File file = new File(PUB_KEY);
if (file.exists()) {
FileUtils.forceDelete(file);
}
} catch (Exception ex) {
log.error(ex.toString(), ex);
}
}
/**
* delete SSH keys
*/
public static void deletePvtGenSSHKey() {
//delete private key
try {
File file = new File(PVT_KEY);
if (file.exists()) {
FileUtils.forceDelete(file);
}
} catch (Exception ex) {
log.error(ex.toString(), ex);
}
}
/**
* generates system's public/private key par and returns passphrase
*
* @return passphrase for system generated key
*/
public static String keyGen(String passphrase) {
try {
FileUtils.forceMkdir(new File(KEY_PATH));
deleteGenSSHKeys();
if (StringUtils.isEmpty(AppConfig.getProperty(PRIVATE_KEY)) || StringUtils.isEmpty(AppConfig.getProperty(PUBLIC_KEY))) {
//set key type
int type = KeyPair.RSA;
if ("dsa".equals(SSHUtil.KEY_TYPE)) {
type = KeyPair.DSA;
} else if ("ecdsa".equals(SSHUtil.KEY_TYPE)) {
type = KeyPair.ECDSA;
}
String comment = "keybox@global_key";
JSch jsch = new JSch();
KeyPair keyPair = KeyPair.genKeyPair(jsch, type, KEY_LENGTH);
keyPair.writePrivateKey(PVT_KEY, passphrase.getBytes());
keyPair.writePublicKey(PUB_KEY, comment);
System.out.println("Finger print: " + keyPair.getFingerPrint());
keyPair.dispose();
}
} catch (Exception e) {
log.error(e.toString(), e);
}
return passphrase;
}
/**
* distributes authorized keys for host system
*
* @param hostSystem object contains host system information
* @param passphrase ssh key passphrase
* @param password password to host system if needed
* @return status of key distribution
*/
public static HostSystem authAndAddPubKey(HostSystem hostSystem, String passphrase, String password) {
JSch jsch = new JSch();
Session session = null;
hostSystem.setStatusCd(HostSystem.SUCCESS_STATUS);
try {
ApplicationKey appKey = PrivateKeyDB.getApplicationKey();
//check to see if passphrase has been provided
if (passphrase == null || passphrase.trim().equals("")) {
passphrase = appKey.getPassphrase();
//check for null inorder to use key without passphrase
if (passphrase == null) {
passphrase = "";
}
}
//add private key
jsch.addIdentity(appKey.getId().toString(), appKey.getPrivateKey().trim().getBytes(), appKey.getPublicKey().getBytes(), passphrase.getBytes());
//create session
session = jsch.getSession(hostSystem.getUser(), hostSystem.getHost(), hostSystem.getPort());
//set password if passed in
if (password != null && !password.equals("")) {
session.setPassword(password);
}
session.setConfig("StrictHostKeyChecking", "no");
session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");
session.setServerAliveInterval(SERVER_ALIVE_INTERVAL);
session.connect(SESSION_TIMEOUT);
addPubKey(hostSystem, session, appKey.getPublicKey());
} catch (Exception e) {
log.info(e.toString(), e);
hostSystem.setErrorMsg(e.getMessage());
if (e.getMessage().toLowerCase().contains("userauth fail")) {
hostSystem.setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS);
} else if (e.getMessage().toLowerCase().contains("auth fail") || e.getMessage().toLowerCase().contains("auth cancel")) {
hostSystem.setStatusCd(HostSystem.AUTH_FAIL_STATUS);
} else if (e.getMessage().toLowerCase().contains("unknownhostexception")) {
hostSystem.setErrorMsg("DNS Lookup Failed");
hostSystem.setStatusCd(HostSystem.HOST_FAIL_STATUS);
} else {
hostSystem.setStatusCd(HostSystem.GENERIC_FAIL_STATUS);
}
}
if (session != null) {
session.disconnect();
}
return hostSystem;
}
/**
* distributes uploaded item to system defined
*
* @param hostSystem object contains host system information
* @param session an established SSH session
* @param source source file
* @param destination destination file
* @return status uploaded file
*/
public static HostSystem pushUpload(HostSystem hostSystem, Session session, String source, String destination) {
hostSystem.setStatusCd(HostSystem.SUCCESS_STATUS);
Channel channel = null;
ChannelSftp c = null;
try (FileInputStream file = new FileInputStream(source)) {
channel = session.openChannel("sftp");
channel.setInputStream(System.in);
channel.setOutputStream(System.out);
channel.connect(CHANNEL_TIMEOUT);
c = (ChannelSftp) channel;
destination = destination.replaceAll("~\\/|~", "");
c.put(file, destination);
} catch (Exception e) {
log.info(e.toString(), e);
hostSystem.setErrorMsg(e.getMessage());
hostSystem.setStatusCd(HostSystem.GENERIC_FAIL_STATUS);
}
//exit
if (c != null) {
c.exit();
}
//disconnect
if (channel != null) {
channel.disconnect();
}
return hostSystem;
}
/**
* distributes authorized keys for host system
*
* @param hostSystem object contains host system information
* @param session an established SSH session
* @param appPublicKey application public key value
* @return status of key distribution
*/
public static HostSystem addPubKey(HostSystem hostSystem, Session session, String appPublicKey) {
try {
String authorizedKeys = hostSystem.getAuthorizedKeys().replaceAll("~\\/|~", "");
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand("cat " + authorizedKeys);
((ChannelExec) channel).setErrStream(System.err);
channel.setInputStream(null);
InputStream in = channel.getInputStream();
InputStreamReader is = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(is);
channel.connect(CHANNEL_TIMEOUT);
String appPubKey = appPublicKey.replace("\n", "").trim();
StringBuilder existingKeysBuilder = new StringBuilder("");
String currentKey;
while ((currentKey = reader.readLine()) != null) {
existingKeysBuilder.append(currentKey).append("\n");
}
String existingKeys = existingKeysBuilder.toString();
existingKeys = existingKeys.replaceAll("\\n$", "");
reader.close();
//disconnect
channel.disconnect();
StringBuilder newKeysBuilder = new StringBuilder("");
if (keyManagementEnabled) {
//get keys assigned to system
List<String> assignedKeys = PublicKeyDB.getPublicKeysForSystem(hostSystem.getId());
for (String key : assignedKeys) {
newKeysBuilder.append(key.replace("\n", "").trim()).append("\n");
}
newKeysBuilder.append(appPubKey);
} else {
if (existingKeys.indexOf(appPubKey) < 0) {
newKeysBuilder.append(existingKeys).append("\n").append(appPubKey);
} else {
newKeysBuilder.append(existingKeys);
}
}
String newKeys = newKeysBuilder.toString();
if (!newKeys.equals(existingKeys)) {
log.info("Update Public Keys ==> " + newKeys);
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand("echo '" + newKeys + "' > " + authorizedKeys + "; chmod 600 " + authorizedKeys);
((ChannelExec) channel).setErrStream(System.err);
channel.setInputStream(null);
channel.connect(CHANNEL_TIMEOUT);
//disconnect
channel.disconnect();
}
} catch (Exception e) {
log.info(e.toString(), e);
hostSystem.setErrorMsg(e.getMessage());
hostSystem.setStatusCd(HostSystem.GENERIC_FAIL_STATUS);
}
return hostSystem;
}
/**
* return the next instance id based on ids defined in the session map
*
* @param sessionId session id
* @param userSessionMap user session map
* @return
*/
private static int getNextInstanceId(Long sessionId, Map<Long, UserSchSessions> userSessionMap) {
Integer instanceId = 1;
if (userSessionMap.get(sessionId) != null) {
for (Integer id : userSessionMap.get(sessionId).getSchSessionMap().keySet()) {
if (!id.equals(instanceId) && userSessionMap.get(sessionId).getSchSessionMap().get(instanceId) == null) {
return instanceId;
}
instanceId = instanceId + 1;
}
}
return instanceId;
}
/**
* open new ssh session on host system
*
* @param passphrase key passphrase for instance
* @param password password for instance
* @param userId user id
* @param sessionId session id
* @param hostSystem host system
* @param userSessionMap user session map
* @return status of systems
*/
public static HostSystem openSSHTermOnSystem(String passphrase, String password, Long userId, Long sessionId, HostSystem hostSystem, Map<Long, UserSchSessions> userSessionMap) {
JSch jsch = new JSch();
int instanceId = getNextInstanceId(sessionId, userSessionMap);
hostSystem.setStatusCd(HostSystem.SUCCESS_STATUS);
hostSystem.setInstanceId(instanceId);
SchSession schSession = null;
try {
ApplicationKey appKey = PrivateKeyDB.getApplicationKey();
//check to see if passphrase has been provided
if (passphrase == null || passphrase.trim().equals("")) {
passphrase = appKey.getPassphrase();
//check for null inorder to use key without passphrase
if (passphrase == null) {
passphrase = "";
}
}
//add private key
jsch.addIdentity(appKey.getId().toString(), appKey.getPrivateKey().trim().getBytes(), appKey.getPublicKey().getBytes(), passphrase.getBytes());
//create session
Session session = jsch.getSession(hostSystem.getUser(), hostSystem.getHost(), hostSystem.getPort());
//set password if it exists
if (password != null && !password.trim().equals("")) {
session.setPassword(password);
}
session.setConfig("StrictHostKeyChecking", "no");
session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");
session.setServerAliveInterval(SERVER_ALIVE_INTERVAL);
session.connect(SESSION_TIMEOUT);
Channel channel = session.openChannel("shell");
if ("true".equals(AppConfig.getProperty("agentForwarding"))) {
((ChannelShell) channel).setAgentForwarding(true);
}
((ChannelShell) channel).setPtyType("xterm");
InputStream outFromChannel = channel.getInputStream();
//new session output
SessionOutput sessionOutput = new SessionOutput(sessionId, hostSystem);
Runnable run = new SecureShellTask(sessionOutput, outFromChannel);
Thread thread = new Thread(run);
thread.start();
OutputStream inputToChannel = channel.getOutputStream();
PrintStream commander = new PrintStream(inputToChannel, true);
channel.connect();
schSession = new SchSession();
schSession.setUserId(userId);
schSession.setSession(session);
schSession.setChannel(channel);
schSession.setCommander(commander);
schSession.setInputToChannel(inputToChannel);
schSession.setOutFromChannel(outFromChannel);
schSession.setHostSystem(hostSystem);
//refresh keys for session
addPubKey(hostSystem, session, appKey.getPublicKey());
} catch (Exception e) {
log.info(e.toString(), e);
hostSystem.setErrorMsg(e.getMessage());
if (e.getMessage().toLowerCase().contains("userauth fail")) {
hostSystem.setStatusCd(HostSystem.PUBLIC_KEY_FAIL_STATUS);
} else if (e.getMessage().toLowerCase().contains("auth fail") || e.getMessage().toLowerCase().contains("auth cancel")) {
hostSystem.setStatusCd(HostSystem.AUTH_FAIL_STATUS);
} else if (e.getMessage().toLowerCase().contains("unknownhostexception")) {
hostSystem.setErrorMsg("DNS Lookup Failed");
hostSystem.setStatusCd(HostSystem.HOST_FAIL_STATUS);
} else {
hostSystem.setStatusCd(HostSystem.GENERIC_FAIL_STATUS);
}
}
//add session to map
if (hostSystem.getStatusCd().equals(HostSystem.SUCCESS_STATUS)) {
//get the server maps for user
UserSchSessions userSchSessions = userSessionMap.get(sessionId);
//if no user session create a new one
if (userSchSessions == null) {
userSchSessions = new UserSchSessions();
}
Map<Integer, SchSession> schSessionMap = userSchSessions.getSchSessionMap();
//add server information
schSessionMap.put(instanceId, schSession);
userSchSessions.setSchSessionMap(schSessionMap);
//add back to map
userSessionMap.put(sessionId, userSchSessions);
}
SystemStatusDB.updateSystemStatus(hostSystem, userId);
SystemDB.updateSystem(hostSystem);
return hostSystem;
}
/**
* distributes public keys to all systems
*/
public static void distributePubKeysToAllSystems() {
if (keyManagementEnabled) {
List<HostSystem> hostSystemList = SystemDB.getAllSystems();
for (HostSystem hostSystem : hostSystemList) {
hostSystem = SSHUtil.authAndAddPubKey(hostSystem, null, null);
SystemDB.updateSystem(hostSystem);
}
}
}
/**
* distributes public keys to all systems under profile
*
* @param profileId profile id
*/
public static void distributePubKeysToProfile(Long profileId) {
if (keyManagementEnabled) {
List<HostSystem> hostSystemList = ProfileSystemsDB.getSystemsByProfile(profileId);
for (HostSystem hostSystem : hostSystemList) {
hostSystem = SSHUtil.authAndAddPubKey(hostSystem, null, null);
SystemDB.updateSystem(hostSystem);
}
}
}
/**
* distributes public keys to all systems under all user profiles
*
* @param userId user id
*/
public static void distributePubKeysToUser(Long userId) {
if (keyManagementEnabled) {
for (Profile profile : UserProfileDB.getProfilesByUser(userId)) {
List<HostSystem> hostSystemList = ProfileSystemsDB.getSystemsByProfile(profile.getId());
for (HostSystem hostSystem : hostSystemList) {
hostSystem = SSHUtil.authAndAddPubKey(hostSystem, null, null);
SystemDB.updateSystem(hostSystem);
}
}
}
}
/**
* returns public key fingerprint
*
* @param publicKey public key
* @return fingerprint of public key
*/
public static String getFingerprint(String publicKey) {
String fingerprint = null;
if (StringUtils.isNotEmpty(publicKey)) {
try {
KeyPair keyPair = KeyPair.load(new JSch(), null, publicKey.getBytes());
if (keyPair != null) {
fingerprint = keyPair.getFingerPrint();
}
} catch (JSchException ex) {
log.error(ex.toString(), ex);
}
}
return fingerprint;
}
/**
* returns public key type
*
* @param publicKey public key
* @return fingerprint of public key
*/
public static String getKeyType(String publicKey) {
String keyType = null;
if (StringUtils.isNotEmpty(publicKey)) {
try {
KeyPair keyPair = KeyPair.load(new JSch(), null, publicKey.getBytes());
if (keyPair != null) {
int type = keyPair.getKeyType();
if (KeyPair.DSA == type) {
keyType = "DSA";
} else if (KeyPair.RSA == type) {
keyType = "RSA";
} else if (KeyPair.ECDSA == type) {
keyType = "ECDSA";
} else if (KeyPair.UNKNOWN == type) {
keyType = "UNKNOWN";
} else if (KeyPair.ERROR == type) {
keyType = "ERROR";
}
}
} catch (JSchException ex) {
log.error(ex.toString(), ex);
}
}
return keyType;
}
}