jsign 0.0.1

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.
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: []