{"id":24129289,"url":"https://github.com/rsyncosx/sshcreatekey","last_synced_at":"2026-04-14T02:31:24.325Z","repository":{"id":253056685,"uuid":"842104623","full_name":"rsyncOSX/SSHCreateKey","owner":"rsyncOSX","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-13T08:37:19.000Z","size":51,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-14T22:54:49.188Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rsyncOSX.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-13T17:19:42.000Z","updated_at":"2025-12-13T08:37:23.000Z","dependencies_parsed_at":"2025-02-15T18:19:55.053Z","dependency_job_id":"0d38c537-f5c6-42a5-ba37-bda16b64053e","html_url":"https://github.com/rsyncOSX/SSHCreateKey","commit_stats":null,"previous_names":["rsyncosx/sshcreatekey"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rsyncOSX/SSHCreateKey","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsyncOSX%2FSSHCreateKey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsyncOSX%2FSSHCreateKey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsyncOSX%2FSSHCreateKey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsyncOSX%2FSSHCreateKey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rsyncOSX","download_url":"https://codeload.github.com/rsyncOSX/SSHCreateKey/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsyncOSX%2FSSHCreateKey/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31779943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-11T19:20:14.911Z","updated_at":"2026-04-14T02:31:24.318Z","avatar_url":"https://github.com/rsyncOSX.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Hi there 👋\n\nThis package is code for assisting users to create SSH identityfile and key in RsyncUI. The user can either let RsyncUI assist in creating SSH identityfile and key in RsyncUI, or create it by commandline.\n\n# SSHCreateKey\n\nA Swift package for managing SSH keys, including creation, validation, and deployment to remote servers. Provides a safe, type-safe interface for common SSH key operations.\n\n## Features\n\n- **SSH Key Generation**: Create RSA key pairs with ssh-keygen\n- **Key Deployment**: Copy public keys to remote servers using ssh-copy-id\n- **Key Validation**: Verify public key presence and remote key access\n- **Path Management**: Handle custom SSH key paths with tilde expansion\n- **Security Validation**: Input sanitization for server addresses and usernames\n- **Port Configuration**: Support for custom SSH ports\n- **Directory Management**: Automatic SSH directory creation\n- **Key Discovery**: List all SSH keys in the key directory\n\n## Requirements\n\n- Swift 5.9+\n- macOS 13.0+ / iOS 16.0+ (macOS recommended for full SSH functionality)\n- Foundation framework\n- SSH command-line tools (`ssh-keygen`, `ssh-copy-id`, `ssh`)\n\n## Usage\n\n### Basic Key Creation\n\n```swift\nimport SSHCreateKey\n\n// Initialize with default settings\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: nil,\n    sharedSSHKeyPathAndIdentityFile: nil\n)\n\n// Create SSH directory if needed\ntry sshKey.createSSHKeyRootPath()\n\n// Generate key creation arguments\nlet args = try sshKey.argumentsCreateKey()\n// Returns: [\"-t\", \"rsa\", \"-N\", \"\", \"-f\", \"/Users/username/.ssh/id_rsa\"]\n\n// Execute ssh-keygen (using your process execution framework)\n// /usr/bin/ssh-keygen -t rsa -N \"\" -f /Users/username/.ssh/id_rsa\n```\n\n### Custom SSH Key Path\n\n```swift\n// Use custom key location\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: \"2222\",\n    sharedSSHKeyPathAndIdentityFile: \"~/.ssh/my_custom_key\"\n)\n\n// Get full path\nif let fullPath = sshKey.sshKeyPathAndIdentityFile {\n    print(\"Key will be created at: \\(fullPath)\")\n    // Output: /Users/username/.ssh/my_custom_key\n}\n\n// Get just the identity file name\nprint(\"Identity file: \\(sshKey.identityFileOnly)\")\n// Output: my_custom_key\n\n// Get directory path only\nif let dirPath = sshKey.sshKeyPath {\n    print(\"SSH directory: \\(dirPath)\")\n    // Output: /Users/username/.ssh\n}\n```\n\n### Deploy Key to Remote Server\n\n```swift\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: \"22\",\n    sharedSSHKeyPathAndIdentityFile: \"~/.ssh/id_rsa\"\n)\n\n// Generate ssh-copy-id arguments\nlet args = try sshKey.argumentsSSHCopyID(\n    offsiteServer: \"example.com\",\n    offsiteUsername: \"john\"\n)\n// Returns arguments for: ssh-copy-id -i ~/.ssh/id_rsa -p 22 john@example.com\n\n// Execute ssh-copy-id with these arguments\n```\n\n### Verify Remote Key Access\n\n```swift\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: \"2222\",\n    sharedSSHKeyPathAndIdentityFile: \"~/.ssh/id_rsa\"\n)\n\n// Generate SSH verification arguments\nlet args = try sshKey.argumentsVerifyRemotePublicSSHKey(\n    offsiteServer: \"example.com\",\n    offsiteUsername: \"john\"\n)\n// Returns arguments for: ssh -p 2222 -i ~/.ssh/id_rsa john@example.com\n\n// Test connection with these arguments\n```\n\n### Validate Key Presence\n\n```swift\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: nil,\n    sharedSSHKeyPathAndIdentityFile: \"~/.ssh/id_rsa\"\n)\n\n// Check if public key exists\nif sshKey.validatePublicKeyPresent() {\n    print(\"✓ Public key (id_rsa.pub) exists\")\n} else {\n    print(\"✗ Public key not found - need to create it\")\n}\n```\n\n### List All SSH Keys\n\n```swift\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: nil,\n    sharedSSHKeyPathAndIdentityFile: nil\n)\n\n// Get all files in SSH directory\nif let keyFiles = sshKey.allSSHKeyFiles {\n    print(\"SSH Key Files:\")\n    for file in keyFiles {\n        print(\"  - \\(file)\")\n    }\n}\n// Example output:\n//   - id_rsa\n//   - id_rsa.pub\n//   - known_hosts\n//   - config\n```\n\n## Complete Workflow Example\n\n### Creating and Deploying an SSH Key\n\n```swift\nimport SSHCreateKey\nimport ProcessCommand  // Your process execution framework\n\nfunc setupSSHKey(\n    server: String,\n    username: String,\n    customKeyPath: String? = nil,\n    port: String? = nil\n) async throws {\n    \n    // 1. Initialize SSH key manager\n    let sshKey = SSHCreateKey(\n        sharedSSHPort: port,\n        sharedSSHKeyPathAndIdentityFile: customKeyPath\n    )\n    \n    // 2. Create SSH directory if needed\n    print(\"Creating SSH directory...\")\n    try sshKey.createSSHKeyRootPath()\n    \n    // 3. Check if key already exists\n    if sshKey.validatePublicKeyPresent() {\n        print(\"✓ SSH key already exists\")\n    } else {\n        print(\"Creating new SSH key...\")\n        \n        // 4. Generate the key\n        let keygenArgs = try sshKey.argumentsCreateKey()\n        \n        // Execute ssh-keygen (pseudo-code)\n        let process = ProcessCommand(\n            command: \"/usr/bin/ssh-keygen\",\n            arguments: keygenArgs,\n            handlers: createHandlers()\n        )\n        try await process.executeProcess()\n        \n        print(\"✓ SSH key created\")\n    }\n    \n    // 5. Copy key to remote server\n    print(\"Deploying key to \\(server)...\")\n    let copyArgs = try sshKey.argumentsSSHCopyID(\n        offsiteServer: server,\n        offsiteUsername: username\n    )\n    \n    // Execute ssh-copy-id (pseudo-code)\n    let copyProcess = ProcessCommand(\n        command: \"/usr/bin/ssh-copy-id\",\n        arguments: Array(copyArgs.dropFirst()), // Remove command itself\n        handlers: createHandlers()\n    )\n    try await copyProcess.executeProcess()\n    \n    print(\"✓ Key deployed to server\")\n    \n    // 6. Verify connection\n    print(\"Verifying SSH connection...\")\n    let verifyArgs = try sshKey.argumentsVerifyRemotePublicSSHKey(\n        offsiteServer: server,\n        offsiteUsername: username\n    )\n    \n    // Test SSH connection (pseudo-code)\n    let verifyProcess = ProcessCommand(\n        command: \"/usr/bin/ssh\",\n        arguments: Array(verifyArgs.dropFirst()) + [\"echo\", \"Connection successful\"],\n        handlers: createHandlers()\n    )\n    try await verifyProcess.executeProcess()\n    \n    print(\"✓ SSH setup complete!\")\n}\n\n// Usage\ntry await setupSSHKey(\n    server: \"example.com\",\n    username: \"john\",\n    customKeyPath: \"~/.ssh/my_server_key\",\n    port: \"2222\"\n)\n```\n\n## SwiftUI Integration\n\n### SSH Key Setup View\n\n```swift\nimport SwiftUI\nimport SSHCreateKey\n\nstruct SSHKeySetupView: View {\n    @State private var server = \"\"\n    @State private var username = \"\"\n    @State private var port = \"22\"\n    @State private var customKeyPath = \"\"\n    @State private var useCustomPath = false\n    \n    @State private var isProcessing = false\n    @State private var statusMessage = \"\"\n    @State private var errorMessage: String?\n    \n    var body: some View {\n        Form {\n            Section(\"Server Details\") {\n                TextField(\"Server Address\", text: $server)\n                    .textContentType(.URL)\n                \n                TextField(\"Username\", text: $username)\n                    .textContentType(.username)\n                \n                TextField(\"Port\", text: $port)\n                    .keyboardType(.numberPad)\n            }\n            \n            Section(\"SSH Key Configuration\") {\n                Toggle(\"Use Custom Key Path\", isOn: $useCustomPath)\n                \n                if useCustomPath {\n                    TextField(\"Key Path\", text: $customKeyPath)\n                        .font(.system(.body, design: .monospaced))\n                    Text(\"Example: ~/.ssh/my_custom_key\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                }\n            }\n            \n            Section {\n                Button(action: setupKey) {\n                    if isProcessing {\n                        ProgressView()\n                    } else {\n                        Label(\"Setup SSH Key\", systemImage: \"key.fill\")\n                    }\n                }\n                .disabled(isProcessing || server.isEmpty || username.isEmpty)\n            }\n            \n            if !statusMessage.isEmpty {\n                Section(\"Status\") {\n                    Text(statusMessage)\n                        .foregroundStyle(.secondary)\n                }\n            }\n            \n            if let errorMessage {\n                Section(\"Error\") {\n                    Text(errorMessage)\n                        .foregroundStyle(.red)\n                }\n            }\n        }\n        .navigationTitle(\"SSH Key Setup\")\n    }\n    \n    func setupKey() {\n        Task {\n            isProcessing = true\n            errorMessage = nil\n            statusMessage = \"Initializing...\"\n            \n            do {\n                let sshKey = SSHCreateKey(\n                    sharedSSHPort: port,\n                    sharedSSHKeyPathAndIdentityFile: useCustomPath ? customKeyPath : nil\n                )\n                \n                // Create directory\n                statusMessage = \"Creating SSH directory...\"\n                try sshKey.createSSHKeyRootPath()\n                \n                // Check for existing key\n                if sshKey.validatePublicKeyPresent() {\n                    statusMessage = \"✓ SSH key already exists\"\n                } else {\n                    statusMessage = \"Creating new SSH key...\"\n                    let args = try sshKey.argumentsCreateKey()\n                    // Execute key creation here\n                    statusMessage = \"✓ SSH key created\"\n                }\n                \n                // Deploy key\n                statusMessage = \"Deploying key to server...\"\n                let copyArgs = try sshKey.argumentsSSHCopyID(\n                    offsiteServer: server,\n                    offsiteUsername: username\n                )\n                // Execute ssh-copy-id here\n                \n                statusMessage = \"✓ Setup complete!\"\n                \n            } catch let error as SSHKeyError {\n                errorMessage = error.localizedDescription\n                statusMessage = \"Setup failed\"\n            } catch {\n                errorMessage = error.localizedDescription\n                statusMessage = \"Setup failed\"\n            }\n            \n            isProcessing = false\n        }\n    }\n}\n```\n\n## API Reference\n\n### SSHCreateKey\n\nMain class for SSH key management.\n\n#### Initialization\n\n```swift\npublic init(\n    sharedSSHPort: String?,\n    sharedSSHKeyPathAndIdentityFile: String?\n)\n```\n\n#### Properties\n\n- `createKeyCommand: String` - Path to ssh-keygen (default: \"/usr/bin/ssh-keygen\")\n- `allSSHKeyFiles: [String]?` - List of all files in SSH directory\n- `sshKeyPathAndIdentityFile: String?` - Full path including identity file\n- `identityFileOnly: String` - Just the identity file name\n- `sshKeyPath: String?` - Directory path without identity file\n- `userHomeDirectoryPath: String?` - User's home directory\n\n#### Methods\n\n**Directory Management:**\n```swift\nfunc createSSHKeyRootPath() throws\n```\nCreates SSH directory if it doesn't exist.\n\n**Key Generation:**\n```swift\nfunc argumentsCreateKey() throws -\u003e [String]\n```\nReturns arguments for ssh-keygen to create RSA key pair.\n\n**Key Deployment:**\n```swift\nfunc argumentsSSHCopyID(\n    offsiteServer: String,\n    offsiteUsername: String\n) throws -\u003e [String]\n```\nReturns arguments for ssh-copy-id to deploy public key.\n\n**Key Verification:**\n```swift\nfunc argumentsVerifyRemotePublicSSHKey(\n    offsiteServer: String,\n    offsiteUsername: String\n) throws -\u003e [String]\n```\nReturns arguments for SSH connection test.\n\n**Validation:**\n```swift\nfunc validatePublicKeyPresent() -\u003e Bool\n```\nChecks if public key file exists.\n\n### Error Types\n\n```swift\npublic enum SSHKeyError: LocalizedError {\n    case invalidPath                    // Invalid SSH key path\n    case invalidPort                    // Invalid port number\n    case keyDirectoryCreationFailed     // Cannot create directory\n    case homeDirectoryNotFound          // Cannot find home directory\n    case invalidServerAddress           // Invalid server address\n    case invalidUsername                // Invalid username\n}\n```\n\n### LocationKind\n\nHelper enum for file system checks:\n\n```swift\npublic enum LocationKind {\n    case file      // Location is a file\n    case folder    // Location is a folder\n}\n```\n\n## Security Features\n\n### Input Validation\n\nSSHCreateKey validates all inputs to prevent command injection:\n\n```swift\n// These characters are blocked in server addresses and usernames\nlet invalidCharacters = \";|\u0026$`\\n\\r\"\n\ntry sshKey.argumentsSSHCopyID(\n    offsiteServer: \"example.com; rm -rf /\",  // ❌ Throws SSHKeyError.invalidServerAddress\n    offsiteUsername: \"user\"\n)\n```\n\n### Port Validation\n\n```swift\n// Port must be between 1 and 65535\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: \"99999\",  // ❌ Will throw SSHKeyError.invalidPort\n    sharedSSHKeyPathAndIdentityFile: nil\n)\n```\n\n## Path Handling\n\n### Tilde Expansion\n\n```swift\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: nil,\n    sharedSSHKeyPathAndIdentityFile: \"~/.ssh/custom_key\"\n)\n\n// Automatically expands ~ to user home directory\nif let fullPath = sshKey.sshKeyPathAndIdentityFile {\n    print(fullPath)\n    // Output: /Users/john/.ssh/custom_key (not ~/.ssh/custom_key)\n}\n```\n\n### Default Paths\n\nIf no custom path is provided, defaults are used:\n\n- **Key directory**: `~/.ssh`\n- **Identity file**: `id_rsa`\n- **Full default path**: `~/.ssh/id_rsa`\n\n## Best Practices\n\n1. **Always validate key presence** before attempting to create a new one\n2. **Use custom key paths** for server-specific keys (e.g., `~/.ssh/production_key`)\n3. **Handle errors appropriately** - all methods throw typed errors\n4. **Create directory first** using `createSSHKeyRootPath()` before key generation\n5. **Validate input** - the class handles validation, but check return values\n6. **Use specific ports** when servers don't use default SSH port (22)\n7. **Test connections** after deployment using `argumentsVerifyRemotePublicSSHKey()`\n\n## Common Patterns\n\n### Multi-Server Setup\n\n```swift\nstruct ServerConfig {\n    let name: String\n    let address: String\n    let username: String\n    let port: String\n    let keyPath: String\n}\n\nlet servers = [\n    ServerConfig(name: \"Production\", address: \"prod.example.com\", \n                 username: \"deploy\", port: \"22\", keyPath: \"~/.ssh/prod_key\"),\n    ServerConfig(name: \"Staging\", address: \"staging.example.com\", \n                 username: \"deploy\", port: \"2222\", keyPath: \"~/.ssh/staging_key\")\n]\n\nfor server in servers {\n    let sshKey = SSHCreateKey(\n        sharedSSHPort: server.port,\n        sharedSSHKeyPathAndIdentityFile: server.keyPath\n    )\n    \n    // Setup key for this server\n    try sshKey.createSSHKeyRootPath()\n    \n    if !sshKey.validatePublicKeyPresent() {\n        let args = try sshKey.argumentsCreateKey()\n        // Execute key creation\n    }\n    \n    // Deploy to server\n    let copyArgs = try sshKey.argumentsSSHCopyID(\n        offsiteServer: server.address,\n        offsiteUsername: server.username\n    )\n    // Execute deployment\n}\n```\n\n## Troubleshooting\n\n### Key Already Exists Error\n\n```swift\n// Check before creating\nif sshKey.validatePublicKeyPresent() {\n    print(\"Key already exists - skipping creation\")\n} else {\n    let args = try sshKey.argumentsCreateKey()\n    // Create key\n}\n```\n\n### Permission Denied\n\nEnsure the SSH directory has correct permissions (700):\n\n```bash\nchmod 700 ~/.ssh\nchmod 600 ~/.ssh/id_rsa\nchmod 644 ~/.ssh/id_rsa.pub\n```\n\n### Connection Refused\n\nVerify the port and server address:\n\n```swift\nlet sshKey = SSHCreateKey(\n    sharedSSHPort: \"22\",  // Verify correct port\n    sharedSSHKeyPathAndIdentityFile: nil\n)\n```\n\n## License\n\nMIT\n\n## Author\n\nThomas Evensen","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsyncosx%2Fsshcreatekey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frsyncosx%2Fsshcreatekey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsyncosx%2Fsshcreatekey/lists"}