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