mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-21 10:09:57 +01:00
keyboxd: Add basic support for X.509.
* kbx/keybox-blob.c (x509_email_kludge): Rename to ... (_keybox_x509_email_kludge): this and make global. * kbx/backend.h: Include ksba.h. * kbx/backend-support.c (be_get_x509_serial): New. (be_get_x509_keygrip): New. * kbx/backend-sqlite.c (table_definitions): New table 'issuers'. (run_select_statement): Implements modes ISSUER, ISSUER_SN, SUBJECT. (store_into_userid): Add arg override_mbox. (store_into_issuer): New. (be_sqlite_store): Implement x509 part. Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
parent
6fcc263c18
commit
c9677d416e
@ -123,12 +123,14 @@ static struct
|
||||
* It is also used for the primary key and the X.509 fingerprint
|
||||
* because we want to be able to use the keyid and keygrip. */
|
||||
{ "CREATE TABLE IF NOT EXISTS fingerprint ("
|
||||
/* The fingerprint, for OpenPGP either 20 octets or 32 octets;
|
||||
* for X.509 it is the same as the UBID. */
|
||||
"fpr BLOB NOT NULL PRIMARY KEY,"
|
||||
/* The long keyid as 64 bit integer. */
|
||||
"kid INTEGER NOT NULL,"
|
||||
/* The keygrip for this key. */
|
||||
"keygrip BLOB NOT NULL,"
|
||||
/* 0 = primary, > 0 = subkey. */
|
||||
/* 0 = primary or X.509, > 0 = subkey. */
|
||||
"subkey INTEGER NOT NULL,"
|
||||
/* The Unique Blob ID (possibly truncated fingerprint). */
|
||||
"ubid BLOB NOT NULL REFERENCES pubkey"
|
||||
@ -139,10 +141,9 @@ static struct
|
||||
{ "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)" },
|
||||
{ "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)" },
|
||||
|
||||
|
||||
/* Table to allow fast access via user ids or mail addresses. */
|
||||
{ "CREATE TABLE IF NOT EXISTS userid ("
|
||||
/* The full user id. */
|
||||
/* The full user id - for X.509 the Subject or altSubject. */
|
||||
"uid TEXT NOT NULL,"
|
||||
/* The mail address if available or NULL. */
|
||||
"addrspec TEXT,"
|
||||
@ -155,7 +156,18 @@ static struct
|
||||
/* Indices for the userid table. */
|
||||
{ "CREATE INDEX IF NOT EXISTS userididx0 on userid (ubid)" },
|
||||
{ "CREATE INDEX IF NOT EXISTS userididx1 on userid (uid)" },
|
||||
{ "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" }
|
||||
{ "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" },
|
||||
|
||||
/* Table to allow fast access via s/n + issuer DN (X.509 only). */
|
||||
{ "CREATE TABLE IF NOT EXISTS issuer ("
|
||||
/* The hex encoded S/N. */
|
||||
"sn TEXT NOT NULL,"
|
||||
/* The RFC2253 issuer DN. */
|
||||
"dn TEXT NOT NULL,"
|
||||
/* The Unique Blob ID (usually the truncated fingerprint). */
|
||||
"ubid BLOB NOT NULL REFERENCES pubkey"
|
||||
")" },
|
||||
{ "CREATE INDEX IF NOT EXISTS issueridx1 on issuer (dn)" }
|
||||
|
||||
};
|
||||
|
||||
@ -786,28 +798,57 @@ run_select_statement (ctrl_t ctrl, be_sqlite_local_t ctx,
|
||||
break;
|
||||
|
||||
case KEYDB_SEARCH_MODE_ISSUER:
|
||||
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
|
||||
/* if (has_issuer (blob, desc[n].u.name)) */
|
||||
/* goto found; */
|
||||
if (!ctx->select_stmt)
|
||||
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
|
||||
" FROM pubkey as p, issuer as i"
|
||||
" WHERE p.ubid = i.ubid"
|
||||
" AND i.dn = $1",
|
||||
extra, &ctx->select_stmt);
|
||||
if (!err)
|
||||
err = run_sql_bind_text (ctx->select_stmt, 1,
|
||||
desc[descidx].u.name);
|
||||
break;
|
||||
|
||||
case KEYDB_SEARCH_MODE_ISSUER_SN:
|
||||
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
|
||||
/* if (has_issuer_sn (blob, desc[n].u.name, */
|
||||
/* sn_array? sn_array[n].sn : desc[n].sn, */
|
||||
/* sn_array? sn_array[n].snlen : desc[n].snlen)) */
|
||||
/* goto found; */
|
||||
if (!desc[descidx].snhex)
|
||||
{
|
||||
/* We should never get a binary S/N here. */
|
||||
log_debug ("%s: issuer_sn with binary s/n\n", __func__);
|
||||
err = gpg_error (GPG_ERR_INTERNAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ctx->select_stmt)
|
||||
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
|
||||
" FROM pubkey as p, issuer as i"
|
||||
" WHERE p.ubid = i.ubid"
|
||||
" AND i.sn = $1 AND i.dn = $2",
|
||||
extra, &ctx->select_stmt);
|
||||
if (!err)
|
||||
err = run_sql_bind_ntext (ctx->select_stmt, 1,
|
||||
desc[descidx].sn, desc[descidx].snlen);
|
||||
if (!err)
|
||||
err = run_sql_bind_text (ctx->select_stmt, 2,
|
||||
desc[descidx].u.name);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEYDB_SEARCH_MODE_SN:
|
||||
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
|
||||
/* if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, */
|
||||
/* sn_array? sn_array[n].snlen : desc[n].snlen)) */
|
||||
/* goto found; */
|
||||
break;
|
||||
|
||||
case KEYDB_SEARCH_MODE_SUBJECT:
|
||||
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
|
||||
/* if (has_subject (blob, desc[n].u.name)) */
|
||||
/* goto found; */
|
||||
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
|
||||
" FROM pubkey as p, userid as u"
|
||||
" WHERE p.ubid = u.ubid"
|
||||
" AND u.uid = $1",
|
||||
extra, &ctx->select_stmt);
|
||||
if (!err)
|
||||
err = run_sql_bind_text (ctx->select_stmt, 1,
|
||||
desc[descidx].u.name);
|
||||
break;
|
||||
|
||||
case KEYDB_SEARCH_MODE_SHORT_KID:
|
||||
@ -1101,11 +1142,12 @@ store_into_fingerprint (const unsigned char *ubid, int subkey,
|
||||
}
|
||||
|
||||
|
||||
/* Helper for be_sqlite_store to update or insert a row in the
|
||||
* userid table. */
|
||||
/* Helper for be_sqlite_store to update or insert a row in the userid
|
||||
* table. If OVERRIDE_MBOX is set, that value is used instead of a
|
||||
* value extracted from UID. */
|
||||
static gpg_error_t
|
||||
store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
|
||||
const char *uid)
|
||||
const char *uid, const char *override_mbox)
|
||||
{
|
||||
gpg_error_t err;
|
||||
const char *sqlstr;
|
||||
@ -1121,10 +1163,17 @@ store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
|
||||
err = run_sql_bind_text (stmt, 1, uid);
|
||||
if (err)
|
||||
goto leave;
|
||||
addrspec = mailbox_from_userid (uid, 0);
|
||||
err = run_sql_bind_text (stmt, 2, addrspec);
|
||||
|
||||
if (override_mbox)
|
||||
err = run_sql_bind_text (stmt, 2, override_mbox);
|
||||
else
|
||||
{
|
||||
addrspec = mailbox_from_userid (uid, 0);
|
||||
err = run_sql_bind_text (stmt, 2, addrspec);
|
||||
}
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
err = run_sql_bind_int (stmt, 3, pktype);
|
||||
if (err)
|
||||
goto leave;
|
||||
@ -1142,6 +1191,43 @@ store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
|
||||
}
|
||||
|
||||
|
||||
/* Helper for be_sqlite_store to update or insert a row in the
|
||||
* issuer table. */
|
||||
static gpg_error_t
|
||||
store_into_issuer (const unsigned char *ubid,
|
||||
const char *sn, const char *issuer)
|
||||
{
|
||||
gpg_error_t err;
|
||||
const char *sqlstr;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
char *addrspec = NULL;
|
||||
|
||||
sqlstr = ("INSERT OR REPLACE INTO issuer(sn,dn,ubid)"
|
||||
" VALUES(:1,:2,:3)");
|
||||
err = run_sql_prepare (sqlstr, NULL, &stmt);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
err = run_sql_bind_text (stmt, 1, sn);
|
||||
if (err)
|
||||
goto leave;
|
||||
err = run_sql_bind_text (stmt, 2, issuer);
|
||||
if (err)
|
||||
goto leave;
|
||||
err = run_sql_bind_blob (stmt, 3, ubid, UBID_LEN);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
err = run_sql_step (stmt);
|
||||
|
||||
leave:
|
||||
if (stmt)
|
||||
sqlite3_finalize (stmt);
|
||||
xfree (addrspec);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Store (BLOB,BLOBLEN) into the database. UBID is the UBID matching
|
||||
* that blob. BACKEND_HD is the handle for this backend and REQUEST
|
||||
* is the current database request object. MODE is the store
|
||||
@ -1159,7 +1245,10 @@ be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
|
||||
int in_transaction = 0;
|
||||
int info_valid = 0;
|
||||
struct _keybox_openpgp_info info;
|
||||
struct _keybox_openpgp_key_info *kinfo;
|
||||
ksba_cert_t cert = NULL;
|
||||
char *sn = NULL;
|
||||
char *dn = NULL;
|
||||
char *kludge_mbox = NULL;
|
||||
|
||||
(void)ctrl;
|
||||
|
||||
@ -1172,10 +1261,14 @@ be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
|
||||
|
||||
if (be_is_x509_blob (blob, bloblen))
|
||||
{
|
||||
/* The UBID is also our fingerprint. */
|
||||
/* FIXME: Extract keygrip and KID. */
|
||||
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
||||
goto leave;
|
||||
log_assert (pktype == PUBKEY_TYPE_X509);
|
||||
|
||||
err = ksba_cert_new (&cert);
|
||||
if (err)
|
||||
goto leave;
|
||||
err = ksba_cert_init_from_mem (cert, blob, bloblen);
|
||||
if (err)
|
||||
goto leave;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1221,56 +1314,127 @@ be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
|
||||
("DELETE FROM userid WHERE ubid = :1", ubid);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
kinfo = &info.primary;
|
||||
err = store_into_fingerprint (ubid, 0, kinfo->grip,
|
||||
kid_from_mem (kinfo->keyid),
|
||||
kinfo->fpr, kinfo->fprlen);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
if (info.nsubkeys)
|
||||
if (cert)
|
||||
{
|
||||
int subkey = 1;
|
||||
for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++)
|
||||
{
|
||||
err = store_into_fingerprint (ubid, subkey, kinfo->grip,
|
||||
kid_from_mem (kinfo->keyid),
|
||||
kinfo->fpr, kinfo->fprlen);
|
||||
if (err)
|
||||
goto leave;
|
||||
}
|
||||
err = run_sql_statement_bind_ubid
|
||||
("DELETE FROM issuer WHERE ubid = :1", ubid);
|
||||
if (err)
|
||||
goto leave;
|
||||
}
|
||||
|
||||
if (info.nuids)
|
||||
if (cert) /* X.509 */
|
||||
{
|
||||
struct _keybox_openpgp_uid_info *u;
|
||||
unsigned char grip[KEYGRIP_LEN];
|
||||
int idx;
|
||||
|
||||
u = &info.uids;
|
||||
do
|
||||
err = be_get_x509_keygrip (cert, grip);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
/* Note that for X.509 the UBID is also the fingerprint. */
|
||||
err = store_into_fingerprint (ubid, 0, grip,
|
||||
kid_from_mem (ubid+12),
|
||||
ubid, UBID_LEN);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
/* Now the issuer. */
|
||||
sn = be_get_x509_serial (cert);
|
||||
if (!sn)
|
||||
{
|
||||
log_assert (u->off <= bloblen);
|
||||
log_assert (u->off + u->len <= bloblen);
|
||||
{
|
||||
char *uid = xtrymalloc (u->len + 1);
|
||||
if (!uid)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
memcpy (uid, (const unsigned char *)blob + u->off, u->len);
|
||||
uid[u->len] = 0;
|
||||
/* Note that we ignore embedded zeros in the user id; this
|
||||
* is what we do all over the place. */
|
||||
err = store_into_userid (ubid, pktype, uid);
|
||||
xfree (uid);
|
||||
}
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
dn = ksba_cert_get_issuer (cert, 0);
|
||||
if (!dn)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
err = store_into_issuer (ubid, sn, dn);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
/* Loop over the subject and alternate subjects. */
|
||||
for (idx=0; (xfree (dn), dn = ksba_cert_get_subject (cert, idx)); idx++)
|
||||
{
|
||||
/* In the case that the same email address is in the
|
||||
* subject DN as well as in an alternate subject name
|
||||
* we avoid printing it a second time. */
|
||||
if (kludge_mbox && !strcmp (kludge_mbox, dn))
|
||||
continue;
|
||||
|
||||
err = store_into_userid (ubid, PUBKEY_TYPE_X509, dn, NULL);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
u = u->next;
|
||||
if (!idx)
|
||||
{
|
||||
kludge_mbox = _keybox_x509_email_kludge (dn);
|
||||
if (kludge_mbox)
|
||||
{
|
||||
err = store_into_userid (ubid, PUBKEY_TYPE_X509,
|
||||
dn, kludge_mbox);
|
||||
if (err)
|
||||
goto leave;
|
||||
}
|
||||
}
|
||||
} /* end loop over the subjects. */
|
||||
}
|
||||
else /* OpenPGP */
|
||||
{
|
||||
struct _keybox_openpgp_key_info *kinfo;
|
||||
|
||||
kinfo = &info.primary;
|
||||
err = store_into_fingerprint (ubid, 0, kinfo->grip,
|
||||
kid_from_mem (kinfo->keyid),
|
||||
kinfo->fpr, kinfo->fprlen);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
if (info.nsubkeys)
|
||||
{
|
||||
int subkey = 1;
|
||||
for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++)
|
||||
{
|
||||
err = store_into_fingerprint (ubid, subkey, kinfo->grip,
|
||||
kid_from_mem (kinfo->keyid),
|
||||
kinfo->fpr, kinfo->fprlen);
|
||||
if (err)
|
||||
goto leave;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.nuids)
|
||||
{
|
||||
struct _keybox_openpgp_uid_info *u;
|
||||
|
||||
u = &info.uids;
|
||||
do
|
||||
{
|
||||
log_assert (u->off <= bloblen);
|
||||
log_assert (u->off + u->len <= bloblen);
|
||||
{
|
||||
char *uid = xtrymalloc (u->len + 1);
|
||||
if (!uid)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
memcpy (uid, (const unsigned char *)blob + u->off, u->len);
|
||||
uid[u->len] = 0;
|
||||
/* Note that we ignore embedded zeros in the user id;
|
||||
* this is what we do all over the place. */
|
||||
err = store_into_userid (ubid, pktype, uid, NULL);
|
||||
xfree (uid);
|
||||
}
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
u = u->next;
|
||||
}
|
||||
while (u);
|
||||
}
|
||||
while (u);
|
||||
}
|
||||
|
||||
leave:
|
||||
@ -1285,6 +1449,11 @@ be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
|
||||
release_mutex ();
|
||||
if (info_valid)
|
||||
_keybox_destroy_openpgp_info (&info);
|
||||
if (cert)
|
||||
ksba_cert_release (cert);
|
||||
ksba_free (dn);
|
||||
xfree (sn);
|
||||
xfree (kludge_mbox);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -282,3 +282,73 @@ be_ubid_from_blob (const void *blob, size_t bloblen,
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Return a certificates serial number in hex encoding. Caller must
|
||||
* free the returned string. NULL is returned on error but ERRNO
|
||||
* might not be set if the certificate and thus Libksba is broken. */
|
||||
char *
|
||||
be_get_x509_serial (ksba_cert_t cert)
|
||||
{
|
||||
const char *p;
|
||||
unsigned long n;
|
||||
char *endp;
|
||||
|
||||
p = (const char *)ksba_cert_get_serial (cert);
|
||||
if (!p)
|
||||
{
|
||||
log_debug ("oops: Libksba returned a certificate w/o a serial\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*p != '(')
|
||||
{
|
||||
log_debug ("oops: Libksba returned an invalid s-expression\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p++;
|
||||
n = strtoul (p, &endp, 10);
|
||||
p = endp;
|
||||
if (*p != ':')
|
||||
{
|
||||
log_debug ("oops: Libksba returned an invalid s-expression\n");
|
||||
return NULL;
|
||||
}
|
||||
p++;
|
||||
|
||||
return bin2hex (p, n, NULL);
|
||||
}
|
||||
|
||||
|
||||
/* Return the keygrip for the X.509 certificate CERT. The grip is
|
||||
* stored at KEYGRIP which must have been allocated by the caller
|
||||
* with a size of KEYGRIP_LEN. */
|
||||
gpg_error_t
|
||||
be_get_x509_keygrip (ksba_cert_t cert, unsigned char *keygrip)
|
||||
{
|
||||
gpg_error_t err;
|
||||
size_t n;
|
||||
ksba_sexp_t p;
|
||||
gcry_sexp_t s_pkey;
|
||||
|
||||
p = ksba_cert_get_public_key (cert);
|
||||
if (!p)
|
||||
return gpg_error (GPG_ERR_NO_PUBKEY);
|
||||
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
|
||||
if (!n)
|
||||
{
|
||||
ksba_free (p);
|
||||
return gpg_error (GPG_ERR_NO_PUBKEY);
|
||||
}
|
||||
err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
|
||||
ksba_free (p);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!gcry_pk_get_keygrip (s_pkey, keygrip))
|
||||
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
|
||||
gcry_sexp_release (s_pkey);
|
||||
return err;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#ifndef KBX_BACKEND_H
|
||||
#define KBX_BACKEND_H
|
||||
|
||||
#include <ksba.h>
|
||||
#include "keybox-search-desc.h"
|
||||
|
||||
/* Forward declaration of the keybox handle type. */
|
||||
@ -118,6 +119,8 @@ gpg_error_t be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
|
||||
int be_is_x509_blob (const unsigned char *blob, size_t bloblen);
|
||||
gpg_error_t be_ubid_from_blob (const void *blob, size_t bloblen,
|
||||
enum pubkey_types *r_pktype, char *r_ubid);
|
||||
char *be_get_x509_serial (ksba_cert_t cert);
|
||||
gpg_error_t be_get_x509_keygrip (ksba_cert_t cert, unsigned char *keygrip);
|
||||
|
||||
|
||||
/*-- backend-cache.c --*/
|
||||
|
@ -841,12 +841,10 @@ _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob,
|
||||
}
|
||||
|
||||
|
||||
#ifdef KEYBOX_WITH_X509
|
||||
|
||||
/* Return an allocated string with the email address extracted from a
|
||||
DN. Note hat we use this code also in ../sm/keylist.c. */
|
||||
static char *
|
||||
x509_email_kludge (const char *name)
|
||||
char *
|
||||
_keybox_x509_email_kludge (const char *name)
|
||||
{
|
||||
const char *p, *string;
|
||||
unsigned char *buf;
|
||||
@ -887,6 +885,8 @@ x509_email_kludge (const char *name)
|
||||
|
||||
|
||||
|
||||
#ifdef KEYBOX_WITH_X509
|
||||
|
||||
/* Note: We should move calculation of the digest into libksba and
|
||||
remove that parameter */
|
||||
int
|
||||
@ -965,7 +965,7 @@ _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert,
|
||||
names = tmp;
|
||||
}
|
||||
names[blob->nuids++] = p;
|
||||
if (!i && (p=x509_email_kludge (p)))
|
||||
if (!i && (p=_keybox_x509_email_kludge (p)))
|
||||
names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/
|
||||
}
|
||||
|
||||
|
@ -149,6 +149,8 @@ gpg_error_t _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob,
|
||||
const unsigned char *image,
|
||||
size_t imagelen,
|
||||
int as_ephemeral);
|
||||
char *_keybox_x509_email_kludge (const char *name);
|
||||
|
||||
#ifdef KEYBOX_WITH_X509
|
||||
int _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert,
|
||||
unsigned char *sha1_digest, int as_ephemeral);
|
||||
|
Loading…
x
Reference in New Issue
Block a user