From ae8de738fec70de9cb7d7db28ecbb458530885bc Mon Sep 17 00:00:00 2001 From: aldo Date: Thu, 14 Feb 2019 20:48:33 -0600 Subject: [PATCH] Add key derivation, original_hash and master key set up --- lib/mifiel.rb | 1 + lib/mifiel/config.rb | 2 ++ lib/mifiel/crypto.rb | 15 ++++++++--- lib/mifiel/crypto/digest.rb | 11 ++++++++ lib/mifiel/document.rb | 21 ++++++++------- lib/mifiel/errors.rb | 2 ++ spec/fixtures/widget_ids.json | 31 +++++++++++++--------- spec/mifiel/crypto_spec.rb | 49 ++++++++++++++++++++++++++--------- spec/mifiel/document_spec.rb | 9 +++++++ spec/support/fake_mifiel.rb | 6 ++--- 10 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 lib/mifiel/crypto/digest.rb diff --git a/lib/mifiel.rb b/lib/mifiel.rb index fd66430..d1c790a 100755 --- a/lib/mifiel.rb +++ b/lib/mifiel.rb @@ -1,4 +1,5 @@ require 'flexirest' +require 'money-tree' module Mifiel require 'mifiel/errors' diff --git a/lib/mifiel/config.rb b/lib/mifiel/config.rb index da556d5..d31742d 100644 --- a/lib/mifiel/config.rb +++ b/lib/mifiel/config.rb @@ -2,10 +2,12 @@ module Mifiel module Config class << self attr_reader :app_id, :app_secret, :base_url + attr_accessor :master_key def reset @app_id = nil @app_secret = nil + @master_key = nil @base_url = Mifiel::BASE_URL end diff --git a/lib/mifiel/crypto.rb b/lib/mifiel/crypto.rb index 711270a..8b7ebf2 100755 --- a/lib/mifiel/crypto.rb +++ b/lib/mifiel/crypto.rb @@ -1,5 +1,4 @@ require_relative '../core_extensions.rb' -require 'money-tree' CoreExtensions.load module Mifiel @@ -9,6 +8,7 @@ module Crypto autoload :AES, 'mifiel/crypto/aes' autoload :ECIES, 'mifiel/crypto/ecies' autoload :PKCS5, 'mifiel/crypto/pkcs5' + autoload :Digest, 'mifiel/crypto/digest' def self.decrypt(asn1, pass) pkcs5 = Mifiel::Crypto::PKCS5.parse(asn1.force_binary) @@ -32,15 +32,22 @@ def self.encrypt(document, password) Mifiel::Crypto::PKCS5.new(params.slice(:salt, :iv, :iterations, :cipher_text)) end - def self.derive_path(key, path) + def self.derive_path(key = Mifiel::Config.master_key, path) master = key.is_a?(MoneyTree::Master) ? key : MoneyTree::Master.from_bip32(key) master.node_for_path(path) + rescue ArgumentError => e + raise Mifiel::InvalidKeyFormatError, e.message end - def self.derive_from_widget(key, widget_id) + def self.derive_from_widget(key, widget_id, hardened: true) path = widget_id.split('-') - raise Mifiel::ERROR unless path.count == 7 + raise Mifiel::ERROR, 'Wrong widget_id' unless path.count == 7 + path[-2] += 'p' if hardened derive_path(key, path.last(2).join('/')) end + + def self.generate_random_key + MoneyTree::Master.new + end end end diff --git a/lib/mifiel/crypto/digest.rb b/lib/mifiel/crypto/digest.rb new file mode 100644 index 0000000..d48596f --- /dev/null +++ b/lib/mifiel/crypto/digest.rb @@ -0,0 +1,11 @@ +module Mifiel + module Crypto + module Digest + class << self + def sha256(message) + OpenSSL::Digest::SHA256.new.digest(message) + end + end + end + end +end diff --git a/lib/mifiel/document.rb b/lib/mifiel/document.rb index 42e9f11..2bf685b 100644 --- a/lib/mifiel/document.rb +++ b/lib/mifiel/document.rb @@ -11,19 +11,18 @@ class Document < Mifiel::Base post :create_from_template, '/templates/:template_id/generate_document', timeout: 60 post :create_many_from_template, '/templates/:template_id/generate_documents', timeout: 60 - # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.create(args) signatories = args[:signatories] file = args[:file] - hash = args[:hash] - raise ArgumentError, 'Either file or hash must be provided' unless file || hash - raise ArgumentError, 'Only one of file or hash must be provided' if file && hash + doc_hash = args[:hash] + raise ArgumentError, 'Either file or hash must be provided' unless file || doc_hash return Mifiel::Document.create_encrypted(args.dup) if args[:encrypted] payload = { signatories: build_signatories(signatories), callback_url: args[:callback_url], file: (File.new(file) if file), - original_hash: hash, + original_hash: doc_hash, name: args[:name] } payload[:encrypted] = true if args[:file].last(3) == Mifiel::Document::ENC_EXTENSION @@ -34,31 +33,35 @@ def self.create(args) end def self.create_encrypted(args) + e_path = "#{args[:file]}.#{Mifiel::Document::ENC_EXTENSION}" + raise Mifiel::MasterKeyError, 'Master key should be configured' unless Mifiel::Config.master_key pdf = File.read(args[:file]) if args[:file] password = Mifiel::Crypto::PBE.random_password - e_path = "#{args[:file]}.#{Mifiel::Document::ENC_EXTENSION}" File.open(e_path, 'wb+') { |f| f.write(Mifiel::Crypto.encrypt(pdf, password).to_der) } args[:file] = e_path + args[:hash] = Mifiel::Crypto::Digest.sha256(pdf).bth args.delete(:encrypted) document = Mifiel::Document.create(args) payload = { signatories: encrypt_password(document.signers, password) } + Mifiel::Config.master_key = MoneyTree::Master.new seed_hex: '000102030405060708090a0b0c0d0e0f' response = process_request("/documents/#{document.id}", :put, payload) Mifiel::Document.new(JSON.parse(response)) ensure File.unlink(e_path) if File.exist?(e_path) end - # rubocop:enable Metrics/MethodLength, Metrics/AbcSize def self.encrypt_password(signers, password) signers.map do |s| - e_passwords = s['e2ee']['group'].map do |k, e| - key = Mifiel::Crypto::ECIES.public_from_hex(e['pub']) + e_passwords = s['e2ee']['group'].keys.map do |k| + derived_pub = Mifiel::Crypto.derive_path(Mifiel::Config.master_key, s['e2ee']['e_index']).public_key.to_hex + key = Mifiel::Crypto::ECIES.public_from_hex(derived_pub) ecies = Mifiel::Crypto::ECIES.new [k, { e_pass: ecies.encrypt(key, password).bth }] end.to_h [s['id'], e_passwords] end.to_h end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize def request_signature(email, cc: nil) params = { email: email } diff --git a/lib/mifiel/errors.rb b/lib/mifiel/errors.rb index 81fb8a7..ea5dbb9 100644 --- a/lib/mifiel/errors.rb +++ b/lib/mifiel/errors.rb @@ -9,4 +9,6 @@ module Mifiel PKCS5Error = Class.new StandardError AESError = Class.new StandardError PBError = Class.new StandardError + InvalidKeyFormatError = Class.new ArgumentError + MasterKeyError = Class.new StandardError end diff --git a/spec/fixtures/widget_ids.json b/spec/fixtures/widget_ids.json index f86083e..b82c2fe 100644 --- a/spec/fixtures/widget_ids.json +++ b/spec/fixtures/widget_ids.json @@ -11,32 +11,39 @@ ], "keys": [ { - "priv": "b21ab38d62adc2f2248302300caf1926ca14a9b19ee80130424a7a6ced85f578", - "pub": "035507ffc29904f955eab279e5f82151529bacca2b7a7623fca2ad95c6281ad5b9" + "priv": "e615cec9f4dc47b6b6d2874b950b1e6185b09afcb4baa248931419f1e66db505", + "pub": "02f573918f42f613a9bffc28db4b58819cb87d2342c06ead9c805ea1ec1a13a14b", + "hardened": true }, { - "priv": "827697afbf1b2244369dcbdd2cb26714f032fb959d909fad645f4afae2d44381", - "pub": "0354492c44d62b1548c27f48171f3e0839cf2d8381808f8c00d5e6219b1b3ca764" + "priv": "742ba3e0912540f1a5e89b9c1e381f26945dfc660d2658aef4ab8741e1bceb43", + "pub": "03649af861065e98293dee9fc1382de9b237aed5eae46bb54eeea7a005fa66011b", + "hardened": true }, { - "priv": "21b03a3504c9d5712d912da748ae1d8d131f070ba7c6d765e72c0da5ab5bbab2", - "pub": "02b551bdc9353c20eb7cae3ed0949b4042006ace9db42a7e4f95b1dbba7fab4cf7" + "priv": "4c2f2d74d795486887c89e31a8f11b9024686a48471908e36be334b679cf925e", + "pub": "0359784f3a5706a0eafacc8203623211b6202b9b65aead37b392bf1fa4e841eac3", + "hardened": true }, { - "priv": "e31c5f2e2d8bea5ea02d04129af841bfd1e2f875c1614c4d4d4c72fc1a711951", - "pub": "03d18a97975c5f2e11dfa22dd686315f27b35c2db5d32cd7d0c11aea146fdd17c2" + "priv": "bfa319f57e67ecb0d1ecdfa5bbbb04332f802081286655d476a44a3038ed6022", + "pub": "02348bb8c38be3c6f05767c5e6f88fd0a0f6235b2d74dc0fdd2072ff786d3098ca", + "hardened": true }, { - "priv": "602ace22b1ac42d8b590ce97028eb1d0b8d59a7c8631e87790298dcb7ab651fa", - "pub": "025570318f733b57b920cf37c31aad4fbdadcae8fe50c4e68f4c33e9c5f84aa4c9" + "priv": "ba70ce9cfbce82ff696caecc57059cbe35ed42005ad94144ee678e0065df6015", + "pub": "03fc4de3c406dab26cc8a1ca801d186876172ee2b8ba81dd1050133dfc729fbce0", + "hardened": true }, { "priv": "0cef0f50ea92135a56e3be1bc738d92f17dedfe8a9b14b80e80ae9fabac32de2", - "pub": "0322193b830d0356dedeb42895f3520f92ed2ddf5aa00af613b81c1d040b15e2f0" + "pub": "0322193b830d0356dedeb42895f3520f92ed2ddf5aa00af613b81c1d040b15e2f0", + "hardened": false }, { "priv": "4f5fe53512bedf353d2950bf8d43eb3933472ef897581a2de38b9239f5afbb82", - "pub": "0291ef5fcc3061b99ec902b2ef2c13f16a879ee4c6a2b614a8fc2b67c2f185cf7c" + "pub": "0291ef5fcc3061b99ec902b2ef2c13f16a879ee4c6a2b614a8fc2b67c2f185cf7c", + "hardened": false } ] } diff --git a/spec/mifiel/crypto_spec.rb b/spec/mifiel/crypto_spec.rb index 8ee2f20..8bf7df8 100644 --- a/spec/mifiel/crypto_spec.rb +++ b/spec/mifiel/crypto_spec.rb @@ -14,7 +14,6 @@ describe 'Document encryption' do document_hash = 'f4dee35b52fc06aa9d47f6297c7cff51e8bcebf90683da234a07ed507dafd57b' - let(:sha256) { OpenSSL::Digest::SHA256.new } let(:encrypted) { Mifiel::Crypto.encrypt(pdf, v[:password]) } it "Should encrypt & decrypt a document, password: #{v[:password]}" do @@ -23,26 +22,52 @@ end it "should decrypt document and validate doc_hash #{document_hash}" do - expect(sha256.digest(Mifiel::Crypto.decrypt(encrypted.to_der, v[:password])).bth).to eq(document_hash) + expect(Mifiel::Crypto::Digest.sha256(Mifiel::Crypto.decrypt(encrypted.to_der, v[:password])).bth).to eq(document_hash) end end end + + context 'Generate random key' do + let(:key) { Mifiel::Crypto.generate_random_key } + it { expect(key).to be_instance_of(MoneyTree::Master) } + end end - describe 'Key derivation from widget_id' do + describe 'Key derivation' do let(:master) { MoneyTree::Master.new seed_hex: widgets[:master_seed] } keys = widgets[:keys] - widgets[:ids].each_with_index do |widget_id, idx| - describe "Derive #{widget_id} to keys #{keys[idx]}" do - let(:priv) { keys[idx][:priv] } - let(:pub) { keys[idx][:pub] } - it 'should derive priv key' do - expect(Mifiel::Crypto.derive_from_widget(master.to_bip32(:private), widget_id).private_key.to_hex).to eq(priv) - end + context 'From invalid key' do + it 'should raise invalid key error' do + expect { Mifiel::Crypto.derive_path('f4dee35b52fc06aa9d47f629', '1') }.to raise_error Mifiel::InvalidKeyFormatError + end + end + + context 'From widget_id' do + widgets[:ids].each_with_index do |widget_id, idx| + describe "Derive #{widget_id} to keys #{keys[idx]}" do + let(:priv) { keys[idx][:priv] } + let(:pub) { keys[idx][:pub] } + let(:hardened) { keys[idx][:hardened] } + + it 'should derive priv key from base58' do + expect(Mifiel::Crypto.derive_from_widget(master.to_bip32(:private), widget_id, hardened: hardened).private_key.to_hex).to eq(priv) + end + + it 'should derive pub key from base58' do + if hardened + expect(Mifiel::Crypto.derive_from_widget(master.to_bip32(:private), widget_id).public_key.to_hex).to eq(pub) + else + expect(Mifiel::Crypto.derive_from_widget(master.to_bip32(:public), widget_id, hardened: hardened).public_key.to_hex).to eq(pub) + end + end + it 'should derive priv key from MoneyTree instance' do + expect(Mifiel::Crypto.derive_from_widget(master, widget_id, hardened: hardened).private_key.to_hex).to eq(priv) + end - it 'should derive pub key' do - expect(Mifiel::Crypto.derive_from_widget(master.to_bip32(:public), widget_id).public_key.to_hex).to eq(pub) + it 'should derive pub key from MoneyTree instance' do + expect(Mifiel::Crypto.derive_from_widget(master, widget_id, hardened: hardened).public_key.to_hex).to eq(pub) + end end end end diff --git a/spec/mifiel/document_spec.rb b/spec/mifiel/document_spec.rb index 16d8385..3359684 100644 --- a/spec/mifiel/document_spec.rb +++ b/spec/mifiel/document_spec.rb @@ -2,6 +2,7 @@ let!(:certificate) { File.read('spec/fixtures/FIEL_AAA010101AAA.cer') } let!(:private_key) { File.read('spec/fixtures/FIEL_AAA010101AAA.key') } let!(:private_key_pass) { '12345678a' } + before { Mifiel::Config.master_key = MoneyTree::Master.new seed_hex: '000102030405060708090a0b0c0d0e0f' } describe '#create' do context 'with a document' do @@ -123,6 +124,14 @@ expect(v['e_user'][:e_pass]).to eq password end end + + context 'Masterk Key is requiered to encrypt documents' do + let!(:document) { Mifiel::Document.create_encrypted(params) } + before { Mifiel::Config.master_key = nil } + it 'should encrypt e_client & e_user passwords' do + expect { Mifiel::Document.create_encrypted(params) }.to raise_error(Mifiel::MasterKeyError, 'Master key should be configured') + end + end end end diff --git a/spec/support/fake_mifiel.rb b/spec/support/fake_mifiel.rb index b4ac033..cf0802c 100644 --- a/spec/support/fake_mifiel.rb +++ b/spec/support/fake_mifiel.rb @@ -169,10 +169,10 @@ def document(args={}) }] } e2ee = { - e_index: 0, + e_index: "1'/28", group: { - e_client: { pub: '02e47233754d5abae537aebf2efa9b13410b811a270363f4a685e5ccd049fee627' }, - e_user: { pub: '02e47233754d5abae537aebf2efa9b13410b811a270363f4a685e5ccd049fee627' } + e_client: { e_pass: nil }, + e_user: { e_pass: nil } } } doc[:signers] = doc[:signatures]