blob: 7db5059d0a0f344a52c755faf327a7ace12cadb7 [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// 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
//
// http://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.crypto.tink.apps.webpush;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.google.crypto.tink.HybridDecrypt;
import com.google.crypto.tink.HybridEncrypt;
import com.google.crypto.tink.subtle.EllipticCurves;
import com.google.crypto.tink.subtle.Hex;
import com.google.crypto.tink.subtle.Random;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Set;
import java.util.TreeSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@code WebPushHybridEncrypt}. */
@RunWith(JUnit4.class)
public class WebPushHybridEncryptTest {
@Test
public void testEncryptDecrypt() throws Exception {
for (int i = 0; i < 10; i++) {
KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
byte[] uaPublicKeyBytes =
EllipticCurves.pointEncode(
WebPushConstants.NIST_P256_CURVE_TYPE,
WebPushConstants.UNCOMPRESSED_POINT_FORMAT,
uaPublicKey.getW());
byte[] authSecret = Random.randBytes(16);
HybridEncrypt hybridEncrypt =
new WebPushHybridEncrypt.Builder()
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKeyBytes)
.build();
HybridDecrypt hybridDecrypt =
new WebPushHybridDecrypt.Builder()
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKeyBytes)
.withRecipientPrivateKey(uaPrivateKey)
.build();
Set<String> salts = new TreeSet<String>();
Set<String> ephemeralPublicKeys = new TreeSet<String>();
Set<String> payloads = new TreeSet<String>();
int numTests = 50;
for (int j = 0; j < numTests; j++) {
byte[] plaintext = Random.randBytes(j);
byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
// Checks that the encryption is randomized.
ByteBuffer record = ByteBuffer.wrap(ciphertext);
byte[] salt = new byte[WebPushConstants.SALT_SIZE];
record.get(salt);
salts.add(Hex.encode(salt));
int unused1 = record.getInt();
int unused2 = (int) record.get();
byte[] ephemeralPublicKey = new byte[WebPushConstants.PUBLIC_KEY_SIZE];
record.get(ephemeralPublicKey);
ephemeralPublicKeys.add(Hex.encode(ephemeralPublicKey));
byte[] payload = new byte[ciphertext.length - WebPushConstants.CONTENT_CODING_HEADER_SIZE];
record.get(payload);
payloads.add(Hex.encode(payload));
}
assertEquals(numTests, salts.size());
assertEquals(numTests, ephemeralPublicKeys.size());
assertEquals(numTests, payloads.size());
}
}
@Test
public void testEncryptDecryptWithVaryingRecordSizes() throws Exception {
KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
ECPrivateKey uaPrivateKey = (ECPrivateKey) uaKeyPair.getPrivate();
ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
byte[] authSecret = Random.randBytes(16);
// Test with random, valid record sizes.
{
for (int i = 0; i < 100; i++) {
int recordSize =
WebPushConstants.CIPHERTEXT_OVERHEAD
+ Random.randInt(
WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD);
HybridEncrypt hybridEncrypt =
new WebPushHybridEncrypt.Builder()
.withRecordSize(recordSize)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.build();
HybridDecrypt hybridDecrypt =
new WebPushHybridDecrypt.Builder()
.withRecordSize(recordSize)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.withRecipientPrivateKey(uaPrivateKey)
.build();
byte[] plaintext = Random.randBytes(recordSize - WebPushConstants.CIPHERTEXT_OVERHEAD);
byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
}
}
// Test with largest possible record size.
{
HybridEncrypt hybridEncrypt =
new WebPushHybridEncrypt.Builder()
.withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.build();
HybridDecrypt hybridDecrypt =
new WebPushHybridDecrypt.Builder()
.withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.withRecipientPrivateKey(uaPrivateKey)
.build();
byte[] plaintext =
Random.randBytes(
WebPushConstants.MAX_CIPHERTEXT_SIZE - WebPushConstants.CIPHERTEXT_OVERHEAD);
byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
}
// Test with smallest possible record size.
{
HybridEncrypt hybridEncrypt =
new WebPushHybridEncrypt.Builder()
.withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.build();
HybridDecrypt hybridDecrypt =
new WebPushHybridDecrypt.Builder()
.withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.withRecipientPrivateKey(uaPrivateKey)
.build();
byte[] plaintext = new byte[0];
byte[] ciphertext = hybridEncrypt.encrypt(plaintext, null /* contextInfo */);
assertEquals(ciphertext.length, plaintext.length + WebPushConstants.CIPHERTEXT_OVERHEAD);
assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, null /* contextInfo */));
}
// Test with out of range record sizes.
{
try {
new WebPushHybridEncrypt.Builder()
.withRecordSize(WebPushConstants.MAX_CIPHERTEXT_SIZE + 1)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.build();
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected.
}
try {
new WebPushHybridEncrypt.Builder()
.withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1)
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.build();
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
// expected.
}
}
}
@Test
public void testNonNullContextInfo() throws Exception {
KeyPair uaKeyPair = EllipticCurves.generateKeyPair(WebPushConstants.NIST_P256_CURVE_TYPE);
ECPublicKey uaPublicKey = (ECPublicKey) uaKeyPair.getPublic();
byte[] authSecret = Random.randBytes(16);
HybridEncrypt hybridEncrypt =
new WebPushHybridEncrypt.Builder()
.withAuthSecret(authSecret)
.withRecipientPublicKey(uaPublicKey)
.build();
byte[] plaintext = Random.randBytes(20);
byte[] contextInfo = new byte[0];
try {
byte[] unusedCiphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
fail("Expected GeneralSecurityException");
} catch (GeneralSecurityException ex) {
// expected;
}
}
}