1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.car.encryptionrunner; 18 19 import android.annotation.NonNull; 20 import android.util.Log; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import com.google.security.cryptauth.lib.securegcm.D2DConnectionContext; 25 import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake; 26 27 import java.security.SignatureException; 28 29 /** 30 * An {@link EncryptionRunner} that uses Ukey2 as the underlying implementation. 31 */ 32 public class Ukey2EncryptionRunner implements EncryptionRunner { 33 34 private static final Ukey2Handshake.HandshakeCipher CIPHER = 35 Ukey2Handshake.HandshakeCipher.P256_SHA512; 36 37 private static final int AUTH_STRING_LENGTH = 6; 38 39 private Ukey2Handshake mUkey2client; 40 private boolean mRunnerIsInvalid; 41 42 @Override initHandshake()43 public HandshakeMessage initHandshake() { 44 checkRunnerIsNew(); 45 try { 46 mUkey2client = Ukey2Handshake.forInitiator(CIPHER); 47 return HandshakeMessage.newBuilder() 48 .setHandshakeState(getHandshakeState()) 49 .setNextMessage(mUkey2client.getNextHandshakeMessage()) 50 .build(); 51 } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException e) { 52 Log.e(TAG, "unexpected exception", e); 53 throw new RuntimeException(e); 54 } 55 56 } 57 58 @Override respondToInitRequest(byte[] initializationRequest)59 public HandshakeMessage respondToInitRequest(byte[] initializationRequest) 60 throws HandshakeException { 61 checkRunnerIsNew(); 62 try { 63 if (mUkey2client != null) { 64 throw new IllegalStateException("Cannot reuse encryption runners, " 65 + "this one is already initialized"); 66 } 67 mUkey2client = Ukey2Handshake.forResponder(CIPHER); 68 mUkey2client.parseHandshakeMessage(initializationRequest); 69 return HandshakeMessage.newBuilder() 70 .setHandshakeState(getHandshakeState()) 71 .setNextMessage(mUkey2client.getNextHandshakeMessage()) 72 .build(); 73 74 } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException 75 | Ukey2Handshake.AlertException e) { 76 throw new HandshakeException(e); 77 } 78 } 79 checkRunnerIsNew()80 private void checkRunnerIsNew() { 81 if (mUkey2client != null) { 82 throw new IllegalStateException("This runner is already initialized."); 83 } 84 } 85 86 87 @Override continueHandshake(byte[] response)88 public HandshakeMessage continueHandshake(byte[] response) throws HandshakeException { 89 checkInitialized(); 90 try { 91 if (mUkey2client.getHandshakeState() != Ukey2Handshake.State.IN_PROGRESS) { 92 throw new IllegalStateException("handshake is not in progress, state =" 93 + mUkey2client.getHandshakeState()); 94 } 95 mUkey2client.parseHandshakeMessage(response); 96 97 // Not obvious from ukey2 api, but getting the next message can change the state. 98 // calling getNext message might go from in progress to verification needed, on 99 // the assumption that we already send this message to the peer. 100 byte[] nextMessage = null; 101 if (mUkey2client.getHandshakeState() == Ukey2Handshake.State.IN_PROGRESS) { 102 nextMessage = mUkey2client.getNextHandshakeMessage(); 103 } 104 105 String verificationCode = null; 106 if (mUkey2client.getHandshakeState() == Ukey2Handshake.State.VERIFICATION_NEEDED) { 107 verificationCode = generateReadablePairingCode( 108 mUkey2client.getVerificationString(AUTH_STRING_LENGTH)); 109 } 110 return HandshakeMessage.newBuilder() 111 .setHandshakeState(getHandshakeState()) 112 .setNextMessage(nextMessage) 113 .setVerificationCode(verificationCode) 114 .build(); 115 } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException 116 | Ukey2Handshake.AlertException e) { 117 throw new HandshakeException(e); 118 } 119 } 120 121 /** 122 * Returns a human-readable pairing code string generated from the verification bytes. Converts 123 * each byte into a digit with a simple modulo. 124 * 125 * <p>This should match the implementation in the iOS and Android client libraries. 126 */ 127 @VisibleForTesting generateReadablePairingCode(byte[] verificationCode)128 String generateReadablePairingCode(byte[] verificationCode) { 129 StringBuilder outString = new StringBuilder(); 130 for (byte b : verificationCode) { 131 int unsignedInt = Byte.toUnsignedInt(b); 132 int digit = unsignedInt % 10; 133 outString.append(digit); 134 } 135 136 return outString.toString(); 137 } 138 139 private static class UKey2Key implements Key { 140 141 private final D2DConnectionContext mConnectionContext; 142 UKey2Key(@onNull D2DConnectionContext connectionContext)143 UKey2Key(@NonNull D2DConnectionContext connectionContext) { 144 this.mConnectionContext = connectionContext; 145 } 146 147 @Override asBytes()148 public byte[] asBytes() { 149 return mConnectionContext.saveSession(); 150 } 151 152 @Override encryptData(byte[] data)153 public byte[] encryptData(byte[] data) { 154 return mConnectionContext.encodeMessageToPeer(data); 155 } 156 157 @Override decryptData(byte[] encryptedData)158 public byte[] decryptData(byte[] encryptedData) throws SignatureException { 159 return mConnectionContext.decodeMessageFromPeer(encryptedData); 160 } 161 } 162 163 @Override verifyPin()164 public HandshakeMessage verifyPin() throws HandshakeException { 165 checkInitialized(); 166 mUkey2client.verifyHandshake(); 167 try { 168 return HandshakeMessage.newBuilder() 169 .setHandshakeState(getHandshakeState()) 170 .setKey(new UKey2Key(mUkey2client.toConnectionContext())) 171 .build(); 172 } catch (com.google.security.cryptauth.lib.securegcm.HandshakeException e) { 173 throw new HandshakeException(e); 174 } 175 } 176 177 @HandshakeMessage.HandshakeState getHandshakeState()178 private int getHandshakeState() { 179 checkInitialized(); 180 switch (mUkey2client.getHandshakeState()) { 181 case ALREADY_USED: 182 case ERROR: 183 throw new IllegalStateException("unexpected error state"); 184 case FINISHED: 185 return HandshakeMessage.HandshakeState.FINISHED; 186 case IN_PROGRESS: 187 return HandshakeMessage.HandshakeState.IN_PROGRESS; 188 case VERIFICATION_IN_PROGRESS: 189 case VERIFICATION_NEEDED: 190 return HandshakeMessage.HandshakeState.VERIFICATION_NEEDED; 191 default: 192 throw new IllegalStateException("unexpected handshake state"); 193 } 194 } 195 196 @Override keyOf(byte[] serialized)197 public Key keyOf(byte[] serialized) { 198 return new UKey2Key(D2DConnectionContext.fromSavedSession(serialized)); 199 } 200 201 @Override invalidPin()202 public void invalidPin() { 203 mRunnerIsInvalid = true; 204 } 205 checkIsUkey2Key(Key key)206 private UKey2Key checkIsUkey2Key(Key key) { 207 if (!(key instanceof UKey2Key)) { 208 throw new IllegalArgumentException("wrong key type"); 209 } 210 return (UKey2Key) key; 211 } 212 checkInitialized()213 private void checkInitialized() { 214 if (mUkey2client == null) { 215 throw new IllegalStateException("runner not initialized"); 216 } 217 if (mRunnerIsInvalid) { 218 throw new IllegalStateException("runner has been invalidated"); 219 } 220 } 221 } 222