cose-1.3.1/0000755000004100000410000000000014700627371012504 5ustar www-datawww-datacose-1.3.1/bin/0000755000004100000410000000000014700627371013254 5ustar www-datawww-datacose-1.3.1/bin/setup0000755000004100000410000000025414700627371014343 0ustar www-datawww-data#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx git submodule update --init --recursive bundle install # Do any other automated setup that you need to do here cose-1.3.1/bin/console0000755000004100000410000000056114700627371014646 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require "bundler/setup" require "cose" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) cose-1.3.1/.gitignore0000644000004100000410000000025214700627371014473 0ustar www-datawww-data/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ # rspec failure tracking .rspec_status /Gemfile.lock /gemfiles/*.gemfile.lock .byebug_history cose-1.3.1/.gitmodules0000644000004100000410000000017714700627371014666 0ustar www-datawww-data[submodule "spec/fixtures/cose-wg-examples"] path = spec/fixtures/cose-wg-examples url = https://github.com/cose-wg/Examples cose-1.3.1/SECURITY.md0000644000004100000410000000060014700627371014271 0ustar www-datawww-data# Security Policy ## Supported Versions | Version | Supported | | ------- | ------------------ | | 0.9.z | :white_check_mark: | | 0.8.z | :white_check_mark: | | < 0.8 | :x: | ## Reporting a Vulnerability If you have discovered a security bug, please send an email to security@cedarcode.com instead of posting to the GitHub issue tracker. Thank you! cose-1.3.1/.github/0000755000004100000410000000000014700627371014044 5ustar www-datawww-datacose-1.3.1/.github/workflows/0000755000004100000410000000000014700627371016101 5ustar www-datawww-datacose-1.3.1/.github/workflows/build.yml0000644000004100000410000000214014700627371017720 0ustar www-datawww-data# This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby name: build on: push jobs: test: runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: ruby: - 3.0.0 - 2.7.2 - 2.6.6 - 2.5.8 - 2.4.10 gemfile: - openssl_3_0 - openssl_2_2 - openssl_2_1 - openssl_default exclude: - ruby: '2.4.10' gemfile: openssl_3_0 - ruby: '2.5.8' gemfile: openssl_3_0 env: BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake cose-1.3.1/lib/0000755000004100000410000000000014700627371013252 5ustar www-datawww-datacose-1.3.1/lib/cose/0000755000004100000410000000000014700627371014203 5ustar www-datawww-datacose-1.3.1/lib/cose/sign1.rb0000644000004100000410000000164614700627371015560 0ustar www-datawww-data# frozen_string_literal: true require "cbor" require "cose/error" require "cose/security_message" module COSE class Sign1 < SecurityMessage CONTEXT = "Signature1" attr_reader :payload, :signature def self.keyword_arguments_for_initialize(decoded) { payload: decoded[0], signature: decoded[1] } end def self.tag 18 end def initialize(payload:, signature:, **keyword_arguments) super(**keyword_arguments) @payload = payload @signature = signature end def verify(key, external_aad = nil) if key.kid == headers.kid algorithm.verify(key, signature, verification_data(external_aad)) else raise(COSE::Error, "Non matching kid") end end private def verification_data(external_aad = nil) CBOR.encode([CONTEXT, serialized_map(protected_headers), external_aad || ZERO_LENGTH_BIN_STRING, payload]) end end end cose-1.3.1/lib/cose/security_message/0000755000004100000410000000000014700627371017556 5ustar www-datawww-datacose-1.3.1/lib/cose/security_message/headers.rb0000644000004100000410000000113614700627371021517 0ustar www-datawww-data# frozen_string_literal: true module COSE class SecurityMessage class Headers HEADER_LABEL_ALG = 1 HEADER_LABEL_KID = 4 attr_reader :protected_bucket, :unprotected_bucket def initialize(protected_bucket, unprotected_bucket) @protected_bucket = protected_bucket @unprotected_bucket = unprotected_bucket end def alg header(HEADER_LABEL_ALG) end def kid header(HEADER_LABEL_KID) end private def header(label) protected_bucket[label] || unprotected_bucket[label] end end end end cose-1.3.1/lib/cose/mac.rb0000644000004100000410000000137114700627371015272 0ustar www-datawww-data# frozen_string_literal: true require "cose/recipient" require "cose/mac0" module COSE class Mac < Mac0 CONTEXT = "MAC" attr_reader :recipients def self.keyword_arguments_for_initialize(decoded) super.merge(recipients: decoded.last.map { |r| COSE::Recipient.from_array(r) }) end def self.tag 97 end def initialize(recipients:, **keyword_arguments) super(**keyword_arguments) @recipients = recipients end def verify(key, external_aad = nil) recipient = recipients.detect { |r| r.headers.kid == key.kid } if recipient super else raise(COSE::Error, "No recipient match the key") end end private def context CONTEXT end end end cose-1.3.1/lib/cose/error.rb0000644000004100000410000000012214700627371015654 0ustar www-datawww-data# frozen_string_literal: true module COSE class Error < StandardError; end end cose-1.3.1/lib/cose/mac0.rb0000644000004100000410000000152214700627371015350 0ustar www-datawww-data# frozen_string_literal: true require "cbor" require "cose/security_message" require "openssl" module COSE class Mac0 < SecurityMessage CONTEXT = "MAC0" attr_reader :payload, :tag def self.keyword_arguments_for_initialize(decoded) { payload: decoded[0], tag: decoded[1] } end def self.tag 17 end def initialize(payload:, tag:, **keyword_arguments) super(**keyword_arguments) @payload = payload @tag = tag end def verify(key, external_aad = nil) tag == algorithm.mac(key.k, data(external_aad)) || raise(COSE::Error, "Mac0 verification failed") end private def data(external_aad = nil) CBOR.encode([context, serialized_map(protected_headers), external_aad || zero_length_bin_string, payload]) end def context CONTEXT end end end cose-1.3.1/lib/cose/key/0000755000004100000410000000000014700627371014773 5ustar www-datawww-datacose-1.3.1/lib/cose/key/okp.rb0000644000004100000410000000413614700627371016115 0ustar www-datawww-data# frozen_string_literal: true require "cose/key/curve" require "cose/key/curve_key" require "openssl" module COSE module Key class OKP < CurveKey KTY_OKP = 1 def self.enforce_type(map) if map[LABEL_KTY] != KTY_OKP raise "Not an OKP key" end end def self.from_pkey(pkey) curve = Curve.by_pkey_name(pkey.oid) || raise("Unsupported edwards curve #{pkey.oid}") attributes = { crv: curve.id } asymmetric_key = pkey.public_to_der public_key_bit_string = OpenSSL::ASN1.decode(asymmetric_key).value.last.value attributes[:x] = public_key_bit_string begin asymmetric_key = pkey.private_to_der private_key = OpenSSL::ASN1.decode(asymmetric_key).value.last.value curve_private_key = OpenSSL::ASN1.decode(private_key).value attributes[:d] = curve_private_key rescue OpenSSL::PKey::PKeyError # work around lack of https://github.com/ruby/openssl/pull/527, otherwise raises this error # with message 'i2d_PKCS8PrivateKey_bio: error converting private key' for public keys nil end new(**attributes) end def map super.merge(LABEL_KTY => KTY_OKP) end def to_pkey if curve private_key_algo = OpenSSL::ASN1::Sequence.new( [OpenSSL::ASN1::ObjectId.new(curve.pkey_name)] ) seq = if d version = OpenSSL::ASN1::Integer.new(0) curve_private_key = OpenSSL::ASN1::OctetString.new(d).to_der private_key = OpenSSL::ASN1::OctetString.new(curve_private_key) [version, private_key_algo, private_key] else public_key = OpenSSL::ASN1::BitString.new(x) [private_key_algo, public_key] end asymmetric_key = OpenSSL::ASN1::Sequence.new(seq) OpenSSL::PKey.read(asymmetric_key.to_der) else raise "Unsupported curve #{crv}" end end def curve Curve.find(crv) end end end end cose-1.3.1/lib/cose/key/rsa.rb0000644000004100000410000000705214700627371016111 0ustar www-datawww-data# frozen_string_literal: true require "cose/key/base" require "openssl" module COSE module Key class RSA < Base LABEL_N = -1 LABEL_E = -2 LABEL_D = -3 LABEL_P = -4 LABEL_Q = -5 LABEL_DP = -6 LABEL_DQ = -7 LABEL_QINV = -8 KTY_RSA = 3 def self.enforce_type(map) if map[LABEL_KTY] != KTY_RSA raise "Not an RSA key" end end def self.from_pkey(pkey) params = pkey.params attributes = { n: params["n"].to_s(2), e: params["e"].to_s(2) } if pkey.private? attributes.merge!( d: params["d"].to_s(2), p: params["p"].to_s(2), q: params["q"].to_s(2), dp: params["dmp1"].to_s(2), dq: params["dmq1"].to_s(2), qinv: params["iqmp"].to_s(2) ) end new(**attributes) end attr_reader :n, :e, :d, :p, :q, :dp, :dq, :qinv def initialize(n:, e:, d: nil, p: nil, q: nil, dp: nil, dq: nil, qinv: nil, **keyword_arguments) # rubocop:disable Naming/MethodParameterName super(**keyword_arguments) if !n raise ArgumentError, "Required public field n is missing" elsif !e raise ArgumentError, "Required public field e is missing" else private_fields = { d: d, p: p, q: q, dp: dp, dq: dq, qinv: qinv } if private_fields.values.all?(&:nil?) || private_fields.values.none?(&:nil?) @n = n @e = e @d = d @p = p @q = q @dp = dp @dq = dq @qinv = qinv else missing = private_fields.detect { |_k, v| v.nil? }[0] raise ArgumentError, "Incomplete private fields, #{missing} is missing" end end end def map super.merge( Base::LABEL_KTY => KTY_RSA, LABEL_N => n, LABEL_E => e, LABEL_D => d, LABEL_P => p, LABEL_Q => q, LABEL_DP => dp, LABEL_DQ => dq, LABEL_QINV => qinv ).compact end def to_pkey # PKCS#1 RSAPublicKey asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer.new(bn(n)), OpenSSL::ASN1::Integer.new(bn(e)), ] ) pkey = OpenSSL::PKey::RSA.new(asn1.to_der) if private? # PKCS#1 RSAPrivateKey asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer.new(0), OpenSSL::ASN1::Integer.new(bn(n)), OpenSSL::ASN1::Integer.new(bn(e)), OpenSSL::ASN1::Integer.new(bn(d)), OpenSSL::ASN1::Integer.new(bn(p)), OpenSSL::ASN1::Integer.new(bn(q)), OpenSSL::ASN1::Integer.new(bn(dp)), OpenSSL::ASN1::Integer.new(bn(dq)), OpenSSL::ASN1::Integer.new(bn(qinv)), ] ) pkey = OpenSSL::PKey::RSA.new(asn1.to_der) end pkey end def self.keyword_arguments_for_initialize(map) { n: map[LABEL_N], e: map[LABEL_E], d: map[LABEL_D], p: map[LABEL_P], q: map[LABEL_Q], dp: map[LABEL_DP], dq: map[LABEL_DQ], qinv: map[LABEL_QINV] } end private def private? [d, p, q, dp, dq, qinv].none?(&:nil?) end def bn(data) if data OpenSSL::BN.new(data, 2) end end end end end cose-1.3.1/lib/cose/key/symmetric.rb0000644000004100000410000000137714700627371017344 0ustar www-datawww-data# frozen_string_literal: true require "cose/key/base" module COSE module Key class Symmetric < Base LABEL_K = -1 KTY_SYMMETRIC = 4 attr_reader :k def self.enforce_type(map) if map[LABEL_KTY] != KTY_SYMMETRIC raise "Not a Symmetric key" end end def initialize(k:, **keyword_arguments) # rubocop:disable Naming/MethodParameterName super(**keyword_arguments) if !k raise ArgumentError, "Required key value k is missing" else @k = k end end def map super.merge(LABEL_KTY => KTY_SYMMETRIC, LABEL_K => k) end def self.keyword_arguments_for_initialize(map) { k: map[LABEL_K] } end end end end cose-1.3.1/lib/cose/key/base.rb0000644000004100000410000000201214700627371016225 0ustar www-datawww-data# frozen_string_literal: true require "cbor" module COSE module Key class Base LABEL_BASE_IV = 5 LABEL_KEY_OPS = 4 LABEL_ALG = 3 LABEL_KID = 2 LABEL_KTY = 1 def self.deserialize(cbor) from_map(CBOR.decode(cbor)) end def self.from_map(map) enforce_type(map) new( base_iv: map[LABEL_BASE_IV], key_ops: map[LABEL_KEY_OPS], alg: map[LABEL_ALG], kid: map[LABEL_KID], **keyword_arguments_for_initialize(map) ) end attr_accessor :kid, :alg, :key_ops, :base_iv def initialize(kid: nil, alg: nil, key_ops: nil, base_iv: nil) @kid = kid @alg = alg @key_ops = key_ops @base_iv = base_iv end def serialize CBOR.encode(map) end def map { LABEL_BASE_IV => base_iv, LABEL_KEY_OPS => key_ops, LABEL_ALG => alg, LABEL_KID => kid, }.compact end end end end cose-1.3.1/lib/cose/key/curve_key.rb0000644000004100000410000000166314700627371017322 0ustar www-datawww-data# frozen_string_literal: true require "cose/key/base" require "openssl" module COSE module Key class CurveKey < Base LABEL_CRV = -1 LABEL_X = -2 LABEL_D = -4 attr_reader :crv, :d, :x def self.keyword_arguments_for_initialize(map) { crv: map[LABEL_CRV], x: map[LABEL_X], d: map[LABEL_D] } end def initialize(crv:, x: nil, d: nil, **keyword_arguments) # rubocop:disable Naming/MethodParameterName super(**keyword_arguments) if !crv raise ArgumentError, "Required crv is missing" elsif !x && !d raise ArgumentError, "x and d cannot be missing simultaneously" else @crv = crv @x = x @d = d end end def map super.merge( LABEL_CRV => crv, LABEL_X => x, LABEL_D => d ).compact end end end end cose-1.3.1/lib/cose/key/curve.rb0000644000004100000410000000163414700627371016450 0ustar www-datawww-data# frozen_string_literal: true module COSE module Key # https://tools.ietf.org/html/rfc8152#section-13.1 Curve = Struct.new(:id, :name, :pkey_name) do @curves = {} def self.register(id, name, pkey_name) @curves[id] = new(id, name, pkey_name) end def self.find(id) @curves[id] end def self.by_name(name) @curves.values.detect { |curve| curve.name == name } end def self.by_pkey_name(pkey_name) @curves.values.detect { |curve| curve.pkey_name == pkey_name } end def value id end end end end COSE::Key::Curve.register(1, "P-256", "prime256v1") COSE::Key::Curve.register(2, "P-384", "secp384r1") COSE::Key::Curve.register(3, "P-521", "secp521r1") COSE::Key::Curve.register(6, "Ed25519", "ED25519") COSE::Key::Curve.register(7, "Ed448", "ED448") COSE::Key::Curve.register(8, "secp256k1", "secp256k1") cose-1.3.1/lib/cose/key/ec2.rb0000644000004100000410000000656414700627371016004 0ustar www-datawww-data# frozen_string_literal: true require "cose/key/curve" require "cose/key/curve_key" require "openssl" module COSE module Key class EC2 < CurveKey LABEL_Y = -3 KTY_EC2 = 2 ZERO_BYTE = "\0".b def self.enforce_type(map) if map[LABEL_KTY] != KTY_EC2 raise "Not an EC2 key" end end def self.from_pkey(pkey) curve = Curve.by_pkey_name(pkey.group.curve_name) || raise("Unsupported EC curve #{pkey.group.curve_name}") case pkey when OpenSSL::PKey::EC::Point public_key = pkey when OpenSSL::PKey::EC public_key = pkey.public_key private_key = pkey.private_key else raise "Unsupported" end if public_key bytes = public_key.to_bn.to_s(2)[1..-1] coordinate_length = bytes.size / 2 x = bytes[0..(coordinate_length - 1)] y = bytes[coordinate_length..-1] end if private_key d = private_key.to_s(2) end new(crv: curve.id, x: x, y: y, d: d) end attr_reader :y def initialize(y: nil, **keyword_arguments) # rubocop:disable Naming/MethodParameterName if (!y || !keyword_arguments[:x]) && !keyword_arguments[:d] raise ArgumentError, "Both x and y are required if d is missing" else super(**keyword_arguments) @y = y end end def map super.merge( Base::LABEL_KTY => KTY_EC2, LABEL_Y => y, ).compact end def to_pkey if curve group = OpenSSL::PKey::EC::Group.new(curve.pkey_name) public_key_bn = OpenSSL::BN.new("\x04" + pad_coordinate(group, x) + pad_coordinate(group, y), 2) public_key_point = OpenSSL::PKey::EC::Point.new(group, public_key_bn) # RFC5480 SubjectPublicKeyInfo asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId(curve.pkey_name), ] ), OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed)) ] ) if d # RFC5915 ECPrivateKey asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Integer.new(1), # Not properly padded but OpenSSL doesn't mind OpenSSL::ASN1::OctetString(OpenSSL::BN.new(d, 2).to_s(2)), OpenSSL::ASN1::ObjectId(curve.pkey_name, 0, :EXPLICIT), OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed), 1, :EXPLICIT), ] ) der = asn1.to_der return OpenSSL::PKey::EC.new(der) end OpenSSL::PKey::EC.new(asn1.to_der) else raise "Unsupported curve #{crv}" end end def curve Curve.find(crv) end def self.keyword_arguments_for_initialize(map) super.merge(y: map[LABEL_Y]) end def pad_coordinate(group, coordinate) coordinate_length = (group.degree + 7) / 8 padding_required = coordinate_length - coordinate.length return coordinate if padding_required <= 0 (ZERO_BYTE * padding_required) + coordinate end end end end cose-1.3.1/lib/cose/sign.rb0000644000004100000410000000240314700627371015467 0ustar www-datawww-data# frozen_string_literal: true require "cbor" require "cose/error" require "cose/security_message" require "cose/signature" module COSE class Sign < SecurityMessage CONTEXT = "Signature" attr_reader :payload, :signatures def self.keyword_arguments_for_initialize(decoded) { payload: decoded[0], signatures: decoded[1].map { |s| COSE::Signature.from_array(s) } } end def self.tag 98 end def initialize(payload:, signatures:, **keyword_arguments) super(**keyword_arguments) @payload = payload @signatures = signatures end def verify(key, external_aad = nil) signature = signatures.detect { |s| s.headers.kid == key.kid } if signature signature.algorithm.verify(key, signature.signature, verification_data(signature, external_aad)) else raise(COSE::Error, "No signature matches key kid") end end private def verification_data(signature, external_aad = nil) @verification_data ||= CBOR.encode( [ CONTEXT, serialized_map(protected_headers), serialized_map(signature.protected_headers), external_aad || ZERO_LENGTH_BIN_STRING, payload ] ) end end end cose-1.3.1/lib/cose/algorithm/0000755000004100000410000000000014700627371016171 5ustar www-datawww-datacose-1.3.1/lib/cose/algorithm/hmac.rb0000644000004100000410000000141514700627371017427 0ustar www-datawww-data# frozen_string_literal: true require "cose/algorithm/base" require "openssl" module COSE module Algorithm class HMAC < Base BYTE_LENGTH = 8 attr_reader :hash_function, :tag_length def initialize(*args, hash_function:, tag_length:) super(*args) @hash_function = hash_function @tag_length = tag_length end def mac(key, to_be_signed) mac = OpenSSL::HMAC.digest(hash_function, key, to_be_signed) if tag_bytesize && tag_bytesize < mac.bytesize mac.byteslice(0, tag_bytesize) else mac end end private def tag_bytesize @tag_bytesize ||= if tag_length tag_length / BYTE_LENGTH end end end end end cose-1.3.1/lib/cose/algorithm/eddsa.rb0000644000004100000410000000154614700627371017604 0ustar www-datawww-data# frozen_string_literal: true require "cose/algorithm/signature_algorithm" require "cose/error" require "cose/key/okp" require "openssl" module COSE module Algorithm class EdDSA < SignatureAlgorithm private def valid_key?(key) cose_key = to_cose_key(key) cose_key.is_a?(COSE::Key::OKP) && (!cose_key.alg || cose_key.alg == id) end def to_pkey(key) case key when COSE::Key::OKP key.to_pkey when OpenSSL::PKey::PKey key else raise(COSE::Error, "Incompatible key for algorithm") end end def valid_signature?(key, signature, verification_data) pkey = to_pkey(key) begin pkey.verify(nil, signature, verification_data) rescue OpenSSL::PKey::PKeyError false end end end end end cose-1.3.1/lib/cose/algorithm/rsa_pss.rb0000644000004100000410000000161414700627371020172 0ustar www-datawww-data# frozen_string_literal: true require "cose/algorithm/signature_algorithm" require "cose/key/rsa" require "cose/error" require "openssl" require "openssl/signature_algorithm/rsapss" module COSE module Algorithm class RSAPSS < SignatureAlgorithm attr_reader :hash_function, :salt_length def initialize(*args, hash_function:, salt_length:) super(*args) @hash_function = hash_function @salt_length = salt_length end private def valid_key?(key) to_cose_key(key).is_a?(COSE::Key::RSA) end def signature_algorithm_class OpenSSL::SignatureAlgorithm::RSAPSS end def to_pkey(key) case key when COSE::Key::RSA key.to_pkey when OpenSSL::PKey::RSA key else raise(COSE::Error, "Incompatible key for algorithm") end end end end end cose-1.3.1/lib/cose/algorithm/base.rb0000644000004100000410000000031414700627371017426 0ustar www-datawww-data# frozen_string_literal: true module COSE module Algorithm class Base attr_reader :id, :name def initialize(id, name) @id = id @name = name end end end end cose-1.3.1/lib/cose/algorithm/signature_algorithm.rb0000644000004100000410000000302014700627371022560 0ustar www-datawww-data# frozen_string_literal: true require "cose/algorithm/base" require "cose/error" module COSE module Algorithm class SignatureAlgorithm < Base def verify(key, signature, verification_data) compatible_key?(key) || raise(COSE::Error, "Incompatible key for signature verification") valid_signature?(key, signature, verification_data) || raise(COSE::Error, "Signature verification failed") end def compatible_key?(key) valid_key?(key) && to_pkey(key) rescue COSE::Error false end private def valid_signature?(key, signature, verification_data) signature_algorithm = signature_algorithm_class.new(**signature_algorithm_parameters) signature_algorithm.verify_key = to_pkey(key) begin signature_algorithm.verify(signature, verification_data) rescue OpenSSL::SignatureAlgorithm::Error false end end def signature_algorithm_parameters { hash_function: hash_function } end def to_cose_key(key) case key when COSE::Key::Base key when OpenSSL::PKey::PKey COSE::Key.from_pkey(key) else raise(COSE::Error, "Don't know how to transform #{key.class} to COSE::Key") end end def signature_algorithm_class raise NotImplementedError end def valid_key?(_key) raise NotImplementedError end def to_pkey(_key) raise NotImplementedError end end end end cose-1.3.1/lib/cose/algorithm/ecdsa.rb0000644000004100000410000000231114700627371017572 0ustar www-datawww-data# frozen_string_literal: true require "cose/algorithm/signature_algorithm" require "cose/error" require "cose/key/curve" require "cose/key/ec2" require "openssl" require "openssl/signature_algorithm/ecdsa" module COSE module Algorithm class ECDSA < SignatureAlgorithm attr_reader :hash_function, :curve def initialize(*args, hash_function:, curve_name:) super(*args) @hash_function = hash_function @curve = COSE::Key::Curve.by_name(curve_name) || raise("Couldn't find curve with name='#{curve_name}'") end private def valid_key?(key) cose_key = to_cose_key(key) cose_key.is_a?(COSE::Key::EC2) && (!cose_key.alg || cose_key.alg == id) end def signature_algorithm_class OpenSSL::SignatureAlgorithm::ECDSA end def signature_algorithm_parameters if curve super.merge(curve: curve.pkey_name) else super end end def to_pkey(key) case key when COSE::Key::EC2 key.to_pkey when OpenSSL::PKey::EC key else raise(COSE::Error, "Incompatible key for algorithm") end end end end end cose-1.3.1/lib/cose/encrypt.rb0000644000004100000410000000101114700627371016205 0ustar www-datawww-data# frozen_string_literal: true require "cose/security_message" require "cose/recipient" module COSE class Encrypt < SecurityMessage attr_reader :ciphertext, :recipients def self.keyword_arguments_for_initialize(decoded) { ciphertext: decoded[0], recipients: decoded[1].map { |s| COSE::Recipient.deserialize(s) } } end def initialize(ciphertext:, recipients:, **keyword_arguments) super(**keyword_arguments) @ciphertext = ciphertext @recipients = recipients end end end cose-1.3.1/lib/cose/signature.rb0000644000004100000410000000056114700627371016533 0ustar www-datawww-data# frozen_string_literal: true require "cose/security_message" module COSE class Signature < SecurityMessage attr_reader :signature def self.keyword_arguments_for_initialize(decoded) { signature: decoded[0] } end def initialize(signature:, **keyword_arguments) super(**keyword_arguments) @signature = signature end end end cose-1.3.1/lib/cose/recipient.rb0000644000004100000410000000113614700627371016513 0ustar www-datawww-data# frozen_string_literal: true require "cose/security_message" module COSE class Recipient < SecurityMessage attr_reader :ciphertext, :recipients def self.keyword_arguments_for_initialize(decoded) keyword_arguments = { ciphertext: decoded[0] } if decoded[1] keyword_arguments[:recipients] = decoded[1].map { |s| COSE::Recipient.deserialize(s) } end keyword_arguments end def initialize(ciphertext:, recipients: nil, **keyword_arguments) super(**keyword_arguments) @ciphertext = ciphertext @recipients = recipients end end end cose-1.3.1/lib/cose/key.rb0000644000004100000410000000320514700627371015320 0ustar www-datawww-data# frozen_string_literal: true require "cbor" require "cose/key/ec2" require "cose/key/okp" require "cose/key/rsa" require "cose/key/symmetric" require "openssl" module COSE class Error < StandardError; end class KeyDeserializationError < Error; end class MalformedKeyError < KeyDeserializationError; end class UnknownKeyType < KeyDeserializationError; end module Key def self.serialize(pkey) from_pkey(pkey).serialize end def self.from_pkey(pkey) case pkey when OpenSSL::PKey::EC, OpenSSL::PKey::EC::Point COSE::Key::EC2.from_pkey(pkey) when OpenSSL::PKey::RSA COSE::Key::RSA.from_pkey(pkey) when OpenSSL::PKey::PKey COSE::Key::OKP.from_pkey(pkey) else raise "Unsupported #{pkey.class} object" end end def self.deserialize(data) map = cbor_decode(data) case map[Base::LABEL_KTY] when COSE::Key::OKP::KTY_OKP COSE::Key::OKP.from_map(map) when COSE::Key::EC2::KTY_EC2 COSE::Key::EC2.from_map(map) when COSE::Key::RSA::KTY_RSA COSE::Key::RSA.from_map(map) when COSE::Key::Symmetric::KTY_SYMMETRIC COSE::Key::Symmetric.from_map(map) when nil raise COSE::UnknownKeyType, "Missing required key type kty label" else raise COSE::UnknownKeyType, "Unsupported or unknown key type #{map[Base::LABEL_KTY]}" end end def self.cbor_decode(data) CBOR.decode(data) rescue CBOR::MalformedFormatError, EOFError, FloatDomainError, RegexpError, TypeError, URI::InvalidURIError raise COSE::MalformedKeyError, "Malformed CBOR key input" end end end cose-1.3.1/lib/cose/version.rb0000644000004100000410000000010314700627371016207 0ustar www-datawww-data# frozen_string_literal: true module COSE VERSION = "1.3.1" end cose-1.3.1/lib/cose/encrypt0.rb0000644000004100000410000000056514700627371016302 0ustar www-datawww-data# frozen_string_literal: true require "cose/security_message" module COSE class Encrypt0 < SecurityMessage attr_reader :ciphertext def self.keyword_arguments_for_initialize(decoded) { ciphertext: decoded[0] } end def initialize(ciphertext:, **keyword_arguments) super(**keyword_arguments) @ciphertext = ciphertext end end end cose-1.3.1/lib/cose/algorithm.rb0000644000004100000410000000304114700627371016514 0ustar www-datawww-data# frozen_string_literal: true require "cose/algorithm/ecdsa" require "cose/algorithm/eddsa" require "cose/algorithm/hmac" require "cose/algorithm/rsa_pss" module COSE module Algorithm @registered_by_id = {} @registered_by_name = {} def self.register(algorithm) @registered_by_id[algorithm.id] = algorithm @registered_by_name[algorithm.name] = algorithm end def self.find(id_or_name) by_id(id_or_name) || by_name(id_or_name) end def self.by_id(id) @registered_by_id[id] end def self.by_name(name) @registered_by_name[name] end register(ECDSA.new(-7, "ES256", hash_function: "SHA256", curve_name: "P-256")) register(ECDSA.new(-35, "ES384", hash_function: "SHA384", curve_name: "P-384")) register(ECDSA.new(-36, "ES512", hash_function: "SHA512", curve_name: "P-521")) register(ECDSA.new(-47, "ES256K", hash_function: "SHA256", curve_name: "secp256k1")) register(EdDSA.new(-8, "EdDSA")) register(RSAPSS.new(-37, "PS256", hash_function: "SHA256", salt_length: 32)) register(RSAPSS.new(-38, "PS384", hash_function: "SHA384", salt_length: 48)) register(RSAPSS.new(-39, "PS512", hash_function: "SHA512", salt_length: 64)) register(HMAC.new(4, "HMAC 256/64", hash_function: "SHA256", tag_length: 64)) register(HMAC.new(5, "HMAC 256/256", hash_function: "SHA256", tag_length: 256)) register(HMAC.new(6, "HMAC 384/384", hash_function: "SHA384", tag_length: 384)) register(HMAC.new(7, "HMAC 512/512", hash_function: "SHA512", tag_length: 512)) end end cose-1.3.1/lib/cose/security_message.rb0000644000004100000410000000301514700627371020102 0ustar www-datawww-data# frozen_string_literal: true require "cbor" require "cose/algorithm" require "cose/error" require "cose/security_message/headers" module COSE class SecurityMessage ZERO_LENGTH_BIN_STRING = "".b attr_reader :protected_headers, :unprotected_headers def self.deserialize(cbor) decoded = CBOR.decode(cbor) if decoded.is_a?(CBOR::Tagged) if respond_to?(:tag) && tag != decoded.tag raise(COSE::Error, "Invalid CBOR tag") end decoded = decoded.value end from_array(decoded) end def self.from_array(array) new( protected_headers: deserialize_headers(array[0]), unprotected_headers: array[1], **keyword_arguments_for_initialize(array[2..-1]) ) end def self.deserialize_headers(data) if data == ZERO_LENGTH_BIN_STRING {} else CBOR.decode(data) end end def initialize(protected_headers:, unprotected_headers:) @protected_headers = protected_headers @unprotected_headers = unprotected_headers end def algorithm @algorithm ||= COSE::Algorithm.find(headers.alg) || raise(COSE::Error, "Unsupported algorithm '#{headers.alg}'") end def headers @headers ||= Headers.new(protected_headers, unprotected_headers) end private def serialized_map(map) if map && !map.empty? map.to_cbor else zero_length_bin_string end end def zero_length_bin_string ZERO_LENGTH_BIN_STRING end end end cose-1.3.1/lib/cose.rb0000644000004100000410000000033514700627371014531 0ustar www-datawww-data# frozen_string_literal: true require "cose/encrypt" require "cose/encrypt0" require "cose/error" require "cose/key" require "cose/mac" require "cose/mac0" require "cose/sign" require "cose/sign1" require "cose/version" cose-1.3.1/Appraisals0000644000004100000410000000036214700627371014527 0ustar www-datawww-data# frozen_string_literal: true appraise "openssl_2_2" do gem "openssl", "~> 2.2.0" end appraise "openssl_2_1" do gem "openssl", "~> 2.1.0" end appraise "openssl_3_0" do gem "openssl", "~> 3.0.0" end appraise "openssl_default" do end cose-1.3.1/LICENSE.txt0000644000004100000410000000206214700627371014327 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2018 Gonzalo 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. cose-1.3.1/cose.gemspec0000644000004100000410000000345714700627371015013 0ustar www-datawww-data# frozen_string_literal: true lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "cose/version" Gem::Specification.new do |spec| spec.name = "cose" spec.version = COSE::VERSION spec.authors = ["Gonzalo Rodriguez", "Braulio Martinez"] spec.email = ["gonzalo@cedarcode.com", "braulio@cedarcode.com"] spec.summary = "Ruby implementation of RFC 8152 CBOR Object Signing and Encryption (COSE)" spec.homepage = "https://github.com/cedarcode/cose-ruby" spec.license = "MIT" spec.metadata = { "bug_tracker_uri" => "https://github.com/cedarcode/cose-ruby/issues", "changelog_uri" => "https://github.com/cedarcode/cose-ruby/blob/master/CHANGELOG.md", "source_code_uri" => "https://github.com/cedarcode/cose-ruby" } # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.required_ruby_version = ">= 2.4" spec.add_dependency "cbor", "~> 0.5.9" spec.add_dependency "openssl-signature_algorithm", "~> 1.0" spec.add_development_dependency "appraisal", "~> 2.2.0" spec.add_development_dependency "bundler", ">= 1.17", "< 3" spec.add_development_dependency "byebug", "~> 11.0" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.8" spec.add_development_dependency "rubocop", "0.80.1" spec.add_development_dependency "rubocop-performance", "~> 1.4" end cose-1.3.1/gemfiles/0000755000004100000410000000000014700627371014277 5ustar www-datawww-datacose-1.3.1/gemfiles/openssl_2_2.gemfile0000644000004100000410000000016614700627371017761 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "openssl", "~> 2.2.0" gemspec path: "../" cose-1.3.1/gemfiles/openssl_2_1.gemfile0000644000004100000410000000016614700627371017760 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "openssl", "~> 2.1.0" gemspec path: "../" cose-1.3.1/gemfiles/openssl_default.gemfile0000644000004100000410000000013314700627371021015 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gemspec path: "../" cose-1.3.1/gemfiles/openssl_3_0.gemfile0000644000004100000410000000016614700627371017760 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "openssl", "~> 3.0.0" gemspec path: "../" cose-1.3.1/.rspec0000644000004100000410000000005514700627371013621 0ustar www-datawww-data--color --require spec_helper --order random cose-1.3.1/Rakefile0000644000004100000410000000031714700627371014152 0ustar www-datawww-data# frozen_string_literal: true require "bundler/gem_tasks" require "rspec/core/rake_task" require "rubocop/rake_task" RSpec::Core::RakeTask.new(:spec) RuboCop::RakeTask.new task default: [:rubocop, :spec] cose-1.3.1/Gemfile0000644000004100000410000000027714700627371014005 0ustar www-datawww-data# frozen_string_literal: true source "https://rubygems.org" git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # Specify your gem's dependencies in cose.gemspec gemspec cose-1.3.1/README.md0000644000004100000410000001175614700627371013775 0ustar www-datawww-data# cose-ruby Ruby implementation of RFC [8152](https://tools.ietf.org/html/rfc8152) CBOR Object Signing and Encryption (COSE) [![Gem](https://img.shields.io/gem/v/cose.svg?style=flat-square&color=informational)](https://rubygems.org/gems/cose) [![Actions Build](https://github.com/cedarcode/cose-ruby/workflows/build/badge.svg)](https://github.com/cedarcode/cose-ruby/actions) ## Installation Add this line to your application's Gemfile: ```ruby gem 'cose' ``` And then execute: $ bundle Or install it yourself as: $ gem install cose ## Usage ### Key Objects #### Deserialization (from CBOR to Ruby objects) ```ruby cbor_data = "..." key = COSE::Key.deserialize(cbor_data) ``` Once you have a `COSE::Key` instance you can either access key parameters directly and/or convert it to an `OpenSSL::PKey::PKey` instance (if supported for the key type) for operating with it (encrypting/decrypting, signing/verifying, etc). ```ruby # Convert to an OpenSSL::PKey::PKey if key.respond_to?(:to_pkey) openssl_pkey = key.to_pkey end # Access COSE key parameters case key when COSE::Key::OKP key.crv key.x key.d when COSE::Key::EC2 key.crv key.x key.y key.d when COSE::Key::RSA key.n key.e key.d key.p key.q key.dp key.dq key.qinv when COSE::Key::Symmetric key.k end ``` If you already know which COSE key type is encoded in the CBOR data, then: ```ruby okp_key_cbor = "..." cose_okp_key = COSE::Key::OKP.deserialize(okp_key_cbor) cose_okp_key.crv cose_okp_key.x cose_okp_key.d ``` ```ruby ec2_key_cbor = "..." cose_ec2_key = COSE::Key::EC2.deserialize(ec2_key_cbor) cose_ec2_key.crv cose_ec2_key.x cose_ec2_key.y cose_ec2_key.d # or ec_pkey = cose_ec2_key.to_pkey # Instance of an OpenSSL::PKey::EC ``` ```ruby symmetric_key_cbor = "..." cose_symmetric_key = COSE::Key::Symmetric.deserialize(symmetric_key_cbor) cose_symmetric_key.k ``` ```ruby rsa_key_cbor = "..." cose_rsa_key = COSE::Key::RSA.deserialize(rsa_key_cbor) cose_rsa_key.n cose_rsa_key.e cose_rsa_key.d cose_rsa_key.p cose_rsa_key.q cose_rsa_key.dp cose_rsa_key.dq cose_rsa_key.qinv # or rsa_pkey = cose_rsa_key.to_pkey # Instance of an OpenSSL::PKey::RSA ``` #### Serialization (from Ruby objects to CBOR) ```ruby ec_pkey = OpenSSL::PKey::EC.new("prime256v1").generate_key cose_ec2_key_cbor = COSE::Key.serialize(ec_pkey) ``` ```ruby rsa_pkey = OpenSSL::PKey::RSA.new(2048) cose_rsa_key_cbor = COSE::Key.serialize(rsa_pkey) ``` ### Signing Objects #### COSE_Sign ```ruby cbor_data = "..." sign = COSE::Sign.deserialize(cbor_data) # Verify by doing (key should be a COSE::Key): sign.verify(key) # or, if externally supplied authenticated data exists: sign.verify(key, external_aad) # Then access payload sign.payload ``` #### COSE_Sign1 ```ruby cbor_data = "..." sign1 = COSE::Sign1.deserialize(cbor_data) # Verify by doing (key should be a COSE::Key): sign1.verify(key) # or, if externally supplied authenticated data exists: sign1.verify(key, external_aad) # Then access payload sign1.payload ``` ### MAC Objects #### COSE_Mac ```ruby cbor_data = "..." mac = COSE::Mac.deserialize(cbor_data) # Verify by doing (key should be a COSE::Key::Symmetric): mac.verify(key) # or, if externally supplied authenticated data exists: mac.verify(key, external_aad) # Then access payload mac.payload ``` #### COSE_Mac0 ```ruby cbor_data = "..." mac0 = COSE::Mac0.deserialize(cbor_data) # Verify by doing (key should be a COSE::Key::Symmetric): mac0.verify(key) # or, if externally supplied authenticated data exists: mac0.verify(key, external_aad) # Then access payload mac0.payload ``` ### Encryption Objects #### COSE_Encrypt ```ruby cbor_data = "..." encrypt = COSE::Encrypt.deserialize(cbor_data) encrypt.protected_headers encrypt.unprotected_headers encrypt.ciphertext encrypt.recipients.each do |recipient| recipient.protected_headers recipient.unprotected_headers recipient.ciphertext if recipient.recipients recipient.recipients.each do |recipient| recipient.protected_headers recipient.unprotected_headers recipient.ciphertext end end end ``` #### COSE_Encrypt0 ```ruby cbor_data = "..." encrypt0 = COSE::Encrypt0.deserialize(cbor_data) encrypt0.protected_headers encrypt0.unprotected_headers encrypt0.ciphertext ``` ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/cedarcode/cose-ruby. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). cose-1.3.1/.rubocop.yml0000644000004100000410000000077014700627371014762 0ustar www-datawww-datarequire: - rubocop-performance inherit_mode: merge: - Exclude AllCops: TargetRubyVersion: 2.4 DisabledByDefault: true Exclude: - "gemfiles/**/*" Bundler: Enabled: true Gemspec: Enabled: true Layout: Enabled: true Layout/LineLength: Max: 120 Lint: Enabled: true Naming: Enabled: true Performance: Enabled: true Security: Enabled: true Style/FrozenStringLiteralComment: Enabled: true Style/HashSyntax: Enabled: true Style/RedundantFreeze: Enabled: true cose-1.3.1/CHANGELOG.md0000644000004100000410000001047014700627371014317 0ustar www-datawww-data# Changelog ## [v1.3.1] - 2024-08-12 - Handling COSE EC keys encoded without leading 0 bytes in coordinates (#64). Credits to @waltercacau. ## [v1.3.0] - 2022-10-28 - Add support for EdDSA (#55). Credits to @bdewater. ## [v1.2.1] - 2022-07-03 - Support OpenSSL ~>3.0.0. Credits to @ClearlyClaire <3 ## [v1.2.0] - 2020-07-10 ### Added - Support ES256K signature algorithm ## [v1.1.0] - 2020-07-09 ### Dependencies - Update `openssl-signature_algorithm` runtime dependency from `~> 0.4.0` to `~> 1.0`. ## [v1.0.0] - 2020-03-29 ### Added - Signature verification validates key `alg` is compatible with the signature algorithm NOTE: No breaking changes. Moving out of `v0.x` to express the intention to keep the public API stable. ## [v0.11.0] - 2020-01-30 ### Added - Let others easily support more signature algorithms by making `COSE::Algorithm::SignatureAlgorithm` smarter ## [v0.10.0] - 2019-12-19 ### Added - Works on ruby 2.7 without throwing any warnings - Simpler way to rescue key deserialization error, now possible to: ```rb begin COSE::Key.deserialize(cbor) rescue COSE::KeyDeserializationError # handle error end ``` ## [v0.9.0] - 2019-08-31 ### Added - `COSE::Sign1#verify` - `COSE::Sign#verify` - `COSE::Mac0#verify` - `COSE::Mac#verify` ## [v0.8.0] - 2019-08-17 ### Added - Support `COSE::Key` instantiation based on an `OpenSSL::PKey` object with `COSE::Key.from_pkey` - Provide writer methods for `COSE::Key` Common Parameters (`#base_iv=`, `#key_ops=`, `#alg=` and `#kid=`) ## [v0.7.0] - 2019-05-02 ### Fixed - `require "cose"` now correctly requires all features ## [v0.6.1] - 2019-04-06 ### Fixed - Fix COSE::Key::RSA#to_pkey for a public key ## [v0.6.0] - 2019-04-03 ### Added - Support Key Common Parameters (`#base_iv`, `key_ops`, `#alg` and `#kid`) - Support OKP Key - Support RSA private key serialization - Works with ruby 2.3 ### Changed - Key type-specific parameters names better match RFC ## [v0.5.0] - 2019-03-25 ### Added - `COSE::Key.serialize(openssl_pkey)` serializes an `OpenSSL::PKey::PKey` object into CBOR data. Supports RSA keys plus EC keys from curves prime256v1, secp384r1 and secp521r1. - `COSE::Key::EC2#to_pkey` converts to an `OpenSSL::PKey::EC` object - `COSE::Key::RSA#to_pkey` converts to an `OpenSSL::PKey::RSA` object ## [v0.4.1] - 2019-03-12 ### Fixed - Fix `uninitialized constant COSE::Key::Base::LABEL_KTY` when requiring only particular key ## [v0.4.0] - 2019-03-12 ### Added - RSA public key deserialization - Key type-agnostic deserialization ### Changed - Keys `.from_cbor` methods changed to `.deserialize` ## [v0.3.0] - 2019-03-09 ### Added - Support deserialization of security messages: - COSE_Sign - COSE_Sign1 - COSE_Mac - COSE_Mac0 - COSE_Encrypt - COSE_Encrypt0 - Works with ruby 2.6 ## [v0.2.0] - 2019-03-04 ### Added - Symmetric key object - EC2 key suppors D coordinate - Works with ruby 2.4 ## [v0.1.0] - 2018-06-08 ### Added - EC2 key object - Works with ruby 2.5 [v1.3.0]: https://github.com/cedarcode/cose-ruby/compare/v1.2.1...v1.3.0/ [v1.2.1]: https://github.com/cedarcode/cose-ruby/compare/v1.2.0...v1.2.1/ [v1.2.0]: https://github.com/cedarcode/cose-ruby/compare/v1.1.0...v1.2.0/ [v1.1.0]: https://github.com/cedarcode/cose-ruby/compare/v1.0.0...v1.1.0/ [v1.0.0]: https://github.com/cedarcode/cose-ruby/compare/v0.11.0...v1.0.0/ [v0.11.0]: https://github.com/cedarcode/cose-ruby/compare/v0.10.0...v0.11.0/ [v0.10.0]: https://github.com/cedarcode/cose-ruby/compare/v0.9.0...v0.10.0/ [v0.9.0]: https://github.com/cedarcode/cose-ruby/compare/v0.8.0...v0.9.0/ [v0.8.0]: https://github.com/cedarcode/cose-ruby/compare/v0.7.0...v0.8.0/ [v0.7.0]: https://github.com/cedarcode/cose-ruby/compare/v0.6.1...v0.7.0/ [v0.6.1]: https://github.com/cedarcode/cose-ruby/compare/v0.6.0...v0.6.1/ [v0.6.0]: https://github.com/cedarcode/cose-ruby/compare/v0.5.0...v0.6.0/ [v0.5.0]: https://github.com/cedarcode/cose-ruby/compare/v0.4.1...v0.5.0/ [v0.4.1]: https://github.com/cedarcode/cose-ruby/compare/v0.4.0...v0.4.1/ [v0.4.0]: https://github.com/cedarcode/cose-ruby/compare/v0.3.0...v0.4.0/ [v0.3.0]: https://github.com/cedarcode/cose-ruby/compare/v0.2.0...v0.3.0/ [v0.2.0]: https://github.com/cedarcode/cose-ruby/compare/v0.1.0...v0.2.0/ [v0.1.0]: https://github.com/cedarcode/cose-ruby/compare/5725d9b5db978f19a21bd59182f092d31a118eff...v0.1.0/