From 6f2d6f78cd11231d6f7ffd6361812b1bd49a4c34 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 12 Jun 2008 17:25:55 -0400 Subject: [PATCH] Major openpgp2ssh overhaul. It's an unforgiving and brittle tool, but it should do what we expect it to do, and its major limitations should be documented in the man page. --- man/man1/openpgp2ssh.1 | 47 +++-- src/keytrans/gnutls-helpers.c | 68 +++++++ src/keytrans/gnutls-helpers.h | 4 + src/keytrans/openpgp2ssh.c | 357 +++++++++++++++++++++++++--------- 4 files changed, 371 insertions(+), 105 deletions(-) diff --git a/man/man1/openpgp2ssh.1 b/man/man1/openpgp2ssh.1 index 6267141..1a02b38 100644 --- a/man/man1/openpgp2ssh.1 +++ b/man/man1/openpgp2ssh.1 @@ -12,16 +12,16 @@ openpgp2ssh .Nm gpg --export-secret-key $KEYID | openpgp2ssh $KEYID .Sh DESCRIPTION -openpgp2ssh takes OpenPGP-formatted RSA and DSA keys on standard -input, and spits out the requested equivalent SSH-style key on -standard output. +openpgp2ssh takes an OpenPGP-formatted primary key and associated +subkeys on standard input, and spits out the requested equivalent +SSH-style key on standard output. -If the data on standard input contains only a single key, you can -invoke openpgp2ssh without arguments. If the data on standard input -contains multiple keys (e.g. a primary key and associated subkeys), -you must specify a specific OpenPGP keyid (e.g. CCD2ED94D21739E9) or +If the data on standard input contains no subkeys, you can invoke +openpgp2ssh without arguments. If the data on standard input contains +multiple keys (e.g. a primary key and associated subkeys), you must +specify a specific OpenPGP keyid (e.g. CCD2ED94D21739E9) or fingerprint as the first argument to indicate which key to export. -The keyid must be at least 8 hex characters. +The keyid must be exactly 16 hex characters. If the input contains an OpenPGP RSA or DSA public key, it will be converted to the OpenSSH-style single-line keystring, prefixed with @@ -31,20 +31,29 @@ insertion into known_hosts files and authorized_keys files. If the input contains an OpenPGP RSA or DSA secret key, it will be converted to the equivalent PEM-encoded private key. -Note that the keys produced by this process are stripped of all -identifying information, including certifications, self-signatures, -etc. - openpgp2ssh is part of the .Xr monkeysphere 1 framework for providing a PKI for SSH. +.Sh CAVEATS +The keys produced by this process are stripped of all identifying +information, including certifications, self-signatures, etc. This is +intentional, since ssh attaches no inherent significance to these +features. + +openpgp2ssh only works with RSA or DSA keys, because those are the +only ones which work with ssh. + +Assuming a valid key type, though, openpgp2ssh will produce output for +any requested key. This means, among other things, that it will +happily export revoked keys, unverifiable keys, expired keys, etc. +Make sure you do your own key validation before using this tool! .Sh EXAMPLES .Nm gpg --export-secret-key $KEYID | openpgp2ssh $KEYID | ssh-add -c /dev/stdin This pushes the secret key into the active .Xr ssh-agent 1 . -Tools (such as -.Xr ssh 1 ) +Tools such as +.Xr ssh 1 which know how to talk to the .Xr ssh-agent 1 can now rely on the key. @@ -58,8 +67,14 @@ lsh(1) and putty(1). Secret key output is currently not passphrase-protected. -This program is not yet implemented, and this man page currently only -describes expected functionality. +openpgp2ssh currently cannot handle passphrase-protected secret keys on input. + +It would be nice to be able to use keyids shorter or longer than 16 +hex characters. + +openpgp2ssh only acts on keys associated with the first primary key +passed in. If you send it more than one primary key, it will silently +ignore later ones. .Sh SEE ALSO .Xr monkeysphere 1 , .Xr ssh 1 , diff --git a/src/keytrans/gnutls-helpers.c b/src/keytrans/gnutls-helpers.c index 6eae29e..7c342bb 100644 --- a/src/keytrans/gnutls-helpers.c +++ b/src/keytrans/gnutls-helpers.c @@ -12,6 +12,9 @@ /* for isalnum() */ #include +/* for exit() */ +#include + int loglevel = 0; @@ -46,6 +49,71 @@ void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid) } } +unsigned char hex2bin(unsigned char x) { + if ((x >= '0') && (x <= '9')) + return x - '0'; + if ((x >= 'A') && (x <= 'F')) + return 10 + x - 'A'; + if ((x >= 'a') && (x <= 'f')) + return 10 + x - 'a'; + return 0xff; +} + +void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in) { + unsigned int pkix = 0, outkix = 0; + + while (pkix < sizeof(printable_keyid)) { + unsigned hi = hex2bin(in[pkix]); + unsigned lo = hex2bin(in[pkix + 1]); + if (hi == 0xff) { + err("character '%c' is not a hex char\n", in[pkix]); + exit(1); + } + if (lo == 0xff) { + err("character '%c' is not a hex char\n", in[pkix + 1]); + exit(1); + } + out[outkix] = lo | (hi << 4); + + pkix += 2; + outkix++; + } +} + +int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str) { + printable_keyid p; + int ret; + + ret = convert_string_to_printable_keyid(p, str); + if (ret == 0) + collapse_printable_keyid(out, p); + return ret; +} +int convert_string_to_printable_keyid(printable_keyid pkeyid, const char* str) { + int arglen, x; + arglen = 0; + x = 0; + while ((arglen <= sizeof(printable_keyid)) && + (str[x] != '\0')) { + if (isxdigit(str[x])) { + if (arglen == sizeof(printable_keyid)) { + err("There are more than %d hex digits in the keyid '%s'\n", sizeof(printable_keyid), str); + return 1; + } + pkeyid[arglen] = str[x]; + arglen++; + } + x++; + } + + if (arglen != sizeof(printable_keyid)) { + err("keyid '%s' is not %d hex digits in length\n", str, sizeof(printable_keyid)); + return 1; + } + return 0; +} + + int init_gnutls() { const char* version = NULL; diff --git a/src/keytrans/gnutls-helpers.h b/src/keytrans/gnutls-helpers.h index 9ea22a3..00cdec7 100644 --- a/src/keytrans/gnutls-helpers.h +++ b/src/keytrans/gnutls-helpers.h @@ -44,6 +44,10 @@ typedef unsigned char printable_keyid[16]; void init_keyid(gnutls_openpgp_keyid_t keyid); void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid); +void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in); +int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str); +int convert_string_to_printable_keyid(printable_keyid out, const char* str); + /* functions to get data into datum objects: */ diff --git a/src/keytrans/openpgp2ssh.c b/src/keytrans/openpgp2ssh.c index d6bac68..30e19d5 100644 --- a/src/keytrans/openpgp2ssh.c +++ b/src/keytrans/openpgp2ssh.c @@ -3,47 +3,47 @@ #include #include +/* for waitpid() */ +#include +#include + /* Author: Daniel Kahn Gillmor - Date: Tue, 01 Apr 2008 + Date: 2008-06-12 13:47:41-0400 License: GPL v3 or later - monkeysphere private key translator: execute this with an GPG - secret key on stdin (at the moment, only passphraseless RSA keys - work). + monkeysphere key translator: execute this with an OpenPGP key on + stdin, (please indicate the specific keyid that you want as the + first argument if there are subkeys). At the moment, only public + keys and passphraseless secret keys work. - It will spit out a PEM-encoded version of the key on stdout, which - can be fed into ssh-add like this: + For secret keys, it will spit out a PEM-encoded version of the key + on stdout, which can be fed into ssh-add like this: - gpg --export-secret-keys $KEYID | monkeysphere | ssh-add -c /dev/stdin + gpg --export-secret-keys $KEYID | openpgp2ssh $KEYID | ssh-add -c /dev/stdin - Requirements: I've only built this so far with GnuTLS v2.3.4 -- - version 2.2.0 does not contain the appropriate pieces. + For public keys, it will spit out a single line of text that can + (with some massaging) be used in an openssh known_hosts or + authorized_keys file. For example: - Notes: gpgkey2ssh doesn't seem to provide the same public - keys. Mighty weird! + echo server.example.org $(gpg --export $KEYID | openpgp2ssh $KEYID) >> ~/.ssh/known_hosts -0 wt215@squeak:~/monkeysphere$ gpg --export-secret-keys 1DCDF89F | ~dkg/src/monkeysphere/monkeysphere | ssh-add -c /dev/stdin -gnutls version: 2.3.4 -OpenPGP RSA Key, with 1024 bits -Identity added: /dev/stdin (/dev/stdin) -The user has to confirm each use of the key -0 wt215@squeak:~/monkeysphere$ ssh-add -L -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9gWQqfrnhQKDQnND/3eOexpddE64J+1zp9fcyCje7H5LKclb6DBV2HS6WgW32PJhIzvP+fYZM3dzXea3fpv14y1SicXiRBDgF9SnsNA1qWn2RyzkLcKy7PmM0PDYtU1oiLTcQj/xkWcqW2sLKHT/WW+vZP5XP7RMGN/yWNMfE2Q== /dev/stdin -0 wt215@squeak:~/monkeysphere$ gpgkey2ssh 1DCDF89F -ssh-rsa AAAAB3NzaC1yc2EAAACBAL2BZCp+ueFAoNCc0P/d457Gl10Trgn7XOn19zIKN7sfkspyVvoMFXYdLpaBbfY8mEjO8/59hkzd3Nd5rd+m/XjLVKJxeJEEOAX1Kew0DWpafZHLOQtwrLs+YzQ8Ni1TWiItNxCP/GRZypbawsodP9Zb69k/lc/tEwY3/JY0x8TZAAAAAwEAAQ== COMMENT -0 wt215@squeak:~/monkeysphere$ + Requirements: I've only built this so far with GnuTLS v2.3.x. + GnuTLS 2.2.x does not contain the appropriate functionality. */ -int convert_pgp_to_x509(gnutls_x509_privkey_t* output, gnutls_datum_t* input) { - gnutls_openpgp_privkey_t pgp_privkey; +/* FIXME: keyid should be const as well */ +int convert_private_pgp_to_x509(gnutls_x509_privkey_t* output, const gnutls_openpgp_privkey_t* pgp_privkey, gnutls_openpgp_keyid_t* keyid) { gnutls_datum_t m, e, d, p, q, u, g, y, x; gnutls_pk_algorithm_t pgp_algo; unsigned int pgp_bits; int ret; +/* FIXME: actually respect keyid argument. At the moment, we just + emit the primary key. */ + init_datum(&m); init_datum(&e); init_datum(&d); @@ -54,34 +54,14 @@ int convert_pgp_to_x509(gnutls_x509_privkey_t* output, gnutls_datum_t* input) { init_datum(&y); init_datum(&x); - if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) { - err("Failed to initialized OpenPGP private key (error: %d)\n", ret); - return 1; - } - - - /* format could be either: GNUTLS_OPENPGP_FMT_RAW, - GNUTLS_OPENPGP_FMT_BASE64; if MONKEYSPHERE_RAW is set, use RAW, - otherwise, use BASE64: */ - - if (getenv("MONKEYSPHERE_RAW")) { - err("assuming RAW formatted private keys\n"); - if (ret = gnutls_openpgp_privkey_import(pgp_privkey, input, GNUTLS_OPENPGP_FMT_RAW, NULL, 0), ret) - err("failed to import the OpenPGP private key in RAW format (error: %d)\n", ret); - } else { - err("assuming BASE64 formatted private keys\n"); - if (ret = gnutls_openpgp_privkey_import (pgp_privkey, input, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0), ret) - err("failed to import the OpenPGP private key in BASE64 format (error: %d)\n", ret); - } - - pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(pgp_privkey, &pgp_bits); + pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(*pgp_privkey, &pgp_bits); if (pgp_algo < 0) { err("failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo); return 1; } if (pgp_algo == GNUTLS_PK_RSA) { err("OpenPGP RSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_rsa_raw(pgp_privkey, &m, &e, &d, &p, &q, &u); + ret = gnutls_openpgp_privkey_export_rsa_raw(*pgp_privkey, &m, &e, &d, &p, &q, &u); if (GNUTLS_E_SUCCESS != ret) { err ("failed to export RSA key parameters (error: %d)\n", ret); return 1; @@ -94,7 +74,7 @@ int convert_pgp_to_x509(gnutls_x509_privkey_t* output, gnutls_datum_t* input) { } } else if (pgp_algo == GNUTLS_PK_DSA) { err("OpenPGP DSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_dsa_raw(pgp_privkey, &p, &q, &g, &y, &x); + ret = gnutls_openpgp_privkey_export_dsa_raw(*pgp_privkey, &p, &q, &g, &y, &x); if (GNUTLS_E_SUCCESS != ret) { err ("failed to export DSA key parameters (error: %d)\n", ret); return 1; @@ -116,10 +96,190 @@ int convert_pgp_to_x509(gnutls_x509_privkey_t* output, gnutls_datum_t* input) { return 1; } - gnutls_openpgp_privkey_deinit(pgp_privkey); return 0; } +/* FIXME: keyid should be const also */ +int emit_public_openssh_from_pgp(const gnutls_openpgp_crt_t* pgp_crt, gnutls_openpgp_keyid_t* keyid) { + gnutls_openpgp_keyid_t curkeyid; + int ret; + int subkeyidx; + int subkeycount; + int found = 0; + gnutls_datum_t m, e, p, q, g, y, algolabel; + unsigned int bits; + gnutls_pk_algorithm_t algo; + const gnutls_datum_t* all[5]; + const char* algoname; + int mpicount; + /* output_data must be at least 2 chars longer than the maximum possible + algorithm name: */ + char output_data[20]; + + /* variables for the output conversion: */ + int pipestatus; + int pipefd, child_pid; + char* const b64args[] = {"/usr/bin/base64", "--wrap=0", NULL}; + + init_datum(&m); + init_datum(&e); + init_datum(&p); + init_datum(&q); + init_datum(&g); + init_datum(&algolabel); + + + /* figure out if we've got the right thing: */ + subkeycount = gnutls_openpgp_crt_get_subkey_count(*pgp_crt); + if (subkeycount < 0) { + err("Could not determine subkey count (got value %d)\n", subkeycount); + return 1; + } + + if ((keyid == NULL) && + (subkeycount > 0)) { + err("No keyid passed in, but there were %d keys to choose from\n", subkeycount + 1); + return 1; + } + + if (keyid != NULL) { + ret = gnutls_openpgp_crt_get_key_id(*pgp_crt, curkeyid); + if (ret) { + err("Could not get keyid (error: %d)\n", ret); + return 1; + } + } + if ((keyid == NULL) || (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0)) { + /* we want to export the primary key: */ + err("exporting primary key\n"); + + /* FIXME: this is almost identical to the block below for subkeys. + This clumsiness seems inherent in the gnutls OpenPGP API, + though. ugh. */ + algo = gnutls_openpgp_crt_get_pk_algorithm(*pgp_crt, &bits); + if (algo < 0) { + err("failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); + return algo; + } else if (algo == GNUTLS_PK_RSA) { + err("OpenPGP RSA certificate, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_pk_rsa_raw(*pgp_crt, &m, &e); + if (GNUTLS_E_SUCCESS != ret) { + err ("failed to export RSA key parameters (error: %d)\n", ret); + return 1; + } + } else if (algo == GNUTLS_PK_DSA) { + err("OpenPGP DSA Key, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_pk_dsa_raw(*pgp_crt, &p, &q, &g, &y); + if (GNUTLS_E_SUCCESS != ret) { + err ("failed to export DSA key parameters (error: %d)\n", ret); + return 1; + } + } + found = 1; + + } else { + /* lets trawl through the subkeys until we find the one we want: */ + for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) { + ret = gnutls_openpgp_crt_get_subkey_id(*pgp_crt, subkeyidx, curkeyid); + if (ret) { + err("Could not get keyid of subkey with index %d (error: %d)\n", subkeyidx, ret); + return 1; + } + if (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0) { + err("exporting subkey index %d\n", subkeyidx); + + /* FIXME: this is almost identical to the block above for the + primary key. */ + algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(*pgp_crt, subkeyidx, &bits); + if (algo < 0) { + err("failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); + return algo; + } else if (algo == GNUTLS_PK_RSA) { + err("OpenPGP RSA certificate, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(*pgp_crt, subkeyidx, &m, &e); + if (GNUTLS_E_SUCCESS != ret) { + err ("failed to export RSA key parameters (error: %d)\n", ret); + return 1; + } + } else if (algo == GNUTLS_PK_DSA) { + err("OpenPGP DSA Key, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(*pgp_crt, subkeyidx, &p, &q, &g, &y); + if (GNUTLS_E_SUCCESS != ret) { + err ("failed to export DSA key parameters (error: %d)\n", ret); + return 1; + } + } + found = 1; + + } + } + } + + if (!found) { + err("Could not find key in input\n"); + return 1; + } + + /* if we made it this far, we've got MPIs, and we've got the + algorithm, so we just need to emit the info */ + if (algo == GNUTLS_PK_RSA) { + algoname = "ssh-rsa"; + mpicount = 3; + + all[0] = &algolabel; + all[1] = &e; + all[2] = &m; + } else if (algo == GNUTLS_PK_DSA) { + algoname = "ssh-dss"; + mpicount = 5; + + all[0] = &algolabel; + all[1] = &p; + all[2] = &q; + all[3] = &g; + all[4] = &y; + } else { + err("Key algorithm was neither DSA nor RSA (it was %d). Can't deal. Sorry!\n", algo); + return 1; + } + + if (ret = datum_from_string(&algolabel, algoname), ret) { + err("couldn't label string (error: %d)\n", ret); + return ret; + } + + snprintf(output_data, sizeof(output_data), "%s ", algoname); + + pipefd = create_writing_pipe(&child_pid, b64args[0], b64args); + if (pipefd < 0) { + err("failed to create a writing pipe (returned %d)\n", pipefd); + return pipefd; + } + + write(1, output_data, strlen(output_data)); + + if (0 != write_data_fd_with_length(pipefd, all, mpicount)) { + err("was not able to write out RSA key data\n"); + return 1; + } + close(pipefd); + if (child_pid != waitpid(child_pid, &pipestatus, 0)) { + err("could not wait for child process to return for some reason.\n"); + return 1; + } + if (pipestatus != 0) { + err("base64 pipe died with return code %d\n", pipestatus); + return pipestatus; + } + + write(1, "\n", 1); + + + return 0; +} + + + int convert_x509_to_pgp(gnutls_openpgp_privkey_t* output, gnutls_datum_t* input) { gnutls_x509_privkey_t x509_privkey; gnutls_datum_t m, e, d, p, q, u, g, y, x; @@ -203,69 +363,88 @@ int main(int argc, char* argv[]) { gnutls_datum_t data; int ret; gnutls_x509_privkey_t x509_privkey; + gnutls_openpgp_privkey_t pgp_privkey; + gnutls_openpgp_crt_t pgp_crt; char output_data[10240]; size_t ods = sizeof(output_data); + + gnutls_openpgp_keyid_t keyid; + gnutls_openpgp_keyid_t* use_keyid; init_gnutls(); + + /* figure out what keyid we should be looking for: */ + use_keyid = NULL; + if (argv[1] != NULL) { + ret = convert_string_to_keyid(keyid, argv[1]); + if (ret != 0) + return ret; + use_keyid = &keyid; + } + init_datum(&data); - /* slurp in the private key from stdin */ + /* slurp in the key from stdin */ if (ret = set_datum_fd(&data, 0), ret) { err("didn't read file descriptor 0\n"); return 1; } - - /* Or, instead, read in key from a file name: - if (ret = set_datum_file(&data, argv[1]), ret) { - err("didn't read file '%s'\n", argv[1]); + if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) { + err("Failed to initialized OpenPGP private key (error: %d)\n", ret); return 1; } -*/ - - /* treat the passed file as an X.509 private key, and extract its - component values: */ - -/* if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) { */ -/* err("Failed to import the X.509 key (error: %d)\n", ret); */ -/* return 1; */ -/* } */ -/* gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); */ - - /* try to print the PEM-encoded private key: */ -/* ret = gnutls_x509_privkey_export (x509_privkey, */ -/* GNUTLS_X509_FMT_PEM, */ -/* output_data, */ -/* &ods); */ -/* printf("ret: %u; ods: %u;\n", ret, ods); */ -/* if (ret == 0) { */ -/* write(0, output_data, ods); */ -/* } */ - - - if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { - err("Failed to initialize X.509 private key (error: %d)\n", ret); - return 1; + /* check whether it's a private key or a public key, by trying them: */ + if ((gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0) == 0) || + (gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0) == 0)) { + /* we're dealing with a private key */ + err("Translating private key\n"); + if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { + err("Failed to initialize X.509 private key for output (error: %d)\n", ret); + return 1; + } + + ret = convert_private_pgp_to_x509(&x509_privkey, &pgp_privkey, use_keyid); + + gnutls_openpgp_privkey_deinit(pgp_privkey); + if (ret) + return ret; + + ret = gnutls_x509_privkey_export (x509_privkey, + GNUTLS_X509_FMT_PEM, + output_data, + &ods); + if (ret == 0) { + write(1, output_data, ods); + } + gnutls_x509_privkey_deinit(x509_privkey); + + } else { + if (ret = gnutls_openpgp_crt_init(&pgp_crt), ret) { + err("Failed to initialized OpenPGP certificate (error: %d)\n", ret); + return 1; + } + + if ((gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW) == 0) || + (gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64) == 0)) { + /* we're dealing with a public key */ + err("Translating public key\n"); + + ret = emit_public_openssh_from_pgp(&pgp_crt, use_keyid); + + } else { + /* we have no idea what kind of key this is at all anyway! */ + err("Input does contain any form of OpenPGP key I recognize."); + return 1; + } } - if (ret = convert_pgp_to_x509(&x509_privkey, &data), ret) { - return ret; - } - ret = gnutls_x509_privkey_export (x509_privkey, - GNUTLS_X509_FMT_PEM, - output_data, - &ods); - printf("ret: %u; ods: %u;\n", ret, ods); - if (ret == 0) { - write(1, output_data, ods); - } - gnutls_x509_privkey_deinit(x509_privkey); gnutls_global_deinit(); return 0; } -- 2.25.1