···11+#!/usr/bin/env ruby
22+#
33+# Copyright (c) 2017 joshua stein <jcs@jcs.org>
44+# Chrome importer by Haluk Unal <admin@halukunal.com>
55+#
66+# Permission to use, copy, modify, and distribute this software for any
77+# purpose with or without fee is hereby granted, provided that the above
88+# copyright notice and this permission notice appear in all copies.
99+#
1010+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1111+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1212+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1313+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1414+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1515+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1616+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717+#
1818+1919+#
2020+# Read a given Chrome CSV file, ask for the given user's master password,
2121+# then lookup the given user in the bitwarden-ruby SQLite database and
2222+# fetch its key. Import each entry into the bitwarden-ruby database.
2323+#
2424+# No check is done to eliminate duplicates, so this is best used on a fresh
2525+# bitwarden-ruby installation after creating a new account.
2626+#
2727+2828+require File.realpath(File.dirname(__FILE__) + '/../lib/rubywarden.rb')
2929+3030+require 'csv'
3131+require 'getoptlong'
3232+3333+def usage
3434+ puts "usage: #{$PROGRAM_NAME} -f data.csv -u user@example.com"
3535+ exit 1
3636+end
3737+3838+def encrypt(str)
3939+ @u.encrypt_data_with_master_password_key(str, @master_key).to_s
4040+end
4141+4242+username = nil
4343+file = nil
4444+@folders = {}
4545+4646+begin
4747+ GetoptLong.new(
4848+ ['--file', '-f', GetoptLong::REQUIRED_ARGUMENT],
4949+ ['--user', '-u', GetoptLong::REQUIRED_ARGUMENT]
5050+ ).each do |opt, arg|
5151+ case opt
5252+ when '--file'
5353+ file = arg
5454+ when '--user'
5555+ username = arg
5656+ end
5757+ end
5858+rescue GetoptLong::InvalidOption
5959+ usage
6060+end
6161+6262+usage unless file && username
6363+6464+@u = User.find_by_email(username)
6565+raise "can't find existing User record for #{username.inspect}" unless @u
6666+6767+print "master password for #{@u.email}: "
6868+system('stty -echo') if STDIN.tty?
6969+password = STDIN.gets.chomp
7070+system('stty echo') if STDIN.tty?
7171+puts
7272+7373+unless @u.has_password_hash?(Bitwarden.hashPassword(password, @u.email,
7474+Bitwarden::KDF::TYPES[@u.kdf_type], @u.kdf_iterations))
7575+ raise "master password does not match stored hash"
7676+end
7777+7878+@master_key = Bitwarden.makeKey(password, @u.email,
7979+ Bitwarden::KDF::TYPES[@u.kdf_type], @u.kdf_iterations)
8080+8181+@u.folders.each do |folder|
8282+ folder_name = @u.decrypt_data_with_master_password_key(folder.name, @master_key)
8383+ @folders[folder_name] = folder.uuid
8484+end
8585+8686+to_save = {}
8787+skipped = 0
8888+8989+CSV.foreach(file, headers: true) do |row|
9090+ next if row['name'].blank?
9191+9292+ puts "converting #{row['name']}..."
9393+9494+ c = Cipher.new
9595+ c.user_uuid = @u.uuid
9696+ c.type = Cipher::TYPE_LOGIN
9797+9898+ cdata = { 'Name' => encrypt(row['name']) }
9999+ cdata['Uri'] = encrypt(row['url']) if row['url'].present?
100100+ cdata['Username'] = encrypt(row['username']) if row['username'].present?
101101+ cdata['Password'] = encrypt(row['password']) if row['password'].present?
102102+103103+ c.data = cdata.to_json
104104+105105+ to_save[c.type] ||= []
106106+ to_save[c.type].push c
107107+end
108108+109109+puts
110110+111111+to_save.each do |k, v|
112112+ puts "#{format('% 4d', v.count)} #{Cipher.type_s(k)}" <<
113113+ (v.count == 1 ? '' : 's')
114114+end
115115+116116+puts "#{format('% 4d', skipped)} skipped" if skipped > 0
117117+118118+print 'ready to import? [Y/n] '
119119+exit 1 if STDIN.gets =~ /n/i
120120+121121+imp = 0
122122+Cipher.transaction do
123123+ to_save.each_value do |v|
124124+ v.each do |c|
125125+ # TODO: convert data to each field natively and call save! on our own
126126+ c.migrate_data!
127127+128128+ imp += 1
129129+ end
130130+ end
131131+end
132132+133133+puts "successfully imported #{imp} item#{imp == 1 ? '' : 's'}"
134134+135135+# EOF