cose 0.8.0 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f2e83dd45585da762903b90f50376dcc75f175acf5ebbc3804763a963dfedfc
4
- data.tar.gz: 6f83ee059a2b00539f20b932c9b6382df837aef0dbb42e201be344b055a51dbf
3
+ metadata.gz: ef03f2ea3b2f4f4bb8f81fdd777cf3ae3905e1eae953599f0e6b0feca89343f5
4
+ data.tar.gz: 94db44b9a0a449bdb81ae9274fb193365838ec84f9df3db3540dcf5eb1ca3303
5
5
  SHA512:
6
- metadata.gz: 536b1620403cf8f2a6d54ffd8c7918fc0196e3fbb14b0c8ccf99ad85620184463cc92140b6ad1e810d06afeb84c4b9ce03d17c926a316270df2a3eb77f922f10
7
- data.tar.gz: 230af044b097a08fd714bb180ce41e3df194c6c880f046d7628acb984d954c0f7308a5e9a89c52705c0e9341f2404f24753a4cc5e47da89784d21a7b32734a23
6
+ metadata.gz: 17492d5ba3f7fce248418a158d29419392f9d849f33421d7b6192e61e021e5d62ca14f6a42721a05e77e31dfc67ef267c18e8be7138360a9146e359e5cb5b740
7
+ data.tar.gz: 49834d1df018a49669dfc02b8548c30f2aedf99fbad979cd072ee480eff99e9b6076e74654cfff72e6d6a6da96798d1de7902497f19f70e53b6464ac5a7d716a
@@ -0,0 +1,3 @@
1
+ [submodule "spec/fixtures/cose-wg-examples"]
2
+ path = spec/fixtures/cose-wg-examples
3
+ url = https://github.com/cose-wg/Examples
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
3
+ --order random
@@ -6,7 +6,7 @@ inherit_mode:
6
6
  - Exclude
7
7
 
8
8
  AllCops:
9
- TargetRubyVersion: 2.2
9
+ TargetRubyVersion: 2.4
10
10
  DisabledByDefault: true
11
11
  Exclude:
12
12
  - "gemfiles/**/*"
@@ -20,13 +20,12 @@ Gemspec:
20
20
  Layout:
21
21
  Enabled: true
22
22
 
23
+ Layout/LineLength:
24
+ Max: 120
25
+
23
26
  Lint:
24
27
  Enabled: true
25
28
 
26
- Metrics/LineLength:
27
- Max: 120
28
- IgnoreCopDirectives: true
29
-
30
29
  Naming:
31
30
  Enabled: true
32
31
 
@@ -1,35 +1,25 @@
1
- sudo: false
1
+ dist: bionic
2
2
  language: ruby
3
3
  cache: bundler
4
4
 
5
5
  rvm:
6
6
  - ruby-head
7
- - 2.7.0-preview1
8
- - 2.6.3
9
- - 2.5.5
10
- - 2.4.6
11
- - 2.3.8
12
- - 2.2.10
7
+ - 2.7.1
8
+ - 2.6.6
9
+ - 2.5.8
10
+ - 2.4.10
13
11
 
14
12
  gemfile:
15
13
  - gemfiles/openssl_head.gemfile
14
+ - gemfiles/openssl_2_2.gemfile
16
15
  - gemfiles/openssl_2_1.gemfile
17
16
  - gemfiles/openssl_2_0.gemfile
18
17
  - gemfiles/openssl_default.gemfile
19
18
 
20
- before_install: gem install bundler -v '~> 1.17'
19
+ before_install: gem install bundler -v '~> 2.0'
21
20
 
22
21
  matrix:
23
22
  fast_finish: true
24
23
  allow_failures:
25
24
  - rvm: ruby-head
26
- - rvm: 2.7.0-preview1
27
- - rvm: 2.2.10
28
25
  - gemfile: gemfiles/openssl_head.gemfile
29
- exclude:
30
- - rvm: 2.2.10
31
- gemfile: gemfiles/openssl_head.gemfile
32
- - rvm: 2.2.10
33
- gemfile: gemfiles/openssl_2_1.gemfile
34
- - rvm: 2.2.10
35
- gemfile: gemfiles/openssl_2_0.gemfile
data/Appraisals CHANGED
@@ -1,7 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  appraise "openssl_head" do
2
4
  gem "openssl", git: "https://github.com/ruby/openssl"
3
5
  end
4
6
 
7
+ appraise "openssl_2_2" do
8
+ gem "openssl", "~> 2.2.0"
9
+ end
10
+
5
11
  appraise "openssl_2_1" do
6
12
  gem "openssl", "~> 2.1.0"
7
13
  end
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.1.0] - 2020-07-09
4
+
5
+ ### Dependencies
6
+
7
+ - Update `openssl-signature_algorithm` runtime dependency from `~> 0.4.0` to `~> 1.0`.
8
+
9
+ ## [v1.0.0] - 2020-03-29
10
+
11
+ ### Added
12
+
13
+ - Signature verification validates key `alg` is compatible with the signature algorithm
14
+
15
+ NOTE: No breaking changes. Moving out of `v0.x` to express the intention to keep the public API stable.
16
+
17
+ ## [v0.11.0] - 2020-01-30
18
+
19
+ ### Added
20
+
21
+ - Let others easily support more signature algorithms by making `COSE::Algorithm::SignatureAlgorithm` smarter
22
+
23
+ ## [v0.10.0] - 2019-12-19
24
+
25
+ ### Added
26
+
27
+ - Works on ruby 2.7 without throwing any warnings
28
+ - Simpler way to rescue key deserialization error, now possible to:
29
+ ```rb
30
+ begin
31
+ COSE::Key.deserialize(cbor)
32
+ rescue COSE::KeyDeserializationError
33
+ # handle error
34
+ end
35
+ ```
36
+
37
+ ## [v0.9.0] - 2019-08-31
38
+
39
+ ### Added
40
+
41
+ - `COSE::Sign1#verify`
42
+ - `COSE::Sign#verify`
43
+ - `COSE::Mac0#verify`
44
+ - `COSE::Mac#verify`
45
+
3
46
  ## [v0.8.0] - 2019-08-17
4
47
 
5
48
  ### Added
@@ -86,6 +129,11 @@
86
129
  - EC2 key object
87
130
  - Works with ruby 2.5
88
131
 
132
+ [v1.1.0]: https://github.com/cedarcode/cose-ruby/compare/v1.0.0...v1.1.0/
133
+ [v1.0.0]: https://github.com/cedarcode/cose-ruby/compare/v0.11.0...v1.0.0/
134
+ [v0.11.0]: https://github.com/cedarcode/cose-ruby/compare/v0.10.0...v0.11.0/
135
+ [v0.10.0]: https://github.com/cedarcode/cose-ruby/compare/v0.9.0...v0.10.0/
136
+ [v0.9.0]: https://github.com/cedarcode/cose-ruby/compare/v0.8.0...v0.9.0/
89
137
  [v0.8.0]: https://github.com/cedarcode/cose-ruby/compare/v0.7.0...v0.8.0/
90
138
  [v0.7.0]: https://github.com/cedarcode/cose-ruby/compare/v0.6.1...v0.7.0/
91
139
  [v0.6.1]: https://github.com/cedarcode/cose-ruby/compare/v0.6.0...v0.6.1/
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # cose
1
+ # cose-ruby
2
2
 
3
- Ruby implementation of RFC 8152 CBOR Object Signing and Encryption (COSE)
3
+ Ruby implementation of RFC [8152](https://tools.ietf.org/html/rfc8152) CBOR Object Signing and Encryption (COSE)
4
4
 
5
- [![Gem](https://img.shields.io/gem/v/cose.svg?style=flat-square)](https://rubygems.org/gems/cose)
5
+ [![Gem](https://img.shields.io/gem/v/cose.svg?style=flat-square&color=informational)](https://rubygems.org/gems/cose)
6
6
  [![Travis](https://img.shields.io/travis/cedarcode/cose-ruby.svg?style=flat-square)](https://travis-ci.org/cedarcode/cose-ruby)
7
7
 
8
8
  ## Installation
@@ -145,15 +145,14 @@ cbor_data = "..."
145
145
 
146
146
  sign = COSE::Sign.deserialize(cbor_data)
147
147
 
148
- sign.protected_headers
149
- sign.unprotected_headers
150
- sign.payload
148
+ # Verify by doing (key should be a COSE::Key):
149
+ sign.verify(key)
151
150
 
152
- sign.signatures.each do |signature|
153
- signature.protected_headers
154
- signature.unprotected_headers
155
- signature.signature
156
- end
151
+ # or, if externally supplied authenticated data exists:
152
+ sign.verify(key, external_aad)
153
+
154
+ # Then access payload
155
+ sign.payload
157
156
  ```
158
157
 
159
158
  #### COSE_Sign1
@@ -163,10 +162,14 @@ cbor_data = "..."
163
162
 
164
163
  sign1 = COSE::Sign1.deserialize(cbor_data)
165
164
 
166
- sign1.protected_headers
167
- sign1.unprotected_headers
165
+ # Verify by doing (key should be a COSE::Key):
166
+ sign1.verify(key)
167
+
168
+ # or, if externally supplied authenticated data exists:
169
+ sign1.verify(key, external_aad)
170
+
171
+ # Then access payload
168
172
  sign1.payload
169
- sign1.signature
170
173
  ```
171
174
 
172
175
  ### MAC Objects
@@ -178,24 +181,14 @@ cbor_data = "..."
178
181
 
179
182
  mac = COSE::Mac.deserialize(cbor_data)
180
183
 
181
- mac.protected_headers
182
- mac.unprotected_headers
183
- mac.payload
184
- mac.tag
184
+ # Verify by doing (key should be a COSE::Key::Symmetric):
185
+ mac.verify(key)
185
186
 
186
- mac.recipients.each do |recipient|
187
- recipient.protected_headers
188
- recipient.unprotected_headers
189
- recipient.ciphertext
187
+ # or, if externally supplied authenticated data exists:
188
+ mac.verify(key, external_aad)
190
189
 
191
- if recipient.recipients
192
- recipient.recipients.each do |recipient|
193
- recipient.protected_headers
194
- recipient.unprotected_headers
195
- recipient.ciphertext
196
- end
197
- end
198
- end
190
+ # Then access payload
191
+ mac.payload
199
192
  ```
200
193
 
201
194
  #### COSE_Mac0
@@ -205,10 +198,14 @@ cbor_data = "..."
205
198
 
206
199
  mac0 = COSE::Mac0.deserialize(cbor_data)
207
200
 
208
- mac0.protected_headers
209
- mac0.unprotected_headers
201
+ # Verify by doing (key should be a COSE::Key::Symmetric):
202
+ mac0.verify(key)
203
+
204
+ # or, if externally supplied authenticated data exists:
205
+ mac0.verify(key, external_aad)
206
+
207
+ # Then access payload
210
208
  mac0.payload
211
- mac0.tag
212
209
  ```
213
210
 
214
211
  ### Encryption Objects
@@ -4,9 +4,9 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------- | ------------------ |
7
+ | 0.9.z | :white_check_mark: |
7
8
  | 0.8.z | :white_check_mark: |
8
- | 0.7.z | :white_check_mark: |
9
- | < 0.7 | :x: |
9
+ | < 0.8 | :x: |
10
10
 
11
11
  ## Reporting a Vulnerability
12
12
 
data/bin/setup CHANGED
@@ -3,6 +3,8 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
+ git submodule update --init --recursive
7
+
6
8
  bundle install
7
9
 
8
10
  # Do any other automated setup that you need to do here
@@ -29,15 +29,16 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
- spec.required_ruby_version = ">= 2.2"
32
+ spec.required_ruby_version = ">= 2.4"
33
33
 
34
34
  spec.add_dependency "cbor", "~> 0.5.9"
35
+ spec.add_dependency "openssl-signature_algorithm", "~> 1.0"
35
36
 
36
37
  spec.add_development_dependency "appraisal", "~> 2.2.0"
37
38
  spec.add_development_dependency "bundler", ">= 1.17", "< 3"
38
- spec.add_development_dependency "byebug", ">= 10.0"
39
- spec.add_development_dependency "rake", "~> 12.3"
39
+ spec.add_development_dependency "byebug", "~> 11.0"
40
+ spec.add_development_dependency "rake", "~> 13.0"
40
41
  spec.add_development_dependency "rspec", "~> 3.8"
41
- spec.add_development_dependency "rubocop", "0.68.0"
42
- spec.add_development_dependency "rubocop-performance", "~> 1.3"
42
+ spec.add_development_dependency "rubocop", "0.80.1"
43
+ spec.add_development_dependency "rubocop-performance", "~> 1.4"
43
44
  end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "openssl", "~> 2.2.0"
6
+
7
+ gemspec path: "../"
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "cose/encrypt"
4
4
  require "cose/encrypt0"
5
+ require "cose/error"
5
6
  require "cose/key"
6
7
  require "cose/mac"
7
8
  require "cose/mac0"
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose/algorithm/ecdsa"
4
+ require "cose/algorithm/hmac"
5
+ require "cose/algorithm/rsa_pss"
6
+
7
+ module COSE
8
+ module Algorithm
9
+ @registered_by_id = {}
10
+ @registered_by_name = {}
11
+
12
+ def self.register(algorithm)
13
+ @registered_by_id[algorithm.id] = algorithm
14
+ @registered_by_name[algorithm.name] = algorithm
15
+ end
16
+
17
+ def self.find(id_or_name)
18
+ by_id(id_or_name) || by_name(id_or_name)
19
+ end
20
+
21
+ def self.by_id(id)
22
+ @registered_by_id[id]
23
+ end
24
+
25
+ def self.by_name(name)
26
+ @registered_by_name[name]
27
+ end
28
+
29
+ register(ECDSA.new(-7, "ES256", hash_function: "SHA256"))
30
+ register(ECDSA.new(-35, "ES384", hash_function: "SHA384"))
31
+ register(ECDSA.new(-36, "ES512", hash_function: "SHA512"))
32
+ register(RSAPSS.new(-37, "PS256", hash_function: "SHA256", salt_length: 32))
33
+ register(RSAPSS.new(-38, "PS384", hash_function: "SHA384", salt_length: 48))
34
+ register(RSAPSS.new(-39, "PS512", hash_function: "SHA512", salt_length: 64))
35
+ register(HMAC.new(4, "HMAC 256/64", hash_function: "SHA256", tag_length: 64))
36
+ register(HMAC.new(5, "HMAC 256/256", hash_function: "SHA256", tag_length: 256))
37
+ register(HMAC.new(6, "HMAC 384/384", hash_function: "SHA384", tag_length: 384))
38
+ register(HMAC.new(7, "HMAC 512/512", hash_function: "SHA512", tag_length: 512))
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module COSE
4
+ module Algorithm
5
+ class Base
6
+ attr_reader :id, :name
7
+
8
+ def initialize(id, name)
9
+ @id = id
10
+ @name = name
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose/algorithm/signature_algorithm"
4
+ require "cose/error"
5
+ require "cose/key/ec2"
6
+ require "openssl"
7
+ require "openssl/signature_algorithm/ecdsa"
8
+
9
+ module COSE
10
+ module Algorithm
11
+ class ECDSA < SignatureAlgorithm
12
+ attr_reader :hash_function
13
+
14
+ def initialize(*args, hash_function:)
15
+ super(*args)
16
+
17
+ @hash_function = hash_function
18
+ end
19
+
20
+ private
21
+
22
+ def valid_key?(key)
23
+ cose_key = to_cose_key(key)
24
+
25
+ cose_key.is_a?(COSE::Key::EC2) && (!cose_key.alg || cose_key.alg == id)
26
+ end
27
+
28
+ def signature_algorithm_class
29
+ OpenSSL::SignatureAlgorithm::ECDSA
30
+ end
31
+
32
+ def to_pkey(key)
33
+ case key
34
+ when COSE::Key::EC2
35
+ key.to_pkey
36
+ when OpenSSL::PKey::EC
37
+ key
38
+ else
39
+ raise(COSE::Error, "Incompatible key for algorithm")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose/algorithm/base"
4
+ require "openssl"
5
+
6
+ module COSE
7
+ module Algorithm
8
+ class HMAC < Base
9
+ BYTE_LENGTH = 8
10
+
11
+ attr_reader :hash_function, :tag_length
12
+
13
+ def initialize(*args, hash_function:, tag_length:)
14
+ super(*args)
15
+
16
+ @hash_function = hash_function
17
+ @tag_length = tag_length
18
+ end
19
+
20
+ def mac(key, to_be_signed)
21
+ mac = OpenSSL::HMAC.digest(hash_function, key, to_be_signed)
22
+
23
+ if tag_bytesize && tag_bytesize < mac.bytesize
24
+ mac.byteslice(0, tag_bytesize)
25
+ else
26
+ mac
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def tag_bytesize
33
+ @tag_bytesize ||=
34
+ if tag_length
35
+ tag_length / BYTE_LENGTH
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose/algorithm/signature_algorithm"
4
+ require "cose/key/rsa"
5
+ require "cose/error"
6
+ require "openssl"
7
+ require "openssl/signature_algorithm/rsapss"
8
+
9
+ module COSE
10
+ module Algorithm
11
+ class RSAPSS < SignatureAlgorithm
12
+ attr_reader :hash_function, :salt_length
13
+
14
+ def initialize(*args, hash_function:, salt_length:)
15
+ super(*args)
16
+
17
+ @hash_function = hash_function
18
+ @salt_length = salt_length
19
+ end
20
+
21
+ private
22
+
23
+ def valid_key?(key)
24
+ to_cose_key(key).is_a?(COSE::Key::RSA)
25
+ end
26
+
27
+ def signature_algorithm_class
28
+ OpenSSL::SignatureAlgorithm::RSAPSS
29
+ end
30
+
31
+ def to_pkey(key)
32
+ case key
33
+ when COSE::Key::RSA
34
+ key.to_pkey
35
+ when OpenSSL::PKey::RSA
36
+ key
37
+ else
38
+ raise(COSE::Error, "Incompatible key for algorithm")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cose/algorithm/base"
4
+ require "cose/error"
5
+
6
+ module COSE
7
+ module Algorithm
8
+ class SignatureAlgorithm < Base
9
+ def verify(key, signature, verification_data)
10
+ compatible_key?(key) || raise(COSE::Error, "Incompatible key for signature verification")
11
+ valid_signature?(key, signature, verification_data) || raise(COSE::Error, "Signature verification failed")
12
+ end
13
+
14
+ def compatible_key?(key)
15
+ valid_key?(key) && to_pkey(key)
16
+ rescue COSE::Error
17
+ false
18
+ end
19
+
20
+ private
21
+
22
+ def valid_signature?(key, signature, verification_data)
23
+ signature_algorithm = signature_algorithm_class.new(hash_function: hash_function)
24
+ signature_algorithm.verify_key = to_pkey(key)
25
+
26
+ begin
27
+ signature_algorithm.verify(signature, verification_data)
28
+ rescue OpenSSL::SignatureAlgorithm::Error
29
+ false
30
+ end
31
+ end
32
+
33
+ def to_cose_key(key)
34
+ case key
35
+ when COSE::Key::Base
36
+ key
37
+ when OpenSSL::PKey::PKey
38
+ COSE::Key.from_pkey(key)
39
+ else
40
+ raise(COSE::Error, "Don't know how to transform #{key.class} to COSE::Key")
41
+ end
42
+ end
43
+
44
+ def signature_algorithm_class
45
+ raise NotImplementedError
46
+ end
47
+
48
+ def valid_key?(_key)
49
+ raise NotImplementedError
50
+ end
51
+
52
+ def to_pkey(_key)
53
+ raise NotImplementedError
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cose/security_message"
2
4
  require "cose/recipient"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cose/security_message"
2
4
 
3
5
  module COSE
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module COSE
4
+ class Error < StandardError; end
5
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cbor"
2
4
  require "cose/key/ec2"
3
5
  require "cose/key/okp"
@@ -6,7 +8,10 @@ require "cose/key/symmetric"
6
8
  require "openssl"
7
9
 
8
10
  module COSE
9
- class UnknownKeyType < StandardError; end
11
+ class Error < StandardError; end
12
+ class KeyDeserializationError < Error; end
13
+ class MalformedKeyError < KeyDeserializationError; end
14
+ class UnknownKeyType < KeyDeserializationError; end
10
15
 
11
16
  module Key
12
17
  def self.serialize(pkey)
@@ -25,7 +30,7 @@ module COSE
25
30
  end
26
31
 
27
32
  def self.deserialize(data)
28
- map = CBOR.decode(data)
33
+ map = cbor_decode(data)
29
34
 
30
35
  case map[Base::LABEL_KTY]
31
36
  when COSE::Key::OKP::KTY_OKP
@@ -37,10 +42,16 @@ module COSE
37
42
  when COSE::Key::Symmetric::KTY_SYMMETRIC
38
43
  COSE::Key::Symmetric.from_map(map)
39
44
  when nil
40
- raise UnknownKeyType, "Missing required key type kty label"
45
+ raise COSE::UnknownKeyType, "Missing required key type kty label"
41
46
  else
42
- raise UnknownKeyType, "Unsupported or unknown key type #{map[Base::LABEL_KTY]}"
47
+ raise COSE::UnknownKeyType, "Unsupported or unknown key type #{map[Base::LABEL_KTY]}"
43
48
  end
44
49
  end
50
+
51
+ def self.cbor_decode(data)
52
+ CBOR.decode(data)
53
+ rescue CBOR::MalformedFormatError, EOFError, FloatDomainError, RegexpError, TypeError, URI::InvalidURIError
54
+ raise COSE::MalformedKeyError, "Malformed CBOR key input"
55
+ end
45
56
  end
46
57
  end
@@ -41,14 +41,12 @@ module COSE
41
41
  end
42
42
 
43
43
  def map
44
- map = {
44
+ {
45
45
  LABEL_BASE_IV => base_iv,
46
46
  LABEL_KEY_OPS => key_ops,
47
47
  LABEL_ALG => alg,
48
48
  LABEL_KID => kid,
49
- }
50
-
51
- map.reject { |_k, v| v.nil? }
49
+ }.compact
52
50
  end
53
51
  end
54
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module COSE
2
4
  module Key
3
5
  # https://tools.ietf.org/html/rfc8152#section-13.1
@@ -20,7 +20,7 @@ module COSE
20
20
  }
21
21
  end
22
22
 
23
- def initialize(crv:, x: nil, d: nil, **keyword_arguments) # rubocop:disable Naming/UncommunicativeMethodParamName
23
+ def initialize(crv:, x: nil, d: nil, **keyword_arguments) # rubocop:disable Naming/MethodParameterName
24
24
  super(**keyword_arguments)
25
25
 
26
26
  if !crv
@@ -35,13 +35,11 @@ module COSE
35
35
  end
36
36
 
37
37
  def map
38
- map = super.merge(
38
+ super.merge(
39
39
  LABEL_CRV => crv,
40
40
  LABEL_X => x,
41
41
  LABEL_D => d
42
- )
43
-
44
- map.reject { |_k, v| v.nil? }
42
+ ).compact
45
43
  end
46
44
  end
47
45
  end
@@ -48,7 +48,7 @@ module COSE
48
48
 
49
49
  attr_reader :y
50
50
 
51
- def initialize(y: nil, **keyword_arguments) # rubocop:disable Naming/UncommunicativeMethodParamName
51
+ def initialize(y: nil, **keyword_arguments) # rubocop:disable Naming/MethodParameterName
52
52
  if (!y || !keyword_arguments[:x]) && !keyword_arguments[:d]
53
53
  raise ArgumentError, "Both x and y are required if d is missing"
54
54
  else
@@ -59,12 +59,10 @@ module COSE
59
59
  end
60
60
 
61
61
  def map
62
- map = super.merge(
62
+ super.merge(
63
63
  Base::LABEL_KTY => KTY_EC2,
64
64
  LABEL_Y => y,
65
- )
66
-
67
- map.reject { |_k, v| v.nil? }
65
+ ).compact
68
66
  end
69
67
 
70
68
  def to_pkey
@@ -42,12 +42,12 @@ module COSE
42
42
  )
43
43
  end
44
44
 
45
- new(attributes)
45
+ new(**attributes)
46
46
  end
47
47
 
48
48
  attr_reader :n, :e, :d, :p, :q, :dp, :dq, :qinv
49
49
 
50
- def initialize(n:, e:, d: nil, p: nil, q: nil, dp: nil, dq: nil, qinv: nil, **keyword_arguments) # rubocop:disable Naming/UncommunicativeMethodParamName
50
+ def initialize(n:, e:, d: nil, p: nil, q: nil, dp: nil, dq: nil, qinv: nil, **keyword_arguments) # rubocop:disable Naming/MethodParameterName
51
51
  super(**keyword_arguments)
52
52
 
53
53
  if !n
@@ -74,7 +74,7 @@ module COSE
74
74
  end
75
75
 
76
76
  def map
77
- map = super.merge(
77
+ super.merge(
78
78
  Base::LABEL_KTY => KTY_RSA,
79
79
  LABEL_N => n,
80
80
  LABEL_E => e,
@@ -84,9 +84,7 @@ module COSE
84
84
  LABEL_DP => dp,
85
85
  LABEL_DQ => dq,
86
86
  LABEL_QINV => qinv
87
- )
88
-
89
- map.reject { |_k, v| v.nil? }
87
+ ).compact
90
88
  end
91
89
 
92
90
  def to_pkey
@@ -17,7 +17,7 @@ module COSE
17
17
  end
18
18
  end
19
19
 
20
- def initialize(k:, **keyword_arguments) # rubocop:disable Naming/UncommunicativeMethodParamName
20
+ def initialize(k:, **keyword_arguments) # rubocop:disable Naming/MethodParameterName
21
21
  super(**keyword_arguments)
22
22
 
23
23
  if !k
@@ -1,25 +1,42 @@
1
- require "cbor"
1
+ # frozen_string_literal: true
2
+
2
3
  require "cose/recipient"
3
- require "cose/security_message"
4
+ require "cose/mac0"
4
5
 
5
6
  module COSE
6
- class Mac < SecurityMessage
7
- attr_reader :payload, :tag, :recipients
7
+ class Mac < Mac0
8
+ CONTEXT = "MAC"
9
+
10
+ attr_reader :recipients
8
11
 
9
12
  def self.keyword_arguments_for_initialize(decoded)
10
- {
11
- payload: CBOR.decode(decoded[0]),
12
- tag: decoded[1],
13
- recipients: decoded[2].map { |s| COSE::Recipient.deserialize(s) }
14
- }
13
+ super.merge(recipients: decoded.last.map { |r| COSE::Recipient.from_array(r) })
15
14
  end
16
15
 
17
- def initialize(payload:, tag:, recipients:, **keyword_arguments)
16
+ def self.tag
17
+ 97
18
+ end
19
+
20
+ def initialize(recipients:, **keyword_arguments)
18
21
  super(**keyword_arguments)
19
22
 
20
- @payload = payload
21
- @tag = tag
22
23
  @recipients = recipients
23
24
  end
25
+
26
+ def verify(key, external_aad = nil)
27
+ recipient = recipients.detect { |r| r.headers.kid == key.kid }
28
+
29
+ if recipient
30
+ super
31
+ else
32
+ raise(COSE::Error, "No recipient match the key")
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def context
39
+ CONTEXT
40
+ end
24
41
  end
25
42
  end
@@ -1,12 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cbor"
2
4
  require "cose/security_message"
5
+ require "openssl"
3
6
 
4
7
  module COSE
5
8
  class Mac0 < SecurityMessage
9
+ CONTEXT = "MAC0"
10
+
6
11
  attr_reader :payload, :tag
7
12
 
8
13
  def self.keyword_arguments_for_initialize(decoded)
9
- { payload: CBOR.decode(decoded[0]), tag: decoded[1] }
14
+ { payload: decoded[0], tag: decoded[1] }
15
+ end
16
+
17
+ def self.tag
18
+ 17
10
19
  end
11
20
 
12
21
  def initialize(payload:, tag:, **keyword_arguments)
@@ -15,5 +24,19 @@ module COSE
15
24
  @payload = payload
16
25
  @tag = tag
17
26
  end
27
+
28
+ def verify(key, external_aad = nil)
29
+ tag == algorithm.mac(key.k, data(external_aad)) || raise(COSE::Error, "Mac0 verification failed")
30
+ end
31
+
32
+ private
33
+
34
+ def data(external_aad = nil)
35
+ CBOR.encode([context, serialized_map(protected_headers), external_aad || zero_length_bin_string, payload])
36
+ end
37
+
38
+ def context
39
+ CONTEXT
40
+ end
18
41
  end
19
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cose/security_message"
2
4
 
3
5
  module COSE
@@ -1,26 +1,71 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cbor"
4
+ require "cose/algorithm"
5
+ require "cose/error"
6
+ require "cose/security_message/headers"
2
7
 
3
8
  module COSE
4
9
  class SecurityMessage
10
+ ZERO_LENGTH_BIN_STRING = "".b
11
+
5
12
  attr_reader :protected_headers, :unprotected_headers
6
13
 
7
14
  def self.deserialize(cbor)
8
15
  decoded = CBOR.decode(cbor)
9
16
 
10
- if decoded.respond_to?(:value)
17
+ if decoded.is_a?(CBOR::Tagged)
18
+ if respond_to?(:tag) && tag != decoded.tag
19
+ raise(COSE::Error, "Invalid CBOR tag")
20
+ end
21
+
11
22
  decoded = decoded.value
12
23
  end
13
24
 
25
+ from_array(decoded)
26
+ end
27
+
28
+ def self.from_array(array)
14
29
  new(
15
- protected_headers: CBOR.decode(decoded[0]),
16
- unprotected_headers: decoded[1],
17
- **keyword_arguments_for_initialize(decoded[2..-1])
30
+ protected_headers: deserialize_headers(array[0]),
31
+ unprotected_headers: array[1],
32
+ **keyword_arguments_for_initialize(array[2..-1])
18
33
  )
19
34
  end
20
35
 
36
+ def self.deserialize_headers(data)
37
+ if data == ZERO_LENGTH_BIN_STRING
38
+ {}
39
+ else
40
+ CBOR.decode(data)
41
+ end
42
+ end
43
+
21
44
  def initialize(protected_headers:, unprotected_headers:)
22
45
  @protected_headers = protected_headers
23
46
  @unprotected_headers = unprotected_headers
24
47
  end
48
+
49
+ def algorithm
50
+ @algorithm ||= COSE::Algorithm.find(headers.alg) || raise(COSE::Error, "Unsupported algorithm '#{headers.alg}'")
51
+ end
52
+
53
+ def headers
54
+ @headers ||= Headers.new(protected_headers, unprotected_headers)
55
+ end
56
+
57
+ private
58
+
59
+ def serialized_map(map)
60
+ if map && !map.empty?
61
+ map.to_cbor
62
+ else
63
+ zero_length_bin_string
64
+ end
65
+ end
66
+
67
+ def zero_length_bin_string
68
+ ZERO_LENGTH_BIN_STRING
69
+ end
25
70
  end
26
71
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module COSE
4
+ class SecurityMessage
5
+ class Headers
6
+ HEADER_LABEL_ALG = 1
7
+ HEADER_LABEL_KID = 4
8
+
9
+ attr_reader :protected_bucket, :unprotected_bucket
10
+
11
+ def initialize(protected_bucket, unprotected_bucket)
12
+ @protected_bucket = protected_bucket
13
+ @unprotected_bucket = unprotected_bucket
14
+ end
15
+
16
+ def alg
17
+ header(HEADER_LABEL_ALG)
18
+ end
19
+
20
+ def kid
21
+ header(HEADER_LABEL_KID)
22
+ end
23
+
24
+ private
25
+
26
+ def header(label)
27
+ protected_bucket[label] || unprotected_bucket[label]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,13 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cbor"
4
+ require "cose/error"
2
5
  require "cose/security_message"
3
6
  require "cose/signature"
4
7
 
5
8
  module COSE
6
9
  class Sign < SecurityMessage
10
+ CONTEXT = "Signature"
11
+
7
12
  attr_reader :payload, :signatures
8
13
 
9
14
  def self.keyword_arguments_for_initialize(decoded)
10
- { payload: CBOR.decode(decoded[0]), signatures: decoded[1].map { |s| COSE::Signature.deserialize(s) } }
15
+ { payload: decoded[0], signatures: decoded[1].map { |s| COSE::Signature.from_array(s) } }
16
+ end
17
+
18
+ def self.tag
19
+ 98
11
20
  end
12
21
 
13
22
  def initialize(payload:, signatures:, **keyword_arguments)
@@ -16,5 +25,30 @@ module COSE
16
25
  @payload = payload
17
26
  @signatures = signatures
18
27
  end
28
+
29
+ def verify(key, external_aad = nil)
30
+ signature = signatures.detect { |s| s.headers.kid == key.kid }
31
+
32
+ if signature
33
+ signature.algorithm.verify(key, signature.signature, verification_data(signature, external_aad))
34
+ else
35
+ raise(COSE::Error, "No signature matches key kid")
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def verification_data(signature, external_aad = nil)
42
+ @verification_data ||=
43
+ CBOR.encode(
44
+ [
45
+ CONTEXT,
46
+ serialized_map(protected_headers),
47
+ serialized_map(signature.protected_headers),
48
+ external_aad || ZERO_LENGTH_BIN_STRING,
49
+ payload
50
+ ]
51
+ )
52
+ end
19
53
  end
20
54
  end
@@ -1,12 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cbor"
4
+ require "cose/error"
2
5
  require "cose/security_message"
3
6
 
4
7
  module COSE
5
8
  class Sign1 < SecurityMessage
9
+ CONTEXT = "Signature1"
10
+
6
11
  attr_reader :payload, :signature
7
12
 
8
13
  def self.keyword_arguments_for_initialize(decoded)
9
- { payload: CBOR.decode(decoded[0]), signature: decoded[1] }
14
+ { payload: decoded[0], signature: decoded[1] }
15
+ end
16
+
17
+ def self.tag
18
+ 18
10
19
  end
11
20
 
12
21
  def initialize(payload:, signature:, **keyword_arguments)
@@ -15,5 +24,19 @@ module COSE
15
24
  @payload = payload
16
25
  @signature = signature
17
26
  end
27
+
28
+ def verify(key, external_aad = nil)
29
+ if key.kid == headers.kid
30
+ algorithm.verify(key, signature, verification_data(external_aad))
31
+ else
32
+ raise(COSE::Error, "Non matching kid")
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def verification_data(external_aad = nil)
39
+ CBOR.encode([CONTEXT, serialized_map(protected_headers), external_aad || ZERO_LENGTH_BIN_STRING, payload])
40
+ end
18
41
  end
19
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "cose/security_message"
2
4
 
3
5
  module COSE
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module COSE
4
- VERSION = "0.8.0"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gonzalo Rodriguez
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-08-17 00:00:00.000000000 Z
12
+ date: 2020-07-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cbor
@@ -25,6 +25,20 @@ dependencies:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
27
  version: 0.5.9
28
+ - !ruby/object:Gem::Dependency
29
+ name: openssl-signature_algorithm
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: appraisal
30
44
  requirement: !ruby/object:Gem::Requirement
@@ -63,30 +77,30 @@ dependencies:
63
77
  name: byebug
64
78
  requirement: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - ">="
80
+ - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '10.0'
82
+ version: '11.0'
69
83
  type: :development
70
84
  prerelease: false
71
85
  version_requirements: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - ">="
87
+ - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: '10.0'
89
+ version: '11.0'
76
90
  - !ruby/object:Gem::Dependency
77
91
  name: rake
78
92
  requirement: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: '12.3'
96
+ version: '13.0'
83
97
  type: :development
84
98
  prerelease: false
85
99
  version_requirements: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '12.3'
103
+ version: '13.0'
90
104
  - !ruby/object:Gem::Dependency
91
105
  name: rspec
92
106
  requirement: !ruby/object:Gem::Requirement
@@ -107,28 +121,28 @@ dependencies:
107
121
  requirements:
108
122
  - - '='
109
123
  - !ruby/object:Gem::Version
110
- version: 0.68.0
124
+ version: 0.80.1
111
125
  type: :development
112
126
  prerelease: false
113
127
  version_requirements: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - '='
116
130
  - !ruby/object:Gem::Version
117
- version: 0.68.0
131
+ version: 0.80.1
118
132
  - !ruby/object:Gem::Dependency
119
133
  name: rubocop-performance
120
134
  requirement: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
- version: '1.3'
138
+ version: '1.4'
125
139
  type: :development
126
140
  prerelease: false
127
141
  version_requirements: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - "~>"
130
144
  - !ruby/object:Gem::Version
131
- version: '1.3'
145
+ version: '1.4'
132
146
  description:
133
147
  email:
134
148
  - gonzalo@cedarcode.com
@@ -138,6 +152,7 @@ extensions: []
138
152
  extra_rdoc_files: []
139
153
  files:
140
154
  - ".gitignore"
155
+ - ".gitmodules"
141
156
  - ".rspec"
142
157
  - ".rubocop.yml"
143
158
  - ".travis.yml"
@@ -153,11 +168,19 @@ files:
153
168
  - cose.gemspec
154
169
  - gemfiles/openssl_2_0.gemfile
155
170
  - gemfiles/openssl_2_1.gemfile
171
+ - gemfiles/openssl_2_2.gemfile
156
172
  - gemfiles/openssl_default.gemfile
157
173
  - gemfiles/openssl_head.gemfile
158
174
  - lib/cose.rb
175
+ - lib/cose/algorithm.rb
176
+ - lib/cose/algorithm/base.rb
177
+ - lib/cose/algorithm/ecdsa.rb
178
+ - lib/cose/algorithm/hmac.rb
179
+ - lib/cose/algorithm/rsa_pss.rb
180
+ - lib/cose/algorithm/signature_algorithm.rb
159
181
  - lib/cose/encrypt.rb
160
182
  - lib/cose/encrypt0.rb
183
+ - lib/cose/error.rb
161
184
  - lib/cose/key.rb
162
185
  - lib/cose/key/base.rb
163
186
  - lib/cose/key/curve.rb
@@ -170,6 +193,7 @@ files:
170
193
  - lib/cose/mac0.rb
171
194
  - lib/cose/recipient.rb
172
195
  - lib/cose/security_message.rb
196
+ - lib/cose/security_message/headers.rb
173
197
  - lib/cose/sign.rb
174
198
  - lib/cose/sign1.rb
175
199
  - lib/cose/signature.rb
@@ -189,14 +213,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
213
  requirements:
190
214
  - - ">="
191
215
  - !ruby/object:Gem::Version
192
- version: '2.2'
216
+ version: '2.4'
193
217
  required_rubygems_version: !ruby/object:Gem::Requirement
194
218
  requirements:
195
219
  - - ">="
196
220
  - !ruby/object:Gem::Version
197
221
  version: '0'
198
222
  requirements: []
199
- rubygems_version: 3.0.6
223
+ rubygems_version: 3.1.4
200
224
  signing_key:
201
225
  specification_version: 4
202
226
  summary: Ruby implementation of RFC 8152 CBOR Object Signing and Encryption (COSE)