jsign 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/lib/jsign.rb +79 -0
  2. data/lib/jsign.rb~ +96 -0
  3. data/lib/jsign/version.rb +5 -0
  4. metadata +62 -0
data/lib/jsign.rb ADDED
@@ -0,0 +1,79 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tempfile'
4
+ require 'base64'
5
+ require 'openssl'
6
+
7
+
8
+ module Jsign
9
+
10
+ class << self
11
+
12
+ def check_for_keyfile!(key_filename)
13
+ raise "Key #{key_filename.inspect} doesn't exist!" unless
14
+ File.file?(key_filename)
15
+ end
16
+ private :check_for_keyfile!
17
+
18
+
19
+ # Given a json serialisation of a hash, return another json
20
+ # serialisation which includes a key "_jsign" and a value which is
21
+ # the Base64-encoded signature of the *original* json. Well,
22
+ # nearly - any internal whitespace after the last value in the
23
+ # original json is stripped.
24
+ #
25
+ # @param [String] json The json serialisation to sign.
26
+ # @param [String] key_filename The filename of the private key to
27
+ # sign with.
28
+ # @return [String] another json string containing the signature,
29
+ # which can be passed to Jsign.verify.
30
+ def sign(json, key_filename)
31
+ check_for_keyfile!(key_filename)
32
+
33
+ # Remove any whitespace inside the last bracket.
34
+ stripped_json = json.sub(/\s+}\Z/, "}")
35
+ sig = ""
36
+ # Use openssl -sha256 to do the signing. This will probably
37
+ # be configurable in a future version, but it'll do for now
38
+ digest = OpenSSL::Digest::SHA256.new
39
+ key = OpenSSL::PKey::RSA.new(File.read(key_filename))
40
+ sig = key.sign(digest, stripped_json)
41
+
42
+ # Use the urlsafe version so we don't get newline literals
43
+ enc_sig = Base64.urlsafe_encode64(sig).strip
44
+ return stripped_json.sub(/}\Z/, %Q[,"_jsign":"#{enc_sig}"}])
45
+ end
46
+
47
+
48
+ # Given a string of json and the filename of a public key,
49
+ # see if the json string contains a signature which the public
50
+ # key validates. This doesn't actually do any JSON decoding.
51
+ #
52
+ # @param [String] json The putative JSON string
53
+ # @param [String] pubkey_filename The filename of the public key
54
+ # @return [Boolean] true if the public key validates the
55
+ # signature, false otherwise.
56
+ def verify(json, pubkey_filename)
57
+ check_for_keyfile!(pubkey_filename)
58
+
59
+ # Note that we mirror the whitespace stripping here:
60
+ # this is so that we have a chance to survive being
61
+ # deserialised and reserialised.
62
+ if m = json.match(/\A(.+?)\s*,"_jsign":"([^"]+)"}/)
63
+ enc_sig = m[2]
64
+
65
+ sig = Base64.urlsafe_decode64(enc_sig)
66
+ reconstructed_json = m[1]+"}"
67
+
68
+ digest = OpenSSL::Digest::SHA256.new
69
+ key = OpenSSL::PKey::RSA.new(File.read(pubkey_filename))
70
+ return key.verify(digest, sig, reconstructed_json)
71
+
72
+ else
73
+ return false
74
+ end
75
+ end
76
+
77
+
78
+ end # class << self
79
+ end # module Jsign
data/lib/jsign.rb~ ADDED
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tempfile'
4
+ require 'base64'
5
+
6
+
7
+ module Jsign
8
+
9
+ class << self
10
+
11
+ def check_for_openssl!
12
+ raise "Can't find the openssl command!" unless
13
+ system "which openssl > /dev/null"
14
+ end
15
+ private :check_for_openssl!
16
+
17
+ def check_for_keyfile!(key_filename)
18
+ raise "Key #{key_filename.inspect} doesn't exist!" unless
19
+ File.file?(key_filename)
20
+ end
21
+ private :check_for_keyfile!
22
+
23
+
24
+ # Given a json serialisation of a hash, return another json
25
+ # serialisation which includes a key "_jsign" and a value which is
26
+ # the Base64-encoded signature of the *original* json. Well,
27
+ # nearly - any internal whitespace after the last value in the
28
+ # original json is stripped.
29
+ #
30
+ # @param [String] json The json serialisation to sign.
31
+ # @param [String] key_filename The filename of the private key to
32
+ # sign with.
33
+ # @return [String] another json string containing the signature,
34
+ # which can be passed to Jsign.verify.
35
+ def sign(json, key_filename)
36
+ check_for_openssl!
37
+ check_for_keyfile!(key_filename)
38
+
39
+ # Remove any whitespace inside the last bracket.
40
+ stripped_json = json.sub(/\s+}\Z/, "}")
41
+ sig = ""
42
+ # Use openssl -sha256 to do the signing. This will probably
43
+ # be configurable in a future version, but it'll do for now
44
+ IO.popen("openssl dgst -sha256 -sign #{key_filename}","r+"){|f|
45
+ f.write stripped_json
46
+ f.close_write
47
+ sig = f.read.strip
48
+ }
49
+ # Use the urlsafe version so we don't get newline literals
50
+ enc_sig = Base64.urlsafe_encode64(sig).strip
51
+ return stripped_json.sub(/}\Z/, %Q[,"_jsign":"#{enc_sig}"}])
52
+ end
53
+
54
+
55
+ # Given a string of json and the filename of a public key,
56
+ # see if the json string contains a signature which the public
57
+ # key validates. This doesn't actually do any JSON decoding.
58
+ #
59
+ # @param [String] json The putative JSON string
60
+ # @param [String] pubkey_filename The filename of the public key
61
+ # @return [Boolean] true if the public key validates the
62
+ # signature, false otherwise.
63
+ def verify(json, pubkey_filename)
64
+ check_for_openssl!
65
+ check_for_keyfile!(pubkey_filename)
66
+
67
+ # Note that we mirror the whitespace stripping here:
68
+ # this is so that we have a chance to survive being
69
+ # deserialised and reserialised.
70
+ if m = json.match(/\A(.+?)\s*,"_jsign":"([^"]+)"}/)
71
+ # Write the signature out to a tempfile. We can't
72
+ # use -hex with openssl because it doesn't seem to
73
+ # support verifying with it.
74
+ Tempfile.open("jsign") do |tempfile|
75
+ enc_sig = m[2]
76
+ tempfile.write(Base64.urlsafe_decode64(enc_sig))
77
+ tempfile.close
78
+ reconstructed_json = m[1]+"}"
79
+ IO.popen(%W{openssl dgst -sha256
80
+ -verify #{pubkey_filename}
81
+ -signature #{tempfile.path}}, "r+"){|f|
82
+ f.write( reconstructed_json )
83
+ f.close_write
84
+ msg = f.read().strip
85
+ return msg == "Verified OK"
86
+ }
87
+ end
88
+
89
+ else
90
+ return false
91
+ end
92
+ end
93
+
94
+
95
+ end # class << self
96
+ end # module Jsign
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Jsign
4
+ VERSION="0.0.1"
5
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsign
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Young
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-06-05 00:00:00.000000000 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &21390960 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *21390960
26
+ description: Jsign uses OpenSSL to sign JSON serialisations. The signed objects are
27
+ valid JSON objects themselves.
28
+ email:
29
+ - alex@blackkettle.org
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/jsign/version.rb
35
+ - lib/jsign.rb
36
+ - lib/jsign.rb~
37
+ has_rdoc: true
38
+ homepage: http://github.com/regularfry/jsign
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project: jsign
58
+ rubygems_version: 1.6.2
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Make and verify signed JSON objects.
62
+ test_files: []