1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.security.identity.cts;
18 
19 import android.security.identity.ResultData;
20 import android.security.identity.IdentityCredentialStore;
21 
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.content.pm.FeatureInfo;
25 import android.os.SystemProperties;
26 import android.security.keystore.KeyProperties;
27 import android.util.Log;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 import androidx.test.InstrumentationRegistry;
32 
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.math.BigInteger;
37 import java.security.InvalidAlgorithmParameterException;
38 import java.security.InvalidKeyException;
39 import java.security.KeyStore;
40 import java.security.KeyPair;
41 import java.security.KeyPairGenerator;
42 import java.security.MessageDigest;
43 import java.security.NoSuchAlgorithmException;
44 import java.security.PublicKey;
45 import java.security.PrivateKey;
46 import java.security.Signature;
47 import java.security.SignatureException;
48 import java.security.cert.CertificateEncodingException;
49 import java.security.cert.CertificateException;
50 import java.security.cert.CertificateFactory;
51 import java.security.cert.X509Certificate;
52 import java.security.spec.ECGenParameterSpec;
53 import java.text.DecimalFormat;
54 import java.text.DecimalFormatSymbols;
55 import java.text.ParseException;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.Date;
60 import java.util.List;
61 import java.util.Locale;
62 import java.util.Formatter;
63 import java.util.Map;
64 
65 import javax.crypto.KeyAgreement;
66 import javax.crypto.Mac;
67 import javax.crypto.SecretKey;
68 import javax.crypto.spec.SecretKeySpec;
69 
70 import java.security.interfaces.ECPublicKey;
71 import java.security.spec.ECPoint;
72 
73 import org.bouncycastle.asn1.ASN1InputStream;
74 import org.bouncycastle.asn1.ASN1OctetString;
75 
76 import co.nstant.in.cbor.CborBuilder;
77 import co.nstant.in.cbor.CborDecoder;
78 import co.nstant.in.cbor.CborEncoder;
79 import co.nstant.in.cbor.CborException;
80 import co.nstant.in.cbor.builder.ArrayBuilder;
81 import co.nstant.in.cbor.builder.MapBuilder;
82 import co.nstant.in.cbor.model.AbstractFloat;
83 import co.nstant.in.cbor.model.Array;
84 import co.nstant.in.cbor.model.ByteString;
85 import co.nstant.in.cbor.model.DataItem;
86 import co.nstant.in.cbor.model.DoublePrecisionFloat;
87 import co.nstant.in.cbor.model.MajorType;
88 import co.nstant.in.cbor.model.NegativeInteger;
89 import co.nstant.in.cbor.model.SimpleValue;
90 import co.nstant.in.cbor.model.SimpleValueType;
91 import co.nstant.in.cbor.model.SpecialType;
92 import co.nstant.in.cbor.model.UnicodeString;
93 import co.nstant.in.cbor.model.UnsignedInteger;
94 
95 class Util {
96     private static final String TAG = "Util";
97 
98     // Returns 0 if not implemented. Otherwise returns the feature version.
99     //
getFeatureVersion()100     static int getFeatureVersion() {
101         Context appContext = InstrumentationRegistry.getTargetContext();
102         PackageManager pm = appContext.getPackageManager();
103 
104         int featureVersionFromPm = 0;
105         if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
106             FeatureInfo info = null;
107             FeatureInfo[] infos = pm.getSystemAvailableFeatures();
108             for (int n = 0; n < infos.length; n++) {
109                 FeatureInfo i = infos[n];
110                 if (i.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
111                     info = i;
112                     break;
113                 }
114             }
115             if (info != null) {
116                 featureVersionFromPm = info.version;
117             }
118         }
119 
120         // Use of the system feature is not required since Android 12. So for Android 11
121         // return 202009 which is the feature version shipped with Android 11.
122         if (featureVersionFromPm == 0) {
123             IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
124             if (store != null) {
125                 featureVersionFromPm = 202009;
126             }
127         }
128 
129         return featureVersionFromPm;
130     }
131 
canonicalizeCbor(byte[] encodedCbor)132     static byte[] canonicalizeCbor(byte[] encodedCbor) throws CborException {
133         ByteArrayInputStream bais = new ByteArrayInputStream(encodedCbor);
134         List<DataItem> dataItems = new CborDecoder(bais).decode();
135         ByteArrayOutputStream baos = new ByteArrayOutputStream();
136         for(DataItem dataItem : dataItems) {
137             CborEncoder encoder = new CborEncoder(baos);
138             encoder.encode(dataItem);
139         }
140         return baos.toByteArray();
141     }
142 
143 
cborPrettyPrint(byte[] encodedBytes)144     static String cborPrettyPrint(byte[] encodedBytes) throws CborException {
145         StringBuilder sb = new StringBuilder();
146 
147         ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
148         List<DataItem> dataItems = new CborDecoder(bais).decode();
149         int count = 0;
150         for (DataItem dataItem : dataItems) {
151             if (count > 0) {
152                 sb.append(",\n");
153             }
154             cborPrettyPrintDataItem(sb, 0, dataItem);
155             count++;
156         }
157 
158         return sb.toString();
159     }
160 
161     // Returns true iff all elements in |items| are not compound (e.g. an array or a map).
cborAreAllDataItemsNonCompound(List<DataItem> items)162     static boolean cborAreAllDataItemsNonCompound(List<DataItem> items) {
163         for (DataItem item : items) {
164             switch (item.getMajorType()) {
165                 case ARRAY:
166                 case MAP:
167                     return false;
168                 default:
169                     // continue inspecting other data items
170             }
171         }
172         return true;
173     }
174 
cborPrettyPrintDataItem(StringBuilder sb, int indent, DataItem dataItem)175     static void cborPrettyPrintDataItem(StringBuilder sb, int indent, DataItem dataItem) {
176         StringBuilder indentBuilder = new StringBuilder();
177         for (int n = 0; n < indent; n++) {
178             indentBuilder.append(' ');
179         }
180         String indentString = indentBuilder.toString();
181 
182         if (dataItem.hasTag()) {
183             sb.append(String.format("tag %d ", dataItem.getTag().getValue()));
184         }
185 
186         switch (dataItem.getMajorType()) {
187             case INVALID:
188                 // TODO: throw
189                 sb.append("<invalid>");
190                 break;
191             case UNSIGNED_INTEGER: {
192                 // Major type 0: an unsigned integer.
193                 BigInteger value = ((UnsignedInteger) dataItem).getValue();
194                 sb.append(value);
195             }
196             break;
197             case NEGATIVE_INTEGER: {
198                 // Major type 1: a negative integer.
199                 BigInteger value = ((NegativeInteger) dataItem).getValue();
200                 sb.append(value);
201             }
202             break;
203             case BYTE_STRING: {
204                 // Major type 2: a byte string.
205                 byte[] value = ((ByteString) dataItem).getBytes();
206                 sb.append("[");
207                 int count = 0;
208                 for (byte b : value) {
209                     if (count > 0) {
210                         sb.append(", ");
211                     }
212                     sb.append(String.format("0x%02x", b));
213                     count++;
214                 }
215                 sb.append("]");
216             }
217             break;
218             case UNICODE_STRING: {
219                 // Major type 3: string of Unicode characters that is encoded as UTF-8 [RFC3629].
220                 String value = ((UnicodeString) dataItem).getString();
221                 // TODO: escape ' in |value|
222                 sb.append("'" + value + "'");
223             }
224             break;
225             case ARRAY: {
226                 // Major type 4: an array of data items.
227                 List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
228                 if (items.size() == 0) {
229                     sb.append("[]");
230                 } else if (cborAreAllDataItemsNonCompound(items)) {
231                     // The case where everything fits on one line.
232                     sb.append("[");
233                     int count = 0;
234                     for (DataItem item : items) {
235                         cborPrettyPrintDataItem(sb, indent, item);
236                         if (++count < items.size()) {
237                             sb.append(", ");
238                         }
239                     }
240                     sb.append("]");
241                 } else {
242                     sb.append("[\n" + indentString);
243                     int count = 0;
244                     for (DataItem item : items) {
245                         sb.append("  ");
246                         cborPrettyPrintDataItem(sb, indent + 2, item);
247                         if (++count < items.size()) {
248                             sb.append(",");
249                         }
250                         sb.append("\n" + indentString);
251                     }
252                     sb.append("]");
253                 }
254             }
255             break;
256             case MAP: {
257                 // Major type 5: a map of pairs of data items.
258                 Collection<DataItem> keys = ((co.nstant.in.cbor.model.Map) dataItem).getKeys();
259                 if (keys.size() == 0) {
260                     sb.append("{}");
261                 } else {
262                     sb.append("{\n" + indentString);
263                     int count = 0;
264                     for (DataItem key : keys) {
265                         sb.append("  ");
266                         DataItem value = ((co.nstant.in.cbor.model.Map) dataItem).get(key);
267                         cborPrettyPrintDataItem(sb, indent + 2, key);
268                         sb.append(" : ");
269                         cborPrettyPrintDataItem(sb, indent + 2, value);
270                         if (++count < keys.size()) {
271                             sb.append(",");
272                         }
273                         sb.append("\n" + indentString);
274                     }
275                     sb.append("}");
276                 }
277             }
278             break;
279             case TAG:
280                 // Major type 6: optional semantic tagging of other major types
281                 //
282                 // We never encounter this one since it's automatically handled via the
283                 // DataItem that is tagged.
284                 throw new RuntimeException("Semantic tag data item not expected");
285 
286             case SPECIAL:
287                 // Major type 7: floating point numbers and simple data types that need no
288                 // content, as well as the "break" stop code.
289                 if (dataItem instanceof SimpleValue) {
290                     switch (((SimpleValue) dataItem).getSimpleValueType()) {
291                         case FALSE:
292                             sb.append("false");
293                             break;
294                         case TRUE:
295                             sb.append("true");
296                             break;
297                         case NULL:
298                             sb.append("null");
299                             break;
300                         case UNDEFINED:
301                             sb.append("undefined");
302                             break;
303                         case RESERVED:
304                             sb.append("reserved");
305                             break;
306                         case UNALLOCATED:
307                             sb.append("unallocated");
308                             break;
309                     }
310                 } else if (dataItem instanceof DoublePrecisionFloat) {
311                     DecimalFormat df = new DecimalFormat("0",
312                             DecimalFormatSymbols.getInstance(Locale.ENGLISH));
313                     df.setMaximumFractionDigits(340);
314                     sb.append(df.format(((DoublePrecisionFloat) dataItem).getValue()));
315                 } else if (dataItem instanceof AbstractFloat) {
316                     DecimalFormat df = new DecimalFormat("0",
317                             DecimalFormatSymbols.getInstance(Locale.ENGLISH));
318                     df.setMaximumFractionDigits(340);
319                     sb.append(df.format(((AbstractFloat) dataItem).getValue()));
320                 } else {
321                     sb.append("break");
322                 }
323                 break;
324         }
325     }
326 
encodeCbor(List<DataItem> dataItems)327     public static byte[] encodeCbor(List<DataItem> dataItems) {
328         ByteArrayOutputStream baos = new ByteArrayOutputStream();
329         CborEncoder encoder = new CborEncoder(baos);
330         try {
331             encoder.encode(dataItems);
332         } catch (CborException e) {
333             throw new RuntimeException("Error encoding data", e);
334         }
335         return baos.toByteArray();
336     }
337 
coseBuildToBeSigned(byte[] encodedProtectedHeaders, byte[] payload, byte[] detachedContent)338     public static byte[] coseBuildToBeSigned(byte[] encodedProtectedHeaders,
339             byte[] payload,
340             byte[] detachedContent) {
341         CborBuilder sigStructure = new CborBuilder();
342         ArrayBuilder<CborBuilder> array = sigStructure.addArray();
343 
344         array.add("Signature1");
345         array.add(encodedProtectedHeaders);
346 
347         // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
348         // so external_aad is the empty bstr
349         byte emptyExternalAad[] = new byte[0];
350         array.add(emptyExternalAad);
351 
352         // Next field is the payload, independently of how it's transported (RFC
353         // 8152 section 4.4). Since our API specifies only one of |data| and
354         // |detachedContent| can be non-empty, it's simply just the non-empty one.
355         if (payload != null && payload.length > 0) {
356             array.add(payload);
357         } else {
358             array.add(detachedContent);
359         }
360         array.end();
361         return encodeCbor(sigStructure.build());
362     }
363 
364     private static final int COSE_LABEL_ALG = 1;
365     private static final int COSE_LABEL_X5CHAIN = 33;  // temporary identifier
366 
367     // From "COSE Algorithms" registry
368     private static final int COSE_ALG_ECDSA_256 = -7;
369     private static final int COSE_ALG_HMAC_256_256 = 5;
370 
signatureDerToCose(byte[] signature)371     private static byte[] signatureDerToCose(byte[] signature) {
372         if (signature.length > 128) {
373             throw new RuntimeException("Unexpected length " + signature.length
374                     + ", expected less than 128");
375         }
376         if (signature[0] != 0x30) {
377             throw new RuntimeException("Unexpected first byte " + signature[0]
378                     + ", expected 0x30");
379         }
380         if ((signature[1] & 0x80) != 0x00) {
381             throw new RuntimeException("Unexpected second byte " + signature[1]
382                     + ", bit 7 shouldn't be set");
383         }
384         int rOffset = 2;
385         int rSize = signature[rOffset + 1];
386         byte[] rBytes = stripLeadingZeroes(
387             Arrays.copyOfRange(signature,rOffset + 2, rOffset + rSize + 2));
388 
389         int sOffset = rOffset + 2 + rSize;
390         int sSize = signature[sOffset + 1];
391         byte[] sBytes = stripLeadingZeroes(
392             Arrays.copyOfRange(signature, sOffset + 2, sOffset + sSize + 2));
393 
394         if (rBytes.length > 32) {
395             throw new RuntimeException("rBytes.length is " + rBytes.length + " which is > 32");
396         }
397         if (sBytes.length > 32) {
398             throw new RuntimeException("sBytes.length is " + sBytes.length + " which is > 32");
399         }
400 
401         ByteArrayOutputStream baos = new ByteArrayOutputStream();
402         try {
403             for (int n = 0; n < 32 - rBytes.length; n++) {
404                 baos.write(0x00);
405             }
406             baos.write(rBytes);
407             for (int n = 0; n < 32 - sBytes.length; n++) {
408                 baos.write(0x00);
409             }
410             baos.write(sBytes);
411         } catch (IOException e) {
412             e.printStackTrace();
413             return null;
414         }
415         return baos.toByteArray();
416     }
417 
418     // Adds leading 0x00 if the first encoded byte MSB is set.
encodePositiveBigInteger(BigInteger i)419     private static byte[] encodePositiveBigInteger(BigInteger i) {
420         byte[] bytes = i.toByteArray();
421         if ((bytes[0] & 0x80) != 0) {
422             ByteArrayOutputStream baos = new ByteArrayOutputStream();
423             try {
424                 baos.write(0x00);
425                 baos.write(bytes);
426             } catch (IOException e) {
427                 e.printStackTrace();
428                 throw new RuntimeException("Failed writing data", e);
429             }
430             bytes = baos.toByteArray();
431         }
432         return bytes;
433     }
434 
signatureCoseToDer(byte[] signature)435     private static byte[] signatureCoseToDer(byte[] signature) {
436         if (signature.length != 64) {
437             throw new RuntimeException("signature.length is " + signature.length + ", expected 64");
438         }
439         BigInteger r = new BigInteger(Arrays.copyOfRange(signature, 0, 32));
440         BigInteger s = new BigInteger(Arrays.copyOfRange(signature, 32, 64));
441         byte[] rBytes = encodePositiveBigInteger(r);
442         byte[] sBytes = encodePositiveBigInteger(s);
443         ByteArrayOutputStream baos = new ByteArrayOutputStream();
444         try {
445             baos.write(0x30);
446             baos.write(2 + rBytes.length + 2 + sBytes.length);
447             baos.write(0x02);
448             baos.write(rBytes.length);
449             baos.write(rBytes);
450             baos.write(0x02);
451             baos.write(sBytes.length);
452             baos.write(sBytes);
453         } catch (IOException e) {
454             e.printStackTrace();
455             return null;
456         }
457         return baos.toByteArray();
458     }
459 
coseSign1Sign(PrivateKey key, @Nullable byte[] data, byte[] detachedContent, @Nullable Collection<X509Certificate> certificateChain)460     public static byte[] coseSign1Sign(PrivateKey key,
461             @Nullable byte[] data,
462             byte[] detachedContent,
463             @Nullable Collection<X509Certificate> certificateChain)
464             throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
465 
466         int dataLen = (data != null ? data.length : 0);
467         int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
468         if (dataLen > 0 && detachedContentLen > 0) {
469             throw new RuntimeException("data and detachedContent cannot both be non-empty");
470         }
471 
472         CborBuilder protectedHeaders = new CborBuilder();
473         MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
474         protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);
475         byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build());
476 
477         byte[] toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent);
478 
479         byte[] coseSignature = null;
480         try {
481             Signature s = Signature.getInstance("SHA256withECDSA");
482             s.initSign(key);
483             s.update(toBeSigned);
484             byte[] derSignature = s.sign();
485             coseSignature = signatureDerToCose(derSignature);
486         } catch (SignatureException e) {
487             throw new RuntimeException("Error signing data");
488         }
489 
490         CborBuilder builder = new CborBuilder();
491         ArrayBuilder<CborBuilder> array = builder.addArray();
492         array.add(protectedHeadersBytes);
493         MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
494         if (certificateChain != null && certificateChain.size() > 0) {
495             if (certificateChain.size() == 1) {
496                 X509Certificate cert = certificateChain.iterator().next();
497                 unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.getEncoded());
498             } else {
499                 ArrayBuilder<MapBuilder<ArrayBuilder<CborBuilder>>> x5chainsArray =
500                         unprotectedHeaders.putArray(COSE_LABEL_X5CHAIN);
501                 for (X509Certificate cert : certificateChain) {
502                     x5chainsArray.add(cert.getEncoded());
503                 }
504             }
505         }
506         if (data == null || data.length == 0) {
507             array.add(new SimpleValue(SimpleValueType.NULL));
508         } else {
509             array.add(data);
510         }
511         array.add(coseSignature);
512 
513         return encodeCbor(builder.build());
514     }
515 
coseSign1CheckSignature(byte[] signatureCose1, byte[] detachedContent, PublicKey publicKey)516     public static boolean coseSign1CheckSignature(byte[] signatureCose1,
517             byte[] detachedContent,
518             PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException {
519         ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
520         List<DataItem> dataItems = null;
521         try {
522             dataItems = new CborDecoder(bais).decode();
523         } catch (CborException e) {
524             throw new RuntimeException("Given signature is not valid CBOR", e);
525         }
526         if (dataItems.size() != 1) {
527             throw new RuntimeException("Expected just one data item");
528         }
529         DataItem dataItem = dataItems.get(0);
530         if (dataItem.getMajorType() != MajorType.ARRAY) {
531             throw new RuntimeException("Data item is not an array");
532         }
533         List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
534         if (items.size() < 4) {
535             throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
536         }
537         if (items.get(0).getMajorType() != MajorType.BYTE_STRING) {
538             throw new RuntimeException("Item 0 (protected headers) is not a byte-string");
539         }
540         byte[] encodedProtectedHeaders =
541                 ((co.nstant.in.cbor.model.ByteString) items.get(0)).getBytes();
542         byte[] payload = new byte[0];
543         if (items.get(2).getMajorType() == MajorType.SPECIAL) {
544             if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
545                 != SpecialType.SIMPLE_VALUE) {
546                 throw new RuntimeException("Item 2 (payload) is a special but not a simple value");
547             }
548             SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
549             if (simple.getSimpleValueType() != SimpleValueType.NULL) {
550                 throw new RuntimeException("Item 2 (payload) is a simple but not the value null");
551             }
552         } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
553             payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
554         } else {
555             throw new RuntimeException("Item 2 (payload) is not nil or byte-string");
556         }
557         if (items.get(3).getMajorType() != MajorType.BYTE_STRING) {
558             throw new RuntimeException("Item 3 (signature) is not a byte-string");
559         }
560         byte[] coseSignature = ((co.nstant.in.cbor.model.ByteString) items.get(3)).getBytes();
561 
562         byte[] derSignature = signatureCoseToDer(coseSignature);
563 
564         int dataLen = payload.length;
565         int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
566         if (dataLen > 0 && detachedContentLen > 0) {
567             throw new RuntimeException("data and detachedContent cannot both be non-empty");
568         }
569 
570         byte[] toBeSigned = Util.coseBuildToBeSigned(encodedProtectedHeaders,
571                 payload, detachedContent);
572 
573         try {
574             Signature verifier = Signature.getInstance("SHA256withECDSA");
575             verifier.initVerify(publicKey);
576             verifier.update(toBeSigned);
577             return verifier.verify(derSignature);
578         } catch (SignatureException e) {
579             throw new RuntimeException("Error verifying signature");
580         }
581     }
582 
583     // Returns the empty byte-array if no data is included in the structure.
584     //
585     // Throws RuntimeException if the given bytes aren't valid COSE_Sign1.
586     //
coseSign1GetData(byte[] signatureCose1)587     public static byte[] coseSign1GetData(byte[] signatureCose1) {
588         ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
589         List<DataItem> dataItems = null;
590         try {
591             dataItems = new CborDecoder(bais).decode();
592         } catch (CborException e) {
593             throw new RuntimeException("Given signature is not valid CBOR", e);
594         }
595         if (dataItems.size() != 1) {
596             throw new RuntimeException("Expected just one data item");
597         }
598         DataItem dataItem = dataItems.get(0);
599         if (dataItem.getMajorType() != MajorType.ARRAY) {
600             throw new RuntimeException("Data item is not an array");
601         }
602         List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
603         if (items.size() < 4) {
604             throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
605         }
606         byte[] payload = new byte[0];
607         if (items.get(2).getMajorType() == MajorType.SPECIAL) {
608             if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
609                 != SpecialType.SIMPLE_VALUE) {
610                 throw new RuntimeException("Item 2 (payload) is a special but not a simple value");
611             }
612             SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
613             if (simple.getSimpleValueType() != SimpleValueType.NULL) {
614                 throw new RuntimeException("Item 2 (payload) is a simple but not the value null");
615             }
616         } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
617             payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
618         } else {
619             throw new RuntimeException("Item 2 (payload) is not nil or byte-string");
620         }
621         return payload;
622     }
623 
624     // Returns the empty collection if no x5chain is included in the structure.
625     //
626     // Throws RuntimeException if the given bytes aren't valid COSE_Sign1.
627     //
coseSign1GetX5Chain(byte[] signatureCose1)628     public static Collection<X509Certificate> coseSign1GetX5Chain(byte[] signatureCose1)
629             throws CertificateException {
630         ArrayList<X509Certificate> ret = new ArrayList<>();
631         ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
632         List<DataItem> dataItems = null;
633         try {
634             dataItems = new CborDecoder(bais).decode();
635         } catch (CborException e) {
636             throw new RuntimeException("Given signature is not valid CBOR", e);
637         }
638         if (dataItems.size() != 1) {
639             throw new RuntimeException("Expected just one data item");
640         }
641         DataItem dataItem = dataItems.get(0);
642         if (dataItem.getMajorType() != MajorType.ARRAY) {
643             throw new RuntimeException("Data item is not an array");
644         }
645         List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
646         if (items.size() < 4) {
647             throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
648         }
649         if (items.get(1).getMajorType() != MajorType.MAP) {
650             throw new RuntimeException("Item 1 (unprocted headers) is not a map");
651         }
652         co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) items.get(1);
653         DataItem x5chainItem = map.get(new UnsignedInteger(COSE_LABEL_X5CHAIN));
654         if (x5chainItem != null) {
655             CertificateFactory factory = CertificateFactory.getInstance("X.509");
656             if (x5chainItem instanceof ByteString) {
657                 ByteArrayInputStream certBais =
658                         new ByteArrayInputStream(((ByteString) x5chainItem).getBytes());
659                 ret.add((X509Certificate) factory.generateCertificate(certBais));
660             } else if (x5chainItem instanceof Array) {
661                 for (DataItem certItem : ((Array) x5chainItem).getDataItems()) {
662                     if (!(certItem instanceof ByteString)) {
663                         throw new RuntimeException(
664                             "Unexpected type for array item in x5chain value");
665                     }
666                     ByteArrayInputStream certBais =
667                             new ByteArrayInputStream(((ByteString) certItem).getBytes());
668                     ret.add((X509Certificate) factory.generateCertificate(certBais));
669                 }
670             } else {
671                 throw new RuntimeException("Unexpected type for x5chain value");
672             }
673         }
674         return ret;
675     }
676 
coseBuildToBeMACed(byte[] encodedProtectedHeaders, byte[] payload, byte[] detachedContent)677     public static byte[] coseBuildToBeMACed(byte[] encodedProtectedHeaders,
678             byte[] payload,
679             byte[] detachedContent) {
680         CborBuilder macStructure = new CborBuilder();
681         ArrayBuilder<CborBuilder> array = macStructure.addArray();
682 
683         array.add("MAC0");
684         array.add(encodedProtectedHeaders);
685 
686         // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
687         // so external_aad is the empty bstr
688         byte emptyExternalAad[] = new byte[0];
689         array.add(emptyExternalAad);
690 
691         // Next field is the payload, independently of how it's transported (RFC
692         // 8152 section 4.4). Since our API specifies only one of |data| and
693         // |detachedContent| can be non-empty, it's simply just the non-empty one.
694         if (payload != null && payload.length > 0) {
695             array.add(payload);
696         } else {
697             array.add(detachedContent);
698         }
699 
700         return encodeCbor(macStructure.build());
701     }
702 
coseMac0(SecretKey key, @Nullable byte[] data, byte[] detachedContent)703     public static byte[] coseMac0(SecretKey key,
704             @Nullable byte[] data,
705             byte[] detachedContent)
706             throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
707 
708         int dataLen = (data != null ? data.length : 0);
709         int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
710         if (dataLen > 0 && detachedContentLen > 0) {
711             throw new RuntimeException("data and detachedContent cannot both be non-empty");
712         }
713 
714         CborBuilder protectedHeaders = new CborBuilder();
715         MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
716         protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);
717         byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build());
718 
719         byte[] toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data, detachedContent);
720 
721         byte[] mac = null;
722         Mac m = Mac.getInstance("HmacSHA256");
723         m.init(key);
724         m.update(toBeMACed);
725         mac = m.doFinal();
726 
727         CborBuilder builder = new CborBuilder();
728         ArrayBuilder<CborBuilder> array = builder.addArray();
729         array.add(protectedHeadersBytes);
730         MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
731         if (data == null || data.length == 0) {
732             array.add(new SimpleValue(SimpleValueType.NULL));
733         } else {
734             array.add(data);
735         }
736         array.add(mac);
737 
738         return encodeCbor(builder.build());
739     }
740 
replaceLine(String text, int lineNumber, String replacementLine)741     public static String replaceLine(String text, int lineNumber, String replacementLine) {
742         String[] lines = text.split("\n");
743         int numLines = lines.length;
744         if (lineNumber < 0) {
745             lineNumber = numLines - (-lineNumber);
746         }
747         StringBuilder sb = new StringBuilder();
748         for (int n = 0; n < numLines; n++) {
749             if (n == lineNumber) {
750                 sb.append(replacementLine);
751             } else {
752                 sb.append(lines[n]);
753             }
754             // Only add terminating newline if passed-in string ends in a newline.
755             if (n == numLines - 1) {
756                 if (text.endsWith(("\n"))) {
757                     sb.append('\n');
758                 }
759             } else {
760                 sb.append('\n');
761             }
762         }
763         return sb.toString();
764     }
765 
cborEncode(DataItem dataItem)766     static byte[] cborEncode(DataItem dataItem) {
767         ByteArrayOutputStream baos = new ByteArrayOutputStream();
768         try {
769             new CborEncoder(baos).encode(dataItem);
770         } catch (CborException e) {
771             // This should never happen and we don't want cborEncode() to throw since that
772             // would complicate all callers. Log it instead.
773             e.printStackTrace();
774             Log.e(TAG, "Error encoding DataItem");
775         }
776         return baos.toByteArray();
777     }
778 
cborEncodeBoolean(boolean value)779     static byte[] cborEncodeBoolean(boolean value) {
780         return cborEncode(new CborBuilder().add(value).build().get(0));
781     }
782 
cborEncodeString(@onNull String value)783     static byte[] cborEncodeString(@NonNull String value) {
784         return cborEncode(new CborBuilder().add(value).build().get(0));
785     }
786 
cborEncodeBytestring(@onNull byte[] value)787     static byte[] cborEncodeBytestring(@NonNull byte[] value) {
788         return cborEncode(new CborBuilder().add(value).build().get(0));
789     }
790 
cborEncodeInt(long value)791     static byte[] cborEncodeInt(long value) {
792         return cborEncode(new CborBuilder().add(value).build().get(0));
793     }
794 
795     static final int CBOR_SEMANTIC_TAG_ENCODED_CBOR = 24;
796 
cborToDataItem(byte[] data)797     static DataItem cborToDataItem(byte[] data) {
798         ByteArrayInputStream bais = new ByteArrayInputStream(data);
799         try {
800             List<DataItem> dataItems = new CborDecoder(bais).decode();
801             if (dataItems.size() != 1) {
802                 throw new RuntimeException("Expected 1 item, found " + dataItems.size());
803             }
804             return dataItems.get(0);
805         } catch (CborException e) {
806             throw new RuntimeException("Error decoding data", e);
807         }
808     }
809 
cborDecodeBoolean(@onNull byte[] data)810     static boolean cborDecodeBoolean(@NonNull byte[] data) {
811         return cborToDataItem(data) == SimpleValue.TRUE;
812     }
813 
cborDecodeString(@onNull byte[] data)814     static String cborDecodeString(@NonNull byte[] data) {
815         return ((co.nstant.in.cbor.model.UnicodeString) cborToDataItem(data)).getString();
816     }
817 
cborDecodeInt(@onNull byte[] data)818     static long cborDecodeInt(@NonNull byte[] data) {
819         return ((co.nstant.in.cbor.model.Number) cborToDataItem(data)).getValue().longValue();
820     }
821 
cborDecodeBytestring(@onNull byte[] data)822     static byte[] cborDecodeBytestring(@NonNull byte[] data) {
823         return ((co.nstant.in.cbor.model.ByteString) cborToDataItem(data)).getBytes();
824     }
825 
getStringEntry(ResultData data, String namespaceName, String name)826     static String getStringEntry(ResultData data, String namespaceName, String name) {
827         return Util.cborDecodeString(data.getEntry(namespaceName, name));
828     }
829 
getBooleanEntry(ResultData data, String namespaceName, String name)830     static boolean getBooleanEntry(ResultData data, String namespaceName, String name) {
831         return Util.cborDecodeBoolean(data.getEntry(namespaceName, name));
832     }
833 
getIntegerEntry(ResultData data, String namespaceName, String name)834     static long getIntegerEntry(ResultData data, String namespaceName, String name) {
835         return Util.cborDecodeInt(data.getEntry(namespaceName, name));
836     }
837 
getBytestringEntry(ResultData data, String namespaceName, String name)838     static byte[] getBytestringEntry(ResultData data, String namespaceName, String name) {
839         return Util.cborDecodeBytestring(data.getEntry(namespaceName, name));
840     }
841 
842     /*
843 Certificate:
844     Data:
845         Version: 3 (0x2)
846         Serial Number: 1 (0x1)
847     Signature Algorithm: ecdsa-with-SHA256
848         Issuer: CN=fake
849         Validity
850             Not Before: Jan  1 00:00:00 1970 GMT
851             Not After : Jan  1 00:00:00 2048 GMT
852         Subject: CN=fake
853         Subject Public Key Info:
854             Public Key Algorithm: id-ecPublicKey
855                 Public-Key: (256 bit)
856                 00000000  04 9b 60 70 8a 99 b6 bf  e3 b8 17 02 9e 93 eb 48  |..`p...........H|
857                 00000010  23 b9 39 89 d1 00 bf a0  0f d0 2f bd 6b 11 bc d1  |#.9......./.k...|
858                 00000020  19 53 54 28 31 00 f5 49  db 31 fb 9f 7d 99 bf 23  |.ST(1..I.1..}..#|
859                 00000030  fb 92 04 6b 23 63 55 98  ad 24 d2 68 c4 83 bf 99  |...k#cU..$.h....|
860                 00000040  62                                                |b|
861     Signature Algorithm: ecdsa-with-SHA256
862          30:45:02:20:67:ad:d1:34:ed:a5:68:3f:5b:33:ee:b3:18:a2:
863          eb:03:61:74:0f:21:64:4a:a3:2e:82:b3:92:5c:21:0f:88:3f:
864          02:21:00:b7:38:5c:9b:f2:9c:b1:27:86:37:44:df:eb:4a:b2:
865          6c:11:9a:c1:ff:b2:80:95:ce:fc:5f:26:b4:20:6e:9b:0d
866      */
867 
868 
signPublicKeyWithPrivateKey(String keyToSignAlias, String keyToSignWithAlias)869     static @NonNull X509Certificate signPublicKeyWithPrivateKey(String keyToSignAlias,
870             String keyToSignWithAlias) {
871 
872         KeyStore ks = null;
873         try {
874             ks = KeyStore.getInstance("AndroidKeyStore");
875             ks.load(null);
876 
877             /* First note that KeyStore.getCertificate() returns a self-signed X.509 certificate
878              * for the key in question. As per RFC 5280, section 4.1 an X.509 certificate has the
879              * following structure:
880              *
881              *   Certificate  ::=  SEQUENCE  {
882              *        tbsCertificate       TBSCertificate,
883              *        signatureAlgorithm   AlgorithmIdentifier,
884              *        signatureValue       BIT STRING  }
885              *
886              * Conveniently, the X509Certificate class has a getTBSCertificate() method which
887              * returns the tbsCertificate blob. So all we need to do is just sign that and build
888              * signatureAlgorithm and signatureValue and combine it with tbsCertificate. We don't
889              * need a full-blown ASN.1/DER encoder to do this.
890              */
891             X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(keyToSignAlias);
892             byte[] tbsCertificate = selfSignedCert.getTBSCertificate();
893 
894             KeyStore.Entry keyToSignWithEntry = ks.getEntry(keyToSignWithAlias, null);
895             Signature s = Signature.getInstance("SHA256withECDSA");
896             s.initSign(((KeyStore.PrivateKeyEntry) keyToSignWithEntry).getPrivateKey());
897             s.update(tbsCertificate);
898             byte[] signatureValue = s.sign();
899 
900             /* The DER encoding for a SEQUENCE of length 128-65536 - the length is updated below.
901              *
902              * We assume - and test for below - that the final length is always going to be in
903              * this range. This is a sound assumption given we're using 256-bit EC keys.
904              */
905             byte[] sequence = new byte[]{
906                     0x30, (byte) 0x82, 0x00, 0x00
907             };
908 
909             /* The DER encoding for the ECDSA with SHA-256 signature algorithm:
910              *
911              *   SEQUENCE (1 elem)
912              *      OBJECT IDENTIFIER 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA
913              *      algorithm with SHA256)
914              */
915             byte[] signatureAlgorithm = new byte[]{
916                     0x30, 0x0a, 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x04, 0x03,
917                     0x02
918             };
919 
920             /* The DER encoding for a BIT STRING with one element - the length is updated below.
921              *
922              * We assume the length of signatureValue is always going to be less than 128. This
923              * assumption works since we know ecdsaWithSHA256 signatures are always 69, 70, or
924              * 71 bytes long when DER encoded.
925              */
926             byte[] bitStringForSignature = new byte[]{0x03, 0x00, 0x00};
927 
928             // Calculate sequence length and set it in |sequence|.
929             int sequenceLength = tbsCertificate.length
930                     + signatureAlgorithm.length
931                     + bitStringForSignature.length
932                     + signatureValue.length;
933             if (sequenceLength < 128 || sequenceLength > 65535) {
934                 throw new Exception("Unexpected sequenceLength " + sequenceLength);
935             }
936             sequence[2] = (byte) (sequenceLength >> 8);
937             sequence[3] = (byte) (sequenceLength & 0xff);
938 
939             // Calculate signatureValue length and set it in |bitStringForSignature|.
940             int signatureValueLength = signatureValue.length + 1;
941             if (signatureValueLength >= 128) {
942                 throw new Exception("Unexpected signatureValueLength " + signatureValueLength);
943             }
944             bitStringForSignature[1] = (byte) signatureValueLength;
945 
946             // Finally concatenate everything together.
947             ByteArrayOutputStream baos = new ByteArrayOutputStream();
948             baos.write(sequence);
949             baos.write(tbsCertificate);
950             baos.write(signatureAlgorithm);
951             baos.write(bitStringForSignature);
952             baos.write(signatureValue);
953             byte[] resultingCertBytes = baos.toByteArray();
954 
955             CertificateFactory cf = CertificateFactory.getInstance("X.509");
956             ByteArrayInputStream bais = new ByteArrayInputStream(resultingCertBytes);
957             X509Certificate result = (X509Certificate) cf.generateCertificate(bais);
958             return result;
959         } catch (Exception e) {
960             throw new RuntimeException("Error signing public key with private key", e);
961         }
962     }
963 
buildDeviceAuthenticationCbor(String docType, byte[] encodedSessionTranscript, byte[] deviceNameSpacesBytes)964     static byte[] buildDeviceAuthenticationCbor(String docType,
965             byte[] encodedSessionTranscript,
966             byte[] deviceNameSpacesBytes) {
967         ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
968         try {
969             ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
970             List<DataItem> dataItems = null;
971             dataItems = new CborDecoder(bais).decode();
972             DataItem sessionTranscript = dataItems.get(0);
973             ByteString deviceNameSpacesBytesItem = new ByteString(deviceNameSpacesBytes);
974             deviceNameSpacesBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
975             new CborEncoder(daBaos).encode(new CborBuilder()
976                     .addArray()
977                     .add("DeviceAuthentication")
978                     .add(sessionTranscript)
979                     .add(docType)
980                     .add(deviceNameSpacesBytesItem)
981                     .end()
982                     .build());
983         } catch (CborException e) {
984             throw new RuntimeException("Error encoding DeviceAuthentication", e);
985         }
986         return daBaos.toByteArray();
987     }
988 
buildReaderAuthenticationBytesCbor( byte[] encodedSessionTranscript, byte[] requestMessageBytes)989     static byte[] buildReaderAuthenticationBytesCbor(
990             byte[] encodedSessionTranscript,
991             byte[] requestMessageBytes) {
992 
993         ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
994         try {
995             ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
996             List<DataItem> dataItems = null;
997             dataItems = new CborDecoder(bais).decode();
998             DataItem sessionTranscript = dataItems.get(0);
999             ByteString requestMessageBytesItem = new ByteString(requestMessageBytes);
1000             requestMessageBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
1001             new CborEncoder(daBaos).encode(new CborBuilder()
1002                     .addArray()
1003                     .add("ReaderAuthentication")
1004                     .add(sessionTranscript)
1005                     .add(requestMessageBytesItem)
1006                     .end()
1007                     .build());
1008         } catch (CborException e) {
1009             throw new RuntimeException("Error encoding ReaderAuthentication", e);
1010         }
1011         byte[] readerAuthentication = daBaos.toByteArray();
1012         return Util.prependSemanticTagForEncodedCbor(readerAuthentication);
1013     }
1014 
prependSemanticTagForEncodedCbor(byte[] encodedCbor)1015     static byte[] prependSemanticTagForEncodedCbor(byte[] encodedCbor) {
1016         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1017         try {
1018             ByteString taggedBytestring = new ByteString(encodedCbor);
1019             taggedBytestring.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
1020             new CborEncoder(baos).encode(taggedBytestring);
1021         } catch (CborException e) {
1022             throw new RuntimeException("Error encoding with semantic tag for CBOR encoding", e);
1023         }
1024         return baos.toByteArray();
1025     }
1026 
concatArrays(byte[] a, byte[] b)1027     static byte[] concatArrays(byte[] a, byte[] b) {
1028         byte[] ret = new byte[a.length + b.length];
1029         System.arraycopy(a, 0, ret, 0, a.length);
1030         System.arraycopy(b, 0, ret, a.length, b.length);
1031         return ret;
1032     }
1033 
calcEMacKeyForReader(PublicKey authenticationPublicKey, PrivateKey ephemeralReaderPrivateKey, byte[] encodedSessionTranscript)1034     static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey,
1035             PrivateKey ephemeralReaderPrivateKey,
1036             byte[] encodedSessionTranscript) {
1037         try {
1038             KeyAgreement ka = KeyAgreement.getInstance("ECDH");
1039             ka.init(ephemeralReaderPrivateKey);
1040             ka.doPhase(authenticationPublicKey, true);
1041             byte[] sharedSecret = ka.generateSecret();
1042 
1043             byte[] sessionTranscriptBytes =
1044                     Util.prependSemanticTagForEncodedCbor(encodedSessionTranscript);
1045 
1046             byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
1047             byte[] info = new byte[] {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
1048             byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
1049             SecretKey secretKey = new SecretKeySpec(derivedKey, "");
1050             return secretKey;
1051         } catch (InvalidKeyException
1052                 | NoSuchAlgorithmException e) {
1053             throw new RuntimeException("Error performing key agreement", e);
1054         }
1055     }
1056 
1057     /**
1058      * Computes an HKDF.
1059      *
1060      * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
1061      * /crypto/tink/subtle/Hkdf.java
1062      * which is also Copyright (c) Google and also licensed under the Apache 2 license.
1063      *
1064      * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
1065      *                     "HMACSHA256".
1066      * @param ikm          the input keying material.
1067      * @param salt         optional salt. A possibly non-secret random value. If no salt is
1068      *                     provided (i.e. if
1069      *                     salt has length 0) then an array of 0s of the same size as the hash
1070      *                     digest is used as salt.
1071      * @param info         optional context and application specific information.
1072      * @param size         The length of the generated pseudorandom string in bytes. The maximal
1073      *                     size is
1074      *                     255.DigestSize, where DigestSize is the size of the underlying HMAC.
1075      * @return size pseudorandom bytes.
1076      */
computeHkdf( String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size)1077     static byte[] computeHkdf(
1078             String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
1079         Mac mac = null;
1080         try {
1081             mac = Mac.getInstance(macAlgorithm);
1082         } catch (NoSuchAlgorithmException e) {
1083             throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
1084         }
1085         if (size > 255 * mac.getMacLength()) {
1086             throw new RuntimeException("size too large");
1087         }
1088         try {
1089             if (salt == null || salt.length == 0) {
1090                 // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
1091                 // then HKDF uses a salt that is an array of zeros of the same length as the hash
1092                 // digest.
1093                 mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
1094             } else {
1095                 mac.init(new SecretKeySpec(salt, macAlgorithm));
1096             }
1097             byte[] prk = mac.doFinal(ikm);
1098             byte[] result = new byte[size];
1099             int ctr = 1;
1100             int pos = 0;
1101             mac.init(new SecretKeySpec(prk, macAlgorithm));
1102             byte[] digest = new byte[0];
1103             while (true) {
1104                 mac.update(digest);
1105                 mac.update(info);
1106                 mac.update((byte) ctr);
1107                 digest = mac.doFinal();
1108                 if (pos + digest.length < size) {
1109                     System.arraycopy(digest, 0, result, pos, digest.length);
1110                     pos += digest.length;
1111                     ctr++;
1112                 } else {
1113                     System.arraycopy(digest, 0, result, pos, size - pos);
1114                     break;
1115                 }
1116             }
1117             return result;
1118         } catch (InvalidKeyException e) {
1119             throw new RuntimeException("Error MACing", e);
1120         }
1121     }
1122 
stripLeadingZeroes(byte[] value)1123     static byte[] stripLeadingZeroes(byte[] value) {
1124         int n = 0;
1125         while (n < value.length && value[n] == 0) {
1126             n++;
1127         }
1128         int newLen = value.length - n;
1129         byte[] ret = new byte[newLen];
1130         int m = 0;
1131         while (n < value.length) {
1132             ret[m++] = value[n++];
1133         }
1134         return ret;
1135     }
1136 
hexdump(String name, byte[] data)1137     static void hexdump(String name, byte[] data) {
1138         int n, m, o;
1139         StringBuilder sb = new StringBuilder();
1140         Formatter fmt = new Formatter(sb);
1141         for (n = 0; n < data.length; n += 16) {
1142             fmt.format("%04x  ", n);
1143             for (m = 0; m < 16 && n + m < data.length; m++) {
1144                 fmt.format("%02x ", data[n + m]);
1145             }
1146             for (o = m; o < 16; o++) {
1147                 sb.append("   ");
1148             }
1149             sb.append(" ");
1150             for (m = 0; m < 16 && n + m < data.length; m++) {
1151                 int c = data[n + m] & 0xff;
1152                 fmt.format("%c", Character.isISOControl(c) ? '.' : c);
1153             }
1154             sb.append("\n");
1155         }
1156         sb.append("\n");
1157         Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString());
1158     }
1159 
1160 
1161     // This returns a SessionTranscript which satisfy the requirement
1162     // that the uncompressed X and Y coordinates of the public key for the
1163     // mDL's ephemeral key-pair appear somewhere in the encoded
1164     // DeviceEngagement.
buildSessionTranscript(KeyPair ephemeralKeyPair)1165     static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) {
1166         // Make the coordinates appear in an already encoded bstr - this
1167         // mimics how the mDL COSE_Key appear as encoded data inside the
1168         // encoded DeviceEngagement
1169         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1170         try {
1171             ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
1172             // X and Y are always positive so for interop we remove any leading zeroes
1173             // inserted by the BigInteger encoder.
1174             byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
1175             byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
1176             baos.write(new byte[]{42});
1177             baos.write(x);
1178             baos.write(y);
1179             baos.write(new byte[]{43, 44});
1180         } catch (IOException e) {
1181             e.printStackTrace();
1182             return null;
1183         }
1184         byte[] blobWithCoords = baos.toByteArray();
1185 
1186         baos = new ByteArrayOutputStream();
1187         try {
1188             new CborEncoder(baos).encode(new CborBuilder()
1189                     .addArray()
1190                     .add(blobWithCoords)
1191                     .end()
1192                     .build());
1193         } catch (CborException e) {
1194             e.printStackTrace();
1195             return null;
1196         }
1197         ByteString encodedDeviceEngagementItem = new ByteString(baos.toByteArray());
1198         ByteString encodedEReaderKeyItem = new ByteString(cborEncodeString("doesn't matter"));
1199         encodedDeviceEngagementItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
1200         encodedEReaderKeyItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
1201 
1202         baos = new ByteArrayOutputStream();
1203         try {
1204             new CborEncoder(baos).encode(new CborBuilder()
1205                     .addArray()
1206                     .add(encodedDeviceEngagementItem)
1207                     .add(encodedEReaderKeyItem)
1208                     .end()
1209                     .build());
1210         } catch (CborException e) {
1211             e.printStackTrace();
1212             return null;
1213         }
1214         return baos.toByteArray();
1215     }
1216 
1217     /*
1218      * Helper function to create a CBOR data for requesting data items. The IntentToRetain
1219      * value will be set to false for all elements.
1220      *
1221      * <p>The returned CBOR data conforms to the following CDDL schema:</p>
1222      *
1223      * <pre>
1224      *   ItemsRequest = {
1225      *     ? "docType" : DocType,
1226      *     "nameSpaces" : NameSpaces,
1227      *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
1228      *   }
1229      *
1230      *   NameSpaces = {
1231      *     + NameSpace => DataElements     ; Requested data elements for each NameSpace
1232      *   }
1233      *
1234      *   DataElements = {
1235      *     + DataElement => IntentToRetain
1236      *   }
1237      *
1238      *   DocType = tstr
1239      *
1240      *   DataElement = tstr
1241      *   IntentToRetain = bool
1242      *   NameSpace = tstr
1243      * </pre>
1244      *
1245      * @param entriesToRequest       The entries to request, organized as a map of namespace
1246      *                               names with each value being a collection of data elements
1247      *                               in the given namespace.
1248      * @param docType                  The document type or {@code null} if there is no document
1249      *                                 type.
1250      * @return CBOR data conforming to the CDDL mentioned above.
1251      */
createItemsRequest( @onNull Map<String, Collection<String>> entriesToRequest, @Nullable String docType)1252     static @NonNull byte[] createItemsRequest(
1253             @NonNull Map<String, Collection<String>> entriesToRequest,
1254             @Nullable String docType) {
1255         CborBuilder builder = new CborBuilder();
1256         MapBuilder<CborBuilder> mapBuilder = builder.addMap();
1257         if (docType != null) {
1258             mapBuilder.put("docType", docType);
1259         }
1260 
1261         MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces");
1262         for (String namespaceName : entriesToRequest.keySet()) {
1263             Collection<String> entryNames = entriesToRequest.get(namespaceName);
1264             MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder =
1265                     nsMapBuilder.putMap(namespaceName);
1266             for (String entryName : entryNames) {
1267                 entryNameMapBuilder.put(entryName, false);
1268             }
1269         }
1270 
1271         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1272         CborEncoder encoder = new CborEncoder(baos);
1273         try {
1274             encoder.encode(builder.build());
1275         } catch (CborException e) {
1276             throw new RuntimeException("Error encoding CBOR", e);
1277         }
1278         return baos.toByteArray();
1279     }
1280 
createEphemeralKeyPair()1281     static KeyPair createEphemeralKeyPair() {
1282         try {
1283             KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
1284             ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
1285             kpg.initialize(ecSpec);
1286             KeyPair keyPair = kpg.generateKeyPair();
1287             return keyPair;
1288         } catch (NoSuchAlgorithmException
1289                 | InvalidAlgorithmParameterException e) {
1290             throw new RuntimeException("Error generating ephemeral key-pair", e);
1291         }
1292     }
1293 
1294     // Returns true if, and only if, the Identity Credential HAL (and credstore) is implemented
1295     // on the device under test.
isHalImplemented()1296     static boolean isHalImplemented() {
1297         Context appContext = InstrumentationRegistry.getTargetContext();
1298         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
1299         if (store != null) {
1300             return true;
1301         }
1302         return false;
1303     }
1304 
1305     // Returns true if, and only if, the Direct Access Identity Credential HAL (and credstore) is
1306     // implemented on the device under test.
isDirectAccessHalImplemented()1307     static boolean isDirectAccessHalImplemented() {
1308         Context appContext = InstrumentationRegistry.getTargetContext();
1309         IdentityCredentialStore store = IdentityCredentialStore.getDirectAccessInstance(appContext);
1310         if (store != null) {
1311             return true;
1312         }
1313         return false;
1314     }
1315 
getPopSha256FromAuthKeyCert(X509Certificate cert)1316     static byte[] getPopSha256FromAuthKeyCert(X509Certificate cert) {
1317         byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26");
1318         if (octetString == null) {
1319             return null;
1320         }
1321         Util.hexdump("octetString", octetString);
1322 
1323         try {
1324             ASN1InputStream asn1InputStream = new ASN1InputStream(octetString);
1325             byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
1326             Util.hexdump("cborBytes", cborBytes);
1327 
1328             ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes);
1329             List<DataItem> dataItems = new CborDecoder(bais).decode();
1330             if (dataItems.size() != 1) {
1331                 throw new RuntimeException("Expected 1 item, found " + dataItems.size());
1332             }
1333             if (!(dataItems.get(0) instanceof co.nstant.in.cbor.model.Array)) {
1334                 throw new RuntimeException("Item is not a map");
1335             }
1336             co.nstant.in.cbor.model.Array array = (co.nstant.in.cbor.model.Array) dataItems.get(0);
1337             List<DataItem> items = array.getDataItems();
1338             if (items.size() < 2) {
1339                 throw new RuntimeException("Expected at least 2 array items, found " + items.size());
1340             }
1341             if (!(items.get(0) instanceof UnicodeString)) {
1342                 throw new RuntimeException("First array item is not a string");
1343             }
1344             String id = ((UnicodeString) items.get(0)).getString();
1345             if (!id.equals("ProofOfBinding")) {
1346                 throw new RuntimeException("Expected ProofOfBinding, got " + id);
1347             }
1348             if (!(items.get(1) instanceof ByteString)) {
1349                 throw new RuntimeException("Second array item is not a bytestring");
1350             }
1351             byte[] popSha256 = ((ByteString) items.get(1)).getBytes();
1352             if (popSha256.length != 32) {
1353                 throw new RuntimeException("Expected bstr to be 32 bytes, it is " + popSha256.length);
1354             }
1355             return popSha256;
1356         } catch (IOException e) {
1357             throw new RuntimeException("Error decoding extension data", e);
1358         } catch (CborException e) {
1359             throw new RuntimeException("Error decoding data", e);
1360         }
1361     }
1362 
1363 }
1364