Skip to content

Commit

Permalink
Add key derivation, original_hash and master key set up
Browse files Browse the repository at this point in the history
  • Loading branch information
aldosolorzano committed Feb 15, 2019
1 parent e3f6c84 commit ae8de73
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 40 deletions.
1 change: 1 addition & 0 deletions lib/mifiel.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'flexirest'
require 'money-tree'

module Mifiel
require 'mifiel/errors'
Expand Down
2 changes: 2 additions & 0 deletions lib/mifiel/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 11 additions & 4 deletions lib/mifiel/crypto.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require_relative '../core_extensions.rb'
require 'money-tree'
CoreExtensions.load

module Mifiel
Expand All @@ -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)
Expand All @@ -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
11 changes: 11 additions & 0 deletions lib/mifiel/crypto/digest.rb
Original file line number Diff line number Diff line change
@@ -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
21 changes: 12 additions & 9 deletions lib/mifiel/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
Expand Down
2 changes: 2 additions & 0 deletions lib/mifiel/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 19 additions & 12 deletions spec/fixtures/widget_ids.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
}
49 changes: 37 additions & 12 deletions spec/mifiel/crypto_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions spec/mifiel/document_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions spec/support/fake_mifiel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit ae8de73

Please sign in to comment.