From da3be98515423903e3dde1e3c287498f90c1147f Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Sat, 21 Jul 2007 12:57:50 +0000 Subject: [PATCH] Added SILC SSH2 library providing support for SSH2 public and private keys. --- lib/silcssh/DIRECTORY | 32 + lib/silcssh/Makefile.ad | 30 + lib/silcssh/silcssh.c | 429 ++++++++++++ lib/silcssh/silcssh.h | 225 +++++++ lib/silcssh/silcssh_i.h | 32 + lib/silcssh/silcssh_pkcs.c | 1087 ++++++++++++++++++++++++++++++ lib/silcssh/silcssh_pkcs.h | 49 ++ lib/silcssh/tests/Makefile.am | 28 + lib/silcssh/tests/test_silcssh.c | 76 +++ 9 files changed, 1988 insertions(+) create mode 100644 lib/silcssh/DIRECTORY create mode 100644 lib/silcssh/Makefile.ad create mode 100644 lib/silcssh/silcssh.c create mode 100644 lib/silcssh/silcssh.h create mode 100644 lib/silcssh/silcssh_i.h create mode 100644 lib/silcssh/silcssh_pkcs.c create mode 100644 lib/silcssh/silcssh_pkcs.h create mode 100644 lib/silcssh/tests/Makefile.am create mode 100644 lib/silcssh/tests/test_silcssh.c diff --git a/lib/silcssh/DIRECTORY b/lib/silcssh/DIRECTORY new file mode 100644 index 00000000..dc5b5e74 --- /dev/null +++ b/lib/silcssh/DIRECTORY @@ -0,0 +1,32 @@ + + +SILC SSH2 Library +
+Directory: lib/silcssh/ +
+Library: libsilc.a, libsilc.lib +

+Introduction + +

+SILC SSH2 Library provides SSH2 public key and private key support for +applications. The SILC SSH Library has been integrated to the SILC Crypto +Toolkit allowing easy use of the SSH keys through the generic SILC PKCS +API. The library also provides an interface for low level API to directly +manipulate the SSH keys. + +The library supports creation of new SSH2 key pairs, encryption, +decryption, signatures and verification. Both RSA and DSS SSH2 keys are +supported. The library supports the standard SSH2 public key file format +defined in RFC 4716 and the OpenSSH public key file format. The private +key file format support includes OpenSSH private key files. + +The SILC SSH2 Library does not include any other aspect of SSH2 protocol. +Only the SSH2 public keys and private keys are supported. + +

+@LINKS@ diff --git a/lib/silcssh/Makefile.ad b/lib/silcssh/Makefile.ad new file mode 100644 index 00000000..c439bdd9 --- /dev/null +++ b/lib/silcssh/Makefile.ad @@ -0,0 +1,30 @@ +# +# Makefile.ad +# +# Author: Pekka Riikonen +# +# Copyright (C) 2007 Pekka Riikonen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign + +noinst_LTLIBRARIES = libsilcssh.la + +libsilcssh_la_SOURCES = silcssh.c silcssh_pkcs.c + +#ifdef SILC_DIST_TOOLKIT +include_HEADERS = silcssh.h silcssh_i.h +#endif SILC_DIST_TOOLKIT + +EXTRA_DIST = *.h $(SILC_EXTRA_DIST) + +include $(top_srcdir)/Makefile.defines.in diff --git a/lib/silcssh/silcssh.c b/lib/silcssh/silcssh.c new file mode 100644 index 00000000..0e4f606b --- /dev/null +++ b/lib/silcssh/silcssh.c @@ -0,0 +1,429 @@ +/* + + silcssh.c + + Author: Pekka Riikonen + + Copyright (C) 2007 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +#include "silc.h" + +/************************* Static utility functions *************************/ + +/* Key fields destructor */ + +static void silc_ssh_field_dest(void *key, void *context, void *user_context) +{ + silc_free(key); + silc_free(context); +} + +/* Parse header line from key. Doesn't return the line termination + characters. */ + +SilcBool silc_ssh_parse_line(SilcBuffer key, SilcBuffer line, + SilcBool cont) +{ + char *tmp; + int i, data_len; + SilcBool valid = cont; + + data_len = silc_buffer_len(key); + tmp = silc_buffer_data(key); + for (i = 0; i < data_len; i++) { + /* All header lines must have ':' character */ + if (!cont && tmp[i] == ':') + valid = TRUE; + + if ((data_len - i >= 1 && tmp[i] == '\r') || + (data_len - i >= 1 && tmp[i] == '\n')) { + + if (!valid) + return FALSE; + + if (line) + silc_buffer_set(line, tmp, i); + + if (data_len - i >= 2 && tmp[i] == '\r' && tmp[i + 1] == '\n') + silc_buffer_pull(key, i + 2); + else + silc_buffer_pull(key, i + 1); + + return TRUE; + } + } + + return FALSE; +} + +/* Allocate fields hash table */ + +SilcHashTable silc_ssh_allocate_fields(void) +{ + return silc_hash_table_alloc(NULL, 0, silc_hash_string, NULL, + silc_hash_string_compare, NULL, + silc_ssh_field_dest, NULL, TRUE); +} + +/* Parse key headers and return them into a hash table */ + +SilcHashTable silc_ssh_parse_headers(SilcBuffer key) +{ + SilcHashTable fields; + unsigned char *field, *value; + SilcBufferStruct line, v; + SilcBool quoted = FALSE; + + SILC_LOG_DEBUG(("Parsing SSH key headers")); + + fields = silc_ssh_allocate_fields(); + if (!fields) + return NULL; + + /* Parse the fields */ + while (silc_buffer_len(key) > 0) { + if (!silc_ssh_parse_line(key, &line, FALSE)) + break; + + /* Get field */ + + field = strchr(silc_buffer_data(&line), ':'); + if (!field) + goto err; + if (field - silc_buffer_data(&line) > 64) + goto err; + field = silc_memdup(silc_buffer_data(&line), + field - silc_buffer_data(&line)); + if (!field) + goto err; + + /* Skip ':' and following whitespace */ + if (!silc_buffer_pull(&line, strlen(field) + 2)) + goto err; + + /* Get value */ + + memset(&v, 0, sizeof(v)); + silc_buffer_format(&v, + SILC_STR_DATA(silc_buffer_data(&line), + silc_buffer_len(&line)), + SILC_STR_END); + + /* Handle quoted Comment lines by removing the quotation */ + if (*silc_buffer_data(&v) == '"' && !strcmp(field, "Comment")) + quoted = TRUE; + + /* Handle wrapping value lines */ + while (silc_buffer_len(&v) > 0) { + if (*silc_buffer_data(&v) == '\\') { + if (!silc_ssh_parse_line(key, &line, TRUE)) + goto err; + silc_buffer_format(&v, + SILC_STR_DATA(silc_buffer_data(&line), + silc_buffer_len(&line)), + SILC_STR_END); + continue; + } + silc_buffer_pull(&v, 1); + } + silc_buffer_start(&v); + + if (silc_buffer_len(&v) > 1024) + goto err; + + if (quoted) { + /* If the last character is quotation also, remove the quotation */ + if (*(silc_buffer_data(&v) + silc_buffer_len(&v) - 1) == '"') { + silc_buffer_pull(&v, 1); + silc_buffer_push_tail(&v, 1); + } + } + + value = silc_memdup(silc_buffer_data(&v), silc_buffer_len(&v)); + if (!value) + goto err; + silc_buffer_purge(&v); + + /* Add to hash table */ + SILC_LOG_DEBUG(("Header '%s' '%s'", field, value)); + silc_hash_table_add(fields, field, value); + } + + return fields; + + err: + SILC_LOG_ERROR(("Malformed SSH2 key headers")); + silc_hash_table_free(fields); + return NULL; +} + +/******************************* SILC SSH API *******************************/ + +/* Generate key pair */ + +SilcBool silc_ssh_generate_key(const char *algorithm, + int bits_len, SilcRng rng, + SilcPublicKey *ret_public_key, + SilcPrivateKey *ret_private_key) +{ + SilcSshPublicKey pubkey; + SilcSshPrivateKey privkey; + const SilcPKCSAlgorithm *alg; + const SilcPKCSObject *pkcs; + + SILC_LOG_DEBUG(("Generating SSH2 %s key pair with key length %d bits", + algorithm, bits_len)); + + if (!rng) + return FALSE; + + pkcs = silc_pkcs_find_pkcs(SILC_PKCS_SSH2); + if (!pkcs) + return FALSE; + + /* Allocate SSH public key */ + pubkey = silc_calloc(1, sizeof(*pubkey)); + if (!pubkey) + return FALSE; + + /* Allocate algorithm */ + alg = silc_pkcs_find_algorithm(algorithm, "ssh"); + if (!alg) { + SILC_LOG_ERROR(("Public key algorithm %s/ssh not supported", algorithm)); + silc_free(pubkey); + return FALSE; + } + pubkey->pkcs = alg; + pubkey->type = SILC_SSH_KEY_OPENSSH; + + /* Allocate SSH private key */ + privkey = silc_calloc(1, sizeof(*privkey)); + if (!privkey) { + silc_free(pubkey); + return FALSE; + } + privkey->pkcs = alg; + privkey->type = SILC_SSH_KEY_OPENSSH; + + /* Allocate public key */ + *ret_public_key = silc_calloc(1, sizeof(**ret_public_key)); + if (!(*ret_public_key)) { + silc_free(pubkey); + silc_free(privkey); + return FALSE; + } + (*ret_public_key)->pkcs = (SilcPKCSObject *)pkcs; + (*ret_public_key)->alg = alg; + (*ret_public_key)->public_key = pubkey; + + /* Allocate private key */ + *ret_private_key = silc_calloc(1, sizeof(**ret_private_key)); + if (!(*ret_private_key)) { + silc_free(pubkey); + silc_free(privkey); + silc_free(*ret_public_key); + return FALSE; + } + (*ret_private_key)->pkcs = (SilcPKCSObject *)pkcs; + (*ret_private_key)->alg = alg; + (*ret_private_key)->private_key = privkey; + + /* Generate the algorithm key pair */ + if (!alg->generate_key(alg, bits_len, rng, &pubkey->public_key, + &privkey->private_key)) { + silc_free(pubkey); + silc_free(privkey); + silc_free(*ret_public_key); + silc_free(*ret_private_key); + return FALSE; + } + + return TRUE; +} + +/* Decode SSH public key. */ + +int silc_ssh_public_key_decode(unsigned char *key, SilcUInt32 key_len, + SilcSshPublicKey *ret_public_key) +{ + SilcSshPublicKey public_key; + const SilcPKCSAlgorithm *alg; + SilcBufferStruct keybuf; + char *type = NULL; + + SILC_LOG_DEBUG(("Parse SSH2 public key")); + + if (!ret_public_key) + return 0; + + public_key = silc_calloc(1, sizeof(*public_key)); + if (!public_key) + return 0; + + silc_buffer_set(&keybuf, key, key_len); + + SILC_LOG_HEXDUMP(("SSH public key, len %d", key_len), key, key_len); + + /* Parse public key type */ + if (silc_buffer_unformat(&keybuf, + SILC_STR_ADVANCE, + SILC_STR_UI32_STRING_ALLOC(&type), + SILC_STR_END) < 0) { + SILC_LOG_ERROR(("Malformed SSH2 public key")); + goto err; + } + + SILC_LOG_DEBUG(("SSH2 public key type %s", type)); + + if (!strcmp(type, "ssh-rsa")) { + /* RSA public key */ + alg = silc_pkcs_find_algorithm("rsa", "ssh"); + if (!alg) { + SILC_LOG_ERROR(("Unsupported SSH2 public key type '%s'", type)); + goto err; + } + public_key->pkcs = alg; + + } else if (!strcmp(type, "ssh-dss")) { + /* DSS public key */ + alg = silc_pkcs_find_algorithm("dsa", "ssh"); + if (!alg) { + SILC_LOG_ERROR(("Unsupported SSH2 public key type '%s'", type)); + goto err; + } + public_key->pkcs = alg; + + } else { + SILC_LOG_ERROR(("Unsupported SSH2 public key type '%s'", type)); + goto err; + } + + /* Parse the algorithm specific public key */ + if (!alg->import_public_key(alg, silc_buffer_data(&keybuf), + silc_buffer_len(&keybuf), + &public_key->public_key)) + goto err; + + silc_free(type); + + *ret_public_key = public_key; + + return key_len; + + err: + silc_free(type); + silc_free(public_key); + return 0; +} + +/* Encode SSH public key */ + +unsigned char *silc_ssh_public_key_encode(SilcStack stack, + SilcSshPublicKey public_key, + SilcUInt32 *ret_key_len) +{ + const SilcPKCSAlgorithm *alg = public_key->pkcs; + SilcBufferStruct buf; + unsigned char *pk = NULL, tmp[16]; + SilcUInt32 pk_len; + + SILC_LOG_DEBUG(("Encode SSH2 public key")); + + /* Get algorithm name */ + if (!strcmp(alg->name, "rsa")) + silc_snprintf(tmp, sizeof(tmp), "ssh-rsa"); + else if (!strcmp(alg->name, "dsa")) + silc_snprintf(tmp, sizeof(tmp), "ssh-dss"); + else + return NULL; + + /* Export PKCS algorithm public key */ + if (alg->export_public_key) + pk = alg->export_public_key(alg, stack, public_key->public_key, &pk_len); + if (!pk) { + SILC_LOG_ERROR(("Error exporting PKCS algorithm key")); + return NULL; + } + + /* Encode public key */ + memset(&buf, 0, sizeof(buf)); + if (silc_buffer_sformat(stack, &buf, + SILC_STR_UI_INT(strlen(tmp)), + SILC_STR_UI32_STRING(tmp), + SILC_STR_UI_XNSTRING(pk, pk_len), + SILC_STR_END) < 0) { + silc_sfree(stack, pk); + return NULL; + } + + silc_sfree(stack, pk); + pk = silc_buffer_steal(&buf, ret_key_len); + + return pk; +} + +/* Free public key */ + +void silc_ssh_public_key_free(SilcSshPublicKey public_key) +{ + if (public_key->fields) + silc_hash_table_free(public_key->fields); + silc_free(public_key); +} + +/* Return public key header field value */ + +const char *silc_ssh_public_key_get_field(SilcSshPublicKey public_key, + const char *field) +{ + char *value; + + if (!field || !public_key->fields) + return NULL; + + if (!silc_hash_table_find(public_key->fields, (void *)field, + NULL, (void *)&value)) + return NULL; + + return (const char *)value; +} + +/* Add public key header value */ + +SilcBool silc_ssh_public_key_add_field(SilcSshPublicKey public_key, + const char *field, + const char *value) +{ + if (!field || !value) + return FALSE; + + if (!public_key->fields) { + public_key->fields = + silc_hash_table_alloc(NULL, 0, silc_hash_string, NULL, + silc_hash_string_compare, NULL, + silc_ssh_field_dest, NULL, TRUE); + if (!public_key->fields) + return FALSE; + } + + return silc_hash_table_add(public_key->fields, strdup(field), strdup(value)); +} + +/* Set public key type */ + +void silc_ssh_public_key_set_type(SilcSshPublicKey public_key, + SilcSshKeyType type) +{ + public_key->type = type; +} diff --git a/lib/silcssh/silcssh.h b/lib/silcssh/silcssh.h new file mode 100644 index 00000000..68848943 --- /dev/null +++ b/lib/silcssh/silcssh.h @@ -0,0 +1,225 @@ +/* + + silcssh.h + + Author: Pekka Riikonen + + Copyright (C) 2007 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +/****h* silcssh/SILC SSH Interface + * + * DESCRIPTION + * + * SILC SSH Library provides SSH2 public key and private key support for + * applications. The SILC SSH Library has been integrated to the SILC Crypto + * Toolkit allowing easy use of the SSH keys through the SILC PKCS API. The + * interface provides also a low level API to directly manipulate the SSH + * keys. + * + * The library supports creation of new SSH2 key pairs, encryption, decryption, + * signatures and verification. Both RSA and DSS SSH2 keys are supported. + * The library supports the standard SSH2 public key file format defined + * in RFC 4716 and the OpenSSH public key file format. The private key file + * format support includes OpenSSH private key files. + * + * EXAMPLE + * + * SilcPublicKey public_key; + * SilcPrivateKey private_key; + * SilcSshPublicKey ssh_pubkey; + * SilcSshPrivateKey ssh_privkey; + * + * // Generate new SSH2 key pair, RSA algorithm, 2048 bits + * silc_ssh_generate_key("rsa", 2048, rng, &public_key, &private_key); + * + * // Add (optional) headers to the key before saving to a file + * ssh_pubkey = silc_pkcs_public_key_get_pkcs(SILC_PKCS_SSH2, public_key); + * silc_ssh_public_key_set_type(ssh_pubkey, SILC_SSH_KEY_SSH2); + * silc_ssh_public_key_add_field(ssh_pubkey, "Subject", "foo@example.com"); + * silc_ssh_public_key_add_field(ssh_pubkey, "Comment", "My own key"); + * + * // Rest of the operations use standard SILC PKCS API + * + * // Save new key pair to file + * silc_pkcs_save_public_key("pubkey.pub", public_key, SILC_PKCS_FILE_BASE64); + * silc_pkcs_save_private_key("privkey.pub", private_key, passphrase, + * passphrase_len, SILC_PKCS_FILE_BASE64, rng); + * + * // Load SSH2 key pair + * silc_pkcs_load_public_key("pubkey.pub", SILC_PKCS_SSH2, &public_key); + * silc_pkcs_load_private_key("privkey.pub", passphrase, passphrase_len, + * SILC_PKCS_SSH2, &public_key); + * + * // Compute signature + * silc_pkcs_sign(private_key, src, src_len, TRUE, sha1, sign_cb, ctx); + * + ***/ +#ifndef SILCSSH_H +#define SILCSSH_H + +typedef enum { + SILC_SSH_KEY_OPENSSH = 1, /* OpenSSH public/private key (default) */ + SILC_SSH_KEY_SSH2 = 2, /* SSH2 public key, RFC 4716 */ +} SilcSshKeyType; + +typedef struct SilcSshPublicKeyStruct { + SilcHashTable fields; /* Public key headers */ + const SilcPKCSAlgorithm *pkcs; /* PKCS Algorithm */ + void *public_key; /* PKCS Algorithm specific public key */ + SilcSshKeyType type; /* Public key type */ +} *SilcSshPublicKey; + +typedef struct SilcSshPrivateKeyStruct { + SilcHashTable fields; /* Private key headers */ + const SilcPKCSAlgorithm *pkcs; /* PKCS Algorithm */ + void *private_key; /* PKCS Algorithm specific private key */ + SilcSshKeyType type; /* Private key type */ +} *SilcSshPrivateKey; + +/****f* silcssh/SilcSshAPI/silc_ssh_generate_key + * + * SYNOPSIS + * + * SilcBool silc_ssh_generate_key(const char *algorithm, + * int bits_len, SilcRng rng, + * SilcPublicKey *ret_public_key, + * SilcPrivateKey *ret_private_key); + * + * DESCRIPTION + * + * Generates new SSH2 key pair. The `algorithm' is either rsa or dsa. + * The `bits_len' specify the key length in bits. Returns FALSE on error. + * + ***/ +SilcBool silc_ssh_generate_key(const char *algorithm, + int bits_len, SilcRng rng, + SilcPublicKey *ret_public_key, + SilcPrivateKey *ret_private_key); + +/****f* silcssh/SilcSshAPI/silc_ssh_public_key_decode + * + * SYNOPSIS + * + * int silc_ssh_public_key_decode(unsigned char *key, SilcUInt32 key_len, + * SilcSshPublicKey *ret_public_key); + * + * DESCRIPTION + * + * Decodes SSH Public Key indicated by `key' of length of `key_len' + * bytes. The decoded public key is returned into the `ret_public_key' + * which the caller must free by calling the silc_ssh_public_key_free + * function. This function expects the public key to be in raw binary + * format, without any public key file markers or headers. + * + * This function returns the number of bytes decoded from the public + * key buffer or 0 on error. + * + ***/ +int silc_ssh_public_key_decode(unsigned char *key, SilcUInt32 key_len, + SilcSshPublicKey *ret_public_key); + +/****f* silcssh/SilcSshAPI/silc_ssh_public_key_encode + * + * SYNOPSIS + * + * unsigned char *silc_ssh_public_key_encode(SilcStack stack, + * SilcSshPublicKey public_key, + * SilcUInt32 *ret_key_len); + * + * DESCRIPTION + * + * Encodes SSH Public key and returns the encoded buffer. Caller must + * free the returned buffer. + * + * If the `stack' is non-NULL the returned buffer is allocated from the + * `stack'. This call will consume `stack' so caller should push the stack + * before calling and then later pop it. + * + ***/ +unsigned char *silc_ssh_public_key_encode(SilcStack stack, + SilcSshPublicKey public_key, + SilcUInt32 *ret_key_len); + +/****f* silcssh/SilcSshAPI/silc_ssh_public_key_free + * + * SYNOPSIS + * + * void silc_ssh_public_key_free(SilcSshPublicKey public_key); + * + * DESCRIPTION + * + * Frees the public key. + * + ***/ +void silc_ssh_public_key_free(SilcSshPublicKey public_key); + +/****f* silcssh/SilcSshAPI/silc_ssh_public_key_get_field + * + * SYNOPSIS + * + * const char *silc_ssh_public_key_get_field(SilcSshPublicKey public_key, + * const char *field); + * + * DESCRIPTION + * + * Returns public key header field `field' value from the public key or + * NULL if such header field was not present in the public key. + * + * EXAMPLE + * + * subject = silc_ssh_public_key_get_field(public_key, "Subject"); + * comment = silc_ssh_public_key_get_field(public_key, "Comment"); + * + ***/ +const char *silc_ssh_public_key_get_field(SilcSshPublicKey public_key, + const char *field); + +/****f* silcssh/SilcSshAPI/silc_ssh_public_key_add_field + * + * SYNOPSIS + * + * SilcBool silc_ssh_public_key_add_field(SilcSshPublicKey public_key, + * const char *field, + * const char *value); + * + * DESCRIPTION + * + * Add new public key header field and value to public key. Returns + * FALSE if field could not be added or has been added already. + * + ***/ +SilcBool silc_ssh_public_key_add_field(SilcSshPublicKey public_key, + const char *field, + const char *value); + +/****f* silcssh/SilcSshAPI/silc_ssh_public_key_set_type + * + * SYNOPSIS + * + * void silc_ssh_public_key_set_type(SilcSshPublicKey public_key, + * SilcSshKeyType type); + * + * DESCRIPTION + * + * Set the type of the SSH public key. This affects the format of the + * public key file when `public_key' is saved to a file. If this is + * not called the default type is always SILC_SSH_KEY_OPENSSH. + * + ***/ +void silc_ssh_public_key_set_type(SilcSshPublicKey public_key, + SilcSshKeyType type); + +#include "silcssh_i.h" + +#endif /* SILCSSH_H */ diff --git a/lib/silcssh/silcssh_i.h b/lib/silcssh/silcssh_i.h new file mode 100644 index 00000000..afca335f --- /dev/null +++ b/lib/silcssh/silcssh_i.h @@ -0,0 +1,32 @@ +/* + + silcssh_i.h + + Author: Pekka Riikonen + + Copyright (C) 2007 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +#ifndef SILCSSH_I_H +#define SILCSSH_I_H + +#ifndef SILCSSH_H +#error "Do not include this header directly" +#endif + +SilcHashTable silc_ssh_allocate_fields(void); +SilcBool silc_ssh_parse_line(SilcBuffer key, SilcBuffer line, + SilcBool cont); +SilcHashTable silc_ssh_parse_headers(SilcBuffer key); + +#endif /* SILCSSH_I_H */ diff --git a/lib/silcssh/silcssh_pkcs.c b/lib/silcssh/silcssh_pkcs.c new file mode 100644 index 00000000..1821709e --- /dev/null +++ b/lib/silcssh/silcssh_pkcs.c @@ -0,0 +1,1087 @@ +/* + + silcssh_pkcs.c + + Author: Pekka Riikonen + + Copyright (C) 2007 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +#include "silc.h" +#include "rsa.h" +#include "dsa.h" +#include "silcssh_pkcs.h" + +/************************** Types and definitions ***************************/ + +/* RFC 4716 public key file markers */ +#define SILC_SSH_PUBLIC_KEY_BEGIN "---- BEGIN SSH2 PUBLIC KEY ----" +#define SILC_SSH_PUBLIC_KEY_END "---- END SSH2 PUBLIC KEY ----" + +/* OpenSSH private key file markers */ +#define SILC_SSH_RSA_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define SILC_SSH_RSA_END "-----END RSA PRIVATE KEY-----" +#define SILC_SSH_DSA_BEGIN "-----BEGIN DSA PRIVATE KEY-----" +#define SILC_SSH_DSA_END "-----END DSA PRIVATE KEY-----" + +/****************************** SSH2 PKCS API *******************************/ + +/* Get algorithm context */ + +SILC_PKCS_GET_ALGORITHM(silc_pkcs_ssh_get_algorithm) +{ + SilcSshPublicKey pubkey = public_key; + return pubkey->pkcs; +} + +/* Import public key file */ + +SILC_PKCS_IMPORT_PUBLIC_KEY_FILE(silc_pkcs_ssh_import_public_key_file) +{ + SilcSshPublicKey pubkey; + SilcBufferStruct keybuf, line; + SilcHashTable fields; + unsigned char *data; + SilcSshKeyType type; + int ret; + + SILC_LOG_DEBUG(("Parsing SSH2 public key file")); + + if (!ret_public_key) + return FALSE; + if (encoding == SILC_PKCS_FILE_BIN) + return FALSE; + + silc_buffer_set(&keybuf, filedata, filedata_len); + + /* Check for RFC 4716 style public key markers */ + if (!silc_ssh_parse_line(&keybuf, &line, TRUE)) { + SILC_LOG_DEBUG(("Malformed SSH2 public key markers")); + return FALSE; + } + if ((silc_buffer_len(&keybuf) < (strlen(SILC_SSH_PUBLIC_KEY_BEGIN) + + strlen(SILC_SSH_PUBLIC_KEY_END))) || + strncmp(silc_buffer_data(&line), SILC_SSH_PUBLIC_KEY_BEGIN, + silc_buffer_len(&line))) { + /* We assume the key is OpenSSH style public key. */ + type = SILC_SSH_KEY_OPENSSH; + silc_buffer_set(&keybuf, filedata, filedata_len); + + /* Get subject name from the end of the file */ + if (!silc_buffer_strchr(&keybuf, ' ', FALSE)) { + SILC_LOG_DEBUG(("Malformed SSH2 public key")); + return FALSE; + } + if (!silc_buffer_pull(&keybuf, 1)) { + SILC_LOG_DEBUG(("Malformed SSH2 public key")); + return FALSE; + } + if (!silc_buffer_len(&keybuf)) { + SILC_LOG_DEBUG(("Malformed SSH2 public key")); + return FALSE; + } + + /* Add subject name to public key headers */ + fields = silc_ssh_allocate_fields(); + if (!fields) + return FALSE; + silc_hash_table_add(fields, strdup("Subject"), + silc_memdup(silc_buffer_data(&keybuf), + silc_buffer_len(&keybuf))); + + filedata_len = silc_buffer_headlen(&keybuf) - 1; + SILC_LOG_DEBUG(("Add Subject header to public key")); + + /* Skip algorithm name */ + silc_buffer_start(&keybuf); + if (!silc_buffer_strchr(&keybuf, ' ', TRUE)) { + SILC_LOG_DEBUG(("Malformed SSH2 public key")); + silc_hash_table_free(fields); + return FALSE; + } + if (!silc_buffer_pull(&keybuf, 1)) { + SILC_LOG_DEBUG(("Malformed SSH2 public key")); + silc_hash_table_free(fields); + return FALSE; + } + if (silc_buffer_len(&keybuf) < filedata_len) { + SILC_LOG_DEBUG(("Malformed SSH2 public key")); + silc_hash_table_free(fields); + return FALSE; + } + + filedata = silc_buffer_data(&keybuf); + SILC_LOG_DEBUG(("Public key is OpenSSH public key")); + + } else { + /* RFC 4716 style public key */ + type = SILC_SSH_KEY_SSH2; + + filedata = silc_buffer_data(&keybuf); + filedata_len = silc_buffer_len(&keybuf) - strlen(SILC_SSH_PUBLIC_KEY_END); + silc_buffer_set(&keybuf, filedata, filedata_len); + + /* Parse public key headers */ + fields = silc_ssh_parse_headers(&keybuf); + if (!fields) + return FALSE; + + filedata = silc_buffer_data(&keybuf); + filedata_len = silc_buffer_len(&keybuf); + + SILC_LOG_DEBUG(("Public key is standard SSH2 public key")); + } + + /* Decode */ + data = silc_base64_decode(NULL, filedata, filedata_len, &filedata_len); + if (!data) { + silc_hash_table_free(fields); + return FALSE; + } + filedata = data; + + /* Decode the public key */ + ret = silc_pkcs_ssh_import_public_key(pkcs, NULL, filedata, filedata_len, + (void *)&pubkey, ret_alg); + silc_free(data); + + if (ret) { + pubkey->fields = fields; + pubkey->type = type; + *ret_public_key = pubkey; + SILC_LOG_DEBUG(("SSH2 public key file imported successfully")); + return TRUE; + } + + silc_hash_table_free(fields); + return FALSE; +} + +/* Import public key */ + +SILC_PKCS_IMPORT_PUBLIC_KEY(silc_pkcs_ssh_import_public_key) +{ + SilcSshPublicKey pubkey; + int ret; + + ret = silc_ssh_public_key_decode(key, key_len, &pubkey); + if (ret) { + if (ret_alg) + *ret_alg = pubkey->pkcs; + if (ret_public_key) + *ret_public_key = pubkey; + } + + return ret; +} + +/* Export public key file */ + +SILC_PKCS_EXPORT_PUBLIC_KEY_FILE(silc_pkcs_ssh_export_public_key_file) +{ + SilcSshPublicKey pubkey = public_key; + SilcHashTableList htl; + SilcBufferStruct buf, fields; + unsigned char *key, *data; + SilcUInt32 key_len; + char *field, *value, tmp[1024], tmp2[1024 + 24]; + int i, j, c; + + SILC_LOG_DEBUG(("Encoding %s public key file", + pubkey->type == SILC_SSH_KEY_SSH2 ? "SSH2" : "OpenSSH")); + + /* Export key */ + key = silc_pkcs_ssh_export_public_key(pkcs, stack, pubkey, &key_len); + if (!key) + return NULL; + + /* Base64 encode the key data */ + if (pubkey->type == SILC_SSH_KEY_SSH2) + data = silc_base64_encode_file(stack, key, key_len); + else + data = silc_base64_encode(stack, key, key_len); + if (!data) + return NULL; + silc_sfree(stack, key); + key = data; + key_len = strlen(data); + + memset(&buf, 0, sizeof(buf)); + memset(&fields, 0, sizeof(fields)); + memset(tmp, 0, sizeof(tmp)); + memset(tmp2, 0, sizeof(tmp2)); + + switch (pubkey->type) { + case SILC_SSH_KEY_SSH2: + /* RFC 4716 style public key file */ + + if (pubkey->fields) { + /* Encode public key headers */ + silc_hash_table_list(pubkey->fields, &htl); + while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) { + /* Wrap lines with over 72 characters */ + silc_snprintf(tmp, sizeof(tmp), "%s: %s", field, value); + for (i = 0, j = 0, c = 1; i < strlen(tmp); i++, c++) { + if (c == 72) { + tmp2[j++] = '\\'; + tmp2[j++] = '\n'; + i--; + c = 0; + continue; + } + + tmp2[j++] = tmp[i]; + } + tmp2[j++] = '\n'; + + if (silc_buffer_sstrformat(stack, &fields, tmp2, SILC_STRFMT_END) < 0) { + silc_buffer_spurge(stack, &fields); + silc_sfree(stack, key); + return NULL; + } + + memset(tmp2, 0, sizeof(tmp2)); + } + silc_hash_table_list_reset(&htl); + } + + /* Encode the file */ + if (silc_buffer_sformat(stack, &buf, + SILC_STR_UI32_STRING(SILC_SSH_PUBLIC_KEY_BEGIN), + SILC_STR_UI32_STRING("\n"), + SILC_STR_UI_XNSTRING(silc_buffer_data(&fields), + silc_buffer_len(&fields)), + SILC_STR_UI_XNSTRING(key, key_len), + SILC_STR_UI32_STRING("\n"), + SILC_STR_UI32_STRING(SILC_SSH_PUBLIC_KEY_END), + SILC_STR_UI32_STRING("\n"), + SILC_STR_END) < 0) { + silc_buffer_spurge(stack, &fields); + silc_sfree(stack, key); + return NULL; + } + + break; + + case SILC_SSH_KEY_OPENSSH: + /* OpenSSH style public key file */ + + if (!strcmp(pubkey->pkcs->name, "rsa")) + silc_snprintf(tmp, sizeof(tmp), "ssh-rsa "); + else if (!strcmp(pubkey->pkcs->name, "dsa")) + silc_snprintf(tmp, sizeof(tmp), "ssh-dss "); + + /* Get subject */ + value = (char *)silc_ssh_public_key_get_field(pubkey, "Subject"); + + /* Encode the file */ + if (silc_buffer_sformat(stack, &buf, + SILC_STR_UI32_STRING(tmp), + SILC_STR_UI_XNSTRING(key, key_len), + SILC_STR_UI32_STRING(" "), + SILC_STR_UI32_STRING(value), + SILC_STR_UI32_STRING("\n"), + SILC_STR_END) < 0) { + silc_buffer_spurge(stack, &buf); + silc_sfree(stack, key); + return NULL; + } + + break; + + default: + silc_sfree(stack, key); + return NULL; + break; + } + + silc_sfree(stack, key); + key = silc_buffer_steal(&buf, ret_len); + + silc_buffer_spurge(stack, &fields); + + return key; +} + +/* Export public key */ + +SILC_PKCS_EXPORT_PUBLIC_KEY(silc_pkcs_ssh_export_public_key) +{ + return silc_ssh_public_key_encode(stack, public_key, ret_len); +} + +/* Return key length in bits */ + +SILC_PKCS_PUBLIC_KEY_BITLEN(silc_pkcs_ssh_public_key_bitlen) +{ + SilcSshPublicKey pubkey = public_key; + return pubkey->pkcs->public_key_bitlen(pubkey->pkcs, pubkey->public_key); +} + +/* Copy public key */ + +SILC_PKCS_PUBLIC_KEY_COPY(silc_pkcs_ssh_public_key_copy) +{ + SilcSshPublicKey pubkey = public_key, new_pubkey; + SilcHashTableList htl; + char *field, *value; + + new_pubkey = silc_calloc(1, sizeof(*new_pubkey)); + if (!new_pubkey) + return NULL; + new_pubkey->pkcs = pubkey->pkcs; + new_pubkey->type = pubkey->type; + + new_pubkey->public_key = + pubkey->pkcs->public_key_copy(pubkey->pkcs, pubkey->public_key); + if (!new_pubkey->public_key) { + silc_free(new_pubkey); + return NULL; + } + + if (pubkey->fields) { + new_pubkey->fields = silc_ssh_allocate_fields(); + if (!new_pubkey->fields) { + pubkey->pkcs->public_key_free(pubkey->pkcs, pubkey->public_key); + silc_free(new_pubkey); + return NULL; + } + + silc_hash_table_list(pubkey->fields, &htl); + while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) + silc_hash_table_add(new_pubkey->fields, strdup(field), strdup(value)); + silc_hash_table_list_reset(&htl); + } + + return new_pubkey; +} + +/* Compare two public keys */ + +SILC_PKCS_PUBLIC_KEY_COMPARE(silc_pkcs_ssh_public_key_compare) +{ + SilcSshPublicKey k1 = key1, k2 = key2; + SilcHashTableList htl; + char *field, *value, *value2; + + if (strcmp(k1->pkcs->name, k2->pkcs->name)) + return FALSE; + + if (k1->fields && !k2->fields) + return FALSE; + if (!k1->fields && k2->fields) + return FALSE; + + if (k1->fields && k2->fields) { + if (silc_hash_table_count(k1->fields) != silc_hash_table_count(k2->fields)) + return FALSE; + + silc_hash_table_list(k1->fields, &htl); + while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) { + value2 = (char *)silc_ssh_public_key_get_field(k2, field); + if (!value2) + return FALSE; + if (strcmp(value, value2)) + return FALSE; + } + silc_hash_table_list_reset(&htl); + } + + return k1->pkcs->public_key_compare(k1->pkcs, k1->public_key, + k2->public_key); +} + +/* Free public key */ + +SILC_PKCS_PUBLIC_KEY_FREE(silc_pkcs_ssh_public_key_free) +{ + silc_ssh_public_key_free(public_key); +} + +/* Import private key file. Supports only OpenSSH (OpenSSL to be exact) + private key files. */ + +SILC_PKCS_IMPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_import_private_key_file) +{ + const SilcPKCSAlgorithm *alg; + SilcSshPrivateKey privkey = NULL; + SilcHashTable fields; + SilcBufferStruct keybuf, line; + unsigned char *data, iv[8], key[32]; + SilcSshKeyType type; + char *proctype, *dekinfo; + SilcCipher des; + SilcHash md5; + int ret; + + SILC_LOG_DEBUG(("Parsing SSH2 private key file")); + + if (!ret_private_key) + return FALSE; + if (encoding == SILC_PKCS_FILE_BIN) + return FALSE; + + silc_buffer_set(&keybuf, filedata, filedata_len); + + /* Check for private key markers */ + if (!silc_ssh_parse_line(&keybuf, &line, TRUE)) { + SILC_LOG_DEBUG(("Malformed SSH2 private key markers")); + return FALSE; + } + if ((silc_buffer_len(&keybuf) < (strlen(SILC_SSH_RSA_BEGIN) + + strlen(SILC_SSH_RSA_END))) || + (strncmp(silc_buffer_data(&line), SILC_SSH_RSA_BEGIN, + silc_buffer_len(&line)) && + strncmp(silc_buffer_data(&line), SILC_SSH_DSA_BEGIN, + silc_buffer_len(&line)))) { + SILC_LOG_DEBUG(("Malformed SSH2 private key markers")); + return FALSE; + } + + /* Get PKCS algorithm */ + if (!strncmp(silc_buffer_data(&line), SILC_SSH_RSA_BEGIN, + silc_buffer_len(&line))) { + alg = silc_pkcs_find_algorithm("rsa", "ssh"); + if (!alg) { + SILC_LOG_ERROR(("Unsupported PKCS algorithm rsa/ssh")); + return FALSE; + } + } else if (!strncmp(silc_buffer_data(&line), SILC_SSH_DSA_BEGIN, + silc_buffer_len(&line))) { + alg = silc_pkcs_find_algorithm("dsa", "ssh"); + if (!alg) { + SILC_LOG_ERROR(("Unsupported PKCS algorithm dsa/ssh")); + return FALSE; + } + } else + return FALSE; + + type = SILC_SSH_KEY_OPENSSH; + filedata = silc_buffer_data(&keybuf); + + /* Skip end marker */ + if (!silc_buffer_strchr(&keybuf, '-', FALSE)) { + SILC_LOG_DEBUG(("Malformed SSH2 private key markers")); + return FALSE; + } + filedata_len = silc_buffer_data(&keybuf) - filedata; + silc_buffer_set(&keybuf, filedata, filedata_len); + + /* Parse private key headers. They define how the private key has been + encrypted. */ + fields = silc_ssh_parse_headers(&keybuf); + if (!fields) + return FALSE; + + /* Skip empty line after headers */ + if (silc_hash_table_count(fields) > 0) + silc_ssh_parse_line(&keybuf, NULL, TRUE); + + filedata = silc_buffer_data(&keybuf); + filedata_len = silc_buffer_len(&keybuf); + + /* Decode */ + data = silc_base64_decode(NULL, filedata, filedata_len, &filedata_len); + if (!data) { + SILC_LOG_DEBUG(("Malformed SSH2 private key")); + goto err; + } + filedata = data; + + SILC_LOG_DEBUG(("Private key is %s", (silc_hash_table_count(fields) ? + "encrypted" : "not encrypted"))); + + if (silc_hash_table_count(fields) > 0 && passphrase) { + /* Decrypt */ + + /* Get encryption info */ + if (!silc_hash_table_find(fields, "Proc-Type", NULL, (void *)&proctype)) { + SILC_LOG_ERROR(("Malformed SSH2 private key")); + goto err; + } + if (strcmp(proctype, "4,ENCRYPTED")) { + SILC_LOG_ERROR(("Malformed SSH2 private key")); + goto err; + } + + /* OpenSSH uses 3DES-EDE only */ + if (!silc_hash_table_find(fields, "DEK-Info", NULL, (void *)&dekinfo)) { + SILC_LOG_ERROR(("Malformed SSH2 private key")); + goto err; + } + if (strncmp(dekinfo, "DES-EDE3-CBC", strlen("DES-EDE3-CBC"))) { + SILC_LOG_ERROR(("Unsupported SSH2 private key cipher '%s'", dekinfo)); + goto err; + } + + /* Allocate cipher */ + if (!silc_cipher_alloc("3des-168-cbc", &des)) { + SILC_LOG_ERROR(("Unsupported algorithm 3des-168-cbc")); + goto err; + } + + /* Allocate hash */ + if (!silc_hash_alloc("md5", &md5)) { + SILC_LOG_ERROR(("Unsupported hash algorithm md5")); + goto err; + } + + /* Get IV from private key file */ + dekinfo = strchr(dekinfo, ','); + if (!dekinfo || strlen(dekinfo) < 16) { + SILC_LOG_ERROR(("Malformed SSH2 private key")); + goto err; + } + dekinfo++; + silc_hex2data(dekinfo, iv, sizeof(iv), NULL); + + /* Generate key from passphrase and IV as salt. The passphrase is + hashed with the IV, then rehashed with the previous hash, passphrase + and the IV to produce the final key, which is the concatenation of + the two hashes. */ + silc_hash_init(md5); + silc_hash_update(md5, passphrase, passphrase_len); + silc_hash_update(md5, iv, 8); + silc_hash_final(md5, key); + silc_hash_init(md5); + silc_hash_update(md5, key, 16); + silc_hash_update(md5, passphrase, passphrase_len); + silc_hash_update(md5, iv, 8); + silc_hash_final(md5, key + 16); + + /* Decrypt */ + silc_cipher_set_key(des, key, 192, FALSE); + if (!silc_cipher_decrypt(des, filedata, filedata, filedata_len, iv)) { + SILC_LOG_ERROR(("Malformed SSH2 private key")); + silc_cipher_free(des); + silc_hash_free(md5); + goto err; + } + + silc_cipher_free(des); + silc_hash_free(md5); + } + + /* Decode the private key */ + ret = silc_pkcs_ssh_import_private_key(pkcs, alg, filedata, filedata_len, + (void *)&privkey, ret_alg); + silc_free(data); + + if (ret) { + privkey->fields = fields; + privkey->type = type; + *ret_private_key = privkey; + SILC_LOG_DEBUG(("SSH2 private key file imported successfully")); + return TRUE; + } + + err: + if (fields) + silc_hash_table_free(fields); + return FALSE; +} + +/* Import private key. The key format for RSA is PKCS#1 compliant and for + DSA is equivalent to our DSA implementation, so we just simply call the + algorithm specific import function to do the magic. */ + +SILC_PKCS_IMPORT_PRIVATE_KEY(silc_pkcs_ssh_import_private_key) +{ + SilcSshPrivateKey privkey; + int ret; + + if (!ret_private_key || !alg) + return 0; + + /* Allocate SSH private key context */ + privkey = silc_calloc(1, sizeof(*privkey)); + if (!privkey) + return 0; + + /* Import PKCS algorithm private key */ + ret = alg->import_private_key(alg, key, key_len, &privkey->private_key); + if (!ret) { + silc_free(privkey); + return 0; + } + + privkey->pkcs = alg; + privkey->type = SILC_SSH_KEY_OPENSSH; + + *ret_private_key = privkey; + if (ret_alg) + *ret_alg = alg; + + return ret; +} + +/* Export private key file */ + +SILC_PKCS_EXPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_export_private_key_file) +{ + SilcSshPrivateKey privkey = private_key; + const SilcPKCSAlgorithm *alg = privkey->pkcs; + SilcBufferStruct buf; + unsigned char *key, *keyenc, ivdata[8], iv[16 + 1], enc[32]; + SilcUInt32 key_len, pad_len; + SilcCipher des = NULL; + SilcHash md5 = NULL; + + SILC_LOG_DEBUG(("Encode SSH2 private key file")); + + /* Export the private key */ + key = silc_pkcs_ssh_export_private_key(pkcs, stack, private_key, &key_len); + if (!key) + return NULL; + + memset(&buf, 0, sizeof(buf)); + if (!strcmp(alg->name, "rsa")) { + if (silc_buffer_sformat(stack, &buf, + SILC_STR_ADVANCE, + SILC_STR_UI32_STRING(SILC_SSH_RSA_BEGIN), + SILC_STR_UI32_STRING("\n"), + SILC_STR_END) < 0) + goto err; + } else if (!strcmp(alg->name, "dsa")) { + if (silc_buffer_sformat(stack, &buf, + SILC_STR_ADVANCE, + SILC_STR_UI32_STRING(SILC_SSH_DSA_BEGIN), + SILC_STR_UI32_STRING("\n"), + SILC_STR_END) < 0) + goto err; + } else + goto err; + + if (passphrase && strlen(passphrase) > 0) { + /* Encrypt the key */ + + /* Allocate cipher */ + if (!silc_cipher_alloc("3des-168-cbc", &des)) { + SILC_LOG_ERROR(("Unsupported algorithm 3des-168-cbc")); + goto err; + } + + /* Allocate hash */ + if (!silc_hash_alloc("md5", &md5)) { + SILC_LOG_ERROR(("Unsupported hash algorithm md5")); + goto err; + } + + /* Generate IV */ + silc_rng_get_rn_data(rng, sizeof(ivdata), ivdata, sizeof(ivdata)); + silc_data2hex(ivdata, sizeof(ivdata), iv, sizeof(iv)); + + /* Encode header */ + if (silc_buffer_sformat(stack, &buf, + SILC_STR_ADVANCE, + SILC_STR_UI32_STRING("Proc-Type: 4,ENCRYPTED\n"), + SILC_STR_UI32_STRING("DEK-Info: DES-EDE3-CBC,"), + SILC_STR_UI32_STRING(iv), + SILC_STR_UI32_STRING("\n\n"), + SILC_STR_END) < 0) + goto err; + + /* Generate key from passphrase and IV as salt. The passphrase is + hashed with the IV, then rehashed with the previous hash, passphrase + and the IV to produce the final key, which is the concatenation of + the two hashes. */ + silc_hash_init(md5); + silc_hash_update(md5, passphrase, passphrase_len); + silc_hash_update(md5, ivdata, 8); + silc_hash_final(md5, enc); + silc_hash_init(md5); + silc_hash_update(md5, enc, 16); + silc_hash_update(md5, passphrase, passphrase_len); + silc_hash_update(md5, ivdata, 8); + silc_hash_final(md5, enc + 16); + + /* Pad */ + pad_len = 8 - (key_len % 8); + if (pad_len) { + keyenc = silc_smalloc(stack, (key_len + pad_len) * sizeof(*keyenc)); + if (!key) + goto err; + memset(keyenc + key_len, 'F', pad_len); + memcpy(keyenc, key, key_len); + } else { + keyenc = silc_memdup(key, key_len); + if (!keyenc) + goto err; + } + + /* Encrypt */ + silc_cipher_set_key(des, enc, 192, TRUE); + silc_cipher_encrypt(des, keyenc, keyenc, key_len + pad_len, ivdata); + + silc_sfree(stack, key); + key = keyenc; + key_len += pad_len; + + silc_cipher_free(des); + silc_hash_free(md5); + } + + /* Base64 encode */ + keyenc = silc_base64_encode_file(stack, key, key_len); + if (!keyenc) + goto err; + + silc_sfree(stack, key); + key = keyenc; + key_len = strlen(keyenc); + + /* Encode rest of the public key */ + if (!strcmp(alg->name, "rsa")) { + if (silc_buffer_sformat(stack, &buf, + SILC_STR_ADVANCE, + SILC_STR_DATA(key, key_len), + SILC_STR_UI32_STRING("\n"), + SILC_STR_UI32_STRING(SILC_SSH_RSA_END), + SILC_STR_UI32_STRING("\n"), + SILC_STR_END) < 0) + goto err; + } else if (!strcmp(alg->name, "dsa")) { + if (silc_buffer_sformat(stack, &buf, + SILC_STR_ADVANCE, + SILC_STR_DATA(key, key_len), + SILC_STR_UI32_STRING("\n"), + SILC_STR_UI32_STRING(SILC_SSH_DSA_END), + SILC_STR_UI32_STRING("\n"), + SILC_STR_END) < 0) + goto err; + } + + silc_sfree(stack, key); + key = silc_buffer_steal(&buf, ret_len); + return key; + + err: + if (des) + silc_cipher_free(des); + if (md5) + silc_hash_free(md5); + silc_sfree(stack, key); + return NULL; +} + +/* Export private key */ + +SILC_PKCS_EXPORT_PRIVATE_KEY(silc_pkcs_ssh_export_private_key) +{ + SilcSshPrivateKey privkey = private_key; + const SilcPKCSAlgorithm *alg = privkey->pkcs; + + SILC_LOG_DEBUG(("Encode SSH2 private key")); + + /* Export PKCS algorithm private key */ + if (alg->export_private_key) + return alg->export_private_key(alg, stack, + privkey->private_key, ret_len); + return NULL; +} + +/* Return key length in bits */ + +SILC_PKCS_PRIVATE_KEY_BITLEN(silc_pkcs_ssh_private_key_bitlen) +{ + SilcSshPrivateKey privkey = private_key; + return privkey->pkcs->private_key_bitlen(privkey->pkcs, + privkey->private_key); +} + +/* Free private key */ + +SILC_PKCS_PRIVATE_KEY_FREE(silc_pkcs_ssh_private_key_free) +{ + SilcSshPrivateKey privkey = private_key; + + privkey->pkcs->private_key_free(privkey->pkcs, + privkey->private_key); + + if (privkey->fields) + silc_hash_table_free(privkey->fields); + silc_free(privkey); +} + +/* Encrypt */ + +SILC_PKCS_ENCRYPT(silc_pkcs_ssh_encrypt) +{ + SilcSshPublicKey pubkey = public_key; + + if (!pubkey->pkcs->encrypt) { + encrypt_cb(FALSE, NULL, 0, context); + return NULL; + } + + return pubkey->pkcs->encrypt(pubkey->pkcs, pubkey->public_key, + src, src_len, rng, encrypt_cb, context); +} + +/* Decrypt */ + +SILC_PKCS_DECRYPT(silc_pkcs_ssh_decrypt) +{ + SilcSshPrivateKey privkey = private_key; + + if (!privkey->pkcs->decrypt) { + decrypt_cb(FALSE, NULL, 0, context); + return NULL; + } + + return privkey->pkcs->decrypt(privkey->pkcs, privkey->private_key, + src, src_len, decrypt_cb, context); +} + +/* Sign */ + +SILC_PKCS_SIGN(silc_pkcs_ssh_sign) +{ + SilcSshPrivateKey privkey = private_key; + + if (!privkey->pkcs->sign) { + sign_cb(FALSE, NULL, 0, context); + return NULL; + } + + return privkey->pkcs->sign(privkey->pkcs, privkey->private_key, + src, src_len, + compute_hash, hash, rng, + sign_cb, context); +} + +/* Verify */ + +SILC_PKCS_VERIFY(silc_pkcs_ssh_verify) +{ + SilcSshPublicKey pubkey = public_key; + + if (!pubkey->pkcs->verify) { + verify_cb(FALSE, context); + return NULL; + } + + return pubkey->pkcs->verify(pubkey->pkcs, pubkey->public_key, + signature, signature_len, + data, data_len, hash, rng, + verify_cb, context); +} + +/************************** SSH2 PKCS RSA Alg API ***************************/ + +/* The SSH2 RSA PKCS Algorithm API. We implement here only the necessary + parts of the API and the common code is used from PKCS#1 Algorithm API + in silccrypt/silcpkcs1.c. Basically everything else is PKCS#1 except + the format of the public key. */ + +/* Import RSA public key. Both RFC 4716 and OpenSSH have same format. */ + +SILC_PKCS_ALG_IMPORT_PUBLIC_KEY(silc_ssh_rsa_import_public_key) +{ + SilcBufferStruct alg_key; + RsaPublicKey *pubkey; + unsigned char *n, *e; + SilcUInt32 n_len, e_len; + int ret; + + SILC_LOG_DEBUG(("Import public key")); + + if (!ret_public_key) + return 0; + + /* Allocate RSA public key */ + *ret_public_key = pubkey = silc_calloc(1, sizeof(*pubkey)); + if (!pubkey) + return 0; + + /* Parse SSH2 RSA public key */ + silc_buffer_set(&alg_key, key, key_len); + ret = silc_buffer_unformat(&alg_key, + SILC_STR_UI32_NSTRING(&e, &e_len), + SILC_STR_UI32_NSTRING(&n, &n_len), + SILC_STR_END); + if (ret < 0) + goto err; + if (!n_len || !e_len) + goto err; + + /* Get MP integers */ + silc_mp_init(&pubkey->n); + silc_mp_init(&pubkey->e); + silc_mp_bin2mp(n, n_len, &pubkey->n); + silc_mp_bin2mp(e, e_len, &pubkey->e); + + /* Set key length */ + pubkey->bits = ((silc_mp_sizeinbase(&pubkey->n, 2) + 7) / 8) * 8; + + return ret; + + err: + silc_free(pubkey); + return 0; +} + +/* Export RSA public key. Both RFC 4716 and OpenSSH have same format. */ + +SILC_PKCS_ALG_EXPORT_PUBLIC_KEY(silc_ssh_rsa_export_public_key) +{ + RsaPublicKey *pubkey = public_key; + SilcBufferStruct alg_key; + unsigned char *n = NULL, *e = NULL, *ret; + SilcUInt32 n_len, e_len; + + SILC_LOG_DEBUG(("Export public key")); + + /* Encode MP integers */ + n = silc_mp_mp2bin(&pubkey->n, 0, &n_len); + if (!n) + goto err; + e = silc_mp_mp2bin(&pubkey->e, 0, &e_len); + if (!e) + goto err; + + /* Encode SSH2 RSA public key */ + memset(&alg_key, 0, sizeof(alg_key)); + if (silc_buffer_sformat(stack, &alg_key, + SILC_STR_UI_INT(e_len), + SILC_STR_DATA(e, e_len), + SILC_STR_UI_INT(n_len), + SILC_STR_DATA(n, n_len), + SILC_STR_END) < 0) + goto err; + + silc_free(n); + silc_free(e); + + ret = silc_buffer_steal(&alg_key, ret_len); + return ret; + + err: + silc_free(n); + silc_free(e); + return NULL; +} + +/************************** SSH2 PKCS DSA Alg API ***************************/ + +/* The SSH2 DSA PKCS Algorithm API. We implement here only the necessary + parts of the API and the common code is used from DSS Algorithm API + in silccrypt/dsa.c. */ + +/* Import DSA public key. Both RFC 4716 and OpenSSH have same format. */ + +SILC_PKCS_ALG_IMPORT_PUBLIC_KEY(silc_ssh_dsa_import_public_key) +{ + SilcBufferStruct alg_key; + DsaPublicKey *pubkey; + unsigned char *p, *q, *g, *y; + SilcUInt32 p_len, q_len, g_len, y_len; + int ret; + + SILC_LOG_DEBUG(("Import public key")); + + if (!ret_public_key) + return 0; + + /* Allocate DSA public key */ + *ret_public_key = pubkey = silc_calloc(1, sizeof(*pubkey)); + if (!pubkey) + return 0; + + /* Parse SSH2 DSA public key */ + silc_buffer_set(&alg_key, key, key_len); + ret = silc_buffer_unformat(&alg_key, + SILC_STR_UI32_NSTRING(&p, &p_len), + SILC_STR_UI32_NSTRING(&q, &q_len), + SILC_STR_UI32_NSTRING(&g, &g_len), + SILC_STR_UI32_NSTRING(&y, &y_len), + SILC_STR_END); + if (ret < 0) + goto err; + if (!p_len || !q_len || !g_len || !y_len) + goto err; + + /* Get MP integers */ + silc_mp_init(&pubkey->p); + silc_mp_init(&pubkey->q); + silc_mp_init(&pubkey->g); + silc_mp_init(&pubkey->y); + silc_mp_bin2mp(p, p_len, &pubkey->p); + silc_mp_bin2mp(q, q_len, &pubkey->q); + silc_mp_bin2mp(g, g_len, &pubkey->g); + silc_mp_bin2mp(y, y_len, &pubkey->y); + + /* Set key length */ + pubkey->bits = ((silc_mp_sizeinbase(&pubkey->p, 2) + 7) / 8) * 8; + + return ret; + + err: + silc_free(pubkey); + return 0; +} + +/* Export DSA public key. Both RFC 4716 and OpenSSH have same format. */ + +SILC_PKCS_ALG_EXPORT_PUBLIC_KEY(silc_ssh_dsa_export_public_key) +{ + DsaPublicKey *pubkey = public_key; + SilcBufferStruct alg_key; + unsigned char *p = NULL, *q = NULL, *g = NULL, *y = NULL, *ret; + SilcUInt32 p_len, q_len, g_len, y_len; + + SILC_LOG_DEBUG(("Export public key")); + + /* Encode MP integers */ + p = silc_mp_mp2bin(&pubkey->p, 0, &p_len); + if (!p) + goto err; + q = silc_mp_mp2bin(&pubkey->q, 0, &q_len); + if (!q) + goto err; + g = silc_mp_mp2bin(&pubkey->g, 0, &g_len); + if (!g) + goto err; + y = silc_mp_mp2bin(&pubkey->y, 0, &y_len); + if (!y) + goto err; + + /* Encode SSH2 DSA public key */ + memset(&alg_key, 0, sizeof(alg_key)); + if (silc_buffer_sformat(stack, &alg_key, + SILC_STR_UI_INT(p_len), + SILC_STR_DATA(p, p_len), + SILC_STR_UI_INT(q_len), + SILC_STR_DATA(q, q_len), + SILC_STR_UI_INT(g_len), + SILC_STR_DATA(g, g_len), + SILC_STR_UI_INT(y_len), + SILC_STR_DATA(y, y_len), + SILC_STR_END) < 0) + goto err; + + silc_free(p); + silc_free(q); + silc_free(g); + silc_free(y); + + ret = silc_buffer_steal(&alg_key, ret_len); + return ret; + + err: + silc_free(p); + silc_free(q); + silc_free(g); + silc_free(y); + return NULL; +} diff --git a/lib/silcssh/silcssh_pkcs.h b/lib/silcssh/silcssh_pkcs.h new file mode 100644 index 00000000..21aed806 --- /dev/null +++ b/lib/silcssh/silcssh_pkcs.h @@ -0,0 +1,49 @@ +/* + + silcssh_pkcs.h + + Author: Pekka Riikonen + + Copyright (C) 2007 Pekka Riikonen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + +*/ + +#ifndef SILCSSH_PKCS_H +#define SILCSSH_PKCS_H + +SILC_PKCS_GET_ALGORITHM(silc_pkcs_ssh_get_algorithm); +SILC_PKCS_IMPORT_PUBLIC_KEY_FILE(silc_pkcs_ssh_import_public_key_file); +SILC_PKCS_IMPORT_PUBLIC_KEY(silc_pkcs_ssh_import_public_key); +SILC_PKCS_EXPORT_PUBLIC_KEY_FILE(silc_pkcs_ssh_export_public_key_file); +SILC_PKCS_EXPORT_PUBLIC_KEY(silc_pkcs_ssh_export_public_key); +SILC_PKCS_PUBLIC_KEY_BITLEN(silc_pkcs_ssh_public_key_bitlen); +SILC_PKCS_PUBLIC_KEY_COPY(silc_pkcs_ssh_public_key_copy); +SILC_PKCS_PUBLIC_KEY_COMPARE(silc_pkcs_ssh_public_key_compare); +SILC_PKCS_PUBLIC_KEY_FREE(silc_pkcs_ssh_public_key_free); +SILC_PKCS_IMPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_import_private_key_file); +SILC_PKCS_IMPORT_PRIVATE_KEY(silc_pkcs_ssh_import_private_key); +SILC_PKCS_EXPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_export_private_key_file); +SILC_PKCS_EXPORT_PRIVATE_KEY(silc_pkcs_ssh_export_private_key); +SILC_PKCS_PRIVATE_KEY_BITLEN(silc_pkcs_ssh_private_key_bitlen); +SILC_PKCS_PRIVATE_KEY_FREE(silc_pkcs_ssh_private_key_free); +SILC_PKCS_ENCRYPT(silc_pkcs_ssh_encrypt); +SILC_PKCS_DECRYPT(silc_pkcs_ssh_decrypt); +SILC_PKCS_SIGN(silc_pkcs_ssh_sign); +SILC_PKCS_VERIFY(silc_pkcs_ssh_verify); + +SILC_PKCS_ALG_IMPORT_PUBLIC_KEY(silc_ssh_rsa_import_public_key); +SILC_PKCS_ALG_EXPORT_PUBLIC_KEY(silc_ssh_rsa_export_public_key); + +SILC_PKCS_ALG_IMPORT_PUBLIC_KEY(silc_ssh_dsa_import_public_key); +SILC_PKCS_ALG_EXPORT_PUBLIC_KEY(silc_ssh_dsa_export_public_key); + +#endif /* SILCSSH_PKCS_H */ diff --git a/lib/silcssh/tests/Makefile.am b/lib/silcssh/tests/Makefile.am new file mode 100644 index 00000000..1aae529b --- /dev/null +++ b/lib/silcssh/tests/Makefile.am @@ -0,0 +1,28 @@ +# +# Makefile.am +# +# Author: Pekka Riikonen +# +# Copyright (C) 2007 Pekka Riikonen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# + +AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign + +bin_PROGRAMS = test_silcssh + +test_silcssh_SOURCES = test_silcssh.c + +LIBS = $(SILC_COMMON_LIBS) +LDADD = -L.. -L../.. -lsilc -lsilcssh +CFLAGS = -O0 + +include $(top_srcdir)/Makefile.defines.in diff --git a/lib/silcssh/tests/test_silcssh.c b/lib/silcssh/tests/test_silcssh.c new file mode 100644 index 00000000..7962dc17 --- /dev/null +++ b/lib/silcssh/tests/test_silcssh.c @@ -0,0 +1,76 @@ +/* SILC SSH2 library tests */ + +#include "silc.h" + +int main(int argc, char **argv) +{ + SilcBool success = FALSE; + SilcRng rng; + SilcPublicKey public_key; + SilcPrivateKey private_key; + SilcSshPublicKey ssh_pubkey; + + if (argc > 1 && !strcmp(argv[1], "-d")) { + silc_log_debug(TRUE); + silc_log_quick(TRUE); + silc_log_debug_hexdump(TRUE); + silc_log_set_debug_string("*ssh*,*pkcs1*,*asn1*,*rsa*,*dsa*"); + } + + silc_crypto_init(NULL); + rng = silc_rng_alloc(); + silc_rng_init(rng); + + SILC_LOG_DEBUG(("Generate key pair")); + silc_ssh_generate_key("dsa", 1024, rng, &public_key, &private_key); + + SILC_LOG_DEBUG(("Set SSH2 public key headers")); + ssh_pubkey = silc_pkcs_public_key_get_pkcs(SILC_PKCS_SSH2, public_key); + silc_ssh_public_key_set_type(ssh_pubkey, SILC_SSH_KEY_SSH2); + silc_ssh_public_key_add_field(ssh_pubkey, "Subject", "foo@example.com"); + silc_ssh_public_key_add_field(ssh_pubkey, "Comment", "My own key"); + + SILC_LOG_DEBUG(("Save public and private key")); + if (!silc_pkcs_save_public_key("pubkey.pub", public_key, + SILC_PKCS_FILE_BASE64)) + goto err; + if (!silc_pkcs_save_private_key("privkey.prv", private_key, "testi", 5, + SILC_PKCS_FILE_BASE64, rng)) + goto err; + + SILC_LOG_DEBUG(("Load public key")); + if (!silc_pkcs_load_public_key("pubkey.pub", SILC_PKCS_ANY, &public_key)) + goto err; + ssh_pubkey = silc_pkcs_public_key_get_pkcs(SILC_PKCS_SSH2, public_key); + SILC_LOG_DEBUG(("Subject: '%s'", + silc_ssh_public_key_get_field(ssh_pubkey, "Subject"))); + SILC_LOG_DEBUG(("Comment: '%s'", + silc_ssh_public_key_get_field(ssh_pubkey, "Comment"))); + + SILC_LOG_DEBUG(("Load private key")); + if (!silc_pkcs_load_private_key("privkey.prv", "testi", 5, + SILC_PKCS_ANY, &private_key)) + goto err; + + SILC_LOG_DEBUG(("Save as OpenSSH public key")); + ssh_pubkey = silc_pkcs_public_key_get_pkcs(SILC_PKCS_SSH2, public_key); + silc_ssh_public_key_set_type(ssh_pubkey, SILC_SSH_KEY_OPENSSH); + if (!silc_pkcs_save_public_key("pubkey_openssh.pub", public_key, + SILC_PKCS_FILE_BASE64)) + goto err; + + SILC_LOG_DEBUG(("Load public key")); + if (!silc_pkcs_load_public_key("pubkey_openssh.pub", SILC_PKCS_SSH2, + &public_key)) + goto err; + + silc_rng_free(rng); + + success = TRUE; + + err: + SILC_LOG_DEBUG(("Testing was %s", success ? "SUCCESS" : "FAILURE")); + fprintf(stderr, "Testing was %s\n", success ? "SUCCESS" : "FAILURE"); + + return success; +} -- 2.24.0