sshkey-2.0.0/0000755000004100000410000000000013445575002013054 5ustar www-datawww-datasshkey-2.0.0/.travis.yml0000644000004100000410000000032713445575002015167 0ustar www-datawww-datalanguage: ruby rvm: - 2.0 - 2.1 - 2.2 - 2.3 - 2.4 - 2.5 - 2.6 - ruby-head - jruby - jruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head sudo: required dist: xenial sshkey-2.0.0/test/0000755000004100000410000000000013445575002014033 5ustar www-datawww-datasshkey-2.0.0/test/sshkey_test.rb0000644000004100000410000007145613445575002016742 0ustar www-datawww-datarequire 'test/unit' require 'sshkey' class SSHKeyTest < Test::Unit::TestCase SSH_PRIVATE_KEY1 = <<-EOF -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14 g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n 6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4 HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjy eGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcS shoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUzKQIBIwKCAQBAnLy2O+5N3s/X/I8R y9E+nrgY79Z7XRTEmrc5JelTnI+eOggwwbVxhP1dR7zE5kItPz2O4NqYGJXbY9u7 V++qiri65oQMJP6Tc7ROwiYzS/jObtugMFPSpLHzwJyrMho6fTOuz3zuRH0qHiJ8 3o4WAs9I8brJqY+UQxmI56t3gfHcX4nRhueyUvmEdDG+4Mob21wED1GD5ENh9ebX UiuYkeROqd+lfBUkWoxUXi2fjRMSRt7n3bq59pyZQCwKiShIVaonciV8xAAlNvhI RBzYvXbQ47YgsTmcW4Srlv0j/Oij2/RaDhkJtaXyPkqw9k4B8oCaX3C2x4sdhcwa iLU7AoGBANb4Rmz1w4wfZSnu/HlW4G0Us+AWVEX+6zePoOartP5Pe5t3XhHW7vpi YoB4ecqhz4Y1LoYZL07cSsQZHfntUV4eh/apuo/5slrhDkk0ewJkUh6SKLOFNv6Q 7iJnmtzzRovW1MQPa0NeInsUrZYe4B4iGZmK4yEr9+c7IQCPFQvVAoGBAM8ofVgb gzDYY2uX1lvU9bGAHqA/qNJHcYZBu5AZr7bkZC1GlSKh93ppczdQhiZmj2FQr09R Z5GgKIlSWk8MYC+kYq7l5r2O42g3Unp+i1Zc5KCYUWYpyeE/jfl5IFJFQJFVtdB1 JlsFxruQIF/HuTzY6D+zF8GzK/T5ZQwigBgFAoGAGJFnImU663FNY+DMZaOHXOxs VB/PHfE/dBBqKP2uSPMkEcR/x4ZHMo7mr5i9dj5g3CNVxi7Dk/vrSZx4dFWi5i9f /u7TfisqU4dvWNLMOsmi/C32BeNWvgHvVGOcq4mEZ8DH2+SBSYcZ4i4/uWKdRUW5 yGek7dkjpWXX4s6GD/sCgYEAiCHr+BIUYe1Ipcotx1FuQXFzNhs0bO0et0/EZgJA RPx8WERTX+bHMy9aV4yv7VlW6C21CDzPB+zncC7NoakMAgzwZE3vZp+6AqgDAAoD ywnYEcMuLTFnaCJzPYocjdW8t0bz0iEZNIAjgpHpY4M/Np0q6Af5qyyZOpVCZw9b fX8CgYEAqFpBwetp78UfwvWyKkfN56cY8EaC7gMkwE4gnXsByrqW0f/Shf5ofpO1 kCMav5GhplRYcF3mUO9xiAPx1FxWj/MjeevkmmugIrcYi5OpGu70KoaBmCmb5uV6 zJLsX4h3i0JFdIOaECZEOXhPA7btQT8Vvznj8cHFeeronqdFWf0= -----END RSA PRIVATE KEY----- EOF SSH_PRIVATE_KEY2 = <<-EOF -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YW CeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LAD UElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3 qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k 5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnI XpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKwIBIwKCAQEAiAZWnPCjQmNegDKg fu5jMWsmzLbccP5TqLnWrFYDFvAKn+46/3fA421HBUVxJ7AoS6+pt6mL54tYsHhu 6rOvsfAlT/AGhbxw1Biyk6P9sOLiRCDsVE+dBjp5jRR9/N1WkLh10FH5hZETCW0b 0y88DG8DkWeR2UUIgngLr+pFr5jV/e4nvA5QpvbNscOwoiR7sFsMGLcMgM2fT4Hj ZZovcGQMrDr6AG+y0/Vdf9wX22j+XKj7huIqM3GZvyqGPqJnP9sOKkPcuTck8Wx3 55BX675RVdoW9OTcHbUh3qHcCND4d9WZqHarW/a7XBdIiuRmC2kBX5WBmVXnm/RF bvxoCwKBgQDqyVNWwm98gIw7LS8dR6Ec7t3kOeLNo/rtNTszo631yZ4hqdwgYR3Q q6rhjufsVVRLVzfTDXucbhZ5h+UB9wXAM49ZPxKNw+ddHsRbhCuIWUl/iO8E/Aub H3eZupo73N9JGa4STFw056ejOQrTTCMf0M316V4wgFAXOZeHEErxSQKBgQDYSuqR nr3Hdw1n/iXfKrfd9fJI++nm14uQ4mkA+9HrtQpj/RTxr66/QSj7p3r6GF4dDYY4 XaqK+iCfhUKMr8+3CP7NoS/saZAUqvMnL+RCvX14sV55xRMwplaaNIwqDhQAhkmL UeOBq40kmBsunjfp06JedmWhWKHYc1eR2iPw0wKBgA1qlwxFn/h8X8jeArE3Swj3 tOh4VhphJEgRq5yNAqBUqfNLiOvoSti5WjjGVmVGtFwTnMo7SOReD+mv/nUkDvUK QrSkhLeky2RoKHpCER279ZJCVs0Vt4U0/4UgmxldFBLORHYS/fRlAkPXX7RNflmW 5zKfnvt1C+QR62bNuyO7AoGBAI4imiUtzStOPAKCcKiYajLGMYBrB2vPePjPTFEa gqI1JBXSMlWt9n2uegR1X3El9LQBkrdTfrMZZeUrr2PD/Ybop3EvaKKrxRTlXfUu GagzYRTMVAbgl5T/l/7vVMst0qFCTZYRPbucnpRj9Jr6QgAOuygh6wOgpN6yMjtG NOAVAoGACIdfR5oZ4tvNIeqjtLF83HmUJARv86eMmjqgiQTFcZS3A8vk5y05STxX HU3kTCfT6sypRi9zDQafIIyqYFgaOezr2eRRFRojQZqzHjtuFUeKLrKf7R9bzwwx DPlNgYq8p4FOY5ZOL/ZOxUHW4vKRewURJttnxzw+LEy0T1FyAE0= -----END RSA PRIVATE KEY----- EOF SSH_PRIVATE_KEY3 = <<-EOF -----BEGIN DSA PRIVATE KEY----- MIIBvAIBAAKBgQC8lcuXcFcIC9wsV87L6PAwYefKgK0CwTSD1v3/aabZsu4w+UF8 zsPtdsNP8+JWfOp3KFbrUTH+ODgAXF/aL4UZfpbsQe446ZFV8v6dmWqj23sk0FLX U5l2tsuJ9OdyXetVXjBvoiz+/r4k/iG/esvWlVGEHwq5eYXgQ1GfXABY3QIVAMVe c7skmkUrCR6iivgZYYe3PQPZAoGBAKnpdEVATtDGOW9w2evSf5kc1InzdTurcJOH q9qYdCaa8rlMGaIS6XFWcKqBlpj0Mv2R5ldW90bU/RllGvh1KinTIRVTsf4qtZIV Xy4vN8IYzDL1493nKndMsxsRh50rI1Snn2tssAix64eJ5VFSGlyOYEKYDMlWzHK6 Jg3tVmc6AoGBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6p ItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZt cMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyRAhQLhz0l GzM8qwTcXd06uIZAJdTHIQ== -----END DSA PRIVATE KEY----- EOF SSH_PUBLIC_KEY1 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUzKQ==' SSH_PUBLIC_KEY2 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKw==' SSH_PUBLIC_KEY3 = 'AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKwkeoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyR' SSH_PUBLIC_KEY_ED25519 = 'AAAAC3NzaC1lZDI1NTE5AAAAIBrNsRCISAtKXV5OVxqV6unVcdis5Uh3oiC6B7CMB7HQ' SSH_PUBLIC_KEY_ED25519_0_BYTE = 'AAAAC3NzaC1lZDI1NTE5AAAAIADK9x9t3yQQH7h4OEJpUa7l2j7mcmKf4LAsNXHxNbSm' SSH_PUBLIC_KEY_ECDSA_256 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHJFDZ5qymZfIzoJcxYeu3C9HjJ08QAbqR28C2zSMLwcb3ZzWdRApnj6wEgRvizsBmr9zyPKb2u5Rp0vjJtQcZo=' SSH_PUBLIC_KEY_ECDSA_384 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBP+GtUCOR8aW7xTtpkbJS0qqNZ98PgbUNtTFhE+Oe+khgoFMX+o0JG5bckVuvtkRl8dr+63kUK0QPTtzP9O5yixB9CYnB8CgCgYo1FCXZuJIImf12wW5nWKglrCH4kV1Qg==' SSH_PUBLIC_KEY_ECDSA_521 = 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACsunidnIZ77AjCHSDp/xknLGDW3M0Ia7nxLdImmp0XGbxtbwYm2ga5XUzV9dMO9wF9ICC3OuH6g9DtGOBNPru1PwFDjaPISGgm0vniEzWazLsvjJVLThOA3VyYLxmtjm0WfS+/DfxgWVS6oeCTnDjjoVVpwU/fDbUbYPPRZI84/hOGNA==' KEY1_MD5_FINGERPRINT = "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af" KEY2_MD5_FINGERPRINT = "3c:af:74:87:cc:cc:a1:12:05:1a:09:b7:7b:ce:ed:ce" KEY3_MD5_FINGERPRINT = "14:f6:6a:12:96:be:44:32:e6:3c:77:43:94:52:f5:7a" ED25519_MD5_FINGERPRINT = "6f:1a:8a:c1:4f:13:5c:36:6e:3f:be:eb:49:3b:8e:3e" ECDSA_256_MD5_FINGERPRINT = "d9:3a:7f:de:b2:65:04:ac:62:05:1a:1e:97:e9:2b:9d" KEY1_SHA1_FINGERPRINT = "e4:f9:79:f2:fe:d6:be:2d:ef:2e:c2:fa:aa:f8:b0:17:34:fe:0d:c0" KEY2_SHA1_FINGERPRINT = "9a:52:78:2b:6b:cb:39:b7:85:ed:90:8a:28:62:aa:b3:98:88:e6:07" KEY3_SHA1_FINGERPRINT = "15:68:c6:72:ac:18:d1:fc:ab:a2:b7:b5:8c:d1:fe:8f:b9:ae:a9:47" ED25519_SHA1_FINGERPRINT = "57:41:7c:d0:e2:53:28:87:7e:87:53:d4:69:ef:ef:63:ec:c0:0e:5e" ECDSA_256_SHA1_FINGERPRINT = "94:e8:92:2b:1b:ec:49:de:ff:85:ea:6e:10:d6:8d:87:7a:67:40:ee" KEY1_SHA256_FINGERPRINT = "js3llFehloxCfsVuDw5xu3NtS9AOAxcXY8WL6vkDIts=" KEY2_SHA256_FINGERPRINT = "23f/6U/LdxIFx1CQFKHylw76n+LIHYoY4nRxKcFoos4=" KEY3_SHA256_FINGERPRINT = "mPqEPQlOPGORrTJrU17sPax1jOqeutZja6MOsFIca+8=" ED25519_SHA256_FINGERPRINT = "gyzHUKl1eO8Bk1Cvn4joRgxRlXo1+1HJ3Vho/hAtKEg=" ECDSA_256_SHA256_FINGERPRINT = "ncy2crhoL44R58GCZPQ5chPRrjlQKKgu07FDNelDmdk=" KEY1_RANDOMART = <<-EOF.rstrip +--[ RSA 2048]----+ |o+ o.. | |..+.o | | ooo | |.++. o | |+o+ + S | |.. + o . | | . + . | | . . | | Eo. | +-----------------+ EOF KEY2_RANDOMART = <<-EOF.rstrip +--[ RSA 2048]----+ | ..o.. | | ..+ . | | o . | | . o | | . o S . | | + o O o | | + + O . | | = o . | | .E | +-----------------+ EOF KEY3_RANDOMART = <<-EOF.rstrip +--[ DSA 1024]----+ | .=o. | | .+.o . | | + =.o . . | | + * + . . | | + = S . E | | + = . . | | . | | | | | +-----------------+ EOF KEY1_SSHFP = <<-EOF.rstrip localhost IN SSHFP 1 1 e4f979f2fed6be2def2ec2faaaf8b01734fe0dc0 localhost IN SSHFP 1 2 8ecde59457a1968c427ec56e0f0e71bb736d4bd00e03171763c58beaf90322db EOF KEY2_SSHFP = <<-EOF.rstrip localhost IN SSHFP 1 1 9a52782b6bcb39b785ed908a2862aab39888e607 localhost IN SSHFP 1 2 db77ffe94fcb771205c7509014a1f2970efa9fe2c81d8a18e2747129c168a2ce EOF KEY3_SSHFP = <<-EOF.rstrip localhost IN SSHFP 2 1 1568c672ac18d1fcaba2b7b58cd1fe8fb9aea947 localhost IN SSHFP 2 2 98fa843d094e3c6391ad326b535eec3dac758cea9ebad6636ba30eb0521c6bef EOF SSH2_PUBLIC_KEY1 = <<-EOF.rstrip ---- BEGIN SSH2 PUBLIC KEY ---- Comment: me@example.com AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElav RHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n 6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM 9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PT PXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N 2CQA+IfHgrXJ+A+QUzKQ== ---- END SSH2 PUBLIC KEY ---- EOF SSH2_PUBLIC_KEY2 = <<-EOF.rstrip ---- BEGIN SSH2 PUBLIC KEY ---- AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgX Nk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LAD UElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQG w5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9 F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedR QhCyiELeSMGwio1vRPKw== ---- END SSH2 PUBLIC KEY ---- EOF SSH2_PUBLIC_KEY3 = <<-EOF.rstrip ---- BEGIN SSH2 PUBLIC KEY ---- Comment: 1024-bit DSA with provided comment x-private-use-header: some value that is long enough to go to wrap aro\\ und to a new line. AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QX zOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2 y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKw keoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZ ohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0 yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroq OzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJE aSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3 nnh7pJWwsbGjSMQexpyR ---- END SSH2 PUBLIC KEY ---- EOF def setup @key1 = SSHKey.new(SSH_PRIVATE_KEY1, :comment => "me@example.com") @key2 = SSHKey.new(SSH_PRIVATE_KEY2, :comment => "me@example.com") @key3 = SSHKey.new(SSH_PRIVATE_KEY3, :comment => "me@example.com") @key_without_comment = SSHKey.new(SSH_PRIVATE_KEY1) end def test_generator_with_no_args assert_kind_of SSHKey, SSHKey.generate end def test_generator_with_comment assert_equal "foo", SSHKey.generate(:comment => "foo").comment end def test_generator_with_type assert_equal "dsa", SSHKey.generate(:type => "dsa").type end def test_generator_with_passphrase assert_equal "password", SSHKey.generate(:passphrase => "password").passphrase end def test_private_key1 assert_equal SSH_PRIVATE_KEY1, @key1.private_key assert_equal SSH_PRIVATE_KEY1, @key1.rsa_private_key end def test_private_key2 assert_equal SSH_PRIVATE_KEY2, @key2.private_key assert_equal SSH_PRIVATE_KEY2, @key2.rsa_private_key end def test_private_key3 assert_equal SSH_PRIVATE_KEY3, @key3.private_key assert_equal SSH_PRIVATE_KEY3, @key3.dsa_private_key end def test_ssh_public_key_decoded1 assert_equal Base64.decode64(SSH_PUBLIC_KEY1), @key1.send(:ssh_public_key_conversion) end def test_ssh_public_key_decoded2 assert_equal Base64.decode64(SSH_PUBLIC_KEY2), @key2.send(:ssh_public_key_conversion) end def test_ssh_public_key_decoded3 assert_equal Base64.decode64(SSH_PUBLIC_KEY3), @key3.send(:ssh_public_key_conversion) end def test_ssh_public_key_encoded1 assert_equal SSH_PUBLIC_KEY1, Base64.encode64(@key1.send(:ssh_public_key_conversion)).gsub("\n", "") end def test_ssh_public_key_encoded2 assert_equal SSH_PUBLIC_KEY2, Base64.encode64(@key2.send(:ssh_public_key_conversion)).gsub("\n", "") end def test_ssh_public_key_encoded3 assert_equal SSH_PUBLIC_KEY3, Base64.encode64(@key3.send(:ssh_public_key_conversion)).gsub("\n", "") end def test_ssh_public_key_output expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com" expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}" assert_equal expected1, @key1.ssh_public_key assert_equal expected2, @key2.ssh_public_key assert_equal expected3, @key3.ssh_public_key assert_equal expected4, @key_without_comment.ssh_public_key end def test_ssh2_public_key_output expected1 = SSH2_PUBLIC_KEY1 expected2 = SSH2_PUBLIC_KEY2 expected3 = SSH2_PUBLIC_KEY3 assert_equal expected1, @key1.ssh2_public_key assert_equal expected2, @key2.ssh2_public_key({}) assert_equal expected3, @key3.ssh2_public_key({'Comment' => '1024-bit DSA with provided comment', 'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'}) end def test_public_key_directives assert_equal [], SSHKey.generate.directives @key1.directives = "no-pty" assert_equal ["no-pty"], @key1.directives @key1.directives = ["no-pty"] assert_equal ["no-pty"], @key1.directives @key1.directives = [ "no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding", "no-pty", "command='/home/user/bin/authprogs'" ] expected1 = "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command='/home/user/bin/authprogs' ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" assert_equal expected1, @key1.ssh_public_key assert SSHKey.valid_ssh_public_key?(expected1) @key2.directives = "no-pty" expected2 = "no-pty ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" assert_equal expected2, @key2.ssh_public_key assert SSHKey.valid_ssh_public_key?(expected2) end def test_ssh_public_key_validation expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com" expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}" expected5 = %Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ssh-rsa #{SSH_PUBLIC_KEY1}} invalid1 = "ssh-rsa #{SSH_PUBLIC_KEY1}= me@example.com" invalid2 = "ssh-rsa #{SSH_PUBLIC_KEY2}= me@example.com" invalid3 = "ssh-dss #{SSH_PUBLIC_KEY3}= me@example.com" invalid4 = "ssh-rsa A#{SSH_PUBLIC_KEY1}" invalid5 = "ssh-rsa #{SSH_PUBLIC_KEY3} me@example.com" assert SSHKey.valid_ssh_public_key?(expected1) assert SSHKey.valid_ssh_public_key?(expected2) assert SSHKey.valid_ssh_public_key?(expected3) assert SSHKey.valid_ssh_public_key?(expected4) assert SSHKey.valid_ssh_public_key?(expected5) assert !SSHKey.valid_ssh_public_key?(invalid1) assert !SSHKey.valid_ssh_public_key?(invalid2) assert !SSHKey.valid_ssh_public_key?(invalid3) assert !SSHKey.valid_ssh_public_key?(invalid4) assert !SSHKey.valid_ssh_public_key?(invalid5) end def test_ssh_public_key_validation_elliptic assert SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com") assert SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519_0_BYTE} me@example.com") assert SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}") assert SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") assert SSHKey.valid_ssh_public_key?(%Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@example.com}) assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}= me@example.com") # bad base64 assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") # mismatched key format assert !SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") # mismatched key format assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 asdf me@example.com") # gibberish key data assert !SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 asdf me@example.com") # gibberish key data end def test_ssh_public_key_validation_with_newlines expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1}\n" expected2 = "ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com\n" invalid1 = "ssh-rsa #{SSH_PUBLIC_KEY1}\nme@example.com" invalid2 = "ssh-rsa #{SSH_PUBLIC_KEY1}\n me@example.com" invalid3 = "ssh-rsa #{SSH_PUBLIC_KEY1} \nme@example.com" invalid4 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}\nme@example.com" assert SSHKey.valid_ssh_public_key?(expected1) assert SSHKey.valid_ssh_public_key?(expected2) assert !SSHKey.valid_ssh_public_key?(invalid1) assert !SSHKey.valid_ssh_public_key?(invalid2) assert !SSHKey.valid_ssh_public_key?(invalid3) assert !SSHKey.valid_ssh_public_key?(invalid4) end def test_ssh_public_key_sshfp assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY1}\n") assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY2}\n") assert_equal KEY3_SSHFP, SSHKey.sshfp("localhost", "ssh-dss #{SSH_PUBLIC_KEY3}\n") assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY1) assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY2) assert_equal KEY3_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY3) end def test_ssh_public_key_bits expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com" expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}" expected5 = %Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ssh-rsa #{SSH_PUBLIC_KEY1}} invalid1 = "#{SSH_PUBLIC_KEY1} me@example.com" assert_equal 2048, SSHKey.ssh_public_key_bits(expected1) assert_equal 2048, SSHKey.ssh_public_key_bits(expected2) assert_equal 1024, SSHKey.ssh_public_key_bits(expected3) assert_equal 2048, SSHKey.ssh_public_key_bits(expected4) assert_equal 2048, SSHKey.ssh_public_key_bits(expected5) assert_equal 512, SSHKey.ssh_public_key_bits(SSHKey.generate(:bits => 512).ssh_public_key) exception1 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits( expected1.gsub('A','.') ) } exception2 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits( expected1[0..-20] ) } exception3 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits(invalid1) } assert_equal( "validation error", exception1.message ) assert_equal( "byte array too short", exception2.message ) assert_equal( "cannot determine key type", exception3.message ) end def test_ssh2_public_key_bits public_key1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" public_key2 = "ssh-rsa #{SSH_PUBLIC_KEY2}" public_key3 = "ssh-dss #{SSH_PUBLIC_KEY3} 1024-bit DSA with provided comment" assert_equal(SSH2_PUBLIC_KEY1, SSHKey.ssh_public_key_to_ssh2_public_key(public_key1)) assert_equal(SSH2_PUBLIC_KEY2, SSHKey.ssh_public_key_to_ssh2_public_key(public_key2)) assert_equal(SSH2_PUBLIC_KEY2, SSHKey.ssh_public_key_to_ssh2_public_key(public_key2, {})) assert_equal(SSH2_PUBLIC_KEY3, SSHKey.ssh_public_key_to_ssh2_public_key(public_key3, {'Comment' => '1024-bit DSA with provided comment', 'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'})) end def test_exponent assert_equal 35, @key1.key_object.e.to_i assert_equal 35, @key2.key_object.e.to_i end def test_modulus assert_equal 21959919395955180268707532246136630338880737002345156586705317733493418045367765414088155418090419238250026039981229751319343545922377196559932805781226688384973919515037364518167604848468288361633800200593870224270802677578686553567598208927704479575929054501425347794297979215349516030584575472280923909378896367886007339003194417496761108245404573433556449606964806956220743380296147376168499567508629678037211105349574822849913423806275470761711930875368363589001630573570236600319099783704171412637535837916991323769813598516411655563604244942820475880695152610674934239619752487880623016350579174487901241422633, @key1.key_object.n.to_i assert_equal 25041821909255634338594631709409930006900629565221199423527442992482865961613864019776541767273966885435978473182530882048894721263421597979360058644777295324028381353356143013803778109979347540540538361684778724178534886189535456555760676722894784592989232554962714835255398111716791175503010379276254975882143986862239829255392231575481418297073759441882528318940783011390002193682320028951031205422825662402426266933458263786546846123394508656926946338411182471843223455365249418245551220933173115037201835242811305615780499842939975996344432437312062643436832423359634116147870328774728910949553186982115987967787, @key2.key_object.n.to_i end def test_fingerprint assert_equal KEY1_MD5_FINGERPRINT, @key1.md5_fingerprint assert_equal KEY1_MD5_FINGERPRINT, @key1.fingerprint # Aliased method assert_equal KEY2_MD5_FINGERPRINT, @key2.md5_fingerprint assert_equal KEY3_MD5_FINGERPRINT, @key3.md5_fingerprint assert_equal KEY1_SHA1_FINGERPRINT, @key1.sha1_fingerprint assert_equal KEY2_SHA1_FINGERPRINT, @key2.sha1_fingerprint assert_equal KEY3_SHA1_FINGERPRINT, @key3.sha1_fingerprint assert_equal KEY1_SHA256_FINGERPRINT, @key1.sha256_fingerprint assert_equal KEY2_SHA256_FINGERPRINT, @key2.sha256_fingerprint assert_equal KEY3_SHA256_FINGERPRINT, @key3.sha256_fingerprint assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY1) assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}") assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY2) assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com") assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY3) assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}") assert_equal ED25519_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}") assert_equal ECDSA_256_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com") assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY1) assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}") assert_equal KEY2_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY2) assert_equal KEY2_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com") assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY3) assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}") assert_equal ED25519_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}") assert_equal ECDSA_256_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com") assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY1) assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}") assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY2) assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com") assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY3) assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}") assert_equal ED25519_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}") assert_equal ECDSA_256_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com") end def test_bits assert_equal 2048, @key1.bits assert_equal 2048, @key2.bits assert_equal 1024, @key3.bits assert_equal 512, SSHKey.generate(:bits => 512).bits end def test_randomart assert_equal KEY1_RANDOMART, @key1.randomart assert_equal KEY2_RANDOMART, @key2.randomart assert_equal KEY3_RANDOMART, @key3.randomart end def test_sshfp assert_equal KEY1_SSHFP, @key1.sshfp("localhost") assert_equal KEY2_SSHFP, @key2.sshfp("localhost") assert_equal KEY3_SSHFP, @key3.sshfp("localhost") end end class SSHKeyEncryptedTest < Test::Unit::TestCase ENCRYPTED_PRIVATE_KEY = <<-EOF -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,3514D8812B519059944A811726594515 fSr1v51I65MZrSs7u12nype6RH6NS15xN5FDPaPKV++EBPxysEzicU5QHDt/aHa3 t87nXkra1M400+zNgFcfi5Ga7w5SBmqEjdNgwhUV2/j1Yqqlr5c7l804OfIPxdE6 ELoWH7pen72JnlZe6gXq495W96QTg3IzIWdiKEbKJlEwrNBligqT7GB2mup8nY1D o71R07dIrvfDy3xVgCoRjX4LKUilO6nRnwVCFRgVQTEVKclqt8NiSFGMjzv3iekR f1fJ8Wm6CiST8zdetIXgMnHEK1KELhNeMhI/42Tn/gHPDsckBiKLtM+85OOT92wh L9o/KUySdcsb/ld0yT/kAc99/wqNitHAqUEcLshIWDVhqoT1XK46hEuRN782AN2U shQKirF8QFopYF+u9K2Q0mr1EsYaBWOFFBR7EiwFvEYOx+ad6qGQGPcxWhbf6eCU D///9g1g5q8nWb80UH9Hw1aMhIA+VTlIasM6XJKmGr1LapxlrYsqRovPwkgOQg01 jhSV1fy10bbaFBwd9qTdTTVqa368/e3/TxF2VKhDaqoy5lqvRqKzGJxi3ubzDuz9 m3qRTCgy1v3XI5DgcjWt5xC5gZLHjKf79fQKRJjuEnWALahpDVWQ6PRCuqPfyph5 /vVqGHqvA53HJ9pmXz4J9qtQQ2gkYRj1m2tlRJjtGRMqnAj7bpcDKIrdLudOiWB0 FXwmsXljzPaf/SPUa+tGg7jbh+Jq+72vdpo1ijJtLXhWQAJasIbvSXOVHbZ6YhJj vES98gJPzevqemS//C1DMrr0ci6pM9ciT2szkrg71zRacnfqrjeZUI7qHKAsRbD5 258Jj6BeFPeQSrUg8sqQdoPxVTNnVr2bOB5SNfh7gqLanPksi6Kr7XNIzsYP5Wzf ADAElPdcRRwYc2kLVqugZTMLSn1r8rQjEyQ8/TT2QefI4ma8mCrBKgqYX+SDbhMJ +KUrah0jCgj86z6fSNkNHaKuzvGCovZsJHXt2SoIWVYWVUz91IHPKXXycIqvf3Yj 9HFpJRAPh30MYBgiImCJjmk8tqKGn0Tc80vOsrKMlVuMIIu2eNrddrnHUzSbjIHr tTtSDvsJ/Bn/6+Ox77U/FKg6s6/6PxOA1a+ffKkBXB/g4jS5CfGZl1owb3kX91C/ a+bcNWp07DsaTaZd/0UEL2gIvaEuCULgyIvmnBPCOY6Pc5GGegWEJne/sk7j9W4I 59YtVfcSXiovYR/QEywytfN/tfPxKfUoqNMIxLkukjFYz4Zzk5kXEeI+1lcry398 UQSaOboSDKY6boX4rWgiiqyn5LN+47eAIZPO+zsWXky16F04JpT7V7XqZPXQn7vI pMAoPCkT4qE9Gp2CcSj2l2CoZ3ZA5lOs6Wvxuz0q1zd0uSe8O81/3rnw28DthDQO SuzrY0HinPEFomwMGbfhosB5kOmBXEk7XbSWWHhK0QG63CYqp5caUst2Mie21b0Y FgFTrS1VqUiqDjCmt8F8UCPQS89aFm096wqtmwDO+VWKanuHUUShtTPlYyLe1RTm wqh1BBa05ydM0Vf1NagFB4JNT1qSIL5x4XtkOFwqcdXWYvwYfT8PkZjX/kz+W7jb -----END RSA PRIVATE KEY----- EOF DECRYPTED_PRIVATE_KEY = <<-EOF -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA33H9u4rG0SVMzK8PFyIi30kVHvogmpKVOQL2g2tozi2GipkA imzoCW9jIx1jo6zo0kMKCbMdJCNUc93tQxkJAAWA07WYwE9z+J6MC/3urb4Q0UAZ orlNyN3pPP8ATFIQomd0wW/EHwbmknlJOIZY4MNUQpNJunnBAAzZyLef6+sbLzQZ wc0/vgtmatoCxh4mZWFuFMopkMqScYDKxL30xXXPRGfJvAVWZDnY3ErJSe7uyefI Zo/lp4tsH5XGMcFm+nLs++nJjYZ6Ud/ie1g+ZCJv5Qit1m7zCVjiJ4RVMC/4yTb7 CO8n4PhX8gR9d1DRPVvlKa1sZfQeULEIXMniKQIDAQABAoIBAQDEXS74s5rJjhgS AP4n/E3dICK5mGMytAMDmUD+eVQfbQ7BmnhJLjA0qnjbESbRXlE1Bsk5gPjpG0tK kAvEXan1JOD0LLDSwIBQSzUUDNLGSTQKUGS3BlX/YlVoz0h5ydzofDa1D/2wrqXO r1vTmu1ciQvxffLbN8iOvLxfkk+uSMhqnhf/q3WVinu+VALPg8e/v3p4VbnSfP4D eClBiMKEKRFdsa9xBxShijcX1HxjIvp3zDgb127fo2iFw09PIHUZCUo6M77iGrQY mscoA+5q1qSyD6Btw0EkKK55ytNMC2T+KfGYV0ySwhadmM+5+G64etvNGCn/j7CU rhuRhMlpAoGBAPiJlcWaNVw3ttlea+jllxnaCEOmG29NGWxw080XRvjSIrltPAiE 8e4FynAMx49aXKsaB8Df2WspdKBxHJgv1U0sapADwxlMO8erj2eDSRqy6bwnI4CT T+vvo2vdmkvkV0D9RskXCi5tgTO5FYnf7CjON6JLkg83V3vzyjsKlvDzAoGBAOYn iC50OdZ84U58wQUsvwXFuyzMQX9r+h0jL2tIDv/yYlMWg9tNt0HkGUOo7H1ZVhdL 9Z1B0Ztl2qoJipcQvhzfwdo4XwuLk7D0bOAfZo9YMbU5Jqy+rqE5yv/P4wa7ba4S uUQYvSuv54CtiOZDFyK8dU7y9mm+no3Fvrd9RwdzAoGANzlLACctqBnxFQd37r3k /yeFIpLsEaUN+xxu02lSqcL3WEA/UJ1JrFu5CYCtbtrjMFmOU3rpsnf5pBS+B8rJ GGbAHtPXK+3Wcp1aNePkAHy0lswThWQ2I/SRWUxaFnbcNGKSsefeqUZHqRh9Aq+w p7h6gCNOhvcDB1W6H7hQpaUCgYEAyBRDygaWJUVI5N+FOUduBMmhb09d/TTUKTJm TcBF8fE30v12wVZtYqW15ODcPhhExFnverc2Tf6cukczKSKP8y/+KQPqdHHxgdrr L2d81E6aX+4AFhpqW5SPShXiSf70WWjDkFRlV65C9dVmdq6KVVM6M9j5qHHjCmKG 6qLI9csCgYBuhFwwI9DiYvJPR1LJZnJtE0qZiTwpmCjU2LoBRsywvuBeyXtwpmIM 5IgfgXXLK5qK/+cp9047T5rzT6ndu5fNZINPzynA8tNhTtHXK8l/GT/iq8Rd6AcM WJmIe8EnUDhHqg7Z2h5tGpX1QPMSA4G8RGPPyrcd3v0G/PZ6pFALlQ== -----END RSA PRIVATE KEY----- EOF DECRYPTED_KEY_FINGERPRINT = "2e:ba:06:b1:c7:13:37:24:6f:2d:3a:ba:45:e2:b4:78" def setup @key_encrypted = SSHKey.new(ENCRYPTED_PRIVATE_KEY, :passphrase => "password") end def test_encrypted_private_key_can_be_decrypted assert_equal DECRYPTED_PRIVATE_KEY, @key_encrypted.private_key end def test_encrypted_private_key_matches_when_reencrypted key = SSHKey.new(@key_encrypted.encrypted_private_key, :passphrase => "password") assert_equal DECRYPTED_PRIVATE_KEY, key.private_key assert_equal DECRYPTED_KEY_FINGERPRINT, key.md5_fingerprint end end sshkey-2.0.0/README.md0000644000004100000410000003161113445575002014335 0ustar www-datawww-data# SSHKey Generate private and public SSH keys (RSA and DSA supported) using pure Ruby. [![Build Status](https://secure.travis-ci.org/bensie/sshkey.svg?branch=master)](http://travis-ci.org/bensie/sshkey) ## Requirements Tested / supported on CRuby 2.0.0+ and JRuby. ## Installation gem install sshkey ## Usage ### Generate a new key When generating a new keypair the default key type is 2048-bit RSA, but you can supply the `type` (RSA or DSA) and `bits` in the options. You can also (optionally) supply a `comment` or `passphrase`. ```ruby k = SSHKey.generate k = SSHKey.generate( type: "DSA", bits: 1024, comment: "foo@bar.com", passphrase: "foobar" ) ``` ### Use your existing key Return an SSHKey object from an existing RSA or DSA private key (provided as a string). ```ruby f = File.read(File.expand_path("~/.ssh/id_rsa")) k = SSHKey.new(f, comment: "foo@bar.com") ``` ### The SSHKey object #### Private and public keys Fetch the private and public keys as strings. Note that the `public_key` is the RSA or DSA public key, not an SSH public key. ```ruby k.private_key # => "-----BEGIN RSA PRIVATE KEY-----\nMIIEoAIBAAKCAQEAvR7l72CT7UBP6P+02Iut8gKKbKyekz/pQxnckPp1VafuaIwC\nMvYfP4ffVJTcY5IhU9mISNxZf6YDQ0TuD1aOrZYG9wsIgGY0nXhOUZxe/Q5I+V7D\nOI/hSzKF7W0cNCvaJPUSo8+soCLNSQ5mjnV3sRZ6uJwGFN30i1GulqHHKkx3vGxb\niaAL9YG58dPSbPGHFTA/epqUyd1fzCuWHyL9dHW7aw4RroNyEtVdiftAQfaK20I2\nueeDfuEtCPaxQYFQqbz5kKnXQx3fwHRpC7/84xHxsrY576evGxHw4p5EJD37scNN\ncneTG3Ly79/VVSAlrSm6ltutx0+S70scCqK0ewIDAQABAoH/MjwC15LPuDVdBIbn\ngp2XlrEWE8fGV1ainzA/ZkMg55+ztBF8hAzcQAPXTqA76jbmo18k1DWzkDSIqVWl\n5m0XeQRg1T4ZBAIh97H9G7BtispAl/yT3nJZZaAF8wsIctMzHp36VYjUUbTs0nsA\nwtZw9JkEAAVxmBlc26TWuyw9uv4fYXuR+uOsWH8jTTVPvxM9FaCCdK+dOMnswm7Y\nlOAlJj5dANkB2KPwIeE461ThyMo9GHEjpsvciMhKLuBoTSucNkhdgapAmYTSI+/1\nf1cA/KEdCMs9ANr1HFujeS01+N1Xrw/yW6EazaDN1oFHCVORtlB295Eac0Wq6y/P\npf1BAoGBAPIw4HQWsolU3f4FdIvc2POAcSJDRgt++I9Qt/QXq1SJ2dGKIveFiJgo\nZjCfHQFVZ8xl64cLzQ1WagZA1JBbbk9g5RxHDxRv7q+Kn3ogugDo9GUoQvpuuAU6\nXHoR/mLinDorJUnttL3U49xTMfrrut4qkUg+daBVptPtylpio6EDAoGBAMfnYq08\nfd/cPEQ2XPeswgtzXsKNLqA6UXBM7ZauKaFLByjy8peMMF6JPOYlBKQif5k+Egmu\nWIe8oTm8Nn5Ymt32bEd+MkHUC7kFzQeiXnM3u0oKzJMXLAvjSTs296g50YM5zJTC\nl64ACQmQOLZ9tdKorl52ZcmdbBEcZ2uwRvkpAoGAKhs5SrWPgLTSi5FjO9W/mkYg\nZTaQ/PqsOC5ubO+Yh/AXgIiln6cFon6Tlax0HIE+tJibpDT3B3SYplGrIxXiTcao\nzovEIWd8deSB6Xe7HuFhbBzd2DBbqf0FiuuJ8KM5ShuqNfovzDkxDGMic198c5eu\n/oJtbNy3Tm0vGxu/GwUCgYAgmRPXShkAq0pMmUzZups+AMdAFIO47ymelXzc6HOz\ncKevPsbefabZk6mRohG6rkF+fMe2Om8HW3QzFQUR32MJtQh9NA//+hMbTd3cU9bx\nFPJ+pXostkehfKPReyoxjZQjwQYicAUKA8l1fMYyxBclTgp5Lvd0RC5+L9KRlgJM\n2QKBgGVIWRNVpGg38dDqdq/4ue1BoTFhqoMGi6WQm3xa+NH+lyJGacdUhGRz8PxN\nhVKpIj8ljg2Rq/CA9qSgL/Z9rhn8QUMWULuAroCp0S2pMBtZ2RB+Mg2FdVFR9/Ft\nfG7co6mKUGkFPtr48EMfeKY88BRsp3yGOsROGdDsCHItjOVH\n-----END RSA PRIVATE KEY-----\n" k.public_key # => "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvR7l72CT7UBP6P+02Iut\n8gKKbKyekz/pQxnckPp1VafuaIwCMvYfP4ffVJTcY5IhU9mISNxZf6YDQ0TuD1aO\nrZYG9wsIgGY0nXhOUZxe/Q5I+V7DOI/hSzKF7W0cNCvaJPUSo8+soCLNSQ5mjnV3\nsRZ6uJwGFN30i1GulqHHKkx3vGxbiaAL9YG58dPSbPGHFTA/epqUyd1fzCuWHyL9\ndHW7aw4RroNyEtVdiftAQfaK20I2ueeDfuEtCPaxQYFQqbz5kKnXQx3fwHRpC7/8\n4xHxsrY576evGxHw4p5EJD37scNNcneTG3Ly79/VVSAlrSm6ltutx0+S70scCqK0\newIDAQAB\n-----END PUBLIC KEY-----\n" ``` Fetch the SSH public key as a string. ```ruby k.ssh_public_key # => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" ``` #### Encryption If a passphrase is set when a key is generated or by setting the `passphrase` accessor, you can fetch the encrypted version of the private key. ```ruby k.passphrase = "foo" # => "foo" k.encrypted_private_key # => "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,748B766CFB185C3BD1D7E4D31113EBDA\n\ntWbfOuAjBlSZdq3kdJTLZJ7prjNWOKuGpeesNfVZDziIaZNCUakvgnUFdX3IZZnj\nEYITfjZ1TEUY3EkemL/57txiP3A4iOMDK2JGg8lp3G45x6c9XucJ2YxgvMye/ugP\n014MzLvBNunWq8TolkFj4gbc+WCqsyFqGdpRsf/hx7PcLDd2nvS5zxjBAPno87KN\nYgEnZYrpyl01ePucwFVWlrlGJdc0+F+0Ms5gpjMds56YL3Rwv9BlWzapVtrqN29r\nZg0otylPAyuGJOQ8srDOa+pbSySXvcdoKfR6xQ9fIB0tUfGgrH3c5O0/rEW7FSiO\n6ng4ntXXOKKkQfCezXQVvqMjKtKAbcKaPYAvrB2Gp2VIPUN5tN52nKuWvQWPA0P/\nm/uKiFkvzDWj8xMEOdzDAG9/7ysX+T5angvhfT23+NEdGIlPZLDRHI3f+2Itn99f\nvVoDYUXiyd5h7VwOTn6scebbvyPY8DiWpB/5iaU8WBPr7TVTl9n2z+Gmy4eg3wS0\nTU4hGlKv7MiITO2+dOCZTVrKn9/gTgmtyiLucb4huBH88Nsj4zWnTrVjMMBWsTUD\nkzvo9081zgDKKeawcbZYdI1Tc4epV7SMTHpx1ztzIlPdQ6kRaWomwMSarQeSlhJe\naFx67cde6M3Kc3LOgE0VT+3NvVLnkDwkytwnQKLd6oT3d1kFxWXjMwqiPbSzz3bf\nkOhG01gsJDXIzAgDlOlhE+Qlsd3yc734UIH98rTFMVB00HS36WLuz3hh+Ew4rsrf\nDIuRIdxL/4GVdQ8J5WpSoN0tF5iQD1wpEMU2vUjYjj9TZkhpOpnK3UVvbKd4WPsV\n956XJT7ZDvX4+pvHc5GJq/UX5h42kycY0hftUoLapXt5Nhb/fL8mUT8Eix184uiO\n5mA3fgRP3oGJ28N653X/+kL2YhXCeTd2VjkVhKruuoex96Igyt8W7wW5y7MOPezf\nwfo8IzidcJcDR1W4OEOXr+oDlCE1CLGCzmenR+AUIisqz45yb5G076l8PQkI3NWC\nBhT1YbTds4QzrndIDZgMm65ZCaklm+FVHWV61rXd9rlugcq+flQuXAE/EnFtySMc\n3lztrzXulLXzgLrYG355JbQFddwehO7LdxKZA9LHC9/odcoVI9RBj1CzshYtlftR\nn56nxPTIxRTVjQdgCZ6VcjZhwv1I904NtGm4SZupiShXsbHzAfaeJ54GMq4PRlgN\nmH7JrI9/puBb1dLD0XNgPtmYIo18v9e7g9o+un/wDtxCTxhQtD0npPo1IuW4cW7q\n07lZPwGkN2FD2PNTBGXeQ6/EXTHxlyFn62GSr+DmXu0O8MJS827Vd4b8QmKzRTxf\nFEmtVhiD15KlrQxwajmhqfY6KHRxbBuG/w7ioRr2Vl0G9NmKwmJkQO8dM+mJ8rVE\nsWvm8xVm1bowahzDVPnyFUUjuGNi6jFElkv8zvlQUoTcjSZHPrQSHuX742f5Spph\nLLCHdGZ2Ry8UGPlqKtvd6V/z25NsBgbuit+hNkBsdIztH7MVGAhKSMgk1FgXmKzV\nmZnPigq5WAHtIvojzI9NfZxU2Avif0yymXNtOnipw0sCJ0notN8NuGdQEmyxThqW\n-----END RSA PRIVATE KEY-----\n" ``` #### Comments Keys can optionally have a comment that is shown as part of the public SSH key. Get or set the key's comment with the `comment` accessor. ```ruby k.comment # => nil k.comment = "me@me.com" # => "me@me.com" k.ssh_public_key # => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7 me@me.com" k.ssh2_public_key # => "---- BEGIN SSH2 PUBLIC KEY ----\nComment: me@me.com\nAAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+n\nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5\nXsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoA\nv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I\n9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVIC\nWtKbqW263HT5LvSxwKorR7\n---- END SSH2 PUBLIC KEY ----" ``` #### Bit length Determine the strength of the key in bits as an integer. ```ruby k.bits # => 2048 ``` #### Fingerprints It is often helpful to use a fingerprint to visually or programmatically check if one key matches another. Fetch an MD5, SHA1, or SHA256 fingerprint of the SSH public key. ```ruby k.md5_fingerprint # => "04:1b:d4:18:df:87:60:94:8c:83:8a:7b:5a:35:59:3d" k.sha1_fingerprint # => "e5:c2:43:9e:e4:0c:0c:47:82:7a:3b:e9:61:13:bd:9c:43:eb:4c:b7" k.sha256_fingerprint # => "x1GEnx1SRY/QwxjMAoyO6mhQlaBedDHtYLEmfeUXy3o=" ``` #### Public Key Directives Add optional directives prefixed to the public key that will be enforced when a key is authenticated. Accepts a string or an array of strings. ```ruby k.directives = "no-pty" # => ["no-pty"] k.directives = [ "no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding", "no-pty", "command='/home/user/bin/authprogs'" ] # => ["no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding", "no-pty", "command='/home/user/bin/authprogs'"] k.ssh_public_key # => "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command='/home/user/bin/authprogs' ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" ``` #### Randomart Generate [OpenSSH compatible](http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c) ASCII art fingerprints. ```ruby puts k.randomart +--[ RSA 2048]----+ |o+ o.. | |..+.o | | ooo | |.++. o | |+o+ + S | |.. + o . | | . + . | | . . | | Eo. | +-----------------+ ``` #### Original OpenSSL key object Return the original [OpenSSL::PKey::RSA](http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/PKey/RSA.html) or [OpenSSL::PKey::DSA](http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/PKey/DSA.html) object. ```ruby k.key_object # => -----BEGIN RSA PRIVATE KEY-----\nMIIEowI... ``` ### Existing SSH public keys #### Validation Determine if a given SSH public key is valid. Very useful to test user input of public keys to make sure they accurately copy/pasted the key. Just pass the SSH public key as a string. Returns false if the key is invalid. ```ruby SSHKey.valid_ssh_public_key? "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => true ``` #### Bit length Determine the strength of the key in bits as an integer. Returns `SSHKey::PublicKeyError` if bits cannot be determined. ```ruby SSHKey.ssh_public_key_bits "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => 2048 ``` #### Fingerprints Fetch an MD5, SHA1, or SHA256 fingerprint of the SSH public key. Returns `SSHKey::PublicKeyError` if a fingerprint cannot be determined. ```ruby SSHKey.fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => "04:1b:d4:18:df:87:60:94:8c:83:8a:7b:5a:35:59:3d" SSHKey.sha1_fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => "e5:c2:43:9e:e4:0c:0c:47:82:7a:3b:e9:61:13:bd:9c:43:eb:4c:b7" SSHKey.sha256_fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => "x1GEnx1SRY/QwxjMAoyO6mhQlaBedDHtYLEmfeUXy3o=" ``` #### Convert to SSH2 Public Key Convert an existing SSH Public Key into an SSH2 Public key. Returns `SSHKey::PublicKeyError` if a valid key cannot be generated. ```ruby SSHKey.ssh_public_key_to_ssh2_public_key "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7 me@me.com" # => "---- BEGIN SSH2 PUBLIC KEY ----\nComment: me@me.com\nAAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+n\nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5\nXsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoA\nv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I\n9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVIC\nWtKbqW263HT5LvSxwKorR7\n---- END SSH2 PUBLIC KEY ----" ``` ## Copyright Copyright (c) 2011-2019 James Miller sshkey-2.0.0/sshkey.gemspec0000644000004100000410000000155013445575002015730 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "sshkey/version" Gem::Specification.new do |s| s.name = "sshkey" s.version = SSHKey::VERSION s.platform = Gem::Platform::RUBY s.authors = ["James Miller"] s.email = ["bensie@gmail.com"] s.homepage = "https://github.com/bensie/sshkey" s.summary = %q{SSH private/public key generator in Ruby} s.description = %q{Generate private/public SSH keypairs using pure Ruby} s.licenses = ["MIT"] s.rubyforge_project = "sshkey" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_development_dependency("rake") s.add_development_dependency("test-unit") end sshkey-2.0.0/.gitignore0000644000004100000410000000004113445575002015037 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* sshkey-2.0.0/LICENSE0000644000004100000410000000204513445575002014062 0ustar www-datawww-dataCopyright (c) 2011-2016 James Miller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sshkey-2.0.0/Rakefile0000644000004100000410000000040713445575002014522 0ustar www-datawww-datarequire "rake" require "rake/testtask" desc "Default: run unit tests." task :default => :test desc "Test the sshkey gem" Rake::TestTask.new(:test) do |t| t.libs << "lib" t.libs << "test" t.test_files = FileList['test/*_test.rb'] t.verbose = true end sshkey-2.0.0/lib/0000755000004100000410000000000013445575002013622 5ustar www-datawww-datasshkey-2.0.0/lib/sshkey.rb0000644000004100000410000003272413445575002015465 0ustar www-datawww-datarequire 'openssl' require 'base64' require 'digest/md5' require 'digest/sha1' require 'digest/sha2' class SSHKey SSH_TYPES = { "ssh-rsa" => "rsa", "ssh-dss" => "dsa", "ssh-ed25519" => "ed25519", "ecdsa-sha2-nistp256" => "ecdsa", "ecdsa-sha2-nistp384" => "ecdsa", "ecdsa-sha2-nistp521" => "ecdsa", } SSHFP_TYPES = { "rsa" => 1, "dsa" => 2, "ecdsa" => 3, "ed25519" => 4, } SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]} SSH2_LINE_LENGTH = 70 # +1 (for line wrap '/' character) must be <= 72 class << self # Generate a new keypair and return an SSHKey object # # The default behavior when providing no options will generate a 2048-bit RSA # keypair. # # ==== Parameters # * options<~Hash>: # * :type<~String> - "rsa" or "dsa", "rsa" by default # * :bits<~Integer> - Bit length # * :comment<~String> - Comment to use for the public key, defaults to "" # * :passphrase<~String> - Encrypt the key with this passphrase # def generate(options = {}) type = options[:type] || "rsa" # JRuby modulus size must range from 512 to 1024 default_bits = type == "rsa" ? 2048 : 1024 bits = options[:bits] || default_bits cipher = OpenSSL::Cipher.new("AES-128-CBC") if options[:passphrase] case type.downcase when "rsa" then new(OpenSSL::PKey::RSA.generate(bits).to_pem(cipher, options[:passphrase]), options) when "dsa" then new(OpenSSL::PKey::DSA.generate(bits).to_pem(cipher, options[:passphrase]), options) else raise "Unknown key type: #{type}" end end # Validate an existing SSH public key # # Returns true or false depending on the validity of the public key provided # # ==== Parameters # * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...." # def valid_ssh_public_key?(ssh_public_key) ssh_type, encoded_key = parse_ssh_public_key(ssh_public_key) sections = unpacked_byte_array(ssh_type, encoded_key) case ssh_type when "ssh-rsa", "ssh-dss" sections.size == SSH_CONVERSION[SSH_TYPES[ssh_type]].size when "ssh-ed25519" sections.size == 1 # https://tools.ietf.org/id/draft-bjh21-ssh-ed25519-00.html#rfc.section.4 when "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521" sections.size == 2 # https://tools.ietf.org/html/rfc5656#section-3.1 else false end rescue false end # Bits # # Returns ssh public key bits or false depending on the validity of the public key provided # # ==== Parameters # * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...." # def ssh_public_key_bits(ssh_public_key) unpacked_byte_array( *parse_ssh_public_key(ssh_public_key) ).last.num_bytes * 8 end # Fingerprints # # Accepts either a public or private key # # MD5 fingerprint for the given SSH key def md5_fingerprint(key) if key.match(/PRIVATE/) new(key).md5_fingerprint else Digest::MD5.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2') end end alias_method :fingerprint, :md5_fingerprint # SHA1 fingerprint for the given SSH key def sha1_fingerprint(key) if key.match(/PRIVATE/) new(key).sha1_fingerprint else Digest::SHA1.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2') end end # SHA256 fingerprint for the given SSH key def sha256_fingerprint(key) if key.match(/PRIVATE/) new(key).sha256_fingerprint else Base64.encode64(Digest::SHA256.digest(decoded_key(key))).gsub("\n", "") end end # SSHFP records for the given SSH key def sshfp(hostname, key) if key.match(/PRIVATE/) new(key).sshfp hostname else type, encoded_key = parse_ssh_public_key(key) format_sshfp_record(hostname, SSH_TYPES[type], Base64.decode64(encoded_key)) end end # Convert an existing SSH public key to SSH2 (RFC4716) public key # # ==== Parameters # * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...." # * headers<~Hash> - The Key will be used as the header-tag and the value as the header-value # def ssh_public_key_to_ssh2_public_key(ssh_public_key, headers = nil) raise PublicKeyError, "invalid ssh public key" unless SSHKey.valid_ssh_public_key?(ssh_public_key) _source_format, source_key = parse_ssh_public_key(ssh_public_key) # Add a 'Comment' Header Field unless others are explicitly passed in if source_comment = ssh_public_key.split(source_key)[1] headers = {'Comment' => source_comment.strip} if headers.nil? && !source_comment.empty? end header_fields = build_ssh2_headers(headers) ssh2_key = "---- BEGIN SSH2 PUBLIC KEY ----\n" ssh2_key << header_fields unless header_fields.nil? ssh2_key << source_key.scan(/.{1,#{SSH2_LINE_LENGTH}}/).join("\n") ssh2_key << "\n---- END SSH2 PUBLIC KEY ----" end def format_sshfp_record(hostname, type, key) [[Digest::SHA1, 1], [Digest::SHA256, 2]].map { |f, num| fpr = f.hexdigest(key) "#{hostname} IN SSHFP #{SSHFP_TYPES[type]} #{num} #{fpr}" }.join("\n") end private def unpacked_byte_array(ssh_type, encoded_key) prefix = [ssh_type.length].pack("N") + ssh_type decoded = Base64.decode64(encoded_key) # Base64 decoding is too permissive, so we should validate if encoding is correct unless Base64.encode64(decoded).gsub("\n", "") == encoded_key && decoded.slice!(0, prefix.length) == prefix raise PublicKeyError, "validation error" end byte_count = 0 data = [] until decoded.empty? front = decoded.slice!(0,4) size = front.unpack("N").first segment = decoded.slice!(0, size) byte_count += segment.length unless front.length == 4 && segment.length == size raise PublicKeyError, "byte array too short" end data << OpenSSL::BN.new(segment, 2) end if ssh_type == "ssh-ed25519" unless byte_count == 32 raise PublicKeyError, "validation error, ed25519 key length not OK" end end return data end def decoded_key(key) Base64.decode64(parse_ssh_public_key(key).last) end def fingerprint_regex /(.{2})(?=.)/ end def parse_ssh_public_key(public_key) raise PublicKeyError, "newlines are not permitted between key data" if public_key =~ /\n(?!$)/ parsed = public_key.split(" ") parsed.each_with_index do |el, index| return parsed[index..(index+1)] if SSH_TYPES[el] end raise PublicKeyError, "cannot determine key type" end def build_ssh2_headers(headers = {}) return nil if headers.nil? || headers.empty? headers.keys.sort.collect do |header_tag| # header-tag must be us-ascii & <= 64 bytes and header-data must be UTF-8 & <= 1024 bytes raise PublicKeyError, "SSH2 header-tag '#{header_tag}' must be US-ASCII" unless header_tag.each_byte.all? {|b| b < 128 } raise PublicKeyError, "SSH2 header-tag '#{header_tag}' must be <= 64 bytes" unless header_tag.size <= 64 raise PublicKeyError, "SSH2 header-value for '#{header_tag}' must be <= 1024 bytes" unless headers[header_tag].size <= 1024 header_field = "#{header_tag}: #{headers[header_tag]}" header_field.scan(/.{1,#{SSH2_LINE_LENGTH}}/).join("\\\n") end.join("\n") << "\n" end end attr_reader :key_object, :type attr_accessor :passphrase, :comment # Create a new SSHKey object # # ==== Parameters # * private_key - Existing RSA or DSA private key # * options<~Hash> # * :comment<~String> - Comment to use for the public key, defaults to "" # * :passphrase<~String> - If the key is encrypted, supply the passphrase # * :directives<~Array> - Options prefixed to the public key # def initialize(private_key, options = {}) @passphrase = options[:passphrase] @comment = options[:comment] || "" self.directives = options[:directives] || [] begin @key_object = OpenSSL::PKey::RSA.new(private_key, passphrase) @type = "rsa" rescue @key_object = OpenSSL::PKey::DSA.new(private_key, passphrase) @type = "dsa" end end # Fetch the RSA/DSA private key # # rsa_private_key and dsa_private_key are aliased for backward compatibility def private_key key_object.to_pem end alias_method :rsa_private_key, :private_key alias_method :dsa_private_key, :private_key # Fetch the encrypted RSA/DSA private key using the passphrase provided # # If no passphrase is set, returns the unencrypted private key def encrypted_private_key return private_key unless passphrase key_object.to_pem(OpenSSL::Cipher.new("AES-128-CBC"), passphrase) end # Fetch the RSA/DSA public key # # rsa_public_key and dsa_public_key are aliased for backward compatibility def public_key key_object.public_key.to_pem end alias_method :rsa_public_key, :public_key alias_method :dsa_public_key, :public_key # SSH public key def ssh_public_key [directives.join(",").strip, SSH_TYPES.invert[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip end # SSH2 public key (RFC4716) # # ==== Parameters # * headers<~Hash> - Keys will be used as header-tags and values as header-values. # # ==== Examples # {'Comment' => '2048-bit RSA created by user@example'} # {'x-private-use-tag' => 'Private Use Value'} # def ssh2_public_key(headers = nil) self.class.ssh_public_key_to_ssh2_public_key(ssh_public_key, headers) end # Fingerprints # # MD5 fingerprint for the given SSH public key def md5_fingerprint Digest::MD5.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') end alias_method :fingerprint, :md5_fingerprint # SHA1 fingerprint for the given SSH public key def sha1_fingerprint Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') end # SHA256 fingerprint for the given SSH public key def sha256_fingerprint Base64.encode64(Digest::SHA256.digest(ssh_public_key_conversion)).gsub("\n", "") end # Determine the length (bits) of the key as an integer def bits self.class.ssh_public_key_bits(ssh_public_key) end # Randomart # # Generate OpenSSH compatible ASCII art fingerprints # See http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c (key_fingerprint_randomart function) # # Example: # +--[ RSA 2048]----+ # |o+ o.. | # |..+.o | # | ooo | # |.++. o | # |+o+ + S | # |.. + o . | # | . + . | # | . . | # | Eo. | # +-----------------+ def randomart fieldsize_x = 17 fieldsize_y = 9 x = fieldsize_x / 2 y = fieldsize_y / 2 raw_digest = Digest::MD5.digest(ssh_public_key_conversion) num_bytes = raw_digest.bytesize field = Array.new(fieldsize_x) { Array.new(fieldsize_y) {0} } raw_digest.bytes.each do |byte| 4.times do x += (byte & 0x1 != 0) ? 1 : -1 y += (byte & 0x2 != 0) ? 1 : -1 x = [[x, 0].max, fieldsize_x - 1].min y = [[y, 0].max, fieldsize_y - 1].min field[x][y] += 1 if (field[x][y] < num_bytes - 2) byte >>= 2 end end field[fieldsize_x / 2][fieldsize_y / 2] = num_bytes - 1 field[x][y] = num_bytes augmentation_string = " .o+=*BOX@%&#/^SE" output = "+--#{sprintf("[%4s %4u]", type.upcase, bits)}----+\n" fieldsize_y.times do |y| output << "|" fieldsize_x.times do |x| output << augmentation_string[[field[x][y], num_bytes].min] end output << "|" output << "\n" end output << "+#{"-" * fieldsize_x}+" output end def sshfp(hostname) self.class.format_sshfp_record(hostname, @type, ssh_public_key_conversion) end def directives=(directives) @directives = Array[directives].flatten.compact end attr_reader :directives private # SSH Public Key Conversion # # All data type encoding is defined in the section #5 of RFC #4251. # String and mpint (multiple precision integer) types are encoded this way: # 4-bytes word: data length (unsigned big-endian 32 bits integer) # n bytes: binary representation of the data # For instance, the "ssh-rsa" string is encoded as the following byte array # [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'] def ssh_public_key_conversion typestr = SSH_TYPES.invert[type] methods = SSH_CONVERSION[type] pubkey = key_object.public_key methods.inject([7].pack("N") + typestr) do |pubkeystr, m| # Given pubkey.class == OpenSSL::BN, pubkey.to_s(0) returns an MPI # formatted string (length prefixed bytes). This is not supported by # JRuby, so we still have to deal with length and data separately. val = pubkey.send(m) # Get byte-representation of absolute value of val data = val.to_s(2) first_byte = data[0,1].unpack("c").first if val < 0 # For negative values, highest bit must be set data[0] = [0x80 & first_byte].pack("c") elsif first_byte < 0 # For positive values where highest bit would be set, prefix with \0 data = "\0" + data end pubkeystr + [data.length].pack("N") + data end end class PublicKeyError < StandardError; end end sshkey-2.0.0/lib/sshkey/0000755000004100000410000000000013445575002015130 5ustar www-datawww-datasshkey-2.0.0/lib/sshkey/version.rb0000644000004100000410000000004513445575002017141 0ustar www-datawww-dataclass SSHKey VERSION = "2.0.0" end sshkey-2.0.0/Gemfile0000644000004100000410000000013213445575002014343 0ustar www-datawww-datasource "https://rubygems.org" gem "jruby-openssl", ">= 0.8.2", platform: :jruby gemspec