orthrus-ssh 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,88 @@
1
+ require 'orthrus/ssh/key'
2
+
3
+ require 'orthrus/ssh/buffer'
4
+
5
+ module Orthrus::SSH
6
+ module DSA
7
+ def initialize(k)
8
+ super k, OpenSSL::Digest::DSS1
9
+ end
10
+
11
+ def public_identity(base64=true)
12
+ b = Buffer.from :string, "ssh-dss",
13
+ :bignum, @key.p,
14
+ :bignum, @key.q,
15
+ :bignum, @key.g,
16
+ :bignum, @key.pub_key
17
+
18
+ d = b.to_s
19
+
20
+ return d unless base64
21
+
22
+ [d].pack("m").gsub("\n","")
23
+ end
24
+
25
+ def type
26
+ "ssh-dss"
27
+ end
28
+ end
29
+
30
+ class DSAPrivateKey < PrivateKey
31
+ include DSA
32
+
33
+ def sign(data)
34
+ sig = super data
35
+
36
+ a1sig = OpenSSL::ASN1.decode sig
37
+
38
+ sig_r = a1sig.value[0].value.to_s(2)
39
+ sig_s = a1sig.value[1].value.to_s(2)
40
+
41
+ if sig_r.length > 20 || sig_s.length > 20
42
+ raise OpenSSL::PKey::DSAError, "bad sig size"
43
+ end
44
+
45
+ sig_r = "\0" * ( 20 - sig_r.length ) + sig_r if sig_r.length < 20
46
+ sig_s = "\0" * ( 20 - sig_s.length ) + sig_s if sig_s.length < 20
47
+ return sig_r + sig_s
48
+ end
49
+ end
50
+
51
+ class DSAPublicKey < PublicKey
52
+ def self.parse(data)
53
+ raw = data.unpack("m").first
54
+
55
+ b = Buffer.new raw
56
+
57
+ type = b.read_string
58
+ unless type == "ssh-dss"
59
+ raise "Unvalid key data"
60
+ end
61
+
62
+ k = OpenSSL::PKey::DSA.new
63
+ k.p = b.read_bignum
64
+ k.q = b.read_bignum
65
+ k.g = b.read_bignum
66
+ k.pub_key = b.read_bignum
67
+
68
+ new k
69
+ end
70
+
71
+ include DSA
72
+
73
+ # Adapted from net-ssh
74
+ # Verifies the given signature matches the given data.
75
+ def verify(sig, data)
76
+ sig_r = sig[0,20].unpack("H*")[0].to_i(16)
77
+ sig_s = sig[20,20].unpack("H*")[0].to_i(16)
78
+
79
+ a1sig = OpenSSL::ASN1::Sequence([
80
+ OpenSSL::ASN1::Integer(sig_r),
81
+ OpenSSL::ASN1::Integer(sig_s)
82
+ ])
83
+
84
+ super a1sig.to_der, data
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,83 @@
1
+ require 'orthrus/ssh'
2
+ require 'orthrus/ssh/agent'
3
+
4
+ require 'uri'
5
+
6
+ require 'net/http'
7
+
8
+ module Orthrus::SSH
9
+ class HTTPAgent
10
+ def initialize(url)
11
+ @url = url
12
+ @keys = []
13
+ @access_token = nil
14
+ end
15
+
16
+ attr_reader :access_token
17
+
18
+ def add_key(key)
19
+ @keys << Orthrus::SSH.load_private(key)
20
+ end
21
+
22
+ def check(user, k)
23
+ id = Rack::Utils.escape(k.public_identity)
24
+ user = Rack::Utils.escape(user)
25
+
26
+ url = @url + "?state=find&user=#{user}&id=#{id}"
27
+ response = Net::HTTP.get_response url
28
+ params = Rack::Utils.parse_query response.body
29
+
30
+ return nil unless params["code"] == "check"
31
+
32
+ [params['session_id'], params['nonce']]
33
+ end
34
+
35
+ def negotiate(k, sid, sig)
36
+ sig = Rack::Utils.escape sig
37
+
38
+ url = @url + "?state=signed&sig=#{sig}&session_id=#{sid}"
39
+
40
+ response = Net::HTTP.get_response url
41
+ params = Rack::Utils.parse_query response.body
42
+
43
+ if params['code'] == "verified"
44
+ return params['access_token']
45
+ end
46
+
47
+ return nil
48
+ end
49
+
50
+ def start(user)
51
+ @keys.each do |k|
52
+ sid, data = check(user, k)
53
+ next unless sid
54
+
55
+ sig = k.hexsign(data)
56
+
57
+ token = negotiate(k, sid, sig)
58
+ if token
59
+ @access_token = token
60
+ return
61
+ end
62
+ end
63
+
64
+ if Agent.available?
65
+ agent = Agent.connect
66
+ agent.identities.each do |k|
67
+ sid, data = check(user, k)
68
+ next unless sid
69
+
70
+ type, sig = agent.hexsign k, data
71
+
72
+ token = negotiate(k, sid, sig)
73
+ if token
74
+ @access_token = token
75
+ return
76
+ end
77
+ end
78
+ end
79
+
80
+ raise "Unable to find key to authenticate with"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,50 @@
1
+ module Orthrus::SSH
2
+ class Key
3
+ def initialize(k, digest)
4
+ @key = k
5
+ @digest = digest
6
+ @comment = nil
7
+ end
8
+
9
+ attr_reader :key
10
+ attr_accessor :comment
11
+
12
+ def rsa?
13
+ @key.kind_of? OpenSSL::PKey::RSA
14
+ end
15
+
16
+ def dsa?
17
+ @key.kind_of? OpenSSL::PKey::DSA
18
+ end
19
+
20
+ def fingerprint
21
+ blob = public_identity(false)
22
+ OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":")
23
+ end
24
+
25
+ def inspect
26
+ "#<#{self.class} #{fingerprint}>"
27
+ end
28
+ end
29
+
30
+ class PrivateKey < Key
31
+ def sign(data)
32
+ @key.sign @digest.new, data
33
+ end
34
+
35
+ def hexsign(data)
36
+ [sign(data)].pack("m").gsub("\n","")
37
+ end
38
+ end
39
+
40
+ class PublicKey < Key
41
+ def verify(sign, data)
42
+ @key.verify @digest.new, sign, data
43
+ end
44
+
45
+ def hexverify(sign, data)
46
+ verify sign.unpack("m").first, data
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,30 @@
1
+ require 'orthrus/ssh'
2
+
3
+ module Orthrus::SSH
4
+ class PublicKeySet
5
+ def self.load_file(path)
6
+ keys = {}
7
+
8
+ File.readlines(path).each do |x|
9
+ type, dig, comment = x.split(" ", 3)
10
+
11
+ keys[dig] = Orthrus::SSH.parse_public x
12
+ end
13
+
14
+ new keys
15
+ end
16
+
17
+ def initialize(keys)
18
+ @keys = keys
19
+ end
20
+
21
+ def find(dig)
22
+ @keys[dig]
23
+ end
24
+
25
+ def num_keys
26
+ @keys.size
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,62 @@
1
+ require 'orthrus/ssh'
2
+ require 'rack'
3
+
4
+ module Orthrus::SSH
5
+ class RackApp
6
+ def initialize(sessions)
7
+ @sessions = sessions
8
+ end
9
+
10
+ attr_reader :sessions
11
+
12
+ def call(env)
13
+ req = Rack::Request.new(env)
14
+
15
+ case req.params['state']
16
+ when 'find'
17
+ find req
18
+ when 'signed'
19
+ verify req
20
+ else
21
+ [500, {}, ["unknown state"]]
22
+ end
23
+ end
24
+
25
+ def form(body)
26
+ [200,
27
+ { "Content-Type" => "application/x-www-form-urlencoded" },
28
+ [body]
29
+ ]
30
+ end
31
+
32
+ def find(req)
33
+ user = req.params['user']
34
+ id = req.params["id"]
35
+
36
+ unless pub = @sessions.find_key(user, id)
37
+ return form("code=unknown")
38
+ end
39
+
40
+ session, nonce = @sessions.new_session(user, pub)
41
+
42
+ nonce = Rack::Utils.escape Utils.sha1_hash(nonce)
43
+
44
+ form "code=check&session_id=#{session}&nonce=#{nonce}"
45
+ end
46
+
47
+ def verify(req)
48
+ id = req.params["session_id"].to_i
49
+ nonce, pub = @sessions.find_session(id)
50
+
51
+ nonce = Utils.sha1_hash(nonce)
52
+
53
+ sig = req.params['sig']
54
+
55
+ if pub.hexverify(sig, nonce)
56
+ form "code=verified&access_token=1"
57
+ else
58
+ form "code=fail"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,51 @@
1
+ require 'orthrus/ssh/key'
2
+
3
+ module Orthrus::SSH
4
+
5
+ module RSA
6
+ def initialize(k)
7
+ super k, OpenSSL::Digest::SHA1
8
+ end
9
+
10
+ def public_identity(base64=true)
11
+ b = Buffer.from :string, "ssh-rsa",
12
+ :bignum, @key.e,
13
+ :bignum, @key.n
14
+
15
+ d = b.to_s
16
+
17
+ return d unless base64
18
+
19
+ [d].pack("m").gsub("\n","")
20
+ end
21
+
22
+ def type
23
+ "ssh-rsa"
24
+ end
25
+ end
26
+
27
+ class RSAPrivateKey < PrivateKey
28
+ include RSA
29
+ end
30
+
31
+ class RSAPublicKey < PublicKey
32
+ def self.parse(data)
33
+ raw = data.unpack("m").first
34
+
35
+ b = Buffer.new raw
36
+
37
+ type = b.read_string
38
+ unless type == "ssh-rsa"
39
+ raise "Unvalid key data"
40
+ end
41
+
42
+ k = OpenSSL::PKey::RSA.new
43
+ k.e = b.read_bignum
44
+ k.n = b.read_bignum
45
+
46
+ new k
47
+ end
48
+
49
+ include RSA
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ module Orthrus::SSH
2
+ module Utils
3
+ def self.write_bignum(bn)
4
+ # Cribbed from net-ssh
5
+ if bn.zero?
6
+ return [0].pack("N")
7
+ else
8
+ buf = bn.to_s(2)
9
+ if buf[0][7] == 1
10
+ return [buf.length+1, 0, buf].pack("NCA*")
11
+ else
12
+ return [buf.length, buf].pack("NA*")
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.write_string(str)
18
+ [str.size].pack("N") + str
19
+ end
20
+
21
+ def self.sha1_hash(data)
22
+ OpenSSL::Digest::SHA1.hexdigest data
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,2 @@
1
+ ssh-dss AAAAB3NzaC1kc3MAAACBAK8JRdWMWVcnFxRn8Pwhu6a7W8HQGPd93GY4JbKTTZjDrJEjKMnNUaFVZpu13MQwow8kaolqS+6tUaNDTFY+bymYcVWdQKWFAC/8MrOeJZq1GugNBxk9RC/VZBvW4nDTPgXhRftSxB7KBk0Tvk70fbwffaVGW7A+GBJKXYHdA/PdAAAAFQCjif6zl1zEITOq8us9WfvvBiQCkQAAAIEAqwFVbyhPWEVp5Ierq1syOJv4Hjqnbo/69mU/mFpnhIhreJ+uNIaFlEdwJY+WaQ7mUpWKCjhRMcDICQ+Lgq3zjaZhSe6F+SBHWI2ISDgRs73W2z42oECRrQoNJ9WZSF+6l+cHEHsD6Qv+B4cYtr7JJj7IGtMZzDO+zhQaUWTNeUoAAACADRsQHiTTr+50iRiiC26dq+W8GxksI4A5nESv3LpbqMI/SNzglK5OlLkKw403Ox7O1Hb+uZmmOZQmk1OTPjS3QPT+LC/lkUANpP7T7Bkh9RwUUVJF3qvhHDH94BDO3clZKdV558gSHi+T9P31h1gcw5rAVh+yK2ZyZGV/jP0r0z0= evan@aero.local
2
+ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDgYlt6gUVZUZE4xgW2TRvi8HjVgrWZ5e6Av76/H3PzvZpsgHSZyDiU1rgVsgwfb1NmJiwflNpILLprSmp3RRqdOEKzEPgxdscQY1sJtTQcmdlWeIvN6KvmImPwV9krqtN8vji7Zqr0N3mcDmdK1MbQ56Cjx5l6/y9rYGLmIZvoLOLDVe3olOHjpapHQLHrQL3c/2Il5y+9aXR1c/gKFeEwwhRL6hcSIufBnanXqVGa5QNrfzw4si8oAIWDNfXDGRdFkxrnGxHOguj8hFeYXNtz6OHu2UPbvum9sUNHXdDHBYSTPqUJfdLvo49ZMqShcEgNrlBe8rx7ooPdDas40mH evan@aero.local
data/test/data/id_dsa ADDED
@@ -0,0 +1,12 @@
1
+ -----BEGIN DSA PRIVATE KEY-----
2
+ MIIBuwIBAAKBgQCvCUXVjFlXJxcUZ/D8Ibumu1vB0Bj3fdxmOCWyk02Yw6yRIyjJ
3
+ zVGhVWabtdzEMKMPJGqJakvurVGjQ0xWPm8pmHFVnUClhQAv/DKzniWatRroDQcZ
4
+ PUQv1WQb1uJw0z4F4UX7UsQeygZNE75O9H28H32lRluwPhgSSl2B3QPz3QIVAKOJ
5
+ /rOXXMQhM6ry6z1Z++8GJAKRAoGBAKsBVW8oT1hFaeSHq6tbMjib+B46p26P+vZl
6
+ P5haZ4SIa3ifrjSGhZRHcCWPlmkO5lKVigo4UTHAyAkPi4Kt842mYUnuhfkgR1iN
7
+ iEg4EbO91ts+NqBAka0KDSfVmUhfupfnBxB7A+kL/geHGLa+ySY+yBrTGcwzvs4U
8
+ GlFkzXlKAoGADRsQHiTTr+50iRiiC26dq+W8GxksI4A5nESv3LpbqMI/SNzglK5O
9
+ lLkKw403Ox7O1Hb+uZmmOZQmk1OTPjS3QPT+LC/lkUANpP7T7Bkh9RwUUVJF3qvh
10
+ HDH94BDO3clZKdV558gSHi+T9P31h1gcw5rAVh+yK2ZyZGV/jP0r0z0CFEY8QELo
11
+ OZ+AwfX3cTZZTQMaNgAm
12
+ -----END DSA PRIVATE KEY-----
@@ -0,0 +1 @@
1
+ ssh-dss AAAAB3NzaC1kc3MAAACBAK8JRdWMWVcnFxRn8Pwhu6a7W8HQGPd93GY4JbKTTZjDrJEjKMnNUaFVZpu13MQwow8kaolqS+6tUaNDTFY+bymYcVWdQKWFAC/8MrOeJZq1GugNBxk9RC/VZBvW4nDTPgXhRftSxB7KBk0Tvk70fbwffaVGW7A+GBJKXYHdA/PdAAAAFQCjif6zl1zEITOq8us9WfvvBiQCkQAAAIEAqwFVbyhPWEVp5Ierq1syOJv4Hjqnbo/69mU/mFpnhIhreJ+uNIaFlEdwJY+WaQ7mUpWKCjhRMcDICQ+Lgq3zjaZhSe6F+SBHWI2ISDgRs73W2z42oECRrQoNJ9WZSF+6l+cHEHsD6Qv+B4cYtr7JJj7IGtMZzDO+zhQaUWTNeUoAAACADRsQHiTTr+50iRiiC26dq+W8GxksI4A5nESv3LpbqMI/SNzglK5OlLkKw403Ox7O1Hb+uZmmOZQmk1OTPjS3QPT+LC/lkUANpP7T7Bkh9RwUUVJF3qvhHDH94BDO3clZKdV558gSHi+T9P31h1gcw5rAVh+yK2ZyZGV/jP0r0z0= evan@aero.local
data/test/data/id_rsa ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEogIBAAKCAQEAw4GJbeoFFWVGROMYFtk0b4vB41YK1meXugL++vx9z872abIB
3
+ 0mcg4lNa4FbIMH29TZiYsH5TaSCy6a0pqd0UanThCsxD4MXbHEGNbCbU0HJnZVni
4
+ Lzeir5iJj8FfZK6rTfL44u2aq9Dd5nA5nStTG0Oego8eZev8va2Bi5iGb6Cziw1X
5
+ t6JTh46WqR0Cx60C93P9iJecvvWl0dXP4ChXhMMIUS+oXEiLnwZ2p16lRmuUDa38
6
+ 8OLIvKACFgzX1wxkXRZMa5xsRzoLo/IRXmFzbc+jh7tlD277pvbFDR13QxwWEkz6
7
+ lCX3S76OPWTKkoXBIDa5QXvK8e6KD3Q2rONJhwIDAQABAoIBAH0yRr+MTRUWdZlH
8
+ k/WNwnZsGQ1r3CTQ0ejcYkx3xFl/P20QAPqr7/L/TgK7kBb9bmxye9UKEIAR4ICj
9
+ 0zpjyN8jWbmAdTdLfLTrhZTsiPuzR2Mv3BhAmH26QN0+B8iB0lFodtlbLuE4L+GR
10
+ nFN5mw6qjqcs31qFdKRCp+KtGeoA8B2OvPLH6tBoLo4UPtB9OgZR9nhi6ZSkUuNP
11
+ xzFU+QgtfBumGOfspwa8Cy+zatwI0Xw4O4FY5bIcp7n+E43pzMLVnkrBxQKouugR
12
+ ldgW2PYm5yX/ffuKdjruSbXRb1Li+Ikx7rK76GfbBSzr33qH1PDqoOdPS4JPuAzj
13
+ wh4YLoECgYEA6q6GJBFui3xFmN+CXVDGeNGXutxxVksMtbHw3P3xwmerfs3+KQD6
14
+ UIb2r22dfu/dILAIkgsx6lsUpvpj0vyyav7mNVRv06qbcGs/zxTquTZXd7NA8+Gm
15
+ ZPv2WKugdsVKHO22D40pFdBM13GPnYo2FGSIsvTC2oZHScJxpnySrKMCgYEA1UP9
16
+ O0CrpYqI7Ktt3iPeX/a+tTCIcmxoWvKwq29dEg6Ck4JmLDcQETQms7tflbNyjX3L
17
+ VgzmBEeRo8cz0YpvSHFmcgy7F0Rnk2ijb85ppJF0rJS0yBJOlHzoYsqIlbFWihl/
18
+ QEy0sBMXQjcvLvIDpiQDzWNdLf7DS9aNKKAyec0CgYA+3rRW80iPG6K1eqM9Boe1
19
+ FEk2qRm/yWlFP79MJMfgkc9SsDK3n2hvrEhn5NC9kdrGiAIzxcYAh5f3x7p4anQN
20
+ z+2yOcWfieQMcN7uRic/qPwzuBTdgQUHpqxvQsNBLkdViqUsc1+fVWdQjD6yMLWe
21
+ LvSkJIgS7MgqTWoO9O6CSwKBgDhB3yMqRB0/Fi+YaTsYKykVZelWDChjAIQ9UO1o
22
+ SxzgRwGyfFFdlRd0smDnJKfQ1n8Ml/7zGBo45upVOg4kfoaVo3iicxgIK2pvR+3O
23
+ fX+z/xsnfyjn62KwMH0fADi8tx9m6nKDyYZJAvGsrP2tSdkh1v7vHz1q3wm6ZzI4
24
+ UBhhAoGAGno46WOio1hGOQoT5GFvyMPBsZAWvFdWSd14gOUzbX4cf+hRsBW4KqZy
25
+ v3jMQ1YgYXf0yvXODKycuFP9uz9HcznwwahT23T0jYtDWkGeMqdoncpenarsfVjF
26
+ PUQgoSpveFwz5UtsP3WhZKCl3/et7FDN1hbXqmWF1i6YbnjIJo8=
27
+ -----END RSA PRIVATE KEY-----