|
1 /* |
|
2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package sun.security.ssl; |
|
27 |
|
28 import java.io.IOException; |
|
29 import java.math.BigInteger; |
|
30 import java.security.GeneralSecurityException; |
|
31 import java.security.InvalidKeyException; |
|
32 import java.security.KeyFactory; |
|
33 import java.security.KeyPair; |
|
34 import java.security.KeyPairGenerator; |
|
35 import java.security.NoSuchAlgorithmException; |
|
36 import java.security.PrivateKey; |
|
37 import java.security.PublicKey; |
|
38 import java.security.SecureRandom; |
|
39 import java.security.spec.AlgorithmParameterSpec; |
|
40 import java.security.spec.InvalidKeySpecException; |
|
41 import javax.crypto.KeyAgreement; |
|
42 import javax.crypto.SecretKey; |
|
43 import javax.crypto.interfaces.DHPublicKey; |
|
44 import javax.crypto.spec.DHParameterSpec; |
|
45 import javax.crypto.spec.DHPublicKeySpec; |
|
46 import javax.crypto.spec.SecretKeySpec; |
|
47 import javax.net.ssl.SSLHandshakeException; |
|
48 import sun.security.action.GetPropertyAction; |
|
49 import sun.security.ssl.CipherSuite.HashAlg; |
|
50 import sun.security.ssl.SupportedGroupsExtension.NamedGroup; |
|
51 import sun.security.ssl.SupportedGroupsExtension.NamedGroupType; |
|
52 import sun.security.ssl.SupportedGroupsExtension.SupportedGroups; |
|
53 import sun.security.ssl.X509Authentication.X509Possession; |
|
54 import sun.security.util.KeyUtil; |
|
55 |
|
56 final class DHKeyExchange { |
|
57 static final SSLPossessionGenerator poGenerator = |
|
58 new DHEPossessionGenerator(false); |
|
59 static final SSLPossessionGenerator poExportableGenerator = |
|
60 new DHEPossessionGenerator(true); |
|
61 static final SSLKeyAgreementGenerator kaGenerator = |
|
62 new DHEKAGenerator(); |
|
63 |
|
64 static final class DHECredentials implements SSLCredentials { |
|
65 final DHPublicKey popPublicKey; |
|
66 final NamedGroup namedGroup; |
|
67 |
|
68 DHECredentials(DHPublicKey popPublicKey, NamedGroup namedGroup) { |
|
69 this.popPublicKey = popPublicKey; |
|
70 this.namedGroup = namedGroup; |
|
71 } |
|
72 |
|
73 static DHECredentials valueOf(NamedGroup ng, |
|
74 byte[] encodedPublic) throws IOException, GeneralSecurityException { |
|
75 |
|
76 if (ng.type != NamedGroupType.NAMED_GROUP_FFDHE) { |
|
77 throw new RuntimeException( |
|
78 "Credentials decoding: Not FFDHE named group"); |
|
79 } |
|
80 |
|
81 if (encodedPublic == null || encodedPublic.length == 0) { |
|
82 return null; |
|
83 } |
|
84 |
|
85 DHParameterSpec params = (DHParameterSpec)ng.getParameterSpec(); |
|
86 if (params == null) { |
|
87 return null; |
|
88 } |
|
89 |
|
90 KeyFactory kf = JsseJce.getKeyFactory("DiffieHellman"); |
|
91 DHPublicKeySpec spec = new DHPublicKeySpec( |
|
92 new BigInteger(1, encodedPublic), |
|
93 params.getP(), params.getG()); |
|
94 DHPublicKey publicKey = |
|
95 (DHPublicKey)kf.generatePublic(spec); |
|
96 |
|
97 return new DHECredentials(publicKey, ng); |
|
98 } |
|
99 } |
|
100 |
|
101 static final class DHEPossession implements SSLPossession { |
|
102 final PrivateKey privateKey; |
|
103 final DHPublicKey publicKey; |
|
104 final NamedGroup namedGroup; |
|
105 |
|
106 DHEPossession(NamedGroup namedGroup, SecureRandom random) { |
|
107 try { |
|
108 KeyPairGenerator kpg = |
|
109 JsseJce.getKeyPairGenerator("DiffieHellman"); |
|
110 DHParameterSpec params = |
|
111 (DHParameterSpec)namedGroup.getParameterSpec(); |
|
112 kpg.initialize(params, random); |
|
113 KeyPair kp = generateDHKeyPair(kpg); |
|
114 if (kp == null) { |
|
115 throw new RuntimeException("Could not generate DH keypair"); |
|
116 } |
|
117 privateKey = kp.getPrivate(); |
|
118 publicKey = (DHPublicKey)kp.getPublic(); |
|
119 } catch (GeneralSecurityException gse) { |
|
120 throw new RuntimeException( |
|
121 "Could not generate DH keypair", gse); |
|
122 } |
|
123 |
|
124 this.namedGroup = namedGroup; |
|
125 } |
|
126 |
|
127 DHEPossession(int keyLength, SecureRandom random) { |
|
128 DHParameterSpec params = |
|
129 PredefinedDHParameterSpecs.definedParams.get(keyLength); |
|
130 try { |
|
131 KeyPairGenerator kpg = |
|
132 JsseJce.getKeyPairGenerator("DiffieHellman"); |
|
133 if (params != null) { |
|
134 kpg.initialize(params, random); |
|
135 } else { |
|
136 kpg.initialize(keyLength, random); |
|
137 } |
|
138 |
|
139 KeyPair kp = generateDHKeyPair(kpg); |
|
140 if (kp == null) { |
|
141 throw new RuntimeException( |
|
142 "Could not generate DH keypair of " + |
|
143 keyLength + " bits"); |
|
144 } |
|
145 privateKey = kp.getPrivate(); |
|
146 publicKey = (DHPublicKey)kp.getPublic(); |
|
147 } catch (GeneralSecurityException gse) { |
|
148 throw new RuntimeException( |
|
149 "Could not generate DH keypair", gse); |
|
150 } |
|
151 |
|
152 this.namedGroup = NamedGroup.valueOf(publicKey.getParams()); |
|
153 } |
|
154 |
|
155 DHEPossession(DHECredentials credentials, SecureRandom random) { |
|
156 try { |
|
157 KeyPairGenerator kpg = |
|
158 JsseJce.getKeyPairGenerator("DiffieHellman"); |
|
159 kpg.initialize(credentials.popPublicKey.getParams(), random); |
|
160 KeyPair kp = generateDHKeyPair(kpg); |
|
161 if (kp == null) { |
|
162 throw new RuntimeException("Could not generate DH keypair"); |
|
163 } |
|
164 privateKey = kp.getPrivate(); |
|
165 publicKey = (DHPublicKey)kp.getPublic(); |
|
166 } catch (GeneralSecurityException gse) { |
|
167 throw new RuntimeException( |
|
168 "Could not generate DH keypair", gse); |
|
169 } |
|
170 |
|
171 this.namedGroup = credentials.namedGroup; |
|
172 } |
|
173 |
|
174 // Generate and validate DHPublicKeySpec |
|
175 private KeyPair generateDHKeyPair( |
|
176 KeyPairGenerator kpg) throws GeneralSecurityException { |
|
177 boolean doExtraValiadtion = |
|
178 (!KeyUtil.isOracleJCEProvider(kpg.getProvider().getName())); |
|
179 boolean isRecovering = false; |
|
180 for (int i = 0; i <= 2; i++) { // Try to recover from failure. |
|
181 KeyPair kp = kpg.generateKeyPair(); |
|
182 // validate the Diffie-Hellman public key |
|
183 if (doExtraValiadtion) { |
|
184 DHPublicKeySpec spec = getDHPublicKeySpec(kp.getPublic()); |
|
185 try { |
|
186 KeyUtil.validate(spec); |
|
187 } catch (InvalidKeyException ivke) { |
|
188 if (isRecovering) { |
|
189 throw ivke; |
|
190 } |
|
191 // otherwise, ignore the exception and try again |
|
192 isRecovering = true; |
|
193 continue; |
|
194 } |
|
195 } |
|
196 |
|
197 return kp; |
|
198 } |
|
199 |
|
200 return null; |
|
201 } |
|
202 |
|
203 private static DHPublicKeySpec getDHPublicKeySpec(PublicKey key) { |
|
204 if (key instanceof DHPublicKey) { |
|
205 DHPublicKey dhKey = (DHPublicKey)key; |
|
206 DHParameterSpec params = dhKey.getParams(); |
|
207 return new DHPublicKeySpec(dhKey.getY(), |
|
208 params.getP(), params.getG()); |
|
209 } |
|
210 try { |
|
211 KeyFactory factory = JsseJce.getKeyFactory("DiffieHellman"); |
|
212 return factory.getKeySpec(key, DHPublicKeySpec.class); |
|
213 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
|
214 // unlikely |
|
215 throw new RuntimeException("Unable to get DHPublicKeySpec", e); |
|
216 } |
|
217 } |
|
218 |
|
219 @Override |
|
220 public byte[] encode() { |
|
221 // Note: the DH public value is encoded as a big-endian integer |
|
222 // and padded to the left with zeros to the size of p in bytes. |
|
223 byte[] encoded = publicKey.getY().toByteArray(); |
|
224 int pSize = KeyUtil.getKeySize(publicKey); |
|
225 if (pSize > 0 && encoded.length < pSize) { |
|
226 byte[] buffer = new byte[pSize]; |
|
227 System.arraycopy(encoded, 0, |
|
228 buffer, pSize - encoded.length, encoded.length); |
|
229 encoded = buffer; |
|
230 } |
|
231 |
|
232 return encoded; |
|
233 } |
|
234 } |
|
235 |
|
236 private static final class |
|
237 DHEPossessionGenerator implements SSLPossessionGenerator { |
|
238 // Flag to use smart ephemeral DH key which size matches the |
|
239 // corresponding authentication key |
|
240 private static final boolean useSmartEphemeralDHKeys; |
|
241 |
|
242 // Flag to use legacy ephemeral DH key which size is 512 bits for |
|
243 // exportable cipher suites, and 768 bits for others |
|
244 private static final boolean useLegacyEphemeralDHKeys; |
|
245 |
|
246 // The customized ephemeral DH key size for non-exportable |
|
247 // cipher suites. |
|
248 private static final int customizedDHKeySize; |
|
249 |
|
250 // Is it for exportable cipher suite? |
|
251 private final boolean exportable; |
|
252 |
|
253 static { |
|
254 String property = GetPropertyAction.privilegedGetProperty( |
|
255 "jdk.tls.ephemeralDHKeySize"); |
|
256 if (property == null || property.length() == 0) { |
|
257 useLegacyEphemeralDHKeys = false; |
|
258 useSmartEphemeralDHKeys = false; |
|
259 customizedDHKeySize = -1; |
|
260 } else if ("matched".equals(property)) { |
|
261 useLegacyEphemeralDHKeys = false; |
|
262 useSmartEphemeralDHKeys = true; |
|
263 customizedDHKeySize = -1; |
|
264 } else if ("legacy".equals(property)) { |
|
265 useLegacyEphemeralDHKeys = true; |
|
266 useSmartEphemeralDHKeys = false; |
|
267 customizedDHKeySize = -1; |
|
268 } else { |
|
269 useLegacyEphemeralDHKeys = false; |
|
270 useSmartEphemeralDHKeys = false; |
|
271 |
|
272 try { |
|
273 // DH parameter generation can be extremely slow, best to |
|
274 // use one of the supported pre-computed DH parameters |
|
275 // (see DHCrypt class). |
|
276 customizedDHKeySize = Integer.parseUnsignedInt(property); |
|
277 if (customizedDHKeySize < 1024 || |
|
278 customizedDHKeySize > 8192 || |
|
279 (customizedDHKeySize & 0x3f) != 0) { |
|
280 throw new IllegalArgumentException( |
|
281 "Unsupported customized DH key size: " + |
|
282 customizedDHKeySize + ". " + |
|
283 "The key size must be multiple of 64, " + |
|
284 "and range from 1024 to 8192 (inclusive)"); |
|
285 } |
|
286 } catch (NumberFormatException nfe) { |
|
287 throw new IllegalArgumentException( |
|
288 "Invalid system property jdk.tls.ephemeralDHKeySize"); |
|
289 } |
|
290 } |
|
291 } |
|
292 |
|
293 // Prevent instantiation of this class. |
|
294 private DHEPossessionGenerator(boolean exportable) { |
|
295 this.exportable = exportable; |
|
296 } |
|
297 |
|
298 // Used for ServerKeyExchange, TLS 1.2 and prior versions. |
|
299 @Override |
|
300 public SSLPossession createPossession(HandshakeContext context) { |
|
301 NamedGroup preferableNamedGroup = null; |
|
302 if (!useLegacyEphemeralDHKeys && |
|
303 (context.clientRequestedNamedGroups != null) && |
|
304 (!context.clientRequestedNamedGroups.isEmpty())) { |
|
305 preferableNamedGroup = |
|
306 SupportedGroups.getPreferredGroup( |
|
307 context.negotiatedProtocol, |
|
308 context.algorithmConstraints, |
|
309 NamedGroupType.NAMED_GROUP_FFDHE, |
|
310 context.clientRequestedNamedGroups); |
|
311 if (preferableNamedGroup != null) { |
|
312 return new DHEPossession(preferableNamedGroup, |
|
313 context.sslContext.getSecureRandom()); |
|
314 } |
|
315 } |
|
316 |
|
317 /* |
|
318 * 768 bits ephemeral DH private keys were used to be used in |
|
319 * ServerKeyExchange except that exportable ciphers max out at 512 |
|
320 * bits modulus values. We still adhere to this behavior in legacy |
|
321 * mode (system property "jdk.tls.ephemeralDHKeySize" is defined |
|
322 * as "legacy"). |
|
323 * |
|
324 * Old JDK (JDK 7 and previous) releases don't support DH keys |
|
325 * bigger than 1024 bits. We have to consider the compatibility |
|
326 * requirement. 1024 bits DH key is always used for non-exportable |
|
327 * cipher suites in default mode (system property |
|
328 * "jdk.tls.ephemeralDHKeySize" is not defined). |
|
329 * |
|
330 * However, if applications want more stronger strength, setting |
|
331 * system property "jdk.tls.ephemeralDHKeySize" to "matched" |
|
332 * is a workaround to use ephemeral DH key which size matches the |
|
333 * corresponding authentication key. For example, if the public key |
|
334 * size of an authentication certificate is 2048 bits, then the |
|
335 * ephemeral DH key size should be 2048 bits accordingly unless |
|
336 * the cipher suite is exportable. This key sizing scheme keeps |
|
337 * the cryptographic strength consistent between authentication |
|
338 * keys and key-exchange keys. |
|
339 * |
|
340 * Applications may also want to customize the ephemeral DH key |
|
341 * size to a fixed length for non-exportable cipher suites. This |
|
342 * can be approached by setting system property |
|
343 * "jdk.tls.ephemeralDHKeySize" to a valid positive integer between |
|
344 * 1024 and 8192 bits, inclusive. |
|
345 * |
|
346 * Note that the minimum acceptable key size is 1024 bits except |
|
347 * exportable cipher suites or legacy mode. |
|
348 * |
|
349 * Note that per RFC 2246, the key size limit of DH is 512 bits for |
|
350 * exportable cipher suites. Because of the weakness, exportable |
|
351 * cipher suites are deprecated since TLS v1.1 and they are not |
|
352 * enabled by default in Oracle provider. The legacy behavior is |
|
353 * reserved and 512 bits DH key is always used for exportable |
|
354 * cipher suites. |
|
355 */ |
|
356 int keySize = exportable ? 512 : 1024; // default mode |
|
357 if (!exportable) { |
|
358 if (useLegacyEphemeralDHKeys) { // legacy mode |
|
359 keySize = 768; |
|
360 } else if (useSmartEphemeralDHKeys) { // matched mode |
|
361 PrivateKey key = null; |
|
362 ServerHandshakeContext shc = |
|
363 (ServerHandshakeContext)context; |
|
364 if (shc.interimAuthn instanceof X509Possession) { |
|
365 key = ((X509Possession)shc.interimAuthn).popPrivateKey; |
|
366 } |
|
367 |
|
368 if (key != null) { |
|
369 int ks = KeyUtil.getKeySize(key); |
|
370 |
|
371 // DH parameter generation can be extremely slow, make |
|
372 // sure to use one of the supported pre-computed DH |
|
373 // parameters. |
|
374 // |
|
375 // Old deployed applications may not be ready to |
|
376 // support DH key sizes bigger than 2048 bits. Please |
|
377 // DON'T use value other than 1024 and 2048 at present. |
|
378 // May improve the underlying providers and key size |
|
379 // limit in the future when the compatibility and |
|
380 // interoperability impact is limited. |
|
381 keySize = ks <= 1024 ? 1024 : 2048; |
|
382 } // Otherwise, anonymous cipher suites, 1024-bit is used. |
|
383 } else if (customizedDHKeySize > 0) { // customized mode |
|
384 keySize = customizedDHKeySize; |
|
385 } |
|
386 } |
|
387 |
|
388 return new DHEPossession( |
|
389 keySize, context.sslContext.getSecureRandom()); |
|
390 } |
|
391 } |
|
392 |
|
393 private static final |
|
394 class DHEKAGenerator implements SSLKeyAgreementGenerator { |
|
395 static private DHEKAGenerator instance = new DHEKAGenerator(); |
|
396 |
|
397 // Prevent instantiation of this class. |
|
398 private DHEKAGenerator() { |
|
399 // blank |
|
400 } |
|
401 |
|
402 @Override |
|
403 public SSLKeyDerivation createKeyDerivation( |
|
404 HandshakeContext context) throws IOException { |
|
405 DHEPossession dhePossession = null; |
|
406 DHECredentials dheCredentials = null; |
|
407 for (SSLPossession poss : context.handshakePossessions) { |
|
408 if (!(poss instanceof DHEPossession)) { |
|
409 continue; |
|
410 } |
|
411 |
|
412 DHEPossession dhep = (DHEPossession)poss; |
|
413 for (SSLCredentials cred : context.handshakeCredentials) { |
|
414 if (!(cred instanceof DHECredentials)) { |
|
415 continue; |
|
416 } |
|
417 DHECredentials dhec = (DHECredentials)cred; |
|
418 if (dhep.namedGroup != null && dhec.namedGroup != null) { |
|
419 if (dhep.namedGroup.equals(dhec.namedGroup)) { |
|
420 dheCredentials = (DHECredentials)cred; |
|
421 break; |
|
422 } |
|
423 } else { |
|
424 DHParameterSpec pps = dhep.publicKey.getParams(); |
|
425 DHParameterSpec cps = dhec.popPublicKey.getParams(); |
|
426 if (pps.getP().equals(cps.getP()) && |
|
427 pps.getG().equals(cps.getG())) { |
|
428 dheCredentials = (DHECredentials)cred; |
|
429 break; |
|
430 } |
|
431 } |
|
432 } |
|
433 |
|
434 if (dheCredentials != null) { |
|
435 dhePossession = (DHEPossession)poss; |
|
436 break; |
|
437 } |
|
438 } |
|
439 |
|
440 if (dhePossession == null || dheCredentials == null) { |
|
441 context.conContext.fatal(Alert.HANDSHAKE_FAILURE, |
|
442 "No sufficient DHE key agreement parameters negotiated"); |
|
443 } |
|
444 |
|
445 return new DHEKAKeyDerivation(context, |
|
446 dhePossession.privateKey, dheCredentials.popPublicKey); |
|
447 } |
|
448 |
|
449 private static final |
|
450 class DHEKAKeyDerivation implements SSLKeyDerivation { |
|
451 private final HandshakeContext context; |
|
452 private final PrivateKey localPrivateKey; |
|
453 private final PublicKey peerPublicKey; |
|
454 |
|
455 DHEKAKeyDerivation(HandshakeContext context, |
|
456 PrivateKey localPrivateKey, |
|
457 PublicKey peerPublicKey) { |
|
458 this.context = context; |
|
459 this.localPrivateKey = localPrivateKey; |
|
460 this.peerPublicKey = peerPublicKey; |
|
461 } |
|
462 |
|
463 @Override |
|
464 public SecretKey deriveKey(String algorithm, |
|
465 AlgorithmParameterSpec params) throws IOException { |
|
466 if (!context.negotiatedProtocol.useTLS13PlusSpec()) { |
|
467 return t12DeriveKey(algorithm, params); |
|
468 } else { |
|
469 return t13DeriveKey(algorithm, params); |
|
470 } |
|
471 } |
|
472 |
|
473 private SecretKey t12DeriveKey(String algorithm, |
|
474 AlgorithmParameterSpec params) throws IOException { |
|
475 try { |
|
476 KeyAgreement ka = JsseJce.getKeyAgreement("DiffieHellman"); |
|
477 ka.init(localPrivateKey); |
|
478 ka.doPhase(peerPublicKey, true); |
|
479 SecretKey preMasterSecret = |
|
480 ka.generateSecret("TlsPremasterSecret"); |
|
481 SSLMasterKeyDerivation mskd = |
|
482 SSLMasterKeyDerivation.valueOf( |
|
483 context.negotiatedProtocol); |
|
484 if (mskd == null) { |
|
485 // unlikely |
|
486 throw new SSLHandshakeException( |
|
487 "No expected master key derivation for protocol: " + |
|
488 context.negotiatedProtocol.name); |
|
489 } |
|
490 SSLKeyDerivation kd = mskd.createKeyDerivation( |
|
491 context, preMasterSecret); |
|
492 return kd.deriveKey("MasterSecret", params); |
|
493 } catch (GeneralSecurityException gse) { |
|
494 throw (SSLHandshakeException) new SSLHandshakeException( |
|
495 "Could not generate secret").initCause(gse); |
|
496 } |
|
497 } |
|
498 |
|
499 private SecretKey t13DeriveKey(String algorithm, |
|
500 AlgorithmParameterSpec params) throws IOException { |
|
501 try { |
|
502 KeyAgreement ka = JsseJce.getKeyAgreement("DiffieHellman"); |
|
503 ka.init(localPrivateKey); |
|
504 ka.doPhase(peerPublicKey, true); |
|
505 SecretKey sharedSecret = |
|
506 ka.generateSecret("TlsPremasterSecret"); |
|
507 |
|
508 HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; |
|
509 SSLKeyDerivation kd = context.handshakeKeyDerivation; |
|
510 HKDF hkdf = new HKDF(hashAlg.name); |
|
511 if (kd == null) { // No PSK is in use. |
|
512 // If PSK is not in use Early Secret will still be |
|
513 // HKDF-Extract(0, 0). |
|
514 byte[] zeros = new byte[hashAlg.hashLength]; |
|
515 SecretKeySpec ikm = |
|
516 new SecretKeySpec(zeros, "TlsPreSharedSecret"); |
|
517 SecretKey earlySecret = |
|
518 hkdf.extract(zeros, ikm, "TlsEarlySecret"); |
|
519 kd = new SSLSecretDerivation(context, earlySecret); |
|
520 } |
|
521 |
|
522 // derive salt secret |
|
523 SecretKey saltSecret = kd.deriveKey("TlsSaltSecret", null); |
|
524 |
|
525 // derive handshake secret |
|
526 return hkdf.extract(saltSecret, sharedSecret, algorithm); |
|
527 } catch (GeneralSecurityException gse) { |
|
528 throw (SSLHandshakeException) new SSLHandshakeException( |
|
529 "Could not generate secret").initCause(gse); |
|
530 } |
|
531 } |
|
532 } |
|
533 } |
|
534 } |