Skip to content
Merged
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
15 changes: 13 additions & 2 deletions jpasskit/src/main/java/de/brendamour/jpasskit/PKPass.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
*/
package de.brendamour.jpasskit;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;
import java.net.URL;
import java.time.Instant;
Expand Down Expand Up @@ -103,7 +107,9 @@ public class PKPass implements Cloneable, Serializable {
protected URL managementURL;
protected String transitProviderPhoneNumber;
protected String transitProviderEmail;
protected URL transitProviderWebsiteUrl;
@JsonProperty("transitProviderWebsiteURL")
@JsonAlias("transitProviderWebsiteUrl")
protected URL transitProviderWebsiteURL;
protected URL upgradeURL;
protected URL bagPolicyURL;
protected URL accessibilityURL;
Expand Down Expand Up @@ -284,8 +290,13 @@ public String getTransitProviderPhoneNumber() {
public String getTransitProviderEmail() {
return transitProviderEmail;
}
@Deprecated
@JsonIgnore
public URL getTransitProviderWebsiteUrl() {
return transitProviderWebsiteUrl;
return getTransitProviderWebsiteURL();
}
public URL getTransitProviderWebsiteURL() {
return transitProviderWebsiteURL;
}
public URL getUpgradeURL() {
return upgradeURL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,13 @@ public PKPassBuilder transitProviderEmail(String transitProviderEmail) {
return this;
}

@Deprecated
public PKPassBuilder transitProviderWebsiteUrl(URL transitProviderWebsiteUrl) {
this.pkPass.transitProviderWebsiteUrl = transitProviderWebsiteUrl;
return transitProviderWebsiteURL(transitProviderWebsiteUrl);
}

public PKPassBuilder transitProviderWebsiteURL(URL transitProviderWebsiteURL) {
this.pkPass.transitProviderWebsiteURL = transitProviderWebsiteURL;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,35 @@ public void testWebServiceURLSetter() {
}
}

@Test
public void testTransitProviderWebsiteURLBuilderMethods() throws MalformedURLException {
URL website = new URL("https://example.com/transit");

PKPass pass = builder
.serialNumber("123")
.passTypeIdentifier("com.test.pass")
.teamIdentifier("TEAM123")
.description("Test Pass")
.organizationName("Test Org")
.pass(PKGenericPass.builder())
.transitProviderWebsiteURL(website)
.build();

Assert.assertEquals(pass.getTransitProviderWebsiteURL(), website);

PKPass passFromDeprecated = PKPass.builder()
.serialNumber("123")
.passTypeIdentifier("com.test.pass")
.teamIdentifier("TEAM123")
.description("Test Pass")
.organizationName("Test Org")
.pass(PKGenericPass.builder())
.transitProviderWebsiteUrl(website)
.build();

Assert.assertEquals(passFromDeprecated.getTransitProviderWebsiteURL(), website);
}

@Test
public void testExpirationDateSetter() {
Date expirationDate = new Date();
Expand Down
34 changes: 34 additions & 0 deletions jpasskit/src/test/java/de/brendamour/jpasskit/PKPassTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,40 @@ public void test_passSemanticsSerialization() {
});
}

@Test
public void test_transitProviderWebsiteURL_serializationAndAliasDeserialization() throws MalformedURLException {
var website = new URL("https://example.com/transit");
var pass = this.builder
.serialNumber("123")
.passTypeIdentifier("com.test.pass")
.teamIdentifier("TEAM123")
.description("Test Pass")
.organizationName("Test Org")
.pass(PKGenericPass.builder())
.transitProviderWebsiteURL(website)
.build();

assertThat(pass.getTransitProviderWebsiteURL()).isEqualTo(website);
assertThat(pass.getTransitProviderWebsiteUrl()).isEqualTo(website);

var mapper = new ObjectMapper();
var serializedPass = mapper.convertValue(pass, Map.class);
assertThat(serializedPass).containsKey("transitProviderWebsiteURL");
assertThat(serializedPass).doesNotContainKey("transitProviderWebsiteUrl");

var aliasInput = ImmutableMap.<String, Object>of(
"serialNumber", "123",
"passTypeIdentifier", "com.test.pass",
"teamIdentifier", "TEAM123",
"description", "Test Pass",
"organizationName", "Test Org",
"generic", ImmutableMap.of(),
"transitProviderWebsiteUrl", website.toString()
);
var fromAlias = mapper.convertValue(aliasInput, PKPass.class);
assertThat(fromAlias.getTransitProviderWebsiteURL()).isEqualTo(website);
}

private static URL asUrl(String value) {
try {
return new URL(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (C) 2024 Patrice Brend'amour <patrice@brendamour.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.brendamour.jpasskit.signing;

import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class PKSigningInformationUtilTest {

private static final String KEYSTORE_PATH = "passbook/jpasskittest.p12";
private static final String KEYSTORE_PASSWORD = "password";
private static final String APPLE_WWDRCA_CERT_PATH = "passbook/ca-chain.cert.pem";

@Test
public void testLoadSigningInformationFromPKCS12AndIntermediateCertificate_classpath() throws Exception {
PKSigningInformation info = new PKSigningInformationUtil()
.loadSigningInformationFromPKCS12AndIntermediateCertificate(KEYSTORE_PATH, KEYSTORE_PASSWORD, APPLE_WWDRCA_CERT_PATH);

Assert.assertNotNull(info);
Assert.assertTrue(info.isValid());
Assert.assertNotNull(info.getSigningPrivateKey());
Assert.assertNotNull(info.getSigningCert());
Assert.assertNotNull(info.getAppleWWDRCACert());
}

@Test
public void testLoadSigningInformation_wrapsExceptions() {
assertThatThrownBy(() -> new PKSigningInformationUtil()
.loadSigningInformation("does-not-exist.p12", "password", "passbook/ca-chain.cert.pem"))
.isInstanceOf(PKSigningException.class)
.hasMessage("Failed to load signing information");
}

@Test
public void testDeprecatedLoadDERCertificate_invalidInputStreamThrowsIOException() {
InputStream invalid = new ByteArrayInputStream("not-a-cert".getBytes(StandardCharsets.UTF_8));

assertThatThrownBy(() -> new PKSigningInformationUtil().loadDERCertificate(invalid))
.isInstanceOfAny(CertificateException.class, java.io.IOException.class);
}

@Test
public void testDeprecatedLoadPKCS12File_invalidInputStreamThrowsIOException() {
InputStream invalid = new ByteArrayInputStream("not-a-keystore".getBytes(StandardCharsets.UTF_8));

assertThatThrownBy(() -> new PKSigningInformationUtil().loadPKCS12File(invalid, "password"))
.isInstanceOfAny(CertificateException.class, java.io.IOException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright (C) 2024 Patrice Brend'amour <patrice@brendamour.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.brendamour.jpasskit.util;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class CertUtilsTest {

private static final String KEYSTORE_PATH = "passbook/jpasskittest.p12";
private static final char[] KEYSTORE_PASSWORD = "password".toCharArray();
private static final String CERTIFICATE_PATH = "passbook/ca-chain.cert.pem";
private static final String CERTIFICATE_WITH_UID_PATH = "passbook/expired_cert.p12";

@Test
public void testToInputStream_missingFileThrows() {
assertThatThrownBy(() -> CertUtils.toInputStream("does-not-exist.p12"))
.isInstanceOf(FileNotFoundException.class);
}

@Test
public void testToKeyStore_extractCertificateWithKey() throws Exception {
try (InputStream keyStoreStream = CertUtils.toInputStream(KEYSTORE_PATH)) {
KeyStore keyStore = CertUtils.toKeyStore(keyStoreStream, KEYSTORE_PASSWORD);
ImmutablePair<PrivateKey, X509Certificate> pair = CertUtils.extractCertificateWithKey(keyStore, KEYSTORE_PASSWORD);

assertThat(pair.getLeft()).isNotNull();
assertThat(pair.getRight()).isNotNull();
}
}

@Test
public void testToX509Certificate_andExtractApnsTopics() throws Exception {
try (InputStream certificateStream = CertUtils.toInputStream(CERTIFICATE_PATH)) {
X509Certificate certificate = CertUtils.toX509Certificate(certificateStream);
assertThat(certificate).isNotNull();

// This certificate typically doesn't contain the Pass-specific topic extension; still exercises null-extension branch.
assertThat(CertUtils.extractApnsTopics(certificate)).isNotNull();
}

// Use an APNS-like Pass certificate with UID in subject to exercise UID extraction.
try (InputStream expiredCertP12 = CertUtils.toInputStream(CERTIFICATE_WITH_UID_PATH)) {
KeyStore keyStore = CertUtils.toKeyStore(expiredCertP12, "cert".toCharArray());
ImmutablePair<PrivateKey, X509Certificate> pair = CertUtils.extractCertificateWithKey(keyStore, "cert".toCharArray());

// May be expired; extractApnsTopics doesn't validate.
var topics = CertUtils.extractApnsTopics(pair.getRight());
Assert.assertNotNull(topics);
}
}
}
Loading