diff --git a/doc/manual/scute.texi b/doc/manual/scute.texi index 1d699a9..e5a74f4 100644 --- a/doc/manual/scute.texi +++ b/doc/manual/scute.texi @@ -1,872 +1,872 @@ \input texinfo @c -*- Texinfo -*- @setfilename scute.info @settitle The Scute Manual @dircategory GNU Utilities @direntry * Scute: (scute). PKCS #11 module for the OpenPGP Card @end direntry @include version.texi @c Unify some of the indices. @syncodeindex tp fn @syncodeindex pg fn @ifinfo This file documents the Scute module. This is Edition @value{EDITION}, last updated @value{UPDATED}, of @cite{The Scute Manual}, for Version @value{VERSION}. @c NOTE: Don't forget to update the year for the TeX version, too. Copyright @copyright{} 2002, 2003, 2004, 2005, 2006, 2007 g10 Code GmbH. The Scute Manual 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. The text of the license can be found in the section entitled ``Library Copying''. The Scute Manual 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 Lesser General Public License for more details. @end ifinfo @iftex @shorttitlepage The Scute Manual @end iftex @titlepage @center @titlefont{The Scute Manual} @sp 6 @center Edition @value{EDITION} @sp 1 @center last updated @value{UPDATED} @sp 1 @center for version @value{VERSION} @page @vskip 0pt plus 1filll Copyright @copyright{} 2002, 2003, 2004, 2005, 2006, 2007 g10 Code GmbH. The Scute Manual 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. The text of the license can be found in the section entitled ``Library Copying''. The Scute Manual 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 lesser General Public License for more details. @end titlepage @page @ifnottex @node Top @top Main Menu This is Edition @value{EDITION}, last updated @value{UPDATED}, of @cite{The Scute Manual}, for Version @value{VERSION} of the Scute module. @end ifnottex @menu * Introduction:: How to use this manual. * Preparation:: What you should do before using Scute. * Client Authentication:: How to use Scute for client authentication. * Email Signing:: How to use Scute for S/MIME email signing. * Document Signing:: How to use Scute with LibreOffice. * Troubleshooting:: What to do when things go wrong. * Internals:: Technical details about Scute. Appendices * Library Copying:: The GNU General Public License says how you can copy and share Scute. Indices * Concept Index:: Index of concepts and programs. @detailmenu --- The Detailed Node Listing --- Introduction * Getting Started:: Purpose of the manual, and how to use it. * Features:: Reasons to install and use Scute. * Overview:: Basic architecture of the Scute module. Preparation * Prerequisites:: Dependencies of Scute. * Building the Source:: How to build Scute. * Certificate Preparation:: Preparing certificates for use with Scute. Client Authentication * Application Configuration:: Preparing the application for use with Scute. * Authentication With Service:: Using Scute for client authentication. Email Signing Document Signing Troubleshooting Internals * Features and Limitations:: Details about the interfaces implemented. * Developing Scute:: How to understand or improve Scute. * Mozilla Compatibility:: Interoperability notes for Mozilla NSS. @end detailmenu @end menu @node Introduction @chapter Introduction This is a PKCS #11 implementation for the GnuPG Agent using the GnuPG Smart Card Daemon. Currently, only the OpenPGP card is supported. Scute enables use of the OpenPGP smart card in applications supporting PKCS #11 compliant security tokens. The main application at this time is client authentication in Mozilla-based web browsers. In the future, other applications will be supported. @menu * Getting Started:: Purpose of the manual, and how to use it. * Features:: Reasons to install and use Scute. * Overview:: Basic architecture of the Scute module. @end menu @node Getting Started @section Getting Started This manual documents the Scute module, how it can be used for common applications supported by it, and how it can be extended and improved by programmers. It is thus a user manual as well as a developer manual. The reader is assumed to possess basic knowledge about cryptography in general, and public key cryptography in particular. The underlying cryptographic engines that are used by the library are not explained, but where necessary, special features or requirements are provided. This manual can be used in several ways. If read from the beginning to the end, it gives a good introduction into the module and how it can be used in an application. Forward references are included where necessary. Later on, the manual can be used as a reference manual to get just the information needed about any particular application of the module. @node Features @section Features Scute is currently the only implementation of PKCS #11 for the OpenPGP smart card. Apart from that, it offers a couple of other benefits: @table @asis @item it's free software Anybody can use, modify, and redistribute it under the terms of the GNU General Public License (@pxref{Library Copying}). @item it's built to grow Although Scute initially provided a single function, client authentication using OpenPGP smart cards in Mozilla-based web browsers, it was built with the intention of supporting other applications as well in the future. @item it's easy Building and installing Scute is easy, and preparing smart cards for use with Scute is a snatch using the GnuPG 2 framework. The integration of Scute into the application is seamless. @end table @node Overview @section Overview Scute is a security device that implements the PKCS #11 interface for security tokens. Applications which know how to use the PKCS #11 interface to access security tokens for cryptographic operations can use Scute to access the OpenPGP smart card. An important example of such an application is the Firefox web browser by the Mozilla project, which uses the Mozilla Network Security Services library (NSS). Scute itself does not include a driver for the smart card itself. Instead, it uses the GnuPG 2 framework to access the smart cards and associated data like certificates. Scute acts as the glue between the application and GnuPG 2. Currently supported usages are client authentication over HTTPS with Firefox (allowing users to authenticate themselves to a remote web service without entering their log-in information), email signing with Thunderbird, and document signing with LibreOffice. @node Preparation @chapter Preparation To use Scute, you first have to install the software. You also have to prepare each card you want to use with Scute before it can be used. Furthermore, you need to configure the application to make use of Scute for cryptographic operations. This chapter explains each of these steps in detail. @menu * Prerequisites:: Dependencies of Scute. * Building the Source:: How to build Scute. * Certificate Preparation:: Preparing certificates for use with Scute. @end menu @node Prerequisites @section Prerequisites There are two types of dependencies for Scute: compile-time dependencies and run-time dependencies. The compile-time dependencies only need to be fulfilled when Scute is compiled and installed. The run-time dependencies need to be fulfilled when Scute is used in an application. Scute depends, in addition to the essential build utilities, on the following packages at build time: @table @code @item libgpg-error Scute uses the GnuPG 2 framework for error handling, so it depends on the GPG error library. The minimum version required is 1.14. @item libassuan Scute uses the GnuPG 2 framework for communication with the GPG Agent, so it depends on the Assuan library. The minimum version required is 2.0.0. @end table At run-time, in addition to the run-time versions of the above libraries, you also need the following packages installed and configured: @table @asis @item GnuPG Scute uses the GnuPG 2 framework to access the OpenPGP card and for certificate management. The minimum version required is 2.0.0 for client authentication with TLS 1.0 and 1.1. Client authentication with TLS 1.2, email and document signing require GnuPG 2.1.0. @item Pinentry Pinentry is a dependency of GnuPG 2, so it also needs to be installed with it. @item Firefox et al. Firefox is the first application supported by Scute. In the future, other applications may be supported. The applications are not dependencies of Scute, but Scute can not be used stand-alone, so you can not experience it without an application. @end table @node Building the Source @section Building the Source Scute does comply to the GNU coding standards and thus can be compiled and installed according to the generic installation instructions found in the source package in the file @code{INSTALL}. There are no Scute specific options to the configure script. After installation, the @code{scute.so} module file can be found in the library directory of the installation path. @node Certificate Preparation @section Certificate Preparation To use an OpenPGP card with Scute, it first has to be initialized by generating or loading a key on the card, see @uref{http://www.gnupg.org/(en)/howtos/card-howto/en/smartcard-howto.html, the OpenPGP Card How-To}. Then a certificate has to be created and imported into GPGSM. This task involves three steps: First, a certificate signing request (@acronym{CSR}) has to be created that matches the key on the card. This certificate signing request then has to be submitted to a certificate authority (@acronym{CA}), which will create the certificate and send it back to you. At last, the certificate has to be imported into GPGSM. This section will explain all of these steps in detail. @menu * Creating a CSR:: How to create a card-based CSR. * Signing the CSR:: Obtain a certificate from the CSR. * Importing the Certificate:: How to import the certificate into GPGSM. * On-card Certificate:: How to store the certificate on the card. @end menu @node Creating a CSR @subsection Creating a CSR Before you start, make sure that the GPG Agent is running, see @ref{Prerequisites} and that your card is in the reader. There is no need to configure GPGSM, so you can create a CSR with the command: @example $ gpgsm --gen-key > floppy-head.csr Please select what kind of key you want: (1) RSA (2) Existing key (3) Existing key from card Your selection? 3 @end example As we create a certificate for the OpenPGP Card, the option ``@code{[3] Direct from card}'' should be selected. @example Serial number of the card: 355F9746499F0D4B4ECEE4928B007D16 Available keys: (1) D53137B94C38D9BF6A199706EA6D5253 OPENPGP.1 (2) B0CD1A9DFC3539A1D6A8B851A11C8665 OPENPGP.2 (3) 53DB41052CC590A40B403F3E6350E5DC OPENPGP.3 Your selection? 3 Possible actions for a RSA key: (1) sign, encrypt (2) sign (3) encrypt Your selection? 2 @end example The only operation currently supported is client authentication. For this, the authentication key has to be selected. This is the third key on the card, so the options ``@code{[3] OPENPGP.3}'' and ``@code{[2] sign}'' should be chosen. Note that the key usage is only advisory, and the CA may assign different capabilities. @example Enter the X.509 subject name: CN=Floppy Head,OU="Webserver Team",O="Snake Oil, Ltd",L="Snake Town",ST="Snake Desert",C=XY Enter email addresses (end with an empty line): > floppy.head@@example.org > Enter DNS names (optional; end with an empty line): > Enter URIs (optional; end with an empty line): > Create self-signed certificate? (y/N) n @end example As a last step, the common name and e-mail address of the key owner need to be specified by you. The above are only an example for a fictious person working at a fictious company. DNS names are only meaningful for server certificates and thus should be left empty. We have now entered all required information and gpgsm will display what it has gathered and ask whether to create the certificate request: @example These parameters are used: Key-Type: card:OPENPGP.3 Key-Length: 1024 Key-Usage: sign Name-DN: CN=Floppy Head,OU="Webserver Team",O="Snake Oil, Ltd",L="Snake Town",ST="Snake Desert",C=XY Name-Email: floppy.head@@example.org Proceed with creation? (y/N) y Now creating certificate request. This may take a while ... gpgsm: about to sign the CSR for key: &53DB41052CC590A40B403F3E6350E5DC @end example GPGSM will now start working on creating the request. During this time you will be asked once for a passphrase to unprotect the authentication key on the card. A pop up window will appear to ask for it. When it is ready, you should see the final notice: @example gpgsm: certificate request created Ready. You should now send this request to your CA. @end example Now, you may look at the created request: @example $ cat floppy-head.csr -----BEGIN CERTIFICATE REQUEST----- MIICCDCCAXECAQAwgYExCzAJBgNVBAYTAlhZMRUwEwYDVQQIEwxTbmFrZSBEZXNl cnQxEzARBgNVBAcTClNuYWtlIFRvd24xFzAVBgNVBAoTDlNuYWtlIE9pbCwgTHRk MRcwFQYDVQQLEw5XZWJzZXJ2ZXIgVGVhbTEUMBIGA1UEAxMLRmxvcHB5IEhlYWQw gaAwDQYJKoZIhvcNAQEBBQADgY4AMIGKAoGBANWaM9YS89AOx3GX1Rua+4DUHwbL wt0rBYdBddlabMMteVjUcOOhbFMirLpLAi1S8fUXNiy84ysOmFStmvSIXDsAgXq5 1ESOU4SNg2zEkPDF1WYJ5BFIXdYq9i2k5W7+ctV8PkKv3e5IeYXTa5qppIPD31de gM8Qj7tK0hL/eNCfAgQAAQABoEUwQwYJKoZIhvcNAQkOMTYwNDAiBgNVHREEGzAZ gRdmbG9wcHkuaGVhZEBleGFtcGxlLmNvbTAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZI hvcNAQEFBQADgYEAFC9q6+ib9YGCLB/2AlZR+/dvb+pEeXR1EbpV/dw/gjP1yPY6 29n8ZIDLUvQvNCtfCcXFxFimVSSB/KmFXXsJbM+NXQyT6Ocn34iHmkf9IVRMWQWg ZBYfQVeXAd7XlxI6d1wXDLwD/26lTU/rH2JU6H1+zSfZxqwVC4Iu+kiN4Y8= -----END CERTIFICATE REQUEST----- $ @end example @node Signing the CSR @subsection Signing the CSR The next step is to submit this certificate request to the CA, which can then create a certificate and send it back to you. If, for example, you use the CA @uref{http://www.cacert.org, CAcert}, then you can log into your account at the CAcert website, choose ``Client Certificates -> New'', check ``Show advanced options'', paste the above request block into the text field and click on ``Submit''. If everything works correctly, a certificate will be shown, which you can cut and paste into a new file @file{floppy-head.crt}. Alternatively if, for example, you set up your own CA with OpenSSL, then you can create your own certificate by issueing a command similar @code{openssl ca -in floppy-head.csr -cert snakeoil-ca-rsa.crt -keyfile snakeoil-ca-rsa.key -out floppy-head.crt}. Please see the OpenSSL documentation for more details on how to set up and administrate a certificate authority infrastructure. @node Importing the Certificate @subsection Importing the Certificate into GPGSM Once the CSR has been signed, you should end up with a certificate file @file{floppy-head.crt}, which you then have to import into GPGSM. It is also recommended that you import the root certificate of the CA first in the same fashion. @example $ gpgsm --import floppy-head.crt gpgsm: certificate imported gpgsm: total number processed: 1 gpgsm: imported: 1 @end example gpgsm tells you that it has imported the certificate. It is now associated with the key you used when creating the request. To see the content of your certificate, you may now enter: @example $ gpgsm -K Floppy /home/foo/.gnupg/pubring.kbx --------------------------- Serial number: 10 Issuer: /CN=Snake Oil CA/OU=Certificate Authority/O=Snake Oil, Ltd/L=Snake Town/ST=Snake Desert/C=XY/EMail=ca@@snakeoil.dom Subject: /CN=Floppy Head/OU=Webserver Team/O=Snake Oil, Ltd/ST=Snake Desert/C=XY validity: 2006-11-11 14:09:12 through 2007-11-11 14:09:12 key type: 1024 bit RSA fingerprint: EC:93:A2:55:C6:58:7F:C9:9E:96:DB:12:6E:64:99:54:BB:E1:94:68 @end example The option ``@code{-K}'' is used above because this will only list certificates for which a private key is available. To see more details, you may use ``@code{--dump-secret-keys}'' instead of ``@code{-K}''. @node On-card Certificate @subsection Loading the Certificate onto the Card This step is optional. You may choose to store the certificate directly into your OpenPGP card. The benefit of doing so is that Scute will then be able to fetch the certificate from the card without having to look into the GPGSM store. You need your certificate in the DER format. Export it from the GPGSM store with the following command: @example $ gpgsm -o floppy-head.crt --export Floppy @end example Then, fire up the GnuPG card editor to transfer the certificate to the card (note that the @code{writecert} command is not listed in the editor's online help): @example $ gpg2 --card-edit Application ID ...: D27600012301020000005000012340000 [...] gpg/card> admin Admin commands are allowed gpg/card> writecert 3 < floppy-head.crt gpg/card> quit @end example @node Client Authentication @chapter Client Authentication @menu * Application Configuration:: Preparing the application for use with Scute. * Authentication With Service:: Using Scute for client authentication. @end menu Scute allows you to authenticate yourself to a website securely without entering a username or password by simply using your OpenPGP card. Currently, only Mozilla-based browsers like Firefox are supported, although other applications using Mozilla NSS or supporting PKCS #11 modules may work. @node Application Configuration @section Application Configuration To prepare your application for use with Scute, you have to load the Scute module as a PKCS #11 module into the application. With Firefox, this can be done by choosing @code{Edit->Preferences} in the menu. In the preferences configuration dialog, you should select the @code{Advanced} configuration section, then the @code{Security} tab, and then select @code{Security Devices} in the category @code{Certificates}. @center @image{firefox-pref,13cm} In the devices manager dialog, you can select @code{Load} to load a new PKCS #11 device. @center @image{firefox-dm-load-before,13cm} In the pop-up dialog that follows, you can give a module name (e.g. ``@code{Scute}'') and a module filename. The latter should correspond to the full file name of the installed Scute module file @file{scute.so}. The default installation path is @file{/usr/local/lib}, which would mean that you have to provide the file name @file{/usr/local/lib/scute.so}. If you or your system administrator installed Scute in a different location, you have to adjust the file name correspondingly. @center @image{firefox-dm-load,8cm} After confirming installation of the security device, a pop-up window should confirm that the module was successfully loaded, and an entry for the security device should appear in the device manager list of @code{Security Modules and Devices}. @center @image{firefox-dm-load-after,15cm} When you insert the OpenPGP card for which you generated and imported a certificate earlier (see @ref{Certificate Preparation}), the device manager should detect this security token and display some information about it in the @code{Details} list when you select it from the module list. @center @image{firefox-dm-token-present,15cm} The client certificate will show up in the @code{Certificate Manager} under @code{Your Certificates}: @center @image{firefox-cm,13cm} @node Authentication With Service @section Authentication With Service Before you access a web service which requires client authentication, for instance a fictious web service @ifnottex @indicateurl{https://example.com}, @end ifnottex @iftex @code{https://example.com}, @end iftex the OpenPGP card should be present. In this case, a pop-up window will appear that requests you to enter the PIN number protecting the authentication key on the OpenPGP card. After entering the PIN number, your browser will be authenticated to the server. If the server accepts your request and certificate, this is all which is required. You should leave the card in the reader as long as the connection persists. Depending on how aggressively GPG Agent caches your PIN number, you may have to enter the PIN number again later to keep up the connection to the server. If the card is not present, or you enter the wrong PIN, or the server does not admit your certificate, you will get an error message. This error message is generated by the application and Scute can not influence it. Unfortunately, in Firefox (at least up to version 38.5.0), this error message is not very user friendly. For example, entering a bad PIN results in the following generic error message, and the @code{Try Again} button does not work as expected: @center @image{firefox-bad-pin,11cm} @comment FIXME: Document possible error codes. @node Email Signing @chapter Email Signing Scute also allows you to use your card-based X.509 certificate to sign your emails with the S/MIME signature format. This has been tested with Mozilla Thunderbird only, but should work with any mail client with support for PKCS #11 (notably GNOME Evolution). You must first load the Scute module into your mail client. With Mozilla Thunderbird, the procedure is the same as the one described above for Mozilla Firefox. -Then, open your accent configuration dialog (@code{Edit->Account +Then, open your account configuration dialog (@code{Edit->Account Settings}), and in the @code{Security} tab, under the section @code{Digital Signing}, use the @code{Select...} button to associate your card-based certificate with your account. @center @image{thunderbird-account-settings,13cm} When writing a new message, you may then use the @code{S/MIME} button and select @code{Digitally sign this message} in the popup menu. You will be prompted for your User PIN before the message is sent. @center @image{thunderbird-smime-button,13cm} @node Document Signing @chapter Document Signing Scute can also be used with LibreOffice to sign OpenDocument files. First, you must load the Scute module into Mozilla Firefox according to the above procedure. Then, configure LibreOffice to use Firefox's certificate store by defining the @code{MOZILLA_CERTIFICATE_FOLDER} environment variable to your Firefox profile directory. Then, to sign the document you are editing, select the @code{File->Digital Signatures...} menu option to open the @code{Digital Signatures} dialog. @center @image{libreoffice-digital-signatures,13cm} Click the @code{Sign Document} button to open the certificate selection dialog. Select your card-based certificate, then validate. Enter your User PIN when prompted by GPG Agent. @center @image{libreoffice-certificate-selection,13cm} You may also sign a PDF export of your document. Select the @code{File->Export as PDF...} menu option to open the @code{PDF Options} dialog. In the @code{Digital Signatures} tab, use the @code{Select} button to open the certificate selection dialog as above. You will be prompted for your User PIN when you will click the @code{Export} button. @center @image{libreoffice-pdf-signature,13cm} @node Troubleshooting @chapter Troubleshooting @strong{Symptom:} Loading the Scute security device in the security device manager of Firefox fails with "Unable to load module". @strong{Solution:} Make sure that Scute is correctly installed, and that all libraries and executables are available. If you are using GnuPG 2.0 (instead of 2.1), you may need to make sure that the GPG Agent is running and can be found via the environment variable @code{GPG_AGENT_INFO}. @xref{Invoking GPG-AGENT, , , gnupg, Using the GNU Privacy Guard}, for details on how to run the GPG Agent. @strong{Symptom:} Client authentication fails with " has received an incorrect or unexpected message. Error code: -12227". @strong{Solution:} Make sure that the correct OpenPGP card is inserted and the certificate available in GPGSM. Check that the OpenPGP card is detected correctly in the security device manager and the corresponding certificate is displayed in the certificate manager of Firefox. @xref{Authentication With Service}. @strong{Symptom:} The OpenPGP card is detected and displayed in the security device manager in Firefox, but no corresponding certificate is displayed in the certificate manager of Firefox. @strong{Solution:} Make sure that the corresponding certificate is imported in GPGSM. @comment FIXME: Can this really happen??? @node Internals @chapter Internals The following notes are intended for people interested in more technical details about Scute and its implementation. They give an overview about its scope and potential compatibility issues with applications. @menu * Features and Limitations:: Details about the interfaces implemented. * Developing Scute:: How to understand or improve Scute. * Mozilla Compatibility:: Interoperability notes for Mozilla NSS. @end menu @node Features and Limitations @section Features and Limitations Scute implements version 2.20 of the @uref{https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-11-cryptographic-token-interface-standard.htm, PKCS #11} specification. The @uref{http://www.g10code.com/p-card.html,OpenPGP smart card} application is supported in read-only mode. The following functions are not supported: @table @code @item C_Initialize No support for native thread package. Locking callbacks must be provided if multi-threaded operation is desired. @item C_WaitForSlotEvent Not implemented. The interface as specified by PKCS #11 is broken anyway, as the function can not safely be canceled. Thus, we require polling. @item C_GetOperationState @itemx C_SetOperationState Not supported. @item C_InitToken @itemx C_InitPIN @itemx C_SetPIN Not supported. No write operations are allowed. To configure the token, please use the tools accompanying the GnuPG software suite. @item C_Login @itemx C_Logout Not supported. No login into the token by the software is required. Passphrase queries are implemented by the use of GPG Agent and Pinentry. @item C_EncryptInit @itemx C_Encrypt @itemx C_EncryptUpdate @itemx C_EncryptFinal @itemx C_DigestInit @itemx C_Digest @itemx C_DigestUpdate @itemx C_DigestKey @itemx C_DigestFinal @itemx C_VerifyInit @itemx C_Verify @itemx C_VerifyUpdate @itemx C_VerifyFinal @itemx C_VerifyRecoverInit @itemx C_VerifyRec Not supported. Only secret key operations are supported. @item C_DecryptInit @itemx C_Decrypt Not yet supported, but will be in the future. @item C_SignUpdate @itemx C_SignFinal @itemx C_DecryptUpdate @itemx C_DecryptFinal No progressive crypto-operations are supported. @item C_SignRecoverInit @itemx C_SignRecover Not supported. @item C_DigestEncryptUpdate @itemx C_DecryptDigestUpdate @itemx C_SignEncryptUpdate @itemx C_DecryptVerifyUpdate Dual-purpose cryptographic functions are not supported. @item C_GenerateKey @itemx C_GenerateKeyPair @itemx C_WrapKey @itemx C_UnwrapKey @itemx C_DeriveKey Key management functions are not supported. Please use the tools accompanying the GnuPG software suite to generate and import keys for use with the token. @item C_SeedRandom Not supported. @item C_CreateObject @itemx C_CopyObject @itemx C_DestroyObject @itemx C_SetAttributeValue: Only read-only operations are supported on objects. @item C_GetObjectSize Not supported. @item CKO_CERTIFICATE The label specifies the key on the card used (e.g. @code{OPENPGP.3}). The ID is the fingerprint. @item CKO_PRIVATE_KEY: The @code{CKA_LOCAL} attribute can not be supported by the OpenPGP card. It is always set to false (as the key on the card may be copied to the card from an external source). @end table @node Developing Scute @section Developing Scute Scute is single-threaded. There is a global lock that is taken in all entry points of Scute, except for @code{C_Initialize}, @code{C_Finalize}, @code{C_GetFunctionList}, and stubs. Here are a couple of hints on how to develop PKCS #11 modules for Mozilla: @code{libopensc2} ships with a @code{pkcs11-spy} library that can be loaded as a wrapper around the PKCS #11 library you want to use to log all functions invoked by Mozilla. Here is how to use it: Set the @code{PKCS11SPY_OUTPUT} environment variable to a filename. @code{pkcs11-spy} appends its log messages at the end of this file. Set the @code{PKCS11SPY} environment variable to the filename of the PKCS #11 module you actually want to use. Start Mozilla within this environment. There is a different, probably more powerful way to debug Mozilla PKCS #11 libraries. However, to be able to use it, you need to configure and compile the Mozilla NSS sources with @code{--enable-debug}. Instructions can be found at: @uref{https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/nss_tech_notes} Here are a couple of links to more information about implementing a PKCS #11 module for Mozilla: @table @uref @item https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11_Implement Guidelines for implementors of PKCS #11 modules targeting Mozilla @item http://www-archive.mozilla.org/projects/security/pki/pkcs11/ PKCS #11 Conformance Testing @item https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS The Mozilla NSS web page @end table @node Mozilla Compatibility @section Mozilla Compatibility Mozilla has a bug that causes the wrong security device to be unloaded when unloading a security device. Also, the displayed list becomes corrupt. When closing and reopening the security device manager, the list displayed is correct, but in anyway the wrong security module is unloaded. @include lesser.texi @node Concept Index @unnumbered Concept Index @printindex cp @summarycontents @contents @bye diff --git a/src/agent.c b/src/agent.c index a6500cd..7a907ff 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1,1458 +1,1479 @@ /* agent.c - Talking to gpg-agent. * Copyright (C) 2006, 2007, 2008, 2015, 2019 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define PATHSEP_C ';' # define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */ # include # include #else # define PATHSEP_C ':' #endif #include #include #include "debug.h" #include "support.h" #include "sexp-parse.h" #include "cert.h" #include "agent.h" /* The global agent context. */ static assuan_context_t agent_ctx; /* Hack required for Windows. */ void gnupg_allow_set_foregound_window (pid_t pid) { if (!pid || pid == (pid_t)(-1)) return; #ifdef HAVE_W32_SYSTEM else if (!AllowSetForegroundWindow (pid)) DEBUG (DBG_CRIT, "AllowSetForegroundWindow(%lu) failed: %u\n", (unsigned long)pid, (unsigned int)GetLastError ()); #endif } /* Establish a connection to a running GPG agent. */ static gpg_error_t agent_connect (assuan_context_t *ctx_r) { gpg_error_t err = 0; assuan_context_t ctx = NULL; char buffer[512]; /* Use gpgconf to make sure that gpg-agent is started and to obtain * the socket name. For older version of gnupg we will fallback to * using two gpgconf commands with the same effect. */ snprintf (buffer, sizeof buffer, "%s --show-socket --launch gpg-agent", get_gpgconf_path ()); err = read_first_line (buffer, buffer, sizeof buffer); if (gpg_err_code (err) == GPG_ERR_NO_AGENT && is_gnupg_older_than (2, 2, 14)) { snprintf (buffer, sizeof buffer, "%s --launch gpg-agent", get_gpgconf_path ()); err = read_first_line (buffer, NULL, 0); if (!err) { snprintf (buffer, sizeof buffer, "%s --list-dirs agent-socket", get_gpgconf_path ()); err = read_first_line (buffer, buffer, sizeof buffer); } } /* Then connect to the socket we got. */ if (!err) { err = assuan_new (&ctx); if (!err) { err = assuan_socket_connect (ctx, buffer, 0, 0); if (!err) { *ctx_r = ctx; if (_scute_debug_flags & DBG_ASSUAN) assuan_set_log_stream (*ctx_r, _scute_debug_stream); } else assuan_release (ctx); } } /* We do not try any harder. If gpg-connect-agent somehow failed * to give us a suitable socket, we probably cannot do better. */ if (err) DEBUG (DBG_CRIT, "cannot connect to GPG agent: %s", gpg_strerror (err)); return err; } /* This is the default inquiry callback. It mainly handles the Pinentry notifications. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { (void)opaque; if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17])) { gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10)); /* We do not pass errors to avoid breaking other code. */ } else DEBUG (DBG_CRIT, "ignoring gpg-agent inquiry `%s'\n", line); return 0; } /* Send a simple command to the agent. */ static gpg_error_t agent_simple_cmd (assuan_context_t ctx, const char *fmt, ...) { gpg_error_t err; char *optstr; va_list arg; int res; va_start (arg, fmt); res = vasprintf (&optstr, fmt, arg); va_end (arg); if (res < 0) return gpg_error_from_errno (errno); err = assuan_transact (ctx, optstr, NULL, NULL, default_inq_cb, NULL, NULL, NULL); if (err) DEBUG (DBG_CRIT, "gpg-agent command '%s' failed: %s", optstr, gpg_strerror (err)); free (optstr); return err; } /* Configure the GPG agent at connection CTX. */ static gpg_error_t agent_configure (assuan_context_t ctx) { gpg_error_t err = 0; char *dft_display = NULL; char *dft_ttyname = NULL; char *dft_ttytype = NULL; #if defined(HAVE_SETLOCALE) && (defined(LC_CTYPE) || defined(LC_MESSAGES)) char *old_lc = NULL; char *dft_lc = NULL; #endif char *dft_xauthority = NULL; char *dft_pinentry_user_data = NULL; err = agent_simple_cmd (ctx, "RESET"); if (err) return err; /* Set up display, terminal and locale options. */ dft_display = getenv ("DISPLAY"); if (dft_display) err = agent_simple_cmd (ctx, "OPTION display=%s", dft_display); if (err) return err; dft_ttyname = getenv ("GPG_TTY"); if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); if (dft_ttyname) { err = agent_simple_cmd (ctx, "OPTION ttyname=%s", dft_ttyname); if (err) return err; } dft_ttytype = getenv ("TERM"); if (dft_ttytype) err = agent_simple_cmd (ctx, "OPTION ttytype=%s", dft_ttytype); if (err) return err; #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) return gpg_error_from_errno (errno); } dft_lc = setlocale (LC_CTYPE, ""); if (dft_lc) err = agent_simple_cmd ("OPTION lc-ctype=%s", dft_lc); if (old_lc) { setlocale (LC_CTYPE, old_lc); free (old_lc); } #endif if (err) return err; #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) err = gpg_error_from_errno (errno); } dft_lc = setlocale (LC_MESSAGES, ""); if (dft_lc) err = agent_simple_cmd ("OPTION lc-messages=%s", dft_lc); if (old_lc) { setlocale (LC_MESSAGES, old_lc); free (old_lc); } #endif dft_xauthority = getenv ("XAUTHORITY"); if (dft_xauthority) err = agent_simple_cmd (ctx, "OPTION xauthority=%s", dft_xauthority); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; else if (err) return err; dft_pinentry_user_data = getenv ("PINENTRY_USER_DATA"); if (dft_pinentry_user_data) err = agent_simple_cmd (ctx, "OPTION pinentry_user_data=%s", dft_pinentry_user_data); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; err = agent_simple_cmd (ctx, "OPTION allow-pinentry-notify"); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; return err; } /* Check for a broken pipe, that is a lost connection to the agent. * Update the gloabls so that a re-connect is done the next time. * Returns ERR or the modified code GPG_ERR_NO_AGENT. */ static gpg_error_t check_broken_pipe (gpg_error_t err) { /* Note that Scute _currently_ uses GPG_ERR_SOURCE_ANY. */ if (gpg_err_code (err) == GPG_ERR_EPIPE && gpg_err_source (err) == GPG_ERR_SOURCE_ANY) { DEBUG (DBG_INFO, "Broken connection to the gpg-agent"); scute_agent_finalize (); err = gpg_error (GPG_ERR_NO_AGENT); } return err; } /* If the connection to the agent was lost earlier and detected by * check_broken_pipe we try to reconnect. */ static gpg_error_t ensure_agent_connection (void) { gpg_error_t err; if (agent_ctx) return 0; /* Connection still known. */ DEBUG (DBG_INFO, "Re-connecting to gpg-agent"); err = agent_connect (&agent_ctx); if (err) return err; err = agent_configure (agent_ctx); return check_broken_pipe (err); } /* Try to connect to the agent via socket. Handle the server's initial greeting. This is used only once when SCute is loaded. Re-connection is done using ensure_agent_connection. */ gpg_error_t scute_agent_initialize (void) { gpg_error_t err = 0; if (agent_ctx) { DEBUG (DBG_CRIT, "GPG Agent connection already established"); return 0; } DEBUG (DBG_INFO, "Establishing connection to gpg-agent"); err = agent_connect (&agent_ctx); if (err) return err; err = agent_configure (agent_ctx); if (err) scute_agent_finalize (); return err; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. */ static char * unescape_status_string (const unsigned char *src) { char *buffer; char *dst; buffer = malloc (strlen (src) + 1); if (!buffer) return NULL; dst = buffer; while (*src) { if (*src == '%' && src[1] && src[2]) { src++; *dst = xtoi_2 (src); if (*dst == '\0') *dst = '\xff'; dst++; src += 2; } else if (*src == '+') { *(dst++) = ' '; src++; } else *(dst++) = *(src++); } *dst = 0; return buffer; } /* Take a 20 byte hexencoded string and put it into the provided 20 byte buffer FPR in binary format. Returns true if successful, and false otherwise. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *src; int cnt; /* Check for invalid or wrong length. */ for (src = hexstr, cnt = 0; hexdigitp (src); src++, cnt++) ; if ((*src && !spacep (src)) || (cnt != 40)) return 0; for (src = hexstr, cnt = 0; *src && !spacep (src); src += 2, cnt++) fpr[cnt] = xtoi_2 (src); return 1; } /* Return true if HEXSTR is a valid keygrip. */ static unsigned int hexgrip_valid_p (const char *hexstr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || n != 40) return 0; /* Bad keygrip */ else return 1; /* Valid. */ } /* Take the serial number from LINE and return it verbatim in a newly allocated string. We make sure that only hex characters are returned. */ static char * store_serialno (const char *line) { const char *src; char *ptr; for (src = line; hexdigitp (src); src++) ; ptr = malloc (src + 1 - line); if (ptr) { memcpy (ptr, line, src - line); ptr[src - line] = 0; } return ptr; } /* Release the card info structure INFO. */ void scute_agent_release_card_info (struct agent_card_info_s *info) { if (!info) return; free (info->serialno); free (info->dispserialno); free (info->cardtype); free (info->disp_name); free (info->disp_lang); free (info->pubkey_url); free (info->login_data); while (info->kinfo) { key_info_t ki = info->kinfo->next; free (info->kinfo); info->kinfo = ki; } memset (info, 0, sizeof (*info)); } /* Return the key info object for the key KEYREF. If it is not found * NULL is returned. */ key_info_t scute_find_kinfo (agent_card_info_t info, const char *keyref) { key_info_t kinfo; for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) if (!strcmp (kinfo->keyref, keyref)) return kinfo; return NULL; } /* Create a new key info object with KEYREF. All fields but the * keyref are zeroed out. The created object is appended to the list * at INFO. */ static key_info_t create_kinfo (agent_card_info_t info, const char *keyref) { key_info_t kinfo, ki; kinfo = calloc (1, sizeof *kinfo + strlen (keyref)); if (!kinfo) return NULL; strcpy (kinfo->keyref, keyref); if (!info->kinfo) info->kinfo = kinfo; else { for (ki=info->kinfo; ki->next; ki = ki->next) ; ki->next = kinfo; } return kinfo; } /* FIXME: We are not returning out of memory errors. */ static gpg_error_t learn_status_cb (void *opaque, const char *line) { agent_card_info_t parm = opaque; const char *keyword = line; int keywordlen; key_info_t kinfo; const char *keyref; int i; for (keywordlen = 0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { free (parm->serialno); parm->serialno = store_serialno (line); } else if (keywordlen == 13 && !memcmp (keyword, "$DISPSERIALNO", keywordlen)) { free (parm->dispserialno); parm->dispserialno = unescape_status_string (line); } else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) { parm->is_piv = !strcmp (line, "piv"); + parm->is_opgp = !strcmp (line, "openpgp"); } else if (keywordlen == 8 && !memcmp (keyword, "CARDTYPE", keywordlen)) { free (parm->cardtype); parm->cardtype = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { if (parm->disp_name) free (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) { if (parm->disp_lang) free (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) { if (parm->pubkey_url) free (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) { if (parm->login_data) free (parm->login_data); parm->login_data = unescape_status_string (line); } else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) { while (spacep (p)) p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; for (i = 0; *p && i < 3; i++) { parm->chvmaxlen[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } for (i=0; *p && i < 3; i++) { parm->chvretry[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } free (buf); } } else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1valid = unhexify_fpr (line, parm->fpr1); else if (no == 2) parm->fpr2valid = unhexify_fpr (line, parm->fpr2); else if (no == 3) parm->fpr3valid = unhexify_fpr (line, parm->fpr3); } else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1); else if (no == 2) parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2); else if (no == 3) parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: * KEYPAIRINFO [] */ const char *hexgrip = line; char *line_buffer, *p; const char *usage; while (*line && !spacep (line)) line++; while (spacep (line)) line++; p = line_buffer = strdup (line); if (!line_buffer) goto no_core; keyref = line_buffer; while (*p && !spacep (p)) p++; if (*p) { *p++ = 0; while (spacep (p)) p++; usage = p; while (*p && !spacep (p)) p++; *p = 0; } else usage = ""; if (hexgrip_valid_p (hexgrip)) { /* Check whether we already have an item for the keyref. */ kinfo = scute_find_kinfo (parm, keyref); if (!kinfo) /* New entry. */ { kinfo = create_kinfo (parm, keyref); if (!kinfo) goto no_core; } else /* Existing entry - clear grip and usage. */ { *kinfo->grip = 0; memset (&kinfo->usage, 0, sizeof kinfo->usage); } strncpy (kinfo->grip, hexgrip, sizeof kinfo->grip); kinfo->grip[sizeof kinfo->grip -1] = 0; for (; *usage; usage++) { switch (*usage) { case 's': kinfo->usage.sign = 1; break; case 'c': kinfo->usage.cert = 1; break; case 'a': kinfo->usage.auth = 1; break; case 'e': kinfo->usage.encr = 1; break; } } } free (line_buffer); } else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen)) { char *p, *p2, *buf; int abool; buf = p = unescape_status_string (line); if (buf) { for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) { p2 = strchr (p, '='); if (p2) { *p2++ = 0; abool = (*p2 == '1'); if (!strcmp (p, "gc")) parm->rng_available = abool; /* We're currently not interested in the * other capabilities. */ } } free (buf); } } return 0; no_core: return gpg_error_from_syserror (); } /* Call the agent to learn about a smartcard. */ gpg_error_t scute_agent_learn (struct agent_card_info_s *info) { gpg_error_t err; + int has_opt_all = 0; + memset (info, 0, sizeof (*info)); err = ensure_agent_connection (); + if (!err && !agent_simple_cmd (agent_ctx, + "SCD GETINFO cmd_has_option SERIALNO all")) + has_opt_all = 1; /* SERIALNO --all and LEARN --multi is okay. */ + if (!err) - err = assuan_transact (agent_ctx, "SCD LEARN --force", - NULL, NULL, - default_inq_cb, NULL, - learn_status_cb, info); + { + /* First do a serialno to reset the card-removed-flag and also + * to make sure that additional applications are enabled. We do + * not check the error here as we catch that after the LEARN. */ + agent_simple_cmd (agent_ctx, (has_opt_all? "SCD SERIALNO --all" + /* */: "SCD SERIALNO" )); + err = assuan_transact (agent_ctx, + (has_opt_all? "SCD LEARN --force --multi" + /* */: "SCD LEARN --force"), + NULL, NULL, + default_inq_cb, NULL, + learn_status_cb, info); + } + if (gpg_err_source(err) == GPG_ERR_SOURCE_SCD && gpg_err_code (err) == GPG_ERR_CARD_REMOVED) { - /* SCD session is in card removed state. clear that state. */ + /* SCD session is in card removed state. clear that state. + * That should have been cleared by the initial SERIALNO but + * other processes may race with that. */ err = assuan_transact (agent_ctx, "SCD SERIALNO", NULL, NULL, NULL, NULL, NULL, NULL); if (!err) { memset (info, 0, sizeof (*info)); - err = assuan_transact (agent_ctx, "SCD LEARN --force", + err = assuan_transact (agent_ctx, + (has_opt_all? "SCD LEARN --force --multi" + /* */: "SCD LEARN --force"), NULL, NULL, default_inq_cb, NULL, learn_status_cb, info); } } if (!err) { /* Also try to get the human readable serial number. */ err = assuan_transact (agent_ctx, "SCD GETATTR $DISPSERIALNO", NULL, NULL, default_inq_cb, NULL, learn_status_cb, info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ } return check_broken_pipe (err); } static gpg_error_t geteventcounter_status_cb (void *opaque, const char *line) { int *result = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 12 && !memcmp (keyword, "EVENTCOUNTER", keywordlen)) { static int any_count; static unsigned int last_count; unsigned int count; if (sscanf (line, "%*u %*u %u ", &count) == 1) { if (any_count && last_count != count) *result = 1; any_count = 1; last_count = count; } } return 0; } static gpg_error_t read_status_cb (void *opaque, const void *buffer, size_t length) { char *flag = opaque; if (length == 0) *flag = 'r'; else *flag = *((char *) buffer); return 0; } /* Check the agent status. This returns 0 if a token is present, GPG_ERR_CARD_REMOVED if no token is present, and an error code otherwise. */ gpg_error_t scute_agent_check_status (void) { static char last_flag; gpg_error_t err; int any = 0; char flag = '-'; err = ensure_agent_connection (); if (err) return err; /* First we look at the eventcounter to see if anything happened at all. This is a low overhead function which won't even clutter a gpg-agent log file. There is no need for error checking here. */ if (last_flag) { err = assuan_transact (agent_ctx, "GETEVENTCOUNTER", NULL, NULL, NULL, NULL, geteventcounter_status_cb, &any); check_broken_pipe (err); } if (any || !last_flag) { err = assuan_transact (agent_ctx, "SCD GETINFO status", read_status_cb, &flag, default_inq_cb, NULL, NULL, NULL); err = check_broken_pipe (err); if (err) return err; last_flag = flag; } else flag = last_flag; if (flag == 'r') return gpg_error (GPG_ERR_CARD_REMOVED); return 0; } /* We only support RSA signatures up to 4096 bits. */ #define MAX_SIGNATURE_BITS 4096 /* Enough space to hold a 4096 bit RSA signature in an S-expression. */ #define MAX_SIGNATURE_LEN 640 /* FIXME: magic value */ struct signature { unsigned char data[MAX_SIGNATURE_LEN]; int len; }; static gpg_error_t pksign_cb (void *opaque, const void *buffer, size_t length) { struct signature *sig = opaque; if (sig->len + length > MAX_SIGNATURE_LEN) { DEBUG (DBG_INFO, "maximum signature length exceeded"); return gpg_error (GPG_ERR_BAD_DATA); } memcpy (&sig->data[sig->len], buffer, length); sig->len += length; return 0; } /* Parse the result of an pksign operation which is a s-expression in canonical form that looks like (7:sig-val(3:rsa(1:s:))). The raw result is stored in RESULT of size *LEN, and *LEN is adjusted to the actual size. */ static gpg_error_t pksign_parse_result (const struct signature *sig, unsigned char *result, unsigned int *len) { gpg_error_t err; const unsigned char *s = sig->data; size_t n; int depth; if (*s++ != '(') gpg_error (GPG_ERR_INV_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); if (! smatch (&s, n, "sig-val")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s++ != '(') gpg_error (GPG_ERR_UNKNOWN_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); if (! smatch (&s, n, "rsa")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s++ != '(') gpg_error (GPG_ERR_UNKNOWN_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); if (! smatch (&s, n, "s")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); /* Remove a possible prepended zero byte. */ if (!*s && n > 1) { n -= 1; s += 1; } if (*len < (unsigned int) n) return gpg_error (GPG_ERR_INV_LENGTH); *len = (unsigned int) n; memcpy (result, s, n); s += n; depth = 3; err = sskip (&s, &depth); if (err) return err; if (s - sig->data != sig->len || depth != 0) return gpg_error (GPG_ERR_INV_SEXP); return 0; } /* Decodes the hash DATA of size LEN (if necessary). Returns a pointer to the raw hash data in R_DATA, the size in R_LEN, and the name of the hash function in R_HASH. Prior to TLSv1.2, the hash function was the concatenation of MD5 and SHA1 applied to the data respectively, and no encoding was applied. From TLSv1.2 on, the hash value is prefixed with an hash identifier and encoded using ASN1. FIXME: Reference. */ static gpg_error_t decode_hash (const unsigned char *data, int len, const unsigned char **r_data, size_t *r_len, const char **r_hash) { static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C }; static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 }; static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 }; #define HANDLE(hash,hashlen) \ if (len == sizeof hash ## _prefix + (hashlen) \ && !memcmp (data, hash ## _prefix, sizeof hash ## _prefix)) \ { \ *r_data = data + sizeof hash ## _prefix; \ *r_len = hashlen; \ *r_hash = #hash; \ } if (len == 36) { /* Prior to TLSv1.2, a combination of MD5 and SHA1 was used. */ *r_data = data; *r_len = 36; *r_hash = "tls-md5sha1"; } /* TLSv1.2 encodes the hash value using ASN1. */ else HANDLE (sha1, 20) else HANDLE (rmd160, 20) else HANDLE (sha224, 28) else HANDLE (sha256, 32) else HANDLE (sha384, 48) else HANDLE (sha512, 64) else return gpg_error (GPG_ERR_INV_ARG); #undef HANDLE return 0; } /* Call the agent to sign (DATA,LEN) using the key described by * HEXGRIP. Stores the signature in SIG_RESULT and its length at * SIG_LEN; SIGLEN must initially point to the allocated size of * SIG_RESULT. */ gpg_error_t scute_agent_sign (const char *hexgrip, unsigned char *data, int len, unsigned char *sig_result, unsigned int *sig_len) { char cmd[150]; gpg_error_t err; const char *hash; const unsigned char *raw_data; size_t raw_len; #define MAX_DATA_LEN 64 /* Size of an SHA512 sum. */ unsigned char pretty_data[2 * MAX_DATA_LEN + 1]; int i; struct signature sig; sig.len = 0; if (sig_len == NULL) return gpg_error (GPG_ERR_INV_ARG); err = decode_hash (data, len, &raw_data, &raw_len, &hash); if (err) return err; if (sig_result == NULL) { *sig_len = raw_len; return 0; } if (!hexgrip || !sig_result) return gpg_error (GPG_ERR_INV_ARG); snprintf (cmd, sizeof (cmd), "SIGKEY %s", hexgrip); err = ensure_agent_connection (); if (err) return err; err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); err = check_broken_pipe (err); if (err) return err; for (i = 0; i < raw_len; i++) snprintf (&pretty_data[2 * i], 3, "%02X", raw_data[i]); pretty_data[2 * raw_len] = '\0'; snprintf (cmd, sizeof (cmd), "SETHASH --hash=%s %s", hash, pretty_data); err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); err = check_broken_pipe (err); if (err) return err; err = assuan_transact (agent_ctx, "PKSIGN", pksign_cb, &sig, default_inq_cb, NULL, NULL, NULL); err = check_broken_pipe (err); if (err) return err; err = pksign_parse_result (&sig, sig_result, sig_len); return err; } struct pkdecrypt_parm_s { unsigned int len; unsigned char data[512]; assuan_context_t ctx; const unsigned char *ciphertext; size_t ciphertextlen; }; static gpg_error_t pkdecrypt_data_cb (void *opaque, const void *buffer, size_t length) { struct pkdecrypt_parm_s *parm = opaque; if (parm->len + length > sizeof parm->data) { DEBUG (DBG_INFO, "maximum decryption result length exceeded"); return gpg_error (GPG_ERR_BAD_DATA); } memcpy (parm->data + parm->len, buffer, length); parm->len += length; return 0; } /* Handle the inquiries from pkdecrypt. Note, we only send the data, * assuan_transact takes care of flushing and writing the "END". */ static gpg_error_t pkdecrypt_inq_cb (void *opaque, const char *line) { struct pkdecrypt_parm_s *parm = opaque; gpg_error_t err; const char *keyword = line; int keywordlen; for (keywordlen = 0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 10 && !memcmp (keyword, "CIPHERTEXT", 10)) err = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); else err = default_inq_cb (NULL, line); return err; } /* Parse the result of a pkdecrypt operation which is an s-expression * in canonical form that looks like * (5:value:). * * The raw result is stored in RESULT which has a size of *R_LEN, and * *R_LEN is adjusted to the actual size. */ static gpg_error_t pkdecrypt_parse_result (struct pkdecrypt_parm_s *ctx, unsigned char *result, unsigned int *r_len) { char *buf = ctx->data; size_t len = ctx->len; char *endp, *raw; size_t n, rawlen; if (len < 13 || memcmp (buf, "(5:value", 8) ) return gpg_error (GPG_ERR_INV_SEXP); len -= 8; buf += 8; n = strtoul (buf, &endp, 10); if (!n || *endp != ':') return gpg_error (GPG_ERR_INV_SEXP); endp++; if ((endp-buf)+n > len) return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ /* Let (RAW,RAWLEN) describe the pkcs#1 block and remove that padding. */ raw = endp; rawlen = n; if (rawlen < 10) /* 0x00 + 0x02 + <1_random> + 0x00 + <16-session> */ return gpg_error (GPG_ERR_INV_SESSION_KEY); if (raw[0] || raw[1] != 2 ) /* Wrong block type version. */ return gpg_error (GPG_ERR_INV_SESSION_KEY); for (n=2; n < rawlen && raw[n]; n++) /* Skip the random bytes. */ ; if (n+1 >= rawlen || raw[n] ) return gpg_error (GPG_ERR_INV_SESSION_KEY); n++; /* Skip the zero byte */ if (*r_len < (rawlen - n)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (result, raw + n, rawlen - n); *r_len = rawlen - n; return 0; } /* Call the agent to decrypt (ENCDATA,ENCDATALEN) using the key * described by HEXGRIP. Stores the plaintext at R_PLAINDATA and its * length at R_PLAINDATALEN; R_PLAINDATALEN must initially point to * the allocated size of R_PLAINDATA and is updated to the actual used * size on return. */ gpg_error_t scute_agent_decrypt (const char *hexgrip, unsigned char *encdata, int encdatalen, unsigned char *r_plaindata, unsigned int *r_plaindatalen) { char cmd[150]; gpg_error_t err; struct pkdecrypt_parm_s pkdecrypt; char *s_data; size_t s_datalen; if (!hexgrip || !encdata || !encdatalen || !r_plaindatalen) return gpg_error (GPG_ERR_INV_ARG); if (!r_plaindata) { /* Fixme: We do not return the minimal required length but our * internal buffer size. */ pkdecrypt.len = *r_plaindatalen; *r_plaindatalen = sizeof pkdecrypt.data - 1; if (pkdecrypt.len > sizeof pkdecrypt.data - 1) return gpg_error (GPG_ERR_INV_LENGTH); return 0; } err = ensure_agent_connection (); if (err) return err; snprintf (cmd, sizeof (cmd), "SETKEY %s", hexgrip); err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); err = check_broken_pipe (err); if (err) return err; /* Convert the input into an appropriate s-expression as expected by * gpg-agent which is: * * (enc-val * (flags pkcs1) * (rsa * (a VALUE))) * * Out of convenience we append a non-counted extra nul to the * created canonical s-expression. */ s_data = malloc (100 + encdatalen); if (!s_data) return gpg_error_from_syserror (); snprintf (s_data, 50, "(7:enc-val(5:flags5:pkcs1)(3:rsa(1:a%d:", encdatalen); s_datalen = strlen (s_data); memcpy (s_data + s_datalen, encdata, encdatalen); s_datalen += encdatalen; memcpy (s_data + s_datalen, ")))", 4); s_datalen += 3; pkdecrypt.len = 0; pkdecrypt.ctx = agent_ctx; pkdecrypt.ciphertext = s_data; pkdecrypt.ciphertextlen = s_datalen; err = assuan_transact (agent_ctx, "PKDECRYPT", pkdecrypt_data_cb, &pkdecrypt, pkdecrypt_inq_cb, &pkdecrypt, NULL, NULL); err = check_broken_pipe (err); if (!err) err = pkdecrypt_parse_result (&pkdecrypt, r_plaindata, r_plaindatalen); free (s_data); return err; } /* Determine if FPR is trusted. */ gpg_error_t scute_agent_is_trusted (const char *fpr, bool *is_trusted) { gpg_error_t err; bool trusted = false; char cmd[150]; err = ensure_agent_connection (); if (err) return err; snprintf (cmd, sizeof (cmd), "ISTRUSTED %s", fpr); err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); err = check_broken_pipe (err); if (err && gpg_err_code (err) != GPG_ERR_NOT_TRUSTED) return err; else if (!err) trusted = true; *is_trusted = trusted; return 0; } #define GET_CERT_INIT_SIZE 2048 struct get_cert_s { unsigned char *cert_der; int cert_der_len; int cert_der_size; }; gpg_error_t get_cert_data_cb (void *opaque, const void *data, size_t data_len) { struct get_cert_s *cert_s = opaque; int needed_size; needed_size = cert_s->cert_der_len + data_len; if (needed_size > cert_s->cert_der_size) { unsigned char *new_cert_der; int new_cert_der_size = cert_s->cert_der_size; if (new_cert_der_size == 0) new_cert_der_size = GET_CERT_INIT_SIZE; while (new_cert_der_size < needed_size) new_cert_der_size *= 2; if (cert_s->cert_der == NULL) new_cert_der = malloc (new_cert_der_size); else new_cert_der = realloc (cert_s->cert_der, new_cert_der_size); if (new_cert_der == NULL) return gpg_error_from_syserror (); cert_s->cert_der = new_cert_der; cert_s->cert_der_size = new_cert_der_size; } memcpy (cert_s->cert_der + cert_s->cert_der_len, data, data_len); cert_s->cert_der_len += data_len; return 0; } /* Try to get certificate for CERTREF. */ gpg_error_t scute_agent_get_cert (const char *certref, struct cert *cert) { gpg_error_t err; char cmd[150]; struct get_cert_s cert_s; cert_s.cert_der = NULL; cert_s.cert_der_len = 0; cert_s.cert_der_size = 0; err = ensure_agent_connection (); if (err) return err; snprintf (cmd, sizeof (cmd), "SCD READCERT %s", certref); err = assuan_transact (agent_ctx, cmd, get_cert_data_cb, &cert_s, NULL, NULL, NULL, NULL); err = check_broken_pipe (err); /* Just to be safe... */ if (!err && (cert_s.cert_der_len <= 16 || cert_s.cert_der[0] != 0x30)) { DEBUG (DBG_INFO, "bad card certificate rejected"); err = gpg_error (GPG_ERR_BAD_CERT); } if (err) { if (cert_s.cert_der) free (cert_s.cert_der); return err; } DEBUG (DBG_INFO, "got certificate from card with length %i", cert_s.cert_der_len); cert->cert_der = cert_s.cert_der; cert->cert_der_len = cert_s.cert_der_len; strncpy (cert->certref, certref, sizeof cert->certref -1); cert->certref[sizeof cert->certref - 1] = 0; return 0; } struct random_request { unsigned char *buffer; size_t len; }; gpg_error_t get_challenge_data_cb (void *opaque, const void *line, size_t len) { struct random_request *request = opaque; if (len != request->len) return gpg_error (GPG_ERR_INV_LENGTH); memcpy (request->buffer, line, len); return 0; } gpg_error_t scute_agent_get_random (unsigned char *data, size_t len) { char command[16]; gpg_error_t err; struct random_request request; err = ensure_agent_connection (); if (err) return err; snprintf (command, sizeof(command), "SCD RANDOM %zu", len); request.buffer = data; request.len = len; err = assuan_transact (agent_ctx, command, get_challenge_data_cb, &request, NULL, NULL, NULL, NULL); err = check_broken_pipe (err); return err; } void scute_agent_finalize (void) { if (!agent_ctx) { DEBUG (DBG_CRIT, "no GPG Agent connection established"); return; } DEBUG (DBG_INFO, "releasing agent context"); assuan_release (agent_ctx); agent_ctx = NULL; } diff --git a/src/agent.h b/src/agent.h index 710f14c..26875cb 100644 --- a/src/agent.h +++ b/src/agent.h @@ -1,126 +1,129 @@ /* agent.h - Interface for talking to gpg-agent. Copyright (C) 2006, 2007 g10 Code GmbH This file is part of Scute. Scute is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Scute 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 Scute; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, g10 Code GmbH gives permission to link this library: with the Mozilla Foundation's code for Mozilla (or with modified versions of it that use the same license as the "Mozilla" code), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "Mozilla". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef AGENT_H #define AGENT_H 1 #include #include #include "cert.h" /* The information structure for a smart card. */ struct agent_card_info_s { char *serialno; /* Malloced hex string. */ char *dispserialno; /* NULL or malloced human readable S/N. */ char *cardtype; /* Null or malloced string with the card type. */ char *disp_name; /* Malloced. */ char *disp_lang; /* Malloced. */ int disp_sex; /* 0 = unspecified, 1 = male, 2 = female. */ char *pubkey_url; /* Malloced. */ char *login_data; /* Malloced. */ char *private_do[4]; /* Malloced. */ char cafpr1valid; char cafpr2valid; char cafpr3valid; char cafpr1[20]; char cafpr2[20]; char cafpr3[20]; key_info_t kinfo; /* Linked list with all keypair related data. */ char fpr1valid; /* Duplicated info for the legacy parts of the code. */ char fpr2valid; char fpr3valid; char fpr1[20]; char fpr2[20]; char fpr3[20]; unsigned int fpr1time; unsigned int fpr2time; unsigned int fpr3time; unsigned long sig_counter; int chv1_cached; /* True if a PIN is not required for each signing. Note that the gpg-agent might cache it anyway. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ int rng_available; /* True if the GET CHALLENGE operation is supported. */ - int is_piv; /* True if this is a PIV card. */ + int is_piv; /* True if this is a PIV card or has PIV as an + * additional application. */ + int is_opgp; /* True if this is a OpenPGP card or has + * OpenPGP as an additional application. */ }; typedef struct agent_card_info_s *agent_card_info_t; /* Try to connect to the agent via socket. Handle the server's initial greeting. */ gpg_error_t scute_agent_initialize (void); /* Tear down the agent connection and release all associated resources. */ void scute_agent_finalize (void); /* Check the agent status. This returns 0 if a token is present, GPG_ERR_CARD_REMOVED if no token is present, and an error code otherwise. */ gpg_error_t scute_agent_check_status (void); /* Call the agent to learn about a smartcard. */ gpg_error_t scute_agent_learn (struct agent_card_info_s *info); /* Release the card info structure INFO. */ void scute_agent_release_card_info (struct agent_card_info_s *info); key_info_t scute_find_kinfo (agent_card_info_t info, const char *keyref); /* Sign the data DATA of length LEN with the key HEXGRIP and return * the signature in SIG_RESULT and SIG_LEN. */ gpg_error_t scute_agent_sign (const char *hexgrip, unsigned char *data, int len, unsigned char *sig_result, unsigned int *sig_len); /* Decrypt data. */ gpg_error_t scute_agent_decrypt (const char *hexgrip, unsigned char *encdata, int encdatalen, unsigned char *r_plaindata, unsigned int *r_plaindatalen); /* Determine if FPR is trusted. */ gpg_error_t scute_agent_is_trusted (const char *fpr, bool *is_trusted); /* Try to get certificate for key numer NO. */ gpg_error_t scute_agent_get_cert (const char *certref, struct cert *cert); /* Get random bytes from the card. */ gpg_error_t scute_agent_get_random (unsigned char *data, size_t len); #endif /* AGENT_H */ diff --git a/src/slots.c b/src/slots.c index 5ac8486..abf7195 100644 --- a/src/slots.c +++ b/src/slots.c @@ -1,1240 +1,1250 @@ /* slots.c - Slot management. * Copyright (C) 2006, 2019 g10 Code GmbH * * This file is part of Scute. * * Scute 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. * * Scute 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "cryptoki.h" #include "table.h" #include "error-mapping.h" #include "slots.h" #include "agent.h" #include "support.h" #include "gpgsm.h" #include "debug.h" /* A session is just a slot identifier with a per-slot session identifier. */ /* Must be power of two. */ #define SLOT_MAX (1 << 15) #define SESSION_SLOT_MASK (SLOT_MAX - 1) #define SESSION_SLOT_SHIFT 16 #define SESSION_MAX (1 << SESSION_SLOT_SHIFT) #define SESSION_ID_MASK (SESSION_MAX - 1) /* Get slot ID from session. */ #define SESSION_SLOT(session) \ ((session >> SESSION_SLOT_SHIFT) & SESSION_SLOT_MASK) /* Get session ID from session. */ #define SESSION_ID(session) (session & SESSION_ID_MASK) /* Because the slot is already 1-based, we can make the session 0-based. */ #define SESSION_BUILD_ID(slot, session) \ (((slot & SESSION_SLOT_MASK) << SESSION_SLOT_SHIFT) \ | (session & SESSION_ID_MASK)) /* We use one-based IDs. */ #define OBJECT_ID_TO_IDX(id) (id - 1) #define OBJECT_IDX_TO_ID(idx) (idx + 1) struct object { CK_ATTRIBUTE_PTR attributes; CK_ULONG attributes_count; }; /* A mechanism. */ struct mechanism { CK_MECHANISM_TYPE type; CK_MECHANISM_INFO info; }; /* We use one-based IDs. */ #define MECHANISM_ID_TO_IDX(id) (id - 1) #define MECHANISM_IDX_TO_ID(idx) (idx + 1) /* The session state. */ struct session { /* True iff read-write session. */ bool rw; /* The list of objects for the current search. */ object_iterator_t *search_result; /* The length of the list of objects for the current search. */ int search_result_len; /* The signing key. */ CK_OBJECT_HANDLE signing_key; /* The decryption key. */ CK_OBJECT_HANDLE decryption_key; }; /* The slot status. */ typedef enum { SLOT_STATUS_USED = 0, SLOT_STATUS_DEAD = 1 } slot_status_t; struct slot { /* The slot status. Starts out as 0 (pristine). */ slot_status_t status; /* The slot login status. Starts out as 0 (public). */ slot_login_t login; /* True iff a token is present. */ bool token_present; /* The supported mechanisms. */ scute_table_t mechanisms; /* The sessions. */ scute_table_t sessions; /* The objects on the token. */ scute_table_t objects; /* The info about the current token. */ struct agent_card_info_s info; }; /* The slot table. */ /* FIXME: That symbol name is pretty short for a global. */ static scute_table_t slots; /* Deallocator for mechanisms. */ static void mechanism_dealloc (void *data) { free (data); } /* Allocator for mechanisms. The hook must be a pointer to a CK_FLAGS that should be a combination of CKF_SIGN and/or CKF_DECRYPT. */ static gpg_error_t mechanism_alloc (void **data_r, void *hook) { struct mechanism *mechanism; CK_FLAGS *flags = hook; mechanism = calloc (1, sizeof (*mechanism)); if (mechanism == NULL) return gpg_error_from_syserror (); /* Set some default values. */ mechanism->type = CKM_RSA_PKCS; mechanism->info.ulMinKeySize = 1024; mechanism->info.ulMaxKeySize = 4096; mechanism->info.flags = CKF_HW | (*flags); *data_r = mechanism; return 0; } static void object_dealloc (void *data) { struct object *obj = data; while (0 < obj->attributes_count--) free (obj->attributes[obj->attributes_count].pValue); free (obj->attributes); free (obj); } /* Allocator for objects. The hook is currently unused. */ static gpg_error_t object_alloc (void **data_r, void *hook) { struct object *object; (void) hook; object = calloc (1, sizeof (*object)); if (object == NULL) return gpg_error_from_syserror (); *data_r = object; return 0; } static void session_dealloc (void *data) { struct session *session = data; if (session->search_result) free (session->search_result); free (session); } /* Allocator for sessions. The hook is currently unused. */ static gpg_error_t session_alloc (void **data_r, void *hook) { struct session *session; (void) hook; session = calloc (1, sizeof (*session)); if (session == NULL) return gpg_error_from_syserror (); *data_r = session; return 0; } /* Deallocator for slots. */ static void slot_dealloc (void *data) { struct slot *slot = data; scute_table_destroy (slot->sessions); scute_table_destroy (slot->mechanisms); scute_table_destroy (slot->objects); free (slot); } /* Allocator for slots. The hook does not indicate anything at this point. */ static gpg_error_t slot_alloc (void **data_r, void *hook) { gpg_error_t err; struct slot *slot; int idx; CK_FLAGS flags; (void) hook; slot = calloc (1, sizeof (*slot)); if (slot == NULL) return gpg_error_from_syserror (); err = scute_table_create (&slot->mechanisms, mechanism_alloc, mechanism_dealloc); if (err) goto slot_alloc_out; /* Register the signing mechanism. */ flags = CKF_SIGN; err = scute_table_alloc (slot->mechanisms, &idx, NULL, &flags); if (err) goto slot_alloc_out; err = scute_table_create (&slot->sessions, session_alloc, session_dealloc); if (err) goto slot_alloc_out; err = scute_table_create (&slot->objects, object_alloc, object_dealloc); if (err) goto slot_alloc_out; slot->status = SLOT_STATUS_USED; slot->token_present = false; slot->login = SLOT_LOGIN_PUBLIC; *data_r = slot; slot_alloc_out: if (err) slot_dealloc (slot); return err; } /* Initialize the slot list. */ CK_RV scute_slots_initialize (void) { gpg_error_t err; int slot_idx; err = scute_table_create (&slots, slot_alloc, slot_dealloc); if (err) return err; /* Allocate a new slot for authentication. */ err = scute_table_alloc (slots, &slot_idx, NULL, NULL); if (err) scute_slots_finalize (); /* FIXME: Allocate a new slot for signing and decryption of email. */ return scute_gpg_err_to_ck (err); } void scute_slots_finalize (void) { if (slots == NULL) return; /* This recursively releases all slots and any objects associated with them. */ scute_table_destroy (slots); slots = NULL; } /* Reset the slot SLOT after the token has been removed. */ static void slot_reset (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); int oid; /* This also resets the login state. */ slot_close_all_sessions (id); oid = scute_table_first (slot->objects); while (!scute_table_last (slot->objects, oid)) scute_table_dealloc (slot->objects, &oid); assert (scute_table_used (slot->objects) == 0); scute_agent_release_card_info (&slot->info); slot->token_present = false; } static gpg_error_t add_object (void *hook, CK_ATTRIBUTE_PTR attrp, CK_ULONG attr_countp) { gpg_error_t err; struct slot *slot = hook; struct object *object; unsigned int oidx; void *objp; err = scute_table_alloc (slot->objects, &oidx, &objp, NULL); if (err) return err; object = objp; object->attributes = attrp; object->attributes_count = attr_countp; return 0; } /* Initialize the slot after a token has been inserted. SLOT->info must already be valid. */ static gpg_error_t slot_init (slot_iterator_t id) { gpg_error_t err = 0; struct slot *slot = scute_table_data (slots, id); key_info_t ki; for (ki = slot->info.kinfo; ki; ki = ki->next) { err = scute_gpgsm_get_cert (ki, add_object, slot); if (err) goto leave; } /* FIXME: Perform the rest of the initialization of the token. */ slot->token_present = true; leave: if (err) slot_reset (id); return err; } /* Update the slot SLOT. */ CK_RV slots_update_slot (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); gpg_error_t err; if (slot->token_present) { err = scute_agent_check_status (); if (gpg_err_code (err) == GPG_ERR_CARD_REMOVED) slot_reset (id); else if (err) return scute_gpg_err_to_ck (err); else return 0; } /* At this point, the card was or is removed, and we need to reopen the session, if possible. */ err = scute_agent_learn (&slot->info); /* First check if this is really a PIV or an OpenPGP card. FIXME: * Should probably report the error in a better way and use a * generic way to identify cards without resorting to special-casing * PIV cards. */ - if (!err && slot->info.is_piv) - ; /* Okay, this is a PIV card. */ - else if (!err && (!slot->info.serialno - || strncmp (slot->info.serialno, "D27600012401", 12) - || strlen (slot->info.serialno) != 32)) + if (!err && (slot->info.is_piv || slot->info.is_opgp)) + ; /* Okay, this card has a usable application. */ + else if (!err && (slot->info.serialno + && !strncmp (slot->info.serialno, "D27600012401", 12) + && strlen (slot->info.serialno) == 32)) { - DEBUG (DBG_INFO, "token not an OpenPGP card: %s", slot->info.serialno); + /* Kludge to allow for old GnuPG versions. */ + slot->info.is_opgp = 1; + } + else + { + DEBUG (DBG_INFO, "token not a PIV or OpenPGP card: %s", + slot->info.serialno); err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); scute_agent_release_card_info (&slot->info); } /* We also ignore card errors, because unusable cards should not affect slots, and firefox is quite unhappy about returning errors here. */ if (gpg_err_code (err) == GPG_ERR_CARD_REMOVED || gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT || gpg_err_code (err) == GPG_ERR_CARD || gpg_err_code (err) == GPG_ERR_ENODEV) /* Nothing to do. */ err = 0; else if (err == 0) err = slot_init (id); return scute_sys_to_ck (err); } /* Update the slot list by finding new devices. Please note that Mozilla NSS currently assumes that the slot list never shrinks (see TODO file for a discussion). This is the only function allowed to manipulate the slot list. */ CK_RV slots_update (void) { slot_iterator_t id = scute_table_first (slots); while (!scute_table_last (slots, id)) { CK_RV err; err = slots_update_slot (id); if (err) return err; id = scute_table_next (slots, id); } return CKR_OK; } /* Begin iterating over the list of slots. */ CK_RV slots_iterate_first (slot_iterator_t *slot) { *slot = scute_table_first (slots); return CKR_OK; } /* Continue iterating over the list of slots. */ CK_RV slots_iterate_next (slot_iterator_t *slot) { *slot = scute_table_next (slots, *slot); return CKR_OK; } /* Return true iff the previous slot was the last one. */ bool slots_iterate_last (slot_iterator_t *slot) { return scute_table_last (slots, *slot); } /* Acquire the slot for the slot ID ID. */ CK_RV slots_lookup (CK_SLOT_ID id, slot_iterator_t *id_r) { struct slot *slot = scute_table_data (slots, id); if (slot == NULL) return CKR_SLOT_ID_INVALID; *id_r = id; return CKR_OK; } /* Return true iff a token is present in slot SLOT. */ bool slot_token_present (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); return slot->token_present; } /* Return the token label. We use the dispserialno here too because * Firefox prints that value in the prompt ("Stored at:"). */ const char * slot_token_label (slot_iterator_t id) { return slot_token_serial (id); } /* Get the manufacturer of the token. */ const char * slot_token_manufacturer (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); unsigned int uval; if (slot->info.is_piv) { if (slot->info.cardtype && !strcmp (slot->info.cardtype, "yubikey")) return "Yubikey"; return "Unknown"; } /* slots_update() makes sure this is valid. */ uval = xtoi_2 (slot->info.serialno + 16) * 256 + xtoi_2 (slot->info.serialno + 18); /* Note: Make sure that there is no colon or linefeed in the string. */ switch (uval) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid AB"; case 0x0005: return "ZeitControl"; case 0x002A: return "Magrathea"; case 0x0000: case 0xffff: return "test card"; default: return (uval & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; } /* Not reached. */ } /* Get the application used on the token. */ const char * slot_token_application (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); if (!slot) return "[ooops]"; /* slots_update() makes sure this is correct. */ - if (slot->info.is_piv) + if (slot->info.is_piv && slot->info.is_piv) + return "PIV+OpenPGP"; + else if (slot->info.is_piv) return "PIV"; else return "OpenPGP"; } /* Get the serial number of the token. */ const char * slot_token_serial (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); /* slots_update() makes sure this is valid. */ return slot->info.dispserialno? slot->info.dispserialno : slot->info.serialno; } /* Get the manufacturer of the token. */ void slot_token_version (slot_iterator_t id, CK_BYTE *hw_major, CK_BYTE *hw_minor, CK_BYTE *fw_major, CK_BYTE *fw_minor) { struct slot *slot = scute_table_data (slots, id); /* slots_update() makes sure serialno is valid. */ + /* Fixme: If we have PIV+OpenPGP we do not have the OpenPGP + * serialnumber thus we can't take its version number. */ if (slot->info.is_piv) { *hw_major = 0; *hw_minor = 0; *fw_major = 0; *fw_minor = 0; } else { *hw_major = xtoi_2 (slot->info.serialno + 12); *hw_minor = xtoi_2 (slot->info.serialno + 14); *fw_major = 0; *fw_minor = 0; } } /* Get the maximum and minimum pin length. */ void slot_token_maxpinlen (slot_iterator_t id, CK_ULONG *max, CK_ULONG *min) { struct slot *slot = scute_table_data (slots, id); /* In version 2 of the OpenPGP card, the second counter is for the reset operation, so we only take the first counter. */ *max = slot->info.chvmaxlen[0]; /* FIXME: This is true at least for the user pin (CHV1 and CHV2). */ *min = 6; } /* Get the maximum and the actual pin count. */ void slot_token_pincount (slot_iterator_t id, int *max, int *len) { struct slot *slot = scute_table_data (slots, id); *max = 3; /* In version 2 of the OpenPGP card, the second counter is for the reset operation, so we only take the first counter. */ *len = slot->info.chvretry[0]; } /* Return the ID of slot SLOT. */ CK_SLOT_ID slot_get_id (slot_iterator_t slot) { return slot; } /* Return true if the token supports the GET CHALLENGE operation. */ bool slot_token_has_rng (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); return slot->info.rng_available; } /* Mechanism management. */ /* Begin iterating over the list of mechanisms. */ CK_RV mechanisms_iterate_first (slot_iterator_t id, mechanism_iterator_t *mechanism) { struct slot *slot = scute_table_data (slots, id); *mechanism = scute_table_first (slot->mechanisms); return CKR_OK; } /* Continue iterating over the list of mechanisms. */ CK_RV mechanisms_iterate_next (slot_iterator_t id, mechanism_iterator_t *mechanism) { struct slot *slot = scute_table_data (slots, id); *mechanism = scute_table_next (slot->mechanisms, *mechanism); return CKR_OK; } /* Return true iff the previous slot was the last one. */ bool mechanisms_iterate_last (slot_iterator_t id, mechanism_iterator_t *mechanism) { struct slot *slot = scute_table_data (slots, id); return scute_table_last (slot->mechanisms, *mechanism); } /* Acquire the mechanism TYPE for the slot id ID. */ CK_RV mechanisms_lookup (slot_iterator_t id, mechanism_iterator_t *mid_r, CK_MECHANISM_TYPE type) { struct slot *slot = scute_table_data (slots, id); int mid = scute_table_first (slot->mechanisms); while (!scute_table_last (slot->mechanisms, mid)) { struct mechanism *mechanism = scute_table_data (slot->mechanisms, mid); if (mechanism->type == type) { *mid_r = mid; return CKR_OK; } mid = scute_table_next (slot->mechanisms, mid); } return CKR_MECHANISM_INVALID; } /* Return the type of mechanism MID in slot ID. */ CK_MECHANISM_TYPE mechanism_get_type (slot_iterator_t id, mechanism_iterator_t mid) { struct slot *slot = scute_table_data (slots, id); struct mechanism *mechanism = scute_table_data (slot->mechanisms, mid); return mechanism->type; } /* Return the info of mechanism MID. */ CK_MECHANISM_INFO_PTR mechanism_get_info (slot_iterator_t id, mechanism_iterator_t mid) { struct slot *slot = scute_table_data (slots, id); struct mechanism *mechanism = scute_table_data (slot->mechanisms, mid); return &mechanism->info; } /* Session management. */ /* Create a new session. */ CK_RV slot_create_session (slot_iterator_t id, session_iterator_t *session, bool rw) { int err; struct slot *slot = scute_table_data (slots, id); unsigned int tsid; void *rawp; struct session *session_p; assert (slot); if (scute_table_used (slot->sessions) == SESSION_MAX) return CKR_SESSION_COUNT; if (slot->login == SLOT_LOGIN_SO && !rw) return CKR_SESSION_READ_WRITE_SO_EXISTS; err = scute_table_alloc (slot->sessions, &tsid, &rawp, NULL); if (err) return scute_sys_to_ck (err); session_p = rawp; session_p->rw = rw; session_p->search_result = NULL; session_p->search_result_len = 0; session_p->signing_key = CK_INVALID_HANDLE; session_p->decryption_key = CK_INVALID_HANDLE; *session = SESSION_BUILD_ID (id, tsid); return CKR_OK; } /* Look up session. */ CK_RV slots_lookup_session (CK_SESSION_HANDLE sid, slot_iterator_t *id, session_iterator_t *session_id) { CK_RV err; unsigned int idx = SESSION_SLOT (sid); unsigned session_idx = SESSION_ID (sid); struct slot *slot; /* Verify the slot. */ err = slots_lookup (SESSION_SLOT (sid), id); if (err) return err; *session_id = session_idx; /* Verify the session. */ slot = scute_table_data (slots, idx); if (!scute_table_data (slot->sessions, session_idx)) return CKR_SESSION_HANDLE_INVALID; return 0; } /* Close the session. */ CK_RV slot_close_session (slot_iterator_t id, session_iterator_t sid) { struct slot *slot = scute_table_data (slots, id); scute_table_dealloc (slot->sessions, &sid); /* At last session closed, return to public sessions. */ if (!scute_table_used (slot->sessions)) slot->login = SLOT_LOGIN_PUBLIC; return CKR_OK; } /* Close all sessions. */ CK_RV slot_close_all_sessions (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); int sid = scute_table_first (slot->sessions); while (!scute_table_last (slot->sessions, sid)) { slot_close_session (id, sid); sid = scute_table_next (slot->sessions, sid); } assert (scute_table_used (slot->sessions) == 0); return CKR_OK; } /* Get the RW flag from the session SID in slot ID. */ bool session_get_rw (slot_iterator_t id, session_iterator_t sid) { struct slot *slot = scute_table_data (slots, id); struct session *session = scute_table_data (slot->sessions, sid); return session->rw; } /* Get the login state from the slot ID. */ slot_login_t slot_get_status (slot_iterator_t id) { struct slot *slot = scute_table_data (slots, id); return slot->status; } /* Object management. */ /* Begin iterating over the list of objects. */ CK_RV objects_iterate_first (slot_iterator_t id, object_iterator_t *object) { struct slot *slot = scute_table_data (slots, id); *object = scute_table_first (slot->objects); return CKR_OK; } /* Continue iterating over the list of objects. */ CK_RV objects_iterate_next (slot_iterator_t id, object_iterator_t *object) { struct slot *slot = scute_table_data (slots, id); *object = scute_table_next (slot->objects, *object); return CKR_OK; } /* Return true iff the previous slot was the last one. */ bool objects_iterate_last (slot_iterator_t id, object_iterator_t *object) { struct slot *slot = scute_table_data (slots, id); return scute_table_last (slot->objects, *object); } /* Return the max. number of objects in the slot. May overcount somewhat. */ CK_RV slot_get_object_count (slot_iterator_t id, int *nr) { struct slot *slot = scute_table_data (slots, id); *nr = scute_table_used (slot->objects); return CKR_OK; } /* Get the object information for object OBJECT_ID in slot ID. */ CK_RV slot_get_object (slot_iterator_t id, object_iterator_t oid, CK_ATTRIBUTE_PTR *obj, CK_ULONG *obj_count) { struct slot *slot = scute_table_data (slots, id); struct object *object = scute_table_data (slot->objects, oid); if (!object) return CKR_OBJECT_HANDLE_INVALID; *obj = object->attributes; *obj_count = object->attributes_count; return 0; } /* Set the result of a search for session SID in slot ID to SEARCH_RESULT and SEARCH_RESULT_LEN. */ CK_RV session_set_search_result (slot_iterator_t id, session_iterator_t sid, object_iterator_t *search_result, int search_result_len) { struct slot *slot = scute_table_data (slots, id); struct session *session = scute_table_data (slot->sessions, sid); if (session->search_result && session->search_result != search_result) free (session->search_result); session->search_result = search_result; session->search_result_len = search_result_len; return 0; } /* Get the stored search result for the session SID in slot ID. */ CK_RV session_get_search_result (slot_iterator_t id, session_iterator_t sid, object_iterator_t **search_result, int *search_result_len) { struct slot *slot = scute_table_data (slots, id); struct session *session = scute_table_data (slot->sessions, sid); assert (search_result); assert (search_result_len); *search_result = session->search_result; *search_result_len = session->search_result_len; return 0; } /* Set the signing key for session SID in slot ID to KEY. This is the * core of C_SignInit. */ CK_RV session_set_signing_key (slot_iterator_t id, session_iterator_t sid, object_iterator_t key) { struct slot *slot = scute_table_data (slots, id); struct session *session = scute_table_data (slot->sessions, sid); CK_RV err; CK_ATTRIBUTE_PTR attr; CK_ULONG attr_count; CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY; err = slot_get_object (id, key, &attr, &attr_count); if (err) return err; /* FIXME: What kind of strange loop is this? */ while (attr_count-- > 0) if (attr->type == CKA_CLASS) break; if (attr_count == (CK_ULONG) -1) return CKR_KEY_HANDLE_INVALID; if (attr->ulValueLen != sizeof (key_class) || memcmp (attr->pValue, &key_class, sizeof (key_class))) return CKR_KEY_HANDLE_INVALID; /* It's the private RSA key object. */ session->signing_key = key; return 0; } /* The core of C_Sign - see there for a description. */ CK_RV session_sign (slot_iterator_t id, session_iterator_t sid, CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) { struct slot *slot = scute_table_data (slots, id); struct session *session = scute_table_data (slot->sessions, sid); int rc; gpg_error_t err; CK_ATTRIBUTE_PTR attr; CK_ULONG attr_count; CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY; unsigned int sig_len; CK_BYTE key_id[100]; int i; const char *keyref; if (!session->signing_key) return CKR_OPERATION_NOT_INITIALIZED; rc = slot_get_object (id, session->signing_key, &attr, &attr_count); if (rc) goto leave; if (attr_count == (CK_ULONG) -1) { rc = CKR_KEY_HANDLE_INVALID; goto leave; } if (attr->ulValueLen != sizeof (key_class) || memcmp (attr->pValue, &key_class, sizeof (key_class))) { rc = CKR_KEY_HANDLE_INVALID; goto leave; } /* Find the CKA_ID */ for (i = 0; i < attr_count; i++) if (attr[i].type == CKA_ID) break; if (i == attr_count) { rc = CKR_GENERAL_ERROR; goto leave; } if (attr[i].ulValueLen >= sizeof key_id - 1) { rc = CKR_GENERAL_ERROR; goto leave; } strncpy (key_id, attr[i].pValue, attr[i].ulValueLen); key_id[attr[i].ulValueLen] = 0; DEBUG (DBG_INFO, "Found CKA_ID '%s'", key_id); for (keyref=key_id; *keyref && *keyref != ' '; keyref++) ; if (*keyref) keyref++; /* Point to the grip. */ sig_len = *pulSignatureLen; err = scute_agent_sign (keyref, pData, ulDataLen, pSignature, &sig_len); /* Take care of error codes which are not mapped by default. */ if (gpg_err_code (err) == GPG_ERR_INV_LENGTH) rc = CKR_BUFFER_TOO_SMALL; else if (gpg_err_code (err) == GPG_ERR_INV_ARG) rc = CKR_ARGUMENTS_BAD; else rc = scute_gpg_err_to_ck (err); /* Return the length. */ if (rc == CKR_OK || rc == CKR_BUFFER_TOO_SMALL) *pulSignatureLen = sig_len; leave: if (rc != CKR_OK && rc != CKR_BUFFER_TOO_SMALL) session->signing_key = 0; return rc; } /* Prepare a decryption for slot with SLOTID and the session SID using * MECHANISM and KEY. This is the core of C_DecryptInit. */ CK_RV session_init_decrypt (slot_iterator_t slotid, session_iterator_t sid, CK_MECHANISM *mechanism, object_iterator_t key) { struct slot *slot; struct session *session; CK_RV rv; CK_ATTRIBUTE_PTR attr; CK_ULONG attr_count; CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY; if (mechanism->mechanism != CKM_RSA_PKCS) return CKR_MECHANISM_INVALID; slot = scute_table_data (slots, slotid); session = scute_table_data (slot->sessions, sid); rv = slot_get_object (slotid, key, &attr, &attr_count); if (rv) return rv; /* FIXME: What kind of strange loop is this? */ while (attr_count-- > 0) if (attr->type == CKA_CLASS) break; if (attr_count == (CK_ULONG) -1) return CKR_KEY_HANDLE_INVALID; if (attr->ulValueLen != sizeof (key_class) || memcmp (attr->pValue, &key_class, sizeof (key_class))) return CKR_KEY_HANDLE_INVALID; /* It's the private RSA key object. */ session->decryption_key = key; return 0; } /* The core of C_Decrypt - see there for a description. */ CK_RV session_decrypt (slot_iterator_t slotid, session_iterator_t sid, CK_BYTE *encdata, CK_ULONG encdatalen, CK_BYTE *r_plaindata, CK_ULONG *r_plaindatalen) { CK_RV rv; gpg_error_t err; struct slot *slot; struct session *session; CK_ATTRIBUTE_PTR attr; CK_ULONG attr_count; CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY; CK_BYTE key_id[100]; int i; const char *keyref; unsigned int plaindatalen; slot = scute_table_data (slots, slotid); session = scute_table_data (slot->sessions, sid); if (!session->decryption_key) return CKR_OPERATION_NOT_INITIALIZED; rv = slot_get_object (slotid, session->decryption_key, &attr, &attr_count); if (rv) goto leave; if (attr_count == (CK_ULONG) -1) { rv = CKR_KEY_HANDLE_INVALID; goto leave; } if (attr->ulValueLen != sizeof (key_class) || memcmp (attr->pValue, &key_class, sizeof (key_class))) { rv = CKR_KEY_HANDLE_INVALID; goto leave; } /* Find the CKA_ID */ for (i = 0; i < attr_count; i++) if (attr[i].type == CKA_ID) break; if (i == attr_count) { rv = CKR_GENERAL_ERROR; goto leave; } if (attr[i].ulValueLen >= sizeof key_id - 1) { rv = CKR_GENERAL_ERROR; goto leave; } strncpy (key_id, attr[i].pValue, attr[i].ulValueLen); key_id[attr[i].ulValueLen] = 0; DEBUG (DBG_INFO, "Found CKA_ID '%s'", key_id); for (keyref=key_id; *keyref && *keyref != ' '; keyref++) ; if (*keyref) keyref++; /* Point to the grip. */ plaindatalen = *r_plaindatalen; err = scute_agent_decrypt (keyref, encdata, encdatalen, r_plaindata, &plaindatalen); DEBUG (DBG_INFO, "agent returned gpg error %d datalen=%u", err, plaindatalen); /* Take care of error codes which are not mapped by default. */ if (gpg_err_code (err) == GPG_ERR_INV_LENGTH) rv = CKR_BUFFER_TOO_SMALL; else if (gpg_err_code (err) == GPG_ERR_INV_ARG) rv = CKR_ARGUMENTS_BAD; else rv = scute_gpg_err_to_ck (err); /* Return the length. */ if (rv == CKR_OK || rv == CKR_BUFFER_TOO_SMALL) *r_plaindatalen = plaindatalen; leave: if (rv != CKR_BUFFER_TOO_SMALL) session->decryption_key = 0; DEBUG (DBG_INFO, "leaving decrypt with rv=%lu", rv); return rv; }