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