• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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