Skip to content
Snippets Groups Projects
Commit 73e8186f authored by Tianqi Ren's avatar Tianqi Ren
Browse files

[Feature] support file pubkey authentication

parent 710ce41a
Branches main
No related tags found
No related merge requests found
Pipeline #94179 failed
package org.apache.sshd.server.auth.pubkey;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.server.auth.AsyncAuthException;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.ServerSessionImpl;
import java.io.IOException;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FilePublickeyAuthenticator extends AbstractLoggingBean implements PublickeyAuthenticator {
Map<Long, PublicKey> storedKeys;
PublickeyAuthenticator delegate;
Map<String, PublickeyAuthenticator> fileAuths;
public FilePublickeyAuthenticator(PublickeyAuthenticator auth, Map<String, PublickeyAuthenticator> fileAuths) {
delegate = auth;
storedKeys = new HashMap<>();
this.fileAuths = fileAuths;
}
public FilePublickeyAuthenticator(PublickeyAuthenticator auth) {
this(auth, new HashMap<>());
}
public boolean authenticateFile(String remotePath, ServerSession session) {
if (!(session instanceof ServerSessionImpl)) {
return false;
}
ServerSessionImpl sessionImpl = (ServerSessionImpl) session;
if (!storedKeys.containsKey(sessionImpl.getId())) {
return false;
}
PublicKey key = storedKeys.get(sessionImpl.getId());
log.debug("Authenticating {} with {}", remotePath, key);
PublickeyAuthenticator fileAuth = fileAuths.get(remotePath);
if (fileAuth == null) {
return false;
}
return fileAuth.authenticate(session.getUsername(), key, session);
}
public boolean authenticateFile(String remotePath, Path localAuth, ServerSession session) throws IOException {
if (!(session instanceof ServerSessionImpl)) {
return false;
}
ServerSessionImpl sessionImpl = (ServerSessionImpl) session;
if (!storedKeys.containsKey(sessionImpl.getId())) {
return false;
}
PublicKey key = storedKeys.get(sessionImpl.getId());
log.debug("Authenticating {} with {}", remotePath, key);
List<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(localAuth);
PublickeyAuthenticator fileAuth;
try {
fileAuth = PublickeyAuthenticator.fromAuthorizedEntries(localAuth, null, entries, null);
} catch (GeneralSecurityException e) {
throw new IOException("failed resolving public key authenticator: " + e.getMessage(), e);
}
return fileAuth.authenticate(session.getUsername(), key, session);
}
@Override
public boolean authenticate(String username, PublicKey key, ServerSession session) throws AsyncAuthException {
if (session instanceof ServerSessionImpl) {
ServerSessionImpl sessionImpl = (ServerSessionImpl) session;
if (!storedKeys.containsKey(sessionImpl.getId())) {
storedKeys.put(sessionImpl.getId(), key);
}
}
log.debug("Authenticated {} with {}", username, key);
return delegate.authenticate(username, key, session);
}
}
......@@ -18,19 +18,9 @@
*/
package org.apache.sshd.scp.common;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
......@@ -56,6 +46,9 @@ import org.apache.sshd.scp.common.helpers.ScpPathCommandDetailsSupport;
import org.apache.sshd.scp.common.helpers.ScpReceiveDirCommandDetails;
import org.apache.sshd.scp.common.helpers.ScpReceiveFileCommandDetails;
import org.apache.sshd.scp.common.helpers.ScpTimestampCommandDetails;
import org.apache.sshd.server.auth.pubkey.FilePublickeyAuthenticator;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
......@@ -93,8 +86,8 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
public ScpHelper(Session session, InputStream in, OutputStream out,
FileSystem fileSystem, ScpFileOpener opener, ScpTransferEventListener eventListener) {
this(session, in, ScpModuleProperties.SCP_INCOMING_ENCODING.getRequired(session),
out, ScpModuleProperties.SCP_OUTGOING_ENCODING.getRequired(session),
fileSystem, opener, eventListener);
out, ScpModuleProperties.SCP_OUTGOING_ENCODING.getRequired(session),
fileSystem, opener, eventListener);
}
public ScpHelper(Session session,
......@@ -125,7 +118,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
receiveStream(line, new ScpTargetStreamResolver() {
@Override
@SuppressWarnings("synthetic-access") // see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=537593
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=537593
public OutputStream resolveTargetStream(
Session session, String name, long length,
Set<PosixFilePermission> perms, OpenOption... options)
......@@ -144,7 +137,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
@Override
@SuppressWarnings("synthetic-access") // see
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=537593
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=537593
public void postProcessReceivedData(
String name, boolean preserve, Set<PosixFilePermission> perms,
ScpTimestampCommandDetails time)
......@@ -179,8 +172,8 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
/**
* Reads command line(s) and invokes the handler until EOF or and &quot;E&quot; command is received
*
* @param cmd The receive command being attempted
* @param handler The {@link ScpReceiveLineHandler} to invoke when a command has been read
* @param cmd The receive command being attempted
* @param handler The {@link ScpReceiveLineHandler} to invoke when a command has been read
* @throws IOException If failed to read/write
*/
protected void receive(String cmd, ScpReceiveLineHandler handler) throws IOException {
......@@ -188,7 +181,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
boolean debugEnabled = log.isDebugEnabled();
Session session = getSession();
for (ScpTimestampCommandDetails time = null;; debugEnabled = log.isDebugEnabled()) {
for (ScpTimestampCommandDetails time = null; ; debugEnabled = log.isDebugEnabled()) {
ScpAckInfo ackInfo = receiveNextCmd();
if (ackInfo == null) {
return;
......@@ -293,7 +286,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
listener.startFolderEvent(session, FileOperation.RECEIVE, path, perms);
try {
for (;;) {
for (; ; ) {
header = readLine();
if (debugEnabled) {
log.debug("receiveDir({})[{}] Received header: {}", this, file, header);
......@@ -341,7 +334,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
throws IOException {
if (bufferSize < MIN_RECEIVE_BUFFER_SIZE) {
throw new IOException("receiveStream(" + resolver + ") buffer size (" + bufferSize + ") below minimum ("
+ MIN_RECEIVE_BUFFER_SIZE + ")");
+ MIN_RECEIVE_BUFFER_SIZE + ")");
}
ScpReceiveFileCommandDetails details = new ScpReceiveFileCommandDetails(header);
......@@ -503,8 +496,8 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
}
/**
* @param commandPath The command path using the <U>local</U> file separator
* @return The resolved absolute and normalized local {@link Path}
* @param commandPath The command path using the <U>local</U> file separator
* @return The resolved absolute and normalized local {@link Path}
* @throws IOException If failed to resolve the path
* @throws InvalidPathException If invalid local path value
*/
......@@ -517,20 +510,43 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
return p;
}
private boolean authenticateFile(ServerSession ss, Path path) throws IOException {
PublickeyAuthenticator pa = ss.getPublickeyAuthenticator();
if (pa instanceof FilePublickeyAuthenticator) {
FilePublickeyAuthenticator fpa = (FilePublickeyAuthenticator) pa;
Path localKeys = path.getParent().resolve(path.getFileName() + ".ka");
System.out.println("authenticating file " + localKeys);
if (localKeys.toFile().exists()) {
return fpa.authenticateFile(localKeys.toString(), localKeys, ss);
}
return fpa.authenticateFile(path.toString(), ss);
}
return true;
}
public void sendFile(Path local, boolean preserve, int bufferSize) throws IOException {
Path path = Objects.requireNonNull(local, "No local path").normalize().toAbsolutePath();
if (log.isDebugEnabled()) {
log.debug("sendFile({})[preserve={},buffer-size={}] Sending file {}", this, preserve, bufferSize, path);
}
ScpSourceStreamResolver sourceStreamResolver = opener.createScpSourceStreamResolver(getSession(), path);
Session s = getSession();
if (s.isServerSession()) {
ServerSession ss = (ServerSession) s;
if (!authenticateFile(ss, path)) {
throw new IOException("Authentication failed for " + path);
}
}
ScpSourceStreamResolver sourceStreamResolver = opener.createScpSourceStreamResolver(s, path);
sendStream(sourceStreamResolver, preserve, bufferSize);
}
public void sendStream(ScpSourceStreamResolver resolver, boolean preserve, int bufferSize) throws IOException {
if (bufferSize < MIN_SEND_BUFFER_SIZE) {
throw new IOException("sendStream(" + resolver + ") buffer size (" + bufferSize + ") below minimum ("
+ MIN_SEND_BUFFER_SIZE + ")");
+ MIN_SEND_BUFFER_SIZE + ")");
}
long fileSize = resolver.getSize();
......@@ -684,7 +700,7 @@ public class ScpHelper extends AbstractLoggingBean implements SessionHolder<Sess
? ScpReceiveDirCommandDetails.DEFAULT_DIR_OCTAL_PERMISSIONS
: ScpPathCommandDetailsSupport.getOctalPermissions(perms);
String cmd = ScpReceiveDirCommandDetails.COMMAND_NAME + octalPerms + " " + "0" + " "
+ Objects.toString(path.getFileName(), null);
+ Objects.toString(path.getFileName(), null);
if (debugEnabled) {
log.debug("sendDir({})[{}] send 'D' command: {}", this, path, cmd);
}
......
package org.apache.sshd.scp.server;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
import org.apache.sshd.common.file.FileSystemFactory;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
import org.apache.sshd.server.auth.pubkey.FilePublickeyAuthenticator;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.util.test.client.simple.BaseSimpleClientTestSupport;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.util.*;
public class ScpFileAuthTest extends BaseSimpleClientTestSupport {
private final FileSystemFactory fileSystemFactory;
PublickeyAuthenticator delegateAuth;
private final Path parentPath;
public ScpFileAuthTest() throws IOException, GeneralSecurityException {
Path targetPath = detectTargetFolder();
parentPath = targetPath.getParent();
fileSystemFactory = new VirtualFileSystemFactory(parentPath);
delegateAuth = createFileAuth(getCurrentTestName(), "authorized_keys");
}
private PublickeyAuthenticator createFileAuth(Object id, String path) throws IOException, GeneralSecurityException {
List<AuthorizedKeyEntry> entries = AuthorizedKeyEntry.readAuthorizedKeys(parentPath.resolve(path));
return PublickeyAuthenticator.fromAuthorizedEntries(id, null, entries, PublicKeyEntryResolver.FAILING);
}
private Map<String, PublickeyAuthenticator> getFileAuths() throws GeneralSecurityException, IOException {
Map<String, PublickeyAuthenticator> fileAuths = new HashMap<>();
PublickeyAuthenticator auth1 = createFileAuth("file1", "file1_auth");
PublickeyAuthenticator auth2 = createFileAuth("file2", "file2_auth");
fileAuths.put("/target/scp/file1.txt", auth1);
fileAuths.put("/target/scp/file2.txt", auth2);
return fileAuths;
}
@Before
public void setUp() throws Exception {
super.setUp();
sshd.setCommandFactory(new ScpCommandFactory());
sshd.setFileSystemFactory(fileSystemFactory);
sshd.setPublickeyAuthenticator(new FilePublickeyAuthenticator(delegateAuth));
client.start();
}
@Test
public void testFileAuth() throws Exception {
System.out.println("User: " + getCurrentTestName());
System.out.println("Port: " + port);
while (true) ; // keep the server running
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment