From 131bf94b9bd68af421dc8b309fbb056d66906be3 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 14 May 2016 13:14:31 +0600 Subject: [PATCH] SecurityToken: add useful classes from javax.smartcardio package --- .../securitytoken/smartcardio/ATR.java | 165 +++++ .../securitytoken/smartcardio/Card.java | 167 +++++ .../smartcardio/CardChannel.java | 184 ++++++ .../smartcardio/CardException.java | 68 ++ .../smartcardio/CardNotPresentException.java | 68 ++ .../smartcardio/CardPermission.java | 301 +++++++++ .../smartcardio/CommandAPDU.java | 606 ++++++++++++++++++ .../smartcardio/ResponseAPDU.java | 185 ++++++ 8 files changed, 1744 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ATR.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/Card.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardChannel.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardNotPresentException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardPermission.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CommandAPDU.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ResponseAPDU.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ATR.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ATR.java new file mode 100644 index 000000000..5d1581b19 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ATR.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +import java.util.*; + +/** + * A Smart Card's answer-to-reset bytes. A Card's ATR object can be obtained + * by calling {@linkplain Card#getATR}. + * This class does not attempt to verify that the ATR encodes a semantically + * valid structure. + * + *

Instances of this class are immutable. Where data is passed in or out + * via byte arrays, defensive cloning is performed. + * + * @see Card#getATR + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public final class ATR implements java.io.Serializable { + + private static final long serialVersionUID = 6695383790847736493L; + + private byte[] atr; + + private transient int startHistorical, nHistorical; + + /** + * Constructs an ATR from a byte array. + * + * @param atr the byte array containing the answer-to-reset bytes + * @throws NullPointerException if atr is null + */ + public ATR(byte[] atr) { + this.atr = atr.clone(); + parse(); + } + + private void parse() { + if (atr.length < 2) { + return; + } + if ((atr[0] != 0x3b) && (atr[0] != 0x3f)) { + return; + } + int t0 = (atr[1] & 0xf0) >> 4; + int n = atr[1] & 0xf; + int i = 2; + while ((t0 != 0) && (i < atr.length)) { + if ((t0 & 1) != 0) { + i++; + } + if ((t0 & 2) != 0) { + i++; + } + if ((t0 & 4) != 0) { + i++; + } + if ((t0 & 8) != 0) { + if (i >= atr.length) { + return; + } + t0 = (atr[i++] & 0xf0) >> 4; + } else { + t0 = 0; + } + } + int k = i + n; + if ((k == atr.length) || (k == atr.length - 1)) { + startHistorical = i; + nHistorical = n; + } + } + + /** + * Returns a copy of the bytes in this ATR. + * + * @return a copy of the bytes in this ATR. + */ + public byte[] getBytes() { + return atr.clone(); + } + + /** + * Returns a copy of the historical bytes in this ATR. + * If this ATR does not contain historical bytes, an array of length + * zero is returned. + * + * @return a copy of the historical bytes in this ATR. + */ + public byte[] getHistoricalBytes() { + byte[] b = new byte[nHistorical]; + System.arraycopy(atr, startHistorical, b, 0, nHistorical); + return b; + } + + /** + * Returns a string representation of this ATR. + * + * @return a String representation of this ATR. + */ + public String toString() { + return "ATR: " + atr.length + " bytes"; + } + + /** + * Compares the specified object with this ATR for equality. + * Returns true if the given object is also an ATR and its bytes are + * identical to the bytes in this ATR. + * + * @param obj the object to be compared for equality with this ATR + * @return true if the specified object is equal to this ATR + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ATR == false) { + return false; + } + ATR other = (ATR)obj; + return Arrays.equals(this.atr, other.atr); + } + + /** + * Returns the hash code value for this ATR. + * + * @return the hash code value for this ATR. + */ + public int hashCode() { + return Arrays.hashCode(atr); + } + + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, ClassNotFoundException { + atr = (byte[])in.readUnshared(); + parse(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/Card.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/Card.java new file mode 100644 index 000000000..b3b7fdc4f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/Card.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +import java.nio.ByteBuffer; + +/** + * A Smart Card with which a connection has been established. Card objects + * are obtained by calling {@link CardTerminal#connect CardTerminal.connect()}. + * + * @see CardTerminal + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group +*/ +public abstract class Card { + + /** + * Constructs a new Card object. + * + *

This constructor is called by subclasses only. Application should + * call the {@linkplain CardTerminal#connect CardTerminal.connect()} + * method to obtain a Card + * object. + */ + protected Card() { + // empty + } + + /** + * Returns the ATR of this card. + * + * @return the ATR of this card. + */ + public abstract ATR getATR(); + + /** + * Returns the protocol in use for this card. + * + * @return the protocol in use for this card, for example "T=0" or "T=1" + */ + public abstract String getProtocol(); + + /** + * Returns the CardChannel for the basic logical channel. The basic + * logical channel has a channel number of 0. + * + * @throws SecurityException if a SecurityManager exists and the + * caller does not have the required + * {@linkplain CardPermission permission} + * @throws IllegalStateException if this card object has been disposed of + * via the {@linkplain #disconnect disconnect()} method + */ + public abstract CardChannel getBasicChannel(); + + /** + * Opens a new logical channel to the card and returns it. The channel is + * opened by issuing a MANAGE CHANNEL command that should use + * the format [00 70 00 00 01]. + * + * @throws SecurityException if a SecurityManager exists and the + * caller does not have the required + * {@linkplain CardPermission permission} + * @throws CardException is a new logical channel could not be opened + * @throws IllegalStateException if this card object has been disposed of + * via the {@linkplain #disconnect disconnect()} method + */ + public abstract CardChannel openLogicalChannel() throws CardException; + + /** + * Requests exclusive access to this card. + * + *

Once a thread has invoked beginExclusive, only this + * thread is allowed to communicate with this card until it calls + * endExclusive. Other threads attempting communication + * will receive a CardException. + * + *

Applications have to ensure that exclusive access is correctly + * released. This can be achieved by executing + * the beginExclusive() and endExclusive calls + * in a try ... finally block. + * + * @throws SecurityException if a SecurityManager exists and the + * caller does not have the required + * {@linkplain CardPermission permission} + * @throws CardException if exclusive access has already been set + * or if exclusive access could not be established + * @throws IllegalStateException if this card object has been disposed of + * via the {@linkplain #disconnect disconnect()} method + */ + public abstract void beginExclusive() throws CardException; + + /** + * Releases the exclusive access previously established using + * beginExclusive. + * + * @throws SecurityException if a SecurityManager exists and the + * caller does not have the required + * {@linkplain CardPermission permission} + * @throws IllegalStateException if the active Thread does not currently have + * exclusive access to this card or + * if this card object has been disposed of + * via the {@linkplain #disconnect disconnect()} method + * @throws CardException if the operation failed + */ + public abstract void endExclusive() throws CardException; + + /** + * Transmits a control command to the terminal device. + * + *

This can be used to, for example, control terminal functions like + * a built-in PIN pad or biometrics. + * + * @param controlCode the control code of the command + * @param command the command data + * + * @throws SecurityException if a SecurityManager exists and the + * caller does not have the required + * {@linkplain CardPermission permission} + * @throws NullPointerException if command is null + * @throws CardException if the card operation failed + * @throws IllegalStateException if this card object has been disposed of + * via the {@linkplain #disconnect disconnect()} method + */ + public abstract byte[] transmitControlCommand(int controlCode, + byte[] command) throws CardException; + + /** + * Disconnects the connection with this card. After this method returns, + * calling methods on this object or in CardChannels associated with this + * object that require interaction with the card will raise an + * IllegalStateException. + * + * @param reset whether to reset the card after disconnecting. + * + * @throws CardException if the card operation failed + * @throws SecurityException if a SecurityManager exists and the + * caller does not have the required + * {@linkplain CardPermission permission} + */ + public abstract void disconnect(boolean reset) throws CardException; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardChannel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardChannel.java new file mode 100644 index 000000000..243c171bd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardChannel.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +import java.nio.*; + +/** + * A logical channel connection to a Smart Card. It is used to exchange APDUs + * with a Smart Card. + * A CardChannel object can be obtained by calling the method + * {@linkplain Card#getBasicChannel} or {@linkplain Card#openLogicalChannel}. + * + * @see Card + * @see CommandAPDU + * @see ResponseAPDU + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public abstract class CardChannel { + + /** + * Constructs a new CardChannel object. + * + *

This constructor is called by subclasses only. Application should + * call the {@linkplain Card#getBasicChannel} and + * {@linkplain Card#openLogicalChannel} methods to obtain a CardChannel + * object. + */ + protected CardChannel() { + // empty + } + + /** + * Returns the Card this channel is associated with. + * + * @return the Card this channel is associated with + */ + public abstract Card getCard(); + + /** + * Returns the channel number of this CardChannel. A channel number of + * 0 indicates the basic logical channel. + * + * @return the channel number of this CardChannel. + * + * @throws IllegalStateException if this channel has been + * {@linkplain #close closed} or if the corresponding Card has been + * {@linkplain Card#disconnect disconnected}. + */ + public abstract int getChannelNumber(); + + /** + * Transmits the specified command APDU to the Smart Card and returns the + * response APDU. + * + *

The CLA byte of the command APDU is automatically adjusted to + * match the channel number of this CardChannel. + * + *

Note that this method cannot be used to transmit + * MANAGE CHANNEL APDUs. Logical channels should be managed + * using the {@linkplain Card#openLogicalChannel} and {@linkplain + * CardChannel#close CardChannel.close()} methods. + * + *

Implementations should transparently handle artifacts + * of the transmission protocol. + * For example, when using the T=0 protocol, the following processing + * should occur as described in ISO/IEC 7816-4: + * + *

+ * + *

The ResponseAPDU returned by this method is the result + * after this processing has been performed. + * + * @param command the command APDU + * @return the response APDU received from the card + * + * @throws IllegalStateException if this channel has been + * {@linkplain #close closed} or if the corresponding Card has been + * {@linkplain Card#disconnect disconnected}. + * @throws IllegalArgumentException if the APDU encodes a + * MANAGE CHANNEL command + * @throws NullPointerException if command is null + * @throws CardException if the card operation failed + */ + public abstract ResponseAPDU transmit(CommandAPDU command) throws CardException; + + /** + * Transmits the command APDU stored in the command ByteBuffer and receives + * the reponse APDU in the response ByteBuffer. + * + *

The command buffer must contain valid command APDU data starting + * at command.position() and the APDU must be + * command.remaining() bytes long. + * Upon return, the command buffer's position will be equal + * to its limit; its limit will not have changed. The output buffer + * will have received the response APDU bytes. Its position will have + * advanced by the number of bytes received, which is also the return + * value of this method. + * + *

The CLA byte of the command APDU is automatically adjusted to + * match the channel number of this CardChannel. + * + *

Note that this method cannot be used to transmit + * MANAGE CHANNEL APDUs. Logical channels should be managed + * using the {@linkplain Card#openLogicalChannel} and {@linkplain + * CardChannel#close CardChannel.close()} methods. + * + *

See {@linkplain #transmit transmit()} for a discussion of the handling + * of response APDUs with the SW1 values 61 or 6C. + * + * @param command the buffer containing the command APDU + * @param response the buffer that shall receive the response APDU from + * the card + * @return the length of the received response APDU + * + * @throws IllegalStateException if this channel has been + * {@linkplain #close closed} or if the corresponding Card has been + * {@linkplain Card#disconnect disconnected}. + * @throws NullPointerException if command or response is null + * @throws ReadOnlyBufferException if the response buffer is read-only + * @throws IllegalArgumentException if command and response are the + * same object, if response may not have + * sufficient space to receive the response APDU + * or if the APDU encodes a MANAGE CHANNEL command + * @throws CardException if the card operation failed + */ + public abstract int transmit(ByteBuffer command, ByteBuffer response) + throws CardException; + + /** + * Closes this CardChannel. The logical channel is closed by issuing + * a MANAGE CHANNEL command that should use the format + * [xx 70 80 0n] where n is the channel number + * of this channel and xx is the CLA + * byte that encodes this logical channel and has all other bits set to 0. + * After this method returns, calling other + * methods in this class will raise an IllegalStateException. + * + *

Note that the basic logical channel cannot be closed using this + * method. It can be closed by calling {@link Card#disconnect}. + * + * @throws CardException if the card operation failed + * @throws IllegalStateException if this CardChannel represents a + * connection the basic logical channel + */ + public abstract void close() throws CardException; + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardException.java new file mode 100644 index 000000000..23d02e24e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardException.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +/** + * Exception for errors that occur during communication with the + * Smart Card stack or the card itself. + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public class CardException extends Exception { + + private static final long serialVersionUID = 7787607144922050628L; + + /** + * Constructs a new CardException with the specified detail message. + * + * @param message the detail message + */ + public CardException(String message) { + super(message); + } + + /** + * Constructs a new CardException with the specified cause and a detail message + * of (cause==null ? null : cause.toString()). + * + * @param cause the cause of this exception or null + */ + public CardException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new CardException with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause of this exception or null + */ + public CardException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardNotPresentException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardNotPresentException.java new file mode 100644 index 000000000..3b8289b75 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardNotPresentException.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +/** + * Exception thrown when an application tries to establish a connection with a + * terminal that has no card present. + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public class CardNotPresentException extends CardException { + + private final static long serialVersionUID = 1346879911706545215L; + + /** + * Constructs a new CardNotPresentException with the specified detail message. + * + * @param message the detail message + */ + public CardNotPresentException(String message) { + super(message); + } + + /** + * Constructs a new CardNotPresentException with the specified cause and a detail message + * of (cause==null ? null : cause.toString()). + * + * @param cause the cause of this exception or null + */ + public CardNotPresentException(Throwable cause) { + super(cause); + } + + /** + * Constructs a new CardNotPresentException with the specified detail message and cause. + * + * @param message the detail message + * @param cause the cause of this exception or null + */ + public CardNotPresentException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardPermission.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardPermission.java new file mode 100644 index 000000000..8731fa467 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CardPermission.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +import java.io.*; + +import java.security.Permission; + +/** + * A permission for Smart Card operations. A CardPermission consists of the + * name of the card terminal the permission applies to and a set of actions + * that are valid for that terminal. + * + *

A CardPermission with a name of * applies to all + * card terminals. The actions string is a comma separated list of the actions + * listed below, or * to signify "all actions." + * + *

Individual actions are: + *

+ *
connect + *
connect to a card using + * {@linkplain CardTerminal#connect CardTerminal.connect()} + * + *
reset + *
reset the card using {@linkplain Card#disconnect Card.disconnect(true)} + * + *
exclusive + *
establish exclusive access to a card using + * {@linkplain Card#beginExclusive} and {@linkplain Card#endExclusive + * endExclusive()} + * + *
transmitControl + *
transmit a control command using + * {@linkplain Card#transmitControlCommand Card.transmitControlCommand()} + * + *
getBasicChannel + *
obtain the basic logical channel using + * {@linkplain Card#getBasicChannel} + * + *
openLogicalChannel + *
open a new logical channel using + * {@linkplain Card#openLogicalChannel} + * + *
+ * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public class CardPermission extends Permission { + + private static final long serialVersionUID = 7146787880530705613L; + + private final static int A_CONNECT = 0x01; + private final static int A_EXCLUSIVE = 0x02; + private final static int A_GET_BASIC_CHANNEL = 0x04; + private final static int A_OPEN_LOGICAL_CHANNEL = 0x08; + private final static int A_RESET = 0x10; + private final static int A_TRANSMIT_CONTROL = 0x20; + + // sum of all the actions above + private final static int A_ALL = 0x3f; + + private final static int[] ARRAY_MASKS = { + A_ALL, + A_CONNECT, + A_EXCLUSIVE, + A_GET_BASIC_CHANNEL, + A_OPEN_LOGICAL_CHANNEL, + A_RESET, + A_TRANSMIT_CONTROL, + }; + + private final static String S_CONNECT = "connect"; + private final static String S_EXCLUSIVE = "exclusive"; + private final static String S_GET_BASIC_CHANNEL = "getBasicChannel"; + private final static String S_OPEN_LOGICAL_CHANNEL = "openLogicalChannel"; + private final static String S_RESET = "reset"; + private final static String S_TRANSMIT_CONTROL = "transmitControl"; + + private final static String S_ALL = "*"; + + private final static String[] ARRAY_STRINGS = { + S_ALL, + S_CONNECT, + S_EXCLUSIVE, + S_GET_BASIC_CHANNEL, + S_OPEN_LOGICAL_CHANNEL, + S_RESET, + S_TRANSMIT_CONTROL, + }; + + private transient int mask; + + /** + * @serial + */ + private volatile String actions; + + /** + * Constructs a new CardPermission with the specified actions. + * terminalName is the name of a CardTerminal or * + * if this permission applies to all terminals. actions + * contains a comma-separated list of the individual actions + * or * to signify all actions. For more information, + * see the documentation at the top of this {@linkplain CardPermission + * class}. + * + * @param terminalName the name of the card terminal, or * + * @param actions the action string (or null if the set of permitted + * actions is empty) + * + * @throws NullPointerException if terminalName is null + * @throws IllegalArgumentException if actions is an invalid actions + * specification + */ + public CardPermission(String terminalName, String actions) { + super(terminalName); + if (terminalName == null) { + throw new NullPointerException(); + } + mask = getMask(actions); + } + + private static int getMask(String actions) { + if ((actions == null) || (actions.length() == 0)) { + throw new IllegalArgumentException("actions must not be empty"); + } + + // try exact matches for simple actions first + for (int i = 0; i < ARRAY_STRINGS.length; i++) { + if (actions == ARRAY_STRINGS[i]) { + return ARRAY_MASKS[i]; + } + } + + if (actions.endsWith(",")) { + throw new IllegalArgumentException("Invalid actions: '" + actions + "'"); + } + int mask = 0; + String[] split = actions.split(","); + outer: + for (String s : split) { + for (int i = 0; i < ARRAY_STRINGS.length; i++) { + if (ARRAY_STRINGS[i].equalsIgnoreCase(s)) { + mask |= ARRAY_MASKS[i]; + continue outer; + } + } + throw new IllegalArgumentException("Invalid action: '" + s + "'"); + } + + return mask; + } + + private static String getActions(int mask) { + if (mask == A_ALL) { + return S_ALL; + } + boolean first = true; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ARRAY_MASKS.length; i++) { + int action = ARRAY_MASKS[i]; + if ((mask & action) == action) { + if (first == false) { + sb.append(","); + } else { + first = false; + } + sb.append(ARRAY_STRINGS[i]); + } + } + return sb.toString(); + } + + + /** + * Returns the canonical string representation of the actions. + * It is * to signify all actions defined by this class or + * the string concatenation of the comma-separated, + * lexicographically sorted list of individual actions. + * + * @return the canonical string representation of the actions. + */ + public String getActions() { + if (actions == null) { + actions = getActions(mask); + } + return actions; + } + + /** + * Checks if this CardPermission object implies the specified permission. + * That is the case, if and only if + * + * + * @param permission the permission to check against + * @return true if and only if this CardPermission object implies the + * specified permission. + */ + public boolean implies(Permission permission) { + if (permission instanceof CardPermission == false) { + return false; + } + CardPermission other = (CardPermission)permission; + if ((this.mask & other.mask) != other.mask) { + return false; + } + String thisName = getName(); + if (thisName.equals("*")) { + return true; + } + if (thisName.equals(other.getName())) { + return true; + } + return false; + } + + /** + * Compares the specified object with this CardPermission for equality. + * This CardPermission is equal to another Object object, if + * and only if + * + * + * @param obj the object to be compared for equality with this CardPermission + * @return true if and only if the specified object is equal to this + * CardPermission + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof CardPermission == false) { + return false; + } + CardPermission other = (CardPermission)obj; + return this.getName().equals(other.getName()) && (this.mask == other.mask); + } + + /** + * Returns the hash code value for this CardPermission object. + * + * @return the hash code value for this CardPermission object. + */ + public int hashCode() { + return getName().hashCode() + 31 * mask; + } + + private void writeObject(ObjectOutputStream s) throws IOException { + // Write out the actions. The superclass takes care of the name. + // Call getActions to make sure actions field is initialized + if (actions == null) { + getActions(); + } + s.defaultWriteObject(); + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + // Read in the actions, then restore the mask. + s.defaultReadObject(); + mask = getMask(actions); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CommandAPDU.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CommandAPDU.java new file mode 100644 index 000000000..137f307f2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/CommandAPDU.java @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +import java.util.Arrays; + +import java.nio.ByteBuffer; + +/** + * A command APDU following the structure defined in ISO/IEC 7816-4. + * It consists of a four byte header and a conditional body of variable length. + * This class does not attempt to verify that the APDU encodes a semantically + * valid command. + * + *

Note that when the expected length of the response APDU is specified + * in the {@linkplain #CommandAPDU(int,int,int,int,int) constructors}, + * the actual length (Ne) must be specified, not its + * encoded form (Le). Similarly, {@linkplain #getNe} returns the actual + * value Ne. In other words, a value of 0 means "no data in the response APDU" + * rather than "maximum length." + * + *

This class supports both the short and extended forms of length + * encoding for Ne and Nc. However, note that not all terminals and Smart Cards + * are capable of accepting APDUs that use the extended form. + * + *

For the header bytes CLA, INS, P1, and P2 the Java type int + * is used to represent the 8 bit unsigned values. In the constructors, only + * the 8 lowest bits of the int value specified by the application + * are significant. The accessor methods always return the byte as an unsigned + * value between 0 and 255. + * + *

Instances of this class are immutable. Where data is passed in or out + * via byte arrays, defensive cloning is performed. + * + * @see ResponseAPDU + * @see CardChannel#transmit CardChannel.transmit + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public final class CommandAPDU implements java.io.Serializable { + + private static final long serialVersionUID = 398698301286670877L; + + private static final int MAX_APDU_SIZE = 65544; + + /** @serial */ + private byte[] apdu; + + // value of nc + private transient int nc; + + // value of ne + private transient int ne; + + // index of start of data within the apdu array + private transient int dataOffset; + + /** + * Constructs a CommandAPDU from a byte array containing the complete + * APDU contents (header and body). + * + *

Note that the apdu bytes are copied to protect against + * subsequent modification. + * + * @param apdu the complete command APDU + * + * @throws NullPointerException if apdu is null + * @throws IllegalArgumentException if apdu does not contain a valid + * command APDU + */ + public CommandAPDU(byte[] apdu) { + this.apdu = apdu.clone(); + parse(); + } + + /** + * Constructs a CommandAPDU from a byte array containing the complete + * APDU contents (header and body). The APDU starts at the index + * apduOffset in the byte array and is apduLength + * bytes long. + * + *

Note that the apdu bytes are copied to protect against + * subsequent modification. + * + * @param apdu the complete command APDU + * @param apduOffset the offset in the byte array at which the apdu + * data begins + * @param apduLength the length of the APDU + * + * @throws NullPointerException if apdu is null + * @throws IllegalArgumentException if apduOffset or apduLength are + * negative or if apduOffset + apduLength are greater than apdu.length, + * or if the specified bytes are not a valid APDU + */ + public CommandAPDU(byte[] apdu, int apduOffset, int apduLength) { + checkArrayBounds(apdu, apduOffset, apduLength); + this.apdu = new byte[apduLength]; + System.arraycopy(apdu, apduOffset, this.apdu, 0, apduLength); + parse(); + } + + private void checkArrayBounds(byte[] b, int ofs, int len) { + if ((ofs < 0) || (len < 0)) { + throw new IllegalArgumentException + ("Offset and length must not be negative"); + } + if (b == null) { + if ((ofs != 0) && (len != 0)) { + throw new IllegalArgumentException + ("offset and length must be 0 if array is null"); + } + } else { + if (ofs > b.length - len) { + throw new IllegalArgumentException + ("Offset plus length exceed array size"); + } + } + } + + /** + * Creates a CommandAPDU from the ByteBuffer containing the complete APDU + * contents (header and body). + * The buffer's position must be set to the start of the APDU, + * its limit to the end of the APDU. Upon return, the buffer's + * position is equal to its limit; its limit remains unchanged. + * + *

Note that the data in the ByteBuffer is copied to protect against + * subsequent modification. + * + * @param apdu the ByteBuffer containing the complete APDU + * + * @throws NullPointerException if apdu is null + * @throws IllegalArgumentException if apdu does not contain a valid + * command APDU + */ + public CommandAPDU(ByteBuffer apdu) { + this.apdu = new byte[apdu.remaining()]; + apdu.get(this.apdu); + parse(); + } + + /** + * Constructs a CommandAPDU from the four header bytes. This is case 1 + * in ISO 7816, no command body. + * + * @param cla the class byte CLA + * @param ins the instruction byte INS + * @param p1 the parameter byte P1 + * @param p2 the parameter byte P2 + */ + public CommandAPDU(int cla, int ins, int p1, int p2) { + this(cla, ins, p1, p2, null, 0, 0, 0); + } + + /** + * Constructs a CommandAPDU from the four header bytes and the expected + * response data length. This is case 2 in ISO 7816, empty command data + * field with Ne specified. If Ne is 0, the APDU is encoded as ISO 7816 + * case 1. + * + * @param cla the class byte CLA + * @param ins the instruction byte INS + * @param p1 the parameter byte P1 + * @param p2 the parameter byte P2 + * @param ne the maximum number of expected data bytes in a response APDU + * + * @throws IllegalArgumentException if ne is negative or greater than + * 65536 + */ + public CommandAPDU(int cla, int ins, int p1, int p2, int ne) { + this(cla, ins, p1, p2, null, 0, 0, ne); + } + + /** + * Constructs a CommandAPDU from the four header bytes and command data. + * This is case 3 in ISO 7816, command data present and Ne absent. The + * value Nc is taken as data.length. If data is null or + * its length is 0, the APDU is encoded as ISO 7816 case 1. + * + *

Note that the data bytes are copied to protect against + * subsequent modification. + * + * @param cla the class byte CLA + * @param ins the instruction byte INS + * @param p1 the parameter byte P1 + * @param p2 the parameter byte P2 + * @param data the byte array containing the data bytes of the command body + * + * @throws IllegalArgumentException if data.length is greater than 65535 + */ + public CommandAPDU(int cla, int ins, int p1, int p2, byte[] data) { + this(cla, ins, p1, p2, data, 0, arrayLength(data), 0); + } + + /** + * Constructs a CommandAPDU from the four header bytes and command data. + * This is case 3 in ISO 7816, command data present and Ne absent. The + * value Nc is taken as dataLength. If dataLength + * is 0, the APDU is encoded as ISO 7816 case 1. + * + *

Note that the data bytes are copied to protect against + * subsequent modification. + * + * @param cla the class byte CLA + * @param ins the instruction byte INS + * @param p1 the parameter byte P1 + * @param p2 the parameter byte P2 + * @param data the byte array containing the data bytes of the command body + * @param dataOffset the offset in the byte array at which the data + * bytes of the command body begin + * @param dataLength the number of the data bytes in the command body + * + * @throws NullPointerException if data is null and dataLength is not 0 + * @throws IllegalArgumentException if dataOffset or dataLength are + * negative or if dataOffset + dataLength are greater than data.length + * or if dataLength is greater than 65535 + */ + public CommandAPDU(int cla, int ins, int p1, int p2, byte[] data, + int dataOffset, int dataLength) { + this(cla, ins, p1, p2, data, dataOffset, dataLength, 0); + } + + /** + * Constructs a CommandAPDU from the four header bytes, command data, + * and expected response data length. This is case 4 in ISO 7816, + * command data and Ne present. The value Nc is taken as data.length + * if data is non-null and as 0 otherwise. If Ne or Nc + * are zero, the APDU is encoded as case 1, 2, or 3 per ISO 7816. + * + *

Note that the data bytes are copied to protect against + * subsequent modification. + * + * @param cla the class byte CLA + * @param ins the instruction byte INS + * @param p1 the parameter byte P1 + * @param p2 the parameter byte P2 + * @param data the byte array containing the data bytes of the command body + * @param ne the maximum number of expected data bytes in a response APDU + * + * @throws IllegalArgumentException if data.length is greater than 65535 + * or if ne is negative or greater than 65536 + */ + public CommandAPDU(int cla, int ins, int p1, int p2, byte[] data, int ne) { + this(cla, ins, p1, p2, data, 0, arrayLength(data), ne); + } + + private static int arrayLength(byte[] b) { + return (b != null) ? b.length : 0; + } + + /** + * Command APDU encoding options: + * + * case 1: |CLA|INS|P1 |P2 | len = 4 + * case 2s: |CLA|INS|P1 |P2 |LE | len = 5 + * case 3s: |CLA|INS|P1 |P2 |LC |...BODY...| len = 6..260 + * case 4s: |CLA|INS|P1 |P2 |LC |...BODY...|LE | len = 7..261 + * case 2e: |CLA|INS|P1 |P2 |00 |LE1|LE2| len = 7 + * case 3e: |CLA|INS|P1 |P2 |00 |LC1|LC2|...BODY...| len = 8..65542 + * case 4e: |CLA|INS|P1 |P2 |00 |LC1|LC2|...BODY...|LE1|LE2| len =10..65544 + * + * LE, LE1, LE2 may be 0x00. + * LC must not be 0x00 and LC1|LC2 must not be 0x00|0x00 + */ + private void parse() { + if (apdu.length < 4) { + throw new IllegalArgumentException("apdu must be at least 4 bytes long"); + } + if (apdu.length == 4) { + // case 1 + return; + } + int l1 = apdu[4] & 0xff; + if (apdu.length == 5) { + // case 2s + this.ne = (l1 == 0) ? 256 : l1; + return; + } + if (l1 != 0) { + if (apdu.length == 4 + 1 + l1) { + // case 3s + this.nc = l1; + this.dataOffset = 5; + return; + } else if (apdu.length == 4 + 2 + l1) { + // case 4s + this.nc = l1; + this.dataOffset = 5; + int l2 = apdu[apdu.length - 1] & 0xff; + this.ne = (l2 == 0) ? 256 : l2; + return; + } else { + throw new IllegalArgumentException + ("Invalid APDU: length=" + apdu.length + ", b1=" + l1); + } + } + if (apdu.length < 7) { + throw new IllegalArgumentException + ("Invalid APDU: length=" + apdu.length + ", b1=" + l1); + } + int l2 = ((apdu[5] & 0xff) << 8) | (apdu[6] & 0xff); + if (apdu.length == 7) { + // case 2e + this.ne = (l2 == 0) ? 65536 : l2; + return; + } + if (l2 == 0) { + throw new IllegalArgumentException("Invalid APDU: length=" + + apdu.length + ", b1=" + l1 + ", b2||b3=" + l2); + } + if (apdu.length == 4 + 3 + l2) { + // case 3e + this.nc = l2; + this.dataOffset = 7; + return; + } else if (apdu.length == 4 + 5 + l2) { + // case 4e + this.nc = l2; + this.dataOffset = 7; + int leOfs = apdu.length - 2; + int l3 = ((apdu[leOfs] & 0xff) << 8) | (apdu[leOfs + 1] & 0xff); + this.ne = (l3 == 0) ? 65536 : l3; + } else { + throw new IllegalArgumentException("Invalid APDU: length=" + + apdu.length + ", b1=" + l1 + ", b2||b3=" + l2); + } + } + + /** + * Constructs a CommandAPDU from the four header bytes, command data, + * and expected response data length. This is case 4 in ISO 7816, + * command data and Le present. The value Nc is taken as + * dataLength. + * If Ne or Nc + * are zero, the APDU is encoded as case 1, 2, or 3 per ISO 7816. + * + *

Note that the data bytes are copied to protect against + * subsequent modification. + * + * @param cla the class byte CLA + * @param ins the instruction byte INS + * @param p1 the parameter byte P1 + * @param p2 the parameter byte P2 + * @param data the byte array containing the data bytes of the command body + * @param dataOffset the offset in the byte array at which the data + * bytes of the command body begin + * @param dataLength the number of the data bytes in the command body + * @param ne the maximum number of expected data bytes in a response APDU + * + * @throws NullPointerException if data is null and dataLength is not 0 + * @throws IllegalArgumentException if dataOffset or dataLength are + * negative or if dataOffset + dataLength are greater than data.length, + * or if ne is negative or greater than 65536, + * or if dataLength is greater than 65535 + */ + public CommandAPDU(int cla, int ins, int p1, int p2, byte[] data, + int dataOffset, int dataLength, int ne) { + checkArrayBounds(data, dataOffset, dataLength); + if (dataLength > 65535) { + throw new IllegalArgumentException("dataLength is too large"); + } + if (ne < 0) { + throw new IllegalArgumentException("ne must not be negative"); + } + if (ne > 65536) { + throw new IllegalArgumentException("ne is too large"); + } + this.ne = ne; + this.nc = dataLength; + if (dataLength == 0) { + if (ne == 0) { + // case 1 + this.apdu = new byte[4]; + setHeader(cla, ins, p1, p2); + } else { + // case 2s or 2e + if (ne <= 256) { + // case 2s + // 256 is encoded as 0x00 + byte len = (ne != 256) ? (byte)ne : 0; + this.apdu = new byte[5]; + setHeader(cla, ins, p1, p2); + this.apdu[4] = len; + } else { + // case 2e + byte l1, l2; + // 65536 is encoded as 0x00 0x00 + if (ne == 65536) { + l1 = 0; + l2 = 0; + } else { + l1 = (byte)(ne >> 8); + l2 = (byte)ne; + } + this.apdu = new byte[7]; + setHeader(cla, ins, p1, p2); + this.apdu[5] = l1; + this.apdu[6] = l2; + } + } + } else { + if (ne == 0) { + // case 3s or 3e + if (dataLength <= 255) { + // case 3s + apdu = new byte[4 + 1 + dataLength]; + setHeader(cla, ins, p1, p2); + apdu[4] = (byte)dataLength; + this.dataOffset = 5; + System.arraycopy(data, dataOffset, apdu, 5, dataLength); + } else { + // case 3e + apdu = new byte[4 + 3 + dataLength]; + setHeader(cla, ins, p1, p2); + apdu[4] = 0; + apdu[5] = (byte)(dataLength >> 8); + apdu[6] = (byte)dataLength; + this.dataOffset = 7; + System.arraycopy(data, dataOffset, apdu, 7, dataLength); + } + } else { + // case 4s or 4e + if ((dataLength <= 255) && (ne <= 256)) { + // case 4s + apdu = new byte[4 + 2 + dataLength]; + setHeader(cla, ins, p1, p2); + apdu[4] = (byte)dataLength; + this.dataOffset = 5; + System.arraycopy(data, dataOffset, apdu, 5, dataLength); + apdu[apdu.length - 1] = (ne != 256) ? (byte)ne : 0; + } else { + // case 4e + apdu = new byte[4 + 5 + dataLength]; + setHeader(cla, ins, p1, p2); + apdu[4] = 0; + apdu[5] = (byte)(dataLength >> 8); + apdu[6] = (byte)dataLength; + this.dataOffset = 7; + System.arraycopy(data, dataOffset, apdu, 7, dataLength); + if (ne != 65536) { + int leOfs = apdu.length - 2; + apdu[leOfs] = (byte)(ne >> 8); + apdu[leOfs + 1] = (byte)ne; + } // else le == 65536: no need to fill in, encoded as 0 + } + } + } + } + + private void setHeader(int cla, int ins, int p1, int p2) { + apdu[0] = (byte)cla; + apdu[1] = (byte)ins; + apdu[2] = (byte)p1; + apdu[3] = (byte)p2; + } + + /** + * Returns the value of the class byte CLA. + * + * @return the value of the class byte CLA. + */ + public int getCLA() { + return apdu[0] & 0xff; + } + + /** + * Returns the value of the instruction byte INS. + * + * @return the value of the instruction byte INS. + */ + public int getINS() { + return apdu[1] & 0xff; + } + + /** + * Returns the value of the parameter byte P1. + * + * @return the value of the parameter byte P1. + */ + public int getP1() { + return apdu[2] & 0xff; + } + + /** + * Returns the value of the parameter byte P2. + * + * @return the value of the parameter byte P2. + */ + public int getP2() { + return apdu[3] & 0xff; + } + + /** + * Returns the number of data bytes in the command body (Nc) or 0 if this + * APDU has no body. This call is equivalent to + * getData().length. + * + * @return the number of data bytes in the command body or 0 if this APDU + * has no body. + */ + public int getNc() { + return nc; + } + + /** + * Returns a copy of the data bytes in the command body. If this APDU as + * no body, this method returns a byte array with length zero. + * + * @return a copy of the data bytes in the command body or the empty + * byte array if this APDU has no body. + */ + public byte[] getData() { + byte[] data = new byte[nc]; + System.arraycopy(apdu, dataOffset, data, 0, nc); + return data; + } + + /** + * Returns the maximum number of expected data bytes in a response + * APDU (Ne). + * + * @return the maximum number of expected data bytes in a response APDU. + */ + public int getNe() { + return ne; + } + + /** + * Returns a copy of the bytes in this APDU. + * + * @return a copy of the bytes in this APDU. + */ + public byte[] getBytes() { + return apdu.clone(); + } + + /** + * Returns a string representation of this command APDU. + * + * @return a String representation of this command APDU. + */ + public String toString() { + return "CommmandAPDU: " + apdu.length + " bytes, nc=" + nc + ", ne=" + ne; + } + + /** + * Compares the specified object with this command APDU for equality. + * Returns true if the given object is also a CommandAPDU and its bytes are + * identical to the bytes in this CommandAPDU. + * + * @param obj the object to be compared for equality with this command APDU + * @return true if the specified object is equal to this command APDU + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof CommandAPDU == false) { + return false; + } + CommandAPDU other = (CommandAPDU)obj; + return Arrays.equals(this.apdu, other.apdu); + } + + /** + * Returns the hash code value for this command APDU. + * + * @return the hash code value for this command APDU. + */ + public int hashCode() { + return Arrays.hashCode(apdu); + } + + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, ClassNotFoundException { + apdu = (byte[])in.readUnshared(); + // initialize transient fields + parse(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ResponseAPDU.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ResponseAPDU.java new file mode 100644 index 000000000..c822dc07c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/smartcardio/ResponseAPDU.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.sufficientlysecure.keychain.securitytoken.smartcardio; + +import java.util.Arrays; + +/** + * A response APDU as defined in ISO/IEC 7816-4. It consists of a conditional + * body and a two byte trailer. + * This class does not attempt to verify that the APDU encodes a semantically + * valid response. + * + *

Instances of this class are immutable. Where data is passed in or out + * via byte arrays, defensive cloning is performed. + * + * @see CommandAPDU + * @see CardChannel#transmit CardChannel.transmit + * + * @since 1.6 + * @author Andreas Sterbenz + * @author JSR 268 Expert Group + */ +public final class ResponseAPDU implements java.io.Serializable { + + private static final long serialVersionUID = 6962744978375594225L; + + /** @serial */ + private byte[] apdu; + + /** + * Constructs a ResponseAPDU from a byte array containing the complete + * APDU contents (conditional body and trailed). + * + *

Note that the byte array is cloned to protect against subsequent + * modification. + * + * @param apdu the complete response APDU + * + * @throws NullPointerException if apdu is null + * @throws IllegalArgumentException if apdu.length is less than 2 + */ + public ResponseAPDU(byte[] apdu) { + apdu = apdu.clone(); + check(apdu); + this.apdu = apdu; + } + + private static void check(byte[] apdu) { + if (apdu.length < 2) { + throw new IllegalArgumentException("apdu must be at least 2 bytes long"); + } + } + + /** + * Returns the number of data bytes in the response body (Nr) or 0 if this + * APDU has no body. This call is equivalent to + * getData().length. + * + * @return the number of data bytes in the response body or 0 if this APDU + * has no body. + */ + public int getNr() { + return apdu.length - 2; + } + + /** + * Returns a copy of the data bytes in the response body. If this APDU as + * no body, this method returns a byte array with a length of zero. + * + * @return a copy of the data bytes in the response body or the empty + * byte array if this APDU has no body. + */ + public byte[] getData() { + byte[] data = new byte[apdu.length - 2]; + System.arraycopy(apdu, 0, data, 0, data.length); + return data; + } + + /** + * Returns the value of the status byte SW1 as a value between 0 and 255. + * + * @return the value of the status byte SW1 as a value between 0 and 255. + */ + public int getSW1() { + return apdu[apdu.length - 2] & 0xff; + } + + /** + * Returns the value of the status byte SW2 as a value between 0 and 255. + * + * @return the value of the status byte SW2 as a value between 0 and 255. + */ + public int getSW2() { + return apdu[apdu.length - 1] & 0xff; + } + + /** + * Returns the value of the status bytes SW1 and SW2 as a single + * status word SW. + * It is defined as + * (getSW1() << 8) | getSW2(). + * + * @return the value of the status word SW. + */ + public int getSW() { + return (getSW1() << 8) | getSW2(); + } + + /** + * Returns a copy of the bytes in this APDU. + * + * @return a copy of the bytes in this APDU. + */ + public byte[] getBytes() { + return apdu.clone(); + } + + /** + * Returns a string representation of this response APDU. + * + * @return a String representation of this response APDU. + */ + public String toString() { + return "ResponseAPDU: " + apdu.length + " bytes, SW=" + + Integer.toHexString(getSW()); + } + + /** + * Compares the specified object with this response APDU for equality. + * Returns true if the given object is also a ResponseAPDU and its bytes are + * identical to the bytes in this ResponseAPDU. + * + * @param obj the object to be compared for equality with this response APDU + * @return true if the specified object is equal to this response APDU + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof ResponseAPDU == false) { + return false; + } + ResponseAPDU other = (ResponseAPDU)obj; + return Arrays.equals(this.apdu, other.apdu); + } + + /** + * Returns the hash code value for this response APDU. + * + * @return the hash code value for this response APDU. + */ + public int hashCode() { + return Arrays.hashCode(apdu); + } + + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, ClassNotFoundException { + apdu = (byte[])in.readUnshared(); + check(apdu); + } + +}