1 /* Copyright 2018 Google LLC 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * https://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.google.security.cryptauth.lib.securegcm; 16 17 import com.google.protobuf.ByteString; 18 import com.google.protobuf.InvalidProtocolBufferException; 19 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Alert; 20 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished; 21 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit; 22 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment; 23 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message; 24 import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit; 25 import com.google.security.cryptauth.lib.securemessage.CryptoOps; 26 import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil; 27 import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey; 28 import java.io.ByteArrayOutputStream; 29 import java.io.IOException; 30 import java.io.UnsupportedEncodingException; 31 import java.security.InvalidKeyException; 32 import java.security.KeyPair; 33 import java.security.MessageDigest; 34 import java.security.NoSuchAlgorithmException; 35 import java.security.PublicKey; 36 import java.security.SecureRandom; 37 import java.security.spec.InvalidKeySpecException; 38 import java.util.Arrays; 39 import java.util.HashMap; 40 import java.util.List; 41 import javax.annotation.Nullable; 42 import javax.crypto.SecretKey; 43 import javax.crypto.spec.SecretKeySpec; 44 45 /** 46 * Implements UKEY2 and produces a {@link D2DConnectionContext}. 47 * 48 * <p>Client Usage: 49 * <code> 50 * try { 51 * Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); 52 * byte[] handshakeMessage; 53 * 54 * // Message 1 (Client Init) 55 * handshakeMessage = client.getNextHandshakeMessage(); 56 * sendMessageToServer(handshakeMessage); 57 * 58 * // Message 2 (Server Init) 59 * handshakeMessage = receiveMessageFromServer(); 60 * client.parseHandshakeMessage(handshakeMessage); 61 * 62 * // Message 3 (Client Finish) 63 * handshakeMessage = client.getNextHandshakeMessage(); 64 * sendMessageToServer(handshakeMessage); 65 * 66 * // Get the auth string 67 * byte[] clientAuthString = client.getVerificationString(STRING_LENGTH); 68 * showStringToUser(clientAuthString); 69 * 70 * // Using out-of-band channel, verify auth string, then call: 71 * client.verifyHandshake(); 72 * 73 * // Make a connection context 74 * D2DConnectionContext clientContext = client.toConnectionContext(); 75 * } catch (AlertException e) { 76 * log(e.getMessage); 77 * sendMessageToServer(e.getAlertMessageToSend()); 78 * } catch (HandshakeException e) { 79 * log(e); 80 * // terminate handshake 81 * } 82 * </code> 83 * 84 * <p>Server Usage: 85 * <code> 86 * try { 87 * Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); 88 * byte[] handshakeMessage; 89 * 90 * // Message 1 (Client Init) 91 * handshakeMessage = receiveMessageFromClient(); 92 * server.parseHandshakeMessage(handshakeMessage); 93 * 94 * // Message 2 (Server Init) 95 * handshakeMessage = server.getNextHandshakeMessage(); 96 * sendMessageToServer(handshakeMessage); 97 * 98 * // Message 3 (Client Finish) 99 * handshakeMessage = receiveMessageFromClient(); 100 * server.parseHandshakeMessage(handshakeMessage); 101 * 102 * // Get the auth string 103 * byte[] serverAuthString = server.getVerificationString(STRING_LENGTH); 104 * showStringToUser(serverAuthString); 105 * 106 * // Using out-of-band channel, verify auth string, then call: 107 * server.verifyHandshake(); 108 * 109 * // Make a connection context 110 * D2DConnectionContext serverContext = server.toConnectionContext(); 111 * } catch (AlertException e) { 112 * log(e.getMessage); 113 * sendMessageToClient(e.getAlertMessageToSend()); 114 * } catch (HandshakeException e) { 115 * log(e); 116 * // terminate handshake 117 * } 118 * </code> 119 */ 120 public class Ukey2Handshake { 121 122 /** 123 * Creates a {@link Ukey2Handshake} with a particular cipher that can be used by an initiator / 124 * client. 125 * 126 * @throws HandshakeException 127 */ forInitiator(HandshakeCipher cipher)128 public static Ukey2Handshake forInitiator(HandshakeCipher cipher) throws HandshakeException { 129 return new Ukey2Handshake(InternalState.CLIENT_START, cipher); 130 } 131 132 /** 133 * Creates a {@link Ukey2Handshake} with a particular cipher that can be used by an responder / 134 * server. 135 * 136 * @throws HandshakeException 137 */ forResponder(HandshakeCipher cipher)138 public static Ukey2Handshake forResponder(HandshakeCipher cipher) throws HandshakeException { 139 return new Ukey2Handshake(InternalState.SERVER_START, cipher); 140 } 141 142 /** 143 * Handshake States. Meaning of states: 144 * <ul> 145 * <li>IN_PROGRESS: The handshake is in progress, caller should use 146 * {@link Ukey2Handshake#getNextHandshakeMessage()} and 147 * {@link Ukey2Handshake#parseHandshakeMessage(byte[])} to continue the handshake. 148 * <li>VERIFICATION_NEEDED: The handshake is complete, but pending verification of the 149 * authentication string. Clients should use {@link Ukey2Handshake#getVerificationString(int)} to 150 * get the verification string and use out-of-band methods to authenticate the handshake. 151 * <li>VERIFICATION_IN_PROGRESS: The handshake is complete, verification string has been 152 * generated, but has not been confirmed. After authenticating the handshake out-of-band, use 153 * {@link Ukey2Handshake#verifyHandshake()} to mark the handshake as verified. 154 * <li>FINISHED: The handshake is finished, and caller can use 155 * {@link Ukey2Handshake#toConnectionContext()} to produce a {@link D2DConnectionContext}. 156 * <li>ALREADY_USED: The handshake has already been used and should be discarded / garbage 157 * collected. 158 * <li>ERROR: The handshake produced an error and should be destroyed. 159 * </ul> 160 */ 161 public enum State { 162 IN_PROGRESS, 163 VERIFICATION_NEEDED, 164 VERIFICATION_IN_PROGRESS, 165 FINISHED, 166 ALREADY_USED, 167 ERROR, 168 } 169 170 /** 171 * Currently implemented UKEY2 handshake ciphers. Each cipher is a tuple consisting of a key 172 * negotiation cipher and a hash function used for a commitment. Currently the ciphers are: 173 * <code> 174 * +-----------------------------------------------------+ 175 * | Enum | Key negotiation | Hash function | 176 * +-------------+-----------------------+---------------+ 177 * | P256_SHA512 | ECDH using NIST P-256 | SHA512 | 178 * +-----------------------------------------------------+ 179 * </code> 180 * 181 * <p>Note that these should correspond to values in device_to_device_messages.proto. 182 */ 183 public enum HandshakeCipher { 184 P256_SHA512(UkeyProto.Ukey2HandshakeCipher.P256_SHA512); 185 // TODO(aczeskis): add CURVE25519_SHA512 186 187 private final UkeyProto.Ukey2HandshakeCipher value; 188 HandshakeCipher(UkeyProto.Ukey2HandshakeCipher value)189 HandshakeCipher(UkeyProto.Ukey2HandshakeCipher value) { 190 // Make sure we only accept values that are valid as per the ukey protobuf. 191 // NOTE: Don't use switch statement on value, as that will trigger a bug. b/30682989. 192 if (value == UkeyProto.Ukey2HandshakeCipher.P256_SHA512) { 193 this.value = value; 194 } else { 195 throw new IllegalArgumentException("Unknown cipher value: " + value); 196 } 197 } 198 getValue()199 public UkeyProto.Ukey2HandshakeCipher getValue() { 200 return value; 201 } 202 } 203 204 /** 205 * If thrown, this exception contains information that should be sent on the wire. Specifically, 206 * the {@link #getAlertMessageToSend()} method returns a <code>byte[]</code> that communicates the 207 * error to the other party in the handshake. Meanwhile, the {@link #getMessage()} method can be 208 * used to get a log-able error message. 209 */ 210 public static class AlertException extends Exception { 211 private final Ukey2Alert alertMessageToSend; 212 AlertException(String alertMessageToLog, Ukey2Alert alertMessageToSend)213 public AlertException(String alertMessageToLog, Ukey2Alert alertMessageToSend) { 214 super(alertMessageToLog); 215 this.alertMessageToSend = alertMessageToSend; 216 } 217 218 /** 219 * @return a message suitable for sending to other member of handshake. 220 */ getAlertMessageToSend()221 public byte[] getAlertMessageToSend() { 222 return alertMessageToSend.toByteArray(); 223 } 224 } 225 226 // Maximum version of the handshake supported by this class. 227 public static final int VERSION = 1; 228 229 // Random nonce is fixed at 32 bytes (as per go/ukey2). 230 private static final int NONCE_LENGTH_IN_BYTES = 32; 231 232 private static final String UTF_8 = "UTF-8"; 233 234 // Currently, we only support one next protocol. 235 private static final String NEXT_PROTOCOL = "AES_256_CBC-HMAC_SHA256"; 236 237 // Clients need to store a map of message 3's (client finishes) for each commitment. 238 private final HashMap<HandshakeCipher, byte[]> rawMessage3Map = new HashMap<>(); 239 240 private final HandshakeCipher handshakeCipher; 241 private final HandshakeRole handshakeRole; 242 private InternalState handshakeState; 243 private final KeyPair ourKeyPair; 244 private PublicKey theirPublicKey; 245 private SecretKey derivedSecretKey; 246 247 // Servers need to store client commitments. 248 private byte[] theirCommitment; 249 250 // We store the raw messages sent for computing the authentication strings and next key. 251 private byte[] rawMessage1; 252 private byte[] rawMessage2; 253 254 // Enums for internal state machinery 255 private enum InternalState { 256 // Initiator/client state 257 CLIENT_START, 258 CLIENT_WAITING_FOR_SERVER_INIT, 259 CLIENT_AFTER_SERVER_INIT, 260 261 // Responder/server state 262 SERVER_START, 263 SERVER_AFTER_CLIENT_INIT, 264 SERVER_WAITING_FOR_CLIENT_FINISHED, 265 266 // Common completion state 267 HANDSHAKE_VERIFICATION_NEEDED, 268 HANDSHAKE_VERIFICATION_IN_PROGRESS, 269 HANDSHAKE_FINISHED, 270 HANDSHAKE_ALREADY_USED, 271 HANDSHAKE_ERROR, 272 } 273 274 // Helps us remember our role in the handshake 275 private enum HandshakeRole { 276 CLIENT, 277 SERVER 278 } 279 280 /** 281 * Never invoked directly. Caller should use {@link #forInitiator(HandshakeCipher)} or 282 * {@link #forResponder(HandshakeCipher)} instead. 283 * 284 * @throws HandshakeException if an unrecoverable error occurs and the connection should be shut 285 * down. 286 */ Ukey2Handshake(InternalState state, HandshakeCipher cipher)287 private Ukey2Handshake(InternalState state, HandshakeCipher cipher) throws HandshakeException { 288 if (cipher == null) { 289 throwIllegalArgumentException("Invalid handshake cipher"); 290 } 291 this.handshakeCipher = cipher; 292 293 switch (state) { 294 case CLIENT_START: 295 handshakeRole = HandshakeRole.CLIENT; 296 break; 297 case SERVER_START: 298 handshakeRole = HandshakeRole.SERVER; 299 break; 300 default: 301 throwIllegalStateException("Invalid handshake state"); 302 handshakeRole = null; // unreachable, but makes compiler happy 303 } 304 this.handshakeState = state; 305 306 this.ourKeyPair = genKeyPair(cipher); 307 } 308 309 /** 310 * Get the next handshake message suitable for sending on the wire. 311 * 312 * @throws HandshakeException if an unrecoverable error occurs and the connection should be shut 313 * down. 314 */ getNextHandshakeMessage()315 public byte[] getNextHandshakeMessage() throws HandshakeException { 316 switch (handshakeState) { 317 case CLIENT_START: 318 rawMessage1 = makeUkey2Message(Ukey2Message.Type.CLIENT_INIT, makeClientInitMessage()); 319 handshakeState = InternalState.CLIENT_WAITING_FOR_SERVER_INIT; 320 return rawMessage1; 321 322 case SERVER_AFTER_CLIENT_INIT: 323 rawMessage2 = makeUkey2Message(Ukey2Message.Type.SERVER_INIT, makeServerInitMessage()); 324 handshakeState = InternalState.SERVER_WAITING_FOR_CLIENT_FINISHED; 325 return rawMessage2; 326 327 case CLIENT_AFTER_SERVER_INIT: 328 // Make sure we have a message 3 for the chosen cipher. 329 if (!rawMessage3Map.containsKey(handshakeCipher)) { 330 throwIllegalStateException( 331 "Client state is CLIENT_AFTER_SERVER_INIT, and cipher is " 332 + handshakeCipher 333 + ", but no corresponding raw client finished message has been generated"); 334 } 335 handshakeState = InternalState.HANDSHAKE_VERIFICATION_NEEDED; 336 return rawMessage3Map.get(handshakeCipher); 337 338 default: 339 throwIllegalStateException("Cannot get next message in state: " + handshakeState); 340 return null; // unreachable, but makes compiler happy 341 } 342 } 343 344 /** 345 * Returns an authentication string suitable for authenticating the handshake out-of-band. Note 346 * that the authentication string can be short (e.g., a 6 digit visual confirmation code). Note: 347 * this should only be called when the state returned byte {@link #getHandshakeState()} is 348 * {@link State#VERIFICATION_NEEDED}, which means this can only be called once. 349 * 350 * @param byteLength length of output in bytes. Min length is 1; max length is 32. 351 */ getVerificationString(int byteLength)352 public byte[] getVerificationString(int byteLength) throws HandshakeException { 353 if (byteLength < 1 || byteLength > 32) { 354 throwIllegalArgumentException("Minimum length is 1 byte, max is 32 bytes"); 355 } 356 357 if (handshakeState != InternalState.HANDSHAKE_VERIFICATION_NEEDED) { 358 throwIllegalStateException("Unexpected state: " + handshakeState); 359 } 360 361 try { 362 derivedSecretKey = 363 EnrollmentCryptoOps.doKeyAgreement(ourKeyPair.getPrivate(), theirPublicKey); 364 } catch (InvalidKeyException e) { 365 // unreachable in practice 366 throwHandshakeException(e); 367 } 368 369 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 370 try { 371 byteStream.write(rawMessage1); 372 byteStream.write(rawMessage2); 373 } catch (IOException e) { 374 // unreachable in practice 375 throwHandshakeException(e); 376 } 377 byte[] info = byteStream.toByteArray(); 378 379 byte[] salt = null; 380 381 try { 382 salt = "UKEY2 v1 auth".getBytes(UTF_8); 383 } catch (UnsupportedEncodingException e) { 384 // unreachable in practice 385 throwHandshakeException(e); 386 } 387 388 byte[] authString = null; 389 try { 390 authString = CryptoOps.hkdf(derivedSecretKey, salt, info); 391 } catch (InvalidKeyException | NoSuchAlgorithmException e) { 392 // unreachable in practice 393 throwHandshakeException(e); 394 } 395 396 handshakeState = InternalState.HANDSHAKE_VERIFICATION_IN_PROGRESS; 397 return Arrays.copyOf(authString, byteLength); 398 } 399 400 /** 401 * Invoked to let handshake state machine know that caller has validated the authentication 402 * string obtained via {@link #getVerificationString(int)}; Note: this should only be called when 403 * the state returned byte {@link #getHandshakeState()} is {@link State#VERIFICATION_IN_PROGRESS}. 404 */ verifyHandshake()405 public void verifyHandshake() { 406 if (handshakeState != InternalState.HANDSHAKE_VERIFICATION_IN_PROGRESS) { 407 throwIllegalStateException("Unexpected state: " + handshakeState); 408 } 409 handshakeState = InternalState.HANDSHAKE_FINISHED; 410 } 411 412 /** 413 * Parses the given handshake message. 414 * @throws AlertException if an error occurs that should be sent to other party. 415 * @throws HandshakeException in an error occurs and the connection should be torn down. 416 */ parseHandshakeMessage(byte[] handshakeMessage)417 public void parseHandshakeMessage(byte[] handshakeMessage) 418 throws AlertException, HandshakeException { 419 switch (handshakeState) { 420 case SERVER_START: 421 parseMessage1(handshakeMessage); 422 handshakeState = InternalState.SERVER_AFTER_CLIENT_INIT; 423 break; 424 425 case CLIENT_WAITING_FOR_SERVER_INIT: 426 parseMessage2(handshakeMessage); 427 handshakeState = InternalState.CLIENT_AFTER_SERVER_INIT; 428 break; 429 430 case SERVER_WAITING_FOR_CLIENT_FINISHED: 431 parseMessage3(handshakeMessage); 432 handshakeState = InternalState.HANDSHAKE_VERIFICATION_NEEDED; 433 break; 434 435 default: 436 throwIllegalStateException("Cannot parse message in state " + handshakeState); 437 } 438 } 439 440 /** 441 * Returns the current state of the handshake. See {@link State}. 442 */ getHandshakeState()443 public State getHandshakeState() { 444 switch (handshakeState) { 445 case CLIENT_START: 446 case CLIENT_WAITING_FOR_SERVER_INIT: 447 case CLIENT_AFTER_SERVER_INIT: 448 case SERVER_START: 449 case SERVER_WAITING_FOR_CLIENT_FINISHED: 450 case SERVER_AFTER_CLIENT_INIT: 451 // fallback intended -- these are all in-progress states 452 return State.IN_PROGRESS; 453 454 case HANDSHAKE_ERROR: 455 return State.ERROR; 456 457 case HANDSHAKE_VERIFICATION_NEEDED: 458 return State.VERIFICATION_NEEDED; 459 460 case HANDSHAKE_VERIFICATION_IN_PROGRESS: 461 return State.VERIFICATION_IN_PROGRESS; 462 463 case HANDSHAKE_FINISHED: 464 return State.FINISHED; 465 466 case HANDSHAKE_ALREADY_USED: 467 return State.ALREADY_USED; 468 469 default: 470 // unreachable in practice 471 throwIllegalStateException("Unknown state"); 472 return null; // really unreachable, but makes compiler happy 473 } 474 } 475 476 /** 477 * Can be called to generate a {@link D2DConnectionContext}. Note: this should only be called 478 * when the state returned byte {@link #getHandshakeState()} is {@link State#FINISHED}. 479 * 480 * @throws HandshakeException 481 */ toConnectionContext()482 public D2DConnectionContext toConnectionContext() throws HandshakeException { 483 switch (handshakeState) { 484 case HANDSHAKE_ERROR: 485 throwIllegalStateException("Cannot make context; handshake had error"); 486 return null; // makes linter happy 487 case HANDSHAKE_ALREADY_USED: 488 throwIllegalStateException("Cannot reuse handshake context; is has already been used"); 489 return null; // makes linter happy 490 case HANDSHAKE_VERIFICATION_NEEDED: 491 throwIllegalStateException("Handshake not verified, cannot create context"); 492 return null; // makes linter happy 493 case HANDSHAKE_FINISHED: 494 // We're done, okay to return a context 495 break; 496 default: 497 // unreachable in practice 498 throwIllegalStateException("Handshake is not complete; cannot create connection context"); 499 } 500 501 if (derivedSecretKey == null) { 502 throwIllegalStateException("Unexpected state error: derived key is null"); 503 } 504 505 ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 506 try { 507 byteStream.write(rawMessage1); 508 byteStream.write(rawMessage2); 509 } catch (IOException e) { 510 // unreachable in practice 511 throwHandshakeException(e); 512 } 513 byte[] info = byteStream.toByteArray(); 514 515 byte[] salt = null; 516 try { 517 salt = "UKEY2 v1 next".getBytes(UTF_8); 518 } catch (UnsupportedEncodingException e) { 519 // unreachable 520 throwHandshakeException(e); 521 } 522 523 SecretKey nextProtocolKey = null; 524 try { 525 nextProtocolKey = new SecretKeySpec(CryptoOps.hkdf(derivedSecretKey, salt, info), "AES"); 526 } catch (InvalidKeyException | NoSuchAlgorithmException e) { 527 // unreachable in practice 528 throwHandshakeException(e); 529 } 530 531 SecretKey clientKey = null; 532 SecretKey serverKey = null; 533 try { 534 clientKey = D2DCryptoOps.deriveNewKeyForPurpose(nextProtocolKey, "client"); 535 serverKey = D2DCryptoOps.deriveNewKeyForPurpose(nextProtocolKey, "server"); 536 } catch (InvalidKeyException | NoSuchAlgorithmException e) { 537 // unreachable in practice 538 throwHandshakeException(e); 539 } 540 541 handshakeState = InternalState.HANDSHAKE_ALREADY_USED; 542 543 return new D2DConnectionContextV1( 544 handshakeRole == HandshakeRole.CLIENT ? clientKey : serverKey, 545 handshakeRole == HandshakeRole.CLIENT ? serverKey : clientKey, 546 0 /* initial encode sequence number */, 547 0 /* initial decode sequence number */); 548 } 549 550 /** 551 * Generates the byte[] encoding of a {@link Ukey2ClientInit} message. 552 * 553 * @throws HandshakeException 554 */ makeClientInitMessage()555 private byte[] makeClientInitMessage() throws HandshakeException { 556 Ukey2ClientInit.Builder clientInit = Ukey2ClientInit.newBuilder(); 557 clientInit.setVersion(VERSION); 558 clientInit.setRandom(ByteString.copyFrom(generateRandomNonce())); 559 clientInit.setNextProtocol(NEXT_PROTOCOL); 560 561 // At the moment, we only support one cipher 562 clientInit.addCipherCommitments(generateP256SHA512Commitment()); 563 564 return clientInit.build().toByteArray(); 565 } 566 567 /** 568 * Generates the byte[] encoding of a {@link Ukey2ServerInit} message. 569 */ makeServerInitMessage()570 private byte[] makeServerInitMessage() { 571 Ukey2ServerInit.Builder serverInit = Ukey2ServerInit.newBuilder(); 572 serverInit.setVersion(VERSION); 573 serverInit.setRandom(ByteString.copyFrom(generateRandomNonce())); 574 serverInit.setHandshakeCipher(handshakeCipher.getValue()); 575 serverInit.setPublicKey( 576 PublicKeyProtoUtil.encodePublicKey(ourKeyPair.getPublic()).toByteString()); 577 578 return serverInit.build().toByteArray(); 579 } 580 581 /** 582 * Generates a keypair for the provided handshake cipher. Currently only P256_SHA512 is 583 * supported. 584 * 585 * @throws HandshakeException 586 */ genKeyPair(HandshakeCipher cipher)587 private KeyPair genKeyPair(HandshakeCipher cipher) throws HandshakeException { 588 switch (cipher) { 589 case P256_SHA512: 590 return PublicKeyProtoUtil.generateEcP256KeyPair(); 591 default: 592 // Should never happen 593 throwHandshakeException("unknown cipher: " + cipher); 594 } 595 return null; // unreachable, but makes compiler happy 596 } 597 598 /** 599 * Attempts to parse message 1 (which is a wrapped {@link Ukey2ClientInit}). See go/ukey2 for 600 * details. 601 * 602 * @throws AlertException if an error occurs 603 */ parseMessage1(byte[] handshakeMessage)604 private void parseMessage1(byte[] handshakeMessage) throws AlertException, HandshakeException { 605 // Deserialize the protobuf; send a BAD_MESSAGE message if deserialization fails 606 Ukey2Message message = null; 607 try { 608 message = Ukey2Message.parseFrom(handshakeMessage); 609 } catch (InvalidProtocolBufferException e) { 610 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE, 611 "Can't parse message 1 " + e.getMessage()); 612 } 613 614 // Verify that message_type == Type.CLIENT_INIT; send a BAD_MESSAGE_TYPE message if mismatch 615 if (!message.hasMessageType() || message.getMessageType() != Ukey2Message.Type.CLIENT_INIT) { 616 throwAlertException( 617 Ukey2Alert.AlertType.BAD_MESSAGE_TYPE, 618 "Expected, but did not find ClientInit message type"); 619 } 620 621 // Deserialize message_data as a ClientInit message; send a BAD_MESSAGE_DATA message if 622 // deserialization fails 623 if (!message.hasMessageData()) { 624 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA, 625 "Expected message data, but didn't find it"); 626 } 627 Ukey2ClientInit clientInit = null; 628 try { 629 clientInit = Ukey2ClientInit.parseFrom(message.getMessageData()); 630 } catch (InvalidProtocolBufferException e) { 631 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA, 632 "Can't parse message data into ClientInit"); 633 } 634 635 // Check that version == VERSION; send BAD_VERSION message if mismatch 636 if (!clientInit.hasVersion()) { 637 throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ClientInit missing version"); 638 } 639 if (clientInit.getVersion() != VERSION) { 640 throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ClientInit version mismatch"); 641 } 642 643 // Check that random is exactly NONCE_LENGTH_IN_BYTES bytes; send Alert.BAD_RANDOM message if 644 // not. 645 if (!clientInit.hasRandom()) { 646 throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ClientInit missing random"); 647 } 648 if (clientInit.getRandom().toByteArray().length != NONCE_LENGTH_IN_BYTES) { 649 throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ClientInit has incorrect nonce length"); 650 } 651 652 // Check to see if any of the handshake_cipher in cipher_commitment are acceptable. Servers 653 // should select the first handshake_cipher that it finds acceptable to support clients 654 // signaling deprecated but supported HandshakeCiphers. If no handshake_cipher is acceptable 655 // (or there are no HandshakeCiphers in the message), the server sends a BAD_HANDSHAKE_CIPHER 656 // message 657 List<Ukey2ClientInit.CipherCommitment> commitments = clientInit.getCipherCommitmentsList(); 658 if (commitments.isEmpty()) { 659 throwAlertException( 660 Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, "ClientInit is missing cipher commitments"); 661 } 662 for (Ukey2ClientInit.CipherCommitment commitment : commitments) { 663 if (!commitment.hasHandshakeCipher() 664 || !commitment.hasCommitment()) { 665 throwAlertException( 666 Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, 667 "ClientInit has improperly formatted cipher commitment"); 668 } 669 670 // TODO(aczeskis): for now we only support one cipher, eventually support more 671 if (commitment.getHandshakeCipher() == handshakeCipher.getValue()) { 672 theirCommitment = commitment.getCommitment().toByteArray(); 673 } 674 } 675 if (theirCommitment == null) { 676 throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, 677 "No acceptable commitments found"); 678 } 679 680 // Checks that next_protocol contains a protocol that the server supports. Send a 681 // BAD_NEXT_PROTOCOL message if not. We currently only support one protocol 682 if (!clientInit.hasNextProtocol() || !NEXT_PROTOCOL.equals(clientInit.getNextProtocol())) { 683 throwAlertException(Ukey2Alert.AlertType.BAD_NEXT_PROTOCOL, "Incorrect next protocol"); 684 } 685 686 // Store raw message for AUTH_STRING computation 687 rawMessage1 = handshakeMessage; 688 } 689 690 /** 691 * Attempts to parse message 2 (which is a wrapped {@link Ukey2ServerInit}). See go/ukey2 for 692 * details. 693 */ parseMessage2(final byte[] handshakeMessage)694 private void parseMessage2(final byte[] handshakeMessage) 695 throws AlertException, HandshakeException { 696 // Deserialize the protobuf; send a BAD_MESSAGE message if deserialization fails 697 Ukey2Message message = null; 698 try { 699 message = Ukey2Message.parseFrom(handshakeMessage); 700 } catch (InvalidProtocolBufferException e) { 701 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE, 702 "Can't parse message 2 " + e.getMessage()); 703 } 704 705 // Verify that message_type == Type.SERVER_INIT; send a BAD_MESSAGE_TYPE message if mismatch 706 if (!message.hasMessageType()) { 707 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_TYPE, 708 "Expected, but did not find message type"); 709 } 710 if (message.getMessageType() == Ukey2Message.Type.ALERT) { 711 handshakeState = InternalState.HANDSHAKE_ERROR; 712 throwHandshakeMessageFromAlertMessage(message); 713 } 714 if (message.getMessageType() != Ukey2Message.Type.SERVER_INIT) { 715 throwAlertException( 716 Ukey2Alert.AlertType.BAD_MESSAGE_TYPE, 717 "Expected, but did not find SERVER_INIT message type"); 718 } 719 720 // Deserialize message_data as a ServerInit message; send a BAD_MESSAGE_DATA message if 721 // deserialization fails 722 if (!message.hasMessageData()) { 723 724 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA, 725 "Expected message data, but didn't find it"); 726 } 727 Ukey2ServerInit serverInit = null; 728 try { 729 serverInit = Ukey2ServerInit.parseFrom(message.getMessageData()); 730 } catch (InvalidProtocolBufferException e) { 731 throwAlertException(Ukey2Alert.AlertType.BAD_MESSAGE_DATA, 732 "Can't parse message data into ServerInit"); 733 } 734 735 // Check that version == VERSION; send BAD_VERSION message if mismatch 736 if (!serverInit.hasVersion()) { 737 throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ServerInit missing version"); 738 } 739 if (serverInit.getVersion() != VERSION) { 740 throwAlertException(Ukey2Alert.AlertType.BAD_VERSION, "ServerInit version mismatch"); 741 } 742 743 // Check that random is exactly NONCE_LENGTH_IN_BYTES bytes; send Alert.BAD_RANDOM message if 744 // not. 745 if (!serverInit.hasRandom()) { 746 throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ServerInit missing random"); 747 } 748 if (serverInit.getRandom().toByteArray().length != NONCE_LENGTH_IN_BYTES) { 749 throwAlertException(Ukey2Alert.AlertType.BAD_RANDOM, "ServerInit has incorrect nonce length"); 750 } 751 752 // Check that handshake_cipher matches a handshake cipher that was sent in 753 // ClientInit.cipher_commitments. If not, send a BAD_HANDSHAKECIPHER message 754 if (!serverInit.hasHandshakeCipher()) { 755 throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, "No handshake cipher found"); 756 } 757 HandshakeCipher serverCipher = null; 758 for (HandshakeCipher cipher : HandshakeCipher.values()) { 759 if (cipher.getValue() == serverInit.getHandshakeCipher()) { 760 serverCipher = cipher; 761 break; 762 } 763 } 764 if (serverCipher == null || serverCipher != handshakeCipher) { 765 throwAlertException(Ukey2Alert.AlertType.BAD_HANDSHAKE_CIPHER, 766 "No acceptable handshake cipher found"); 767 } 768 769 // Check that public_key parses into a correct public key structure. If not, send a 770 // BAD_PUBLIC_KEY message. 771 if (!serverInit.hasPublicKey()) { 772 throwAlertException(Ukey2Alert.AlertType.BAD_PUBLIC_KEY, "No public key found in ServerInit"); 773 } 774 theirPublicKey = parseP256PublicKey(serverInit.getPublicKey().toByteArray()); 775 776 // Store raw message for AUTH_STRING computation 777 rawMessage2 = handshakeMessage; 778 } 779 780 /** 781 * Attempts to parse message 3 (which is a wrapped {@link Ukey2ClientFinished}). See go/ukey2 for 782 * details. 783 */ parseMessage3(final byte[] handshakeMessage)784 private void parseMessage3(final byte[] handshakeMessage) throws HandshakeException { 785 // Deserialize the protobuf; terminate the connection if deserialization fails. 786 Ukey2Message message = null; 787 try { 788 message = Ukey2Message.parseFrom(handshakeMessage); 789 } catch (InvalidProtocolBufferException e) { 790 throwHandshakeException("Can't parse message 3", e); 791 } 792 793 // Verify that message_type == Type.CLIENT_FINISH; terminate connection if mismatch occurs 794 if (!message.hasMessageType()) { 795 throw new HandshakeException("Expected, but did not find message type"); 796 } 797 if (message.getMessageType() == Ukey2Message.Type.ALERT) { 798 throwHandshakeMessageFromAlertMessage(message); 799 } 800 if (message.getMessageType() != Ukey2Message.Type.CLIENT_FINISH) { 801 throwHandshakeException("Expected, but did not find CLIENT_FINISH message type"); 802 } 803 804 // Verify that the hash of the ClientFinished matches the expected commitment from ClientInit. 805 // Terminate the connection if the expected match fails. 806 verifyCommitment(handshakeMessage); 807 808 // Deserialize message_data as a ClientFinished message; terminate the connection if 809 // deserialization fails. 810 if (!message.hasMessageData()) { 811 throwHandshakeException("Expected message data, but didn't find it"); 812 } 813 Ukey2ClientFinished clientFinished = null; 814 try { 815 clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData()); 816 } catch (InvalidProtocolBufferException e) { 817 throwHandshakeException(e); 818 } 819 820 // Check that public_key parses into a correct public key structure. If not, terminate the 821 // connection. 822 if (!clientFinished.hasPublicKey()) { 823 throwHandshakeException("No public key found in ClientFinished"); 824 } 825 try { 826 theirPublicKey = parseP256PublicKey(clientFinished.getPublicKey().toByteArray()); 827 } catch (AlertException e) { 828 // Wrap in a HandshakeException because error should not be sent on the wire. 829 throwHandshakeException(e); 830 } 831 } 832 verifyCommitment(byte[] handshakeMessage)833 private void verifyCommitment(byte[] handshakeMessage) throws HandshakeException { 834 byte[] actualClientFinishHash = null; 835 switch (handshakeCipher) { 836 case P256_SHA512: 837 actualClientFinishHash = sha512(handshakeMessage); 838 break; 839 default: 840 // should be unreachable 841 throwIllegalStateException("Unexpected handshakeCipher"); 842 } 843 844 // Time constant after Java SE 6 Update 17 845 // See http://www.oracle.com/technetwork/java/javase/6u17-141447.html 846 if (!MessageDigest.isEqual(actualClientFinishHash, theirCommitment)) { 847 throwHandshakeException("Commitment does not match"); 848 } 849 } 850 throwHandshakeMessageFromAlertMessage(Ukey2Message message)851 private void throwHandshakeMessageFromAlertMessage(Ukey2Message message) 852 throws HandshakeException { 853 if (message.hasMessageData()) { 854 Ukey2Alert alert = null; 855 try { 856 alert = Ukey2Alert.parseFrom(message.getMessageData()); 857 } catch (InvalidProtocolBufferException e) { 858 throwHandshakeException("Cannot parse alert message", e); 859 } 860 861 if (alert.hasType() && alert.hasErrorMessage()) { 862 throwHandshakeException( 863 "Received Alert message. Type: " 864 + alert.getType() 865 + " Error Message: " 866 + alert.getErrorMessage()); 867 } else if (alert.hasType()) { 868 throwHandshakeException("Received Alert message. Type: " + alert.getType()); 869 } 870 } 871 872 throwHandshakeException("Received empty Alert Message"); 873 } 874 875 /** 876 * Parses an encoded public P256 key. 877 */ parseP256PublicKey(byte[] encodedPublicKey)878 private PublicKey parseP256PublicKey(byte[] encodedPublicKey) 879 throws AlertException, HandshakeException { 880 try { 881 return PublicKeyProtoUtil.parsePublicKey(GenericPublicKey.parseFrom(encodedPublicKey)); 882 } catch (InvalidProtocolBufferException | InvalidKeySpecException e) { 883 throwAlertException(Ukey2Alert.AlertType.BAD_PUBLIC_KEY, 884 "Cannot parse public key: " + e.getMessage()); 885 return null; // unreachable, but makes compiler happy 886 } 887 } 888 889 /** 890 * Generates a {@link CipherCommitment} for the P256_SHA512 cipher. 891 */ generateP256SHA512Commitment()892 private CipherCommitment generateP256SHA512Commitment() throws HandshakeException { 893 // Generate the corresponding finished message if it's not done yet 894 if (!rawMessage3Map.containsKey(HandshakeCipher.P256_SHA512)) { 895 generateP256SHA512ClientFinished(ourKeyPair); 896 } 897 898 CipherCommitment.Builder cipherCommitment = CipherCommitment.newBuilder(); 899 cipherCommitment.setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.P256_SHA512); 900 cipherCommitment.setCommitment( 901 ByteString.copyFrom(sha512(rawMessage3Map.get(HandshakeCipher.P256_SHA512)))); 902 903 return cipherCommitment.build(); 904 } 905 906 /** 907 * Generates and records a {@link Ukey2ClientFinished} message for the P256_SHA512 cipher. 908 */ generateP256SHA512ClientFinished(KeyPair p256KeyPair)909 private Ukey2ClientFinished generateP256SHA512ClientFinished(KeyPair p256KeyPair) { 910 byte[] encodedKey = PublicKeyProtoUtil.encodePublicKey(p256KeyPair.getPublic()).toByteArray(); 911 912 Ukey2ClientFinished.Builder clientFinished = Ukey2ClientFinished.newBuilder(); 913 clientFinished.setPublicKey(ByteString.copyFrom(encodedKey)); 914 915 rawMessage3Map.put( 916 HandshakeCipher.P256_SHA512, 917 makeUkey2Message(Ukey2Message.Type.CLIENT_FINISH, clientFinished.build().toByteArray())); 918 919 return clientFinished.build(); 920 } 921 922 /** 923 * Generates the serialized representation of a {@link Ukey2Message} based on the provided type 924 * and data. 925 */ makeUkey2Message(Ukey2Message.Type messageType, byte[] messageData)926 private byte[] makeUkey2Message(Ukey2Message.Type messageType, byte[] messageData) { 927 Ukey2Message.Builder message = Ukey2Message.newBuilder(); 928 929 switch (messageType) { 930 case ALERT: 931 case CLIENT_INIT: 932 case SERVER_INIT: 933 case CLIENT_FINISH: 934 // fall through intentional; valid message types 935 break; 936 default: 937 throwIllegalArgumentException("Invalid message type: " + messageType); 938 } 939 message.setMessageType(messageType); 940 941 // Alerts a blank message data field 942 if (messageType != Ukey2Message.Type.ALERT) { 943 if (messageData == null || messageData.length == 0) { 944 throwIllegalArgumentException("Cannot send empty message data for non-alert messages"); 945 } 946 message.setMessageData(ByteString.copyFrom(messageData)); 947 } 948 949 return message.build().toByteArray(); 950 } 951 952 /** 953 * Returns a {@link Ukey2Alert} message of given type and having the loggable additional data if 954 * present. 955 */ makeAlertMessage(Ukey2Alert.AlertType alertType, @Nullable String loggableAdditionalData)956 private Ukey2Alert makeAlertMessage(Ukey2Alert.AlertType alertType, 957 @Nullable String loggableAdditionalData) throws HandshakeException { 958 switch (alertType) { 959 case BAD_MESSAGE: 960 case BAD_MESSAGE_TYPE: 961 case INCORRECT_MESSAGE: 962 case BAD_MESSAGE_DATA: 963 case BAD_VERSION: 964 case BAD_RANDOM: 965 case BAD_HANDSHAKE_CIPHER: 966 case BAD_NEXT_PROTOCOL: 967 case BAD_PUBLIC_KEY: 968 case INTERNAL_ERROR: 969 // fall through intentional; valid alert types 970 break; 971 default: 972 throwHandshakeException("Unknown alert type: " + alertType); 973 } 974 975 Ukey2Alert.Builder alert = Ukey2Alert.newBuilder(); 976 alert.setType(alertType); 977 978 if (loggableAdditionalData != null) { 979 alert.setErrorMessage(loggableAdditionalData); 980 } 981 982 return alert.build(); 983 } 984 985 /** 986 * Generates a cryptoraphically random nonce of NONCE_LENGTH_IN_BYTES bytes. 987 */ generateRandomNonce()988 private static byte[] generateRandomNonce() { 989 SecureRandom rng = new SecureRandom(); 990 byte[] randomNonce = new byte[NONCE_LENGTH_IN_BYTES]; 991 rng.nextBytes(randomNonce); 992 return randomNonce; 993 } 994 995 /** 996 * Handy wrapper to do SHA512. 997 */ sha512(byte[] input)998 private byte[] sha512(byte[] input) throws HandshakeException { 999 MessageDigest sha512; 1000 try { 1001 sha512 = MessageDigest.getInstance("SHA-512"); 1002 return sha512.digest(input); 1003 } catch (NoSuchAlgorithmException e) { 1004 throwHandshakeException("No security provider initialized yet?", e); 1005 return null; // unreachable in practice, but makes compiler happy 1006 } 1007 } 1008 1009 // Exception wrappers that remember to set the handshake state to ERROR 1010 throwAlertException(Ukey2Alert.AlertType alertType, String alertLogStatement)1011 private void throwAlertException(Ukey2Alert.AlertType alertType, String alertLogStatement) 1012 throws AlertException, HandshakeException { 1013 handshakeState = InternalState.HANDSHAKE_ERROR; 1014 throw new AlertException(alertLogStatement, makeAlertMessage(alertType, alertLogStatement)); 1015 } 1016 throwHandshakeException(String logMessage)1017 private void throwHandshakeException(String logMessage) throws HandshakeException { 1018 handshakeState = InternalState.HANDSHAKE_ERROR; 1019 throw new HandshakeException(logMessage); 1020 } 1021 throwHandshakeException(Exception e)1022 private void throwHandshakeException(Exception e) throws HandshakeException { 1023 handshakeState = InternalState.HANDSHAKE_ERROR; 1024 throw new HandshakeException(e); 1025 } 1026 throwHandshakeException(String logMessage, Exception e)1027 private void throwHandshakeException(String logMessage, Exception e) throws HandshakeException { 1028 handshakeState = InternalState.HANDSHAKE_ERROR; 1029 throw new HandshakeException(logMessage, e); 1030 } 1031 throwIllegalStateException(String logMessage)1032 private void throwIllegalStateException(String logMessage) { 1033 handshakeState = InternalState.HANDSHAKE_ERROR; 1034 throw new IllegalStateException(logMessage); 1035 } 1036 throwIllegalArgumentException(String logMessage)1037 private void throwIllegalArgumentException(String logMessage) { 1038 handshakeState = InternalState.HANDSHAKE_ERROR; 1039 throw new IllegalArgumentException(logMessage); 1040 } 1041 } 1042