commit 6d37b980cdeefd410eddd7011c4d6ba1a8f3a36c Author: Joeri Date: Mon Sep 12 11:32:57 2016 +0200 Moved repository to Git and fixed support for adding keys diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..8a08e1e --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e660fd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/.jcop b/.jcop new file mode 100644 index 0000000..51d5d6a --- /dev/null +++ b/.jcop @@ -0,0 +1 @@ +1.0D276000124011.0D2760001240102000000000000010000 \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..12fdca4 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + OpenPGP JavaCard + + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.ibm.bluez.jcop.eclipse.jcopbuilder + + + + + + org.eclipse.jdt.core.javanature + com.ibm.bluez.jcop.eclipse.jcopnature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..a1c38f1 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.1 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.3 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=ignore +org.eclipse.jdt.core.compiler.source=1.3 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..aa13a97 --- /dev/null +++ b/build.properties @@ -0,0 +1,17 @@ +project.name=OpenPGP JavaCard + +base.dir=. +bin.dir=bin +lib.dir=lib +src.dir=src + +jar.jctasks=jctasks.jar +jar.gpj=gpj.jar + +cap.package=openpgpcard +cap.package_aid=0xD2:0x76:0x00:0x01:0x24:0x01 +cap.applet=openpgpcard.OpenPGPApplet +cap.applet_aid=0xD2:0x76:0x00:0x01:0x24:0x01:0x02:0x00:0x00:0x00:0x00:0x00:0x00:0x01:0x00:0x00 + +gpj.sd_aid=0xA0:0x00:0x00:0x00:0x03:0x00:0x00:0x00 + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..388688c --- /dev/null +++ b/build.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/api.jar b/lib/api.jar new file mode 100644 index 0000000..e8ea0ad Binary files /dev/null and b/lib/api.jar differ diff --git a/lib/api_export_files/java/io/javacard/io.exp b/lib/api_export_files/java/io/javacard/io.exp new file mode 100644 index 0000000..931133a Binary files /dev/null and b/lib/api_export_files/java/io/javacard/io.exp differ diff --git a/lib/api_export_files/java/lang/javacard/lang.exp b/lib/api_export_files/java/lang/javacard/lang.exp new file mode 100644 index 0000000..f349818 Binary files /dev/null and b/lib/api_export_files/java/lang/javacard/lang.exp differ diff --git a/lib/api_export_files/java/rmi/javacard/rmi.exp b/lib/api_export_files/java/rmi/javacard/rmi.exp new file mode 100644 index 0000000..209cdf3 Binary files /dev/null and b/lib/api_export_files/java/rmi/javacard/rmi.exp differ diff --git a/lib/api_export_files/javacard/framework/javacard/framework.exp b/lib/api_export_files/javacard/framework/javacard/framework.exp new file mode 100644 index 0000000..7deb667 Binary files /dev/null and b/lib/api_export_files/javacard/framework/javacard/framework.exp differ diff --git a/lib/api_export_files/javacard/framework/service/javacard/service.exp b/lib/api_export_files/javacard/framework/service/javacard/service.exp new file mode 100644 index 0000000..8fbef2f Binary files /dev/null and b/lib/api_export_files/javacard/framework/service/javacard/service.exp differ diff --git a/lib/api_export_files/javacard/security/javacard/security.exp b/lib/api_export_files/javacard/security/javacard/security.exp new file mode 100644 index 0000000..ece3b83 Binary files /dev/null and b/lib/api_export_files/javacard/security/javacard/security.exp differ diff --git a/lib/api_export_files/javacardx/crypto/javacard/crypto.exp b/lib/api_export_files/javacardx/crypto/javacard/crypto.exp new file mode 100644 index 0000000..3df48b1 Binary files /dev/null and b/lib/api_export_files/javacardx/crypto/javacard/crypto.exp differ diff --git a/lib/converter.jar b/lib/converter.jar new file mode 100644 index 0000000..a9ad89d Binary files /dev/null and b/lib/converter.jar differ diff --git a/lib/gpj.jar b/lib/gpj.jar new file mode 100644 index 0000000..64b16cb Binary files /dev/null and b/lib/gpj.jar differ diff --git a/lib/jctasks.jar b/lib/jctasks.jar new file mode 100644 index 0000000..496e90c Binary files /dev/null and b/lib/jctasks.jar differ diff --git a/lib/offcardverifier.jar b/lib/offcardverifier.jar new file mode 100644 index 0000000..2369c14 Binary files /dev/null and b/lib/offcardverifier.jar differ diff --git a/src/openpgpcard/OpenPGPApplet.java b/src/openpgpcard/OpenPGPApplet.java new file mode 100644 index 0000000..b989f49 --- /dev/null +++ b/src/openpgpcard/OpenPGPApplet.java @@ -0,0 +1,1397 @@ +/** + * Java Card implementation of the OpenPGP card + * Copyright (C) 2011 Joeri de Ruiter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package openpgpcard; + +import javacard.framework.*; +import javacard.security.*; +import javacardx.crypto.*; + +/** + * AID of the applet should be according to the OpenPGP card standard v2.0.1 + * E.g. D2760001240102000000000000010000: + * D276000124 - RID of FSFE + * 01 - OpenPGP application + * Version needs to be higher than 0.07, otherwise tags for fingerprints are of by one in gpg + * 0200 - Version + * 0000 - Manufacturer + * 00000001 - Serial number + * 0000 - RFU + * + * @author Joeri de Ruiter (joeri@cs.ru.nl) + * @version $Revision: 13 $ by $Author: joeridr $ + * $LastChangedDate: 2015-04-13 16:02:31 +0200 (Mon, 13 Apr 2015) $ + */ +public class OpenPGPApplet extends Applet implements ISO7816 { + //TODO Check atomicity of all storage commands + + private static final short _0 = 0; + + private static final boolean FORCE_SM_GET_CHALLENGE = true; + + private static final byte[] HISTORICAL = { 0x00, 0x73, 0x00, 0x00, + (byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + + private static final byte[] EXTENDED_CAP = { + (byte) 0xF0, // Support for GET CHALLENGE + // Support for Key Import + // PW1 Status byte changeable + 0x00, // Secure messaging using 3DES + 0x00, (byte) 0xFF, // Maximum length of challenges + 0x00, (byte) 0xFF, // Maximum length Cardholder Certificate + 0x00, (byte) 0xFF, // Maximum length command data + 0x00, (byte) 0xFF // Maximum length response data + }; + + private static short RESPONSE_MAX_LENGTH = 255; + private static short RESPONSE_SM_MAX_LENGTH = 231; + private static short CHALLENGES_MAX_LENGTH = 255; + + private static short BUFFER_MAX_LENGTH = 674; + + private static short LOGINDATA_MAX_LENGTH = 254; + private static short URL_MAX_LENGTH = 254; + private static short NAME_MAX_LENGTH = 39; + private static short LANG_MAX_LENGTH = 8; + private static short CERT_MAX_LENGTH = 500; + + private static byte PW1_MIN_LENGTH = 6; + private static byte PW1_MAX_LENGTH = 127; + // Default PW1 '123456' + private static byte[] PW1_DEFAULT = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; + private static byte PW1_MODE_NO81 = 0; + private static byte PW1_MODE_NO82 = 0; + + private static final byte RC_MIN_LENGTH = 8; + private static final byte RC_MAX_LENGTH = 127; + + private static final byte PW3_MIN_LENGTH = 8; + private static final byte PW3_MAX_LENGTH = 127; + // Default PW3 '12345678' + private static final byte[] PW3_DEFAULT = { 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38 }; + + private byte[] loginData = new byte[LOGINDATA_MAX_LENGTH]; + private short loginData_length = 0; + + private byte[] url = new byte[URL_MAX_LENGTH]; + private short url_length = 0; + + private byte[] name = new byte[NAME_MAX_LENGTH]; + private short name_length = 0; + + private byte[] lang = new byte[LANG_MAX_LENGTH]; + private short lang_length = 0; + + private byte[] cert = new byte[CERT_MAX_LENGTH]; + private short cert_length = 0; + + private byte sex = 0x39; + + private OwnerPIN pw1; + private byte pw1_length = 0; + private byte pw1_status = 0x00; + private boolean[] pw1_modes; + + private OwnerPIN rc; + private byte rc_length = 0; + + private OwnerPIN pw3; + private byte pw3_length = 0; + + private byte[] ds_counter = { 0x00, 0x00, 0x00 }; + + private PGPKey sig_key; + private PGPKey dec_key; + private PGPKey auth_key; + + private byte[] ca1_fp = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + private byte[] ca2_fp = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + private byte[] ca3_fp = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + + private Cipher cipher; + private RandomData random; + + private byte[] tmp; + + private byte[] buffer; + private short out_left = 0; + private short out_sent = 0; + private short in_received = 0; + + private boolean chain = false; + private byte chain_ins = 0; + private short chain_p1p2 = 0; + + private OpenPGPSecureMessaging sm; + private boolean sm_success = false; + + public static void install(byte[] bArray, short bOffset, byte bLength) { + new OpenPGPApplet().register(bArray, (short) (bOffset + 1), + bArray[bOffset]); + } + + public OpenPGPApplet() { + // Create temporary arrays + tmp = JCSystem.makeTransientByteArray(BUFFER_MAX_LENGTH, + JCSystem.CLEAR_ON_DESELECT); + buffer = JCSystem.makeTransientByteArray(BUFFER_MAX_LENGTH, + JCSystem.CLEAR_ON_DESELECT); + pw1_modes = JCSystem.makeTransientBooleanArray((short) 2, + JCSystem.CLEAR_ON_DESELECT); + + // Initialize PW1 with default password + pw1 = new OwnerPIN((byte) 3, PW1_MAX_LENGTH); + pw1.update(PW1_DEFAULT, _0, (byte) PW1_DEFAULT.length); + pw1_length = (byte) PW1_DEFAULT.length; + + // Initialize RC + rc = new OwnerPIN((byte) 3, RC_MAX_LENGTH); + + // Initialize PW3 with default password + pw3 = new OwnerPIN((byte) 3, PW3_MAX_LENGTH); + pw3.update(PW3_DEFAULT, _0, (byte) PW3_DEFAULT.length); + pw3_length = (byte) PW3_DEFAULT.length; + + // Create empty keys + sig_key = new PGPKey(); + dec_key = new PGPKey(); + auth_key = new PGPKey(); + + // + cipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false); + random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + + // Initialize Secure Messaging + sm = new OpenPGPSecureMessaging(); + } + + public void process(APDU apdu) { + if (selectingApplet()) { + // Reset PW1 modes + pw1_modes[PW1_MODE_NO81] = false; + pw1_modes[PW1_MODE_NO82] = false; + + return; + } + + byte[] buf = apdu.getBuffer(); + byte cla= buf[OFFSET_CLA]; + byte ins = buf[OFFSET_INS]; + byte p1 = buf[OFFSET_P1]; + byte p2 = buf[OFFSET_P2]; + short p1p2 = Util.makeShort(p1, p2); + short lc = (short) (buf[OFFSET_LC] & 0xFF); + + // Secure messaging + //TODO Force SM if contactless is used + sm_success = false; + if ((byte) (cla & (byte) 0x0C) == (byte) 0x0C) { + // Force initialization of SSC before using SM to prevent replays + if(FORCE_SM_GET_CHALLENGE && !sm.isSetSSC() && (ins != (byte) 0x84)) ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + lc = sm.unwrapCommandAPDU(); + sm_success = true; + } + + short status = SW_NO_ERROR; + short le = 0; + + try { + // Support for command chaining + commandChaining(apdu); + + // Reset buffer for GET RESPONSE + if (ins != (byte) 0xC0) { + out_sent = 0; + out_left = 0; + } + + // Other instructions + switch (ins) { + // GET RESPONSE + case (byte) 0xC0: + // Will be handled in finally clause + break; + + // VERIFY + case (byte) 0x20: + verify(apdu, p2); + break; + + // CHANGE REFERENCE DATA + case (byte) 0x24: + changeReferenceData(apdu, p2); + break; + + // RESET RETRY COUNTER + case (byte) 0x2C: + // Reset only available for PW1 + if (p2 != (byte) 0x81) + ISOException.throwIt(SW_INCORRECT_P1P2); + + resetRetryCounter(apdu, p1); + break; + + // PERFORM SECURITY OPERATION + case (byte) 0x2A: + // COMPUTE DIGITAL SIGNATURE + if (p1p2 == (short) 0x9E9A) { + le = computeDigitalSignature(apdu); + } + // DECIPHER + else if (p1p2 == (short) 0x8086) { + le = decipher(apdu); + } else { + ISOException.throwIt(SW_WRONG_P1P2); + } + + break; + + // INTERNAL AUTHENTICATE + case (byte) 0x88: + le = internalAuthenticate(apdu); + break; + + // GENERATE ASYMMETRIC KEY PAIR + case (byte) 0x47: + le = genAsymKey(apdu, p1); + break; + + // GET CHALLENGE + case (byte) 0x84: + le = getChallenge(apdu, lc); + break; + + // GET DATA + case (byte) 0xCA: + le = getData(p1p2); + break; + + // PUT DATA + case (byte) 0xDA: + putData(p1p2); + break; + + // DB - PUT DATA (Odd) + case (byte) 0xDB: + // Odd PUT DATA only supported for importing keys + // 4D - Extended Header list + if (p1p2 == (short) 0x3FFF) { + importKey(apdu); + } else { + ISOException.throwIt(SW_RECORD_NOT_FOUND); + } + break; + + default: + // good practice: If you don't know the INStruction, say so: + ISOException.throwIt(SW_INS_NOT_SUPPORTED); + } + } + catch(ISOException e) { + status = e.getReason(); + } + finally { + if(status != (short)0x9000) { + // Send the exception that was thrown + sendException(apdu, status); + } + else { + // GET RESPONSE + if (ins == (byte) 0xC0) { + sendNext(apdu); + } + else { + sendBuffer(apdu, le); + } + } + } + } + + /** + * Provide support for command chaining by storing the received data in + * buffer + * + * @param apdu + */ + private void commandChaining(APDU apdu) { + byte[] buf = apdu.getBuffer(); + short p1p2 = Util.makeShort(buf[OFFSET_P1], + buf[OFFSET_P2]); + short len = (short) (buf[OFFSET_LC] & 0xFF); + + // Reset chaining if it was not yet initiated + if (!chain) + resetChaining(); + + if ((byte) (buf[OFFSET_CLA] & (byte) 0x10) == (byte) 0x10) { + // If chaining was already initiated, INS and P1P2 should match + if (chain + && (buf[OFFSET_INS] != chain_ins && p1p2 != chain_p1p2)) { + resetChaining(); + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + } + + // Check whether data to be received is larger than size of the + // buffer + if ((short) (in_received + len) > BUFFER_MAX_LENGTH) { + resetChaining(); + ISOException.throwIt(SW_WRONG_LENGTH); + } + + // Store received data in buffer + in_received = Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, + buffer, in_received, len); + + chain = true; + chain_ins = buf[OFFSET_INS]; + chain_p1p2 = p1p2; + + ISOException.throwIt(SW_NO_ERROR); + } + + if (chain && buf[OFFSET_INS] == chain_ins && p1p2 == chain_p1p2) { + chain = false; + + // Check whether data to be received is larger than size of the + // buffer + if ((short) (in_received + len) > BUFFER_MAX_LENGTH) { + resetChaining(); + ISOException.throwIt(SW_WRONG_LENGTH); + } + + // Add received data to the buffer + in_received = Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, + buffer, in_received, len); + } else if (chain) { + // Chained command expected + resetChaining(); + ISOException.throwIt(SW_UNKNOWN); + } else { + // No chaining was used, so copy data to buffer + in_received = Util.arrayCopyNonAtomic(buf, OFFSET_CDATA, + buffer, _0, len); + } + } + + private void resetChaining() { + chain = false; + in_received = 0; + } + + /** + * Provide the VERIFY command (INS 20) + * + * Verify one of the passwords depending on mode: - 81: PW1 for a PSO:CDS + * command - 82: PW1 for other commands - 83: PW3 + * + * @param apdu + * @param mode + * Password and mode to be verified + */ + private void verify(APDU apdu, byte mode) { + if (mode == (byte) 0x81 || mode == (byte) 0x82) { + // Check length of input + if (in_received < PW1_MIN_LENGTH || in_received > PW1_MAX_LENGTH) + ISOException.throwIt(SW_WRONG_LENGTH); + + // Check given PW1 and set requested mode if verified succesfully + if (pw1.check(buffer, _0, (byte) in_received)) { + if (mode == (byte) 0x81) + pw1_modes[PW1_MODE_NO81] = true; + else + pw1_modes[PW1_MODE_NO82] = true; + } else { + ISOException + .throwIt((short) (0x63C0 | pw1.getTriesRemaining())); + } + } else if (mode == (byte) 0x83) { + // Check length of input + if (in_received < PW3_MIN_LENGTH || in_received > PW3_MAX_LENGTH) + ISOException.throwIt(SW_WRONG_LENGTH); + + // Check PW3 + if (!pw3.check(buffer, _0, (byte) in_received)) { + ISOException + .throwIt((short) (0x63C0 | pw3.getTriesRemaining())); + } + } else { + ISOException.throwIt(SW_INCORRECT_P1P2); + } + } + + /** + * Provide the CHANGE REFERENCE DATA command (INS 24) + * + * Change the password specified using mode: - 81: PW1 - 82: PW3 + * + * @param apdu + * @param mode + * Password to be changed + */ + private void changeReferenceData(APDU apdu, byte mode) { + if (mode == (byte) 0x81) { + // Check length of the new password + short new_length = (short) (in_received - pw1_length); + if (new_length < PW1_MIN_LENGTH || new_length > PW1_MAX_LENGTH) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + if (!pw1.check(buffer, _0, pw1_length)) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + // Change PW1 + JCSystem.beginTransaction(); + pw1.update(buffer, pw1_length, (byte) new_length); + pw1_length = (byte) new_length; + pw1_modes[PW1_MODE_NO81] = false; + pw1_modes[PW1_MODE_NO82] = false; + JCSystem.commitTransaction(); + } else if (mode == (byte) 0x83) { + // Check length of the new password + short new_length = (short) (in_received - pw3_length); + if (new_length < PW3_MIN_LENGTH || new_length > PW3_MAX_LENGTH) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + if (!pw3.check(buffer, _0, pw3_length)) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + // Change PW3 + JCSystem.beginTransaction(); + pw3.update(buffer, pw3_length, (byte) new_length); + pw3_length = (byte) new_length; + JCSystem.commitTransaction(); + } + } + + /** + * Provide the RESET RETRY COUNTER command (INS 2C) + * + * Reset PW1 either using the Resetting Code (mode = 00) or PW3 (mode = 02) + * + * @param apdu + * @param mode + * Mode used to reset PW1 + */ + private void resetRetryCounter(APDU apdu, byte mode) { + if (mode == (byte) 0x00) { + // Authentication using RC + if (rc_length == 0) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + short new_length = (short) (in_received - rc_length); + if (new_length < PW1_MIN_LENGTH || new_length > PW1_MAX_LENGTH) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + if (!rc.check(buffer, _0, rc_length)) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + // Change PW1 + JCSystem.beginTransaction(); + pw1.update(buffer, rc_length, (byte) new_length); + pw1_length = (byte) new_length; + JCSystem.commitTransaction(); + } else if (mode == (byte) 0x02) { + // Authentication using PW3 + if (!pw3.isValidated()) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + if (in_received < PW1_MIN_LENGTH || in_received > PW1_MAX_LENGTH) + ISOException.throwIt(SW_WRONG_LENGTH); + + // Change PW1 + JCSystem.beginTransaction(); + pw1.update(buffer, _0, (byte) in_received); + pw1_length = (byte) in_received; + JCSystem.commitTransaction(); + } else { + ISOException.throwIt(SW_WRONG_P1P2); + } + } + + /** + * Provide the PSO: COMPUTE DIGITAL SIGNATURE command (INS 2A, P1P2 9E9A) + * + * Sign the data provided using the key for digital signatures. + * + * Before using this method PW1 has to be verified with mode No. 81. If the + * first status byte of PW1 is 00, access condition PW1 with No. 81 is + * reset. + * + * @param apdu + * @return Length of data written in buffer + */ + private short computeDigitalSignature(APDU apdu) { + if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO81])) + ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); + + if (pw1_status == (byte) 0x00) + pw1_modes[PW1_MODE_NO81] = false; + + if (!sig_key.getPrivate().isInitialized()) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + // Copy data to be signed to tmp + short length = Util + .arrayCopyNonAtomic(buffer, _0, tmp, _0, in_received); + + cipher.init(sig_key.getPrivate(), Cipher.MODE_ENCRYPT); + increaseDSCounter(); + + return cipher.doFinal(tmp, _0, length, buffer, _0); + } + + /** + * Provide the PSO: DECIPHER command (INS 2A, P1P2 8086) + * + * Decrypt the data provided using the key for confidentiality. + * + * Before using this method PW1 has to be verified with mode No. 82. + * + * @param apdu + * @return Length of data written in buffer + */ + private short decipher(APDU apdu) { + // DECIPHER + if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO82])) + ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); + if (!dec_key.getPrivate().isInitialized()) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + // Copy data to be decrypted to tmp, omit padding indicator + short length = Util.arrayCopyNonAtomic(buffer, (short) 1, tmp, _0, + (short) (in_received - 1)); + + cipher.init(dec_key.getPrivate(), Cipher.MODE_DECRYPT); + + return cipher.doFinal(tmp, _0, length, buffer, _0); + } + + /** + * Provide the INTERNAL AUTHENTICATE command (INS 88) + * + * Sign the data provided using the key for authentication. Before using + * this method PW1 has to be verified with mode No. 82. + * + * @param apdu + * @return Length of data written in buffer + */ + private short internalAuthenticate(APDU apdu) { + if (!(pw1.isValidated() && pw1_modes[PW1_MODE_NO82])) + ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); + Util.arrayCopyNonAtomic(buffer, _0, tmp, _0, in_received); + + if (!auth_key.getPrivate().isInitialized()) + ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED); + + cipher.init(auth_key.getPrivate(), Cipher.MODE_ENCRYPT); + return cipher.doFinal(tmp, _0, in_received, buffer, _0); + } + + /** + * Provide the GENERATE ASYMMETRIC KEY PAIR command (INS 47) + * + * For mode 80, generate a new key pair, specified in the first element of + * buffer, and output the public key. + * + * For mode 81, output the public key specified in the first element of + * buffer. + * + * Before using this method PW3 has to be verified. + * + * @param apdu + * @param mode + * Generate key pair (80) or read public key (81) + * @return Length of data written in buffer + */ + private short genAsymKey(APDU apdu, byte mode) { + PGPKey key = getKey(buffer[0]); + + if (mode == (byte) 0x80) { + if (!pw3.isValidated()) + ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); + + // TODO Usage of transaction resultsin SW 6F00 on new cards + JCSystem.beginTransaction(); + key.genKeyPair(); + + if (buffer[0] == (byte) 0xB6) { + // Reset signature counter + for(short i = 0; i < ds_counter.length; i++) { + ds_counter[i] = (byte) 0; + } + } + JCSystem.commitTransaction(); + } + + // Output requested key + return sendPublicKey(key); + } + + /** + * Provide the GET CHALLENGE command (INS 84) + * + * Generate a random number of the length given in len. + * + * @param apdu + * @param len + * Length of the requested challenge + * @return Length of data written in buffer + */ + private short getChallenge(APDU apdu, short len) { + if (len > CHALLENGES_MAX_LENGTH) + ISOException.throwIt(SW_WRONG_LENGTH); + + random.generateData(buffer, _0, len); + + // Set the SSC used in Secure Messaging if the size of the requested + // challenge is equal to the size of the SSC + if(len == sm.getSSCSize()) { + sm.setSSC(buffer, _0); + } + + return len; + } + + /** + * Provide the GET DATA command (INS CA) + * + * Output the data specified with tag. + * + * @param apdu + * @param tag + * Tag of the requested data + */ + private short getData(short tag) { + short offset = 0; + + switch (tag) { + // 4F - Application identifier (AID) + case (short) 0x004F: + return JCSystem.getAID().getBytes(buffer, _0); + + // 5E - Login data + case (short) 0x005E: + return Util.arrayCopyNonAtomic(loginData, _0, buffer, _0, loginData_length); + + // 5F50 - URL + case (short) 0x5F50: + return Util.arrayCopyNonAtomic(url, _0, buffer, _0, url_length); + + // 5F52 - Historical bytes + case (short) 0x5F52: + return Util.arrayCopyNonAtomic(HISTORICAL, _0, buffer, _0, (short)HISTORICAL.length); + + // 65 - Cardholder Related Data + case (short) 0x0065: + buffer[offset++] = 0x65; + buffer[offset++] = 0x00; + + // 5B - Name + buffer[offset++] = 0x5B; + buffer[offset++] = (byte) name_length; + offset = Util.arrayCopyNonAtomic(name, _0, buffer, offset, + name_length); + + // 5F2D - Language + buffer[offset++] = 0x5F; + buffer[offset++] = 0x2D; + buffer[offset++] = (byte) lang_length; + offset = Util.arrayCopyNonAtomic(lang, _0, buffer, offset, + lang_length); + + // 5F35 - Sex + buffer[offset++] = 0x5F; + buffer[offset++] = 0x35; + buffer[offset++] = 0x01; + buffer[offset++] = sex; + + // Set length for combined data + buffer[1] = (byte) (offset - 2); + + return offset; + + // 6E - Application Related Data + case (short) 0x006E: + buffer[offset++] = 0x6E; + // Total length assumed to be >= 128 and < 256 + buffer[offset++] = (byte) 0x81; + buffer[offset++] = 0; + + // 4F - AID + buffer[offset++] = 0x4F; + byte len = JCSystem.getAID().getBytes(buffer, (short)(offset + 1)); + buffer[offset++] = len; + offset += len; + + // 5F52 - Historical bytes + buffer[offset++] = 0x5F; + buffer[offset++] = 0x52; + buffer[offset++] = (byte) HISTORICAL.length; + offset = Util.arrayCopyNonAtomic(HISTORICAL, _0, buffer, offset, + (short) HISTORICAL.length); + + // 73 - Discretionary data objects + buffer[offset++] = 0x73; + buffer[offset++] = 0x00; + + // C0 - Extended capabilities + buffer[offset++] = (byte) 0xC0; + buffer[offset++] = (byte) EXTENDED_CAP.length; + offset = Util.arrayCopyNonAtomic(EXTENDED_CAP, _0, buffer, offset, + (short) EXTENDED_CAP.length); + + // C1 - Algorithm attributes signature + buffer[offset++] = (byte) 0xC1; + buffer[offset++] = (byte) 0x06; + offset = sig_key.getAttributes(buffer, offset); + + // C2 - Algorithm attributes decryption + buffer[offset++] = (byte) 0xC2; + buffer[offset++] = (byte) 0x06; + offset = dec_key.getAttributes(buffer, offset); + + // C3 - Algorithm attributes authentication + buffer[offset++] = (byte) 0xC3; + buffer[offset++] = (byte) 0x06; + offset = auth_key.getAttributes(buffer, offset); + + // C4 - PW1 Status bytes + buffer[offset++] = (byte) 0xC4; + buffer[offset++] = 0x07; + buffer[offset++] = pw1_status; + buffer[offset++] = PW1_MAX_LENGTH; + buffer[offset++] = RC_MAX_LENGTH; + buffer[offset++] = PW3_MAX_LENGTH; + buffer[offset++] = pw1.getTriesRemaining(); + buffer[offset++] = rc.getTriesRemaining(); + buffer[offset++] = pw3.getTriesRemaining(); + + // C5 - Fingerprints sign, dec and auth keys + buffer[offset++] = (byte) 0xC5; + buffer[offset++] = (short) 60; + offset = sig_key.getFingerprint(buffer, offset); + offset = dec_key.getFingerprint(buffer, offset); + offset = auth_key.getFingerprint(buffer, offset); + + // C6 - Fingerprints CA 1, 2 and 3 + buffer[offset++] = (byte) 0xC6; + buffer[offset++] = (short) 60; + offset = Util.arrayCopyNonAtomic(ca1_fp, _0, buffer, offset, + (short) 20); + offset = Util.arrayCopyNonAtomic(ca2_fp, _0, buffer, offset, + (short) 20); + offset = Util.arrayCopyNonAtomic(ca3_fp, _0, buffer, offset, + (short) 20); + + // CD - Generation times of public key pair + buffer[offset++] = (byte) 0xCD; + buffer[offset++] = (short) 12; + offset = sig_key.getTime(buffer, offset); + offset = dec_key.getTime(buffer, offset); + offset = auth_key.getTime(buffer, offset); + + // Set length of combined data + buffer[2] = (byte) (offset - 3); + + return offset; + + // 7A - Security support template + case (short) 0x007A: + buffer[offset++] = 0x7A; + buffer[offset++] = (byte) 0x05; + + // 93 - Digital signature counter + buffer[offset++] = (byte) 0x93; + buffer[offset++] = 0x03; + offset = Util.arrayCopyNonAtomic(ds_counter, _0, buffer, offset, + (short) 3); + + return offset; + + // 7F21 - Cardholder Certificate + case (short) 0x7F21: + // Use buffer since certificate may be longer than + // RESPONSE_MAX_LENGTH + buffer[offset++] = 0x7F; + buffer[offset++] = 0x21; + + if (cert_length < 128) { + buffer[offset++] = (byte) cert_length; + } else if (cert_length < 256) { + buffer[offset++] = (byte) 0x81; + buffer[offset++] = (byte) cert_length; + } else { + buffer[offset++] = (byte) 0x82; + Util.setShort(buffer, offset, cert_length); + offset += 2; + } + + offset = Util.arrayCopyNonAtomic(cert, _0, buffer, offset, + cert_length); + + return offset; + + // C4 - PW Status Bytes + case (short) 0x00C4: + buffer[offset++] = pw1_status; + buffer[offset++] = PW1_MAX_LENGTH; + buffer[offset++] = RC_MAX_LENGTH; + buffer[offset++] = PW3_MAX_LENGTH; + buffer[offset++] = pw1.getTriesRemaining(); + buffer[offset++] = rc.getTriesRemaining(); + buffer[offset++] = pw3.getTriesRemaining(); + + return offset; + + default: + ISOException.throwIt(SW_RECORD_NOT_FOUND); + } + + return offset; + } + + /** + * Provide the PUT DATA command (INS DA) + * + * Write the data specified using tag. + * + * Before using this method PW3 has to be verified. + * + * @param apdu + * @param tag + * Tag of the requested data + */ + private void putData(short tag) { + if (!pw3.isValidated()) + ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); + + switch (tag) { + // 5B - Name + case (short) 0x005B: + if (in_received > name.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + name_length = Util.arrayCopy(buffer, _0, name, _0, in_received); + break; + + // 5E - Login data + case (short) 0x005E: + if (in_received > loginData.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + loginData_length = Util.arrayCopy(buffer, _0, loginData, _0, + in_received); + break; + + // 5F2D - Language preferences + case (short) 0x5F2D: + if (in_received > lang.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + lang_length = Util.arrayCopy(buffer, _0, lang, _0, in_received); + break; + + // 5F35 - Sex + case (short) 0x5F35: + if (in_received != 1) + ISOException.throwIt(SW_WRONG_LENGTH); + + // Check for valid values + if (buffer[0] != (byte) 0x31 && buffer[0] != (byte) 0x32 + && buffer[0] != (byte) 0x39) + ISOException.throwIt(SW_WRONG_DATA); + + sex = buffer[0]; + break; + + // 5F50 - URL + case (short) 0x5F50: + if (in_received > url.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + url_length = Util.arrayCopy(buffer, _0, url, _0, in_received); + break; + + // 7F21 - Cardholder certificate + case (short) 0x7F21: + if (in_received > cert.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + cert_length = Util.arrayCopy(buffer, _0, cert, _0, in_received); + break; + + // C4 - PW Status Bytes + case (short) 0x00C4: + if (in_received != 1) + ISOException.throwIt(SW_WRONG_LENGTH); + + // Check for valid values + if (buffer[0] != (byte) 0x00 && buffer[0] != (byte) 0x01) + ISOException.throwIt(SW_WRONG_DATA); + + pw1_status = buffer[0]; + break; + + // C7 - Fingerprint signature key + case (short) 0x00C7: + if (in_received != PGPKey.FP_SIZE) + ISOException.throwIt(SW_WRONG_LENGTH); + + sig_key.setFingerprint(buffer, _0); + break; + + // C8 - Fingerprint decryption key + case (short) 0x00C8: + if (in_received != PGPKey.FP_SIZE) + ISOException.throwIt(SW_WRONG_LENGTH); + + dec_key.setFingerprint(buffer, _0); + break; + + // C9 - Fingerprint authentication key + case (short) 0x00C9: + if (in_received != PGPKey.FP_SIZE) + ISOException.throwIt(SW_WRONG_LENGTH); + + auth_key.setFingerprint(buffer, _0); + break; + + // CA - Fingerprint Certification Authority 1 + case (short) 0x00CA: + if (in_received != ca1_fp.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + Util.arrayCopy(buffer, _0, ca1_fp, _0, in_received); + break; + + // CB - Fingerprint Certification Authority 2 + case (short) 0x00CB: + if (in_received != ca2_fp.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + Util.arrayCopy(buffer, _0, ca2_fp, _0, in_received); + break; + + // CC - Fingerprint Certification Authority 3 + case (short) 0x00CC: + if (in_received != ca3_fp.length) + ISOException.throwIt(SW_WRONG_LENGTH); + + Util.arrayCopy(buffer, _0, ca3_fp, _0, in_received); + break; + + // CE - Signature key generation date/time + case (short) 0x00CE: + if (in_received != 4) + ISOException.throwIt(SW_WRONG_LENGTH); + + sig_key.setTime(buffer, _0); + break; + + // CF - Decryption key generation date/time + case (short) 0x00CF: + if (in_received != 4) + ISOException.throwIt(SW_WRONG_LENGTH); + + dec_key.setTime(buffer, _0); + break; + + // D0 - Authentication key generation date/time + case (short) 0x00D0: + if (in_received != 4) + ISOException.throwIt(SW_WRONG_LENGTH); + + auth_key.setTime(buffer, _0); + break; + + // D3 - Resetting Code + case (short) 0x00D3: + if (in_received == 0) { + rc_length = 0; + } else if (in_received >= RC_MIN_LENGTH + && in_received <= RC_MAX_LENGTH) { + JCSystem.beginTransaction(); + rc.update(buffer, _0, (byte) in_received); + rc_length = (byte) in_received; + JCSystem.commitTransaction(); + } else { + ISOException.throwIt(SW_WRONG_LENGTH); + } + break; + + // D1 - SM-Key-ENC + case (short) 0x00D1: + sm.setSessionKeyEncryption(buffer, _0); + break; + + // D2 - SM-Key-MAC + case (short) 0x00D2: + sm.setSessionKeyMAC(buffer, _0); + break; + + // F4 - SM-Key-Container + case (short) 0x00F4: + short offset = 0; + short key_len = 0; + // Set encryption key + if(buffer[offset++] == (byte)0xD1) { + key_len = (short)(buffer[offset++] & 0x7F); + sm.setSessionKeyEncryption(buffer, offset); + offset += key_len; + } + + // Set MAC key + if(buffer[offset++] == (byte)0xD2) { + key_len = (short)(buffer[offset++] & 0x7F); + sm.setSessionKeyMAC(buffer, offset); + offset += key_len; + } + break; + + default: + ISOException.throwIt(SW_RECORD_NOT_FOUND); + break; + } + } + + /** + * EXPERIMENTAL: Provide functionality for importing keys. + * + * @param apdu + */ + private void importKey(APDU apdu) { + // TODO The following code still has to be tested + if (!pw3.isValidated()) + ISOException.throwIt(SW_SECURITY_STATUS_NOT_SATISFIED); + + short offset = 0; + + // Check for tag 4D + if (buffer[offset++] != 0x4D) + ISOException.throwIt(SW_DATA_INVALID); + + // Length of 4D + offset += getLengthBytes(getLength(buffer, offset)); + + // Get key for Control Reference Template + PGPKey key = getKey(buffer[offset++]); + + // Skip empty length of CRT + offset++; + + // Check for tag 7F48 + if (!(buffer[offset++] == 0x7F && buffer[offset++] == 0x48)) + ISOException.throwIt(SW_DATA_INVALID); + short len_template = getLength(buffer, offset); + offset += getLengthBytes(len_template); + + short offset_data = (short) (offset + len_template); + + if (buffer[offset++] != (byte) 0x91) + ISOException.throwIt(SW_DATA_INVALID); + short len_e = getLength(buffer, offset); + offset += getLengthBytes(len_e); + + if (buffer[offset++] != (byte) 0x92) + ISOException.throwIt(SW_DATA_INVALID); + short len_p = getLength(buffer, offset); + offset += getLengthBytes(len_p); + + if (buffer[offset++] != (byte) 0x93) + ISOException.throwIt(SW_DATA_INVALID); + short len_q = getLength(buffer, offset); + offset += getLengthBytes(len_q); + + if (buffer[offset++] != (byte) 0x94) + ISOException.throwIt(SW_DATA_INVALID); + short len_pq = getLength(buffer, offset); + offset += getLengthBytes(len_pq); + + if (buffer[offset++] != (byte) 0x95) + ISOException.throwIt(SW_DATA_INVALID); + short len_dp1 = getLength(buffer, offset); + offset += getLengthBytes(len_dp1); + + if (buffer[offset++] != (byte) 0x96) + ISOException.throwIt(SW_DATA_INVALID); + short len_dq1 = getLength(buffer, offset); + offset += getLengthBytes(len_dq1); + + if (!(buffer[offset_data++] == 0x5F && buffer[offset_data++] == 0x48)) + ISOException.throwIt(SW_DATA_INVALID); + offset_data += getLengthBytes(getLength(buffer, offset_data)); + + // TODO Check value of e + offset_data += len_e; + + key.setP(buffer, offset_data, len_p); + offset_data += len_p; + + key.setQ(buffer, offset_data, len_q); + offset_data += len_q; + + key.setPQ(buffer, offset_data, len_pq); + offset_data += len_pq; + + key.setDP1(buffer, offset_data, len_dp1); + offset_data += len_dp1; + + key.setDQ1(buffer, offset_data, len_dq1); + offset_data += len_dq1; + } + + /** + * Output the public key of the given key pair. + * + * @param apdu + * @param key + * Key pair containing public key to be output + */ + private short sendPublicKey(PGPKey key) { + RSAPublicKey pubkey = key.getPublic(); + + // Build message in tmp + short offset = 0; + + // 81 - Modulus + tmp[offset++] = (byte) 0x81; + + // Length of modulus is always greater than 128 bytes + if (key.getModulusLength() < 256) { + tmp[offset++] = (byte) 0x81; + tmp[offset++] = (byte) key.getModulusLength(); + } else { + tmp[offset++] = (byte) 0x82; + offset = Util.setShort(tmp, offset, key.getModulusLength()); + } + pubkey.getModulus(tmp, offset); + offset += key.getModulusLength(); + + // 82 - Exponent + tmp[offset++] = (byte) 0x82; + tmp[offset++] = (byte) key.getExponentLength(); + pubkey.getExponent(tmp, offset); + offset += key.getExponentLength(); + + short len = offset; + + offset = 0; + + buffer[offset++] = 0x7F; + buffer[offset++] = 0x49; + + if (len < 256) { + buffer[offset++] = (byte) 0x81; + buffer[offset++] = (byte) len; + } else { + buffer[offset++] = (byte) 0x82; + offset = Util.setShort(buffer, offset, len); + } + + offset = Util.arrayCopyNonAtomic(tmp, _0, buffer, offset, len); + + return offset; + } + + /** + * Send len bytes from buffer. If len is greater than RESPONSE_MAX_LENGTH, + * remaining data can be retrieved using GET RESPONSE. + * + * @param apdu + * @param len + * The byte length of the data to send + */ + private void sendBuffer(APDU apdu, short len) { + out_sent = 0; + out_left = len; + sendNext(apdu); + } + + /** + * Send provided status + * + * @param apdu + * @param status Status to send + */ + private void sendException(APDU apdu, short status) { + out_sent = 0; + out_left = 0; + sendNext(apdu, status); + } + + /** + * Send next block of data in buffer. Used for sending data in + * + * @param apdu + */ + private void sendNext(APDU apdu) { + sendNext(apdu, SW_NO_ERROR); + } + + /** + * Send next block of data in buffer. Used for sending data in + * + * @param apdu + * @param status Status to send + */ + private void sendNext(APDU apdu, short status) { + byte[] buf = APDU.getCurrentAPDUBuffer(); + apdu.setOutgoing(); + + // Determine maximum size of the messages + short max_length; + if(sm_success) { + max_length = RESPONSE_SM_MAX_LENGTH; + } + else { + max_length = RESPONSE_MAX_LENGTH; + } + + Util.arrayCopyNonAtomic(buffer, out_sent, buf, _0, max_length); + + short len = 0; + + if (out_left > max_length) { + len = max_length; + + // Compute byte left and sent + out_left -= max_length; + out_sent += max_length; + + // Determine new status word + if (out_left > max_length) { + status = (short) (SW_BYTES_REMAINING_00 | max_length); + } else { + status = (short) (SW_BYTES_REMAINING_00 | out_left); + } + } + else { + len = out_left; + + // Reset buffer + out_sent = 0; + out_left = 0; + } + + // If SM is used, wrap response + if(sm_success) { + len = sm.wrapResponseAPDU(buf, _0, len, status); + } + + // Send data in buffer + apdu.setOutgoingLength(len); + apdu.sendBytes(_0, len); + + // Send status word + if(status != SW_NO_ERROR) + ISOException.throwIt(status); + } + + /** + * Get length of TLV element. + * + * @param data + * Byte array + * @param offset + * Offset within byte array containing first byte + * @return Length of value + */ + private short getLength(byte[] data, short offset) { + short len = 0; + + if ((data[offset] & (byte) 0x80) == (byte) 0x00) { + len = data[offset]; + } else if ((data[offset] & (byte) 0x7F) == (byte) 0x01) { + len = (short)(0xFF & data[(short) (offset + 1)]); + } else if ((data[offset] & (byte) 0x7F) == (byte) 0x02) { + len = Util.makeShort(data[(short) (offset + 1)], data[(short) (offset + 2)]); + } else { + ISOException.throwIt(SW_UNKNOWN); + } + + return len; + } + + /** + * Get number of bytes needed to represent length for TLV element. + * + * @param length + * Length of value + * @return Number of bytes needed to represent length + */ + private short getLengthBytes(short length) { + if (length <= 127) + return 1; + else if (length <= 255) + return 2; + else + return 3; + } + + /** + * Return the key of the type requested: - B6: Digital signatures - B8: + * Confidentiality - A4: Authentication + * + * @param type + * Type of key to be returned + * @return Key of requested type + */ + private PGPKey getKey(byte type) { + PGPKey key = sig_key; + + if (type == (byte) 0xB6) + key = sig_key; + else if (type == (byte) 0xB8) + key = dec_key; + else if (type == (byte) 0xA4) + key = auth_key; + else + ISOException.throwIt(SW_UNKNOWN); + + return key; + } + + /** + * Increase the digital signature counter by one. In case of overflow + * SW_WARNING_STATE_UNCHANGED will be thrown and nothing will + * change. + */ + private void increaseDSCounter() { + for (short i = (short) (ds_counter.length - 1); i >= 0; i--) { + if ((short) (ds_counter[i] & 0xFF) >= 0xFF) { + if (i == 0) { + // Overflow + ISOException.throwIt(SW_WARNING_STATE_UNCHANGED); + } else { + ds_counter[i] = 0; + } + } else { + ds_counter[i]++; + break; + } + } + } +} diff --git a/src/openpgpcard/OpenPGPSecureMessaging.java b/src/openpgpcard/OpenPGPSecureMessaging.java new file mode 100644 index 0000000..f05c50e --- /dev/null +++ b/src/openpgpcard/OpenPGPSecureMessaging.java @@ -0,0 +1,415 @@ +/** + * Java Card implementation of the OpenPGP card + * + * Copyright (C) 2011 Joeri de Ruiter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * OpenPGPSecureMessaging.java is based on OVSecureMessaging.java which is part + * of OVchip-ng + * + * Copyright (C) Pim Vullers, Radboud University Nijmegen, February 2011. + */ + +package openpgpcard; + +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.DESKey; +import javacard.security.KeyBuilder; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** + * OV secure messaging functionality. + * + *

OVSecureMessaging is based on PassportCrypto which is part of the + * e-passport Java Card applet from the JMRTD project (http://jmrtd.org/). + * + * @author Pim Vullers + * @version $Revision: 12 $ by $Author: joeridr $ + * $LastChangedDate: 2012-02-23 15:31:33 +0100 (Thu, 23 Feb 2012) $ + */ +public class OpenPGPSecureMessaging { + private static final short SW_INTERNAL_ERROR = (short) 0x6D66; + private static final byte[] PAD_DATA = {(byte) 0x80, 0, 0, 0, 0, 0, 0, 0}; + private static final short SSC_SIZE = 8; + private static final short TMP_SIZE = 256; + private static final short MAC_SIZE = 8; + private static final short KEY_SIZE = 16; + private static final byte[] EMPTY_KEY = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /** + * The needed cryptographic functionality. + */ + private Signature signer; + private Signature verifier; + private Cipher cipher; + private Cipher decipher; + + /** + * The needed keys. + */ + private DESKey keyMAC; + private DESKey keyENC; + + /** + * The send sequence counter. + */ + private byte[] ssc; + + /** + * Storage for temporary data. + */ + private byte[] tmp; + + private boolean[] ssc_set; + + /** + * Construct a new secure messaging wrapper. + */ + public OpenPGPSecureMessaging() { + ssc = JCSystem.makeTransientByteArray(SSC_SIZE, + JCSystem.CLEAR_ON_DESELECT); + tmp = JCSystem.makeTransientByteArray(TMP_SIZE, + JCSystem.CLEAR_ON_DESELECT); + signer = Signature.getInstance( + Signature.ALG_DES_MAC8_ISO9797_1_M2_ALG3, false); + verifier = Signature.getInstance( + Signature.ALG_DES_MAC8_ISO9797_1_M2_ALG3, false); + cipher = Cipher.getInstance( + Cipher.ALG_DES_CBC_ISO9797_M2, false); + decipher = Cipher.getInstance( + Cipher.ALG_DES_CBC_ISO9797_M2, false); + + keyMAC = (DESKey) KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_DESELECT, + KeyBuilder.LENGTH_DES3_2KEY, false); + keyENC = (DESKey) KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_DESELECT, + KeyBuilder.LENGTH_DES3_2KEY, false); + + ssc_set = JCSystem.makeTransientBooleanArray((short)1, JCSystem.CLEAR_ON_DESELECT); + ssc_set[0] = false; + } + + /** + * Set the MAC and encryption (and decryption) session keys. Each key is a + * 16 byte 3DES EDE key. This method may be called at any time and will + * immediately replace the session key. + * + * @param buffer byte array containing the session keys. + * @param offset location of the session keys in the buffer. + */ + public void setSessionKeys(byte[] buffer, short offset) { + // Check for empty keys + if(Util.arrayCompare(buffer, (short)0, EMPTY_KEY, (short)0, KEY_SIZE) == 0 || + Util.arrayCompare(buffer, KEY_SIZE, EMPTY_KEY, (short)0, KEY_SIZE) == 0) { + keyMAC.clearKey(); + keyENC.clearKey(); + } + else { + keyMAC.setKey(buffer, offset); + keyENC.setKey(buffer, (short) (offset + KEY_SIZE)); + + signer.init(keyMAC, Signature.MODE_SIGN); + verifier.init(keyMAC, Signature.MODE_VERIFY); + + cipher.init(keyENC, Cipher.MODE_ENCRYPT); + decipher.init(keyENC, Cipher.MODE_DECRYPT); + } + } + + /** + * Set the MAC session key. Each key is a 16 byte 3DES EDE key. This method + * may be called at any time and will immediately replace the session key. + * + * @param buffer byte array containing the session key. + * @param offset location of the session key in the buffer. + */ + public void setSessionKeyMAC(byte[] buffer, short offset) { + // Check for empty keys + if(Util.arrayCompare(buffer, (short)0, EMPTY_KEY, (short)0, KEY_SIZE) == 0) { + keyMAC.clearKey(); + keyENC.clearKey(); + } + else { + keyMAC.setKey(buffer, offset); + + signer.init(keyMAC, Signature.MODE_SIGN); + verifier.init(keyMAC, Signature.MODE_VERIFY); + } + } + + /** + * Set the encryption session key. Each key is a 16 byte 3DES EDE key. This method + * may be called at any time and will immediately replace the session key. + * + * @param buffer byte array containing the session key. + * @param offset location of the session key in the buffer. + */ + public void setSessionKeyEncryption(byte[] buffer, short offset) { + // Check for empty keys + if(Util.arrayCompare(buffer, (short)0, EMPTY_KEY, (short)0, KEY_SIZE) == 0) { + keyMAC.clearKey(); + keyENC.clearKey(); + } + else { + keyENC.setKey(buffer, (short) (offset + KEY_SIZE)); + + cipher.init(keyENC, Cipher.MODE_ENCRYPT); + decipher.init(keyENC, Cipher.MODE_DECRYPT); + } + } + + /** + * Set the MAC and encryption (and decryption) 3DES session keys to zero. + */ + public void clearSessionKeys() { + keyMAC.clearKey(); + keyENC.clearKey(); + } + + /** + * Unwraps (verify and decrypt) the command APDU located in the APDU buffer. + * The command buffer has to be filled by the APDU.setIncomingAndReceive() + * method beforehand. The verified and decrypted command data get placed at + * the start of the APDU buffer. + * + * @return the length value encoded by DO97, 0 if this object is missing. + */ + public short unwrapCommandAPDU() { + byte[] buf = APDU.getCurrentAPDUBuffer(); + short apdu_p = (short) (ISO7816.OFFSET_CDATA & 0xff); + short start_p = apdu_p; + short le = 0; + short do87DataLen = 0; + short do87Data_p = 0; + short do87LenBytes = 0; + short hdrLen = 4; + short hdrPadLen = (short) (8 - hdrLen); + + incrementSSC(); + + if (buf[apdu_p] == (byte) 0x87) { + apdu_p++; + // do87 + if ((buf[apdu_p] & 0xff) > 0x80) { + do87LenBytes = (short) (buf[apdu_p] & 0x7f); + apdu_p++; + } else { + do87LenBytes = 1; + } + if (do87LenBytes > 2) { // sanity check + ISOException.throwIt(SW_INTERNAL_ERROR); + } + for (short i = 0; i < do87LenBytes; i++) { + do87DataLen += (short) ((buf[(short)(apdu_p + i)] & 0xff) << (short) ((do87LenBytes - 1 - i) * 8)); + } + apdu_p += do87LenBytes; + + if (buf[apdu_p] != 1) { + ISOException.throwIt(SW_INTERNAL_ERROR); + } + // store pointer to data and defer decrypt to after mac check (do8e) + do87Data_p = (short) (apdu_p + 1); + apdu_p += do87DataLen; + do87DataLen--; // compensate for 0x01 marker + } + + if (buf[apdu_p] == (byte) 0x97) { + // do97 + if (buf[++apdu_p] != 1) + ISOException.throwIt(SW_INTERNAL_ERROR); + le = (short) (buf[++apdu_p] & 0xff); + apdu_p++; + } + + // do8e + if (buf[apdu_p] != (byte) 0x8e) { + ISOException.throwIt(SW_INTERNAL_ERROR); + } + if (buf[++apdu_p] != 8) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + // verify mac + verifier.update(ssc, (short)0, (short)ssc.length); + verifier.update(buf, (short)0, hdrLen); + verifier.update(PAD_DATA, (short)0, hdrPadLen); + if (!verifier.verify(buf, start_p, (short) (apdu_p - 1 - start_p), buf, + (short)(apdu_p + 1), MAC_SIZE)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + + short lc = 0; + if (do87DataLen != 0) { + // decrypt data, and leave room for lc + lc = decipher.doFinal(buf, do87Data_p, do87DataLen, buf, + (short) (hdrLen + 1)); + buf[hdrLen] = (byte) (lc & 0xff); + } + + return le; + } + + /** + * Wraps (encrypts and build MAC) the response data and places it in the + * APDU buffer starting at offset 0. The buffer can be any buffer including + * the APDU buffer itself. If the length is zero the buffer will not be + * addressed and no response data will be present in the wrapped output. + * + * @param buffer byte array containing the data which needs to be wrapped. + * @param offset location of the data in the buffer. + * @param length of the data in the buffer (in bytes). + * @param status word which has to be wrapped in the response APDU. + * @return the length of the wrapped data in the buffer + */ + public short wrapResponseAPDU(byte[] buffer, short offset, short length, + short status) { + byte[] apdu = APDU.getCurrentAPDUBuffer(); + short apdu_p = 0; + // smallest multiple of 8 strictly larger than plaintextLen (length + padding) + short do87DataLen = (short) ((((short) (length + 8)) / 8) * 8); + // for 0x01 marker (indicating padding is used) + do87DataLen++; + short do87DataLenBytes = (short)(do87DataLen > 0xff? 2 : 1); + short do87HeaderBytes = getApduBufferOffset(length); + short do87Bytes = (short)(do87HeaderBytes + do87DataLen - 1); // 0x01 is counted twice + boolean hasDo87 = length > 0; + + incrementSSC(); + + short ciphertextLength=0; + if(hasDo87) { + // Copy the plain text to temporary buffer to avoid data corruption. + Util.arrayCopyNonAtomic(buffer, offset, tmp, (short) 0, length); + // Put the cipher text in the proper position. + ciphertextLength = cipher.doFinal(tmp, (short) 0, length, apdu, + do87HeaderBytes); + } + //sanity check + //note that this check + // (possiblyPaddedPlaintextLength != (short)(do87DataLen -1)) + //does not always hold because some algs do the padding in the final, some in the init. + if (hasDo87 && (((short) (do87DataLen - 1) != ciphertextLength))) + ISOException.throwIt(SW_INTERNAL_ERROR); + + if (hasDo87) { + // build do87 + apdu[apdu_p++] = (byte) 0x87; + if(do87DataLen < 0x80) { + apdu[apdu_p++] = (byte)do87DataLen; + } else { + apdu[apdu_p++] = (byte) (0x80 + do87DataLenBytes); + for(short i = (short) (do87DataLenBytes - 1); i >= 0; i--) { + apdu[apdu_p++] = (byte) ((do87DataLen >>> (i * 8)) & 0xff); + } + } + apdu[apdu_p++] = 0x01; + } + + if(hasDo87) { + apdu_p = do87Bytes; + } + + // build do99 + apdu[apdu_p++] = (byte) 0x99; + apdu[apdu_p++] = 0x02; + Util.setShort(apdu, apdu_p, status); + apdu_p += 2; + + // calculate and write mac + signer.update(ssc, (short) 0, (short) ssc.length); + signer.sign(apdu, (short) 0, apdu_p, apdu, (short) (apdu_p + 2)); + + // write do8e + apdu[apdu_p++] = (byte) 0x8e; + apdu[apdu_p++] = 0x08; + apdu_p += 8; // for mac written earlier + + return apdu_p; + } + + /** + * Increment the send sequence counter. + */ + private void incrementSSC() { + if (ssc == null || ssc.length <= 0) { + return; + } + + for (short s = (short) (ssc.length - 1); s >= 0; s--) { + if ((short) ((ssc[s] & 0xff) + 1) > 0xff) { + ssc[s] = 0; + } else { + ssc[s]++; + break; + } + } + } + + /*** + * Get the amount of space to reserve in the buffer when using secure + * messaging. + * + * @param length length of plain text in which this offset depends. + * @return the amount of space to reserve. + */ + private short getApduBufferOffset(short length) { + short do87Bytes = 2; // 0x87 length data 0x01 + // smallest multiple of 8 strictly larger than plaintextLen + 1 + // byte is probably the length of the cipher text (including do87 0x01) + short do87DataLen = (short) ((((short) (length + 8) / 8) * 8) + 1); + + if (do87DataLen < 0x80) { + do87Bytes++; + } else if (do87DataLen <= 0xff) { + do87Bytes += 2; + } else { + do87Bytes += (short) (length > 0xff ? 2 : 1); + } + + return do87Bytes; + } + + /** + * Set the SSC + * + * @param buffer byte array containing the SSC + * @param offset location of the data in the buffer + */ + public void setSSC(byte[] buffer, short offset) { + Util.arrayCopy(buffer, offset, ssc, (short)0, SSC_SIZE); + ssc_set[0] = true; + } + + /** + * @return size in bytes of the SSC + */ + public short getSSCSize() { + return SSC_SIZE; + } + + /** + * @return boolean indicating whether SSC has been set + */ + public boolean isSetSSC() { + return ssc_set[0]; + } +} diff --git a/src/openpgpcard/PGPKey.java b/src/openpgpcard/PGPKey.java new file mode 100644 index 0000000..4a2c85e --- /dev/null +++ b/src/openpgpcard/PGPKey.java @@ -0,0 +1,249 @@ +/** + * Java Card implementation of the OpenPGP card + * Copyright (C) 2011 Joeri de Ruiter + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package openpgpcard; + +import javacard.framework.*; +import javacard.security.*; + +/** + * @author Joeri de Ruiter (joeri@cs.ru.nl) + * @version $Revision: 12 $ by $Author: joeridr $ + * $LastChangedDate: 2012-02-23 15:31:33 +0100 (Thu, 23 Feb 2012) $ + */ +public class PGPKey implements ISO7816 { + public static final short KEY_SIZE = 2048;// 2368; + public static final short KEY_SIZE_BYTES = KEY_SIZE / 8; + public static final short EXPONENT_SIZE = 17; + public static final short EXPONENT_SIZE_BYTES = 3; + public static final short FP_SIZE = 20; + + private KeyPair key; + private byte[] fp; + private byte[] time = { 0x00, 0x00, 0x00, 0x00 }; + private byte[] attributes = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x02 }; + + public PGPKey() { + key = new KeyPair(KeyPair.ALG_RSA_CRT, KEY_SIZE); + + fp = new byte[FP_SIZE]; + Util.arrayFillNonAtomic(fp, (short) 0, (short) fp.length, (byte) 0); + + Util.setShort(attributes, (short) 1, KEY_SIZE); + Util.setShort(attributes, (short) 3, EXPONENT_SIZE); + } + + /** + * Generate the key pair. + */ + public void genKeyPair() { + key.genKeyPair(); + } + + /** + * Set the fingerprint for the public key. + * + * @param data + * Byte array + * @param offset + * Offset within byte array containing first byte + */ + public void setFingerprint(byte[] data, short offset) { + // Check whether there are enough bytes to copy + if ((short) (offset + fp.length) > data.length) + ISOException.throwIt(SW_UNKNOWN); + + Util.arrayCopy(data, offset, fp, (short) 0, (short) fp.length); + } + + /** + * Set the generation time for the key pair. + * + * @param data + * Byte array + * @param offset + * Offset within byte array containing first byte + */ + public void setTime(byte[] data, short offset) { + // Check whether there are enough bytes to copy + if ((short) (offset + time.length) > data.length) + ISOException.throwIt(SW_UNKNOWN); + + Util.arrayCopy(data, offset, time, (short) 0, (short) 4); + } + + /** + * Get the fingerprint for the public key. + * + * @param data + * Byte array + * @param offset + * Offset within byte array indicating first byte + */ + public short getFingerprint(byte[] data, short offset) { + Util.arrayCopyNonAtomic(fp, (short) 0, data, offset, (short) fp.length); + return (short) (offset + fp.length); + } + + /** + * Get the generation time for the key pair. + * + * @param data + * Byte array + * @param offset + * Offset within byte array indicating first byte + */ + public short getTime(byte[] data, short offset) { + Util.arrayCopyNonAtomic(time, (short) 0, data, offset, + (short) time.length); + return (short) (offset + time.length); + } + + /** + * Get the algorithm attributes for the key pair. + * + * @param data + * Byte array + * @param offset + * Offset within byte array indicating first byte + */ + public short getAttributes(byte[] data, short offset) { + Util.arrayCopyNonAtomic(attributes, (short) 0, data, offset, + (short) attributes.length); + return (short) (offset + attributes.length); + } + + /** + * @return Public key of the key pair + */ + public RSAPublicKey getPublic() { + return (RSAPublicKey) key.getPublic(); + } + + /** + * @return Private key of the key pair + */ + public RSAPrivateCrtKey getPrivate() { + return (RSAPrivateCrtKey) key.getPrivate(); + } + + /** + * @return Length in bytes of the exponent + */ + public short getExponentLength() { + // Fixed value of 65537 for exponent + return EXPONENT_SIZE_BYTES; + } + + /** + * @return Length in bytes of the modulus + */ + public short getModulusLength() { + return KEY_SIZE_BYTES; + } + + /** + * Sets the value of the DP1 parameter. The plain text data format is + * big-endian and right-aligned (the least significant bit is the least + * significant bit of last byte). Input DP1 parameter data is copied into + * the internal representation. + * + * @param buffer + * The input buffer + * @param offset + * The offset into the input buffer at which the parameter value + * begins + * @param length + * The length of the parameter + */ + public void setDP1(byte[] buffer, short offset, short length) { + ((RSAPrivateCrtKey) key.getPrivate()).setDP1(buffer, offset, length); + } + + /** + * Sets the value of the DQ1 parameter. The plain text data format is + * big-endian and right-aligned (the least significant bit is the least + * significant bit of last byte). Input DQ1 parameter data is copied into + * the internal representation. + * + * @param buffer + * The input buffer + * @param offset + * The offset into the input buffer at which the parameter value + * begins + * @param length + * The length of the parameter + */ + public void setDQ1(byte[] buffer, short offset, short length) { + ((RSAPrivateCrtKey) key.getPrivate()).setDQ1(buffer, offset, length); + } + + /** + * Sets the value of the P parameter. The plain text data format is + * big-endian and right-aligned (the least significant bit is the least + * significant bit of last byte). Input P parameter data is copied into the + * internal representation. + * + * @param buffer + * The input buffer + * @param offset + * The offset into the input buffer at which the parameter value + * begins + * @param length + * The length of the parameter + */ + public void setP(byte[] buffer, short offset, short length) { + ((RSAPrivateCrtKey) key.getPrivate()).setP(buffer, offset, length); + } + + /** + * Sets the value of the PQ parameter. The plain text data format is + * big-endian and right-aligned (the least significant bit is the least + * significant bit of last byte). Input PQ parameter data is copied into the + * internal representation. + * + * @param buffer + * The input buffer + * @param offset + * The offset into the input buffer at which the parameter value + * begins + * @param length + * The length of the parameter + */ + public void setPQ(byte[] buffer, short offset, short length) { + ((RSAPrivateCrtKey) key.getPrivate()).setPQ(buffer, offset, length); + } + + /** + * Sets the value of the Q parameter. The plain text data format is + * big-endian and right-aligned (the least significant bit is the least + * significant bit of last byte). Input Q parameter data is copied into the + * internal representation. + * + * @param buffer + * The input buffer + * @param offset + * The offset into the input buffer at which the parameter value + * begins + * @param length + * The length of the parameter + */ + public void setQ(byte[] buffer, short offset, short length) { + ((RSAPrivateCrtKey) key.getPrivate()).setQ(buffer, offset, length); + } +}