Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Adélie Linux
apkkit
Commits
b10dc873
Commit
b10dc873
authored
Oct 17, 2015
by
A. Wilcox
🦊
Browse files
I/O: we can actually write unsigned APKs that work now
parent
10d3506a
Changes
2
Hide whitespace changes
Inline
Side-by-side
apkkit/io/apkfile.py
View file @
b10dc873
"""I/O classes and helpers for APK files."""
from
apkkit.base.package
import
Package
# Not used, but we need to raise ImportError if gzip isn't built.
import
gzip
# pylint: disable=unused-import
from
apkkit.io.util
import
recursive_size
import
glob
import
gzip
import
hashlib
import
io
import
logging
import
os
import
shutil
from
subprocess
import
Popen
,
PIPE
import
sys
import
tarfile
from
tempfile
import
mkstemp
LOGGER
=
logging
.
getLogger
(
__name__
)
try
:
import
rsa
except
ImportError
:
LOGGER
.
warning
(
"RSA module is not available - signing packages won't work."
)
def
_ensure_no_debug
(
filename
):
"""tarfile exclusion predicate to ensure /usr/lib/debug isn't included.
:returns bool: True if the file is a debug file, otherwise False.
"""
return
'usr/lib/debug'
in
filename
def
_sign_control
(
control
,
privkey
,
pubkey
):
"""Sign control.tar.
:param control:
A file-like object representing the current control.tar.
:param privkey:
The path to the private key.
:param pubkey:
The public name of the public key (this will be included in the
signature, so it must match /etc/apk/keys/<name>).
:returns:
A file-like object representing the signed control.tar.
"""
control
.
seek
(
0
)
control_hash
=
hashlib
.
sha256
(
control
.
read
())
signature
=
b
'signed sha256sum here'
iosignature
=
io
.
BytesIO
(
signature
)
new_control
=
io
.
BytesIO
()
new_control_tar
=
tarfile
.
open
(
mode
=
'w'
,
fileobj
=
new_control
)
tarinfo
=
tarfile
.
TarInfo
(
'.SIGN.RSA.'
+
pubkey
)
tarinfo
.
size
=
len
(
signature
)
new_control_tar
.
addfile
(
tarinfo
,
fileobj
=
iosignature
)
control
.
seek
(
0
)
new_control
.
seek
(
0
,
2
)
shutil
.
copyfileobj
(
control
,
new_control
)
new_control
.
seek
(
0
)
return
new_control
class
APKFile
:
"""Represents an APK file on disk (or in memory)."""
def
__init__
(
self
,
filename
,
mode
=
'r'
):
self
.
tar
=
tarfile
.
open
(
filename
,
mode
)
self
.
package
=
Package
.
from_pkginfo
(
self
.
tar
.
extractfile
(
'.PKGINFO'
))
def
__init__
(
self
,
filename
=
None
,
mode
=
'r'
,
fileobj
=
None
,
package
=
None
):
if
filename
is
not
None
:
self
.
tar
=
tarfile
.
open
(
filename
,
mode
)
elif
fileobj
is
not
None
:
self
.
tar
=
tarfile
.
open
(
mode
=
mode
,
fileobj
=
fileobj
)
self
.
fileobj
=
fileobj
else
:
raise
ValueError
(
"No filename or file object specified."
)
if
package
is
None
:
self
.
package
=
Package
.
from_pkginfo
(
self
.
tar
.
extractfile
(
'.PKGINFO'
)
)
else
:
self
.
package
=
package
@
classmethod
def
create
(
cls
,
package
,
datadir
,
sign
=
True
,
signfile
=
None
,
data_hash
=
True
,
...
...
@@ -37,4 +111,88 @@ class APKFile:
The hash method to use for hashing the data - default is sha1 to
maintain compatibility with upstream apk-tools.
"""
raise
NotImplementedError
LOGGER
.
info
(
'Creating APK from data in: %s'
,
datadir
)
package
.
size
=
recursive_size
(
datadir
)
# XXX TODO BAD RUN AWAY
# eventually we need to just a write tarfile replacement that can do
# the sign-mangling required for APK
if
sys
.
version_info
[:
2
]
>=
(
3
,
5
):
mode
=
'x'
else
:
mode
=
'w'
fd
,
pkg_data_path
=
mkstemp
(
prefix
=
'apkkit-'
,
suffix
=
'.tar'
)
gzio
=
io
.
BytesIO
()
LOGGER
.
info
(
'Creating data.tar...'
)
with
os
.
fdopen
(
fd
,
'xb'
)
as
fdfile
:
with
tarfile
.
open
(
mode
=
mode
,
fileobj
=
fdfile
,
format
=
tarfile
.
PAX_FORMAT
)
as
data
:
for
item
in
glob
.
glob
(
datadir
+
'/*'
):
data
.
add
(
item
,
arcname
=
os
.
path
.
basename
(
item
),
exclude
=
_ensure_no_debug
)
LOGGER
.
info
(
'Hashing data.tar [pass 1]...'
)
fdfile
.
seek
(
0
)
abuild_pipe
=
Popen
([
'abuild-tar'
,
'--hash'
],
stdin
=
fdfile
,
stdout
=
PIPE
)
LOGGER
.
info
(
'Compressing data...'
)
with
gzip
.
GzipFile
(
mode
=
'wb'
,
fileobj
=
gzio
)
as
gzobj
:
gzobj
.
write
(
abuild_pipe
.
communicate
()[
0
])
# make the datahash
if
data_hash
:
LOGGER
.
info
(
'Hashing data.tar [pass 2]...'
)
gzio
.
seek
(
0
)
hasher
=
getattr
(
hashlib
,
hash_method
)(
gzio
.
read
())
package
.
data_hash
=
hasher
.
hexdigest
()
# if we made the hash, we need to seek back again
# if we didn't, we haven't seeked back yet
gzio
.
seek
(
0
)
# we are finished with fdfile (data.tar), now let's make control
LOGGER
.
info
(
'Creating package header...'
)
control
=
io
.
BytesIO
()
control_tar
=
tarfile
.
open
(
mode
=
mode
,
fileobj
=
control
)
ioinfo
=
io
.
BytesIO
(
package
.
to_pkginfo
().
encode
(
'utf-8'
))
tarinfo
=
tarfile
.
TarInfo
(
'.PKGINFO'
)
ioinfo
.
seek
(
0
,
2
)
tarinfo
.
size
=
ioinfo
.
tell
()
ioinfo
.
seek
(
0
)
control_tar
.
addfile
(
tarinfo
,
fileobj
=
ioinfo
)
# we do NOT close control_tar yet, because we don't want the end of
# archive written out.
if
sign
:
LOGGER
.
info
(
'Signing package...'
)
signfile
=
os
.
getenv
(
'PACKAGE_PRIVKEY'
,
signfile
)
pubkey
=
os
.
getenv
(
'PACKAGE_PUBKEY'
,
os
.
path
.
basename
(
signfile
)
+
'.pub'
)
control
=
_sign_control
(
control
,
signfile
,
pubkey
)
LOGGER
.
info
(
'Compressing package header...'
)
controlgz
=
io
.
BytesIO
()
with
gzip
.
GzipFile
(
mode
=
'wb'
,
fileobj
=
controlgz
)
as
gzobj
:
shutil
.
copyfileobj
(
control
,
gzobj
)
control_tar
.
close
()
# we are done with it now
controlgz
.
seek
(
0
)
LOGGER
.
info
(
'Creating package file (in memory)...'
)
combined
=
io
.
BytesIO
()
shutil
.
copyfileobj
(
controlgz
,
combined
)
shutil
.
copyfileobj
(
gzio
,
combined
)
controlgz
.
close
()
gzio
.
close
()
return
cls
(
fileobj
=
combined
,
package
=
package
)
def
write
(
self
,
path
):
LOGGER
.
info
(
'Writing APK to %s'
,
path
)
self
.
fileobj
.
seek
(
0
)
with
open
(
path
,
'xb'
)
as
new_package
:
shutil
.
copyfileobj
(
self
.
fileobj
,
new_package
)
apkkit/io/util.py
0 → 100644
View file @
b10dc873
"""I/O-related utility functions for APK Kit."""
try
:
from
os
import
scandir
# Python 3.5! :D
except
ImportError
:
from
scandir
import
scandir
# Python older-than-3.5!
def
recursive_size
(
path
):
"""Calculate the size of an entire directory tree, starting at `path`."""
running_total
=
0
for
entry
in
scandir
(
path
):
if
entry
.
is_file
():
running_total
+=
entry
.
stat
().
st_size
elif
entry
.
is_dir
():
running_total
+=
recursive_size
(
entry
.
path
)
return
running_total
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