diff --git a/passkit/lib/src/pkpass/pkpass.dart b/passkit/lib/src/pkpass/pkpass.dart index a55336a..b99b9eb 100644 --- a/passkit/lib/src/pkpass/pkpass.dart +++ b/passkit/lib/src/pkpass/pkpass.dart @@ -55,7 +55,7 @@ class PkPass { /// Parses bytes to a [PkPass] file. /// Setting [skipVerification] to true disables any checksum or signature /// verification and validation. - // TODO(ueman): Provide an async method for this. + // TODO(any): Provide an async method for this. static PkPass fromBytes( final List bytes, { bool skipVerification = false, @@ -76,12 +76,11 @@ class PkPass { final signatureContent = archive.findFile('signature')!.content as List; - final isValid = verifySignature( - Uint8List.fromList(signatureContent), - Uint8List.fromList(sha256.convert(manifestContent).bytes), - passData, + verifySignature( + signature: Uint8List.fromList(signatureContent), + manifestHash: Uint8List.fromList(sha256.convert(manifestContent).bytes), + pass: passData, ); - print(isValid); } return PkPass( diff --git a/passkit/lib/src/signature_verification.dart b/passkit/lib/src/signature_verification.dart index 600c026..b22d0ee 100644 --- a/passkit/lib/src/signature_verification.dart +++ b/passkit/lib/src/signature_verification.dart @@ -1,27 +1,42 @@ import 'dart:typed_data'; import 'package:passkit/passkit.dart'; import 'package:pkcs7/pkcs7.dart'; +import 'package:collection/collection.dart'; -bool verifySignature( - Uint8List signature, - Uint8List manifestHash, - PassData pass, -) { - final wwdrG4 = - X509.fromDer(Uint8List.fromList(Worldwide_Developer_Relations_G4)); +// What about old WWDR certs? Apple seemingly accepts them just fine? +// Only make sure the signing cert matches the pass' contents? +// Should pass updates just have valid certs, or is it fine +// as long as the contents match? +bool verifySignature({ + required Uint8List signature, + required Uint8List manifestHash, + required PassData pass, + DateTime? now, + bool checkOutdatedIssuerCerts = false, +}) { final pkcs7 = Pkcs7.fromDer(signature); -/* - final passKitIssuerCert = pkcs7.certificates.firstWhere((x509) { - return x509.serialNumber == BigInt.parse(pass.serialNumber); + if (checkOutdatedIssuerCerts) { + for (final cert in pkcs7.certificates) { + if (cert.notAfter.isBefore(now ?? DateTime.now())) { + throw Exception('Certificate of the PkPass expired'); + } + } + } + + final issuerCert = pkcs7.certificates.firstWhereOrNull((x509) { + return x509.subject.firstWhereOrNull((it) => it.key.name == 'UID')?.value == + pass.passTypeIdentifier && + x509.subject + .firstWhereOrNull( + (it) => it.key.name == 'organizationalUnitName', + ) + ?.value == + pass.teamIdentifier; }); - passKitIssuerCert.subject.firstWhere((it) => it.key.name == 'UID').value == - pass.passTypeIdentifier; - passKitIssuerCert.subject - .firstWhere((it) => it.key.name == 'organizationalUnitName') - .value == - pass.passTypeIdentifier; - */ + if (issuerCert == null) { + throw Exception("Cert from issues doesn't match content of the pass"); + } // there must be a certificate in pkcs7.certificates which // - has a subject with a UID which matches the passTypeIdentifier @@ -29,16 +44,22 @@ bool verifySignature( // there must be a certificate in there which matches an APPLE WWDR certificate // From https://developer.apple.com/documentation/walletpasses/building_a_pass - // Set the passTypeIdentifier of Pass in the pass.json file to the identifier. Set the serialNumber key to the unique serial number for that identifier. + // Set the passTypeIdentifier of Pass in the pass.json file to the identifier. + // Set the serialNumber key to the unique serial number for that identifier. - final si = pkcs7.verify([wwdrG4]); + final signerInfo = pkcs7.verify([_wwdrG4]); // final algo = si.getDigest(si.digestAlgorithm); Calculate hash based on the algo? - return si.listEquality(manifestHash, si.messageDigest!); + return signerInfo.listEquality(manifestHash, signerInfo.messageDigest!); } -X509 get wwdrG4 => +X509 get _wwdrG4 => X509.fromDer(Uint8List.fromList(Worldwide_Developer_Relations_G4)); +/// This is the content of https://www.apple.com/certificateauthority/AppleWWDRCAG4.cer . +/// It was basically read like this `File('AppleWWDRCAG4.cer').readAsBytesSync()` and then just pasted +/// here. +/// +/// More info at: /// https://developer.apple.com/help/account/reference/wwdr-intermediate-certificates/ /// https://www.apple.com/certificateauthority/ // ignore: constant_identifier_names diff --git a/passkit/pubspec.yaml b/passkit/pubspec.yaml index c494d14..951f7af 100644 --- a/passkit/pubspec.yaml +++ b/passkit/pubspec.yaml @@ -15,6 +15,7 @@ environment: dependencies: archive: ^3.6.0 + collection: ^1.18.0 crypto: ^3.0.0 csslib: ^1.0.0 http: ^1.2.0 diff --git a/passkit/test/AppleWWDRMPCA1G1.cer b/passkit/test/AppleWWDRMPCA1G1.cer deleted file mode 100644 index fe532b2..0000000 Binary files a/passkit/test/AppleWWDRMPCA1G1.cer and /dev/null differ diff --git a/passkit/test/cert_test.dart b/passkit/test/cert_test.dart deleted file mode 100644 index 9f0f64d..0000000 --- a/passkit/test/cert_test.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:io'; - -import 'package:test/test.dart'; - -void main() { - test('foo', () { - final bytes = File('test/AppleWWDRMPCA1G1.cer').readAsBytesSync(); - print(bytes); - }); - - test('bar', () { - final bytes = File('test/AppleWWDRCAG4.cer').readAsBytesSync(); - print(bytes); - }); -} diff --git a/passkit/test/pkpass/checksum_test.dart b/passkit/test/pkpass/checksum_test.dart index 5f08e31..7f2b800 100644 --- a/passkit/test/pkpass/checksum_test.dart +++ b/passkit/test/pkpass/checksum_test.dart @@ -52,7 +52,8 @@ void main() { }); test('does not throw for valid checksums', () async { - final bytes = File('test/sample_passes/kino.pkpass').readAsBytesSync(); + final bytes = + File('test/sample_passes/coffee_club.pkpass').readAsBytesSync(); expect( () => PkPass.fromBytes(bytes), diff --git a/passkit/test/sample_passes/coffee_club.pkpass b/passkit/test/sample_passes/coffee_club.pkpass new file mode 100644 index 0000000..256334c Binary files /dev/null and b/passkit/test/sample_passes/coffee_club.pkpass differ diff --git a/passkit_ui/example/pubspec.lock b/passkit_ui/example/pubspec.lock index 4ece089..5a45ea1 100644 --- a/passkit_ui/example/pubspec.lock +++ b/passkit_ui/example/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" crypto: dependency: transitive description: @@ -131,6 +139,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" json_annotation: dependency: transitive description: @@ -217,6 +233,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + pem: + dependency: transitive + description: + name: pem + sha256: "3dfb24524f805ad694ba3cdbb6387ab31ab661fdb8ea873052ed88487fcfef86" + url: "https://pub.dev" + source: hosted + version: "2.0.5" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + pkcs7: + dependency: transitive + description: + name: pkcs7 + sha256: "13c916f7577a4dbfc638af2266891a8f6bef06117ff78cff8982802419efbd37" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" qr: dependency: transitive description: