diff --git a/lib.omw.android/pom.xml b/lib.omw.android/pom.xml index 6c64ddb..6ba8e62 100644 --- a/lib.omw.android/pom.xml +++ b/lib.omw.android/pom.xml @@ -4,7 +4,7 @@ net.vx4 lib.omw - 0.0.1-SNAPSHOT + 0.1.0 lib.omw.android \ No newline at end of file diff --git a/lib.omw.android/src/main/java/android/content/Context.java b/lib.omw.android/src/main/java/android/content/Context.java index b2f998a..77fa174 100644 --- a/lib.omw.android/src/main/java/android/content/Context.java +++ b/lib.omw.android/src/main/java/android/content/Context.java @@ -7,5 +7,9 @@ * @author ckahlo */ public class Context { + public static final java.lang.String TELEPHONY_SERVICE = "phone"; + public Object getSystemService(String name) { + return null; + }; } diff --git a/lib.omw.android/src/main/java/android/nfc/NfcAdapter.java b/lib.omw.android/src/main/java/android/nfc/NfcAdapter.java new file mode 100644 index 0000000..9b4597d --- /dev/null +++ b/lib.omw.android/src/main/java/android/nfc/NfcAdapter.java @@ -0,0 +1,11 @@ +package android.nfc; + +import android.content.Context; + +public class NfcAdapter { + public static NfcAdapter getDefaultAdapter(Context context) { + return null; + } + + +} diff --git a/lib.omw.android/src/main/java/android/se/omapi/Channel.java b/lib.omw.android/src/main/java/android/se/omapi/Channel.java new file mode 100644 index 0000000..3730ac6 --- /dev/null +++ b/lib.omw.android/src/main/java/android/se/omapi/Channel.java @@ -0,0 +1,36 @@ +package android.se.omapi; + +public class Channel { + public void close() { + } + + + public Session getSession() { + return null; + } + + + public boolean isBasicChannel() { + return false; + } + + + public boolean isOpen() { + return false; + } + + + public boolean selectNext() { + return false; + } + + + public byte[] transmit(final byte[] command) { + return null; + } + + + public byte[] getSelectResponse() { + return null; + } +} \ No newline at end of file diff --git a/lib.omw.android/src/main/java/android/se/omapi/Reader.java b/lib.omw.android/src/main/java/android/se/omapi/Reader.java new file mode 100644 index 0000000..13b5c9e --- /dev/null +++ b/lib.omw.android/src/main/java/android/se/omapi/Reader.java @@ -0,0 +1,33 @@ +package android.se.omapi; + +import java.io.IOException; + + +public class Reader { + Reader(final String name, final SEService service) { + } + + + public String getName() { + return null; + } + + + public Session openSession() throws IOException { + return null; + } + + + public boolean isSecureElementPresent() { + return false; + } + + + public SEService getSEService() { + return null; + } + + + public void closeSessions() { + } +} \ No newline at end of file diff --git a/lib.omw.android/src/main/java/android/se/omapi/SEService.java b/lib.omw.android/src/main/java/android/se/omapi/SEService.java new file mode 100644 index 0000000..3133dac --- /dev/null +++ b/lib.omw.android/src/main/java/android/se/omapi/SEService.java @@ -0,0 +1,40 @@ +package android.se.omapi; + +import java.util.concurrent.Executor; + +import android.content.Context; + + +public class SEService { + public SEService(final Context context, final Executor executor, final SEService.OnConnectedListener listener) { + } + + + public Reader[] getReaders() { + return null; + } + + + public Reader getUiccReader(final int slotNumber) throws IllegalArgumentException { + return null; + } + + + public String getVersion() { + return "VX4 OMAPI mock"; + } + + + public boolean isConnected() { + return true; + } + + + public void shutdown() { + } + + + public static interface OnConnectedListener { + abstract void onConnected(); + } +} diff --git a/lib.omw.android/src/main/java/android/se/omapi/Session.java b/lib.omw.android/src/main/java/android/se/omapi/Session.java new file mode 100644 index 0000000..1c942df --- /dev/null +++ b/lib.omw.android/src/main/java/android/se/omapi/Session.java @@ -0,0 +1,46 @@ +package android.se.omapi; + +public class Session { + public void close() { + } + + + public void closeChannels() { + } + + + public byte[] getATR() { + return null; + } + + + public Reader getReader() { + return null; + } + + + public boolean isClosed() { + return false; + } + + + public Channel openBasicChannel(final byte[] aid) { + return null; + } + + + public Channel openLogicalChannel(final byte[] aid) { + return null; + } + + + public Channel openBasicChannel(final byte[] aid, final byte p2) { + return null; + } + + + public Channel openLogicalChannel(final byte[] aid, final byte p2) { + return null; + } + +} \ No newline at end of file diff --git a/lib.omw.android/src/main/java/android/telephony/IccOpenLogicalChannelResponse.java b/lib.omw.android/src/main/java/android/telephony/IccOpenLogicalChannelResponse.java new file mode 100644 index 0000000..2e2baf9 --- /dev/null +++ b/lib.omw.android/src/main/java/android/telephony/IccOpenLogicalChannelResponse.java @@ -0,0 +1,17 @@ +package android.telephony; + +public class IccOpenLogicalChannelResponse { + public static int STATUS_NO_ERROR = 1; + + public int getStatus() { + return -1; + } + + public int getChannel() { + return 0; + } + + public byte[] getSelectResponse() { + return null; + } +} diff --git a/lib.omw.android/src/main/java/android/telephony/TelephonyManager.java b/lib.omw.android/src/main/java/android/telephony/TelephonyManager.java new file mode 100644 index 0000000..f174e52 --- /dev/null +++ b/lib.omw.android/src/main/java/android/telephony/TelephonyManager.java @@ -0,0 +1,23 @@ +package android.telephony; + +public class TelephonyManager { + public boolean hasCarrierPrivileges() { + return false; + } + + public boolean hasIccCard() { + return false; + } + + public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID) { + return null; + } + + public boolean iccCloseLogicalChannel(int channel) { + return false; + } + + public String iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2, int p3, String data) { + return null; + } +} diff --git a/lib.omw.android/src/main/java/android/util/Log.java b/lib.omw.android/src/main/java/android/util/Log.java new file mode 100644 index 0000000..bbf6ca5 --- /dev/null +++ b/lib.omw.android/src/main/java/android/util/Log.java @@ -0,0 +1,18 @@ +package android.util; + +public class Log { + public static int d(String tag, String msg) { + System.out.println("D/" + tag + ": " + msg); + return 0; + }; + + public static int e(String tag, String msg) { + System.out.println("E/" + tag + ": " + msg); + return 0; + }; + + public static int i(String tag, String msg) { + System.out.println("I/" + tag + ": " + msg); + return 0; + }; +} diff --git a/lib.omw.android/src/main/java/com/android/nfc_extras/EeIOException.java b/lib.omw.android/src/main/java/com/android/nfc_extras/EeIOException.java new file mode 100644 index 0000000..66a3b25 --- /dev/null +++ b/lib.omw.android/src/main/java/com/android/nfc_extras/EeIOException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011, 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.nfc_extras; + +import java.io.IOException; + +public class EeIOException extends IOException { + public EeIOException() { + super(); + } + + public EeIOException(String message) { + super(message); + } +} diff --git a/lib.omw.android/src/main/java/com/android/nfc_extras/NfcAdapterExtras.java b/lib.omw.android/src/main/java/com/android/nfc_extras/NfcAdapterExtras.java new file mode 100644 index 0000000..3d346f9 --- /dev/null +++ b/lib.omw.android/src/main/java/com/android/nfc_extras/NfcAdapterExtras.java @@ -0,0 +1,17 @@ +package com.android.nfc_extras; + +import android.nfc.NfcAdapter; + +public class NfcAdapterExtras { + public static NfcAdapterExtras get(NfcAdapter adapter) { + return null; + } + + public String getDriverName() { + return null; + } + + public NfcExecutionEnvironment getEmbeddedExecutionEnvironment() { + return null; + } +} diff --git a/lib.omw.android/src/main/java/com/android/nfc_extras/NfcExecutionEnvironment.java b/lib.omw.android/src/main/java/com/android/nfc_extras/NfcExecutionEnvironment.java new file mode 100644 index 0000000..26f2198 --- /dev/null +++ b/lib.omw.android/src/main/java/com/android/nfc_extras/NfcExecutionEnvironment.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2011 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.nfc_extras; + +import java.io.IOException; + +public class NfcExecutionEnvironment { + public void open() throws EeIOException { + } + + public void close() throws IOException { + } + + public byte[] transceive(final byte[] in) throws IOException { + return null; + } + + public NfcExecutionEnvironment() { + } +} diff --git a/lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/Channel.java b/lib.omw.android/src/main/java/org/simalliance/openmobileapi/Channel.java similarity index 100% rename from lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/Channel.java rename to lib.omw.android/src/main/java/org/simalliance/openmobileapi/Channel.java diff --git a/lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/Reader.java b/lib.omw.android/src/main/java/org/simalliance/openmobileapi/Reader.java similarity index 100% rename from lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/Reader.java rename to lib.omw.android/src/main/java/org/simalliance/openmobileapi/Reader.java diff --git a/lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/SEService.java b/lib.omw.android/src/main/java/org/simalliance/openmobileapi/SEService.java similarity index 100% rename from lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/SEService.java rename to lib.omw.android/src/main/java/org/simalliance/openmobileapi/SEService.java diff --git a/lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/Session.java b/lib.omw.android/src/main/java/org/simalliance/openmobileapi/Session.java similarity index 100% rename from lib.omw.omapi/src/main/java/org/simalliance/openmobileapi/Session.java rename to lib.omw.android/src/main/java/org/simalliance/openmobileapi/Session.java diff --git a/lib.omw.ivid/pom.xml b/lib.omw.ivid/pom.xml index 3a9bdd2..d5d8f2c 100644 --- a/lib.omw.ivid/pom.xml +++ b/lib.omw.ivid/pom.xml @@ -4,7 +4,7 @@ net.vx4 lib.omw - 0.0.1-SNAPSHOT + 0.1.1 lib.omw.ivid @@ -14,17 +14,17 @@ - - net.vx4 - lib.omw.omapi - 0.0.1-SNAPSHOT - provided - junit junit 4.12 test + + net.vx4 + lib.omw.android + 0.1.0 + provided + \ No newline at end of file diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ArrayTool.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ArrayTool.java similarity index 99% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ArrayTool.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ArrayTool.java index 1532cb5..5a6879b 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ArrayTool.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ArrayTool.java @@ -59,7 +59,7 @@ * . */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; /** *

diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/C2Transport.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/C2Transport.java new file mode 100644 index 0000000..969c439 --- /dev/null +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/C2Transport.java @@ -0,0 +1,169 @@ +/* + * Copyright 2017-2020 adesso SE + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the + * European Commission - subsequent versions of the EUPL (the "Licence"); You may + * not use this work except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the + * specific language governing permissions and limitations under the Licence. + */ +package net.vx4.lib.ivid; + +/** + * C2Transport is an implementation of a transport provider stack element to handle extended length APDU mapping to + * ENVELOPE (C2) / GET RESPONSE (C0) APDUs. Hence its name as the response is handled by the underlying stack and this + * class "only" cuts long APDUs into shorter ENVELOPE ADPUS. + * + * It is currently expected that GET RESPONSE handling is done under the hood. There exist only rare fails, which + * are assumed to be implementation fails. Might be extended if there is a reasonable demand. (Don't support bugs.) + * + * @author kahlo, 2018 + * @version $Id$ + */ +public class C2Transport implements TransportProvider { + + private final short APDULen = 261; // T=0 limit + private final TransportProvider parent; + private byte envCLA = 0x00; // stick to ETSI-style without command chaining as we're on T=0 anyway + private byte envCLAlast = 0x00; + private byte envINS = (byte) 0xC2; + private short C2Len = 255; + + /** + * Create an ENVELOPE transport provider from a physical layer transport provider such as + * ChannelTransportProvider. + * + * @param parent + */ + public C2Transport(final TransportProvider parent) { + if (parent == null) { + throw new NullPointerException("parent transport provider required"); + } + this.parent = parent; + } + + + /** + * Transmit an extended length APDU over a physical link layer. + * (Usually and intended for T=0, but sometimes necessary for T=1 over SWP also.) + * + * NOTE: If not using secure messaging but plain-text extended length APDUs and the other end is a card that + * responds "early" with intended no data, the additional envelope might result in an erroneous 6700. + * Only real fix: don't do it. + * + * @param apdu - APDU to be transmitted + * @return + */ + @Override + public byte[] transmit(byte[] apdu) { + final byte channelId = ((ChannelTransportProvider) this.getParent()).getChannelId(); + + if (channelId < 4) { + apdu[0] = (byte) (apdu[0] & 0xBC | channelId); + } else if (channelId < 20) { + final boolean isSM = (apdu[0] & 0x0C) != 0; + apdu[0] = (byte) (apdu[0] & 0xB0 | 0x40 | channelId - 4); + if (isSM) { + apdu[0] |= 0x20; + } + } + + if (apdu.length > APDULen || apdu.length > 5 && apdu[4] == 0) { + // sanitize APDU encoding + final short lc = (short) (((apdu[5] & 0xFF) << 8) + (apdu[6] & 0xFF)); + String a2 = Hex.toString(apdu, 0, 7 + lc); // shorten APDU, strip off Le + + // if (apdu[5] == 0) { // shorten APDU if length < 0x0100 + if (lc < 0x0100) { // shorten APDU if length < 0x0100 + a2 = a2.substring(0, 8) + a2.substring(12); + } + apdu = Hex.fromString(a2); + + if (apdu.length > APDULen) { + int sent = 0; + byte[] last = null; + while (apdu.length - sent > 0) { + final int len = apdu.length - sent > C2Len ? C2Len : apdu.length - sent; + if (apdu.length - (sent + len) > 0) { + last = parent.transmit(Hex.fromString(Hex.toString(new byte[]{envCLA, envINS, 0, 0, (byte) len}) + + Hex.toString(apdu, sent, len & 0xFF))); + } else { + last = parent.transmit(Hex.fromString(Hex.toString(new byte[]{envCLAlast, envINS, 0, 0, (byte) len}) + + Hex.toString(apdu, sent, len & 0xFF))); + } + sent += len; + } + + // ETSI-style, long variant with no data but SW OK send another empty ENVELOPE for EOF. + if (envCLA == 0x00 && last != null && last.length == 0 && parent.lastSW() == 0x9000) { + last = parent.transmit(new byte[] { envCLAlast, envINS, 0, 0, 0 }); + } + + return last; + } + } + + return parent.transmit(apdu); + } + + /** + * change maximum length of ENVELOPE frame + * @param len + */ + public void setLength(final byte len) { + if (len <= 255) { + C2Len = (short) (len & 0xFF); + } + } + + /** + * Set class byte values for "first" / "ongoing" frames and "only" / "last" frame. + * + * ETSI as well as JavaCard reference implementations stick to "00" for both, the last frame is detected + * either by a length smaller than 0xFF or a single frame with length 00. Frames with length 00 are + * mandatory for some implementations. That's why some assumptions are made if an empty frame should be + * sent. + * + * ISO on the hand chooses command chaining mode, which is supported in hand crafted implementations, tolerated by + * some JCREs and denied by some others. setCLA((byte) 0x10, (byte) 0x00) enforces ISO mode. + * + * @param firstCLA + * @param lastCLA + */ + public void setCLA(final byte firstCLA, final byte lastCLA) { + envCLA = firstCLA; + envCLAlast = lastCLA; + } + + /** + * Set a custom instruction byte values for ENVELOPEs. Very unusual. + * + * @param INS + */ + public void setINS(final byte INS) { + envINS = INS; + } + + @Override + public void close() { + parent.close(); + } + + + @Override + public Object getParent() { + return parent; + } + + + @Override + public int lastSW() { + return parent.lastSW(); + } +} diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/CMac.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/CMac.java similarity index 99% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/CMac.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/CMac.java index d676f7d..5a532d8 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/CMac.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/CMac.java @@ -58,7 +58,7 @@ * zusammen mit diesem Programm erhalten haben. Wenn nicht, siehe * . */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; import javax.crypto.Cipher; import java.security.GeneralSecurityException; diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ChannelTransportProvider.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ChannelTransportProvider.java new file mode 100644 index 0000000..aecbf3b --- /dev/null +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ChannelTransportProvider.java @@ -0,0 +1,124 @@ +/* + * Copyright 2017-2020 adesso SE + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the + * European Commission - subsequent versions of the EUPL (the "Licence"); You may + * not use this work except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the + * specific language governing permissions and limitations under the Licence. + */ +package net.vx4.lib.ivid; + +/** + * The ChannelTransportProvider deals with automatically negotiated channels on the underlying terminal interface. If a + * channel has been opened successfully it is the first contact to the selected app, so the SELECT APDU response is + * fetch belowed and used to adopt to protocol and implementation specifics of the secure element. + * + * @author kahlo, 2018 + * @version $Id$ + */ +public class ChannelTransportProvider implements TransportProvider { + + private final byte channelId, protocol, appletState; + private final SEAL.Channel channel; + private int lastSW = -1; + + + /** + * Constructor based on SEAL.Channel. If the previously selected applet does not respond with proprietary FCI data + * to determine logical channel ID, protocol and state defaults are used. (T = 0 and basic channel) + * + * @param channel + */ + public ChannelTransportProvider(final SEAL.Channel channel) { + this.channel = channel; + byte[] info = this.channel.getSelectResponse(); + info = info == null ? null : TLV.get(info, (byte) 0x6F); + info = info == null ? null : TLV.get(info, (byte) 0x85); + + if (info != null && info.length >= 3) { + this.channelId = info[0]; + this.protocol = info[1]; + this.appletState = info[2]; + } else { + this.channelId = this.protocol = this.appletState = 0; + } + + // is often "null" anyway, might be a decision helper with some secure elements + // atr = channel.getSession().getATR(); + } + + + @Override + public void close() { + // System.out.println("ChannelTransportProvider.close(): WARNING, not closed"); + this.channel.close(); + } + + + @Override + public SEAL.Channel getParent() { + return this.channel; + } + + + @Override + public int lastSW() { + return this.lastSW; + } + + + /** + * @return + */ + public byte getChannelId() { + return this.channelId; + } + + + /** + * @return + */ + public byte getProtocol() { + return this.protocol; + } + + + /** + * @return + */ + public byte getAppletState() { + return this.appletState; + } + + + /** + * + */ + @Override + public byte[] transmit(final byte[] apdu) { + this.lastSW = -1; + + System.out.println("ChannelTrannsport: channel = " + this.channel + " open? " + + (this.channel != null ? this.channel.isOpen() : "")); + if (this.channel != null && this.channel.isOpen()) { + System.out.println("<[SE] apdu = [" + Hex.x(apdu) + "]"); + byte[] rpdu = this.channel.transmit(apdu); + System.out.println(">[SE] rpdu = [" + Hex.x(rpdu) + "]"); + + if (rpdu != null && rpdu.length >= 2) { + this.lastSW = ((rpdu[rpdu.length - 2] & 0xFF) << 8) + (rpdu[rpdu.length - 1] & 0xFF); + rpdu = ArrayTool.sub(rpdu, 0, rpdu.length - 2); + } + return rpdu; + } else { + return null; + } + } +} diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/Hex.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/Hex.java similarity index 99% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/Hex.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/Hex.java index 05b13ad..a3dd431 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/Hex.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/Hex.java @@ -130,7 +130,7 @@ * @author Systemics Ltd * @since Cryptix 2.2.0a, 2.2.2 */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; /** * Static functions for converting to and from hexadecimal strings. diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ISOSMTransport.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ISOSMTransport.java similarity index 99% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ISOSMTransport.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ISOSMTransport.java index 4799478..c019750 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ISOSMTransport.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/ISOSMTransport.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 adesso AG + * Copyright 2017-2020 adesso SE * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the * European Commission - subsequent versions of the EUPL (the "Licence"); You may @@ -13,7 +13,7 @@ * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the * specific language governing permissions and limitations under the Licence. */ -/** +/* * COPYRIGHT (C) 2010, 2011, 2012, 2013, 2014 AGETO Innovation GmbH *

* Authors Christian Kahlo, Ralf Wondratschek @@ -58,11 +58,12 @@ * zusammen mit diesem Programm erhalten haben. Wenn nicht, siehe * . */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import java.nio.Buffer; import java.nio.ByteBuffer; @@ -213,7 +214,7 @@ public void setupKeys(final byte[] newkEnc, final byte[] newkMac) { */ private byte[] getIV() { try { - ivBuf.rewind(); + ((Buffer) ivBuf).rewind(); // JDK 9+ fix ivBuf.putLong(0); ivBuf.putLong(++ssc); return ivBuf.array(); diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/OMAPITP.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/IVIDTP.java similarity index 52% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/OMAPITP.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/IVIDTP.java index 6e87c57..f0d491c 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/OMAPITP.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/IVIDTP.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 adesso AG + * Copyright 2017-2020 adesso SE * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the * European Commission - subsequent versions of the EUPL (the "Licence"); You may @@ -13,13 +13,8 @@ * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the * specific language governing permissions and limitations under the Licence. */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; -import org.simalliance.openmobileapi.Channel; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -29,71 +24,88 @@ import java.util.ArrayList; import java.util.List; +import javax.crypto.Cipher; + /** * @author ckahlo, 2018 - 2019 - *

- * This file is a stub, proving a working prototype. Work in progress. As storage on the UICC is limited the OMAPITP - * wraps away "default" data as EF DIRECTORY, EF CARD ACCESS and EF CARD SECURITY. This data is public and until - * algorithms are added or changed quite constant. EF CARD SECURITY changes due its included public key for chip - * authentication, so this will be the first part to be stored separately and in an updatable manner according to the - * personalization of the applet(s) in use. - * - * The OMAPITP also implements the PACE-to-CCID vendor command mapping to trigger PACE' / PACElight on "EstablishPACE" - * command using the interal unlock key mechanism. Afterwards data is tunneled transparently through the standard - * ISO secure messaging transport provider. - * + *

+ * This file is a stub, proving a working prototype. Work in progress. As storage on the UICC is limited the + * IVIDTP wraps away "default" data as EF DIRECTORY, EF CARD ACCESS and EF CARD SECURITY. This data is public + * and until algorithms are added or changed quite constant. EF CARD SECURITY changes due its included public + * key for chip authentication, so this will be the first part to be stored separately and in an updatable + * manner according to the personalization of the applet(s) in use. The IVIDTP also implements the PACE-to-CCID + * vendor command mapping to trigger PACE' / PACElight on "EstablishPACE" command using the interal unlock key + * mechanism. Afterwards data is tunneled transparently through the standard ISO secure messaging transport + * provider. */ -public final class OMAPITP implements TransportProvider { +public final class IVIDTP implements TransportProvider { public static final String AID_NPA = "E80704007F00070302"; public static final String AID_VX4ID = "D2760000930101"; private static final byte[] EF_DIR = Hex.x( "61324F0FE828BD080FA000000167455349474E500F434941207A752044462E655369676E5100730C4F0AA000000167455349474E61094F07A0000002471001610B4F09E80704007F00070302610C4F0AA000000167455349474E"); - private static final byte[] EF_ATR = Hex.x(""); + private static final byte[] EF_ATR = Hex.x(""); // intentionally left blank private static final byte[] EF_CA = Hex.x( "3181C13012060A04007F0007020204020202010202010D300D060804007F00070202020201023012060A04007F00070202030202020102020129301C060904007F000702020302300C060704007F0007010202010D020129303E060804007F000702020831323012060A04007F0007020203020202010202012D301C060904007F000702020302300C060704007F0007010202010D02012D302A060804007F0007020206161E687474703A2F2F6273692E62756E642E64652F6369662F6E70612E786D6C"); private static final byte[] EF_CS = Hex.x( "308206B006092A864886F70D010702A08206A13082069D020103310F300D0609608648016503040204050030820188060804007F0007030201A082017A04820176318201723012060A04007F0007020204020202010202010D300D060804007F00070202020201023017060A04007F0007020205020330090201010201010101003019060904007F000702020502300C060704007F0007010202010D3017060A04007F0007020205020330090201010201020101FF3012060A04007F00070202030202020102020129301C060904007F000702020302300C060704007F0007010202010D0201293062060904007F0007020201023052300C060704007F0007010202010D0342000419D4B7447788B0E1993DB35500999627E739A4E5E35F02D8FB07D6122E76567F17758D7A3AA6943EF23E5E2909B3E8B31BFAA4544C2CBF1FB487F31FF239C8F8020129303E060804007F000702020831323012060A04007F0007020203020202010202012D301C060904007F000702020302300C060704007F0007010202010D02012D302A060804007F0007020206161E687474703A2F2F6273692E62756E642E64652F6369662F6E70612E786D6CA08203EE308203EA30820371A00302010202012D300A06082A8648CE3D0403033055310B3009060355040613024445310D300B060355040A0C0462756E64310C300A060355040B0C03627369310D300B0603550405130430303033311A301806035504030C115445535420637363612D6765726D616E79301E170D3134303732333036333034305A170D3235303232333233353935395A305C310B3009060355040613024445310C300A060355040A0C03425349310D300B06035504051304303035303130302E06035504030C275445535420446F63756D656E74205369676E6572204964656E7469747920446F63756D656E7473308201133081D406072A8648CE3D02013081C8020101302806072A8648CE3D0101021D00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001303C041CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE041CB4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4043904B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34021D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D020101033A00043A79C3CBFDB8A6E569C9226CD54E81DE14381BC92A61AD554EBF349BFAFD72F18DC85D78E49742F37A75411E28E894308D6880D1380FBEB4A382016D30820169301F0603551D23041830168014A38DB7C0DBECF5A91FCA6B3D5EB2F328B5A5DC17301D0603551D0E04160414CF0A2AC150F28ADE4329F662E3D21CE5C78BCDE9300E0603551D0F0101FF040403020780302B0603551D1004243022800F32303134303732333036333034305A810F32303135303232333233353935395A30160603551D20040F300D300B060904007F000703010101302D0603551D1104263024821262756E646573647275636B657265692E6465A40E300C310A300806035504070C014430510603551D12044A30488118637363612D6765726D616E79406273692E62756E642E6465861C68747470733A2F2F7777772E6273692E62756E642E64652F63736361A40E300C310A300806035504070C01443019060767810801010602040E300C02010031071301411302494430350603551D1F042E302C302AA028A0268624687474703A2F2F7777772E6273692E62756E642E64652F746573745F637363615F63726C300A06082A8648CE3D040303036700306402300D90B1C6E52B5E20D8ECE1520981E11EF1AF02906A930420F87E90315588B70C0C9642160E877E42B1CE311849E388B802303450209749C1368D965CE879460F729E68BAB9D5D3269724721D0C564FB2752EC4C0F8F5542990CFDB7C848AA7D0A2BB3182010730820103020101305A3055310B3009060355040613024445310D300B060355040A0C0462756E64310C300A060355040B0C03627369310D300B0603550405130430303033311A301806035504030C115445535420637363612D6765726D616E7902012D300D06096086480165030402040500A046301706092A864886F70D010903310A060804007F0007030201302B06092A864886F70D010904311E041CC57AFB616E6837B63B22666F48547E3AD71795E33326C0CE5FF27C3A300A06082A8648CE3D040301043F303D021C58AE1E82475BE9C9167810593FCF7CA791DE45910380D5CF4FEB84D7021D00FFD316D91D85664479596BAFBBB2532540047334668E0C47EE99B826"); + // + private final TransportProvider plainTP; // - int lastSW = -1; + + private int lastSW = -1; private TransportProvider tp; - private CallbackHandler cbh; + private AuthenticationCallback authCB; private byte[] efData = null; + // take care of freaked out implementations + private static final byte HCI_T1 = (byte) 0xC1; + + /** + * Create a wrapper transport provider on top of some physical transport layer. * + * @param chTP */ - public OMAPITP(final Channel channel) { - this(new ChannelTransportProvider(channel)); + // ChannelTransportProvider + public IVIDTP(final ChannelTransportProvider chTP) { + System.out.println("IVIDTP: seTP = [" + chTP + "]"); + System.out.println("IVIDTP: seTP.getParent = [" + chTP.getParent() + "]"); + + final byte protocol = chTP.getProtocol(); + if ((protocol & 0x0F) == 0x00) { // use T=0 ENVELOPE framing for T=0 on any physical link + this.tp = new C2Transport(chTP); + } else if (protocol == HCI_T1) { // we got CLF HCI over SWP with T=1, doesn't behave + this.tp = new C2Transport(chTP); + ((C2Transport) this.tp).setCLA((byte) 0x10, (byte) 0x00); // no ext. length, enforce ISO ENVELOPE of applet + } else { // i.e. 0xD1 -> SCI2C or GP SPI/I2C, use plain link layer + this.tp = chTP; + } + + System.out.println("IVIDTP: tp = " + this.tp); + this.plainTP = this.tp; } /** - * @param seTP + * Set the callback handler for user authentication and secret retrieval. + * + * @param authCB */ - public OMAPITP(final TransportProvider seTP) { - System.out.println("OMAPITP: seTP = [" + seTP + "]"); - System.out.println("OMAPITP: seTP.getParent = [" + seTP.getParent() + "]"); - - // if ("T=0".equals(card.getProtocol())) { - tp = new C2Transport(seTP); - // tp = seTP; - // } - - System.out.println(tp); - plainTP = tp; - } - - public final void setCallbackHandler(final CallbackHandler cbh) { - this.cbh = cbh; + public final void setAuthenticationCallback(final AuthenticationCallback authCB) { + this.authCB = authCB; } + /** + * @param apdu + * @return + */ public final byte[] process(final byte[] apdu) { byte[] rpdu = new byte[0]; short sw = (short) 0x9000; @@ -106,16 +118,16 @@ public final byte[] process(final byte[] apdu) { if (cmd.startsWith("FF9A0101")) { // get vendor rpdu = "VX4.NET".getBytes(StandardCharsets.ISO_8859_1); } else if (cmd.startsWith("FF9A0103")) { // get product - rpdu = "OMAPI-SE".getBytes(StandardCharsets.ISO_8859_1); + rpdu = "IVID-SE".getBytes(StandardCharsets.ISO_8859_1); } else if (cmd.startsWith("FF9A010600")) { // get firmware // NOP } else if (cmd.startsWith("FF9A010700")) { // get driver // NOP - } else if (cmd.startsWith("FF9A0401")) { // GetReaderPACE Capabilities - rpdu = new byte[]{ 0x03 }; - } else if (cmd.startsWith("FF9A0402")) { // EstablishPACEChannel - if (tp != plainTP) { // reset transport provider if channel already exists - tp = plainTP; + } else if (cmd.startsWith("FF9A0401")) { // GetReaderPACE Capabilities + rpdu = new byte[] { 0x03 }; + } else if (cmd.startsWith("FF9A0402")) { // EstablishPACEChannel + if (this.tp != this.plainTP) { // reset transport provider if channel already exists + this.tp = this.plainTP; } final byte[] miniPACERes = miniPACE(); @@ -124,78 +136,78 @@ public final byte[] process(final byte[] apdu) { rpdu = new byte[0]; sw = 0x6985; } else { - //rpdu = miniPACERes; -// if(this.lastSW() == 0x9000) { // doesn't work here, because sw is not set, comes from HAL-SE + // rpdu = miniPACERes; + // if(this.lastSW() == 0x9000) { // doesn't work here, because sw is not set, comes from HAL-SE - byte[] IDPICC = TLV.get(miniPACERes, (byte) 0x86); - byte[] CAR = TLV.get(miniPACERes, (byte) 0x87); + final byte[] IDPICC = TLV.get(miniPACERes, (byte) 0x86); + final byte[] CAR = TLV.get(miniPACERes, (byte) 0x87); - StringBuffer sb = new StringBuffer(); + final StringBuffer sb = new StringBuffer(); sb.append("9000"); // SW - sb.append(Hex.byteToString(EF_CA.length) + "00"); // len EF_CardAccess - sb.append(Hex.toString(EF_CA)); + sb.append(Hex.x((byte) EF_CA.length) + "00"); // len EF_CardAccess + sb.append(Hex.x(EF_CA)); sb.append("0E").append(Hex.x(CAR)); sb.append("00"); sb.append("2000"); sb.append(Hex.x(IDPICC)); - byte[] res = Hex.x(sb.toString()); - int dataLen = res.length; - rpdu = ArrayTool.concat(new byte[]{0, 0, 0, 0, (byte) dataLen, (byte) (dataLen >> 8)}, res); -// } else { -// rpdu = new byte[]{0x01, 0x00, 0x20, (byte) 0xF0}; // status, little-endian, abort -// } + final byte[] res = Hex.x(sb.toString()); + final int dataLen = res.length; + rpdu = ArrayTool.concat(new byte[] { 0, 0, 0, 0, (byte) dataLen, (byte) (dataLen >> 8) }, res); + // } else { + // rpdu = new byte[]{0x01, 0x00, 0x20, (byte) 0xF0}; // status, little-endian, abort + // } sw = (short) 0x9000; } - } else if (cmd.startsWith("FF9A0403")) { // DestroyPACEChannel + } else if (cmd.startsWith("FF9A0403")) { // DestroyPACEChannel // reset transport provider - tp = plainTP; + this.tp = this.plainTP; rpdu = new byte[0]; - } else if (cmd.startsWith("FF9A0410")) { // VerifyPIN / ModifyPIN + } else if (cmd.startsWith("FF9A0410")) { // VerifyPIN / ModifyPIN // NOP rpdu = new byte[0]; } - return ArrayTool.concat(rpdu, new byte[]{(byte) (sw >> 8 & 0xFF), (byte) (sw & 0xFF)}); + return ArrayTool.concat(rpdu, new byte[] { (byte) (sw >> 8 & 0xFF), (byte) (sw & 0xFF) }); } - if ("00A4040C09E80704007F00070302".equals(cmd)) { // select DF_EID + if ("00A4040C09E80704007F00070302".equals(cmd)) { // select DF_EID // return Hex.x("9000"); - } else if ("00A4000000".equals(cmd)) { // select MF + } else if ("00A4000000".equals(cmd)) { // select MF // return Hex.x("9000"); - } else if ("00A40000023F00".equals(cmd)) { // select MF + } else if ("00A40000023F00".equals(cmd)) { // select MF // return Hex.x("9000"); - } else if ("00A4000C023F00".equals(cmd)) { // select MF + } else if ("00A4000C023F00".equals(cmd)) { // select MF // return Hex.x("9000"); - } else if ("00A4020C022F00".equals(cmd)) { // select EF.DIR - efData = EF_DIR; + } else if ("00A4020C022F00".equals(cmd)) { // select EF.DIR + this.efData = EF_DIR; // return Hex.x("9000"); - } else if ("00A4020C022F01".equals(cmd)) { // select EF.ATR - efData = EF_ATR; + } else if ("00A4020C022F01".equals(cmd)) { // select EF.ATR + this.efData = EF_ATR; // return Hex.x("9000"); - } else if ("00A4020C02011C".equals(cmd)) { // select EF.CA - efData = EF_CA; + } else if ("00A4020C02011C".equals(cmd)) { // select EF.CA + this.efData = EF_CA; // return Hex.x("9000"); - } else if ("00A4020C02011D".equals(cmd)) { // select EF.CS + } else if ("00A4020C02011D".equals(cmd)) { // select EF.CS - efData = EF_CS; + this.efData = EF_CS; // return Hex.x("9000"); - } else if (cmd.startsWith("00B0")) { // read binary + } else if (cmd.startsWith("00B0")) { // read binary int ofs = ((apdu[2] & 0xFF) << 8) + (apdu[3] & 0xFF); int len = apdu[4] & 0xFF; if (cmd.startsWith("00B09C00")) { - efData = EF_CA; + this.efData = EF_CA; ofs = 0; } len = len == 0 ? 255 : len; - if (efData.length - ofs < len) { - len = efData.length - ofs; + if (this.efData.length - ofs < len) { + len = this.efData.length - ofs; sw = 0x6282; } - rpdu = ArrayTool.sub(efData, ofs, len); + rpdu = ArrayTool.sub(this.efData, ofs, len); } else if (cmd.startsWith("0022C1A4")) { // MSE PACE, 0022C1A4 12 80 0A 04007F00070202040202830103 84010D // NOP for now } else { @@ -209,76 +221,77 @@ public final byte[] process(final byte[] apdu) { apdu[3] ^= (byte) 0xAA; } - rpdu = plainTP.transmit(apdu); // CA-SM + rpdu = this.plainTP.transmit(apdu); // CA-SM } else { - rpdu = tp.transmit(apdu); // transmit with PACE channel + rpdu = this.tp.transmit(apdu); // transmit with PACE channel } - sw = (short) tp.lastSW(); + sw = (short) this.tp.lastSW(); } } catch (final Exception e) { e.printStackTrace(); - return new byte[]{0x6F, (byte) 0xFF}; + return new byte[] { 0x6F, (byte) 0xFF }; } - return ArrayTool.concat(rpdu, new byte[]{(byte) (sw >> 8 & 0xFF), (byte) (sw & 0xFF)}); + return ArrayTool.concat(rpdu, new byte[] { (byte) (sw >> 8 & 0xFF), (byte) (sw & 0xFF) }); } + /** + * PoC implementation of "PACE-light" in case we lack PACE support + * + * @return + */ private byte[] miniPACE() { - if (cbh == null) { - System.err.println("OMAPI-TP: miniPACE: no callback handler for secret registered."); - } - - System.out.println("OMAPI-TP: using callback handler: " + cbh.getClass() + " / " + cbh.toString()); - final byte[] ulk = cbh.getSecret(); - if (ulk == null) { - System.out.println("OMAPI-TP: secret is null, aborting and returning with null."); + if (this.authCB == null) { + System.out.println("IVID-TP: authentication callback is null, aborting and returning with null."); return null; } - - System.out.println("OMAPI-TP: got secret: " + Hex.x(ulk)); + System.out.println("IVID-TP: callback = " + this.authCB.getClass() + " / " + this.authCB.toString()); try { - final SecretKeySpec pinKey = new SecretKeySpec(ulk, "AES"); - - final Cipher c = Cipher.getInstance("AES/CBC/NoPadding"); - c.init(Cipher.ENCRYPT_MODE, pinKey, new IvParameterSpec(new byte[16])); - - final byte[] hsRandom = new byte[32]; - new SecureRandom().nextBytes(hsRandom); - byte[] hsRandEnc = c.doFinal(hsRandom, 0, hsRandom.length); - hsRandEnc = TLV.build(0x7C, TLV.build(0x81, hsRandEnc)); - - byte[] plRes = tp.transmit(Hex.fromString( - "80CE0000" + Hex.byteToString((byte) hsRandEnc.length) + Hex.toString(hsRandEnc) + "00")); + // byte[] buf = TLV.build(0x7C, TLV.build(0x81, this.authCB.init("TEST"))); + // buf = this.tp.transmit(Hex.x("80CE0000" + Hex.x((byte) buf.length) + Hex.x(buf) + "00")); + // if (this.tp.lastSW() != 0x9000) { + // System.out.println("IVID-TP: error setting up secure channel: " + Hex.x((short) this.tp.lastSW())); + // return null; + // } + // + // final byte[] IDPICC = TLV.get(TLV.get(buf, (byte) 0x7C), (byte) 0x82); // IDPICC = encrypted card random + // buf = this.authCB.finish(IDPICC); - // TODO: lastSW != 0x9000 -> error + final Cipher unlockCipher = this.authCB.getCipher("TEST"); - plRes = TLV.get(TLV.get(plRes, (byte) 0x7C), (byte) 0x82); + final byte[] hsRandPlain = new byte[32]; + new SecureRandom().nextBytes(hsRandPlain); + byte[] buf = TLV.build(0x7C, TLV.build(0x81, hsRandPlain)); - final byte[] IDPICC = plRes.clone(); // encrypted card random is IDPICC + buf = this.tp.transmit(Hex.x("80CE0000" + Hex.x((byte) buf.length) + Hex.x(buf) + "00")); + if (this.tp.lastSW() != 0x9000) { + System.out.println("IVID-TP: error setting up secure channel: " + Hex.x((short) this.tp.lastSW())); + return null; + } - System.out.println("PL-RES 1: " + Hex.toString(IDPICC)); + final byte[] IDPICC = TLV.get(TLV.get(buf, (byte) 0x7C), (byte) 0x82); // IDPICC = encrypted card random // - final MessageDigest mdSHA256 = MessageDigest.getInstance("SHA-256"); - c.init(Cipher.DECRYPT_MODE, pinKey, new IvParameterSpec(new byte[16])); - mdSHA256.update(c.doFinal(plRes)); - plRes = mdSHA256.digest(hsRandom); - - System.out.println("PL-RES 2: " + Hex.toString(IDPICC)); - - System.out.println("PACE-light secret2: " + Hex.toString(plRes)); - - System.out.println("SE-TP parent: " + tp + " / " + tp.getParent()); + { + final MessageDigest mdSHA256 = MessageDigest.getInstance("SHA-256"); + mdSHA256.update(unlockCipher.doFinal(IDPICC)); + buf = mdSHA256.digest(unlockCipher.doFinal(hsRandPlain)); + } - final MessageDigest mdSHA1 = MessageDigest.getInstance("SHA-1"); - final ISOSMTransport sesmTP = new ISOSMTransport(tp); - sesmTP.setupKeys(KDF(mdSHA1, plRes, 1, 16), KDF(mdSHA1, plRes, 2, 16)); - tp = sesmTP; + // + { + System.out.println("SE-TP parent: " + this.tp + " / " + this.tp.getParent()); + final ISOSMTransport sesmTP = new ISOSMTransport(this.tp); + final MessageDigest mdSHA1 = MessageDigest.getInstance("SHA-1"); + sesmTP.setupKeys(KDF(mdSHA1, buf, 1, 16), KDF(mdSHA1, buf, 2, 16)); + this.tp = sesmTP; + } - byte[] ceres = tp.transmit(Hex.fromString("80CE0000")); + byte[] ceres = this.tp.transmit(Hex.x("80CE0000")); + // tp.lastSW() ceres = TLV.get(ceres, (byte) 0x7C); final List CAReferences = new ArrayList(); @@ -291,7 +304,8 @@ private byte[] miniPACE() { // this.CAReferences.add(CARef); // } - return TLV.concat(TLV.build(0x80, Hex.x("9000")), TLV.build(0x86, IDPICC), TLV.build(0x87, CAReferences.get(0)), Hex.x("9000")); + return TLV.concat(TLV.build(0x80, Hex.x("9000")), TLV.build(0x86, IDPICC), + TLV.build(0x87, CAReferences.get(0)), Hex.x("9000")); } catch (final GeneralSecurityException e) { e.printStackTrace(); } @@ -311,33 +325,72 @@ private byte[] KDF(final MessageDigest md, final byte[] secret, final int counte return ArrayTool.sub(md.digest(temp.array()), 0, limit); } + + /** + * No parents ... don't allow climbing up for the lower layers, this is also an intended "end-marker" + * + * @return + */ @Override - public Object getParent() { + public TransportProvider getParent() { return null; } + + /** + * @param apdu + * - APDU to be transmitted + * @return + */ @Override public byte[] transmit(final byte[] apdu) { byte[] rpdu = process(apdu); if (rpdu != null && rpdu.length >= 2) { - lastSW = ((rpdu[rpdu.length - 2] & 0xFF) << 8) + (rpdu[rpdu.length - 1] & 0xFF); + this.lastSW = ((rpdu[rpdu.length - 2] & 0xFF) << 8) + (rpdu[rpdu.length - 1] & 0xFF); rpdu = ArrayTool.sub(rpdu, 0, rpdu.length - 2); } return rpdu; } + @Override public int lastSW() { - return lastSW; + return this.lastSW; } + @Override public void close() { + this.tp.close(); } - public interface CallbackHandler { - byte[] getSecret(); + /** + * The callback handler interface to retrieve the authentication secret. + */ + public interface AuthenticationCallback { + + /** + * Request user authentication and request the secret, giving it a name i.e. SE slot and applet ID. + * + * @return a standard java interface cipher object for authentication + */ + Cipher getCipher(String realm); + + // /** + // * + // * @param realm + // * @return encrypted random + // */ + // byte[] init(String realm); + // + // + // /** + // * + // * @param cardNonce + // * @return secure messaging channel secret + // */ + // byte[] finish(byte[] cardNonce); } } diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/SEAL.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/SEAL.java new file mode 100644 index 0000000..82cc1ed --- /dev/null +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/SEAL.java @@ -0,0 +1,651 @@ +package net.vx4.lib.ivid; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; + +import com.android.nfc_extras.NfcAdapterExtras; +import com.android.nfc_extras.NfcExecutionEnvironment; + +import android.content.Context; +import android.nfc.NfcAdapter; +import android.telephony.IccOpenLogicalChannelResponse; +import android.telephony.TelephonyManager; +import android.util.Log; + + +/** + * Secure Element Abstraction Layer + * + * @author kahlo, 2020 + * @version $Id$ + */ +public class SEAL { + private static final String LOG_TAG = "SEAL"; + private final Context context; + private final SEAL delegate; + + + /** + * TODO comment + * + * @throws Exception + */ + private SEAL() throws Exception { + this.context = null; + this.delegate = null; + } + + + /** + * TODO comment + * + * @param context + * @throws Exception + */ + public SEAL(final Context context) { + this.context = context; + this.delegate = new Callable() { + @Override + public SEAL call() { + try { // new OMAPI + return getOMAPI2SEAL(context); + } catch (final SecurityException e) { + log("Binding not allowed, uses-permission SMARTCARD? " + e.getMessage()); + e.printStackTrace(); + } catch (final Exception e) { + log("Exception: " + e.getMessage()); + e.printStackTrace(); + } catch (final Error e) { + log("Error: " + e.getMessage()); + e.printStackTrace(); + } + + try { // old OMAPI + return getOMAPI1SEAL(context); + } catch (final SecurityException e) { + log("Binding not allowed, uses-permission SMARTCARD? " + e.getMessage()); + e.printStackTrace(); + } catch (final Exception e) { + log("Exception: " + e.getMessage()); + e.printStackTrace(); + } catch (final Error e) { + log("Error: " + e.getMessage()); + e.printStackTrace(); + } + + return null; + } + }.call(); + } + + + /** + * TODO comment + * + * @author kahlo, 2020 + * @version $Id$ + */ + public interface Channel { + public void close(); + + + public Object getSession(); + + + public boolean isOpen(); + + + public byte[] transmit(final byte[] command); + + + public byte[] getSelectResponse(); + + + public byte[] getATR(); + + + public String getName(); + + + public boolean isBasicChannel(); + } + + + /** + * TODO comment + * + * @param context + * @return + * @throws Exception + */ + private SEAL getOMAPI1SEAL(final Context context) throws Exception { + return new SEAL() { + private final org.simalliance.openmobileapi.SEService seService = new org.simalliance.openmobileapi.SEService( + context, new org.simalliance.openmobileapi.SEService.CallBack() { + @Override + public void serviceConnected(final org.simalliance.openmobileapi.SEService service2) { + log("connected to OMAPI1: " + seService); + + if (service2 != seService) { + log("WARNING: service in listener differs: " + service2); + } + } + }); + + + @Override + public boolean isConnected() { + return this.seService != null && this.seService.isConnected(); + } + + + @Override + public void shutdown() { + if (this.seService != null && this.seService.isConnected()) { + this.seService.shutdown(); + } + } + + + @Override + public ChannelTransportProvider open(final String slotName, final String AID) { + for (final org.simalliance.openmobileapi.Reader reader : this.seService.getReaders()) { + try { + log("Reader: " + reader.getName() + " - SE present? " + reader.isSecureElementPresent()); + if (!reader.isSecureElementPresent()) { + continue; + } + + final org.simalliance.openmobileapi.Session session = reader.openSession(); + log("ATR: " + toHex(session.getATR())); + log("Create logical channel to " + AID + " within the session..."); + + final org.simalliance.openmobileapi.Channel channel = session.openLogicalChannel(Hex.x(AID)); + log("Channel: " + channel); + if (channel != null) { + log("SELECT: " + Hex.x(channel.getSelectResponse())); + return new ChannelTransportProvider(new Channel() { + @Override + public void close() { + channel.close(); + } + + + @Override + public Object getSession() { + return channel.getSession(); + } + + + @Override + public boolean isOpen() { + return !channel.isClosed(); + } + + + @Override + public byte[] transmit(final byte[] command) { + return channel.transmit(command); + } + + + @Override + public byte[] getSelectResponse() { + return channel.getSelectResponse(); + } + + + @Override + public byte[] getATR() { + return channel.getSession().getATR(); + } + + + @Override + public String getName() { + return channel.getSession().getReader().getName(); + } + + + @Override + public boolean isBasicChannel() { + return channel.isBasicChannel(); + } + }); + } else { + log("No free logical channel avaiable."); + } + } catch (final SecurityException e) { // not allowed + log("Access not allowed on " + reader.getName() + " for " + AID); + } catch (final Exception e) { + log("Error using reader " + reader.getName() + " occured: " + e); + e.printStackTrace(); + } + reader.closeSessions(); + } + + this.seService.shutdown(); + return null; + } + }; + } + + + /** + * TODO comment + * + * @param context + * @return + * @throws Exception + */ + private SEAL getOMAPI2SEAL(final Context context) throws Exception { + return new SEAL() { + final android.se.omapi.SEService seService = new android.se.omapi.SEService(context, + Executors.newSingleThreadExecutor(), new android.se.omapi.SEService.OnConnectedListener() { + @Override + public void onConnected() { + log("connected to OMAPI2: " + seService); + } + }); + + + @Override + public boolean isConnected() { + return this.seService != null && this.seService.isConnected(); + } + + + @Override + public void shutdown() { + if (this.seService != null && this.seService.isConnected()) { + this.seService.shutdown(); + } + } + + + @Override + public ChannelTransportProvider open(final String slotName, final String AID) { + for (final android.se.omapi.Reader reader : this.seService.getReaders()) { + try { + log("Reader: " + reader.getName() + " - SE present? " + reader.isSecureElementPresent()); + if (!reader.isSecureElementPresent()) { + continue; + } + + final android.se.omapi.Session session = reader.openSession(); + log("ATR: " + toHex(session.getATR())); + log("Create logical channel to " + AID + " within the session..."); + + final android.se.omapi.Channel channel = session.openLogicalChannel(Hex.x(AID)); + log("Channel: " + channel); + if (channel != null) { + log("SELECT: " + Hex.x(channel.getSelectResponse())); + return new ChannelTransportProvider(new Channel() { + @Override + public void close() { + channel.close(); + } + + + @Override + public Object getSession() { + return channel.getSession(); + } + + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + + @Override + public byte[] transmit(final byte[] command) { + return channel.transmit(command); + } + + + @Override + public byte[] getSelectResponse() { + return channel.getSelectResponse(); + } + + + @Override + public byte[] getATR() { + return channel.getSession().getATR(); + } + + + @Override + public String getName() { + return channel.getSession().getReader().getName(); + } + + + @Override + public boolean isBasicChannel() { + return channel.isBasicChannel(); + } + }); + } else { + log("No free logical channel avaiable."); + } + } catch (final SecurityException e) { // not allowed + log("Access not allowed on " + reader.getName() + " for " + AID); + } catch (final Exception e) { + log("Error using reader " + reader.getName() + " occured: " + e); + e.printStackTrace(); + } + reader.closeSessions(); + } + + this.seService.shutdown(); + return null; + } + }; + } + + + /** + * TODO comment + * + * @param context + * @return + * @throws Exception + */ + private SEAL getTMSEAL(final Context context) throws Exception { + return new SEAL() { + final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + + + @Override + public boolean isConnected() { + try { + return this.tm != null && this.tm.hasCarrierPrivileges() && this.tm.hasIccCard(); + } catch (final NoSuchMethodError nsme) { + nsme.printStackTrace(); + } + return false; + } + + + @Override + public void shutdown() { + // + } + + + @Override + public ChannelTransportProvider open(final String slotName, final String AID) { + if (!isConnected()) { + return null; + } + + final IccOpenLogicalChannelResponse iccOpenLCres = this.tm.iccOpenLogicalChannel(AID); + if (iccOpenLCres == null || iccOpenLCres.getStatus() != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) { + return null; + } + + return new ChannelTransportProvider(new Channel() { + boolean open = true; + + + @Override + public void close() { + this.open = false; + tm.iccCloseLogicalChannel(iccOpenLCres.getChannel()); + } + + + @Override + public Object getSession() { + return iccOpenLCres; + } + + + @Override + public boolean isOpen() { + return this.open; + } + + + @Override + public byte[] transmit(final byte[] command) { + byte[] res = isOpen() ? Hex.x(tm.iccTransmitApduLogicalChannel(iccOpenLCres.getChannel(), + command[0] & 0xFF, command[1] & 0xFF, command[2] & 0xFF, command[3] & 0xFF, + command[4] & 0xFF, Hex.x(command, 5, command[4] & 0xFF))) : null; + + while (res != null && res.length >= 2 && res[res.length - 2] == 0x61) { + res = ArrayTool.concat(ArrayTool.sub(res, 0, res.length - 2), + Hex.x(tm.iccTransmitApduLogicalChannel(iccOpenLCres.getChannel(), + 0x00, 0xC0, 0x00, 0x00, res[res.length - 1] & 0xFF, ""))); + } + + return res; + } + + + @Override + public byte[] getSelectResponse() { + return iccOpenLCres.getSelectResponse(); + } + + + @Override + public byte[] getATR() { + return null; + } + + + @Override + public String getName() { + return "TM-UICC"; + } + + + @Override + public boolean isBasicChannel() { + return false; + } + }); + } + }; + } + + + // + protected void log(final String msg) { + Log.i(LOG_TAG, msg); + } + + + protected static String toHex(final byte[] in) { + return in != null ? Hex.x(in) : "null"; + } + + + /** + * TODO comment + * + * @return + */ + public boolean isConnected() { + return this.delegate != null && this.delegate.isConnected(); + } + + + /** + * TODO comment + */ + public void shutdown() { + if (this.delegate != null) { + this.delegate.shutdown(); + } + } + + + /** + * TODO comment + * + * @param AID + * @return + */ + public ChannelTransportProvider open(final String AID) { + return open(null, AID); + } + + + /** + * TODO comment + * + * @param slotName + * @param AID + * @return + */ + public ChannelTransportProvider open(final String slotName, final String AID) { + ChannelTransportProvider channel = null; + if (this.delegate != null) { + if (!this.delegate.isConnected()) { + try { + for (int i = 0; !this.delegate.isConnected() && i < 10; i++) { + Thread.sleep(100); + } + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + + if (this.delegate.isConnected()) { + channel = this.delegate.open(slotName, AID); + } + } + return channel; + } + + + /** + * TODO comment + * + * @param AID + * @return + */ + public ChannelTransportProvider openTM(final String AID) { + SEAL tmSEAL = null; + try { // Google TelephonyManager API + tmSEAL = getTMSEAL(this.context); + } catch (final SecurityException e) { + log("Binding not allowed, uses-permission READ PHONE STATE? " + e.getMessage()); + e.printStackTrace(); + } catch (final Exception e) { + log("Exception: " + e.getMessage()); + e.printStackTrace(); + } catch (final Error e) { + log("Error: " + e.getMessage()); + e.printStackTrace(); + } + + if (tmSEAL != null && tmSEAL.isConnected()) { + return tmSEAL.open(AID); + } + return null; + } + + + /** + * TODO comment + * + * @return + */ + public ChannelTransportProvider openNFCEE() { + try { + Class.forName("com.android.nfc_extras.NfcAdapterExtras"); + } catch (final Throwable e) { + log("No NfcAdapterExtras Support"); + return null; + } + + final NfcAdapterExtras ne = NfcAdapterExtras.get(NfcAdapter.getDefaultAdapter(this.context)); + log("NFC Extras: " + ne); + + try { // XPeria Z1 throws NullPointer Exception from internal stuff + log("NFC driver: " + ne.getDriverName()); + } catch (final Exception e) { + e.printStackTrace(); // print and ignore + } + + final NfcExecutionEnvironment nee = ne.getEmbeddedExecutionEnvironment(); + log("EE: " + nee); + try { + nee.open(); + // } catch (final EeIOException e) { + } catch (final Exception e) { // reduce dependencies for some implementations + log("EE.open error: " + e.getMessage()); + e.printStackTrace(); + return null; + } + + return new ChannelTransportProvider(new Channel() { + private boolean open = true; + + + @Override + public void close() { + this.open = false; + try { + nee.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + + @Override + public Object getSession() { + return nee; + } + + + @Override + public boolean isOpen() { + return this.open; + } + + + @Override + public byte[] transmit(final byte[] command) { + try { + return nee.transceive(command); + } catch (final IOException e) { + e.printStackTrace(); + return null; + } + } + + + @Override + public byte[] getSelectResponse() { + return null; + } + + + @Override + public byte[] getATR() { + return null; + } + + + @Override + public String getName() { + return "NFC-EE"; + } + + + @Override + public boolean isBasicChannel() { + return true; + } + }); + } +} diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/TLV.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/TLV.java similarity index 99% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/TLV.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/TLV.java index 46a54ac..ce20e6f 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/TLV.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/TLV.java @@ -58,10 +58,9 @@ * zusammen mit diesem Programm erhalten haben. Wenn nicht, siehe * . */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; import java.io.ByteArrayOutputStream; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/TransportProvider.java b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/TransportProvider.java similarity index 99% rename from lib.omw.ivid/src/main/java/net/vx4/lib/omapi/TransportProvider.java rename to lib.omw.ivid/src/main/java/net/vx4/lib/ivid/TransportProvider.java index 8b1153b..c7cc19a 100644 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/TransportProvider.java +++ b/lib.omw.ivid/src/main/java/net/vx4/lib/ivid/TransportProvider.java @@ -58,7 +58,7 @@ * zusammen mit diesem Programm erhalten haben. Wenn nicht, siehe * . */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; /** *

diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/C2Transport.java b/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/C2Transport.java deleted file mode 100644 index 04ca2e9..0000000 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/C2Transport.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2017-2019 adesso AG - * - * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the - * European Commission - subsequent versions of the EUPL (the "Licence"); You may - * not use this work except in compliance with the Licence. - * - * You may obtain a copy of the Licence at: - * https://joinup.ec.europa.eu/software/page/eupl - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the - * specific language governing permissions and limitations under the Licence. - */ -package net.vx4.lib.omapi; - -/** - * C2Transport is an implementation of a transport provider stack element to handle extended length APDU mapping to - * ENVELOPE (C2) / GET RESPONSE (C0) APDUs. Hence its name as the response is handled by the underlying stack and this - * class "only" cuts long APDUs into shorter ENVELOPE ADPUS. - * - * @author kahlo, 2018 - * @version $Id$ - */ -public class C2Transport implements TransportProvider { - - private final TransportProvider parent; - private final short APDULen = 261; // T=0 limit - private byte envCLA = 0x10; - private byte envCLAlast = 0x00; - private byte envINS = (byte) 0xC2; - private short C2Len = 255; - - - public C2Transport(final TransportProvider parent) { - if (parent == null) { - throw new NullPointerException("parent transport provider required"); - } - this.parent = parent; - } - - - @Override - public byte[] transmit(byte[] apdu) { - final byte channelId = ((ChannelTransportProvider) this.getParent()).getChannelId(); - - if (channelId < 4) { - apdu[0] = (byte) (apdu[0] & 0xBC | channelId); - } else if (channelId < 20) { - final boolean isSM = (apdu[0] & 0x0C) != 0; - apdu[0] = (byte) (apdu[0] & 0xB0 | 0x40 | channelId - 4); - if (isSM) { - apdu[0] |= 0x20; - } - } - - if (apdu.length > APDULen || apdu.length > 5 && apdu[4] == 0) { - // sanitize APDU encoding - String a2 = Hex.toString(apdu); - if (apdu[5] == 0) { - a2 = a2.substring(0, 8) + a2.substring(12); - } - - apdu = Hex.fromString(a2.endsWith("0000") ? a2.substring(0, a2.length() - 4) : a2); - - if (apdu.length > APDULen) { - int sent = 0; - byte[] last = null; - while (apdu.length - sent > 0) { - final int len = apdu.length - sent > C2Len ? C2Len : apdu.length - sent; - if (apdu.length - (sent + len) > 0) { - last = parent.transmit( - Hex.fromString(Hex.toString(new byte[]{envCLA, envINS, 0, 0, (byte) len}) - + Hex.toString(apdu, sent, len & 0xFF))); - } else { - last = parent.transmit( - Hex.fromString( - Hex.toString(new byte[]{envCLAlast, envINS, 0, 0, (byte) len}) - + Hex.toString(apdu, sent, len & 0xFF))); - } - sent += len; - } - return last; - } - } - - return parent.transmit(apdu); - } - - @Override - public void close() { - parent.close(); - } - - - @Override - public Object getParent() { - return parent; - } - - - @Override - public int lastSW() { - return parent.lastSW(); - } -} diff --git a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ChannelTransportProvider.java b/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ChannelTransportProvider.java deleted file mode 100644 index a51f88c..0000000 --- a/lib.omw.ivid/src/main/java/net/vx4/lib/omapi/ChannelTransportProvider.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2017-2019 adesso AG - * - * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the - * European Commission - subsequent versions of the EUPL (the "Licence"); You may - * not use this work except in compliance with the Licence. - * - * You may obtain a copy of the Licence at: - * https://joinup.ec.europa.eu/software/page/eupl - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the - * specific language governing permissions and limitations under the Licence. - */ -package net.vx4.lib.omapi; - -import org.simalliance.openmobileapi.Channel; - -/** - * The ChannelTransportProvider deals with automatically negotiated channels on the underlying terminal interface. - * If a channel has been opened successfully it is the first contact to the selected app, so the SELECT APDU - * response is fetch belowed and used to adopt to protocol and implementation specifics of the secure element. - * - * @author kahlo, 2018 - * @version $Id$ - */ -public class ChannelTransportProvider implements TransportProvider { - - private final byte channelId; - private Channel channel = null; - private int lastSW = -1; - - /** - * - * @param channel - */ - public ChannelTransportProvider(final Channel channel) { - this.channel = channel; - final byte[] selRes = this.channel.getSelectResponse(); - channelId = TLV.get(TLV.get(selRes, (byte) 0x6F), (byte) 0x85)[0]; - } - - - @Override - public void close() { - // TODO: checking closing behaviour - System.out.println("ChannelTransportProvider.close(): WARNING, not closed"); - // channel.close(); - } - - - @Override - public Object getParent() { - return channel; - } - - - @Override - public int lastSW() { - return lastSW; - } - - - public byte getChannelId() { - return channelId; - } - - - @Override - public byte[] transmit(final byte[] apdu) { - lastSW = -1; - - System.out.println("ChannelTrannsport: channel = " + channel + " open? " + (channel != null ? !channel.isClosed() : "")); - if (channel != null && !channel.isClosed()) { - System.out.println("<[SE] apdu = [" + Hex.toString(apdu) + "]"); - byte[] rpdu = channel.transmit(apdu); - System.out.println(">[SE] rpdu = [" + Hex.toString(rpdu) + "]"); - - if (rpdu != null && rpdu.length >= 2) { - lastSW = ((rpdu[rpdu.length - 2] & 0xFF) << 8) + (rpdu[rpdu.length - 1] & 0xFF); - rpdu = ArrayTool.sub(rpdu, 0, rpdu.length - 2); - } - return rpdu; - } else { - return null; - } - } -} diff --git a/lib.omw.ivid/src/test/java/net/vx4/lib/omapi/OMAPITest.java b/lib.omw.ivid/src/test/java/net/vx4/lib/ivid/OMAPITest.java similarity index 78% rename from lib.omw.ivid/src/test/java/net/vx4/lib/omapi/OMAPITest.java rename to lib.omw.ivid/src/test/java/net/vx4/lib/ivid/OMAPITest.java index e0818d4..de4a454 100644 --- a/lib.omw.ivid/src/test/java/net/vx4/lib/omapi/OMAPITest.java +++ b/lib.omw.ivid/src/test/java/net/vx4/lib/ivid/OMAPITest.java @@ -13,7 +13,7 @@ * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the * specific language governing permissions and limitations under the Licence. */ -package net.vx4.lib.omapi; +package net.vx4.lib.ivid; import org.junit.*; @@ -69,7 +69,7 @@ public void tearDown() throws Exception { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#OMAPITP(org.simalliance.openmobileapi.Channel)}. + * Test method for {@link IVIDITP#IVIDITP(org.simalliance.openmobileapi.Channel)}. */ @Test public final void testOMAPITPChannel() { @@ -78,7 +78,7 @@ public final void testOMAPITPChannel() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#OMAPITP(net.vx4.lib.omapi.TransportProvider)}. + * Test method for {@link IVIDITP#IVIDITP(net.vx4.lib.ivid.TransportProvider)}. */ @Test public final void testOMAPITPTransportProvider() { @@ -87,7 +87,7 @@ public final void testOMAPITPTransportProvider() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#setCallbackHandler(net.vx4.lib.omapi.OMAPITP.CallbackHandler)}. + * Test method for {@link IVIDITP#setCallbackHandler(IVIDITP.CallbackHandler)}. */ @Test public final void testSetCallbackHandler() { @@ -96,7 +96,7 @@ public final void testSetCallbackHandler() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#process(byte[])}. + * Test method for {@link IVIDITP#process(byte[])}. */ @Test public final void testProcess() { @@ -105,7 +105,7 @@ public final void testProcess() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#getParent()}. + * Test method for {@link IVIDITP#getParent()}. */ @Test public final void testGetParent() { @@ -114,7 +114,7 @@ public final void testGetParent() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#transmit(byte[])}. + * Test method for {@link IVIDITP#transmit(byte[])}. */ @Test public final void testTransmit() { @@ -123,7 +123,7 @@ public final void testTransmit() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#lastSW()}. + * Test method for {@link IVIDITP#lastSW()}. */ @Test public final void testLastSW() { @@ -132,7 +132,7 @@ public final void testLastSW() { /** - * Test method for {@link net.vx4.lib.omapi.OMAPITP#close()}. + * Test method for {@link IVIDITP#close()}. */ @Test public final void testClose() { diff --git a/lib.omw.omapi/pom.xml b/lib.omw.omapi/pom.xml deleted file mode 100644 index edbe26e..0000000 --- a/lib.omw.omapi/pom.xml +++ /dev/null @@ -1,19 +0,0 @@ - - 4.0.0 - - net.vx4 - lib.omw - 0.0.1-SNAPSHOT - - lib.omw.omapi - - - - net.vx4 - lib.omw.android - 0.0.1-SNAPSHOT - provided - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3bba8e4..4645ea0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,13 +3,14 @@ 4.0.0 net.vx4 lib.omw - 0.0.1-SNAPSHOT + 0.1.1 + OMW OMAPI Wrapper pom + lib.omw.android - lib.omw.omapi lib.omw.ivid