Repository navigation
Expand file tree
/
Copy pathssh-new
More file actions
executable file
·290 lines (254 loc) · 10.2 KB
/
Copy pathssh-new
File metadata and controls
executable file
·290 lines (254 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#!/bin/bash
set -e
SCRIPT_DIR=$(dirname "$0")
# shellcheck source=./_ssh-unique-key.inc.sh
source "${SCRIPT_DIR}/_ssh-unique-key.inc.sh"
ensure_base_dirs
usage() {
echo "Usage: ssh-new [options] <user@host>"
echo "Options:"
echo " --template <name> Use a key template"
echo " --legacy Enable outdated algorithms (rsa/dss/cbc)"
echo " --comment \"text\" Display this message BEFORE connecting"
echo " --key-comment \"text\" Set the public key comment (description)"
echo " -V, --verbose Enable verbose output"
echo " -h, --help Show this help message"
echo " -v, --version Show version information"
exit 0
}
TEMPLATE_NAME=""
LEGACY_MODE=0
NO_CONNECT=0
COMMENT_TEXT=""
KEY_COMMENT=""
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage ;;
-v|--version) show_version ;;
-V|--verbose) VERBOSE=1; shift ;;
--template) TEMPLATE_NAME="$2"; shift 2 ;;
--legacy) LEGACY_MODE=1; shift ;;
--no-connect) NO_CONNECT=1; shift ;;
--comment) COMMENT_TEXT="$2"; shift 2 ;;
--key-comment) KEY_COMMENT="$2"; shift 2 ;;
-*|--*) err "Unknown option $1"; usage ;;
*) POSITIONAL_ARGS+=("$1"); shift ;;
esac
done
if [ ${#POSITIONAL_ARGS[@]} -ne 1 ]; then
usage
fi
USER_HOST_ARG="${POSITIONAL_ARGS[0]}"
HOST_NAME=$(get_host_from_arg "$USER_HOST_ARG")
USER_NAME=$(get_user_from_arg "$USER_HOST_ARG")
# Set default key comment if not provided
if [ -z "$KEY_COMMENT" ]; then
KEY_COMMENT="$USER_NAME@$HOST_NAME"
fi
info "Checking host $HOST_NAME..."
# --- Auth Check ---
if ! check_key_auth_support "$USER_HOST_ARG"; then
warn "Host does NOT appear to support publickey authentication."
read -p "Continue anyway? (y/N) " CONT
if [[ "$CONT" != "y" && "$CONT" != "Y" ]]; then
exit 1
fi
fi
# --- Scan & Verify ---
RAW_SCAN=$(get_full_host_scan "$HOST_NAME")
if [ -z "$RAW_SCAN" ]; then
err "Scan failed. Host unreachable or blocked."
fi
SCAN_SORTED=$(echo "$RAW_SCAN" | sort)
UUID=$(get_host_uuid_from_scan_data "$SCAN_SORTED")
CANONICAL="$UUID_DIR/$UUID"
if [ -d "$CANONICAL" ]; then
info "Known host identity. Verifying..."
# Compare keys only (ignore hostname/IP in first column)
SCAN_KEYS=$(echo "$SCAN_SORTED" | awk '{$1=""; print $0}' | sort)
STORED_KEYS=$(cat "$CANONICAL/known_host_keys" | awk '{$1=""; print $0}' | sort)
if [ "$SCAN_KEYS" != "$STORED_KEYS" ]; then
warn "Diff between new scan (left) and stored keys (right):"
diff <(echo "$SCAN_SORTED") "$CANONICAL/known_host_keys" || true
err "SECURITY WARNING: Host keys have changed! Possible MITM."
else
info "Host verification passed."
# Keys verified identical; refresh the (informational) hostname column
# so known_host_keys can pin connections to the current target name.
echo "$SCAN_SORTED" > "$CANONICAL/known_host_keys"
fi
else
info "Creating new identity..."
mkdir -p -m 700 "$CANONICAL"
echo "$SCAN_SORTED" > "$CANONICAL/known_host_keys" && chmod 644 "$CANONICAL/known_host_keys"
log_event "create-identity" "$HOST_NAME" "UUID: $UUID"
fi
# --- User Keys ---
USER_DIR="$CANONICAL/$USER_NAME"
mkdir -p -m 700 "$USER_DIR"
KEY_FILE="$USER_DIR/identity"
if [ ! -f "$KEY_FILE" ]; then
if [ -n "$TEMPLATE_NAME" ]; then
T_PATH="$TEMPLATE_DIR/$TEMPLATE_NAME"
if [ ! -d "$T_PATH" ]; then
err "Template not found."
fi
# --- Check for OPK Type ---
if [ -f "$T_PATH/.type" ] && [ "$(cat "$T_PATH/.type")" == "opk" ]; then
info "Using OpenPubKey template..."
# Pre-flight: Check if opkssh is installed
if ! command -v opkssh &> /dev/null; then
err "Template requires 'opkssh', but it is not installed on this system."
fi
CERT_PATH="$T_PATH/identity-cert.pub"
NEED_LOGIN=0
if [ ! -f "$CERT_PATH" ]; then
warn "OPK Certificate missing in template."
NEED_LOGIN=1
else
if ! check_cert_validity "$CERT_PATH"; then
warn "OPK Certificate has expired."
NEED_LOGIN=1
else
debug "OPK Certificate is valid."
fi
fi
if [ "$NEED_LOGIN" -eq 1 ]; then
LOGIN_OPTS=""
if [ -f "$T_PATH/.issuer" ]; then
ISSUER=$(cat "$T_PATH/.issuer")
info "Refreshing credentials via opkssh (Issuer: $ISSUER)..."
LOGIN_OPTS="--issuer $ISSUER"
else
info "Refreshing credentials via opkssh..."
fi
opkssh login $LOGIN_OPTS
# --- VALIDATION: Check if credentials actually updated ---
if [ ! -f "$CERT_PATH" ]; then
err "Login completed, but certificate file not found at $CERT_PATH."
fi
if ! check_cert_validity "$CERT_PATH"; then
echo "--------------------------------------------------------"
echo "ERROR: Certificate is still expired after login."
echo "The template points to: $CERT_PATH"
echo "opkssh likely updated a different file (e.g. ~/.ssh/id_ecdsa-cert.pub)."
echo "Please update the template file with the new certificate and try again."
echo "--------------------------------------------------------"
err "Credential refresh failed. Aborting."
fi
info "Credentials successfully refreshed and validated."
fi
ln -s "../../../templates/$TEMPLATE_NAME/identity" "$KEY_FILE"
if [ -f "$T_PATH/identity.pub" ]; then
ln -s "../../../templates/$TEMPLATE_NAME/identity.pub" "$KEY_FILE.pub"
fi
if [ -f "$T_PATH/identity-cert.pub" ]; then
ln -s "../../../templates/$TEMPLATE_NAME/identity-cert.pub" "$KEY_FILE-cert.pub"
fi
log_event "link-key" "$USER_HOST_ARG" "Template: $TEMPLATE_NAME (OPK)"
else
# --- Standard Template Logic ---
if [ -f "$T_PATH/id_ed25519_sk" ]; then
K_TYPE="ed25519_sk"
else
for t in ed25519 ecdsa rsa; do
if echo "$SCAN_SORTED" | grep -q "ssh-$t" && [ -f "$T_PATH/id_$t" ]; then
K_TYPE="$t"; break
fi
done
fi
if [ -z "$K_TYPE" ]; then
err "No compatible key in template."
fi
ln -s "../../../templates/$TEMPLATE_NAME/id_$K_TYPE" "$KEY_FILE"
if [ -f "$T_PATH/id_$K_TYPE.pub" ]; then
ln -s "../../../templates/$TEMPLATE_NAME/id_$K_TYPE.pub" "$KEY_FILE.pub"
fi
log_event "link-key" "$USER_HOST_ARG" "Template: $TEMPLATE_NAME ($K_TYPE)"
fi
else
K_TYPE=$(get_best_key_type_from_scan_data "$SCAN_SORTED")
if [ "$K_TYPE" == "rsa" ]; then
K_ARGS=("-b" "4096")
else
K_ARGS=()
fi
ssh-keygen -t "$K_TYPE" -f "$KEY_FILE" -N "" -C "$KEY_COMMENT" "${K_ARGS[@]}"
chmod 600 "$KEY_FILE"
log_event "gen-key" "$USER_HOST_ARG" "Type: $K_TYPE"
fi
fi
# --- Config Generation ---
CONF="$CANONICAL/config"
TRUSTED_CONF="$CANONICAL/trusted.conf"
if [ ! -f "$CONF" ]; then
touch "$CONF" && chmod 600 "$CONF"
if [ -n "$TEMPLATE_NAME" ]; then
echo "Include ../../../templates/$TEMPLATE_NAME/config" >> "$CONF"
fi
fi
if [ "$LEGACY_MODE" -eq 1 ]; then
if ! grep -q "KexAlgorithms" "$CONF"; then
echo -e "\n# Legacy Options enabled via --legacy" >> "$CONF"
get_legacy_options >> "$CONF"
log_event "config-legacy" "$HOST_NAME" "Enabled legacy options"
fi
fi
if [ -n "$COMMENT_TEXT" ]; then
# Sanitize comment text: strip single quotes and backslashes to prevent shell injection
SAFE_COMMENT=$(printf '%s' "$COMMENT_TEXT" | tr -d "'\\" )
sed -i.bak '/^Match host %h exec/d' "$CONF" && rm -f "$CONF.bak"
echo "Match host %h exec \"echo -e '\033[1;33m[NOTE] $SAFE_COMMENT\033[0m' >&2\"" >> "$CONF"
fi
cat > "$TRUSTED_CONF" << EOF
# Trusted by cryptographic path lookup (%K)
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
LogLevel ERROR
Include $CONF
EOF
chmod 600 "$TRUSTED_CONF"
# --- Finishing Up ---
if [ "$NO_CONNECT" -eq 0 ]; then
if [ -f "$KEY_FILE.pub" ]; then
info "Copying key..."
ssh-copy-id -i "$KEY_FILE.pub" -o UserKnownHostsFile="$CANONICAL/known_host_keys" -o StrictHostKeyChecking=yes "$USER_HOST_ARG"
else
if [ -f "$KEY_FILE-cert.pub" ]; then
warn "Certificate found. Assuming server trusts CA. Skipping copy."
else
warn "No public key file found. Skipping ssh-copy-id."
fi
fi
else
info "Skipping ssh-copy-id because --no-connect was specified."
fi
info "Linking..."
ln -sf "$CANONICAL" "$HOST_DIR/$HOST_NAME"
echo "$SCAN_SORTED" | while read -r l; do
B64=$(echo "$l" | awk '{print $3}')
if [ -n "$B64" ]; then
MAX_PATH=4096
MAX_NAME=255
FULL_PATH="$KEY_DIR/$B64"
# Check total path length
if [ ${#FULL_PATH} -gt $MAX_PATH ]; then
warn "Total path too long (${#FULL_PATH} > $MAX_PATH). Skipping by-key link."
continue
fi
# Check if any path component (separated by /) exceeds filesystem limit (255)
MAX_LEN=$(echo "$B64" | awk -F/ '{max=0; for(i=1;i<=NF;i++) if(length($i)>max) max=length($i); print max}')
if [ "$MAX_LEN" -gt $MAX_NAME ]; then
warn "Key path component too long ($MAX_LEN > $MAX_NAME). Skipping by-key link."
else
mkdir -p "$KEY_DIR/$(dirname "$B64")" && ln -sf "$CANONICAL" "$KEY_DIR/$B64"
fi
fi
done
if [ "$NO_CONNECT" -eq 1 ]; then
info "Setup complete. Connection skipped (--no-connect)."
exit 0
fi
info "Connecting..."
exec ssh -o UserKnownHostsFile="$CANONICAL/known_host_keys" -o StrictHostKeyChecking=yes "$USER_HOST_ARG"