diff options
Diffstat (limited to 'src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java')
-rw-r--r-- | src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java | 342 |
1 files changed, 0 insertions, 342 deletions
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java deleted file mode 100644 index 2b73653..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2ShellCppWrapper.java +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package com.google.security.cryptauth.lib.securegcm; - -import com.google.common.io.BaseEncoding; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.ProcessBuilder.Redirect; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import javax.annotation.Nullable; - -/** - * A wrapper to execute and interact with the //security/cryptauth/lib/securegcm:ukey2_shell binary. - * - * <p>This binary is a shell over the C++ implementation of the UKEY2 protocol, so this wrapper is - * used to test compatibility between the C++ and Java implementations. - * - * <p>The ukey2_shell is invoked as follows: - * - * <pre>{@code - * ukey2_shell --mode=<mode> --verification_string_length=<length> - * }</pre> - * - * where {@code mode={initiator, responder}} and {@code verification_string_length} is a positive - * integer. - */ -public class Ukey2ShellCppWrapper { - // The path the the ukey2_shell binary. - private static final String BINARY_PATH = "build/src/main/cpp/src/securegcm/ukey2_shell"; - - // The time to wait before timing out a read or write operation to the shell. - @SuppressWarnings("GoodTime") // TODO(b/147378611): store a java.time.Duration instead - private static final long IO_TIMEOUT_MILLIS = 5000; - - public enum Mode { - INITIATOR, - RESPONDER - } - - private final Mode mode; - private final int verificationStringLength; - private final ExecutorService executorService; - - @Nullable private Process shellProcess; - private boolean secureContextEstablished; - - /** - * @param mode The mode to run the shell in (initiator or responder). - * @param verificationStringLength The length of the verification string used in the handshake. - */ - public Ukey2ShellCppWrapper(Mode mode, int verificationStringLength) { - this.mode = mode; - this.verificationStringLength = verificationStringLength; - this.executorService = Executors.newSingleThreadExecutor(); - } - - /** - * Begins execution of the ukey2_shell binary. - * - * @throws IOException - */ - public void startShell() throws IOException { - if (shellProcess != null) { - throw new IllegalStateException("Shell already started."); - } - - String modeArg = "--mode=" + getModeString(); - String verificationStringLengthArg = "--verification_string_length=" + verificationStringLength; - - final ProcessBuilder builder = - new ProcessBuilder(BINARY_PATH, modeArg, verificationStringLengthArg); - - // Merge the shell's stderr with the stderr of the current process. - builder.redirectError(Redirect.INHERIT); - - shellProcess = builder.start(); - } - - /** - * Stops execution of the ukey2_shell binary. - * - * @throws IOException - */ - public void stopShell() { - if (shellProcess == null) { - throw new IllegalStateException("Shell not started."); - } - shellProcess.destroy(); - } - - /** - * @return the handshake message read from the shell. - * @throws IOException - */ - public byte[] readHandshakeMessage() throws IOException { - return readFrameWithTimeout(); - } - - /** - * Sends the handshake message to the shell. - * - * @param message - * @throws IOException - */ - public void writeHandshakeMessage(byte[] message) throws IOException { - writeFrameWithTimeout(message); - } - - /** - * Reads the auth string from the shell and compares it with {@code authString}. If verification - * succeeds, then write "ok" back as a confirmation. - * - * @param authString the auth string to compare to. - * @throws IOException - */ - public void confirmAuthString(byte[] authString) throws IOException { - byte[] shellAuthString = readFrameWithTimeout(); - if (!Arrays.equals(authString, shellAuthString)) { - throw new IOException( - String.format( - "Unable to verify auth string: 0x%s != 0x%s", - BaseEncoding.base16().encode(authString), - BaseEncoding.base16().encode(shellAuthString))); - } - writeFrameWithTimeout("ok".getBytes()); - secureContextEstablished = true; - } - - /** - * Sends {@code payload} to be encrypted by the shell. This function can only be called after a - * handshake is performed and a secure context established. - * - * @param payload the data to be encrypted. - * @return the encrypted message returned by the shell. - * @throws IOException - */ - public byte[] sendEncryptCommand(byte[] payload) throws IOException { - writeFrameWithTimeout(createExpression("encrypt", payload)); - return readFrameWithTimeout(); - } - - /** - * Sends {@code message} to be decrypted by the shell. This function can only be called after a - * handshake is performed and a secure context established. - * - * @param message the data to be decrypted. - * @return the decrypted payload returned by the shell. - * @throws IOException - */ - public byte[] sendDecryptCommand(byte[] message) throws IOException { - writeFrameWithTimeout(createExpression("decrypt", message)); - return readFrameWithTimeout(); - } - - /** - * Requests the session unique value from the shell. This function can only be called after a - * handshake is performed and a secure context established. - * - * @return the session unique value returned by the shell. - * @throws IOException - */ - public byte[] sendSessionUniqueCommand() throws IOException { - writeFrameWithTimeout(createExpression("session_unique", null)); - return readFrameWithTimeout(); - } - - /** - * Reads a frame from the shell's stdout with a timeout. - * - * @return The contents of the frame. - * @throws IOException - */ - private byte[] readFrameWithTimeout() throws IOException { - Future<byte[]> future = - executorService.submit( - new Callable<byte[]>() { - @Override - public byte[] call() throws Exception { - return readFrame(); - } - }); - - try { - return future.get(IO_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException(e); - } - } - - /** - * Writes a frame to the shell's stdin with a timeout. - * - * @param contents the contents of the frame. - * @throws IOException - */ - private void writeFrameWithTimeout(final byte[] contents) throws IOException { - Future<?> future = - executorService.submit( - new Runnable() { - @Override - public void run() { - try { - writeFrame(contents); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - - try { - future.get(IO_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException(e); - } - } - - /** - * Reads a frame from the shell's stdout, which has the format: - * - * <pre>{@code - * +---------------------+-----------------+ - * | 4-bytes | |length| bytes | - * +---------------------+-----------------+ - * | (unsigned) length | contents | - * +---------------------+-----------------+ - * }</pre> - * - * @return the contents that were read - * @throws IOException - */ - private byte[] readFrame() throws IOException { - if (shellProcess == null) { - throw new IllegalStateException("Shell not started."); - } - - InputStream inputStream = shellProcess.getInputStream(); - byte[] lengthBytes = new byte[4]; - if (inputStream.read(lengthBytes) != lengthBytes.length) { - throw new IOException("Failed to read length."); - } - - int length = ByteBuffer.wrap(lengthBytes).order(ByteOrder.BIG_ENDIAN).getInt(); - if (length < 0) { - throw new IOException("Length too large: " + Arrays.toString(lengthBytes)); - } - - byte[] contents = new byte[length]; - int bytesRead = inputStream.read(contents); - if (bytesRead != length) { - throw new IOException("Failed to read entire contents: " + bytesRead + " != " + length); - } - - return contents; - } - - /** - * Writes a frame to the shell's stdin, which has the format: - * - * <pre>{@code - * +---------------------+-----------------+ - * | 4-bytes | |length| bytes | - * +---------------------+-----------------+ - * | (unsigned) length | contents | - * +---------------------+-----------------+ - * }</pre> - * - * @param contents the contents to send. - * @throws IOException - */ - private void writeFrame(byte[] contents) throws IOException { - if (shellProcess == null) { - throw new IllegalStateException("Shell not started."); - } - - // The length is big-endian encoded, network byte order. - long length = contents.length; - byte[] lengthBytes = new byte[4]; - lengthBytes[0] = (byte) (length >> 32 & 0xFF); - lengthBytes[1] = (byte) (length >> 16 & 0xFF); - lengthBytes[2] = (byte) (length >> 8 & 0xFF); - lengthBytes[3] = (byte) (length >> 0 & 0xFF); - - OutputStream outputStream = shellProcess.getOutputStream(); - outputStream.write(lengthBytes); - outputStream.write(contents); - outputStream.flush(); - } - - /** - * Creates an expression to be processed when a secure connection is established, after the - * handshake is done. - * - * @param command The command to send. - * @param argument The argument of the command. Can be null. - * @return the expression that can be sent to the shell. - * @throws IOException. - */ - private byte[] createExpression(String command, @Nullable byte[] argument) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - outputStream.write(command.getBytes()); - outputStream.write(" ".getBytes()); - if (argument != null) { - outputStream.write(argument); - } - return outputStream.toByteArray(); - } - - /** @return the mode string to use in the argument to start the ukey2_shell process. */ - private String getModeString() { - switch (mode) { - case INITIATOR: - return "initiator"; - case RESPONDER: - return "responder"; - default: - throw new IllegalArgumentException("Uknown mode " + mode); - } - } -} |