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