Java tutorial
/* * Copyright (c) 2013-2017 Turo * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.turo.pushy.apns; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; import com.turo.pushy.apns.auth.ApnsKey; import com.turo.pushy.apns.auth.ApnsSigningKey; import com.turo.pushy.apns.auth.ApnsVerificationKey; import org.apache.commons.codec.binary.Base64; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.Signature; import java.security.SignatureException; import java.util.Date; import java.util.concurrent.TimeUnit; class AuthenticationToken { private static class AuthenticationTokenHeader { @SerializedName("alg") private final String algorithm = "ES256"; @SerializedName("typ") private final String tokenType = "JWT"; @SerializedName("kid") private final String keyId; public AuthenticationTokenHeader(final String keyId) { this.keyId = keyId; } @SuppressWarnings("unused") public String getAlgorithm() { return this.algorithm; } @SuppressWarnings("unused") public String getTokenType() { return this.tokenType; } public String getKeyId() { return this.keyId; } } private static class AuthenticationTokenClaims { @SerializedName("iss") private final String issuer; @SerializedName("iat") private final Date issuedAt; public AuthenticationTokenClaims(final String teamId, final Date issuedAt) { this.issuer = teamId; this.issuedAt = issuedAt; } public String getIssuer() { return this.issuer; } public Date getIssuedAt() { return this.issuedAt; } } private static final Gson GSON = new GsonBuilder().disableHtmlEscaping() .registerTypeAdapter(Date.class, new DateAsTimeSinceEpochTypeAdapter(TimeUnit.SECONDS)).create(); private final AuthenticationTokenHeader header; private final AuthenticationTokenClaims claims; private final byte[] signatureBytes; private final String base64EncodedToken; public AuthenticationToken(final ApnsSigningKey signingKey, final Date issuedAt) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { this.header = new AuthenticationTokenHeader(signingKey.getKeyId()); this.claims = new AuthenticationTokenClaims(signingKey.getTeamId(), issuedAt); final String headerJson = GSON.toJson(this.header); final String claimsJson = GSON.toJson(this.claims); final StringBuilder payloadBuilder = new StringBuilder(); payloadBuilder.append(Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.US_ASCII))); payloadBuilder.append('.'); payloadBuilder.append(Base64.encodeBase64URLSafeString(claimsJson.getBytes(StandardCharsets.US_ASCII))); { final Signature signature = Signature.getInstance(ApnsKey.APNS_SIGNATURE_ALGORITHM); signature.initSign(signingKey); signature.update(payloadBuilder.toString().getBytes(StandardCharsets.US_ASCII)); this.signatureBytes = signature.sign(); } payloadBuilder.append('.'); payloadBuilder.append(Base64.encodeBase64URLSafeString(this.signatureBytes)); this.base64EncodedToken = payloadBuilder.toString(); } public AuthenticationToken(final String base64EncodedToken) { this.base64EncodedToken = base64EncodedToken; final String[] pieces = this.base64EncodedToken.split("\\."); if (pieces.length != 3) { throw new IllegalArgumentException(); } this.header = GSON.fromJson(new String(Base64.decodeBase64(pieces[0]), StandardCharsets.US_ASCII), AuthenticationTokenHeader.class); this.claims = GSON.fromJson(new String(Base64.decodeBase64(pieces[1]), StandardCharsets.US_ASCII), AuthenticationTokenClaims.class); this.signatureBytes = Base64.decodeBase64(pieces[2]); } public Date getIssuedAt() { return this.claims.getIssuedAt(); } public String getKeyId() { return this.header.getKeyId(); } public String getTeamId() { return this.claims.getIssuer(); } public boolean verifySignature(final ApnsVerificationKey verificationKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { if (!this.header.getKeyId().equals(verificationKey.getKeyId())) { return false; } if (!this.claims.getIssuer().equals(verificationKey.getTeamId())) { return false; } final byte[] headerAndClaimsBytes; final String headerJson = GSON.toJson(this.header); final String claimsJson = GSON.toJson(this.claims); final StringBuilder headerAndClaimsBuilder = new StringBuilder(); headerAndClaimsBuilder .append(Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.US_ASCII))); headerAndClaimsBuilder.append('.'); headerAndClaimsBuilder .append(Base64.encodeBase64URLSafeString(claimsJson.getBytes(StandardCharsets.US_ASCII))); headerAndClaimsBytes = headerAndClaimsBuilder.toString().getBytes(StandardCharsets.US_ASCII); final Signature signature = Signature.getInstance(ApnsKey.APNS_SIGNATURE_ALGORITHM); signature.initVerify(verificationKey); signature.update(headerAndClaimsBytes); return signature.verify(this.signatureBytes); } @Override public String toString() { return this.base64EncodedToken; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.base64EncodedToken == null) ? 0 : this.base64EncodedToken.hashCode()); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof AuthenticationToken)) { return false; } final AuthenticationToken other = (AuthenticationToken) obj; if (this.base64EncodedToken == null) { if (other.base64EncodedToken != null) { return false; } } else if (!this.base64EncodedToken.equals(other.base64EncodedToken)) { return false; } return true; } }