From c3474e646863278af941f4fc6e828b606cd935d6 Mon Sep 17 00:00:00 2001 From: Suzanna Jiwani Date: Thu, 17 Aug 2023 15:27:08 -0400 Subject: [PATCH] Update wwwverifier to WD6 Modified build scripts to make wwwverifier a subproject of the Identity Credential project to reduce copy-pasted code. Updated OriginInfo to match 18013-7 WD6 updates. Tested appholder changes manually with unattended presentations via the wwwverifier as well as attended presentations with appverifier. --- .gitignore | 3 +- .../java/com/android/mdl/app/MainActivity.kt | 2 +- .../app/fragment/ReverseEngagementFragment.kt | 5 +- .../transfer/ReverseQrCommunicationSetup.kt | 2 +- .../mdl/app/transfer/TransferManager.kt | 2 +- .../app/viewmodel/ShareDocumentViewModel.kt | 2 +- .../DeviceRetrievalHelper.java | 6 +- .../mdoc/engagement/EngagementGenerator.java | 10 +- .../mdoc/mso/MobileSecurityObjectParser.java | 3 +- .../mdoc/mso/StaticAuthDataGenerator.java | 2 - .../identity/mdoc/origininfo/OriginInfo.java | 25 +- .../mdoc/origininfo/OriginInfoNfc.java | 76 - .../mdoc/origininfo/OriginInfoQr.java | 76 - .../mdoc/origininfo/OriginInfoWebsite.java | 61 +- .../engagement/EngagementGeneratorTest.java | 5 +- .../mdoc/origininfo/OriginInfoTest.java | 76 +- settings.gradle | 3 +- wwwverifier/build.gradle | 22 +- .../identity/wwwreader}/RequestServlet.java | 55 +- .../identity/wwwreader}/ServletConsts.java | 16 + .../google/sps/identity/ConnectionMethod.java | 61 - .../sps/identity/ConnectionMethodHttp.java | 96 - .../com/google/sps/identity/Constants.java | 136 -- .../sps/identity/DeviceRequestGenerator.java | 175 -- .../sps/identity/DeviceRequestParser.java | 415 ---- .../sps/identity/DeviceResponseParser.java | 947 --------- .../sps/identity/EngagementGenerator.java | 128 -- .../google/sps/identity/EngagementParser.java | 171 -- .../com/google/sps/identity/OriginInfo.java | 76 - .../sps/identity/OriginInfoWebsite.java | 88 - .../sps/identity/SessionEncryptionDevice.java | 250 --- .../sps/identity/SessionEncryptionReader.java | 285 --- .../com/google/sps/identity/TestVectors.java | 366 ---- .../com/google/sps/identity/Timestamp.java | 68 - .../java/com/google/sps/identity/Util.java | 1808 ----------------- .../wwwreader}/RequestServletTest.java | 98 +- .../identity/wwwreader/TestVectors.java | 376 ++++ 37 files changed, 583 insertions(+), 5413 deletions(-) delete mode 100644 identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoNfc.java delete mode 100644 identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoQr.java rename wwwverifier/src/main/java/com/{google/sps/servlets => android/identity/wwwreader}/RequestServlet.java (90%) rename wwwverifier/src/main/java/com/{google/sps/servlets => android/identity/wwwreader}/ServletConsts.java (77%) delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethod.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethodHttp.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/Constants.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestGenerator.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestParser.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/DeviceResponseParser.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/EngagementGenerator.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/EngagementParser.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/OriginInfo.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/OriginInfoWebsite.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionDevice.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionReader.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/TestVectors.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/Timestamp.java delete mode 100644 wwwverifier/src/main/java/com/google/sps/identity/Util.java rename wwwverifier/src/test/java/com/{google/sps => android/identity/wwwreader}/RequestServletTest.java (82%) create mode 100644 wwwverifier/src/test/java/com/android/identity/wwwreader/TestVectors.java diff --git a/.gitignore b/.gitignore index d75981411..b79a35b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ .idea/ fastlane/report.xml /mdl-ref-server/mdl-server-db.sqlite3 -/version* \ No newline at end of file +/version* +wwwverifier/build/* \ No newline at end of file diff --git a/appholder/src/main/java/com/android/mdl/app/MainActivity.kt b/appholder/src/main/java/com/android/mdl/app/MainActivity.kt index 07e00e4d2..0f85dffb7 100644 --- a/appholder/src/main/java/com/android/mdl/app/MainActivity.kt +++ b/appholder/src/main/java/com/android/mdl/app/MainActivity.kt @@ -105,7 +105,7 @@ class MainActivity : AppCompatActivity() { // TODO: maybe bail in the future if this isn't set. } else { logInfo("referrer: $mdocReferrerUri") - originInfos.add(OriginInfoWebsite(1, mdocReferrerUri)) + originInfos.add(OriginInfoWebsite(mdocReferrerUri, OriginInfoWebsite.TYPE_BASE)) } viewModel.startPresentationReverseEngagement(mdocUri, originInfos) diff --git a/appholder/src/main/java/com/android/mdl/app/fragment/ReverseEngagementFragment.kt b/appholder/src/main/java/com/android/mdl/app/fragment/ReverseEngagementFragment.kt index 968b9e2c0..cdd333d2b 100644 --- a/appholder/src/main/java/com/android/mdl/app/fragment/ReverseEngagementFragment.kt +++ b/appholder/src/main/java/com/android/mdl/app/fragment/ReverseEngagementFragment.kt @@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.android.identity.mdoc.origininfo.OriginInfo -import com.android.identity.mdoc.origininfo.OriginInfoQr import com.android.mdl.app.databinding.FragmentReverseEngagementBinding import com.android.mdl.app.util.log import com.android.mdl.app.util.logWarning @@ -53,9 +52,7 @@ class ReverseEngagementFragment : Fragment() { log("qrText: $qrText") val uri = Uri.parse(qrText) if (uri.scheme.equals("mdoc")) { - val originInfos = ArrayList() - originInfos.add(OriginInfoQr(1)) - vm.startPresentationReverseEngagement(qrText, originInfos) + vm.startPresentationReverseEngagement(qrText, null) findNavController().navigate( ReverseEngagementFragmentDirections.actionReverseEngagementFragmentToTransferDocumentFragment() ) diff --git a/appholder/src/main/java/com/android/mdl/app/transfer/ReverseQrCommunicationSetup.kt b/appholder/src/main/java/com/android/mdl/app/transfer/ReverseQrCommunicationSetup.kt index 718b646e4..ab04f043b 100644 --- a/appholder/src/main/java/com/android/mdl/app/transfer/ReverseQrCommunicationSetup.kt +++ b/appholder/src/main/java/com/android/mdl/app/transfer/ReverseQrCommunicationSetup.kt @@ -46,7 +46,7 @@ class ReverseQrCommunicationSetup( fun configure( reverseEngagementUri: String, - origins: List + origins: List? ) { val uri = Uri.parse(reverseEngagementUri) if (!uri.scheme.equals("mdoc")) { diff --git a/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt b/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt index 700780c96..d62cb5881 100644 --- a/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt +++ b/appholder/src/main/java/com/android/mdl/app/transfer/TransferManager.kt @@ -76,7 +76,7 @@ class TransferManager private constructor(private val context: Context) { fun startPresentationReverseEngagement( reverseEngagementUri: String, - origins: List + origins: List? ) { if (hasStarted) { throw IllegalStateException("Transfer has already started.") diff --git a/appholder/src/main/java/com/android/mdl/app/viewmodel/ShareDocumentViewModel.kt b/appholder/src/main/java/com/android/mdl/app/viewmodel/ShareDocumentViewModel.kt index 22c8a8be1..e51fb6995 100644 --- a/appholder/src/main/java/com/android/mdl/app/viewmodel/ShareDocumentViewModel.kt +++ b/appholder/src/main/java/com/android/mdl/app/viewmodel/ShareDocumentViewModel.kt @@ -20,7 +20,7 @@ class ShareDocumentViewModel(val app: Application) : AndroidViewModel(app) { fun startPresentationReverseEngagement( reverseEngagementUri: String, - originInfos: List + originInfos: List? ) { if (!hasStarted) { transferManager.startPresentationReverseEngagement(reverseEngagementUri, originInfos) diff --git a/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/DeviceRetrievalHelper.java b/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/DeviceRetrievalHelper.java index 25e16bc3a..0039e5b04 100644 --- a/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/DeviceRetrievalHelper.java +++ b/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/DeviceRetrievalHelper.java @@ -166,7 +166,9 @@ public void onConnected() { EngagementGenerator generator = new EngagementGenerator( mEDeviceKeyPair.getPublic(), EngagementGenerator.ENGAGEMENT_VERSION_1_1); - generator.setOriginInfos(mReverseEngagementOriginInfos); + if (mReverseEngagementOriginInfos != null) { + generator.setOriginInfos(mReverseEngagementOriginInfos); + } mDeviceEngagement = generator.generate(); // 18013-7 says to use ReaderEngagementBytes for Handover when ReaderEngagement @@ -713,7 +715,7 @@ public Builder(@NonNull Context context, */ public @NonNull Builder useReverseEngagement(@NonNull DataTransport transport, @Nullable byte[] readerEngagement, - List originInfos) { + @Nullable List originInfos) { mHelper.mTransport = transport; mHelper.mReverseEngagementReaderEngagement = readerEngagement; mHelper.mReverseEngagementOriginInfos = originInfos; diff --git a/identity/src/main/java/com/android/identity/mdoc/engagement/EngagementGenerator.java b/identity/src/main/java/com/android/identity/mdoc/engagement/EngagementGenerator.java index b98879384..8a730e457 100644 --- a/identity/src/main/java/com/android/identity/mdoc/engagement/EngagementGenerator.java +++ b/identity/src/main/java/com/android/identity/mdoc/engagement/EngagementGenerator.java @@ -41,7 +41,7 @@ public final class EngagementGenerator { private static final String TAG = "EngagementGenerator"; private final String mVersion; final private PublicKey mESenderKey; - private ArrayBuilder mConnectionMethodsArrayBuilder; + private ArrayBuilder mDeviceRetrievalMethodsArrayBuilder; private ArrayBuilder mOriginInfoArrayBuilder; public static final String ENGAGEMENT_VERSION_1_0 = "1.0"; @@ -75,9 +75,9 @@ public EngagementGenerator(@NonNull PublicKey ESenderKey, */ public @NonNull EngagementGenerator setConnectionMethods(@NonNull List connectionMethods) { - mConnectionMethodsArrayBuilder = new CborBuilder().addArray(); + mDeviceRetrievalMethodsArrayBuilder = new CborBuilder().addArray(); for (ConnectionMethod connectionMethod : connectionMethods) { - mConnectionMethodsArrayBuilder.add(Util.cborDecode(connectionMethod.toDeviceEngagement())); + mDeviceRetrievalMethodsArrayBuilder.add(Util.cborDecode(connectionMethod.toDeviceEngagement())); } return this; } @@ -118,8 +118,8 @@ byte[] generate() { MapBuilder map = builder.addMap(); map.put(0, mVersion); map.put(new UnsignedInteger(1), securityDataItem); - if (mConnectionMethodsArrayBuilder != null) { - map.put(new UnsignedInteger(2), mConnectionMethodsArrayBuilder.end().build().get(0)); + if (mDeviceRetrievalMethodsArrayBuilder != null) { + map.put(new UnsignedInteger(2), mDeviceRetrievalMethodsArrayBuilder.end().build().get(0)); } if (mOriginInfoArrayBuilder != null) { map.put(new UnsignedInteger(5), mOriginInfoArrayBuilder.end().build().get(0)); diff --git a/identity/src/main/java/com/android/identity/mdoc/mso/MobileSecurityObjectParser.java b/identity/src/main/java/com/android/identity/mdoc/mso/MobileSecurityObjectParser.java index af6c3244b..d93a3d3d2 100644 --- a/identity/src/main/java/com/android/identity/mdoc/mso/MobileSecurityObjectParser.java +++ b/identity/src/main/java/com/android/identity/mdoc/mso/MobileSecurityObjectParser.java @@ -24,6 +24,7 @@ import java.security.PublicKey; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -305,7 +306,7 @@ void parse(byte[] encodedMobileSecurityObject) { } mDigestAlgorithm = Util.cborMapExtractString(mso, "digestAlgorithm"); - final List allowableDigestAlgorithms = List.of("SHA-256", "SHA-384", "SHA-512"); + final List allowableDigestAlgorithms = Arrays.asList("SHA-256", "SHA-384", "SHA-512"); if (!allowableDigestAlgorithms.contains(mDigestAlgorithm)) { throw new IllegalArgumentException("Given digest algorithm '" + mDigestAlgorithm + "' one of " + allowableDigestAlgorithms); diff --git a/identity/src/main/java/com/android/identity/mdoc/mso/StaticAuthDataGenerator.java b/identity/src/main/java/com/android/identity/mdoc/mso/StaticAuthDataGenerator.java index 5f8bb5090..39d7922ae 100644 --- a/identity/src/main/java/com/android/identity/mdoc/mso/StaticAuthDataGenerator.java +++ b/identity/src/main/java/com/android/identity/mdoc/mso/StaticAuthDataGenerator.java @@ -20,8 +20,6 @@ import com.android.identity.internal.Util; -import java.security.cert.X509Certificate; -import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfo.java b/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfo.java index 4623a46c4..e948e51ee 100644 --- a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfo.java +++ b/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfo.java @@ -30,23 +30,7 @@ public abstract class OriginInfo { private static final String TAG = "OriginInfo"; - /** - * The constant used to specify how the current engagement structure is delivered. - */ - public static final long CAT_DELIVERY = 0; - - /** - * The constant used to specify how the other party engagement structure has been received. - */ - public static final long CAT_RECEIVE = 1; - - /** - * Specifies whether the OriginInfoOptions are about this engagement or the one - * received previously - * - * @return one of {@link #CAT_DELIVERY} or {@link #CAT_RECEIVE}. - */ - public abstract long getCat(); + public static final long CAT = 1; public abstract @NonNull DataItem encode(); @@ -56,11 +40,8 @@ public abstract class OriginInfo { } long type = Util.cborMapExtractNumber(oiDataItem, "type"); switch ((int) type) { - case OriginInfoQr.TYPE: - return OriginInfoQr.decode(oiDataItem); - case OriginInfoNfc.TYPE: - return OriginInfoNfc.decode(oiDataItem); - case OriginInfoWebsite.TYPE: + case OriginInfoWebsite.TYPE_REFERRER: + case OriginInfoWebsite.TYPE_BASE: return OriginInfoWebsite.decode(oiDataItem); } Logger.w(TAG, "Unsupported type " + type); diff --git a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoNfc.java b/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoNfc.java deleted file mode 100644 index 3cf7756c0..000000000 --- a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoNfc.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.mdoc.origininfo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.identity.internal.Util; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.SimpleValue; -import co.nstant.in.cbor.model.UnicodeString; -import com.android.identity.util.Logger; - -public class OriginInfoNfc extends OriginInfo { - private static final String TAG = "OriginInfoNfc"; - - static final int TYPE = 3; - private final long mCat; - - public OriginInfoNfc(long cat) { - mCat = cat; - } - - /** - * Specifies whether the OriginInfoOptions are about this engagement or the one - * received previously - * - * @return one of {@link #CAT_DELIVERY} or {@link #CAT_RECEIVE}. - */ - @Override - public long getCat() { - return mCat; - } - - @NonNull - @Override - public DataItem encode() { - return new CborBuilder() - .addMap() - .put("cat", mCat) - .put("type", TYPE) - .put(new UnicodeString("Details"), SimpleValue.NULL) - .end() - .build().get(0); - } - - @Nullable - public static OriginInfoNfc decode(@NonNull DataItem oiDataItem) { - if (!(oiDataItem instanceof co.nstant.in.cbor.model.Map)) { - throw new IllegalArgumentException("Top-level CBOR is not an map"); - } - long cat = Util.cborMapExtractNumber(oiDataItem, "cat"); - long type = Util.cborMapExtractNumber(oiDataItem, "type"); - if (type != TYPE) { - Logger.w(TAG, "Unexpected type " + type); - return null; - } - return new OriginInfoNfc(cat); - } -} diff --git a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoQr.java b/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoQr.java deleted file mode 100644 index a198f6783..000000000 --- a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoQr.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.mdoc.origininfo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.identity.internal.Util; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.SimpleValue; -import co.nstant.in.cbor.model.UnicodeString; -import com.android.identity.util.Logger; - -public class OriginInfoQr extends OriginInfo { - private static final String TAG = "OriginInfoQr"; - - static final int TYPE = 2; - private final long mCat; - - public OriginInfoQr(long cat) { - mCat = cat; - } - - /** - * Specifies whether the OriginInfoOptions are about this engagement or the one - * received previously - * - * @return one of {@link #CAT_DELIVERY} or {@link #CAT_RECEIVE}. - */ - @Override - public long getCat() { - return mCat; - } - - @NonNull - @Override - public DataItem encode() { - return new CborBuilder() - .addMap() - .put("cat", mCat) - .put("type", TYPE) - .put(new UnicodeString("Details"), SimpleValue.NULL) - .end() - .build().get(0); - } - - @Nullable - public static OriginInfoQr decode(@NonNull DataItem oiDataItem) { - if (!(oiDataItem instanceof co.nstant.in.cbor.model.Map)) { - throw new IllegalArgumentException("Top-level CBOR is not an map"); - } - long cat = Util.cborMapExtractNumber(oiDataItem, "cat"); - long type = Util.cborMapExtractNumber(oiDataItem, "type"); - if (type != TYPE) { - Logger.w(TAG, "Unexpected type " + type); - return null; - } - return new OriginInfoQr(cat); - } -} diff --git a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoWebsite.java b/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoWebsite.java index 0fcdfb2dc..c746edfa1 100644 --- a/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoWebsite.java +++ b/identity/src/main/java/com/android/identity/mdoc/origininfo/OriginInfoWebsite.java @@ -16,6 +16,7 @@ package com.android.identity.mdoc.origininfo; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,31 +27,35 @@ import co.nstant.in.cbor.model.Map; import com.android.identity.util.Logger; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class OriginInfoWebsite extends OriginInfo { private static final String TAG = "OriginInfoWebsite"; - static final int TYPE = 1; - private final long mCat; - private final String mBaseUrl; - - public OriginInfoWebsite(long cat, String baseUrl) { - mCat = cat; - mBaseUrl = baseUrl; - } + public static final int TYPE_REFERRER = 1; + public static final int TYPE_BASE = 2; /** - * Specifies whether the OriginInfoOptions are about this engagement or the one - * received previously - * - * @return one of {@link #CAT_DELIVERY} or {@link #CAT_RECEIVE}. + * All allowable types for OriginInfos related to a website. */ - @Override - public long getCat() { - return mCat; + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + TYPE_REFERRER, + TYPE_BASE, + }) + @interface Type{} + + private final String mUrl; + private final @Type int mType; + + public OriginInfoWebsite(String url, @Type int mType) { + mUrl = url; + this.mType = mType; } - public String getBaseUrl() { - return mBaseUrl; + public String getUrl() { + return mUrl; } @NonNull @@ -58,11 +63,9 @@ public String getBaseUrl() { public DataItem encode() { return new CborBuilder() .addMap() - .put("cat", mCat) - .put("type", TYPE) - .putMap("Details") - .put("baseUrl", mBaseUrl) - .end() + .put("cat", CAT) + .put("type", mType) + .put("details", mUrl) .end() .build().get(0); } @@ -72,17 +75,13 @@ public static OriginInfoWebsite decode(@NonNull DataItem oiDataItem) { if (!(oiDataItem instanceof Map)) { throw new IllegalArgumentException("Top-level CBOR is not an map"); } - long cat = Util.cborMapExtractNumber(oiDataItem, "cat"); - long type = Util.cborMapExtractNumber(oiDataItem, "type"); - DataItem details = Util.cborMapExtractMap(oiDataItem, "Details"); - if (!(details instanceof Map)) { - throw new IllegalArgumentException("Details is not a map"); - } - String baseUrl = Util.cborMapExtractString(details, "baseUrl"); - if (type != TYPE) { + long cat = Util.cborMapExtractNumber(oiDataItem, "cat"); // always 1 + int type = (int) Util.cborMapExtractNumber(oiDataItem, "type"); + String url = Util.cborMapExtractString(oiDataItem, "details"); + if (type != TYPE_REFERRER && type != TYPE_BASE) { Logger.w(TAG, "Unexpected type " + type); return null; } - return new OriginInfoWebsite(cat, baseUrl); + return new OriginInfoWebsite(url, type); } } diff --git a/identity/src/test/java/com/android/identity/mdoc/engagement/EngagementGeneratorTest.java b/identity/src/test/java/com/android/identity/mdoc/engagement/EngagementGeneratorTest.java index 72b98d73b..2ef7e824a 100644 --- a/identity/src/test/java/com/android/identity/mdoc/engagement/EngagementGeneratorTest.java +++ b/identity/src/test/java/com/android/identity/mdoc/engagement/EngagementGeneratorTest.java @@ -69,7 +69,8 @@ public void testWebsiteEngagement() throws Exception { connectionMethods.add(new ConnectionMethodHttp("http://www.example.com/verifier/123")); eg.setConnectionMethods(connectionMethods); List originInfos = new ArrayList<>(); - originInfos.add(new OriginInfoWebsite(OriginInfo.CAT_DELIVERY, "http://www.example.com/verifier")); + originInfos.add(new OriginInfoWebsite("http://www.example.com/verifier", + OriginInfoWebsite.TYPE_REFERRER)); eg.setOriginInfos(originInfos); byte[] encodedEngagement = eg.generate(); @@ -83,7 +84,7 @@ public void testWebsiteEngagement() throws Exception { Assert.assertEquals("http://www.example.com/verifier/123", cm.getUri()); Assert.assertEquals(1, engagement.getOriginInfos().size()); OriginInfoWebsite oi = (OriginInfoWebsite) engagement.getOriginInfos().get(0); - Assert.assertEquals("http://www.example.com/verifier", oi.getBaseUrl()); + Assert.assertEquals("http://www.example.com/verifier", oi.getUrl()); } @Test diff --git a/identity/src/test/java/com/android/identity/mdoc/origininfo/OriginInfoTest.java b/identity/src/test/java/com/android/identity/mdoc/origininfo/OriginInfoTest.java index 3f0ad843b..3dcc3a99f 100644 --- a/identity/src/test/java/com/android/identity/mdoc/origininfo/OriginInfoTest.java +++ b/identity/src/test/java/com/android/identity/mdoc/origininfo/OriginInfoTest.java @@ -26,80 +26,28 @@ @RunWith(JUnit4.class) public class OriginInfoTest { @Test - public void testOriginInfoQr() { - OriginInfoQr info = new OriginInfoQr(OriginInfo.CAT_RECEIVE); - OriginInfoQr decoded = OriginInfoQr.decode(info.encode()); - Assert.assertEquals(OriginInfo.CAT_RECEIVE, decoded.getCat()); - Assert.assertEquals("{\n" + - " 'cat' : 1,\n" + - " 'type' : 2,\n" + - " 'Details' : null\n" + - "}", Util.cborPrettyPrint(info.encode())); - } - - @Test - public void testOriginInfoQrDelivery() { - OriginInfoQr info = new OriginInfoQr(OriginInfo.CAT_DELIVERY); - OriginInfoQr decoded = OriginInfoQr.decode(info.encode()); - Assert.assertEquals(OriginInfo.CAT_DELIVERY, decoded.getCat()); - Assert.assertEquals("{\n" + - " 'cat' : 0,\n" + - " 'type' : 2,\n" + - " 'Details' : null\n" + - "}", Util.cborPrettyPrint(info.encode())); - } - - @Test - public void testOriginInfoNfc() { - OriginInfoNfc info = new OriginInfoNfc(OriginInfo.CAT_RECEIVE); - OriginInfoNfc decoded = OriginInfoNfc.decode(info.encode()); - Assert.assertEquals(OriginInfo.CAT_RECEIVE, decoded.getCat()); - Assert.assertEquals("{\n" + - " 'cat' : 1,\n" + - " 'type' : 3,\n" + - " 'Details' : null\n" + - "}", Util.cborPrettyPrint(info.encode())); - } - - @Test - public void testOriginInfoNfcDelivery() { - OriginInfoNfc info = new OriginInfoNfc(OriginInfo.CAT_DELIVERY); - OriginInfoNfc decoded = OriginInfoNfc.decode(info.encode()); - Assert.assertEquals(OriginInfo.CAT_DELIVERY, decoded.getCat()); - Assert.assertEquals("{\n" + - " 'cat' : 0,\n" + - " 'type' : 3,\n" + - " 'Details' : null\n" + - "}", Util.cborPrettyPrint(info.encode())); - } - - @Test - public void testOriginInfoWebsite() { - OriginInfoWebsite info = new OriginInfoWebsite(OriginInfo.CAT_RECEIVE, "https://foo.com/bar"); + public void testOriginInfoWebsiteReferrer() { + OriginInfoWebsite info = new OriginInfoWebsite("https://foo.com/bar", + OriginInfoWebsite.TYPE_REFERRER); OriginInfoWebsite decoded = OriginInfoWebsite.decode(info.encode()); - Assert.assertEquals(OriginInfo.CAT_RECEIVE, decoded.getCat()); - Assert.assertEquals("https://foo.com/bar", decoded.getBaseUrl()); + Assert.assertEquals("https://foo.com/bar", decoded.getUrl()); Assert.assertEquals("{\n" + " 'cat' : 1,\n" + " 'type' : 1,\n" + - " 'Details' : {\n" + - " 'baseUrl' : 'https://foo.com/bar'\n" + - " }\n" + + " 'details' : 'https://foo.com/bar'\n" + "}", Util.cborPrettyPrint(info.encode())); } @Test - public void testOriginInfoWebsiteDelivery() { - OriginInfoWebsite info = new OriginInfoWebsite(OriginInfo.CAT_DELIVERY, "https://foo.com/baz"); + public void testOriginInfoWebsiteBase() { + OriginInfoWebsite info = new OriginInfoWebsite("https://foo.com/bar", + OriginInfoWebsite.TYPE_BASE); OriginInfoWebsite decoded = OriginInfoWebsite.decode(info.encode()); - Assert.assertEquals(OriginInfo.CAT_DELIVERY, decoded.getCat()); + Assert.assertEquals("https://foo.com/bar", decoded.getUrl()); Assert.assertEquals("{\n" + - " 'cat' : 0,\n" + - " 'type' : 1,\n" + - " 'Details' : {\n" + - " 'baseUrl' : 'https://foo.com/baz'\n" + - " }\n" + + " 'cat' : 1,\n" + + " 'type' : 2,\n" + + " 'details' : 'https://foo.com/bar'\n" + "}", Util.cborPrettyPrint(info.encode())); } - } diff --git a/settings.gradle b/settings.gradle index 9b99412b2..f48cc0d38 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,9 +19,10 @@ dependencyResolutionManagement { content { includeGroup("org.jetbrains.anko") includeGroup("com.budiyev.android") + includeGroup("org.gretty") } } } } -include ':appholder', ':appverifier', ':identity', ':identity-android', ':testapp' +include ':appholder', ':appverifier', ':identity', ':identity-android', ':testapp', ':wwwverifier' rootProject.name = 'Identity Credential' diff --git a/wwwverifier/build.gradle b/wwwverifier/build.gradle index 0d69feb79..5891cf4c5 100644 --- a/wwwverifier/build.gradle +++ b/wwwverifier/build.gradle @@ -17,13 +17,13 @@ plugins { id 'maven-publish' } -repositories { - jcenter() - mavenLocal() - maven { - url = uri('https://repo.maven.apache.org/maven2/') - } -} +//repositories { +// jcenter() +// mavenLocal() +// maven { +// url = uri('https://repo.maven.apache.org/maven2/') +// } +//} apply plugin: 'org.gretty' apply plugin: 'java' @@ -37,6 +37,8 @@ dependencies { implementation 'co.nstant.in:cbor:0.9' implementation 'org.json:json:20160810' implementation 'org.bouncycastle:bcprov-jdk15on:1.70' +// implementation 'com.android.identity:identity-credential:+' + implementation project(path: ':identity') testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:3.2.4' testImplementation 'com.google.appengine:appengine-testing:1.9.64' @@ -55,9 +57,9 @@ appengine { } } -group = 'com.google.sps' -version = '1' -description = 'portfolio' +// group = 'com.google.sps' +// version = '1' +// description = 'portfolio' java.sourceCompatibility = JavaVersion.VERSION_1_8 java.targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/wwwverifier/src/main/java/com/google/sps/servlets/RequestServlet.java b/wwwverifier/src/main/java/com/android/identity/wwwreader/RequestServlet.java similarity index 90% rename from wwwverifier/src/main/java/com/google/sps/servlets/RequestServlet.java rename to wwwverifier/src/main/java/com/android/identity/wwwreader/RequestServlet.java index ed3185894..b352a8e8e 100644 --- a/wwwverifier/src/main/java/com/google/sps/servlets/RequestServlet.java +++ b/wwwverifier/src/main/java/com/android/identity/wwwreader/RequestServlet.java @@ -1,12 +1,39 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.identity.wwwreader; import co.nstant.in.cbor.CborBuilder; + +import com.android.identity.internal.Util; +import com.android.identity.mdoc.connectionmethod.ConnectionMethod; +import com.android.identity.mdoc.connectionmethod.ConnectionMethodHttp; +import com.android.identity.mdoc.engagement.EngagementGenerator; +import com.android.identity.mdoc.engagement.EngagementParser; +import com.android.identity.mdoc.origininfo.OriginInfo; +import com.android.identity.mdoc.origininfo.OriginInfoWebsite; +import com.android.identity.mdoc.request.DeviceRequestGenerator; +import com.android.identity.mdoc.response.DeviceResponseParser; +import com.android.identity.mdoc.sessionencryption.SessionEncryption; +import com.android.identity.util.Timestamp; import com.google.gson.Gson; import java.util.ArrayList; import java.util.Base64; import java.util.Date; import java.util.HashMap; -import java.util.OptionalInt; import java.util.List; import java.util.Map; import java.text.DateFormat; @@ -27,6 +54,8 @@ // Java servlet imports import java.io.IOException; +import java.util.OptionalLong; + import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -170,14 +199,14 @@ private static byte[] createDeviceRequest(byte[] messageData, Key key) { byte[] sessionTranscript = buildSessionTranscript(encodedEngagement, eReaderKeyPublic, key); setDatastoreProp(ServletConsts.TRANSCRIPT_PROP, sessionTranscript, key); - SessionEncryptionReader ser = new SessionEncryptionReader(eReaderKeyPrivate, - eReaderKeyPublic, eDeviceKeyPublic, sessionTranscript); + SessionEncryption ser = new SessionEncryption(SessionEncryption.ROLE_MDOC_READER, + new KeyPair(eReaderKeyPublic, eReaderKeyPrivate), eDeviceKeyPublic, sessionTranscript); ser.setSendSessionEstablishment(false); byte[] dr = new DeviceRequestGenerator() .setSessionTranscript(sessionTranscript) .addDocumentRequest(ServletConsts.MDL_DOCTYPE, createMdlItemsToRequest(), null, null, null) .generate(); - return ser.encryptMessageToDevice(dr, OptionalInt.empty()); + return ser.encryptMessage(dr, OptionalLong.empty()); } /** @@ -202,7 +231,7 @@ public static byte[] buildSessionTranscript(byte[] de, PublicKey eReaderKeyPubli */ private static void verifyOriginInfo(List oiList, Key key) { if (oiList.size() > 0) { - String oiUrl = ((OriginInfoWebsite) oiList.get(0)).getBaseUrl(); + String oiUrl = ((OriginInfoWebsite) oiList.get(0)).getUrl(); if (!oiUrl.equals(ServletConsts.BASE_URL)) { setOriginInfoStatus(ServletConsts.OI_FAILURE_START + oiUrl + ServletConsts.OI_FAILURE_END, key); @@ -243,8 +272,10 @@ private static Map> createMdlItemsToRequest() { public static byte[] generateReaderEngagement(PublicKey publicKey, Key key) { EngagementGenerator eg = new EngagementGenerator(publicKey, EngagementGenerator.ENGAGEMENT_VERSION_1_1); - eg.addConnectionMethod(new ConnectionMethodHttp(ServletConsts.ABSOLUTE_URL + "/" - + com.google.appengine.api.datastore.KeyFactory.keyToString(key))); + List connectionMethods = new ArrayList<>(); + connectionMethods.add(new ConnectionMethodHttp(ServletConsts.ABSOLUTE_URL + "/" + + com.google.appengine.api.datastore.KeyFactory.keyToString(key))); + eg.setConnectionMethods(connectionMethods); return eg.generate(); } @@ -294,19 +325,19 @@ private static byte[] parseDeviceResponse(byte[] messageData, Key key) { getPublicKey(getDatastoreProp(ServletConsts.DEVKEY_PROP, key)); byte[] sessionTranscript = getDatastoreProp(ServletConsts.TRANSCRIPT_PROP, key); - SessionEncryptionReader ser = new SessionEncryptionReader(eReaderKeyPrivate, - eReaderKeyPublic, eDeviceKeyPublic, sessionTranscript); + SessionEncryption ser = new SessionEncryption(SessionEncryption.ROLE_MDOC_READER, + new KeyPair(eReaderKeyPublic, eReaderKeyPrivate), eDeviceKeyPublic, sessionTranscript); ser.setSendSessionEstablishment(false); DeviceResponseParser.DeviceResponse dr = new DeviceResponseParser() - .setDeviceResponse(ser.decryptMessageFromDevice(messageData)) + .setDeviceResponse(ser.decryptMessage(messageData).getData()) .setSessionTranscript(sessionTranscript) .setEphemeralReaderKey(eReaderKeyPrivate) .parse(); String json = new Gson().toJson(buildArrayFromDocuments(dr.getDocuments(), key)); setDeviceResponse(json, key); - return ser.encryptMessageToDevice(null, OptionalInt.of(20)); + return ser.encryptMessage(null, OptionalLong.of(20L)); } /** @@ -398,7 +429,7 @@ private static Entity getEntity(Key key) { /** * @return String converted from a Timestamp object @param ts */ - private static String timestampToString(com.android.identity.wwwreader.Timestamp ts) { + private static String timestampToString(Timestamp ts) { DateFormat df = new SimpleDateFormat("MM/dd/yyyy"); return df.format(new Date(ts.toEpochMilli())); } diff --git a/wwwverifier/src/main/java/com/google/sps/servlets/ServletConsts.java b/wwwverifier/src/main/java/com/android/identity/wwwreader/ServletConsts.java similarity index 77% rename from wwwverifier/src/main/java/com/google/sps/servlets/ServletConsts.java rename to wwwverifier/src/main/java/com/android/identity/wwwreader/ServletConsts.java index d166939af..0bc94a910 100644 --- a/wwwverifier/src/main/java/com/google/sps/servlets/ServletConsts.java +++ b/wwwverifier/src/main/java/com/android/identity/wwwreader/ServletConsts.java @@ -1,3 +1,19 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.identity.wwwreader; public class ServletConsts { diff --git a/wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethod.java b/wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethod.java deleted file mode 100644 index 67d8636dd..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethod.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.android.identity.wwwreader; - -import static java.nio.charset.StandardCharsets.UTF_8; - -//import android.content.Context; - -//import android.util.Log; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.util.List; - -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Number; - -/** - * A class representing the ConnectionMethod structure exchanged between mdoc and mdoc reader. - * - *

This is an abstract class - applications are expected to interact with concrete - * implementations, for example {@link ConnectionMethodBle} or {@link ConnectionMethodNfc}. - */ -public abstract class ConnectionMethod { - private static final String TAG = "ConnectionMethod"; - - abstract - DataItem toDeviceEngagement(); - - static - ConnectionMethod fromDeviceEngagement(DataItem cmDataItem) { - if (!(cmDataItem instanceof co.nstant.in.cbor.model.Array)) { - throw new IllegalArgumentException("Top-level CBOR is not an array"); - } - List items = ((Array) cmDataItem).getDataItems(); - if (items.size() != 3) { - throw new IllegalArgumentException("Expected array with 3 elements, got " + items.size()); - } - if (!(items.get(0) instanceof Number) || !(items.get(1) instanceof Number)) { - throw new IllegalArgumentException("First two items are not numbers"); - } - long type = ((Number) items.get(0)).getValue().longValue(); - if (!(items.get(2) instanceof co.nstant.in.cbor.model.Map)) { - throw new IllegalArgumentException("Third item is not a map"); - } - switch ((int) type) { - /* - case ConnectionMethodNfc.METHOD_TYPE: - return ConnectionMethodNfc.decode(cmDataItem); - case ConnectionMethodBle.METHOD_TYPE: - return ConnectionMethodBle.decode(cmDataItem); - case ConnectionMethodWifiAware.METHOD_TYPE: - return ConnectionMethodWifiAware.decode(cmDataItem); - */ - case ConnectionMethodHttp.METHOD_TYPE: - return ConnectionMethodHttp.fromDeviceEngagement(cmDataItem); - } - //Log.w(TAG, "Unsupported type " + type); - return null; - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethodHttp.java b/wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethodHttp.java deleted file mode 100644 index 457e70dd9..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/ConnectionMethodHttp.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.android.identity.wwwreader; - -//import android.content.Context; - -//import android.util.Log; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.Number; - -/** - * Connection method for REST API. - */ -public class ConnectionMethodHttp extends ConnectionMethod { - private static final String TAG = "ConnectionOptionsRestApi"; - private String mUriWebsite; - - static final int METHOD_TYPE = 4; - static final int METHOD_MAX_VERSION = 1; - private static final int OPTION_KEY_URI_WEBSITE = 0; - - /** - * Creates a new connection method for REST API. - * - * @param uriWebsite the URL for the website. - */ - public ConnectionMethodHttp(String uriWebsite) { - mUriWebsite = uriWebsite; - } - - /** - * Gets the URL for the website. - * - * @return the website URL. - */ - public String getUriWebsite() { - return mUriWebsite; - } - - @Override - public String toString() { - return "http:uri=" + mUriWebsite; - } - - static ConnectionMethodHttp fromDeviceEngagement(DataItem cmDataItem) { - if (!(cmDataItem instanceof co.nstant.in.cbor.model.Array)) { - throw new IllegalArgumentException("Top-level CBOR is not an array"); - } - List items = ((Array) cmDataItem).getDataItems(); - if (items.size() != 3) { - throw new IllegalArgumentException("Expected array with 3 elements, got " + items.size()); - } - if (!(items.get(0) instanceof Number) || !(items.get(1) instanceof Number)) { - throw new IllegalArgumentException("First two items are not numbers"); - } - long type = ((Number) items.get(0)).getValue().longValue(); - long version = ((Number) items.get(1)).getValue().longValue(); - if (!(items.get(2) instanceof co.nstant.in.cbor.model.Map)) { - throw new IllegalArgumentException("Third item is not a map"); - } - DataItem options = (Map) items.get(2); - if (type != METHOD_TYPE) { - //Log.w(TAG, "Unexpected method type " + type); - return null; - } - if (version > METHOD_MAX_VERSION) { - //Log.w(TAG, "Unsupported options version " + version); - return null; - } - return new ConnectionMethodHttp( - Util.cborMapExtractString(options, OPTION_KEY_URI_WEBSITE)); - } - - @Override - DataItem toDeviceEngagement() { - MapBuilder builder = new CborBuilder().addMap(); - builder.put(OPTION_KEY_URI_WEBSITE, mUriWebsite); - return new CborBuilder() - .addArray() - .add(METHOD_TYPE) - .add(METHOD_MAX_VERSION) - .add(builder.end().build().get(0)) - .end() - .build().get(0); - } -} diff --git a/wwwverifier/src/main/java/com/google/sps/identity/Constants.java b/wwwverifier/src/main/java/com/google/sps/identity/Constants.java deleted file mode 100644 index ba660024b..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/Constants.java +++ /dev/null @@ -1,136 +0,0 @@ -/* -* Copyright 2022 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.identity.wwwreader; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -//import androidx.annotation.IntDef; -//import androidx.annotation.LongDef; - -import java.lang.annotation.Retention; - -/** -* Various constants used by other classes. -*/ -public class Constants { - - /** - * Normal processing. This status message shall be - * returned if no other status is returned. - * - *

This value is defined in ISO/IEC 18013-5 Table 8. - */ - public static final long DEVICE_RESPONSE_STATUS_OK = 0; - /** - * The mdoc returns an error without any given - * reason. No data is returned. - * - *

This value is defined in ISO/IEC 18013-5 Table 8. - */ - public static final long DEVICE_RESPONSE_STATUS_GENERAL_ERROR = 10; - /** - * The mdoc indicates an error during CBOR decoding - * that the data received is not valid CBOR. Returning - * this status code is optional. - * - *

This value is defined in ISO/IEC 18013-5 Table 8. - */ - public static final long DEVICE_RESPONSE_STATUS_CBOR_DECODING_ERROR = 11; - /** - * The mdoc indicates an error during CBOR - * validation, e.g. wrong CBOR structures. Returning - * this status code is optional. - * - *

This value is defined in ISO/IEC 18013-5 Table 8. - */ - public static final long DEVICE_RESPONSE_STATUS_CBOR_VALIDATION_ERROR = 12; - /** - * If this flag is set, {@link PresentationHelper} and {@link VerificationHelper} - * will log informational messages. - */ - public static final int LOGGING_FLAG_INFO = (1 << 0); - /** - * If this flag is set, {@link PresentationHelper} and {@link VerificationHelper} - * will log messages related to Device Engagement. - */ - public static final int LOGGING_FLAG_ENGAGEMENT = (1 << 1); - /** - * If this flag is set, {@link PresentationHelper} and {@link VerificationHelper} - * will log messages related to session layer encryption including the - * the hexadecimal representation of the cleartext messages in {@code SessionData} - * and {@code SessionEstablishment} CBOR messages that are sent and received. - * - *

This might generate a lot of data. - */ - public static final int LOGGING_FLAG_SESSION = (1 << 2); - /** - * If this flag is set, {@link PresentationHelper} and {@link VerificationHelper} - * will log transport specific high-level messages. For the actual content of - * each packet, use {@link #LOGGING_FLAG_TRANSPORT_VERBOSE}. - */ - public static final int LOGGING_FLAG_TRANSPORT = (1 << 3); - /** - * If this flag is set, {@link PresentationHelper} and {@link VerificationHelper} - * will log transport-specific data packets, for example APDUs for NFC transport. - * - *

This might generate a lot of data. - */ - public static final int LOGGING_FLAG_TRANSPORT_VERBOSE = (1 << 4); - - /** - * Constant to request maximum amount of logging when using {@link PresentationHelper} and - * {@link VerificationHelper}. - * - *

This is useful for e.g. tests using these primitives. - */ - public static final int LOGGING_FLAG_MAXIMUM = Integer.MAX_VALUE; - - /** - * Flag indicating that the mdoc central client mode should be supported - * for BLE data retrieval. - */ - public static final int BLE_DATA_RETRIEVAL_OPTION_MDOC_CENTRAL_CLIENT_MODE = (1 << 0); - /** - * Flag indicating that the mdoc peripheral server mode should be supported - * for BLE data retrieval. - */ - public static final int BLE_DATA_RETRIEVAL_OPTION_MDOC_PERIPHERAL_SERVER_MODE = (1 << 1); - /** - * Flag indicating that L2CAP should be used for data retrieval if available and supported. - */ - public static final int BLE_DATA_RETRIEVAL_OPTION_L2CAP = (1 << 2); - /** - * Flag indicating that BLE Services Cache should be cleared before service discovery - * when acting as a GATT Client. - */ - public static final int BLE_DATA_RETRIEVAL_CLEAR_CACHE = (1 << 3); - - /** - * The status code of the document response. - * - * These values are defined in ISO/IEC 18013-5 Table 8. - * - * @hidden - */ - @Retention(SOURCE) - //@LongDef({DEVICE_RESPONSE_STATUS_OK, - // DEVICE_RESPONSE_STATUS_GENERAL_ERROR, - // DEVICE_RESPONSE_STATUS_CBOR_DECODING_ERROR, - // DEVICE_RESPONSE_STATUS_CBOR_VALIDATION_ERROR}) - public @interface DeviceResponseStatus { - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestGenerator.java b/wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestGenerator.java deleted file mode 100644 index 39f51aaf1..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestGenerator.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.security.Signature; -import java.security.cert.X509Certificate; -import java.util.Collection; -import java.util.Map; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.builder.ArrayBuilder; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.UnicodeString; - -/** - * Helper class for building DeviceRequest CBOR - * as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. - * - *

This class supports requesting data for multiple documents in a single presentation. - */ -public final class DeviceRequestGenerator { - private static final String TAG = "DeviceRequestGenerator"; - - private final ArrayBuilder mDocRequestsBuilder; - private byte[] mEncodedSessionTranscript; - - /** - * Constructs a new {@link DeviceRequestGenerator}. - */ - public DeviceRequestGenerator() { - mDocRequestsBuilder = new CborBuilder().addArray(); - } - - /** - * Sets the bytes of the SessionTranscript CBOR. - * - * This must be called if any of the document requests use reader authentication. - * - * @param encodedSessionTranscript the bytes of SessionTranscript. - * @return the DeviceRequestGenerator. - */ - public DeviceRequestGenerator setSessionTranscript(byte[] encodedSessionTranscript) { - mEncodedSessionTranscript = encodedSessionTranscript; - return this; - } - - /** - * Adds a request for a document and which data elements to request. - * - * @param docType the document type. - * @param itemsToRequest the items to request as a map of namespaces into data - * element - * names into the intent-to-retain for each data element. - * @param requestInfo null or additional information provided. - * This is - * a map from keys and the values must be valid - * CBOR. - * @param readerKeySignature null if not signing the request, otherwise a - * {@link Signature} to be used for signing the request. - * @param readerKeyCertificateChain null if readerKeySignature is - * null, otherwise a chain of X.509 - * certificates for readerKey. - * @return the DeviceRequestGenerator. - */ - public DeviceRequestGenerator addDocumentRequest(String docType, - Map> itemsToRequest, - Map requestInfo, - Signature readerKeySignature, - Collection readerKeyCertificateChain) { - - CborBuilder nameSpacesBuilder = new CborBuilder(); - MapBuilder nsBuilder = nameSpacesBuilder.addMap(); - - for (String namespaceName : itemsToRequest.keySet()) { - Map innerMap = itemsToRequest.get(namespaceName); - MapBuilder> elemBuilder = nsBuilder.putMap(namespaceName); - for (String elemName : innerMap.keySet()) { - boolean intentToRetain = innerMap.get(elemName).booleanValue(); - elemBuilder.put(elemName, intentToRetain); - } - elemBuilder.end(); - } - nsBuilder.end(); - - CborBuilder itemsRequestBuilder = new CborBuilder(); - MapBuilder irMapBuilder = itemsRequestBuilder.addMap(); - irMapBuilder.put("docType", docType); - irMapBuilder.put(new UnicodeString("nameSpaces"), nameSpacesBuilder.build().get(0)); - if (requestInfo != null) { - MapBuilder> riBuilder = irMapBuilder.putMap("requestInfo"); - for (String key : requestInfo.keySet()) { - byte[] value = requestInfo.get(key); - DataItem valueDataItem = Util.cborDecode(value); - riBuilder.put(new UnicodeString(key), valueDataItem); - } - riBuilder.end(); - } - irMapBuilder.end(); - byte[] encodedItemsRequest = Util.cborEncode(itemsRequestBuilder.build().get(0)); - - DataItem itemsRequestBytesDataItem = Util.cborBuildTaggedByteString(encodedItemsRequest); - - DataItem readerAuth = null; - if (readerKeySignature != null) { - if (readerKeyCertificateChain == null) { - throw new IllegalArgumentException("readerKey is provided but no cert chain"); - } - if (mEncodedSessionTranscript == null) { - throw new IllegalStateException("sessionTranscript has not been set"); - } - - byte[] encodedReaderAuthentication = Util.cborEncode(new CborBuilder() - .addArray() - .add("ReaderAuthentication") - .add(Util.cborDecode(mEncodedSessionTranscript)) - .add(itemsRequestBytesDataItem) - .end() - .build().get(0)); - - byte[] readerAuthenticationBytes = - Util.cborEncode( - Util.cborBuildTaggedByteString( - encodedReaderAuthentication)); - - readerAuth = Util.coseSign1Sign(readerKeySignature, - null, - readerAuthenticationBytes, - readerKeyCertificateChain); - } - - CborBuilder docRequestBuilder = new CborBuilder(); - MapBuilder mapBuilder = docRequestBuilder.addMap(); - mapBuilder.put(new UnicodeString("itemsRequest"), itemsRequestBytesDataItem); - if (readerAuth != null) { - mapBuilder.put(new UnicodeString("readerAuth"), readerAuth); - } - DataItem docRequest = docRequestBuilder.build().get(0); - - mDocRequestsBuilder.add(docRequest); - return this; - } - - /** - * Builds the DeviceRequest CBOR. - * - * @return the bytes of DeviceRequest CBOR. - */ - public byte[] generate() { - return Util.cborEncode(new CborBuilder() - .addMap() - .put("version", "1.0") - .put(new UnicodeString("docRequests"), mDocRequestsBuilder.end().build().get(0)) - .end() - .build().get(0)); - } - -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestParser.java b/wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestParser.java deleted file mode 100644 index e5d7a1273..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/DeviceRequestParser.java +++ /dev/null @@ -1,415 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.UnicodeString; - -/** - * Helper class for parsing the bytes of DeviceRequest - * CBOR - * as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. - */ -public final class DeviceRequestParser { - static final String TAG = "DeviceRequestParser"; - - private byte[] mEncodedDeviceRequest; - private byte[] mEncodedSessionTranscript; - - /** - * Constructs a {@link DeviceRequestParser}. - */ - public DeviceRequestParser() { - } - - /** - * Sets the bytes of the DeviceRequest CBOR. - * - * @param encodedDeviceRequest the bytes of the DeviceRequest CBOR. - * @return the DeviceRequestParser. - */ - public DeviceRequestParser setDeviceRequest(byte[] encodedDeviceRequest) { - mEncodedDeviceRequest = encodedDeviceRequest; - return this; - } - - /** - * Sets the bytes of the SessionTranscript CBOR. - * - * @param encodedSessionTranscript the bytes of SessionTranscript. - * @return the DeviceRequestParser. - */ - public DeviceRequestParser setSessionTranscript(byte[] encodedSessionTranscript) { - mEncodedSessionTranscript = encodedSessionTranscript; - return this; - } - - /** - * Parses the device request. - * - *

This parser will successfully parse requests where the request is signed by the reader - * but the signature check fails. The method {@link DocumentRequest#getReaderAuthenticated()} - * can used to get additional information whether {@code ItemsRequest} was authenticated. - * - * @return a {@link DeviceRequestParser.DeviceRequest} with the parsed data. - * @throws IllegalArgumentException if the given data isn't valid CBOR or not conforming - * to the CDDL for its type. - * @throws IllegalStateException if required data hasn't been set using the setter - * methods on this class. - */ - public DeviceRequest parse() { - if (mEncodedDeviceRequest == null) { - throw new IllegalStateException("deviceRequest has not been set"); - } - if (mEncodedSessionTranscript == null) { - throw new IllegalStateException("sessionTranscript has not been set"); - } - DataItem sessionTranscript = Util.cborDecode(mEncodedSessionTranscript); - DeviceRequestParser.DeviceRequest request = new DeviceRequestParser.DeviceRequest(); - request.parse(mEncodedDeviceRequest, sessionTranscript); - return request; - } - - /** - * An object used to represent data parsed from DeviceRequest - * CBOR - * as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. - */ - public static class DeviceRequest { - static final String TAG = "DeviceRequest"; - private final List mDocumentRequests = new ArrayList<>(); - private String mVersion; - - DeviceRequest() { - } - - /** - * Gets the version string set in the DeviceRequest CBOR. - * - * @return the version string e.g. "1.0". - */ - public String getVersion() { - return mVersion; - } - - /** - * Gets the document requests in the DeviceRequest CBOR. - * - * @return a collection of {@link DocumentRequest} objects. - */ - public List getDocumentRequests() { - return Collections.unmodifiableList(mDocumentRequests); - } - - void parse(byte[] encodedDeviceRequest, - DataItem sessionTranscript) { - - DataItem request = Util.cborDecode(encodedDeviceRequest); - if (!(request instanceof Map)) { - throw new IllegalArgumentException("CBOR is not a map"); - } - - mVersion = Util.cborMapExtractString(request, "version"); - if (mVersion.compareTo("1.0") < 0) { - throw new IllegalArgumentException("Given version '" + mVersion + "' not >= '1.0'"); - } - - List readerCertChain = null; - if (Util.cborMapHasKey(request, "docRequests")) { - List docRequestsDataItems = Util.cborMapExtractArray(request, - "docRequests"); - for (DataItem docRequestDataItem : docRequestsDataItems) { - DataItem itemsRequestBytesDataItem = Util.cborMapExtract(docRequestDataItem, - "itemsRequest"); - if (!(itemsRequestBytesDataItem instanceof ByteString) - || !itemsRequestBytesDataItem.hasTag() - || itemsRequestBytesDataItem.getTag().getValue() != 24) { - throw new IllegalArgumentException( - "itemsRequest value is not a tagged bytestring"); - } - byte[] encodedItemsRequest = - ((ByteString) itemsRequestBytesDataItem).getBytes(); - DataItem itemsRequest = Util.cborDecode(encodedItemsRequest); - if (!(itemsRequest instanceof Map)) { - throw new IllegalArgumentException("itemsRequest is not a map"); - } - - DataItem readerAuth = ((Map) docRequestDataItem).get(new UnicodeString( - "readerAuth")); - byte[] encodedReaderAuth = null; - boolean readerAuthenticated = false; - if (readerAuth != null) { - encodedReaderAuth = Util.cborEncode(readerAuth); - - readerCertChain = Util.coseSign1GetX5Chain(readerAuth); - if (readerCertChain.size() < 1) { - throw new IllegalArgumentException( - "No x5chain element in reader signature"); - } - PublicKey readerKey = readerCertChain.iterator().next().getPublicKey(); - - byte[] encodedReaderAuthentication = Util.cborEncode(new CborBuilder() - .addArray() - .add("ReaderAuthentication") - .add(sessionTranscript) - .add(itemsRequestBytesDataItem) - .end() - .build().get(0)); - - byte[] readerAuthenticationBytes = - Util.cborEncode( - Util.cborBuildTaggedByteString( - encodedReaderAuthentication)); - - readerAuthenticated = Util.coseSign1CheckSignature(readerAuth, - readerAuthenticationBytes, // detached content - readerKey); - } - - DataItem requestInfoDataItem = ((Map) itemsRequest).get( - new UnicodeString("requestInfo")); - java.util.Map requestInfo = new HashMap<>(); - if (requestInfoDataItem != null) { - for (String key : Util.cborMapExtractMapStringKeys(requestInfoDataItem)) { - byte[] encodedValue = Util.cborEncode( - Util.cborMapExtract(requestInfoDataItem, key)); - requestInfo.put(key, encodedValue); - } - } - - String docType = Util.cborMapExtractString(itemsRequest, "docType"); - DocumentRequest.Builder builder = new DocumentRequest.Builder(docType, - encodedItemsRequest, requestInfo, encodedReaderAuth, readerCertChain, - readerAuthenticated); - - // parse nameSpaces - DataItem nameSpaces = Util.cborMapExtractMap(itemsRequest, "nameSpaces"); - parseNamespaces(nameSpaces, builder); - - - mDocumentRequests.add(builder.build()); - } - } - } - - private void parseNamespaces(DataItem nameSpaces, DocumentRequest.Builder builder) { - Collection nameSpacesKeys = Util.cborMapExtractMapStringKeys(nameSpaces); - for (String nameSpace : nameSpacesKeys) { - DataItem itemsMap = Util.cborMapExtractMap(nameSpaces, nameSpace); - Collection itemKeys = Util.cborMapExtractMapStringKeys(itemsMap); - for (String itemKey : itemKeys) { - boolean intentToRetain = Util.cborMapExtractBoolean(itemsMap, itemKey); - builder.addEntry(nameSpace, itemKey, intentToRetain); - } - } - } - } - - /** - * An object used to represent data parsed from the DocRequest - * CBOR (part of DeviceRequest) - * as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. - */ - public static final class DocumentRequest { - String mDocType; - byte[] mEncodedItemsRequest; - java.util.Map mRequestInfo; - byte[] mEncodedReaderAuth; - java.util.Map> mRequestMap = new LinkedHashMap<>(); - List mReaderCertificateChain; - boolean mReaderAuthenticated; - DocumentRequest(String docType, byte[] encodedItemsRequest, - java.util.Map requestInfo, - byte[] encodedReaderAuth, - List readerCertChain, - boolean readerAuthenticated) { - mDocType = docType; - mRequestInfo = requestInfo; - mEncodedItemsRequest = encodedItemsRequest; - mEncodedReaderAuth = encodedReaderAuth; - mReaderCertificateChain = readerCertChain; - mReaderAuthenticated = readerAuthenticated; - } - - /** - * Returns the document type (commonly referred to as docType) in the request. - * - * @return the document type. - */ - public String getDocType() { - return mDocType; - } - - /** - * Gets the requestInfo associated with the document request. - * - *

This is a map from strings into encoded CBOR. - * - * @return the request info map or the empty collection if not present in the request. - */ - public java.util.Map getRequestInfo() { - return mRequestInfo; - } - - /** - * Gets the bytes of the ItemsRequest CBOR. - * - * @return the bytes of the ItemsRequest CBOR. - */ - public byte[] getItemsRequest() { - return mEncodedItemsRequest; - } - - /** - * Gets the bytes of the ReaderAuth CBOR. - * - * @return the bytes of ReaderAuth or null if the reader didn't - * sign the document request. - */ - public byte[] getReaderAuth() { - return mEncodedReaderAuth; - } - - /** - * Returns the X509 certificate chain for the reader which signed the data in the - * document request. - * - * @return A X.509 certificate chain. - * @throws IllegalStateException if the reader didn't sign the request, that is - * if {@link #getReaderAuth()} returns a non-null value. - */ - public List getReaderCertificateChain() { - if (mEncodedReaderAuth == null) { - throw new IllegalStateException("Request isn't signed"); - } - return mReaderCertificateChain; - } - - /** - * Returns whether {@code ItemsRequest} was authenticated. - * - *

This returns {@code true} if and only if the {@code ItemsRequest} CBOR was - * signed by the leaf certificate in the X509 certificate chain presented by the - * reader. - * - *

If {@code true} is returned it only means that the signature was well-formed, - * not that the key-pair used to make the signature is trusted. Applications may - * examine the X509 certificate chain presented by the reader to determine if they - * trust any of the public keys in there. - * - * @return {@code true} if {@code ItemsRequest} was authenticated, {@code false} otherwise. - * @throws IllegalStateException if the reader didn't sign the request, that is - * if {@link #getReaderAuth()} returns a non-null value. - */ - public boolean getReaderAuthenticated() { - if (mEncodedReaderAuth == null) { - throw new IllegalStateException("Request isn't signed"); - } - return mReaderAuthenticated; - } - - /** - * Gets the names of namespaces that the reader requested. - * - * @return Collection of names of namespaces in the request. - */ - public List getNamespaces() { - return new ArrayList<>(mRequestMap.keySet()); - } - - /** - * Gets the names of data elements in the given namespace. - * - * @param namespaceName the name of the namespace. - * @return A collection of data element names or null if the namespace - * wasn't requested. - * @throws IllegalArgumentException if the given namespace wasn't requested. - */ - public List getEntryNames(String namespaceName) { - java.util.Map innerMap = mRequestMap.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace wasn't requested"); - } - return new ArrayList<>(innerMap.keySet()); - } - - /** - * Gets the intent-to-retain value set by the reader for a data element in the request. - * - * @param namespaceName the name of the namespace. - * @param entryName the name of the data element - * @return whether the reader intents to retain the value. - * @throws IllegalArgumentException if the given data element or namespace wasn't - * requested. - */ - public boolean getIntentToRetain(String namespaceName, String entryName) { - java.util.Map innerMap = mRequestMap.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace wasn't requested"); - } - Boolean value = innerMap.get(entryName); - if (value == null) { - throw new IllegalArgumentException("Data element wasn't requested"); - } - return value.booleanValue(); - } - - static class Builder { - private final DeviceRequestParser.DocumentRequest mResult; - - Builder(String docType, byte[] encodedItemsRequest, - java.util.Map requestInfo, - byte[] encodedReaderAuth, - List readerCertChain, - boolean readerAuthenticated) { - this.mResult = new DeviceRequestParser.DocumentRequest(docType, - encodedItemsRequest, requestInfo, encodedReaderAuth, readerCertChain, - readerAuthenticated); - } - - Builder addEntry(String namespaceName, String entryName, boolean intentToRetain) { - java.util.Map innerMap = mResult.mRequestMap.get(namespaceName); - if (innerMap == null) { - innerMap = new LinkedHashMap<>(); - mResult.mRequestMap.put(namespaceName, innerMap); - } - innerMap.put(entryName, intentToRetain); - return this; - } - - DocumentRequest build() { - return mResult; - } - } - - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/DeviceResponseParser.java b/wwwverifier/src/main/java/com/google/sps/identity/DeviceResponseParser.java deleted file mode 100644 index ffabc4e89..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/DeviceResponseParser.java +++ /dev/null @@ -1,947 +0,0 @@ -/* -* Copyright 2022 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.identity.wwwreader; - -//import android.util.Log; -//import android.util.Pair; -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import javax.crypto.SecretKey; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.UnicodeString; - -/** -* Helper class for parsing the bytes of DeviceResponse -* CBOR -* as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. -*/ -public final class DeviceResponseParser { - - private byte[] mEncodedDeviceResponse; - private byte[] mEncodedSessionTranscript; - private PrivateKey mEReaderKey; - - /** - * Constructs a {@link DeviceResponseParser}. - */ - public DeviceResponseParser() { - } - - /** - * Sets the bytes of the DeviceResponse CBOR. - * - * @param encodedDeviceResponse the bytes of DeviceResponse. - * @return the DeviceResponseParser. - */ - public DeviceResponseParser setDeviceResponse( byte[] encodedDeviceResponse) { - mEncodedDeviceResponse = encodedDeviceResponse; - return this; - } - - /** - * Sets the bytes of the SessionTranscript CBOR. - * - * @param encodedSessionTranscript the bytes of SessionTranscript. - * @return the DeviceResponseParser. - */ - public DeviceResponseParser setSessionTranscript( - byte[] encodedSessionTranscript) { - mEncodedSessionTranscript = encodedSessionTranscript; - return this; - } - - /** - * Sets the private part of the ephemeral key used in the session where the - * DeviceResponse was obtained. - * - *

This is only required if the DeviceResponse is using - * the MAC method for device authentication. - * - * @param eReaderKey the private part of the reader ephemeral key. - * @return the DeviceResponseParser. - */ - public DeviceResponseParser setEphemeralReaderKey( PrivateKey eReaderKey) { - mEReaderKey = eReaderKey; - return this; - } - - /** - * Parses the device response. - * - *

It's mandatory to call {@link #setDeviceResponse(byte[])}, - * {@link #setSessionTranscript(byte[])} before this call. If the response is using MAC for - * device authentication, {@link #setEphemeralReaderKey(PrivateKey)} must also have been - * called. - * - *

This parser will successfully parse responses where issuer-signed data elements fails - * the digest check against the MSO, where {@code DeviceSigned} authentication checks fail, - * and where {@code IssuerSigned} authentication checks fail. The methods - * {@link Document#getIssuerEntryDigestMatch(String, String)}, - * {@link Document#getDeviceSignedAuthenticated()}, and - * {@link Document#getIssuerSignedAuthenticated()} - * can be used to get additional information about this. - * - * @return a {@link DeviceResponseParser.DeviceResponse} with the parsed data. - * @exception IllegalArgumentException if the given data isn't valid CBOR or not conforming - * to the CDDL for its type. - * @exception IllegalStateException if required data hasn't been set using the setter - * methods on this class. - */ - public DeviceResponse parse() { - if (mEncodedDeviceResponse == null) { - throw new IllegalStateException("deviceResponse has not been set"); - } - if (mEncodedSessionTranscript == null) { - throw new IllegalStateException("sessionTranscript has not been set"); - } - // mEReaderKey may be omitted if the response is using ECDSA instead of MAC - // for device authentiation. - DeviceResponse response = new DeviceResponse(); - response.parse(mEncodedDeviceResponse, mEncodedSessionTranscript, mEReaderKey); - return response; - } - - /** - * An object used to represent data parsed from DeviceResponse - * CBOR - * as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. - */ - public static final class DeviceResponse { - static final String TAG = "DeviceResponse"; - - List mResultDocuments = null; - private String mVersion; - - // Returns deviceKey and digestIdMapping. The byte[] is the digest. - // - private Map>> parseMso(DataItem mso, - String expectedDoctype) { - /* don't care about version for now */ - String digestAlgorithm = Util.cborMapExtractString(mso, "digestAlgorithm"); - if (!digestAlgorithm.equals("SHA-256")) { - throw new IllegalArgumentException("Unsupported digestAlgorithm '" - + digestAlgorithm + "' in MSO"); - } - String msoDocType = Util.cborMapExtractString(mso, "docType"); - if (!msoDocType.equals(expectedDoctype)) { - throw new IllegalArgumentException("docType in MSO '" + msoDocType - + "' does not match docType from Document"); - } - DataItem valueDigests = Util.cborMapExtract(mso, "valueDigests"); - Collection nameSpaceNames = Util.cborMapExtractMapStringKeys(valueDigests); - Map> ret = new HashMap<>(); - for (String nameSpaceName : nameSpaceNames) { - DataItem elementMap = Util.cborMapExtract(valueDigests, nameSpaceName); - Collection elementDigestIDs = Util.cborMapExtractMapNumberKeys(elementMap); - Map innerRet = new HashMap<>(); - for (Long elementDigestID : elementDigestIDs) { - byte[] digest = Util.cborMapExtractByteString(elementMap, elementDigestID); - innerRet.put(elementDigestID, digest); - } - ret.put(nameSpaceName, innerRet); - } - - DataItem deviceKeyInfo = Util.cborMapExtractMap(mso, "deviceKeyInfo"); - DataItem deviceKeyCoseKey = Util.cborMapExtract(deviceKeyInfo, "deviceKey"); - PublicKey deviceKey = Util.coseKeyDecode(deviceKeyCoseKey); - - Map>> tempMap = new HashMap<>(); - tempMap.put(deviceKey, ret); - - //return Pair(deviceKey, ret); - return tempMap; - } - - private void parseValidityInfo(DataItem mso, Document.Builder builder) { - DataItem map = Util.cborMapExtractMap(mso, "validityInfo"); - builder.setValidityInfoSigned(Util.cborMapExtractDateTime(map, "signed")); - builder.setValidityInfoValidFrom(Util.cborMapExtractDateTime(map, "validFrom")); - builder.setValidityInfoValidUntil(Util.cborMapExtractDateTime(map, "validUntil")); - if (Util.cborMapHasKey(map, "expectedUpdate")) { - builder.setValidityInfoExpectedUpdate(Util.cborMapExtractDateTime(map, "expectedUpdate")); - } - } - - // Returns the DeviceKey from the MSO - // - private - PublicKey parseIssuerSigned( - String expectedDocType, - DataItem issuerSigned, - Document.Builder builder) { - - MessageDigest digester; - try { - digester = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Failed creating digester"); - } - - DataItem issuerAuthDataItem = Util.cborMapExtract(issuerSigned, "issuerAuth"); - - List issuerAuthorityCertChain = Util.coseSign1GetX5Chain( - issuerAuthDataItem); - if (issuerAuthorityCertChain.size() < 1) { - throw new IllegalArgumentException("No x5chain element in issuer signature"); - } - PublicKey issuerAuthorityKey = - issuerAuthorityCertChain.iterator().next().getPublicKey(); - - boolean issuerSignedAuthenticated = Util.coseSign1CheckSignature( - issuerAuthDataItem, null, issuerAuthorityKey); - //Log.d(TAG, "issuerSignedAuthenticated: " + issuerSignedAuthenticated); - builder.setIssuerSignedAuthenticated(issuerSignedAuthenticated); - builder.setIssuerCertificateChain(issuerAuthorityCertChain); - - DataItem mobileSecurityObjectBytes = Util.cborDecode( - Util.coseSign1GetData(issuerAuthDataItem)); - DataItem mobileSecurityObject = Util.cborExtractTaggedAndEncodedCbor( - mobileSecurityObjectBytes); - - Map>> msoResult = - parseMso(mobileSecurityObject, expectedDocType); - - Map.Entry>> entry = msoResult.entrySet().iterator().next(); - final PublicKey deviceKey = entry.getKey(); - Map> digestMapping = entry.getValue(); - //for (Map.Entry>> e : msoResult.entrySet()) { - // deviceKey = e.getKey(); - // digestMapping = e.getValue(); - //} - //final PublicKey deviceKey = msoResult.first; - //Map> digestMapping = msoResult.second; - - parseValidityInfo(mobileSecurityObject, builder); - - DataItem nameSpaces = Util.cborMapExtractMap(issuerSigned, "nameSpaces"); - Collection nameSpacesKeys = Util.cborMapExtractMapStringKeys(nameSpaces); - for (String nameSpace : nameSpacesKeys) { - Map innerDigestMapping = digestMapping.get(nameSpace); - if (innerDigestMapping == null) { - throw new IllegalArgumentException("No digestID MSO entry for namespace " - + nameSpace); - } - List elems = Util.cborMapExtractArray(nameSpaces, nameSpace); - for (DataItem elem : elems) { - if (!(elem.hasTag() && elem.getTag().getValue() == 24 - && (elem instanceof ByteString))) { - throw new IllegalArgumentException( - "issuerSignedItemBytes is not a tagged ByteString"); - } - // We need the encoded representation with the tag. - byte[] encodedIssuerSignedItem = ((ByteString) elem).getBytes(); - byte[] encodedIssuerSignedItemBytes = Util.cborEncode( - Util.cborBuildTaggedByteString(encodedIssuerSignedItem)); - byte[] expectedDigest = digester.digest(encodedIssuerSignedItemBytes); - - DataItem issuerSignedItem = Util.cborExtractTaggedAndEncodedCbor(elem); - String elementName = Util.cborMapExtractString(issuerSignedItem, - "elementIdentifier"); - DataItem elementValue = Util.cborMapExtract(issuerSignedItem, "elementValue"); - long digestId = Util.cborMapExtractNumber(issuerSignedItem, "digestID"); - - byte[] digest = innerDigestMapping.get(digestId); - if (digest == null) { - throw new IllegalArgumentException("No digestID MSO entry for ID " - + digestId + " in namespace " + nameSpace); - } - boolean digestMatch = Arrays.equals(expectedDigest, digest); - builder.addIssuerEntry(nameSpace, elementName, Util.cborEncode(elementValue), - digestMatch); - } - } - - return deviceKey; - } - - private void parseDeviceSigned( - DataItem deviceSigned, - String docType, - byte[] encodedSessionTranscript, - PublicKey deviceKey, - PrivateKey eReaderKey, - Document.Builder builder) { - DataItem nameSpacesBytes = Util.cborMapExtract(deviceSigned, "nameSpaces"); - if (!(nameSpacesBytes.hasTag() - || nameSpacesBytes.getTag().getValue() != 24 - || (nameSpacesBytes instanceof ByteString))) { - throw new IllegalArgumentException("nameSpaces isn't a tagged ByteString"); - } - byte[] encodedNamespaces = ((ByteString) nameSpacesBytes).getBytes(); - - DataItem sessionTranscript = Util.cborDecode(encodedSessionTranscript); - - DataItem deviceAuth = Util.cborMapExtractMap(deviceSigned, "deviceAuth"); - DataItem deviceSignature = ((co.nstant.in.cbor.model.Map) deviceAuth).get( - new UnicodeString("deviceSignature")); - byte[] encodedDeviceAuthentication = Util.cborEncode(new CborBuilder() - .addArray() - .add("DeviceAuthentication") - .add(sessionTranscript) - .add(docType) - .add(Util.cborBuildTaggedByteString(encodedNamespaces)) - .end() - .build().get(0)); - - boolean deviceSignedAuthenticated; - if (deviceSignature != null) { - byte[] deviceAuthenticationBytes = - Util.cborEncode( - Util.cborBuildTaggedByteString(encodedDeviceAuthentication)); - deviceSignedAuthenticated = Util.coseSign1CheckSignature( - deviceSignature, deviceAuthenticationBytes, deviceKey); - builder.setDeviceSignedAuthenticatedViaSignature(true); - } else { - DataItem deviceMac = ((co.nstant.in.cbor.model.Map) deviceAuth).get( - new UnicodeString("deviceMac")); - if (deviceMac == null) { - throw new IllegalArgumentException( - "Neither deviceSignature nor deviceMac in deviceAuth"); - } - - SecretKey eMacKey = Util.calcEMacKeyForReader(deviceKey, eReaderKey, - encodedSessionTranscript); - byte[] deviceAuthenticationBytes = Util.cborEncode( - Util.cborBuildTaggedByteString(encodedDeviceAuthentication)); - DataItem expectedMac = Util.coseMac0(eMacKey, - new byte[0], // payload - deviceAuthenticationBytes); // detached content - byte[] tagInResponse = Util.coseMac0GetTag(deviceMac); - byte[] expectedTag = Util.coseMac0GetTag(expectedMac); - deviceSignedAuthenticated = Arrays.equals(expectedTag, tagInResponse); - if (deviceSignedAuthenticated) { - //Log.i(TAG, "Verified DeviceSigned using MAC"); - } else { - //Log.i(TAG, "Device MAC mismatch, got " + Util.toHex(tagInResponse) - // + " expected " + Util.toHex(expectedTag)); - } - } - builder.setDeviceSignedAuthenticated(deviceSignedAuthenticated); - - - DataItem nameSpaces = Util.cborDecode(encodedNamespaces); - - Collection nameSpacesKeys = Util.cborMapExtractMapStringKeys(nameSpaces); - for (String nameSpace : nameSpacesKeys) { - DataItem innerMap = Util.cborMapExtract(nameSpaces, nameSpace); - Collection elementNames = Util.cborMapExtractMapStringKeys(innerMap); - for (String elementName : elementNames) { - DataItem elementValue = Util.cborMapExtract(innerMap, elementName); - builder.addDeviceEntry(nameSpace, elementName, Util.cborEncode(elementValue)); - } - } - } - - void parse(byte[] encodedDeviceResponse, - byte[] encodedSessionTranscript, - PrivateKey eReaderKey) { - mResultDocuments = null; - - DataItem deviceResponse = Util.cborDecode(encodedDeviceResponse); - - ArrayList documents = new ArrayList<>(); - - mVersion = Util.cborMapExtractString(deviceResponse, "version"); - if (mVersion.compareTo("1.0") < 0) { - throw new IllegalArgumentException("Given version '" + mVersion + "' not >= '1.0'"); - } - - if (Util.cborMapHasKey(deviceResponse, "documents")) { - List documentsDataItem = Util.cborMapExtractArray(deviceResponse, - "documents"); - for (DataItem documentDataItem : documentsDataItem) { - String docType = Util.cborMapExtractString(documentDataItem, "docType"); - Document.Builder builder = new Document.Builder( - docType); - - DataItem issuerSigned = Util.cborMapExtractMap(documentDataItem, - "issuerSigned"); - PublicKey deviceKey = parseIssuerSigned(docType, issuerSigned, builder); - builder.setDeviceKey(deviceKey); - - DataItem deviceSigned = Util.cborMapExtractMap(documentDataItem, - "deviceSigned"); - parseDeviceSigned(deviceSigned, docType, encodedSessionTranscript, deviceKey, - eReaderKey, builder); - - documents.add(builder.build()); - } - } - - mResultStatus = Util.cborMapExtractNumber(deviceResponse, "status"); - - // TODO: maybe also parse + convey "documentErrors" and "errors" keys in - // DeviceResponse map. - - mResultDocuments = documents; - } - - DeviceResponse() { - } - - /** - * Gets the version string set in the DeviceResponse CBOR. - * - * @return the version string e.g. "1.0". - */ - public String getVersion() { - return mVersion; - } - - /** - * Gets the documents in the device response. - * - * @return A list of {@link Document} objects. - */ - public List getDocuments() { - return mResultDocuments; - } - - private @Constants.DeviceResponseStatus - long mResultStatus = Constants.DEVICE_RESPONSE_STATUS_OK; - - /** - * Gets the top-level status in the DeviceResponse CBOR. - * - *

Note that this value is not a result of parsing/validating the - * DeviceResponse CBOR. It's a value which was part of - * the CBOR and chosen by the remote device. - * - * @return One of {@link Constants#DEVICE_RESPONSE_STATUS_OK}, - * {@link Constants#DEVICE_RESPONSE_STATUS_GENERAL_ERROR}, - * {@link Constants#DEVICE_RESPONSE_STATUS_CBOR_DECODING_ERROR}, or - * {@link Constants#DEVICE_RESPONSE_STATUS_CBOR_VALIDATION_ERROR}. - */ - @Constants.DeviceResponseStatus public - long getStatus() { - return mResultStatus; - } - } - - /** - * An object used to represent data parsed from the Document - * CBOR (part of DeviceResponse) - * as specified in ISO/IEC 18013-5 section 8.3 Device Retrieval. - */ - public static class Document { - static final String TAG = "Document"; - - static class EntryData { - byte[] mValue; - boolean mDigestMatch; - - EntryData(byte[] value, boolean digestMatch) { - this.mValue = value; - this.mDigestMatch = digestMatch; - } - } - - String mDocType; - Map> mDeviceData = new LinkedHashMap<>(); - Map> mIssuerData = new LinkedHashMap<>(); - List mIssuerCertificateChain; - int mNumIssuerEntryDigestMatchFailures; - boolean mDeviceSignedAuthenticated; - boolean mIssuerSignedAuthenticated; - Timestamp mValidityInfoSigned; - Timestamp mValidityInfoValidFrom; - Timestamp mValidityInfoValidUntil; - Timestamp mValidityInfoExpectedUpdate; - PublicKey mDeviceKey; - boolean mDeviceSignedAuthenticatedViaSignature; - - /** - * Returns the type of document (commonly referred to as docType). - * - * @return the document type. - */ - public String getDocType() { - return mDocType; - } - - /** - * Returns the signed date from the MSO. - * - * @return a {@code Timestamp} for when the MSO was signed. - */ - public - Timestamp getValidityInfoSigned() { - return mValidityInfoSigned; - } - - /** - * Returns the validFrom date from the MSO. - * - * @return a {@code Timestamp} for when the MSO is valid from. - */ - public - Timestamp getValidityInfoValidFrom() { - return mValidityInfoValidFrom; - } - - /** - * Returns the validUntil date from the MSO. - * - * @return a {@code Timestamp} for when the MSO is valid until. - */ - public - Timestamp getValidityInfoValidUntil() { - return mValidityInfoValidUntil; - } - - /** - * Returns the expectedUpdate date from the MSO. - * - * @return a {@code Timestamp} for when the MSO is valid until or {@code null} if - * this isn't set. - */ - public - Timestamp getValidityInfoExpectedUpdate() { - return mValidityInfoExpectedUpdate; - } - - /** - * Returns the DeviceKey from the MSO. - * - * @return a {@code PublicKey} representing the DeviceKey. - */ - public - PublicKey getDeviceKey() { - return mDeviceKey; - } - - /** - * Returns the X509 certificate chain for the issuer which signed the data in the document. - * - * @return A X.509 certificate chain. - */ - public List getIssuerCertificateChain() { - return mIssuerCertificateChain; - } - - /** - * Returns whether the {@code IssuerSigned} data was authenticated. - * - *

This returns {@code true} only if the signature on the {@code MobileSecurityObject} - * data was made with the public key in the leaf certificate returned by. - * {@link #getIssuerCertificateChain()} - * - * @return whether the {@code DeviceSigned} data was authenticated. - */ - public boolean getIssuerSignedAuthenticated() { - return mIssuerSignedAuthenticated; - } - - /** - * Gets the names of namespaces with retrieved entries of the issuer-signed data. - * - *

If the document doesn't contain any issuer-signed data, this returns the empty - * collection. - * - * @return Collection of names of namespaces in the issuer-signed data. - */ - public List getIssuerNamespaces() { - return new ArrayList<>(mIssuerData.keySet()); - } - - /** - * Gets the names of data elements in the given issuer-signed namespace. - * - * @param namespaceName the name of the namespace to get data element names from. - * @return A collection of data element names for the namespace. - * @exception IllegalArgumentException if the given namespace isn't in the data. - */ - public List getIssuerEntryNames( String namespaceName) { - Map innerMap = mIssuerData.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace not in data"); - } - return new ArrayList<>(innerMap.keySet()); - } - - /** - * Gets whether the digest for the given entry matches the digest in the MSO. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the encoded CBOR data for the data element - * @exception IllegalArgumentException if the given namespace or entry isn't in the data. - */ - public boolean getIssuerEntryDigestMatch( String namespaceName, - String name) { - Map innerMap = mIssuerData.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace not in data"); - } - EntryData entryData = innerMap.get(name); - if (entryData == null || entryData.mValue == null) { - throw new IllegalArgumentException("Entry not in data"); - } - return entryData.mDigestMatch; - } - - /** - * Gets the number of issuer entries for that didn't match the digest in the MSO. - * - * @return Number of entries for which {@link #getIssuerEntryDigestMatch(String, String)} - * returns {@code false}. - */ - public int getNumIssuerEntryDigestMatchFailures() { - return mNumIssuerEntryDigestMatchFailures; - } - - /** - * Gets the raw CBOR data for the value of given data element in a given namespace in - * issuer-signed data. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the encoded CBOR data for the data element - * @exception IllegalArgumentException if the given namespace or entry isn't in the data. - */ - public byte[] getIssuerEntryData( String namespaceName, - String name) { - Map innerMap = mIssuerData.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace not in data"); - } - EntryData entryData = innerMap.get(name); - if (entryData == null || entryData.mValue == null) { - throw new IllegalArgumentException("Entry not in data"); - } - return entryData.mValue; - } - - /** - * Like {@link #getIssuerEntryData(String, String)} but returns the CBOR decoded - * as a string. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public String getIssuerEntryString( String namespaceName, - String name) { - byte[] value = getIssuerEntryData(namespaceName, name); - return Util.cborDecodeString(value); - } - - /** - * Like {@link #getIssuerEntryData(String, String)} but returns the CBOR decoded - * as a byte-string. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public byte[] getIssuerEntryByteString( String namespaceName, - String name) { - byte[] value = getIssuerEntryData(namespaceName, name); - return Util.cborDecodeByteString(value); - } - - /** - * Like {@link #getIssuerEntryData(String, String)} but returns the CBOR decoded - * as a boolean. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public boolean getIssuerEntryBoolean( String namespaceName, String name) { - byte[] value = getIssuerEntryData(namespaceName, name); - return Util.cborDecodeBoolean(value); - } - - /** - * Like {@link #getIssuerEntryData(String, String)} but returns the CBOR decoded - * as a long. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public long getIssuerEntryNumber( String namespaceName, String name) { - byte[] value = getIssuerEntryData(namespaceName, name); - return Util.cborDecodeLong(value); - } - - /** - * Like {@link #getIssuerEntryData(String, String)} but returns the CBOR decoded - * as a {@link Timestamp}. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public Timestamp getIssuerEntryDateTime( String namespaceName, - String name) { - byte[] value = getIssuerEntryData(namespaceName, name); - return Util.cborDecodeDateTime(value); - } - - // --- - - /** - * Returns whether the {@code DeviceSigned} data was authenticated. - * - *

This returns {@code true} only if the returned device-signed data was properly - * MACed or signed by a {@code DeviceKey} in the MSO. - * - * @return whether the {@code DeviceSigned} data was authenticated. - */ - public boolean getDeviceSignedAuthenticated() { - return mDeviceSignedAuthenticated; - } - - /** - * Returns whether {@code DeviceSigned} was authenticated using ECDSA signature or - * using a MAC. - * - * @return {@code true} if ECDSA signature was used, {@code false} otherwise. - */ - public boolean getDeviceSignedAuthenticatedViaSignature() { - return mDeviceSignedAuthenticatedViaSignature; - } - - /** - * Gets the names of namespaces with retrieved entries of the device-signed data. - * - *

If the document doesn't contain any device-signed data, this returns the empty - * collection. - * - * @return Collection of names of namespaces in the device-signed data. - */ - public List getDeviceNamespaces() { - return new ArrayList<>(mDeviceData.keySet()); - } - - /** - * Gets the names of data elements in the given device-signed namespace. - * - * @param namespaceName the name of the namespace to get data element names from. - * @return A collection of data element names for the namespace. - * @exception IllegalArgumentException if the given namespace isn't in the data. - */ - public List getDeviceEntryNames( String namespaceName) { - Map innerMap = mDeviceData.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace not in data"); - } - return new ArrayList<>(innerMap.keySet()); - } - - /** - * Gets the raw CBOR data for the value of given data element in a given namespace in - * device-signed data. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the encoded CBOR data for the data element - * @exception IllegalArgumentException if the given namespace or entry isn't in the data. - */ - public byte[] getDeviceEntryData( String namespaceName, - String name) { - Map innerMap = mDeviceData.get(namespaceName); - if (innerMap == null) { - throw new IllegalArgumentException("Namespace not in data"); - } - byte[] value = innerMap.get(name).mValue; - if (value == null) { - throw new IllegalArgumentException("Entry not in data"); - } - return value; - } - - /** - * Like {@link #getDeviceEntryData(String, String)} but returns the CBOR decoded - * as a string. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public String getDeviceEntryString( String namespaceName, - String name) { - byte[] value = getDeviceEntryData(namespaceName, name); - return Util.cborDecodeString(value); - } - - /** - * Like {@link #getDeviceEntryData(String, String)} but returns the CBOR decoded - * as a byte-string. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public byte[] getDeviceEntryByteString( String namespaceName, - String name) { - byte[] value = getDeviceEntryData(namespaceName, name); - return Util.cborDecodeByteString(value); - } - - /** - * Like {@link #getDeviceEntryData(String, String)} but returns the CBOR decoded - * as a boolean. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public boolean getDeviceEntryBoolean( String namespaceName, - String name) { - byte[] value = getDeviceEntryData(namespaceName, name); - return Util.cborDecodeBoolean(value); - } - - /** - * Like {@link #getDeviceEntryData(String, String)} but returns the CBOR decoded - * as a long. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public long getDeviceEntryNumber( String namespaceName, String name) { - byte[] value = getDeviceEntryData(namespaceName, name); - return Util.cborDecodeLong(value); - } - - /** - * Like {@link #getDeviceEntryData(String, String)} but returns the CBOR decoded - * as a {@link Timestamp}. - * - * @param namespaceName the name of the namespace to get a data element value from. - * @param name the name of the data element in the given namespace. - * @return the decoded data. - * @exception IllegalArgumentException if the CBOR data isn't in data or not the right type. - */ - public Timestamp getDeviceEntryDateTime( String namespaceName, - String name) { - byte[] value = getDeviceEntryData(namespaceName, name); - return Util.cborDecodeDateTime(value); - } - - static class Builder { - private final Document mResult; - - Builder( String docType) { - this.mResult = new Document(); - this.mResult.mDocType = docType; - } - - Builder addIssuerEntry(String namespaceName, String name, byte[] value, - boolean digestMatch) { - Map innerMap = mResult.mIssuerData.get(namespaceName); - if (innerMap == null) { - innerMap = new LinkedHashMap<>(); - mResult.mIssuerData.put(namespaceName, innerMap); - } - innerMap.put(name, new EntryData(value, digestMatch)); - if (!digestMatch) { - mResult.mNumIssuerEntryDigestMatchFailures += 1; - } - return this; - } - - void setIssuerCertificateChain(List certificateChain) { - mResult.mIssuerCertificateChain = certificateChain; - } - - Builder addDeviceEntry(String namespaceName, String name, byte[] value) { - Map innerMap = mResult.mDeviceData.get(namespaceName); - if (innerMap == null) { - innerMap = new LinkedHashMap<>(); - mResult.mDeviceData.put(namespaceName, innerMap); - } - innerMap.put(name, new EntryData(value, true)); - return this; - } - - Builder setDeviceSignedAuthenticated(boolean deviceSignedAuthenticated) { - mResult.mDeviceSignedAuthenticated = deviceSignedAuthenticated; - return this; - } - - Builder setIssuerSignedAuthenticated(boolean issuerSignedAuthenticated) { - mResult.mIssuerSignedAuthenticated = issuerSignedAuthenticated; - return this; - } - - Builder setValidityInfoSigned( Timestamp value) { - mResult.mValidityInfoSigned = value; - return this; - } - - Builder setValidityInfoValidFrom( Timestamp value) { - mResult.mValidityInfoValidFrom = value; - return this; - } - - Builder setValidityInfoValidUntil( Timestamp value) { - mResult.mValidityInfoValidUntil = value; - return this; - } - - Builder setValidityInfoExpectedUpdate( Timestamp value) { - mResult.mValidityInfoExpectedUpdate = value; - return this; - } - - Builder setDeviceKey( PublicKey deviceKey) { - mResult.mDeviceKey = deviceKey; - return this; - } - - Builder setDeviceSignedAuthenticatedViaSignature(boolean value) { - mResult.mDeviceSignedAuthenticatedViaSignature = value; - return this; - } - - Document build() { - return mResult; - } - } - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/EngagementGenerator.java b/wwwverifier/src/main/java/com/google/sps/identity/EngagementGenerator.java deleted file mode 100644 index e42431bbf..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/EngagementGenerator.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import androidx.annotation.NonNull; -//import androidx.annotation.StringDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.security.PublicKey; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.builder.ArrayBuilder; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.UnsignedInteger; - -/** - * Helper to generate DeviceEngagement or ReaderEngagement CBOR. - */ -public final class EngagementGenerator { - private static final String TAG = "EngagementGenerator"; - private final String mVersion; - private PublicKey mESenderKey; - private ArrayBuilder mConnectionMethodsArrayBuilder; - private ArrayBuilder mOriginInfoArrayBuilder; - private int mNumConnectionMethods = 0; - private int mNumOriginInfos = 0; - - public static final String ENGAGEMENT_VERSION_1_0 = "1.0"; - public static final String ENGAGEMENT_VERSION_1_1 = "1.1"; - - /** @hidden */ - @Retention(RetentionPolicy.SOURCE) - //@StringDef(value = {ENGAGEMENT_VERSION_1_0, ENGAGEMENT_VERSION_1_1}) - public @interface EngagementVersion { - } - - /** - * Helper class for building Engagement structures. - * - * @param ESenderKey The ephemeral key used by the device (when generating - * DeviceEngagement) or the reader (when generating - * ReaderEngagement). - * @param version the version to use. - */ - public EngagementGenerator(PublicKey ESenderKey, - @EngagementVersion String version) { - mESenderKey = ESenderKey; - mVersion = version; - - mConnectionMethodsArrayBuilder = new CborBuilder().addArray(); - mOriginInfoArrayBuilder = new CborBuilder().addArray(); - } - - /** - * Adds a connection method to the engagement. - * - * @param connectionMethod An instance of a type derived from {@link ConnectionMethod}. - * @return the generator. - */ - public - EngagementGenerator addConnectionMethod(ConnectionMethod connectionMethod) { - mConnectionMethodsArrayBuilder.add(connectionMethod.toDeviceEngagement()); - mNumConnectionMethods++; - return this; - } - - /** - * Adds origin info to the engagement. - * - * @param originInfo An instance of a type derived from {@link OriginInfo}. - * @return the generator. - */ - public - EngagementGenerator addOriginInfo(OriginInfo originInfo) { - mOriginInfoArrayBuilder.add(originInfo.encode()); - mNumOriginInfos++; - return this; - } - - /** - * Generates the binary Engagement structure. - * - * @return the bytes of the Engagement structure. - */ - public - byte[] generate() { - DataItem eDeviceKeyBytes = Util.cborBuildTaggedByteString( - Util.cborEncode(Util.cborBuildCoseKey(mESenderKey))); - - DataItem securityDataItem = new CborBuilder() - .addArray() - .add(1) // cipher suite - .add(eDeviceKeyBytes) - .end() - .build().get(0); - - CborBuilder builder = new CborBuilder(); - MapBuilder map = builder.addMap(); - // TODO: support other versions... - map.put(0, mVersion); - map.put(new UnsignedInteger(1), securityDataItem); - if (mNumConnectionMethods > 0) { - map.put(new UnsignedInteger(2), mConnectionMethodsArrayBuilder.end().build().get(0)); - } - if (mNumOriginInfos > 0) { - map.put(new UnsignedInteger(5), mOriginInfoArrayBuilder.end().build().get(0)); - } - map.end(); - return Util.cborEncode(builder.build().get(0)); - } - -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/EngagementParser.java b/wwwverifier/src/main/java/com/google/sps/identity/EngagementParser.java deleted file mode 100644 index e18507800..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/EngagementParser.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import androidx.annotation.NonNull; - -import java.security.PublicKey; -import java.util.ArrayList; -import java.util.List; - -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; - -/** - * Helper for parsing DeviceEngagement or ReaderEngagement CBOR. - */ -public class EngagementParser { - private static final String TAG = "EngagementParser"; - - private final byte[] mEncodedEngagement; - - /** - * Constructs a new parser. - * - * @param encodedEngagement the bytes of the Engagement structure. - */ - public EngagementParser(byte[] encodedEngagement) { - mEncodedEngagement = encodedEngagement; - } - - /** - * Parses the given Engagement structure. - * - * @return A {@link Engagement} object with the parsed data. - */ - public Engagement parse() { - EngagementParser.Engagement engagement = new EngagementParser.Engagement(); - engagement.parse(mEncodedEngagement); - return engagement; - } - - /** - * An object used to represent data extract from an Engagement structure. - */ - public class Engagement { - private static final String TAG = "Engagement"; - private String mVersion; - private PublicKey mESenderKey; - private byte[] mESenderKeyBytes; - private List mConnectionMethods = new ArrayList<>(); - private List mOriginInfos = new ArrayList<>(); - - Engagement() { - } - - /** - * Gets the version string set in the Engagement CBOR. - * - * @return the version string in the engagement e.g. "1.0" or "1.1". - */ - public String getVersion() { - return mVersion; - } - - /** - * Gets the ephemeral key used by the other side. - * - * @return The ephemeral key used by the device (when parsing DeviceEngagement) - * or the reader (when generating ReaderEngagement). - */ - public PublicKey getESenderKey() { - return mESenderKey; - } - - /** - * Gets the encoding of the key that was sent from the other side. - * - *

The returned data are the bytes of ESenderKeyBytes which is defined - * as #6.24(bstr .cbor ESenderKey) where ESenderKey is a - * COSE_Key. - * - * @return The encoding of the ephemeral reader key that was sent from the mdoc - * (when parsing DeviceEngagement) or the mdoc reader (when - * generating ReaderEngagement). - */ - public byte[] getESenderKeyBytes() { - return mESenderKeyBytes; - } - - /** - * Gets the connection methods listed in the engagement. - * - * @return a list of {@link ConnectionMethod}-derived instances. - */ - public List getConnectionMethods() { - return mConnectionMethods; - } - - /** - * Gets the origin infos listed in the engagement. - * - * @return A list of {@link OriginInfo}-derived instances. - */ - public List getOriginInfos() { - return mOriginInfos; - } - - void parse(byte[] encodedEngagement) { - DataItem map = Util.cborDecode(encodedEngagement); - if (!(map instanceof co.nstant.in.cbor.model.Map)) { - throw new IllegalArgumentException("Top-level Engagement CBOR is not a map"); - } - - mVersion = Util.cborMapExtractString(map, 0); - - List securityItems = Util.cborMapExtractArray(map, 1); - if (securityItems.size() < 2) { - throw new IllegalArgumentException("Expected at least two items in Security array"); - } - if (!(securityItems.get(0) instanceof co.nstant.in.cbor.model.UnsignedInteger)) { - throw new IllegalArgumentException("First item in Security array is not a number"); - } - int cipherSuite = ((co.nstant.in.cbor.model.UnsignedInteger) securityItems.get(0)).getValue().intValue(); - if (cipherSuite != 1) { - throw new IllegalArgumentException("Expected cipher suite 1, got " + cipherSuite); - } - if (!(securityItems.get(1) instanceof co.nstant.in.cbor.model.ByteString) || - securityItems.get(1).getTag().getValue() != 24) { - throw new IllegalArgumentException("Second item in Security array is not a tagged bstr"); - } - ByteString eSenderKeyBytes = ((ByteString) securityItems.get(1)); - DataItem coseKey = Util.cborDecode(eSenderKeyBytes.getBytes()); - mESenderKey = Util.coseKeyDecode(coseKey); - mESenderKeyBytes = Util.cborEncode(Util.cborBuildTaggedByteString(eSenderKeyBytes.getBytes())); - - if (Util.cborMapHasKey(map, 2)) { - List connectionMethodItems = Util.cborMapExtractArray(map, 2); - for (DataItem cmDataItem : connectionMethodItems) { - ConnectionMethod connectionMethod = ConnectionMethod.fromDeviceEngagement(cmDataItem); - if (connectionMethod != null) { - mConnectionMethods.add(connectionMethod); - } - } - } - - if (Util.cborMapHasKey(map, 5)) { - List originInfoItems = Util.cborMapExtractArray(map, 5); - for (DataItem oiDataItem : originInfoItems) { - OriginInfo originInfo = OriginInfo.decode(oiDataItem); - if (originInfo != null) { - mOriginInfos.add(originInfo); - } - } - } - } - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/OriginInfo.java b/wwwverifier/src/main/java/com/google/sps/identity/OriginInfo.java deleted file mode 100644 index c0c489546..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/OriginInfo.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import android.util.Log; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.util.List; - -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Number; - -/** - * A class representing the OriginInfo structure exchanged by the mdoc and the mdoc reader. - */ -public abstract class OriginInfo { - private static final String TAG = "OriginInfo"; - - /** - * The constant used to specify how the current engagement structure is delivered. - */ - public static final long CAT_DELIVERY = 0; - - /** - * The constant used to specify how the other party engagement structure has been received. - */ - public static final long CAT_RECEIVE = 1; - - /** - * Specifies whether the OriginInfoOptions are about this engagement or the one - * received previously - * - * @return one of {@link #CAT_DELIVERY} or {@link #CAT_RECEIVE}. - */ - public abstract long getCat(); - - abstract - DataItem encode(); - - static - OriginInfo decode(DataItem oiDataItem) { - if (!(oiDataItem instanceof co.nstant.in.cbor.model.Map)) { - throw new IllegalArgumentException("Top-level CBOR is not a Map"); - } - long type = Util.cborMapExtractNumber(oiDataItem, "type"); - switch ((int) type) { - /* - case OriginInfoQr.TYPE: - return OriginInfoQr.decode(oiDataItem); - case OriginInfoNfc.TYPE: - return OriginInfoNfc.decode(oiDataItem); - */ - case OriginInfoWebsite.TYPE: - return OriginInfoWebsite.decode(oiDataItem); - } - //Log.w(TAG, "Unsupported type " + type); - return null; - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/OriginInfoWebsite.java b/wwwverifier/src/main/java/com/google/sps/identity/OriginInfoWebsite.java deleted file mode 100644 index 9df4889b3..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/OriginInfoWebsite.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import android.util.Log; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.util.List; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.Number; - -public class OriginInfoWebsite extends OriginInfo { - private static final String TAG = "OriginInfoWebsite"; - - static final int TYPE = 1; - private final long mCat; - private final String mBaseUrl; - - public OriginInfoWebsite(long cat, String baseUrl) { - mCat = cat; - mBaseUrl = baseUrl; - } - - /** - * Specifies whether the OriginInfoOptions are about this engagement or the one - * received previously - * - * @return one of {@link #CAT_DELIVERY} or {@link #CAT_RECEIVE}. - */ - @Override - public long getCat() { - return mCat; - } - - public String getBaseUrl() { - return mBaseUrl; - } - - @Override - DataItem encode() { - return new CborBuilder() - .addMap() - .put("cat", mCat) - .put("type", TYPE) - .putMap("Details") - .put("baseUrl", mBaseUrl) - .end() - .end() - .build().get(0); - } - - static OriginInfoWebsite decode(DataItem oiDataItem) { - if (!(oiDataItem instanceof Map)) { - throw new IllegalArgumentException("Top-level CBOR is not an map"); - } - long cat = Util.cborMapExtractNumber(oiDataItem, "cat"); - long type = Util.cborMapExtractNumber(oiDataItem, "type"); - DataItem details = Util.cborMapExtractMap(oiDataItem, "Details"); - if (!(details instanceof Map)) { - throw new IllegalArgumentException("Details is not a map"); - } - String baseUrl = Util.cborMapExtractString(details, "baseUrl"); - if (type != TYPE) { - return null; - } - return new OriginInfoWebsite(cat, baseUrl); - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionDevice.java b/wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionDevice.java deleted file mode 100644 index 9fb014ed8..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionDevice.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -import static java.nio.charset.StandardCharsets.UTF_8; - -//import androidx.core.util.Pair; -import java.util.HashMap; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.io.ByteArrayInputStream; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.List; -import java.util.OptionalLong; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyAgreement; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborDecoder; -import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.UnicodeString; - -/** - * A helper class for encrypting and decrypting messages exchanged with a remote - * mDL reader, conforming to ISO 18013-5 9.1.1 Session encryption. - */ -public final class SessionEncryptionDevice { - - private static final String TAG = "SessionEncryptionDevice"; - - private byte[] mEncodedEReaderKeyPub; - private PublicKey mEReaderKeyPub; - - private final PrivateKey mEDeviceKeyPrivate; - - private SecretKeySpec mSKDevice; - private SecretKeySpec mSKReader; - private int mSKDeviceCounter = 1; - private int mSKReaderCounter = 1; - - /** - * Creates a new {@link SessionEncryptionDevice} object. - * - *

The DeviceEngagement and Handover CBOR referenced in the - * parameters below must conform to the CDDL in ISO 18013-5. - * - * @param eDeviceKeyPrivate the device private ephemeral key. - * @param encodedSessionTranscript - * @param eReaderKeyPublic - */ - public SessionEncryptionDevice(PrivateKey eDeviceKeyPrivate, - PublicKey eReaderKeyPublic, - byte[] encodedSessionTranscript) { - mEDeviceKeyPrivate = eDeviceKeyPrivate; - mEReaderKeyPub = eReaderKeyPublic; - - try { - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(mEDeviceKeyPrivate); - ka.doPhase(mEReaderKeyPub, true); - byte[] sharedSecret = ka.generateSecret(); - - byte[] sessionTranscriptBytes = Util.cborEncode( - Util.cborBuildTaggedByteString(encodedSessionTranscript)); - byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes); - - byte[] info = "SKDevice".getBytes(UTF_8); - byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - - mSKDevice = new SecretKeySpec(derivedKey, "AES"); - - info = "SKReader".getBytes(UTF_8); - derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - mSKReader = new SecretKeySpec(derivedKey, "AES"); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalStateException("Error deriving keys", e); - } - } - - /** - * Encrypts a message to the remote mDL reader. - * - *

This method returns SessionData CBOR as defined in ISO 18013-5 9.1.1 - * Session encryption. - * - * @param messagePlaintext if not null, the message to encrypt and include - * in SessionData. - * @param statusCode if set, the status code to include in SessionData. - * @return the bytes of the SessionData CBOR as described above. - * @exception IllegalStateException if trying to send a message to the reader before a - * message from the reader has been received. - */ - public byte[] encryptMessageToReader(byte[] messagePlaintext, OptionalLong statusCode) { - byte[] messageCiphertextAndAuthTag = null; - if (messagePlaintext != null) { - try { - // The IV and these constants are specified in ISO/IEC 18013-5:2021 clause 9.1.1.5. - ByteBuffer iv = ByteBuffer.allocate(12); - iv.putInt(0, 0x00000000); - iv.putInt(4, 0x00000001); - iv.putInt(8, mSKDeviceCounter); - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array()); - cipher.init(Cipher.ENCRYPT_MODE, mSKDevice, encryptionParameterSpec); - messageCiphertextAndAuthTag = cipher.doFinal(messagePlaintext); - } catch (BadPaddingException - | IllegalBlockSizeException - | NoSuchPaddingException - | InvalidKeyException - | NoSuchAlgorithmException - | InvalidAlgorithmParameterException e) { - throw new IllegalStateException("Error encrypting message", e); - } - mSKDeviceCounter += 1; - } - - CborBuilder builder = new CborBuilder(); - MapBuilder mapBuilder = builder.addMap(); - if (messageCiphertextAndAuthTag != null) { - mapBuilder.put("data", messageCiphertextAndAuthTag); - } - if (statusCode.isPresent()) { - mapBuilder.put("status", statusCode.getAsLong()); - } - mapBuilder.end(); - return Util.cborEncode(builder.build().get(0)); - } - - /** - * Decrypts a message received from the remote mDL reader. - * - *

This method expects the passed-in data to conform to the SessionData - * or SessionEstablishment CDDL as defined in ISO 18013-5 9.1.1 Session encryption. - * - *

The return value is a pair of two values where both values are optional. The - * first element is the decrypted data and the second element is the status. - * - * @param messageData the bytes of the SessionData CBOR as described above. - * @return null if decryption fails, otherwise a pair with the decrypted data and - * status, as described above. - * @exception IllegalArgumentException if the passed in data does not conform to the CDDL. - */ - public java.util.Map.Entry decryptMessageFromReader(byte[] messageData) { - ByteArrayInputStream bais = new ByteArrayInputStream(messageData); - List dataItems = null; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new IllegalArgumentException("Data is not valid CBOR", e); - } - if (dataItems.size() != 1) { - throw new IllegalArgumentException("Expected 1 item, found " + dataItems.size()); - } - if (!(dataItems.get(0) instanceof Map)) { - throw new IllegalArgumentException("Item is not a map"); - } - Map map = (Map) dataItems.get(0); - - DataItem dataItemData = map.get(new UnicodeString("data")); - byte[] messageCiphertext = null; - if (dataItemData != null) { - if (!(dataItemData instanceof ByteString)) { - throw new IllegalArgumentException("data is not a bstr"); - } - messageCiphertext = ((ByteString) dataItemData).getBytes(); - } - - OptionalLong status = OptionalLong.empty(); - DataItem dataItemStatus = map.get(new UnicodeString("status")); - if (dataItemStatus != null) { - status = OptionalLong.of(Util.checkedLongValue(dataItemStatus)); - } - - byte[] plainText = null; - if (messageCiphertext != null) { - ByteBuffer iv = ByteBuffer.allocate(12); - iv.putInt(0, 0x00000000); - iv.putInt(4, 0x00000000); - iv.putInt(8, mSKReaderCounter); - try { - final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, mSKReader, new GCMParameterSpec(128, - iv.array())); - plainText = cipher.doFinal(messageCiphertext); - } catch (BadPaddingException - | IllegalBlockSizeException - | InvalidAlgorithmParameterException - | InvalidKeyException - | NoSuchAlgorithmException - | NoSuchPaddingException e) { - throw new IllegalStateException("Error decrypting data", e); - } - mSKReaderCounter += 1; - } - HashMap hashMap = new HashMap(); - hashMap.put(plainText, status); - return hashMap.entrySet().iterator().next(); - } - - /** - * Gets the number of messages encrypted with - * {@link #encryptMessageToReader(byte[], OptionalLong)} . - * - * @return Number of messages encrypted. - */ - public int getNumMessagesEncrypted() { - return mSKDeviceCounter - 1; - } - - /** - * Gets the number of messages decrypted with {@link #decryptMessageFromReader(byte[])}. - * - * @return Number of messages decrypted. - */ - public int getNumMessagesDecrypted() { - return mSKReaderCounter - 1; - } -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionReader.java b/wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionReader.java deleted file mode 100644 index 6d663b1b9..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/SessionEncryptionReader.java +++ /dev/null @@ -1,285 +0,0 @@ -/* -* Copyright 2022 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.identity.wwwreader; - -import static java.nio.charset.StandardCharsets.UTF_8; - -//import android.util.Pair; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -import java.io.ByteArrayInputStream; -import java.nio.ByteBuffer; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.List; -import java.util.OptionalInt; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyAgreement; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborDecoder; -import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.Number; -import co.nstant.in.cbor.model.UnicodeString; -import co.nstant.in.cbor.model.UnsignedInteger; - - -/** -* A helper class for encrypting and decrypting messages exchanged with a remote -* mDL prover, conforming to ISO 18013-5 9.1.1 Session encryption. -*/ -public final class SessionEncryptionReader { - - private static final String TAG = "SessionEncryptionReader"; - - private boolean mSessionEstablishmentSent; - - private final PrivateKey mEReaderKeyPrivate; - private final PublicKey mEReaderKeyPublic; - private final PublicKey mEDeviceKeyPublic; - - private SecretKeySpec mSKDevice; - private SecretKeySpec mSKReader; - private int mSKDeviceCounter = 1; - private int mSKReaderCounter = 1; - private boolean mSendSessionEstablishment = true; - - /** - * Creates a new {@link SessionEncryptionReader} object. - * - * @param eReaderKeyPrivate the reader private ephemeral key. - * @param eReaderKeyPublic the reader public ephemeral key. - * @param eDeviceKeyPublic the device public key. - * @param encodedSessionTranscript the bytes of the SessionTranscript CBOR. - */ - public SessionEncryptionReader(PrivateKey eReaderKeyPrivate, - PublicKey eReaderKeyPublic, - PublicKey eDeviceKeyPublic, - byte[] encodedSessionTranscript) { - mEReaderKeyPrivate = eReaderKeyPrivate; - mEReaderKeyPublic = eReaderKeyPublic; - mEDeviceKeyPublic = eDeviceKeyPublic; - - try { - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(mEReaderKeyPrivate); - ka.doPhase(mEDeviceKeyPublic, true); - byte[] sharedSecret = ka.generateSecret(); - - byte[] sessionTranscriptBytes = Util.cborEncode( - Util.cborBuildTaggedByteString(encodedSessionTranscript)); - byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes); - - byte[] info = "SKDevice".getBytes(UTF_8); - byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - - mSKDevice = new SecretKeySpec(derivedKey, "AES"); - - info = "SKReader".getBytes(UTF_8); - derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - mSKReader = new SecretKeySpec(derivedKey, "AES"); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalStateException("Error deriving keys", e); - } - } - - /** - * Configure whether to send SessionEstablishment as the first message. - * - *

If set to false the first message to the mdoc will not - * contain eReaderKey. This is useful for situations where this key has - * already been conveyed out-of-band, for example via reverse engagement. - * - *

The default value for this is true. - * - * @param sendSessionEstablishment whether to send SessionEstablishment - * as the first message. - */ - void setSendSessionEstablishment(boolean sendSessionEstablishment) { - mSendSessionEstablishment = sendSessionEstablishment; - } - - /** - * Encrypts a message to the remote mDL prover. - * - *

This method returns SessionEstablishment CBOR for the first call and - * SessionData CBOR for subsequent calls. These CBOR data structures are - * defined in ISO 18013-5 9.1.1 Session encryption. - * - * @param messagePlaintext if not null, the message to encrypt and include - * in SessionData. - * @param statusCode if set, the status code to include in SessionData. - * @return the bytes of the SessionEstablishment or SessionData - * CBOR as described above. - */ - public byte[] encryptMessageToDevice(byte[] messagePlaintext, - OptionalInt statusCode) { - byte[] messageCiphertext = null; - if (messagePlaintext != null) { - try { - // The IV and these constants are specified in ISO/IEC 18013-5:2021 clause 9.1.1.5. - ByteBuffer iv = ByteBuffer.allocate(12); - iv.putInt(0, 0x00000000); - iv.putInt(4, 0x00000000); - iv.putInt(8, mSKReaderCounter); - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - GCMParameterSpec encryptionParameterSpec = new GCMParameterSpec(128, iv.array()); - cipher.init(Cipher.ENCRYPT_MODE, mSKReader, encryptionParameterSpec); - messageCiphertext = cipher.doFinal(messagePlaintext); // This includes the auth tag - } catch (BadPaddingException - | IllegalBlockSizeException - | NoSuchPaddingException - | InvalidKeyException - | NoSuchAlgorithmException - | InvalidAlgorithmParameterException e) { - throw new IllegalStateException("Error encrypting message", e); - } - mSKReaderCounter += 1; - } - - CborBuilder builder = new CborBuilder(); - MapBuilder mapBuilder = builder.addMap(); - if (!mSessionEstablishmentSent && mSendSessionEstablishment) { - DataItem eReaderKey = Util.cborBuildCoseKey(mEReaderKeyPublic); - DataItem eReaderKeyBytes = Util.cborBuildTaggedByteString( - Util.cborEncode(eReaderKey)); - mapBuilder.put(new UnicodeString("eReaderKey"), eReaderKeyBytes); - if (messageCiphertext == null) { - throw new IllegalStateException("Data cannot be empty in initial message"); - } - } - if (messageCiphertext != null) { - mapBuilder.put("data", messageCiphertext); - } - if (statusCode.isPresent()) { - mapBuilder.put("status", statusCode.getAsInt()); - } - mapBuilder.end(); - byte[] messageData = Util.cborEncode(builder.build().get(0)); - - mSessionEstablishmentSent = true; - - return messageData; - } - - /** - * Decrypts a message received from the remote mDL prover. - * - *

This method expects the passed-in data to conform to the SessionData - * DDL as defined in ISO 18013-5 9.1.1 Session encryption. - * - *

The return value is a pair of two values where both values are optional. The - * first element is the decrypted data and the second element is the status. - * - * @param messageData the bytes of the SessionData CBOR as described above. - * @return A pair with the decrypted data and status, as decribed above. - * @exception IllegalArgumentException if the passed in data does not conform to the CDDL. - * @exception IllegalStateException if decryption fails. - */ - public byte[] decryptMessageFromDevice(byte[] messageData) { - ByteArrayInputStream bais = new ByteArrayInputStream(messageData); - List dataItems; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new IllegalArgumentException("Data is not valid CBOR", e); - } - if (dataItems.size() != 1) { - throw new IllegalArgumentException("Expected 1 item, found " + dataItems.size()); - } - if (!(dataItems.get(0) instanceof Map)) { - throw new IllegalArgumentException("Item is not a map"); - } - Map map = (Map) dataItems.get(0); - - DataItem dataItemData = map.get(new UnicodeString("data")); - byte[] messageCiphertext = null; - if (dataItemData != null) { - if (!(dataItemData instanceof ByteString)) { - throw new IllegalArgumentException("data is not a bstr"); - } - messageCiphertext = ((ByteString) dataItemData).getBytes(); - } - - OptionalInt status = OptionalInt.empty(); - DataItem dataItemStatus = map.get(new UnicodeString("status")); - if (dataItemStatus != null) { - if (!(dataItemStatus instanceof Number)) { - throw new IllegalArgumentException("status is not a number"); - } - status = OptionalInt.of(((Number) dataItemStatus).getValue().intValue()); - } - - byte[] plainText = null; - if (messageCiphertext != null) { - ByteBuffer iv = ByteBuffer.allocate(12); - iv.putInt(0, 0x00000000); - iv.putInt(4, 0x00000001); - iv.putInt(8, mSKDeviceCounter); - try { - final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, mSKDevice, new GCMParameterSpec(128, iv.array())); - plainText = cipher.doFinal(messageCiphertext); - } catch (BadPaddingException - | IllegalBlockSizeException - | InvalidAlgorithmParameterException - | InvalidKeyException - | NoSuchAlgorithmException - | NoSuchPaddingException e) { - throw new IllegalStateException("Error decrypting data", e); - } - mSKDeviceCounter += 1; - } - return plainText; - } - - /** - * Gets the number of messages encrypted with - * {@link #encryptMessageToDevice(byte[], OptionalInt)}. - * - * @return Number of messages encrypted. - */ - public int getNumMessagesEncrypted() { - return mSKReaderCounter - 1; - } - - /** - * Gets the number of messages decrypted with {@link #decryptMessageFromDevice(byte[])} - * - * @return Number of messages decrypted. - */ - public int getNumMessagesDecrypted() { - return mSKDeviceCounter - 1; - } -} diff --git a/wwwverifier/src/main/java/com/google/sps/identity/TestVectors.java b/wwwverifier/src/main/java/com/google/sps/identity/TestVectors.java deleted file mode 100644 index 1f7be6649..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/TestVectors.java +++ /dev/null @@ -1,366 +0,0 @@ -/* -* Copyright 2022 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.identity.wwwreader; - -public class TestVectors { - - public static final String ISO_18013_5_ANNEX_D_DEVICE_RESPONSE = - "a36776657273696f6e63312e3069646f63756d656e747381a367646f6354797065756f72672e69736" - + "f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d65537061636573a1" - + "716f72672e69736f2e31383031332e352e3186d8185863a4686469676573744944006672616e646f6" - + "d58208798645b20ea200e19ffabac92624bee6aec63aceedecfb1b80077d22bfc20e971656c656d65" - + "6e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f6" - + "5d818586ca4686469676573744944036672616e646f6d5820b23f627e8999c706df0c0a4ed98ad74a" - + "f988af619b4bb078b89058553f44615d71656c656d656e744964656e7469666965726a69737375655" - + "f646174656c656c656d656e7456616c7565d903ec6a323031392d31302d3230d818586da468646967" - + "6573744944046672616e646f6d5820c7ffa307e5de921e67ba5878094787e8807ac8e7b5b3932d2ce" - + "80f00f3e9abaf71656c656d656e744964656e7469666965726b6578706972795f646174656c656c65" - + "6d656e7456616c7565d903ec6a323032342d31302d3230d818586da46864696765737449440766726" - + "16e646f6d582026052a42e5880557a806c1459af3fb7eb505d3781566329d0b604b845b5f9e687165" - + "6c656d656e744964656e7469666965726f646f63756d656e745f6e756d6265726c656c656d656e745" - + "6616c756569313233343536373839d818590471a4686469676573744944086672616e646f6d5820d0" - + "94dad764a2eb9deb5210e9d899643efbd1d069cc311d3295516ca0b024412d71656c656d656e74496" - + "4656e74696669657268706f7274726169746c656c656d656e7456616c7565590412ffd8ffe000104a" - + "46494600010101009000900000ffdb004300130d0e110e0c13110f11151413171d301f1d1a1a1d3a2" - + "a2c2330453d4947443d43414c566d5d4c51685241435f82606871757b7c7b4a5c869085778f6d787b" - + "76ffdb0043011415151d191d381f1f38764f434f76767676767676767676767676767676767676767" - + "67676767676767676767676767676767676767676767676767676767676ffc0001108001800640301" - + "2200021101031101ffc4001b00000301000301000000000000000000000005060401020307ffc4003" - + "21000010303030205020309000000000000010203040005110612211331141551617122410781a116" - + "3542527391b2c1f1ffc4001501010100000000000000000000000000000001ffc4001a11010101000" - + "3010000000000000000000000014111213161ffda000c03010002110311003f00a5bbde22da2329c7" - + "d692bc7d0d03f52cfb0ff75e7a7ef3e7709723a1d0dae146ddfbb3c039ce07ad2bd47a7e32dbb8dd1" - + "d52d6ef4b284f64a480067dfb51f87ffb95ff00eb9ff14d215de66af089ce44b7dbde9cb6890a2838" - + "eddf18078f7add62d411ef4db9b10a65d6b95a147381ea0d495b933275fe6bba75c114104a8ba4104" - + "13e983dff004f5af5d34b4b4cde632d0bf1fd1592bdd91c6411f3934c2fa6af6b54975d106dcf4a65" - + "ae56e856001ebc03c7ce29dd9eef1ef10fc447dc9da76ad2aee93537a1ba7e4f70dd8eff0057c6dff" - + "b5e1a19854a83758e54528750946ec6704850cd037bceb08b6d7d2cc76d3317fc7b5cc04fb6707269" - + "c5c6e0c5b60ae549242123b0e493f602a075559e359970d98db89525456b51c951c8afa13ea8e98e3" - + "c596836783d5c63f5a61a99fdb7290875db4be88ab384bbbbbfc7183fdeaa633e8951db7da396dc48" - + "524fb1a8bd611a5aa2a2432f30ab420a7a6d3240c718cf031fa9ef4c9ad550205aa02951df4a1d6c8" - + "421b015b769db8c9229837ea2be8b1b0d39d0eba9c51484efdb8c0efd8d258daf3c449699f2edbd45" - + "84e7af9c64e3f96b9beb28d4ac40931e6478c8e76a24a825449501d867d2b1dcdebae99b9c752ae4e" - + "cd6dde4a179c1c1e460938f9149ef655e515c03919a289cb3dca278fb7bf177f4faa829dd8ce3f2ac" - + "9a7ecde490971fafd7dce15eed9b71c018c64fa514514b24e8e4f8c5c9b75c1e82579dc1233dfec08" - + "238f6add62d391acc1c5256a79e706d52d431c7a0145140b9fd149eb3a60dc5e88cbbc2da092411e9" - + "dc71f39a7766b447b344e847dcac9dcb5abba8d145061d43a6fcf1e65cf15d0e90231d3dd9cfe6299" - + "5c6dcc5ca12a2c904a15f71dd27d451453e09d1a21450961cbb3ea8a956433b781f1ce33dfed54f0e" - + "2b50a2b71d84ed6db18028a28175f74fc6bda105c529a791c25c4f3c7a11f71586268f4a66b726e33" - + "de9ea6f1b52b181c760724e47b514520a5a28a283ffd9d81858ffa468646967657374494409667261" - + "6e646f6d58204599f81beaa2b20bd0ffcc9aa03a6f985befab3f6beaffa41e6354cdb2ab2ce471656" - + "c656d656e744964656e7469666965727264726976696e675f70726976696c656765736c656c656d65" - + "6e7456616c756582a37576656869636c655f63617465676f72795f636f646561416a69737375655f6" - + "4617465d903ec6a323031382d30382d30396b6578706972795f64617465d903ec6a323032342d3130" - + "2d3230a37576656869636c655f63617465676f72795f636f646561426a69737375655f64617465d90" - + "3ec6a323031372d30322d32336b6578706972795f64617465d903ec6a323032342d31302d32306a69" - + "7373756572417574688443a10126a118215901f3308201ef30820195a00302010202143c4416eed78" - + "4f3b413e48f56f075abfa6d87eb84300a06082a8648ce3d04030230233114301206035504030c0b75" - + "746f7069612069616361310b3009060355040613025553301e170d3230313030313030303030305a1" - + "70d3231313030313030303030305a30213112301006035504030c0975746f706961206473310b3009" - + "0603550406130255533059301306072a8648ce3d020106082a8648ce3d03010703420004ace7ab734" - + "0e5d9648c5a72a9a6f56745c7aad436a03a43efea77b5fa7b88f0197d57d8983e1b37d3a539f4d588" - + "365e38cbbf5b94d68c547b5bc8731dcd2f146ba381a83081a5301e0603551d1204173015811365786" - + "16d706c65406578616d706c652e636f6d301c0603551d1f041530133011a00fa00d820b6578616d70" - + "6c652e636f6d301d0603551d0e0416041414e29017a6c35621ffc7a686b7b72db06cd12351301f060" - + "3551d2304183016801454fa2383a04c28e0d930792261c80c4881d2c00b300e0603551d0f0101ff04" - + "040302078030150603551d250101ff040b3009060728818c5d050102300a06082a8648ce3d0403020" - + "34800304502210097717ab9016740c8d7bcdaa494a62c053bbdecce1383c1aca72ad08dbc04cbb202" - + "203bad859c13a63c6d1ad67d814d43e2425caf90d422422c04a8ee0304c0d3a68d5903a2d81859039" - + "da66776657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c7661" - + "6c756544696765737473a2716f72672e69736f2e31383031332e352e31ad00582075167333b47b6c2" - + "bfb86eccc1f438cf57af055371ac55e1e359e20f254adcebf01582067e539d6139ebd131aef441b44" - + "5645dd831b2b375b390ca5ef6279b205ed45710258203394372ddb78053f36d5d869780e61eda313d" - + "44a392092ad8e0527a2fbfe55ae0358202e35ad3c4e514bb67b1a9db51ce74e4cb9b7146e41ac52da" - + "c9ce86b8613db555045820ea5c3304bb7c4a8dcb51c4c13b65264f845541341342093cca786e058fa" - + "c2d59055820fae487f68b7a0e87a749774e56e9e1dc3a8ec7b77e490d21f0e1d3475661aa1d065820" - + "7d83e507ae77db815de4d803b88555d0511d894c897439f5774056416a1c7533075820f0549a145f1" - + "cf75cbeeffa881d4857dd438d627cf32174b1731c4c38e12ca936085820b68c8afcb2aaf7c581411d" - + "2877def155be2eb121a42bc9ba5b7312377e068f660958200b3587d1dd0c2a07a35bfb120d99a0abf" - + "b5df56865bb7fa15cc8b56a66df6e0c0a5820c98a170cf36e11abb724e98a75a5343dfa2b6ed3df2e" - + "cfbb8ef2ee55dd41c8810b5820b57dd036782f7b14c6a30faaaae6ccd5054ce88bdfa51a016ba75ed" - + "a1edea9480c5820651f8736b18480fe252a03224ea087b5d10ca5485146c67c74ac4ec3112d4c3a74" - + "6f72672e69736f2e31383031332e352e312e5553a4005820d80b83d25173c484c5640610ff1a31c94" - + "9c1d934bf4cf7f18d5223b15dd4f21c0158204d80e1e2e4fb246d97895427ce7000bb59bb24c8cd00" - + "3ecf94bf35bbd2917e340258208b331f3b685bca372e85351a25c9484ab7afcdf0d2233105511f778" - + "d98c2f544035820c343af1bd1690715439161aba73702c474abf992b20c9fb55c36a336ebe01a876d" - + "6465766963654b6579496e666fa1696465766963654b6579a40102200121582096313d6c63e24e337" - + "2742bfdb1a33ba2c897dcd68ab8c753e4fbd48dca6b7f9a2258201fb3269edd418857de1b39a4e4a4" - + "4b92fa484caa722c228288f01d0c03a2c3d667646f6354797065756f72672e69736f2e31383031332" - + "e352e312e6d444c6c76616c6964697479496e666fa3667369676e6564c074323032302d31302d3031" - + "5431333a33303a30325a6976616c696446726f6dc074323032302d31302d30315431333a33303a303" - + "25a6a76616c6964556e74696cc074323032312d31302d30315431333a33303a30325a584059e64205" - + "df1e2f708dd6db0847aed79fc7c0201d80fa55badcaf2e1bcf5902e1e5a62e4832044b890ad85aa53" - + "f129134775d733754d7cb7a413766aeff13cb2e6c6465766963655369676e6564a26a6e616d655370" - + "61636573d81841a06a64657669636541757468a1696465766963654d61638443a10105a0f65820e99" - + "521a85ad7891b806a07f8b5388a332d92c189a7bf293ee1f543405ae6824d6673746174757300"; - - static final String ISO_18013_5_ANNEX_D_DEVICE_RESPONSE_PORTRAIT_DATA = - "ffd8ffe000104a46494600010101009000900000ffdb004300130d0e110e0c13110f11151413171" - + "d301f1d1a1a1d3a2a2c2330453d4947443d43414c566d5d4c51685241435f82606871757b7c7b4a5c" - + "869085778f6d787b76ffdb0043011415151d191d381f1f38764f434f7676767676767676767676767" - + "676767676767676767676767676767676767676767676767676767676767676767676767676ffc000" - + "11080018006403012200021101031101ffc4001b00000301000301000000000000000000000005060" - + "401020307ffc400321000010303030205020309000000000000010203040005110612211331141551" - + "617122410781a1163542527391b2c1f1ffc4001501010100000000000000000000000000000001ffc" - + "4001a110101010003010000000000000000000000014111213161ffda000c03010002110311003f00" - + "a5bbde22da2329c7d692bc7d0d03f52cfb0ff75e7a7ef3e7709723a1d0dae146ddfbb3c039ce07ad2" - + "bd47a7e32dbb8dd1d52d6ef4b284f64a480067dfb51f87ffb95ff00eb9ff14d215de66af089ce44b7" - + "dbde9cb6890a2838eddf18078f7add62d411ef4db9b10a65d6b95a147381ea0d495b933275fe6bba7" - + "5c114104a8ba410413e983dff004f5af5d34b4b4cde632d0bf1fd1592bdd91c6411f3934c2fa6af6b" - + "54975d106dcf4a65ae56e856001ebc03c7ce29dd9eef1ef10fc447dc9da76ad2aee93537a1ba7e4f7" - + "0dd8eff0057c6dffb5e1a19854a83758e54528750946ec6704850cd037bceb08b6d7d2cc76d3317fc" - + "7b5cc04fb6707269c5c6e0c5b60ae549242123b0e493f602a075559e359970d98db89525456b51c95" - + "1c8afa13ea8e98e3c596836783d5c63f5a61a99fdb7290875db4be88ab384bbbbbfc7183fdeaa633e" - + "8951db7da396dc48524fb1a8bd611a5aa2a2432f30ab420a7a6d3240c718cf031fa9ef4c9ad550205" - + "aa02951df4a1d6c8421b015b769db8c9229837ea2be8b1b0d39d0eba9c51484efdb8c0efd8d258daf" - + "3c449699f2edbd4584e7af9c64e3f96b9beb28d4ac40931e6478c8e76a24a825449501d867d2b1dcd" - + "ebae99b9c752ae4ecd6dde4a179c1c1e460938f9149ef655e515c03919a289cb3dca278fb7bf177f4" - + "faa829dd8ce3f2ac9a7ecde490971fafd7dce15eed9b71c018c64fa514514b24e8e4f8c5c9b75c1e8" - + "2579dc1233dfec08238f6add62d391acc1c5256a79e706d52d431c7a0145140b9fd149eb3a60dc5e8" - + "8cbbc2da092411e9dc71f39a7766b447b344e847dcac9dcb5abba8d145061d43a6fcf1e65cf15d0e9" - + "0231d3dd9cfe62995c6dcc5ca12a2c904a15f71dd27d451453e09d1a21450961cbb3ea8a956433b78" - + "1f1ce33dfed54f0e2b50a2b71d84ed6db18028a28175f74fc6bda105c529a791c25c4f3c7a11f7158" - + "6268f4a66b726e33de9ea6f1b52b181c760724e47b514520a5a28a283ffd9"; - - static final String ISO_18013_5_ANNEX_D_DS_CERT = - "308201ef30820195a00302010202143c4416eed784f3b413e48f56f075abfa6d87eb84300a06082" - + "a8648ce3d04030230233114301206035504030c0b75746f7069612069616361310b30090603550406" - + "13025553301e170d3230313030313030303030305a170d3231313030313030303030305a302131123" - + "01006035504030c0975746f706961206473310b30090603550406130255533059301306072a8648ce" - + "3d020106082a8648ce3d03010703420004ace7ab7340e5d9648c5a72a9a6f56745c7aad436a03a43e" - + "fea77b5fa7b88f0197d57d8983e1b37d3a539f4d588365e38cbbf5b94d68c547b5bc8731dcd2f146b" - + "a381a83081a5301e0603551d120417301581136578616d706c65406578616d706c652e636f6d301c0" - + "603551d1f041530133011a00fa00d820b6578616d706c652e636f6d301d0603551d0e0416041414e2" - + "9017a6c35621ffc7a686b7b72db06cd12351301f0603551d2304183016801454fa2383a04c28e0d93" - + "0792261c80c4881d2c00b300e0603551d0f0101ff04040302078030150603551d250101ff040b3009" - + "060728818c5d050102300a06082a8648ce3d040302034800304502210097717ab9016740c8d7bcdaa" - + "494a62c053bbdecce1383c1aca72ad08dbc04cbb202203bad859c13a63c6d1ad67d814d43e2425caf" - + "90d422422c04a8ee0304c0d3a68d"; - - public static final String ISO_18013_5_ANNEX_D_SESSION_TRANSCRIPT_BYTES = - "d81859024183d8185858a20063312e30018201d818584ba4010220012158205a88d182bce5f42efa5" - + "9943f33359d2e8a968ff289d93e5fa444b624343167fe225820b16e8cf858ddc7690407ba61d4c338" - + "237a8cfcf3de6aa672fc60a557aa32fc67d818584ba40102200121582060e3392385041f51403051f" - + "2415531cb56dd3f999c71687013aac6768bc8187e225820e58deb8fdbe907f7dd5368245551a34796" - + "f7d2215c440c339bb0f7b67beccdfa8258c391020f487315d10209616301013001046d646f631a200" - + "c016170706c69636174696f6e2f766e642e626c7565746f6f74682e6c652e6f6f6230081b28128b37" - + "282801021c015c1e580469736f2e6f72673a31383031333a646576696365656e676167656d656e746" - + "d646f63a20063312e30018201d818584ba4010220012158205a88d182bce5f42efa59943f33359d2e" - + "8a968ff289d93e5fa444b624343167fe225820b16e8cf858ddc7690407ba61d4c338237a8cfcf3de6" - + "aa672fc60a557aa32fc6758cd91022548721591020263720102110204616301013000110206616301" - + "036e6663005102046163010157001a201e016170706c69636174696f6e2f766e642e626c7565746f6" - + "f74682e6c652e6f6f6230081b28078080bf2801021c021107c832fff6d26fa0beb34dfcd555d4823a" - + "1c11010369736f2e6f72673a31383031333a6e66636e6663015a172b016170706c69636174696f6e2" - + "f766e642e7766612e6e616e57030101032302001324fec9a70b97ac9684a4e326176ef5b981c5e853" - + "3e5f00298cfccbc35e700a6b020414"; - - // From ISO/IEC 18013-5 Annex D.4.1.1 mdoc request: - // - public static final String ISO_18013_5_ANNEX_D_DEVICE_REQUEST = - "a26776657273696f6e63312e306b646f63526571756573747381a26c6974656d7352657175657374d" - + "8185893a267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6a6e616d6553" - + "7061636573a1716f72672e69736f2e31383031332e352e31a66b66616d696c795f6e616d65f56f646" - + "f63756d656e745f6e756d626572f57264726976696e675f70726976696c65676573f56a6973737565" - + "5f64617465f56b6578706972795f64617465f568706f727472616974f46a726561646572417574688" - + "443a10126a118215901b7308201b330820158a00302010202147552715f6add323d4934a1ba175dc9" - + "45755d8b50300a06082a8648ce3d04030230163114301206035504030c0b72656164657220726f6f7" - + "4301e170d3230313030313030303030305a170d3233313233313030303030305a3011310f300d0603" - + "5504030c067265616465723059301306072a8648ce3d020106082a8648ce3d03010703420004f8912" - + "ee0f912b6be683ba2fa0121b2630e601b2b628dff3b44f6394eaa9abdbcc2149d29d6ff1a3e091135" - + "177e5c3d9c57f3bf839761eed02c64dd82ae1d3bbfa38188308185301c0603551d1f041530133011a" - + "00fa00d820b6578616d706c652e636f6d301d0603551d0e04160414f2dfc4acafc5f30b464fada20b" - + "fcd533af5e07f5301f0603551d23041830168014cfb7a881baea5f32b6fb91cc29590c50dfac416e3" - + "00e0603551d0f0101ff04040302078030150603551d250101ff040b3009060728818c5d050106300a" - + "06082a8648ce3d0403020349003046022100fb9ea3b686fd7ea2f0234858ff8328b4efef6a1ef71ec" - + "4aae4e307206f9214930221009b94f0d739dfa84cca29efed529dd4838acfd8b6bee212dc6320c46f" - + "eb839a35f658401f3400069063c189138bdcd2f631427c589424113fc9ec26cebcacacfcdb9695d28" - + "e99953becabc4e30ab4efacc839a81f9159933d192527ee91b449bb7f80bf"; - - public static final String ISO_18013_5_ANNEX_D_ITEMS_REQUEST = - "a267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6a6e616d65537061636" - + "573a1716f72672e69736f2e31383031332e352e31a66b66616d696c795f6e616d65f56f646f63756d" - + "656e745f6e756d626572f57264726976696e675f70726976696c65676573f56a69737375655f64617" - + "465f56b6578706972795f64617465f568706f727472616974f4"; - - static final String ISO_18013_5_ANNEX_D_READER_CERT = - "308201b330820158a00302010202147552715f6add323d4934a1ba175dc945755d8b50300a06082" - + "a8648ce3d04030230163114301206035504030c0b72656164657220726f6f74301e170d3230313030" - + "313030303030305a170d3233313233313030303030305a3011310f300d06035504030c06726561646" - + "5723059301306072a8648ce3d020106082a8648ce3d03010703420004f8912ee0f912b6be683ba2fa" - + "0121b2630e601b2b628dff3b44f6394eaa9abdbcc2149d29d6ff1a3e091135177e5c3d9c57f3bf839" - + "761eed02c64dd82ae1d3bbfa38188308185301c0603551d1f041530133011a00fa00d820b6578616d" - + "706c652e636f6d301d0603551d0e04160414f2dfc4acafc5f30b464fada20bfcd533af5e07f5301f0" - + "603551d23041830168014cfb7a881baea5f32b6fb91cc29590c50dfac416e300e0603551d0f0101ff" - + "04040302078030150603551d250101ff040b3009060728818c5d050106300a06082a8648ce3d04030" - + "20349003046022100fb9ea3b686fd7ea2f0234858ff8328b4efef6a1ef71ec4aae4e307206f921493" - + "0221009b94f0d739dfa84cca29efed529dd4838acfd8b6bee212dc6320c46feb839a35"; - - // Note: manually extracted from ISO_18013_5_ANNEX_D_DEVICE_REQUEST - // - static final String ISO_18013_5_ANNEX_D_READER_AUTH = - "8443a10126a118215901b7308201b330820158a00302010202147552715f6add323d4934a1ba175" - + "dc945755d8b50300a06082a8648ce3d04030230163114301206035504030c0b72656164657220726f" - + "6f74301e170d3230313030313030303030305a170d3233313233313030303030305a3011310f300d0" - + "6035504030c067265616465723059301306072a8648ce3d020106082a8648ce3d03010703420004f8" - + "912ee0f912b6be683ba2fa0121b2630e601b2b628dff3b44f6394eaa9abdbcc2149d29d6ff1a3e091" - + "135177e5c3d9c57f3bf839761eed02c64dd82ae1d3bbfa38188308185301c0603551d1f0415301330" - + "11a00fa00d820b6578616d706c652e636f6d301d0603551d0e04160414f2dfc4acafc5f30b464fada" - + "20bfcd533af5e07f5301f0603551d23041830168014cfb7a881baea5f32b6fb91cc29590c50dfac41" - + "6e300e0603551d0f0101ff04040302078030150603551d250101ff040b3009060728818c5d0501063" - + "00a06082a8648ce3d0403020349003046022100fb9ea3b686fd7ea2f0234858ff8328b4efef6a1ef7" - + "1ec4aae4e307206f9214930221009b94f0d739dfa84cca29efed529dd4838acfd8b6bee212dc6320c" - + "46feb839a35f658401f3400069063c189138bdcd2f631427c589424113fc9ec26cebcacacfcdb9695" - + "d28e99953becabc4e30ab4efacc839a81f9159933d192527ee91b449bb7f80bf"; - - public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_READER_KEY_X = - "60e3392385041f51403051f2415531cb56dd3f999c71687013aac6768bc8187e"; - public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_READER_KEY_Y = - "e58deb8fdbe907f7dd5368245551a34796f7d2215c440c339bb0f7b67beccdfa"; - public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_READER_KEY_D = - "de3b4b9e5f72dd9b58406ae3091434da48a6f9fd010d88fcb0958e2cebec947c"; - - public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_DEVICE_KEY_X = - "5a88d182bce5f42efa59943f33359d2e8a968ff289d93e5fa444b624343167fe"; - public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_DEVICE_KEY_Y = - "b16e8cf858ddc7690407ba61d4c338237a8cfcf3de6aa672fc60a557aa32fc67"; - public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_DEVICE_KEY_D = - "c1917a1579949a042f1ba9fc53a2df9b1bc47adf31c10f813ed75702d1c1f136"; - - static final String ISO_18013_5_ANNEX_D_STATIC_DEVICE_KEY_X = - "96313d6c63e24e3372742bfdb1a33ba2c897dcd68ab8c753e4fbd48dca6b7f9a"; - static final String ISO_18013_5_ANNEX_D_STATIC_DEVICE_KEY_Y = - "1fb3269edd418857de1b39a4e4a44b92fa484caa722c228288f01d0c03a2c3d6"; - static final String ISO_18013_5_ANNEX_D_STATIC_DEVICE_KEY_D = - "6ed542ad4783f0b18c833fadf2171273a35d969c581691ef704359cc7cf1e8c0"; - - public static final String ISO_18013_5_ANNEX_D_SESSION_ESTABLISHMENT = - "a26a655265616465724b6579d818584ba40102200121582060e3392385041f51403051f2415531cb5" - + "6dd3f999c71687013aac6768bc8187e225820e58deb8fdbe907f7dd5368245551a34796f7d2215c44" - + "0c339bb0f7b67beccdfa64646174615902df52ada2acbeb6c390f2ca0bc659b484678eb94dd450743" - + "86aadece23777b44606e42e2846bc2e2ee3c1e867b1d1685e41354a021abb0fda36f09cf5d5c51b56" - + "1d3be41c9347ae71cf2b49de9dec7b44046ab02247931b210c9157840c1514a6027b08810716adf61" - + "966344979314ac3ae9f40e66e015c1254a684108bd093e8772ec333fb663fd6803af02ea10bdbe83a" - + "999f75b55a180f872139fb57ac04acd58ca15eca150cde1c3b849401188b7a30ce887dd7b71b12eda" - + "2fc6ec6e5235a6c9498351fcd301f2292a4ebba7555285cee84ead96ef1677b0af8239f6a7a52af4b" - + "8809b1d52ab21a162ca31ade21c57bd1d9970a2832aac41c7d52d1c4fee4ee64030a218df51363be7" - + "01792fa6c515c489bd39dcad6fba48f1d6eb19e9c769531a3bf9998a32c01841305f23844ca3db6a1" - + "ff0d0d917343d62fc72ad58eab01a3198116f19606609f94e35eacb78d23c59c67852a361915fe878" - + "48cdba5630c99fab71aeff72d131cf442654f7708ec48216416f2d996cf6cf91012b771b88907b1d1" - + "629dfa794343e653c31207482e2f6621cd4b5dcf3b3c328625c33fe98be99c5f264a264315be41baf" - + "dc726f8bcde5920de0a71884d860af44c1ff1b3d78b2e8d720d85dae53fea2b3fa1806162a4be02d0" - + "39567c5eb2419c2ad879af48fcb7df55ca94f1b00f62187fa2329c8227aae0130ec052ca3e2102e57" - + "e72911b328cfdcfbaaf6b9364660f613415382644c30c0bd4e222c5cf94ba5a73679c53d5ced95ca5" - + "0787c2289a0c17358393c1e0f2272361002fb9b160606888a59ef7a2c389f68b7cb424572db026b17" - + "cf2bdcafcb67c8292d92b50050356900a62a82b16f854759052b00f0f4673a46229f43257e8e83254" - + "01b3fecc8c6d2258baf7f7c2fbbafab3a1b6aded4eceac1eafd5b61118df93bc0a622b03504fde47c" - + "ebb224e983db12677e316c22aae042d6ce4adae0d8b0f40437b8e1afa0859c9501beb63974496859a" - + "60f11069b1965b4ffac5779a96191f89eac7caa688b9e67c"; - - public static final String ISO_18013_5_ANNEX_D_SESSION_DATA = - "a16464617461590dfa46da5fb292a7880af38f2e4d9eb23ecac231ad21dfe81e3b5c21e7f3d2b5a2d" - + "4676dd13331112f0fba678275dc2fcd889150a7bbb333b7ecf5f35fc8b2e4aee701651a4bdc93cd25" - + "bc533582647507b5b9a075deaf7e1ef035acb3c8b403ac6e51a19d4289381035199da169b5ab175d8" - + "bc2075ac73dbaa76aa79d9a26ac3930034515525c413110abaeae731545b36400205f3130e902242d" - + "b99066a04ab6cef9d14672c3dfe467802e5364dff5535c8c36fdb53afde285ee9462f72a4f8b57078" - + "79590d8b5ee83a3068c1c25f13681085cf4a5af3cb2e77bcc7cae6def76ab5ad119e6db563799d15a" - + "f9f5861b7ec0003c68fa46f12ca366263b5fe5bb8f5b16bede1e5e5919abf8b675fb10ce4655815fe" - + "6a3582ec44e0c93f0cf3a5ea1ca2e476113b47bc1ff2484f791c68385cd5ff3f1f27f70a88c7c6495" - + "81e59a5bb2371d6268704526bf1b16cd36d9e739bef50a199deafb8ceadd42c260e58688bd569b420" - + "f32ad0502e6dff9346440459febbb49d843e50e93d46c0fd2125d4131e1d528435110b5e0db9e4179" - + "5716422dc895425773eae490fbfba7c10f85ec364bdfac7de120ba4883142e854bf19510f969be690" - + "fad9a3f7197885dcb44bd9028998adf6e95356af058e4c20502b5c4d3c4c52469727042e75e0ca8e4" - + "3efb590da9a1a1cc3ffdd03a422e7589ec237c36c0d5587ab853ab39be4388cdd9feb7b763ecb6344" - + "172fafde86a9501975dd86f19f095f98a1e65ace933e8723db8f1e5074f7c9c0415e4e69f12c2a6c1" - + "4c0ca09c872f2ac83e0ec7294e6dc4f1fa087d48201c9c68d1cef4b793fb97b374505b348e090207a" - + "179305347599060200a1f398e9d9aa2eadbe31ee0172b25d1f0b1287b3aad8f1afa560981c6490a1a" - + "ec1052f47201ab5baed0ecf826c7b2b416ce0c2e265403a922f80d1cf6842a7182c8bbe7a138096cb" - + "1fcffcfc102d16d08cbf86108393ef8276120e9341c11c5e5e1cebc257b697d2182ed1ead67d7e581" - + "4a3f9b380cf917ccf59921f4ae545a14a8ec0f8642b7bd5565039b60546efb2c68aa0ac2d3f972386" - + "cd9f47280e2f27af727335cd6ca1aec25c2ad2d079cd3fb045ccbaf6f4a5b0a80e07795f5790ff53a" - + "aefdf3a63240ca7e9d5658ce84a21d310463335cf1f6015dc89ee54d687abcd4787c060a0bce53020" - + "b1c6a5bb6c3ea4d759b7f3eddabcaade785c23caa9ba15272724e0589cef502d80085bd77bae93897" - + "b01e2588ddd504aebe8333a153790a9699748a4269f5017c81aaf8061f9985e0cf7553b4c4d0a5c40" - + "1f20f0ab6d85078728676bcfdc9c1a1bc4dea2b01c68e0bb7bf8ada66aa93fc322673efa3895b09d0" - + "85a0cd085dab3018a9e032acd3d715bf190c629fc55d55ed815b0ba56e83f78c453165c024ca5b2de" - + "5b4accfbab07aa1c482b8e960db03652167f903db0ecf03ef85834951a603c3e6069210744d70c587" - + "5e50badb91dcc8beca4945b869e73e535a7967fbcb377ce861f07e537349c8a33793c0c05c1b4dd22" - + "0634e1f52f6701d79d83f99311d6c1463ea3bbcf4d1a7614cbedfceafa1e874b84e6b30698551c6da" - + "9f8487fc5270cd5dc73f9aa9dc8a513b1514d7abb0bec4d2cd06b14efd23f3c59602fcfc5d9aecf48" - + "148128ca86d6d4ebbe2f68460db00a49759887a478a1eccdd8b426aa130d8b7da0e331415a9682d71" - + "25e56285a927f54ac5139a3f8d78c2dc6ffa0bc9ad4db749e8dae10169aa6be9206b635943b7a0970" - + "a23e04a2ba66712365b2ea0afb0422a0224592074f723781b8df86627296f9f126f94efe089f8db5f" - + "2eea4f28673e11ccd80726901ee1bcc8fa49b6f65c0f8587222f2bb81744a3ece2f74ac21e8c5805d" - + "054f93e2d669616ecbc07f3d017a36b951aecd28a14ada87e4f935ef7f93c2c7d01cedf5658131908" - + "e4d36aa4690ba952d3f7fa25b6a8481d42f6a7794c8b1dd4de2f7e6bb3ed6fd3915591997e06f0171" - + "5bac3cdfb0885ce97136e4b7b4b1e6685fd42de1e8ae610523c2b2603977cf3c45058802d734322a9" - + "aa0181ff1231b4674071e5c75d8f11e56d67916b4d5f7a9b42a26519c990fdb728630f0755cf8fc0d" - + "177c24e1375cc793cee463da5dbef1fe6a835bf75317052b7b077d397c7b617a632371d22eb64c91f" - + "91d8fdf5aa3345f0e6e7a0d42e8da52087a89428a1241b4527d5f4d3751b10d40e8768bc6b1a55b53" - + "1a9e8fb9afdfaf3aed0a3bc71b9c432b89da34e4685b8b48f650394e760270df2c837ab51ded999b9" - + "03c0f9630478adb8843e462ad8123466ff837dc18241db341ecabbc0a8693bd35831d49c09adf8c5d" - + "7c4f80b3628b633d46a93c2b7b61e5127abb5e877e6904de7049a53213b357cdb443c31bababdc048" - + "0fe6986951ea9d684e53f5b53871e80148afc30c4e2eed05a0aeafa0b98f98e8ffbad6af968ecfd28" - + "6b24c43bf2b54e64e28fab328d791bbf80292e2226dfc7d0d562b511321b6b501eadbd1661f028bad" - + "02e37575104f646222484aac1548a762cfec36b9597a4e7309497b49f1921a948a597cc7d721f6396" - + "389aaffd2828da0443e4f2354cf2fbe69c0a2a8f3d2271207440e95445a4fb24de95d5232e66ff84c" - + "8e1ab5c5576e316ce3e78d7c0047695ef0037cc7f7d0f21fa53a1976a7e1bb2d70751c6ebebb5fd24" - + "dad9fa58ffee261b0ee7a55a86a6236901feb322400965313175a263e5b8437eb2a7ddf239d15c27b" - + "2e8b55abccd7574df807f3e8483670aac580fec1bbeb779a3cb2e89440e8badccdcdccf6f6e7dd9a2" - + "d62080c7057824ede7186f924e2c59934a0fc0d518d05ff48da1b012322bb55d2493a850954b6af58" - + "5e72f5bef55180f9fc293e40bd48a261cee4099ad4674541ddb5a81683034e9e31f59c8942321f09a" - + "5ed1e11854b6cd8af4afcf919f2001722c2396b9b30e1c5d8182061bb39201477a0538db896a39949" - + "3151b1f6162284284b8cc42701fad81c63846f0d7e3be5004a3d1e5dc047c2b28d7c28940e70ba52a" - + "c7aa0de584a9ff7859aa0e9af8d24d5dcfd07cf5c6fd8034e2a3ef63b41be59ab93d941cee00b3029" - + "478181e576d3b0c09f28b1c70ab7c2e622e7cacb2f6d2337c430afa5736169bddfe379f5b3d3392cf" - + "aa1bc45f6d3e47e73678ec19cecb20b3aee82378e41f84b59c172abc8d40370f8bb833f8edca890cd" - + "4f85f5610d3e208bada4deb6f15d9cb0a9f805b1f9915e40bed12ed2494886801d6ff83df037b4a24" - + "5d03489fa7dda552dccbd11b84101c4147595e0ee6d89b5d7621492d21dc01b5afc1ce3c65cc878d8" - + "23810f7d0c4232374a4e82013ad3a99f2422aa8e4e2cc33c66dc5b07bd2ece416999563a6f2e62ef3" - + "67904990f1442519eb00c6eecb1c886bde5613dba37908cc79f71bcfbf5fb96142488f0be69c510e1" - + "44bd1a25f2e6f22bf78cc213d7ba83def9146224b67c1ca5cad8520e55d1b01daaa70475caa1e4ed1" - + "17ac8952a3f4566edd28e599b59a3be80405bedf150e5d5dd4d0f3d1d84e8e7f7dfe945d358493bb1" - + "19470aabbf5b3eb7205f2184793e5f9b41c0c622ebf0b83730feb3c2d06de8f7992cee469bf104595" - + "cd98307f7d3d584760b6f1d75e10f51325626627050f5b636f34b93471c290b1afede150055b796d3" - + "d08f31b52360ef28630acc273bfcda6aa14df76867cc230c0597bd76105d7a54426698d87ad1ac683" - + "c7569b80f0f4c5d169dd0dbcf390e8449b698649183cdf214a13e51ed5c5c38df9931ad23a05e49b8" - + "5975bcc65cca5ebfb404f62552cf46fd535e5d9e8178dff0156fbd6227e2e04c56af73e8a149dbd63" - + "f5cd0a5ec1046c30b3a25ba60cfc869df084553430e7058e948b8e426003421988789c266fcf9f6c3" - + "09fcc785e11e76121316f82b61555352c91f3d936dbc1e181a6924d480ba09a62adf930dee5884ce5" - + "362ff31f1e2a4558702bc0d8c871cc322efad66efc946c3a9ae959ef20c052787d6a5e04d7dc9dfc2" - + "c9941104ad26c136a9827a866b9e0942dbacd4aed56b48547c6dc1d0216fcdd2b5ce40828bdae5df4" - + "8e724232b01cc567173e07b9089e7834bb92c873c5e08ba055698df5f79ee73e122b5b72ee3e2e100" - + "858dad409da55ad0fa1aa9caa60bf9c25e9ba3e1dc012724c89903a820b63dd5f14f0019007b18068" - + "4afbe125a0cf87af796ae20e465641c5e8fb91b6e7c6395d2f49f17bac3e35110c16b119bf289e11a" - + "fb4bbae40266aa87605298ec5bb0601fa415038621ea100db5d9d8a5da4d74fd92ed882546afc7a8a" - + "3dc648b1c7852e8fa43ca9ed287a4bb9dd299bfc69414f990bf3b7b58a932a4ff9c86e2fb131b7cc7" - + "b65464cc80011267ab49f5c599b9ce43acc9a06b856b25b6fe5f51afd6db147386a1ab30575a0eae6" - + "07ffe116cae7fb70df6841ae0f52eaf359305becdf7f4636f3c31fab45387ef97cef0b8ef6a8de702" - + "a2f1c21c3aee10382e8b8610a03bf6f1c54527a1d6db79f80f71c7f29880bb4b0d46cee3a6063028e" - + "901f2f3514639bce237259a3122beea38763e205263f663543bf9bb390337f6a992b74dbfab966a9a" - + "bf3dd54b15956b47ee731d23ec9a87a0027ed16f6fc4384812df153231c61331ef22e16035719044f" - + "ddd7b6a9626aa8397073d0cbf42fffd2b8201dca65aadaf98048e7bc0917ed83c7cbb59086ceff885" - + "b523fca2d65d87323fd6bbd75b0a312b317b217985334086a63cdc4f065be41dfc590c76d71f93f76" - + "c091ee6b2878c2d718adde4493eef3a4cb8b2764e7013b80f922bf2b3b3572029975bc1388b7c975f" - + "353a02fa01ab679bbe041041dc1425787606df44013b7627c4d43b1edbda3768386f2b9a78470ac7a" - + "c5ca6b2d20385d53e2e412828e2ebcdd17ce8658c655babeefe0b2f43f606a192613263a2c27831a7" - + "c5953c9a1f2910093b4720e78e3b34ae7102e1094fcc6655d3af6d9a4c8f58f2478ef532121b7c91f" - + "e558a80257d63d8e9b4300c09dc04d9d37975f5cf3e548f23e40c070822419a74267d08453815ba8e" - + "02943c3bc5a13ad669fd79d8e4ee5f5be82dc638ba9a1d"; - - static final String ISO_18013_5_ANNEX_D_SESSION_TERMINATION = - "a16673746174757314"; -} \ No newline at end of file diff --git a/wwwverifier/src/main/java/com/google/sps/identity/Timestamp.java b/wwwverifier/src/main/java/com/google/sps/identity/Timestamp.java deleted file mode 100644 index 10354dff8..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/Timestamp.java +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Copyright 2022 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.identity.wwwreader; - -//import androidx.annotation.NonNull; - -/** -* Represents a single instant in time. Ideally, we'd use {@code java.time.Instant}, but we cannot -* do so until we move to API level 26. -*/ -public final class Timestamp { - private final long mEpochMillis; - - private Timestamp(long epochMillis) { - mEpochMillis = epochMillis; - } - - /** - * @return a {@code Timestamp} representing the current time - */ - public static Timestamp now() { - return new Timestamp(System.currentTimeMillis()); - } - - /** - * @return a {@code Timestamp} representing the given time - */ - public static Timestamp ofEpochMilli(long epochMillis) { - return new Timestamp(epochMillis); - } - - /** - * @return this represented as the number of milliseconds since midnight, January 1, 1970 UTC - */ - public long toEpochMilli() { - return mEpochMillis; - } - - @Override - public String toString() { - return "Timestamp{epochMillis=" + mEpochMillis + "}"; - } - - @Override - public boolean equals(Object other) { - return (other instanceof Timestamp) && ((Timestamp) other).mEpochMillis == mEpochMillis; - } - - @Override - public int hashCode() { - return Long.hashCode(mEpochMillis); - } -} - diff --git a/wwwverifier/src/main/java/com/google/sps/identity/Util.java b/wwwverifier/src/main/java/com/google/sps/identity/Util.java deleted file mode 100644 index 6a3bc4be8..000000000 --- a/wwwverifier/src/main/java/com/google/sps/identity/Util.java +++ /dev/null @@ -1,1808 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.identity.wwwreader; - -//import android.content.Context; -//import android.security.keystore.KeyProperties; -//import android.util.Log; - -//import androidx.annotation.NonNull; -//import androidx.annotation.Nullable; - -//import androidx.annotation.VisibleForTesting; -import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.ASN1InputStream; -import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.ASN1OctetString; -import org.bouncycastle.asn1.ASN1Primitive; -import org.bouncycastle.asn1.ASN1Sequence; -import org.bouncycastle.asn1.DERSequenceGenerator; -import org.bouncycastle.util.BigIntegers; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.AlgorithmParameters; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.UnrecoverableEntryException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Queue; -import java.util.TimeZone; - -import javax.crypto.KeyAgreement; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import co.nstant.in.cbor.CborBuilder; -import co.nstant.in.cbor.CborDecoder; -import co.nstant.in.cbor.CborEncoder; -import co.nstant.in.cbor.CborException; -import co.nstant.in.cbor.builder.ArrayBuilder; -import co.nstant.in.cbor.builder.MapBuilder; -import co.nstant.in.cbor.model.AbstractFloat; -import co.nstant.in.cbor.model.Array; -import co.nstant.in.cbor.model.ByteString; -import co.nstant.in.cbor.model.DataItem; -import co.nstant.in.cbor.model.DoublePrecisionFloat; -import co.nstant.in.cbor.model.MajorType; -import co.nstant.in.cbor.model.Map; -import co.nstant.in.cbor.model.NegativeInteger; -import co.nstant.in.cbor.model.Number; -import co.nstant.in.cbor.model.SimpleValue; -import co.nstant.in.cbor.model.SimpleValueType; -import co.nstant.in.cbor.model.SpecialType; -import co.nstant.in.cbor.model.UnicodeString; -import co.nstant.in.cbor.model.UnsignedInteger; - -/** - * Utility functions. - */ -public class Util { - private static final String TAG = "Util"; - private static final long COSE_LABEL_ALG = 1; - private static final long COSE_LABEL_X5CHAIN = 33; // temporary identifier - // From RFC 8152: Table 5: ECDSA Algorithm Values - private static final long COSE_ALG_ECDSA_256 = -7; - private static final long COSE_ALG_ECDSA_384 = -35; - private static final long COSE_ALG_ECDSA_512 = -36; - private static final long COSE_ALG_HMAC_256_256 = 5; - private static final long CBOR_SEMANTIC_TAG_ENCODED_CBOR = 24; - private static final long COSE_KEY_KTY = 1; - private static final long COSE_KEY_TYPE_EC2 = 2; - private static final long COSE_KEY_EC2_CRV = -1; - private static final long COSE_KEY_EC2_X = -2; - private static final long COSE_KEY_EC2_Y = -3; - private static final long COSE_KEY_EC2_CRV_P256 = 1; - - // Not called. - private Util() { - } - - /* TODO: add cborBuildDate() which generates a full-date where - * - * full-date = #6.1004(tstr), - * - * and where tag 1004 is specified in RFC 8943. - */ - public static - byte[] fromHex(String stringWithHex) { - int stringLength = stringWithHex.length(); - if ((stringLength % 2) != 0) { - throw new IllegalArgumentException("Invalid length of hex string: " + stringLength); - } - int numBytes = stringLength / 2; - byte[] data = new byte[numBytes]; - for (int n = 0; n < numBytes; n++) { - String byteStr = stringWithHex.substring(2 * n, 2 * n + 2); - data[n] = (byte) Integer.parseInt(byteStr, 16); - } - return data; - } - - static - String toHex(byte[] bytes) { - return toHex(bytes, 0, bytes.length); - } - - static String toHex(byte[] bytes, int from, int to) { - if (from < 0 || to > bytes.length || from > to) { - String msg = String.format(Locale.US, "Expected 0 <= from <= to <= %d, got %d, %d.", - bytes.length, from, to); - throw new IllegalArgumentException(msg); - } - StringBuilder sb = new StringBuilder(); - for (int n = from; n < to; n++) { - byte b = bytes[n]; - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } - - /** - static void dumpHex(String tag, String message, byte[] bytes) { - Log.i(tag, message + " (" + bytes.length + " bytes)"); - final int chunkSize = 1024; - for (int offset = 0; offset < bytes.length; offset += chunkSize) { - String s = toHex(bytes, offset, Math.min(bytes.length, offset + chunkSize)); - Log.i(tag, "data: " + s); - } - } - */ - - static - String base16(byte[] bytes) { - return toHex(bytes).toUpperCase(Locale.ROOT); - } - - public static - byte[] cborEncode(DataItem dataItem) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).encode(dataItem); - } catch (CborException e) { - // This should never happen and we don't want cborEncode() to throw since that - // would complicate all callers. Log it instead. - throw new IllegalStateException("Unexpected failure encoding data", e); - } - return baos.toByteArray(); - } - - static - byte[] cborEncodeWithoutCanonicalizing(DataItem dataItem) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - new CborEncoder(baos).nonCanonical().encode(dataItem); - } catch (CborException e) { - // This should never happen and we don't want cborEncode() to throw since that - // would complicate all callers. Log it instead. - throw new IllegalStateException("Unexpected failure encoding data", e); - } - return baos.toByteArray(); - } - - static - byte[] cborEncodeBoolean(boolean value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - static - byte[] cborEncodeString(String value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - static - byte[] cborEncodeNumber(long value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - static - byte[] cborEncodeBytestring(byte[] value) { - return cborEncode(new CborBuilder().add(value).build().get(0)); - } - - static - byte[] cborEncodeDateTime(Timestamp timestamp) { - return cborEncode(cborBuildDateTime(timestamp)); - } - - /** - * Returns #6.0(tstr) where tstr is the ISO 8601 encoding of the given point in time. - * Only supports UTC times. - */ - static - DataItem cborBuildDateTime(Timestamp timestamp) { - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.US); - df.setTimeZone(TimeZone.getTimeZone("UTC")); - Date val = new Date(timestamp.toEpochMilli()); - String dateString = df.format(val); - DataItem dataItem = new UnicodeString(dateString); - dataItem.setTag(0); - return dataItem; - } - - public static - DataItem cborDecode( byte[] encodedBytes) { - ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes); - List dataItems = null; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new IllegalArgumentException("Error decoding CBOR", e); - } - if (dataItems.size() != 1) { - throw new IllegalArgumentException("Unexpected number of items, expected 1 got " - + dataItems.size()); - } - return dataItems.get(0); - } - - static boolean cborDecodeBoolean( byte[] data) { - SimpleValue simple = (SimpleValue) cborDecode(data); - return simple.getSimpleValueType() == SimpleValueType.TRUE; - } - - /** - * Accepts a {@code DataItem}, attempts to cast it to a {@code Number}, then returns the value - * Throws {@code IllegalArgumentException} if the {@code DataItem} is not a {@code Number}. This - * method also checks bounds, and if the given data item is too large to fit in a long, it - * throws {@code ArithmeticException}. - */ - static long checkedLongValue(DataItem item) { - final BigInteger bigNum = castTo(Number.class, item).getValue(); - final long result = bigNum.longValue(); - if (!bigNum.equals(BigInteger.valueOf(result))) { - throw new ArithmeticException("Expected long value, got '" + bigNum + "'"); - } - return result; - } - - static - String cborDecodeString( byte[] data) { - return checkedStringValue(cborDecode(data)); - } - - /** - * Accepts a {@code DataItem}, attempts to cast it to a {@code UnicodeString}, then returns the - * value. Throws {@code IllegalArgumentException} if the {@code DataItem} is not a - * {@code UnicodeString}. - */ - static String checkedStringValue(DataItem item) { - return castTo(UnicodeString.class, item).getString(); - } - - static long cborDecodeLong( byte[] data) { - return checkedLongValue(cborDecode(data)); - } - - static - byte[] cborDecodeByteString( byte[] data) { - DataItem dataItem = cborDecode(data); - return castTo(ByteString.class, dataItem).getBytes(); - } - - static - Timestamp cborDecodeDateTime( byte[] data) { - return cborDecodeDateTime(cborDecode(data)); - } - - static - Timestamp cborDecodeDateTime(DataItem di) { - if (!(di instanceof co.nstant.in.cbor.model.UnicodeString)) { - throw new IllegalArgumentException("Passed in data is not a Unicode-string"); - } - if (!di.hasTag() || di.getTag().getValue() != 0) { - throw new IllegalArgumentException("Passed in data is not tagged with tag 0"); - } - String dateString = checkedStringValue(di); - - // Manually parse the timezone - TimeZone parsedTz = TimeZone.getTimeZone("UTC"); - if (!dateString.endsWith("Z")) { - String timeZoneSubstr = dateString.substring(dateString.length() - 6); - parsedTz = TimeZone.getTimeZone("GMT" + timeZoneSubstr); - } - - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); - df.setTimeZone(parsedTz); - Date date = null; - try { - date = df.parse(dateString); - } catch (ParseException e) { - throw new RuntimeException("Error parsing string", e); - } - - return Timestamp.ofEpochMilli(date.getTime()); - } - - /** - * Similar to a typecast of {@code value} to the given type {@code clazz}, except: - *

    - *
  • Throws {@code IllegalArgumentException} instead of {@code ClassCastException} if - * {@code !clazz.isAssignableFrom(value.getClass())}.
  • - *
  • Also throws {@code IllegalArgumentException} if {@code value == null}.
  • - *
- */ - static T castTo(Class clazz, V value) { - if (value == null || !clazz.isAssignableFrom(value.getClass())) { - String valueStr = (value == null) ? "null" : value.getClass().toString(); - throw new IllegalArgumentException("Expected type " + clazz + ", got type " + valueStr); - } else { - return (T) value; - } - } - - /** - * Helper function to check if a given certificate chain is valid. - * - * NOTE NOTE NOTE: We only check that the certificates in the chain sign each other. We - * specifically don't check that each certificate is also a CA certificate. - * - * @param certificateChain the chain to validate. - * @return true if valid, false otherwise. - */ - static boolean validateCertificateChain( - Collection certificateChain) { - // First check that each certificate signs the previous one... - X509Certificate prevCertificate = null; - for (X509Certificate certificate : certificateChain) { - if (prevCertificate != null) { - // We're not the leaf certificate... - // - // Check the previous certificate was signed by this one. - try { - prevCertificate.verify(certificate.getPublicKey()); - } catch (CertificateException - | InvalidKeyException - | NoSuchAlgorithmException - | NoSuchProviderException - | SignatureException e) { - return false; - } - } else { - // we're the leaf certificate so we're not signing anything nor - // do we need to be e.g. a CA certificate. - } - prevCertificate = certificate; - } - return true; - } - - /** - * Computes an HKDF. - * - * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google - * /crypto/tink/subtle/Hkdf.java - * which is also Copyright (c) Google and also licensed under the Apache 2 license. - * - * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or - * "HMACSHA256". - * @param ikm the input keying material. - * @param salt optional salt. A possibly non-secret random value. If no salt is - * provided (i.e. if - * salt has length 0) then an array of 0s of the same size as the hash - * digest is used as salt. - * @param info optional context and application specific information. - * @param size The length of the generated pseudorandom string in bytes. The maximal - * size is - * 255.DigestSize, where DigestSize is the size of the underlying HMAC. - * @return size pseudorandom bytes. - */ - static - byte[] computeHkdf( - String macAlgorithm, final byte[] ikm, final byte[] salt, - final byte[] info, int size) { - Mac mac = null; - try { - mac = Mac.getInstance(macAlgorithm); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("No such algorithm: " + macAlgorithm, e); - } - if (size > 255 * mac.getMacLength()) { - throw new IllegalArgumentException("size too large"); - } - try { - if (salt == null || salt.length == 0) { - // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided - // then HKDF uses a salt that is an array of zeros of the same length as the hash - // digest. - mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); - } else { - mac.init(new SecretKeySpec(salt, macAlgorithm)); - } - byte[] prk = mac.doFinal(ikm); - byte[] result = new byte[size]; - int ctr = 1; - int pos = 0; - mac.init(new SecretKeySpec(prk, macAlgorithm)); - byte[] digest = new byte[0]; - while (true) { - mac.update(digest); - mac.update(info); - mac.update((byte) ctr); - digest = mac.doFinal(); - if (pos + digest.length < size) { - System.arraycopy(digest, 0, result, pos, digest.length); - pos += digest.length; - ctr++; - } else { - System.arraycopy(digest, 0, result, pos, size - pos); - break; - } - } - return result; - } catch (InvalidKeyException e) { - throw new IllegalStateException("Error MACing", e); - } - } - - private static byte[] coseBuildToBeSigned(byte[] encodedProtectedHeaders, - byte[] payload, - byte[] detachedContent) { - CborBuilder sigStructure = new CborBuilder(); - ArrayBuilder array = sigStructure.addArray(); - - array.add("Signature1"); - array.add(encodedProtectedHeaders); - - // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) - // so external_aad is the empty bstr - byte[] emptyExternalAad = new byte[0]; - array.add(emptyExternalAad); - - // Next field is the payload, independently of how it's transported (RFC - // 8152 section 4.4). Since our API specifies only one of |data| and - // |detachedContent| can be non-empty, it's simply just the non-empty one. - if (payload != null && payload.length > 0) { - array.add(payload); - } else { - array.add(detachedContent); - } - array.end(); - return cborEncode(sigStructure.build().get(0)); - } - - private static byte[] signatureCoseToDer(byte[] signature) { - // r and s are always positive and may use all bits so use the constructor which - // parses them as unsigned. - BigInteger r = new BigInteger(1, Arrays.copyOfRange( - signature, 0, signature.length / 2)); - BigInteger s = new BigInteger(1, Arrays.copyOfRange( - signature, signature.length / 2, signature.length)); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - DERSequenceGenerator seq = new DERSequenceGenerator(baos); - seq.addObject(new ASN1Integer(r)); - seq.addObject(new ASN1Integer(s)); - seq.close(); - } catch (IOException e) { - throw new IllegalStateException("Error generating DER signature", e); - } - return baos.toByteArray(); - } - - /** - * Currently only ECDSA signatures are supported. - * - * TODO: add support and tests for Ed25519 and Ed448. - */ - static boolean coseSign1CheckSignature( DataItem coseSign1, - byte[] detachedContent, PublicKey publicKey) { - if (coseSign1.getMajorType() != MajorType.ARRAY) { - throw new IllegalArgumentException("Data item is not an array"); - } - List items = ((co.nstant.in.cbor.model.Array) coseSign1).getDataItems(); - if (items.size() < 4) { - throw new IllegalArgumentException("Expected at least four items in COSE_Sign1 array"); - } - if (items.get(0).getMajorType() != MajorType.BYTE_STRING) { - throw new IllegalArgumentException("Item 0 (protected headers) is not a byte-string"); - } - byte[] encodedProtectedHeaders = ((co.nstant.in.cbor.model.ByteString) items.get( - 0)).getBytes(); - byte[] payload = new byte[0]; - if (items.get(2).getMajorType() == MajorType.SPECIAL) { - if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType() - != SpecialType.SIMPLE_VALUE) { - throw new IllegalArgumentException( - "Item 2 (payload) is a special but not a simple value"); - } - SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2); - if (simple.getSimpleValueType() != SimpleValueType.NULL) { - throw new IllegalArgumentException( - "Item 2 (payload) is a simple but not the value null"); - } - } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) { - payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes(); - } else { - throw new IllegalArgumentException("Item 2 (payload) is not nil or byte-string"); - } - if (items.get(3).getMajorType() != MajorType.BYTE_STRING) { - throw new IllegalArgumentException("Item 3 (signature) is not a byte-string"); - } - byte[] coseSignature = ((co.nstant.in.cbor.model.ByteString) items.get(3)).getBytes(); - - byte[] derSignature = signatureCoseToDer(coseSignature); - - int dataLen = payload.length; - int detachedContentLen = (detachedContent != null ? detachedContent.length : 0); - if (dataLen > 0 && detachedContentLen > 0) { - throw new IllegalArgumentException("data and detachedContent cannot both be non-empty"); - } - - DataItem protectedHeaders = cborDecode(encodedProtectedHeaders); - long alg = cborMapExtractNumber((Map) protectedHeaders, COSE_LABEL_ALG); - String signature; - if (alg == COSE_ALG_ECDSA_256) { - signature = "SHA256withECDSA"; - } else if (alg == COSE_ALG_ECDSA_384) { - signature = "SHA384withECDSA"; - } else if (alg == COSE_ALG_ECDSA_512) { - signature = "SHA512withECDSA"; - } else { - throw new IllegalArgumentException("Unsupported COSE alg " + alg); - } - - byte[] toBeSigned = Util.coseBuildToBeSigned(encodedProtectedHeaders, payload, - detachedContent); - - try { - // Use BouncyCastle provider for verification since it supports a lot more curves than - // the default provider, including the brainpool curves - // - Signature verifier = Signature.getInstance(signature, - new org.bouncycastle.jce.provider.BouncyCastleProvider()); - verifier.initVerify(publicKey); - verifier.update(toBeSigned); - return verifier.verify(derSignature); - } catch (SignatureException | NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalStateException("Error verifying signature", e); - } - } - - private static - byte[] coseBuildToBeMACed( byte[] encodedProtectedHeaders, - byte[] payload, - byte[] detachedContent) { - CborBuilder macStructure = new CborBuilder(); - ArrayBuilder array = macStructure.addArray(); - - array.add("MAC0"); - array.add(encodedProtectedHeaders); - - // We currently don't support Externally Supplied Data (RFC 8152 section 4.3) - // so external_aad is the empty bstr - byte[] emptyExternalAad = new byte[0]; - array.add(emptyExternalAad); - - // Next field is the payload, independently of how it's transported (RFC - // 8152 section 4.4). Since our API specifies only one of |data| and - // |detachedContent| can be non-empty, it's simply just the non-empty one. - if (payload != null && payload.length > 0) { - array.add(payload); - } else { - array.add(detachedContent); - } - - return cborEncode(macStructure.build().get(0)); - } - - static - DataItem coseMac0( SecretKey key, byte[] data, byte[] detachedContent) { - - int dataLen = (data != null ? data.length : 0); - int detachedContentLen = (detachedContent != null ? detachedContent.length : 0); - if (dataLen > 0 && detachedContentLen > 0) { - throw new IllegalArgumentException("data and detachedContent cannot both be non-empty"); - } - - CborBuilder protectedHeaders = new CborBuilder(); - MapBuilder protectedHeadersMap = protectedHeaders.addMap(); - protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256); - byte[] protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0)); - - byte[] toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data, detachedContent); - - byte[] mac; - try { - Mac m = Mac.getInstance("HmacSHA256"); - m.init(key); - m.update(toBeMACed); - mac = m.doFinal(); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalStateException("Unexpected error", e); - } - - CborBuilder builder = new CborBuilder(); - ArrayBuilder array = builder.addArray(); - array.add(protectedHeadersBytes); - /* MapBuilder> unprotectedHeaders = */ - array.addMap(); - if (data == null || data.length == 0) { - array.add(new SimpleValue(SimpleValueType.NULL)); - } else { - array.add(data); - } - array.add(mac); - - return builder.build().get(0); - } - - static - byte[] coseMac0GetTag( DataItem coseMac0) { - List items = castTo(Array.class, coseMac0).getDataItems(); - if (items.size() < 4) { - throw new IllegalArgumentException("coseMac0 have less than 4 elements"); - } - DataItem tagItem = items.get(3); - return castTo(ByteString.class, tagItem).getBytes(); - } - - /** - * Brute-force but good enough since users will only pass relatively small amounts of data. - */ - static boolean hasSubByteArray( byte[] haystack, byte[] needle) { - int n = 0; - while (needle.length + n <= haystack.length) { - boolean found = true; - for (int m = 0; m < needle.length; m++) { - if (needle[m] != haystack[n + m]) { - found = false; - break; - } - } - if (found) { - return true; - } - n++; - } - return false; - } - - static - byte[] stripLeadingZeroes( byte[] value) { - int n = 0; - while (n < value.length && value[n] == 0) { - n++; - } - int newLen = value.length - n; - byte[] ret = new byte[newLen]; - int m = 0; - while (n < value.length) { - ret[m++] = value[n++]; - } - return ret; - } - - /** - * Returns #6.24(bstr) of the given already encoded CBOR - */ - public static - DataItem cborBuildTaggedByteString( byte[] encodedCbor) { - DataItem item = new ByteString(encodedCbor); - item.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR); - return item; - } - - /** - * For a #6.24(bstr), extracts the bytes. - */ - public static - byte[] cborExtractTaggedCbor( byte[] encodedTaggedBytestring) { - DataItem item = cborDecode(encodedTaggedBytestring); - ByteString itemByteString = castTo(ByteString.class, item); - if (!item.hasTag() || item.getTag().getValue() != CBOR_SEMANTIC_TAG_ENCODED_CBOR) { - throw new IllegalArgumentException("ByteString is not tagged with tag 24"); - } - return itemByteString.getBytes(); - } - - /** - * For a #6.24(bstr), extracts the bytes and decodes it and returns - * the decoded CBOR as a DataItem. - */ - public static - DataItem cborExtractTaggedAndEncodedCbor( DataItem item) { - ByteString itemByteString = castTo(ByteString.class, item); - if (!item.hasTag() || item.getTag().getValue() != CBOR_SEMANTIC_TAG_ENCODED_CBOR) { - throw new IllegalArgumentException("ByteString is not tagged with tag 24"); - } - byte[] encodedCbor = itemByteString.getBytes(); - DataItem embeddedItem = cborDecode(encodedCbor); - return embeddedItem; - } - - /** - * Returns the empty byte-array if no data is included in the structure. - */ - static - byte[] coseSign1GetData( DataItem coseSign1) { - if (coseSign1.getMajorType() != MajorType.ARRAY) { - throw new IllegalArgumentException("Data item is not an array"); - } - List items = castTo(co.nstant.in.cbor.model.Array.class, coseSign1).getDataItems(); - if (items.size() < 4) { - throw new IllegalArgumentException("Expected at least four items in COSE_Sign1 array"); - } - byte[] payload = new byte[0]; - if (items.get(2).getMajorType() == MajorType.SPECIAL) { - if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType() - != SpecialType.SIMPLE_VALUE) { - throw new IllegalArgumentException( - "Item 2 (payload) is a special but not a simple value"); - } - SimpleValue simple = castTo(co.nstant.in.cbor.model.SimpleValue.class, items.get(2)); - if (simple.getSimpleValueType() != SimpleValueType.NULL) { - throw new IllegalArgumentException( - "Item 2 (payload) is a simple but not the value null"); - } - } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) { - payload = castTo(co.nstant.in.cbor.model.ByteString.class, items.get(2)).getBytes(); - } else { - throw new IllegalArgumentException("Item 2 (payload) is not nil or byte-string"); - } - return payload; - } - - /** - * Returns the empty collection if no x5chain is included in the structure. - * - * Throws exception if the given bytes aren't valid COSE_Sign1. - */ - static - List coseSign1GetX5Chain( - DataItem coseSign1) { - ArrayList ret = new ArrayList<>(); - if (coseSign1.getMajorType() != MajorType.ARRAY) { - throw new IllegalArgumentException("Data item is not an array"); - } - List items = castTo(co.nstant.in.cbor.model.Array.class, coseSign1).getDataItems(); - if (items.size() < 4) { - throw new IllegalArgumentException("Expected at least four items in COSE_Sign1 array"); - } - if (items.get(1).getMajorType() != MajorType.MAP) { - throw new IllegalArgumentException("Item 1 (unprotected headers) is not a map"); - } - co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) items.get(1); - DataItem x5chainItem = map.get(new UnsignedInteger(COSE_LABEL_X5CHAIN)); - if (x5chainItem != null) { - try { - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - if (x5chainItem instanceof ByteString) { - ByteArrayInputStream certBais = new ByteArrayInputStream( - castTo(ByteString.class, x5chainItem).getBytes()); - ret.add((X509Certificate) factory.generateCertificate(certBais)); - } else if (x5chainItem instanceof Array) { - for (DataItem certItem : castTo(Array.class, x5chainItem).getDataItems()) { - ByteArrayInputStream certBais = new ByteArrayInputStream( - castTo(ByteString.class, certItem).getBytes()); - ret.add((X509Certificate) factory.generateCertificate(certBais)); - } - } else { - throw new IllegalArgumentException("Unexpected type for x5chain value"); - } - } catch (CertificateException e) { - throw new IllegalArgumentException("Unexpected error", e); - } - } - return ret; - } - - /* Encodes an integer according to Section 2.3.5 Field-Element-to-Octet-String Conversion - * of SEC 1: Elliptic Curve Cryptography (https://www.secg.org/sec1-v2.pdf). - */ - static byte[] sec1EncodeFieldElementAsOctetString(int octetStringSize, BigInteger fieldValue) { - return BigIntegers.asUnsignedByteArray(octetStringSize, fieldValue); - } - - public static - DataItem cborBuildCoseKey(PublicKey key) { - ECPublicKey ecKey = (ECPublicKey) key; - ECPoint w = ecKey.getW(); - byte[] x = sec1EncodeFieldElementAsOctetString(32, w.getAffineX()); - byte[] y = sec1EncodeFieldElementAsOctetString(32, w.getAffineY()); - DataItem item = new CborBuilder() - .addMap() - .put(COSE_KEY_KTY, COSE_KEY_TYPE_EC2) - .put(COSE_KEY_EC2_CRV, COSE_KEY_EC2_CRV_P256) - .put(COSE_KEY_EC2_X, x) - .put(COSE_KEY_EC2_Y, y) - .end() - .build().get(0); - return item; - } - - static boolean cborMapHasKey( DataItem map, String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return item != null; - } - - static boolean cborMapHasKey( DataItem map, long key) { - DataItem keyDataItem = key >= 0 ? new UnsignedInteger(key) : new NegativeInteger(key); - DataItem item = castTo(Map.class, map).get(keyDataItem); - return item != null; - } - - static long cborMapExtractNumber( DataItem map, long key) { - DataItem keyDataItem = key >= 0 ? new UnsignedInteger(key) : new NegativeInteger(key); - DataItem item = castTo(Map.class, map).get(keyDataItem); - return checkedLongValue(item); - } - - static long cborMapExtractNumber( DataItem map, String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return checkedLongValue(item); - } - - static - String cborMapExtractString( DataItem map, - String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return checkedStringValue(item); - } - - static - String cborMapExtractString( DataItem map, long key) { - DataItem keyDataItem = key >= 0 ? new UnsignedInteger(key) : new NegativeInteger(key); - DataItem item = castTo(Map.class, map).get(keyDataItem); - return checkedStringValue(item); - } - - static - List cborMapExtractArray( DataItem map, - String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return castTo(Array.class, item).getDataItems(); - } - - static - List cborMapExtractArray( DataItem map, long key) { - DataItem keyDataItem = key >= 0 ? new UnsignedInteger(key) : new NegativeInteger(key); - DataItem item = castTo(Map.class, map).get(keyDataItem); - return castTo(Array.class, item).getDataItems(); - } - - static - DataItem cborMapExtractMap( DataItem map, - String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return castTo(Map.class, item); - } - - static - Collection cborMapExtractMapStringKeys( DataItem map) { - List ret = new ArrayList<>(); - for (DataItem item : castTo(Map.class, map).getKeys()) { - ret.add(checkedStringValue(item)); - } - return ret; - } - - static - Collection cborMapExtractMapNumberKeys( DataItem map) { - List ret = new ArrayList<>(); - for (DataItem item : castTo(Map.class, map).getKeys()) { - ret.add(checkedLongValue(item)); - } - return ret; - } - - static - byte[] cborMapExtractByteString( DataItem map, - long key) { - DataItem keyDataItem = key >= 0 ? new UnsignedInteger(key) : new NegativeInteger(key); - DataItem item = castTo(Map.class, map).get(keyDataItem); - return castTo(ByteString.class, item).getBytes(); - } - - public static - byte[] cborMapExtractByteString( DataItem map, - String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return castTo(ByteString.class, item).getBytes(); - } - - static boolean cborMapExtractBoolean( DataItem map, String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - return castTo(SimpleValue.class, item).getSimpleValueType() == SimpleValueType.TRUE; - } - - static boolean cborMapExtractBoolean( DataItem map, long key) { - DataItem keyDataItem = key >= 0 ? new UnsignedInteger(key) : new NegativeInteger(key); - DataItem item = castTo(Map.class, map).get(keyDataItem); - return castTo(SimpleValue.class, item).getSimpleValueType() == SimpleValueType.TRUE; - } - - static Timestamp cborMapExtractDateTime( DataItem map, String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - UnicodeString unicodeString = castTo(UnicodeString.class, item); - return cborDecodeDateTime(unicodeString); - } - - static - DataItem cborMapExtract( DataItem map, String key) { - DataItem item = castTo(Map.class, map).get(new UnicodeString(key)); - if (item == null) { - throw new IllegalArgumentException("Expected item"); - } - return item; - } - - public static - PublicKey coseKeyDecode( DataItem coseKey) { - long kty = cborMapExtractNumber(coseKey, COSE_KEY_KTY); - if (kty != COSE_KEY_TYPE_EC2) { - throw new IllegalArgumentException("Expected COSE_KEY_TYPE_EC2, got " + kty); - } - long crv = cborMapExtractNumber(coseKey, COSE_KEY_EC2_CRV); - if (crv != COSE_KEY_EC2_CRV_P256) { - throw new IllegalArgumentException("Expected COSE_KEY_EC2_CRV_P256, got " + crv); - } - byte[] encodedX = cborMapExtractByteString(coseKey, COSE_KEY_EC2_X); - byte[] encodedY = cborMapExtractByteString(coseKey, COSE_KEY_EC2_Y); - - BigInteger x = new BigInteger(1, encodedX); - BigInteger y = new BigInteger(1, encodedY); - - try { - AlgorithmParameters params = AlgorithmParameters.getInstance("EC"); - params.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec ecParameters = params.getParameterSpec(ECParameterSpec.class); - - ECPoint ecPoint = new ECPoint(x, y); - ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters); - KeyFactory kf = KeyFactory.getInstance("EC"); - ECPublicKey ecPublicKey = (ECPublicKey) kf.generatePublic(keySpec); - return ecPublicKey; - - } catch (NoSuchAlgorithmException - | InvalidParameterSpecException - | InvalidKeySpecException e) { - throw new IllegalStateException("Unexpected error", e); - } - } - - static - SecretKey calcEMacKeyForReader( - PublicKey authenticationPublicKey, - PrivateKey ephemeralReaderPrivateKey, - byte[] encodedSessionTranscript) { - try { - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(ephemeralReaderPrivateKey); - ka.doPhase(authenticationPublicKey, true); - byte[] sharedSecret = ka.generateSecret(); - - byte[] sessionTranscriptBytes = - Util.cborEncode(Util.cborBuildTaggedByteString(encodedSessionTranscript)); - - byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes); - byte[] info = new byte[]{'E', 'M', 'a', 'c', 'K', 'e', 'y'}; - byte[] derivedKey = computeHkdf("HmacSha256", sharedSecret, salt, info, 32); - - SecretKey secretKey = new SecretKeySpec(derivedKey, ""); - return secretKey; - } catch (InvalidKeyException - | NoSuchAlgorithmException e) { - throw new IllegalStateException("Error performing key agreement", e); - } - } - - static - String cborPrettyPrint( DataItem dataItem) { - StringBuilder sb = new StringBuilder(); - cborPrettyPrintDataItem(sb, 0, dataItem); - return sb.toString(); - } - - static - String cborPrettyPrint( byte[] encodedBytes) { - StringBuilder sb = new StringBuilder(); - - ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes); - List dataItems = null; - try { - dataItems = new CborDecoder(bais).decode(); - } catch (CborException e) { - throw new IllegalStateException(e); - } - int count = 0; - for (DataItem dataItem : dataItems) { - if (count > 0) { - sb.append(",\n"); - } - cborPrettyPrintDataItem(sb, 0, dataItem); - count++; - } - - return sb.toString(); - } - - // Returns true iff all elements in |items| are not compound (e.g. an array or a map). - private static boolean cborAreAllDataItemsNonCompound( List items) { - for (DataItem item : items) { - switch (item.getMajorType()) { - case ARRAY: - case MAP: - return false; - default: - // Do nothing - break; - } - } - return true; - } - - private static void cborPrettyPrintDataItem( StringBuilder sb, int indent, - DataItem dataItem) { - StringBuilder indentBuilder = new StringBuilder(); - for (int n = 0; n < indent; n++) { - indentBuilder.append(' '); - } - String indentString = indentBuilder.toString(); - - if (dataItem.hasTag()) { - sb.append(String.format(Locale.US, "tag %d ", dataItem.getTag().getValue())); - } - - switch (dataItem.getMajorType()) { - case INVALID: - // TODO: throw - sb.append(""); - break; - case UNSIGNED_INTEGER: { - // Major type 0: an unsigned integer. - BigInteger value = ((UnsignedInteger) dataItem).getValue(); - sb.append(value); - } - break; - case NEGATIVE_INTEGER: { - // Major type 1: a negative integer. - BigInteger value = ((NegativeInteger) dataItem).getValue(); - sb.append(value); - } - break; - case BYTE_STRING: { - // Major type 2: a byte string. - byte[] value = ((ByteString) dataItem).getBytes(); - sb.append("["); - int count = 0; - for (byte b : value) { - if (count > 0) { - sb.append(", "); - } - sb.append(String.format("0x%02x", b)); - count++; - } - sb.append("]"); - } - break; - case UNICODE_STRING: { - // Major type 3: string of Unicode characters that is encoded as UTF-8 [RFC3629]. - String value = checkedStringValue(dataItem); - // TODO: escape ' in |value| - sb.append("'" + value + "'"); - } - break; - case ARRAY: { - // Major type 4: an array of data items. - List items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems(); - if (items.size() == 0) { - sb.append("[]"); - } else if (cborAreAllDataItemsNonCompound(items)) { - // The case where everything fits on one line. - sb.append("["); - int count = 0; - for (DataItem item : items) { - cborPrettyPrintDataItem(sb, indent, item); - if (++count < items.size()) { - sb.append(", "); - } - } - sb.append("]"); - } else { - sb.append("[\n" + indentString); - int count = 0; - for (DataItem item : items) { - sb.append(" "); - cborPrettyPrintDataItem(sb, indent + 2, item); - if (++count < items.size()) { - sb.append(","); - } - sb.append("\n" + indentString); - } - sb.append("]"); - } - } - break; - case MAP: { - // Major type 5: a map of pairs of data items. - Collection keys = ((co.nstant.in.cbor.model.Map) dataItem).getKeys(); - if (keys.size() == 0) { - sb.append("{}"); - } else { - sb.append("{\n" + indentString); - int count = 0; - for (DataItem key : keys) { - sb.append(" "); - DataItem value = ((co.nstant.in.cbor.model.Map) dataItem).get(key); - cborPrettyPrintDataItem(sb, indent + 2, key); - sb.append(" : "); - cborPrettyPrintDataItem(sb, indent + 2, value); - if (++count < keys.size()) { - sb.append(","); - } - sb.append("\n" + indentString); - } - sb.append("}"); - } - } - break; - case TAG: - // Major type 6: optional semantic tagging of other major types - // - // We never encounter this one since it's automatically handled via the - // DataItem that is tagged. - throw new IllegalStateException("Semantic tag data item not expected"); - - case SPECIAL: - // Major type 7: floating point numbers and simple data types that need no - // content, as well as the "break" stop code. - if (dataItem instanceof SimpleValue) { - switch (((SimpleValue) dataItem).getSimpleValueType()) { - case FALSE: - sb.append("false"); - break; - case TRUE: - sb.append("true"); - break; - case NULL: - sb.append("null"); - break; - case UNDEFINED: - sb.append("undefined"); - break; - case RESERVED: - sb.append("reserved"); - break; - case UNALLOCATED: - sb.append("unallocated"); - break; - } - } else if (dataItem instanceof DoublePrecisionFloat) { - DecimalFormat df = new DecimalFormat("0", - DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - df.setMaximumFractionDigits(340); - sb.append(df.format(((DoublePrecisionFloat) dataItem).getValue())); - } else if (dataItem instanceof AbstractFloat) { - DecimalFormat df = new DecimalFormat("0", - DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - df.setMaximumFractionDigits(340); - sb.append(df.format(((AbstractFloat) dataItem).getValue())); - } else { - sb.append("break"); - } - break; - } - } - - static - byte[] canonicalizeCbor( byte[] encodedCbor) { - return cborEncode(cborDecode(encodedCbor)); - } - - static - String replaceLine( String text, int lineNumber, - String replacementLine) { - @SuppressWarnings("StringSplitter") - String[] lines = text.split("\n"); - int numLines = lines.length; - if (lineNumber < 0) { - lineNumber = numLines - -lineNumber; - } - StringBuilder sb = new StringBuilder(); - for (int n = 0; n < numLines; n++) { - if (n == lineNumber) { - sb.append(replacementLine); - } else { - sb.append(lines[n]); - } - // Only add terminating newline if passed-in string ends in a newline. - if (n == numLines - 1) { - if (text.endsWith("\n")) { - sb.append('\n'); - } - } else { - sb.append('\n'); - } - } - return sb.toString(); - } - - /** - * Helper function to create a CBOR data for requesting data items. The IntentToRetain - * value will be set to false for all elements. - * - *

The returned CBOR data conforms to the following CDDL schema:

- * - *
-     *   ItemsRequest = {
-     *     ? "docType" : DocType,
-     *     "nameSpaces" : NameSpaces,
-     *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
-     *   }
-     *
-     *   NameSpaces = {
-     *     + NameSpace => DataElements     ; Requested data elements for each NameSpace
-     *   }
-     *
-     *   DataElements = {
-     *     + DataElement => IntentToRetain
-     *   }
-     *
-     *   DocType = tstr
-     *
-     *   DataElement = tstr
-     *   IntentToRetain = bool
-     *   NameSpace = tstr
-     * 
- * - * @param entriesToRequest The entries to request, organized as a map of namespace - * names with each value being a collection of data elements - * in the given namespace. - * @param docType The document type or {@code null} if there is no document - * type. - * @return CBOR data conforming to the CDDL mentioned above. - * - * TODO: docType is no longer optional so change docType to be NonNull and update all callers. - */ - static - byte[] createItemsRequest( - java.util.Map> entriesToRequest, String docType) { - CborBuilder builder = new CborBuilder(); - MapBuilder mapBuilder = builder.addMap(); - if (docType != null) { - mapBuilder.put("docType", docType); - } - - MapBuilder> nsMapBuilder = mapBuilder.putMap("nameSpaces"); - for (String namespaceName : entriesToRequest.keySet()) { - Collection entryNames = entriesToRequest.get(namespaceName); - MapBuilder>> entryNameMapBuilder = - nsMapBuilder.putMap(namespaceName); - for (String entryName : entryNames) { - entryNameMapBuilder.put(entryName, false); - } - } - return cborEncode(builder.build().get(0)); - } - - static - byte[] getPopSha256FromAuthKeyCert( X509Certificate cert) { - byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26"); - if (octetString == null) { - return null; - } - try { - ASN1InputStream asn1InputStream = new ASN1InputStream(octetString); - byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets(); - - ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes); - List dataItems = new CborDecoder(bais).decode(); - if (dataItems.size() != 1) { - throw new IllegalArgumentException("Expected 1 item, found " + dataItems.size()); - } - Array array = castTo(Array.class, dataItems.get(0)); - List items = array.getDataItems(); - if (items.size() < 2) { - throw new IllegalArgumentException( - "Expected at least 2 array items, found " + items.size()); - } - String id = checkedStringValue(items.get(0)); - if (!id.equals("ProofOfBinding")) { - throw new IllegalArgumentException("Expected ProofOfBinding, got " + id); - } - byte[] popSha256 = castTo(ByteString.class, items.get(1)).getBytes(); - if (popSha256.length != 32) { - throw new IllegalArgumentException( - "Expected bstr to be 32 bytes, it is " + popSha256.length); - } - return popSha256; - } catch (IOException e) { - throw new IllegalArgumentException("Error decoding extension data", e); - } catch (CborException e) { - throw new IllegalArgumentException("Error decoding data", e); - } - } - - /** - * Clears elementValue in IssuerSignedItemBytes CBOR. - * - * @param encodedIssuerSignedItem encoded CBOR conforming to IssuerSignedItem. - * @return Same as given CBOR but with elementValue set to NULL. - */ - static - byte[] issuerSignedItemClearValue( byte[] encodedIssuerSignedItem) { - byte[] encodedNullValue = Util.cborEncode(SimpleValue.NULL); - return issuerSignedItemSetValue(encodedIssuerSignedItem, encodedNullValue); - } - - /** - * Sets elementValue in IssuerSignedItem CBOR. - * - * Throws if the given encodedIssuerSignedItemBytes isn't IssuersignedItemBytes. - * - * @param encodedIssuerSignedItem encoded CBOR conforming to IssuerSignedItem. - * @param encodedElementValue the value to set elementValue to. - * @return Same as given CBOR but with elementValue set to given value. - */ - static - byte[] issuerSignedItemSetValue( - byte[] encodedIssuerSignedItem, - byte[] encodedElementValue) { - DataItem issuerSignedItemElem = Util.cborDecode(encodedIssuerSignedItem); - Map issuerSignedItem = castTo(Map.class, issuerSignedItemElem); - DataItem elementValue = Util.cborDecode(encodedElementValue); - issuerSignedItem.put(new UnicodeString("elementValue"), elementValue); - - // By using the non-canonical encoder the order is preserved. - return Util.cborEncodeWithoutCanonicalizing(issuerSignedItem); - } - - public static - PrivateKey getPrivateKeyFromInteger( BigInteger s) { - try { - AlgorithmParameters params = AlgorithmParameters.getInstance("EC"); - params.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec ecParameters = params.getParameterSpec(ECParameterSpec.class); - - ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(s, ecParameters); - KeyFactory keyFactory = KeyFactory.getInstance("EC"); - return keyFactory.generatePrivate(privateKeySpec); - - } catch (NoSuchAlgorithmException - | InvalidParameterSpecException - | InvalidKeySpecException e) { - throw new IllegalStateException(e); - } - } - - public static - PublicKey getPublicKeyFromIntegers( BigInteger x, - BigInteger y) { - try { - AlgorithmParameters params = AlgorithmParameters.getInstance("EC"); - params.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec ecParameters = params.getParameterSpec(ECParameterSpec.class); - - ECPoint ecPoint = new ECPoint(x, y); - ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters); - KeyFactory kf = KeyFactory.getInstance("EC"); - ECPublicKey ecPublicKey = (ECPublicKey) kf.generatePublic(keySpec); - return ecPublicKey; - } catch (NoSuchAlgorithmException - | InvalidParameterSpecException - | InvalidKeySpecException e) { - throw new IllegalStateException("Unexpected error", e); - } - } - - // Returns null on End Of Stream. - // - static - ByteBuffer readBytes( InputStream inputStream, int numBytes) - throws IOException { - ByteBuffer data = ByteBuffer.allocate(numBytes); - int offset = 0; - int numBytesRemaining = numBytes; - while (numBytesRemaining > 0) { - int numRead = inputStream.read(data.array(), offset, numBytesRemaining); - if (numRead == -1) { - return null; - } - if (numRead == 0) { - throw new IllegalStateException("read() returned zero bytes"); - } - numBytesRemaining -= numRead; - offset += numRead; - } - return data; - } - - // TODO: Maybe return List instead of reencoding. - // - static - List extractDeviceRetrievalMethods( - byte[] encodedDeviceEngagement) { - List ret = new ArrayList<>(); - DataItem deviceEngagement = Util.cborDecode(encodedDeviceEngagement); - List methods = Util.cborMapExtractArray(deviceEngagement, 2); - for (DataItem method : methods) { - ret.add(Util.cborEncode(method)); - } - return ret; - } - - static long getDeviceRetrievalMethodType( byte[] encodeDeviceRetrievalMethod) { - List di = ((Array) Util.cborDecode(encodeDeviceRetrievalMethod)).getDataItems(); - return checkedLongValue(di.get(0)); - } - - static KeyPair createEphemeralKeyPair() { - try { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); - ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); - kpg.initialize(ecSpec); - KeyPair keyPair = kpg.generateKeyPair(); - return keyPair; - } catch (NoSuchAlgorithmException - | InvalidAlgorithmParameterException e) { - throw new IllegalStateException("Error generating ephemeral key-pair", e); - } - } - - static - X509Certificate signPublicKeyWithPrivateKey( String keyToSignAlias, - String keyToSignWithAlias) { - KeyStore ks = null; - try { - ks = KeyStore.getInstance("AndroidKeyStore"); - ks.load(null); - - /* First note that KeyStore.getCertificate() returns a self-signed X.509 certificate - * for the key in question. As per RFC 5280, section 4.1 an X.509 certificate has the - * following structure: - * - * Certificate ::= SEQUENCE { - * tbsCertificate TBSCertificate, - * signatureAlgorithm AlgorithmIdentifier, - * signatureValue BIT STRING } - * - * Conveniently, the X509Certificate class has a getTBSCertificate() method which - * returns the tbsCertificate blob. So all we need to do is just sign that and build - * signatureAlgorithm and signatureValue and combine it with tbsCertificate. We don't - * need a full-blown ASN.1/DER encoder to do this. - */ - X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(keyToSignAlias); - byte[] tbsCertificate = selfSignedCert.getTBSCertificate(); - - KeyStore.Entry keyToSignWithEntry = ks.getEntry(keyToSignWithAlias, null); - Signature s = Signature.getInstance("SHA256withECDSA"); - s.initSign(((KeyStore.PrivateKeyEntry) keyToSignWithEntry).getPrivateKey()); - s.update(tbsCertificate); - byte[] signatureValue = s.sign(); - - /* The DER encoding for a SEQUENCE of length 128-65536 - the length is updated below. - * - * We assume - and test for below - that the final length is always going to be in - * this range. This is a sound assumption given we're using 256-bit EC keys. - */ - byte[] sequence = new byte[]{ - 0x30, (byte) 0x82, 0x00, 0x00 - }; - - /* The DER encoding for the ECDSA with SHA-256 signature algorithm: - * - * SEQUENCE (1 elem) - * OBJECT IDENTIFIER 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA - * algorithm with SHA256) - */ - byte[] signatureAlgorithm = new byte[]{ - 0x30, 0x0a, 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x04, 0x03, - 0x02 - }; - - /* The DER encoding for a BIT STRING with one element - the length is updated below. - * - * We assume the length of signatureValue is always going to be less than 128. This - * assumption works since we know ecdsaWithSHA256 signatures are always 69, 70, or - * 71 bytes long when DER encoded. - */ - byte[] bitStringForSignature = new byte[]{0x03, 0x00, 0x00}; - - // Calculate sequence length and set it in |sequence|. - int sequenceLength = tbsCertificate.length - + signatureAlgorithm.length - + bitStringForSignature.length - + signatureValue.length; - if (sequenceLength < 128 || sequenceLength > 65535) { - throw new IllegalStateException("Unexpected sequenceLength " + sequenceLength); - } - sequence[2] = (byte) (sequenceLength >> 8); - sequence[3] = (byte) (sequenceLength & 0xff); - - // Calculate signatureValue length and set it in |bitStringForSignature|. - int signatureValueLength = signatureValue.length + 1; - if (signatureValueLength >= 128) { - throw new IllegalStateException("Unexpected signatureValueLength " - + signatureValueLength); - } - bitStringForSignature[1] = (byte) signatureValueLength; - - // Finally concatenate everything together. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write(sequence); - baos.write(tbsCertificate); - baos.write(signatureAlgorithm); - baos.write(bitStringForSignature); - baos.write(signatureValue); - byte[] resultingCertBytes = baos.toByteArray(); - - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - ByteArrayInputStream bais = new ByteArrayInputStream(resultingCertBytes); - X509Certificate result = (X509Certificate) cf.generateCertificate(bais); - return result; - } catch (IOException - | InvalidKeyException - | KeyStoreException - | NoSuchAlgorithmException - | SignatureException - | UnrecoverableEntryException - | CertificateException e) { - throw new IllegalStateException("Error signing key with private key", e); - } - } - - // This returns a SessionTranscript which satisfy the requirement - // that the uncompressed X and Y coordinates of the key for the - // mDL's ephemeral key-pair appear somewhere in the encoded - // DeviceEngagement. - // - // TODO: rename to buildFakeSessionTranscript(). - // - static byte[] buildSessionTranscript( KeyPair ephemeralKeyPair) { - // Make the coordinates appear in an already encoded bstr - this - // mimics how the mDL COSE_Key appear as encoded data inside the - // encoded DeviceEngagement - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW(); - // X and Y are always positive so for interop we remove any leading zeroes - // inserted by the BigInteger encoder. - byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray()); - byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray()); - baos.write(new byte[]{42}); - baos.write(x); - baos.write(y); - baos.write(new byte[]{43, 44}); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - byte[] blobWithCoords = baos.toByteArray(); - - DataItem encodedDeviceEngagementItem = cborBuildTaggedByteString( - cborEncode(new CborBuilder() - .addArray() - .add(blobWithCoords) - .end() - .build().get(0))); - DataItem encodedEReaderKeyItem = - cborBuildTaggedByteString(cborEncodeString("doesn't matter")); - - baos = new ByteArrayOutputStream(); - try { - byte[] handoverSelectBytes = new byte[]{0x01, 0x02, 0x03}; - DataItem handover = new CborBuilder() - .addArray() - .add(handoverSelectBytes) - .add(SimpleValue.NULL) - .end() - .build().get(0); - new CborEncoder(baos).encode(new CborBuilder() - .addArray() - .add(encodedDeviceEngagementItem) - .add(encodedEReaderKeyItem) - .add(handover) - .end() - .build()); - } catch (CborException e) { - e.printStackTrace(); - return null; - } - return baos.toByteArray(); - } - - /** - * Helper to determine the length of a single encoded CBOR data item. - * - *

This is used for handling 18013-5:2021 L2CAP data where messages are not separated - * by any framing. - * - * @param data data with a single encoded CBOR data item and possibly more - * @return -1 if no single encoded CBOR data item could be found, otherwise the length of the - * CBOR data that was decoded. - */ - static int cborGetLength(byte[] data) { - ByteArrayInputStream bais = new ByteArrayInputStream(data); - DataItem dataItem = null; - try { - dataItem = new CborDecoder(bais).decodeNext(); - } catch (CborException e) { - return -1; - } - if (dataItem == null) { - return -1; - } - return cborEncodeWithoutCanonicalizing(dataItem).length; - } - - /** - * Extracts the first CBOR data item from a stream of bytes. - * - *

If a data item was found, returns the bytes and removes it from the given output stream. - * - * @param pendingDataBaos A {@link ByteArrayOutputStream} with incoming bytes which must all - * be valid CBOR. - * @return the bytes of the first CBOR data item or {@code null} if not enough bytes have - * been received. - */ - static byte[] cborExtractFirstDataItem( ByteArrayOutputStream pendingDataBaos) { - byte[] pendingData = pendingDataBaos.toByteArray(); - int dataItemLength = Util.cborGetLength(pendingData); - if (dataItemLength == -1) { - return null; - } - byte[] dataItemBytes = new byte[dataItemLength]; - System.arraycopy(pendingDataBaos.toByteArray(),0, dataItemBytes, 0, dataItemLength); - pendingDataBaos.reset(); - pendingDataBaos.write(pendingData, dataItemLength, pendingData.length - dataItemLength); - return dataItemBytes; - } - - static - DataItem coseSign1Sign(Signature s, byte[] data, byte[] detachedContent, Collection certificateChain) { - - int dataLen = (data != null ? data.length : 0); - int detachedContentLen = (detachedContent != null ? detachedContent.length : 0); - if (dataLen > 0 && detachedContentLen > 0) { - throw new IllegalArgumentException("data and detachedContent cannot both be non-empty"); - } - - int keySize; - long alg; - if (s.getAlgorithm().equals("SHA256withECDSA")) { - keySize = 32; - alg = COSE_ALG_ECDSA_256; - } else if (s.getAlgorithm().equals("SHA384withECDSA")) { - keySize = 48; - alg = COSE_ALG_ECDSA_384; - } else if (s.getAlgorithm().equals("SHA512withECDSA")) { - keySize = 64; - alg = COSE_ALG_ECDSA_512; - } else { - throw new IllegalArgumentException("Unsupported algorithm " + s.getAlgorithm()); - } - - CborBuilder protectedHeaders = new CborBuilder(); - MapBuilder protectedHeadersMap = protectedHeaders.addMap(); - protectedHeadersMap.put(COSE_LABEL_ALG, alg); - byte[] protectedHeadersBytes = cborEncode(protectedHeaders.build().get(0)); - - byte[] toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent); - - byte[] coseSignature = null; - try { - s.update(toBeSigned); - byte[] derSignature = s.sign(); - coseSignature = signatureDerToCose(derSignature, keySize); - } catch (SignatureException e) { - throw new IllegalStateException("Error signing data", e); - } - - CborBuilder builder = new CborBuilder(); - ArrayBuilder array = builder.addArray(); - array.add(protectedHeadersBytes); - MapBuilder> unprotectedHeaders = array.addMap(); - try { - if (certificateChain != null && certificateChain.size() > 0) { - if (certificateChain.size() == 1) { - X509Certificate cert = certificateChain.iterator().next(); - unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.getEncoded()); - } else { - ArrayBuilder>> x5chainsArray = - unprotectedHeaders.putArray(COSE_LABEL_X5CHAIN); - for (X509Certificate cert : certificateChain) { - x5chainsArray.add(cert.getEncoded()); - } - } - } - } catch (CertificateEncodingException e) { - throw new IllegalStateException("Error encoding certificate", e); - } - if (data == null || data.length == 0) { - array.add(new SimpleValue(SimpleValueType.NULL)); - } else { - array.add(data); - } - array.add(coseSignature); - - return builder.build().get(0); - } - - /** - * Note: this uses the default JCA provider which may not support a lot of curves, for - * example it doesn't support Brainpool curves. If you need to use such curves, use - * {@link #coseSign1Sign(Signature, byte[], byte[], Collection)} instead with a - * Signature created using a provider that does have support. - * - * Currently only ECDSA signatures are supported. - * - * TODO: add support and tests for Ed25519 and Ed448. - */ - static - DataItem coseSign1Sign(PrivateKey key, String algorithm, byte[] data, byte[] additionalData, Collection certificateChain) { - try { - Signature s = Signature.getInstance(algorithm); - s.initSign(key); - return coseSign1Sign(s, data, additionalData, certificateChain); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalStateException("Caught exception", e); - } - } - - private static byte[] signatureDerToCose(byte[] signature, int keySize) { - ASN1Primitive asn1; - try { - asn1 = new ASN1InputStream(new ByteArrayInputStream(signature)).readObject(); - } catch (IOException e) { - throw new IllegalArgumentException("Error decoding DER signature", e); - } - ASN1Encodable[] asn1Encodables = castTo(ASN1Sequence.class, asn1).toArray(); - if (asn1Encodables.length != 2) { - throw new IllegalArgumentException("Expected two items in sequence"); - } - BigInteger r = castTo(ASN1Integer.class, asn1Encodables[0].toASN1Primitive()).getValue(); - BigInteger s = castTo(ASN1Integer.class, asn1Encodables[1].toASN1Primitive()).getValue(); - - byte[] rBytes = stripLeadingZeroes(r.toByteArray()); - byte[] sBytes = stripLeadingZeroes(s.toByteArray()); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - for (int n = 0; n < keySize - rBytes.length; n++) { - baos.write(0x00); - } - baos.write(rBytes); - for (int n = 0; n < keySize - sBytes.length; n++) { - baos.write(0x00); - } - baos.write(sBytes); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - return baos.toByteArray(); - } -} \ No newline at end of file diff --git a/wwwverifier/src/test/java/com/google/sps/RequestServletTest.java b/wwwverifier/src/test/java/com/android/identity/wwwreader/RequestServletTest.java similarity index 82% rename from wwwverifier/src/test/java/com/google/sps/RequestServletTest.java rename to wwwverifier/src/test/java/com/android/identity/wwwreader/RequestServletTest.java index 778e5a97b..bdf0d0ba3 100644 --- a/wwwverifier/src/test/java/com/google/sps/RequestServletTest.java +++ b/wwwverifier/src/test/java/com/android/identity/wwwreader/RequestServletTest.java @@ -1,7 +1,22 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.identity.wwwreader; // imports for CBOR encoding/decoding -import co.nstant.in.cbor.builder.MapBuilder; import co.nstant.in.cbor.CborBuilder; import co.nstant.in.cbor.model.Array; import co.nstant.in.cbor.model.ByteString; @@ -20,6 +35,7 @@ import javax.servlet.WriteListener; // key generation imports +import java.security.KeyPair; import java.security.PublicKey; import java.security.PrivateKey; @@ -35,12 +51,18 @@ import org.mockito.MockitoAnnotations; import static org.mockito.Mockito.doReturn; +// identity credential logic imports +import com.android.identity.internal.Util; +import com.android.identity.mdoc.connectionmethod.ConnectionMethod; +import com.android.identity.mdoc.connectionmethod.ConnectionMethodHttp; +import com.android.identity.mdoc.engagement.EngagementGenerator; +import com.android.identity.mdoc.engagement.EngagementParser; +import com.android.identity.mdoc.origininfo.OriginInfoWebsite; +import com.android.identity.mdoc.request.DeviceRequestParser; +import com.android.identity.mdoc.sessionencryption.SessionEncryption; + // imports from Datastore -import com.google.appengine.api.datastore.DatastoreService; -import com.google.appengine.api.datastore.DatastoreServiceFactory; -import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; -import com.google.appengine.api.datastore.Text; import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; import com.google.appengine.tools.development.testing.LocalServiceTestHelper; @@ -48,8 +70,8 @@ import java.math.BigInteger; import java.io.IOException; import java.util.Base64; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.OptionalLong; @RunWith(JUnit4.class) @@ -112,7 +134,7 @@ public void checkSessionCreation() throws IOException { List connectionMethods = engagement.getConnectionMethods(); Assert.assertEquals(connectionMethods.size(), 1); ConnectionMethodHttp connectionMethod = (ConnectionMethodHttp) connectionMethods.get(0); - String generatedUriWebsite = connectionMethod.getUriWebsite(); + String generatedUriWebsite = connectionMethod.getUri(); Assert.assertEquals(generatedUriWebsite.trim(), ServletConsts.ABSOLUTE_URL + "/" + generatedKeyString.trim()); } @@ -154,9 +176,11 @@ public void checkDeviceRequestGenerationWithWrongOriginInfo() throws IOException // construct messageData (containing Device Engagement) EngagementGenerator eg = new EngagementGenerator(eDeviceKeyPublic, EngagementGenerator.ENGAGEMENT_VERSION_1_1); - eg.addConnectionMethod(new ConnectionMethodHttp(ServletConsts.ABSOLUTE_URL + "/" + dKeyStr)); + eg.setConnectionMethods(Collections.singletonList(new ConnectionMethodHttp( + ServletConsts.ABSOLUTE_URL + "/" + dKeyStr))); String fakeBaseUrl = "https://fake-mdoc-reader.appspot.com/"; - eg.addOriginInfo(new OriginInfoWebsite(OriginInfo.CAT_DELIVERY, fakeBaseUrl)); + eg.setOriginInfos(Collections.singletonList( + new OriginInfoWebsite(fakeBaseUrl, OriginInfoWebsite.TYPE_REFERRER))); byte[] encodedDeviceEngagement = eg.generate(); byte[] messageDataBytes = createMessageData(encodedDeviceEngagement); @@ -167,10 +191,13 @@ public void checkDeviceRequestGenerationWithWrongOriginInfo() throws IOException // parse sessionData to extract DeviceRequest byte[] generatedTranscript = RequestServlet.getDatastoreProp(ServletConsts.TRANSCRIPT_PROP, dKey); - SessionEncryptionDevice sed = - new SessionEncryptionDevice(eDeviceKeyPrivate, eReaderKeyPublic, generatedTranscript); + SessionEncryption sed = + new SessionEncryption(SessionEncryption.ROLE_MDOC, + new KeyPair(eDeviceKeyPublic, eDeviceKeyPrivate), + eReaderKeyPublic, + generatedTranscript); DeviceRequestParser.DeviceRequest dr = new DeviceRequestParser() - .setDeviceRequest(sed.decryptMessageFromReader(sessionData).getKey()) + .setDeviceRequest(sed.decryptMessage(sessionData).getData()) .setSessionTranscript(generatedTranscript) .parse(); @@ -200,8 +227,10 @@ public void checkDeviceRequestGenerationWithCorrectOriginInfo() throws IOExcepti // construct messageData (containing Device Engagement) EngagementGenerator eg = new EngagementGenerator(eDeviceKeyPublic, EngagementGenerator.ENGAGEMENT_VERSION_1_1); - eg.addConnectionMethod(new ConnectionMethodHttp(ServletConsts.ABSOLUTE_URL + "/" + dKeyStr)); - eg.addOriginInfo(new OriginInfoWebsite(OriginInfo.CAT_DELIVERY, ServletConsts.BASE_URL)); + eg.setConnectionMethods(Collections.singletonList( + new ConnectionMethodHttp(ServletConsts.ABSOLUTE_URL + "/" + dKeyStr))); + eg.setOriginInfos(Collections.singletonList( + new OriginInfoWebsite(ServletConsts.BASE_URL, OriginInfoWebsite.TYPE_REFERRER))); byte[] encodedDeviceEngagement = eg.generate(); byte[] messageDataBytes = createMessageData(encodedDeviceEngagement); @@ -212,12 +241,15 @@ public void checkDeviceRequestGenerationWithCorrectOriginInfo() throws IOExcepti // parse sessionData to extract DeviceRequest byte[] generatedTranscript = RequestServlet.getDatastoreProp(ServletConsts.TRANSCRIPT_PROP, dKey); - SessionEncryptionDevice sed = - new SessionEncryptionDevice(eDeviceKeyPrivate, eReaderKeyPublic, generatedTranscript); + SessionEncryption sed = + new SessionEncryption(SessionEncryption.ROLE_MDOC, + new KeyPair(eDeviceKeyPublic, eDeviceKeyPrivate), + eReaderKeyPublic, + generatedTranscript); DeviceRequestParser.DeviceRequest dr = new DeviceRequestParser() - .setDeviceRequest(sed.decryptMessageFromReader(sessionData).getKey()) - .setSessionTranscript(generatedTranscript) - .parse(); + .setDeviceRequest(sed.decryptMessage(sessionData).getData()) + .setSessionTranscript(generatedTranscript) + .parse(); Assert.assertEquals(EngagementGenerator.ENGAGEMENT_VERSION_1_0, dr.getVersion()); List docRequestsList = dr.getDocumentRequests(); @@ -251,12 +283,15 @@ public void checkDeviceRequestGenerationWithTestVector() throws IOException { // parse sessionData to extract DeviceRequest byte[] generatedTranscript = RequestServlet.getDatastoreProp(ServletConsts.TRANSCRIPT_PROP, dKey); - SessionEncryptionDevice sed = - new SessionEncryptionDevice(eDeviceKeyPrivate, eReaderKeyPublic, generatedTranscript); + SessionEncryption sed = + new SessionEncryption(SessionEncryption.ROLE_MDOC, + new KeyPair(eDeviceKeyPublic, eDeviceKeyPrivate), + eReaderKeyPublic, + generatedTranscript); DeviceRequestParser.DeviceRequest dr = new DeviceRequestParser() - .setDeviceRequest(sed.decryptMessageFromReader(sessionData).getKey()) - .setSessionTranscript(generatedTranscript) - .parse(); + .setDeviceRequest(sed.decryptMessage(sessionData).getData()) + .setSessionTranscript(generatedTranscript) + .parse(); Assert.assertEquals(EngagementGenerator.ENGAGEMENT_VERSION_1_0, dr.getVersion()); List docRequestsList = dr.getDocumentRequests(); @@ -291,12 +326,15 @@ public void checkDeviceResponseParsingWithTestVector() throws IOException { // process response byte[] responseMessage = byteWriter.toByteArray(); - SessionEncryptionDevice sed = new SessionEncryptionDevice(eDeviceKeyPrivate, - eReaderKeyPublic, Util.cborEncode(sessionTranscript)); - Map.Entry responseMessageDecrypted = - sed.decryptMessageFromReader(responseMessage); - Assert.assertEquals(responseMessageDecrypted.getKey(), null); - Assert.assertEquals(responseMessageDecrypted.getValue(), OptionalLong.of(20)); + SessionEncryption sed = + new SessionEncryption(SessionEncryption.ROLE_MDOC, + new KeyPair(eDeviceKeyPublic, eDeviceKeyPrivate), + eReaderKeyPublic, + Util.cborEncode(sessionTranscript)); + SessionEncryption.DecryptedMessage responseMessageDecrypted = + sed.decryptMessage(responseMessage); + Assert.assertEquals(responseMessageDecrypted.getData(), null); + Assert.assertEquals(responseMessageDecrypted.getStatus(), OptionalLong.of(20)); String devResponseJSON = RequestServlet.getDeviceResponse(dKey); Assert.assertTrue(devResponseJSON.length() > 0); } diff --git a/wwwverifier/src/test/java/com/android/identity/wwwreader/TestVectors.java b/wwwverifier/src/test/java/com/android/identity/wwwreader/TestVectors.java new file mode 100644 index 000000000..b1fc053ec --- /dev/null +++ b/wwwverifier/src/test/java/com/android/identity/wwwreader/TestVectors.java @@ -0,0 +1,376 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.identity.wwwreader; + +public class TestVectors { + public static final String ISO_18013_5_ANNEX_D_DEVICE_ENGAGEMENT = + "a30063312e30018201d818584ba4010220012158205a88d182bce5f42efa59943f33359d2" + + "e8a968ff289d93e5fa444b624343167fe225820b16e8cf858ddc7690407ba61d4c338237a" + + "8cfcf3de6aa672fc60a557aa32fc670281830201a300f401f50b5045efef742b2c4837a9a" + + "3b0e1d05a6917"; + + public static final String ISO_18013_5_ANNEX_D_E_DEVICE_KEY_BYTES = + "d818584ba4010220012158205a88d182bce5f42efa59943f33359d2e8a968ff289d93e5fa" + + "444b624343167fe225820b16e8cf858ddc7690407ba61d4c338237a8cfcf3de6aa672fc60" + + "a557aa32fc67"; + + public static final String ISO_18013_5_ANNEX_D_DEVICE_RESPONSE = + "a36776657273696f6e63312e3069646f63756d656e747381a367646f6354797065756f72672e69736" + + "f2e31383031332e352e312e6d444c6c6973737565725369676e6564a26a6e616d65537061636573a1" + + "716f72672e69736f2e31383031332e352e3186d8185863a4686469676573744944006672616e646f6" + + "d58208798645b20ea200e19ffabac92624bee6aec63aceedecfb1b80077d22bfc20e971656c656d65" + + "6e744964656e7469666965726b66616d696c795f6e616d656c656c656d656e7456616c756563446f6" + + "5d818586ca4686469676573744944036672616e646f6d5820b23f627e8999c706df0c0a4ed98ad74a" + + "f988af619b4bb078b89058553f44615d71656c656d656e744964656e7469666965726a69737375655" + + "f646174656c656c656d656e7456616c7565d903ec6a323031392d31302d3230d818586da468646967" + + "6573744944046672616e646f6d5820c7ffa307e5de921e67ba5878094787e8807ac8e7b5b3932d2ce" + + "80f00f3e9abaf71656c656d656e744964656e7469666965726b6578706972795f646174656c656c65" + + "6d656e7456616c7565d903ec6a323032342d31302d3230d818586da46864696765737449440766726" + + "16e646f6d582026052a42e5880557a806c1459af3fb7eb505d3781566329d0b604b845b5f9e687165" + + "6c656d656e744964656e7469666965726f646f63756d656e745f6e756d6265726c656c656d656e745" + + "6616c756569313233343536373839d818590471a4686469676573744944086672616e646f6d5820d0" + + "94dad764a2eb9deb5210e9d899643efbd1d069cc311d3295516ca0b024412d71656c656d656e74496" + + "4656e74696669657268706f7274726169746c656c656d656e7456616c7565590412ffd8ffe000104a" + + "46494600010101009000900000ffdb004300130d0e110e0c13110f11151413171d301f1d1a1a1d3a2" + + "a2c2330453d4947443d43414c566d5d4c51685241435f82606871757b7c7b4a5c869085778f6d787b" + + "76ffdb0043011415151d191d381f1f38764f434f76767676767676767676767676767676767676767" + + "67676767676767676767676767676767676767676767676767676767676ffc0001108001800640301" + + "2200021101031101ffc4001b00000301000301000000000000000000000005060401020307ffc4003" + + "21000010303030205020309000000000000010203040005110612211331141551617122410781a116" + + "3542527391b2c1f1ffc4001501010100000000000000000000000000000001ffc4001a11010101000" + + "3010000000000000000000000014111213161ffda000c03010002110311003f00a5bbde22da2329c7" + + "d692bc7d0d03f52cfb0ff75e7a7ef3e7709723a1d0dae146ddfbb3c039ce07ad2bd47a7e32dbb8dd1" + + "d52d6ef4b284f64a480067dfb51f87ffb95ff00eb9ff14d215de66af089ce44b7dbde9cb6890a2838" + + "eddf18078f7add62d411ef4db9b10a65d6b95a147381ea0d495b933275fe6bba75c114104a8ba4104" + + "13e983dff004f5af5d34b4b4cde632d0bf1fd1592bdd91c6411f3934c2fa6af6b54975d106dcf4a65" + + "ae56e856001ebc03c7ce29dd9eef1ef10fc447dc9da76ad2aee93537a1ba7e4f70dd8eff0057c6dff" + + "b5e1a19854a83758e54528750946ec6704850cd037bceb08b6d7d2cc76d3317fc7b5cc04fb6707269" + + "c5c6e0c5b60ae549242123b0e493f602a075559e359970d98db89525456b51c951c8afa13ea8e98e3" + + "c596836783d5c63f5a61a99fdb7290875db4be88ab384bbbbbfc7183fdeaa633e8951db7da396dc48" + + "524fb1a8bd611a5aa2a2432f30ab420a7a6d3240c718cf031fa9ef4c9ad550205aa02951df4a1d6c8" + + "421b015b769db8c9229837ea2be8b1b0d39d0eba9c51484efdb8c0efd8d258daf3c449699f2edbd45" + + "84e7af9c64e3f96b9beb28d4ac40931e6478c8e76a24a825449501d867d2b1dcdebae99b9c752ae4e" + + "cd6dde4a179c1c1e460938f9149ef655e515c03919a289cb3dca278fb7bf177f4faa829dd8ce3f2ac" + + "9a7ecde490971fafd7dce15eed9b71c018c64fa514514b24e8e4f8c5c9b75c1e82579dc1233dfec08" + + "238f6add62d391acc1c5256a79e706d52d431c7a0145140b9fd149eb3a60dc5e88cbbc2da092411e9" + + "dc71f39a7766b447b344e847dcac9dcb5abba8d145061d43a6fcf1e65cf15d0e90231d3dd9cfe6299" + + "5c6dcc5ca12a2c904a15f71dd27d451453e09d1a21450961cbb3ea8a956433b781f1ce33dfed54f0e" + + "2b50a2b71d84ed6db18028a28175f74fc6bda105c529a791c25c4f3c7a11f71586268f4a66b726e33" + + "de9ea6f1b52b181c760724e47b514520a5a28a283ffd9d81858ffa468646967657374494409667261" + + "6e646f6d58204599f81beaa2b20bd0ffcc9aa03a6f985befab3f6beaffa41e6354cdb2ab2ce471656" + + "c656d656e744964656e7469666965727264726976696e675f70726976696c656765736c656c656d65" + + "6e7456616c756582a37576656869636c655f63617465676f72795f636f646561416a69737375655f6" + + "4617465d903ec6a323031382d30382d30396b6578706972795f64617465d903ec6a323032342d3130" + + "2d3230a37576656869636c655f63617465676f72795f636f646561426a69737375655f64617465d90" + + "3ec6a323031372d30322d32336b6578706972795f64617465d903ec6a323032342d31302d32306a69" + + "7373756572417574688443a10126a118215901f3308201ef30820195a00302010202143c4416eed78" + + "4f3b413e48f56f075abfa6d87eb84300a06082a8648ce3d04030230233114301206035504030c0b75" + + "746f7069612069616361310b3009060355040613025553301e170d3230313030313030303030305a1" + + "70d3231313030313030303030305a30213112301006035504030c0975746f706961206473310b3009" + + "0603550406130255533059301306072a8648ce3d020106082a8648ce3d03010703420004ace7ab734" + + "0e5d9648c5a72a9a6f56745c7aad436a03a43efea77b5fa7b88f0197d57d8983e1b37d3a539f4d588" + + "365e38cbbf5b94d68c547b5bc8731dcd2f146ba381a83081a5301e0603551d1204173015811365786" + + "16d706c65406578616d706c652e636f6d301c0603551d1f041530133011a00fa00d820b6578616d70" + + "6c652e636f6d301d0603551d0e0416041414e29017a6c35621ffc7a686b7b72db06cd12351301f060" + + "3551d2304183016801454fa2383a04c28e0d930792261c80c4881d2c00b300e0603551d0f0101ff04" + + "040302078030150603551d250101ff040b3009060728818c5d050102300a06082a8648ce3d0403020" + + "34800304502210097717ab9016740c8d7bcdaa494a62c053bbdecce1383c1aca72ad08dbc04cbb202" + + "203bad859c13a63c6d1ad67d814d43e2425caf90d422422c04a8ee0304c0d3a68d5903a2d81859039" + + "da66776657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c7661" + + "6c756544696765737473a2716f72672e69736f2e31383031332e352e31ad00582075167333b47b6c2" + + "bfb86eccc1f438cf57af055371ac55e1e359e20f254adcebf01582067e539d6139ebd131aef441b44" + + "5645dd831b2b375b390ca5ef6279b205ed45710258203394372ddb78053f36d5d869780e61eda313d" + + "44a392092ad8e0527a2fbfe55ae0358202e35ad3c4e514bb67b1a9db51ce74e4cb9b7146e41ac52da" + + "c9ce86b8613db555045820ea5c3304bb7c4a8dcb51c4c13b65264f845541341342093cca786e058fa" + + "c2d59055820fae487f68b7a0e87a749774e56e9e1dc3a8ec7b77e490d21f0e1d3475661aa1d065820" + + "7d83e507ae77db815de4d803b88555d0511d894c897439f5774056416a1c7533075820f0549a145f1" + + "cf75cbeeffa881d4857dd438d627cf32174b1731c4c38e12ca936085820b68c8afcb2aaf7c581411d" + + "2877def155be2eb121a42bc9ba5b7312377e068f660958200b3587d1dd0c2a07a35bfb120d99a0abf" + + "b5df56865bb7fa15cc8b56a66df6e0c0a5820c98a170cf36e11abb724e98a75a5343dfa2b6ed3df2e" + + "cfbb8ef2ee55dd41c8810b5820b57dd036782f7b14c6a30faaaae6ccd5054ce88bdfa51a016ba75ed" + + "a1edea9480c5820651f8736b18480fe252a03224ea087b5d10ca5485146c67c74ac4ec3112d4c3a74" + + "6f72672e69736f2e31383031332e352e312e5553a4005820d80b83d25173c484c5640610ff1a31c94" + + "9c1d934bf4cf7f18d5223b15dd4f21c0158204d80e1e2e4fb246d97895427ce7000bb59bb24c8cd00" + + "3ecf94bf35bbd2917e340258208b331f3b685bca372e85351a25c9484ab7afcdf0d2233105511f778" + + "d98c2f544035820c343af1bd1690715439161aba73702c474abf992b20c9fb55c36a336ebe01a876d" + + "6465766963654b6579496e666fa1696465766963654b6579a40102200121582096313d6c63e24e337" + + "2742bfdb1a33ba2c897dcd68ab8c753e4fbd48dca6b7f9a2258201fb3269edd418857de1b39a4e4a4" + + "4b92fa484caa722c228288f01d0c03a2c3d667646f6354797065756f72672e69736f2e31383031332" + + "e352e312e6d444c6c76616c6964697479496e666fa3667369676e6564c074323032302d31302d3031" + + "5431333a33303a30325a6976616c696446726f6dc074323032302d31302d30315431333a33303a303" + + "25a6a76616c6964556e74696cc074323032312d31302d30315431333a33303a30325a584059e64205" + + "df1e2f708dd6db0847aed79fc7c0201d80fa55badcaf2e1bcf5902e1e5a62e4832044b890ad85aa53" + + "f129134775d733754d7cb7a413766aeff13cb2e6c6465766963655369676e6564a26a6e616d655370" + + "61636573d81841a06a64657669636541757468a1696465766963654d61638443a10105a0f65820e99" + + "521a85ad7891b806a07f8b5388a332d92c189a7bf293ee1f543405ae6824d6673746174757300"; + + public static final String ISO_18013_5_ANNEX_D_DEVICE_RESPONSE_PORTRAIT_DATA = + "ffd8ffe000104a46494600010101009000900000ffdb004300130d0e110e0c13110f11151413171" + + "d301f1d1a1a1d3a2a2c2330453d4947443d43414c566d5d4c51685241435f82606871757b7c7b4a5c" + + "869085778f6d787b76ffdb0043011415151d191d381f1f38764f434f7676767676767676767676767" + + "676767676767676767676767676767676767676767676767676767676767676767676767676ffc000" + + "11080018006403012200021101031101ffc4001b00000301000301000000000000000000000005060" + + "401020307ffc400321000010303030205020309000000000000010203040005110612211331141551" + + "617122410781a1163542527391b2c1f1ffc4001501010100000000000000000000000000000001ffc" + + "4001a110101010003010000000000000000000000014111213161ffda000c03010002110311003f00" + + "a5bbde22da2329c7d692bc7d0d03f52cfb0ff75e7a7ef3e7709723a1d0dae146ddfbb3c039ce07ad2" + + "bd47a7e32dbb8dd1d52d6ef4b284f64a480067dfb51f87ffb95ff00eb9ff14d215de66af089ce44b7" + + "dbde9cb6890a2838eddf18078f7add62d411ef4db9b10a65d6b95a147381ea0d495b933275fe6bba7" + + "5c114104a8ba410413e983dff004f5af5d34b4b4cde632d0bf1fd1592bdd91c6411f3934c2fa6af6b" + + "54975d106dcf4a65ae56e856001ebc03c7ce29dd9eef1ef10fc447dc9da76ad2aee93537a1ba7e4f7" + + "0dd8eff0057c6dffb5e1a19854a83758e54528750946ec6704850cd037bceb08b6d7d2cc76d3317fc" + + "7b5cc04fb6707269c5c6e0c5b60ae549242123b0e493f602a075559e359970d98db89525456b51c95" + + "1c8afa13ea8e98e3c596836783d5c63f5a61a99fdb7290875db4be88ab384bbbbbfc7183fdeaa633e" + + "8951db7da396dc48524fb1a8bd611a5aa2a2432f30ab420a7a6d3240c718cf031fa9ef4c9ad550205" + + "aa02951df4a1d6c8421b015b769db8c9229837ea2be8b1b0d39d0eba9c51484efdb8c0efd8d258daf" + + "3c449699f2edbd4584e7af9c64e3f96b9beb28d4ac40931e6478c8e76a24a825449501d867d2b1dcd" + + "ebae99b9c752ae4ecd6dde4a179c1c1e460938f9149ef655e515c03919a289cb3dca278fb7bf177f4" + + "faa829dd8ce3f2ac9a7ecde490971fafd7dce15eed9b71c018c64fa514514b24e8e4f8c5c9b75c1e8" + + "2579dc1233dfec08238f6add62d391acc1c5256a79e706d52d431c7a0145140b9fd149eb3a60dc5e8" + + "8cbbc2da092411e9dc71f39a7766b447b344e847dcac9dcb5abba8d145061d43a6fcf1e65cf15d0e9" + + "0231d3dd9cfe62995c6dcc5ca12a2c904a15f71dd27d451453e09d1a21450961cbb3ea8a956433b78" + + "1f1ce33dfed54f0e2b50a2b71d84ed6db18028a28175f74fc6bda105c529a791c25c4f3c7a11f7158" + + "6268f4a66b726e33de9ea6f1b52b181c760724e47b514520a5a28a283ffd9"; + + public static final String ISO_18013_5_ANNEX_D_DS_CERT = + "308201ef30820195a00302010202143c4416eed784f3b413e48f56f075abfa6d87eb84300a06082" + + "a8648ce3d04030230233114301206035504030c0b75746f7069612069616361310b30090603550406" + + "13025553301e170d3230313030313030303030305a170d3231313030313030303030305a302131123" + + "01006035504030c0975746f706961206473310b30090603550406130255533059301306072a8648ce" + + "3d020106082a8648ce3d03010703420004ace7ab7340e5d9648c5a72a9a6f56745c7aad436a03a43e" + + "fea77b5fa7b88f0197d57d8983e1b37d3a539f4d588365e38cbbf5b94d68c547b5bc8731dcd2f146b" + + "a381a83081a5301e0603551d120417301581136578616d706c65406578616d706c652e636f6d301c0" + + "603551d1f041530133011a00fa00d820b6578616d706c652e636f6d301d0603551d0e0416041414e2" + + "9017a6c35621ffc7a686b7b72db06cd12351301f0603551d2304183016801454fa2383a04c28e0d93" + + "0792261c80c4881d2c00b300e0603551d0f0101ff04040302078030150603551d250101ff040b3009" + + "060728818c5d050102300a06082a8648ce3d040302034800304502210097717ab9016740c8d7bcdaa" + + "494a62c053bbdecce1383c1aca72ad08dbc04cbb202203bad859c13a63c6d1ad67d814d43e2425caf" + + "90d422422c04a8ee0304c0d3a68d"; + + public static final String ISO_18013_5_ANNEX_D_SESSION_TRANSCRIPT_BYTES = + "d81859024183d8185858a20063312e30018201d818584ba4010220012158205a88d182bce5f42efa5" + + "9943f33359d2e8a968ff289d93e5fa444b624343167fe225820b16e8cf858ddc7690407ba61d4c338" + + "237a8cfcf3de6aa672fc60a557aa32fc67d818584ba40102200121582060e3392385041f51403051f" + + "2415531cb56dd3f999c71687013aac6768bc8187e225820e58deb8fdbe907f7dd5368245551a34796" + + "f7d2215c440c339bb0f7b67beccdfa8258c391020f487315d10209616301013001046d646f631a200" + + "c016170706c69636174696f6e2f766e642e626c7565746f6f74682e6c652e6f6f6230081b28128b37" + + "282801021c015c1e580469736f2e6f72673a31383031333a646576696365656e676167656d656e746" + + "d646f63a20063312e30018201d818584ba4010220012158205a88d182bce5f42efa59943f33359d2e" + + "8a968ff289d93e5fa444b624343167fe225820b16e8cf858ddc7690407ba61d4c338237a8cfcf3de6" + + "aa672fc60a557aa32fc6758cd91022548721591020263720102110204616301013000110206616301" + + "036e6663005102046163010157001a201e016170706c69636174696f6e2f766e642e626c7565746f6" + + "f74682e6c652e6f6f6230081b28078080bf2801021c021107c832fff6d26fa0beb34dfcd555d4823a" + + "1c11010369736f2e6f72673a31383031333a6e66636e6663015a172b016170706c69636174696f6e2" + + "f766e642e7766612e6e616e57030101032302001324fec9a70b97ac9684a4e326176ef5b981c5e853" + + "3e5f00298cfccbc35e700a6b020414"; + + // From ISO/IEC 18013-5 Annex D.4.1.1 mdoc request: + // + public static final String ISO_18013_5_ANNEX_D_DEVICE_REQUEST = + "a26776657273696f6e63312e306b646f63526571756573747381a26c6974656d7352657175657374d" + + "8185893a267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6a6e616d6553" + + "7061636573a1716f72672e69736f2e31383031332e352e31a66b66616d696c795f6e616d65f56f646" + + "f63756d656e745f6e756d626572f57264726976696e675f70726976696c65676573f56a6973737565" + + "5f64617465f56b6578706972795f64617465f568706f727472616974f46a726561646572417574688" + + "443a10126a118215901b7308201b330820158a00302010202147552715f6add323d4934a1ba175dc9" + + "45755d8b50300a06082a8648ce3d04030230163114301206035504030c0b72656164657220726f6f7" + + "4301e170d3230313030313030303030305a170d3233313233313030303030305a3011310f300d0603" + + "5504030c067265616465723059301306072a8648ce3d020106082a8648ce3d03010703420004f8912" + + "ee0f912b6be683ba2fa0121b2630e601b2b628dff3b44f6394eaa9abdbcc2149d29d6ff1a3e091135" + + "177e5c3d9c57f3bf839761eed02c64dd82ae1d3bbfa38188308185301c0603551d1f041530133011a" + + "00fa00d820b6578616d706c652e636f6d301d0603551d0e04160414f2dfc4acafc5f30b464fada20b" + + "fcd533af5e07f5301f0603551d23041830168014cfb7a881baea5f32b6fb91cc29590c50dfac416e3" + + "00e0603551d0f0101ff04040302078030150603551d250101ff040b3009060728818c5d050106300a" + + "06082a8648ce3d0403020349003046022100fb9ea3b686fd7ea2f0234858ff8328b4efef6a1ef71ec" + + "4aae4e307206f9214930221009b94f0d739dfa84cca29efed529dd4838acfd8b6bee212dc6320c46f" + + "eb839a35f658401f3400069063c189138bdcd2f631427c589424113fc9ec26cebcacacfcdb9695d28" + + "e99953becabc4e30ab4efacc839a81f9159933d192527ee91b449bb7f80bf"; + + public static final String ISO_18013_5_ANNEX_D_ITEMS_REQUEST = + "a267646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6a6e616d65537061636" + + "573a1716f72672e69736f2e31383031332e352e31a66b66616d696c795f6e616d65f56f646f63756d" + + "656e745f6e756d626572f57264726976696e675f70726976696c65676573f56a69737375655f64617" + + "465f56b6578706972795f64617465f568706f727472616974f4"; + + public static final String ISO_18013_5_ANNEX_D_READER_CERT = + "308201b330820158a00302010202147552715f6add323d4934a1ba175dc945755d8b50300a06082" + + "a8648ce3d04030230163114301206035504030c0b72656164657220726f6f74301e170d3230313030" + + "313030303030305a170d3233313233313030303030305a3011310f300d06035504030c06726561646" + + "5723059301306072a8648ce3d020106082a8648ce3d03010703420004f8912ee0f912b6be683ba2fa" + + "0121b2630e601b2b628dff3b44f6394eaa9abdbcc2149d29d6ff1a3e091135177e5c3d9c57f3bf839" + + "761eed02c64dd82ae1d3bbfa38188308185301c0603551d1f041530133011a00fa00d820b6578616d" + + "706c652e636f6d301d0603551d0e04160414f2dfc4acafc5f30b464fada20bfcd533af5e07f5301f0" + + "603551d23041830168014cfb7a881baea5f32b6fb91cc29590c50dfac416e300e0603551d0f0101ff" + + "04040302078030150603551d250101ff040b3009060728818c5d050106300a06082a8648ce3d04030" + + "20349003046022100fb9ea3b686fd7ea2f0234858ff8328b4efef6a1ef71ec4aae4e307206f921493" + + "0221009b94f0d739dfa84cca29efed529dd4838acfd8b6bee212dc6320c46feb839a35"; + + // Note: manually extracted from ISO_18013_5_ANNEX_D_DEVICE_REQUEST + // + public static final String ISO_18013_5_ANNEX_D_READER_AUTH = + "8443a10126a118215901b7308201b330820158a00302010202147552715f6add323d4934a1ba175" + + "dc945755d8b50300a06082a8648ce3d04030230163114301206035504030c0b72656164657220726f" + + "6f74301e170d3230313030313030303030305a170d3233313233313030303030305a3011310f300d0" + + "6035504030c067265616465723059301306072a8648ce3d020106082a8648ce3d03010703420004f8" + + "912ee0f912b6be683ba2fa0121b2630e601b2b628dff3b44f6394eaa9abdbcc2149d29d6ff1a3e091" + + "135177e5c3d9c57f3bf839761eed02c64dd82ae1d3bbfa38188308185301c0603551d1f0415301330" + + "11a00fa00d820b6578616d706c652e636f6d301d0603551d0e04160414f2dfc4acafc5f30b464fada" + + "20bfcd533af5e07f5301f0603551d23041830168014cfb7a881baea5f32b6fb91cc29590c50dfac41" + + "6e300e0603551d0f0101ff04040302078030150603551d250101ff040b3009060728818c5d0501063" + + "00a06082a8648ce3d0403020349003046022100fb9ea3b686fd7ea2f0234858ff8328b4efef6a1ef7" + + "1ec4aae4e307206f9214930221009b94f0d739dfa84cca29efed529dd4838acfd8b6bee212dc6320c" + + "46feb839a35f658401f3400069063c189138bdcd2f631427c589424113fc9ec26cebcacacfcdb9695" + + "d28e99953becabc4e30ab4efacc839a81f9159933d192527ee91b449bb7f80bf"; + + public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_READER_KEY_X = + "60e3392385041f51403051f2415531cb56dd3f999c71687013aac6768bc8187e"; + public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_READER_KEY_Y = + "e58deb8fdbe907f7dd5368245551a34796f7d2215c440c339bb0f7b67beccdfa"; + public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_READER_KEY_D = + "de3b4b9e5f72dd9b58406ae3091434da48a6f9fd010d88fcb0958e2cebec947c"; + + public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_DEVICE_KEY_X = + "5a88d182bce5f42efa59943f33359d2e8a968ff289d93e5fa444b624343167fe"; + public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_DEVICE_KEY_Y = + "b16e8cf858ddc7690407ba61d4c338237a8cfcf3de6aa672fc60a557aa32fc67"; + public static final String ISO_18013_5_ANNEX_D_EPHEMERAL_DEVICE_KEY_D = + "c1917a1579949a042f1ba9fc53a2df9b1bc47adf31c10f813ed75702d1c1f136"; + + public static final String ISO_18013_5_ANNEX_D_STATIC_DEVICE_KEY_X = + "96313d6c63e24e3372742bfdb1a33ba2c897dcd68ab8c753e4fbd48dca6b7f9a"; + public static final String ISO_18013_5_ANNEX_D_STATIC_DEVICE_KEY_Y = + "1fb3269edd418857de1b39a4e4a44b92fa484caa722c228288f01d0c03a2c3d6"; + public static final String ISO_18013_5_ANNEX_D_STATIC_DEVICE_KEY_D = + "6ed542ad4783f0b18c833fadf2171273a35d969c581691ef704359cc7cf1e8c0"; + + public static final String ISO_18013_5_ANNEX_D_SESSION_ESTABLISHMENT = + "a26a655265616465724b6579d818584ba40102200121582060e3392385041f51403051f2415531cb5" + + "6dd3f999c71687013aac6768bc8187e225820e58deb8fdbe907f7dd5368245551a34796f7d2215c44" + + "0c339bb0f7b67beccdfa64646174615902df52ada2acbeb6c390f2ca0bc659b484678eb94dd450743" + + "86aadece23777b44606e42e2846bc2e2ee3c1e867b1d1685e41354a021abb0fda36f09cf5d5c51b56" + + "1d3be41c9347ae71cf2b49de9dec7b44046ab02247931b210c9157840c1514a6027b08810716adf61" + + "966344979314ac3ae9f40e66e015c1254a684108bd093e8772ec333fb663fd6803af02ea10bdbe83a" + + "999f75b55a180f872139fb57ac04acd58ca15eca150cde1c3b849401188b7a30ce887dd7b71b12eda" + + "2fc6ec6e5235a6c9498351fcd301f2292a4ebba7555285cee84ead96ef1677b0af8239f6a7a52af4b" + + "8809b1d52ab21a162ca31ade21c57bd1d9970a2832aac41c7d52d1c4fee4ee64030a218df51363be7" + + "01792fa6c515c489bd39dcad6fba48f1d6eb19e9c769531a3bf9998a32c01841305f23844ca3db6a1" + + "ff0d0d917343d62fc72ad58eab01a3198116f19606609f94e35eacb78d23c59c67852a361915fe878" + + "48cdba5630c99fab71aeff72d131cf442654f7708ec48216416f2d996cf6cf91012b771b88907b1d1" + + "629dfa794343e653c31207482e2f6621cd4b5dcf3b3c328625c33fe98be99c5f264a264315be41baf" + + "dc726f8bcde5920de0a71884d860af44c1ff1b3d78b2e8d720d85dae53fea2b3fa1806162a4be02d0" + + "39567c5eb2419c2ad879af48fcb7df55ca94f1b00f62187fa2329c8227aae0130ec052ca3e2102e57" + + "e72911b328cfdcfbaaf6b9364660f613415382644c30c0bd4e222c5cf94ba5a73679c53d5ced95ca5" + + "0787c2289a0c17358393c1e0f2272361002fb9b160606888a59ef7a2c389f68b7cb424572db026b17" + + "cf2bdcafcb67c8292d92b50050356900a62a82b16f854759052b00f0f4673a46229f43257e8e83254" + + "01b3fecc8c6d2258baf7f7c2fbbafab3a1b6aded4eceac1eafd5b61118df93bc0a622b03504fde47c" + + "ebb224e983db12677e316c22aae042d6ce4adae0d8b0f40437b8e1afa0859c9501beb63974496859a" + + "60f11069b1965b4ffac5779a96191f89eac7caa688b9e67c"; + + public static final String ISO_18013_5_ANNEX_D_SESSION_DATA = + "a16464617461590dfa46da5fb292a7880af38f2e4d9eb23ecac231ad21dfe81e3b5c21e7f3d2b5a2d" + + "4676dd13331112f0fba678275dc2fcd889150a7bbb333b7ecf5f35fc8b2e4aee701651a4bdc93cd25" + + "bc533582647507b5b9a075deaf7e1ef035acb3c8b403ac6e51a19d4289381035199da169b5ab175d8" + + "bc2075ac73dbaa76aa79d9a26ac3930034515525c413110abaeae731545b36400205f3130e902242d" + + "b99066a04ab6cef9d14672c3dfe467802e5364dff5535c8c36fdb53afde285ee9462f72a4f8b57078" + + "79590d8b5ee83a3068c1c25f13681085cf4a5af3cb2e77bcc7cae6def76ab5ad119e6db563799d15a" + + "f9f5861b7ec0003c68fa46f12ca366263b5fe5bb8f5b16bede1e5e5919abf8b675fb10ce4655815fe" + + "6a3582ec44e0c93f0cf3a5ea1ca2e476113b47bc1ff2484f791c68385cd5ff3f1f27f70a88c7c6495" + + "81e59a5bb2371d6268704526bf1b16cd36d9e739bef50a199deafb8ceadd42c260e58688bd569b420" + + "f32ad0502e6dff9346440459febbb49d843e50e93d46c0fd2125d4131e1d528435110b5e0db9e4179" + + "5716422dc895425773eae490fbfba7c10f85ec364bdfac7de120ba4883142e854bf19510f969be690" + + "fad9a3f7197885dcb44bd9028998adf6e95356af058e4c20502b5c4d3c4c52469727042e75e0ca8e4" + + "3efb590da9a1a1cc3ffdd03a422e7589ec237c36c0d5587ab853ab39be4388cdd9feb7b763ecb6344" + + "172fafde86a9501975dd86f19f095f98a1e65ace933e8723db8f1e5074f7c9c0415e4e69f12c2a6c1" + + "4c0ca09c872f2ac83e0ec7294e6dc4f1fa087d48201c9c68d1cef4b793fb97b374505b348e090207a" + + "179305347599060200a1f398e9d9aa2eadbe31ee0172b25d1f0b1287b3aad8f1afa560981c6490a1a" + + "ec1052f47201ab5baed0ecf826c7b2b416ce0c2e265403a922f80d1cf6842a7182c8bbe7a138096cb" + + "1fcffcfc102d16d08cbf86108393ef8276120e9341c11c5e5e1cebc257b697d2182ed1ead67d7e581" + + "4a3f9b380cf917ccf59921f4ae545a14a8ec0f8642b7bd5565039b60546efb2c68aa0ac2d3f972386" + + "cd9f47280e2f27af727335cd6ca1aec25c2ad2d079cd3fb045ccbaf6f4a5b0a80e07795f5790ff53a" + + "aefdf3a63240ca7e9d5658ce84a21d310463335cf1f6015dc89ee54d687abcd4787c060a0bce53020" + + "b1c6a5bb6c3ea4d759b7f3eddabcaade785c23caa9ba15272724e0589cef502d80085bd77bae93897" + + "b01e2588ddd504aebe8333a153790a9699748a4269f5017c81aaf8061f9985e0cf7553b4c4d0a5c40" + + "1f20f0ab6d85078728676bcfdc9c1a1bc4dea2b01c68e0bb7bf8ada66aa93fc322673efa3895b09d0" + + "85a0cd085dab3018a9e032acd3d715bf190c629fc55d55ed815b0ba56e83f78c453165c024ca5b2de" + + "5b4accfbab07aa1c482b8e960db03652167f903db0ecf03ef85834951a603c3e6069210744d70c587" + + "5e50badb91dcc8beca4945b869e73e535a7967fbcb377ce861f07e537349c8a33793c0c05c1b4dd22" + + "0634e1f52f6701d79d83f99311d6c1463ea3bbcf4d1a7614cbedfceafa1e874b84e6b30698551c6da" + + "9f8487fc5270cd5dc73f9aa9dc8a513b1514d7abb0bec4d2cd06b14efd23f3c59602fcfc5d9aecf48" + + "148128ca86d6d4ebbe2f68460db00a49759887a478a1eccdd8b426aa130d8b7da0e331415a9682d71" + + "25e56285a927f54ac5139a3f8d78c2dc6ffa0bc9ad4db749e8dae10169aa6be9206b635943b7a0970" + + "a23e04a2ba66712365b2ea0afb0422a0224592074f723781b8df86627296f9f126f94efe089f8db5f" + + "2eea4f28673e11ccd80726901ee1bcc8fa49b6f65c0f8587222f2bb81744a3ece2f74ac21e8c5805d" + + "054f93e2d669616ecbc07f3d017a36b951aecd28a14ada87e4f935ef7f93c2c7d01cedf5658131908" + + "e4d36aa4690ba952d3f7fa25b6a8481d42f6a7794c8b1dd4de2f7e6bb3ed6fd3915591997e06f0171" + + "5bac3cdfb0885ce97136e4b7b4b1e6685fd42de1e8ae610523c2b2603977cf3c45058802d734322a9" + + "aa0181ff1231b4674071e5c75d8f11e56d67916b4d5f7a9b42a26519c990fdb728630f0755cf8fc0d" + + "177c24e1375cc793cee463da5dbef1fe6a835bf75317052b7b077d397c7b617a632371d22eb64c91f" + + "91d8fdf5aa3345f0e6e7a0d42e8da52087a89428a1241b4527d5f4d3751b10d40e8768bc6b1a55b53" + + "1a9e8fb9afdfaf3aed0a3bc71b9c432b89da34e4685b8b48f650394e760270df2c837ab51ded999b9" + + "03c0f9630478adb8843e462ad8123466ff837dc18241db341ecabbc0a8693bd35831d49c09adf8c5d" + + "7c4f80b3628b633d46a93c2b7b61e5127abb5e877e6904de7049a53213b357cdb443c31bababdc048" + + "0fe6986951ea9d684e53f5b53871e80148afc30c4e2eed05a0aeafa0b98f98e8ffbad6af968ecfd28" + + "6b24c43bf2b54e64e28fab328d791bbf80292e2226dfc7d0d562b511321b6b501eadbd1661f028bad" + + "02e37575104f646222484aac1548a762cfec36b9597a4e7309497b49f1921a948a597cc7d721f6396" + + "389aaffd2828da0443e4f2354cf2fbe69c0a2a8f3d2271207440e95445a4fb24de95d5232e66ff84c" + + "8e1ab5c5576e316ce3e78d7c0047695ef0037cc7f7d0f21fa53a1976a7e1bb2d70751c6ebebb5fd24" + + "dad9fa58ffee261b0ee7a55a86a6236901feb322400965313175a263e5b8437eb2a7ddf239d15c27b" + + "2e8b55abccd7574df807f3e8483670aac580fec1bbeb779a3cb2e89440e8badccdcdccf6f6e7dd9a2" + + "d62080c7057824ede7186f924e2c59934a0fc0d518d05ff48da1b012322bb55d2493a850954b6af58" + + "5e72f5bef55180f9fc293e40bd48a261cee4099ad4674541ddb5a81683034e9e31f59c8942321f09a" + + "5ed1e11854b6cd8af4afcf919f2001722c2396b9b30e1c5d8182061bb39201477a0538db896a39949" + + "3151b1f6162284284b8cc42701fad81c63846f0d7e3be5004a3d1e5dc047c2b28d7c28940e70ba52a" + + "c7aa0de584a9ff7859aa0e9af8d24d5dcfd07cf5c6fd8034e2a3ef63b41be59ab93d941cee00b3029" + + "478181e576d3b0c09f28b1c70ab7c2e622e7cacb2f6d2337c430afa5736169bddfe379f5b3d3392cf" + + "aa1bc45f6d3e47e73678ec19cecb20b3aee82378e41f84b59c172abc8d40370f8bb833f8edca890cd" + + "4f85f5610d3e208bada4deb6f15d9cb0a9f805b1f9915e40bed12ed2494886801d6ff83df037b4a24" + + "5d03489fa7dda552dccbd11b84101c4147595e0ee6d89b5d7621492d21dc01b5afc1ce3c65cc878d8" + + "23810f7d0c4232374a4e82013ad3a99f2422aa8e4e2cc33c66dc5b07bd2ece416999563a6f2e62ef3" + + "67904990f1442519eb00c6eecb1c886bde5613dba37908cc79f71bcfbf5fb96142488f0be69c510e1" + + "44bd1a25f2e6f22bf78cc213d7ba83def9146224b67c1ca5cad8520e55d1b01daaa70475caa1e4ed1" + + "17ac8952a3f4566edd28e599b59a3be80405bedf150e5d5dd4d0f3d1d84e8e7f7dfe945d358493bb1" + + "19470aabbf5b3eb7205f2184793e5f9b41c0c622ebf0b83730feb3c2d06de8f7992cee469bf104595" + + "cd98307f7d3d584760b6f1d75e10f51325626627050f5b636f34b93471c290b1afede150055b796d3" + + "d08f31b52360ef28630acc273bfcda6aa14df76867cc230c0597bd76105d7a54426698d87ad1ac683" + + "c7569b80f0f4c5d169dd0dbcf390e8449b698649183cdf214a13e51ed5c5c38df9931ad23a05e49b8" + + "5975bcc65cca5ebfb404f62552cf46fd535e5d9e8178dff0156fbd6227e2e04c56af73e8a149dbd63" + + "f5cd0a5ec1046c30b3a25ba60cfc869df084553430e7058e948b8e426003421988789c266fcf9f6c3" + + "09fcc785e11e76121316f82b61555352c91f3d936dbc1e181a6924d480ba09a62adf930dee5884ce5" + + "362ff31f1e2a4558702bc0d8c871cc322efad66efc946c3a9ae959ef20c052787d6a5e04d7dc9dfc2" + + "c9941104ad26c136a9827a866b9e0942dbacd4aed56b48547c6dc1d0216fcdd2b5ce40828bdae5df4" + + "8e724232b01cc567173e07b9089e7834bb92c873c5e08ba055698df5f79ee73e122b5b72ee3e2e100" + + "858dad409da55ad0fa1aa9caa60bf9c25e9ba3e1dc012724c89903a820b63dd5f14f0019007b18068" + + "4afbe125a0cf87af796ae20e465641c5e8fb91b6e7c6395d2f49f17bac3e35110c16b119bf289e11a" + + "fb4bbae40266aa87605298ec5bb0601fa415038621ea100db5d9d8a5da4d74fd92ed882546afc7a8a" + + "3dc648b1c7852e8fa43ca9ed287a4bb9dd299bfc69414f990bf3b7b58a932a4ff9c86e2fb131b7cc7" + + "b65464cc80011267ab49f5c599b9ce43acc9a06b856b25b6fe5f51afd6db147386a1ab30575a0eae6" + + "07ffe116cae7fb70df6841ae0f52eaf359305becdf7f4636f3c31fab45387ef97cef0b8ef6a8de702" + + "a2f1c21c3aee10382e8b8610a03bf6f1c54527a1d6db79f80f71c7f29880bb4b0d46cee3a6063028e" + + "901f2f3514639bce237259a3122beea38763e205263f663543bf9bb390337f6a992b74dbfab966a9a" + + "bf3dd54b15956b47ee731d23ec9a87a0027ed16f6fc4384812df153231c61331ef22e16035719044f" + + "ddd7b6a9626aa8397073d0cbf42fffd2b8201dca65aadaf98048e7bc0917ed83c7cbb59086ceff885" + + "b523fca2d65d87323fd6bbd75b0a312b317b217985334086a63cdc4f065be41dfc590c76d71f93f76" + + "c091ee6b2878c2d718adde4493eef3a4cb8b2764e7013b80f922bf2b3b3572029975bc1388b7c975f" + + "353a02fa01ab679bbe041041dc1425787606df44013b7627c4d43b1edbda3768386f2b9a78470ac7a" + + "c5ca6b2d20385d53e2e412828e2ebcdd17ce8658c655babeefe0b2f43f606a192613263a2c27831a7" + + "c5953c9a1f2910093b4720e78e3b34ae7102e1094fcc6655d3af6d9a4c8f58f2478ef532121b7c91f" + + "e558a80257d63d8e9b4300c09dc04d9d37975f5cf3e548f23e40c070822419a74267d08453815ba8e" + + "02943c3bc5a13ad669fd79d8e4ee5f5be82dc638ba9a1d"; + + public static final String ISO_18013_5_ANNEX_D_SESSION_TERMINATION = + "a16673746174757314"; +}