diff options
Diffstat (limited to 'src/main/javatest/com/google/security/cryptauth')
14 files changed, 0 insertions, 4707 deletions
diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java deleted file mode 100644 index e671e8c..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextTest.java +++ /dev/null @@ -1,568 +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 static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.security.SignatureException; -import java.util.Arrays; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** - * Base class for Android compatible tests for {@link D2DConnectionContext} subclasses. - * Note: We would use a Parameterized test runner to test different versions, but this - * functionality is not supported by Android tests. - */ -@RunWith(JUnit4.class) -public class D2DConnectionContextTest { - private static final String PING = "ping"; - private static final String PONG = "pong"; - - // Key is: "initiator_encode_key_for_aes_256" - private static final SecretKey INITIATOR_ENCODE_KEY = new SecretKeySpec( - new byte[] { - (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x69, (byte) 0x61, (byte) 0x74, - (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x65, (byte) 0x6e, (byte) 0x63, (byte) 0x6f, - (byte) 0x64, (byte) 0x65, (byte) 0x5f, (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x5f, - (byte) 0x66, (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x61, (byte) 0x65, (byte) 0x73, - (byte) 0x5f, (byte) 0x32, (byte) 0x35, (byte) 0x36 - }, - "AES"); - - // Key is: "initiator_decode_key_for_aes_256" - private static final SecretKey INITIATOR_DECODE_KEY = new SecretKeySpec( - new byte[] { - (byte) 0x69, (byte) 0x6e, (byte) 0x69, (byte) 0x74, (byte) 0x69, (byte) 0x61, (byte) 0x74, - (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x64, (byte) 0x65, (byte) 0x63, (byte) 0x6f, - (byte) 0x64, (byte) 0x65, (byte) 0x5f, (byte) 0x6b, (byte) 0x65, (byte) 0x79, (byte) 0x5f, - (byte) 0x66, (byte) 0x6f, (byte) 0x72, (byte) 0x5f, (byte) 0x61, (byte) 0x65, (byte) 0x73, - (byte) 0x5f, (byte) 0x32, (byte) 0x35, (byte) 0x36 - }, - "AES"); - - private D2DConnectionContext initiatorCtx; - private D2DConnectionContext responderCtx; - - @Before - public void setUp() throws Exception { - KeyEncodingTest.installSunEcSecurityProviderIfNecessary(); - } - - protected void testPeerToPeerProtocol(int protocolVersion) throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */); - responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */); - - byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING); - // (send message to responder) - - // responder - String messageStr = responderCtx.decodeMessageFromPeerAsString(pingMessage); - assertEquals(PING, messageStr); - - byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG); - // (send message to initiator) - - // initiator - messageStr = initiatorCtx.decodeMessageFromPeerAsString(pongMessage); - assertEquals(PONG, messageStr); - - // let's make sure there is actually some crypto involved. - pingMessage = initiatorCtx.encodeMessageToPeer("can you see this?"); - pingMessage[2] = (byte) (pingMessage[2] + 1); // twiddle with the message - try { - responderCtx.decodeMessageFromPeerAsString(pingMessage); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - assertTrue(expected.getMessage().contains("failed verification")); - } - - // Try and replay the previous encoded message to the initiator (replays should not work). - try { - initiatorCtx.decodeMessageFromPeerAsString(pongMessage); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - assertTrue(expected.getMessage().contains("sequence")); - } - - assertEquals(protocolVersion, initiatorCtx.getProtocolVersion()); - assertEquals(protocolVersion, responderCtx.getProtocolVersion()); - } - - @Test - public void testPeerToPeerProtocol_V0() throws Exception { - testPeerToPeerProtocol(D2DConnectionContextV0.PROTOCOL_VERSION); - } - - @Test - public void testPeerToPeerProtocol_V1() throws Exception { - testPeerToPeerProtocol(D2DConnectionContextV1.PROTOCOL_VERSION); - } - - protected void testResponderSendsFirst(int protocolVersion) throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */); - responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */); - - byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG); - assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage)); - - pongMessage = responderCtx.encodeMessageToPeer(PONG); - assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage)); - - // for good measure, if the initiator now responds, it should also work: - byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - } - - @Test - public void testResponderSendsFirst_V0() throws Exception { - testResponderSendsFirst(D2DConnectionContextV0.PROTOCOL_VERSION); - } - - @Test - public void testResponderSendsFirst_V1() throws Exception { - testResponderSendsFirst(D2DConnectionContextV1.PROTOCOL_VERSION); - } - - protected void testAssymmetricFlows(int protocolVersion) throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */); - responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */); - - // Let's test that this still works if one side sends a few messages in a row. - byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - - byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG); - assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage)); - - pongMessage = responderCtx.encodeMessageToPeer(PONG); - assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage)); - } - - @Test - public void testAssymmetricFlows_V0() throws Exception { - testAssymmetricFlows(D2DConnectionContextV0.PROTOCOL_VERSION); - } - - @Test - public void testAssymmetricFlows_V1() throws Exception { - testAssymmetricFlows(D2DConnectionContextV1.PROTOCOL_VERSION); - } - - public void testErrorWhenResponderResendsMessage(int protocolVersion) throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */); - responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */); - - byte[] pongMessage = responderCtx.encodeMessageToPeer(PONG); - assertEquals(PONG, initiatorCtx.decodeMessageFromPeerAsString(pongMessage)); - - try { - // send pongMessage again to the initiator - initiatorCtx.decodeMessageFromPeerAsString(pongMessage); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - assertTrue(expected.getMessage().contains("sequence")); - } - } - - @Test - public void testErrorWhenResponderResendsMessage_V0() throws Exception { - testErrorWhenResponderResendsMessage(D2DConnectionContextV0.PROTOCOL_VERSION); - } - - @Test - public void testErrorWhenResponderResendsMessage_V1() throws Exception { - testErrorWhenResponderResendsMessage(D2DConnectionContextV1.PROTOCOL_VERSION); - } - - protected void testErrorWhenResponderEchoesInitiatorMessage( - int protocolVersion) throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - return; - } - - initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */); - responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */); - - byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - try { - initiatorCtx.decodeMessageFromPeerAsString(pingMessage); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - } - } - - @Test - public void testErrorWhenResponderEchoesInitiatorMessage_V0() throws Exception { - testErrorWhenResponderEchoesInitiatorMessage(D2DConnectionContextV0.PROTOCOL_VERSION); - } - - @Test - public void testErrorWhenResponderEchoesInitiatorMessage_V1() throws Exception { - testErrorWhenResponderEchoesInitiatorMessage(D2DConnectionContextV1.PROTOCOL_VERSION); - } - - @Test - public void testErrorUsingV1InitiatorWithV0Responder() throws SignatureException { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 1, 1); - responderCtx = new D2DConnectionContextV0(INITIATOR_DECODE_KEY, 1); - - // Decoding the responder's message should succeed, because the decode key and sequence numbers - // match. - initiatorCtx.decodeMessageFromPeer(responderCtx.encodeMessageToPeer(PING)); - - // Responder fails to decodes initiator's encoded message because keys do not match. - try { - responderCtx.decodeMessageFromPeer(initiatorCtx.encodeMessageToPeer(PONG)); - fail("Expected verification to fail."); - } catch (SignatureException e) { - // Exception expected. - } - } - - @Test - public void testErrorWithV0InitiatorV1Responder() throws SignatureException { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1); - responderCtx = new D2DConnectionContextV1(INITIATOR_DECODE_KEY, INITIATOR_ENCODE_KEY, 1, 1); - - // Decoding the initiator's message should succeed, because the decode key and sequence numbers - // match. - responderCtx.decodeMessageFromPeer(initiatorCtx.encodeMessageToPeer(PING)); - - // Initiator fails to decodes responder's encoded message because keys do not match. - try { - initiatorCtx.decodeMessageFromPeer(responderCtx.encodeMessageToPeer(PONG)); - fail("Expected verification to fail."); - } catch (SignatureException e) { - // Exception expected. - } - } - - protected void testSessionUnique(int protocolVersion) throws Exception { - // Should be the same (we set them up with the same key and sequence number) - initiatorCtx = createConnectionContext(protocolVersion, true /** isInitiator */); - responderCtx = createConnectionContext(protocolVersion, false /** isInitiator */); - Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique()); - - // Change just the key (should not match) - SecretKey wrongKey = new SecretKeySpec("wrong".getBytes("UTF8"), "AES"); - responderCtx = createConnectionContext(protocolVersion, false, wrongKey, wrongKey, 0, 1); - assertFalse(Arrays.equals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique())); - - // Change just the sequence number (should still match) - responderCtx = createConnectionContext( - protocolVersion, false, INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 2, 2); - Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique()); - } - - @Test - public void testSessionUnique_V0() throws Exception { - testSessionUnique(D2DConnectionContextV0.PROTOCOL_VERSION); - } - - @Test - public void testSessionUnique_V1() throws Exception { - testSessionUnique(D2DConnectionContextV1.PROTOCOL_VERSION); - } - - @Test - public void testSessionUniqueValues_V0() throws Exception { - // The key and the session unique value should match ones in the equivalent test in - // @link {cs/Nearby/D2DCrypto/Tests/D2DConnectionContextTest.m} - byte[] key = - new byte[] { - (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, - (byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, - (byte) 0x0f, (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13, (byte) 0x14, (byte) 0x15, - (byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c, - (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20 - }; - byte[] sessionUnique = - new byte[] { - (byte) 0x70, (byte) 0x7a, (byte) 0x17, (byte) 0x27, (byte) 0xa3, (byte) 0x0e, (byte) 0x68, - (byte) 0x63, (byte) 0x38, (byte) 0xdf, (byte) 0x72, (byte) 0x62, (byte) 0xf4, (byte) 0xb0, - (byte) 0x41, (byte) 0xac, (byte) 0x75, (byte) 0x8b, (byte) 0xca, (byte) 0x3b, (byte) 0x11, - (byte) 0xd4, (byte) 0x09, (byte) 0x64, (byte) 0x96, (byte) 0x54, (byte) 0xb4, (byte) 0x9b, - (byte) 0x43, (byte) 0xe6, (byte) 0x9b, (byte) 0xce - }; - - SecretKey secretKey = new SecretKeySpec(key, "AES"); - D2DConnectionContext context = new D2DConnectionContextV0(secretKey, 1); - - Assert.assertArrayEquals(context.getSessionUnique(), sessionUnique); - } - - @Test - public void testSessionUniqueValues_V1_Initiator() throws Exception { - // The key and the session unique value should match ones in the equivalent test in - // @link {cs/Nearby/D2DCrypto/Tests/D2DConnectionContextTest.m} - byte[] sessionUnique = - new byte[] { - (byte) 0x91, (byte) 0xc7, (byte) 0xc9, (byte) 0x26, (byte) 0x2c, (byte) 0x17, (byte) 0x8a, - (byte) 0xa0, (byte) 0x36, (byte) 0x9f, (byte) 0xf2, (byte) 0x05, (byte) 0x20, (byte) 0x98, - (byte) 0x38, (byte) 0x53, (byte) 0xa5, (byte) 0x46, (byte) 0xab, (byte) 0x3a, (byte) 0x21, - (byte) 0x3b, (byte) 0x76, (byte) 0x58, (byte) 0x59, (byte) 0x4e, (byte) 0xe7, (byte) 0xe3, - (byte) 0xc1, (byte) 0x69, (byte) 0x87, (byte) 0xfa - }; - - D2DConnectionContext initiatorContext = new D2DConnectionContextV1( - INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 0, 1); - D2DConnectionContext responderContext = new D2DConnectionContextV1( - INITIATOR_DECODE_KEY, INITIATOR_ENCODE_KEY, 1, 0); - - // Both the initiator and responder must be the same. - Assert.assertArrayEquals(initiatorContext.getSessionUnique(), sessionUnique); - Assert.assertArrayEquals(responderContext.getSessionUnique(), sessionUnique); - } - - @Test - public void testSaveSessionV0() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1); - D2DConnectionContext responderCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, 1); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - byte[] responderSavedSessionState = responderCtx.saveSession(); - - // Try to rebuild the context - initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState); - responderCtx = D2DConnectionContext.fromSavedSession(responderSavedSessionState); - - // Sanity check - assertEquals(1, initiatorCtx.getSequenceNumberForDecoding()); - assertEquals(1, responderCtx.getSequenceNumberForDecoding()); - Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique()); - - // Make sure they can still talk to one another - assertEquals(PING, - responderCtx.decodeMessageFromPeerAsString(initiatorCtx.encodeMessageToPeer(PING))); - assertEquals(PONG, - initiatorCtx.decodeMessageFromPeerAsString(responderCtx.encodeMessageToPeer(PONG))); - } - - @Test - public void testSaveSessionV0_negativeSeqNumber() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - - // Try to rebuild the context - initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState); - - // Sanity check - assertEquals(-5, initiatorCtx.getSequenceNumberForDecoding()); - } - - @Test - public void testSaveSessionV0_shortKey() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - - // Try to rebuild the context - try { - D2DConnectionContext.fromSavedSession(Arrays.copyOf(initiatorSavedSessionState, - initiatorSavedSessionState.length - 1)); - fail("Expected failure as key is too short"); - } catch (IllegalArgumentException e) { - // expected - } - } - - @Test - public void testSaveSession_unknownProtocolVersion() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV0(INITIATOR_ENCODE_KEY, -5); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - - // Mess with the protocol version - initiatorSavedSessionState[0] = (byte) 0xff; - - // Try to rebuild the context - try { - D2DConnectionContext.fromSavedSession(initiatorSavedSessionState); - fail("Expected failure as 0xff is not a valid protocol version"); - } catch (IllegalArgumentException e) { - // expected - } - - // Mess with the protocol version in the other direction - initiatorSavedSessionState[0] = 2; - - // Try to rebuild the context - try { - D2DConnectionContext.fromSavedSession(initiatorSavedSessionState); - fail("Expected failure as 2 is not a valid protocol version"); - } catch (IllegalArgumentException e) { - // expected - } - } - - @Test - public void testSaveSessionV1() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY, - INITIATOR_DECODE_KEY, 0, 1); - D2DConnectionContext responderCtx = new D2DConnectionContextV1(INITIATOR_DECODE_KEY, - INITIATOR_ENCODE_KEY, 1, 0); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - byte[] responderSavedSessionState = responderCtx.saveSession(); - - // Try to rebuild the context - initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState); - responderCtx = D2DConnectionContext.fromSavedSession(responderSavedSessionState); - - // Sanity check - assertEquals(1, initiatorCtx.getSequenceNumberForDecoding()); - assertEquals(0, initiatorCtx.getSequenceNumberForEncoding()); - assertEquals(0, responderCtx.getSequenceNumberForDecoding()); - assertEquals(1, responderCtx.getSequenceNumberForEncoding()); - Assert.assertArrayEquals(initiatorCtx.getSessionUnique(), responderCtx.getSessionUnique()); - - // Make sure they can still talk to one another - assertEquals(PING, - responderCtx.decodeMessageFromPeerAsString(initiatorCtx.encodeMessageToPeer(PING))); - assertEquals(PONG, - initiatorCtx.decodeMessageFromPeerAsString(responderCtx.encodeMessageToPeer(PONG))); - } - - @Test - public void testSaveSessionV1_negativeSeqNumbers() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY, - INITIATOR_DECODE_KEY, -8, -10); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - - // Try to rebuild the context - initiatorCtx = D2DConnectionContext.fromSavedSession(initiatorSavedSessionState); - - // Sanity check - assertEquals(-10, initiatorCtx.getSequenceNumberForDecoding()); - assertEquals(-8, initiatorCtx.getSequenceNumberForEncoding()); - } - - @Test - public void testSaveSessionV1_tooShort() throws Exception { - D2DConnectionContext initiatorCtx = new D2DConnectionContextV1(INITIATOR_ENCODE_KEY, - INITIATOR_DECODE_KEY, -8, -10); - - // Save the state - byte[] initiatorSavedSessionState = initiatorCtx.saveSession(); - - // Try to rebuild the context - try { - D2DConnectionContext.fromSavedSession( - Arrays.copyOf(initiatorSavedSessionState, initiatorSavedSessionState.length - 1)); - fail("Expected error as saved session is too short"); - } catch (IllegalArgumentException e) { - // expected - } - - // Sanity check - assertEquals(-10, initiatorCtx.getSequenceNumberForDecoding()); - assertEquals(-8, initiatorCtx.getSequenceNumberForEncoding()); - } - - D2DConnectionContext createConnectionContext(int protocolVersion, boolean isInitiator) { - return createConnectionContext( - protocolVersion, isInitiator, INITIATOR_ENCODE_KEY, INITIATOR_DECODE_KEY, 0, 1); - } - - D2DConnectionContext createConnectionContext( - int protocolVersion, boolean isInitiator, - SecretKey initiatorEncodeKey, SecretKey initiatorDecodeKey, - int initiatorSequenceNumber, int responderSequenceNumber) { - if (protocolVersion == D2DConnectionContextV0.PROTOCOL_VERSION) { - return new D2DConnectionContextV0(initiatorEncodeKey, responderSequenceNumber); - } else if (protocolVersion == D2DConnectionContextV1.PROTOCOL_VERSION) { - return isInitiator - ? new D2DConnectionContextV1( - initiatorEncodeKey, initiatorDecodeKey, - initiatorSequenceNumber, responderSequenceNumber) - : new D2DConnectionContextV1( - initiatorDecodeKey, initiatorEncodeKey, - responderSequenceNumber, initiatorSequenceNumber); - } else { - throw new IllegalArgumentException("Unknown version: " + protocolVersion); - } - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java deleted file mode 100644 index 4de794a..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/D2DDiffieHellmanKeyExchangeHandshakeTest.java +++ /dev/null @@ -1,432 +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.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.InitiatorHello; -import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.ResponderHello; -import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload; -import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType; -import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil; -import java.nio.charset.Charset; -import java.security.KeyPair; -import java.security.PublicKey; -import java.security.SignatureException; -import javax.crypto.SecretKey; -import junit.framework.TestCase; -import org.junit.Assert; - -/** - * Android compatible tests for the {@link D2DDiffieHellmanKeyExchangeHandshake} class. - */ -public class D2DDiffieHellmanKeyExchangeHandshakeTest extends TestCase { - - private static final byte[] RESPONDER_HELLO_MESSAGE = - "first payload".getBytes(Charset.forName("UTF-8")); - - private static final String PING = "ping"; - - @Override - protected void setUp() throws Exception { - KeyEncodingTest.installSunEcSecurityProviderIfNecessary(); - super.setUp(); - } - - public void testHandshakeWithPayload() throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // initiator: - D2DHandshakeContext initiatorHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - assertFalse(initiatorHandshakeContext.canSendPayloadInHandshakeMessage()); - assertFalse(initiatorHandshakeContext.isHandshakeComplete()); - byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage(); - assertFalse(initiatorHandshakeContext.isHandshakeComplete()); - // (send initiatorHello to responder) - - // responder: - D2DHandshakeContext responderHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - byte[] payload = responderHandshakeContext.parseHandshakeMessage(initiatorHello); - assertEquals(0, payload.length); - assertTrue(responderHandshakeContext.canSendPayloadInHandshakeMessage()); - assertFalse(responderHandshakeContext.isHandshakeComplete()); - byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage( - RESPONDER_HELLO_MESSAGE); - assertTrue(responderHandshakeContext.isHandshakeComplete()); - D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext(); - // (send responderHelloAndPayload to initiator) - - // initiator - byte[] messageFromPayload = - initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload); - Assert.assertArrayEquals(RESPONDER_HELLO_MESSAGE, messageFromPayload); - assertTrue(initiatorHandshakeContext.isHandshakeComplete()); - D2DConnectionContextV1 initiatorCtx = - (D2DConnectionContextV1) initiatorHandshakeContext.toConnectionContext(); - - // Test that that initiator and responder contexts are initialized correctly. - checkInitializedConnectionContexts(initiatorCtx, responderCtx); - } - - public void testHandshakeWithoutPayload() throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // initiator: - D2DHandshakeContext initiatorHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage(); - // (send initiatorHello to responder) - - // responder: - D2DHandshakeContext responderHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - responderHandshakeContext.parseHandshakeMessage(initiatorHello); - byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage(); - assertTrue(responderHandshakeContext.isHandshakeComplete()); - D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext(); - // (send responderHelloAndPayload to initiator) - - // initiator - byte[] messageFromPayload = - initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload); - assertEquals(0, messageFromPayload.length); - assertTrue(initiatorHandshakeContext.isHandshakeComplete()); - D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext(); - - // Test that that initiator and responder contexts are initialized correctly. - checkInitializedConnectionContexts(initiatorCtx, responderCtx); - } - - public void testErrorWhenInitiatorOrResponderSendTwice() throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // initiator: - D2DHandshakeContext initiatorHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage(); - try { - initiatorHandshakeContext.getNextHandshakeMessage(); - fail("Expected error as initiator has no more initiator messages to send"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("Cannot get next message")); - } - // (send initiatorHello to responder) - - // responder: - D2DHandshakeContext responderHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - responderHandshakeContext.parseHandshakeMessage(initiatorHello); - responderHandshakeContext.getNextHandshakeMessage(); - try { - responderHandshakeContext.getNextHandshakeMessage(); - fail("Expected error as initiator has no more responder messages to send"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("Cannot get")); - } - } - - public void testInitiatorOrResponderFailOnEmptyMessage() throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - D2DHandshakeContext handshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - try { - handshakeContext.parseHandshakeMessage(null); - fail("Expected to crash on null message"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("short")); - } - try { - handshakeContext.parseHandshakeMessage(new byte[0]); - fail("Expected to crash on empty message"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("short")); - } - } - - public void testPrematureConversionToConnection() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // initiator: - D2DHandshakeContext initiatorHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - try { - initiatorHandshakeContext.toConnectionContext(); - fail("Expected to crash: initiator hasn't done anything to deserve full connection"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("not complete")); - } - - byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage(); - try { - initiatorHandshakeContext.toConnectionContext(); - fail("Expected to crash: initiator hasn't yet received responder's key"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("not complete")); - } - // (send initiatorHello to responder) - - // responder: - D2DHandshakeContext responderHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - responderHandshakeContext.parseHandshakeMessage(initiatorHello); - try { - initiatorHandshakeContext.toConnectionContext(); - fail("Expected to crash: responder hasn't yet send their key"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("not complete")); - } - } - - public void testCannotReuseHandshakeContext() throws Exception { - - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // initiator: - D2DHandshakeContext initiatorHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage(); - // (send initiatorHello to responder) - - // responder: - D2DHandshakeContext responderHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - responderHandshakeContext.parseHandshakeMessage(initiatorHello); - byte[] responderHelloAndPayload = responderHandshakeContext.getNextHandshakeMessage(); - D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext(); - // (send responderHelloAndPayload to initiator) - - // initiator - initiatorHandshakeContext.parseHandshakeMessage(responderHelloAndPayload); - D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext(); - - // Test that that initiator and responder contexts are initialized correctly. - checkInitializedConnectionContexts(initiatorCtx, responderCtx); - - // Try to get another full context - try { - initiatorHandshakeContext.toConnectionContext(); - fail("Expected crash: initiator context has already been used"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("used")); - } - try { - responderHandshakeContext.toConnectionContext(); - fail("Expected crash: responder context has already been used"); - } catch (HandshakeException expected) { - assertTrue(expected.getMessage().contains("used")); - } - } - - public void testErrorWhenInitiatorEchosResponderHello() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Initiator echoing back responder's first packet: - D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage(); - - D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - partialResponderCtx.parseHandshakeMessage(initiatorHello); - byte[] responderHelloAndPayload = - partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE); - D2DConnectionContext responderCtx = partialResponderCtx.toConnectionContext(); - - try { - // initiator sends responderHelloAndPayload to responder - responderCtx.decodeMessageFromPeerAsString(responderHelloAndPayload); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - assertTrue(expected.getMessage().contains("Signature failed verification")); - } - } - - public void testErrorWhenInitiatorResendsMessage() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Initiator repeating the same packet twice - D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage(); - - D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - partialResponderCtx.parseHandshakeMessage(initiatorHello); - byte[] responderHelloAndPayload = - partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE); - D2DConnectionContext responderCtx = partialResponderCtx.toConnectionContext(); - - partialInitiatorContext.parseHandshakeMessage(responderHelloAndPayload); - D2DConnectionContext initiatorCtx = partialInitiatorContext.toConnectionContext(); - - byte[] pingMessage = initiatorCtx.encodeMessageToPeer(PING); - assertEquals(PING, responderCtx.decodeMessageFromPeerAsString(pingMessage)); - - try { - // send pingMessage to responder again - responderCtx.decodeMessageFromPeerAsString(pingMessage); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - assertTrue(expected.getMessage().contains("sequence")); - } - } - - public void testErrorWhenResponderResendsFirstMessage() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - D2DDiffieHellmanKeyExchangeHandshake partialInitiatorContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = partialInitiatorContext.getNextHandshakeMessage(); - - D2DDiffieHellmanKeyExchangeHandshake partialResponderCtx = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - partialResponderCtx.parseHandshakeMessage(initiatorHello); - byte[] responderHelloAndPayload = - partialResponderCtx.getNextHandshakeMessage(RESPONDER_HELLO_MESSAGE); - - partialInitiatorContext.parseHandshakeMessage(responderHelloAndPayload); - D2DConnectionContext initiatorCtx = partialInitiatorContext.toConnectionContext(); - - try { - // Send the responderHelloAndPayload again. This time, the initiator will - // process it as a normal message. - initiatorCtx.decodeMessageFromPeerAsString(responderHelloAndPayload); - fail("expected exception, but didn't get it"); - } catch (SignatureException expected) { - assertTrue(expected.getMessage().contains("wrong message type")); - } - } - - public void testHandshakeWithInitiatorV1AndResponderV0() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Initialize initiator side. - D2DHandshakeContext initiatorHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forInitiator(); - byte[] initiatorHello = initiatorHandshakeContext.getNextHandshakeMessage(); - - // Set up keys used by the responder. - PublicKey initiatorPublicKey = PublicKeyProtoUtil.parsePublicKey( - InitiatorHello.parseFrom(initiatorHello).getPublicDhKey()); - KeyPair responderKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - SecretKey sharedKey = - EnrollmentCryptoOps.doKeyAgreement(responderKeyPair.getPrivate(), initiatorPublicKey); - - // Construct a responder hello message without the version field, whose payload is encrypted - // with the shared key. - byte[] responderHello = D2DCryptoOps.signcryptPayload( - new Payload( - PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD, - D2DConnectionContext.createDeviceToDeviceMessage(new byte[] {}, 1).toByteArray()), - sharedKey, - ResponderHello.newBuilder() - .setPublicDhKey( - PublicKeyProtoUtil.encodePublicKey(responderKeyPair.getPublic())) - .build().toByteArray()); - - // Handle V0 responder hello message. - initiatorHandshakeContext.parseHandshakeMessage(responderHello); - D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext(); - - assertEquals(D2DConnectionContextV0.PROTOCOL_VERSION, initiatorCtx.getProtocolVersion()); - assertEquals(1, initiatorCtx.getSequenceNumberForEncoding()); - assertEquals(1, initiatorCtx.getSequenceNumberForDecoding()); - } - - public void testHandshakeWithInitiatorV0AndResponderV1() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Construct an initiator hello message without the version field. - byte[] initiatorHello = InitiatorHello.newBuilder() - .setPublicDhKey(PublicKeyProtoUtil.encodePublicKey( - PublicKeyProtoUtil.generateEcP256KeyPair().getPublic())) - .build() - .toByteArray(); - - // Handle V0 initiator hello message. - D2DHandshakeContext responderHandshakeContext = - D2DDiffieHellmanKeyExchangeHandshake.forResponder(); - responderHandshakeContext.parseHandshakeMessage(initiatorHello); - responderHandshakeContext.getNextHandshakeMessage(); - D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext(); - - assertEquals(D2DConnectionContextV0.PROTOCOL_VERSION, responderCtx.getProtocolVersion()); - assertEquals(1, responderCtx.getSequenceNumberForEncoding()); - assertEquals(1, responderCtx.getSequenceNumberForDecoding()); - } - - private void checkInitializedConnectionContexts( - D2DConnectionContext initiatorCtx, D2DConnectionContext responderCtx) { - assertNotNull(initiatorCtx); - assertNotNull(responderCtx); - assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, initiatorCtx.getProtocolVersion()); - assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, responderCtx.getProtocolVersion()); - assertEquals(initiatorCtx.getEncodeKey(), responderCtx.getDecodeKey()); - assertEquals(initiatorCtx.getDecodeKey(), responderCtx.getEncodeKey()); - assertEquals(0, initiatorCtx.getSequenceNumberForEncoding()); - assertEquals(1, initiatorCtx.getSequenceNumberForDecoding()); - assertEquals(1, responderCtx.getSequenceNumberForEncoding()); - assertEquals(0, responderCtx.getSequenceNumberForDecoding()); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java deleted file mode 100644 index 6ae95d8..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ed25519Test.java +++ /dev/null @@ -1,195 +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 static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertThat; - -import com.google.security.cryptauth.lib.securegcm.Ed25519.Ed25519Exception; -import java.math.BigInteger; -import junit.framework.TestCase; - -/** - * Android compatible tests for the {@link Ed25519} class. - */ -public class Ed25519Test extends TestCase { - - // Points on the curve - private static final int HEX_RADIX = 16; - private static final BigInteger[] KM = new BigInteger[] { - new BigInteger("1981FB43F103290ECF9772022DB8B19BFAF389057ED91E8486EB368763435925", HEX_RADIX), - new BigInteger("A714C34F3B588AAC92FD2587884A20964FD351A1F147D5C4BBF5C2F37A77C36", HEX_RADIX)}; - private static final BigInteger[] KN = new BigInteger[] { - new BigInteger("201A184F47D9A7973891D148E3D1C864D8084547131C2C1CEFB7EEBD26C63567", HEX_RADIX), - new BigInteger("6DA2D3B18EC4F9AA3B08E39C997CD8BF6E9948FFD4FEFFECAF8DD0B3D648B7E8", HEX_RADIX)}; - - // Curve prime P - private static final BigInteger P = - new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED", HEX_RADIX); - - // Test vectors obtain by multiplying KM by k by manually using the official implementation - // see: http://ed25519.cr.yp.to/python/ed25519.py - // k = 2 - private static final BigInteger[] KM_2 = new BigInteger[] { - new BigInteger("718079972e63c2d62caf0ee93ec6f00337ceaff4e283181c04c4082b1d5e1ecf", HEX_RADIX), - new BigInteger("143d18d393a8058c8614335bf36bf59364cc7c451db74726b322ce9d0b826d51", HEX_RADIX) - }; - // k = 3 - private static final BigInteger[] KM_3 = new BigInteger[] { - new BigInteger("39DA3C92EFC0577586B4D58F4A5C0BF65A6CC8F6BF358F38D70B2E6C28A31E8E", HEX_RADIX), - new BigInteger("6D194F054B3FC2BE217F6A360BBEC747D2937FCEBD74B67FC3B20ED638ADD670", HEX_RADIX) - }; - // k = 317698 - private static final BigInteger[] KM_317698 = new BigInteger[] { - new BigInteger("7945D0ADEB568B16495476E81ADF281F4515439AE835914FBF6CEEAFEB9CD7E8", HEX_RADIX), - new BigInteger("3631503DCDEBC0BF9BB1FFC3984A8CB52A34FFC2E77E9C19FD896DC6EE64A530", HEX_RADIX) - }; - // k = P - private static final BigInteger[] KM_HUGE = new BigInteger[] { - new BigInteger("530162B05F440E00E219DFD3188524821C860C41FD87B9AC6AF2A283FDD585A1", HEX_RADIX), - new BigInteger("48385A7D2BB858F3DB7F72E7CDFE218B9CA84DDA8BD64C3775AA43551D974F60", HEX_RADIX) - }; - // k = P + 10000 - private static final BigInteger[] KM_XRAHUGE = new BigInteger[] { - new BigInteger("16377E9F5EE2C0F4C70E17AC298EF670700A7CB186EEB0DA10CDD59635000AF8", HEX_RADIX), - new BigInteger("5BD7921EEE662ACBAC3A96D8B6039D2356F154859FAF41FD2F0D99DF06CD2EAE", HEX_RADIX) - }; - - // Helpful constants - private static final BigInteger ONE = BigInteger.ONE; - private static final BigInteger ZERO = BigInteger.ZERO; - - // Identity element of the group (the zero) in affine and extended representations - private static final BigInteger[] ID = new BigInteger[] {ZERO, ONE}; - private static final BigInteger[] ID_EX = new BigInteger[] {ZERO, ONE, ONE, ZERO}; - - public void testValidPoints() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // We've got a couple of valid points - Ed25519.validateAffinePoint(KM); - Ed25519.validateAffinePoint(KN); - - // And a bunch of invalid ones - try { - Ed25519.validateAffinePoint(new BigInteger[] {ZERO, ONE}); - fail("Validate point not catching zero x coordinates"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("positive")); - } - - try { - Ed25519.validateAffinePoint(new BigInteger[] {ONE, ZERO}); - fail("Validate point not catching zero y coordinates"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("positive")); - } - - try { - Ed25519.validateAffinePoint(new BigInteger[] {new BigInteger("-1"), ONE}); - fail("Validate point not catching negative x coordinates"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("positive")); - } - - try { - Ed25519.validateAffinePoint(new BigInteger[] {ONE, new BigInteger("-1")}); - fail("Validate point not catching negative y coordinates"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("positive")); - } - - try { - Ed25519.validateAffinePoint(new BigInteger[] {ONE, ONE}); - fail("Validate point not catching points that are not on curve"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("expected curve")); - } - } - - public void testAffineExtendedConversion() throws Exception { - BigInteger[] km1 = Ed25519.toAffine(Ed25519.toExtended(KM)); - BigInteger[] kn1 = Ed25519.toAffine(Ed25519.toExtended(KN)); - - assertArrayEquals(KM, km1); - assertArrayEquals(KN, kn1); - - assertArrayEquals(ID, Ed25519.toAffine(ID_EX)); - assertArrayEquals(ID_EX, Ed25519.toExtended(ID)); - } - - public void testRepresentationCheck() throws Exception { - Ed25519.checkPointIsInAffineRepresentation(KM); - Ed25519.checkPointIsInExtendedRepresentation(ID_EX); - - try { - Ed25519.checkPointIsInExtendedRepresentation(KM); - fail("Point is not really in extended representation, expected failure"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("not in extended")); - } - - try { - Ed25519.checkPointIsInAffineRepresentation(Ed25519.toExtended(KM)); - fail("Point is not really in affine representation, expected failure"); - } catch (Ed25519Exception e) { - assertThat(e.getMessage(), containsString("not in affine")); - } - } - - public void testAddSubtractExtendedPoints() throws Exception { - // Adding/subtracting identity to/from itself should yield the identity point - assertArrayEquals(ID, Ed25519.addAffinePoints(ID, ID)); - assertArrayEquals(ID, Ed25519.subtractAffinePoints(ID, ID)); - - // In fact adding/subtracting the identity point to/from any point should yield that point - assertArrayEquals(KM, Ed25519.addAffinePoints(KM, ID)); - assertArrayEquals(KM, Ed25519.subtractAffinePoints(KM, ID)); - - // Subtracting a point from itself should yield the identity element - assertArrayEquals(ID, Ed25519.subtractAffinePoints(KM, KM)); - assertArrayEquals(ID, Ed25519.subtractAffinePoints(KN, KN)); - - // Adding and subtracting should yield the same point - assertArrayEquals(KM, Ed25519.subtractAffinePoints(Ed25519.addAffinePoints(KM, KN), KN)); - assertArrayEquals(KN, Ed25519.subtractAffinePoints(Ed25519.addAffinePoints(KN, KM), KM)); - } - - public void testScalarMultiplyExtendedPoints() throws Exception { - // A point times one is the point itself - assertArrayEquals(KM, Ed25519.scalarMultiplyAffinePoint(KM, ONE)); - assertArrayEquals(KN, Ed25519.scalarMultiplyAffinePoint(KN, ONE)); - - // A point times zero is the identity point - assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(KM, ZERO)); - assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(KN, ZERO)); - - // The identity times a scalar is the identity - assertArrayEquals(ID, Ed25519.scalarMultiplyAffinePoint(ID, BigInteger.valueOf(317698))); - - // Use test vectors - assertArrayEquals(KM_2, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(2))); - assertArrayEquals(KM_3, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(3))); - assertArrayEquals(KM_317698, Ed25519.scalarMultiplyAffinePoint(KM, BigInteger.valueOf(317698))); - assertArrayEquals(KM_HUGE, Ed25519.scalarMultiplyAffinePoint(KM, P)); - assertArrayEquals(KM_XRAHUGE, - Ed25519.scalarMultiplyAffinePoint(KM, P.add(BigInteger.valueOf(10000)))); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java deleted file mode 100644 index 4437045..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/EnrollmentCryptoOpsTest.java +++ /dev/null @@ -1,134 +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.protobuf.ByteString; -import com.google.security.cryptauth.lib.securegcm.SecureGcmProto.GcmDeviceInfo; -import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey; -import java.security.KeyPair; -import java.security.PublicKey; -import java.util.Arrays; -import javax.crypto.SecretKey; -import junit.framework.TestCase; - -/** - * Android compatible tests for the {@link EnrollmentCryptoOps} class. - */ -public class EnrollmentCryptoOpsTest extends TestCase { - - private static final long DEVICE_ID = 1234567890L; - private static final byte[] GCM_REGISTRATION_ID = { -0x80, 0, -0x80, 0, -0x80, 0 }; - private static final String DEVICE_MODEL = "TEST DEVICE"; - private static final String LOCALE = "en"; - private static final byte[] SESSION_ID = { 5, 5, 4, 4, 3, 3, 2, 2, 1, 1 }; - private static final String OAUTH_TOKEN = "1/23456etc"; - - @Override - protected void setUp() throws Exception { - KeyEncodingTest.installSunEcSecurityProviderIfNecessary(); - assertEquals( - PublicKeyProtoUtil.isLegacyCryptoRequired(), KeyEncoding.isLegacyCryptoRequired()); - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - KeyEncoding.setSimulateLegacyCrypto(false); - super.tearDown(); - } - - - public void testSimulatedEnrollment() throws Exception { - boolean isLegacy = KeyEncoding.isLegacyCryptoRequired(); - // Step 1: Server generates an ephemeral DH key pair, saves the private key, and sends - // the public key to the client as server_ephemeral_key. - KeyPair serverEphemeralKeyPair = - EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy); - byte[] savedServerPrivateKey = - KeyEncoding.encodeKeyAgreementPrivateKey(serverEphemeralKeyPair.getPrivate()); - byte[] serverEphemeralKey = KeyEncoding.encodeKeyAgreementPublicKey( - serverEphemeralKeyPair.getPublic()); - - // Step 2a: Client generates an ephemeral DH key pair, and completes the DH key exchange - // to derive the master key. - KeyPair clientEphemeralKeyPair = - EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy); - byte[] clientEphemeralKey = KeyEncoding.encodeKeyAgreementPublicKey( - clientEphemeralKeyPair.getPublic()); - SecretKey clientMasterKey = EnrollmentCryptoOps.doKeyAgreement( - clientEphemeralKeyPair.getPrivate(), - KeyEncoding.parseKeyAgreementPublicKey(serverEphemeralKey)); - - // Step 2b: Client generates its user key pair, and fills in a GcmDeviceInfo message containing - // the enrollment request (which includes the user public key). - KeyPair userKeyPair = isLegacy ? PublicKeyProtoUtil.generateRSA2048KeyPair() - : PublicKeyProtoUtil.generateEcP256KeyPair(); - GcmDeviceInfo clientInfo = createGcmDeviceInfo(userKeyPair.getPublic(), clientMasterKey); - - // Step 2c: Client signcrypts the enrollment request to the server, using a combination of the - // master key and its user signing key. - byte[] enrollmentMessage = EnrollmentCryptoOps.encryptEnrollmentMessage( - clientInfo, clientMasterKey, userKeyPair.getPrivate()); - - - // Step 3a: Server receives the client's DH public key and completes the key exchange using - // the saved DH private key. - SecretKey serverMasterKey = EnrollmentCryptoOps.doKeyAgreement( - KeyEncoding.parseKeyAgreementPrivateKey(savedServerPrivateKey, isLegacy), - KeyEncoding.parseKeyAgreementPublicKey(clientEphemeralKey)); - - // Step 3b: Server uses the exchanged master key to de-signcrypt the enrollment request - // (which also provides the user public key in the clear). - GcmDeviceInfo serverInfo = EnrollmentCryptoOps.decryptEnrollmentMessage( - enrollmentMessage, serverMasterKey, isLegacy); - - // Verify that the server sees the client's original enrollment request - assertTrue(Arrays.equals(clientInfo.toByteArray(), serverInfo.toByteArray())); - - // Confirm that the server can recover a valid user PublicKey from the enrollment - PublicKey serverUserPublicKey = KeyEncoding.parseUserPublicKey( - serverInfo.getUserPublicKey().toByteArray()); - assertTrue(serverUserPublicKey.equals(userKeyPair.getPublic())); - } - - public void testSimulatedEnrollmentWithForcedLegacy() throws Exception { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - // We already test with legacy in this case - return; - } - KeyEncoding.setSimulateLegacyCrypto(true); - testSimulatedEnrollment(); - } - - private GcmDeviceInfo createGcmDeviceInfo(PublicKey userPublicKey, SecretKey masterKey) { - // One possible method of generating a key handle: - GenericPublicKey encodedUserPublicKey = PublicKeyProtoUtil.encodePublicKey(userPublicKey); - byte[] keyHandle = EnrollmentCryptoOps.sha256(encodedUserPublicKey.toByteArray()); - - return GcmDeviceInfo.newBuilder() - .setAndroidDeviceId(DEVICE_ID) - .setGcmRegistrationId(ByteString.copyFrom(GCM_REGISTRATION_ID)) - .setDeviceMasterKeyHash( - ByteString.copyFrom(EnrollmentCryptoOps.getMasterKeyHash(masterKey))) - .setUserPublicKey(ByteString.copyFrom(KeyEncoding.encodeUserPublicKey(userPublicKey))) - .setDeviceModel(DEVICE_MODEL) - .setLocale(LOCALE) - .setKeyHandle(ByteString.copyFrom(keyHandle)) - .setEnrollmentSessionId(ByteString.copyFrom(SESSION_ID)) - .setOauthToken(OAUTH_TOKEN) - .build(); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java deleted file mode 100644 index 7012eae..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/KeyEncodingTest.java +++ /dev/null @@ -1,189 +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.security.cryptauth.lib.securemessage.PublicKeyProtoUtil; -import java.security.Key; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.PublicKey; -import java.security.Security; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import javax.crypto.interfaces.DHPrivateKey; -import javax.crypto.interfaces.DHPublicKey; -import junit.framework.TestCase; - -/** - * Android compatible tests for the {@link KeyEncoding} class. - */ -public class KeyEncodingTest extends TestCase { - private static final byte[] RAW_KEY_BYTES = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2}; - - private Boolean isLegacy; - private KeyPair userKeyPair; - - @Override - protected void setUp() throws Exception { - installSunEcSecurityProviderIfNecessary(); - isLegacy = PublicKeyProtoUtil.isLegacyCryptoRequired(); - setUserKeyPair(); - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - KeyEncoding.setSimulateLegacyCrypto(false); - isLegacy = PublicKeyProtoUtil.isLegacyCryptoRequired(); - super.tearDown(); - } - - private void setUserKeyPair() { - userKeyPair = isLegacy ? PublicKeyProtoUtil.generateRSA2048KeyPair() - : PublicKeyProtoUtil.generateEcP256KeyPair(); - } - - public void testSimulateLegacyCrypto() { - if (isLegacy) { - return; // Nothing to test if we are already stuck in a legacy platform - } - assertFalse(KeyEncoding.isLegacyCryptoRequired()); - KeyEncoding.setSimulateLegacyCrypto(true); - assertTrue(KeyEncoding.isLegacyCryptoRequired()); - } - - public void testMasterKeyEncoding() { - // Require that master keys are encoded/decoded as raw byte arrays - assertTrue(Arrays.equals( - RAW_KEY_BYTES, - KeyEncoding.encodeMasterKey(KeyEncoding.parseMasterKey(RAW_KEY_BYTES)))); - } - - public void testUserPublicKeyEncoding() throws InvalidKeySpecException { - PublicKey pk = userKeyPair.getPublic(); - byte[] encodedPk = KeyEncoding.encodeUserPublicKey(pk); - PublicKey decodedPk = KeyEncoding.parseUserPublicKey(encodedPk); - assertKeysEqual(pk, decodedPk); - } - - public void testUserPrivateKeyEncoding() throws InvalidKeySpecException { - PrivateKey sk = userKeyPair.getPrivate(); - byte[] encodedSk = KeyEncoding.encodeUserPrivateKey(sk); - PrivateKey decodedSk = KeyEncoding.parseUserPrivateKey(encodedSk, isLegacy); - assertKeysEqual(sk, decodedSk); - } - - public void testKeyAgreementPublicKeyEncoding() throws InvalidKeySpecException { - KeyPair clientKeyPair = EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy); - PublicKey pk = clientKeyPair.getPublic(); - byte[] encodedPk = KeyEncoding.encodeKeyAgreementPublicKey(pk); - PublicKey decodedPk = KeyEncoding.parseKeyAgreementPublicKey(encodedPk); - assertKeysEqual(pk, decodedPk); - } - - public void testKeyAgreementPrivateKeyEncoding() throws InvalidKeySpecException { - KeyPair clientKeyPair = EnrollmentCryptoOps.generateEnrollmentKeyAgreementKeyPair(isLegacy); - PrivateKey sk = clientKeyPair.getPrivate(); - byte[] encodedSk = KeyEncoding.encodeKeyAgreementPrivateKey(sk); - PrivateKey decodedSk = KeyEncoding.parseKeyAgreementPrivateKey(encodedSk, isLegacy); - assertKeysEqual(sk, decodedSk); - } - - public void testEncodingsWithForcedLegacy() throws InvalidKeySpecException { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - // We already test with legacy in this case - return; - } - KeyEncoding.setSimulateLegacyCrypto(true); - isLegacy = true; - setUserKeyPair(); - testUserPublicKeyEncoding(); - testUserPrivateKeyEncoding(); - testKeyAgreementPublicKeyEncoding(); - testKeyAgreementPrivateKeyEncoding(); - } - - public void testSigningPublicKeyEncoding() throws InvalidKeySpecException { - KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - PublicKey pk = keyPair.getPublic(); - byte[] encodedPk = KeyEncoding.encodeSigningPublicKey(pk); - PublicKey decodedPk = KeyEncoding.parseSigningPublicKey(encodedPk); - assertKeysEqual(pk, decodedPk); - } - - public void testSigningPrivateKeyEncoding() throws InvalidKeySpecException { - KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - PrivateKey sk = keyPair.getPrivate(); - byte[] encodedSk = KeyEncoding.encodeSigningPrivateKey(sk); - PrivateKey decodedSk = KeyEncoding.parseSigningPrivateKey(encodedSk); - assertKeysEqual(sk, decodedSk); - } - - public void testDeviceSyncPublicKeyEncoding() throws InvalidKeySpecException { - KeyPair keyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - PublicKey pk = keyPair.getPublic(); - byte[] encodedPk = KeyEncoding.encodeDeviceSyncGroupPublicKey(pk); - PublicKey decodedPk = KeyEncoding.parseDeviceSyncGroupPublicKey(encodedPk); - assertKeysEqual(pk, decodedPk); - } - - void assertKeysEqual(Key a, Key b) { - if ((a instanceof ECPublicKey) - || (a instanceof ECPrivateKey) - || (a instanceof RSAPublicKey) - || (a instanceof RSAPrivateKey)) { - assertNotNull(a.getEncoded()); - assertTrue(Arrays.equals(a.getEncoded(), b.getEncoded())); - } - if (a instanceof DHPublicKey) { - DHPublicKey ya = (DHPublicKey) a; - DHPublicKey yb = (DHPublicKey) b; - assertEquals(ya.getY(), yb.getY()); - assertEquals(ya.getParams().getG(), yb.getParams().getG()); - assertEquals(ya.getParams().getP(), yb.getParams().getP()); - } - if (a instanceof DHPrivateKey) { - DHPrivateKey xa = (DHPrivateKey) a; - DHPrivateKey xb = (DHPrivateKey) b; - assertEquals(xa.getX(), xb.getX()); - assertEquals(xa.getParams().getG(), xb.getParams().getG()); - assertEquals(xa.getParams().getP(), xb.getParams().getP()); - } - } - - /** - * Registers the SunEC security provider if no EC security providers are currently registered. - */ - // TODO(shabsi): Remove this method when b/7891565 is fixed - static void installSunEcSecurityProviderIfNecessary() { - if (Security.getProviders("KeyPairGenerator.EC") == null) { - try { - Class<?> providerClass = Class.forName("sun.security.ec.SunEC"); - Security.addProvider((Provider) providerClass.newInstance()); - } catch (Exception e) { - // SunEC is not available, nothing we can do - } - } - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java deleted file mode 100644 index 9e45c0a..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/TransportCryptoOpsTest.java +++ /dev/null @@ -1,110 +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.security.cryptauth.lib.securegcm.SecureGcmProto.Tickle; -import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload; -import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType; -import com.google.security.cryptauth.lib.securemessage.PublicKeyProtoUtil; -import java.security.KeyPair; -import java.security.PublicKey; -import java.util.Arrays; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import junit.framework.TestCase; - -/** - * Android compatible tests for the {@link TransportCryptoOps} class. - */ -public class TransportCryptoOpsTest extends TestCase { - private static final byte[] KEY_BYTES = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2 - }; - private static final byte[] KEY_HANDLE = { 9 }; - - private SecretKey masterKey; - - @Override - protected void setUp() throws Exception { - KeyEncodingTest.installSunEcSecurityProviderIfNecessary(); - masterKey = new SecretKeySpec(KEY_BYTES, "AES"); - super.setUp(); - } - - public void testServerMessage() throws Exception { - long tickleExpiry = 12345L; - Tickle tickle = Tickle.newBuilder() - .setExpiryTime(tickleExpiry) - .build(); - - // Simulate sending a message - byte[] signcryptedMessage = TransportCryptoOps.signcryptServerMessage( - new Payload(PayloadType.TICKLE, tickle.toByteArray()), - masterKey, - KEY_HANDLE); - - // Simulate the process of receiving the message - assertTrue(Arrays.equals(KEY_HANDLE, TransportCryptoOps.getKeyHandleFor(signcryptedMessage))); - Payload received = TransportCryptoOps.verifydecryptServerMessage(signcryptedMessage, masterKey); - assertEquals(PayloadType.TICKLE, received.getPayloadType()); - Tickle receivedTickle = Tickle.parseFrom(received.getMessage()); - assertEquals(tickleExpiry, receivedTickle.getExpiryTime()); - } - - public void testClientMessage() throws Exception { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - return; // This test isn't for legacy crypto - } - KeyPair userKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - doTestClientMessageWith(userKeyPair); - } - - public void testClientMessageWithLegacyCrypto() throws Exception { - KeyPair userKeyPair = PublicKeyProtoUtil.generateRSA2048KeyPair(); - doTestClientMessageWith(userKeyPair); - } - - private void doTestClientMessageWith(KeyPair userKeyPair) throws Exception { - PublicKey userPublicKey = userKeyPair.getPublic(); - // Will use a Tickle for the test message, even though that would normally - // only be sent from the server to the client - long tickleExpiry = 12345L; - Tickle tickle = Tickle.newBuilder() - .setExpiryTime(tickleExpiry) - .build(); - - // Simulate sending a message - byte[] signcryptedMessage = TransportCryptoOps.signcryptClientMessage( - new Payload(PayloadType.TICKLE, tickle.toByteArray()), - userKeyPair, - masterKey); - - // Simulate the process of receiving the message - byte[] encodedUserPublicKey = TransportCryptoOps.getEncodedUserPublicKeyFor(signcryptedMessage); - assertTrue(Arrays.equals(KeyEncoding.encodeUserPublicKey(userPublicKey), encodedUserPublicKey)); - userPublicKey = KeyEncoding.parseUserPublicKey(encodedUserPublicKey); - // At this point the server would have looked up the masterKey for this userPublicKey - - Payload received = TransportCryptoOps.verifydecryptClientMessage( - signcryptedMessage, userPublicKey, masterKey); - - assertEquals(PayloadType.TICKLE, received.getPayloadType()); - Tickle receivedTickle = Tickle.parseFrom(received.getMessage()); - assertEquals(tickleExpiry, receivedTickle.getExpiryTime()); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java deleted file mode 100644 index db319e0..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2CppCompatibilityTest.java +++ /dev/null @@ -1,124 +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.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher; -import com.google.security.cryptauth.lib.securegcm.Ukey2ShellCppWrapper.Mode; -import java.util.Arrays; -import junit.framework.TestCase; - -/** - * Tests the compatibility between the Java and C++ implementations of the UKEY2 protocol. This - * integration test executes and talks to a compiled binary exposing the C++ implementation (wrapped - * by {@link Ukey2ShellCppWrapper}). - * - * <p>The C++ implementation is located in //security/cryptauth/lib/securegcm. - */ -public class Ukey2CppCompatibilityTest extends TestCase { - private static final int VERIFICATION_STRING_LENGTH = 32; - - private static final byte[] sPayload1 = "payload to encrypt1".getBytes(); - private static final byte[] sPayload2 = "payload to encrypt2".getBytes(); - - /** Tests full handshake with C++ client and Java server. */ - public void testCppClientJavaServer() throws Exception { - Ukey2ShellCppWrapper cppUkey2Shell = - new Ukey2ShellCppWrapper(Mode.INITIATOR, VERIFICATION_STRING_LENGTH); - cppUkey2Shell.startShell(); - Ukey2Handshake javaUkey2Handshake = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - - // ClientInit: - byte[] clientInit = cppUkey2Shell.readHandshakeMessage(); - javaUkey2Handshake.parseHandshakeMessage(clientInit); - - // ServerInit: - byte[] serverInit = javaUkey2Handshake.getNextHandshakeMessage(); - cppUkey2Shell.writeHandshakeMessage(serverInit); - - // ClientFinished: - byte[] clientFinished = cppUkey2Shell.readHandshakeMessage(); - javaUkey2Handshake.parseHandshakeMessage(clientFinished); - - // Verification String: - cppUkey2Shell.confirmAuthString( - javaUkey2Handshake.getVerificationString(VERIFICATION_STRING_LENGTH)); - javaUkey2Handshake.verifyHandshake(); - - // Secure channel: - D2DConnectionContext javaSecureContext = javaUkey2Handshake.toConnectionContext(); - - // ukey2_shell encodes data: - byte[] encodedData = cppUkey2Shell.sendEncryptCommand(sPayload1); - byte[] decodedData = javaSecureContext.decodeMessageFromPeer(encodedData); - assertTrue(Arrays.equals(sPayload1, decodedData)); - - // ukey2_shell decodes data: - encodedData = javaSecureContext.encodeMessageToPeer(sPayload2); - decodedData = cppUkey2Shell.sendDecryptCommand(encodedData); - assertTrue(Arrays.equals(sPayload2, decodedData)); - - // ukey2_shell session unique: - byte[] localSessionUnique = javaSecureContext.getSessionUnique(); - byte[] remoteSessionUnique = cppUkey2Shell.sendSessionUniqueCommand(); - assertTrue(Arrays.equals(localSessionUnique, remoteSessionUnique)); - - cppUkey2Shell.stopShell(); - } - - /** Tests full handshake with C++ server and Java client. */ - public void testCppServerJavaClient() throws Exception { - Ukey2ShellCppWrapper cppUkey2Shell = - new Ukey2ShellCppWrapper(Mode.RESPONDER, VERIFICATION_STRING_LENGTH); - cppUkey2Shell.startShell(); - Ukey2Handshake javaUkey2Handshake = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - - // ClientInit: - byte[] clientInit = javaUkey2Handshake.getNextHandshakeMessage(); - cppUkey2Shell.writeHandshakeMessage(clientInit); - - // ServerInit: - byte[] serverInit = cppUkey2Shell.readHandshakeMessage(); - javaUkey2Handshake.parseHandshakeMessage(serverInit); - - // ClientFinished: - byte[] clientFinished = javaUkey2Handshake.getNextHandshakeMessage(); - cppUkey2Shell.writeHandshakeMessage(clientFinished); - - // Verification String: - cppUkey2Shell.confirmAuthString( - javaUkey2Handshake.getVerificationString(VERIFICATION_STRING_LENGTH)); - javaUkey2Handshake.verifyHandshake(); - - // Secure channel: - D2DConnectionContext javaSecureContext = javaUkey2Handshake.toConnectionContext(); - - // ukey2_shell encodes data: - byte[] encodedData = cppUkey2Shell.sendEncryptCommand(sPayload1); - byte[] decodedData = javaSecureContext.decodeMessageFromPeer(encodedData); - assertTrue(Arrays.equals(sPayload1, decodedData)); - - // ukey2_shell decodes data: - encodedData = javaSecureContext.encodeMessageToPeer(sPayload2); - decodedData = cppUkey2Shell.sendDecryptCommand(encodedData); - assertTrue(Arrays.equals(sPayload2, decodedData)); - - // ukey2_shell session unique: - byte[] localSessionUnique = javaSecureContext.getSessionUnique(); - byte[] remoteSessionUnique = cppUkey2Shell.sendSessionUniqueCommand(); - assertTrue(Arrays.equals(localSessionUnique, remoteSessionUnique)); - - cppUkey2Shell.stopShell(); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java deleted file mode 100644 index 49e6f30..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securegcm/Ukey2HandshakeTest.java +++ /dev/null @@ -1,818 +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.protobuf.ByteString; -import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.AlertException; -import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.HandshakeCipher; -import com.google.security.cryptauth.lib.securegcm.Ukey2Handshake.State; -import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientFinished; -import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit; -import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ClientInit.CipherCommitment; -import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2Message; -import com.google.security.cryptauth.lib.securegcm.UkeyProto.Ukey2ServerInit; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import junit.framework.TestCase; -import org.junit.Assert; - -/** - * Android compatible tests for the {@link Ukey2Handshake} class. - */ -public class Ukey2HandshakeTest extends TestCase { - - private static final int MAX_AUTH_STRING_LENGTH = 32; - - @Override - protected void setUp() throws Exception { - KeyEncodingTest.installSunEcSecurityProviderIfNecessary(); - super.setUp(); - } - - /** - * Tests correct use - */ - public void testHandshake() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage; - - assertEquals(State.IN_PROGRESS, client.getHandshakeState()); - assertEquals(State.IN_PROGRESS, server.getHandshakeState()); - - // Message 1 (Client Init) - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - assertEquals(State.IN_PROGRESS, client.getHandshakeState()); - assertEquals(State.IN_PROGRESS, server.getHandshakeState()); - - // Message 2 (Server Init) - handshakeMessage = server.getNextHandshakeMessage(); - client.parseHandshakeMessage(handshakeMessage); - assertEquals(State.IN_PROGRESS, client.getHandshakeState()); - assertEquals(State.IN_PROGRESS, server.getHandshakeState()); - - // Message 3 (Client Finish) - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - assertEquals(State.VERIFICATION_NEEDED, client.getHandshakeState()); - assertEquals(State.VERIFICATION_NEEDED, server.getHandshakeState()); - - // Get the auth string - byte[] clientAuthString = client.getVerificationString(MAX_AUTH_STRING_LENGTH); - byte[] serverAuthString = server.getVerificationString(MAX_AUTH_STRING_LENGTH); - Assert.assertArrayEquals(clientAuthString, serverAuthString); - assertEquals(State.VERIFICATION_IN_PROGRESS, client.getHandshakeState()); - assertEquals(State.VERIFICATION_IN_PROGRESS, server.getHandshakeState()); - - // Verify the auth string - client.verifyHandshake(); - server.verifyHandshake(); - assertEquals(State.FINISHED, client.getHandshakeState()); - assertEquals(State.FINISHED, server.getHandshakeState()); - - // Make a context - D2DConnectionContext clientContext = client.toConnectionContext(); - D2DConnectionContext serverContext = server.toConnectionContext(); - assertContextsCompatible(clientContext, serverContext); - assertEquals(State.ALREADY_USED, client.getHandshakeState()); - assertEquals(State.ALREADY_USED, server.getHandshakeState()); - } - - /** - * Verify enums for ciphers match the proto values - */ - public void testCipherEnumValuesCorrect() { - assertEquals( - "You added a cipher, but forgot to change the test", 1, HandshakeCipher.values().length); - - assertEquals(UkeyProto.Ukey2HandshakeCipher.P256_SHA512, - HandshakeCipher.P256_SHA512.getValue()); - } - - /** - * Tests incorrect use by callers (client and servers accidentally sending the wrong message at - * the wrong time) - */ - public void testHandshakeClientAndServerSendRepeatedOutOfOrderMessages() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Client sends ClientInit (again) instead of ClientFinished - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - server.getNextHandshakeMessage(); // do this to avoid illegal state - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Expected Alert for client sending ClientInit twice"); - } catch (HandshakeException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - - // Server sends ClientInit back to client instead of ServerInit - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Expected Alert for server sending ClientInit back to client"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - - // Clients sends ServerInit back to client instead of ClientFinished - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Expected Alert for client sending ServerInit back to server"); - } catch (HandshakeException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - } - - /** - * Tests that verification codes are different for different handshake runs. Also tests a full - * on-path attack. - */ - public void testVerificationCodeUniqueToSession() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Client 1 and Server 1 - Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client1.getNextHandshakeMessage(); - server1.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server1.getNextHandshakeMessage(); - client1.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client1.getNextHandshakeMessage(); - server1.parseHandshakeMessage(handshakeMessage); - byte[] client1AuthString = client1.getVerificationString(MAX_AUTH_STRING_LENGTH); - byte[] server1AuthString = server1.getVerificationString(MAX_AUTH_STRING_LENGTH); - Assert.assertArrayEquals(client1AuthString, server1AuthString); - - // Client 2 and Server 2 - Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client2.getNextHandshakeMessage(); - server2.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server2.getNextHandshakeMessage(); - client2.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client2.getNextHandshakeMessage(); - server2.parseHandshakeMessage(handshakeMessage); - byte[] client2AuthString = client2.getVerificationString(MAX_AUTH_STRING_LENGTH); - byte[] server2AuthString = server2.getVerificationString(MAX_AUTH_STRING_LENGTH); - Assert.assertArrayEquals(client2AuthString, server2AuthString); - - // Make sure the verification strings differ - assertFalse(Arrays.equals(client1AuthString, client2AuthString)); - } - - /** - * Test an attack where the adversary swaps out the public key in the final message (i.e., - * commitment doesn't match public key) - */ - public void testPublicKeyDoesntMatchCommitment() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Run handshake as usual, but stop before sending client finished - Ukey2Handshake client1 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server1 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client1.getNextHandshakeMessage(); - server1.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server1.getNextHandshakeMessage(); - - // Run another handshake and get the final client finished - Ukey2Handshake client2 = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server2 = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client2.getNextHandshakeMessage(); - server2.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server2.getNextHandshakeMessage(); - client2.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client2.getNextHandshakeMessage(); - - // Now use the client finished from second handshake in first handshake (simulates where an - // attacker switches out the last message). - try { - server1.parseHandshakeMessage(handshakeMessage); - fail("Expected server to catch mismatched ClientFinished"); - } catch (HandshakeException e) { - // success - } - assertEquals(State.ERROR, server1.getHandshakeState()); - - // Make sure caller can't actually do anything with the server now that an error has occurred - try { - server1.getVerificationString(MAX_AUTH_STRING_LENGTH); - fail("Server allows operations post error"); - } catch (IllegalStateException e) { - // success - } - try { - server1.verifyHandshake(); - fail("Server allows operations post error"); - } catch (IllegalStateException e) { - // success - } - try { - server1.toConnectionContext(); - fail("Server allows operations post error"); - } catch (IllegalStateException e) { - // success - } - } - - /** - * Test commitment having unsupported version - */ - public void testClientInitUnsupportedVersion() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ClientInit and modify the version to be too big - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ClientInit.Builder clientInit = - Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData())); - clientInit.setVersion(Ukey2Handshake.VERSION + 1); - message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Server did not catch unsupported version (too big) in ClientInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - - // Get ClientInit and modify the version to be too big - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - - message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - clientInit = Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData())); - clientInit.setVersion(0 /* minimum version is 1 */); - message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Server did not catch unsupported version (too small) in ClientInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - } - - /** - * Tests that server catches wrong number of random bytes in ClientInit - */ - public void testWrongNonceLengthInClientInit() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ClientInit and modify the nonce - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ClientInit.Builder clientInit = - Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData())); - clientInit.setRandom( - ByteString.copyFrom( - Arrays.copyOf( - clientInit.getRandom().toByteArray(), - 31 /* as per go/ukey2, nonces must be 32 bytes long */))); - message.setMessageData(ByteString.copyFrom(clientInit.build().toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Server did not catch nonce being too short in ClientInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - } - - /** - * Test that server catches missing commitment in ClientInit message - */ - public void testServerCatchesMissingCommitmentInClientInit() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ClientInit and modify the commitment - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ClientInit clientInit = - Ukey2ClientInit.newBuilder(Ukey2ClientInit.parseFrom(message.getMessageData())) - .build(); - Ukey2ClientInit.Builder badClientInit = Ukey2ClientInit.newBuilder() - .setVersion(clientInit.getVersion()) - .setRandom(clientInit.getRandom()); - for (CipherCommitment commitment : clientInit.getCipherCommitmentsList()) { - badClientInit.addCipherCommitments(commitment); - } - - message.setMessageData(ByteString.copyFrom(badClientInit.build().toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Server did not catch missing commitment in ClientInit"); - } catch (AlertException e) { - // success - } - } - - /** - * Test that client catches invalid version in ServerInit - */ - public void testServerInitUnsupportedVersion() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ServerInit and modify the version to be too big - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ServerInit serverInit = - Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData())) - .setVersion(Ukey2Handshake.VERSION + 1) - .build(); - message.setMessageData(ByteString.copyFrom(serverInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch unsupported version (too big) in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - - // Get ServerInit and modify the version to be too big - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - - message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage)); - serverInit = - Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData())) - .setVersion(0 /* minimum version is 1 */) - .build(); - message.setMessageData(ByteString.copyFrom(serverInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch unsupported version (too small) in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - } - - /** - * Tests that client catches wrong number of random bytes in ServerInit - */ - public void testWrongNonceLengthInServerInit() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ServerInit and modify the nonce - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ServerInit.Builder serverInitBuilder = - Ukey2ServerInit.newBuilder(Ukey2ServerInit.parseFrom(message.getMessageData())); - Ukey2ServerInit serverInit = serverInitBuilder.setRandom(ByteString.copyFrom(Arrays.copyOf( - serverInitBuilder.getRandom().toByteArray(), - 31 /* as per go/ukey2, nonces must be 32 bytes long */))) - .build(); - message.setMessageData(ByteString.copyFrom(serverInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch nonce being too short in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - } - - /** - * Test that client catches missing or incorrect handshake cipher in serverInit - */ - public void testMissingOrIncorrectHandshakeCipherInServerInit() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ServerInit - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData()); - - // remove handshake cipher - Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder() - .setPublicKey(serverInit.getPublicKey()) - .setRandom(serverInit.getRandom()) - .setVersion(serverInit.getVersion()) - .build(); - - message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch missing handshake cipher in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - - // Get ServerInit - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage)); - serverInit = Ukey2ServerInit.parseFrom(message.getMessageData()); - - // put in a bad handshake cipher - badServerInit = Ukey2ServerInit.newBuilder() - .setPublicKey(serverInit.getPublicKey()) - .setRandom(serverInit.getRandom()) - .setVersion(serverInit.getVersion()) - .setHandshakeCipher(UkeyProto.Ukey2HandshakeCipher.RESERVED) - .build(); - - message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch bad handshake cipher in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - } - - /** - * Test that client catches missing or incorrect public key in serverInit - */ - public void testMissingOrIncorrectPublicKeyInServerInit() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ServerInit - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData()); - - // remove public key - Ukey2ServerInit badServerInit = Ukey2ServerInit.newBuilder() - .setRandom(serverInit.getRandom()) - .setVersion(serverInit.getVersion()) - .setHandshakeCipher(serverInit.getHandshakeCipher()) - .build(); - - message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch missing public key in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - - // Get ServerInit - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - serverInit = Ukey2ServerInit.parseFrom(message.getMessageData()); - - // put in a bad public key - badServerInit = Ukey2ServerInit.newBuilder() - .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1})) - .setRandom(serverInit.getRandom()) - .setVersion(serverInit.getVersion()) - .setHandshakeCipher(serverInit.getHandshakeCipher()) - .build(); - - message.setMessageData(ByteString.copyFrom(badServerInit.toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - client.parseHandshakeMessage(handshakeMessage); - fail("Client did not catch bad public key in ServerInit"); - } catch (AlertException e) { - // success - } - assertEquals(State.ERROR, client.getHandshakeState()); - } - - /** - * Test that client catches missing or incorrect public key in clientFinished - */ - public void testMissingOrIncorrectPublicKeyInClientFinished() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Get ClientFinished - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - client.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client.getNextHandshakeMessage(); - Ukey2Message.Builder message = Ukey2Message.newBuilder( - Ukey2Message.parseFrom(handshakeMessage)); - - // remove public key - Ukey2ClientFinished.Builder badClientFinished = Ukey2ClientFinished.newBuilder(); - - message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Server did not catch missing public key in ClientFinished"); - } catch (HandshakeException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - - // Get ClientFinished - client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - client.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client.getNextHandshakeMessage(); - message = Ukey2Message.newBuilder(Ukey2Message.parseFrom(handshakeMessage)); - - // remove public key - badClientFinished = Ukey2ClientFinished.newBuilder() - .setPublicKey(ByteString.copyFrom(new byte[] {42, 12, 1})); - - message.setMessageData(ByteString.copyFrom(badClientFinished.build().toByteArray())); - handshakeMessage = message.build().toByteArray(); - - try { - server.parseHandshakeMessage(handshakeMessage); - fail("Server did not catch bad public key in ClientFinished"); - } catch (HandshakeException e) { - // success - } - assertEquals(State.ERROR, server.getHandshakeState()); - } - - /** - * Tests that items (nonces, commitments, public keys) that should be random are at least - * different on every run. - */ - public void testRandomItemsDifferentOnEveryRun() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - int numberOfRuns = 50; - - // Search for collisions - Set<Integer> commitments = new HashSet<>(numberOfRuns); - Set<Integer> clientNonces = new HashSet<>(numberOfRuns); - Set<Integer> serverNonces = new HashSet<>(numberOfRuns); - Set<Integer> serverPublicKeys = new HashSet<>(numberOfRuns); - Set<Integer> clientPublicKeys = new HashSet<>(numberOfRuns); - - for (int i = 0; i < numberOfRuns; i++) { - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - Ukey2Message message = Ukey2Message.parseFrom(handshakeMessage); - Ukey2ClientInit clientInit = Ukey2ClientInit.parseFrom(message.getMessageData()); - - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - message = Ukey2Message.parseFrom(handshakeMessage); - Ukey2ServerInit serverInit = Ukey2ServerInit.parseFrom(message.getMessageData()); - - client.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client.getNextHandshakeMessage(); - message = Ukey2Message.parseFrom(handshakeMessage); - Ukey2ClientFinished clientFinished = Ukey2ClientFinished.parseFrom(message.getMessageData()); - - // Clean up to save some memory (b/32054837) - client = null; - server = null; - handshakeMessage = null; - message = null; - System.gc(); - - // ClientInit randomness - Integer nonceHash = Integer.valueOf(Arrays.hashCode(clientInit.getRandom().toByteArray())); - if (clientNonces.contains(nonceHash) || serverNonces.contains(nonceHash)) { - fail("Nonce in ClientINit has repeated!"); - } - clientNonces.add(nonceHash); - - Integer commitmentHash = 0; - for (CipherCommitment commitement : clientInit.getCipherCommitmentsList()) { - commitmentHash += Arrays.hashCode(commitement.toByteArray()); - } - if (commitments.contains(nonceHash)) { - fail("Commitment has repeated!"); - } - commitments.add(commitmentHash); - - // ServerInit randomness - nonceHash = Integer.valueOf(Arrays.hashCode(serverInit.getRandom().toByteArray())); - if (serverNonces.contains(nonceHash) || clientNonces.contains(nonceHash)) { - fail("Nonce in ServerInit repeated!"); - } - serverNonces.add(nonceHash); - - Integer publicKeyHash = - Integer.valueOf(Arrays.hashCode(serverInit.getPublicKey().toByteArray())); - if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) { - fail("Public Key in ServerInit repeated!"); - } - serverPublicKeys.add(publicKeyHash); - - // Client Finished randomness - publicKeyHash = Integer.valueOf(Arrays.hashCode(clientFinished.getPublicKey().toByteArray())); - if (serverPublicKeys.contains(publicKeyHash) || clientPublicKeys.contains(publicKeyHash)) { - fail("Public Key in ClientFinished repeated!"); - } - clientPublicKeys.add(publicKeyHash); - } - } - - /** - * Tests that {@link Ukey2Handshake#getVerificationString(int)} enforces sane verification string - * lengths. - */ - public void testGetVerificationEnforcesSaneLengths() throws Exception { - if (KeyEncoding.isLegacyCryptoRequired()) { - // this means we're running on an old SDK, which doesn't support the - // necessary crypto. Let's not test anything in this case. - return; - } - - // Run the protocol - Ukey2Handshake client = Ukey2Handshake.forInitiator(HandshakeCipher.P256_SHA512); - Ukey2Handshake server = Ukey2Handshake.forResponder(HandshakeCipher.P256_SHA512); - byte[] handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - handshakeMessage = server.getNextHandshakeMessage(); - client.parseHandshakeMessage(handshakeMessage); - handshakeMessage = client.getNextHandshakeMessage(); - server.parseHandshakeMessage(handshakeMessage); - - // Try to get too short verification string - try { - client.getVerificationString(0); - fail("Too short verification string allowed"); - } catch (IllegalArgumentException e) { - // success - } - - // Try to get too long verification string - try { - server.getVerificationString(MAX_AUTH_STRING_LENGTH + 1); - fail("Too long verification string allowed"); - } catch (IllegalArgumentException e) { - // success - } - } - - /** - * Asserts that the given client and server contexts are compatible - */ - private void assertContextsCompatible( - D2DConnectionContext clientContext, D2DConnectionContext serverContext) { - assertNotNull(clientContext); - assertNotNull(serverContext); - assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, clientContext.getProtocolVersion()); - assertEquals(D2DConnectionContextV1.PROTOCOL_VERSION, serverContext.getProtocolVersion()); - assertEquals(clientContext.getEncodeKey(), serverContext.getDecodeKey()); - assertEquals(clientContext.getDecodeKey(), serverContext.getEncodeKey()); - assertFalse(clientContext.getEncodeKey().equals(clientContext.getDecodeKey())); - assertEquals(0, clientContext.getSequenceNumberForEncoding()); - assertEquals(0, clientContext.getSequenceNumberForDecoding()); - assertEquals(0, serverContext.getSequenceNumberForEncoding()); - assertEquals(0, serverContext.getSequenceNumberForDecoding()); - } -} 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); - } - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java deleted file mode 100644 index 65fa094..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/CryptoOpsTest.java +++ /dev/null @@ -1,172 +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.securemessage; - -import static org.junit.Assert.assertThrows; - -import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType; -import java.util.Arrays; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import junit.framework.TestCase; - -/** - * Unit tests for the CryptoOps class - */ -public class CryptoOpsTest extends TestCase { - - /** HKDF Test Case 1 IKM from RFC 5869 */ - private static final byte[] HKDF_CASE1_IKM = { - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b - }; - - /** HKDF Test Case 1 salt from RFC 5869 */ - private static final byte[] HKDF_CASE1_SALT = { - 0x00, 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, 0x09, - 0x0a, 0x0b, 0x0c - }; - - /** HKDF Test Case 1 info from RFC 5869 */ - private static final byte[] HKDF_CASE1_INFO = { - (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4, - (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9 - }; - - /** First 32 bytes of HKDF Test Case 1 OKM (output) from RFC 5869 */ - private static final byte[] HKDF_CASE1_OKM = { - (byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa, - (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43, - (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f, - (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90, - (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d, - (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4, - (byte) 0xc5, (byte) 0xbf, (byte) 0x34, (byte) 0x00, (byte) 0x72, - (byte) 0x08, (byte) 0xd5, (byte) 0xb8, (byte) 0x87, (byte) 0x18, - (byte) 0x58, (byte) 0x65 - }; - - private SecretKey aesKey1; - private SecretKey aesKey2; - - @Override - protected void setUp() throws Exception { - KeyGenerator aesKeygen = KeyGenerator.getInstance("AES"); - aesKeygen.init(256); - aesKey1 = aesKeygen.generateKey(); - aesKey2 = aesKeygen.generateKey(); - super.setUp(); - } - - public void testNoPurposeConflicts() { - // Ensure that signature algorithms and encryption algorithms are not given identical purposes - // (this prevents confusion of derived keys). - for (SigType sigType : SigType.values()) { - for (EncType encType : EncType.values()) { - assertFalse(CryptoOps.getPurpose(sigType).equals(CryptoOps.getPurpose(encType))); - } - } - } - - public void testDeriveAes256KeyFor() throws Exception { - // Test that deriving with the same key and purpose twice is deterministic - assertTrue(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(), - CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded())); - // Test that derived keys with different purposes differ - assertFalse(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(), - CryptoOps.deriveAes256KeyFor(aesKey1, "B").getEncoded())); - // Test that derived keys with the same purpose but different master keys differ - assertFalse(Arrays.equals(CryptoOps.deriveAes256KeyFor(aesKey1, "A").getEncoded(), - CryptoOps.deriveAes256KeyFor(aesKey2, "A").getEncoded())); - } - - public void testHkdf() throws Exception { - SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES"); - byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO); - byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 32); - assertTrue(Arrays.equals(result, expectedResult)); - } - - public void testHkdfLongOutput() throws Exception { - SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES"); - byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 42); - byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 42); - assertTrue(Arrays.equals(result, expectedResult)); - } - - public void testHkdfShortOutput() throws Exception { - SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES"); - byte[] result = CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 12); - byte[] expectedResult = Arrays.copyOf(HKDF_CASE1_OKM, 12); - assertTrue(Arrays.equals(result, expectedResult)); - } - - public void testHkdfInvalidLengths() throws Exception { - SecretKey inputKey = new SecretKeySpec(HKDF_CASE1_IKM, "AES"); - - // Negative length - assertThrows( - IllegalArgumentException.class, - () -> CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, -5)); - - // Too long, would be more than 256 blocks - assertThrows( - IllegalArgumentException.class, - () -> CryptoOps.hkdf(inputKey, HKDF_CASE1_SALT, HKDF_CASE1_INFO, 32 * 256 + 1)); - } - - public void testConcat() { - byte[] a = { 1, 2, 3, 4}; - byte[] b = { 5 , 6 }; - byte[] expectedResult = { 1, 2, 3, 4, 5, 6 }; - byte[] result = CryptoOps.concat(a, b); - assertEquals(a.length + b.length, result.length); - assertTrue(Arrays.equals(expectedResult, result)); - - byte[] empty = { }; - assertEquals(0, CryptoOps.concat(empty, empty).length); - assertTrue(Arrays.equals(a, CryptoOps.concat(a, empty))); - assertTrue(Arrays.equals(a, CryptoOps.concat(empty, a))); - - assertEquals(0, CryptoOps.concat(null, null).length); - assertTrue(Arrays.equals(a, CryptoOps.concat(a, null))); - assertTrue(Arrays.equals(a, CryptoOps.concat(null, a))); - } - - public void testSubarray() { - byte[] in = { 1, 2, 3, 4, 5, 6, 7 }; - assertTrue(Arrays.equals(in, CryptoOps.subarray(in, 0, in.length))); - assertEquals(0, CryptoOps.subarray(in, 0, 0).length); - byte[] expectedResult1 = { 1 }; - assertTrue(Arrays.equals(expectedResult1, CryptoOps.subarray(in, 0, 1))); - byte[] expectedResult34 = { 3, 4 }; - assertTrue(Arrays.equals(expectedResult34, CryptoOps.subarray(in, 2, 4))); - assertThrows(IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, 0, in.length + 1)); - assertThrows(IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, -1, in.length)); - assertThrows( - IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, in.length, in.length)); - assertThrows( - IndexOutOfBoundsException.class, - () -> CryptoOps.subarray(in, Integer.MIN_VALUE, in.length)); - assertThrows( - IndexOutOfBoundsException.class, () -> CryptoOps.subarray(in, 1, Integer.MIN_VALUE)); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java deleted file mode 100644 index c28d2f9..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/NullsGoogle3Test.java +++ /dev/null @@ -1,42 +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.securemessage; - -import com.google.common.testing.NullPointerTester; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage; -import junit.framework.TestCase; - -/** - * Non-portable Google3-based test to check null pointer behavior. - */ -public class NullsGoogle3Test extends TestCase { - - /** - * We test all of the classes in one place to avoid a proliferation of similar test cases, - * noting that {@link NullPointerTester} emits the name of the class where the breakge occurs. - */ - public void testNulls() { - final NullPointerTester tester = new NullPointerTester(); - tester.testAllPublicStaticMethods(CryptoOps.class); - tester.testAllPublicStaticMethods(PublicKeyProtoUtil.class); - - tester.setDefault(SecureMessage.class, SecureMessage.getDefaultInstance()); - tester.testAllPublicStaticMethods(SecureMessageParser.class); - - tester.testAllPublicStaticMethods(SecureMessageBuilder.class); - tester.testAllPublicConstructors(SecureMessageBuilder.class); - tester.testAllPublicInstanceMethods(new SecureMessageBuilder()); - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java deleted file mode 100644 index 8581622..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/PublicKeyProtoUtilTest.java +++ /dev/null @@ -1,412 +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.securemessage; - -import com.google.common.io.BaseEncoding; -import com.google.protobuf.ByteString; -import com.google.security.annotations.SuppressInsecureCipherModeCheckerNoReview; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.DhPublicKey; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey; -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import javax.crypto.KeyAgreement; -import javax.crypto.interfaces.DHPrivateKey; -import javax.crypto.interfaces.DHPublicKey; -import junit.framework.TestCase; - -/** Tests for the PublicKeyProtoUtil class. */ -public class PublicKeyProtoUtilTest extends TestCase { - - private static final byte[] ZERO_BYTE = {0}; - private PublicKey ecPublicKey; - private PublicKey rsaPublicKey; - - /** - * Diffie Hellman {@link PublicKey}s require special treatment, so we store them specifically as a - * {@link DHPublicKey} to minimize casting. - */ - private DHPublicKey dhPublicKey; - - @Override - public void setUp() { - if (!isAndroidOsWithoutEcSupport()) { - ecPublicKey = PublicKeyProtoUtil.generateEcP256KeyPair().getPublic(); - } - rsaPublicKey = PublicKeyProtoUtil.generateRSA2048KeyPair().getPublic(); - dhPublicKey = (DHPublicKey) PublicKeyProtoUtil.generateDh2048KeyPair().getPublic(); - } - - public void testPublicKeyProtoSpecificEncodeParse() throws Exception { - if (!isAndroidOsWithoutEcSupport()) { - assertEquals( - ecPublicKey, - PublicKeyProtoUtil.parseEcPublicKey(PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey))); - } - - assertEquals( - rsaPublicKey, - PublicKeyProtoUtil.parseRsa2048PublicKey( - PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey))); - - // DHPublicKey objects don't seem to properly implement equals(), so we have to test that - // the individual y and p values match (it is safe to assume g = 2 is used if p is correct). - DHPublicKey parsedDHPublicKey = - PublicKeyProtoUtil.parseDh2048PublicKey( - PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey)); - assertEquals(dhPublicKey.getY(), parsedDHPublicKey.getY()); - assertEquals(dhPublicKey.getParams().getP(), parsedDHPublicKey.getParams().getP()); - assertEquals(dhPublicKey.getParams().getG(), parsedDHPublicKey.getParams().getG()); - } - - public void testPublicKeyProtoGenericEncodeParse() throws Exception { - if (!isAndroidOsWithoutEcSupport()) { - assertEquals( - ecPublicKey, - PublicKeyProtoUtil.parsePublicKey( - PublicKeyProtoUtil.encodePaddedEcPublicKey(ecPublicKey))); - assertEquals( - ecPublicKey, - PublicKeyProtoUtil.parsePublicKey(PublicKeyProtoUtil.encodePublicKey(ecPublicKey))); - } - - assertEquals( - rsaPublicKey, - PublicKeyProtoUtil.parsePublicKey(PublicKeyProtoUtil.encodePublicKey(rsaPublicKey))); - - // See above explanation for why we treat DHPublicKey objects differently. - DHPublicKey parsedDHPublicKey = - PublicKeyProtoUtil.parseDh2048PublicKey( - PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey)); - assertEquals(dhPublicKey.getY(), parsedDHPublicKey.getY()); - assertEquals(dhPublicKey.getParams().getP(), parsedDHPublicKey.getParams().getP()); - assertEquals(dhPublicKey.getParams().getG(), parsedDHPublicKey.getParams().getG()); - } - - public void testPaddedECPublicKeyEncodeHasPaddedNullByte() throws Exception { - if (isAndroidOsWithoutEcSupport()) { - return; - } - - // Key where the x coordinate is 33 bytes, y coordinate is 32 bytes - ECPublicKey maxXByteLengthKey = - buildEcPublicKey( - BaseEncoding.base64().decode("AM730WQL7ZAmvyAJX4euNdr3+nAIueGlYYGXE6p732h6"), - BaseEncoding.base64().decode("JEnmaDpKn0fH4/0kKGb97qUSwI2uT+ta0GLe3V7REfk=")); - // Key where both coordinates are 33 bytes - ECPublicKey maxByteLengthKey = - buildEcPublicKey( - BaseEncoding.base64().decode("AOg9TQCxFfVdXv7lO/6UVDyiPsu8XDkEWQIPUfqX6UHP"), - BaseEncoding.base64().decode("AP/RW8uVyu6QImpbza51CqG1mtBTh5c9pjv9CUwOuB7E")); - // Key where both coordinates are 32 bytes - ECPublicKey notMaxByteLengthKey = - buildEcPublicKey( - BaseEncoding.base64().decode("M35bxV8HKr0e8v7f4zuXgw6TYFawvikFdI71u9S1ONI="), - BaseEncoding.base64().decode("OXR+xCpD8AR0VR8TeBXA00eIr3rWE6sV6KrOM6MoWsc=")); - GenericPublicKey encodedMaxXByteLengthKey = - PublicKeyProtoUtil.encodePublicKey(maxXByteLengthKey); - GenericPublicKey paddedEncodedMaxXByteLengthKey = - PublicKeyProtoUtil.encodePaddedEcPublicKey(maxXByteLengthKey); - GenericPublicKey encodedMaxByteLengthKey = PublicKeyProtoUtil.encodePublicKey(maxByteLengthKey); - GenericPublicKey paddedEncodedMaxByteLengthKey = - PublicKeyProtoUtil.encodePaddedEcPublicKey(maxByteLengthKey); - GenericPublicKey encodedNotMaxByteLengthKey = - PublicKeyProtoUtil.encodePublicKey(notMaxByteLengthKey); - GenericPublicKey paddedEncodedNotMaxByteLengthKey = - PublicKeyProtoUtil.encodePaddedEcPublicKey(notMaxByteLengthKey); - - assertEquals(maxXByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedMaxXByteLengthKey)); - assertEquals( - maxXByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedMaxXByteLengthKey)); - assertEquals(maxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedMaxByteLengthKey)); - assertEquals( - maxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedMaxByteLengthKey)); - assertEquals( - notMaxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(paddedEncodedNotMaxByteLengthKey)); - assertEquals( - notMaxByteLengthKey, PublicKeyProtoUtil.parsePublicKey(encodedNotMaxByteLengthKey)); - - assertEquals(33, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getX().size()); - assertEquals(33, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getY().size()); - assertEquals(0, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getX().byteAt(0)); - assertEquals(0, paddedEncodedMaxXByteLengthKey.getEcP256PublicKey().getY().byteAt(0)); - assertEquals(33, encodedMaxXByteLengthKey.getEcP256PublicKey().getX().size()); - assertEquals(32, encodedMaxXByteLengthKey.getEcP256PublicKey().getY().size()); - - assertEquals(33, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getX().size()); - assertEquals(33, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getY().size()); - assertEquals(0, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getX().byteAt(0)); - assertEquals(0, paddedEncodedMaxByteLengthKey.getEcP256PublicKey().getY().byteAt(0)); - assertEquals(33, encodedMaxByteLengthKey.getEcP256PublicKey().getX().size()); - assertEquals(33, encodedMaxByteLengthKey.getEcP256PublicKey().getY().size()); - - assertEquals(32, encodedNotMaxByteLengthKey.getEcP256PublicKey().getX().size()); - assertEquals(32, encodedNotMaxByteLengthKey.getEcP256PublicKey().getY().size()); - assertEquals(0, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getX().byteAt(0)); - assertEquals(0, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getY().byteAt(0)); - assertEquals(33, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getX().size()); - assertEquals(33, paddedEncodedNotMaxByteLengthKey.getEcP256PublicKey().getY().size()); - } - - @SuppressInsecureCipherModeCheckerNoReview - public void testWrongPublicKeyType() throws Exception { - KeyPairGenerator dsaGen = KeyPairGenerator.getInstance("DSA"); - dsaGen.initialize(512); - PublicKey pk = dsaGen.generateKeyPair().getPublic(); - - if (!isAndroidOsWithoutEcSupport()) { - // Try to encode it as EC - try { - PublicKeyProtoUtil.encodeEcPublicKey(pk); - fail(); - } catch (IllegalArgumentException expected) { - } - - try { - PublicKeyProtoUtil.encodePaddedEcPublicKey(pk); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - // Try to encode it as RSA - try { - PublicKeyProtoUtil.encodeRsa2048PublicKey(pk); - fail(); - } catch (IllegalArgumentException expected) { - } - - // Try to encode it as DH - try { - PublicKeyProtoUtil.encodeDh2048PublicKey(pk); - fail(); - } catch (IllegalArgumentException expected) { - } - - // Try to encode it as Generic - try { - PublicKeyProtoUtil.encodePublicKey(pk); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - public void testEcPublicKeyProtoInvalidEncoding() throws Exception { - if (isAndroidOsWithoutEcSupport()) { - return; - } - - EcP256PublicKey validProto = PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey); - EcP256PublicKey.Builder invalidProto = EcP256PublicKey.newBuilder(validProto); - - // Mess up the X coordinate by repeating it twice - byte[] newX = - CryptoOps.concat(validProto.getX().toByteArray(), validProto.getX().toByteArray()); - checkParsingFailsFor(invalidProto.setX(ByteString.copyFrom(newX)).build()); - - // Mess up the Y coordinate by erasing it - invalidProto = EcP256PublicKey.newBuilder(validProto); - checkParsingFailsFor(invalidProto.setY(ByteString.EMPTY).build()); - - // Pick a point that is likely not on the curve by copying X over Y - invalidProto = EcP256PublicKey.newBuilder(validProto); - checkParsingFailsFor(invalidProto.setY(validProto.getX()).build()); - - // Try the point (0, 0) - invalidProto = EcP256PublicKey.newBuilder(validProto); - checkParsingFailsFor( - invalidProto - .setX(ByteString.copyFrom(ZERO_BYTE)) - .setY(ByteString.copyFrom(ZERO_BYTE)) - .build()); - } - - private void checkParsingFailsFor(EcP256PublicKey invalid) { - try { - // Should fail to decode - PublicKeyProtoUtil.parseEcPublicKey(invalid); - fail(); - } catch (InvalidKeySpecException expected) { - } - } - - public void testSimpleRsaPublicKeyProtoInvalidEncoding() throws Exception { - SimpleRsaPublicKey validProto = PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey); - SimpleRsaPublicKey.Builder invalidProto; - - // Double the number of bits in the modulus - invalidProto = SimpleRsaPublicKey.newBuilder(validProto); - byte[] newN = - CryptoOps.concat(validProto.getN().toByteArray(), validProto.getN().toByteArray()); - checkParsingFailsFor(invalidProto.setN(ByteString.copyFrom(newN)).build()); - - // Set the modulus to 0 - invalidProto = SimpleRsaPublicKey.newBuilder(validProto); - checkParsingFailsFor(invalidProto.setN(ByteString.copyFrom(ZERO_BYTE)).build()); - - // Set the modulus to 65537 (way too small) - invalidProto = SimpleRsaPublicKey.newBuilder(validProto); - checkParsingFailsFor( - invalidProto.setN(ByteString.copyFrom(BigInteger.valueOf(65537).toByteArray())).build()); - } - - private static void checkParsingFailsFor(SimpleRsaPublicKey invalid) { - try { - // Should fail to decode - PublicKeyProtoUtil.parseRsa2048PublicKey(invalid); - fail(); - } catch (InvalidKeySpecException expected) { - } - } - - public void testSimpleDhPublicKeyProtoInvalidEncoding() throws Exception { - DhPublicKey validProto = PublicKeyProtoUtil.encodeDh2048PublicKey(dhPublicKey); - DhPublicKey.Builder invalidProto; - - // Double the number of bits in the public element encoding - invalidProto = DhPublicKey.newBuilder(validProto); - byte[] newY = - CryptoOps.concat(validProto.getY().toByteArray(), validProto.getY().toByteArray()); - checkParsingFailsFor(invalidProto.setY(ByteString.copyFrom(newY)).build()); - - // Set the public element to 0 - invalidProto = DhPublicKey.newBuilder(validProto); - checkParsingFailsFor(invalidProto.setY(ByteString.copyFrom(ZERO_BYTE)).build()); - } - - private static void checkParsingFailsFor(DhPublicKey invalid) { - try { - // Should fail to decode - PublicKeyProtoUtil.parseDh2048PublicKey(invalid); - fail(); - } catch (InvalidKeySpecException expected) { - } - } - - public void testDhKeyAgreementWorks() throws Exception { - int minExpectedSecretLength = (PublicKeyProtoUtil.DH_P.bitLength() / 8) - 4; - - KeyPair clientKeyPair = PublicKeyProtoUtil.generateDh2048KeyPair(); - KeyPair serverKeyPair = PublicKeyProtoUtil.generateDh2048KeyPair(); - BigInteger clientY = ((DHPublicKey) clientKeyPair.getPublic()).getY(); - BigInteger serverY = ((DHPublicKey) serverKeyPair.getPublic()).getY(); - assertFalse(clientY.equals(serverY)); // DHPublicKeys should not be equal - - // Run client side of the key exchange - byte[] clientSecret = doDhAgreement(clientKeyPair.getPrivate(), serverKeyPair.getPublic()); - assert (clientSecret.length >= minExpectedSecretLength); - - // Run the server side of the key exchange - byte[] serverSecret = doDhAgreement(serverKeyPair.getPrivate(), clientKeyPair.getPublic()); - assert (serverSecret.length >= minExpectedSecretLength); - - assertTrue(Arrays.equals(clientSecret, serverSecret)); - } - - public void testDh2048PrivateKeyEncoding() throws Exception { - KeyPair testPair = PublicKeyProtoUtil.generateDh2048KeyPair(); - DHPrivateKey sk = (DHPrivateKey) testPair.getPrivate(); - DHPrivateKey skParsed = - PublicKeyProtoUtil.parseDh2048PrivateKey(PublicKeyProtoUtil.encodeDh2048PrivateKey(sk)); - assertEquals(sk.getX(), skParsed.getX()); - assertEquals(sk.getParams().getP(), skParsed.getParams().getP()); - assertEquals(sk.getParams().getG(), skParsed.getParams().getG()); - } - - public void testParseEcPublicKeyOnLegacyPlatform() { - if (!PublicKeyProtoUtil.isLegacyCryptoRequired()) { - return; // This test only runs on legacy platforms - } - byte[] pointBytes = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, - 1, 2 - }; - - try { - PublicKeyProtoUtil.parseEcPublicKey( - EcP256PublicKey.newBuilder() - .setX(ByteString.copyFrom(pointBytes)) - .setY(ByteString.copyFrom(pointBytes)) - .build()); - fail(); - } catch (InvalidKeySpecException expected) { - // Should get this specific exception when EC doesn't work - } - } - - public void testIsLegacyCryptoRequired() { - assertEquals(isAndroidOsWithoutEcSupport(), PublicKeyProtoUtil.isLegacyCryptoRequired()); - } - - /** @return true if running on an Android OS that doesn't support Elliptic Curve algorithms */ - public static boolean isAndroidOsWithoutEcSupport() { - try { - Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("android.os.Build$VERSION"); - int sdkVersion = clazz.getField("SDK_INT").getInt(null); - if (sdkVersion < PublicKeyProtoUtil.ANDROID_HONEYCOMB_SDK_INT) { - return true; - } - } catch (ClassNotFoundException e) { - // Not running on Android - return false; - } catch (SecurityException e) { - throw new AssertionError(e); - } catch (NoSuchFieldException e) { - throw new AssertionError(e); - } catch (IllegalArgumentException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - return false; - } - - @SuppressInsecureCipherModeCheckerNoReview - private static byte[] doDhAgreement(PrivateKey secretKey, PublicKey peerKey) throws Exception { - KeyAgreement agreement = KeyAgreement.getInstance("DH"); - agreement.init(secretKey); - agreement.doPhase(peerKey, true); - return agreement.generateSecret(); - } - - private static ECPublicKey buildEcPublicKey(byte[] encodedX, byte[] encodedY) throws Exception { - try { - BigInteger wX = new BigInteger(encodedX); - BigInteger wY = new BigInteger(encodedY); - return (ECPublicKey) - KeyFactory.getInstance("EC") - .generatePublic( - new ECPublicKeySpec( - new ECPoint(wX, wY), - ((ECPublicKey) PublicKeyProtoUtil.generateEcP256KeyPair().getPublic()) - .getParams())); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java deleted file mode 100644 index 285b259..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageSimpleTestVectorTest.java +++ /dev/null @@ -1,403 +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.securemessage; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Arrays; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import junit.framework.TestCase; - -/** - * Tests the library against some very basic test vectors, to help ensure wire-format - * compatibility is not broken. - */ -public class SecureMessageSimpleTestVectorTest extends TestCase { - - private static final KeyFactory EC_KEY_FACTORY; - static { - try { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - EC_KEY_FACTORY = null; - } else { - EC_KEY_FACTORY = KeyFactory.getInstance("EC"); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static final byte[] TEST_ASSOCIATED_DATA = { - 11, 22, 33, 44, 55 - }; - private static final byte[] TEST_METADATA = { - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 - }; - private static final byte[] TEST_VKID = { - 0, 0, 1 - }; - private static final byte[] TEST_DKID = { - -1, -1, 0, - }; - private static final byte[] TEST_MESSAGE = { - 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, 93, 7, 92, 8, 91, 9, 90 - }; - - // The following fields are initialized below, in a static block that contains auto-generated test - // vectors. Initialization can't just be done inline due to code that throws checked exceptions. - private static final PublicKey TEST_EC_PUBLIC_KEY; - private static final PrivateKey TEST_EC_PRIVATE_KEY; - private static final SecretKey TEST_KEY1; - private static final SecretKey TEST_KEY2; - private static final byte[] TEST_VECTOR_ECDSA_ONLY; - private static final byte[] TEST_VECTOR_ECDSA_AND_AES; - private static final byte[] TEST_VECTOR_HMAC_AND_AES_SAME_KEYS; - private static final byte[] TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS; - - public void testEcdsaOnly() throws Exception { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - // On older Android platforms we can't run this test. - return; - } - SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_ONLY); - Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); - HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage( - testVector, TEST_EC_PUBLIC_KEY, SigType.ECDSA_P256_SHA256, TEST_ASSOCIATED_DATA); - assertTrue(Arrays.equals( - unverifiedHeader.toByteArray(), - headerAndBody.getHeader().toByteArray())); - assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); - assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); - assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); - assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); - assertFalse(unverifiedHeader.hasDecryptionKeyId()); - } - - public void testEcdsaAndAes() throws Exception { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - // On older Android platforms we can't run this test. - return; - } - SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_ECDSA_AND_AES); - Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); - HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( - testVector, - TEST_EC_PUBLIC_KEY, - SigType.ECDSA_P256_SHA256, - TEST_KEY1, - EncType.AES_256_CBC, - TEST_ASSOCIATED_DATA); - assertTrue(Arrays.equals( - unverifiedHeader.toByteArray(), - headerAndBody.getHeader().toByteArray())); - assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); - assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); - assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); - assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); - assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray())); - } - - public void testHmacAndAesSameKeys() throws Exception { - SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_SAME_KEYS); - Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); - - HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( - testVector, - TEST_KEY1, - SigType.HMAC_SHA256, - TEST_KEY1, - EncType.AES_256_CBC, - TEST_ASSOCIATED_DATA); - assertTrue(Arrays.equals( - unverifiedHeader.toByteArray(), - headerAndBody.getHeader().toByteArray())); - assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); - assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); - assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); - assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); - assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray())); - } - - public void testHmacAndAesDifferentKeys() throws Exception { - SecureMessage testVector = SecureMessage.parseFrom(TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS); - Header unverifiedHeader = SecureMessageParser.getUnverifiedHeader(testVector); - HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( - testVector, - TEST_KEY1, - SigType.HMAC_SHA256, - TEST_KEY2, - EncType.AES_256_CBC, - TEST_ASSOCIATED_DATA); - assertTrue(Arrays.equals( - unverifiedHeader.toByteArray(), - headerAndBody.getHeader().toByteArray())); - assertTrue(Arrays.equals(TEST_MESSAGE, headerAndBody.getBody().toByteArray())); - assertEquals(TEST_ASSOCIATED_DATA.length, unverifiedHeader.getAssociatedDataLength()); - assertTrue(Arrays.equals(TEST_METADATA, unverifiedHeader.getPublicMetadata().toByteArray())); - assertTrue(Arrays.equals(TEST_VKID, unverifiedHeader.getVerificationKeyId().toByteArray())); - assertTrue(Arrays.equals(TEST_DKID, unverifiedHeader.getDecryptionKeyId().toByteArray())); - } - - /** - * This code emits the test vectors to {@code System.out}. It will not generate fresh test - * vectors unless an existing test vector is set to {@code null}, but it contains all of the code - * used to the generate the test vector values. Ideally, existing test vectors should never be - * regenerated, but having this code available should make it easier to add new test vectors. - */ - public void testGenerateTestVectorsPseudoTest() throws Exception { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - // On older Android platforms we can't run this test. - return; - } - System.out.printf(" static {\n try {\n"); - String indent = " "; - PublicKey testEcPublicKey = TEST_EC_PUBLIC_KEY; - PrivateKey testEcPrivateKey = TEST_EC_PRIVATE_KEY; - if (testEcPublicKey == null) { - KeyPair testEcKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - testEcPublicKey = testEcKeyPair.getPublic(); - testEcPrivateKey = testEcKeyPair.getPrivate(); - } - System.out.printf("%s%s = parsePublicKey(new byte[] %s);\n", - indent, - "TEST_EC_PUBLIC_KEY", - byteArrayToJavaCode(indent, encodePublicKey(testEcPublicKey))); - System.out.printf("%s%s = parseEcPrivateKey(new byte[] %s);\n", - indent, - "TEST_EC_PRIVATE_KEY", - byteArrayToJavaCode(indent, encodeEcPrivateKey(testEcPrivateKey))); - - SecretKey testKey1 = TEST_KEY1; - if (testKey1 == null) { - testKey1 = makeAesKey(); - } - System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n", - indent, - "TEST_KEY1", - byteArrayToJavaCode(indent, testKey1.getEncoded())); - - SecretKey testKey2 = TEST_KEY2; - if (testKey2 == null) { - testKey2 = makeAesKey(); - } - System.out.printf("%s%s = new SecretKeySpec(new byte[] %s, \"AES\");\n", - indent, - "TEST_KEY2", - byteArrayToJavaCode(indent, testKey2.getEncoded())); - - byte[] testVectorEcdsaOnly = TEST_VECTOR_ECDSA_ONLY; - if (testVectorEcdsaOnly == null) { - testVectorEcdsaOnly = new SecureMessageBuilder() - .setAssociatedData(TEST_ASSOCIATED_DATA) - .setPublicMetadata(TEST_METADATA) - .setVerificationKeyId(TEST_VKID) - .buildSignedCleartextMessage( - testEcPrivateKey, SigType.ECDSA_P256_SHA256, TEST_MESSAGE).toByteArray(); - } - printInitializerFor(indent, "TEST_VECTOR_ECDSA_ONLY", testVectorEcdsaOnly); - - byte[] testVectorEcdsaAndAes = TEST_VECTOR_ECDSA_AND_AES; - if (testVectorEcdsaAndAes == null) { - testVectorEcdsaAndAes = new SecureMessageBuilder() - .setAssociatedData(TEST_ASSOCIATED_DATA) - .setDecryptionKeyId(TEST_DKID) - .setPublicMetadata(TEST_METADATA) - .setVerificationKeyId(TEST_VKID) - .buildSignCryptedMessage( - testEcPrivateKey, - SigType.ECDSA_P256_SHA256, - testKey1, - EncType.AES_256_CBC, - TEST_MESSAGE).toByteArray(); - } - printInitializerFor(indent, "TEST_VECTOR_ECDSA_AND_AES", testVectorEcdsaAndAes); - - byte[] testVectorHmacAndAesSameKeys = TEST_VECTOR_HMAC_AND_AES_SAME_KEYS; - if (testVectorHmacAndAesSameKeys == null) { - testVectorHmacAndAesSameKeys = new SecureMessageBuilder() - .setAssociatedData(TEST_ASSOCIATED_DATA) - .setDecryptionKeyId(TEST_DKID) - .setPublicMetadata(TEST_METADATA) - .setVerificationKeyId(TEST_VKID) - .buildSignCryptedMessage( - testKey1, - SigType.HMAC_SHA256, - testKey1, - EncType.AES_256_CBC, - TEST_MESSAGE).toByteArray(); - } - printInitializerFor(indent, "TEST_VECTOR_HMAC_AND_AES_SAME_KEYS", testVectorHmacAndAesSameKeys); - - byte[] testVectorHmacAndAesDifferentKeys = TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS; - if (testVectorHmacAndAesDifferentKeys == null) { - testVectorHmacAndAesDifferentKeys = new SecureMessageBuilder() - .setAssociatedData(TEST_ASSOCIATED_DATA) - .setDecryptionKeyId(TEST_DKID) - .setPublicMetadata(TEST_METADATA) - .setVerificationKeyId(TEST_VKID) - .buildSignCryptedMessage( - testKey1, - SigType.HMAC_SHA256, - testKey2, - EncType.AES_256_CBC, - TEST_MESSAGE).toByteArray(); - } - printInitializerFor( - indent, "TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS", testVectorHmacAndAesDifferentKeys); - - System.out.printf( - " } catch (Exception e) {\n throw new RuntimeException(e);\n }\n }\n"); - } - - private SecretKey makeAesKey() throws NoSuchAlgorithmException { - KeyGenerator aesKeygen = KeyGenerator.getInstance("AES"); - aesKeygen.init(256); - return aesKeygen.generateKey(); - } - - private void printInitializerFor(String indent, String name, byte[] value) { - System.out.printf("%s%s = new byte[] %s;\n", - indent, - name, - byteArrayToJavaCode(indent, value)); - } - - private static String byteArrayToJavaCode(String lineIndent, byte[] array) { - String newline = "\n" + lineIndent + " "; - String unwrappedArray = Arrays.toString(array).replace("[", "").replace("]", ""); - int wrapAfter = 16; - int count = wrapAfter; - StringBuilder result = new StringBuilder("{"); - for (String entry : unwrappedArray.split(" ")) { - if (++count > wrapAfter) { - result.append(newline); - count = 0; - } else { - result.append(" "); - } - result.append(entry); - } - result.append(" }"); - return result.toString(); - } - - private static byte[] encodePublicKey(PublicKey pk) { - return PublicKeyProtoUtil.encodePublicKey(pk).toByteArray(); - } - - private static PublicKey parsePublicKey(byte[] encodedPk) - throws InvalidKeySpecException, InvalidProtocolBufferException { - GenericPublicKey gpk = GenericPublicKey.parseFrom(encodedPk); - if (PublicKeyProtoUtil.isLegacyCryptoRequired() - && gpk.getType() == SecureMessageProto.PublicKeyType.EC_P256) { - return null; - } - return PublicKeyProtoUtil.parsePublicKey(gpk); - } - - private static byte[] encodeEcPrivateKey(PrivateKey sk) { - return sk.getEncoded(); - } - - private static PrivateKey parseEcPrivateKey(byte[] sk) throws InvalidKeySpecException { - if (PublicKeyProtoUtil.isLegacyCryptoRequired()) { - return null; - } - return EC_KEY_FACTORY.generatePrivate(new PKCS8EncodedKeySpec(sk)); - } - - // The following block of code was automatically generated by cut and pasting the output of the - // generateTestVectorsPseudoTest, which should reliably emit this same test vectors. Please - // DO NOT DELETE any of these existing test vectors unless you _really_ know what you are doing. - // - // --- AUTO GENERATED CODE BEGINS HERE --- - static { - try { - TEST_EC_PUBLIC_KEY = parsePublicKey(new byte[] { - 8, 1, 18, 70, 10, 33, 0, -109, 9, 5, 8, -89, -3, -68, -86, -19, 17, - -126, -11, -95, 35, 101, 102, -57, -84, -118, 73, 83, 66, -62, -49, -91, 71, -19, - 52, 123, 113, 119, 45, 18, 33, 0, -65, -19, 83, -66, -12, 62, 102, -67, 116, - 64, 42, 55, -84, -101, 90, -106, 113, -89, -30, 57, -112, 96, -99, -126, 14, 83, - 41, 95, -24, -114, 23, -5 }); - TEST_EC_PRIVATE_KEY = parseEcPrivateKey(new byte[] { - 48, 65, 2, 1, 0, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, - 8, 42, -122, 72, -50, 61, 3, 1, 7, 4, 39, 48, 37, 2, 1, 1, 4, - 32, 26, -82, -61, -86, -59, -8, 2, -62, -17, -20, 122, 3, 85, -102, -76, 81, - 51, 39, -9, 12, 99, -117, 127, 19, 121, 109, -31, -49, 110, 121, 76, -107 }); - TEST_KEY1 = new SecretKeySpec(new byte[] { - -89, 105, 62, -41, -75, 78, 70, 110, -62, -58, -80, -81, -99, -62, 39, 38, 37, - -7, -112, -83, 81, 23, 125, -72, -100, 103, -34, -23, -68, 21, -46, -104 }, "AES"); - TEST_KEY2 = new SecretKeySpec(new byte[] { - -6, 48, 107, 61, -99, -89, 111, 33, 70, 54, -13, 111, 81, -120, 50, 89, -119, - -113, -114, 63, 12, -68, 40, 42, -77, -58, -49, 18, 69, 91, -20, -65 }, "AES"); - TEST_VECTOR_ECDSA_ONLY = new byte[] { - 10, 56, 10, 32, 8, 2, 16, 1, 26, 3, 0, 0, 1, 50, 19, 10, 11, - 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 56, 5, 18, 20, 0, 99, 1, 98, 2, 97, 3, 96, 4, 95, 5, 94, 6, - 93, 7, 92, 8, 91, 9, 90, 18, 72, 48, 70, 2, 33, 0, -79, 59, 50, - 21, 54, 61, -92, 77, -34, -77, -45, -105, 107, -28, -19, 91, -78, 120, 68, 33, - 11, -76, -1, 50, 64, -127, -78, 6, 108, 115, -13, 126, 2, 33, 0, -72, -44, - 52, 93, 105, 109, -127, -111, 11, 33, -111, 97, -114, 9, 117, -68, -45, 64, 63, - 43, 60, -44, -89, -107, -59, -45, 56, 100, -66, -40, 46, -60 }; - TEST_VECTOR_ECDSA_AND_AES = new byte[] { - 10, 107, 10, 55, 8, 2, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1, - 0, 42, 16, -86, 16, 55, -8, -85, -47, -77, -36, -127, 44, -10, -44, -63, 115, - -111, 26, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, -110, 23, -67, 122, -118, 96, -4, - 32, -113, -104, -107, -16, 76, 37, -61, -67, -63, 90, 38, 96, -47, -105, 56, -34, - 50, -30, 82, 25, 100, 36, 69, 50, 68, 60, 38, 96, -108, -49, -73, -10, -62, - -76, -45, -105, -86, 93, 28, 34, 18, 70, 48, 68, 2, 33, 0, -87, -103, 11, - -70, 34, 33, -41, 90, -83, -74, 19, -13, 127, -43, -116, -32, 88, -13, 125, -122, - 56, -21, 79, 47, 101, 89, -80, -43, 102, 92, 4, -15, 2, 31, 109, -69, 35, - 21, 44, -27, -77, 32, 17, -90, -68, 113, 55, -24, -122, 40, 81, 51, 0, -84, - -29, -12, -26, 73, 105, -32, 116, -28, 84, -116, -117 }; - TEST_VECTOR_HMAC_AND_AES_SAME_KEYS = new byte[] { - 10, 91, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1, - 0, 42, 16, -110, 48, 67, 67, -31, 24, -42, 13, -44, -109, 6, 113, 34, -70, - 121, 6, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 56, 5, 18, 32, -44, -102, -16, 123, 113, -75, 88, - -33, 118, 25, 60, -65, 109, 26, -70, -123, 58, -114, 126, 8, 106, -28, 65, -38, - -4, 68, -78, -91, 49, -13, 22, -122, 18, 32, 20, -120, -113, -76, 85, -35, -53, - 37, -18, 66, -38, 32, 10, 30, 89, 112, -39, -27, 24, 93, -36, -100, -127, -79, - 94, -7, -19, -41, -47, -29, 1, 12 }; - TEST_VECTOR_HMAC_AND_AES_DIFFERENT_KEYS = new byte[] { - 10, 107, 10, 55, 8, 1, 16, 2, 26, 3, 0, 0, 1, 34, 3, -1, -1, - 0, 42, 16, -96, -7, 39, 79, -37, 40, 1, -30, 97, 0, 123, -7, -124, -75, - -127, -18, 50, 19, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 56, 5, 18, 48, 90, 40, -48, -113, 84, -32, 47, - 98, 54, -128, 127, 115, 32, 87, -86, 4, -26, 99, 9, -88, 13, 77, 127, 114, - -48, -117, -94, 96, -86, -105, -123, 11, 116, -69, -83, -110, 3, -10, 0, -34, 72, - 10, -58, 3, -119, -94, 23, -114, 18, 32, -25, -126, 95, 125, -110, -62, -36, -78, - 97, 72, -54, -114, 97, -68, -46, 107, 53, 55, -57, 88, 127, -20, -23, 80, -9, - -91, 115, 42, 24, 49, -76, -111 }; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java b/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java deleted file mode 100644 index 40e5091..0000000 --- a/src/main/javatest/com/google/security/cryptauth/lib/securemessage/SecureMessageTest.java +++ /dev/null @@ -1,766 +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.securemessage; - -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.UninitializedMessageException; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.EncType; -import com.google.security.cryptauth.lib.securemessage.CryptoOps.SigType; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.EcP256PublicKey; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.GenericPublicKey; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.Header; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.HeaderAndBody; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SecureMessage; -import com.google.security.cryptauth.lib.securemessage.SecureMessageProto.SimpleRsaPublicKey; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import java.util.List; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import junit.framework.TestCase; - -/** - * Tests for the SecureMessageBuilder and SecureMessageParser classes. - */ -public class SecureMessageTest extends TestCase { - // Not to be used when generating cross-platform test vectors (due to default charset encoding) - public static final byte[] TEST_MESSAGE = - "Testing 1 2 3... Testing 1 2 3... Testing 1 2 3...".getBytes(); - - private static final byte[] TEST_KEY_ID = - { 0, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; - // Not to be used when generating cross-platform test vectors (due to default charset encoding) - private static final byte[] TEST_METADATA = "Some protocol metadata string goes here".getBytes(); - private static final byte[] TEST_ASSOCIATED_DATA = { - 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 11 }; - private static final byte[] ZERO_BYTE = { 0 }; - private static final byte[] EMPTY_BYTES = { }; - - private static final List<byte[]> MESSAGE_VALUES = Arrays.asList( - EMPTY_BYTES, - TEST_MESSAGE - ); - - private static final List<byte[]> KEY_ID_VALUES = Arrays.asList( - null, - EMPTY_BYTES, - TEST_KEY_ID); - - private static final List<byte[]> METADATA_VALUES = Arrays.asList( - null, - EMPTY_BYTES, - TEST_METADATA - ); - - private static final List<byte[]> ASSOCIATED_DATA_VALUES = Arrays.asList( - null, - ZERO_BYTE, - TEST_ASSOCIATED_DATA); - - private byte[] message; - private byte[] metadata; - private byte[] verificationKeyId; - private byte[] decryptionKeyId; - private byte[] associatedData; - private PublicKey ecPublicKey; - private PrivateKey ecPrivateKey; - private PublicKey rsaPublicKey; - private PrivateKey rsaPrivateKey; - private SecretKey aesEncryptionKey; - private SecretKey hmacKey; - private SecureMessageBuilder secureMessageBuilder = new SecureMessageBuilder(); - private SecureRandom rng = new SecureRandom(); - - @Override - public void setUp() { - message = TEST_MESSAGE; - metadata = null; - verificationKeyId = null; - decryptionKeyId = null; - associatedData = null; - if (!PublicKeyProtoUtil.isLegacyCryptoRequired()) { - KeyPair ecKeyPair = PublicKeyProtoUtil.generateEcP256KeyPair(); - ecPublicKey = ecKeyPair.getPublic(); - ecPrivateKey = ecKeyPair.getPrivate(); - } - KeyPair rsaKeyPair = PublicKeyProtoUtil.generateRSA2048KeyPair(); - rsaPublicKey = rsaKeyPair.getPublic(); - rsaPrivateKey = rsaKeyPair.getPrivate(); - try { - aesEncryptionKey = makeAesKey(); - hmacKey = makeAesKey(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - fail(); - } - secureMessageBuilder.reset(); - } - - private SecureMessage sign(SigType sigType) throws NoSuchAlgorithmException, InvalidKeyException { - return getPreconfiguredBuilder().buildSignedCleartextMessage( - getSigningKeyFor(sigType), sigType, message); - } - - private SecureMessage signCrypt(SigType sigType, EncType encType) - throws NoSuchAlgorithmException, InvalidKeyException { - return getPreconfiguredBuilder().buildSignCryptedMessage( - getSigningKeyFor(sigType), sigType, aesEncryptionKey, encType, message); - } - - private void verify(SecureMessage signed, SigType sigType) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, - InvalidProtocolBufferException { - HeaderAndBody headerAndBody = SecureMessageParser.parseSignedCleartextMessage( - signed, - getVerificationKeyFor(sigType), - sigType, - associatedData); - consistencyCheck(signed, headerAndBody, sigType, EncType.NONE); - } - - private void verifyDecrypt(SecureMessage encryptedAndSigned, SigType sigType, EncType encType) - throws InvalidProtocolBufferException, InvalidKeyException, NoSuchAlgorithmException, - SignatureException { - HeaderAndBody headerAndBody = SecureMessageParser.parseSignCryptedMessage( - encryptedAndSigned, - getVerificationKeyFor(sigType), - sigType, - aesEncryptionKey, - encType, - associatedData); - consistencyCheck(encryptedAndSigned, headerAndBody, sigType, encType); - } - - // A collection of different kinds of "alterations" that can be made to SecureMessage protos. - enum Alteration { - DECRYPTION_KEY_ID, - ENCTYPE, - HEADER_AND_BODY_PROTO, - MESSAGE, - METADATA, - RESIGNCRYPTION_ATTACK, - SIGTYPE, - VERIFICATION_KEY_ID, - ASSOCIATED_DATA_LENGTH, - } - - private void doSignAndVerify(SigType sigType) throws Exception { - System.out.println("BEGIN_TEST -- Testing SigType: " + sigType + " with:"); - System.out.println("VerificationKeyId: " + Arrays.toString(verificationKeyId)); - System.out.println("Metadata: " + Arrays.toString(metadata)); - System.out.println("AssociatedData: " + Arrays.toString(associatedData)); - System.out.println("Message: " + Arrays.toString(message)); - // Positive test cases - SecureMessage signed = sign(sigType); - verify(signed, sigType); - - // Negative test cases - for (Alteration altType : getAlterationsToTest()) { - System.out.println("Testing alteration: " + altType.toString()); - SecureMessage modified = modifyMessage(signed, altType); - try { - verify(modified, sigType); - fail(altType.toString()); - } catch (SignatureException e) { - // We expect this - } - } - - // Try verifying with the wrong associated data - if ((associatedData == null) || (associatedData.length == 0)) { - associatedData = ZERO_BYTE; - } else { - associatedData = null; - } - try { - verify(signed, sigType); - fail("Expected verification to fail due to incorrect associatedData"); - } catch (SignatureException e) { - // We expect this - } - - System.out.println("PASS_TEST -- Testing SigType: " + sigType); - } - - private List<Alteration> getAlterationsToTest() { - if (isRunningInAndroid()) { - // Android is very slow. Only try one alteration attack, intead of all of them. - int randomAlteration = Math.abs(rng.nextInt()) % Alteration.values().length; - return Arrays.asList(Alteration.values()[randomAlteration]); - } else { - // Just try all of them - return Arrays.asList(Alteration.values()); - } - } - - private void doSignCryptAndVerifyDecrypt(SigType sigType) throws Exception { - // For now, EncType is always AES_256_CBC - EncType encType = EncType.AES_256_CBC; - System.out.println("BEGIN_TEST -- Testing SigType: " + sigType - + " EncType: " + encType + " with:"); - System.out.println("DecryptionKeyId: " + Arrays.toString(decryptionKeyId)); - System.out.println("VerificationKeyId: " + Arrays.toString(verificationKeyId)); - System.out.println("Metadata: " + Arrays.toString(metadata)); - System.out.println("AssociatedData: " + Arrays.toString(associatedData)); - System.out.println("Message: " + Arrays.toString(message)); - SecureMessage encryptedAndSigned = null; - encryptedAndSigned = signCrypt(sigType, encType); - verifyDecrypt(encryptedAndSigned, sigType, encType); - - // Negative test cases - for (Alteration altType : getAlterationsToTest()) { - if (skipAlterationTestFor(altType, sigType)) { - System.out.println("Skipping alteration test: " + altType.toString()); - continue; - } - - System.out.println("Testing alteration: " + altType.toString()); - SecureMessage modified = modifyMessage(encryptedAndSigned, altType); - try { - verifyDecrypt(modified, sigType, encType); - fail(); - } catch (SignatureException e) { - // We expect this - } - } - System.out.println("PASS_TEST -- Testing SigType: " + sigType + " EncType: " + encType); - } - - private boolean skipAlterationTestFor(Alteration altType, SigType sigType) { - // The RESIGNCRYPTION_ATTACK may be allowed to succeed iff the same symmetric key - // is being reused for both signature and encryption. - return (altType == Alteration.RESIGNCRYPTION_ATTACK) - // Intentionally testing equality of object address here - && (getVerificationKeyFor(sigType) == aesEncryptionKey); - } - - private SecureMessage modifyMessage(SecureMessage original, Alteration altType) throws Exception { - ByteString bogus = ByteString.copyFromUtf8("BOGUS"); - HeaderAndBody origHAB = HeaderAndBody.parseFrom(original.getHeaderAndBody()); - HeaderAndBody.Builder newHAB = HeaderAndBody.newBuilder(origHAB); - Header.Builder newHeader = Header.newBuilder(origHAB.getHeader()); - Header origHeader = origHAB.getHeader(); - SecureMessage.Builder result = SecureMessage.newBuilder(original); - switch (altType) { - case DECRYPTION_KEY_ID: - if (origHeader.hasDecryptionKeyId()) { - newHeader.clearDecryptionKeyId(); - } else { - newHeader.setDecryptionKeyId(ByteString.copyFrom(TEST_KEY_ID)); - } - break; - case ENCTYPE: - if (origHeader.getEncryptionScheme() == SecureMessageProto.EncScheme.NONE) { - newHeader.setEncryptionScheme(SecureMessageProto.EncScheme.AES_256_CBC); - } else { - newHeader.setEncryptionScheme(SecureMessageProto.EncScheme.NONE); - } - break; - case HEADER_AND_BODY_PROTO: - // Substitute a junk byte string instead of the HeeaderAndBody proto message - return result.setHeaderAndBody(bogus).build(); - case MESSAGE: - byte[] origBody = origHAB.getBody().toByteArray(); - if (origBody.length > 0) { - // Lop off trailing byte of the body - byte[] truncatedBody = CryptoOps.subarray(origBody, 0, origBody.length - 1); - newHAB.setBody(ByteString.copyFrom(truncatedBody)); - } else { - newHAB.setBody(bogus); - } - break; - case METADATA: - if (origHeader.hasPublicMetadata()) { - newHeader.clearPublicMetadata(); - } else { - newHeader.setPublicMetadata(bogus); - } - break; - case RESIGNCRYPTION_ATTACK: - // Simulate stripping a signature, and re-signing a message to see if it will be decrypted. - newHeader - .setVerificationKeyId(bogus) - // In case original was cleartext - .setEncryptionScheme(SecureMessageProto.EncScheme.AES_256_CBC); - - // Now that we've mildly changed the header, compute a new signature for it. - newHAB.setHeader(newHeader.build()); - byte[] headerAndBodyBytes = newHAB.build().toByteArray(); - result.setHeaderAndBody(ByteString.copyFrom(headerAndBodyBytes)); - SigType sigType = SigType.valueOf(origHeader.getSignatureScheme()); - // Note that in all cases where this attack applies, the associatedData is not normally - // used directly inside the signature (but rather inside the inner ciphertext). - result.setSignature(ByteString.copyFrom(CryptoOps.sign( - sigType, getSigningKeyFor(sigType), rng, headerAndBodyBytes))); - return result.build(); - case SIGTYPE: - if (origHeader.getSignatureScheme() == SecureMessageProto.SigScheme.ECDSA_P256_SHA256) { - newHeader - .setSignatureScheme(SecureMessageProto.SigScheme.HMAC_SHA256); - } else { - newHeader - .setSignatureScheme(SecureMessageProto.SigScheme.ECDSA_P256_SHA256); - } - break; - case VERIFICATION_KEY_ID: - if (origHeader.hasVerificationKeyId()) { - newHeader.clearVerificationKeyId(); - } else { - newHeader.setVerificationKeyId( - ByteString.copyFrom(TEST_KEY_ID)); - } - break; - case ASSOCIATED_DATA_LENGTH: - int adLength = origHeader.getAssociatedDataLength(); - switch (adLength) { - case 0: - newHeader.setAssociatedDataLength(1); - break; - case 1: - newHeader.setAssociatedDataLength(0); - break; - default: - newHeader.setAssociatedDataLength(adLength - 1); - } - break; - default: - fail("Forgot to implement an alteration attack: " + altType); - break; - } - // Set the header. - newHAB.setHeader(newHeader.build()); - - return result.setHeaderAndBody(ByteString.copyFrom(newHAB.build().toByteArray())) - .build(); - } - - public void testEcDsaSignedOnly() throws Exception { - doTestSignedOnly(SigType.ECDSA_P256_SHA256); - } - - public void testRsaSignedOnly() throws Exception { - doTestSignedOnly(SigType.RSA2048_SHA256); - } - - public void testHmacSignedOnly() throws Exception { - doTestSignedOnly(SigType.HMAC_SHA256); - } - - private void doTestSignedOnly(SigType sigType) throws Exception { - if (isUnsupported(sigType)) { - return; - } - - // decryptionKeyId must be left null for signature-only operation - for (byte[] vkId : KEY_ID_VALUES) { - verificationKeyId = vkId; - for (byte[] md : METADATA_VALUES) { - metadata = md; - for (byte[] ad : ASSOCIATED_DATA_VALUES) { - associatedData = ad; - for (byte[] msg : MESSAGE_VALUES) { - message = msg; - doSignAndVerify(sigType); - } - } - } - } - - // Test that use of a DecryptionKeyId is not allowed for signature-only - try { - decryptionKeyId = TEST_KEY_ID; // Should trigger a failure - doSignAndVerify(sigType); - fail(); - } catch (IllegalStateException expected) { - } -} - - public void testEncryptedAndMACed() throws Exception { - for (byte[] dkId : KEY_ID_VALUES) { - decryptionKeyId = dkId; - for (byte[] vkId : KEY_ID_VALUES) { - verificationKeyId = vkId; - for (byte[] md : METADATA_VALUES) { - metadata = md; - for (byte[] ad : ASSOCIATED_DATA_VALUES) { - associatedData = ad; - for (byte[] msg : MESSAGE_VALUES) { - message = msg; - doSignCryptAndVerifyDecrypt(SigType.HMAC_SHA256); - } - } - } - } - } - } - - public void testEncryptedAndMACedWithSameKey() throws Exception { - hmacKey = aesEncryptionKey; // Re-use the same key for both - testEncryptedAndMACed(); - } - - public void testEncryptedAndEcdsaSigned() throws Exception { - doTestEncryptedAndSigned(SigType.ECDSA_P256_SHA256); - } - - public void testEncryptedAndRsaSigned() throws Exception { - doTestEncryptedAndSigned(SigType.RSA2048_SHA256); - } - - public void doTestEncryptedAndSigned(SigType sigType) throws Exception { - if (isUnsupported(sigType)) { - return; // EC operations aren't supported on older Android releases - } - - for (byte[] dkId : KEY_ID_VALUES) { - decryptionKeyId = dkId; - for (byte[] vkId : KEY_ID_VALUES) { - verificationKeyId = vkId; - if ((verificationKeyId == null) && sigType.isPublicKeyScheme()) { - continue; // Null verificationKeyId is not allowed with public key signcryption - } - for (byte[] md : METADATA_VALUES) { - metadata = md; - for (byte[] ad : ASSOCIATED_DATA_VALUES) { - associatedData = ad; - for (byte[] msg : MESSAGE_VALUES) { - message = msg; - doSignCryptAndVerifyDecrypt(sigType); - } - } - } - } - } - - // Verify that a missing verificationKeyId is not allowed here - try { - verificationKeyId = null; // Should trigger a failure - signCrypt(sigType, EncType.AES_256_CBC); - fail(); - } catch (IllegalStateException expected) { - } - } - - public void testSignCryptionRequiresEncryption() throws Exception { - try { - signCrypt(SigType.RSA2048_SHA256, EncType.NONE); - } catch (IllegalArgumentException expected) { - } - } - - public void testAssociatedData() throws Exception { - // How much extra room might the encoding of AssociatedDataLength take up? - int maxAssociatedDataOverheadBytes = 4; - // How many bytes might normally vary in the encoding length for SecureMessages generated with - // fresh randomness but identical contents (e.g., due to MSBs being 0) - int maxJitter = 2; - verificationKeyId = TEST_KEY_ID; // So that public key signcryption will work - message = TEST_MESSAGE; - - for (SigType sigType : SigType.values()) { - if (isUnsupported(sigType)) { - continue; - } - associatedData = null; - SecureMessage signed = sign(sigType); - int signedLength = signed.toByteArray().length; - associatedData = EMPTY_BYTES; - // Check that EMPTY_BYTES is equivalent to null associated data under verification - verify(signed, sigType); - // We already tested that incorrect associated data fails elsewhere in negative test cases - associatedData = TEST_ASSOCIATED_DATA; - SecureMessage signedWithAssociatedData = sign(sigType); - int signedWithAssociatedDataLength = signedWithAssociatedData.toByteArray().length; - String logInfo = "Testing associated data overhead for signature using: " + sigType - + " signedLength=" + signedLength - + " signedWithAssociatedDataLength=" + signedWithAssociatedDataLength; - System.out.println(logInfo); - assertTrue(logInfo, - signedWithAssociatedData.toByteArray().length - <= signed.toByteArray().length + maxAssociatedDataOverheadBytes + maxJitter); - } - - for (SigType sigType : SigType.values()) { - if (isUnsupported(sigType)) { - continue; - } - associatedData = null; - SecureMessage signCrypted = signCrypt(sigType, EncType.AES_256_CBC); - int signCryptedLength = signCrypted.toByteArray().length; - // Check that EMPTY_BYTES is equivalent to null associated data under verification - associatedData = EMPTY_BYTES; - verifyDecrypt(signCrypted, sigType, EncType.AES_256_CBC); - // We already tested that incorrect associated data fails elsewhere in negative test cases - associatedData = TEST_ASSOCIATED_DATA; - SecureMessage signCryptedWithAssociatedData = signCrypt(sigType, EncType.AES_256_CBC); - int signCryptedWithAssociatedDataLength = signCryptedWithAssociatedData.toByteArray().length; - String logInfo = "Testing associated data overhead for signcryption using: " + sigType - + " signCryptedLength=" + signCryptedLength - + " signCryptedWithAssociatedDataLength=" + signCryptedWithAssociatedDataLength; - System.out.println(logInfo); - assertTrue(logInfo, - signCryptedWithAssociatedData.toByteArray().length - <= signCrypted.toByteArray().length + maxAssociatedDataOverheadBytes + maxJitter); - } - } - - public void testEncryptedAndEcdsaSignedUsingPublicKeyProto() throws Exception { - if (isUnsupported(SigType.ECDSA_P256_SHA256)) { - return; - } - - // Safest usage of SignCryption is to set the VerificationKeyId to an actual representation of - // the verification key. - verificationKeyId = PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey).toByteArray(); - SecureMessage encryptedAndSigned = signCrypt(SigType.ECDSA_P256_SHA256, EncType.AES_256_CBC); - - // Simulate extracting the verification key ID from the SecureMessage (non-standard usage) - ecPublicKey = - PublicKeyProtoUtil.parseEcPublicKey( - EcP256PublicKey.parseFrom( - SecureMessageParser.getUnverifiedHeader(encryptedAndSigned) - .getVerificationKeyId())); - - // Note that this verification uses the encoded/decoded ecPublicKey value - verifyDecrypt(encryptedAndSigned, SigType.ECDSA_P256_SHA256, EncType.AES_256_CBC); - } - - public void testEncryptedAndRsaSignedUsingPublicKeyProto() throws Exception { - // Safest usage of SignCryption is to set the VerificationKeyId to an actual representation of - // the verification key. - verificationKeyId = PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey).toByteArray(); - SecureMessage encryptedAndSigned = signCrypt(SigType.RSA2048_SHA256, EncType.AES_256_CBC); - - // Simulate extracting the verification key ID from the SecureMessage (non-standard usage) - rsaPublicKey = - PublicKeyProtoUtil.parseRsa2048PublicKey( - SimpleRsaPublicKey.parseFrom( - SecureMessageParser.getUnverifiedHeader(encryptedAndSigned) - .getVerificationKeyId())); - - // Note that this verification uses the encoded/decoded SimpleRsaPublicKey value - verifyDecrypt(encryptedAndSigned, SigType.RSA2048_SHA256, EncType.AES_256_CBC); - } - - // TODO(shabsi): The test was only corrupting header but wasn't setting the body. With protolite, - // not setting a required field causes problems. Modify the SecureMessageParser test and - // enable/remove this test. - /* - public void testCorruptUnverifiedHeader() throws Exception { - // Create a sample message - SecureMessage original = signCrypt(SigType.HMAC_SHA256, EncType.AES_256_CBC); - HeaderAndBody originalHAB = HeaderAndBody.parseFrom(original.getHeaderAndBody().toByteArray()); - for (CorruptHeaderType corruptionType : CorruptHeaderType.values()) { - // Mess with the HeaderAndBody field - HeaderAndBody.Builder corruptHAB = HeaderAndBody.newBuilder(originalHAB); - try { - corruptHeaderWith(corruptionType, corruptHAB); - // Construct the corrupted message using the modified HeaderAndBody - SecureMessage.Builder corrupt = SecureMessage.newBuilder(original); - corrupt.setHeaderAndBody(ByteString.copyFrom(corruptHAB.build().toByteArray())).build(); - SecureMessageParser.getUnverifiedHeader(corrupt.build()); - fail("Corrupt header type " + corruptionType + " parsed without error"); - } catch (InvalidProtocolBufferException expected) { - } - } - } - */ - - public void testParseEmptyMessage() throws Exception { - byte[] bogusData = new byte[0]; - - try { - SecureMessageParser.parseSignedCleartextMessage( - SecureMessage.parseFrom(bogusData), - aesEncryptionKey, - SigType.HMAC_SHA256); - fail("Empty message verified without error"); - } catch (SignatureException | UninitializedMessageException - | InvalidProtocolBufferException expected) { - } - } - - public void testParseKeyInvalidInputs() throws Exception { - GenericPublicKey[] badKeys = new GenericPublicKey[] { - GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.EC_P256).build(), - GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.RSA2048).build(), - GenericPublicKey.newBuilder().setType(SecureMessageProto.PublicKeyType.DH2048_MODP).build(), - }; - for (int i = 0; i < badKeys.length; i++) { - GenericPublicKey key = badKeys[i]; - try { - PublicKeyProtoUtil.parsePublicKey(key); - fail(String.format("%sth key was parsed without exceptions", i)); - } catch (InvalidKeySpecException expected) { - } - } - } - - enum CorruptHeaderType { - EMPTY, - // TODO(shabsi): Remove these test cases and modify code in SecureMessageParser appropriately. - // UNSET, - // JUNK, - } - - private void corruptHeaderWith(CorruptHeaderType corruptionType, - HeaderAndBody.Builder protoToModify) { - switch (corruptionType) { - case EMPTY: - protoToModify.setHeader(Header.getDefaultInstance()); - break; - /* - case JUNK: - Header.Builder junk = Header.newBuilder(); - junk.setDecryptionKeyId(ByteString.copyFromUtf8("fooooo")); - junk.setIv(ByteString.copyFromUtf8("bar")); - // Don't set signature scheme. - junk.setVerificationKeyId(ByteString.copyFromUtf8("bazzzzz")); - protoToModify.setHeader(junk.build()); - break; - case UNSET: - protoToModify.clearHeader(); - break; - */ - default: - throw new RuntimeException("Broken test code"); - } - } - - private void consistencyCheck( - SecureMessage secmsg, HeaderAndBody headerAndBody, SigType sigType, EncType encType) - throws InvalidProtocolBufferException { - Header header = SecureMessageParser.getUnverifiedHeader(secmsg); - checkHeader(header, sigType, encType); // Checks that the "unverified header" looks right - checkHeaderAndBody(header, headerAndBody); // Matches header vs. the "verified" headerAndBody - } - - private Header checkHeader(Header header, SigType sigType, EncType encType) { - assertEquals(sigType.getSigScheme(), header.getSignatureScheme()); - assertEquals(encType.getEncScheme(), header.getEncryptionScheme()); - checkKeyIdsAndMetadata(verificationKeyId, decryptionKeyId, metadata, associatedData, header); - return header; - } - - private void checkHeaderAndBody(Header header, HeaderAndBody headerAndBody) { - assertTrue(header.equals(headerAndBody.getHeader())); - assertTrue(Arrays.equals(message, headerAndBody.getBody().toByteArray())); - } - - private void checkKeyIdsAndMetadata(byte[] verificationKeyId, byte[] decryptionKeyId, - byte[] metadata, byte[] associatedData, Header header) { - if (verificationKeyId == null) { - assertFalse(header.hasVerificationKeyId()); - } else { - assertTrue(Arrays.equals(verificationKeyId, header.getVerificationKeyId().toByteArray())); - } - if (decryptionKeyId == null) { - assertFalse(header.hasDecryptionKeyId()); - } else { - assertTrue(Arrays.equals(decryptionKeyId, header.getDecryptionKeyId().toByteArray())); - } - if (metadata == null) { - assertFalse(header.hasPublicMetadata()); - } else { - assertTrue(Arrays.equals(metadata, header.getPublicMetadata().toByteArray())); - } - if (associatedData == null) { - assertFalse(header.hasAssociatedDataLength()); - } else { - assertEquals(associatedData.length, header.getAssociatedDataLength()); - } - } - - private SecretKey makeAesKey() throws NoSuchAlgorithmException { - KeyGenerator aesKeygen = KeyGenerator.getInstance("AES"); - aesKeygen.init(256); - return aesKeygen.generateKey(); - } - - private Key getSigningKeyFor(SigType sigType) { - if (sigType == SigType.ECDSA_P256_SHA256) { - return ecPrivateKey; - } - if (sigType == SigType.RSA2048_SHA256) { - return rsaPrivateKey; - } - if (sigType == SigType.HMAC_SHA256) { - return hmacKey; - } - return null; // This should not happen - } - - private Key getVerificationKeyFor(SigType sigType) { - try { - if (sigType == SigType.ECDSA_P256_SHA256) { - return PublicKeyProtoUtil.parseEcPublicKey( - PublicKeyProtoUtil.encodeEcPublicKey(ecPublicKey)); - } - if (sigType == SigType.RSA2048_SHA256) { - return PublicKeyProtoUtil.parseRsa2048PublicKey( - PublicKeyProtoUtil.encodeRsa2048PublicKey(rsaPublicKey)); - } - } catch (InvalidKeySpecException e) { - throw new AssertionError(e); - } - - assertFalse(sigType.isPublicKeyScheme()); - // For symmetric key schemes - return getSigningKeyFor(sigType); - } - - private SecureMessageBuilder getPreconfiguredBuilder() { - // Re-use a single instance of SecureMessageBuilder for efficiency. - SecureMessageBuilder builder = secureMessageBuilder.reset(); - if (verificationKeyId != null) { - builder.setVerificationKeyId(verificationKeyId); - } - if (decryptionKeyId != null) { - builder.setDecryptionKeyId(decryptionKeyId); - } - if (metadata != null) { - builder.setPublicMetadata(metadata); - } - if (associatedData != null) { - builder.setAssociatedData(associatedData); - } - return builder; - } - - private static boolean isUnsupported(SigType sigType) { - // EC operations aren't supported on older Android releases - return PublicKeyProtoUtil.isLegacyCryptoRequired() - && (sigType == SigType.ECDSA_P256_SHA256); - } - - private static boolean isRunningInAndroid() { - try { - ClassLoader.getSystemClassLoader().loadClass("android.os.Build$VERSION"); - return true; - } catch (ClassNotFoundException e) { - // Not running on Android - return false; - } - } -} |