/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.sshd.agent.common;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutorService;
import org.apache.sshd.agent.SshAgent;
import org.apache.sshd.agent.SshAgentConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.Pair;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public abstract class AbstractAgentProxy extends AbstractLoggingBean implements SshAgent, ExecutorServiceConfigurer {
private ExecutorService executor;
private boolean shutdownExecutor;
protected AbstractAgentProxy() {
super();
}
@Override
public ExecutorService getExecutorService() {
return executor;
}
@Override
public void setExecutorService(ExecutorService service) {
executor = service;
}
@Override
public boolean isShutdownOnExit() {
return shutdownExecutor;
}
@Override
public void setShutdownOnExit(boolean shutdown) {
shutdownExecutor = shutdown;
}
@Override
public List<Pair<PublicKey, String>> getIdentities() throws IOException {
Buffer buffer = createBuffer(SshAgentConstants.SSH2_AGENTC_REQUEST_IDENTITIES, 1);
buffer = request(prepare(buffer));
int type = buffer.getUByte();
if (type != SshAgentConstants.SSH2_AGENT_IDENTITIES_ANSWER) {
throw new SshException("Bad agent identities answer: " + SshAgentConstants.getCommandMessageName(type));
}
int nbIdentities = buffer.getInt();
if (nbIdentities > 1024) {
throw new SshException("Bad identities count: " + nbIdentities);
}
List<Pair<PublicKey, String>> keys = new ArrayList<>(nbIdentities);
for (int i = 0; i < nbIdentities; i++) {
PublicKey key = buffer.getPublicKey();
String comment = buffer.getString();
if (log.isDebugEnabled()) {
log.debug("getIdentities() key type={}, comment={}, fingerprint={}",
KeyUtils.getKeyType(key), comment, KeyUtils.getFingerPrint(key));
}
keys.add(new Pair<>(key, comment));
}
return keys;
}
@Override
public byte[] sign(PublicKey key, byte[] data) throws IOException {
Buffer buffer = createBuffer(SshAgentConstants.SSH2_AGENTC_SIGN_REQUEST);
buffer.putPublicKey(key);
buffer.putBytes(data);
buffer.putInt(0);
buffer = request(prepare(buffer));
int responseType = buffer.getUByte();
if (responseType != SshAgentConstants.SSH2_AGENT_SIGN_RESPONSE) {
throw new SshException("Bad signing response type: " + SshAgentConstants.getCommandMessageName(responseType));
}
Buffer buf = new ByteArrayBuffer(buffer.getBytes());
String algorithm = buf.getString();
byte[] signature = buf.getBytes();
if (log.isDebugEnabled()) {
log.debug("sign({})[{}] {}: {}",
KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key),
algorithm, BufferUtils.toHex(':', signature));
}
return signature;
}
@Override
public void addIdentity(KeyPair kp, String comment) throws IOException {
Buffer buffer = createBuffer(SshAgentConstants.SSH2_AGENTC_ADD_IDENTITY);
buffer.putKeyPair(kp);
buffer.putString(comment);
if (log.isDebugEnabled()) {
log.debug("addIdentity({})[{}]: {}", KeyUtils.getKeyType(kp), comment, KeyUtils.getFingerPrint(kp.getPublic()));
}
buffer = request(prepare(buffer));
int available = buffer.available();
int response = (available >= 1) ? buffer.getUByte() : -1;
if ((available != 1) || (response != SshAgentConstants.SSH_AGENT_SUCCESS)) {
throw new SshException("Bad addIdentity response (" + SshAgentConstants.getCommandMessageName(response) + ") - available=" + available);
}
}
@Override
public void removeIdentity(PublicKey key) throws IOException {
Buffer buffer = createBuffer(SshAgentConstants.SSH2_AGENTC_REMOVE_IDENTITY);
buffer.putPublicKey(key);
if (log.isDebugEnabled()) {
log.debug("removeIdentity({}) {}", KeyUtils.getKeyType(key), KeyUtils.getFingerPrint(key));
}
buffer = request(prepare(buffer));
int available = buffer.available();
int response = (available >= 1) ? buffer.getUByte() : -1;
if ((available != 1) || (response != SshAgentConstants.SSH_AGENT_SUCCESS)) {
throw new SshException("Bad removeIdentity response (" + SshAgentConstants.getCommandMessageName(response) + ") - available=" + available);
}
}
@Override
public void removeAllIdentities() throws IOException {
Buffer buffer = createBuffer(SshAgentConstants.SSH2_AGENTC_REMOVE_ALL_IDENTITIES, 1);
if (log.isDebugEnabled()) {
log.debug("removeAllIdentities");
}
buffer = request(prepare(buffer));
int available = buffer.available();
int response = (available >= 1) ? buffer.getUByte() : -1;
if ((available != 1) || (response != SshAgentConstants.SSH_AGENT_SUCCESS)) {
throw new SshException("Bad removeAllIdentities response (" + SshAgentConstants.getCommandMessageName(response) + ") - available=" + available);
}
}
@Override
public void close() throws IOException {
ExecutorService service = getExecutorService();
if ((service != null) && isShutdownOnExit() && (!service.isShutdown())) {
Collection<?> runners = service.shutdownNow();
if (log.isDebugEnabled()) {
log.debug("close() - shutdown runners count=" + GenericUtils.size(runners));
}
}
}
protected Buffer createBuffer(byte cmd) {
return createBuffer(cmd, 0);
}
protected Buffer createBuffer(byte cmd, int extraLen) {
Buffer buffer = new ByteArrayBuffer((extraLen <= 0) ? ByteArrayBuffer.DEFAULT_SIZE : extraLen + Byte.SIZE, false);
buffer.putInt(0);
buffer.putByte(cmd);
return buffer;
}
protected Buffer prepare(Buffer buffer) {
int wpos = buffer.wpos();
buffer.wpos(0);
buffer.putInt(wpos - 4);
buffer.wpos(wpos);
return buffer;
}
protected abstract Buffer request(Buffer buffer) throws IOException;
}