Eine .NET-Standardbibliothek zum Generieren von Passbook-Paketen für iOS Wallet (ehemals Passbook)
Wenn Sie dotnet-passbook verwenden, denken Sie bitte darüber nach, mir eine Tasse Kaffee zu kaufen
dotnet-passbook kann auch von NuGet heruntergeladen werden
Install-Package dotnet-passbook
Das Erstellen von Pässen für Apples Passbook ist ziemlich einfach, erfordert jedoch die Verwendung von PKI zum Signieren von Manifestdateien, was nicht so einfach ist! Im Zuge der Erstellung von PassVerse.com (nicht mehr verfügbar) habe ich eine .Net-Bibliothek erstellt, die alle Schritte ausführt. Ich habe beschlossen, diese Bibliothek als Open Source für andere .NET-Entwickler bereitzustellen.
Die Lösung erfordert Visual Studio 2017 oder höher. Die Bibliothek wurde für .NET Standard 2.0 erstellt.
Bevor Sie den PassGenerator ausführen, müssen Sie sicherstellen, dass alle erforderlichen Zertifikate installiert sind. Es sind zwei erforderlich.
Zunächst benötigen Sie Ihr Passbook-Zertifikat, das Sie im Entwicklerportal erhalten. Sie müssen über ein iOS-Entwicklerkonto verfügen.
Zweitens benötigen Sie das Apple WWDR-Zertifikat (WorldWide Developer Relations). Sie können es hier herunterladen: http://www.apple.com/certificateauthority/.
Je nachdem, wann Sie Ihr Passbook-Zertifikat erstellt haben, benötigen Sie entweder das G1- oder das G4-Zertifikat. Wenn Sie Ihr Sparbuchzertifikat am oder vor dem 27. Januar 2022 erstellt haben, verwenden Sie G1. Andernfalls verwenden Sie G4.
Die anderen hier aufgeführten „Worldwide Developer Relations“-Zertifikate funktionieren nicht mit Apple Wallet. („Leider kann Ihr Pass derzeit nicht in Passbook installiert werden.“)
In meinem Blog finden Sie Anweisungen zum Generieren eines Zertifikats mithilfe von IIS, wenn Sie einen Windows-Computer verwenden
Wenn Sie Linux/macOS verwenden oder lieber OpenSSL unter Windows verwenden möchten, finden Sie unter using-openssl.md Anweisungen zum Erstellen der erforderlichen Zertifikate mit OpenSSL.
Stellen Sie beim Verschieben von Zertifikaten sicher, dass Ihr Passbook-Zertifikat immer die private Schlüsselkomponente enthält, da sonst die Signatur fehlschlägt.
Um einen Pass zu generieren, deklarieren Sie zunächst einen PassGenerator.
PassGenerator generator = new PassGenerator ( ) ;
Erstellen Sie als Nächstes eine PassGeneratorRequest. Hierbei handelt es sich um eine Rohanfrage, die Ihnen die Möglichkeit gibt, alle Felder hinzuzufügen, die für den Pass, den Sie erstellen möchten, erforderlich sind. Jeder Durchgang ist in mehrere Abschnitte unterteilt. Jeder Abschnitt wird auf unterschiedliche Weise gerendert, basierend auf dem Stil des Durchgangs, den Sie erstellen möchten. Weitere Informationen hierzu finden Sie im Passbook-Programmierhandbuch von Apple. Das folgende Beispiel zeigt, wie man eine sehr einfache Bordkarte erstellt.
Da jeder Durchgang über einen Satz obligatorischer Daten verfügt, füllen Sie diese zuerst aus.
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . PassTypeIdentifier = " pass.tomsamcguinness.events " ;
request . TeamIdentifier = " RW121242 " ;
request . SerialNumber = " 121212 " ;
request . Description = " My first pass " ;
request . OrganizationName = " Tomas McGuinness " ;
request . LogoText = " My Pass " ;
Farben können im HTML-Format oder im RGB-Format angegeben werden.
request . BackgroundColor = " #FFFFFF " ;
request . LabelColor = " #000000 " ;
request . ForegroundColor = " #000000 " ;
request . BackgroundColor = " rgb(255,255,255) " ;
request . LabelColor = " rgb(0,0,0) " ;
request . ForegroundColor = " rgb(0,0,0) " ;
Sie müssen sowohl das Apple WWDR- als auch Ihr Passbook-Zertifikat als X509Certificate-Instanzen bereitstellen. HINWEIS: Dies ist eine Änderung gegenüber früheren Versionen.
request . AppleWWDRCACertificate = new X509Certificate ( .. . ) ;
request . PassbookCertificate = new X509Certificate ( .. . ) ;
Wenn Sie die X509Certificate-Instanzen in der Cloud erstellen, können beim Signieren Probleme auftreten. Ich empfehle Ihnen, MachineKeySet und Exportable X509KeyStorageFlags zu verwenden.
X509KeyStorageFlags flags = X509KeyStorageFlags . MachineKeySet | X509KeyStorageFlags . Exportable ;
X509Certificate2 certificate = new X509Certificate2 ( bytes , password , flags ) ;
Als nächstes definieren Sie die Bilder, die Sie verwenden möchten. Sie müssen immer sowohl Bilder im Standard- als auch im Retina-Format beifügen. Bilder werden als Byte[] bereitgestellt.
request . Images . Add ( PassbookImage . Icon , System . IO . File . ReadAllBytes ( Server . MapPath ( " ~/Icons/icon.png " ) ) ) ;
request . Images . Add ( PassbookImage . Icon2X , System . IO . File . ReadAllBytes ( Server . MapPath ( " ~/Icons/[email protected] " ) ) ) ;
request . Images . Add ( PassbookImage . Icon3X , System . IO . File . ReadAllBytes ( Server . MapPath ( " ~/Icons/[email protected] " ) ) ) ;
Sie können jetzt mehr passspezifische Informationen bereitstellen. Der Stil muss festgelegt werden und dann werden alle Informationen den Feldern der erforderlichen Abschnitte hinzugefügt. Für eine Bordkarte werden die Felder in drei Abschnitte eingeteilt; primär, sekundär und hilfsweise.
request . Style = PassStyle . BoardingPass ;
request . AddPrimaryField ( new StandardField ( " origin " , " San Francisco " , " SFO " ) ) ;
request . AddPrimaryField ( new StandardField ( " destination " , " London " , " LDN " ) ) ;
request . AddSecondaryField ( new StandardField ( " boarding-gate " , " Gate " , " A55 " ) ) ;
request . AddAuxiliaryField ( new StandardField ( " seat " , " Seat " , " G5 " ) ) ;
request . AddAuxiliaryField ( new StandardField ( " passenger-name " , " Passenger " , " Thomas Anderson " ) ) ;
request . TransitType = TransitType . PKTransitTypeAir ;
Sie können einen Barcode hinzufügen.
request . AddBarcode ( BarcodeType . PKBarcodeFormatPDF417 , " 01927847623423234234 " , " ISO-8859-1 " , " 01927847623423234234 " ) ;
Ab iOS 9 werden nun mehrere Barcodes unterstützt. Diese Hilfsmethode unterstützt diese neue Funktion. Wenn Sie iOS 8 und früher unterstützen möchten, können Sie die Methode SetBarcode() verwenden.
Um den Pass mit einer vorhandenen App zu verknüpfen, können Sie die Apple-ID der App zum AssociatedStoreIdentifiers-Array hinzufügen.
request . AssociatedStoreIdentifiers . Add ( 551768478 ) ;
Generieren Sie abschließend den Pass, indem Sie die Anforderung an die Instanz des Generators übergeben. Dadurch wird das signierte Manifest erstellt und alle Bilddateien in ZIP gepackt.
byte [ ] generatedPass = generator . Generate ( request ) ;
Wenn Sie beispielsweise ASP.NET MVC verwenden, können Sie dieses Byte[] als Passbook-Paket zurückgeben
return new FileContentResult ( generatedPass , " application/vnd.apple.pkpass " ) ;
Mit iOS 15 wurde die Möglichkeit eingeführt, mehrere Pässe mithilfe einer einzelnen .pkpasses-Datei zu bündeln und zu verteilen. Sie können auch Pass-Bundles generieren, indem Sie ein Wörterbuch mit Anforderungswerten und Zeichenfolgenschlüsseln übergeben, die den Dateinamen für jede einzelne Anforderung darstellen.
PassGeneratorRequest myFirstRequest = new PassGeneratorRequest ( ) ;
PassGeneratorRequest mySecondRequest = new PassGeneratorRequest ( ) ;
// Build out your requests
List < PassGeneratorRequest > requests = new List < PassGeneratorRequest > ;
requests . Add ( myFirstRequest ) ;
request . Add ( mySecondRequest ) ;
byte [ ] generatedBundle = generator . Generate ( requests ) ;
Das resultierende Byte-Array wird fast genauso behandelt wie eine einzelne .pkpass
Datei, jedoch mit einer anderen Erweiterung und einem anderen MIME-Typ ( pkpasses ).
return new FileContentResult ( generatedBundle , " application/vnd.apple.pkpasses " )
{
FileDownloadName = " tickets.pkpasses.zip "
} ;
PassGenerator
Klasse entspricht der IPassGenerator
Schnittstelle, die Methoden offenlegt, die zum Generieren der Durchgänge verwendet werden. Wenn Sie über eine benutzerdefinierte Logik verfügen, die die Pass-Generierungsanforderung erstellt, können Sie die IPassGenerator
Schnittstelle mithilfe einer beliebigen Mock-Bibliothek einfach nachahmen und testen, ob Ihre Logik die Generate
-Methode mit der richtigen Generatoranforderung aufruft.
Angenommen, Sie verfügen über einen Dienst, der über DI eine Generatoranfrage empfängt und auf der Grundlage der Anfrage einen Pass generiert.
class PassGeneratorService ( IPassGenerator passGenerator )
{
public byte [ ] GeneratePassWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
// make a request based on parameters
....
passGenerator . Generate ( request ) ;
}
}
Sie können diesen Dienst ganz einfach testen, indem Sie die IPassGenerator
Schnittstelle simulieren und überprüfen, ob die Generate
-Methode mit der richtigen Anforderung aufgerufen wird. Hier ist ein Beispiel mit NSubstitute und xUnit.
[ Fact ]
void ServiceUsesPassedParamsForRequest ( )
{
// Arrange
var passGeneratorMock = Substitute . For < IPassGenerator > ( ) ;
var sut = new PassGeneratorService ( passGeneratorMock ) ;
// Act
sut . GeneratePassWithLogoTextAndBackgroundColor ( " Cup'o'Joe " , " #0CAFE0 " ) ;
// Assert/Verify
passGeneratorMock . Received ( ) . Generate ( Arg . Is < PassGeneratorRequest > ( r =>
{
r . LogoText == " Cup'o'Joe " && r . BackgroundColor == " #0CAFE0 "
} ) ) ;
}
Eine andere Möglichkeit zu testen, ob Ihre Logik gut funktioniert, besteht darin, das erstellte PassGeneratorRequest
Objekt selbst zu testen. Sie können ein Anforderungsobjekt erstellen und die Eigenschaften basierend auf Ihrer Logik festlegen und dann testen, ob das Anforderungsobjekt korrekt erstellt wurde. Betrachten Sie diesen einfachen Anforderungsersteller:
class PassGeneratorRequestBuilder
{
public PassGeneratorRequest BuildRequestWithLogoTextAndBackgroundColor ( String logoText , String backgroundColor )
{
var request = new PassGeneratorRequest ( ) ;
request . LogoText = logoText ;
request . BackgroundColor = backgroundColor ;
return request ;
}
}
Sie können diesen Builder testen, indem Sie ein Anforderungsobjekt erstellen und überprüfen, ob die Eigenschaften richtig festgelegt sind.
[ Fact ]
void RequestBuilderSetsPropertiesCorrectly ( )
{
// Arrange
var sut = new PassGeneratorRequestBuilder ( ) ;
// Act
var request = sut . BuildRequestWithLogoTextAndBackgroundColor ( " Cup'o'Joe " , " #0CAFE0 " ) ;
// Assert
Assert . Equal ( " Cup'o'Joe " , request . LogoText ) ;
Assert . Equal ( " #0CAFE0 " , request . BackgroundColor ) ;
}
Jetzt müssen Sie nur noch sicherstellen, dass die Anfrage auf dem Weg zur PassGenerator
-Klasse nicht verändert/mutiert wird.
Wenn die von Ihnen erstellten Pässe auf iOS oder im Simulator nicht geöffnet zu werden scheinen, ist die Nutzlast wahrscheinlich ungültig. Um die Fehlerbehebung zu erleichtern, habe ich dieses einfache Tool erstellt – https://pkpassvalidator.azurewebsites.net – führen Sie einfach Ihre pkpass
Datei durch und es gibt möglicherweise eine Vorstellung davon, was falsch ist. Das Tool ist neu (18. Juli) und überprüft nicht absolut alles. Ich werde versuchen, dem Generator selbst mehr Validierung hinzuzufügen.
Wenn Sie den Signaturcode in einer IIS-Anwendung ausführen, können beim Zugriff auf den privaten Schlüssel Ihrer Zertifikate Probleme auftreten. Um dieses Problem zu beheben, öffnen Sie das MMC-Snap-In => Zertifikate hinzufügen (Lokaler Computer) => Zertifikate (Lokaler Computer) => Persönlich => Zertifikate => Klicken Sie mit der rechten Maustaste auf das gewünschte Zertifikat => Alle Aufgaben => Privaten Schlüssel verwalten => IIS hinzufügen AppPoolAppPoolName und gewähren Sie ihm Vollzugriff. Ersetzen Sie „AppPoolName“ durch den Namen des Anwendungspools, unter dem Ihre App ausgeführt wird. (manchmal IIS_IUSRS
)
Um Ihren Pass aktualisieren zu können, müssen Sie ihn mit einem Rückruf versehen. Wenn Sie Ihre Anfrage generieren, müssen Sie ihr ein AuthenticationToken und eine WebServiceUrl bereitstellen. Beide Werte sind erforderlich. Die WebServiceUrl muss standardmäßig HTTPS sein, aber Sie können diese Anforderung in den iOS-Entwickleroptionen auf jedem Gerät, auf dem Sie testen, deaktivieren.
Das Authentifizierungstoken ist eine Zeichenfolge, die in den Header aller an Ihre API gestellten Anforderungen eingefügt wird. Es liegt in Ihrer Verantwortung, dieses Token zu validieren.
request . AuthenticationToken = " <a secret to ensure authorized access> " ;
request . WebServiceUrl = " https://<your api> " ;
Der Webdienst, auf den Sie verweisen, muss das Protokoll von Apple unterstützen, das in der PassKit-Webdienstreferenz beschrieben wird
Ab Version 2.0.1 werden nun die NFC-Schlüssel unterstützt. Um sie zu verwenden, legen Sie einfach die Nfc-Eigenschaft mit einem neuen NFC-Objekt fest. Sowohl die Nachricht als auch die codierten öffentlichen Schlüsselwerte sind obligatorisch.
PassGeneratorRequest request = new PassGeneratorRequest ( ) ;
request . Nfc = new Nfc ( " THE NFC Message " , " <encoded private key> " ) ;
Leider kann ich keine Angaben zu den erforderlichen Werten machen, da diese nicht öffentlich verfügbar sind. Wenn jemand weiß, was hier steht, würde ich gerne Änderungen zu meiner Bibliothek hinzufügen, um diesen Schlüssel zu unterstützen.
Alle Pull-Anfragen sind willkommen! Wenn Sie auf ein Problem stoßen, das Sie nicht beheben können, melden Sie es bitte oder schreiben Sie mir eine E-Mail an [email protected] oder folgen Sie mir auf Twitter @tomasmcguinness
dotnet-passbook wird unter der MIT-Lizenz vertrieben: http://tomasmcguinness.mit-license.org/