signed_json 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in signed_json.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ signed_json (0.0.1)
5
+ json
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.2)
11
+ json (1.4.6)
12
+ rake (0.8.7)
13
+ rspec (2.0.1)
14
+ rspec-core (~> 2.0.1)
15
+ rspec-expectations (~> 2.0.1)
16
+ rspec-mocks (~> 2.0.1)
17
+ rspec-core (2.0.1)
18
+ rspec-expectations (2.0.1)
19
+ diff-lcs (>= 1.1.2)
20
+ rspec-mocks (2.0.1)
21
+ rspec-core (~> 2.0.1)
22
+ rspec-expectations (~> 2.0.1)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ json
29
+ rake
30
+ rspec (~> 2.0)
31
+ signed_json!
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ signed_json
2
+ ============
3
+
4
+ Encodes and decodes data to a JSON string signed with OpenSSL HMAC. Great for signed cookies.
5
+
6
+
7
+ Install.
8
+ --------
9
+ gem install signed_json
10
+
11
+
12
+ Use.
13
+ ----
14
+ require 'signed_json'
15
+ s = SignedJson::Signer.new('your secret')
16
+
17
+ ### encode ###
18
+ s.encode 'a string'
19
+ s.encode ['an', 'array']
20
+ s.encode { :a => 'hash' }
21
+
22
+ ### decode ###
23
+ s.decode '["da7555389d05e04a3367b84aed401cafbbecfe3d","example"]'
24
+ # => "example"
25
+ s.decode '["da7555389d05e04a3367b84aed401cafbbecfe3d","tampered"]'
26
+ # SignedJson::SignatureError
27
+
28
+
29
+ Understand.
30
+ -----------
31
+
32
+ `SignedJson::Signer` takes any JSON encodable data, and returns the data in a JSON string along with an [HMAC][1] signature. The output string can then be decoded back into the original data, with certainty that it was generated using the same secret.
33
+
34
+ The signature uses [`OpenSSL::HMAC`][2], with the configurable digest defaulting to SHA1.
35
+
36
+ Note that the data is *not* encrypted - it is clearly readable, but altering the data will cause the signature verification to fail. The encoded output looks like this:
37
+
38
+ ["f47bd6c4108cf503b98b82b2e36ce3e7bae712b5",["an","array","of","strings"]]
39
+
40
+ This is ideal for signed cookies, and allows client cookies to be used as a light-weight session store.
41
+
42
+ Rails already has a nice signed cookie implementation, but because [`ActiveSupport::MessageVerifier`][3] uses Base64 encoded Marshal.dump instead of JSON, it is barely portable between Ruby versions, let alone different platforms.
43
+
44
+ `SignedJson::Signer`, on the other hand, can easily be implemented in other languages, allowing for signed cookies shared between same-domain web applications, for example.
45
+
46
+
47
+ [1]: http://en.wikipedia.org/wiki/HMAC
48
+ [2]: http://ruby-doc.org/ruby-1.9/classes/OpenSSL/HMAC.html
49
+ [3]: http://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html
50
+
51
+
52
+ Status.
53
+ -------
54
+
55
+ Ported from my PHP implementation, which is running in high-traffic production environments.
56
+
57
+ RSpec speaks for the Ruby implementation:
58
+
59
+ $ rake spec
60
+
61
+ SignedJson
62
+ round trip encoding/decoding
63
+ round-trips a string
64
+ round-trips an array of strings and ints
65
+ round-trips a hash with string keys, string and int values
66
+ round-trips a nested array
67
+ round-trips a hash/array/string/int structure
68
+ Signer#encode
69
+ returns a string
70
+ returns a valid JSON-encoded array
71
+ Signer#decode error handling
72
+ raises SignatureError for incorrect key/signature
73
+ raises InputError for invalid input
74
+
75
+ Finished in 0.0186 seconds
76
+ 9 examples, 0 failures
77
+
78
+
79
+ Meh.
80
+ ----
81
+
82
+ (c) 2010 Paul Annesley, MIT license.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ desc "Run specs"
5
+ task :spec do
6
+ system 'bundle exec rspec --color --format documentation spec/*_spec.rb'
7
+ end
@@ -0,0 +1,7 @@
1
+ module SignedJson
2
+
3
+ class Error < RuntimeError; end
4
+ class InputError < Error; end
5
+ class SignatureError < Error; end
6
+
7
+ end
@@ -0,0 +1,3 @@
1
+ module SignedJson
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'json'
2
+ require 'signed_json/errors'
3
+
4
+ module SignedJson
5
+ class Signer
6
+
7
+ def initialize(secret, digest = 'SHA1')
8
+ @secret = secret
9
+ @digest = digest
10
+ end
11
+
12
+ def encode(input)
13
+ [digest_for(input), input].to_json
14
+ end
15
+
16
+ def decode(input)
17
+ digest, data = json_decode(input)
18
+ raise SignatureError unless digest === digest_for(data)
19
+ data
20
+ end
21
+
22
+ def digest_for(input)
23
+ # ActiveSupport::MessageVerifier does this, probably for a good reason.
24
+ require 'openssl' unless defined?(OpenSSL)
25
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, input.to_json)
26
+ end
27
+
28
+ private
29
+
30
+ def json_decode(input)
31
+ begin
32
+ parts = JSON.parse(input)
33
+ rescue JSON::ParserError
34
+ raise InputError
35
+ end
36
+
37
+ raise InputError unless
38
+ parts.instance_of?(Array) && parts.length == 2
39
+
40
+ parts
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "signed_json/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "signed_json"
7
+ s.version = SignedJson::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Paul Annesley"]
10
+ s.email = ["paul@annesley.cc"]
11
+ s.homepage = "http://github.com/pda/signed_json"
12
+ s.summary = %q{Encodes and decodes JSON-encodable data into and from a signed JSON string.}
13
+
14
+ s.rubyforge_project = "signed_json"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency('json')
22
+
23
+ s.add_development_dependency('rspec', ['~> 2.0'])
24
+ s.add_development_dependency('rake')
25
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe SignedJson do
4
+
5
+ describe "round trip encoding/decoding" do
6
+
7
+ it "round-trips a string" do
8
+ "a string".should round_trip_as_signed_json
9
+ end
10
+
11
+ it "round-trips an array of strings and ints" do
12
+ [1, 'a', 2, 'b'].should round_trip_as_signed_json
13
+ end
14
+
15
+ it "round-trips a hash with string keys, string and int values" do
16
+ { 'a' => 'b', 'b' => 2 }.should round_trip_as_signed_json
17
+ end
18
+
19
+ it "round-trips a nested array" do
20
+ [ 'a', [ 'b', [ 'c', 'd' ], 'e' ], 'f' ].should round_trip_as_signed_json
21
+ end
22
+
23
+ it "round-trips a hash/array/string/int structure" do
24
+ { 'a' => [ 'b' ], 'd' => { 'e' => 'f' }, 'g' => 10 }.should round_trip_as_signed_json
25
+ end
26
+
27
+ end
28
+
29
+ describe "Signer#encode" do
30
+
31
+ it "returns a string" do
32
+ encoded = SignedJson::Signer.new('right').encode('test')
33
+ encoded.should be_instance_of(String)
34
+ end
35
+
36
+ it "returns a valid JSON-encoded array" do
37
+ encoded = SignedJson::Signer.new('right').encode('test')
38
+ JSON.parse(encoded).should be_instance_of(Array)
39
+ end
40
+
41
+ end
42
+
43
+ describe "Signer#decode error handling" do
44
+
45
+ it "raises SignatureError for incorrect key/signature" do
46
+ encoded = SignedJson::Signer.new('right').encode('test')
47
+ lambda {
48
+ SignedJson::Signer.new('wrong').decode(encoded)
49
+ }.should raise_error(SignedJson::SignatureError)
50
+ end
51
+
52
+ it "raises InputError for invalid input" do
53
+ lambda {
54
+ SignedJson::Signer.new('key').decode('blarg')
55
+ }.should raise_error(SignedJson::InputError)
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,34 @@
1
+ require 'signed_json'
2
+
3
+ RSpec::Matchers.define :round_trip_as_signed_json do
4
+
5
+ match do |actual|
6
+
7
+ signer = SignedJson::Signer.new('some secret')
8
+
9
+ @encoded = signer.encode(actual)
10
+ @decoded = signer.decode(@encoded)
11
+
12
+ if @encoded == actual
13
+ fail_because :not_encoded
14
+ elsif @decoded != actual
15
+ fail_because :mismatch
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ def fail_because(reason_code)
22
+ @reason = reason_code
23
+ false
24
+ end
25
+
26
+ failure_message_for_should do |actual|
27
+ if @reason == :not_encoded
28
+ "Expected encoded to be different to original input: #{actual}"
29
+ elsif @reason == :mismatch
30
+ "Expected decoded to equal original input, got '#{@decoded}'"
31
+ end
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: signed_json
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Paul Annesley
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-04 00:00:00 +11:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: rspec
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 2
43
+ - 0
44
+ version: "2.0"
45
+ type: :development
46
+ version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id003
60
+ description:
61
+ email:
62
+ - paul@annesley.cc
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ extra_rdoc_files: []
68
+
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - README.md
74
+ - Rakefile
75
+ - lib/signed_json.rb
76
+ - lib/signed_json/errors.rb
77
+ - lib/signed_json/version.rb
78
+ - signed_json.gemspec
79
+ - spec/signed_json_spec.rb
80
+ - spec/spec_helper.rb
81
+ has_rdoc: true
82
+ homepage: http://github.com/pda/signed_json
83
+ licenses: []
84
+
85
+ post_install_message:
86
+ rdoc_options: []
87
+
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ requirements: []
107
+
108
+ rubyforge_project: signed_json
109
+ rubygems_version: 1.3.7
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Encodes and decodes JSON-encodable data into and from a signed JSON string.
113
+ test_files: []
114
+