/
SecurityUtils.java
262 lines (243 loc) · 9.19 KB
/
SecurityUtils.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*
* Copyright (c) 2013 Google Inc.
*
* 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 com.google.api.client.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.X509TrustManager;
/**
* Utilities related to Java security.
*
* @since 1.14
* @author Yaniv Inbar
*/
public final class SecurityUtils {
/** Returns the default key store using {@link KeyStore#getDefaultType()}. */
public static KeyStore getDefaultKeyStore() throws KeyStoreException {
return KeyStore.getInstance(KeyStore.getDefaultType());
}
/** Returns the Java KeyStore (JKS). */
public static KeyStore getJavaKeyStore() throws KeyStoreException {
return KeyStore.getInstance("JKS");
}
/** Returns the PKCS12 key store. */
public static KeyStore getPkcs12KeyStore() throws KeyStoreException {
return KeyStore.getInstance("PKCS12");
}
/**
* Loads a key store from a stream.
*
* <p>Example usage:
*
* <pre>
* KeyStore keyStore = SecurityUtils.getJavaKeyStore();
* SecurityUtils.loadKeyStore(keyStore, new FileInputStream("certs.jks"), "password");
* </pre>
*
* @param keyStore key store
* @param keyStream input stream to the key store stream (closed at the end of this method in a
* finally block)
* @param storePass password protecting the key store file
*/
public static void loadKeyStore(KeyStore keyStore, InputStream keyStream, String storePass)
throws IOException, GeneralSecurityException {
try {
keyStore.load(keyStream, storePass.toCharArray());
} finally {
keyStream.close();
}
}
/**
* Returns the private key from the key store.
*
* @param keyStore key store
* @param alias alias under which the key is stored
* @param keyPass password protecting the key
* @return private key
*/
public static PrivateKey getPrivateKey(KeyStore keyStore, String alias, String keyPass)
throws GeneralSecurityException {
return (PrivateKey) keyStore.getKey(alias, keyPass.toCharArray());
}
/**
* Retrieves a private key from the specified key store stream and specified key store.
*
* @param keyStore key store
* @param keyStream input stream to the key store (closed at the end of this method in a finally
* block)
* @param storePass password protecting the key store file
* @param alias alias under which the key is stored
* @param keyPass password protecting the key
* @return key from the key store
*/
public static PrivateKey loadPrivateKeyFromKeyStore(
KeyStore keyStore, InputStream keyStream, String storePass, String alias, String keyPass)
throws IOException, GeneralSecurityException {
loadKeyStore(keyStore, keyStream, storePass);
return getPrivateKey(keyStore, alias, keyPass);
}
/** Returns the RSA key factory. */
public static KeyFactory getRsaKeyFactory() throws NoSuchAlgorithmException {
return KeyFactory.getInstance("RSA");
}
/** Returns the SHA-1 with RSA signature algorithm. */
public static Signature getSha1WithRsaSignatureAlgorithm() throws NoSuchAlgorithmException {
return Signature.getInstance("SHA1withRSA");
}
/** Returns the SHA-256 with RSA signature algorithm. */
public static Signature getSha256WithRsaSignatureAlgorithm() throws NoSuchAlgorithmException {
return Signature.getInstance("SHA256withRSA");
}
/** Returns the SHA-256 with ECDSA signature algorithm */
public static Signature getEs256SignatureAlgorithm() throws NoSuchAlgorithmException {
return Signature.getInstance("SHA256withECDSA");
}
/**
* Signs content using a private key.
*
* @param signatureAlgorithm signature algorithm
* @param privateKey private key
* @param contentBytes content to sign
* @return signed content
*/
public static byte[] sign(
Signature signatureAlgorithm, PrivateKey privateKey, byte[] contentBytes)
throws InvalidKeyException, SignatureException {
signatureAlgorithm.initSign(privateKey);
signatureAlgorithm.update(contentBytes);
return signatureAlgorithm.sign();
}
/**
* Verifies the signature of signed content based on a public key.
*
* @param signatureAlgorithm signature algorithm
* @param publicKey public key
* @param signatureBytes signature bytes
* @param contentBytes content bytes
* @return whether the signature was verified
*/
public static boolean verify(
Signature signatureAlgorithm, PublicKey publicKey, byte[] signatureBytes, byte[] contentBytes)
throws InvalidKeyException, SignatureException {
signatureAlgorithm.initVerify(publicKey);
signatureAlgorithm.update(contentBytes);
// SignatureException may be thrown if we are trying the wrong key.
try {
return signatureAlgorithm.verify(signatureBytes);
} catch (SignatureException e) {
return false;
}
}
/**
* Verifies the signature of signed content based on a certificate chain.
*
* @param signatureAlgorithm signature algorithm
* @param trustManager trust manager used to verify the certificate chain
* @param certChainBase64 Certificate chain used for verification. The certificates must be base64
* encoded DER, the leaf certificate must be the first element.
* @param signatureBytes signature bytes
* @param contentBytes content bytes
* @return The signature certificate if the signature could be verified, null otherwise.
* @since 1.19.1.
*/
public static X509Certificate verify(
Signature signatureAlgorithm,
X509TrustManager trustManager,
List<String> certChainBase64,
byte[] signatureBytes,
byte[] contentBytes)
throws InvalidKeyException, SignatureException {
CertificateFactory certificateFactory;
try {
certificateFactory = getX509CertificateFactory();
} catch (CertificateException e) {
return null;
}
X509Certificate[] certificates = new X509Certificate[certChainBase64.size()];
int currentCert = 0;
for (String certBase64 : certChainBase64) {
byte[] certDer = Base64.decodeBase64(certBase64);
ByteArrayInputStream bis = new ByteArrayInputStream(certDer);
try {
Certificate cert = certificateFactory.generateCertificate(bis);
if (!(cert instanceof X509Certificate)) {
return null;
}
certificates[currentCert++] = (X509Certificate) cert;
} catch (CertificateException e) {
return null;
}
}
try {
trustManager.checkServerTrusted(certificates, "RSA");
} catch (CertificateException e) {
return null;
}
PublicKey pubKey = certificates[0].getPublicKey();
if (verify(signatureAlgorithm, pubKey, signatureBytes, contentBytes)) {
return certificates[0];
}
return null;
}
/** Returns the X.509 certificate factory. */
public static CertificateFactory getX509CertificateFactory() throws CertificateException {
return CertificateFactory.getInstance("X.509");
}
/**
* Loads a key store with certificates generated from the specified stream using {@link
* CertificateFactory#generateCertificates(InputStream)}.
*
* <p>For each certificate, {@link KeyStore#setCertificateEntry(String, Certificate)} is called
* with an alias that is the string form of incrementing non-negative integers starting with 0 (0,
* 1, 2, 3, ...).
*
* <p>Example usage:
*
* <pre>
* KeyStore keyStore = SecurityUtils.getJavaKeyStore();
* SecurityUtils.loadKeyStoreFromCertificates(keyStore, SecurityUtils.getX509CertificateFactory(),
* new FileInputStream(pemFile));
* </pre>
*
* @param keyStore key store (for example {@link #getJavaKeyStore()})
* @param certificateFactory certificate factory (for example {@link
* #getX509CertificateFactory()})
* @param certificateStream certificate stream
*/
public static void loadKeyStoreFromCertificates(
KeyStore keyStore, CertificateFactory certificateFactory, InputStream certificateStream)
throws GeneralSecurityException {
int i = 0;
for (Certificate cert : certificateFactory.generateCertificates(certificateStream)) {
keyStore.setCertificateEntry(String.valueOf(i), cert);
i++;
}
}
private SecurityUtils() {}
}