cose 0.8.0 → 0.9.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: ad1f050dae008442fcd66013cf0b0dcd6c192b66c8fb308b7aeed1fdd53687f7
4
+ data.tar.gz: 2917bb5f18b73c9651ca33946aea646f72fcfb3d3ee84dc26bf653b7e2573a71
5
5
  SHA512:
6
- metadata.gz: 536b1620403cf8f2a6d54ffd8c7918fc0196e3fbb14b0c8ccf99ad85620184463cc92140b6ad1e810d06afeb84c4b9ce03d17c926a316270df2a3eb77f922f10
7
- data.tar.gz: 230af044b097a08fd714bb180ce41e3df194c6c880f046d7628acb984d954c0f7308a5e9a89c52705c0e9341f2404f24753a4cc5e47da89784d21a7b32734a23
6
+ metadata.gz: da3842878fc2de2d47006aeea49fdc644197226362de585f4b1692929676d0fa048567b29091682ede9f31aeda0a704db88a79f0773548342720c4bcb5a98969
7
+ data.tar.gz: edf9e3bef367c32a46c8a554a422df690c2156fcabc61c55c658cfa1f0b67f70471a9566052574c92bb640002196dc33244a19c86bb7368d00c35447142a4063
@@ -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
@@ -6,7 +6,7 @@ inherit_mode:
6
6
  - Exclude
7
7
 
8
8
  AllCops:
9
- TargetRubyVersion: 2.2
9
+ TargetRubyVersion: 2.3
10
10
  DisabledByDefault: true
11
11
  Exclude:
12
12
  - "gemfiles/**/*"
@@ -5,11 +5,10 @@ cache: bundler
5
5
  rvm:
6
6
  - ruby-head
7
7
  - 2.7.0-preview1
8
- - 2.6.3
9
- - 2.5.5
10
- - 2.4.6
8
+ - 2.6.4
9
+ - 2.5.6
10
+ - 2.4.7
11
11
  - 2.3.8
12
- - 2.2.10
13
12
 
14
13
  gemfile:
15
14
  - gemfiles/openssl_head.gemfile
@@ -17,19 +16,11 @@ 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
25
  - rvm: 2.7.0-preview1
27
- - rvm: 2.2.10
28
26
  - 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,3 +1,5 @@
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
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.9.0] - 2019-08-31
4
+
5
+ ### Added
6
+
7
+ - `COSE::Sign1#verify`
8
+ - `COSE::Sign#verify`
9
+ - `COSE::Mac0#verify`
10
+ - `COSE::Mac#verify`
11
+
3
12
  ## [v0.8.0] - 2019-08-17
4
13
 
5
14
  ### Added
@@ -86,6 +95,7 @@
86
95
  - EC2 key object
87
96
  - Works with ruby 2.5
88
97
 
98
+ [v0.8.0]: https://github.com/cedarcode/cose-ruby/compare/v0.8.0...v0.9.0/
89
99
  [v0.8.0]: https://github.com/cedarcode/cose-ruby/compare/v0.7.0...v0.8.0/
90
100
  [v0.7.0]: https://github.com/cedarcode/cose-ruby/compare/v0.6.1...v0.7.0/
91
101
  [v0.6.1]: https://github.com/cedarcode/cose-ruby/compare/v0.6.0...v0.6.1/
data/README.md CHANGED
@@ -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
 
@@ -29,7 +29,7 @@ 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.3"
33
33
 
34
34
  spec.add_dependency "cbor", "~> 0.5.9"
35
35
 
@@ -38,6 +38,6 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency "byebug", ">= 10.0"
39
39
  spec.add_development_dependency "rake", "~> 12.3"
40
40
  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"
41
+ spec.add_development_dependency "rubocop", "0.74.0"
42
+ spec.add_development_dependency "rubocop-performance", "~> 1.4"
43
43
  end
@@ -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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module COSE
4
+ module Algorithm
5
+ class Base
6
+ BYTE_LENGTH = 8
7
+
8
+ attr_reader :id, :name
9
+
10
+ def initialize(id, name)
11
+ @id = id
12
+ @name = name
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,62 @@
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
+
8
+ module COSE
9
+ module Algorithm
10
+ class ECDSA < SignatureAlgorithm
11
+ attr_reader :hash_function
12
+
13
+ def initialize(*args, hash_function:)
14
+ super(*args)
15
+
16
+ @hash_function = hash_function
17
+ end
18
+
19
+ def compatible_key?(key)
20
+ to_pkey(key)
21
+ rescue COSE::Error
22
+ false
23
+ end
24
+
25
+ private
26
+
27
+ def valid_signature?(key, signature, verification_data)
28
+ pkey = to_pkey(key)
29
+
30
+ pkey.verify(hash_function, in_der(signature, pkey.group.degree), verification_data)
31
+ end
32
+
33
+ def to_pkey(key)
34
+ case key
35
+ when COSE::Key::EC2
36
+ key.to_pkey
37
+ when OpenSSL::PKey::EC
38
+ key
39
+ else
40
+ raise(COSE::Error, "Incompatible key for algorithm")
41
+ end
42
+ end
43
+
44
+ # Borrowed from jwt rubygem.
45
+ # https://github.com/jwt/ruby-jwt/blob/7a6a3f1dbaff806993156d1dff9c217bb2523ff8/lib/jwt/security_utils.rb#L34-L39
46
+ #
47
+ # Hopefully this will be provided by openssl rubygem in the future.
48
+ def in_der(signature, key_length)
49
+ n = (key_length.to_f / BYTE_LENGTH).ceil
50
+
51
+ if signature.size == n * 2
52
+ r = signature[0..(n - 1)]
53
+ s = signature[n..-1]
54
+
55
+ OpenSSL::ASN1::Sequence.new([r, s].map { |int| OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(int, 2)) }).to_der
56
+ else
57
+ signature
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
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
+ attr_reader :hash_function, :tag_length
10
+
11
+ def initialize(*args, hash_function:, tag_length:)
12
+ super(*args)
13
+
14
+ @hash_function = hash_function
15
+ @tag_length = tag_length
16
+ end
17
+
18
+ def mac(key, to_be_signed)
19
+ mac = OpenSSL::HMAC.digest(hash_function, key, to_be_signed)
20
+
21
+ if tag_bytesize && tag_bytesize < mac.bytesize
22
+ mac.byteslice(0, tag_bytesize)
23
+ else
24
+ mac
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def tag_bytesize
31
+ @tag_bytesize ||=
32
+ if tag_length
33
+ tag_length / BYTE_LENGTH
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,50 @@
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
+
8
+ module COSE
9
+ module Algorithm
10
+ class RSAPSS < SignatureAlgorithm
11
+ attr_reader :hash_function, :salt_length
12
+
13
+ def initialize(*args, hash_function:, salt_length:)
14
+ super(*args)
15
+
16
+ @hash_function = hash_function
17
+ @salt_length = salt_length
18
+ end
19
+
20
+ def compatible_key?(key)
21
+ to_pkey(key)
22
+ rescue COSE::Error
23
+ false
24
+ end
25
+
26
+ private
27
+
28
+ def valid_signature?(key, signature, verification_data)
29
+ pkey = to_pkey(key)
30
+
31
+ if pkey.respond_to?(:verify_pss)
32
+ pkey.verify_pss(hash_function, signature, verification_data, salt_length: :digest, mgf1_hash: hash_function)
33
+ else
34
+ raise(COSE::Error, "Update to openssl gem >= v2.1 to have RSA-PSS support")
35
+ end
36
+ end
37
+
38
+ def to_pkey(key)
39
+ case key
40
+ when COSE::Key::RSA
41
+ key.to_pkey
42
+ when OpenSSL::PKey::RSA
43
+ key
44
+ else
45
+ raise(COSE::Error, "Incompatible key for algorithm")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
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
+ valid_signature?(key, signature, verification_data) || raise(COSE::Error, "Signature verification failed")
11
+ end
12
+ end
13
+ end
14
+ 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"
@@ -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
@@ -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 = "0.9.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: 0.9.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: 2019-08-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cbor
@@ -107,28 +107,28 @@ dependencies:
107
107
  requirements:
108
108
  - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: 0.68.0
110
+ version: 0.74.0
111
111
  type: :development
112
112
  prerelease: false
113
113
  version_requirements: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - '='
116
116
  - !ruby/object:Gem::Version
117
- version: 0.68.0
117
+ version: 0.74.0
118
118
  - !ruby/object:Gem::Dependency
119
119
  name: rubocop-performance
120
120
  requirement: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '1.3'
124
+ version: '1.4'
125
125
  type: :development
126
126
  prerelease: false
127
127
  version_requirements: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '1.3'
131
+ version: '1.4'
132
132
  description:
133
133
  email:
134
134
  - gonzalo@cedarcode.com
@@ -138,6 +138,7 @@ extensions: []
138
138
  extra_rdoc_files: []
139
139
  files:
140
140
  - ".gitignore"
141
+ - ".gitmodules"
141
142
  - ".rspec"
142
143
  - ".rubocop.yml"
143
144
  - ".travis.yml"
@@ -156,8 +157,15 @@ files:
156
157
  - gemfiles/openssl_default.gemfile
157
158
  - gemfiles/openssl_head.gemfile
158
159
  - lib/cose.rb
160
+ - lib/cose/algorithm.rb
161
+ - lib/cose/algorithm/base.rb
162
+ - lib/cose/algorithm/ecdsa.rb
163
+ - lib/cose/algorithm/hmac.rb
164
+ - lib/cose/algorithm/rsa_pss.rb
165
+ - lib/cose/algorithm/signature_algorithm.rb
159
166
  - lib/cose/encrypt.rb
160
167
  - lib/cose/encrypt0.rb
168
+ - lib/cose/error.rb
161
169
  - lib/cose/key.rb
162
170
  - lib/cose/key/base.rb
163
171
  - lib/cose/key/curve.rb
@@ -170,6 +178,7 @@ files:
170
178
  - lib/cose/mac0.rb
171
179
  - lib/cose/recipient.rb
172
180
  - lib/cose/security_message.rb
181
+ - lib/cose/security_message/headers.rb
173
182
  - lib/cose/sign.rb
174
183
  - lib/cose/sign1.rb
175
184
  - lib/cose/signature.rb
@@ -189,7 +198,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
198
  requirements:
190
199
  - - ">="
191
200
  - !ruby/object:Gem::Version
192
- version: '2.2'
201
+ version: '2.3'
193
202
  required_rubygems_version: !ruby/object:Gem::Requirement
194
203
  requirements:
195
204
  - - ">="