This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

keys: add Solo 2 keys with encrypted SSH private key

- Add Solo 2 age public key for FIDO2 device
- Add Solo 2 SSH ed25519-sk public key
- Store SSH private key encrypted with age for secure storage
- Update keys flake-module to handle encrypted SSH private keys safely
- Add ssh-with-age tool for auto-detecting and using encrypted SSH keys
- Tool uses only repository keys, no home directory dependencies
- Auto-detects matching FIDO2 device by trying each encrypted key

+178 -8
+42 -8
keys/flake-module.nix
··· 1 1 { lib, ... }: 2 2 let 3 - # Helper to read all files from a subdirectory 4 - readKeysFromDir = 5 - baseDir: subDir: 3 + # Helper to read age keys (public keys only) 4 + readAgeKeys = 5 + baseDir: 6 6 let 7 - dir = baseDir + "/${subDir}"; 7 + dir = baseDir + "/age"; 8 8 in 9 9 if builtins.pathExists dir then 10 10 lib.mapAttrs' ( ··· 18 18 else 19 19 { }; 20 20 21 + # Helper to read SSH keys (handle key pairs and encrypted private keys) 22 + readSshKeys = 23 + baseDir: 24 + let 25 + dir = baseDir + "/ssh"; 26 + in 27 + if builtins.pathExists dir then 28 + let 29 + allFiles = builtins.readDir dir; 30 + # Group files by their base name (remove all suffixes) 31 + keyNames = lib.unique ( 32 + map (name: builtins.head (lib.splitString "." name)) (builtins.attrNames allFiles) 33 + ); 34 + in 35 + lib.listToAttrs ( 36 + map ( 37 + keyName: 38 + let 39 + publicKeyFile = dir + "/${keyName}.pub"; 40 + encryptedPrivateKeyFile = dir + "/${keyName}.age"; 41 + hasPublicKey = builtins.pathExists publicKeyFile; 42 + hasEncryptedPrivateKey = builtins.pathExists encryptedPrivateKeyFile; 43 + in 44 + lib.nameValuePair keyName { 45 + public = if hasPublicKey then builtins.readFile publicKeyFile else null; 46 + # Expose encrypted private keys (safe), but never unencrypted ones 47 + private = if hasEncryptedPrivateKey then encryptedPrivateKeyFile else null; 48 + encrypted = hasEncryptedPrivateKey; 49 + } 50 + ) keyNames 51 + ) 52 + else 53 + { }; 54 + 21 55 in 22 56 { 23 57 flake.keys = { 24 - # SSH keys (any format: rsa, ed25519, ecdsa) 25 - ssh = readKeysFromDir ./. "ssh"; 58 + # SSH keys (handle both public and private key files) 59 + ssh = readSshKeys ./.; 26 60 27 61 # PGP/GPG keys - define directly with metadata 28 62 pgp = { ··· 32 66 }; 33 67 }; 34 68 35 - # Age keys 36 - age = readKeysFromDir ./. "age"; 69 + # Age keys (public keys only) 70 + age = readAgeKeys ./.; 37 71 }; 38 72 }
+1
keys/age/solo2.age
··· 1 + age1fido2-hmac1qqpf9c7ycnn088m5t8s96e5uxlq32nj5n7k37rnqcq9m25yeuj9wv6cqvctfw08pysths7xgs4dhlzvy7ser6352j4xcx3xg5ys7kvpyp8f2xqzcedxcxtsc6nf0mw8gvqlw8vhgu4zhdmljytg473gy9z4dlftx8tumt0hlxgfekpgp5gnt2np5ar2hgahtkhalwem0sg2805hyfteh4hj3fgck2zkd9l76k66laxv2u6fc276hyegdrx2gnk5qpdda7krh60esuwcw3sp753r8lnqqpxktvscchvy809zc3v2tu5dxrh5mmk8klakvhpktqkgjz2uyqpe420vcg8udqf0ph55nz4778km2324yv5lar95d58y20qnqp034q77krlm9v9qg56c9jmgeazqcdt6ftvtte3ypv4nfrpwch8g009hv2tgpfsqpxcvxwlpcpy73uxyvxqjswy9enr33d80xrktwh89nthml7yme8vvv
keys/ssh/solo2.age

This is a binary file and will not be displayed.

+1
keys/ssh/solo2.pub
··· 1 + sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIB1Dy0snO1DvjMsltkJdgh222kpXwQLX1l4KaqMI6BVpAAAABHNzaDo= lassulus@solo2
+134
tools/ssh-with-age/flake-module.nix
··· 1 + { self, ... }: 2 + { 3 + perSystem = 4 + { pkgs, ... }: 5 + let 6 + lib = pkgs.lib; 7 + 8 + # Generate key entries from flake structure 9 + sshKeyEntries = lib.concatStringsSep "\n" ( 10 + lib.mapAttrsToList ( 11 + name: key: 12 + if key.encrypted && key.private != null then 13 + '' 14 + ["${name}"]="${key.private}" 15 + '' 16 + else 17 + "" 18 + ) self.keys.ssh 19 + ); 20 + in 21 + { 22 + packages.ssh-with-age = pkgs.writeShellApplication { 23 + name = "ssh-with-age"; 24 + runtimeInputs = [ 25 + pkgs.openssh 26 + pkgs.age 27 + pkgs.age-plugin-se 28 + pkgs.libfido2 29 + ]; 30 + text = '' 31 + # SSH wrapper that tries each encrypted SSH key with the connected FIDO2 device 32 + 33 + # Key mapping from flake 34 + declare -A SSH_KEYS 35 + ${sshKeyEntries} 36 + 37 + usage() { 38 + echo "Usage: ssh-with-age [ssh_options...] destination" 39 + echo " ssh-with-age --list-keys" 40 + echo " ssh-with-age --list-devices" 41 + echo "" 42 + echo "Tries each encrypted SSH key to find one that works with the connected FIDO2 device." 43 + exit 1 44 + } 45 + 46 + list_keys() { 47 + echo "Available encrypted SSH keys:" 48 + for key in "''${!SSH_KEYS[@]}"; do 49 + echo " $key" 50 + done 51 + } 52 + 53 + list_devices() { 54 + echo "Connected FIDO2 devices:" 55 + fido2-token -L 56 + } 57 + 58 + if [[ "$1" == "--list-keys" ]]; then 59 + list_keys 60 + exit 0 61 + fi 62 + 63 + if [[ "$1" == "--list-devices" ]]; then 64 + list_devices 65 + exit 0 66 + fi 67 + 68 + if [[ "$1" == "-h" || "$1" == "--help" ]]; then 69 + usage 70 + fi 71 + 72 + # Create temporary file for decrypted key 73 + temp_key=$(mktemp) 74 + trap 'rm -f "$temp_key"' EXIT 75 + 76 + # Try each encrypted SSH key 77 + found_key="" 78 + for key_name in "''${!SSH_KEYS[@]}"; do 79 + encrypted_key="''${SSH_KEYS[$key_name]}" 80 + 81 + if [[ ! -f "$encrypted_key" ]]; then 82 + continue 83 + fi 84 + 85 + echo "Trying key: $key_name..." 86 + 87 + # Try to decrypt with repository age keys 88 + decrypted=false 89 + for age_key in ${toString (builtins.attrValues self.keys.age)}; do 90 + if age -d -j fido2-hmac "$encrypted_key" > "$temp_key" 2>/dev/null || \ 91 + age -d -j se "$encrypted_key" > "$temp_key" 2>/dev/null; then 92 + decrypted=true 93 + break 94 + fi 95 + done 96 + 97 + if [[ "$decrypted" == "true" ]]; then 98 + 99 + # Check if this is a FIDO2 key by looking at the key type 100 + if grep -q "sk-" "$temp_key"; then 101 + # Try to use the key with SSH agent to verify it works with connected device 102 + chmod 600 "$temp_key" 103 + 104 + # Test if the key works by trying ssh-add 105 + if SSH_ASKPASS=/bin/false ssh-add -T "$temp_key" 2>/dev/null; then 106 + found_key="$key_name" 107 + echo "✓ Found working key: $key_name" 108 + break 109 + else 110 + echo " Key $key_name requires a different FIDO2 device" 111 + fi 112 + else 113 + echo " Key $key_name is not a FIDO2 key" 114 + fi 115 + else 116 + echo " Could not decrypt $key_name (missing age identity?)" 117 + fi 118 + done 119 + 120 + if [[ -z "$found_key" ]]; then 121 + echo "No matching SSH key found for the connected FIDO2 device." >&2 122 + echo "Make sure:" >&2 123 + echo " 1. Your FIDO2 device is connected" >&2 124 + echo " 2. You have the correct age identity to decrypt the keys" >&2 125 + echo " 3. The SSH key was generated with this FIDO2 device" >&2 126 + exit 1 127 + fi 128 + 129 + # Use the found key with SSH 130 + ssh -i "$temp_key" "$@" 131 + ''; 132 + }; 133 + }; 134 + }