jwk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1575c0b94d636319a7465b52c562595fb9879116
4
+ data.tar.gz: 3a75443a7e7f83443309de1746c661a26c351f4a
5
+ SHA512:
6
+ metadata.gz: dd408f90856457824da0a2f522c0609e404ea794825ffd3354318a0568fad0739c068b860f6d7cd839db456d5b61526940ffed13982211ee2f5958721d9bd976
7
+ data.tar.gz: dc057987055f26dc8fbc3c533eb93e9321a8023d13825c75b40dcfab0a3b586dc620f20522f34f23d4c034bf7cb94468ff2019aacb6ab44c6c1bffbff44e889f
@@ -0,0 +1,15 @@
1
+ engines:
2
+ duplication:
3
+ enabled: true
4
+ config:
5
+ languages:
6
+ - ruby
7
+ rubocop:
8
+ enabled: true
9
+
10
+ ratings:
11
+ paths:
12
+ - lib/**
13
+
14
+ exclude_paths:
15
+ - spec/**/*
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.ruby-gemset
11
+ /.ruby-version
12
+ Gemfile.lock
13
+ /coverage
14
+ .byebug_history
15
+
16
+ # rspec failure tracking
17
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,16 @@
1
+ Metrics/LineLength:
2
+ Max: 120
3
+
4
+ Metrics/BlockLength:
5
+ Exclude:
6
+ - spec/**/*
7
+
8
+ Style/Documentation:
9
+ Enabled: false
10
+
11
+ Style/GuardClause:
12
+ Enabled: false
13
+
14
+ Lint/RescueException:
15
+ Exclude:
16
+ - spec/**/*
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ dist: trusty
3
+ rvm:
4
+ - jruby-1.7.26
5
+ - jruby-9.1.9.0
6
+ - 2.0.0-p648
7
+ - 2.1.10
8
+ - 2.2.8
9
+ - 2.3.5
10
+
11
+ matrix:
12
+ include:
13
+ - rvm: 2.4.2
14
+ env: CODECLIMATE_REPO_TOKEN=5d5a47900abd78d4b430435e4948a78c9b065c20e2f4be4ecb0a084413d75ca0
15
+ after_script:
16
+ - bundle exec codeclimate-test-reporter
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jwk.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ * Copyright © 2017 Francesco Boffa
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # JWK
2
+
3
+ [![Build Status](https://travis-ci.org/aomega08/jwk.svg)](https://travis-ci.org/aomega08/jwk)
4
+ [![Code Climate](https://codeclimate.com/github/aomega08/jwk/badges/gpa.svg)](https://codeclimate.com/github/aomega08/jwk)
5
+ [![Test Coverage](https://codeclimate.com/github/aomega08/jwk/badges/coverage.svg)](https://codeclimate.com/github/aomega08/jwk/coverage)
6
+
7
+ A Ruby implementation of the RFC 7517 JSON Web Keys (JWK) standard.
8
+
9
+ ## Installation
10
+
11
+ $ gem install jwk
12
+
13
+ ## Usage
14
+
15
+ TODO: Write usage instructions here
16
+
17
+ ## License
18
+
19
+ The MIT License
20
+
21
+ Copyright © 2017 Francesco Boffa
22
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
23
+
24
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'jwk'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,62 @@
1
+ # DER Encoded ASN.1
2
+
3
+ * Everything is Big Endian.
4
+ * Every encoded item is prefixed by a tag that specifies its nature followed by its length and data.
5
+ * If lengths are < 128 they are just added to the stream as a single byte. Lengths > 127 are prefixed by (0x80 | bytelength of length).
6
+ * If Tags have 0x80 bit set, they're "custom". If 0xA0, they're custom, but structured like a SEQUENCE.
7
+
8
+ ### Notable tags:
9
+
10
+ * **0x30** SEQUENCE (array of non-uniform objects)
11
+ * **0x02** INTEGER
12
+ * **0x03** BIT STRING (for some reason prefixed by 00)
13
+ * **0x04** OCTET STRING (similar to BIT STRING but not prefixed by 00)
14
+ * **0x05** NULL
15
+ * **0x06** OBJECT IDENTIFIER
16
+
17
+ ## Generic Public Key format:
18
+
19
+ SEQUENCE
20
+ SEQUENCE
21
+ OBJECT IDENTIFIER
22
+ NULL
23
+ BIT STRING
24
+ (Key Type dependent)
25
+
26
+ ## RSA Public Key format:
27
+
28
+ Can be found in the wild, but it is usually embedded in the Generic Public Key format.
29
+
30
+ SEQUENCE
31
+ INTEGER n
32
+ INTEGER e
33
+
34
+ ## RSA Private Key format:
35
+
36
+ SEQUENCE (0x30)
37
+ INTEGER 0
38
+ INTEGER n
39
+ INTEGER e
40
+ INTEGER d
41
+ INTEGER p
42
+ INTEGER q
43
+ INTEGER dp
44
+ INTEGER dq
45
+ INTEGER qi
46
+
47
+ ## EC Private Key format:
48
+
49
+ SEQUENCE
50
+ INTEGER 1
51
+ OCTET STRING d
52
+ (custom structure, 0xA0)
53
+ OBJECT IDENTIFIER
54
+ (custom structure, 0xA1)
55
+ BIT STRING (0x04 followed by x and y)
56
+
57
+ ## Common Object IDs
58
+
59
+ RSA: 1.2.840.113549.1.1.1
60
+ EC P-256: 1.2.840.10045.3.1.7
61
+ EC P-384: 1.3.132.0.34
62
+ EC P-521: 1.3.132.0.35
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'jwk/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'jwk'
7
+ spec.version = JWK::VERSION
8
+ spec.authors = ['Francesco Boffa']
9
+ spec.email = ['fra.boffa@gmail.com']
10
+
11
+ spec.summary = 'JSON Web Keys implementation in Ruby'
12
+ spec.description = 'A Ruby implementation of the RFC 7517 JSON Web Keys (JWK) standard'
13
+ spec.homepage = 'https://github.com/aomega08/jwk'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'rspec'
20
+ spec.add_development_dependency 'rake'
21
+ spec.add_development_dependency 'simplecov'
22
+ spec.add_development_dependency 'codeclimate-test-reporter'
23
+ end
@@ -0,0 +1,15 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'openssl'
4
+
5
+ require 'jwk/asn1'
6
+ require 'jwk/ec_key'
7
+ require 'jwk/key'
8
+ require 'jwk/oct_key'
9
+ require 'jwk/rsa_key'
10
+ require 'jwk/utils'
11
+ require 'jwk/version'
12
+
13
+ module JWK
14
+ class InvalidKey < StandardError; end
15
+ end
@@ -0,0 +1,101 @@
1
+ module JWK
2
+ class ASN1
3
+ class << self
4
+ def rsa_public_key(n, e)
5
+ pubkey = bit_string(sequence(integer(n), integer(e)))
6
+ sequence(rsa_header, pubkey)
7
+ end
8
+
9
+ def rsa_private_key(*args)
10
+ raise ArgumentError('Some pieces missing for RSA Private Key') unless args.length == 8
11
+ sequence(integer(0), *args.map { |n| integer(n) })
12
+ end
13
+
14
+ def ec_private_key(crv, d, x, y)
15
+ _, raw_x = raw_integer_encoding(x)
16
+ _, raw_y = raw_integer_encoding(y)
17
+
18
+ object_id = object_id_for_crv(crv)
19
+
20
+ sequence(
21
+ integer(1),
22
+ integer_octet_string(d),
23
+ context_specific(true, 0, object_id),
24
+ context_specific(true, 1, bit_string("\x04#{raw_x}#{raw_y}"))
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def object_id_for_crv(crv)
31
+ case crv
32
+ when 'P-256'
33
+ "\x06\x08\x2A\x86\x48\xCE\x3D\x03\x01\x07"
34
+ when 'P-384'
35
+ "\x06\x05\x2B\x81\x04\x00\x22"
36
+ when 'P-521'
37
+ "\x06\x05\x2B\x81\x04\x00\x23"
38
+ end
39
+ end
40
+
41
+ def rsa_header
42
+ "\x30\x0D\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01\x05\x00".force_encoding('ASCII-8BIT')
43
+ end
44
+
45
+ def bit_string(data)
46
+ "\x03" + asn_length(data.length + 1) + "\x00" + data
47
+ end
48
+
49
+ def sequence(*items)
50
+ data = items.join
51
+ "\x30" + asn_length(data.length) + data
52
+ end
53
+
54
+ def integer(n)
55
+ len, data = raw_integer_encoding(n)
56
+
57
+ if data[0].ord & 0x80 != 0
58
+ data = "\x00" + data
59
+ len += 1
60
+ end
61
+
62
+ "\x02" + asn_length(len) + data
63
+ end
64
+
65
+ def integer_octet_string(n)
66
+ len, data = raw_integer_encoding(n)
67
+ "\x04" + asn_length(len) + data
68
+ end
69
+
70
+ def asn_length(n)
71
+ if n > 127
72
+ self_len, data = raw_integer_encoding(n)
73
+ self_len |= 0x80
74
+ self_len.chr + data
75
+ else
76
+ n.chr
77
+ end
78
+ end
79
+
80
+ def raw_integer_encoding(n)
81
+ # find out how many octets are required to encode it
82
+ num_octets = (n.to_s(16).length / 2.0).ceil
83
+
84
+ # encode the low num_octets bytes of the integer.
85
+ shifted = n << 8
86
+ data = Array.new(num_octets) do
87
+ ((shifted >>= 8) & 0xFF).chr
88
+ end.join.reverse
89
+
90
+ [num_octets, data]
91
+ end
92
+
93
+ def context_specific(structured, tag, content)
94
+ tag |= 0x80
95
+ tag |= 0x20 if structured
96
+
97
+ tag.chr + asn_length(content.length) + content.force_encoding('ASCII-8BIT')
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,79 @@
1
+ require 'jwk/key'
2
+
3
+ module JWK
4
+ class ECKey < Key
5
+ def initialize(key)
6
+ @key = key
7
+ validate
8
+ end
9
+
10
+ def public?
11
+ true
12
+ end
13
+
14
+ def private?
15
+ !@key['d'].nil?
16
+ end
17
+
18
+ def validate
19
+ unless @key['x'] && @key['y'] && ['P-256', 'P-384', 'P-521'].include?(@key['crv'])
20
+ raise JWK::InvalidKey, 'Invalid EC key.'
21
+ end
22
+ end
23
+
24
+ def to_pem
25
+ raise NotImplementedError, 'Cannot convert an EC public key to PEM.' unless private?
26
+
27
+ asn = ASN1.ec_private_key(crv, d, x, y)
28
+ generate_pem('EC PRIVATE', asn)
29
+ end
30
+
31
+ def to_openssl_key
32
+ OpenSSL::PKey.read(to_pem)
33
+ end
34
+
35
+ def to_s
36
+ to_pem
37
+ end
38
+
39
+ def crv
40
+ @key['crv']
41
+ end
42
+
43
+ %w[d x y].each do |part|
44
+ define_method(part) do
45
+ Utils.decode_ub64_int(@key[part]) if @key[part]
46
+ end
47
+ end
48
+
49
+ class << self
50
+ def from_openssl(k)
51
+ x, y = coords_from_key(k)
52
+
53
+ names = { 'secp256r1' => 'P-256', 'secp384r1' => 'P-384', 'secp521r1' => 'P-521' }
54
+ crv = names[k.group.curve_name]
55
+
56
+ raise NotImplementedError, "Unsupported EC curve type #{k.group.curve_name}" unless crv
57
+
58
+ new('kty' => 'EC',
59
+ 'crv' => crv,
60
+ 'd' => Utils.encode_ub64_int(k.private_key.to_i), 'x' => x, 'y' => y)
61
+ end
62
+
63
+ private
64
+
65
+ def coords_from_key(key)
66
+ pb = key.public_key.to_bn.to_s(16)
67
+
68
+ raise NotImplementedError, 'Cannot convert EC compressed public key' unless pb[0..1] == '04'
69
+
70
+ decode_uncompressed_coords(pb[2..-1])
71
+ end
72
+
73
+ def decode_uncompressed_coords(pb)
74
+ coords = [pb[0...pb.length / 2], pb[pb.length / 2..-1]]
75
+ coords.map { |c| Base64.urlsafe_encode64(Utils.hex_string_to_binary(c)) }
76
+ end
77
+ end
78
+ end
79
+ end