Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
e
os
App Lounge
Commits
df8bd306
Commit
df8bd306
authored
Feb 07, 2019
by
Nihar Thakkar
Browse files
Verify APK integrity, minor UI improvements
parent
b007795d
Changes
5
Hide whitespace changes
Inline
Side-by-side
app/build.gradle
View file @
df8bd306
...
...
@@ -27,7 +27,7 @@ android {
dependencies
{
def
lifecycle_version
=
"1.1.1"
def
work_version
=
"1.0.0-beta0
1
"
def
work_version
=
"1.0.0-beta0
4
"
implementation
"android.arch.work:work-runtime:$work_version"
implementation
fileTree
(
include:
[
'*.jar'
],
dir:
'libs'
)
implementation
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
...
...
@@ -45,4 +45,6 @@ dependencies {
implementation
files
(
'libs/jackson-annotations-2.9.7.jar'
)
implementation
files
(
'libs/jackson-core-2.9.7.jar'
)
implementation
group:
'commons-codec'
,
name:
'commons-codec'
,
version:
'1.11'
implementation
'org.bouncycastle:bcprov-jdk15on:1.60'
implementation
'org.bouncycastle:bcpg-jdk15on:1.60'
}
app/src/main/assets/f-droid.org-signing-key.gpg
0 → 100644
View file @
df8bd306
File added
app/src/main/java/foundation/e/apps/application/ApplicationActivity.kt
View file @
df8bd306
...
...
@@ -450,16 +450,12 @@ class ApplicationActivity : AppCompatActivity(), ApplicationStateListener,
app_install
.
text
=
resources
.
getString
(
state
.
installButtonTextId
)
when
(
state
)
{
State
.
INSTALLED
->
{
app_install
.
setBackgroundResource
(
R
.
drawable
.
app_install_border
)
app_install
.
setTextColor
(
resources
.
getColor
(
android
.
R
.
color
.
primary_text_dark
))
app_install
.
isEnabled
=
Common
.
appHasLaunchActivity
(
this
,
application
.
packageName
)
app_size
.
visibility
=
View
.
VISIBLE
app_download_container
.
visibility
=
View
.
GONE
}
State
.
DOWNLOADING
->
{
app_install
.
setBackgroundResource
(
R
.
drawable
.
app_install_border_simple
)
app_install
.
setTextColor
(
resources
.
getColor
(
android
.
R
.
color
.
primary_text_light
))
app_install
.
isEnabled
=
true
app_size
.
visibility
=
View
.
GONE
app_download_mb
.
text
=
getString
(
R
.
string
.
state_installing
)
...
...
@@ -468,15 +464,11 @@ class ApplicationActivity : AppCompatActivity(), ApplicationStateListener,
app_download_container
.
visibility
=
View
.
VISIBLE
}
State
.
INSTALLING
->
{
app_install
.
setBackgroundResource
(
R
.
drawable
.
app_install_border
)
app_install
.
setTextColor
(
resources
.
getColor
(
android
.
R
.
color
.
primary_text_dark
))
app_install
.
isEnabled
=
false
app_size
.
visibility
=
View
.
VISIBLE
app_download_container
.
visibility
=
View
.
GONE
}
else
->
{
app_install
.
setBackgroundResource
(
R
.
drawable
.
app_install_border
)
app_install
.
setTextColor
(
resources
.
getColor
(
android
.
R
.
color
.
primary_text_dark
))
app_install
.
isEnabled
=
true
app_size
.
visibility
=
View
.
VISIBLE
app_download_container
.
visibility
=
View
.
GONE
...
...
app/src/main/java/foundation/e/apps/application/model/Downloader.kt
View file @
df8bd306
...
...
@@ -10,14 +10,11 @@ import foundation.e.apps.utils.Constants
import
android.content.Intent
import
android.content.BroadcastReceiver
import
android.content.IntentFilter
import
org.apache.commons.codec.binary.Hex
import
java.io.File
import
java.io.FileInputStream
import
java.lang.Exception
import
java.security.MessageDigest
import
android.os.AsyncTask
class
Downloader
(
private
val
applicationInfo
:
ApplicationInfo
,
private
val
fullData
:
FullData
,
private
val
downloaderInterface
:
DownloaderInterface
)
{
private
val
downloaderInterface
:
DownloaderInterface
)
:
IntegrityVerificationCallback
{
private
lateinit
var
downloadManager
:
DownloadManager
private
lateinit
var
request
:
DownloadManager
.
Request
private
var
downloadId
:
Long
=
0
...
...
@@ -103,33 +100,30 @@ class Downloader(private val applicationInfo: ApplicationInfo, private val fullD
downloadManager
.
remove
(
downloadId
)
}
@Throws
(
Exception
::
class
)
private
fun
getApkFileSha1
(
file
:
File
):
String
{
val
messageDigest
=
MessageDigest
.
getInstance
(
"SHA-1"
)
val
fileInputStream
=
FileInputStream
(
file
)
var
length
=
0
val
buffer
=
ByteArray
(
8192
)
while
(
length
!=
-
1
)
{
length
=
fileInputStream
.
read
(
buffer
)
if
(
length
>
0
)
{
messageDigest
.
update
(
buffer
,
0
,
length
)
}
}
return
String
(
Hex
.
encodeHex
(
messageDigest
.
digest
()))
}
private
var
onComplete
:
BroadcastReceiver
=
object
:
BroadcastReceiver
()
{
override
fun
onReceive
(
context
:
Context
,
intent
:
Intent
)
{
unregisterReceivers
(
context
)
val
status
=
getDownloadStatus
()
if
(
status
!=
null
&&
status
==
DownloadManager
.
STATUS_SUCCESSFUL
)
{
downloaderInterface
.
onDownloadComplete
(
context
,
DownloadManager
.
STATUS_SUCCESSFUL
)
IntegrityVerificationTask
(
applicationInfo
,
fullData
,
this
@Downloader
)
.
executeOnExecutor
(
AsyncTask
.
THREAD_POOL_EXECUTOR
,
context
)
}
else
{
downloaderInterface
.
onDownloadComplete
(
context
,
DownloadManager
.
STATUS_FAILED
)
}
}
}
override
fun
onIntegrityVerified
(
context
:
Context
,
verificationSuccessful
:
Boolean
)
{
if
(
verificationSuccessful
)
{
downloaderInterface
.
onDownloadComplete
(
context
,
DownloadManager
.
STATUS_SUCCESSFUL
)
}
else
{
downloaderInterface
.
onDownloadComplete
(
context
,
DownloadManager
.
STATUS_FAILED
)
}
}
private
fun
getDownloadStatus
():
Int
?
{
val
query
=
DownloadManager
.
Query
().
apply
{
setFilterById
(
downloadId
)
...
...
app/src/main/java/foundation/e/apps/application/model/IntegrityVerificationTask.kt
0 → 100644
View file @
df8bd306
package
foundation.e.apps.application.model
import
android.content.Context
import
android.os.AsyncTask
import
foundation.e.apps.application.model.data.FullData
import
org.apache.commons.codec.binary.Hex
import
java.security.MessageDigest
import
org.bouncycastle.jce.provider.BouncyCastleProvider
import
java.security.Security
import
org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider
import
org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
import
org.bouncycastle.openpgp.PGPUtil
import
org.bouncycastle.openpgp.PGPPublicKeyRingCollection
import
org.bouncycastle.openpgp.PGPSignatureList
import
org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
import
org.bouncycastle.openpgp.PGPCompressedData
import
java.io.*
class
IntegrityVerificationTask
(
private
val
applicationInfo
:
ApplicationInfo
,
private
val
fullData
:
FullData
,
private
val
integrityVerificationCallback
:
IntegrityVerificationCallback
)
:
AsyncTask
<
Context
,
Void
,
Context
>()
{
private
var
verificationSuccessful
:
Boolean
=
false
override
fun
doInBackground
(
vararg
context
:
Context
):
Context
{
verificationSuccessful
=
if
(!
fullData
.
getLastVersion
()
!!
.
apkSHA
.
isNullOrEmpty
())
{
getApkFileSha1
(
applicationInfo
.
getApkFile
(
context
[
0
],
fullData
.
basicData
))
==
fullData
.
getLastVersion
()
!!
.
apkSHA
}
else
{
Security
.
addProvider
(
BouncyCastleProvider
())
verifyAPKSignature
(
BufferedInputStream
(
FileInputStream
(
applicationInfo
.
getApkFile
(
context
[
0
],
fullData
.
basicData
).
absolutePath
)),
fullData
.
getLastVersion
()
!!
.
signature
.
byteInputStream
(
Charsets
.
UTF_8
),
context
[
0
].
assets
.
open
(
"f-droid.org-signing-key.gpg"
))
}
return
context
[
0
]
}
override
fun
onPostExecute
(
context
:
Context
)
{
integrityVerificationCallback
.
onIntegrityVerified
(
context
,
verificationSuccessful
)
}
private
fun
getApkFileSha1
(
file
:
File
):
String
{
val
messageDigest
=
MessageDigest
.
getInstance
(
"SHA-1"
)
val
fileInputStream
=
FileInputStream
(
file
)
var
length
=
0
val
buffer
=
ByteArray
(
8192
)
while
(
length
!=
-
1
)
{
length
=
fileInputStream
.
read
(
buffer
)
if
(
length
>
0
)
{
messageDigest
.
update
(
buffer
,
0
,
length
)
}
}
return
String
(
Hex
.
encodeHex
(
messageDigest
.
digest
()))
}
private
fun
verifyAPKSignature
(
apkInputStream
:
BufferedInputStream
,
apkSignatureInputStream
:
InputStream
,
publicKeyInputStream
:
InputStream
):
Boolean
{
var
jcaPGPObjectFactory
=
JcaPGPObjectFactory
(
PGPUtil
.
getDecoderStream
(
apkSignatureInputStream
))
val
pgpSignatureList
:
PGPSignatureList
val
pgpObject
=
jcaPGPObjectFactory
.
nextObject
()
if
(
pgpObject
is
PGPCompressedData
)
{
jcaPGPObjectFactory
=
JcaPGPObjectFactory
(
pgpObject
.
dataStream
)
pgpSignatureList
=
jcaPGPObjectFactory
.
nextObject
()
as
PGPSignatureList
}
else
{
pgpSignatureList
=
pgpObject
as
PGPSignatureList
}
val
pgpPublicKeyRingCollection
=
PGPPublicKeyRingCollection
(
PGPUtil
.
getDecoderStream
(
publicKeyInputStream
),
JcaKeyFingerprintCalculator
())
val
signature
=
pgpSignatureList
.
get
(
0
)
val
key
=
pgpPublicKeyRingCollection
.
getPublicKey
(
signature
.
keyID
)
signature
.
init
(
JcaPGPContentVerifierBuilderProvider
().
setProvider
(
"BC"
),
key
)
var
character
=
apkInputStream
.
read
()
while
(
character
>=
0
)
{
signature
.
update
(
character
.
toByte
())
character
=
apkInputStream
.
read
()
}
apkInputStream
.
close
()
apkSignatureInputStream
.
close
()
publicKeyInputStream
.
close
()
return
signature
.
verify
()
}
}
interface
IntegrityVerificationCallback
{
fun
onIntegrityVerified
(
context
:
Context
,
verificationSuccessful
:
Boolean
)
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment