blob: c77de7959e964c2faa59b65c4b5246532c93d35f [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* 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 android.security.identity.cts;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.security.cert.X509Certificate;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import co.nstant.in.cbor.CborBuilder;
import co.nstant.in.cbor.CborEncoder;
import co.nstant.in.cbor.CborException;
import co.nstant.in.cbor.builder.ArrayBuilder;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DoublePrecisionFloat;
import co.nstant.in.cbor.model.HalfPrecisionFloat;
import co.nstant.in.cbor.model.NegativeInteger;
import co.nstant.in.cbor.model.SimpleValue;
import co.nstant.in.cbor.model.SimpleValueType;
import co.nstant.in.cbor.model.SinglePrecisionFloat;
import co.nstant.in.cbor.model.UnicodeString;
import co.nstant.in.cbor.model.UnsignedInteger;
public class UtilUnitTests {
@Test
public void prettyPrintMultipleCompleteTypes() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new CborBuilder()
.add("text") // add string
.add(1234) // add integer
.add(new byte[]{0x10}) // add byte array
.addArray() // add array
.add(1)
.add("text")
.end()
.build());
assertEquals("'text',\n"
+ "1234,\n"
+ "[0x10],\n"
+ "[1, 'text']", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintString() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new UnicodeString("foobar"));
assertEquals("'foobar'", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintBytestring() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new ByteString(new byte[]{1, 2, 33, (byte) 254}));
assertEquals("[0x01, 0x02, 0x21, 0xfe]", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintUnsignedInteger() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new UnsignedInteger(42));
assertEquals("42", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintNegativeInteger() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new NegativeInteger(-42));
assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintDouble() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new DoublePrecisionFloat(1.1));
assertEquals("1.1", Util.cborPrettyPrint(baos.toByteArray()));
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new DoublePrecisionFloat(-42.0000000001));
assertEquals("-42.0000000001", Util.cborPrettyPrint(baos.toByteArray()));
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new DoublePrecisionFloat(-5));
assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintFloat() throws CborException {
ByteArrayOutputStream baos;
// TODO: These two tests yield different results on different devices, disable for now
/*
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SinglePrecisionFloat(1.1f));
assertEquals("1.100000023841858", Util.cborPrettyPrint(baos.toByteArray()));
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SinglePrecisionFloat(-42.0001f));
assertEquals("-42.000099182128906", Util.cborPrettyPrint(baos.toByteArray()));
*/
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SinglePrecisionFloat(-5f));
assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintHalfFloat() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new HalfPrecisionFloat(1.1f));
assertEquals("1.099609375", Util.cborPrettyPrint(baos.toByteArray()));
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new HalfPrecisionFloat(-42.0001f));
assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new HalfPrecisionFloat(-5f));
assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintFalse() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.FALSE));
assertEquals("false", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintTrue() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.TRUE));
assertEquals("true", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintNull() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.NULL));
assertEquals("null", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintUndefined() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.UNDEFINED));
assertEquals("undefined", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintTag() throws CborException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new CborBuilder()
.addTag(0)
.add("ABC")
.build());
byte[] data = baos.toByteArray();
assertEquals("tag 0 'ABC'", Util.cborPrettyPrint(data));
}
@Test
public void prettyPrintArrayNoCompounds() throws CborException {
// If an array has no compound elements, no newlines are used.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new CborBuilder()
.addArray() // add array
.add(1)
.add("text")
.add(new ByteString(new byte[]{1, 2, 3}))
.end()
.build());
assertEquals("[1, 'text', [0x01, 0x02, 0x03]]", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintArray() throws CborException {
// This array contains a compound value so will use newlines
CborBuilder array = new CborBuilder();
ArrayBuilder<CborBuilder> arrayBuilder = array.addArray();
arrayBuilder.add(2);
arrayBuilder.add(3);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new CborBuilder()
.addArray() // add array
.add(1)
.add("text")
.add(new ByteString(new byte[]{1, 2, 3}))
.add(array.build().get(0))
.end()
.build());
assertEquals("[\n"
+ " 1,\n"
+ " 'text',\n"
+ " [0x01, 0x02, 0x03],\n"
+ " [2, 3]\n"
+ "]", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void prettyPrintMap() throws CborException {
// If an array has no compound elements, no newlines are used.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new CborEncoder(baos).encode(new CborBuilder()
.addMap()
.put("Foo", 42)
.put("Bar", "baz")
.put(43, 44)
.put(new UnicodeString("bstr"), new ByteString(new byte[]{1, 2, 3}))
.put(new ByteString(new byte[]{1, 2, 3}), new UnicodeString("other way"))
.end()
.build());
assertEquals("{\n"
+ " 43 : 44,\n"
+ " [0x01, 0x02, 0x03] : 'other way',\n"
+ " 'Bar' : 'baz',\n"
+ " 'Foo' : 42,\n"
+ " 'bstr' : [0x01, 0x02, 0x03]\n"
+ "}", Util.cborPrettyPrint(baos.toByteArray()));
}
@Test
public void cborEncodeDecode() {
// TODO: add better coverage and check specific encoding etc.
assertEquals(42, Util.cborDecodeInt(Util.cborEncodeInt(42)));
assertEquals(123456, Util.cborDecodeInt(Util.cborEncodeInt(123456)));
assertFalse(Util.cborDecodeBoolean(Util.cborEncodeBoolean(false)));
assertTrue(Util.cborDecodeBoolean(Util.cborEncodeBoolean(true)));
}
private KeyPair coseGenerateKeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
KeyGenParameterSpec.Builder builder =
new KeyGenParameterSpec.Builder(
"coseTestKeyPair",
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
kpg.initialize(builder.build());
return kpg.generateKeyPair();
}
@Test
public void coseSignAndVerify() throws Exception {
KeyPair keyPair = coseGenerateKeyPair();
byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13};
byte[] detachedContent = new byte[] {};
byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
assertArrayEquals(data, Util.coseSign1GetData(sig));
assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig));
}
@Test
public void coseSignAndVerifyDetachedContent() throws Exception {
KeyPair keyPair = coseGenerateKeyPair();
byte[] data = new byte[] {};
byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
assertArrayEquals(data, Util.coseSign1GetData(sig));
assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig));
}
@Test
public void coseSignAndVerifySingleCertificate() throws Exception {
KeyPair keyPair = coseGenerateKeyPair();
byte[] data = new byte[] {};
byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
ArrayList<X509Certificate> certs = new ArrayList() {};
certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
assertArrayEquals(data, Util.coseSign1GetData(sig));
assertEquals(certs, Util.coseSign1GetX5Chain(sig));
}
@Test
public void coseSignAndVerifyMultipleCertificates() throws Exception {
KeyPair keyPair = coseGenerateKeyPair();
byte[] data = new byte[] {};
byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
ArrayList<X509Certificate> certs = new ArrayList() {};
certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
assertArrayEquals(data, Util.coseSign1GetData(sig));
assertEquals(certs, Util.coseSign1GetX5Chain(sig));
}
@Test
public void coseMac0() throws Exception {
SecretKey secretKey = new SecretKeySpec(new byte[32], "");
byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13};
byte[] detachedContent = new byte[] {};
byte[] mac = Util.coseMac0(secretKey, data, detachedContent);
assertEquals("[\n"
+ " [0xa1, 0x01, 0x05],\n"
+ " {},\n"
+ " [0x10, 0x11, 0x12, 0x13],\n"
+ " [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
+ "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
+ "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
+ "]", Util.cborPrettyPrint(mac));
}
@Test
public void coseMac0DetachedContent() throws Exception {
SecretKey secretKey = new SecretKeySpec(new byte[32], "");
byte[] data = new byte[] {};
byte[] detachedContent = new byte[] {0x10, 0x11, 0x12, 0x13};
byte[] mac = Util.coseMac0(secretKey, data, detachedContent);
// Same HMAC as in coseMac0 test, only difference is that payload is null.
assertEquals("[\n"
+ " [0xa1, 0x01, 0x05],\n"
+ " {},\n"
+ " null,\n"
+ " [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
+ "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
+ "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
+ "]", Util.cborPrettyPrint(mac));
}
@Test
public void replaceLineTest() {
assertEquals("foo",
Util.replaceLine("Hello World", 0, "foo"));
assertEquals("foo\n",
Util.replaceLine("Hello World\n", 0, "foo"));
assertEquals("Hello World",
Util.replaceLine("Hello World", 1, "foo"));
assertEquals("Hello World\n",
Util.replaceLine("Hello World\n", 1, "foo"));
assertEquals("foo\ntwo\nthree",
Util.replaceLine("one\ntwo\nthree", 0, "foo"));
assertEquals("one\nfoo\nthree",
Util.replaceLine("one\ntwo\nthree", 1, "foo"));
assertEquals("one\ntwo\nfoo",
Util.replaceLine("one\ntwo\nthree", 2, "foo"));
assertEquals("one\ntwo\nfoo",
Util.replaceLine("one\ntwo\nthree", -1, "foo"));
assertEquals("one\ntwo\nthree\nfoo",
Util.replaceLine("one\ntwo\nthree\nfour", -1, "foo"));
assertEquals("one\ntwo\nfoo\nfour",
Util.replaceLine("one\ntwo\nthree\nfour", -2, "foo"));
}
}