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