-
Notifications
You must be signed in to change notification settings - Fork 450
/
ApnsSigningKey.java
171 lines (150 loc) · 7.2 KB
/
ApnsSigningKey.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
* Copyright (c) 2020 Jon Chambers
*
* 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.eatthepath.pushy.apns.auth;
import java.io.*;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
/**
* A private key used to sign authentication tokens. Signing keys are associated with a developer team (in Apple's
* parlance), and can be used to sign authentication tokens for any topic associated with that team. Signing key
* instances are immutable and thread-safe.
*
* @author <a href="https://github.com/jchambers">Jon Chambers</a>
*
* @since 0.10
*/
public class ApnsSigningKey extends ApnsKey implements ECPrivateKey {
private static final long serialVersionUID = 1L;
/**
* Constructs a new signing key with the given key identifier, team identifier, and elliptic curve private key.
*
* @param keyId the ten-character, Apple-issued identifier for the key itself
* @param teamId the ten-character, Apple-issued identifier for the team to which the key belongs
* @param key the elliptic curve public key underpinning this verification key
*
* @throws NoSuchAlgorithmException if the {@value APNS_SIGNATURE_ALGORITHM} algorith is not supported by the JVM
* @throws InvalidKeyException if the given elliptic curve private key is invalid for any reason
*/
public ApnsSigningKey(final String keyId, final String teamId, final ECPrivateKey key) throws NoSuchAlgorithmException, InvalidKeyException {
super(keyId, teamId, key);
// This is a little goofy, but we want to check early for missing algorithms or bogus keys, and the most direct
// way to do that is to try to actually use the key to create a signature.
final Signature signature = Signature.getInstance(ApnsKey.APNS_SIGNATURE_ALGORITHM);
signature.initSign(key);
}
@Override
protected ECPrivateKey getKey() {
return (ECPrivateKey) super.getKey();
}
@Override
public String getAlgorithm() {
return this.getKey().getAlgorithm();
}
@Override
public String getFormat() {
return this.getKey().getFormat();
}
@Override
public byte[] getEncoded() {
return this.getKey().getEncoded();
}
@Override
public BigInteger getS() {
return this.getKey().getS();
}
/**
* Loads a signing key from the given PKCS#8 file.
*
* @param pkcs8File the file from which to load the key
* @param teamId the ten-character, Apple-issued identifier for the team to which the key belongs
* @param keyId the ten-character, Apple-issued identitifier for the key itself
*
* @return an APNs signing key with the given key ID and associated with the given team
*
* @throws IOException if a key could not be loaded from the given file for any reason
* @throws NoSuchAlgorithmException if the JVM does not support elliptic curve keys
* @throws InvalidKeyException if the loaded key is invalid for any reason
*/
public static ApnsSigningKey loadFromPkcs8File(final File pkcs8File, final String teamId, final String keyId) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try (final FileInputStream fileInputStream = new FileInputStream(pkcs8File)) {
return ApnsSigningKey.loadFromInputStream(fileInputStream, teamId, keyId);
}
}
/**
* Loads a signing key from the given input stream.
*
* @param inputStream the input stream from which to load the key
* @param teamId the ten-character, Apple-issued identifier for the team to which the key belongs
* @param keyId the ten-character, Apple-issued identitifier for the key itself
*
* @return an APNs signing key with the given key ID and associated with the given team
*
* @throws IOException if a key could not be loaded from the given file for any reason
* @throws NoSuchAlgorithmException if the JVM does not support elliptic curve keys
* @throws InvalidKeyException if the loaded key is invalid for any reason
*/
public static ApnsSigningKey loadFromInputStream(final InputStream inputStream, final String teamId, final String keyId) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
final ECPrivateKey signingKey;
{
final String base64EncodedPrivateKey;
{
final StringBuilder privateKeyBuilder = new StringBuilder();
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
boolean haveReadHeader = false;
boolean haveReadFooter = false;
for (String line; (line = reader.readLine()) != null; ) {
if (!haveReadHeader) {
if (line.contains("BEGIN PRIVATE KEY")) {
haveReadHeader = true;
}
} else {
if (line.contains("END PRIVATE KEY")) {
haveReadFooter = true;
break;
} else {
privateKeyBuilder.append(line);
}
}
}
if (!(haveReadHeader && haveReadFooter)) {
throw new IOException("Could not find private key header/footer");
}
base64EncodedPrivateKey = privateKeyBuilder.toString();
}
final byte[] keyBytes = decodeBase64EncodedString(base64EncodedPrivateKey);
final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
final KeyFactory keyFactory = KeyFactory.getInstance("EC");
try {
signingKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (final InvalidKeySpecException e) {
throw new InvalidKeyException(e);
}
}
return new ApnsSigningKey(keyId, teamId, signingKey);
}
}