Added SILC SSH2 library providing support for SSH2 public and
authorPekka Riikonen <priikone@silcnet.org>
Sat, 21 Jul 2007 12:57:50 +0000 (12:57 +0000)
committerPekka Riikonen <priikone@silcnet.org>
Sat, 21 Jul 2007 12:57:50 +0000 (12:57 +0000)
private keys.

lib/silcssh/DIRECTORY [new file with mode: 0644]
lib/silcssh/Makefile.ad [new file with mode: 0644]
lib/silcssh/silcssh.c [new file with mode: 0644]
lib/silcssh/silcssh.h [new file with mode: 0644]
lib/silcssh/silcssh_i.h [new file with mode: 0644]
lib/silcssh/silcssh_pkcs.c [new file with mode: 0644]
lib/silcssh/silcssh_pkcs.h [new file with mode: 0644]
lib/silcssh/tests/Makefile.am [new file with mode: 0644]
lib/silcssh/tests/test_silcssh.c [new file with mode: 0644]

diff --git a/lib/silcssh/DIRECTORY b/lib/silcssh/DIRECTORY
new file mode 100644 (file)
index 0000000..dc5b5e7
--- /dev/null
@@ -0,0 +1,32 @@
+<!--
+@LIBRARY=SILC SSH2 Library
+@FILENAME=silcsshlib.html
+@LINK=silcssh.html:SILC SSH2 Interface
+-->
+
+<big><b>SILC SSH2 Library</b></big>
+<br />
+<small>Directory: lib/silcssh/</small>
+<br />
+<small>Library: libsilc.a, libsilc.lib</small>
+<br /><br />
+<b>Introduction</b>
+
+<br /><br />
+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.
+
+<br /><br />
+@LINKS@
diff --git a/lib/silcssh/Makefile.ad b/lib/silcssh/Makefile.ad
new file mode 100644 (file)
index 0000000..c439bdd
--- /dev/null
@@ -0,0 +1,30 @@
+#
+#  Makefile.ad
+#
+#  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.
+#
+
+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 (file)
index 0000000..0e4f606
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+
+  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;
+}
diff --git a/lib/silcssh/silcssh.h b/lib/silcssh/silcssh.h
new file mode 100644 (file)
index 0000000..6884894
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+
+  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 */
diff --git a/lib/silcssh/silcssh_i.h b/lib/silcssh/silcssh_i.h
new file mode 100644 (file)
index 0000000..afca335
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+
+  silcssh_i.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.
+
+*/
+
+#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 (file)
index 0000000..1821709
--- /dev/null
@@ -0,0 +1,1087 @@
+/*
+
+  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;
+}
diff --git a/lib/silcssh/silcssh_pkcs.h b/lib/silcssh/silcssh_pkcs.h
new file mode 100644 (file)
index 0000000..21aed80
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+
+  silcssh_pkcs.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.
+
+*/
+
+#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 (file)
index 0000000..1aae529
--- /dev/null
@@ -0,0 +1,28 @@
+#
+#  Makefile.am
+#
+#  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.
+#
+
+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 (file)
index 0000000..7962dc1
--- /dev/null
@@ -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;
+}