aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md19
-rw-r--r--afssh.1.md2
-rw-r--r--changelog101
-rw-r--r--rfc4251.h1
-rw-r--r--ssh-agent-filter.C149
-rw-r--r--ssh-agent-filter.bash-completion30
-rwxr-xr-xssh-askpass-noinput3
-rw-r--r--ssh-askpass-noinput.1.md48
-rw-r--r--version.h2
10 files changed, 330 insertions, 27 deletions
diff --git a/Makefile b/Makefile
index 644b055..f07d0d1 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,7 @@ CXXFLAGS ?= -g -O2 -Wall -Wold-style-cast
CXXFLAGS += -std=c++11
LDFLAGS += -lstdc++ -lboost_program_options -lboost_filesystem -lboost_system -lnettle
-all: ssh-agent-filter.1 afssh.1
+all: ssh-agent-filter.1 afssh.1 ssh-askpass-noinput.1
%.1: %.1.md
pandoc -s -w man $< -o $@
diff --git a/README.md b/README.md
index 71b834f..05ca21d 100644
--- a/README.md
+++ b/README.md
@@ -13,20 +13,27 @@ our solution
------------
1. create one key(pair) for each realm you connect to
-2. load keys into your ssh-agent as usual
-3. use ssh-agent-filter to allow only the key(s) you need
+2. load keys into your `ssh-agent` as usual
+3. use `ssh-agent-filter` to allow only the key(s) you need
-afssh (agent filtered ssh) can wrap ssh-agent-filter and ssh for you:
+`afssh` (agent filtered ssh) can wrap `ssh-agent-filter` and `ssh` for you, forwarding only the key with the comment `id_example`:
- $ afssh -c id_example -- example.com
+ $ afssh --comment id_example -- example.com
-starts an `ssh-agent-filter -c id_example`, runs `ssh -A example.com` and kills the ssh-agent-filter afterwards.
+starts an `ssh-agent-filter --comment id_example`, runs `ssh -A example.com` and kills the `ssh-agent-filter` afterwards.
If you leave out the options before the `--`:
$ afssh -- example.com
-it will ask you via whiptail or dialog which keys you want to have forwarded.
+it will ask you via `whiptail` or `dialog` which keys you want to have forwarded.
+
+
+confirmation
+------------
+
+You can use the `--*-confirmed` options (e.g.`--comment-confirmed`) to add keys for which you want to be asked on each use through the filter.
+The confirmation is done in the same way as when you `ssh-add -c` a key to your `ssh-agent`, but the question will contain some additional information extracted from the sign request.
how it works
diff --git a/afssh.1.md b/afssh.1.md
index 5d17d2e..7c4ab92 100644
--- a/afssh.1.md
+++ b/afssh.1.md
@@ -19,7 +19,7 @@ If there are no arguments before `--`, whiptail(1) or dialog(1) is used to ask t
After setting up the ssh-agent-filter(1) ssh(1) is started with `-A` and the rest of the arguments (after `--`).
-When ssh(1) exits, the ssh-agent-filter(1) is killed to not leave tons of them
+When ssh(1) exits, the ssh-agent-filter(1) is killed to not leave tons of them idling.
# OPTIONS
diff --git a/changelog b/changelog
index a260eeb..87a658b 100644
--- a/changelog
+++ b/changelog
@@ -1,3 +1,104 @@
+commit 5d55704009d1052db6f4039544aedb17ca8f541b
+Author: chrysn <chrysn@fsfe.org>
+Date: 2013-10-26 00:50:36 +0200
+
+ ssh-askpass-noinput: a simpler confirmation dialog
+
+ this adds an ssh-askpass imlementation that will only show allow/deny
+ buttons and is based on zenity.
+
+commit d8b50eb96e4e4f971803fcf2ba30312b2dac9d08
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-25 21:28:32 +0200
+
+ update documentation to mention confirmation stuff
+
+ Thanks: Christian Amsüss <chrysn@fsfe.org> for requesting that feature
+
+commit fad26b644eef6883810203a1bd143180484ff8fb
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-23 21:24:15 +0200
+
+ add quotes around ssh-agent-filter's name
+
+commit 8401112f358cd35ea72ddf2d67effbf3ac782a5e
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-23 21:20:29 +0200
+
+ rfc4251.h: add #include <stdexcept>
+
+commit 4bea277b6a2efd93321e5d6a0d96c87eb4d4e7c5
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-11 11:51:42 +0200
+
+ add confirmed key operations
+
+ normal ssh authentications get dissected to allow the user a more
+ informed decision.
+
+commit 82f6ec6201c4fb64d0fc9ec43e9c003849ddc6ba
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-11 11:45:06 +0200
+
+ update bash-completion for confirmation options
+
+commit be10d6a2c1b646ce3cc67bab2896dd3720a80e64
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-09 20:18:07 +0200
+
+ improve inserting into (allow|confirm)ed_pubkeys
+
+commit cf11590789a66485623ed11508ae137e2b78a96d
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-10-09 18:15:57 +0200
+
+ add cmdline options for confirmation
+
+commit 48ae39cf004d22c0cf96703e382dec41304c3280
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-22 23:26:20 +0200
+
+ add confirmation via ssh-askpass
+
+commit 4b2644c5cf45bb0775e777667aa5a54b9cd6bef8
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-22 23:23:11 +0200
+
+ add CLOEXEC flag to sockets
+
+ SOCK_CLOEXEC is currently only available on linux >= 2.6.27 so fcntl is used
+ as a fallback.
+
+commit 06df197ec118243a073923bb9f6803ffa426ea89
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-19 13:15:24 +0200
+
+ ssh-agent-filter.bash-completion: add missing license
+
+commit 59f0ba5eb0a1a4c9bcada66bfe3cf1780e268aff
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-18 16:43:22 +0200
+
+ add one missing exception activation
+
+commit 3707680021de040472cd792131a8df42902d08ba
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-18 16:40:21 +0200
+
+ also print out SSH_AUTH_SOCK in debug mode
+
+commit f6ae7a38cfec1d4f8abc6d5c13999682efcfca03
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-18 15:24:32 +0200
+
+ afssh.1.md: complete the sentence.
+
+commit ad2ee59254259fb117a16f3cc60c9a19b7c78ab9 (tag: 0.2)
+Author: Timo Weingärtner <timo@tiwe.de>
+Date: 2013-09-08 17:20:11 +0200
+
+ release 0.2
+
commit bd8e837efbc00eb49607ca493287d307ea47697b
Author: Timo Weingärtner <timo@tiwe.de>
Date: 2013-09-01 12:32:55 +0200
diff --git a/rfc4251.h b/rfc4251.h
index 80723a9..b2f9658 100644
--- a/rfc4251.h
+++ b/rfc4251.h
@@ -32,6 +32,7 @@
#include <string>
#include <iostream>
#include <limits>
+#include <stdexcept>
#include <arpa/inet.h> // ntohl() / htonl()
#include <gmpxx.h>
diff --git a/ssh-agent-filter.C b/ssh-agent-filter.C
index a5ff38e..dd1b508 100644
--- a/ssh-agent-filter.C
+++ b/ssh-agent-filter.C
@@ -37,9 +37,11 @@ namespace fs = boost::filesystem;
#include <csignal>
#include <cstdlib>
#include <fcntl.h>
+#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <sys/wait.h>
#include <sysexits.h>
#include <nettle/md5.h>
#include <nettle/base64.h>
@@ -49,12 +51,21 @@ namespace fs = boost::filesystem;
#include "ssh-agent.h"
#include "version.h"
+#ifndef SOCK_CLOEXEC
+#define SOCK_CLOEXEC 0
+#endif
std::vector<std::string> allowed_b64;
std::vector<std::string> allowed_md5;
std::vector<std::string> allowed_comment;
+std::vector<std::string> confirmed_b64;
+std::vector<std::string> confirmed_md5;
+std::vector<std::string> confirmed_comment;
std::set<rfc4251string> allowed_pubkeys;
+std::map<rfc4251string, std::string> confirmed_pubkeys;
bool debug{false};
+bool all_confirmed{false};
+std::string saf_name;
fs::path path;
@@ -88,10 +99,14 @@ int make_upstream_agent_conn () {
exit(EX_UNAVAILABLE);
}
- if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) {
perror("socket");
exit(EX_UNAVAILABLE);
}
+ if (fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC)) {
+ perror("fcntl");
+ exit(EX_UNAVAILABLE);
+ }
addr.sun_family = AF_UNIX;
@@ -114,10 +129,14 @@ int make_listen_sock () {
int sock;
struct sockaddr_un addr;
- if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) {
perror("socket");
exit(EX_UNAVAILABLE);
}
+ if (fcntl(sock, F_SETFD, fcntl(sock, F_GETFD) | FD_CLOEXEC)) {
+ perror("fcntl");
+ exit(EX_UNAVAILABLE);
+ }
addr.sun_family = AF_UNIX;
@@ -144,12 +163,17 @@ int make_listen_sock () {
void parse_cmdline (int const argc, char const * const * const argv) {
po::options_description opts{"OPTIONS"};
opts.add_options()
- ("comment,c", po::value(&allowed_comment), "key specified by comment")
- ("debug,d", po::bool_switch(&debug), "show some debug info, don't fork")
- ("fingerprint,fp,f", po::value(&allowed_md5), "key specified by pubkey's hex-encoded md5 fingerprint")
- ("help,h", "print this help message")
- ("key,k", po::value(&allowed_b64), "key specified by base64-encoded pubkey")
- ("version,V", "print version information")
+ ("all-confirmed,A", po::bool_switch(&all_confirmed),"allow all other keys with confirmation")
+ ("comment,c", po::value(&allowed_comment), "key specified by comment")
+ ("comment-confirmed,C", po::value(&confirmed_comment), "key specified by comment, with confirmation")
+ ("debug,d", po::bool_switch(&debug), "show some debug info, don't fork")
+ ("fingerprint,fp,f", po::value(&allowed_md5), "key specified by pubkey's hex-encoded md5 fingerprint")
+ ("fingerprint-confirmed,F", po::value(&confirmed_md5), "key specified by pubkey's hex-encoded md5 fingerprint, with confirmation")
+ ("help,h", "print this help message")
+ ("key,k", po::value(&allowed_b64), "key specified by base64-encoded pubkey")
+ ("key-confirmed,K", po::value(&confirmed_b64), "key specified by base64-encoded pubkey, with confirmation")
+ ("name,n", po::value(&saf_name), "name for this instance of ssh-agent-filter, for confirmation puposes")
+ ("version,V", "print version information")
;
po::variables_map config;
store(parse_command_line(argc, argv, opts), config);
@@ -206,23 +230,91 @@ void setup_filters () {
std::string comm(comment);
if (debug) std::clog << comm << std::endl;
+ bool allow{false};
+
if (std::count(allowed_b64.begin(), allowed_b64.end(), b64)) {
- allowed_pubkeys.insert(key);
+ allow = true;
if (debug) std::clog << "key allowed by equal base64 representation" << std::endl;
}
if (std::count(allowed_md5.begin(), allowed_md5.end(), md5)) {
- allowed_pubkeys.insert(key);
+ allow = true;
if (debug) std::clog << "key allowed by matching md5 fingerprint" << std::endl;
}
if (std::count(allowed_comment.begin(), allowed_comment.end(), comm)) {
- allowed_pubkeys.insert(key);
+ allow = true;
if (debug) std::clog << "key allowed by matching comment" << std::endl;
}
+ if (allow) allowed_pubkeys.emplace(std::move(key));
+ else {
+ bool confirm{false};
+
+ if (std::count(confirmed_b64.begin(), confirmed_b64.end(), b64)) {
+ confirm = true;
+ if (debug) std::clog << "key allowed with confirmation by equal base64 representation" << std::endl;
+ }
+ if (std::count(confirmed_md5.begin(), confirmed_md5.end(), md5)) {
+ confirm = true;
+ if (debug) std::clog << "key allowed with confirmation by matching md5 fingerprint" << std::endl;
+ }
+ if (std::count(confirmed_comment.begin(), confirmed_comment.end(), comm)) {
+ confirm = true;
+ if (debug) std::clog << "key allowed with confirmation by matching comment" << std::endl;
+ }
+ if (all_confirmed) {
+ confirm = true;
+ if (debug) std::clog << "key allowed with confirmation by catch-all (-A)" << std::endl;
+ }
+
+ if (confirm) confirmed_pubkeys.emplace(std::move(key), std::move(comm));
+ }
+
if (debug) std::clog << std::endl;
}
}
+bool confirm (std::string const & question) {
+ char const * sap;
+ if (!(sap = getenv("SSH_ASKPASS")))
+ sap = "ssh-askpass";
+ pid_t pid = fork();
+ if (pid < 0)
+ throw std::runtime_error("fork()");
+ if (pid == 0) {
+ // child
+ char const * args[3] = { sap, question.c_str(), nullptr };
+ // see execvp(3p) for cast rationale
+ execvp(sap, const_cast<char * const *>(args));
+ perror("exec");
+ exit(EX_UNAVAILABLE);
+ } else {
+ // parent
+ int status;
+ return waitpid(pid, &status, 0) > 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
+ }
+}
+
+bool dissect_auth_data_ssh (rfc4251string const & data, std::string & request_description) try {
+ std::istringstream datastream{data};
+ datastream.exceptions(std::ios::badbit | std::ios::failbit);
+
+ // Format specified in RFC 4252 Section 7
+ rfc4251string session_identifier; datastream >> session_identifier;
+ rfc4251byte requesttype; datastream >> requesttype;
+ rfc4251string username; datastream >> username;
+ rfc4251string servicename; datastream >> servicename;
+ rfc4251string publickeystring; datastream >> publickeystring;
+ rfc4251bool shouldbetrue; datastream >> shouldbetrue;
+ rfc4251string publickeyalgorithm; datastream >> publickeyalgorithm;
+ rfc4251string publickey; datastream >> publickey;
+
+ request_description = "The request is for an ssh connection as user '" + std::string{username} + "' with service name '" + std::string{servicename} + "'.";
+
+ return true;
+} catch (...) {
+ return false;
+}
+
rfc4251string handle_request (rfc4251string const & r) {
std::istringstream request{r};
std::ostringstream answer;
@@ -242,6 +334,7 @@ rfc4251string handle_request (rfc4251string const & r) {
// temp to test key filtering when signing
//return agent_answer;
std::istringstream agent_answer_iss{agent_answer};
+ agent_answer_iss.exceptions(std::ios::badbit | std::ios::failbit);
rfc4251byte answer_code;
rfc4251uint32 keycount;
agent_answer_iss >> answer_code >> keycount;
@@ -252,7 +345,7 @@ rfc4251string handle_request (rfc4251string const & r) {
rfc4251string key;
rfc4251string comment;
agent_answer_iss >> key >> comment;
- if (allowed_pubkeys.count(key))
+ if (allowed_pubkeys.count(key) or confirmed_pubkeys.count(key))
keys.emplace_back(std::move(key), std::move(comment));
}
answer << answer_code << rfc4251uint32{static_cast<uint32_t>(keys.size())};
@@ -263,8 +356,33 @@ rfc4251string handle_request (rfc4251string const & r) {
case SSH2_AGENTC_SIGN_REQUEST:
{
rfc4251string key;
- request >> key;
- if (allowed_pubkeys.count(key)) {
+ rfc4251string data;
+ rfc4251uint32 flags;
+ request >> key >> data >> flags;
+ bool allow{false};
+
+ if (allowed_pubkeys.count(key))
+ allow = true;
+ else {
+ auto it = confirmed_pubkeys.find(key);
+ if (it != confirmed_pubkeys.end()) {
+ std::string request_description;
+ bool dissect_ok{false};
+ if (!dissect_ok)
+ dissect_ok = dissect_auth_data_ssh(data, request_description);
+ if (!dissect_ok)
+ request_description = "The request format is unknown.";
+
+ std::string question = "Something behind the ssh-agent-filter";
+ if (saf_name.length())
+ question += " named '" + saf_name + "'";
+ question += " requested use of the key named '" + it->second + "'.\n";
+ question += request_description;
+ allow = confirm(question);
+ }
+ }
+
+ if (allow) {
__gnu_cxx::stdio_filebuf<char> agent_filebuf{make_upstream_agent_conn(), std::ios::in | std::ios::out};
std::iostream agent{&agent_filebuf};
agent.exceptions(std::ios::badbit | std::ios::failbit);
@@ -362,6 +480,9 @@ int main (int const argc, char const * const * const argv) {
dup2(devnull, 1);
dup2(devnull, 2);
close(devnull);
+ } else {
+ std::cout << "copy this to another terminal:" << std::endl;
+ std::cout << "SSH_AUTH_SOCK='" << path.native() << "'; export SSH_AUTH_SOCK;" << std::endl;
}
signal(SIGINT, sighandler);
diff --git a/ssh-agent-filter.bash-completion b/ssh-agent-filter.bash-completion
index c1080f6..61debb9 100644
--- a/ssh-agent-filter.bash-completion
+++ b/ssh-agent-filter.bash-completion
@@ -1,31 +1,53 @@
+# bash completion for ssh-agent-filter and afssh
+#
+# Copyright (C) 2013 Timo Weingärtner <timo@tiwe.de>
+#
+# This file is part of ssh-agent-filter.
+#
+# ssh-agent-filter is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ssh-agent-filter is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ssh-agent-filter. If not, see <http://www.gnu.org/licenses/>.
+
_ssh-agent-filter () {
local cur prev words cword opts
_init_completion -n : || return
_quote_readline_by_ref "$cur" cur
- opts="--comment --debug --fingerprint --help --key --version"
+ opts="--all-confirmed --comment --comment-confirmed --debug --fingerprint --fingerprint-confirmed --help --key --key-confirmed --name --version"
case "$prev" in
- -c|--comment)
+ -c|--comment|-C|--comment-confirmed)
# hm, key comments might contain anything, how can I quote them ?
local comments="$(ssh-add -L | cut -d\ -f3- )"
COMPREPLY=( $(compgen -W "$comments" -- "$cur") )
return 0
;;
- -f|--fp|--fingerprint)
+ -f|--fp|--fingerprint|-F|--fingerprint-confirmed)
# fingerprints contain many colons
local fingerprints="$(ssh-add -l | cut -d\ -f2 )"
COMPREPLY=( $(compgen -W "$fingerprints" -- "$cur") )
__ltrim_colon_completions "$cur"
return 0
;;
- -k|--key)
+ -k|--key|-K|--key-confirmed)
# this is base64, no quoting needed
local keys="$(ssh-add -L | cut -d\ -f2 )"
COMPREPLY=( $(compgen -W "$keys" -- "$cur") )
return 0
;;
+ -n|--name)
+ COMPREPLY=( $(compgen -W "" -- "$cur") )
+ return 0
esac
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
diff --git a/ssh-askpass-noinput b/ssh-askpass-noinput
new file mode 100755
index 0000000..2954b88
--- /dev/null
+++ b/ssh-askpass-noinput
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec zenity --question --title "SSH Request" --no-markup --text "$1" --ok-label Allow --cancel-label Deny
diff --git a/ssh-askpass-noinput.1.md b/ssh-askpass-noinput.1.md
new file mode 100644
index 0000000..2c9ce73
--- /dev/null
+++ b/ssh-askpass-noinput.1.md
@@ -0,0 +1,48 @@
+% SSH-ASKPASS-NOINPUT
+% chrysn <chrysn@fsfe.org>
+% 2013-10-26
+
+# NAME
+
+ssh-askpass-noinput - an `ssh-askpass` implementation for asking allow/deny questions
+
+# SYNOPSIS
+
+*ssh-askpass-noinput* text
+
+# DESCRIPTION
+
+*ssh-askpass-noinput* is an implementation of *ssh-askpass*, which does not
+acutally ask for a password; instead, it only asks a binary (allow/deny)
+question and exits with 0 for allow and 1 for deny.
+
+It is not intended as a general replacement for *ssh-askpass*, but for special
+applications that don't care about a passphrase.
+
+# OPTIONS
+
+As usual with *ssh-askpass* implementations, *ssh-askpass-noinput* only takes a
+single argument, which will be presented as the question.
+
+# BACKGROUND AND APPLICATIONS
+
+Some programs (*ssh-agent* and *ssh-agent-filter*) use *ssh-askpass* to have
+users confirm actions without entering a passphrase; *ssh-agent* does this when
+used via *ssh-add*'s `-c` option. They do not indicate that it is a binary
+question (because in the classical *ssh-agent* invocation, there is no option to
+do this), and expect the user to ignore the text input and click "OK" or
+"Cancel", whereupon they read the askpass's exit status.
+
+With programs that are known to only ask those questions, setting
+`SSH_ASKPASS=ssh-askpass-noinput` in their environment will make them use this
+particular implementation for their questions. It should never be installed as
+`/usr/bin/ssh-askpass`.
+
+# FUTURE
+
+This solution is obviously a hack, which is needed until a way is established
+and implemented for *ssh-askpass* to be used more flexibly.
+
+# SEE ALSO
+
+ssh-agent-filter(1), ssh-agent(1), ssh-askpass(1)
diff --git a/version.h b/version.h
index 5acbd1c..964fa23 100644
--- a/version.h
+++ b/version.h
@@ -1 +1 @@
-#define SSH_AGENT_FILTER_VERSION "ssh-agent-filter 0.2"
+#define SSH_AGENT_FILTER_VERSION "ssh-agent-filter 0.3"