Class: JavaKeystore

Inherits:
Object
  • Object
show all
Defined in:
lib/calabash-android/java_keystore.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(location, keystore_alias, password, key_password = nil) ⇒ JavaKeystore

Returns a new instance of JavaKeystore.


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/calabash-android/java_keystore.rb', line 5

def initialize(location, keystore_alias, password, key_password = nil)
  raise "No such keystore file '#{location}'" unless File.exists?(File.expand_path(location))
  log "Reading keystore data from keystore file '#{File.expand_path(location)}'"

  keystore_data = system_with_stdout_on_success(Calabash::Android::Dependencies.keytool_path, '-list', '-v', '-alias', keystore_alias, '-keystore', location, '-storepass', password, '-J"-Dfile.encoding=utf-8"', '-J"-Duser.language=en-US"')

  if keystore_data.nil?
    if keystore_alias.empty?
      log "Could not obtain keystore data. Will try to extract alias automatically"

      keystore_data = system_with_stdout_on_success(Calabash::Android::Dependencies.keytool_path, '-list', '-v', '-keystore', location, '-storepass', password, '-J"-Dfile.encoding=utf-8"', '-J"-Duser.language=en-US"')
      aliases = keystore_data.scan(/Alias name\:\s*(.*)/).flatten

      if aliases.length == 0
        raise 'Could not extract alias automatically. Please specify alias using calabash-android setup'
      elsif aliases.length > 1
        raise 'Multiple aliases found in keystore. Please specify alias using calabash-android setup'
      else
        keystore_alias = aliases.first
        log "Extracted keystore alias '#{keystore_alias}'. Continuing"

        return initialize(location, keystore_alias, password)
      end
    else
      error = "Could not list certificates in keystore. Probably because the password was incorrect."
      @errors = [{:message => error}]
      log error
      raise error
    end
  end

  @location = location
  @keystore_alias = keystore_alias
  @password = password
  @key_password = key_password
  log "Key store data:"
  log keystore_data
  @fingerprint = extract_sha1_fingerprint(keystore_data)
  @signature_algorithm_name = extract_signature_algorithm_name(keystore_data)
  log "Fingerprint: #{fingerprint}"
  log "Signature algorithm name: #{signature_algorithm_name}"
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors


2
3
4
# File 'lib/calabash-android/java_keystore.rb', line 2

def errors
  @errors
end

#fingerprintObject (readonly)

Returns the value of attribute fingerprint


2
3
4
# File 'lib/calabash-android/java_keystore.rb', line 2

def fingerprint
  @fingerprint
end

#keystore_aliasObject (readonly)

Returns the value of attribute keystore_alias


2
3
4
# File 'lib/calabash-android/java_keystore.rb', line 2

def keystore_alias
  @keystore_alias
end

#locationObject (readonly)

Returns the value of attribute location


2
3
4
# File 'lib/calabash-android/java_keystore.rb', line 2

def location
  @location
end

#passwordObject (readonly)

Returns the value of attribute password


2
3
4
# File 'lib/calabash-android/java_keystore.rb', line 2

def password
  @password
end

#signature_algorithm_nameObject (readonly)

Returns the value of attribute signature_algorithm_name


3
4
5
# File 'lib/calabash-android/java_keystore.rb', line 3

def signature_algorithm_name
  @signature_algorithm_name
end

Class Method Details

.fail_if_key_missing(map, key) ⇒ Object


143
144
145
# File 'lib/calabash-android/java_keystore.rb', line 143

def self.fail_if_key_missing(map, key)
  raise "Found .calabash_settings but no #{key} defined." unless map[key]
end

.get_keystoresObject


118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/calabash-android/java_keystore.rb', line 118

def self.get_keystores
  if keystore = keystore_from_settings
    [ keystore ]
  else
    [
      read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], "/.android/debug.keystore")),
      read_keystore_with_default_password_and_alias("debug.keystore"),
      read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], ".local/share/Xamarin/Mono\\ for\\ Android/debug.keystore")),
      read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], "AppData/Local/Xamarin/Mono for Android/debug.keystore")),
    ].compact
  end
end

.keystore_from_settingsObject


131
132
133
134
135
136
137
138
139
140
141
# File 'lib/calabash-android/java_keystore.rb', line 131

def self.keystore_from_settings
    keystore = JSON.parse(IO.read(".calabash_settings")) if File.exist? ".calabash_settings"
    keystore = JSON.parse(IO.read("calabash_settings")) if File.exist? "calabash_settings"
    return unless keystore
    fail_if_key_missing(keystore, "keystore_location")
    fail_if_key_missing(keystore, "keystore_password")
    fail_if_key_missing(keystore, "keystore_alias")
    keystore["keystore_location"] = File.expand_path(keystore["keystore_location"])
    log("Keystore location specified in #{File.exist?(".calabash_settings") ? ".calabash_settings" : "calabash_settings"}.")
    JavaKeystore.new(keystore["keystore_location"], keystore["keystore_alias"], keystore["keystore_password"], keystore['key_password'])
end

.read_keystore_with_default_password_and_alias(path) ⇒ Object


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/calabash-android/java_keystore.rb', line 100

def self.read_keystore_with_default_password_and_alias(path)
  path = File.expand_path path

  if File.exists? path
    keystore = JavaKeystore.new(path, 'androiddebugkey', 'android')
    if keystore.errors
      log "Trying to "
      nil
    else
      log "Unlocked keystore at #{path} - fingerprint: #{keystore.fingerprint}"
      keystore
    end
  else
    log "Trying to read keystore from: #{path} - no such file"
    nil
  end
end

Instance Method Details

#sign_apk(apk_path, dest_path) ⇒ Object


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/calabash-android/java_keystore.rb', line 48

def sign_apk(apk_path, dest_path)
  raise "Cannot sign with a miss configured keystore" if errors
  raise "No such file: #{apk_path}" unless File.exists?(apk_path)

  # E.g. MD5withRSA or MD5withRSAandMGF1
  encryption = signature_algorithm_name.split('with')[1].split('and')[0]

  # keytool with newer java versions has "Signature algorithm name: SHA1withRSA (weak)"
  encryption.gsub!(' (weak)', '')

  signing_algorithm = "SHA1with#{encryption}"
  digest_algorithm = 'SHA1'

  log "Signing using the signature algorithm: '#{signing_algorithm}'"
  log "Signing using the digest algorithm: '#{digest_algorithm}'"

  cmd_args = {
    '-sigfile' => 'CERT',
    '-sigalg' => signing_algorithm,
    '-digestalg' => digest_algorithm,
    '-signedjar' => dest_path,
    '-storepass' => password,
    '-keystore' =>  location,
  }

  unless @key_password.nil?
    cmd_args['-keypass'] = @key_password
  end

  cmd_args = cmd_args.flatten
  cmd_args << apk_path
  cmd_args << keystore_alias

  result = system_with_stdout_on_success(Calabash::Android::Dependencies.jarsigner_path, *cmd_args)

  unless result
    raise "Could not sign app: #{apk_path}"
  end
end

#system_with_stdout_on_success(cmd, *args) ⇒ Object


88
89
90
91
92
93
94
95
96
97
98
# File 'lib/calabash-android/java_keystore.rb', line 88

def system_with_stdout_on_success(cmd, *args)
  a = Escape.shell_command(args)
  cmd = "\"#{cmd}\" #{a.gsub("'", '"')}"
  log cmd
  out = `#{cmd}`
  if $?.exitstatus == 0
    out
  else
    nil
  end
end