blob: 8bd3cd0102418809e067b51b64257ce257943e71 [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.signature;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.crypto.tink.Config;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.PublicKeySign;
import com.google.crypto.tink.PublicKeyVerify;
import com.google.crypto.tink.TestUtil;
import com.google.crypto.tink.proto.EcdsaKeyFormat;
import com.google.crypto.tink.proto.EcdsaParams;
import com.google.crypto.tink.proto.EcdsaPrivateKey;
import com.google.crypto.tink.proto.EcdsaPublicKey;
import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
import com.google.crypto.tink.proto.EllipticCurveType;
import com.google.crypto.tink.proto.HashType;
import com.google.crypto.tink.proto.KeyData;
import com.google.crypto.tink.proto.KeyTemplate;
import com.google.crypto.tink.subtle.EllipticCurves;
import com.google.crypto.tink.subtle.Random;
import com.google.protobuf.ByteString;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.util.Set;
import java.util.TreeSet;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for EcdsaSignKeyManager.
*
* <p>TODO(quannguyen): Add more tests.
*/
@RunWith(JUnit4.class)
public class EcdsaSignKeyManagerTest {
@BeforeClass
public static void setUp() throws Exception {
Config.register(SignatureConfig.TINK_1_0_0);
;
}
private static class HashAndCurveType {
public HashType hashType;
public EllipticCurveType curveType;
public HashAndCurveType(HashType hashType, EllipticCurveType curveType) {
this.hashType = hashType;
this.curveType = curveType;
}
}
final byte[] msg = Random.randBytes(20);
private void testNewKeyWithVerifier(KeyTemplate keyTemplate) throws Exception {
// Call newKey multiple times and make sure that it generates different keys.
int numTests = 9;
EcdsaPrivateKey[] privKeys = new EcdsaPrivateKey[numTests];
EcdsaSignKeyManager signManager = new EcdsaSignKeyManager();
Set<String> keys = new TreeSet<String>();
for (int j = 0; j < numTests / 3; j++) {
privKeys[3 * j] =
(EcdsaPrivateKey)
signManager.newKey(EcdsaKeyFormat.parseFrom(keyTemplate.getValue()));
keys.add(TestUtil.hexEncode(privKeys[3 * j].toByteArray()));
privKeys[3 * j + 1] = (EcdsaPrivateKey) signManager.newKey(keyTemplate.getValue());
keys.add(TestUtil.hexEncode(privKeys[3 * j + 1].toByteArray()));
privKeys[3 * j + 2] =
EcdsaPrivateKey.parseFrom(
signManager.newKeyData(keyTemplate.getValue()).getValue());
keys.add(TestUtil.hexEncode(privKeys[3 * j + 2].toByteArray()));
}
assertEquals(numTests, keys.size());
// Tests that generated keys have an adequate size. This is best-effort because keys might
// have leading zeros that are stripped off. These tests are flaky; the probability of
// failure is 2^-64 which happens when a key has 8 leading zeros.
for (int j = 0; j < numTests; j++) {
int keySize = privKeys[j].getKeyValue().toByteArray().length;
EcdsaKeyFormat ecdsaKeyFormat = EcdsaKeyFormat.parseFrom(keyTemplate.getValue());
switch (ecdsaKeyFormat.getParams().getCurve()) {
case NIST_P256:
assertTrue(256 / 8 - 8 <= keySize);
assertTrue(256 / 8 + 1 >= keySize);
break;
case NIST_P384:
assertTrue(384 / 8 - 8 <= keySize);
assertTrue(384 / 8 + 1 >= keySize);
break;
case NIST_P521:
assertTrue(521 / 8 - 8 <= keySize);
assertTrue(521 / 8 + 1 >= keySize);
break;
default:
break;
}
}
// Test whether signer works correctly with the corresponding verifier.
EcdsaVerifyKeyManager verifyManager = new EcdsaVerifyKeyManager();
for (int j = 0; j < numTests; j++) {
PublicKeySign signer = signManager.getPrimitive(privKeys[j]);
byte[] signature = signer.sign(msg);
for (int k = 0; k < numTests; k++) {
PublicKeyVerify verifier = verifyManager.getPrimitive(privKeys[k].getPublicKey());
if (j == k) { // The same key
try {
verifier.verify(signature, msg);
} catch (GeneralSecurityException ex) {
fail("Valid signature, should not throw exception");
}
} else { // Different keys
try {
verifier.verify(signature, msg);
fail("Invalid signature, should have thrown exception");
} catch (GeneralSecurityException expected) {
// Expected
}
}
}
}
}
@Test
public void testNewKeyWithVerifier() throws Exception {
testNewKeyWithVerifier(SignatureKeyTemplates.ECDSA_P256);
testNewKeyWithVerifier(SignatureKeyTemplates.ECDSA_P384);
testNewKeyWithVerifier(SignatureKeyTemplates.ECDSA_P521);
testNewKeyWithVerifier(SignatureKeyTemplates.ECDSA_P256_IEEE_P1363);
testNewKeyWithVerifier(SignatureKeyTemplates.ECDSA_P384_IEEE_P1363);
testNewKeyWithVerifier(SignatureKeyTemplates.ECDSA_P521_IEEE_P1363);
}
@Test
public void testNewKeyWithCorruptedFormat() {
ByteString serialized = ByteString.copyFrom(new byte[128]);
KeyTemplate keyTemplate =
KeyTemplate.newBuilder()
.setTypeUrl(EcdsaSignKeyManager.TYPE_URL)
.setValue(serialized)
.build();
EcdsaSignKeyManager keyManager = new EcdsaSignKeyManager();
try {
keyManager.newKey(serialized);
fail("Corrupted format, should have thrown exception");
} catch (GeneralSecurityException expected) {
// Expected
}
try {
keyManager.newKeyData(keyTemplate.getValue());
fail("Corrupted format, should have thrown exception");
} catch (GeneralSecurityException expected) {
// Expected
}
}
private void testNewKeyUnsupportedKeyFormat(HashAndCurveType hashAndCurve) throws Exception {
HashType hashType = hashAndCurve.hashType;
EllipticCurveType curveType = hashAndCurve.curveType;
EcdsaSignKeyManager signManager = new EcdsaSignKeyManager();
EcdsaParams ecdsaParams =
EcdsaParams.newBuilder()
.setHashType(hashType)
.setCurve(curveType)
.setEncoding(EcdsaSignatureEncoding.DER)
.build();
EcdsaKeyFormat ecdsaFormat = EcdsaKeyFormat.newBuilder().setParams(ecdsaParams).build();
try {
EcdsaPrivateKey unusedPrivKey = (EcdsaPrivateKey) signManager.newKey(ecdsaFormat);
fail("Unsupported key format, should have thrown exception: " + hashType + " " + curveType);
} catch (GeneralSecurityException expected) {
// Expected
}
}
@Test
public void testNewKeyUnsupportedKeyFormat() throws Exception {
HashAndCurveType[] hashAndCurves = {
new HashAndCurveType(HashType.SHA1, EllipticCurveType.NIST_P256),
new HashAndCurveType(HashType.SHA1, EllipticCurveType.NIST_P384),
new HashAndCurveType(HashType.SHA1, EllipticCurveType.NIST_P521),
new HashAndCurveType(HashType.SHA256, EllipticCurveType.NIST_P384),
new HashAndCurveType(HashType.SHA256, EllipticCurveType.NIST_P521),
new HashAndCurveType(HashType.SHA512, EllipticCurveType.NIST_P256),
};
for (int i = 0; i < hashAndCurves.length; i++) {
testNewKeyUnsupportedKeyFormat(hashAndCurves[i]);
}
}
private void testGetPrimitiveWithUnsupportedKey(HashAndCurveType hashAndCurve) throws Exception {
HashType hashType = hashAndCurve.hashType;
EllipticCurveType curveType = hashAndCurve.curveType;
KeyPair keyPair = EllipticCurves.generateKeyPair(SigUtil.toCurveType(curveType));
ECPublicKey pubKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privKey = (ECPrivateKey) keyPair.getPrivate();
ECPoint w = pubKey.getW();
EcdsaPublicKey ecdsaPubKey =
TestUtil.createEcdsaPubKey(
hashType,
curveType,
EcdsaSignatureEncoding.DER,
w.getAffineX().toByteArray(),
w.getAffineY().toByteArray());
EcdsaPrivateKey ecdsaPrivKey =
TestUtil.createEcdsaPrivKey(ecdsaPubKey, privKey.getS().toByteArray());
EcdsaSignKeyManager signManager = new EcdsaSignKeyManager();
try {
PublicKeySign unusedSigner = signManager.getPrimitive(ecdsaPrivKey);
fail("Unsupported key, should have thrown exception: " + hashType + " " + curveType);
} catch (GeneralSecurityException expected) {
// Expected
}
}
@Test
public void testGetPrimitiveWithUnsupportedKey() throws Exception {
HashAndCurveType[] hashAndCurves = {
new HashAndCurveType(HashType.SHA1, EllipticCurveType.NIST_P256),
new HashAndCurveType(HashType.SHA1, EllipticCurveType.NIST_P384),
new HashAndCurveType(HashType.SHA1, EllipticCurveType.NIST_P521),
new HashAndCurveType(HashType.SHA256, EllipticCurveType.NIST_P384),
new HashAndCurveType(HashType.SHA256, EllipticCurveType.NIST_P521),
new HashAndCurveType(HashType.SHA512, EllipticCurveType.NIST_P256),
};
for (int i = 0; i < hashAndCurves.length; i++) {
testGetPrimitiveWithUnsupportedKey(hashAndCurves[i]);
}
}
/** Tests that a public key is extracted properly from a private key. */
@Test
public void testGetPublicKeyData() throws Exception {
KeysetHandle privateHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);
KeyData privateKeyData = TestUtil.getKeyset(privateHandle).getKey(0).getKeyData();
EcdsaSignKeyManager privateManager = new EcdsaSignKeyManager();
KeyData publicKeyData = privateManager.getPublicKeyData(privateKeyData.getValue());
assertEquals(EcdsaVerifyKeyManager.TYPE_URL, publicKeyData.getTypeUrl());
assertEquals(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC, publicKeyData.getKeyMaterialType());
EcdsaPrivateKey privateKey = EcdsaPrivateKey.parseFrom(privateKeyData.getValue());
assertArrayEquals(
privateKey.getPublicKey().toByteArray(), publicKeyData.getValue().toByteArray());
EcdsaVerifyKeyManager publicManager = new EcdsaVerifyKeyManager();
PublicKeySign signer = privateManager.getPrimitive(privateKeyData.getValue());
PublicKeyVerify verifier = publicManager.getPrimitive(publicKeyData.getValue());
byte[] message = Random.randBytes(20);
try {
verifier.verify(signer.sign(message), message);
} catch (GeneralSecurityException e) {
fail("Should not fail: " + e);
}
}
}