mirror of
git://git.gnupg.org/gnupg.git
synced 2024-11-04 20:38:50 +01:00
sm: Support encryption using ECDH keys.
* sm/decrypt.c (hash_ecc_cms_shared_info): Make global.
* sm/encrypt.c (ecdh_encrypt): New.
(encrypt_dek): Add arg PK_ALGO and support ECDH.
(gpgsm_encrypt): Pass PK_ALGO.
--
Note: This has only been tested with a messages created and decrypted
by GnuPG.
GnuPG-bug-id: 4098
Signed-off-by: Werner Koch <wk@gnupg.org>
Backported-from-master: d5051e31a8
GnuPG-bug-id: 6253
This commit is contained in:
parent
d770715e15
commit
28467f3735
@ -1,7 +1,7 @@
|
|||||||
/* decrypt.c - Decrypt a message
|
/* decrypt.c - Decrypt a message
|
||||||
* Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc.
|
* Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc.
|
||||||
* Copyright (C) 2001-2019 Werner Koch
|
* Copyright (C) 2001-2019 Werner Koch
|
||||||
* Copyright (C) 2015-2021 g10 Code GmbH
|
* Copyright (C) 2015-2020 g10 Code GmbH
|
||||||
*
|
*
|
||||||
* This file is part of GnuPG.
|
* This file is part of GnuPG.
|
||||||
*
|
*
|
||||||
@ -88,7 +88,7 @@ string_from_gcry_buffer (gcry_buffer_t *buffer)
|
|||||||
* entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL,
|
* entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL,
|
||||||
* suppPubInfo [2] EXPLICIT OCTET STRING }
|
* suppPubInfo [2] EXPLICIT OCTET STRING }
|
||||||
* as described in RFC-5753, 7.2. */
|
* as described in RFC-5753, 7.2. */
|
||||||
static gpg_error_t
|
gpg_error_t
|
||||||
hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, const char *wrap_algo_str,
|
hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, const char *wrap_algo_str,
|
||||||
unsigned int keylen,
|
unsigned int keylen,
|
||||||
const void *ukm, unsigned int ukmlen)
|
const void *ukm, unsigned int ukmlen)
|
||||||
|
306
sm/encrypt.c
306
sm/encrypt.c
@ -1,6 +1,8 @@
|
|||||||
/* encrypt.c - Encrypt a message
|
/* encrypt.c - Encrypt a message
|
||||||
* Copyright (C) 2001, 2003, 2004, 2007, 2008,
|
* Copyright (C) 2001, 2003, 2004, 2007, 2008,
|
||||||
* 2010 Free Software Foundation, Inc.
|
* 2010 Free Software Foundation, Inc.
|
||||||
|
* Copyright (C) 2001-2019 Werner Koch
|
||||||
|
* Copyright (C) 2015-2020 g10 Code GmbH
|
||||||
*
|
*
|
||||||
* This file is part of GnuPG.
|
* This file is part of GnuPG.
|
||||||
*
|
*
|
||||||
@ -16,6 +18,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
@ -144,7 +147,7 @@ init_dek (DEK dek)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Encrypt an RSA session key. */
|
||||||
static int
|
static int
|
||||||
encode_session_key (DEK dek, gcry_sexp_t * r_data)
|
encode_session_key (DEK dek, gcry_sexp_t * r_data)
|
||||||
{
|
{
|
||||||
@ -165,10 +168,282 @@ encode_session_key (DEK dek, gcry_sexp_t * r_data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Encrypt DEK using ECDH. S_PKEY is the public key. On success the
|
||||||
|
* result is stored at R_ENCVAL. Example of a public key:
|
||||||
|
*
|
||||||
|
* (public-key (ecc (curve "1.3.132.0.34") (q #04B0[...]B8#)))
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static gpg_error_t
|
||||||
|
ecdh_encrypt (DEK dek, gcry_sexp_t s_pkey, gcry_sexp_t *r_encval)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
gcry_sexp_t l1;
|
||||||
|
char *curvebuf = NULL;
|
||||||
|
const char *curve;
|
||||||
|
unsigned int curvebits;
|
||||||
|
const char *encr_algo_str;
|
||||||
|
const char *wrap_algo_str;
|
||||||
|
int hash_algo, cipher_algo;
|
||||||
|
unsigned int keylen, hashlen;
|
||||||
|
unsigned char key[32];
|
||||||
|
gcry_sexp_t s_data = NULL;
|
||||||
|
gcry_sexp_t s_encr = NULL;
|
||||||
|
gcry_buffer_t ioarray[2] = { {0}, {0} };
|
||||||
|
unsigned char *secret; /* Alias for ioarray[0]. */
|
||||||
|
unsigned int secretlen;
|
||||||
|
unsigned char *pubkey; /* Alias for ioarray[1]. */
|
||||||
|
unsigned int pubkeylen;
|
||||||
|
gcry_cipher_hd_t cipher_hd = NULL;
|
||||||
|
unsigned char *result = NULL;
|
||||||
|
unsigned int resultlen;
|
||||||
|
|
||||||
|
*r_encval = NULL;
|
||||||
|
|
||||||
|
/* Figure out the encryption and wrap algo OIDs. */
|
||||||
|
/* Get the curve name if any, */
|
||||||
|
l1 = gcry_sexp_find_token (s_pkey, "curve", 0);
|
||||||
|
if (l1)
|
||||||
|
{
|
||||||
|
curvebuf = gcry_sexp_nth_string (l1, 1);
|
||||||
|
gcry_sexp_release (l1);
|
||||||
|
}
|
||||||
|
if (!curvebuf)
|
||||||
|
{
|
||||||
|
err = gpg_error (GPG_ERR_INV_CURVE);
|
||||||
|
log_error ("%s: invalid public key: no curve\n", __func__);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We need to use our OpenPGP mapping to turn a curve name into its
|
||||||
|
* canonical numerical OID. We also use this to get the size of the
|
||||||
|
* curve which we need to figure out a suitable hash algo. We
|
||||||
|
* should have a Libgcrypt function to do this; see bug report #4926. */
|
||||||
|
curve = openpgp_curve_to_oid (curvebuf, &curvebits, NULL);
|
||||||
|
if (!curve)
|
||||||
|
{
|
||||||
|
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
|
||||||
|
log_error ("%s: invalid public key: %s\n", __func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
xfree (curvebuf);
|
||||||
|
curvebuf = NULL;
|
||||||
|
|
||||||
|
/* Our mapping matches the recommended algorithms from RFC-5753 but
|
||||||
|
* not supporing the short curves which would require 3DES. */
|
||||||
|
if (curvebits < 255)
|
||||||
|
{
|
||||||
|
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
|
||||||
|
log_error ("%s: curve '%s' is not supported\n", __func__, curve);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
else if (curvebits <= 256)
|
||||||
|
{
|
||||||
|
/* dhSinglePass-stdDH-sha256kdf-scheme */
|
||||||
|
encr_algo_str = "1.3.132.1.11.1";
|
||||||
|
wrap_algo_str = "2.16.840.1.101.3.4.1.5";
|
||||||
|
hash_algo = GCRY_MD_SHA256;
|
||||||
|
hashlen = 32;
|
||||||
|
cipher_algo = GCRY_CIPHER_AES128;
|
||||||
|
keylen = 16;
|
||||||
|
}
|
||||||
|
else if (curvebits <= 384)
|
||||||
|
{
|
||||||
|
/* dhSinglePass-stdDH-sha384kdf-scheme */
|
||||||
|
encr_algo_str = "1.3.132.1.11.2";
|
||||||
|
wrap_algo_str = "2.16.840.1.101.3.4.1.25";
|
||||||
|
hash_algo = GCRY_MD_SHA384;
|
||||||
|
hashlen = 48;
|
||||||
|
cipher_algo = GCRY_CIPHER_AES256;
|
||||||
|
keylen = 24;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* dhSinglePass-stdDH-sha512kdf-scheme*/
|
||||||
|
encr_algo_str = "1.3.132.1.11.3";
|
||||||
|
wrap_algo_str = "2.16.840.1.101.3.4.1.45";
|
||||||
|
hash_algo = GCRY_MD_SHA512;
|
||||||
|
hashlen = 64;
|
||||||
|
cipher_algo = GCRY_CIPHER_AES256;
|
||||||
|
keylen = 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Create a secret and an ephemeral key. */
|
||||||
|
{
|
||||||
|
char *k;
|
||||||
|
k = gcry_random_bytes_secure ((curvebits+7)/8, GCRY_STRONG_RANDOM);
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
log_printhex (k, (curvebits+7)/8, "ephm. k .:");
|
||||||
|
err = gcry_sexp_build (&s_data, NULL, "%b", (int)(curvebits+7)/8, k);
|
||||||
|
xfree (k);
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error ("%s: error building ephemeral secret: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gcry_pk_encrypt (&s_encr, s_data, s_pkey);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error ("%s: error encrypting ephemeral secret: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
err = gcry_sexp_extract_param (s_encr, NULL, "&se",
|
||||||
|
&ioarray+0, ioarray+1, NULL);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error ("%s: error extracting ephemeral key and secret: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
secret = ioarray[0].data;
|
||||||
|
secretlen = ioarray[0].len;
|
||||||
|
pubkey = ioarray[1].data;
|
||||||
|
pubkeylen = ioarray[1].len;
|
||||||
|
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
{
|
||||||
|
log_printhex (pubkey, pubkeylen, "pubkey ..:");
|
||||||
|
log_printhex (secret, secretlen, "secret ..:");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract X coordinate from SECRET. */
|
||||||
|
if (secretlen < 5) /* 5 because N could be reduced to (n-1)/2. */
|
||||||
|
err = gpg_error (GPG_ERR_BAD_DATA);
|
||||||
|
else if (*secret == 0x04)
|
||||||
|
{
|
||||||
|
secretlen--;
|
||||||
|
memmove (secret, secret+1, secretlen);
|
||||||
|
if ((secretlen & 1))
|
||||||
|
{
|
||||||
|
err = gpg_error (GPG_ERR_BAD_DATA);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
secretlen /= 2;
|
||||||
|
}
|
||||||
|
else if (*secret == 0x40 || *secret == 0x41)
|
||||||
|
{
|
||||||
|
secretlen--;
|
||||||
|
memmove (secret, secret+1, secretlen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
err = gpg_error (GPG_ERR_BAD_DATA);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
log_printhex (secret, secretlen, "ECDH X ..:");
|
||||||
|
|
||||||
|
/* Derive a KEK (key wrapping key) using MESSAGE and SECRET_X.
|
||||||
|
* According to SEC1 3.6.1 we should check that
|
||||||
|
* SECRETLEN + UKMLEN + 4 < maxhashlen
|
||||||
|
* However, we have no practical limit on the hash length and thus
|
||||||
|
* there is no point in checking this. The second check that
|
||||||
|
* KEYLEN < hashlen*(2^32-1)
|
||||||
|
* is obviously also not needed. Because with our allowed
|
||||||
|
* parameters KEYLEN is always less or equal to HASHLEN so that we
|
||||||
|
* do not need to iterate at all.
|
||||||
|
*/
|
||||||
|
log_assert (gcry_md_get_algo_dlen (hash_algo) == hashlen);
|
||||||
|
{
|
||||||
|
gcry_md_hd_t hash_hd;
|
||||||
|
err = gcry_md_open (&hash_hd, hash_algo, 0);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
gcry_md_write(hash_hd, secret, secretlen);
|
||||||
|
gcry_md_write(hash_hd, "\x00\x00\x00\x01", 4); /* counter */
|
||||||
|
err = hash_ecc_cms_shared_info (hash_hd, wrap_algo_str, keylen, NULL, 0);
|
||||||
|
gcry_md_final (hash_hd);
|
||||||
|
log_assert (keylen <= sizeof key && keylen <= hashlen);
|
||||||
|
memcpy (key, gcry_md_read (hash_hd, 0), keylen);
|
||||||
|
gcry_md_close (hash_hd);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
log_printhex (key, keylen, "KEK .....:");
|
||||||
|
|
||||||
|
/* Wrap the key. */
|
||||||
|
if ((dek->keylen % 8) || dek->keylen < 16)
|
||||||
|
{
|
||||||
|
log_error ("%s: can't use a session key of %u bytes\n",
|
||||||
|
__func__, dek->keylen);
|
||||||
|
err = gpg_error (GPG_ERR_BAD_DATA);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultlen = dek->keylen + 8;
|
||||||
|
result = xtrymalloc_secure (resultlen);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gcry_cipher_open (&cipher_hd, cipher_algo, GCRY_CIPHER_MODE_AESWRAP, 0);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error ("%s: failed to initialize AESWRAP: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gcry_cipher_setkey (cipher_hd, key, keylen);
|
||||||
|
wipememory (key, sizeof key);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error ("%s: failed in gcry_cipher_setkey: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = gcry_cipher_encrypt (cipher_hd, result, resultlen,
|
||||||
|
dek->key, dek->keylen);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error ("%s: failed in gcry_cipher_encrypt: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
log_printhex (result, resultlen, "w(CEK) ..:");
|
||||||
|
|
||||||
|
err = gcry_sexp_build (r_encval, NULL,
|
||||||
|
"(enc-val(ecdh(e%b)(s%b)(encr-algo%s)(wrap-algo%s)))",
|
||||||
|
(int)pubkeylen, pubkey,
|
||||||
|
(int)resultlen, result,
|
||||||
|
encr_algo_str,
|
||||||
|
wrap_algo_str,
|
||||||
|
NULL);
|
||||||
|
if (err)
|
||||||
|
log_error ("%s: failed building final S-exp: %s\n",
|
||||||
|
__func__, gpg_strerror (err));
|
||||||
|
|
||||||
|
leave:
|
||||||
|
gcry_cipher_close (cipher_hd);
|
||||||
|
wipememory (key, sizeof key);
|
||||||
|
xfree (result);
|
||||||
|
xfree (ioarray[0].data);
|
||||||
|
xfree (ioarray[1].data);
|
||||||
|
gcry_sexp_release (s_data);
|
||||||
|
gcry_sexp_release (s_encr);
|
||||||
|
xfree (curvebuf);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Encrypt the DEK under the key contained in CERT and return it as a
|
/* Encrypt the DEK under the key contained in CERT and return it as a
|
||||||
canonical S-Exp in encval. */
|
* canonical S-expressions at ENCVAL. PK_ALGO is the public key
|
||||||
|
* algorithm which the caller has already retrieved from CERT. */
|
||||||
static int
|
static int
|
||||||
encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval)
|
encrypt_dek (const DEK dek, ksba_cert_t cert, int pk_algo,
|
||||||
|
unsigned char **encval)
|
||||||
{
|
{
|
||||||
gcry_sexp_t s_ciph, s_data, s_pkey;
|
gcry_sexp_t s_ciph, s_data, s_pkey;
|
||||||
int rc;
|
int rc;
|
||||||
@ -198,21 +473,38 @@ encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
{
|
||||||
|
log_printsexp (" pubkey:", s_pkey);
|
||||||
|
log_printhex (dek->key, dek->keylen, "CEK .....:");
|
||||||
|
}
|
||||||
|
|
||||||
/* Put the encoded cleartext into a simple list. */
|
/* Put the encoded cleartext into a simple list. */
|
||||||
s_data = NULL; /* (avoid compiler warning) */
|
s_data = NULL; /* (avoid compiler warning) */
|
||||||
|
if (pk_algo == GCRY_PK_ECC)
|
||||||
|
{
|
||||||
|
rc = ecdh_encrypt (dek, s_pkey, &s_ciph);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
rc = encode_session_key (dek, &s_data);
|
rc = encode_session_key (dek, &s_data);
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
gcry_sexp_release (s_pkey);
|
|
||||||
log_error ("encode_session_key failed: %s\n", gpg_strerror (rc));
|
log_error ("encode_session_key failed: %s\n", gpg_strerror (rc));
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
log_printsexp (" data:", s_data);
|
||||||
|
|
||||||
/* pass it to libgcrypt */
|
/* pass it to libgcrypt */
|
||||||
rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
|
rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
|
||||||
|
}
|
||||||
gcry_sexp_release (s_data);
|
gcry_sexp_release (s_data);
|
||||||
gcry_sexp_release (s_pkey);
|
gcry_sexp_release (s_pkey);
|
||||||
|
|
||||||
|
if (DBG_CRYPTO)
|
||||||
|
log_printsexp ("enc-val:", s_ciph);
|
||||||
|
|
||||||
/* Reformat it. */
|
/* Reformat it. */
|
||||||
if (!rc)
|
if (!rc)
|
||||||
{
|
{
|
||||||
@ -387,7 +679,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp)
|
|||||||
err = ksba_cms_set_reader_writer (cms, reader, writer);
|
err = ksba_cms_set_reader_writer (cms, reader, writer);
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
|
log_error ("ksba_cms_set_reader_writer failed: %s\n",
|
||||||
gpg_strerror (err));
|
gpg_strerror (err));
|
||||||
rc = err;
|
rc = err;
|
||||||
goto leave;
|
goto leave;
|
||||||
@ -402,7 +694,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp)
|
|||||||
err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA);
|
err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA);
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
log_debug ("ksba_cms_set_content_type failed: %s\n",
|
log_error ("ksba_cms_set_content_type failed: %s\n",
|
||||||
gpg_strerror (err));
|
gpg_strerror (err));
|
||||||
rc = err;
|
rc = err;
|
||||||
goto leave;
|
goto leave;
|
||||||
@ -500,7 +792,7 @@ gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp)
|
|||||||
&& !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL))
|
&& !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL))
|
||||||
compliant = 0;
|
compliant = 0;
|
||||||
|
|
||||||
rc = encrypt_dek (dek, cl->cert, &encval);
|
rc = encrypt_dek (dek, cl->cert, pk_algo, &encval);
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc);
|
audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc);
|
||||||
|
@ -390,6 +390,10 @@ int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist,
|
|||||||
int in_fd, estream_t out_fp);
|
int in_fd, estream_t out_fp);
|
||||||
|
|
||||||
/*-- decrypt.c --*/
|
/*-- decrypt.c --*/
|
||||||
|
gpg_error_t hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd,
|
||||||
|
const char *wrap_algo_str,
|
||||||
|
unsigned int keylen,
|
||||||
|
const void *ukm, unsigned int ukmlen);
|
||||||
int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp);
|
int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp);
|
||||||
|
|
||||||
/*-- certreqgen.c --*/
|
/*-- certreqgen.c --*/
|
||||||
|
Loading…
Reference in New Issue
Block a user