--- /dev/null
+/*
+
+ silcssh.c
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ 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;
+}
--- /dev/null
+/*
+
+ silcssh.h
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ 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 */
--- /dev/null
+/*
+
+ silcssh_pkcs.c
+
+ Author: Pekka Riikonen <priikone@silcnet.org>
+
+ 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;
+}