orthrus-ssh 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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-----