Added SILC Thread Queue API
[silc.git] / lib / silcssh / silcssh_pkcs.c
index 1821709e4c18d5e6a2c426a00ff8de2646228a58..d2b2a239c3bfa9c6418a2815aa207f51ebe89bfd 100644 (file)
@@ -706,15 +706,15 @@ SILC_PKCS_EXPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_export_private_key_file)
     silc_hash_final(md5, enc + 16);
 
     /* Pad */
-    pad_len = 8 - (key_len % 8);
+    pad_len = (-key_len) % 8;
     if (pad_len) {
       keyenc = silc_smalloc(stack, (key_len + pad_len) * sizeof(*keyenc));
-      if (!key)
+      if (!keyenc)
        goto err;
       memset(keyenc + key_len, 'F', pad_len);
       memcpy(keyenc, key, key_len);
     } else {
-      keyenc = silc_memdup(key, key_len);
+      keyenc = silc_smemdup(stack, key, key_len);
       if (!keyenc)
        goto err;
     }
@@ -843,21 +843,134 @@ SILC_PKCS_DECRYPT(silc_pkcs_ssh_decrypt)
                                src, src_len, decrypt_cb, context);
 }
 
+/* Sign context */
+typedef struct {
+  SilcStack stack;
+  SilcSshPrivateKey privkey;
+  SilcPKCSSignCb sign_cb;
+  void *context;
+} *SilcSshSign;
+
+/* Sign callback.  This formats the signature into SSH2 protocool compliant
+   format. */
+
+static void silc_pkcs_ssh_sign_cb(SilcBool success,
+                                 const unsigned char *signature,
+                                 SilcUInt32 signature_len,
+                                 void *context)
+{
+  SilcSshSign sign = context;
+  SilcStack stack = sign->stack;
+  unsigned char rbuf[20], sbuf[20];
+  SilcBufferStruct sig;
+  SilcAsn1 asn1;
+  SilcMPInt r, s;
+
+  memset(&sig, 0, sizeof(sig));
+
+  /* Format the signature.  RSA is easy because PKCS#1 is already in
+     correct format.  For DSA the returned signature is in PKIX compliant
+     format and we have to reformat it for SSH2. */
+  if (!strcmp(sign->privkey->pkcs->name, "dsa")) {
+    asn1 = silc_asn1_alloc(stack);
+    if (!asn1) {
+      sign->sign_cb(FALSE, NULL, 0, sign->context);
+      silc_sfree(stack, sign);
+      silc_stack_free(stack);
+      return;
+    }
+
+    /* Decode the signature */
+    silc_buffer_set(&sig, (unsigned char *)signature, signature_len);
+    if (!silc_asn1_decode(asn1, &sig,
+                         SILC_ASN1_SEQUENCE,
+                           SILC_ASN1_INT(&r),
+                           SILC_ASN1_INT(&s),
+                         SILC_ASN1_END, SILC_ASN1_END)) {
+      sign->sign_cb(FALSE, NULL, 0, sign->context);
+      silc_asn1_free(asn1);
+      silc_sfree(stack, sign);
+      silc_stack_free(stack);
+      return;
+    }
+
+    /* Encode the integers */
+    memset(rbuf, 0, sizeof(rbuf));
+    memset(sbuf, 0, sizeof(sbuf));
+    silc_mp_mp2bin_noalloc(&r, rbuf, sizeof(rbuf));
+    silc_mp_mp2bin_noalloc(&s, sbuf, sizeof(sbuf));
+
+    silc_asn1_free(asn1);
+
+    /* Encode SSH2 DSS signature */
+    if (silc_buffer_sformat(stack, &sig,
+                           SILC_STR_UI_INT(7),
+                           SILC_STR_UI32_STRING("ssh-dss"),
+                           SILC_STR_UI_INT(sizeof(rbuf) + sizeof(sbuf)),
+                           SILC_STR_DATA(rbuf, sizeof(rbuf)),
+                           SILC_STR_DATA(sbuf, sizeof(sbuf)),
+                           SILC_STR_END) < 0) {
+      sign->sign_cb(FALSE, NULL, 0, sign->context);
+      silc_sfree(stack, sign);
+      silc_stack_free(stack);
+      return;
+    }
+  } else {
+    /* Encode SSH2 RSA signature */
+    if (silc_buffer_sformat(stack, &sig,
+                           SILC_STR_UI_INT(7),
+                           SILC_STR_UI32_STRING("ssh-rsa"),
+                           SILC_STR_UI_INT(signature_len),
+                           SILC_STR_DATA(signature, signature_len),
+                           SILC_STR_END) < 0) {
+      sign->sign_cb(FALSE, NULL, 0, sign->context);
+      silc_sfree(stack, sign);
+      silc_stack_free(stack);
+      return;
+    }
+  }
+
+  /* Deliver result */
+  sign->sign_cb(TRUE, silc_buffer_data(&sig), silc_buffer_len(&sig),
+               sign->context);
+
+  silc_buffer_spurge(stack, &sig);
+  silc_sfree(stack, sign);
+  silc_stack_free(stack);
+}
+
 /* Sign */
 
 SILC_PKCS_SIGN(silc_pkcs_ssh_sign)
 {
   SilcSshPrivateKey privkey = private_key;
+  SilcStack stack;
+  SilcSshSign sign;
 
   if (!privkey->pkcs->sign) {
     sign_cb(FALSE, NULL, 0, context);
     return NULL;
   }
 
+  stack = silc_stack_alloc(0, silc_crypto_stack());
+
+  sign = silc_scalloc(stack, 1, sizeof(*sign));
+  if (!sign) {
+    sign_cb(FALSE, NULL, 0, context);
+    silc_stack_free(stack);
+    return NULL;
+  }
+
+  sign->stack = stack;
+  sign->privkey = privkey;
+  sign->sign_cb = sign_cb;
+  sign->context = context;
+
+  /* Sign.  The callback will format it to SSH2 compliant format. */
   return privkey->pkcs->sign(privkey->pkcs, privkey->private_key,
                             src, src_len,
                             compute_hash, hash, rng,
-                            sign_cb, context);
+                            silc_pkcs_ssh_sign_cb, sign);
 }
 
 /* Verify */
@@ -865,16 +978,81 @@ SILC_PKCS_SIGN(silc_pkcs_ssh_sign)
 SILC_PKCS_VERIFY(silc_pkcs_ssh_verify)
 {
   SilcSshPublicKey pubkey = public_key;
+  SilcAsyncOperation op;
+  SilcBufferStruct sig, r, s;
+  unsigned char *signame;
+  SilcStack stack = NULL;
+  SilcAsn1 asn1;
 
   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);
+  /* Decode the SSH2 protocol style signature encoding. */
+  silc_buffer_set(&sig, signature, signature_len);
+  if (silc_buffer_unformat(&sig,
+                          SILC_STR_UI32_STRING_ALLOC(&signame),
+                          SILC_STR_UI32_NSTRING(&signature, &signature_len),
+                          SILC_STR_END) < 0) {
+    verify_cb(FALSE, context);
+    return NULL;
+  }
+  memset(&sig, 0, sizeof(sig));
+
+  /* DSS signature must be formatted to PKIX compliant format since our
+     implementation expects that. */
+  if (!strcmp(signame, "ssh-dss")) {
+    /* The integers must be 160 bits each */
+    if (signature_len != 40) {
+      verify_cb(FALSE, context);
+      silc_free(signame);
+      return NULL;
+    }
+
+    silc_buffer_set(&r, signature, 20);
+    silc_buffer_set(&s, signature + 20, 20);
+
+    stack = silc_stack_alloc(0, silc_crypto_stack());
+
+    asn1 = silc_asn1_alloc(stack);
+    if (!asn1) {
+      verify_cb(FALSE, context);
+      silc_free(signame);
+      silc_stack_free(stack);
+      return NULL;
+    }
+
+    /* Encode signature to PKIX compliant format. */
+    if (!silc_asn1_encode(asn1, &sig,
+                         SILC_ASN1_OPTS(SILC_ASN1_ALLOC),
+                         SILC_ASN1_SEQUENCE,
+                           SILC_ASN1_ANY_PRIMITIVE(SILC_ASN1_TAG_INTEGER, &r),
+                           SILC_ASN1_ANY_PRIMITIVE(SILC_ASN1_TAG_INTEGER, &s),
+                         SILC_ASN1_END, SILC_ASN1_END)) {
+      verify_cb(FALSE, context);
+      silc_free(signame);
+      silc_asn1_free(asn1);
+      silc_stack_free(stack);
+      return NULL;
+    }
+
+    signature = silc_buffer_steal(&sig, &signature_len);
+
+    silc_asn1_free(asn1);
+  }
+
+  /* Verify */
+  op = pubkey->pkcs->verify(pubkey->pkcs, pubkey->public_key,
+                           signature, signature_len,
+                           data, data_len, hash, rng,
+                           verify_cb, context);
+
+  silc_free(signame);
+  silc_buffer_spurge(stack, &sig);
+  silc_stack_free(stack);
+
+  return op;
 }
 
 /************************** SSH2 PKCS RSA Alg API ***************************/