signed_json 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.
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
+