24 */ |
24 */ |
25 |
25 |
26 package sun.security.ssl; |
26 package sun.security.ssl; |
27 |
27 |
28 import java.io.IOException; |
28 import java.io.IOException; |
29 import java.io.PrintStream; |
29 import java.nio.ByteBuffer; |
30 |
30 import java.security.AlgorithmConstraints; |
|
31 import java.security.CryptoPrimitive; |
|
32 import java.security.GeneralSecurityException; |
|
33 import java.security.KeyFactory; |
|
34 import java.security.PrivateKey; |
31 import java.security.PublicKey; |
35 import java.security.PublicKey; |
|
36 import java.security.interfaces.ECPrivateKey; |
32 import java.security.interfaces.ECPublicKey; |
37 import java.security.interfaces.ECPublicKey; |
33 import java.security.spec.*; |
38 import java.security.spec.ECParameterSpec; |
|
39 import java.security.spec.ECPoint; |
|
40 import java.security.spec.ECPublicKeySpec; |
|
41 import java.text.MessageFormat; |
|
42 import java.util.EnumSet; |
|
43 import java.util.Locale; |
|
44 import javax.crypto.SecretKey; |
|
45 import javax.net.ssl.SSLHandshakeException; |
|
46 import sun.security.ssl.ECDHKeyExchange.ECDHECredentials; |
|
47 import sun.security.ssl.ECDHKeyExchange.ECDHEPossession; |
|
48 import sun.security.ssl.SSLHandshake.HandshakeMessage; |
|
49 import sun.security.ssl.SupportedGroupsExtension.NamedGroup; |
|
50 import sun.security.ssl.X509Authentication.X509Credentials; |
|
51 import sun.security.ssl.X509Authentication.X509Possession; |
|
52 import sun.security.util.HexDumpEncoder; |
34 |
53 |
35 /** |
54 /** |
36 * ClientKeyExchange message for all ECDH based key exchange methods. It |
55 * Pack of the "ClientKeyExchange" handshake message. |
37 * contains the client's ephemeral public value. |
|
38 * |
|
39 * @since 1.6 |
|
40 * @author Andreas Sterbenz |
|
41 */ |
56 */ |
42 final class ECDHClientKeyExchange extends HandshakeMessage { |
57 final class ECDHClientKeyExchange { |
43 |
58 static final SSLConsumer ecdhHandshakeConsumer = |
44 @Override |
59 new ECDHClientKeyExchangeConsumer(); |
45 int messageType() { |
60 static final HandshakeProducer ecdhHandshakeProducer = |
46 return ht_client_key_exchange; |
61 new ECDHClientKeyExchangeProducer(); |
|
62 |
|
63 static final SSLConsumer ecdheHandshakeConsumer = |
|
64 new ECDHEClientKeyExchangeConsumer(); |
|
65 static final HandshakeProducer ecdheHandshakeProducer = |
|
66 new ECDHEClientKeyExchangeProducer(); |
|
67 |
|
68 /** |
|
69 * The ECDH/ECDHE ClientKeyExchange handshake message. |
|
70 */ |
|
71 private static final |
|
72 class ECDHClientKeyExchangeMessage extends HandshakeMessage { |
|
73 private final byte[] encodedPoint; |
|
74 |
|
75 ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext, |
|
76 ECPublicKey publicKey) { |
|
77 super(handshakeContext); |
|
78 |
|
79 ECPoint point = publicKey.getW(); |
|
80 ECParameterSpec params = publicKey.getParams(); |
|
81 encodedPoint = JsseJce.encodePoint(point, params.getCurve()); |
|
82 } |
|
83 |
|
84 ECDHClientKeyExchangeMessage(HandshakeContext handshakeContext, |
|
85 ByteBuffer m) throws IOException { |
|
86 super(handshakeContext); |
|
87 if (m.remaining() != 0) { // explicit PublicValueEncoding |
|
88 this.encodedPoint = Record.getBytes8(m); |
|
89 } else { |
|
90 this.encodedPoint = new byte[0]; |
|
91 } |
|
92 } |
|
93 |
|
94 // Check constraints of the specified EC public key. |
|
95 static void checkConstraints(AlgorithmConstraints constraints, |
|
96 ECPublicKey publicKey, |
|
97 byte[] encodedPoint) throws SSLHandshakeException { |
|
98 |
|
99 try { |
|
100 ECParameterSpec params = publicKey.getParams(); |
|
101 ECPoint point = |
|
102 JsseJce.decodePoint(encodedPoint, params.getCurve()); |
|
103 ECPublicKeySpec spec = new ECPublicKeySpec(point, params); |
|
104 |
|
105 KeyFactory kf = JsseJce.getKeyFactory("EC"); |
|
106 ECPublicKey peerPublicKey = |
|
107 (ECPublicKey)kf.generatePublic(spec); |
|
108 |
|
109 // check constraints of ECPublicKey |
|
110 if (!constraints.permits( |
|
111 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
112 peerPublicKey)) { |
|
113 throw new SSLHandshakeException( |
|
114 "ECPublicKey does not comply to algorithm constraints"); |
|
115 } |
|
116 } catch (GeneralSecurityException | java.io.IOException e) { |
|
117 throw (SSLHandshakeException) new SSLHandshakeException( |
|
118 "Could not generate ECPublicKey").initCause(e); |
|
119 } |
|
120 } |
|
121 |
|
122 @Override |
|
123 public SSLHandshake handshakeType() { |
|
124 return SSLHandshake.CLIENT_KEY_EXCHANGE; |
|
125 } |
|
126 |
|
127 @Override |
|
128 public int messageLength() { |
|
129 if (encodedPoint == null || encodedPoint.length == 0) { |
|
130 return 0; |
|
131 } else { |
|
132 return 1 + encodedPoint.length; |
|
133 } |
|
134 } |
|
135 |
|
136 @Override |
|
137 public void send(HandshakeOutStream hos) throws IOException { |
|
138 if (encodedPoint != null && encodedPoint.length != 0) { |
|
139 hos.putBytes8(encodedPoint); |
|
140 } |
|
141 } |
|
142 |
|
143 @Override |
|
144 public String toString() { |
|
145 MessageFormat messageFormat = new MessageFormat( |
|
146 "\"ECDH ClientKeyExchange\": '{'\n" + |
|
147 " \"ecdh public\": '{'\n" + |
|
148 "{0}\n" + |
|
149 " '}',\n" + |
|
150 "'}'", |
|
151 Locale.ENGLISH); |
|
152 if (encodedPoint == null || encodedPoint.length == 0) { |
|
153 Object[] messageFields = { |
|
154 " <implicit>" |
|
155 }; |
|
156 return messageFormat.format(messageFields); |
|
157 } else { |
|
158 HexDumpEncoder hexEncoder = new HexDumpEncoder(); |
|
159 Object[] messageFields = { |
|
160 Utilities.indent( |
|
161 hexEncoder.encodeBuffer(encodedPoint), " "), |
|
162 }; |
|
163 return messageFormat.format(messageFields); |
|
164 } |
|
165 } |
47 } |
166 } |
48 |
167 |
49 private byte[] encodedPoint; |
168 /** |
50 |
169 * The ECDH "ClientKeyExchange" handshake message producer. |
51 byte[] getEncodedPoint() { |
170 */ |
52 return encodedPoint; |
171 private static final |
|
172 class ECDHClientKeyExchangeProducer implements HandshakeProducer { |
|
173 // Prevent instantiation of this class. |
|
174 private ECDHClientKeyExchangeProducer() { |
|
175 // blank |
|
176 } |
|
177 |
|
178 @Override |
|
179 public byte[] produce(ConnectionContext context, |
|
180 HandshakeMessage message) throws IOException { |
|
181 // The producing happens in client side only. |
|
182 ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
183 |
|
184 X509Credentials x509Credentials = null; |
|
185 for (SSLCredentials credential : chc.handshakeCredentials) { |
|
186 if (credential instanceof X509Credentials) { |
|
187 x509Credentials = (X509Credentials)credential; |
|
188 break; |
|
189 } |
|
190 } |
|
191 |
|
192 if (x509Credentials == null) { |
|
193 chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
194 "No server certificate for ECDH client key exchange"); |
|
195 } |
|
196 |
|
197 PublicKey publicKey = x509Credentials.popPublicKey; |
|
198 if (!publicKey.getAlgorithm().equals("EC")) { |
|
199 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
200 "Not EC server certificate for ECDH client key exchange"); |
|
201 } |
|
202 |
|
203 ECParameterSpec params = ((ECPublicKey)publicKey).getParams(); |
|
204 NamedGroup namedGroup = NamedGroup.valueOf(params); |
|
205 if (namedGroup == null) { |
|
206 chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
207 "Unsupported EC server cert for ECDH client key exchange"); |
|
208 } |
|
209 |
|
210 ECDHEPossession ecdhePossession = new ECDHEPossession( |
|
211 namedGroup, chc.sslContext.getSecureRandom()); |
|
212 chc.handshakePossessions.add(ecdhePossession); |
|
213 ECDHClientKeyExchangeMessage cke = |
|
214 new ECDHClientKeyExchangeMessage( |
|
215 chc, ecdhePossession.publicKey); |
|
216 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
217 SSLLogger.fine( |
|
218 "Produced ECDH ClientKeyExchange handshake message", cke); |
|
219 } |
|
220 |
|
221 // Output the handshake message. |
|
222 cke.write(chc.handshakeOutput); |
|
223 chc.handshakeOutput.flush(); |
|
224 |
|
225 // update the states |
|
226 SSLKeyExchange ke = |
|
227 SSLKeyExchange.valueOf(chc.negotiatedCipherSuite.keyExchange); |
|
228 if (ke == null) { |
|
229 // unlikely |
|
230 chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
231 "Not supported key exchange type"); |
|
232 } else { |
|
233 SSLKeyDerivation masterKD = ke.createKeyDerivation(chc); |
|
234 SecretKey masterSecret = masterKD.deriveKey("TODO", null); |
|
235 chc.handshakeSession.setMasterSecret(masterSecret); |
|
236 |
|
237 SSLTrafficKeyDerivation kd = |
|
238 SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); |
|
239 if (kd == null) { |
|
240 // unlikely |
|
241 chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
242 "Not supported key derivation: " + |
|
243 chc.negotiatedProtocol); |
|
244 } else { |
|
245 chc.handshakeKeyDerivation = |
|
246 kd.createKeyDerivation(chc, masterSecret); |
|
247 } |
|
248 } |
|
249 |
|
250 // The handshake message has been delivered. |
|
251 return null; |
|
252 } |
53 } |
253 } |
54 |
254 |
55 // Called by the client with its ephemeral public key. |
255 /** |
56 ECDHClientKeyExchange(PublicKey publicKey) { |
256 * The ECDH "ClientKeyExchange" handshake message consumer. |
57 ECPublicKey ecKey = (ECPublicKey)publicKey; |
257 */ |
58 ECPoint point = ecKey.getW(); |
258 private static final |
59 ECParameterSpec params = ecKey.getParams(); |
259 class ECDHClientKeyExchangeConsumer implements SSLConsumer { |
60 encodedPoint = JsseJce.encodePoint(point, params.getCurve()); |
260 // Prevent instantiation of this class. |
|
261 private ECDHClientKeyExchangeConsumer() { |
|
262 // blank |
|
263 } |
|
264 |
|
265 @Override |
|
266 public void consume(ConnectionContext context, |
|
267 ByteBuffer message) throws IOException { |
|
268 // The consuming happens in server side only. |
|
269 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
270 |
|
271 X509Possession x509Possession = null; |
|
272 for (SSLPossession possession : shc.handshakePossessions) { |
|
273 if (possession instanceof X509Possession) { |
|
274 x509Possession = (X509Possession)possession; |
|
275 break; |
|
276 } |
|
277 } |
|
278 |
|
279 if (x509Possession == null) { |
|
280 // unlikely, have been checked during cipher suite negotiation. |
|
281 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
282 "No expected EC server cert for ECDH client key exchange"); |
|
283 return; // make the compiler happy |
|
284 } |
|
285 |
|
286 PrivateKey privateKey = x509Possession.popPrivateKey; |
|
287 if (!privateKey.getAlgorithm().equals("EC")) { |
|
288 // unlikely, have been checked during cipher suite negotiation. |
|
289 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
290 "Not EC server cert for ECDH client key exchange"); |
|
291 } |
|
292 |
|
293 ECParameterSpec params = ((ECPrivateKey)privateKey).getParams(); |
|
294 NamedGroup namedGroup = NamedGroup.valueOf(params); |
|
295 if (namedGroup == null) { |
|
296 // unlikely, have been checked during cipher suite negotiation. |
|
297 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
298 "Unsupported EC server cert for ECDH client key exchange"); |
|
299 } |
|
300 |
|
301 SSLKeyExchange ke = SSLKeyExchange.valueOf( |
|
302 shc.negotiatedCipherSuite.keyExchange); |
|
303 if (ke == null) { |
|
304 // unlikely |
|
305 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
306 "Not supported key exchange type"); |
|
307 return; // make the compiler happy |
|
308 } |
|
309 |
|
310 // parse the handshake message |
|
311 ECDHClientKeyExchangeMessage cke = |
|
312 new ECDHClientKeyExchangeMessage(shc, message); |
|
313 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
314 SSLLogger.fine( |
|
315 "Consuming ECDH ClientKeyExchange handshake message", cke); |
|
316 } |
|
317 |
|
318 // create the credentials |
|
319 try { |
|
320 ECPoint point = |
|
321 JsseJce.decodePoint(cke.encodedPoint, params.getCurve()); |
|
322 ECPublicKeySpec spec = new ECPublicKeySpec(point, params); |
|
323 |
|
324 KeyFactory kf = JsseJce.getKeyFactory("EC"); |
|
325 ECPublicKey peerPublicKey = |
|
326 (ECPublicKey)kf.generatePublic(spec); |
|
327 |
|
328 // check constraints of peer ECPublicKey |
|
329 if (!shc.algorithmConstraints.permits( |
|
330 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
331 peerPublicKey)) { |
|
332 throw new SSLHandshakeException( |
|
333 "ECPublicKey does not comply to algorithm constraints"); |
|
334 } |
|
335 |
|
336 shc.handshakeCredentials.add(new ECDHECredentials( |
|
337 peerPublicKey, namedGroup)); |
|
338 } catch (GeneralSecurityException | java.io.IOException e) { |
|
339 throw (SSLHandshakeException)(new SSLHandshakeException( |
|
340 "Could not generate ECPublicKey").initCause(e)); |
|
341 } |
|
342 |
|
343 // update the states |
|
344 SSLKeyDerivation masterKD = ke.createKeyDerivation(shc); |
|
345 SecretKey masterSecret = masterKD.deriveKey("TODO", null); |
|
346 shc.handshakeSession.setMasterSecret(masterSecret); |
|
347 |
|
348 SSLTrafficKeyDerivation kd = |
|
349 SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); |
|
350 if (kd == null) { |
|
351 // unlikely |
|
352 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
353 "Not supported key derivation: " + shc.negotiatedProtocol); |
|
354 } else { |
|
355 shc.handshakeKeyDerivation = |
|
356 kd.createKeyDerivation(shc, masterSecret); |
|
357 } |
|
358 } |
61 } |
359 } |
62 |
360 |
63 ECDHClientKeyExchange(HandshakeInStream input) throws IOException { |
361 /** |
64 encodedPoint = input.getBytes8(); |
362 * The ECDHE "ClientKeyExchange" handshake message producer. |
|
363 */ |
|
364 private static final |
|
365 class ECDHEClientKeyExchangeProducer implements HandshakeProducer { |
|
366 // Prevent instantiation of this class. |
|
367 private ECDHEClientKeyExchangeProducer() { |
|
368 // blank |
|
369 } |
|
370 |
|
371 @Override |
|
372 public byte[] produce(ConnectionContext context, |
|
373 HandshakeMessage message) throws IOException { |
|
374 // The producing happens in client side only. |
|
375 ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
376 |
|
377 ECDHECredentials ecdheCredentials = null; |
|
378 for (SSLCredentials cd : chc.handshakeCredentials) { |
|
379 if (cd instanceof ECDHECredentials) { |
|
380 ecdheCredentials = (ECDHECredentials)cd; |
|
381 break; |
|
382 } |
|
383 } |
|
384 |
|
385 if (ecdheCredentials == null) { |
|
386 chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
387 "No ECDHE credentials negotiated for client key exchange"); |
|
388 } |
|
389 |
|
390 ECDHEPossession ecdhePossession = new ECDHEPossession( |
|
391 ecdheCredentials, chc.sslContext.getSecureRandom()); |
|
392 chc.handshakePossessions.add(ecdhePossession); |
|
393 ECDHClientKeyExchangeMessage cke = |
|
394 new ECDHClientKeyExchangeMessage( |
|
395 chc, ecdhePossession.publicKey); |
|
396 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
397 SSLLogger.fine( |
|
398 "Produced ECDHE ClientKeyExchange handshake message", cke); |
|
399 } |
|
400 |
|
401 // Output the handshake message. |
|
402 cke.write(chc.handshakeOutput); |
|
403 chc.handshakeOutput.flush(); |
|
404 |
|
405 // update the states |
|
406 SSLKeyExchange ke = |
|
407 SSLKeyExchange.valueOf(chc.negotiatedCipherSuite.keyExchange); |
|
408 if (ke == null) { |
|
409 // unlikely |
|
410 chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
411 "Not supported key exchange type"); |
|
412 } else { |
|
413 SSLKeyDerivation masterKD = ke.createKeyDerivation(chc); |
|
414 SecretKey masterSecret = masterKD.deriveKey("TODO", null); |
|
415 chc.handshakeSession.setMasterSecret(masterSecret); |
|
416 |
|
417 SSLTrafficKeyDerivation kd = |
|
418 SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); |
|
419 if (kd == null) { |
|
420 // unlikely |
|
421 chc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
422 "Not supported key derivation: " + |
|
423 chc.negotiatedProtocol); |
|
424 } else { |
|
425 chc.handshakeKeyDerivation = |
|
426 kd.createKeyDerivation(chc, masterSecret); |
|
427 } |
|
428 } |
|
429 |
|
430 // The handshake message has been delivered. |
|
431 return null; |
|
432 } |
65 } |
433 } |
66 |
434 |
67 @Override |
435 /** |
68 int messageLength() { |
436 * The ECDHE "ClientKeyExchange" handshake message consumer. |
69 return encodedPoint.length + 1; |
437 */ |
70 } |
438 private static final |
71 |
439 class ECDHEClientKeyExchangeConsumer implements SSLConsumer { |
72 @Override |
440 // Prevent instantiation of this class. |
73 void send(HandshakeOutStream s) throws IOException { |
441 private ECDHEClientKeyExchangeConsumer() { |
74 s.putBytes8(encodedPoint); |
442 // blank |
75 } |
443 } |
76 |
444 |
77 @Override |
445 @Override |
78 void print(PrintStream s) throws IOException { |
446 public void consume(ConnectionContext context, |
79 s.println("*** ECDHClientKeyExchange"); |
447 ByteBuffer message) throws IOException { |
80 |
448 // The consuming happens in server side only. |
81 if (debug != null && Debug.isOn("verbose")) { |
449 ServerHandshakeContext shc = (ServerHandshakeContext)context; |
82 Debug.println(s, "ECDH Public value", encodedPoint); |
450 |
|
451 ECDHEPossession ecdhePossession = null; |
|
452 for (SSLPossession possession : shc.handshakePossessions) { |
|
453 if (possession instanceof ECDHEPossession) { |
|
454 ecdhePossession = (ECDHEPossession)possession; |
|
455 break; |
|
456 } |
|
457 } |
|
458 if (ecdhePossession == null) { |
|
459 // unlikely |
|
460 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
461 "No expected ECDHE possessions for client key exchange"); |
|
462 return; // make the compiler happy |
|
463 } |
|
464 |
|
465 ECParameterSpec params = ecdhePossession.publicKey.getParams(); |
|
466 NamedGroup namedGroup = NamedGroup.valueOf(params); |
|
467 if (namedGroup == null) { |
|
468 // unlikely, have been checked during cipher suite negotiation. |
|
469 shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
470 "Unsupported EC server cert for ECDHE client key exchange"); |
|
471 } |
|
472 |
|
473 SSLKeyExchange ke = SSLKeyExchange.valueOf( |
|
474 shc.negotiatedCipherSuite.keyExchange); |
|
475 if (ke == null) { |
|
476 // unlikely |
|
477 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
478 "Not supported key exchange type"); |
|
479 return; // make the compiler happy |
|
480 } |
|
481 |
|
482 // parse the handshake message |
|
483 ECDHClientKeyExchangeMessage cke = |
|
484 new ECDHClientKeyExchangeMessage(shc, message); |
|
485 if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
486 SSLLogger.fine( |
|
487 "Consuming ECDHE ClientKeyExchange handshake message", cke); |
|
488 } |
|
489 |
|
490 // create the credentials |
|
491 try { |
|
492 ECPoint point = |
|
493 JsseJce.decodePoint(cke.encodedPoint, params.getCurve()); |
|
494 ECPublicKeySpec spec = new ECPublicKeySpec(point, params); |
|
495 |
|
496 KeyFactory kf = JsseJce.getKeyFactory("EC"); |
|
497 ECPublicKey peerPublicKey = |
|
498 (ECPublicKey)kf.generatePublic(spec); |
|
499 |
|
500 // check constraints of peer ECPublicKey |
|
501 if (!shc.algorithmConstraints.permits( |
|
502 EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
503 peerPublicKey)) { |
|
504 throw new SSLHandshakeException( |
|
505 "ECPublicKey does not comply to algorithm constraints"); |
|
506 } |
|
507 |
|
508 shc.handshakeCredentials.add(new ECDHECredentials( |
|
509 peerPublicKey, namedGroup)); |
|
510 } catch (GeneralSecurityException | java.io.IOException e) { |
|
511 throw (SSLHandshakeException)(new SSLHandshakeException( |
|
512 "Could not generate ECPublicKey").initCause(e)); |
|
513 } |
|
514 |
|
515 // update the states |
|
516 SSLKeyDerivation masterKD = ke.createKeyDerivation(shc); |
|
517 SecretKey masterSecret = masterKD.deriveKey("TODO", null); |
|
518 shc.handshakeSession.setMasterSecret(masterSecret); |
|
519 |
|
520 SSLTrafficKeyDerivation kd = |
|
521 SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); |
|
522 if (kd == null) { |
|
523 // unlikely |
|
524 shc.conContext.fatal(Alert.INTERNAL_ERROR, |
|
525 "Not supported key derivation: " + shc.negotiatedProtocol); |
|
526 } else { |
|
527 shc.handshakeKeyDerivation = |
|
528 kd.createKeyDerivation(shc, masterSecret); |
|
529 } |
83 } |
530 } |
84 } |
531 } |
85 } |
532 } |