commit 93f814548ca9bbebbf768ed52a51399926ce0847 Author: schertz123456 Date: Thu Aug 6 20:25:45 2009 +0000 Initial commit. git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@1 96b47cad-a561-4643-ad3b-153ac7d7599c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2f7c4af --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +SUBDIRS = ccid virtualsmartcard + +default: all + +%: + for d in $(SUBDIRS); do $(MAKE) $@ -C $$d; done diff --git a/ccid/AUTHORS b/ccid/AUTHORS new file mode 100644 index 0000000..e520634 --- /dev/null +++ b/ccid/AUTHORS @@ -0,0 +1 @@ +Frank Morgner diff --git a/ccid/COPYING b/ccid/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/ccid/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. diff --git a/ccid/INSTALL b/ccid/INSTALL new file mode 100644 index 0000000..258b63a --- /dev/null +++ b/ccid/INSTALL @@ -0,0 +1,14 @@ +Installation is simple; type `make install`. If you want to customize the +compilation or install process, you will need to pass variables to make. + + DESTDIR prefix for installation + LIBPCSCLITE_CFLAGS compiler flags for pcsclite usage (needed if no + pkg-config is available) + +See the Makefile for more details. + +Example: + make DESTDIR=/tmp/stage install + +Note: If you want to uninstall the binary, you need to pass the same DESTDIR +to make, for example `make DESTDIR=/tmp/stage uninstall`. diff --git a/ccid/Makefile b/ccid/Makefile new file mode 100644 index 0000000..95fc782 --- /dev/null +++ b/ccid/Makefile @@ -0,0 +1,46 @@ +MAKEFLAGS += -rR --no-print-directory + +# Directories +prefix = +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin + + +# Compiler +CC = gcc +CFLAGS = -Wall -g +LIBPCSCLITE_CFLAGS = `pkg-config --cflags --libs libpcsclite` +PTHREAD_CFLAGS = -pthread + +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) + + +TARGETS = ccid + +# top-level rule +all: $(TARGETS) + + +ccid: ccid.h ccid.c usbstring.c usbstring.h + $(CC) $(LIBPCSCLITE_CFLAGS) $(PTHREAD_CFLAGS) $(CFLAGS) usbstring.c ccid.c -o $@ + + +install: $(TARGETS) installdirs + $(INSTALL_PROGRAM) ccid $(DESTDIR)$(bindir) + +.PHONY: installdirs +installdirs: + $(INSTALL) -d $(DESTDIR)$(bindir) + +.PHONY: install-strip +install-strip: + $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install + +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)$(bindir)/ccid + +.PHONY: clean +clean: + rm -f ccid diff --git a/ccid/README b/ccid/README new file mode 100644 index 0000000..73cf7ec --- /dev/null +++ b/ccid/README @@ -0,0 +1,26 @@ +Title : ccid +Authors : Frank Morgner +Version : 1.0.0 27 Jul 2009 +License : See file COPYING +Tested Platforms : Linux 2.6 (Debian, Ubuntu, Om 2008) + +Welcome to ccid. The purpose of ccid is to forward a PCSC smartcard reader +as a standard USB CCID reader. If the host system is in USB device mode, ccid +forwards the local reader via USB to an other device. If in USB host mode, +ccid virtually plugges in a USB CCID reader to the host system. + +ccid is implemented using GadgetFS. The source code is based on the GadgetFS +example at http://www.linux-usb.org/gadget/. + + +INSTALLATION +------------ + +See file INSTALL. + + +QUESTIONS +--------- + +For questions, please email me at: +Frank Morgner diff --git a/ccid/ccid.bb b/ccid/ccid.bb new file mode 100644 index 0000000..f6ba604 --- /dev/null +++ b/ccid/ccid.bb @@ -0,0 +1,32 @@ +DESCRIPTION = "Virtual Smartcard with PCSC Driver and CCID to PCSC Gadget" +LICENSE = "GPL" + +DEPENDS = "pcsc-lite linux-libc-headers" +RDEPENDS = "pcsc-lite kernel-module-gadgetfs" + +SRC_URI = "svn://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard;module=ccid;proto=https;rev=1" + +S = "${WORKDIR}" + +LIBPCSCLITE_CFLAGS = -I${STAGING_INCDIR}/PCSC -lpcsclite +PTHREAD_CFLAGS = -pthread +# FIXME: let openmoko set the right STAGING_KERNEL_DIR +STAGING_KERNEL_DIR = ${STAGING_DIR}/${MACHINE}-angstrom-${TARGET_OS}/kernel +GADGETFS_CFLAGS = -I${STAGING_KERNEL_DIR}/include + +INSTALL = install +INSTALL_PROGRAM = ${INSTALL} + +FILES_${PN} = "\ + ${bindir}/* \ +" + +do_compile() { + ${CC} ${S}/ccid/ccid.c ${S}/ccid/usbstring.c -o ${S}/ccid/ccid \ + ${LIBPCSCLITE_CFLAGS} ${GADGETFS_CFLAGS} ${PTHREAD_CFLAGS} ${CFLAGS} +} + +do_install() { + ${INSTALL} -d ${D}${bindir} + ${INSTALL_PROGRAM} ${S}/ccid/ccid ${D}${bindir} +} diff --git a/ccid/ccid.c b/ccid/ccid.c new file mode 100644 index 0000000..bf1311c --- /dev/null +++ b/ccid/ccid.c @@ -0,0 +1,1790 @@ +/* + * Copyright (C) 2009 Frank Morgner + * + * This file is part of ccid. + * + * ccid 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 3 of the License, or (at your option) any later + * version. + * + * ccid 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 + * ccid. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +//#include + +#ifdef AIO +/* this aio code works with libaio-0.3.106 */ +#include +#endif + +#include "usbstring.h" +#include "ccid.h" + + +#define DRIVER_VENDOR_NUM 0x08E6 /* FIXME */ +#define DRIVER_ISO_PRODUCT_NUM 0x8000 /* FIXME */ +#define DRIVER_PRODUCT_NUM 0x8000 /* FIXME */ +static int vendorid = DRIVER_VENDOR_NUM; +static int productid = DRIVER_PRODUCT_NUM; +static int verbose = 0; +static int debug = 0; +static int dohid = 0; +static int doint = 0; + + +/* NOTE: these IDs don't imply endpoint numbering; host side drivers + * should use endpoint descriptors, or perhaps bcdDevice, to configure + * such things. Other product IDs could have different policies. + */ + +/*-------------------------------------------------------------------------*/ + +/* these descriptors are modified based on what controller we find */ + +#define STRINGID_MFGR 1 +#define STRINGID_PRODUCT 2 +#define STRINGID_SERIAL 3 +#define STRINGID_CONFIG 4 +#define STRINGID_INTERFACE 5 +#define STRINGID_HID_INTERFACE 6 + +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + + //.bcdUSB = __constant_cpu_to_le16 (0x0110), + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = __constant_cpu_to_le16 (0x0200), + .bDeviceClass = USB_CLASS_VENDOR_SPEC, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + // .bMaxPacketSize0 ... set by gadgetfs + .idVendor = __constant_cpu_to_le16 (DRIVER_VENDOR_NUM), + .idProduct = __constant_cpu_to_le16 (DRIVER_PRODUCT_NUM), + .iManufacturer = STRINGID_MFGR, + .iProduct = STRINGID_PRODUCT, + .iSerialNumber = STRINGID_SERIAL, + .bNumConfigurations = 1, +}; + +#define MAX_USB_POWER 1 + +#define CONFIG_VALUE 3 + +static struct usb_config_descriptor +config = { + .bLength = sizeof config, + .bDescriptorType = USB_DT_CONFIG, + + /* must compute wTotalLength ... */ + .bNumInterfaces = 1, + .bConfigurationValue = CONFIG_VALUE, + .iConfiguration = STRINGID_CONFIG, + .bmAttributes = USB_CONFIG_ATT_ONE + | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = (MAX_USB_POWER + 1) / 2, +}; + +static struct usb_interface_descriptor +source_sink_intf = { + .bLength = sizeof source_sink_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceClass = USB_CLASS_CSCID, + .iInterface = STRINGID_INTERFACE, +}; +static struct hid_class_descriptor +hid_desc = { + .bLength = sizeof hid_desc, + .bDescriptorType = 0x21, + .bcdHID = __constant_cpu_to_le16(0x101), + .bCountryCode = 0, + .bNumDescriptors = 0, +}; + +/* Full speed configurations are used for full-speed only devices as + * well as dual-speed ones (the only kind with high speed support). + */ + +static struct usb_endpoint_descriptor +fs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* NOTE some controllers may need FS bulk max packet size + * to be smaller. it would be a chip-specific option. + */ + .wMaxPacketSize = __constant_cpu_to_le16 (64), +}; + +static struct usb_endpoint_descriptor +fs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16 (64), +}; + +/* some devices can handle other result packet sizes */ +/*#define STATUS_MAXPACKET 16*/ +/*#define LOG2_STATUS_POLL_MSEC 5*/ +#define STATUS_MAXPACKET 8 +#define LOG2_STATUS_POLL_MSEC 3 + +static struct usb_endpoint_descriptor +fs_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), + .bInterval = (1 << LOG2_STATUS_POLL_MSEC), +}; + +static struct usb_endpoint_descriptor +fs_hid_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), + .bInterval = (1 << LOG2_STATUS_POLL_MSEC), +}; + +static const struct usb_endpoint_descriptor *fs_eps [3] = { + &fs_source_desc, + &fs_sink_desc, + &fs_status_desc, +}; + + +/* High speed configurations are used only in addition to a full-speed + * ones ... since all high speed devices support full speed configs. + * Of course, not all hardware supports high speed configurations. + */ + +static struct usb_endpoint_descriptor +hs_source_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16 (512), +}; + +static struct usb_endpoint_descriptor +hs_sink_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16 (512), + .bInterval = 1, +}; + +static struct usb_endpoint_descriptor +hs_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), + .bInterval = LOG2_STATUS_POLL_MSEC + 3, +}; + +static struct usb_endpoint_descriptor +hs_hid_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), + .bInterval = LOG2_STATUS_POLL_MSEC + 3, +}; + +static const struct usb_endpoint_descriptor *hs_eps [] = { + &hs_source_desc, + &hs_sink_desc, + &hs_status_desc, +}; + + +/*-------------------------------------------------------------------------*/ + +static char serial [64]; + +static struct usb_string stringtab [] = { + { STRINGID_MFGR, "morgner@informatik.hu-berlin.de", }, + { STRINGID_PRODUCT, "CCID to PCSC Gadget", }, + { STRINGID_SERIAL, serial, }, + { STRINGID_CONFIG, "The Configuration", }, + { STRINGID_INTERFACE, "CCID to PCSC", }, + { STRINGID_HID_INTERFACE, "blakeks", }, +}; + +static struct usb_gadget_strings strings = { + .language = 0x0409, /* "en-us" */ + .strings = stringtab, +}; + +/*-------------------------------------------------------------------------*/ + +/* kernel drivers could autoconfigure like this too ... if + * they were willing to waste the relevant code/data space. + */ + +static int HIGHSPEED; +static char *DEVNAME; +static char *EP_IN_NAME, *EP_OUT_NAME, *EP_STATUS_NAME, *EP_HID_STATUS_NAME; + +/* gadgetfs currently has no chunking (or O_DIRECT/zerocopy) support + * to turn big requests into lots of smaller ones; so this is "small". + */ +#define USB_BUFSIZE (7 * 1024) + +static enum usb_device_speed current_speed; + +static inline int min(unsigned a, unsigned b) +{ + return (a < b) ? a : b; +} + +static int autoconfig () +{ + struct stat statb; + + /* NetChip 2280 PCI device or dummy_hcd, high/full speed */ + if (stat (DEVNAME = "net2280", &statb) == 0 || + stat (DEVNAME = "dummy_udc", &statb) == 0) { + HIGHSPEED = 1; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0100), + + fs_source_desc.bEndpointAddress + = hs_source_desc.bEndpointAddress + = USB_DIR_IN | 7; + EP_IN_NAME = "ep-a"; + fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress + = USB_DIR_OUT | 3; + EP_OUT_NAME = "ep-b"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress + = hs_status_desc.bEndpointAddress + = USB_DIR_IN | 11; + EP_STATUS_NAME = "ep-f"; + + if (dohid) { + // TODO EP_HID_STATUS_NAME? + EP_HID_STATUS_NAME = "ep-e"; + fs_hid_status_desc.bEndpointAddress + = hs_hid_status_desc.bEndpointAddress + = USB_DIR_IN | 10; + } + + /* Intel PXA 2xx processor, full speed only */ + } else if (stat (DEVNAME = "pxa2xx_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0101), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 6; + EP_IN_NAME = "ep6in-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 7; + EP_OUT_NAME = "ep7out-bulk"; + + /* using bulk for this since the pxa interrupt endpoints + * always use the no-toggle scheme (discouraged). + */ + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 11; + EP_STATUS_NAME = "ep11in-bulk"; + + if (dohid) { + EP_HID_STATUS_NAME = "ep12in-bulk"; + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 12; + } +#if 0 + /* AMD au1x00 processor, full speed only */ + } else if (stat (DEVNAME = "au1x00_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0102), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; + EP_IN_NAME = "ep2in"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 4; + EP_OUT_NAME = "ep4out"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3in"; + + /* Intel SA-1100 processor, full speed only */ + } else if (stat (DEVNAME = "sa1100", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0103), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; + EP_IN_NAME = "ep2in-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; + EP_OUT_NAME = "ep1out-bulk"; + + source_sink_intf.bNumEndpoints = 2; + ccid_desc.dwFeatures &= ~0x100000; // USB Wake up signaling not supported + EP_STATUS_NAME = 0; +#endif + + /* Toshiba TC86c001 PCI device, full speed only */ + } else if (stat (DEVNAME = "goku_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0104), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; + EP_IN_NAME = "ep2-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; + EP_OUT_NAME = "ep1-bulk"; + + if (dohid) { + source_sink_intf.bNumEndpoints = 2; + // USB Wake up signaling not supported + ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); + + EP_HID_STATUS_NAME = "ep3-bulk"; + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 3; + } else { + source_sink_intf.bNumEndpoints = 3; + + EP_STATUS_NAME = "ep3-bulk"; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + } + + /* Samsung S3C24xx series, full speed only */ + } else if (stat (DEVNAME = "s3c2410_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0110), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; + EP_IN_NAME = "ep2-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; + EP_OUT_NAME = "ep1-bulk"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3-bulk"; + + if (dohid) { + EP_HID_STATUS_NAME = "ep4-bulk"; + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; + } + + /* Renesas SH77xx processors, full speed only */ + } else if (stat (DEVNAME = "sh_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0105), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; + EP_IN_NAME = "ep2in-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; + EP_OUT_NAME = "ep1out-bulk"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3in-bulk"; + + if (dohid) { + EP_HID_STATUS_NAME = "ep4in-bulk"; + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; + } + + /* OMAP 1610 and newer devices, full speed only, fifo mode 0 or 3 */ + } else if (stat (DEVNAME = "omap_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0106), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 1; + EP_IN_NAME = "ep1in-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; + EP_OUT_NAME = "ep2out-bulk"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3in-int"; + + if (dohid) { + EP_HID_STATUS_NAME = "ep4in-bulk"; + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; + } + + /* Something based on Mentor USB Highspeed Dual-Role Controller */ + } else if (stat (DEVNAME = "musb_hdrc", &statb) == 0) { + HIGHSPEED = 1; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0107), + + fs_source_desc.bEndpointAddress + = hs_source_desc.bEndpointAddress + = USB_DIR_IN | 1; + EP_IN_NAME = "ep1in"; + fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress + = USB_DIR_OUT | 1; + EP_OUT_NAME = "ep1out"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress + = hs_status_desc.bEndpointAddress + = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3"; + + /* Atmel AT91 processors, full speed only */ + } else if (stat (DEVNAME = "at91_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0106), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 1; + EP_IN_NAME = "ep1"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; + EP_OUT_NAME = "ep2"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3-int"; + + if (dohid) { + EP_HID_STATUS_NAME = "ep4"; + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; + } + + /* Sharp LH740x processors, full speed only */ + } else if (stat (DEVNAME = "lh740x_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0106), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 1; + EP_IN_NAME = "ep1in-bulk"; + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; + EP_OUT_NAME = "ep2out-bulk"; + + if (dohid) { + source_sink_intf.bNumEndpoints = 2; + // USB Wake up signaling not supported + ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); + + fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_HID_STATUS_NAME = "ep3in-int"; + } else { + source_sink_intf.bNumEndpoints = 3; + + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3in-int"; + } + + /* Atmel AT32AP700x processors, high/full speed */ + } else if (stat (DEVNAME = "atmel_usba_udc", &statb) == 0) { + HIGHSPEED = 1; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0108); + + fs_source_desc.bEndpointAddress + = hs_source_desc.bEndpointAddress + = USB_DIR_IN | 1; + EP_IN_NAME = "ep1in-bulk"; + fs_sink_desc.bEndpointAddress + = hs_sink_desc.bEndpointAddress + = USB_DIR_OUT | 2; + EP_OUT_NAME = "ep2out-bulk"; + + if (dohid) { + source_sink_intf.bNumEndpoints = 2; + // USB Wake up signaling not supported + ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); + + fs_hid_status_desc.bEndpointAddress + = hs_hid_status_desc.bEndpointAddress + = USB_DIR_IN | 3; + EP_HID_STATUS_NAME = "ep3in-int"; + } else { + source_sink_intf.bNumEndpoints = 3; + + fs_status_desc.bEndpointAddress + = hs_status_desc.bEndpointAddress + = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3in-int"; + } + + } else { + DEVNAME = 0; + return -ENODEV; + } + if (!doint) { + source_sink_intf.bNumEndpoints = 2; + // USB Wake up signaling not supported + ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); + } + return 0; +} + +#ifdef AIO + +static int iso; +static int interval; +static unsigned iosize; +static unsigned bufsize = USB_BUFSIZE; + +/* This is almost the only place where usb needs to know whether we're + * driving an isochronous stream or a bulk one. + */ +static int iso_autoconfig () +{ + struct stat statb; + + /* ISO endpoints "must not be part of a default interface setting". + * Never do it like this in "real" code! This uses the default + * setting (alt 0) because it's the only one pxa supports. + * + * This code doesn't adjust the sample rate based on feedback. + */ + device_desc.idProduct = __constant_cpu_to_le16(DRIVER_ISO_PRODUCT_NUM); + + /* NetChip 2280 PCI device or dummy_hcd, high/full speed */ + if (stat (DEVNAME = "net2280", &statb) == 0 || + stat (DEVNAME = "dummy_udc", &statb) == 0) { + unsigned bInterval, wMaxPacketSize; + + HIGHSPEED = 1; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0100); + + /* this code won't use two or four uframe periods */ + if (bufsize > 1024) { + interval = 0; + bInterval = 1; + /* "modprobe net2280 fifo_mode=1" may be needed */ + if (bufsize > (2 * 1024)) { + wMaxPacketSize = min ((bufsize + 2)/3, 1024); + bufsize = min (3 * wMaxPacketSize, bufsize); + wMaxPacketSize |= 2 << 11; + } else { + wMaxPacketSize = min ((bufsize + 1)/2, 1024); + wMaxPacketSize |= 1 << 11; + } + } else { + bInterval = interval + 4; + wMaxPacketSize = bufsize; + } + + fs_source_desc.bEndpointAddress + = hs_source_desc.bEndpointAddress + = USB_DIR_IN | 7; + fs_source_desc.bmAttributes + = hs_source_desc.bmAttributes + = USB_ENDPOINT_XFER_ISOC; + fs_source_desc.wMaxPacketSize = min (bufsize, 1023); + hs_source_desc.wMaxPacketSize = wMaxPacketSize; + fs_source_desc.bInterval = interval + 1; + hs_source_desc.bInterval = bInterval; + EP_IN_NAME = "ep-a"; + + fs_sink_desc.bEndpointAddress + = hs_sink_desc.bEndpointAddress + = USB_DIR_OUT | 3; + fs_sink_desc.bmAttributes + = hs_sink_desc.bmAttributes + = USB_ENDPOINT_XFER_ISOC; + fs_sink_desc.wMaxPacketSize = min (bufsize, 1023); + hs_sink_desc.wMaxPacketSize = wMaxPacketSize; + fs_sink_desc.bInterval = interval + 1; + hs_sink_desc.bInterval = bInterval; + EP_OUT_NAME = "ep-b"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress + = hs_status_desc.bEndpointAddress + = USB_DIR_IN | 11; + EP_STATUS_NAME = "ep-f"; + + /* Intel PXA 2xx processor, full speed only */ + } else if (stat (DEVNAME = "pxa2xx_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0101), + + bufsize = min (bufsize, 256); + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 3; + fs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; + fs_source_desc.wMaxPacketSize = bufsize; + fs_source_desc.bInterval = interval; + EP_IN_NAME = "ep3in-iso"; + + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 4; + fs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; + fs_sink_desc.wMaxPacketSize = bufsize; + fs_sink_desc.bInterval = interval; + EP_OUT_NAME = "ep4out-iso"; + + /* using bulk for this since the pxa interrupt endpoints + * always use the no-toggle scheme (discouraged). + */ + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 11; + EP_STATUS_NAME = "ep11in-bulk"; + + /* OMAP 1610 and newer devices, full speed only, fifo mode 3 */ + } else if (stat (DEVNAME = "omap_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0102), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 7; + fs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; + fs_source_desc.wMaxPacketSize = min (bufsize, 256); + fs_source_desc.bInterval = interval; + EP_IN_NAME = "ep7in-iso"; + + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 8; + fs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; + fs_sink_desc.wMaxPacketSize = min (bufsize, 256); + fs_sink_desc.bInterval = interval; + EP_OUT_NAME = "ep8out-iso"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 9; + EP_STATUS_NAME = "ep9in-int"; + + /* Something based on Mentor USB Highspeed Dual-Role Controller; + * assumes a core that doesn't include high bandwidth support. + */ + } else if (stat (DEVNAME = "musb_hdrc", &statb) == 0) { + unsigned bInterval, wMaxPacketSize; + + HIGHSPEED = 1; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0103); + + bInterval = interval + 4; + wMaxPacketSize = bufsize; + + fs_source_desc.bEndpointAddress + = hs_source_desc.bEndpointAddress + = USB_DIR_IN | 1; + fs_source_desc.bmAttributes + = hs_source_desc.bmAttributes + = USB_ENDPOINT_XFER_ISOC; + fs_source_desc.wMaxPacketSize = min (bufsize, 1023); + hs_source_desc.wMaxPacketSize = wMaxPacketSize; + fs_source_desc.bInterval = interval + 1; + hs_source_desc.bInterval = bInterval; + EP_IN_NAME = "ep1in"; + + fs_sink_desc.bEndpointAddress + = hs_sink_desc.bEndpointAddress + = USB_DIR_OUT | 1; + fs_sink_desc.bmAttributes + = hs_sink_desc.bmAttributes + = USB_ENDPOINT_XFER_ISOC; + fs_sink_desc.wMaxPacketSize = min (bufsize, 1023); + hs_sink_desc.wMaxPacketSize = wMaxPacketSize; + fs_sink_desc.bInterval = interval + 1; + hs_sink_desc.bInterval = bInterval; + EP_OUT_NAME = "ep1out"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress + = hs_status_desc.bEndpointAddress + = USB_DIR_IN | 11; + EP_STATUS_NAME = "ep3"; + + /* Atmel AT91 processors, full speed only */ + } else if (stat (DEVNAME = "at91_udc", &statb) == 0) { + HIGHSPEED = 0; + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0104), + + fs_source_desc.bEndpointAddress = USB_DIR_IN | 4; + fs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; + fs_source_desc.wMaxPacketSize = min (bufsize, 256); + fs_source_desc.bInterval = interval; + EP_IN_NAME = "ep4"; + + fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; + fs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; + fs_sink_desc.wMaxPacketSize = min (bufsize, 256); + fs_sink_desc.bInterval = interval; + EP_OUT_NAME = "ep5"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3-int"; + + /* Atmel AT32AP700x processors, high/full speed */ + } else if (stat (DEVNAME = "atmel_usba_udc", &statb) == 0){ + HIGHSPEED = 1; + + device_desc.bcdDevice = __constant_cpu_to_le16 (0x0105); + + fs_source_desc.bEndpointAddress + = hs_source_desc.bEndpointAddress + = USB_DIR_IN | 5; + fs_source_desc.bmAttributes + = hs_source_desc.bmAttributes + = USB_ENDPOINT_XFER_ISOC; + fs_source_desc.wMaxPacketSize + = hs_source_desc.wMaxPacketSize + = __cpu_to_le16(min (bufsize, 1024)); + fs_source_desc.bInterval + = hs_source_desc.bInterval + = interval; + EP_IN_NAME = "ep5in-iso"; + + fs_sink_desc.bEndpointAddress + = hs_sink_desc.bEndpointAddress + = USB_DIR_OUT | 6; + fs_sink_desc.bmAttributes + = hs_sink_desc.bmAttributes + = USB_ENDPOINT_XFER_ISOC; + fs_sink_desc.wMaxPacketSize + = hs_sink_desc.wMaxPacketSize + = __cpu_to_le16(min (bufsize, 1024)); + fs_sink_desc.bInterval + = hs_sink_desc.bInterval + = interval; + EP_OUT_NAME = "ep6out-iso"; + + source_sink_intf.bNumEndpoints = 3; + fs_status_desc.bEndpointAddress + = hs_status_desc.bEndpointAddress + = USB_DIR_IN | 3; + EP_STATUS_NAME = "ep3in-int"; + + } else { + DEVNAME = 0; + return -ENODEV; + } + if (verbose) { + fprintf (stderr, "iso fs wMaxPacket %04x bInterval %02x\n", + __le16_to_cpu(fs_sink_desc.wMaxPacketSize), + fs_sink_desc.bInterval); + if (HIGHSPEED) + fprintf (stderr, + "iso hs wMaxPacket %04x bInterval %02x\n", + __le16_to_cpu(hs_sink_desc.wMaxPacketSize), + hs_sink_desc.bInterval); + } + return 0; +} + +#else +#define iso 0 +#endif /* AIO */ + +/*-------------------------------------------------------------------------*/ + +/* full duplex data, with at least three threads: ep0, sink, and source */ + +static pthread_t ep0; + +static pthread_t ccid_thread; +static pthread_t hidthread; +static int source_fd = -1; +static int sink_fd = -1; +static int status_fd = -1; +static int hidstatus_fd = -1; + +// FIXME no result i/o yet + +static void close_fd (void *fd_ptr) +{ + int result, fd; + + fd = *(int *)fd_ptr; + *(int *)fd_ptr = -1; + + /* test the FIFO ioctls (non-ep0 code paths) */ + if (pthread_self () != ep0) { + result = ioctl (fd, GADGETFS_FIFO_STATUS); + if (result < 0) { + /* ENODEV reported after disconnect */ + if (errno != ENODEV && errno != EOPNOTSUPP) + perror ("get fifo result"); + } else { + fprintf (stderr, "fd %d, unclaimed = %d\n", + fd, result); + if (result) { + result = ioctl (fd, GADGETFS_FIFO_FLUSH); + if (result < 0) + perror ("fifo flush"); + } + } + } + + if (close (fd) < 0) + perror ("close fd"); + else + fprintf (stderr, "closed fd\n"); +} + + +/* you should be able to open and configure endpoints + * whether or not the host is connected + */ +static int +ep_config (char *name, const char *label, + struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs +) +{ + int fd, result; + char buf [USB_BUFSIZE]; + + /* open and initialize with endpoint descriptor(s) */ + fd = open (name, O_RDWR); + if (fd < 0) { + result = -errno; + fprintf (stderr, "%s open %s error %d (%s)\n", + label, name, errno, strerror (errno)); + return result; + } + + /* one (fs or ls) or two (fs + hs) sets of config descriptors */ + *(__u32 *)buf = 1; /* tag for this format */ + memcpy (buf + 4, fs, USB_DT_ENDPOINT_SIZE); + if (HIGHSPEED) + memcpy (buf + 4 + USB_DT_ENDPOINT_SIZE, + hs, USB_DT_ENDPOINT_SIZE); + result = write (fd, buf, 4 + USB_DT_ENDPOINT_SIZE + + (HIGHSPEED ? USB_DT_ENDPOINT_SIZE : 0)); + if (result < 0) { + result = -errno; + fprintf (stderr, "%s config %s error %d (%s)\n", + label, name, errno, strerror (errno)); + close (fd); + return result; + } else if (debug) { + unsigned long id; + + id = pthread_self (); + fprintf (stderr, "%s (%ld): fd %d\n", label, id, fd); + } + return fd; +} + +#define source_open(name) \ + ep_config(name,__FUNCTION__, &fs_source_desc, &hs_source_desc) +#define sink_open(name) \ + ep_config(name,__FUNCTION__, &fs_sink_desc, &hs_sink_desc) +#define status_open(name) \ + ep_config(name,__FUNCTION__, &fs_status_desc, &hs_status_desc) +#define hidstatus_open(name) \ + ep_config(name,__FUNCTION__, &fs_hid_status_desc, &hs_hid_status_desc) + +/* ccid thread, forwards ccid requests to pcsc and returns results */ +static void *ccid (void *param) +{ + + //int tmp; + pthread_t interrupt_thread; + + void *interrupt (void *param) + { + status_fd = status_open (((char **) param)[0]); + if (source_fd < 0) { + /* an error concerning status endpoint is not fatal for in/out bulk*/ + /* transfer */ + if (debug) + perror("status_fd"); + pthread_exit(0); + } + pthread_cleanup_push (close_fd, &status_fd); + + int result; + RDR_to_PC_NotifySlotChange_t slotchange; + do { + + /* original LinuxThreads cancelation didn't work right */ + /* so test for it explicitly. */ + pthread_testcancel (); + + slotchange = get_RDR_to_PC_NotifySlotChange(); + if (slotchange.bmSlotICCState) { + /* don't bother host, when nothing changed */ + if (debug) + fprintf(stderr, "interrupt loop: writing RDR_to_PC_NotifySlotChange... "); + result = write(status_fd, &slotchange, sizeof slotchange); + if (debug) + fprintf(stderr, "done.\n"); + } + sleep(10); + } while (result >= 0); + + if (errno != ESHUTDOWN || result < 0) { + perror ("interrupt loop aborted"); + pthread_cancel(ccid_thread); + pthread_cancel(ep0); + } + + pthread_cleanup_pop (1); + + return 0; + } + + void close_interrupt () + { + if (doint) { + pthread_cancel(interrupt_thread); + if (pthread_join (interrupt_thread, 0) != 0) + perror ("can't join interrupt thread"); + fprintf (stderr, "cancled interrupt loop\n"); + } + } + + void close_pcsc() + { + int result = perform_shutdown(); + if (result != SCARD_S_SUCCESS) + fprintf(stderr, "pc/sc error: %s\n", pcsc_stringify_error(result)); + else if (verbose) + fprintf(stderr, "closed connection to %s\n", reader_name); + } + + char **names = (char **) param; + char *source_name = names[0]; + char *sink_name = names[1]; + char *status_name = names[2]; + int result; + + source_fd = source_open (source_name); + if (source_fd < 0) { + if (debug) + perror("source_fd"); + goto error; + } + pthread_cleanup_push (close_fd, &source_fd); + + sink_fd = sink_open (sink_name); + if (sink_fd < 0) { + if (debug) + perror("sink_fd"); + goto error; + } + pthread_cleanup_push (close_fd, &sink_fd); + + char * reader_name = perform_initialization(reader_num); + if (reader_name == NULL) { + if (debug) + perror("perform_initialization"); + goto error; + } + pthread_cleanup_push (close_pcsc, NULL); + if (verbose) + fprintf (stderr, "connected to %s\n", reader_name); + + if (doint) { + static char * interruptnames[1]; + interruptnames[0] = status_name; + if (pthread_create (&interrupt_thread, NULL, interrupt, (void *) + interruptnames) != 0) { + perror ("can't create interrupt thread"); + goto error; + } + } else if (verbose) { + fprintf (stderr, "interrupt pipe disabled\n"); + } + pthread_cleanup_push (close_interrupt, NULL); + + __u8 *outbuf = NULL; + pthread_cleanup_push (free, outbuf); + size_t bufsize = 512; + __u8 *inbuf = (__u8 *) malloc(bufsize); + pthread_cleanup_push (free, inbuf); + if (inbuf == NULL) { + if (debug) + perror("malloc"); + goto error; + } + + do { + + /* original LinuxThreads cancelation didn't work right + * so test for it explicitly. + */ + pthread_testcancel (); + + if (debug) + fprintf(stderr, "reading %u bytes... ", bufsize); + result = read(sink_fd, inbuf, bufsize); + if (result < 0) break; + if (debug) + fprintf(stderr, "got %d, done.\n", result); + if (!result) break; + + result = parse_ccid(inbuf, &outbuf); + if (result < 0) break; + + if (debug) + fprintf(stderr, "writing %d bytes... ", result); + result = write(source_fd, outbuf, result); + if (debug) + fprintf(stderr, "done.\n"); + } while (result >= 0); + + if (errno != ESHUTDOWN || result < 0) { + perror ("ccid loop aborted"); + pthread_cancel(ep0); + } + + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + + fflush (stdout); + fflush (stderr); + + return 0; + +error: + pthread_cancel(ep0); + pthread_exit(0); + +} + +static void *hid (void *param) +{ + + char **names = (char **) param; + char *status_name = names[0]; + int result = 0; + + hidstatus_fd = hidstatus_open (status_name); + if (hidstatus_fd < 0) { + if (debug) + perror("hidstatus_fd"); + goto error; + } + pthread_cleanup_push (close_fd, &hidstatus_fd); + + __u8 *outbuf = NULL; + pthread_cleanup_push (free, outbuf); + size_t bufsize = 512; + __u8 *inbuf = (__u8 *) malloc(bufsize); + pthread_cleanup_push (free, inbuf); + if (inbuf == NULL) { + if (debug) + perror("malloc"); + goto error; + } + + do { + + /* original LinuxThreads cancelation didn't work right + * so test for it explicitly. + */ + pthread_testcancel (); + + } while (result >= 0); + + if (errno != ESHUTDOWN || result < 0) { + perror ("hid loop aborted"); + pthread_cancel(ep0); + } + + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + + fflush (stdout); + fflush (stderr); + + return 0; + +error: + pthread_cancel(ep0); + pthread_exit(0); +} + +static void start_io () +{ + //int tmp; + sigset_t allsig, oldsig; + +#ifdef AIO + /* iso uses the same API as bulk/interrupt. we queue one + * (u)frame's worth of data per i/o request, and the host + * polls that queue once per interval. + */ + switch (current_speed) { + case USB_SPEED_FULL: + if (iso) + iosize = __le16_to_cpup (&fs_source_desc + .wMaxPacketSize); + else + iosize = bufsize; + break; + case USB_SPEED_HIGH: + /* for iso, we updated bufsize earlier */ + iosize = bufsize; + break; + default: + fprintf (stderr, "bogus link speed %d\n", current_speed); + return; + } +#endif /* AIO */ + + sigfillset (&allsig); + errno = pthread_sigmask (SIG_SETMASK, &allsig, &oldsig); + if (errno < 0) { + perror ("set thread signal mask"); + return; + } + + /* is it true that the LSB requires programs to disconnect + * from their controlling tty before pthread_create()? + * why? this clearly doesn't ... + */ + + static char *names[2]; + names[0] = EP_IN_NAME; + names[1] = EP_OUT_NAME; + names[2] = EP_STATUS_NAME; + if (pthread_create (&ccid_thread, NULL, ccid, (void *) names) != 0) { + perror ("can't create ccid thread"); + goto cleanup; + } + static char * hidnames[1]; + hidnames[0] = EP_HID_STATUS_NAME; + if (dohid) { + config.bNumInterfaces = 2; + if (pthread_create (&hidthread, NULL, hid, (void *) + hidnames) != 0) { + perror ("can't create hid thread"); + goto cleanup; + } + } else { + if (verbose) + fprintf (stderr, "HID disabled.\n"); + } + + /* give the other threads a chance to run before we report + * success to the host. + * FIXME better yet, use pthread_cond_timedwait() and + * synchronize on ep config success. + */ + sched_yield (); + +cleanup: + errno = pthread_sigmask (SIG_SETMASK, &oldsig, 0); + if (errno != 0) { + perror ("restore sigmask"); + exit (-1); + } +} + +static void stop_io () +{ + if (!pthread_equal (ccid_thread, ep0)) { + pthread_cancel (ccid_thread); + if (pthread_join (ccid_thread, 0) != 0) + perror ("can't join ccid thread"); + ccid_thread = ep0; + fprintf (stderr, "cancled ccid\n"); + } + if (!pthread_equal (ccid_thread, ep0)) { + pthread_cancel (hidthread); + if (pthread_join (hidthread, 0) != 0) + perror ("can't join hid thread"); + hidthread = ep0; + fprintf (stderr, "cancled hid\n"); + } +} + +/*-------------------------------------------------------------------------*/ + +static char * +build_config (char *cp, const struct usb_endpoint_descriptor **ep, const struct usb_endpoint_descriptor *hid_ep) +{ + struct usb_config_descriptor *c; + int i; + + c = (struct usb_config_descriptor *) cp; + + memcpy (cp, &config, config.bLength); + cp += config.bLength; + + memcpy (cp, &source_sink_intf, sizeof source_sink_intf); + cp += sizeof source_sink_intf; + + // Append vendor class specification + memcpy (cp, &ccid_desc, sizeof ccid_desc); + cp += sizeof ccid_desc; + + for (i = 0; i < source_sink_intf.bNumEndpoints; i++) { + memcpy (cp, ep [i], USB_DT_ENDPOINT_SIZE); + cp += USB_DT_ENDPOINT_SIZE; + } + + if (dohid) { + struct usb_interface_descriptor hid_intf = { + .bLength = sizeof hid_intf, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + + .bInterfaceClass = USB_CLASS_HID, + .iInterface = STRINGID_HID_INTERFACE, + .bNumEndpoints = 1, + }; + memcpy (cp, &hid_intf, sizeof hid_intf); + cp += sizeof hid_intf; + // Append vendor class specification + memcpy (cp, &hid_desc, sizeof hid_desc); + cp += sizeof hid_desc; + + for (i = 0; i < hid_intf.bNumEndpoints; i++) { + memcpy (cp, &(hid_ep [i]), USB_DT_ENDPOINT_SIZE); + cp += USB_DT_ENDPOINT_SIZE; + } + } + + c->wTotalLength = __cpu_to_le16 (cp - (char *) c); + return cp; +} + +static int init_device (void) +{ + char buf [4096], *cp = &buf [0]; + int fd; + int result; + +#ifdef AIO + if (iso) + result = iso_autoconfig (); + else +#endif + result = autoconfig (); + if (result < 0) { + fprintf (stderr, "?? don't recognize /dev/gadget %s device\n", + iso ? "iso" : "bulk"); + return result; + } + + fd = open (DEVNAME, O_RDWR); + if (fd < 0) { + perror (DEVNAME); + return -errno; + } + + *(__u32 *)cp = 0; /* tag for this format */ + cp += 4; + + /* write full then high speed configs */ + cp = build_config (cp, fs_eps, &fs_hid_status_desc); + if (HIGHSPEED) + cp = build_config (cp, hs_eps, &hs_hid_status_desc); + + device_desc.idVendor = __cpu_to_le16 (vendorid); + device_desc.idProduct = __cpu_to_le16 (productid); + if (verbose) { + fprintf(stderr, "idVendor=%04X idProduct=%04X\n", vendorid, + productid); + } + /* and device descriptor at the end */ + memcpy (cp, &device_desc, sizeof device_desc); + cp += sizeof device_desc; + + result = write (fd, &buf [0], cp - &buf [0]); + if (result < 0) { + perror ("write dev descriptors"); + close (fd); + return result; + } else if (result != (cp - buf)) { + fprintf (stderr, "dev init, wrote %d expected %d\n", + result, cp - buf); + close (fd); + return -EIO; + } + return fd; +} + +static void handle_control (int fd, struct usb_ctrlrequest *setup) +{ + int result, tmp; + __u8 buf [256]; + __u16 value, index, length; + + value = __le16_to_cpu(setup->wValue); + index = __le16_to_cpu(setup->wIndex); + length = __le16_to_cpu(setup->wLength); + + if ((setup->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) + goto special; + + switch (setup->bRequest) { /* usb 2.0 spec ch9 requests */ + case USB_REQ_GET_DESCRIPTOR: + if (debug) + fprintf(stderr, "USB_REQ_GET_DESCRIPTOR\n"); + if (setup->bRequestType != USB_DIR_IN) + goto stall; + switch (value >> 8) { + case USB_DT_STRING: + tmp = value & 0x0ff; + if (debug) + fprintf (stderr, + "... get string %d lang %04x\n", + tmp, index); + if (tmp != 0 && index != strings.language) { + fprintf (stderr, "wrong language\n"); + goto stall; + } + memset (buf, 0, 256); /* zero all the bytes */ + result = usb_gadget_get_string (&strings, tmp, buf); + if (result < 0) { + perror("usb_gadget_get_string"); + goto stall; + } + tmp = result; + if (length < tmp) + tmp = length; + result = write (fd, buf, tmp); + if (result < 0) { + if (errno == EIDRM) + fprintf (stderr, "string timeout\n"); + else + perror ("write string data"); + } else if (result != tmp) { + fprintf (stderr, "short string write, %d\n", + result); + } + break; + default: + goto stall; + } + return; + case USB_REQ_SET_CONFIGURATION: + if (debug) + fprintf (stderr, "USB_REQ_SET_CONFIGURATION #%d\n", value); + if (setup->bRequestType != USB_DIR_OUT) + goto stall; + + /* Kernel is normally waiting for us to finish reconfiguring + * the device. + * + * Some hardware can't, notably older PXA2xx hardware. (With + * racey and restrictive config change automagic. PXA 255 is + * OK, most PXA 250s aren't. If it has a UDC CFR register, + * it can handle deferred response for SET_CONFIG.) To handle + * such hardware, don't write code this way ... instead, keep + * the endpoints always active and don't rely on seeing any + * config change events, either this or SET_INTERFACE. + */ + switch (value) { + case CONFIG_VALUE: + if ( source_fd >= 0 || sink_fd >= 0 || status_fd >= 0 || hidstatus_fd >= 0 ) + stop_io (); + start_io (); + break; + case 0: + stop_io (); + break; + default: + /* kernel bug -- "can't happen" */ + fprintf (stderr, "? illegal config\n"); + goto stall; + } + + /* ... ack (a write would stall) */ + result = read (fd, &result, 0); + if (result) + perror ("ack SET_CONFIGURATION"); + return; + case USB_REQ_GET_INTERFACE: + if (debug) + fprintf (stderr, "USB_REQ_GET_INTERFACE\n"); + if (setup->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE) + || index != 0 + || length > 1) + goto stall; + + /* only one altsetting in this driver */ + buf [0] = 0; + result = write (fd, buf, length); + if (result < 0) { + if (errno == EIDRM) + fprintf (stderr, "GET_INTERFACE timeout\n"); + else + perror ("write GET_INTERFACE data"); + } else if (result != length) { + fprintf (stderr, "short GET_INTERFACE write, %d\n", + result); + } + return; + case USB_REQ_SET_INTERFACE: + if (debug) + fprintf (stderr, "USB_REQ_SET_INTERFACE\n"); + if (setup->bRequestType != USB_RECIP_INTERFACE + || index != 0 + || value != 0) + goto stall; + + /* just reset toggle/halt for the interface's endpoints */ + result = 0; + if (ioctl (source_fd, GADGETFS_CLEAR_HALT) < 0) { + result = errno; + perror ("reset source fd"); + } + if (ioctl (sink_fd, GADGETFS_CLEAR_HALT) < 0) { + result = errno; + perror ("reset sink fd"); + } + if (status_fd > 0) { + if (ioctl (status_fd, GADGETFS_CLEAR_HALT) < 0) { + result = errno; + perror ("reset status fd"); + } + } + if (hidstatus_fd > 0) { + if (ioctl (hidstatus_fd, GADGETFS_CLEAR_HALT) < 0) { + result = errno; + perror ("reset hidstatus fd"); + } + } + /* FIXME eventually reset the result endpoint too */ + if (result) + goto stall; + + /* ... and ack (a write would stall) */ + result = read (fd, &result, 0); + if (result) + perror ("ack SET_INTERFACE"); + return; + default: + goto stall; + } + +special: + switch (setup->bRequestType) { + case USB_REQ_CCID: { + __u8 *outbuf = NULL; + result = parse_ccid_control(setup, &outbuf); + if (result < 0 || result > 256) + goto stall; + if (debug) + fprintf(stderr, "writing %d bytes... ", result); + result = write (fd, outbuf, result); + if (debug) + fprintf(stderr, "done.\n"); + free(outbuf); + if (result < 0) + goto stall; + } return; + default: + goto stall; + } + +stall: + if (verbose) { + fprintf(stderr, "%s:%d\n", __FILE__, __LINE__); + fprintf (stderr, "... protocol stall %02x.%02x\n", + setup->bRequestType, setup->bRequest); + for (tmp = 0; tmp<5; tmp++) { + printf("%d: %s\n", stringtab[tmp].id, stringtab[tmp].s); + } + } + + /* non-iso endpoints are stalled by issuing an i/o request + * in the "wrong" direction. ep0 is special only because + * the direction isn't fixed. + */ + if (setup->bRequestType & USB_DIR_IN) + result = read (fd, &result, 0); + else + result = write (fd, &result, 0); + if (result != -1) + fprintf (stderr, "can't stall ep0 for %02x.%02x\n", + setup->bRequestType, setup->bRequest); + else if (errno != EL2HLT) + perror ("ep0 stall"); +} + +static void signothing (int sig, siginfo_t *info, void *ptr) +{ + /* NOP */ + if (debug) + fprintf (stderr, "%s %d\n", __FUNCTION__, sig); +} + +static const char *speed (enum usb_device_speed s) +{ + switch (s) { + case USB_SPEED_LOW: return "low speed"; + case USB_SPEED_FULL: return "full speed"; + case USB_SPEED_HIGH: return "high speed"; + default: return "UNKNOWN speed"; + } +} + +/*-------------------------------------------------------------------------*/ + +/* control thread, handles main event loop */ + +#define NEVENT 5 +#define LOGDELAY (15 * 60) /* seconds before stdout timestamp */ + +static void *ep0_thread (void *param) +{ + int fd = *(int*) param; + struct sigaction action; + time_t now, last; + struct pollfd ep0_poll; + + ccid_thread = ep0 = pthread_self (); + pthread_cleanup_push (close_fd, param); + pthread_cleanup_push (stop_io, NULL); + + /* REVISIT signal handling ... normally one pthread should + * be doing sigwait() to handle all async signals. + */ + action.sa_sigaction = signothing; + sigfillset (&action.sa_mask); + action.sa_flags = SA_SIGINFO; + if (sigaction (SIGINT, &action, NULL) < 0) { + perror ("SIGINT"); + return 0; + } + if (sigaction (SIGQUIT, &action, NULL) < 0) { + perror ("SIGQUIT"); + return 0; + } + + ep0_poll.fd = fd; + ep0_poll.events = POLLIN | POLLOUT | POLLHUP; + + /* event loop */ + last = 0; + for (;;) { + int tmp; + struct usb_gadgetfs_event event [NEVENT]; + int connected = 0; + int i, nevent; + + /* Use poll() to test that mechanism, to generate + * activity timestamps, and to make it easier to + * tweak this code to work without pthreads. When + * AIO is needed without pthreads, ep0 can be driven + * instead using SIGIO. + */ + tmp = poll(&ep0_poll, 1, -1); + if (verbose) { + time (&now); + if ((now - last) > LOGDELAY) { + char timebuf[26]; + + last = now; + ctime_r (&now, timebuf); + printf ("\n** %s", timebuf); + } + } + if (tmp < 0) { + /* exit path includes EINTR exits */ + perror("poll"); + break; + } + + tmp = read (fd, &event, sizeof event); + if (tmp < 0) { + if (errno == EAGAIN) { + sleep (1); + continue; + } + perror ("ep0 read after poll"); + goto done; + } + nevent = tmp / sizeof event [0]; + if (nevent != 1 && debug) + fprintf (stderr, "read %d ep0 events\n", + nevent); + + for (i = 0; i < nevent; i++) { + switch (event [i].type) { + case GADGETFS_NOP: + if (verbose) + fprintf (stderr, "NOP\n"); + break; + case GADGETFS_CONNECT: + connected = 1; + current_speed = event [i].u.speed; + if (verbose) + fprintf (stderr, + "CONNECT %s\n", + speed (event [i].u.speed)); + break; + case GADGETFS_SETUP: + connected = 1; + handle_control (fd, &event [i].u.setup); + break; + case GADGETFS_DISCONNECT: + connected = 0; + current_speed = USB_SPEED_UNKNOWN; + if (verbose) + fprintf(stderr, "DISCONNECT\n"); + stop_io (); + break; + case GADGETFS_SUSPEND: + // connected = 1; + if (verbose) + fprintf (stderr, "SUSPEND\n"); + break; + default: + fprintf (stderr, + "* unhandled event %d\n", + event [i].type); + } + } + continue; +done: + fflush (stdout); + if (connected) + stop_io (); + break; + } + if (verbose) + fprintf (stderr, "ep0 done.\n"); + + pthread_cleanup_pop (1); + pthread_cleanup_pop (1); + + fflush (stdout); + fflush (stderr); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +int +main (int argc, char **argv) +{ + int fd, c, i; + + /* random initial serial number */ + srand ((int) time (0)); + for (i = 0; i < sizeof serial - 1; ) { + c = rand () % 127; + if ((('a' <= c && c <= 'z') || ('0' <= c && c <= '9'))) + serial [i++] = c; + } + + for (i=1; i. + */ +#include +#include +#include +#include + +#ifndef USB_REQ_CCID +#define USB_REQ_CCID 0xA1 +#define CCID_CONTROL_ABORT 0x01 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x02 +#define CCID_CONTROL_GET_DATA_RATES 0x03 +struct ccid_class_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __le16 bcdCCID; + __u8 bMaxSlotIndex; + __u8 bVoltageSupport; + __le32 dwProtocols; + __le32 dwDefaultClock; + __le32 dwMaximumClock; + __u8 bNumClockSupport; + __le32 dwDataRate; + __le32 dwMaxDataRate; + __u8 bNumDataRatesSupported; + __le32 dwMaxIFSD; + __le32 dwSynchProtocols; + __le32 dwMechanical; + __le32 dwFeatures; + __le32 dwMaxCCIDMessageLength; + __u8 bClassGetResponse; + __u8 bclassEnvelope; + __le16 wLcdLayout; + __u8 bPINSupport; + __u8 bMaxCCIDBusySlots; +} __attribute__ ((packed)); + +typedef struct { + __u8 bmFindexDindex; + __u8 bmTCCKST0; + __u8 bGuardTimeT0; + __u8 bWaitingIntegerT0; + __u8 bClockStop; +} __attribute__ ((packed)) abProtocolDataStructure_T0_t; +typedef struct { + __u8 bmFindexDindex; + __u8 bmTCCKST1; + __u8 bGuardTimeT1; + __u8 bWaitingIntegersT1; + __u8 bClockStop; + __u8 bIFSC; + __u8 bNadValue; +} __attribute__ ((packed)) abProtocolDataStructure_T1_t; + +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 bBWI; + __le16 wLevelParameter; +} __attribute__ ((packed)) PC_to_RDR_XfrBlock_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 abRFU1; + __le16 abRFU2; +} __attribute__ ((packed)) PC_to_RDR_IccPowerOff_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 abRFU1; + __le16 abRFU2; +} __attribute__ ((packed)) PC_to_RDR_GetSlotStatus_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 abRFU1; + __le16 abRFU2; +} __attribute__ ((packed)) PC_to_RDR_GetParameters_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 abRFU1; + __le16 abRFU2; +} __attribute__ ((packed)) PC_to_RDR_ResetParameters_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 bProtocolNum; + __le16 abRFU; +} __attribute__ ((packed)) PC_to_RDR_SetParameters_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 bPowerSelect; + __le16 abRFU; +} __attribute__ ((packed)) PC_to_RDR_IccPowerOn_t; + +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 bStatus; + __u8 bError; + __u8 bClockStatus; +} __attribute__ ((packed)) RDR_to_PC_SlotStatus_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 bStatus; + __u8 bError; + __u8 bChainParameter; +} __attribute__ ((packed)) RDR_to_PC_DataBlock_t; +typedef struct { + __u8 bMessageType; + __le32 dwLength; + __u8 bSlot; + __u8 bSeq; + __u8 bStatus; + __u8 bError; + __u8 bProtocolNum; +} __attribute__ ((packed)) RDR_to_PC_Parameters_t; +typedef struct { + __u8 bMessageType; + __u8 bmSlotICCState; +} __attribute__ ((packed)) RDR_to_PC_NotifySlotChange_t; +#endif + +struct hid_class_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __le16 bcdHID; + __le32 bCountryCode; + __u8 bNumDescriptors; +} __attribute__ ((packed)); + + +static struct ccid_class_descriptor +ccid_desc = { + .bLength = sizeof ccid_desc, + .bDescriptorType = 0x21, + .bcdCCID = __constant_cpu_to_le16(0x0110), + .bMaxSlotIndex = 0, + .bVoltageSupport = 0x01, + .dwProtocols = __constant_cpu_to_le32(0x01| // T=0 + 0x02), // T=1 + .dwDefaultClock = __constant_cpu_to_le32(0xDFC), + .dwMaximumClock = __constant_cpu_to_le32(0xDFC), + .bNumClockSupport = 1, + .dwDataRate = __constant_cpu_to_le32(0x2580), + .dwMaxDataRate = __constant_cpu_to_le32(0x2580), + .bNumDataRatesSupported = 1, + .dwMaxIFSD = __constant_cpu_to_le32(0xFF), // FIXME + .dwSynchProtocols = __constant_cpu_to_le32(0), + .dwMechanical = __constant_cpu_to_le32(0), + .dwFeatures = __constant_cpu_to_le32(0x2| + // Automatic parameter configuration based on ATR data + 0x8| // Automatic ICC voltage selection + 0x10| // Automatic ICC clock frequency change + 0x20| // Automatic baud rate change + 0x40| // Automatic parameters negotiation + 0x80| // Automatic PPS + 0x20000| // Short APDU level exchange + 0x100000),// USB Wake up signaling supported + .dwMaxCCIDMessageLength = __constant_cpu_to_le32(261+10), + .bClassGetResponse = 0xFF, + .bclassEnvelope = 0xFF, + .wLcdLayout = __constant_cpu_to_le16(0), + .bPINSupport = 0, + .bMaxCCIDBusySlots = 1, +}; + + +SCARDCONTEXT hcontext = 0; +SCARDHANDLE hcard = 0; +SCARD_READERSTATE rstate; +DWORD dwActiveProtocol; +char reader_name[MAX_READERNAME]; +int reader_num; + +char* perform_initialization(int num) +{ + char *readers, *str; + DWORD size; + LONG r; + + reader_num = num; + r = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hcontext); + if (r != SCARD_S_SUCCESS) { + fprintf(stderr, "pc/sc error: %s\n", pcsc_stringify_error(r)); + SCardReleaseContext(hcontext); + return NULL; + } + r = SCardListReaders(hcontext, NULL, NULL, &size); + if (size == 0) + r = SCARD_E_UNKNOWN_READER; + if (r != SCARD_S_SUCCESS) { + fprintf(stderr, "pc/sc error: %s\n", pcsc_stringify_error(r)); + SCardReleaseContext(hcontext); + return NULL; + } + + /* get all readers */ + readers = (char *) malloc(size); + if (readers == NULL) { + fprintf(stderr, "pc/sc error: %s\n", + pcsc_stringify_error(SCARD_E_NO_MEMORY)); + SCardReleaseContext(hcontext); + return NULL; + } + r = SCardListReaders(hcontext, NULL, readers, &size); + if (r != SCARD_S_SUCCESS) { + free(readers); + fprintf(stderr, "pc/sc error: %s\n", pcsc_stringify_error(r)); + SCardReleaseContext(hcontext); + return NULL; + } + + /* name of reader number num */ + str = readers; + for (size = 0; size < num; size++) { + /* go to the next name */ + str += strlen(str) + 1; + + /* no more readers available? */ + if (strlen(str) == 0) { + free(readers); + fprintf(stderr, "pc/sc error: %s\n", + pcsc_stringify_error(SCARD_E_UNKNOWN_READER)); + SCardReleaseContext(hcontext); + return NULL; + } + } + strncpy(reader_name, str, MAX_READERNAME); + free(readers); + + rstate.dwCurrentState = SCARD_STATE_UNAWARE; + rstate.dwEventState = SCARD_STATE_UNAWARE; + rstate.szReader = reader_name; + + return reader_name; +} + + +int perform_shutdown() +{ + SCardDisconnect(hcard, SCARD_UNPOWER_CARD); + hcard = 0; + rstate.dwCurrentState = SCARD_STATE_UNAWARE; + rstate.dwEventState = SCARD_STATE_UNAWARE; + return SCardReleaseContext(hcontext); +} + +__u8 get_bError(LONG pcsc_result) +{ + switch (pcsc_result) { + case SCARD_S_SUCCESS : /**< No error was encountered. */ + // Command not supported + return 0; + case SCARD_E_CANCELLED : /**< The action was cancelled by an SCardCancel request. */ + fprintf(stderr, "CMD_ABORTED\n"); + return 0xFF; + case SCARD_E_INVALID_HANDLE : /**< The supplied handle was invalid. */ + case SCARD_E_NO_SMARTCARD : /**< The operation requires a Smart Card, but no Smart Card is currently in the device. */ + case SCARD_E_UNKNOWN_CARD : /**< The specified smart card name is not recognized. */ + case SCARD_E_NOT_READY : /**< The reader or smart card is not ready to accept commands. */ + case SCARD_W_UNRESPONSIVE_CARD : /**< The smart card is not responding to a reset. */ + case SCARD_W_UNPOWERED_CARD : /**< Power has been removed from the smart card, so that further communication is not possible. */ + case SCARD_W_REMOVED_CARD : /**< The smart card has been removed, so further communication is not possible. */ + fprintf(stderr, "ICC_MUTE\n"); + return 0xFE; + case SCARD_E_SHARING_VIOLATION : /**< The smart card cannot be accessed because of other connections outstanding. */ + fprintf(stderr, "CMD_SLOT_BUSY\n"); + return 0xE0; + case SCARD_E_PROTO_MISMATCH : /**< The requested protocols are incompatible with the protocol currently in use with the smart card. */ + fprintf(stderr, "ICC_PROTOCOL_NOT_SUPPORTED\n"); + return 0xF6; + // case SCARD_F_INTERNAL_ERROR : /**< An internal consistency check failed. */ + // case SCARD_E_INVALID_PARAMETER : /**< One or more of the supplied parameters could not be properly interpreted. */ + // case SCARD_E_INVALID_TARGET : /**< Registry startup information is missing or invalid. */ + // case SCARD_E_NO_MEMORY : /**< Not enough memory available to complete this command. */ + // case SCARD_F_WAITED_TOO_LONG : /**< An internal consistency timer has expired. */ + // case SCARD_E_INSUFFICIENT_BUFFER : /**< The data buffer to receive returned data is too small for the returned data. */ + // case SCARD_E_UNKNOWN_READER : /**< The specified reader name is not recognized. */ + // case SCARD_E_TIMEOUT : /**< The user-specified timeout value has expired. */ + // case SCARD_E_CANT_DISPOSE : /**< The system could not dispose of the media in the requested manner. */ + // case SCARD_E_INVALID_VALUE : /**< One or more of the supplied parameters values could not be properly interpreted. */ + // case SCARD_E_SYSTEM_CANCELLED : /**< The action was cancelled by the system, presumably to log off or shut down. */ + // case SCARD_F_COMM_ERROR : /**< An internal communications error has been detected. */ + // case SCARD_F_UNKNOWN_ERROR : /**< An internal error has been detected, but the source is unknown. */ + // case SCARD_E_INVALID_ATR : /**< An ATR obtained from the registry is not a valid ATR string. */ + // case SCARD_E_NOT_TRANSACTED : /**< An attempt was made to end a non-existent transaction. */ + // case SCARD_E_READER_UNAVAILABLE : /**< The specified reader is not currently available for use. */ + // case SCARD_W_UNSUPPORTED_CARD : /**< The reader cannot communicate with the card, due to ATR string configuration conflicts. */ + // case SCARD_W_RESET_CARD : /**< The smart card has been reset, so any shared state information is invalid. */ + // case SCARD_E_PCI_TOO_SMALL : /**< The PCI Receive buffer was too small. */ + // case SCARD_E_READER_UNSUPPORTED : /**< The reader driver does not meet minimal requirements for support. */ + // case SCARD_E_DUPLICATE_READER : /**< The reader driver did not produce a unique reader name. */ + // case SCARD_E_CARD_UNSUPPORTED : /**< The smart card does not meet minimal requirements for support. */ + // case SCARD_E_NO_SERVICE : /**< The Smart card resource manager is not running. */ + // case SCARD_E_SERVICE_STOPPED : /**< The Smart card resource manager has shut down. */ + // case SCARD_E_NO_READERS_AVAILABLE : /**< Cannot find a smart card reader. */ + default: + fprintf(stderr, "HW_ERROR\n"); + return 0xFB; + } +} + +__u8 get_bStatus(LONG pcsc_result) +{ + __u8 bStatus = 0; + + if (rstate.dwEventState & SCARD_STATE_PRESENT) { + if (rstate.dwEventState & SCARD_STATE_MUTE || + rstate.dwEventState & SCARD_STATE_UNPOWERED) { + // inactive + fprintf(stderr, "card inactive\n"); + bStatus = 1; + } else { + // active + fprintf(stderr, "card active\n"); + bStatus = 0; + } + } else { + // absent + fprintf(stderr, "card absent\n"); + bStatus = 2; + if (hcard != 0) { + pcsc_result = SCardDisconnect(hcard, SCARD_UNPOWER_CARD); + hcard = 0; + } + } + + if (pcsc_result != SCARD_S_SUCCESS) { + bStatus |= (1<<6); + fprintf(stderr, "pc/sc error: %s\n", pcsc_stringify_error(pcsc_result)); + } + + return bStatus; +} + +RDR_to_PC_SlotStatus_t get_RDR_to_PC_SlotStatus(__u8 bSlot, __u8 bSeq, + LONG pcsc_result) +{ + RDR_to_PC_SlotStatus_t result; + + result.bMessageType = 0x81; + result.dwLength = __constant_cpu_to_le32(0); + result.bSlot = bSlot; + result.bSeq = bSeq; + result.bStatus = get_bStatus(pcsc_result); + result.bError = get_bError(pcsc_result); + result.bClockStatus = 0; + + return result; +} + +RDR_to_PC_DataBlock_t get_RDR_to_PC_DataBlock(__u8 bSlot, __u8 bSeq, + LONG pcsc_result, __le32 dwLength) +{ + RDR_to_PC_DataBlock_t result; + + result.bMessageType = 0x80; + result.dwLength = dwLength; + result.bSlot = bSlot; + result.bSeq = bSeq; + result.bStatus = get_bStatus(pcsc_result); + result.bError = get_bError(pcsc_result); + result.bChainParameter = 0; + + return result; +} + +RDR_to_PC_SlotStatus_t +perform_PC_to_RDR_GetSlotStatus(const PC_to_RDR_GetSlotStatus_t request) +{ + if ( request.bMessageType != 0x65 || + request.dwLength != __constant_cpu_to_le32(0) || + request.bSlot != 0 || + request.abRFU1 != 0 || + request.abRFU2 != 0) + fprintf(stderr, "warning: malformed PC_to_RDR_GetSlotStatus\n"); + + return get_RDR_to_PC_SlotStatus(request.bSlot, request.bSeq, + SCardGetStatusChange(hcontext, 1, &rstate, 1)); +} + +RDR_to_PC_SlotStatus_t +perform_PC_to_RDR_IccPowerOn(const PC_to_RDR_IccPowerOn_t request, char ** pATR) +{ + if ( request.bMessageType != 0x62 || + request.dwLength != __constant_cpu_to_le32(0) || + request.bSlot != 0 || + !( request.bPowerSelect == 0 || + request.bPowerSelect & ccid_desc.bVoltageSupport ) || + request.abRFU != 0) + fprintf(stderr, "warning: malformed PC_to_RDR_IccPowerOn\n"); + + LONG pcsc_result; + if (hcard) { + pcsc_result = SCardReconnect(hcard, SCARD_SHARE_EXCLUSIVE, + SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, SCARD_LEAVE_CARD, + &dwActiveProtocol); + } else { + pcsc_result = SCardConnect(hcontext, reader_name, + SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, + &hcard, &dwActiveProtocol); + } + if (pcsc_result == SCARD_S_SUCCESS) + pcsc_result = SCardGetStatusChange(hcontext, 1, &rstate, 1); + + RDR_to_PC_SlotStatus_t result = get_RDR_to_PC_SlotStatus(request.bSlot, + request.bSeq, pcsc_result); + + if (pcsc_result != SCARD_S_SUCCESS) { + *pATR = NULL; + result.dwLength = __constant_cpu_to_le32(0); + } else { + *pATR = (char*) rstate.rgbAtr; + result.dwLength = __cpu_to_le32(rstate.cbAtr); + } + + return result; +} +RDR_to_PC_SlotStatus_t +perform_PC_to_RDR_IccPowerOff(const PC_to_RDR_IccPowerOff_t request) +{ + if ( request.bMessageType != 0x63 || + request.dwLength != __constant_cpu_to_le32(0) || + request.bSlot != 0 || + request.abRFU1 != 0 || + request.abRFU2 != 0) + fprintf(stderr, "warning: malformed PC_to_RDR_IccPowerOff\n"); + + LONG result = SCardDisconnect(hcard, SCARD_UNPOWER_CARD); + hcard = 0; + if (result == SCARD_E_INVALID_HANDLE) { + result = SCardGetStatusChange(hcontext, 1, &rstate, 1); + } + + return get_RDR_to_PC_SlotStatus(request.bSlot, request.bSeq, result); +} + +RDR_to_PC_DataBlock_t +perform_PC_to_RDR_XfrBlock(const PC_to_RDR_XfrBlock_t request, const __u8* + abDataIn, __u8** abDataOut) +{ + if ( request.bMessageType != 0x6F || + request.bSlot != 0 || + request.bBWI != 0) + fprintf(stderr, "warning: malformed PC_to_RDR_XfrBlock\n"); + + DWORD dwRecvLength = MAX_BUFFER_SIZE; + *abDataOut = (__u8 *) malloc(dwRecvLength); + if (*abDataOut == NULL) { + return get_RDR_to_PC_DataBlock(request.bSlot, request.bSeq, + SCARD_E_NO_MEMORY, __constant_cpu_to_le32(0)); + } + + LPCSCARD_IO_REQUEST pioSendPci; + if (dwActiveProtocol == SCARD_PROTOCOL_T0) + pioSendPci = SCARD_PCI_T0; + else + pioSendPci = SCARD_PCI_T1; + int pcsc_result = SCardTransmit(hcard, pioSendPci, abDataIn, + __le32_to_cpu(request.dwLength), NULL, *abDataOut, &dwRecvLength); + + return get_RDR_to_PC_DataBlock(request.bSlot, request.bSeq, + pcsc_result, __cpu_to_le32(dwRecvLength)); +} + + +RDR_to_PC_Parameters_t +get_RDR_to_PC_Parameters(__u8 bSlot, __u8 bSeq, LONG pcsc_result, __u8 + **abProtocolDataStructure) +{ + RDR_to_PC_Parameters_t result; + + result.bMessageType = 0x82; + result.bSlot = bSlot; + result.bSeq = bSeq; + if (pcsc_result == SCARD_S_SUCCESS) { + if (dwActiveProtocol == SCARD_PROTOCOL_T0) { + result.bProtocolNum = 0; + *abProtocolDataStructure = (__u8 *) malloc(sizeof + (abProtocolDataStructure_T0_t)); + if (*abProtocolDataStructure) { + fprintf (stderr, "T0\n"); + result.dwLength = __constant_cpu_to_le32(sizeof + (abProtocolDataStructure_T0_t)); + abProtocolDataStructure_T0_t * t0 = + *(abProtocolDataStructure_T0_t**) abProtocolDataStructure; + /* values taken from ISO 7816-3 defaults + * FIXME analyze ATR to get values */ + t0->bmFindexDindex = 1<<4| // index to table 7 ISO 7816-3 (Fi) + 1; // index to table 8 ISO 7816-3 (Di) + t0->bmTCCKST0 = 0<<1; // convention (direct) + t0->bGuardTimeT0 = 0xFF; + t0->bWaitingIntegerT0 = 0x10; + t0->bClockStop = 0; // (not allowed) + } else { + // error malloc + result.dwLength = __constant_cpu_to_le32(0); + *abProtocolDataStructure = NULL; + pcsc_result = SCARD_E_INSUFFICIENT_BUFFER; + } + } else { + result.bProtocolNum = 1; + *abProtocolDataStructure = (__u8 *) malloc(sizeof + (abProtocolDataStructure_T1_t)); + if (*abProtocolDataStructure) { + fprintf (stderr, "T1\n"); + result.dwLength = __constant_cpu_to_le32(sizeof + (abProtocolDataStructure_T1_t)); + abProtocolDataStructure_T1_t * t1 = + *(abProtocolDataStructure_T1_t**) abProtocolDataStructure; + /* values taken from OpenPGP-card + * FIXME analyze ATR to get values */ + t1->bmFindexDindex = 1<<4| // index to table 7 ISO 7816-3 (Fi) + 3; // index to table 8 ISO 7816-3 (Di) + t1->bmTCCKST1 = 0| // checksum type (CRC) + 0<<1| // convention (direct) + 0x10; + t1->bGuardTimeT1 = 0xFF; + t1->bWaitingIntegersT1 = 4<<4| // BWI + 5; // CWI + t1->bClockStop = 0; // (not allowed) + t1->bIFSC = 0x80; + t1->bNadValue = 0; // see 7816-3 9.4.2.1 (only default value) + } else { + // error malloc + result.dwLength = __constant_cpu_to_le32(0); + *abProtocolDataStructure = NULL; + pcsc_result = SCARD_E_INSUFFICIENT_BUFFER; + } + } + } else { + result.dwLength = __constant_cpu_to_le32(0); + *abProtocolDataStructure = NULL; + } + result.bStatus = get_bStatus(pcsc_result); + result.bError = get_bError(pcsc_result); + + return result; +} + +RDR_to_PC_Parameters_t +perform_PC_to_RDR_GetParamters(const PC_to_RDR_GetParameters_t request, + __u8** abProtocolDataStructure) +{ + if ( request.bMessageType != 0x6C || + request.dwLength != __constant_cpu_to_le32(0) || + request.bSlot != 0) + fprintf(stderr, "warning: malformed PC_to_RDR_GetParamters\n"); + + LONG pcsc_result = SCardReconnect(hcard, SCARD_SHARE_EXCLUSIVE, + SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, SCARD_LEAVE_CARD, + &dwActiveProtocol); + + return get_RDR_to_PC_Parameters(request.bSlot, request.bSeq, + pcsc_result, abProtocolDataStructure); +} + +RDR_to_PC_NotifySlotChange_t +get_RDR_to_PC_NotifySlotChange () +{ + RDR_to_PC_NotifySlotChange_t result; + result.bMessageType = 0x50; + result.bmSlotICCState = 0; // no change + + DWORD current = rstate.dwEventState; + if (SCARD_S_SUCCESS != SCardGetStatusChange(hcontext, 1, &rstate, 1)) { + fprintf(stderr, "state changed: error\n"); + result.bmSlotICCState = 2; // changed (error) + } else if (!(current & rstate.dwEventState)) { + fprintf(stderr, "state changed\n"); + result.bmSlotICCState = 2; // changed + } + + return result; +} + +RDR_to_PC_SlotStatus_t +perform_unknown(const PC_to_RDR_GetSlotStatus_t request) +{ + RDR_to_PC_SlotStatus_t result; + switch (request.bMessageType) { + case 0x62: + case 0x6F: + case 0x69: + result.bMessageType = 0x80; + break; + case 0x63: + case 0x65: + case 0x6E: + case 0x6A: + case 0x71: + case 0x72: + result.bMessageType = 0x81; + break; + case 0x61: + case 0x6C: + case 0x6D: + result.bMessageType = 0x82; + break; + case 0x6B: + result.bMessageType = 0x83; + break; + case 0x73: + result.bMessageType = 0x84; + break; + default: + fprintf(stderr, "unknown message type\n"); + result.bMessageType = 0; + } + result.dwLength = __constant_cpu_to_le32(0); + result.bSlot = request.bSlot, + result.bSeq = request.bSeq; + result.bStatus = get_bStatus(SCARD_F_UNKNOWN_ERROR); + result.bError = 0; + result.bClockStatus = 0; + + return result; +} + +int parse_ccid(const __u8* inbuf, __u8** outbuf) { + if (inbuf == NULL) + return 0; + int result = -1; + if (SCardIsValidContext(hcontext) != SCARD_S_SUCCESS) { + if (perform_initialization(reader_num) == NULL) + goto error; + } + + switch (*inbuf) { + case 0x62: + { fprintf(stderr, "PC_to_RDR_IccPowerOn\n"); + + char* atr; + PC_to_RDR_IccPowerOn_t input = + *(PC_to_RDR_IccPowerOn_t*) inbuf; + RDR_to_PC_SlotStatus_t output = + perform_PC_to_RDR_IccPowerOn(input, &atr); + + result = sizeof output + __le32_to_cpu(output.dwLength); + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + result = -1; + break; + } + + memcpy(*outbuf, &output, sizeof output); + memcpy(*outbuf + sizeof output, atr, + __le32_to_cpu(output.dwLength)); + } break; + + case 0x63: + { fprintf(stderr, "PC_to_RDR_IccPowerOff\n"); + + PC_to_RDR_IccPowerOff_t input = + *(PC_to_RDR_IccPowerOff_t*) inbuf; + RDR_to_PC_SlotStatus_t output = + perform_PC_to_RDR_IccPowerOff(input); + + result = sizeof output; + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + result = -1; + break; + } + + memcpy(*outbuf, &output, sizeof output); + } break; + + case 0x65: + { fprintf(stderr, "PC_to_RDR_GetSlotStatus\n"); + + PC_to_RDR_GetSlotStatus_t input = + *(PC_to_RDR_GetSlotStatus_t*) inbuf; + RDR_to_PC_SlotStatus_t output = + perform_PC_to_RDR_GetSlotStatus(input); + + result = sizeof output; + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + result = -1; + break; + } + + memcpy(*outbuf, &output, sizeof output); + } break; + + case 0x6F: + { fprintf(stderr, "PC_to_RDR_XfrBlock\n"); + + __u8* rapdu; + PC_to_RDR_XfrBlock_t input = *(PC_to_RDR_XfrBlock_t*) inbuf; + RDR_to_PC_DataBlock_t output = + perform_PC_to_RDR_XfrBlock(input, inbuf + sizeof input, + &rapdu); + + result = sizeof output + __le32_to_cpu(output.dwLength); + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + free(rapdu); + result = -1; + break; + } + + memcpy(*outbuf, &output, sizeof output); + memcpy(*outbuf + sizeof output, rapdu, + __le32_to_cpu(output.dwLength)); + free(rapdu); + } break; + + case 0x6C: + { fprintf(stderr, "PC_to_RDR_GetParameters\n"); + + __u8* abProtocolDataStructure; + PC_to_RDR_GetParameters_t input = *(PC_to_RDR_GetParameters_t*) + inbuf; + RDR_to_PC_Parameters_t output = perform_PC_to_RDR_GetParamters( + input, &abProtocolDataStructure ); + + result = sizeof output + __le32_to_cpu(output.dwLength); + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + if (abProtocolDataStructure) + free(abProtocolDataStructure); + result = -1; + break; + } + + memcpy(*outbuf, &output, sizeof output); + memcpy(*outbuf + sizeof output, abProtocolDataStructure, + __le32_to_cpu(output.dwLength)); + if (abProtocolDataStructure) + free(abProtocolDataStructure); + } break; + + default: +error: + { fprintf(stderr, "unknown ccid command: 0x%4X\n", *inbuf); + + PC_to_RDR_GetSlotStatus_t input = + *(PC_to_RDR_GetSlotStatus_t*) inbuf; + RDR_to_PC_SlotStatus_t output = + perform_unknown(input); + + result = sizeof output; + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + result = -1; + break; + } + + memcpy(*outbuf, &output, sizeof output); + } + } + + return result; +} + +int parse_ccid_control(struct usb_ctrlrequest *setup, __u8 **outbuf) { + int result = -1; + __u16 value, index, length; + + value = __le16_to_cpu(setup->wValue); + index = __le16_to_cpu(setup->wIndex); + length = __le16_to_cpu(setup->wLength); + if (setup->bRequestType == USB_REQ_CCID) + switch(setup->bRequest) { + case CCID_CONTROL_ABORT: + { + fprintf(stderr, "ABORT\n"); + if (length != 0x00) { + fprintf(stderr, "warning: malformed ABORT\n"); + } + result = SCardCancel(hcontext); + if (result != SCARD_S_SUCCESS) + fprintf(stderr, "pc/sc error: %s\n", + pcsc_stringify_error(result)); + result = 0; + } break; + case CCID_CONTROL_GET_CLOCK_FREQUENCIES: + { + fprintf(stderr, "GET_CLOCK_FREQUENCIES\n"); + if (value != 0x00) { + fprintf(stderr, + "warning: malformed GET_CLOCK_FREQUENCIES\n"); + } + + result = sizeof(__le32); + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + result = -1; + break; + } + __le32 clock = ccid_desc.dwDefaultClock; + memcpy(*outbuf, &clock, sizeof (__le32)); + } break; + case CCID_CONTROL_GET_DATA_RATES: + { + fprintf(stderr, "GET_DATA_RATES\n"); + if (value != 0x00) { + fprintf(stderr, "warning: malformed GET_DATA_RATES\n"); + } + + result = sizeof (__le32); + *outbuf = realloc(*outbuf, result); + if (*outbuf == NULL) { + result = -1; + break; + } + __le32 drate = ccid_desc.dwDataRate; + memcpy(*outbuf, &drate, sizeof (__le32)); + } break; + } + + return result; +} + diff --git a/ccid/usbstring.c b/ccid/usbstring.c new file mode 100644 index 0000000..9cf86d7 --- /dev/null +++ b/ccid/usbstring.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include +#include + +#include +#include + +#include + +#include "usbstring.h" + +static inline void put_unaligned_le16(__u16 val, __u16 *cp) +{ + __u8 *p = (void *)cp; + + *p++ = (__u8) val; + *p++ = (__u8) (val >> 8); +} + +static int utf8_to_utf16le(const char *s, __u16 *cp, unsigned len) +{ + int count = 0; + __u8 c; + __u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (__u8) *s++) != 0) { + if (c & 0x80) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (__u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (__u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (__u8) *s++; + if ((c & 0xc0) != 0xc0) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + + } else + goto fail; + } else + uchar = c; + put_unaligned_le16 (uchar, cp++); + count++; + len--; + } + return count; +fail: + return -1; +} + + +/** + * usb_gadget_get_string - fill out a string descriptor + * @table: of c strings encoded using UTF-8 + * @id: string id, from low byte of wValue in get string descriptor + * @buf: at least 256 bytes + * + * Finds the UTF-8 string matching the ID, and converts it into a + * string descriptor in utf16-le. + * Returns length of descriptor (always even) or negative errno + * + * If your driver needs strings in multiple languages, you'll probably + * "switch (wIndex) { ... }" in your ep0 string descriptor logic, + * using this routine after choosing which set of UTF-8 strings to use. + * + * Note that US-ASCII is a strict subset of UTF-8; any string bytes with + * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1 + * characters. + */ +int +usb_gadget_get_string (struct usb_gadget_strings *table, int id, __u8 *buf) +{ + struct usb_string *s; + int len; + + /* descriptor 0 has the language id */ + if (id == 0) { + buf [0] = 4; + buf [1] = USB_DT_STRING; + buf [2] = (__u8) table->language; + buf [3] = (__u8) (table->language >> 8); + return 4; + } + for (s = table->strings; s && s->s; s++) { + if (s->id == id) + break; +} + + /* unrecognized: stall. */ + if (!s || !s->s) + return -EINVAL; + + /* string descriptors have length, tag, then UTF16-LE text */ + len = strlen (s->s); + if (len > 126) + len = 126; + memset (buf + 2, 0, 2 * len); /* zero all the bytes */ + len = utf8_to_utf16le(s->s, (__u16 *)&buf[2], len); + if (len < 0) + return -EINVAL; + buf [0] = (len + 1) * 2; + buf [1] = USB_DT_STRING; + return buf [0]; +} + diff --git a/ccid/usbstring.h b/ccid/usbstring.h new file mode 100644 index 0000000..982bec4 --- /dev/null +++ b/ccid/usbstring.h @@ -0,0 +1,38 @@ +/* + * (c) Copyright 2003 by David Brownell + * All Rights Reserved. + * + * This software is licensed under the GNU LGPL version 2. + */ + +/* utility to simplify dealing with string descriptors */ + +/** + * struct usb_string - wraps a C string and its USB id + * @id: the (nonzero) ID for this string + * @s: the string, in UTF-8 encoding + * + * If you're using usb_gadget_get_string(), use this to wrap a string + * together with its ID. + */ +struct usb_string { + __u8 id; + const char *s; +}; + +/** + * struct usb_gadget_strings - a set of USB strings in a given language + * @language: identifies the strings' language (0x0409 for en-us) + * @strings: array of strings with their ids + * + * If you're using usb_gadget_get_string(), use this to wrap all the + * strings for a given language. + */ +struct usb_gadget_strings { + __u16 language; /* 0x0409 for en-us */ + struct usb_string *strings; +}; + +/* put descriptor for string with that id into buf (buflen >= 256) */ +int usb_gadget_get_string (struct usb_gadget_strings *table, int id, __u8 *buf); + diff --git a/virtualsmartcard/Makefile b/virtualsmartcard/Makefile new file mode 100644 index 0000000..15a696e --- /dev/null +++ b/virtualsmartcard/Makefile @@ -0,0 +1,108 @@ +MAKEFLAGS += -rR --no-print-directory + +# Directories +prefix = +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin +serialdropdir = `pkg-config libpcsclite --variable=usbdropdir`/serial +sysconfdir = $(prefix)/etc +readerconfdir = $(sysconfdir)/reader.conf.d +python_sitelib = /usr/lib/python2.5/site-packages + + +# Compiler +CC = gcc +CFLAGS = -Wall -g +LIBPCSCLITE_CFLAGS = `pkg-config --cflags --libs libpcsclite` +# use -fPIC in case of problems +SO_CFLAGS = -fpic + +# Linker +LD = ld +LDFLAGS = -G + +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) +INSTALL_DATA = $(INSTALL) -m 644 + +UPDATEREADERCONF = update-reader.conf +RESTARTPCSCD = /etc/init.d/pcscd restart + + +TARGETS = libvpcd.so configuration virtualsmartcard + +# top-level rule +all: $(TARGETS) + +libvpcd.so: vpcd/vpcd.c vpcd/vpcd.h + $(CC) $(LIBPCSCLITE_CFLAGS) $(SO_CFLAGS) $(CFLAGS) -c $< -o $(@:%.so=%.o) + $(LD) $(LDFLAGS) $(@:%.so=%.o) -o $@ -soname=$@.1 -lc + rm -f $(@:%.so=%.o) + +.PHONY: virtualsmartcard +virtualsmartcard: + echo '#!/bin/sh' > virtualsmartcard + echo 'cd "$(python_sitelib)"' \ + >> virtualsmartcard + echo 'python "$(python_sitelib)/VirtualSmartcard.py" "$$@"' \ + >> virtualsmartcard + +.PHONY: configuration +configuration: + echo 'FRIENDLYNAME "Virtual PCD"' > vpcd.conf + echo 'DEVICENAME /dev/null' >> vpcd.conf + echo "LIBPATH $(serialdropdir)/libvpcd.so" >> vpcd.conf + echo 'CHANNELID 0' >> vpcd.conf + + +install: $(TARGETS) installdirs + $(INSTALL_PROGRAM) libvpcd.so $(DESTDIR)$(serialdropdir) + $(INSTALL_PROGRAM) virtualsmartcard $(DESTDIR)$(bindir) + $(INSTALL_DATA) vpcd.conf $(DESTDIR)$(readerconfdir) + $(INSTALL_DATA) vpicc/ConstantDefinitions.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/CryptoUtils.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/SEutils.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/SmartcardFilesystem.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/SmartcardSAM.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/SWutils.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/TLVutils.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/utils.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/VirtualSmartcard.py $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/testconfig.sam $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/testconfig.mf $(DESTDIR)$(python_sitelib) + $(INSTALL_DATA) vpicc/jp2.jpg $(DESTDIR)$(python_sitelib) + +.PHONY: installdirs +installdirs: + $(INSTALL) -d $(DESTDIR)$(serialdropdir) + $(INSTALL) -d $(DESTDIR)$(bindir) + $(INSTALL) -d $(DESTDIR)$(readerconfdir) + $(INSTALL) -d $(DESTDIR)$(python_sitelib) + +.PHONY: install-strip +install-strip: + $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install + +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)$(serialdropdir)/libvpcd.so + rm -f $(DESTDIR)$(bindir)/virtualsmartcard + rm -f $(DESTDIR)$(readerconfdir)/vpcd.conf + rm -f $(DESTDIR)$(python_sitelib)/ConstantDefinitions.py + rm -f $(DESTDIR)$(python_sitelib)/CryptoUtils.py + rm -f $(DESTDIR)$(python_sitelib)/SEutils.py + rm -f $(DESTDIR)$(python_sitelib)/SmartcardFilesystem.py + rm -f $(DESTDIR)$(python_sitelib)/SmartcardSAM.py + rm -f $(DESTDIR)$(python_sitelib)/SWutils.py + rm -f $(DESTDIR)$(python_sitelib)/TLVutils.py + rm -f $(DESTDIR)$(python_sitelib)/utils.py + rm -f $(DESTDIR)$(python_sitelib)/VirtualSmartcard.py + rm -f $(DESTDIR)$(python_sitelib)/testconfig.sam + rm -f $(DESTDIR)$(python_sitelib)/testconfig.mf + rm -f $(DESTDIR)$(python_sitelib)/jp2.jpg + +.PHONY: clean +clean: + rm -f libvpcd.so + rm -f vpcd.conf + rm -f virtualsmartcard diff --git a/virtualsmartcard/virtualsmartcard.bb b/virtualsmartcard/virtualsmartcard.bb new file mode 100644 index 0000000..ff40ddc --- /dev/null +++ b/virtualsmartcard/virtualsmartcard.bb @@ -0,0 +1,69 @@ +DESCRIPTION = "Virtual Smartcard with PCSC Driver and CCID to PCSC Gadget" +LICENSE = "GPL" + +DEPENDS = "pcsc-lite" +RDEPENDS = "pcsc-lite python-pycrypto python-crypt python-textutils python-imaging python-pickle" + +SRC_URI = "svn://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard/;module=virtualsmartcard;proto=https;rev=1" + +S = "${WORKDIR}" +serialdropdir = ${libdir}/pcsc/drivers/serial +readerconfdir = ${sysconfdir}/reader.conf.d +python_sitelib = /usr/lib/python2.5/site-packages + +LIBPCSCLITE_CFLAGS = -I${STAGING_INCDIR}/PCSC -lpcsclite +SO_CFLAGS = -fPIC +SO_LDFLAGS = -shared + +INSTALL = install +INSTALL_PROGRAM = ${INSTALL} +INSTALL_DATA = ${INSTALL} -m 644 + +FILES_${PN} = "\ + ${bindir}/* \ + ${readerconfdir}/* \ + ${serialdropdir}/* \ + ${python_sitelib}/* \ +" + +do_compile() { + ${CC} -c ${S}/virtualsmartcard/vpcd/vpcd.c \ + ${S}/virtualsmartcard/vpcd/vpcd.h \ + ${LIBPCSCLITE_CFLAGS} ${SO_CFLAGS} ${CFLAGS} + ${CC} ${S}/vpcd.o -o ${S}/virtualsmartcard/libvpcd.so \ + ${LIBPCSCLITE_CFLAGS} ${SO_LDFLAGS} ${LDFLAGS} + rm -f ${S}/vpcd.o + + echo '#!/bin/sh' > ${S}/virtualsmartcard/virtualsmartcard + echo "cd \"${python_sitelib}\"" \ + >> ${S}/virtualsmartcard/virtualsmartcard + echo "python \"${python_sitelib}/VirtualSmartcard.py\" \"\$@\"" \ + >> ${S}/virtualsmartcard/virtualsmartcard + + echo 'FRIENDLYNAME "Virtual PCD"' > ${S}/virtualsmartcard/vpcd.conf + echo 'DEVICENAME /dev/null' >> ${S}/virtualsmartcard/vpcd.conf + echo "LIBPATH ${serialdropdir}/libvpcd.so" >> ${S}/virtualsmartcard/vpcd.conf + echo 'CHANNELID 0' >> ${S}/virtualsmartcard/vpcd.conf +} + +do_install() { + ${INSTALL} -d ${D}${bindir} + ${INSTALL} -d ${D}${serialdropdir} + ${INSTALL} -d ${D}${readerconfdir} + ${INSTALL} -d ${D}${python_sitelib} + ${INSTALL_PROGRAM} ${S}/virtualsmartcard/virtualsmartcard ${D}${bindir} + ${INSTALL_PROGRAM} ${S}/virtualsmartcard/libvpcd.so ${D}${serialdropdir} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpcd.conf ${D}${readerconfdir} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/ConstantDefinitions.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/CryptoUtils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/SEutils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/SmartcardFilesystem.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/SmartcardSAM.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/SWutils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/TLVutils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/utils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/VirtualSmartcard.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/testconfig.sam ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/testconfig.mf ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/virtualsmartcard/vpicc/jp2.jpg ${D}${python_sitelib} +} diff --git a/virtualsmartcard/vpcd/vpcd.c b/virtualsmartcard/vpcd/vpcd.c new file mode 100644 index 0000000..d872e64 --- /dev/null +++ b/virtualsmartcard/vpcd/vpcd.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include + +#include "vpcd.h" + +/* + * List of Defined Functions Available to IFD_Handler 1.0 + */ +RESPONSECODE +IO_Create_Channel(DWORD channelid) +{ + if (vicc_init() < 0) return IFD_COMMUNICATION_ERROR; + + return IFD_SUCCESS; +} + +RESPONSECODE +IO_Close_Channel() +{ + RESPONSECODE r = IFD_Eject_ICC(); + if (vicc_exit() < 0) return IFD_COMMUNICATION_ERROR; + + return r; +} + +RESPONSECODE +IFD_Get_Capabilities(DWORD tag, PUCHAR value) +{ + char * atr; + int size; + switch (tag) { + case TAG_IFD_ATR: + + size = vicc_getatr(&atr); + if (size < 0) { + Log1(PCSC_LOG_ERROR, "could not get ATR"); + return IFD_COMMUNICATION_ERROR; + } + + value = memcpy(value, atr, size); + free(atr); + return IFD_SUCCESS; + + case TAG_IFD_SLOTS_NUMBER: + (*value) = 0; + return IFD_SUCCESS; + + //case TAG_IFD_POLLING_THREAD_KILLABLE: + //return IFD_SUCCESS; + + default: + Log2(PCSC_LOG_DEBUG, "unknown tag %d", (int)tag); + } + + return IFD_ERROR_TAG; +} + +RESPONSECODE +IFD_Set_Capabilities(DWORD tag, PUCHAR value) +{ + return IFD_NOT_SUPPORTED; +} + +RESPONSECODE +IFD_Set_Protocol_Parameters(DWORD protocoltype, UCHAR selectionflags, UCHAR + pts1, UCHAR pts2, UCHAR pts3) +{ + return IFD_SUCCESS; +} + +RESPONSECODE +IFD_Power_ICC(DWORD a) +{ + + if ((a == IFD_POWER_DOWN) || (a == IFD_RESET)) { + if (vicc_poweroff() < 0) { + Log1(PCSC_LOG_ERROR, "could not powerdown"); + return IFD_COMMUNICATION_ERROR; + } + } + + if ((a == IFD_POWER_UP) || (a == IFD_RESET)) { + if (vicc_poweron() < 0) { + Log1(PCSC_LOG_ERROR, "could not powerup"); + return IFD_COMMUNICATION_ERROR; + } + } + + return IFD_SUCCESS; +} + +RESPONSECODE +IFD_Swallow_ICC() +{ + Log1(PCSC_LOG_DEBUG, ""); + return IFD_NOT_SUPPORTED; +} + +RESPONSECODE +IFD_Eject_ICC() +{ + if (vicc_eject() < 0) { + return IFD_COMMUNICATION_ERROR; + } + return IFD_SUCCESS; +} + +RESPONSECODE +IFD_Confiscate_ICC() +{ + Log1(PCSC_LOG_DEBUG, ""); + return IFD_NOT_SUPPORTED; +} + +RESPONSECODE +IFD_Transmit_to_ICC(SCARD_IO_HEADER SendPci, PUCHAR TxBuffer, DWORD TxLength, + PUCHAR RxBuffer, PDWORD RxLength, PSCARD_IO_HEADER RecvPci) +{ + (*RxLength) = 0; + char *rapdu; + + int size = vicc_transmit(TxLength, (char *) TxBuffer, &rapdu); + if (size < 0) { + Log1(PCSC_LOG_ERROR, "could not send apdu or receive rapdu"); + return IFD_COMMUNICATION_ERROR; + } + + (*RxLength) = size; + RxBuffer = memcpy(RxBuffer, rapdu, size); + free(rapdu); + + return IFD_SUCCESS; +} + +RESPONSECODE +IFD_Is_ICC_Present() +{ + switch (vicc_present()) { + case 0: return IFD_ICC_NOT_PRESENT; + case 1: return IFD_ICC_PRESENT; + default: return IFD_COMMUNICATION_ERROR; + } + return IFD_COMMUNICATION_ERROR; +} + +RESPONSECODE +IFD_Is_ICC_Absent() +{ + return IFD_Is_ICC_Present(); +} diff --git a/virtualsmartcard/vpcd/vpcd.h b/virtualsmartcard/vpcd/vpcd.h new file mode 100644 index 0000000..2529b8a --- /dev/null +++ b/virtualsmartcard/vpcd/vpcd.h @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include +#include + +#define PORT 35963 +#define LENLEN 3 +int server_sock = -1; +int client_sock = -1; + +/* + * Send all size bytes from buffer to sock + */ +int sendall(int sock, size_t size, const char* buffer) { + /* send a plain message */ + size_t sent = 0; + int i; + while (sent < size) { + i = send(sock, buffer, size-sent, 0); + if (i < 0) return i; + sent += i; + } + return 0; +} + +/* + * Receive size bytes from sock + */ +char* recvall(int sock, size_t size) { + char* buffer = (char*) malloc(size); + if (buffer == NULL) return NULL; + + if (recv(sock, buffer, size, MSG_WAITALL) < size) { + free(buffer); + return NULL; + } + return buffer; +} + +int vicc_eject() { + if (client_sock > 0) { + client_sock = close(client_sock); + if (client_sock < 0) { + return -1; + } + } + return 0; +} + +/* + * First send length of message to the socket on LENLEN bytes, then send the + * message itself to the socket. + */ +int sendToVICC(size_t size, const char* buffer) { + /* send size of message on LENLEN bytes */ + char sizebuf[LENLEN]; + size_t i; + for (i = 0; i < LENLEN; i++) + sizebuf[i] = ((size>>8*(LENLEN-i-1)) & 0xff); + + i = sendall(client_sock, LENLEN, sizebuf); + if (i<0) { + vicc_eject(); + return i; + } + i = sendall(client_sock, size, buffer); + if (i<0) { + vicc_eject(); + return i; + } + + return 0; +} + +/* + * Receive a message from icc + */ +int recvFromVICC(char** buffer) { + /* receive size of message on LENLEN bytes */ + unsigned char* sizebuffer = (unsigned char*) recvall(client_sock, LENLEN); + if (sizebuffer == NULL) { + vicc_eject(); + return -1; + } + + /* calculate size */ + int size = 0; + int i; + for (i = 0; i < LENLEN; i++) { + size <<= 8; + size += sizebuffer[i]; + } + free(sizebuffer); + + /* receive message */ + *buffer = recvall(client_sock, size); + if (*buffer == NULL) { + vicc_eject(); + return -1; + } + + return size; +} + +int vicc_init() { + server_sock = socket(AF_INET, SOCK_STREAM, 0); + if (server_sock < 0) return -1; + + struct sockaddr_in server_sockaddr; + memset(&server_sockaddr, 0, sizeof(server_sockaddr)); + server_sockaddr.sin_family = PF_INET; + server_sockaddr.sin_port = htons(PORT); + server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(server_sock, (struct sockaddr*)&server_sockaddr, + sizeof(server_sockaddr)) < 0) return -1; + + if (listen(server_sock, 0) < 0) return -1; + + return 0; +} + +int vicc_exit() { + if (server_sock > 0) { + server_sock = close(server_sock); + if (server_sock < 0) return -1; + } + + return 0; +} + +int vicc_transmit(int apdu_len, const char *apdu, char **rapdu) { + if (sendToVICC(apdu_len, apdu) < 0) return -1; + + return recvFromVICC(rapdu); +} + +int vicc_getatr(char** atr) { + return vicc_transmit(0, "", atr); +} + +int vicc_present() { + if (client_sock > 0) return 1; + else { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(server_sock, &rfds); + + /* Wait up to one microsecond. */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1; + + if (select(server_sock+1, &rfds, NULL, NULL, &tv) < 0) return -1; + + if (FD_ISSET(server_sock, &rfds)) { + struct sockaddr_in client_sockaddr; + socklen_t client_socklen = sizeof(client_sockaddr); + client_sock = accept(server_sock, + (struct sockaddr*)&client_sockaddr, + &client_socklen); + if (client_sock == -1) return -1; + + return 1; + } + } + + return 0; +} + +int vicc_poweron() { + char on = 1; + return sendToVICC(1, &on); +} + +int vicc_poweroff() { + char off = 0; + return sendToVICC(1, &off); +} diff --git a/virtualsmartcard/vpicc/ConstantDefinitions.py b/virtualsmartcard/vpicc/ConstantDefinitions.py new file mode 100644 index 0000000..8335914 --- /dev/null +++ b/virtualsmartcard/vpicc/ConstantDefinitions.py @@ -0,0 +1,207 @@ +# Life cycle status byte {{{ +LCB = {} +LCB["NOINFORMATION"] = 0x00 +LCB["CREATION"] = 0x01 +LCB["INITIALISATION"] = 0x03 +LCB["ACTIVATED"] = 0x05 +LCB["DEACTIVATED"] = 0x04 +LCB["TERMINATION"] = 0x0C +# }}} +# Channel security attribute {{{ +CS = {} +CS["NOTSHAREABLE"] = 0x01 +CS["SECURED"] = 0x02 +CS["USERAUTHENTICATED"] = 0x03 +# }}} +# Security attribute {{{ +# Security attribute, Access mode byte for DFs/EFs {{{ +SA = {} +SA["AM_DF_DELETESELF"] = SA["AM_EF_DELETEFILE"] = 0x40 +SA["AM_DF_TERMINATEDF"] = SA["AM_EF_TERMINATEFILE"] = 0x20 +SA["AM_DF_ACTIVATEFILE"] = SA["AM_EF_ACTIVATEFILE"] = 0x10 +SA["AM_DF_DEACTIVATEFILE"] = SA["AM_EF_DEACTIVATEFILE"] = 0x08 +SA["AM_DF_CREATEDF"] = SA["AM_EF_WRITEBINARY"] = 0x04 +SA["AM_DF_CREATEEF"] = SA["AM_EF_UPDATEBINARY"] = 0x02 +SA["AM_DF_DELETECHILD"] = SA["AM_EF_READBINARY"] = 0x01 +# }}} +# Security attribute in compact format, Security condition byte {{{ +SA["CF_SC_NOCONDITION"] = 0x00 +SA["CF_SC_NEVER"] = 0xFF +SA["__CF_SC_ATLEASTONE"] = 0x80 +SA["__CF_SC_ALLCONDITIONS"] = 0x00 +SA["__CF_SC_SECUREMESSAGING"] = 0x40 +SA["__CF_SC_EXTERNALAUTHENTICATION"] = 0x40 +SA["__CF_SC_USERAUTHENTICATION"] = 0x40 +SA["CF_SC_ONE_SECUREMESSAGING"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_SECUREMESSAGING"] +SA["CF_SC_ONE_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_EXTERNALAUTHENTICATION"] +SA["CF_SC_ONE_USERAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_USERAUTHENTICATION"] +SA["CF_SC_ALL_SECUREMESSAGING"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_SECUREMESSAGING"] +SA["CF_SC_ALL_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_EXTERNALAUTHENTICATION"] +SA["CF_SC_ALL_USERAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_USERAUTHENTICATION"] +# }}} +# }}} +# Data coding byte {{{ +DCB = {} +DCB["ONETIMEWRITE"] = 0x00 +DCB["PROPRIETARY"] = 0x20 # we use it for XOR +DCB["WRITEOR"] = 0x40 +DCB["WRITEAND"] = 0x60 +DCB["BERTLV_FFVALID"] = 0x10 +DCB["BERTLV_FFINVALID"] = 0x10 +# }}} +# File descriptor byte {{{ +FDB = {} +FDB["NOTSHAREABLEFILE"] = 0x00 +FDB["SHAREABLEFILE"] = 0x40 +FDB["WORKINGEF"] = 0x00 +FDB["INTERNALEF"] = 0x80 +FDB["DF"] = 0x38 +FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"] = 0x00 +FDB["EFSTRUCTURE_TRANSPARENT"] = 0x01 +FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"] = 0x02 +FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] = 0x03 +FDB["EFSTRUCTURE_LINEAR_VARIABLE_NOFURTHERINFO"] = 0x04 +FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] = 0x05 +FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] = 0x06 +FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"] = 0x07 +# }}} +# File identifier {{{ +FID = {} +FID["EFDIR"] = 0x2F00 +FID["EFATR"] = 0x2F00 +FID["MF"] = 0x3F00 +FID["PATHSELECTION"] = 0x3FFF +FID["RESERVED"] = 0x3FFF +# }}} + +#SM constants {{{ +SM_Class = {} + +#Basic secure messaging objects +#'80', '81' Plain value not encoded in BER-TLV +SM_Class["PLAIN_VALUE_NO_TLV"] = 0x80 +SM_Class["PLAIN_VALUE_NO_TLV_ODD"] = 0x81 +# '82', '83' Cryptogram (plain value encoded in BER-TLV and including SM data objects, i.e., an SM field) +SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"] = 0x82 +SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"] = 0x83 +#'84', '85' Cryptogram (plain value encoded in BER-TLV, but not including SM data objects) +SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"] = 0x84 +SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"] = 0x85 +#'86', '87' Padding-content indicator byte followed by cryptogram (plain value not encoded in BER-TLV) +SM_Class["CRYPTOGRAM_PADDING_INDICATOR"] = 0x86 +SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"] = 0x87 +#'89' Command header (CLA INS P1 P2, four bytes) +SM_Class["PLAIN_COMMAND_HEADER"] = 0x89 +# '8E' Cryptographic checksum (at least four bytes) +SM_Class["CHECKSUM"] = 0x8E +# '90', '91' Hash-code +SM_Class["HASH_CODE"] = 0x90 +SM_Class["HASH_CODE_ODD"] = 0x91 +#'92', '93' Certificate (data not encoded in BER-TLV) +SM_Class["CERTIFICATE"] = 0x92 +SM_Class["CERTIFICATE_ODD"] = 0x93 +#'94', '95' Security environment identifier (SEID byte) +SM_Class["SECURITY_ENIVRONMENT_ID"] = 0x94 +SM_Class["SECURITY_ENIVRONMENT_ID_ODD"] = 0x95 +#'96', '97' One or two bytes encoding Ne in the unsecured command-response pair (possibly empty) +SM_Class["Ne"] = 0x96 +SM_Class["Ne_ODD"] = 0x97 +#'99' Processing status (SW1-SW2, two bytes; possibly empty) +SM_Class["PLAIN_PROCESSING_STATUS"] = 0x99 +# '9A', '9B' Input data element for the computation of a digital signature (the value field is signed) +SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION"] = 0x9A +SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION_ODD"] = 0x9B +# '9C', '9D' Public key +SM_Class["PUBLIC_KEY"] = 0x9C +SM_Class["PUBLIC_KEY_ODD"] = 0x9D +# '9E' Digital signature +SM_Class["DIGITAL_SIGNATURE"] = 0x9E + +#Auxiliary secure messaging objects +#Input template for the computation of a hash-code (the template is hashed) +SM_Class["HASH_COMPUTATION_TEMPLATE"] = 0xA0 +SM_Class["HASH_COMPUTATION_TEMPLATE_ODD"] = 0xA1 +#Input template for the verification of a cryptographic checksum (the template is included) +SM_Class["CHECKSUM_VERIFICATION_TEMPLATE"] = 0xA2 +#Control reference template for authentication (AT) +SM_Class["AUTH_CRT"] = 0xA4 +SM_Class["AUTH_CRT_ODD"] = 0xA5 +#Control reference template for key agreement (KAT) +SM_Class["KEY_AGREEMENT_CRT"] = 0xA6 +SM_Class["KEY_AGREEMENT_CRT_ODD"] = 0xA7 +#Input template for the verification of a digital signature (the template is signed) +SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE" +#Control reference template for hash-code (HT) +SM_Class["HASH_CRT"] = 0xAA +SM_Class["HASH_CRT_ODD"] = 0xAB +#Input template for the computation of a digital signature (the concatenated value fields are signed) +SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xAC +SM_Class["SIGNATURE_COMPUTATION_TEMPLATE_ODD"] = 0xAD +#Input template for the verification of a certificate (the concatenated value fields are certified) +SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xAE +SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE_ODD"] = 0xAF +#Plain value encoded in BER-TLV and including SM data objects, i.e., an SM field +SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"] = 0xB0 +SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"] = 0xB1 +#Plain value encoded in BER-TLV, but not including SM data objects +SM_Class["PLAIN_VALUE_TLV_NO_SM"] = 0xB2 +SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"] = 0xB3 +#Control reference template for cryptographic checksum (CCT) +SM_Class["CHECKSUM_CRT"] =0xB4 +SM_Class["CHECKSUM_CRT_ODD"] =0xB5 +#Control reference template for digital signature (DST) +SM_Class["SIGNATURE_CRT"] = 0xB6 +SM_Class["SIGNATURE_CRT_ODD"] = 0xB7 +#Control reference template for confidentiality (CT) +SM_Class["CONFIDENTIALITY_CRT"] = 0xB8 +SM_Class["CONFIDENTIALITY_CRT_ODD"] = 0xB9 +#Response descriptor template +SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE"] = 0xBA +SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE_ODD"] = 0xBB +#Input template for the computation of a digital signature (the template is signed) +SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xBC +SM_Class["SIGNATURE_COMPUTATION_TEMPLATE_ODD"] = 0xBD +#Input template for the verification of a certificate (the template is certified) +SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xBE +#}}} + + +#This table maps algorithms to numbers. It is used in the security environment +ALGO_MAPPING = {} +ALGO_MAPPING[0x00] = "DES3-ECB" +ALGO_MAPPING[0x01] = "DES3-CBC" +ALGO_MAPPING[0x02] = "AES-ECB" +ALGO_MAPPING[0x03] = "AES-CBC" +ALGO_MAPPING[0x04] = "DES-ECB" +ALGO_MAPPING[0x05] = "DES-CBC" +ALGO_MAPPING[0x06] = "MD5" +ALGO_MAPPING[0x07] = "SHA" +ALGO_MAPPING[0x08] = "SHA256" +ALGO_MAPPING[0x09] = "MAC" +ALGO_MAPPING[0x0A] = "HMAC" +ALGO_MAPPING[0x0B] = "CC" +ALGO_MAPPING[0x0C] = "RSA" +ALGO_MAPPING[0x0D] = "DSA" + +#Tags of control reference templates (CRT) +TEMPLATE_AT = 0xA4 +TEMPLATE_KAT = 0xA6 +TEMPLATE_HT = 0xAA +TEMPLATE_CCT = 0xB4 # Template for Cryptographic Checksum +TEMPLATE_DST = 0xB6 +TEMPLATE_CT = 0xB8 # Template for Confidentiality + +# Recerence control for select command, and record handling commands {{{ +REF = { + "IDENTIFIER_FIRST" : 0x00, + "IDENTIFIER_LAST" : 0x01, + "IDENTIFIER_NEXT" : 0x02, + "IDENTIFIER_PREVIOUS" : 0x03, + "IDENTIFIER_CONTROL" : 0x03, + "NUMBER" : 0x04, + "NUMBER_TO_LAST" : 0x05, + "NUMBER_FROM_LAST" : 0x06, + "NUMBER_CONTROL" : 0x07, + "REFERENCE_CONTROL" : 0x07, + } +# }}} diff --git a/virtualsmartcard/vpicc/CryptoUtils.py b/virtualsmartcard/vpicc/CryptoUtils.py new file mode 100644 index 0000000..8840636 --- /dev/null +++ b/virtualsmartcard/vpicc/CryptoUtils.py @@ -0,0 +1,538 @@ +import sys, binascii, utils, random +from Crypto.Cipher import DES3, DES, AES, ARC4 #,IDEA no longer present in python-crypto? +from struct import pack +from binascii import b2a_hex +from random import randint +from utils import inttostring, stringtoint, hexdump +import string + +try: + # Use PyCrypto (if available) + from Crypto.Hash import HMAC, SHA as SHA1 + +except ImportError: + # PyCrypto not available. Use the Python standard library. + import hmac as HMAC + import sha as SHA1 + +iv = '\x00' * 8 +PADDING = '\x80' + '\x00' * 7 + +## ******************************************************************* +## * Generic methods * +## ******************************************************************* +def get_cipher(cipherspec, key, iv = None): + cipherparts = cipherspec.split("-") + + if len(cipherparts) > 2: + raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"' + elif len(cipherparts) == 1: + cipherparts[1] = "ecb" + + c_class = globals().get(cipherparts[0].upper(), None) + if c_class is None: + raise ValueError, "Cipher '%s' not known, must be one of %s" % (cipherparts[0], ", ".join([e.lower() for e in dir() if e.isupper()])) + + mode = getattr(c_class, "MODE_" + cipherparts[1].upper(), None) + if mode is None: + raise ValueError, "Mode '%s' not known, must be one of %s" % (cipherparts[1], ", ".join([e.split("_")[1].lower() for e in dir(c_class) if e.startswith("MODE_")])) + + cipher = None + if iv is None: + cipher = c_class.new(key, mode) + else: + cipher = c_class.new(key, mode, iv) + + return cipher + +def append_padding(cipherspec, data, padding_class=0x01,keylength = 16): + """Append padding to the data. + Length of padding depends on length of data and the block size of the + specified encryption algorithm. + Different types of padding may be selected via the padding_class parameter + """ + key = "DUMMYKEY" * (keylength / 8) #Key doesn't matter for padding + cipher = get_cipher(cipherspec,key,iv) + if padding_class == 0x01: #ISO padding + last_block_length = len(data) % cipher.block_size + padding_length = cipher.block_size - last_block_length + if padding_length == 0: + padding = PADDING + else: + padding = PADDING[:padding_length] + + del cipher + return data + padding + +def strip_padding(cipherspec,data,padding_class=0x01,keylength = 16): + """ + Strip the padding of decrypted data. Returns data without padding + """ + key = "DUMMYKEY" * (keylength / 8) #Key doesn't matter for padding + cipher = get_cipher(cipherspec,key,iv) + if padding_class == 0x01: + tail = len(data) - 1 + while data[tail] != '\x80': + tail = tail - 1 + return data[:tail] + +def crypto_checksum(algo,key,data,iv=None,ssc=None): + if algo not in ("HMAC","MAC","CC"): + raise ValueError, "Unknown Algorithm %s" % algo + + if algo == "MAC": + checksum = calculate_MAC(key,data,0x00,iv) #FIXME: IV? + elif algo == "HMAC": + hmac = HMAC.new(key,data) + checksum = hmac.hexdigest() + del hmac + elif algo == "CC": + if ssc != None: + data = inttostring(ssc) + data + a = cipher(True, "des-cbc", key[:8], data) + b = cipher(False, "des-ecb", key[8:16], a[-8:]) + c = cipher(True, "des-ecb", key[:8], b) + checksum = c + + return checksum + +def cipher(do_encrypt, cipherspec, key, data, iv = None): + """Do a cryptographic operation. + operation = do_encrypt ? encrypt : decrypt, + cipherspec must be of the form "cipher-mode", or "cipher\"""" + + cipher = get_cipher(cipherspec,key,iv) + + result = None + if do_encrypt: + result = cipher.encrypt(data) + else: + result = cipher.decrypt(data) + + del cipher + return result + +def hash(hashmethod,data): + from Crypto.Hash import SHA, MD5#, RIPEMD + hash_class = locals().get(hashmethod.upper(), None) + if hash_class == None: + print "Unknown Hash method %s" % hashmethod + raise ValueError + hash = hash_class.new() + hash.update(data) + return hash.digest() + +def operation_on_string(string1, string2, op): + if len(string1) != len(string2): + raise ValueError, "string1 and string2 must be of equal length" + result = [] + for i in range(len(string1)): + result.append( chr(op(ord(string1[i]),ord(string2[i]))) ) + return "".join(result) + + +## ******************************************************************* +## * Cyberflex specific methods * +## ******************************************************************* +def verify_card_cryptogram(session_key, host_challenge, + card_challenge, card_cryptogram): + message = host_challenge + card_challenge + expected = calculate_MAC(session_key, message, iv) + + print >>sys.stderr, "Original: %s" % binascii.b2a_hex(card_cryptogram) + print >>sys.stderr, "Expected: %s" % binascii.b2a_hex(expected) + + return card_cryptogram == expected + +def calculate_host_cryptogram(session_key, card_challenge, + host_challenge): + message = card_challenge + host_challenge + return calculate_MAC(session_key, message, iv) + +def calculate_MAC(session_key, message, iv): + print >>sys.stderr, "Doing MAC for: %s" % utils.hexdump(message, indent = 17) + + cipher = DES3.new(session_key, DES3.MODE_CBC, iv) + block_count = len(message) / cipher.block_size + for i in range(block_count): + cipher.encrypt(message[i*cipher.block_size:(i+1)*cipher.block_size]) + + last_block_length = len(message) % cipher.block_size + last_block = (message[len(message)-last_block_length:]+PADDING)[:cipher.block_size] + + return cipher.encrypt( last_block ) + +def get_derivation_data(host_challenge, card_challenge): + return card_challenge[4:8] + host_challenge[:4] + \ + card_challenge[:4] + host_challenge[4:8] + +def get_session_key(auth_key, host_challenge, card_challenge): + cipher = DES3.new(auth_key, DES3.MODE_ECB) + return cipher.encrypt(get_derivation_data(host_challenge, card_challenge)) + +def generate_host_challenge(): + random.seed() + return "".join([chr(random.randint(0,255)) for e in range(8)]) + +def andstring(string1, string2): + return operation_on_string(string1, string2, lambda a,b: a & b) + +########################################################################### +# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation +# +# Copyright (C) 2007, 2008 Dwayne C. Litzenberger +# All rights reserved. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose and without fee is hereby granted, +# provided that the above copyright notice appear in all copies and that +# both that copyright notice and this permission notice appear in +# supporting documentation. +# +# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Country of origin: Canada +# +########################################################################### +# Sample PBKDF2 usage: +# from Crypto.Cipher import AES +# from PBKDF2 import PBKDF2 +# import os +# +# salt = os.urandom(8) # 64-bit salt +# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key +# iv = os.urandom(16) # 128-bit IV +# cipher = AES.new(key, AES.MODE_CBC, iv) +# ... +# +# Sample crypt() usage: +# from PBKDF2 import crypt +# pwhash = crypt("secret") +# alleged_pw = raw_input("Enter password: ") +# if pwhash == crypt(alleged_pw, pwhash): +# print "Password good" +# else: +# print "Invalid password" +# +########################################################################### +# History: +# +# 2007-07-27 Dwayne C. Litzenberger +# - Initial Release (v1.0) +# +# 2007-07-31 Dwayne C. Litzenberger +# - Bugfix release (v1.1) +# - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor +# function in the previous release) silently truncates all keys to 64 +# bytes. The way it was used in the previous release, this would only be +# problem if the pseudorandom function that returned values larger than +# 64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like +# anything that silently reduces the security margin from what is +# expected. +# +# 2008-06-17 Dwayne C. Litzenberger +# - Compatibility release (v1.2) +# - Add support for older versions of Python (2.2 and 2.3). +# +########################################################################### + +__version__ = "1.2" + +def strxor(a, b): + return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) + +def b64encode(data, chars="+/"): + tt = string.maketrans("+/", chars) + return data.encode('base64').replace("\n", "").translate(tt) + +class PBKDF2(object): + """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation + + This implementation takes a passphrase and a salt (and optionally an + iteration count, a digest module, and a MAC module) and provides a + file-like object from which an arbitrarily-sized key can be read. + + If the passphrase and/or salt are unicode objects, they are encoded as + UTF-8 before they are processed. + + The idea behind PBKDF2 is to derive a cryptographic key from a + passphrase and a salt. + + PBKDF2 may also be used as a strong salted password hash. The + 'crypt' function is provided for that purpose. + + Remember: Keys generated using PBKDF2 are only as strong as the + passphrases they are derived from. + """ + + def __init__(self, passphrase, salt, iterations=1000, + digestmodule=SHA1, macmodule=HMAC): + self.__macmodule = macmodule + self.__digestmodule = digestmodule + self._setup(passphrase, salt, iterations, self._pseudorandom) + + def _pseudorandom(self, key, msg): + """Pseudorandom function. e.g. HMAC-SHA1""" + return self.__macmodule.new(key=key, msg=msg, + digestmod=self.__digestmodule).digest() + + def read(self, bytes): + """Read the specified number of key bytes.""" + if self.closed: + raise ValueError("file-like object is closed") + + size = len(self.__buf) + blocks = [self.__buf] + i = self.__blockNum + while size < bytes: + i += 1 + if i > 0xffffffffL or i < 1: + # We could return "" here, but + raise OverflowError("derived key too long") + block = self.__f(i) + blocks.append(block) + size += len(block) + buf = "".join(blocks) + retval = buf[:bytes] + self.__buf = buf[bytes:] + self.__blockNum = i + return retval + + def __f(self, i): + # i must fit within 32 bits + assert 1 <= i <= 0xffffffffL + U = self.__prf(self.__passphrase, self.__salt + pack("!L", i)) + result = U + for j in xrange(2, 1+self.__iterations): + U = self.__prf(self.__passphrase, U) + result = strxor(result, U) + return result + + def hexread(self, octets): + """Read the specified number of octets. Return them as hexadecimal. + + Note that len(obj.hexread(n)) == 2*n. + """ + return b2a_hex(self.read(octets)) + + def _setup(self, passphrase, salt, iterations, prf): + # Sanity checks: + + # passphrase and salt must be str or unicode (in the latter + # case, we convert to UTF-8) + if isinstance(passphrase, unicode): + passphrase = passphrase.encode("UTF-8") + if not isinstance(passphrase, str): + raise TypeError("passphrase must be str or unicode") + if isinstance(salt, unicode): + salt = salt.encode("UTF-8") + if not isinstance(salt, str): + raise TypeError("salt must be str or unicode") + + # iterations must be an integer >= 1 + if not isinstance(iterations, (int, long)): + raise TypeError("iterations must be an integer") + if iterations < 1: + raise ValueError("iterations must be at least 1") + + # prf must be callable + if not callable(prf): + raise TypeError("prf must be callable") + + self.__passphrase = passphrase + self.__salt = salt + self.__iterations = iterations + self.__prf = prf + self.__blockNum = 0 + self.__buf = "" + self.closed = False + + def close(self): + """Close the stream.""" + if not self.closed: + del self.__passphrase + del self.__salt + del self.__iterations + del self.__prf + del self.__blockNum + del self.__buf + self.closed = True + +def crypt(word, salt=None, iterations=None): + """PBKDF2-based unix crypt(3) replacement. + + The number of iterations specified in the salt overrides the 'iterations' + parameter. + + The effective hash length is 192 bits. + """ + + # Generate a (pseudo-)random salt if the user hasn't provided one. + if salt is None: + salt = _makesalt() + + # salt must be a string or the us-ascii subset of unicode + if isinstance(salt, unicode): + salt = salt.encode("us-ascii") + if not isinstance(salt, str): + raise TypeError("salt must be a string") + + # word must be a string or unicode (in the latter case, we convert to UTF-8) + if isinstance(word, unicode): + word = word.encode("UTF-8") + if not isinstance(word, str): + raise TypeError("word must be a string or unicode") + + # Try to extract the real salt and iteration count from the salt + if salt.startswith("$p5k2$"): + (iterations, salt, dummy) = salt.split("$")[2:5] + if iterations == "": + iterations = 400 + else: + converted = int(iterations, 16) + if iterations != "%x" % converted: # lowercase hex, minimum digits + raise ValueError("Invalid salt") + iterations = converted + if not (iterations >= 1): + raise ValueError("Invalid salt") + + # Make sure the salt matches the allowed character set + allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" + for ch in salt: + if ch not in allowed: + raise ValueError("Illegal character %r in salt" % (ch,)) + + if iterations is None or iterations == 400: + iterations = 400 + salt = "$p5k2$$" + salt + else: + salt = "$p5k2$%x$%s" % (iterations, salt) + rawhash = PBKDF2(word, salt, iterations).read(24) + # return salt + "$" + b64encode(rawhash, "./") DO: Original return line + return salt + "$" + rawhash +# Add crypt as a static method of the PBKDF2 class +# This makes it easier to do "from PBKDF2 import PBKDF2" and still use +# crypt. +PBKDF2.crypt = staticmethod(crypt) + +def _makesalt(): + """Return a 48-bit pseudorandom salt for crypt(). + + This function is not suitable for generating cryptographic secrets. + """ + binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)]) + return b64encode(binarysalt, "./") + +def test_pbkdf2(): + """Module self-test""" + from binascii import a2b_hex + + # + # Test vectors from RFC 3962 + # + + # Test 1 + result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16) + expected = a2b_hex("cdedb5281bb2f801565a1122b2563515") + if result != expected: + raise RuntimeError("self-test failed") + + # Test 2 + result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32) + expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b" + "a7e52ddbc5e5142f708a31e2e62b1e13") + if result != expected: + raise RuntimeError("self-test failed") + + # Test 3 + result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32) + expected = ("139c30c0966bc32ba55fdbf212530ac9" + "c5ec59f1a452f5cc9ad940fea0598ed1") + if result != expected: + raise RuntimeError("self-test failed") + + # Test 4 + result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32) + expected = ("9ccad6d468770cd51b10e6a68721be61" + "1a8b4d282601db3b36be9246915ec82a") + if result != expected: + raise RuntimeError("self-test failed") + + # + # Other test vectors + # + + # Chunked read + f = PBKDF2("kickstart", "workbench", 256) + result = f.read(17) + result += f.read(17) + result += f.read(1) + result += f.read(2) + result += f.read(3) + expected = PBKDF2("kickstart", "workbench", 256).read(40) + if result != expected: + raise RuntimeError("self-test failed") + + # + # crypt() test vectors + # + + # crypt 1 + result = crypt("cloadm", "exec") + expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql' + if result != expected: + raise RuntimeError("self-test failed") + + # crypt 2 + result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....') + expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g' + if result != expected: + raise RuntimeError("self-test failed") + + # crypt 3 + result = crypt("dcl", "tUsch7fU", iterations=13) + expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL" + if result != expected: + raise RuntimeError("self-test failed") + + # crypt 4 (unicode) + result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2', + '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ') + expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ' + if result != expected: + raise RuntimeError("self-test failed") + print "PBKDF2 self test successfull" + +if __name__ == "__main__": + default_key = binascii.a2b_hex("404142434445464748494A4B4C4D4E4F") + + host_chal = binascii.a2b_hex("".join("89 45 19 BF BC 1A 5B D8".split())) + card_chal = binascii.a2b_hex("".join("27 4D B7 EA CA 66 CE 44".split())) + card_crypto = binascii.a2b_hex("".join("8A D4 A9 2D 9B 6B 24 E0".split())) + + session_key = get_session_key(default_key, host_chal, card_chal) + print "Session-Key: ", utils.hexdump(session_key) + + print verify_card_cryptogram(session_key, host_chal, card_chal, card_crypto) + + host_crypto = calculate_host_cryptogram(session_key, card_chal, host_chal) + print "Host-Crypto: ", utils.hexdump( host_crypto ) + + external_authenticate = binascii.a2b_hex("".join("84 82 01 00 10".split())) + host_crypto + print utils.hexdump(calculate_MAC(session_key, external_authenticate, iv)) + + too_short = binascii.a2b_hex("".join("89 45 19 BF".split())) + padded = append_padding("DES3-ECB",len(too_short),too_short) + print "Padded data: " + utils.hexdump(padded) + unpadded = strip_padding("DES3-ECB",padded) + print "Without padding: " + utils.hexdump(unpadded) + test_pbkdf2() diff --git a/virtualsmartcard/vpicc/SEutils.py b/virtualsmartcard/vpicc/SEutils.py new file mode 100644 index 0000000..04f6b7b --- /dev/null +++ b/virtualsmartcard/vpicc/SEutils.py @@ -0,0 +1,105 @@ +import TLVutils +from time import time +from random import seed, randint +from ConstantDefinitions import * +from utils import inttostring, stringtoint + +class ControlReferenceTemplate: + def __init__(self,type,config=""): + """ + Generates a new CRT + @param type: Type of the CRT (HT, AT, KT, CCT, DST, CT-sym, CT-asym) + @param config: A string containing TLV encoded Security Environment parameters + """ + if type not in (TEMPLATE_AT, TEMPLATE_HT, TEMPLATE_KAT, TEMPLATE_CCT, TEMPLATE_DST, TEMPLATE_CT): + raise ValueError, "Unknown control reference tag." + else: + self.type = type + + self.iv = None + self.keyref = None + self.key = None + self.fileref = None + self.DFref = None + self.keylength = None + self.algorithm = None + self.usage_qualifier = None + if config != "": + self.parse_SE_config(config) + self.__config_string = config + + def parse_SE_config(self,config): + structure = TLVutils.unpack(config) + for object in structure: + tag, length, value = object + if tag == 0x80: + self.__set_algo(data) + elif tag in (0x81,0x82,0x83,0x84): + self.__set_key(tag,length,value) + elif tag in range(0x85, 0x93): + self.__set_iv(tag,length,value) + elif tag == 0x95: + self.usage_qualifier = data + + def __set_algo(self,data): + if not ALGO_MAPPING.has_key(data): + raise ValueError + else: + #TODO: Sanity checking + self.algorithm = ALGO_MAPPING[data] + self.__replace_tag(0x80,data) + + def __set_key(self,tag,length,value): + if tag == 0x81: + self.fileref = value + elif tag == 0x82: + self.DFref = value + elif tag in (0x83,0x84): + #Todo: Resolve reference + self.keyref = value + + def __set_iv(self,tag,length,value): + iv = None + if tag == 0x85: + iv = 0x00 + elif tag == 0x86: + pass #What is initial chaining block? + elif tag == 0x87 or tag == 0x93: + if length != 0: + iv = value + else: + iv = self.iv + 1 + elif tag == 0x91: + if length != 0: + iv = value + else: + seed(time()) + iv = hex(randint(0,255)) + elif tag == 0x92: + if length != 0: + iv = value + else: + iv = int(time()) + self.iv = iv + + def __replace_tag(self,tag,data): + position = 0 + while self.__config_string[position:position+1] != tag and position < len(self.__config_string): + length = inttostring(self.__config_string[position+1:position+2]) + position += length + 3 + if position < len(self.__config_string): #Replace Tag + length = inttostring(self.__config_string[position+1:position+2]) + self.__config_string = self.__config_string[:position] + tag + inttostring(len(data)) + data + self.__config_string[position+2+length:] + else: #Add new tag + self.__config_string += tag + inttostring(len(data)) + data + + def to_string(self): + """ + Return the content of the CRT, encoded as TLV data in a string + """ + return self.__config_string + + def __str__(self): + return self.__config_string + + diff --git a/virtualsmartcard/vpicc/SWutils.py b/virtualsmartcard/vpicc/SWutils.py new file mode 100644 index 0000000..619d0f3 --- /dev/null +++ b/virtualsmartcard/vpicc/SWutils.py @@ -0,0 +1,115 @@ +# Meaning of the interindustry values of SW1-SW2 {{{ +SW = { + "NORMAL" : 0x9000, + "NORMAL_REST" : 0x6100, + "WARN_NOINFO62" : 0x6200, + "WARN_DATACORRUPTED" : 0x6281, + "WARN_EOFBEFORENEREAD" : 0x6282, + "WARN_DEACTIVATED" : 0x6283, + "WARN_FCIFORMATTING" : 0x6284, + "WARN_TERMINATIONSTATE" : 0x6285, + "WARN_NOINPUTSENSOR" : 0x6286, + "WARN_NOINFO63" : 0x6300, + "WARN_FILEFILLED" : 0x6381, + "ERR_EXECUTION" : 0x6400, + "ERR_RESPONSEREQUIRED" : 0x6401, + "ERR_NOINFO65" : 0x6500, + "ERR_MEMFAILURE" : 0x6581, + "ERR_WRONGLENGTH" : 0x6700, + "ERR_NOINFO68" : 0x6800, + "ERR_CHANNELNOTSUPPORTED" : 0x6881, + "ERR_SECMESSNOTSUPPORTED" : 0x6882, + "ERR_LASTCMDEXPECTED" : 0x6883, + "ERR_CHAININGNOTSUPPORTED" : 0x6884, + "ERR_NOINFO69" : 0x6900, + "ERR_INCOMPATIBLEWITHFILE" : 0x6981, + "ERR_SECSTATUS" : 0x6982, + "ERR_AUTHBLOCKED" : 0x6983, + "ERR_REFNOTUSABLE" : 0x6984, + "ERR_CONDITIONNOTSATISFIED" : 0x6985, + "ERR_NOCURRENTEF" : 0x6986, + "ERR_SECMESSOBJECTSMISSING" : 0x6987, + "ERR_SECMESSOBJECTSINCORRECT" : 0x6988, + "ERR_NOINFO6A" : 0x6A00, + "ERR_INCORRECTPARAMETERS" : 0x6A80, + "ERR_NOTSUPPORTED" : 0x6A81, + "ERR_FILENOTFOUND" : 0x6A82, + "ERR_RECORDNOTFOUND" : 0x6A83, + "ERR_NOTENOUGHMEMORY" : 0x6A84, + "ERR_NCINCONSISTENTWITHTLV" : 0x6A85, + "ERR_INCORRECTP1P2" : 0x6A86, + "ERR_NCINCONSISTENTP1P2" : 0x6A87, + "ERR_DATANOTFOUND" : 0x6A88, + "ERR_FILEEXISTS" : 0x6A89, + "ERR_DFNAMEEXISTS" : 0x6A8A, + "ERR_OFFSETOUTOFFILE" : 0x6B00, + "ERR_INSNOTSUPPORTED" : 0x6D00, +} + +SW_MESSAGES = { + 0x9000 : 'Normal processing (No further qualification)', + + 0x6200 : 'Warning processing (State of non-volatile memory is unchanged): No information given', + 0x6281 : 'Warning processing (State of non-volatile memory is unchanged): Part of returned data may be corrupted', + 0x6282 : 'Warning processing (State of non-volatile memory is unchanged): End of file or record reached before reading Ne bytes', + 0x6283 : 'Warning processing (State of non-volatile memory is unchanged): Selected file deactivated', + 0x6284 : 'Warning processing (State of non-volatile memory is unchanged): File control information not formatted according to 5.3.3', + 0x6285 : 'Warning processing (State of non-volatile memory is unchanged): Selected file in termination state', + 0x6286 : 'Warning processing (State of non-volatile memory is unchanged): No input data available from a sensor on the card', + + 0x6300 : 'Warning processing (State of non-volatile memory has changed): No information given', + 0x6381 : 'Warning processing (State of non-volatile memory has changed): File filled up by the last write', + + 0x6400 : 'Execution error (State of non-volatile memory is unchanged): Execution error', + 0x6401 : 'Execution error (State of non-volatile memory is unchanged): Immediate response required by the card', + + 0x6500 : 'Execution error (State of non-volatile memory has changed): No information given', + 0x6581 : 'Execution error (State of non-volatile memory has changed): Memory failure', + + 0x6700 : 'Checking error: Wrong length; no further indication', + + 0x6800 : 'Checking error (Functions in CLA not supported): No information given', + 0x6881 : 'Checking error (Functions in CLA not supported): Logical channel not supported', + 0x6882 : 'Checking error (Functions in CLA not supported): Secure messaging not supported', + 0x6883 : 'Checking error (Functions in CLA not supported): Last command of the chain expected', + 0x6884 : 'Checking error (Functions in CLA not supported): Command chaining not supported', + + 0x6900 : 'Checking error (Command not allowed): No information given', + 0x6981 : 'Checking error (Command not allowed): Command incompatible with file structure', + 0x6982 : 'Checking error (Command not allowed): Security status not satisfied', + 0x6983 : 'Checking error (Command not allowed): Authentication method blocked', + 0x6984 : 'Checking error (Command not allowed): Reference data not usable', + 0x6985 : 'Checking error (Command not allowed): Conditions of use not satisfied', + 0x6986 : 'Checking error (Command not allowed): Command not allowed (no current EF)', + 0x6987 : 'Checking error (Command not allowed): Expected secure messaging data objects missing', + 0x6988 : 'Checking error (Command not allowed): Incorrect secure messaging data objects', + + 0x6A00 : 'Checking error (Wrong parameters P1-P2): No information given', + 0x6A80 : 'Checking error (Wrong parameters P1-P2): Incorrect parameters in the command data field', + 0x6A81 : 'Checking error (Wrong parameters P1-P2): Function not supported', + 0x6A82 : 'Checking error (Wrong parameters P1-P2): File or application not found', + 0x6A83 : 'Checking error (Wrong parameters P1-P2): Record not found', + 0x6A84 : 'Checking error (Wrong parameters P1-P2): Not enough memory space in the file', + 0x6A85 : 'Checking error (Wrong parameters P1-P2): Nc inconsistent with TLV structure', + 0x6A86 : 'Checking error (Wrong parameters P1-P2): Incorrect parameters P1-P2', + 0x6A87 : 'Checking error (Wrong parameters P1-P2): Nc inconsistent with parameters P1-P2', + 0x6A88 : 'Checking error (Wrong parameters P1-P2): Referenced data or reference data not found (exact meaning depending on the command)', + 0x6A89 : 'Checking error (Wrong parameters P1-P2): File already exists', + 0x6A8A : 'Checking error (Wrong parameters P1-P2): DF name already exists', + + 0x6B00 : 'Checking error (Wrong parameters P1-P2): Wrong parameters (offset outside the EF)', + 0x6D00 : 'Checking error (Instruction code not supported or invalid)', + 0x6E00 : 'Checking error (Class not supported)', + 0x6F00 : 'Checking error (No precise diagnosis)', +} +for i in range(0, 0xff): + SW_MESSAGES[0x6100 + i] = 'Normal Processing (%d data bytes still available)' % i + SW_MESSAGES[0x6600 + i] = 'Execution error (Security-related issues)' + SW_MESSAGES[0x6C00 + i] = 'Checking error (Wrong Le field; %d available data bytes)' % i + +# }}} + +class SwError(Exception): + def __init__(self, sw): + self.sw = sw + self.message = SW_MESSAGES[sw] diff --git a/virtualsmartcard/vpicc/SmartcardFilesystem.py b/virtualsmartcard/vpicc/SmartcardFilesystem.py new file mode 100644 index 0000000..3fd3a0e --- /dev/null +++ b/virtualsmartcard/vpicc/SmartcardFilesystem.py @@ -0,0 +1,1684 @@ +""" +@todo use bertlv_pack for fdm +@todo zu lange daten abschneiden und trotzdem tlv laenge beibehalten +""" +from pickle import dumps, loads +from ConstantDefinitions import * +from TLVutils import * +from SWutils import * +from utils import stringtoint, inttostring + +def isEqual(list): # {{{ + """Returns True, if all items are equal, otherwise False""" + if len(list) > 1: + for item in list: + if item != list[0]: + return False + + return True +# }}} + +def walk(start, path): # {{{ + """Walks a path of fids and returns the last file (EF or DF). + + start -- DF, where to look for the first fid + path -- a string of fids + """ + if len(path) % 2 != 0: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + index = 0 + while index < len(path): + if not isinstance(start, DF): + # File or application not found + raise SwError(SW["ERR_FILENOTFOUND"]) + start = start.select('fid', stringtoint(path[:2])) + index = index + 2 + + return start +# }}} + +def getfile_byrefdataobj(mf, refdataobjs): # {{{ + """Returns a list of files according to the given list of reference data + objects. + + mf -- the MF + refdataobjs -- a list of 3-tuples (tag, length, newvalue)""" + + result = [None] + for tag, length, newvalue in refdataobjs: + if tag != TAG["FILE_REFERENCE"]: + raise ValueError + + if length == 0: + file = mf + + elif length <= 2: + newvalue = stringtoint(newvalue) + if length == 1: + if newvalue & 5 != 0 or (newvalue >> 3) == 0 or (newvalue>>3) == (0xff>>3): + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + else: + file = mf.select('shortfid', newvalue >> 3) + + else: + # length == 2: + file = mf.select('fid', newvalue) + + else: + if length % 2 == 1: + raise NotImplementedError + else: + if newvalue[:2] == "\x3f\x00": + # absolute + file = walk(mf, newvalue[2:]) + else: + # relative + file = walk(mf.currentDF(), newvalue[2:]) + + result.append(file) + + return result +# }}} + +def write(old, newlist, offsets, datacoding, maxsize=None): # {{{ + """Returns the status bytes and the result of a writeoperation according to + the given data coding. + + @param old: string of old data + @param newlist: a list of new data string + @param offsets: a list of offsets, each for one new data strings + @param datacoding: DCB["ONETIMEWRITE"] (replace) or DCB["WRITEOR"] (logical or) + or DCB["WRITEAND"] (logical and) or DCB["PROPRIETARY"] + (logical xor) + @param maxsize: the maximum number of bytes in the result + """ + result = old + listindex = 0 + while listindex < len(offsets) and listindex < len(newlist): + offset = offsets[listindex] + new = newlist[listindex] + writenow = len(new) + + if offset > len(result): + raise SwError(SW["ERR_OFFSETOUTOFFILE"]) + if maxsize and offset + writenow > maxsize: + raise SwError(SW["ERR_NOTENOUGHMEMORY"], old) + + if datacoding == DCB["ONETIMEWRITE"]: + result = (result[ 0 : offset ] + + new[ 0 : writenow ] + + result[ offset+writenow : len(result) ]) + + else: + if offset + writenow > len(old): + raise SwError(SW["ERR_NOTENOUGHMEMORY"], old) + + newindex = 0 + resultindex = offset + newindex + while newindex < writenow: + if datacoding == DCB["WRITEOR"]: + newpiece = chr( + ord(result[resultindex]) | ord(new[newindex])) + elif datacoding == DCB["WRITEAND"]: + newpiece = chr( + ord(result[resultindex]) & ord(new[newindex])) + elif datacoding == DCB["PROPRIETARY"]: + # we use it for XOR + newpiece = chr( + ord(result[resultindex]) ^ ord(new[newindex])) + result = (result[0:resultindex] + newpiece + + result[resultindex+1:len(result)]) + newindex = newindex + 1 + resultindex = resultindex + 1 + + listindex = listindex + 1 + + return result +# }}} + +def get_indexes(items, reference=REF["IDENTIFIER_FIRST"], index_current=0): # {{{ + """ + Returns all indexes of the list, which are specified by 'reference' and by + the current index 'index_current' (-1 for no current item) in the correct + order. I. e.: + + REF["IDENTIFIER_FIRST"] : all indexes from first to the last item + REF["IDENTIFIER_LAST"] : all indexes from the last to first item + REF["IDENTIFIER_NEXT"] : all indexes from the next to the last item + REF["IDENTIFIER_PREVIOUS"] : all indexes from the previous to the first item + """ + if (reference in [REF["IDENTIFIER_FIRST"], + REF["IDENTIFIER_LAST"]] + or index_current == -1): + # Read first occurence OR + # Read last occurrence OR + # No current record (and next/previous occurrence) + indexes = range(0, len(items)) + if (reference == REF["IDENTIFIER_LAST"] + or reference == REF["IDENTIFIER_PREVIOUS"]): + # Read last occurrence OR + # No current record and previous occurrence + indexes.reverse(); + elif reference == REF["IDENTIFIER_PREVIOUS"]: + # Read previous occurrence + indexes = range(0, index_current) + indexes.reverse() + else: + # Read next occurrence + indexes = range(index_current + 1, len(items)) + return indexes +# }}} + +def prettyprint_anything(indent, thing): # {{{ + """ + Returns a recursively generated string representation of an object and its + attributes. + """ + s = "%s{%s at 0x%x}:" % (indent, thing.__class__.__name__, id(thing)) + indent = indent + " " + for (attribute, newvalue) in thing.__dict__.items(): + if isinstance(newvalue, int): + s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "0x%x" % (newvalue) + elif isinstance(newvalue, str): + s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "length %d" % len(newvalue) + elif isinstance(newvalue, list): + s = s + "\n" + indent + attribute + (16-len(attribute))*" " + for item in newvalue: + s = s + "\n" + prettyprint_anything(indent + " ", item) + return s +# }}} + +def make_property(prop, doc): # {{{ + """ + Assigns a property to the calling object. This is used to decorate instance + variables with docstrings. + """ + return property( + lambda self: getattr(self, "_"+prop), + lambda self, value: setattr(self, "_"+prop, value), + lambda self: delattr(self, "_"+prop), + doc) +# }}} + + + +class File(object): # {{{ + """Template class for a smartcard file.""" + bertlv_data = make_property("bertlv_data", "list of (tag, length, value)-tuples of BER-TLV coded data objects (encrypted)") + lifecycle = make_property("lifecycle", "life cycle byte") + parent = make_property("parent ", "parent DF") + fid = make_property("fid", "file identifier") + filedescriptor = make_property("filedescriptor", "file descriptor byte") + simpletlv_data = make_property("simpletlv_data", "list of (tag, length, value)-tuples of SIMPLE-TLV coded data objects (encrypted)") + def __init__(self, parent, fid, filedescriptor, + lifecycle=LCB["ACTIVATED"], + simpletlv_data=None, + bertlv_data=None, + SAM=None): + """ + The constructor is supposed to be involved by creation of a DF or EF. + """ + if (fid>0xFFFF or fid<0 or fid in [FID["EFDIR"], FID["EFATR"], + FID["PATHSELECTION"], FID["RESERVED"]] or filedescriptor>0xFF or + lifecycle>0xFF): + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + self.lifecycle=lifecycle + self.parent = parent + if parent: + if parent.fid == fid: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + self.fid=fid + self.filedescriptor=filedescriptor + self.SAM = SAM + if simpletlv_data: + if not isinstance(simpletlv_data, list): + raise ValueError, "must be a list of (tag, length, value)-tuples" + self.setdec('simpletlv_data', simpletlv_data) + if bertlv_data: + if not isinstance(bertlv_data, list): + raise ValueError, "must be a list of (tag, length, value)-tuples" + self.setdec('bertlv_data', bertlv_data) + + def decrypt(self,path, data): + if self.SAM == None: #WARNING: Fails silent + return data + else: + return self.SAM.FSencrypt(path,data) + + def encrypt(self,path, data): + if self.SAM == None: #WARNING: Fails silent + return data + else: + return self.SAM.FSdecrypt(path,data) + + def __str__(self): + """Returns a string of the object using an prettyprint_anything.""" + return prettyprint_anything("", self) + __repr__ = __str__ + + def getpath(self): + """Returns the path to this file beginning with the MF's fid.""" + if self.parent == None: + return inttostring(self.fid, 2) + else: + return self.parent.getpath() + inttostring(self.fid, 2) + + def getenc(self, attribute): + """Returns the decrypted data of an 'attribute' from the file.""" + return loads(self.decrypt(self.getpath(), getattr(self, attribute))) + + def setdec(self, attribute, data): + """Encrypts 'data' and sets the file's 'attribute' to the chiffre.""" + setattr(self, attribute, dumps(self.encrypt(self.getpath(), data))) + + def getdata(self, isSimpleTlv, requestedTL): + """ + Returns a string of either the file's BER-TLV or the file's SIMPLE-TLV + coded data objects depending on the bool 'isSimpleTlv'. 'requestedTL' + is a list of (tag, length)-tuples that specify which tags should be + returned in what size. + """ + if isSimpleTlv: + attribute = 'simpletlv_data' + else: + attribute = 'bertlv_data' + + if not hasattr(self, attribute): + raise SwError(SW["ERR_NOTSUPPORTED"]) + + tlv_data = self.getenc(attribute) + if requestedTL == []: + result = tlv_data + else: + result = [] + for T, L in requestedTL: + tagfound = False + for i in range(0, len(tlv_data)): + tag, _, value = tlv_data[i] + if T == tag: + tagfound = True + if L == 0: + result.append(tlv_data[i]) + else: + result.append((T, L, value[:L])) + if not tagfound: + raise SwError(SW["ERR_DATANOTFOUND"]) + + if isSimpleTlv: + return simpletlv_pack(result) + return bertlv_pack(result) + + def putdata(self, isSimpleTlv, newtlvlist): + """ + Sets either the file's BER-TLV or the file's SIMPLE-TLV coded data + objects depending on the bool 'isSimpleTlv'. 'newtlvlist' is a list of + (tag, length, value)-tuples of new data. + """ + if isSimpleTlv: + attribute = 'simpletlv_data' + else: + attribute = 'bertlv_data' + + if not hasattr(self, attribute): + raise SwError(SW["ERR_NOTSUPPORTED"]) + + tlv_data = self.getenc(attribute) + for tag, newlength, newvalue in newtlvlist: + tagfound = False + for i in range(0, len(tlv_data)): + t, oldlength, oldvalue = tlv_data[i] + if t == tag: + # TODO: what if multiple tags can be found? + value = write(oldvalue, [newvalue], [0], newlength, + self.datacoding) + tlv_data[i] = (tag, len(value), value) + tagfound = True + if not tagfound: + tlv_data.append(tag, newlength, newvalue) + self.setdec(attribute, tlv_data) + + def readbinary(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def writebinary(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def updatebinary(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def erasebinary(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def readrecord(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def writerecord(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def appendrecord(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def updaterecord(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + + def select(*argz, **args): + """Only a template, will raise an error.""" + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) +# }}} + + +class DF(File): # {{{ + """Class for a dedicated file""" + data = make_property("data", "unknown") + content = make_property("content", "list of files of the DF") + dfname = make_property("dfname", "string with up to 16 bytes. DF name, which can also be used as application identifier.") + def __init__(self, parent, fid, filedescriptor=FDB["NOTSHAREABLEFILE"], + lifecycle=LCB["ACTIVATED"], + simpletlv_data=None, bertlv_data=None, dfname=None, data=""): + """ + The flag FDB["DF"] is automatically added to the file descriptor byte + filedescriptor. + + See File for more. + """ + File.__init__(self, parent, fid, filedescriptor|FDB["DF"], lifecycle, + simpletlv_data, bertlv_data) + if dfname: + if len(dfname)>16: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + self.dfname=dfname + self.content = [] + # TODO: opensc sends the length of data, but what does it limit + # (bertlv-data/simpletlv-data/number of files in DF, ...)? + self.setdec('data', data) + + def __len__(self): + """ + x.__len__() <==> len(x.content) + """ + return len(self.content) + + def __getitem__(self, key): + """ + x.__getitem__(key) <==> x.content[key] + """ + return self.content[key] + + def __setitem__(self, key, value): + """ + x.__setitem__(key, value) <==> x.content[key]=value + """ + return self.content[key] + + def __delitem__(self, key): + """ + x.__delitem__(key) <==> del x.content[key] + """ + del content[key] + + def __contains__(self, item): + """ + x.__contains__(item) <==> item in x.content + """ + return item in content + + def append(self, file): + """Appends 'file' to the content of the DF.""" + if not (isinstance(file, DF) or isinstance(file, EF)): + raise TypeError + + if (self.fid == file.fid or file.fid == FID["MF"] or file.fid == + FID["RESERVED"] or file.fid == FID["PATHSELECTION"]): + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + for f in self.content: + if f.fid == file.fid: + raise SwError(SW["ERR_FILEEXISTS"]) + if hasattr(f, 'dfname') and hasattr(file, 'dfname') and f.dfname == file.dfname: + raise SwError(SW["ERR_DFNAMEEXISTS"]) + if hasattr(f, 'shortfid') and hasattr(file, 'shortfid') and f.shortfid == file.shortfid: + raise SwError(SW["ERR_FILEEXISTS"]) + + self.content.append(file) + + def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"], + index_current=0): + """ + Returns the first file of the DF, that has the 'attribute' with the + specified 'value'. For partial DF name selection you must specify the + first/last/next or previous occurence with 'reference' and the index of + the current file 'index_current' (-1 for None). + """ + indexes = get_indexes(self.content, reference, index_current) + + for i in indexes: + file = self.content[i] + if ((hasattr(file, attribute) and getattr(file, attribute)==value) + or (attribute == 'dfname' and getattr(file, + attribute).startswith(value))): + return file + # not found + if isinstance(value, int): + print "file not found %s=%x" % (attribute, value) + elif isinstance(value, str): + print "file not found %s=%r" % (attribute, value) + raise SwError(SW["ERR_FILENOTFOUND"]) + + def remove(self, file): + """Removes 'file' from the content of the DF""" + content.remove(file) +# }}} + + +class MF(DF): # {{{ + """Class for a master file""" + current = make_property("current", "the currently selected file") + firstSFT = make_property("firstSFT", "string of length 1. The first software function table from the historical bytes.") + secondSFT = make_property("secondSFT", "string of length 1. The second software function table from the historical bytes.") + def __init__(self, filedescriptor=FDB["NOTSHAREABLEFILE"], + lifecycle=LCB["ACTIVATED"], + simpletlv_data=None, bertlv_data=None, dfname=None): + """The flag FDB["DF"] is automatically added to the file descriptor + bytey filedescriptor. The file identifier FID["MF"] is automatically + added. + + See DF for more. + """ + DF.__init__(self, None, FID["MF"], filedescriptor|FDB["DF"], lifecycle, + simpletlv_data, bertlv_data, dfname) + self.current = self + self.firstSFT = inttostring(MF.makeFirstSoftwareFunctionTable(), 1) + self.secondSFT = inttostring(MF.makeSecondSoftwareFunctionTable(), 1) + + + @staticmethod + def makeFirstSoftwareFunctionTable(# {{{ + DFSelectionByFullDFName=True, DFSelectionByPartialDFName=True, + DFSelectionByPath=True, DFSelectionByFID=True, + DFSelectionByApplication_implicite=True, ShortFIDSupported=True, + RecordNumberSupported=True, RecordIdentifierSupported=True): + """ + Returns a byte according to the first software function table from the + historical bytes of the card capabilities. + + DFSelectionByFullDFName -- Bool (True). + DFSelectionByPartialDFName -- Bool (True). + DFSelectionByPath -- Bool (True). + DFSelectionByFID -- Bool (True). + DFSelectionByApplication_implicite -- Bool (True). + ShortFIDSupported -- Bool (True). + RecordNumberSupported -- Bool (True). + RecordIdentifierSupported -- Bool (True). + """ + fsft = 0 + if DFSelectionByFullDFName: + fsft |= 1 << 7 + if DFSelectionByPartialDFName: + fsft |= 1 << 6 + if DFSelectionByPath: + fsft |= 1 << 5 + if DFSelectionByFID: + fsft |= 1 << 4 + if DFSelectionByApplication_implicite: + fsft |= 1 << 3 + if ShortFIDSupported: + fsft |= 1 << 2 + if RecordNumberSupported: + fsft |= 1 << 1 + if RecordIdentifierSupported: + fsft |= 1 + return fsft + # }}} + + @staticmethod + def makeSecondSoftwareFunctionTable(DCB=DCB["ONETIMEWRITE"]|1): # {{{ + """ + The second software function table from the historical bytes contains + the data coding byte. + """ + return DCB + # }}} + + def currentDF(self): + """Returns the current DF.""" + if isinstance(self.current, EF): + return self.current.parent + else: + return self.current + + def currentEF(self): + """Returns the current EF or None if not available.""" + if isinstance(self.current, EF): + return self.current + else: + return None + + @staticmethod + def encodeFileControlParameter(file): + """ + Returns a string of TLV-coded file control information of 'file'. Note: + The result is not prepended with tag and length for neither TCP, FMD + nor FCI template. + """ + fdm = [ chr(TAG["FILEIDENTIFIER"])+"\x02"+inttostring(file.fid, 2), + chr(TAG["LIFECYCLESTATUS"])+"\x01"+chr(file.lifecycle) ] + + # TODO filesize and data objects + if isinstance(file, EF): + if hasattr(file, 'shortfid'): + fdm.append("%c\x01%c" % (TAG["SHORTFID"], file.shortfid)) + else: + fdm.append("%c\x00" % TAG["SHORTFID"]) + + if isinstance(file, TransparentStructureEF): + l = inttostring(len(file.getenc('data'))) + fdm.append("%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"], + chr(len(l)), l)) + fdm.append("%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"], + chr(len(l)), l)) + fdm.append("%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"], + file.filedescriptor, file.datacoding)) + + elif isinstance(file, RecordStructureEF): + l = 0 + records = file.getenc('records') + for r in records: + if file.hasSimpleTlv(): + l += simpletlv_unpack(r.data)[0][1] + else: + l += len(r.data) + fdm.append("%c\x02%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"], + inttostring(l, 2))) + fdm.append("%c\x02%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"], + inttostring(l, 2))) + l = len(records) + fdm.append("%c\x06%c%c%c%c%s" % (TAG["FILEDISCRIPTORBYTE"], + file.filedescriptor, file.datacoding, file.maxrecordsize >> + 8, file.maxrecordsize & 0x00ff, inttostring(l, 2))) + + elif isinstance(file, DF): + # TODO number of files == number of data bytes? + fdm.append("%c\x01%c" % (TAG["FILEDISCRIPTORBYTE"], + file.filedescriptor)) + if hasattr(file, 'dfname'): + fdm.append("%c%c%s" % (TAG["DFNAME"], len(file.dfname), + file.dfname)) + + else: + raise TypeError + + return "".join(fdm) + + def __selectFile(self, p1, p2, data): + """ + Returns the file specified by 'p1' and 'data' from the select + file command APDU. + """ + P1_FILE = 0x00 + P1_CHILD_DF = 0x01 + P1_CHILD_EF = 0x02 + P1_PARENT_DF = 0x03 + P1_DF_NAME = 0x04 + P1_PATH_FROM_MF = 0x08 + P1_PATH_FROM_CURRENTDF = 0x09 + + if (p1>>4) != 0 or p1 == P1_FILE: + # RFU OR + # When P1='00', the card knows either because of a specific coding + # of the file identifier or because of the context of execution of + # the command if the file to select is the MF, a DF or an EF. + if data[:2] == inttostring(self.fid): + selected = walk(self, data[2:]) + elif data[:2] == inttostring(self.currentDF().fid): + selected = walk(self.currentDF(), data[2:]) + else: + selected = walk(self.currentDF(), data) + elif p1 == P1_CHILD_DF or p1 == P1_CHILD_EF: + selected = self.select('fid', stringtoint(data)) + if ((p1 == P1_CHILD_DF and not isinstance(selected, DF)) or + (p1 == P1_CHILD_EF and not isinstance(selected, EF))): + # Command incompatible with file structure + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + elif p1 == P1_PATH_FROM_MF: + selected = walk(self, data) + elif p1 == P1_PATH_FROM_CURRENTDF: + selected = walk(self.currentDF(), data) + elif p1 == P1_PARENT_DF: + selected = self.current.parent + elif p1 == P1_DF_NAME: + df = self.currentDF() + if df == self or df not in self.content: + index_current = -1 + else: + index_current = self.content.index(df) + selected = self.select('dfname', data, p2 & + REF["REFERENCE_CONTROL"], index_current) + else: + print "unknown selection method: p1 =", p1 + selected = None + + if selected == None: + raise SwError(SW["ERR_FILENOTFOUND"]) + + return selected + + def selectFile(self, p1, p2, data): + """ + Function for instruction 0xa4. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + P2_FCI = 0 + P2_FCP = 1<<2 + P2_FMD = 2<<2 + P2_NONE = 3<<2 + file = self.__selectFile(p1, p2, data) + + if p2 == P2_NONE: + data = "" + elif p2 == P2_FMD: + # TODO + data = "" + else: + if p2 == P2_FCP: + tag = TAG["FILECONTROLPARAMETERS"] + else: + tag = TAG["FILECONTROLINFORMATION"] + fdm = MF.encodeFileControlParameter(file) + data = bertlv_pack([(tag, len(fdm), fdm)]) + + self.current = file + + return SW["NORMAL"], data + + def dataUnitsDecodePlain(self, p1, p2, data): + """ + Decodes 'p1', 'p2' and 'data' from a data unit command (i. e. + read/write/update/search/erase binary) with *odd* instruction code. + Returns the specified TransparentStructureEF, a list of offsets and a + list of data strings. + """ + if p1 >> 7: + # If bit 1 of INS is set to 0 and bit 8 of P1 to 1, then bits 7 + # and 6 of P1 are set to 00 (RFU), bits 5 to 1 of P1 encode a + # short EF identifier and P2 (eight bits) encodes an offset + # from zero to 255. + ef = self.currentDF().select('shortfid', p1 & 0x1f) + self.current = ef + offsets = [p2] + else: + # If bit 1 of INS is set to 0 and bit 8 of P1 to 0, then P1-P2 + # (fifteen bits) encodes an offset from zero to 32 767. + ef = self.currentEF() + if not ef: + raise SwError(SW["ERR_NOCURRENTEF"]) + offsets = [(p1 << 8) + p2] + + return ef, offsets, [data] + + def dataUnitsDecodeEncapsulated(self, p1, p2, data): + """ + Decodes 'p1', 'p2' and 'data' from a data unit command (i. e. + read/write/update/search/erase binary) with *even* instruction code. + Returns the specified TransparentStructureEF, a list of offsets and a + list of data strings. + """ + # If bit 1 of INS is set to 1, then P1-P2 shall identify an EF. If + # the first eleven bits of P1-P2 are set to 0 and if bits 5 to 1 of + # P2 are not all equal and if the card and / or the EF supports + # selection by short EF identifier, then bits 5 to 1 of P2 encode a + # short EF identifier (a number from one to thirty). Otherwise, + # P1-P2 is a file identifier. P1-P2 set to '0000' identifies the + # current EF. At least one offset data object with tag '54' shall + # be present in the command data field. When present in a command + # or response data field, data shall be encapsulated in a + # discretionary data object with tag '53' or '73'. + tlv_data = bertlv_unpack(data) + offsets = decodeOffsetDataObjects(tlv_data) + datalist = decodeDiscretionaryDataObjects(tlv_data) + + if p1 == 0 and p2 >> 5 == 0: + ef = self.currentDF().select('shortfid', p2) + else: + ef = self.currentDF().select('fid', p1 << 8 + p1) + self.current = ef + + return ef, offsets, datalist + + def readBinaryPlain(self, p1, p2, data): + """ + Function for instruction 0xb0. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) + result = ef.readbinary(offsets[0]) + + return SW["NORMAL"], result + + def readBinaryEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xb1. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) + result = ef.readbinary(offsets[0]) + + r = encodeDiscretionaryDataObjects([result]) + + return SW["NORMAL"], r + + def writeBinaryPlain(self, p1, p2, data): + """ + Function for instruction 0xd0. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) + ef.writebinary(offsets, datalist) + + return SW["NORMAL"], "" + + def writeBinaryEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xd1. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) + ef.writebinary(offsets, datalist) + + return SW["NORMAL"], "" + + def updateBinaryPlain(self, p1, p2, data): + """ + Function for instruction 0xd6. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) + ef.updatebinary(offsets, datalist) + + return SW["NORMAL"], "" + + def updateBinaryEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xd7. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) + ef.updatebinary(offsets, datalist) + + return SW["NORMAL"], "" + + def searchBinaryPlain(self, p1, p2, data): + ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) + r = self.data.find(datalist[0], offsets[0]) + if r == -1: + raise SwError(SW["ERR_DATANOTFOUND"]) + + return SW["NORMAL"], inttostring(r) + + def searchBinaryEncapsulated(self, p1, p2, data): + ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) + if len(offsets) != len(datalist): + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + result = [] + for data, offset in offsets, datalist: + r = self.data.find(data, offset) + if r == -1: + raise SwError(SW["ERR_DATANOTFOUND"]) + result.append(inttostring(r)) + + return SW["NORMAL"], encodeDataOffsetObjects(result) + + def eraseBinaryPlain(self, p1, p2, data): + """ + Function for instruction 0x0e. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) + # If INS = '0E', then, if present, the command data field encodes + # the offset of the first data unit not to be erased. This offset + # shall be higher than the one encoded in P1-P2. If the data field + # is absent, then the command erases up to the end of the file. + erasefrom = offsets[0] + + if data: + tlv_data = bertlv_unpack(data) + eraseto = decodeOffsetDataObjects(tlv_data)[0] + else: + eraseto = None + + ef.erasebinary(erasefrom, eraseto) + return SW["NORMAL"], "" + + def eraseBinaryEncapsulated(self, p1, p2, data): + """ + Function for instruction 0x0f. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) + # If INS = '0F', then, if present, the command data field shall + # consist of zero, one or two offset data objects. If there is no + # offset, then the command erases all the data units in the file. + # If there is one offset, it indicates the first data unit to be + # erased; then the command erases up to the end of the file. Two + # offsets define a sequence of data units: the second offset + # indicates the first data unit not to be erased; it shall be + # higher than the first offset. + if len(offsets) > 1: + eraseto = offsets[1] + else: + eraseto = None + + if len(offsets) > 0: + erasefrom = offsets[0] + else: + erasefrom = None + + ef.erasebinary(erasefrom, eraseto) + return SW["NORMAL"], "" + + def recordHandlingDecode(self, p1, p2): + """ + Decodes 'p1' and 'p2' from a record handling command (i. e. + read/write/update/append/search/erase record). Returns the specified + RecordStructureEF, the number or identifier of the record and a + reference, that specifies which record to select (i. e. the last 3 bits + of 'p1'). + """ + if p1 == 0xff: + # RFU + raise SwError(SW["ERR_INCORRECTP1P2"]) + else: + num_id = p1 + + shortfid = p2 >> 3 + if shortfid == 0: + ef = self.currentEF() + if ef == None: + raise SwError(SW["ERR_NOCURRENTEF"]) + elif shortfid == 0x1f: + # RFU + raise SwError(SW["ERR_INCORRECTP1P2"]) + else: + ef = self.currentDF().select('shortfid', shortfid) + self.current = ef + ef.resetRecordPointer() + + return ef, num_id, p2 & REF["REFERENCE_CONTROL"] + + def readRecordPlain(self, p1, p2, data): + """ + Function for instruction 0xb2. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + result = ef.readrecord(0, num_id, reference) + + r = "" + for item in result: + r += item + + return SW["NORMAL"], r + + def readRecordEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xb3. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + result = ef.readrecord(0, num_id, reference) + + return SW["NORMAL"], encodeDiscretionaryDataObjects(result) + + def writeRecord(self, p1, p2, data): + """ + Function for instruction 0xd2. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + if reference not in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"], + REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"], + REF["NUMBER"]]: + # RFU + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + ef.writerecord(num_id, reference, 1, data) + + return SW["NORMAL"], "" + + def updateRecordPlain(self, p1, p2, data): + """ + Function for instruction 0xdc. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + if reference not in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"], + REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"], + REF["NUMBER"]]: + # RFU + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + ef.updaterecord(num_id, reference, 0, data) + + return SW["NORMAL"], "" + + def updateRecordEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xdd. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + + P2_REPLACE = 0x04 + P2_AND = 0x05 + P2_OR = 0x06 + #P2_XOR = 0x07 + tlv_data = bertlv_unpack(data) + if reference in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"], + REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"]]: + # RFU + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + elif reference == P2_REPLACE: + ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], + decodeDiscretionaryDataObjects(tlv_data)[0], + DCB["ONETIMEWRITE"]) + elif reference == P2_AND: + ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], + decodeDiscretionaryDataObjects(tlv_data)[0], + DCB["WRITEAND"]) + elif reference == P2_OR: + ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], + decodeDiscretionaryDataObjects(tlv_data)[0], + DCB["WRITEOR"]) + else: + # reference == P2_XOR: + ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], + decodeDiscretionaryDataObjects(tlv_data)[0], + DCB["PROPRIETARY"]) + + return SW["NORMAL"], "" + + def appendRecord(self, p1, p2, data): + """ + Function for instruction 0xe2. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + if p1 != 0 or (p2 & REF["REFERENCE_CONTROL"]) != 0: + raise SwError(SW["ERR_INCORRECTP1P2"]) + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + sw = ef.appendrecord(data) + + return SW["NORMAL"], "" + + def eraseRecord(self, p1, p2, data): + """ + Function for instruction 0x0c. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + ef, num_id, reference = self.recordHandlingDecode(p1, p2) + if reference not in [ + REF["NUMBER"], + REF["NUMBER_TO_LAST"]]: + # RFU + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + ef.eraserecord(num_id, reference) + + return SW["NORMAL"], "" + + def dataObjectHandlingDecodePlain(self, p1, p2, data): + """ + Decodes 'p1', 'p2' and 'data' from a data object handling command (i. + e. get/put data) with *even* instruction code. Returns the specified + file, True if the following list regards SIMPLE-TLV data objects False + otherwise and a list of (tag, length, value)-tuples. + """ + if self.current == None: + raise SwError(SW["ERR_NOCURRENTEF"]) + file = self.current + + if (p1 == 0 and 0x40 <= p2 and p2 <= 0xfe) or (0x40 <= p1 and p2 != 0 + and p2 != 0xff): + # If bit 1 of INS is set to 0 and P1 to '00', then P2 from '40' to + # 'FE' shall be a BER-TLV tag on a single byte. OR + # If bit 1 of INS is set to 0 and if P1-P2 lies from '4000' to + # 'FFFF', then they shall be a BER-TLV tag on two bytes. + tlv_data = [((p1<<8) + p2, len(data), data)] + isSimpleTlv = False + elif p1 == 0x02 and 0x01 <= p2 and p2 <= 0xfe: + # If bit 1 of INS is set to 0 and P1 to '02', then P2 from '01' to + # 'FE' shall be a SIMPLE-TLV tag. + tlv_data = [(p2, len(data), data)] + isSimpleTlv = True + elif p1 == 0x00 and p2 == 0xff: + # The value '00FF' is used either for obtaining all the common + # BER-TLV data objects readable in the context, or for indicating + # that the command data field is encoded in BER-TLV. + if data: + tlv_data = bertlv_unpack(data) + else: + tlv_data = [] + isSimpleTlv = False + elif p1 == 0x02 and p2 == 0xff: + # The value '02FF' is used either for obtaining all the common + # SIMPLE-TLV data objects readable in the context or for indicating + # that the command data field is encoded in SIMPLE-TLV. + if data: + tlv_data = simpletlv_unpack(data) + else: + tlv_data = [] + isSimpleTlv = True + + return file, isSimpleTlv, tlv_data + + def dataObjectHandlingDecodeEncapsulated(self, p1, p2, data): + """ + Decodes 'p1', 'p2' and 'data' from a data object handling command (i. + e. get/put data) with *odd* instruction code. Returns the specified + file, True if the following list regards SIMPLE-TLV data objects False + otherwise and a list of (tag, length, value)-tuples. + """ + # If bit 1 of INS is set to 1, then P1-P2 shall identify a file. + tlv_data = bertlv_unpack(data) + if p1 == 0 and p2 == 0: + # P1-P2 set to '0000' identifies the current EF, unless the command + # data field provides a file reference data object (tag '51', see + # 5.3.1.2) for identifying a file. + file = getfile_byrefdataobj(self, + tlv_find_tag(tlv_data, TAG["FILE_REFERENCE"])[0]) + if file == None: + file = self.currentEF() + if file == None: + raise SwError(SW["ERR_NOCURRENTEF"]) + elif p1 == 0 and (p2 >> 5) == 0: + # If the first eleven bits of P1-P2 are set to 0 and if bits 5 to 1 + # of P2 are not all equal and if the card and / or the file + # supports selection by short EF identifier, then bits 5 to 1 of P2 + # encode a short EF identifier (a number from one to thirty). + file = self.currentDF().select('shortfid', p2) + elif p1 == 0x3f and p2 == 0xff: + # P1-P2 set to '3FFF' identifies the current DF. + file = self.currentDF() + else: + # Otherwise, P1-P2 is a file identifier. + file = self.currentDF().select('fid', p1<<8 + p2) + + if file == None: + raise SwError(SW["ERR_FILENOTFOUND"]) + self.current = file + + return file, tlv_data + + def getDataPlain(self, p1, p2, data): + """ + Function for instruction 0xca. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1, p2, data) + # TODO oversized answers + if len(tlvlist) > 0: + return SW["NORMAL"], file.getdata(isSimpleTlv, [(tlvlist[0][0], 0)]) + else: + return SW["NORMAL"], file.getdata(isSimpleTlv, []) + + def getDataEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xcb. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + file, tlv_data = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data) + # TODO oversized answers + requestedTL = decodeTagList(tlv_data) + if requestedTL == []: + requestedTL = decodeHeaderList(tlv_data) + if requestedTL == []: + requestedTL = decodeExtendedHeaderList(tlv_data) + + return SW["NORMAL"], file.getdata(False, requestedTL) + + def putDataPlain(self, p1, p2, data): + """ + Function for instruction 0xda. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1, p2, data) + file.putdata(isSimpleTlv, tlvlist) + + return SW["NORMAL"], "" + + def putDataEncapsulated(self, p1, p2, data): + """ + Function for instruction 0xdb. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + file, tlvlist = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data) + file.putdata(False, tlvlist) + + return SW["NORMAL"], "" + + + @staticmethod + def create(p1, p2, data): + """ + Creates and returns a list of files according to the parameters of a + create file command. + """ + def fdb2args(value, args): + l = len(value) + if l >= 5: + # TODO number of records on one or two bytes + raise SwError(SW["ERR_NOTSUPPORTED"]) + if l >= 3: + args["maxrecordsize"] = stringtoint(value[2:]) + if l >= 2: + args["datacoding"] = ord(value[1]) + if l >=1: + args["filedescriptor"] = ord(value[0]) + def shortfid2args(value, args): + s = stringtoint(value) + if (s & 7) == 0: + shortfid = s >> 3 + if shortfid != 0: + args["shortfid"] = shortfid + def unknown(tag, value): + print "unknown tag 0x%x with %r" % tag, value + tag2cmd = { + # TODO support other tags + TAG["FILEDISCRIPTORBYTE"] : 'fdb2args(value, args)', + TAG["FILEIDENTIFIER"] : 'args["fid"] = stringtoint(value)', + TAG["DFNAME"] : 'args["dfname"] = value', + TAG["SHORTFID"] : 'shortfid2args(value, args)', + TAG["LIFECYCLESTATUS"] : 'args["lifecycle"] = stringtoint(value)', + TAG["BYTES_EXCLUDINGSTRUCTURE"] : 'args["data"] = chr(0)*stringtoint(value)', + TAG["BYTES_INCLUDINGSTRUCTURE"] : 'args["data"] = chr(0)*stringtoint(value)', + } + fcp_list = tlv_find_tags( bertlv_unpack(data), + [TAG["FILECONTROLINFORMATION"], TAG["FILECONTROLPARAMETERS"]]) + if not fcp_list: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + files = [] + args = { "parent": None } + if p1 != 0: + args["filedescriptor"] = p1 + if (p2>>3) != 0: + args["shortfid"] = p2>>3 + for T, _, tlv_data in fcp_list: + if T != TAG["FILECONTROLPARAMETERS"] and T != TAG["FILECONTROLINFORMATION"]: + raise ValueError + for tag, _, value in tlv_data: + exec tag2cmd.get(tag, 'unknown(tag, value)') in locals(), globals() + + if (args["filedescriptor"] & FDB["DF"]) == FDB["DF"]: + # FIXME: data for DF + if "data" in args: + print 'what to do with DF-data %r?' % args["data"] + del args["data"] + file = DF(**args) + elif ((args["filedescriptor"] & 7) in + [FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"], + FDB["EFSTRUCTURE_TRANSPARENT"]]): + file = TransparentStructureEF(**args) + file.writebinary( decodeOffsetDataObjects(tlv_data), + decodeDiscretionaryDataObjects(tlv_data) ) + else: + file = RecordStructureEF(**args) + + files.append(file) + + return files + + def createFile(self, p1, p2, data): + """ + Function for instruction 0xe0. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + df = self.currentDF() + if df == None: + raise SwError(SW["ERR_NOCURRENTEF"]) + + for file in self.create(p1, p2, data): + file.parent = df + df.append(file) + self.current = file + + return SW["NORMAL"], "" + + def deleteFile(self, p1, p2, data): + """ + Function for instruction 0xe4. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + file = self.__selectFile(p1, p2, data) + file.parent.content.remove(file) + # FIXME: free memory of file and remove its content from the security device + + return SW["NORMAL"], "" +# }}} + + +class EF(File): # {{{ + """Template class for an elementary file.""" + shortfid = make_property("shortfid", "integer with 1<=shortfid<=30. Short EF identifier.") + datacoding = make_property("datacoding", "integer. Data coding byte.") + def __init__(self, parent, fid, filedescriptor, + lifecycle=LCB["ACTIVATED"], + simpletlv_data=None, bertlv_data=None, + datacoding=DCB["ONETIMEWRITE"], shortfid=0): + """ + The constructor is supposed to be involved creation of a by creation of + a TransparentStructureEF or RecordStructureEF. + + See File for more. + """ + # exlcude FIDs for DFs + if fid == FID["MF"] or datacoding>0xFF: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + if shortfid: + if not (1 <= shortfid and shortfid <= 30): + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + self.shortfid=shortfid + File.__init__(self, parent, fid, filedescriptor, lifecycle, + simpletlv_data, + bertlv_data) + self.datacoding=datacoding +# }}} + + +class TransparentStructureEF(EF): # {{{ + """Class for an elementary file with transparent structure.""" + data = make_property("data", "string (encrypted). The file's data.") + def __init__(self, parent, fid, filedescriptor, + lifecycle=LCB["ACTIVATED"], + simpletlv_data=None, bertlv_data=None, + datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""): + """ + The flag FDB["EFSTRUCTURE_TRANSPARENT"] is automatically added to + 'filedescriptor'. + + See EF for more. + """ + EF.__init__(self, parent, fid, + filedescriptor|FDB["EFSTRUCTURE_TRANSPARENT"], lifecycle, + simpletlv_data, bertlv_data, + datacoding, shortfid) + self.setdec('data', data) + + def readbinary(self, offset): + """Returns the string of decrypted data beginning at 'offset'.""" + data = self.getenc('data') + + if offset > len(data): + raise SwError(SW["ERR_OFFSETOUTOFFILE"]) + + return data[offset:] + + def writebinary(self, offsets, datalist, datacoding=None): + """ + Writes pieces of data to the specified offsets honoring the given + coding byte. + + offsets -- list of integers. Offsets. + datalist -- list of strings. Data pieces. + datacoding -- the data coding byte to use for writing + """ + data = self.getenc('data') + if datacoding: + data = write(data, datalist, offsets, datacoding) + else: + data = write(data, datalist, offsets, self.datacoding) + self.setdec('data', data) + + def updatebinary(self, offsets, datalist): + """ + x.updatebinary(offsets, datalist) <==> x.writebinary(offsets, datalist, DCB["ONETIMEWRITE"]) + """ + return self.writebinary(offsets, datalist, DCB["ONETIMEWRITE"]) + + def erasebinary(self, erasefrom, eraseto): + """ + Sets (part of) the content of an EF to its logical erased state, + sequentially starting from 'erasefrom' ending at 'eraseto'. + """ + data = self.getenc('data') + if erasefrom == None: + erasefrom = 0 + if eraseto == None: + eraseto = len(data) + + if erasefrom>len(data): + raise SwError(SW["ERR_OFFSETOUTOFFILE"]) + if erasefrom>eraseto: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + data=data[0:erasefrom] + data[eraseto:len(data)] + self.setdec('data', data) +# }}} + + +class Record(object): # {{{ + data = make_property("data", "string. The record's data.") + identifier = make_property("identifier", "integer with 1< = identifier< = 0xfe. The record's identifier.") + """Class for a Record of an elementary of record structure""" + def __init__(self, identifier=None, data=""): + """ + The constructor is supposed to be involved by EF.appendrecord. + """ + if identifier: + if identifier<0x01 or identifier>0xFE: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + self.identifier=identifier + self.data=data + + def __str__(self): + """Returns a string of the object using an prettyprint_anything.""" + return prettyprint_anything("", self) + __repr__ = __str__ +# }}} + + +class RecordStructureEF(EF): # {{{ + """Class for an elementary file with record structure.""" + records = make_property("records", "list of records (encrypted)") + maxrecordsize = make_property("maxrecordsize", "integer. maximum length of a record's data.") + recordpointer = make_property("recordpointer", "integer. Points to the current record (i. e. index of records).") + def __init__(self, parent, fid, filedescriptor, + lifecycle=LCB["ACTIVATED"], + simpletlv_data=None, + bertlv_data=None, datacoding=DCB["ONETIMEWRITE"], shortfid=0, + maxrecordsize=0xffff, records=[]): + """ + You should specify the appropriate file descriptor byte to specify + which kind of record structured file you want to create (i. e. + linear/cyclic or variable/fixed EF). The record pointer is reset. + + records -- list of Records + maxrecordsize -- integer. maximum length of a record's data. + + See EF for more. + """ + if not isinstance(records, list): + raise TypeError, "must be a list of Records" + EF.__init__(self, parent, fid, filedescriptor, lifecycle, + simpletlv_data, bertlv_data, + datacoding, shortfid) + for r in records: + if len(r.data) > maxrecordsize or (self.hasFixedRecordSize() and + len(r.data) < maxrecordsize): + raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) + self.setdec('records', records) + self.resetRecordPointer() + self.maxrecordsize=maxrecordsize + + def resetRecordPointer(self): + """Resets the record pointer.""" + self.recordpointer=-1 + + def isCyclic(self): + """Returns True if the EF is of cyclic structure, False otherwise.""" + attr = self.filedescriptor & 0x07 + if (attr==FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] or + attr==FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"]): + return True + else: + return False + + def hasSimpleTlv(self): + """Returns True if the EF is of TLV structure, False otherwise.""" + attr = self.filedescriptor & 0x03 + if (attr==FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or + attr==FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] or + attr==FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"]): + return True + else: return False + + def hasFixedRecordSize(self): + """Returns True if the records are of fixed size, False otherwise.""" + if (self.filedescriptor == FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or + self.filedescriptor == + FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"]): + return True + else: + return False + + def __getRecords(self, num_id, reference): + """ + Returns a list of records. + + num_id -- The requested record's number or identifier + reference -- Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command) + """ + if (reference >> 2) == 1: + return self.__getRecordsByNumber(num_id, reference) + else: + return self.__getRecordsByIdentifier(num_id, reference) + + def __getRecordsByNumber(self, number, reference): + """ + Returns a list of records. Is to be involved by __getRecords. + + number -- The requested record's number + reference -- Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command) + """ + result = [] + records = self.getenc('records') + + if number == 0: + # refer to the current record + start = self.recordpointer + else: + start = number - 1 + + if reference == REF["NUMBER"]: + end = start + 1 + else: + # Read all records from number up to the last OR + # Read all records from the last up to number + end = len(records) + + result = records[start:end] + if reference == REF["NUMBER_FROM_LAST"]: + result.reverse() + + if result == []: + raise SwError(SW["ERR_RECORDNOTFOUND"]) + + return result + + def __getRecordsByIdentifier(self, id, reference): + """ + Returns a list of records and sets the recordpointer to the first + record, that matched. Is to be involved by __getRecords. + + id -- The requested record's identifier + reference -- Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command) + """ + result = [] + records = self.getenc('records') + + indexes = get_indexes(records, reference, self.recordpointer) + for i in indexes: + if (not self.hasSimpleTlv()) or records[i].identifier==id: + if result == []: + self.recordpointer=i + result.append(records[i]) + + if result == []: + self.resetRecordPointer() + raise SwError(SW["ERR_RECORDNOTFOUND"]) + + return result + + def readrecord(self, offset, num_id, reference): + """ + Returns a data string from the given 'offset'. 'num_id' and 'reference' + specify the record (see __getRecords). + """ + records = self.__getRecords(num_id, reference) + result = [] + for r in records: + result.append(r.data[offset:]) + if offset == 0: + pass + elif offset > 0 and offset > len(r.data): + offset -= len(r.data) + else: + offset = 0 + + return result + + def writerecord(self, num_id, reference, offset, data, + datacoding=None): + """ + Writes a data string to the 'offset' of a record using the given data + coding byte. 'num_id' and 'reference' specify the record (see + __getRecords). + """ + if self.isCyclic() and reference == REF["IDENTIFIER_PREVIOUS"]: + return self.appendrecord(data) + + records = self.__getRecords(num_id, reference) + + if datacoding: + records[0].data = write(records[0].data, [data], [0], datacoding, + self.maxrecordsize) + else: + records[0].data = write(records[0].data, [data], [0], + self.datacoding, self.maxrecordsize) + + if self.hasSimpleTlv(): + # identifier/tag could have changed + records[0].identifier = simpletlv_unpack(records[0].data)[0][0] + + def updaterecord(self, num_id, reference, offset, data): + """ + x.updaterecord(num_id, reference, offset, data) <==> x.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"]) + """ + return writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"]) + + def appendrecord(self, data): + """ + Appends a new record to the file, initializing it with the given data + string. Sets the recordpointer to the newly created record. + """ + if self.hasSimpleTlv(): + recordidentifier = simpletlv_unpack(data)[0][0] + else: + recordidentifier = None + + l = len(data) + if l > self.maxrecordsize: + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + if self.hasFixedRecordSize(): + data = chr(0)*(self.maxrecordsize) + data + + records = self.getenc('records') + if self.isCyclic(): + records.insert(0, Record(recordidentifier, data)) + self.recordpointer=0 + else: + records.append(Record(recordidentifier, data)) + self.recordpointer=len(records)-1 + self.setdec('records', records) + + def eraserecord(self, num_id, reference): + """ + Removes a record from the file. 'num_id' and 'reference' specify the + record (see __getRecords). + """ + records = self.__getRecords(num_id, reference) + for r in records: + r.data = "" + r.identifier = None + return SW["NORMAL"] +# }}} + + + +class CryptoflexMF(MF): # {{{ + + @staticmethod + def create(p1, p2, data): + + if data[0:2] != "\xff\xff": + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + args = { + "parent": None, + "filedescriptor": 0, + "fid": stringtoint(data[4:6]), + } + if data[6] == "\x01": + args["data"] = chr(0)*stringtoint(data[2:4]) + args["filedescriptor"] = FDB["EFSTRUCTURE_TRANSPARENT"] + file = TransparentStructureEF(**args) + elif data[6] == "\x02": + if len(data)>16: + args["maxrecordsize"] = stringtoint(data[16]) + elif p2: + # if given a number of records + args["maxrecordsize"] = (stringtoint(data[2:4])/p2) + args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"] + file = RecordStructureEF(**args) + elif data[6] == "\x03": + args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_VARIABLE_NOFURTHERINFO"] + file = RecordStructureEF(**args) + elif data[6] == "\x04": + args["filedescriptor"] = FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] + file = RecordStructureEF(**args) + elif data[6] == "\x38": + if data[12] != "\x03": + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + file = DF(**args) + else: + print "unknown type: 0x%x" % ord(data[6]) + raise SwError(SW["ERR_INCORRECTPARAMETERS"]) + + + return [file] + + def recordHandlingDecode(self, p1, p2): + if p2 < 4: + p1 = 0 + + return MF.recordHandlingDecode(self, p1, p2) + + + def dataUnitsDecodePlain(self, p1, p2, data): + ef = self.currentEF() + # FIXME: Cyberflex Access TM Software Development Kit Release 3C + # enforces the following: + # offsets = [(p2 << 8) + p1] + # but smartcard_login-0.1.1 requires: + offsets = [(p1 << 8) + p2] + + datalist = [data] + return ef, offsets, datalist +# }}} diff --git a/virtualsmartcard/vpicc/SmartcardSAM.py b/virtualsmartcard/vpicc/SmartcardSAM.py new file mode 100755 index 0000000..87f1df2 --- /dev/null +++ b/virtualsmartcard/vpicc/SmartcardSAM.py @@ -0,0 +1,1374 @@ +import SmartcardFilesystem, CryptoUtils, TLVutils +import random, re, struct, Crypto +import hashlib +from SWutils import SwError, SW, SW_MESSAGES +from pickle import dumps, loads +from utils import inttostring, stringtoint, hexdump, C_APDU, R_APDU +from Crypto.Cipher import DES3 +from ConstantDefinitions import * +from SEutils import ControlReferenceTemplate as CRT + +def generate_SAM_config(cardNumber,PIN,cardSecret,path,password): + """ + This method generates a new SAM configuration using the given parameters, + stores it in a KeyContainer, serialises the container, encrypts it and + writes it to the disk. + """ + container = CardContainer(cardNumber,PIN,cardSecret) + container.saveConfiguration(path,password) + del container + +def get_referenced_cipher(p1): + """ + P1 defines the algorithm and mode to use. We dispatch it and return a + string that is understood by CryptoUtils.py functions + """ + + ciphertable = { + 0x01: "DES3-ECB", + 0x02: "DES3-CBC", + 0x03: "DES-ECB", + 0x04: "DES-CBC", + 0x05: "AES-ECB", + 0x06: "AES-CBC", + 0x07: "RSA", + 0x08: "DSA" + } + + if (ciphertable.has_key(p1)): + return ciphertable[p1] + else: + raise SwError(SW["ERR_INCORRECTP1P2"]) + +class CardContainer: + """ + This class is used to store the data needed by the SAM + It includes the PIN, the master key of the SAM and a + hashmap containing all the keys used by the file encryption + system. The keys in the hashmap are indexed via the path + to the corresponding container. + """ + + def __init__(self,cardNumber=None,PIN=None,cardSecret=None): + self.cardNumber = cardNumber + self.PIN = PIN + self.cardSecret = cardSecret + self.FSkeys = {} + self.cipher = 0x01 # Algorithm reference defined in __get_referenced_algorithm(p1) + self.block_size = 16 #Need to be set according to the needs of self.cipher. Ugly! + self.master_password = None + self.master_key = None + self.salt = None + self.asym_key = None + + def __delete__(self): + print "Smartcard configuration is NOT saved!!!" + + def getPIN(self): + return self.PIN + + def setPIN(self,PIN): + self.PIN = PIN + + def getCardSecret(self): + return self.cardSecret + + def addKey(self,path,key): + """ + Encrypt key and store it in the SAM + """ + crypted_key = CryptoUtils.cipher(True,self.cipher,self.master_password,key) + if self.FSkeys.has_key(path): + print "Overwriting key for path %s" % path + self.FSkeys[path] = crypted_key + + def removeKey(self,path): + """ + Erase a key from the SAM + """ + if(self.FSkeys.has_key(path)): + del self.FSkeys[path] + else: + raise ValueError + + def getKey(self,path): + """ + Fetch a key from the SAM, decrypt and return it + @return: key if the key is in the container, otherwise None + """ + if(self.FSkeys.has_key(path)): + plain_key = CryptoUtils.cipher(False,self.cipher, self.master_password,self.FSkeys[path]) #TODO: Error checking: + return plain_key + else: + return None + + def loadConfiguration(self,path,password): + """ + Reads the configuration of a key container from the disk, decrypts + it and applies it to the current key_ container. + The current configuration gets overwritten. The HMAC is used to verify + data integrity. + """ + decryptedContainer = self.loadFromDisk(path, password, True) + container = loads(decryptedContainer) + self.PIN = container.PIN + self.cardNumber = container.cardNumber + self.cardSecret = container.cardSecret + self.FSkeys = container.FSkeys + self.master_password = password + del decryptedContainer + del container + + def saveConfiguration(self,path,password=None): + """ + Encrypts the configuration of the current key container and writes it + to the disk. Password is used to derivate a key using PBKDF2. A salt and + a HMAC is stored along with the data + """ + serialisedContainer = dumps(self) + if self.master_key == None: #New Configuration + if password == None: + raise ValueError, "Need password but none specified" + pbk = CryptoUtils.crypt(password) + regex = re.compile('\$p5k2\$\$[\w./]*\$') + match = regex.match(pbk) + if match != None: + self.salt = pbk[7:match.end()-1] + self.master_key = pbk[match.end():] + else: + raise ValueError + del pbk + + self.saveToDisk(serialisedContainer,path) + + def saveToDisk(self,data,path,password=None): + if password == None: + key = self.master_key + salt = self.salt + else: + regex = re.compile('\$p5k2\$\$[\w./]*\$') + pbk = CryptoUtils.crypt(password) + match = regex.match(pbk) + salt = pbk[7:match.end()-1] + print salt + key = pbk[match.end():] + + cipher = get_referenced_cipher(self.cipher) + paddedData = CryptoUtils.append_padding(cipher,data) + cryptedData = CryptoUtils.cipher(True,cipher,key,paddedData) + hmac = CryptoUtils.crypto_checksum("HMAC",key,cryptedData) + data = "$p5k2$$" + salt + "$" + cryptedData + hmac + + try: + fd = open(path,"w") + fd.write(data) + fd.close() + except IOError: + print "Failed to write data to disk" + + + def loadFromDisk(self,path,password=None,loading_sam=False): + hmac_length = 32 #FIXME: Ugly + #Read data + try: + fd = open(path,"r") + data = fd.read() + fd.close() + except IOError: + print "Failed to read data from disk" + #Check file structure, ectract components + regex = re.compile('\$p5k2\$\$[\w./]*\$') #TODO: Ensure the right format! + match = regex.match(data) + if match != None: + crypted = data[match.end():len(data) - hmac_length] + salt = data[7:match.end() - 1] + hmac = data[len(data) - hmac_length:] + else: + raise ValueError, "Wrong file format" + if password == None: + if salt != self.salt: + raise ValueError, "Could not decode data" + else: + key = self.master_key + else: + pbk = CryptoUtils.crypt(password,salt) + match = regex.match(pbk) + key = pbk[match.end():] + #Verify HMAC + checksum = CryptoUtils.crypto_checksum("HMAC",key,crypted) + if checksum != hmac: + print "Found HMAC %s expected %s" % (str(hmac), str(checksum)) + raise ValueError, "Failed to authenticate data. Wrong password?" + #Decrypt data + cipher = get_referenced_cipher(self.cipher) + decrypted = CryptoUtils.cipher(False,cipher,key,crypted) + + #If we are loading the SAM config (paramter loading_sam) we set + #the master key and the salt + #This is ugly and should be changed + if loading_sam: + self.master_key = key + self.salt = salt + + return decrypted + + +class SAM(object): + + def __init__(self,path,password,mf=None): + """ + Reads the encrypted SAM configuration from the disk and applies it. + """ + self.CardContainer = CardContainer() + if len(password) % self.CardContainer.block_size != 0: + print "Wrong key length, must be a multiple of %s Bytes" % self.CardContainer.block_size + raise ValueError + + self.CardContainer.loadConfiguration(path, password) + self.mf = mf + self.card_number = self.CardContainer.cardNumber + self.last_challenge = None #Will contain non-readable binary string + self.counter = 3 #Number of tries for PIN validation + self.cardSecret = self.CardContainer.cardSecret + self.SM_handler = Secure_Messaging(self.mf) + + def set_MF(self,mf): + self.mf = mf + self.SM_handler.set_MF(mf) + + def addFSkey(self,path,key): + #Deprecated ? + self.CardContainer.addKey(path, key) + + def delFSkey(self,path): + #Deprecated ? + self.CardContainer.removeKey(path) + + def FSencrypt(self,data): + """ + Encrypt the given data, using the parameters stored in the SAM + """ + if self.CardContainer.master_key == None: + raise ValueError + + cipher = get_referenced_cipher(self.CardContainer.cipher) + padded_data = CryptoUtils.append_padding(cipher,data) + crypted_data = CryptoUtils.cipher(True,cipher,self.CardContainer.master_key,padded_data) + return crypted_data + + def FSdecrypt(self,data): + """ + Decrypt the given data, using the parameters stored in the SAM + """ + if self.CardContainer.master_key == None: + raise ValueError, "No master key set." + + cipher = get_referenced_cipher(self.CardContainer.cipher) + decrypted_data = CryptoUtils.cipher(False,cipher,self.CardContainer.master_key,data) + unpadded_data = CryptoUtils.strip_padding(cipher,decrypted_data) + return unpadded_data + + def set_algorithm(self,algo): + if not algo in range(0x01,0x06): + raise ValueError, "Illegal Parameter" + else: + self.CardContainer.cipher = algo + + def set_asym_algorithm(self,cipher,type): + """ + @param cipher: Public/private key object from Crypto.PublicKey used for encryption + @param type: Type of the public key (e.g. RSA, DSA) + """ + if not type in range(0x07,0x08): + raise ValueError, "Illegal Parameter" + else: + self.CardContainer.cipher = type + self.CardContainer.asym_key = cipher + + def verify(self,p1,p2,PIN): + """ + Authenticate the card user. Check if he entered a valid PIN. + If the PIN is invalid decrement retry counter. If retry counter + equals zero, block the card until reset with correct PUK + """ + + print "Received PIN: %s" % PIN.strip() + PIN = PIN.replace("\0","") #Strip NULL charakters + + if p1 != 0x00: + raise SwError(SW["ERR_INCORRECTP1P2"]) + + if self.counter > 0: + if self.CardContainer.getPIN() == PIN: + self.counter = 3 + return SW["NORMAL"], "" + else: + self.counter -= 1 + raise SwError(SW["WARN_NOINFO63"]) + #raise SwError(0X63C0 + self.counter) #Be verbose + else: + raise SwError(SW["ERR_AUTHBLOCKED"]) + + def change_reference_data(self,p1,p2,data): + """ + Change the specified referenced data (e.g. CHV) of the card + """ + + data = data.replace("\0","") #Strip NULL charakters + self.CardContainer.setPIN(data) + print "New PIN = %s" % self.CardContainer.getPIN() + return SW["NORMAL"], "" + + def internal_authenticate(self,p1,p2,data): + """ + Authenticate card to terminal. Encrypt the challenge of the terminal + to prove key posession + """ + + if p1 == 0x00: #No information given + cipher = get_referenced_cipher(self.CardContainer.cipher) + else: + cipher = get_referenced_cipher(p1) + + if cipher == "RSA" or cipher == "DSA": + crypted_challenge = self.CardContainer.asym_key.sign(data,"") + crypted_challenge = crypted_challenge[0] + crypted_challenge = inttostring(crypted_challenge) + else: + key = self._get_referenced_key(p1,p2) + crypted_challenge = CryptoUtils.cipher(True,cipher,key,data) + + return SW["NORMAL"], crypted_challenge + + def external_authenticate(self,p1,p2,data): + """ + Authenticate the terminal to the card. Check wether Terminal correctly + encrypted the given challenge or not + """ + if self.last_challenge is None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + + key = self._get_referenced_key(p1,p2) + if p1 == 0x00: #No information given + cipher = get_referenced_cipher(self.CardContainer.cipher) + else: + cipher = get_referenced_cipher(p1) + + reference = CryptoUtils.append_padding(cipher,self.last_challenge) + reference = CryptoUtils.cipher(True,cipher,key,reference) + if(reference == data): + #Invalidate last challenge + self.last_challenge = None + return SW["NORMAL"], "" + else: + plain = CryptoUtils.cipher(False,cipher,key,data) + print plain + print "Reference: " + hexdump(reference) + print "Data: " + hexdump(data) + raise SwError(SW["WARN_NOINFO63"]) + #TODO: Counter for external authenticate? + + def mutual_authenticate(self,p1,p2,mutual_challenge): + """ + Takes an encrypted challenge in the form + 'Terminal Challenge | Card Challenge | Card number' + and checks it for validity. If the challenge is succesful + the card encrypts 'Card Challenge | Terminal challenge' and + returns this value + """ + + key = self._get_referenced_key(p1,p2) + card_number = self.get_card_number() + + if (key == None): + raise SwError(SW["ERR_INCORRECTP1P2"]) + if p1 == 0x00: #No information given + cipher = get_referenced_cipher(self.CardContainer.cipher) + else: + cipher = get_referenced_cipher(p1) + + if (cipher == None): + raise SwError(SW["ERR_INCORRECTP1P2"]) + plain = CryptoUtils.cipher(False,cipher,key,mutual_challenge) + terminal_challenge = plain[:len(self.last_challenge)-1] + card_challenge = plain[len(self.last_challenge):len(challenge)-len(card_number)-1] + serial_number = plain[(challenge)-len(card_number):] + if terminal_challenge != self.last_challenge: + print "Mutual authentication failed!" + raise SwError(SW["WARN_NOINFO63"]) + elif serial_number != card_number: + print "Mutual authentication failed!" + raise SwError(SW["WARN_NOINFO63"]) + + result = card_challenge + terminal_challenge + return SW["NORMAL"], CryptoUtils.cipher(True,cipher,key,result) + + def get_challenge(self,p1,p2,data): + """ + Generate a random number of maximum 8 Byte and return it. + """ + if (p1 != 0x00 or p2 != 0x00): #RFU + raise SwError(SW["ERR_INCORRECTP1P2"]) + + length = 8 #Length of the challenge in Byte + max = "0x" + length * "ff" + + #cipher = get_referenced_cipher(self.CardContainer.cipher) + #block_size = Crypto.Cipher.cipher.block_size + + random.seed() + self.last_challenge = random.randint(0, int(max,16)) + print "Generated challenge: %s" % str(self.last_challenge) + self.last_challenge = inttostring(self.last_challenge,length) + + return SW["NORMAL"], self.last_challenge + + def get_card_number(self): + return SW["NORMAL"], inttostring(self.card_number) + + def _get_referenced_key(self,p1,p2): + """ + This method returns the key specified by the p2 parameter. The key may be + stored on the cards filesystem or in memory. + @param p1: Specifies the algorithm to use. Needed to know the keylength. + @param p2: Specifies a reference to the key to be used for encryption + """ + + """ Meaning of p2: + b8 b7 b6 b5 b4 b3 b2 b1 | Meaning + 0 0 0 0 0 0 0 0 | No information is given + 0 -- -- -- -- -- -- -- | Global reference data (e.g. an MF secific key) + 1 -- -- -- -- -- -- -- | Specific reference data (e.g. DF specific key) + -- -- -- x x x x x | Number of the secret + Any other value | RFU + """ + + key = None + qualifier = p2 & 0x1F + algo = get_referenced_cipher(p1) + cipher = CryptoUtils.get_cipher(p1) + keylength = cipher.key_size + + if (p2 == 0x00): #No information given, use the global card key + key = self.CardContainer.cardSecret + #elif ((p2 >> 7) == 0x01 or (p2 >> 7) == 0x00): #We treat global and specific reference data alike + else: + try: #Interpret qualifier as an short fid and try to read the key from the FS + if self.mf == None: raise SwError(SW["CONDITIONSNOTSATISFIED"]) + df = self.mf.currentDF() + fid = df.select("fid", stringtoint(qualifier)) + key = fid.readbinary(keylength) + except SwError: + key = None + + if key == None: #Try to read the key from the Key Container + key = self.CardContainer.getKey(reference_data) + + if key != None: + return key + else: + raise SwError(SW["DATANOTFOUND"]) + + def _read_FS_data(self,fid,length): + + """ + This function creates a dummy filesystem, so that data (for example keys) + can be read from the FS. It is here for testing purposes. Later on the OS + interface to the FS should be used. + """ + mf = SmartcardFilesystem.MF(filedescriptor=FDB["NOTSHAREABLEFILE"], + lifecycle=LCB["NOINFORMATION"], dfname=None) + + mf = SmartcardFilesystem.MF(filedescriptor=FDB["DF"],lifecycle=LCB["ACTIVATED"], dfname=None) + df = mf.append(SmartcardFilesystem.DF(parent=mf,fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[])) + mf.append(SmartcardFilesystem.TransparentStructureEF(parent=df, fid=0x1012, + filedescriptor=0, data="Key from the FS.")) + + df = mf.currentDF() + file = df.select('fid', 0X1012) + sw, result = file.readbinary(0) + return sw, result + + #The following commands define the interface to the CardContainer functions + def saveConfiguration(self,path,password=None): + return self.CardContainer.saveConfiguration(path, password) + + def loadConfiguration(self,path,password): + return self.CardContainer.loadConfiguration(path, password) + + def loadFromDisk(self,path,password=None): + return self.CardContainer.loadFromDisk(path, password) + + def saveToDisk(self,data,path,password=None): + return self.CardContainer.saveToDisk(data, path, password) + + #The following commands define the interface to the Secure Messaging functions + def generate_public_key_pair(self,p1,p2,data): + return self.SM_handler.generate_public_key_pair(p1, p2, data) + + def parse_SM_CAPDU(self,CAPDU,header_authentication): + return self.SM_handler.parse_SM_CAPDU(CAPDU, header_authentication) + + def protect_result(self,sw,unprotected_result): + return self.SM_handler.protect_response(sw, unprotected_result) + + def perform_security_operation(self,p1,p2,data): + return self.SM_handler.perform_security_operation(p1, p2, data) + + def manage_security_environment(self,p1,p2,data): + return self.SM_handler.manage_security_environment(p1, p2, data) + +class PassportSAM(SAM): + #MRZ1 = "Pi", c) + H = hashlib.sha1(D).digest() + Ka = H[0:8] + Kb = H[8:16] + return Ka + Kb + + def external_authenticate(self,p1,p2,resp_data): + """Performes the basic access controll protocoll as defined in + the ICAO MRTD standard""" + rnd_icc = self.last_challenge + + #Receive Mutual Authenticate APDU from terminal + #Decrypt data and check MAC + #cipher = DES3.new(self.KEnc,"des3-cbc") + Eifd = resp_data[:-8] + Mifd = self._mac(self.KMac, Eifd) + #Check the MAC + if not Mifd == resp_data[-8:]: + raise ValueError, "Passport authentication failed: Wrong MAC on incoming data during Mutual Authenticate" + #Decrypt the data + plain = CryptoUtils.cipher(False,"DES3-CBC",self.KEnc,resp_data[:-8]) + #Split decrypted data into the two nonces and + if plain[8:16] != rnd_icc: + raise SwError(SW["WARN_NOINFO63"]) + #Extraxt keying material from IFD, generate ICC keying material + Kifd = plain[16:] + rnd_ifd = plain[:8] + max = "0x" + 16 * "ff" + random.seed() + Kicc = inttostring(random.randint(0, int(max,16))) + #Generate Answer + data = plain[8:16] + plain[:8] + Kicc + Eicc = CryptoUtils.cipher(True,"DES3-CBC",self.KEnc,data) + Micc = self._mac(self.KMac,Eicc) + #Derive the final keys + KSseed = CryptoUtils.operation_on_string(Kicc, Kifd, lambda a,b: a^b) + self.KSenc = self.derive_key(KSseed, 1) + self.KSmac = self.derive_key(KSseed, 2) + #self.ssc = rnd_icc[-4:] + rnd_ifd[-4:] + #Set the current SE + self.SM_handler.CAPDU_SE.ct.key = self.KSenc + self.SM_handler.CAPDU_SE.cct.key = self.KSmac + self.SM_handler.RAPDU_SE.ct.key = self.KSenc + self.SM_handler.RAPDU_SE.cct.key = self.KSmac + self.SM_handler.ssc = stringtoint(rnd_icc[-4:] + rnd_ifd[-4:]) + self.SM_handler.CAPDU_SE.ct.algo = "DES3-CBC" + self.SM_handler.RAPDU_SE.ct.algo = "DES3-CBC" + self.SM_handler.CAPDU_SE.cct.algo = "CC" + self.SM_handler.RAPDU_SE.cct.algo = "CC" + return SW["NORMAL"], Eicc + Micc + + def _mac(self, key, data, ssc = None, dopad=True): + if ssc: + data = ssc + data + if dopad: + topad = 8 - len(data) % 8 + data = data + "\x80" + ("\x00" * (topad-1)) + a = CryptoUtils.cipher(True, "des-cbc", key[:8], data) + b = CryptoUtils.cipher(False, "des-ecb", key[8:16], a[-8:]) + c = CryptoUtils.cipher(True, "des-ecb", key[:8], b) + return c + +class CryptoflexSAM(SAM): + def __init__(self,path="testconfig.sam",key="DUMMYKEYDUMMYKEY",mf=None): + SAM.__init__(self,path,key,mf) + self.SM_handler = CryptoflexSM(mf) + + def generate_public_key_pair(self,p1,p2,data): + asym_key = self.SM_handler.generate_public_key_pair(p1, p2, data) + self.set_asym_algorithm(asym_key, 0x07) + return SW["NORMAL"], "" + + def perform_security_operation(self,p1,p2,data): + """ + In the cryptoflex card, this is the verify key command. A key is send + to the card in plain text and compared to a key stored in the card. + This is used for authentication + @param data: Contains the key to be verified + @return: SW[NORMAL] in case of success otherwise SW[WARN_NOINFO63] + """ + return SW["NORMAL"], "" + #FIXME + #key = self._get_referenced_key(p1,p2) + #if key == data: + # return SW["NORMAL"], "" + #else: + # return SW["WARN_NOINFO63"], "" + + def internal_authenticate(self,p1,p2,data): + data = data[::-1] #Reverse Byte order + sw, data = SAM.internal_authenticate(self, p1, p2, data) + if data != "": + data = data[::-1] + return sw, data + +class Security_Environment(object): + + def __init__(self): + self.SEID = None + self.sm_objects = "" + #Default configuration + ct = "\x80\x01\x01\x82\x01\x00\x85\x01\x00" + #Control Reference Tables + self.at = CRT(TEMPLATE_AT) + self.kat = CRT(TEMPLATE_KAT) + self.ht = CRT(TEMPLATE_HT) + self.cct = CRT(TEMPLATE_CCT) + self.dst = CRT(TEMPLATE_DST) + self.ct = CRT(TEMPLATE_CT) + + + def mse(self,config): + structure = TLVutils.unpack(config) + for tag, length, value in structure: + if tag == TEMPLATE_AT: + self.at.parse_config(config) + elif tag == TEMPLATE_CRT: + self.crt.parse_config(config) + elif tag == TEMPLATE_CCT: + self.cct.parse_config(config) + elif tag == TEMPLATE_KAT: + self.kat.parse_config(config) + elif tag == TEMPLATE_CT: + self.ct.parse_config(config) + elif tag == TEMPLATE_DST: + self.dst.parse_config(config) + else: + raise ValueError + +class Secure_Messaging(object): + def __init__(self,MF,CAPDU_SE=None,RAPDU_SE=None): + self.mf = MF + if not CAPDU_SE: + self.CAPDU_SE = Security_Environment() + else: + self.CAPDU_SE = CAPDU_SE + if not RAPDU_SE: + self.RAPDU_SE = Security_Environment() + else: + self.RAPDU_SE = RAPDU_SE + + self.current_SE = Security_Environment() + + def set_MF(self,mf): + self.mf = mf + + def manage_security_environment(self,p1,p2,data): + """ + This method is used to store, restore or erase Security Environments + or to manipualte the various parameters of the current SE. + P1 specifies the operation to perform, p2 is either the SEID for the + referred SE or the tag of a control reference template + """ + + """ P1: + b8 b7 b6 b5 b4 b3 b2 b1 Meaning + - - - 1 - - - - Secure messaging in command data field + - - 1 - - - - - Secure messaging in response data field + - 1 - - - - - - Computation, decipherment, internal authentication and key agreement + 1 - - - - - - - Verification, encipherment, external authentication and key agreement + - - - - 0 0 0 1 SET + 1 1 1 1 0 0 1 0 STORE + 1 1 1 1 0 0 1 1 RESTORE + 1 1 1 1 0 1 0 0 ERASE + """ + + cmd = p1 & 0x0F + se = p1 & 0xF0 + if(cmd == 0x01): + if se == 0x01: #Set the right SE + self.current_SE = self.CAPDU_SE + elif se == 0x02: + self.current_SE = self.RAPDU_SE + else: + pass #FIXME + self.__set_SE(p2,data) + elif(cmd== 0x02): + self.__store_SE(p2) + elif(cmd == 0x03): + self.__restore_SE(p2) + elif(cmd == 0x04): + self.__erase_SE(p2) + else: + raise SwError(SW["ERR_INCORRECTP1P2"]) + + def __set_SE(self,p2,data): + """ + Manipulate the current Security Environment. P2 is the tag of a + control reference template, data contains control reference objects + """ + + valid_p2 = (0xA4,0xA6,0xB4,0xB6,0xB8) + if not p2 in valid_p2: + raise SwError(SW["ERR_INCORRECTP1P2"]) + tag, length, value = TLVutils.unpack(data) #Only one object? + if p2 == 0xA4: + self.current_SE.at.parse_SE_config(value) + elif p2 == 0xA6: + self.current_SE.kat.parse_SE_config(value) + elif p2 == 0xAA: + self.current_SE.ht.parse_SE_config(value) + elif p2 == 0xB4: + self.current_SE.cct.parse_SE_config(value) + elif p2 == 0xB6: + self.current_SE.dst.parse_SE_config(value) + elif p2 == 0xB8: + self.current_SE.ct.parse_SE_config(value) + + def __store_SE(self,SEID): + """ + Stores the current Security environment in the secure access module. The + SEID is used as a reference to identify the SE. + """ + SEstr = dumps(self.CAPDU_SE) + try: + self.SAM.addkey(SEID, SEstr) #TODO: Need SAM reference + except ValueError: + raise SwError["ERR_INCORRECTP1P2"] + + + def __restore_SE(self,SEID): + """ + Restores a Security Environment from the SAM and replaces the current SE + with it + """ + SEstr = self.SAM.get_key(SEID) + SE = loads(SEstr) + if isinstance(SE, SecurityEnvironment): + self.CAPDU_SE = SE + else: + raise ValueError + + def __erase_SE(self,SEID): + """ + Erases a Security Environment stored under SEID from the SAM + """ + self.SAM.removeKey(SEID) + + def parse_SM_CAPDU(self,CAPDU,header_authentication): + """ + Frontend for the __parse_SM command. We set the right Security Environment and restore the + old one, when the command is finished + """ + + current_SE = self.current_SE + self.current_SE = self.CAPDU_SE + try: + capdu = self.__parse_SM(CAPDU,header_authentication) + except SwError, e: #Restore Security Environment + self.current_SE = current_SE + raise e + + self.current_SE = current_SE + return capdu + + def __parse_SM(self,CAPDU,header_authentication): + """ + This methods parses a data field including Secure Messaging objects. + SM_header indicates wether or not the header of the message shall be authenticated + It returns an unprotected command APDU + @param CAPDU: The protected CAPDU to be parsed + @param header_authentication: Wether or not the header should be included in authentication mechanisms + @return: Unprotected command APDU + """ + structure = TLVutils.unpack(CAPDU.data) + return_data = ["",]; + expected = self.current_SE.sm_objects + + cla = None + ins = None + p1 = None + p2 = None + le = None + + if header_authentication: + to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins) + inttostring(CAPDU.p1) + inttostring(CAPDU.p2) + to_authenticate = CryptoUtils.append_padding("DES-CBC",to_authenticate,0x01,8) + else: + to_authenticate = "" + + for object in structure: + tag, length, value = object + #TODO: Sanity checking + #if not SM_Class.has_key(tag): + # raise ValueError, "unknown Secure messaging tag %#X" % tag + if tag % 2 == 1: #Include object in checksum calculation + to_authenticate += inttostring(tag) + inttostring(length) + value + + #SM data objects for encapsulating plain values + if tag in (SM_Class["PLAIN_VALUE_NO_TLV"],SM_Class["PLAIN_VALUE_NO_TLV_ODD"]): + return_data.append(value) #FIXME: Need TLV coding? + elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"],SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]): + #Encapsulated SM objects. Parse them + return_data.append(self.parse_SM_CAPDU(value, header_authentication)) #FIXME: Need to pack value into a dummy CAPDU + elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"],SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]): + #Encapsulated plaintext BER-TLV objects + return_data.append(value) + elif tag in (SM_Class["Ne"],SM_Class["Ne_ODD"]): + le = value + elif tag == SM_Class["PLAIN_COMMAND_HEADER"]: + if len(value) != 8: + raise ValueError, "Plain Command Header expected, but received insufficient data" + else: + cla = value[:2] + ins = value[2:4] + p1 = value[4:6] + p2 = value[6:8] + + #SM data objects for confidentiality + if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"],SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]): + #The Cryptogram includes SM objects. We decrypt them and parse the objects. + plain = decipher(tag,0x80,value) #TODO: Need Le = length + return_data.append(self.parse_SM_CAPDU(plain, header_authentication)) + elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"],SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]): + #The Cryptogram includes BER-TLV enconded plaintext. We decrypt them and return the objects. + plain = decipher(tag,0x80,value) + return_data.append(plain) + elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"],SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]): + #The first byte of the specified data field indicates the padding to use: + """ + Value Meaning + '00' No further indication + '01' Padding as specified in 6.2.3.1 + '02' No padding + '1X' One to four secret keys for enciphering information, not keys ('X' is a bitmap with any value from '0' to 'F') + '11' indicates the first key (e.g., an "even" control word in a pay TV system) + '12' indicates the second key (e.g., an "odd" control word in a pay TV system) + '13' indicates the first key followed by the second key (e.g., a pair of control words in a pay TV system) + '2X' Secret key for enciphering keys, not information ('X' is a reference with any value from '0' to 'F') + (e.g., in a pay TV system, either an operational key for enciphering control words, or a management key + for enciphering operational keys) + '3X' Private key of an asymmetric key pair ('X' is a reference with any value from '0' to 'F') + '4X' Password ('X' is a reference with any value from '0' to 'F') + '80' to '8E' Proprietary + """ + padding_indicator = stringtoint(value[0]) + sw, plain = self.decipher(tag,0x80,value[1:]) + if sw != 0x9000: + raise ValueError + plain = CryptoUtils.strip_padding(self.CAPDU_SE.ct.algo,plain,padding_indicator) + return_data.append(plain) + + #SM data objects for authentication + if tag == SM_Class["CHECKSUM"]: + auth = CryptoUtils.append_padding("DES-CBC",to_authenticate,0x01,8) + sw, checksum = self.compute_cryptographic_checksum(0x8E, 0x80, auth) + if checksum != value: + print "Failed to verify checksum!" + raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) + elif tag == SM_Class["DIGITAL_SIGNATURE"]: + auth = to_authenticate #FIXME: Need padding? + sw, signature = self.compute_digital_signature(0x9E, 0x9A, auth) + if signature != value: + print "Failed to verify signature!" + raise SwEroor(SW["ERR_SECMESSOBJECTSINCORRECT"]) + elif tag in (SM_Class["HASH_CODE"],SM_Class["HASH_CODE_ODD"]): + sw, hash = self.hash(p1, p2, to_authenticate) + if hash != value: + print "Failed to verify hash!" + raise SwEroor(SW["ERR_SECMESSOBJECTSINCORRECT"]) + + #Check if we just parsed a expected SM Object: + pos = 0 + while (pos < len (expected)): + if expected[pos] == tag: + expected = expected[:pos-1] + expected[pos:] + break + pos += 1 + + #Form unprotected CAPDU + if cla == None: + cla = CAPDU.cla + if ins == None: + ins = CAPDU.ins + if p1 == None: + p1 = CAPDU.p1 + if p2 == None: + p2 = CAPDU.p2 + if le == None: + le = CAPDU.le + if expected != "": + raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) + + c = C_APDU(cla=cla,ins=ins,p1=p1,p2=p2,le=le,data="".join(return_data)) + return c + + def protect_response(self,sw,result): + """ + Frontend for the __protect command. We set the right Security Environment and restore the + old one, when the command is finished + """ + + current_SE = self.current_SE + self.current_SE = self.RAPDU_SE + try: + sw, data = self.__protect(sw,result) + except SwError, e: #Restore Security Environment + self.current_SE = current_SE + raise e + + self.current_SE = current_SE + return sw, data + + def __protect(self,sw,result): + """ + This method protects a response APDU using secure messanging mechanisms + It returns the protected data and the SW bytes + """ + expected = self.current_SE.sm_objects + for pos in range(len(expected)): + tag = expected[pos] + + return_data = "" + #if sw == SW["NORMAL"]: + # sw = inttostring(sw) + # length = len(sw) + # tag = SM_Class["PLAIN_PROCESSING_STATUS"] + # tlv_sw = TLVutils.pack([(tag,length,sw)]) + # return_data += tlv_sw + + if result != "": # Encrypt the data included in the RAPDU + sw, encrypted = self.encipher(0x82, 0x80, result) + encrypted = "\x01" + encrypted + encrypted_tlv = TLVutils.pack([(SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],len(encrypted),encrypted)]) + return_data += encrypted_tlv + + if sw == SW["NORMAL"]: + if self.CAPDU_SE.cct.algo == None: + raise SwError(SW["CONDITIONSNOTSATISFIED"]) + elif self.CAPDU_SE.cct.algo == "CCT": + tag = SM_Class["CHECKSUM"] + to_auth = CryptoUtils.append_padding("DES-ECB",return_data,0x01,8) + sw, auth = self.compute_cryptographic_checksum(0x8E, 0x80, to_auth) + length = len(auth) + return_data += TLVutils.pack([(tag,length,auth)]) + elif self.CAPDU_SE.cct.algo == "SIGNATURE": + tag = SM_Class["DIGITAL_SIGNATURE"] + hash = self.hash(0x90, 0x80, return_data) + sw, auth = self.compute_digital_signature(0x9E, 0x9A, hash) + length = len(auth) + return_data += TLVutils.pack([(tag,length,auth)]) + + return SW["NORMAL"], return_data + + #The following commands implement ISO 7816-8 {{{ + def perform_security_operation(self,p1,p2,data): + """ + In the end this command is nothing but a big switch for all the other commands + in ISO 7816-8. It will invoke the appropiate command and return its result + """ + + allowed_P1P2 = ((0x90,0x80),(0x90,0xA0),(0x9E,0x9A),(0x9E,0xAC),(0x9E,0xBC),(0x00,0xA2), + (0x00,0xA8),(0x00,0x92),(0x00,0xAE),(0x00,0xBE),(0x82,0x80),(0x84,0x80), + (0x86,0x80),(0x80,0x82),(0x80,0x84),(0x80,0x86)) + + if (p1,p2) not in allowed_P1P2: + raise SwError(SW["INCORRECTP1P2"]) + + if(p2 in (0x80,0xA0) and p1==0x90): + sw, response_data = self.hash(p1,p2,data) + elif(p2 in (0x9A,0xAC,0xBC) and p1 == 0x9E): + sw, response_data = self.compute_digital_signature(p1, p2, data) + elif(p2 == 0xA2 and p1 == 0x00): + sw, response_data = self.verify_cryptographic_checksum(p1, p2, data) + elif(p2 == 0xA8 and p1 == 0x00): + sw, response_data = self.verify_digital_signature(p1, p2, data) + elif(p2 in (0x92,0xAE,0xBE) and p1 == 0x00): + sw, response_data = self.verify_certificate(p1, p2, data) + elif (p2 == 0x80 and p1 in (0x82,0x84,0x86)): + sw, response_data = self.encipher(p1, p2, data) + elif (p2 in (0x82,0x84,0x86) and p1 == 0x80): + sw, response_data = self.decipher(p1, p2, data) + + if p1 == 0x00: + assert response_data == "" + + return sw, response_data + + + def compute_cryptographic_checksum(self,p1,p2,data): + """ + Compute a cryptographic checksum (e.g. MAC) for the given data. + Algorithm and key are specified in the current (CAPDU) SE + @bug: Always use CAPDU settings ??? + """ + if p1 != 0x8E or p2 != 0x80: + raise SwError(SW["ERR_INCORRECTP1P2"]) + if self.CAPDU_SE.cct.algo == None or self.current_SE.cct.key == None: + raise SWError(SE["ERR_CONDITIONNOTSATISFIED"]) + + checksum = CryptoUtils.crypto_checksum(self.current_SE.cct.algo, + self.current_SE.cct.key, + data, + self.current_SE.cct.iv) + return SW["NORMAL"], checksum + + def compute_digital_signature(self,p1,p2,data): + """ + Compute a digital signature for the given data. + Algorithm and key are specified in the current SE + @param p1: Must be 0x9E = Secure Messaging class for digital signatures + @param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data is + included in the data field. + """ + + if p1 != 0x9E or not p2 in (0x9A,0xAC,0xBC): + raise SwError(SW["ERR_INCORRECTP1P2"]) + if self.current_SE.dst.key == None: + raise SWError(SE["ERR_CONDITIONNOTSATISFIED"]) + + if p2 == 0x9A: #Data to be signed + to_sign = data + elif p2 == 0xAC: #Data objects, sign values + to_sign = "" + structure = TLVutils.unpack(data) + for tag, length, value in structure: + to_sign += value + elif p2 == 0xBC: #Data objects to be signed + pass + + signature = self.current_SE.dst.key.sign(data,"") + return SW["NORMAL"], signature + + def hash(self,p1,p2,data): + """ + Hash the given data using the algorithm specified by the + current Security environment. + Return raw data (no TLV coding). + """ + if p1 != 0x90 or not p2 in (0x80,0xA0): + raise SwError(SW["ERR_INCORRECTP1P2"]) + algo = self.current_SE.ht.algo + if algo == None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + try: + hash = CryptoUtils.hash("MD5",data) + except ValueError: #FIXME: Type of error + raise SwError(SW["ERR_SECMESSNOTSUPPORTED"]) + + return SW["NORMAL"], hash + + def verify_cryptographic_checksum(self,p1,p2,data): + """ + Verify the cryptographic checksum contained in the data field. Data field must contain + a cryptographic checksum (tag 0x8E) and a plain value (tag 0x80) + """ + plain = "" + cct = "" + + algo = self.current_SE.cct.algo + key = self.current_SE.cct.key + iv = self.current_SE.cct.iv + if algo == None or key == None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + + structure = TLVutils.unpack(config) + for tag, length, value in structure: + if tag == 0x80: + plain = value + elif tag == 0x8E: + cct = value + if plain == "" or cct == "": + raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) + else: + my_cct = CryptoUtils.crypto_checksum(algo,key,plain,iv) + if my_cct == cct: + return SW["NORMAL"], "" + else: + raise SwError["ERR_SECMESSOBJECTSINCORRECT"] + + def verify_digital_signature(self,p1,p2,data): + """ + Verify the digital signature contained in the data field. Data must contain + a data to sign (tag 0x9A, 0xAC or 0xBC) and a digital signature (0x9E) + """ + key = self.current_SE.dst.key + to_sign = "" + signature = "" + + if key == None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + + structure = TLVutils.unpack(config) + for tag, length, value in structure: + if tag == 0x9E: + signature = value + elif tag == 0x9A: #FIXME: Correct treatment of all possible tags + to_sign = value + elif tag == 0xAC: + pass + elif tag == 0xBC: + pass + + if to_sign == "" or signature == "": + raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) + + try: + my_signature = key.sign(value) + except error, e: #FIXME: Foobar + pass + if my_signature == signature: + return SW["NORMAL"], "" + else: + raise SwError(["ERR_SECMESSOBJECTSINCORRECT"]) + + def verify_certificate(self,p1,p2,data): + if p1 != 0x00 or p2 not in (0x92,0xAE,0xBE): + raise SwError(SW["ERR_INCORRECTP1P2"]) + pass + + def encipher(self,p1,p2,data): + """ + Encipher data using key, algorithm, IV and Padding specified + by the current Security environment. + Return raw data (no TLV coding). + """ + algo = self.current_SE.ct.algo + key = self.current_SE.ct.key + if key == None or algo == None: + return SW["ERR_CONDITIONNOTSATISFIED"], "" + else: + padded = CryptoUtils.append_padding(algo,data) + crypted = CryptoUtils.cipher(True,algo,key,padded,self.current_SE.ct.iv) + return SW["NORMAL"], crypted + + def decipher(self,p1,p2,data): + """ + Decipher data using key, algorithm, IV and Padding specified + by the current Security environment. + Return raw data (no TLV coding). Padding is not removed!!! + """ + algo = self.current_SE.ct.algo + key = self.current_SE.ct.key + if key == None or algo == None: + return SW["ERR_CONDITIONNOTSATISFIED"], "" + else: + plain = CryptoUtils.cipher(False,algo,key,data,self.current_SE.ct.iv) + return SW["NORMAL"], plain + + def generate_public_key_pair(self,p1,p2,data): + """ + Generate a new public-private key pair. + """ + from Crypto.PublicKey import RSA, DSA + from Crypto.Util.randpool import RandomPool + rnd = RandomPool() + + cipher = self.CAPDU_SE.ct.algo #FIXME: Current SE? + + c_class = locals().get(cipher, None) + if c_class is None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + + if p1 & 0x01 == 0x00: #Generate key + PublicKey = c_class.generate(self.CAPDU_SE.dst.keylength,rnd.get_bytes) + self.CAPDU_SE.dst.key = PublicKey + else: + pass #Read key + + #Encode keys + if cipher == "RSA": + #Public key + n = str(PublicKey.__getstate__()['n']) + e = str(PublicKey.__getstate__()['e']) + pk = ((0x81,len(n),n),(0x82,len(e),e)) + result = TLVutils.bertlv_pack(pk) + #result = TLVutils.bertlv_pack((0x7F49,len(pk),pk)) + #Private key + d = PublicKey.__getstate__()['d'] + elif cipher == "DSA": + #Public key + p = str(PublicKey.__getstate__()['p']) + q = str(PublicKey.__getstate__()['q']) + g = str(PublicKey.__getstate__()['g']) + y = str(PublicKey.__getstate__()['y']) + #Private key + x = str(PublicKey.__getstate__()['x']) + #Add more algorithms here + + if p1 & 0x02 == 0x02: + return SW["NORMAL"], result + else: + #FIXME: Where to put the keys? + return SW["NORMAL"], "" + + #}}} +class CryptoflexSM(Secure_Messaging): + def __init__(self,mf): + Secure_Messaging.__init__(self,mf) #Does Cryptoflex need its own SE? + + def generate_public_key_pair(self,p1,p2,data): + """ + In the Cryptoflex card this command only supports RSA keys. + + @param data: Contains the public exponent used for key generation + @param p1: The keynumber. Can be used later to refer to the generated key + @param p2: Used to specifiy the keylength. The mapping is: 0x40 => 256 Bit, 0x60 => 512 Bit, 0x80 => 1024 + """ + from Crypto.PublicKey import RSA + from Crypto.Util.randpool import RandomPool + + keynumber = p1 #TODO: Check if key exists + + keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024} + + if not keylength_dict.has_key(p2): + raise SwError(SW["ERR_INCORRECTP1P2"]) + else: + keylength = keylength_dict[p2] + + rnd = RandomPool() + PublicKey = RSA.generate(keylength,rnd.get_bytes) + self.CAPDU_SE.dst.key = PublicKey + + e_in = struct.unpack("> 6 + constructed = (ord(data[0]) & 0x20) != 0 ## 0 = primitive, 0x20 = constructed + tag = ord(data[0]) + data = data[1:] + if (tag & 0x1F) == 0x1F: + tag = (tag << 8) | ord(data[0]) + while ord(data[0]) & 0x80 == 0x80: + data = data[1:] + tag = (tag << 8) | ord(data[0]) + data = data[1:] + + length = ord(data[0]) + if length < 0x80: + data = data[1:] + elif length & 0x80 == 0x80: + length_ = 0 + data = data[1:] + for i in range(0,length & 0x7F): + length_ = length_ * 256 + ord(data[0]) + data = data[1:] + length = length_ + + value = data[:length] + rest = data[length:] + + return ber_class, constructed, tag, length, value, rest +# }}} + +def tlv_find_tags(tlv_data, tags, num_results = None): # {{{ + """Find (and return) all instances of tags in the given tlv structure (as + returned by unpack). If num_results is specified then at most that many + results will be returned.""" + + results = [] + def find_recursive(tlv_data): + for d in tlv_data: + t,l,v = d[:3] + if t in tags: + results.append(d) + else: + if isinstance(v, list): # FIXME Refactor the whole TLV code into a class + find_recursive(v) + + if num_results is not None and len(results) >= num_results: + return + + find_recursive(tlv_data) + + return results +# }}} + +def tlv_find_tag(tlv_data, tag, num_results = None): # {{{ + """Find (and return) all instances of tag in the given tlv structure (as returned by unpack). + If num_results is specified then at most that many results will be returned.""" + + return tlv_find_tags(tlv_data, [tag], num_results) +# }}} + +def pack(tlv_data, recalculate_length = False): # {{{ + result = [] + + for data in tlv_data: + tag, length, value = data[:3] + if tag in (0xff, 0x00): + result.append( chr(tag) ) + continue + + if not isinstance(value, str): + value = pack(value, recalculate_length) + + if recalculate_length: + length = len(value) + + t = "" + while tag > 0: + t = chr( tag & 0xff ) + t + tag = tag >> 8 + + if length < 0x7F: + l = chr(length) + else: + l = "" + while length > 0: + l = chr( length & 0xff ) + l + length = length >> 8 + assert len(l) < 0x7f + l = chr( 0x80 | len(l) ) + l + + result.append(t) + result.append(l) + result.append(value) + + return "".join(result) +# }}} + +def bertlv_pack(data): # {{{ + """Packs a bertlv list of 3-tuples (tag, length, newvalue) into a string""" + return pack(data) +# }}} + +def unpack(data, with_marks = None, offset = 0, include_filler=False): # {{{ + result = [] + while len(data) > 0: + if ord(data[0]) in (0x00, 0xFF): + if include_filler: + if with_marks is None: + result.append( (ord(data[0]), None, None) ) + else: + result.append( (ord(data[0]), None, None, () ) ) + data = data[1:] + offset = offset + 1 + continue + + l = len(data) + ber_class, constructed, tag, length, value, data = tlv_unpack(data) + stop = offset + (l - len(data)) + start = stop - length + + if with_marks is not None: + marks = [] + for type, mark_start, mark_stop in with_marks: + if (mark_start, mark_stop) == (start, stop): + marks.append(type) + marks = (marks, ) + else: + marks = () + + if not constructed: + result.append( (tag, length, value) + marks ) + else: + result.append( (tag, length, unpack(value, with_marks, offset = start)) + marks ) + + offset = stop + + return result +# }}} + +def bertlv_unpack(data): # {{{ + """Unpacks a bertlv coded string into a list of 3-tuples (tag, length, + newvalue).""" + return unpack(data) +# }}} + +def simpletlv_pack(tlv_data, recalculate_length = False): # {{{ + result = "" + + for tag, length, value in tlv_data: + if tag >= 0xff or tag <= 0x00: + # invalid + continue + + if recalculate_length: + length = len(value) + if length > 0xffff or length < 0: + # invalid + continue + + if length < 0xff: + result += chr(tag) + chr(length) + value + else: + result += chr(tag) + chr(0xff)+chr(length>>8)+chr(length&0xff) + value + + return result +# }}} + +def simpletlv_unpack(data): # {{{ + """Unpacks a simpletlv coded string into a list of 3-tuples (tag, length, + newvalue).""" + result = [] + rest = data + while rest != '': + tag = ord(rest[0]) + if tag == 0 or tag == 0xff: + raise ValueError + + length = ord(rest[1]) + if length == 0xff: + length = (ord(rest[2])<<8) + ord(rest[3]) + newvalue = rest[4:4+length] + rest = rest[4+length:] + else: + newvalue = rest[2:2+length] + rest = rest[2+length:] + result.append((tag, length, newvalue)) + + return result +# }}} + + +def decodeDiscretionaryDataObjects(tlv_data): # {{{ + datalist = [] + for (tag, length, newvalue) in (tlv_find_tags(tlv_data, + [TAG["DISCRETIONARY_DATA"], TAG["DISCRETIONARY_TEMPLATE"]])): + datalist.append(newvalue) + return datalist +# }}} +def decodeOffsetDataObjects(tlv_data): # {{{ + offsets = [] + for (tag, length, newvalue) in tlv_find_tag(tlv_data, + TAG["OFFSET_DATA"]): + offsets.append(stringtoint(newvalue)) + return offsets +# }}} + +def decodeTagList(tlv_data): # {{{ + taglist = [] + for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]): + while data != "": + tag = ord(data[0]) + data = data[1:] + if (tag & 0x1F) == 0x1F: + tag = (tag << 8) | ord(data[0]) + while ord(data[0]) & 0x80 == 0x80: + data = data[1:] + tag = (tag << 8) | ord(data[0]) + data = data[1:] + taglist.append((tag, 0)) + return taglist +# }}} + +def decodeHeaderList(tlv_data): # {{{ + headerlist = [] + for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]): + while data != "": + tag = ord(data[0]) + data = data[1:] + if (tag & 0x1F) == 0x1F: + tag = (tag << 8) | ord(data[0]) + while ord(data[0]) & 0x80 == 0x80: + data = data[1:] + tag = (tag << 8) | ord(data[0]) + data = data[1:] + + length = ord(data[0]) + if length < 0x80: + data = data[1:] + elif length & 0x80 == 0x80: + length_ = 0 + data = data[1:] + for i in range(0,length & 0x7F): + length_ = length_ * 256 + ord(data[0]) + data = data[1:] + length = length_ + + headerlist.append((tag, length)) + return headerlist +# }}} + +def decodeExtendedHeaderList(tlv_data): # {{{ + # TODO + return [] +# }}} + +def encodebertlvDatalist(tag, datalist): # {{{ + tlvlist = [] + for data in datalist: + tlvlist.append((tag, len(data), data)) + return bertlv_pack(tlvlist) +# }}} +def encodeDiscretionaryDataObjects(datalist): # {{{ + return encodebertlvDatalist(TAG["DISCRETIONARY_DATA"], datalist) +# }}} +def encodeDataOffsetObjects(datalist): # {{{ + return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist) +# }}} diff --git a/virtualsmartcard/vpicc/VirtualSmartcard.py b/virtualsmartcard/vpicc/VirtualSmartcard.py new file mode 100644 index 0000000..05fd838 --- /dev/null +++ b/virtualsmartcard/vpicc/VirtualSmartcard.py @@ -0,0 +1,550 @@ +from ConstantDefinitions import * +from TLVutils import * +from SWutils import SwError, SW +from SmartcardFilesystem import prettyprint_anything, MF, DF, CryptoflexMF, TransparentStructureEF +from utils import C_APDU, R_APDU, hexdump, stringtoint, inttostring +from SmartcardSAM import CardContainer, SAM, PassportSAM, CryptoflexSAM +from pickle import dumps, loads +import socket, struct, sys, signal, atexit, traceback + + +class SmartcardOS(object): # {{{ + def __init__(self, filename,mf=None,ins2handler=None, maxle=MAX_SHORT_LE,sam=None): + self.config_key = "DUMMYKEYDUMMYKEY" #TODO: Let user enter password + self.filename = filename + self.mf = mf + self.SAM = sam + + if self.filename != None: + try: + self.load(filename,self.config_key) + except ValueError: + print "Failed to load configuration %s" % filename + + if self.SAM == None: + print "Using default SAM. Insecure!!!" + self.SAM = SAM("testconfig.sam",self.config_key) #FIXME: Replace by defaul_SAM() + if self.mf == None: + print "Using default MF." + self.mf = MF(filedescriptor=FDB["DF"], lifecycle=LCB["ACTIVATED"], dfname=None) + self.SAM.set_MF(self.mf) + + # pgp directory + #self.mf.append(DF(parent=self.mf, + #fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[])) + # pkcs-15 directories + #self.mf.append(DF(parent=self.mf, + #fid=1, dfname='\xa0\x00\x00\x00\x01')) + #self.mf.append(DF(parent=self.mf, + #fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00')) + #self.mf.append(DF(parent=self.mf, + #fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00')) + + #import imp, os.path + #SAM_config = os.path.join(os.path.dirname(imp.find_module("SmartcardSAM")[1]), "testconfig.sam") #Path to initial SAM configuration, stored on disk + + if not ins2handler: + self.ins2handler = { + 0x0c: self.mf.eraseRecord, + 0x0e: self.mf.eraseBinaryPlain, + 0x0f: self.mf.eraseBinaryEncapsulated, + 0x2a: self.SAM.perform_security_operation, + 0x20: self.SAM.verify, + 0x22: self.SAM.manage_security_environment, + 0x24: self.SAM.change_reference_data, + 0x46: self.SAM.generate_public_key_pair, + 0x82: self.SAM.external_authenticate, + 0x84: self.SAM.get_challenge, + 0x88: self.SAM.internal_authenticate, + 0xa0: self.mf.searchBinaryPlain, + 0xa1: self.mf.searchBinaryEncapsulated, + 0xa4: self.mf.selectFile, + 0xb0: self.mf.readBinaryPlain, + 0xb1: self.mf.readBinaryEncapsulated, + 0xb2: self.mf.readRecordPlain, + 0xb3: self.mf.readRecordEncapsulated, + 0xc0: self.getResponse, + 0xca: self.mf.getDataPlain, + 0xcb: self.mf.getDataEncapsulated, + 0xd0: self.mf.writeBinaryPlain, + 0xd1: self.mf.writeBinaryEncapsulated, + 0xd2: self.mf.writeRecord, + 0xd6: self.mf.updateBinaryPlain, + 0xd7: self.mf.updateBinaryEncapsulated, + 0xda: self.mf.putDataPlain, + 0xdb: self.mf.putDataEncapsulated, + 0xdc: self.mf.updateRecordPlain, + 0xdd: self.mf.updateRecordEncapsulated, + 0xe0: self.mf.createFile, + 0xe2: self.mf.appendRecord, + 0xe4: self.mf.deleteFile, + } + else: + self.ins2handler = ins2handler + + self.maxle = maxle + self.lastCommandOffcut = "" + self.lastCommandSW = SW["NORMAL"] + card_capabilities = self.mf.firstSFT + self.mf.secondSFT + SmartcardOS.makeThirdSoftwareFunctionTable() + self.atr = SmartcardOS.makeATR(T=1, directConvention = True, TA1=0x13, + histChars = chr(0x80) + chr(0x70 + len(card_capabilities)) + + card_capabilities) + + def save(self): + """ + Save the configuration of the current Smartcard (MF + SAM) to disk. + To files will be stored: .mf for the mf and .sam + for the SAM. + Both files will be encrypted. + """ + if self.filename == None: + raise ValueError, "No filename specified" + else: + mf = dumps(self.mf) + path = self.filename + ".mf" + mf = self.SAM.saveToDisk(mf,path) + path = self.filename + ".sam" + self.SAM.saveConfiguration(path) + #self.SAM.saveConfiguration(path,"DUMMYKEYDUMMYKEY") + + def load(self,filename,password): + """ + Try to load a configuration from the filesystem. MF or SAM are only loaded if + they aren't yet specified. + """ + if self.SAM == None: + self.SAM = SAM("testconfig.sam","DUMMYKEYDUMMYKEY",None) #FIXME: replace by default_SAM() + path = filename + ".sam" + try: + self.SAM.loadConfiguration(path,password) + except IOError: + print "Failed to open %s" % path + if self.mf == None: + path = filename + ".mf" + data = self.SAM.loadFromDisk(path,password) + self.mf = loads(data) + print "Succesfully loaded MF from %s" % path + + def powerUp(self): + pass + + def powerDown(self): + pass + + @staticmethod + def makeATR(**args): # {{{ + """Calculate Answer to Reset (ATR) and returns the bitstring. + + directConvention -- Bool. Whether to use direct convention or inverse convention. + TAi, TBi, TCi -- (optional) Value between 0 and 0xff. Interface Characters (for meaning see ISO 7816-3). Note that if no transmission protocol is given, it is automatically selected with T=max{j-1|TAj in args OR TBj in args OR TCj in args}. + T -- (optional) Value between 0 and 15. Transmission Protocol. Note that if T is set, TAi/TBi/TCi for i>T are omitted. + histChars -- (optional) Bitstring with 0 <= len(histChars) <= 15. Historical Characters T1 to T15 (for meaning see ISO 7816-4). + + T0, TDi and TCK are automatically calculated. + """ + # first byte TS + if args["directConvention"]: + atr = "\x3b" + else: + atr = "\x3f" + + if args.has_key("T"): + T = args["T"] + else: + T = 0 + + # find maximum i of TAi/TBi/TCi in args + maxTD = 0 + i = 15 + while i > 0: + if args.has_key("TA" + str(i)) or args.has_key("TB" + str(i)) or args.has_key("TC" + str(i)): + maxTD = i-1 + break + i -= 1 + + if maxTD == 0 and T > 0: + maxTD = 2 + + # insert TDi into args (TD0 is actually T0) + for i in range(0, maxTD+1): + if i == 0 and args.has_key("histChars"): + args["TD0"] = len(args["histChars"]) + else: + args["TD"+str(i)] = T + + if i < maxTD: + args["TD"+str(i)] |= 1<<7 + + if args.has_key("TA" + str(i+1)): + args["TD"+str(i)] |= 1<<4 + if args.has_key("TB" + str(i+1)): + args["TD"+str(i)] |= 1<<5 + if args.has_key("TC" + str(i+1)): + args["TD"+str(i)] |= 1<<6 + + # initialize checksum + TCK = 0 + + # add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0) + for i in range(0, maxTD+1): + atr = atr + "%c" % args["TD" + str(i)] + TCK ^= args["TD" + str(i)] + for j in ["A", "B", "C"]: + if args.has_key("T" + j + str(i+1)): + atr += "%c" % args["T" + j + str(i+1)] + # calculate checksum for all bytes from T0 to the end + TCK ^= args["T" + j + str(i+1)] + + # add historical characters + if args.has_key("histChars"): + atr += args["histChars"] + for i in range(0, len(args["histChars"])): + TCK ^= ord( args["histChars"][i] ) + + # checksum is omitted for T=0 + if T > 0: + atr += "%c" % TCK + + return atr + # }}} + @staticmethod + def makeThirdSoftwareFunctionTable(commandChainging=False, + extendedLe=False, assignLogicalChannel=0, maximumChannels=0): # {{{ + """ + Returns a byte according to the third software function table from the + historical bytes of the card capabilities. + """ + tsft = 0 + if commandChainging: + tsft |= 1 << 7 + if extendedLe: + tsft |= 1 << 6 + if assignLogicalChannel: + if not (0<=assignLogicalChannel and assignLogicalChannel<=3): + raise ValueError + tsft |= assignLogicalChannel << 3 + if maximumChannels: + if not (0<=maximumChannels and maximumChannels<=7): + raise ValueError + tsft |= maximumChannels + return inttostring(tsft) +# }}} + + def formatResult(self, le, data, sw, sm): + if le == None: + count = 0 + elif le == 0: + count = self.maxle + else: + count = le + + self.lastCommandOffcut = data[count:] + l = len(self.lastCommandOffcut) + if l == 0: + self.lastCommandSW = SW["NORMAL"] + else: + self.lastCommandSW = sw + sw = SW["NORMAL_REST"] + min(0xff, l) + + result = data[:count] + if sm: + sw, result = self.SAM.protect_result(sw,result) + + return R_APDU(result, inttostring(sw)).render() + + def getResponse(self, p1, p2, data): + if not (p1 == 0 and p2 == 0): + raise SwError(SW["ERR_INCORRECTP1P2"]) + + return self.lastCommandSW, self.lastCommandOffcut + + def execute(self, msg): + def notImplemented(*argz, **args): + raise SwError(SW["ERR_INSNOTSUPPORTED"]) + + c = C_APDU(msg) + + #Handle Class Byte{{{ + class_byte = c.cla + SM_STATUS = None + logical_channel = 0 + command_chaining = 0 + header_authentication = 0 + + #Ugly Hack for OpenSC-explorer + if(class_byte == 0xb0): + print "Open SC APDU" + SM_STATUS = "No SM" + + #If Bit 8,7,6 == 0 then first industry values are used + if (class_byte & 0xE0 == 0x00): + #Bit 1 and 2 specify the logical channel + logical_channel = class_byte & 0x03 + #Bit 3 and 4 specify secure messaging + secure_messaging = class_byte >> 2 + secure_messaging &= 0x03 + if (secure_messaging == 0x00): + SM_STATUS = "No SM" + elif (secure_messaging == 0x01): + SM_STATUS = "Propietary SM" # Not supported ? + elif (secure_messaging == 0x02): + SM_STATUS = "Standard SM" + elif (secure_messaging == 0x03): + SM_STATUS = "Standard SM" + header_authentication = 1 + #If Bit 8,7 == 01 then further industry values are used + elif (class_byte & 0x0C == 0x0C): + #Bit 1 to 4 specify logical channel. 4 is added, value range is from four to nineteen + logical_channel = class_byte & 0x0f + logical_channel += 4 + #Bit 6 indicates secure messaging + secure_messaging = class_byte >> 5 + secure_messaging &= 0x01 + if (secure_messaging == 0x00): + SM_STATUS = "No SM" + elif (secure_messaging == 0x01): + SM_STATUS = "Standard SM" + #In both cases Bit 5 specifiys command chaining + command_chaining = class_byte >> 5 + command_chaining &= 0x01 + #}}} + + try: + if SM_STATUS == "Standard SM": + c = self.SAM.parse_SM_CAPDU(c,header_authentication) + elif SM_STATUS == "Propietary SM": + raise SwError("ERR_SECMESSNOTSUPPORTED") + sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data) + if SM_STATUS == "Standard SM": + answer = self.formatResult(c.le, result, sw, True) + else: + answer = self.formatResult(c.le, result, sw, False) + except SwError, e: + print e.message + #traceback.print_exception(*sys.exc_info()) + sw = e.sw + result = "" + answer = self.formatResult(c.le, result, sw, False) + + return answer +# }}} + +class PassportOS(SmartcardOS): + """ + The PassportOS emulates a Passport Application according to ICAO MRTD standard. + It generates a data structure ... + It also integrates a SAM derived from the standard SmartcardSAM, providing the + Basic Access Control (BAC) mechanisms. + """ + + def __init__(self, filename,mf=None, ins2handler=None, maxle=MAX_SHORT_LE): + if filename == None and mf == None: + mf = MF() + else: + pass #TODO: Load data from disk + self.generate_data_structure(mf) + self.SAM = PassportSAM(mf) + SmartcardOS.__init__(self, None, mf=mf, ins2handler=ins2handler, maxle=maxle,sam=self.SAM) + + def generate_data_structure(self,mf): + #MRZ1 = "P>8: + r = inttostring(SW["NORMAL"]) + + # be fully successfull if at least one databyte has been returned + if len(r) > 2 and r[-2] == chr(SW["NORMAL_REST"]>>8): + return r[:-2] + inttostring(SW["NORMAL"]) + + return r +# }}} + + +class VirtualICC(object): # {{{ + + def __init__(self, filename,os=None, lenlen=3, host="localhost", port=35963): + if os == None: + self.os = SmartcardOS(filename) + else: + self.os = os + self.filename = filename + self.sock = self.connectToPort(host, port) + self.sock.settimeout(None) + self.lenlen = lenlen + signal.signal(signal.SIGINT, self.signalHandler) + atexit.register(self.signalHandler) + atexit.register(self.saveCard) + + def saveCard(self): + if self.filename != None: + print "Saving smartcard configuration to %s" % self.filename + self.os.save() + else: + print "No filename specified, card configuration will not be saved." + + def signalHandler(self, signal=None, frame=None): + self.sock.close() + sys.exit() + + @staticmethod + def connectToPort(host, port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + return sock + + def __sendToVPICC(self, msg): + size = inttostring(len(msg), self.lenlen) + self.sock.sendall("%s%s" % (size, msg)) + + def __recvFromVPICC(self): + # receive message size + size = self.sock.recv(self.lenlen) + size = stringtoint(size) + + # receive and return message + if size == 0: + return "" + else: + msg = self.sock.recv(size) + #print("Incoming Message % s") % hexdump(msg) #TEST + return msg + #return self.sock.recv(size) TEST, wieder auskommentieren! + + def run(self): + fd_in = open("input","w") #TEST + fd_out = open("output","w") #TEST + while True : + msg = self.__recvFromVPICC() + if msg == "": + self.__sendToVPICC(self.os.atr) + elif msg == chr(0): + # powerdown + print "Power Down" + self.os.powerDown() + elif msg == chr(1): + # powerup + print "Power Up" + self.os.powerUp() + else: + print "APDU (%d Bytes):\n%s" % (len(msg),hexdump(msg, short=True)) + apdu = struct.pack("i" + str(len(msg)) + "s",len(msg),msg) #TEST + fd_in.write(apdu) #TEST + answer = self.os.execute(msg) + print "RESP (%d Bytes):\n%s\n" % (len(answer),hexdump(answer, short=True)) + rapdu = struct.pack("!i" + str(len(answer)) + "s",len(answer),answer) #TEST + fd_out.write(rapdu) #TEST + self.__sendToVPICC(answer) +# }}} + +if __name__ == "__main__": + from optparse import OptionParser + parser = OptionParser() + parser.add_option("-t", "--type", action="store", type="choice", + default='iso7816', + choices=['iso7816', 'cryptoflex', 'ePass'], + help="Type of Smartcard [default: %default]") + parser.add_option("-f", "--file", action="store", type="string", + dest="filename", default=None, + help="Name of a smartcard stored in the filesystem. The card will be loaded") + (options, args) = parser.parse_args() + if options.type == 'cryptoflex': + vicc = VirtualICC(options.filename,os=CryptoflexOS(options.filename)) + elif options.type == 'ePass': + #raise ValueError, "Not implemented yet!" + vicc = VirtualICC(options.filename,os=PassportOS(options.filename)) + else: + vicc = VirtualICC(options.filename) + vicc.run() diff --git a/virtualsmartcard/vpicc/jp2.jpg b/virtualsmartcard/vpicc/jp2.jpg new file mode 100644 index 0000000..8ecc6a4 Binary files /dev/null and b/virtualsmartcard/vpicc/jp2.jpg differ diff --git a/virtualsmartcard/vpicc/testconfig.mf b/virtualsmartcard/vpicc/testconfig.mf new file mode 100644 index 0000000..211fe1c --- /dev/null +++ b/virtualsmartcard/vpicc/testconfig.mf @@ -0,0 +1 @@ +$p5k2$$RGHiUkhA$UxU3I^낎P(fKciveY/0lh{?gyaP?ZU!oKΦP,c޵o{0F\g#~r˹l/}l3#%ѱ@OG oCa}  Wu?cOm"%"N]Tn<@oE'HocEUpl@E"[5lؽR}xEޜ筩z+|}Dy< i?AW^t%l-Zwj= 0: + #int = (int << i*8) + ord(str[i]) + #i = i - 1 + #return int + if str: + return int(str.encode('hex'), 16) + return 0 +# }}} + +def inttostring(i, length=None): # {{{ + #str = "" + #while i > 0: + #str = chr(i & 0xff) + str + #i >>= 8 + str = "%x" % i + if len(str) % 2 == 0: + str = str.decode('hex') + else: + str = ("0"+str).decode('hex') + + if length: + l = len(str) + if l > length: + raise ValueError, "i too big for the specified stringlength" + else: + str = chr(0)*(length-l) + str + + return str +# }}} + +def represent_binary_fancy(len, value, mask = 0): + result = [] + for i in range(len): + if i%4 == 0: + result.append( " " ) + if i%8 == 0: + result.append( " " ) + if mask & 0x01: + result.append( str(value & 0x01) ) + else: + result.append( "." ) + mask = mask >> 1 + value = value >> 1 + result.reverse() + + return "".join(result).strip() + +def parse_binary(value, bytemasks, verbose = False, value_len = 8): + ## Parses a binary structure and gives information back + ## bytemasks is a sequence of (mask, value, string_if_no_match, string_if_match) tuples + result = [] + for mask, byte, nonmatch, match in bytemasks: + + if verbose: + prefix = represent_binary_fancy(value_len, value, mask) + ": " + else: + prefix = "" + if (value & mask) == (byte & mask): + if match is not None: + result.append(prefix + match) + else: + if nonmatch is not None: + result.append(prefix + nonmatch) + + return result + +_myprintable = " " + string.letters + string.digits + string.punctuation +def hexdump(data, indent = 0, short = False, linelen = 16, offset = 0): + """Generates a nice hexdump of data and returns it. Consecutive lines will + be indented with indent spaces. When short is true, will instead generate + hexdump without adresses and on one line. + + Examples: + hexdump('\x00\x41') -> \ + '0000: 00 41 .A ' + hexdump('\x00\x41', short=True) -> '00 41 (.A)'""" + + def hexable(data): + return " ".join([binascii.b2a_hex(a) for a in data]) + + def printable(data): + return "".join([e in _myprintable and e or "." for e in data]) + + if short: + return "%s (%s)" % (hexable(data), printable(data)) + + FORMATSTRING = "%04x: %-"+ str(linelen*3) +"s %-"+ str(linelen) +"s" + result = "" + (head, tail) = (data[:linelen], data[linelen:]) + pos = 0 + while len(head) > 0: + if pos > 0: + result = result + "\n%s" % (' ' * indent) + result = result + FORMATSTRING % (pos+offset, hexable(head), printable(head)) + pos = pos + len(head) + (head, tail) = (tail[:linelen], tail[linelen:]) + return result + +LIFE_CYCLES = {0x01: "Load file = loaded", + 0x03: "Applet instance / security domain = Installed", + 0x07: "Card manager = Initialized; Applet instance / security domain = Selectable", + 0x0F: "Card manager = Secured; Applet instance / security domain = Personalized", + 0x7F: "Card manager = Locked; Applet instance / security domain = Blocked", + 0xFF: "Applet instance = Locked"} + +def parse_status(data): + """Parses the Response APDU of a GetStatus command.""" + def parse_segment(segment): + def parse_privileges(privileges): + if privileges == 0x0: + return "N/A" + else: + privs = [] + if privileges & (1<<7): + privs.append("security domain") + if privileges & (1<<6): + privs.append("DAP DES verification") + if privileges & (1<<5): + privs.append("delegated management") + if privileges & (1<<4): + privs.append("card locking") + if privileges & (1<<3): + privs.append("card termination") + if privileges & (1<<2): + privs.append("default selected") + if privileges & (1<<1): + privs.append("global PIN modification") + if privileges & (1<<0): + privs.append("mandated DAP verification") + return ", ".join(privs) + + lgth = ord(segment[0]) + aid = segment[1:1+lgth] + lifecycle = ord(segment[1+lgth]) + privileges = ord(segment[1+lgth+1]) + + print "aid length: %i (%x)" % (lgth, lgth) + print "aid: %s" % hexdump(aid, indent = 18, short=True) + print "life cycle state: %x (%s)" % (lifecycle, LIFE_CYCLES.get(lifecycle, "unknown or invalid state")) + print "privileges: %x (%s)\n" % (privileges, parse_privileges(privileges)) + + pos = 0 + while pos < len(data): + lgth = ord(data[pos])+3 + segment = data[pos:pos+lgth] + parse_segment(segment) + pos = pos + lgth + +def _unformat_hexdump(dump): + hexdump = " ".join([line[7:54] for line in dump.splitlines()]) + return binascii.a2b_hex("".join([e != " " and e or "" for e in hexdump])) + +def _make_byte_property(prop): + "Make a byte property(). This is meta code." + return property(lambda self: getattr(self, "_"+prop, None), + lambda self, value: self._setbyte(prop, value), + lambda self: delattr(self, "_"+prop), + "The %s attribute of the APDU" % prop) + +class APDU(object): + "Base class for an APDU" + + def __init__(self, *args, **kwargs): + """Creates a new APDU instance. Can be given positional parameters which + must be sequences of either strings (or strings themselves) or integers + specifying byte values that will be concatenated in order. Alternatively + you may give exactly one positional argument that is an APDU instance. + After all the positional arguments have been concatenated they must + form a valid APDU! + + The keyword arguments can then be used to override those values. + Keywords recognized are: + C_APDU: cla, ins, p1, p2, lc, le, data + R_APDU: sw, sw1, sw2, data + """ + + initbuff = list() + + if len(args) == 1 and isinstance(args[0], self.__class__): + self.parse( args[0].render() ) + else: + for arg in args: + if type(arg) == str: + initbuff.extend(arg) + elif hasattr(arg, "__iter__"): + for elem in arg: + if hasattr(elem, "__iter__"): + initbuff.extend(elem) + else: + initbuff.append(elem) + else: + initbuff.append(arg) + + for (index, value) in enumerate(initbuff): + t = type(value) + if t == str: + initbuff[index] = ord(value) + elif t != int: + raise TypeError, "APDU must consist of ints or one-byte strings, not %s (index %s)" % (t, index) + + self.parse( initbuff ) + + for (name, value) in kwargs.items(): + if value is not None: + setattr(self, name, value) + + def _getdata(self): + return self._data + def _setdata(self, value): + if isinstance(value, str): + self._data = "".join([e for e in value]) + elif isinstance(value, list): + self._data = "".join([chr(int(e)) for e in value]) + else: + raise ValueError, "'data' attribute can only be a str or a list of int, not %s" % type(value) + self.Lc = len(value) + def _deldata(self): + del self._data; self.data = "" + + data = property(_getdata, _setdata, None, + "The data contents of this APDU") + + def _setbyte(self, name, value): + #print "setbyte(%r, %r)" % (name, value) + if isinstance(value, int): + setattr(self, "_"+name, value) + elif isinstance(value, str): + setattr(self, "_"+name, ord(value)) + else: + raise ValueError, "'%s' attribute can only be a byte, that is: int or str, not %s" % (namelower, type(value)) + + def _format_parts(self, fields): + "utility function to be used in __str__ and __repr__" + + parts = [] + for i in fields: + parts.append( "%s=0x%02X" % (i, getattr(self, i)) ) + + return parts + + def __str__(self): + result = "%s(%s)" % (self.__class__.__name__, ", ".join(self._format_fields())) + + if len(self.data) > 0: + result = result + " with %i (0x%02x) bytes of data" % ( + len(self.data), len(self.data) + ) + return result + ":\n" + hexdump(self.data) + else: + return result + + def __repr__(self): + parts = self._format_fields() + + if len(self.data) > 0: + parts.append("data=%r" % self.data) + + return "%s(%s)" % (self.__class__.__name__, ", ".join(parts)) + +class C_APDU(APDU): + "Class for a command APDU" + + def parse(self, apdu): + "Parse a full command APDU and assign the values to our object, overwriting whatever there was." + + apdu = map( lambda a: (isinstance(a, str) and (ord(a),) or (a,))[0], apdu) + apdu = apdu + [0] * max(4-len(apdu), 0) + + self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4 + if len(apdu) == 5: # case 2 + self.Le = apdu[-1] + self.data = "" + elif len(apdu) > 5: # case 3, 4 + self.Lc = apdu[4] + if len(apdu) == 5 + self.Lc: # case 3 + self.data = apdu[5:] + elif len(apdu) == 5 + self.Lc + 1: # case 4 + self.data = apdu[5:-1] + self.Le = apdu[-1] + else: + raise ValueError, "Invalid Lc value. Is %s, should be %s or %s" % (self.Lc, + 5 + self.Lc, 5 + self.Lc + 1) + else: # case 1 + self.data = "" + + CLA = _make_byte_property("CLA"); cla = CLA + INS = _make_byte_property("INS"); ins = INS + P1 = _make_byte_property("P1"); p1 = P1 + P2 = _make_byte_property("P2"); p2 = P2 + Lc = _make_byte_property("Lc"); lc = Lc + Le = _make_byte_property("Le"); le = Le + + def _format_fields(self): + fields = ["CLA", "INS", "P1", "P2"] + if self.Lc > 0: + fields.append("Lc") + if hasattr(self, "_Le"): ## There's a difference between "Le = 0" and "no Le" + fields.append("Le") + + return self._format_parts(fields) + + def render(self): + "Return this APDU as a binary string" + buffer = [] + + for i in self.CLA, self.INS, self.P1, self.P2: + buffer.append(chr(i)) + + if len(self.data) > 0: + buffer.append(chr(self.Lc)) + buffer.append(self.data) + + if hasattr(self, "_Le"): + buffer.append(chr(self.Le)) + + return "".join(buffer) + + def case(self): + "Return 1, 2, 3 or 4, depending on which ISO case we represent." + if self.Lc == 0: + if not hasattr(self, "_Le"): + return 1 + else: + return 2 + else: + if not hasattr(self, "_Le"): + return 3 + else: + return 4 + + _apduregex = re.compile(r'^\s*([0-9a-f]{2}\s*){4,}$', re.I) + _fancyapduregex = re.compile(r'^\s*([0-9a-f]{2}\s*){4,}\s*((xx|yy)\s*)?(([0-9a-f]{2}|:|\)|\(|\[|\])\s*)*$', re.I) + @staticmethod + def parse_fancy_apdu(*args): + apdu_string = " ".join(args) + if not C_APDU._fancyapduregex.match(apdu_string): + raise ValueError + + apdu_string = apdu_string.lower() + have_le = False + pos = apdu_string.find("xx") + if pos == -1: + pos = apdu_string.find("yy") + have_le = True + + apdu_head = "" + apdu_tail = apdu_string + if pos != -1: + apdu_head = apdu_string[:pos] + apdu_tail = apdu_string[pos+2:] + + if apdu_head.strip() != "" and not C_APDU._apduregex.match(apdu_head): + raise ValueError + + class Node(list): + def __init__(self, parent = None, type = None): + list.__init__(self) + self.parent = parent + self.type = type + + def make_binary(self): + "Recursively transform hex strings to binary" + for index, child in enumerate(self): + if isinstance(child,str): + child = "".join( ("".join(child.split())).split(":") ) + assert len(child) % 2 == 0 + self[index] = binascii.a2b_hex(child) + else: + child.make_binary() + + def calculate_lengths(self): + "Recursively calculate lengths and insert length counts" + self.length = 0 + index = 0 + while index < len(self): ## Can't use enumerate() due to the insert() below + child = self[index] + + if isinstance(child,str): + self.length = self.length + len(child) + else: + child.calculate_lengths() + + formatted_len = binascii.a2b_hex("%02x" % child.length) ## FIXME len > 255? + self.length = self.length + len(formatted_len) + child.length + self.insert(index, formatted_len) + index = index + 1 + + index = index + 1 + + def flatten(self, offset = 0, ignore_types=["("]): + "Recursively flatten, gather list of marks" + string_result = [] + mark_result = [] + for child in self: + if isinstance(child,str): + string_result.append(child) + offset = offset + len(child) + else: + start = offset + child_string, child_mark = child.flatten(offset, ignore_types) + string_result.append(child_string) + offset = end = offset + len(child_string) + if not child.type in ignore_types: + mark_result.append( (child.type, start, end) ) + mark_result.extend(child_mark) + + return "".join(string_result), mark_result + + + tree = Node() + current = tree + allowed_parens = {"(": ")", "[":"]"} + + for pos,char in enumerate(apdu_tail): + if char in (" ", "a", "b", "c", "d", "e", "f",":") or char.isdigit(): + if len(current) > 0 and isinstance(current[-1],str): + current[-1] = current[-1] + char + else: + current.append(str(char)) + + elif char in allowed_parens.values(): + if current.parent is None: + raise ValueError + if allowed_parens[current.type] != char: + raise ValueError + + current = current.parent + + elif char in allowed_parens.keys(): + current.append( Node(current, char) ) + current = current[-1] + + else: + raise ValueError + + if current != tree: + raise ValueError + + tree.make_binary() + tree.calculate_lengths() + + apdu_head = apdu_head.strip() + if apdu_head != "": + l = tree.length + if have_le: + l = l - 1 ## FIXME Le > 255? + formatted_len = "%02x" % l ## FIXME len > 255? + apdu_head = binascii.a2b_hex("".join( (apdu_head + formatted_len).split() )) + + apdu_tail, marks = tree.flatten(offset=0) + + apdu = C_APDU(apdu_head + apdu_tail, marks = marks) + return apdu + + +class R_APDU(APDU): + "Class for a response APDU" + + def _getsw(self): return chr(self.SW1) + chr(self.SW2) + def _setsw(self, value): + if len(value) != 2: + raise ValueError, "SW must be exactly two bytes" + self.SW1 = value[0] + self.SW2 = value[1] + + SW = property(_getsw, _setsw, None, + "The Status Word of this response APDU") + sw = SW + + SW1 = _make_byte_property("SW1"); sw1 = SW1 + SW2 = _make_byte_property("SW2"); sw2 = SW2 + + def parse(self, apdu): + "Parse a full response APDU and assign the values to our object, overwriting whatever there was." + self.SW = apdu[-2:] + self.data = apdu[:-2] + + def _format_fields(self): + fields = ["SW1", "SW2"] + return self._format_parts(fields) + + def render(self): + "Return this APDU as a binary string" + return self.data + self.sw + +if __name__ == "__main__": + response = """ +0000: 07 A0 00 00 00 03 00 00 07 00 07 A0 00 00 00 62 ...............b +0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b...... +0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b.. +0030: 01 00 07 A0 00 00 00 03 00 00 01 00 0E A0 00 00 ................ +0040: 00 30 00 00 90 07 81 32 10 00 00 01 00 0E A0 00 .0.....2........ +0050: 00 00 30 00 00 90 07 81 42 10 00 00 01 00 0E A0 ..0.....B....... +0060: 00 00 00 30 00 00 90 07 81 41 10 00 00 07 00 0E ...0.....A...... +0070: A0 00 00 00 30 00 00 90 07 81 12 10 00 00 01 00 ....0........... +0080: 09 53 4C 42 43 52 59 50 54 4F 07 00 90 00 .SLBCRYPTO.... +""" # 64kv1 vorher + response = """ +0000: 07 A0 00 00 00 03 00 00 0F 00 07 A0 00 00 00 62 ...............b +0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b...... +0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b.. +0030: 01 00 07 A0 00 00 00 03 00 00 01 00 08 A0 00 00 ................ +0040: 00 30 00 CA 10 01 00 0E A0 00 00 00 30 00 00 90 .0..........0... +0050: 07 81 32 10 00 00 01 00 0E A0 00 00 00 30 00 00 ..2..........0.. +0060: 90 07 81 42 10 00 00 01 00 0E A0 00 00 00 30 00 ...B..........0. +0070: 00 90 07 81 41 10 00 00 07 00 0E A0 00 00 00 30 ....A..........0 +0080: 00 00 90 07 81 12 10 00 00 01 00 09 53 4C 42 43 ............SLBC +0090: 52 59 50 54 4F 07 00 90 00 RYPTO.... +""" # komische Karte + response = """ +0000: 07 A0 00 00 00 03 00 00 07 00 07 A0 00 00 00 62 ...............b +0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b...... +0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b.. +0030: 01 00 07 A0 00 00 00 03 00 00 01 00 0E A0 00 00 ................ +0040: 00 30 00 00 90 07 81 32 10 00 00 01 00 0E A0 00 .0.....2........ +0050: 00 00 30 00 00 90 07 81 42 10 00 00 01 00 0E A0 ..0.....B....... +0060: 00 00 00 30 00 00 90 07 81 41 10 00 00 07 00 0E ...0.....A...... +0070: A0 00 00 00 30 00 00 90 07 81 12 10 00 00 01 00 ....0........... +0080: 09 53 4C 42 43 52 59 50 54 4F 07 00 05 A0 00 00 .SLBCRYPTO...... +0090: 00 01 01 00 90 00 ...... +""" # 64kv1 nachher + response = """ +0000: 07 A0 00 00 00 03 00 00 07 00 07 A0 00 00 00 62 ...............b +0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b...... +0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b.. +0030: 01 00 07 A0 00 00 00 03 00 00 01 00 0E A0 00 00 ................ +0040: 00 30 00 00 90 07 81 32 10 00 00 01 00 0E A0 00 .0.....2........ +0050: 00 00 30 00 00 90 07 81 42 10 00 00 01 00 0E A0 ..0.....B....... +0060: 00 00 00 30 00 00 90 07 81 41 10 00 00 07 00 0E ...0.....A...... +0070: A0 00 00 00 30 00 00 90 07 81 12 10 00 00 01 00 ....0........... +0080: 09 53 4C 42 43 52 59 50 54 4F 07 00 05 A0 00 00 .SLBCRYPTO...... +0090: 00 01 01 00 06 A0 00 00 00 01 01 07 02 90 00 ............... +""" # 64k1 nach setup + #response = sys.stdin.read() + #parse_status(_unformat_hexdump(response)[:-2]) + + a = C_APDU(1,2,3,4) # case 1 + b = C_APDU(1,2,3,4,5) # case 2 + c = C_APDU((1,2,3), cla=0x23, data="hallo") # case 3 + d = C_APDU(1,2,3,4,2,4,6,0) # case 4 + + print + print a + print b + print c + print d + print + print repr(a) + print repr(b) + print repr(c) + print repr(d) + + print + for i in a, b, c, d: + print hexdump(i.render()) + + print + e = R_APDU(0x90,0) + f = R_APDU("foo\x67\x00") + + print + print e + print f + print + print repr(e) + print repr(f) + + print + for i in e, f: + print hexdump(i.render()) diff --git a/vscarchitecture.bb b/vscarchitecture.bb new file mode 100644 index 0000000..488d259 --- /dev/null +++ b/vscarchitecture.bb @@ -0,0 +1,81 @@ +DESCRIPTION = "Virtual Smartcard with PCSC Driver and CCID to PCSC Gadget" +LICENSE = "GPL" + +DEPENDS = "pcsc-lite linux-libc-headers" +RDEPENDS = "pcsc-lite kernel-module-gadgetfs python-pycrypto python-crypt python-textutils python-imaging python-pickle" + +SRC_URI = "svn://vsmartcard.svn.sourceforge.net/svnroot/;module=vsmartcard;proto=https;rev=1" + +S = "${WORKDIR}" +serialdropdir = ${libdir}/pcsc/drivers/serial +readerconfdir = ${sysconfdir}/reader.conf.d +python_sitelib = /usr/lib/python2.5/site-packages + +LIBPCSCLITE_CFLAGS = -I${STAGING_INCDIR}/PCSC -lpcsclite +PTHREAD_CFLAGS = -pthread +# FIXME: let openmoko set the right STAGING_KERNEL_DIR +STAGING_KERNEL_DIR = ${STAGING_DIR}/${MACHINE}-angstrom-${TARGET_OS}/kernel +GADGETFS_CFLAGS = -I${STAGING_KERNEL_DIR}/include +SO_CFLAGS = -fPIC +SO_LDFLAGS = -shared + +INSTALL = install +INSTALL_PROGRAM = ${INSTALL} +INSTALL_DATA = ${INSTALL} -m 644 + +FILES_${PN} = "\ + ${bindir}/* \ + ${readerconfdir}/* \ + ${serialdropdir}/* \ + ${python_sitelib}/* \ +" + +do_compile() { + ${CC} ${S}/NFC/ccid/ccid.c ${S}/NFC/ccid/usbstring.c -o ccid \ + ${LIBPCSCLITE_CFLAGS} ${GADGETFS_CFLAGS} ${PTHREAD_CFLAGS} ${CFLAGS} + + ${CC} ${S}/NFC/picc_to_pcsc/picc_to_pcsc.c -o picc_to_pcsc \ + ${LIBPCSCLITE_CFLAGS} ${CFLAGS} + + ${CC} -c ${S}/NFC/virtualsmartcard/vpcd/vpcd.c \ + ${S}/NFC/virtualsmartcard/vpcd/vpcd.h \ + ${LIBPCSCLITE_CFLAGS} ${SO_CFLAGS} ${CFLAGS} + ${CC} vpcd.o -o libvpcd.so \ + ${LIBPCSCLITE_CFLAGS} ${SO_LDFLAGS} ${LDFLAGS} + rm -f vpcd.o + + echo '#!/bin/sh' > virtualsmartcard + echo "cd \"${python_sitelib}\"" \ + >> virtualsmartcard + echo "python \"${python_sitelib}/VirtualSmartcard.py\" \"\$@\"" \ + >> virtualsmartcard + + echo 'FRIENDLYNAME "Virtual PCD"' > vpcd.conf + echo 'DEVICENAME /dev/null' >> vpcd.conf + echo "LIBPATH ${serialdropdir}/libvpcd.so" >> vpcd.conf + echo 'CHANNELID 0' >> vpcd.conf +} + +do_install() { + ${INSTALL} -d ${D}${bindir} + ${INSTALL} -d ${D}${serialdropdir} + ${INSTALL} -d ${D}${sysconfdir}/reader.conf.d/ + ${INSTALL} -d ${D}${python_sitelib} + ${INSTALL_PROGRAM} ccid ${D}${bindir} + ${INSTALL_PROGRAM} picc_to_pcsc ${D}${bindir} + ${INSTALL_PROGRAM} virtualsmartcard ${D}${bindir} + ${INSTALL_PROGRAM} libvpcd.so ${D}${serialdropdir} + ${INSTALL_DATA} vpcd.conf ${D}${readerconfdir} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/ConstantDefinitions.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/CryptoUtils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/SEutils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/SmartcardFilesystem.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/SmartcardSAM.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/SWutils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/TLVutils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/utils.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/VirtualSmartcard.py ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/testconfig.sam ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/testconfig.mf ${D}${python_sitelib} + ${INSTALL_DATA} ${S}/NFC/virtualsmartcard/vpicc/jp2.jpg ${D}${python_sitelib} +}