Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ pip-log.txt

.DS_Store
.idea/
.vscode
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ v1.0.0, 12/10/2012 -- Fixed barcode serialization and unicode paths.
v1.0.1, 08/05/2015 -- Added new field values and validations

v1.0.2, 07/25/2016 -- Add compatibility with iOS 9 (thanks @mbaechtold)

v1.0.3, 12/14/2020 -- Switch crypto library from m2crypto to cryptography
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# FORK: Use Cryptography instead of M2Crypto
This fork is updated to use the cryptography library for easy install on windows. In addition, m2crypto seems to be unmaintained.

## Installing this fork
You can install this fork like this:
```
pip install git+https://github.com/shivaRamdeen/passbook.git
```

# Passbook

[![Build Status](https://travis-ci.org/devartis/passbook.svg?branch=master)](https://travis-ci.org/devartis/passbook)
Expand Down
99 changes: 64 additions & 35 deletions passbook/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import zipfile
from io import BytesIO

from M2Crypto import SMIME
from M2Crypto import X509
from M2Crypto.X509 import X509_Stack
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import pkcs7


class Alignment:
Expand Down Expand Up @@ -306,7 +306,8 @@ def addFile(self, name, fd):
def create(self, certificate, key, wwdr_certificate, password, zip_file=None):
pass_json = self._createPassJson()
manifest = self._createManifest(pass_json)
signature = self._createSignature(manifest, certificate, key, wwdr_certificate, password)
signature = self._createSignatureCrypto(manifest, certificate, key, wwdr_certificate, password)
# signature = self._createSignature(manifest, certificate, key, wwdr_certificate, password)
if not zip_file:
zip_file = BytesIO()
self._createZip(pass_json, manifest, signature, zip_file=zip_file)
Expand All @@ -324,44 +325,72 @@ def _createManifest(self, pass_json):
self._hashes[filename] = hashlib.sha1(filedata).hexdigest()
return json.dumps(self._hashes)

def _get_smime(self, certificate, key, wwdr_certificate, password):
# def _get_smime(self, certificate, key, wwdr_certificate, password):
# """
# :return: M2Crypto.SMIME.SMIME
# """
# def passwordCallback(*args, **kwds):
# return bytes(password, encoding='ascii')

# smime = SMIME.SMIME()

# wwdrcert = X509.load_cert(wwdr_certificate)
# stack = X509_Stack()
# stack.push(wwdrcert)
# smime.set_x509_stack(stack)

# smime.load_key(key, certfile=certificate, callback=passwordCallback)
# return smime

# def _sign_manifest(self, manifest, certificate, key, wwdr_certificate, password):
# """
# :return: M2Crypto.SMIME.PKCS7
# """
# smime = self._get_smime(certificate, key, wwdr_certificate, password)
# pkcs7 = smime.sign(
# SMIME.BIO.MemoryBuffer(bytes(manifest, encoding='utf8')),
# flags=SMIME.PKCS7_DETACHED | SMIME.PKCS7_BINARY
# )
# return pkcs7

# def _createSignature(self, manifest, certificate, key,
# wwdr_certificate, password):
# """
# Creates a signature (DER encoded) of the manifest. The manifest is the file
# containing a list of files included in the pass file (and their hashes).
# """
# pk7 = self._sign_manifest(manifest, certificate, key, wwdr_certificate, password)
# der = SMIME.BIO.MemoryBuffer()
# pk7.write_der(der)
# return der.read()

def _readFileBytes(self, path):
"""
:return: M2Crypto.SMIME.SMIME
Utility function to read files as byte data
:param path: file path
:returns bytes
"""
def passwordCallback(*args, **kwds):
return bytes(password, encoding='ascii')
file = open(path)
return file.read().encode('UTF-8')

smime = SMIME.SMIME()

wwdrcert = X509.load_cert(wwdr_certificate)
stack = X509_Stack()
stack.push(wwdrcert)
smime.set_x509_stack(stack)

smime.load_key(key, certfile=certificate, callback=passwordCallback)
return smime

def _sign_manifest(self, manifest, certificate, key, wwdr_certificate, password):
"""
:return: M2Crypto.SMIME.PKCS7
"""
smime = self._get_smime(certificate, key, wwdr_certificate, password)
pkcs7 = smime.sign(
SMIME.BIO.MemoryBuffer(bytes(manifest, encoding='utf8')),
flags=SMIME.PKCS7_DETACHED | SMIME.PKCS7_BINARY
)
return pkcs7

def _createSignature(self, manifest, certificate, key,
def _createSignatureCrypto(self, manifest, certificate, key,
wwdr_certificate, password):
"""
Creates a signature (DER encoded) of the manifest. The manifest is the file
Creates a signature (DER encoded) of the manifest.
Rewritten to use cryptography library instead of M2Crypto
The manifest is the file
containing a list of files included in the pass file (and their hashes).
"""
pk7 = self._sign_manifest(manifest, certificate, key, wwdr_certificate, password)
der = SMIME.BIO.MemoryBuffer()
pk7.write_der(der)
return der.read()
cert = x509.load_pem_x509_certificate(self._readFileBytes(certificate))
priv_key = serialization.load_pem_private_key(self._readFileBytes(key), password=password.encode('UTF-8'))
wwdr_cert = x509.load_pem_x509_certificate(self._readFileBytes(wwdr_certificate))

options = [pkcs7.PKCS7Options.DetachedSignature]
return pkcs7.PKCS7SignatureBuilder()\
.set_data(manifest.encode('UTF-8'))\
.add_signer(cert, priv_key, hashes.SHA1())\
.add_certificate(wwdr_cert)\
.sign(serialization.Encoding.DER, options)

# Creates .pkpass (zip archive)
def _createZip(self, pass_json, manifest, signature, zip_file=None):
Expand Down
100 changes: 49 additions & 51 deletions passbook/test/test_passbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
import json

import pytest
from M2Crypto import BIO
from M2Crypto import SMIME
from M2Crypto import X509
from path import Path

from passbook.models import Barcode, BarcodeFormat, CurrencyField, Pass, StoreCard
Expand Down Expand Up @@ -132,54 +129,54 @@ def test_files():
assert '170eed23019542b0a2890a0bf753effea0db181a' == manifest['logo.png']


def test_signing():
"""
This test can only run locally if you provide your personal Apple Wallet
certificates, private key and password. It would not be wise to add
them to git. Store them in the files indicated below, they are ignored
by git.
"""
try:
with open(password_file) as file_:
password = file_.read().strip()
except IOError:
password = ''

passfile = create_shell_pass()
manifest_json = passfile._createManifest(passfile._createPassJson())
signature = passfile._sign_manifest(
manifest_json,
certificate,
key,
wwdr_certificate,
password,
)

smime = passfile._get_smime(
certificate,
key,
wwdr_certificate,
password,
)

store = X509.X509_Store()
try:
store.load_info(bytes(wwdr_certificate, encoding='utf8'))
except TypeError:
store.load_info(str(wwdr_certificate))

smime.set_x509_store(store)

data_bio = BIO.MemoryBuffer(bytes(manifest_json, encoding='utf8'))

# PKCS7_NOVERIFY = do not verify the signers certificate of a signed message.
assert smime.verify(signature, data_bio, flags=SMIME.PKCS7_NOVERIFY) == bytes(manifest_json, encoding='utf8')

tampered_manifest = bytes('{"pass.json": "foobar"}', encoding='utf8')
data_bio = BIO.MemoryBuffer(tampered_manifest)
# Verification MUST fail!
with pytest.raises(SMIME.PKCS7_Error):
smime.verify(signature, data_bio, flags=SMIME.PKCS7_NOVERIFY)
# def test_signing():
# """
# This test can only run locally if you provide your personal Apple Wallet
# certificates, private key and password. It would not be wise to add
# them to git. Store them in the files indicated below, they are ignored
# by git.
# """
# try:
# with open(password_file) as file_:
# password = file_.read().strip()
# except IOError:
# password = ''

# passfile = create_shell_pass()
# manifest_json = passfile._createManifest(passfile._createPassJson())
# signature = passfile._sign_manifest(
# manifest_json,
# certificate,
# key,
# wwdr_certificate,
# password,
# )

# smime = passfile._get_smime(
# certificate,
# key,
# wwdr_certificate,
# password,
# )

# store = X509.X509_Store()
# try:
# store.load_info(bytes(wwdr_certificate, encoding='utf8'))
# except TypeError:
# store.load_info(str(wwdr_certificate))

# smime.set_x509_store(store)

# data_bio = BIO.MemoryBuffer(bytes(manifest_json, encoding='utf8'))

# # PKCS7_NOVERIFY = do not verify the signers certificate of a signed message.
# assert smime.verify(signature, data_bio, flags=SMIME.PKCS7_NOVERIFY) == bytes(manifest_json, encoding='utf8')

# tampered_manifest = bytes('{"pass.json": "foobar"}', encoding='utf8')
# data_bio = BIO.MemoryBuffer(tampered_manifest)
# # Verification MUST fail!
# with pytest.raises(SMIME.PKCS7_Error):
# smime.verify(signature, data_bio, flags=SMIME.PKCS7_NOVERIFY)


def test_passbook_creation():
Expand Down Expand Up @@ -214,3 +211,4 @@ def test_currency_field_has_no_numberstyle():
pass_json = passfile.json_dict()
assert 'currencyCode' in pass_json['storeCard']['headerFields'][0]
assert 'numberStyle' not in pass_json['storeCard']['headerFields'][0]

4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cffi==1.14.4
cryptography==3.3.1
pycparser==2.20
six==1.15.0
1 change: 0 additions & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
M2Crypto
path.py
pytest
tox-travis
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
download_url='http://pypi.python.org/packages/source/P/Passbook/Passbook-%s.tar.gz' % version,

install_requires=[
'M2Crypto >= 0.28.2',
'cryptography==3.3.1',
],

classifiers=[
Expand Down