/* silcssh_pkcs.c Author: Pekka Riikonen Copyright (C) 2007 Pekka Riikonen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "silc.h" #include "rsa.h" #include "dsa.h" #include "silcssh_pkcs.h" /************************** Types and definitions ***************************/ /* RFC 4716 public key file markers */ #define SILC_SSH_PUBLIC_KEY_BEGIN "---- BEGIN SSH2 PUBLIC KEY ----" #define SILC_SSH_PUBLIC_KEY_END "---- END SSH2 PUBLIC KEY ----" /* OpenSSH private key file markers */ #define SILC_SSH_RSA_BEGIN "-----BEGIN RSA PRIVATE KEY-----" #define SILC_SSH_RSA_END "-----END RSA PRIVATE KEY-----" #define SILC_SSH_DSA_BEGIN "-----BEGIN DSA PRIVATE KEY-----" #define SILC_SSH_DSA_END "-----END DSA PRIVATE KEY-----" /****************************** SSH2 PKCS API *******************************/ /* Get algorithm context */ SILC_PKCS_GET_ALGORITHM(silc_pkcs_ssh_get_algorithm) { SilcSshPublicKey pubkey = public_key; return pubkey->pkcs; } /* Import public key file */ SILC_PKCS_IMPORT_PUBLIC_KEY_FILE(silc_pkcs_ssh_import_public_key_file) { SilcSshPublicKey pubkey; SilcBufferStruct keybuf, line; SilcHashTable fields; unsigned char *data; SilcSshKeyType type; int ret; SILC_LOG_DEBUG(("Parsing SSH2 public key file")); if (!ret_public_key) return FALSE; if (encoding == SILC_PKCS_FILE_BIN) return FALSE; silc_buffer_set(&keybuf, filedata, filedata_len); /* Check for RFC 4716 style public key markers */ if (!silc_ssh_parse_line(&keybuf, &line, TRUE)) { SILC_LOG_DEBUG(("Malformed SSH2 public key markers")); return FALSE; } if ((silc_buffer_len(&keybuf) < (strlen(SILC_SSH_PUBLIC_KEY_BEGIN) + strlen(SILC_SSH_PUBLIC_KEY_END))) || strncmp(silc_buffer_data(&line), SILC_SSH_PUBLIC_KEY_BEGIN, silc_buffer_len(&line))) { /* We assume the key is OpenSSH style public key. */ type = SILC_SSH_KEY_OPENSSH; silc_buffer_set(&keybuf, filedata, filedata_len); /* Get subject name from the end of the file */ if (!silc_buffer_strchr(&keybuf, ' ', FALSE)) { SILC_LOG_DEBUG(("Malformed SSH2 public key")); return FALSE; } if (!silc_buffer_pull(&keybuf, 1)) { SILC_LOG_DEBUG(("Malformed SSH2 public key")); return FALSE; } if (!silc_buffer_len(&keybuf)) { SILC_LOG_DEBUG(("Malformed SSH2 public key")); return FALSE; } /* Add subject name to public key headers */ fields = silc_ssh_allocate_fields(); if (!fields) return FALSE; silc_hash_table_add(fields, strdup("Subject"), silc_memdup(silc_buffer_data(&keybuf), silc_buffer_len(&keybuf))); filedata_len = silc_buffer_headlen(&keybuf) - 1; SILC_LOG_DEBUG(("Add Subject header to public key")); /* Skip algorithm name */ silc_buffer_start(&keybuf); if (!silc_buffer_strchr(&keybuf, ' ', TRUE)) { SILC_LOG_DEBUG(("Malformed SSH2 public key")); silc_hash_table_free(fields); return FALSE; } if (!silc_buffer_pull(&keybuf, 1)) { SILC_LOG_DEBUG(("Malformed SSH2 public key")); silc_hash_table_free(fields); return FALSE; } if (silc_buffer_len(&keybuf) < filedata_len) { SILC_LOG_DEBUG(("Malformed SSH2 public key")); silc_hash_table_free(fields); return FALSE; } filedata = silc_buffer_data(&keybuf); SILC_LOG_DEBUG(("Public key is OpenSSH public key")); } else { /* RFC 4716 style public key */ type = SILC_SSH_KEY_SSH2; filedata = silc_buffer_data(&keybuf); filedata_len = silc_buffer_len(&keybuf) - strlen(SILC_SSH_PUBLIC_KEY_END); silc_buffer_set(&keybuf, filedata, filedata_len); /* Parse public key headers */ fields = silc_ssh_parse_headers(&keybuf); if (!fields) return FALSE; filedata = silc_buffer_data(&keybuf); filedata_len = silc_buffer_len(&keybuf); SILC_LOG_DEBUG(("Public key is standard SSH2 public key")); } /* Decode */ data = silc_base64_decode(NULL, filedata, filedata_len, &filedata_len); if (!data) { silc_hash_table_free(fields); return FALSE; } filedata = data; /* Decode the public key */ ret = silc_pkcs_ssh_import_public_key(pkcs, NULL, filedata, filedata_len, (void *)&pubkey, ret_alg); silc_free(data); if (ret) { pubkey->fields = fields; pubkey->type = type; *ret_public_key = pubkey; SILC_LOG_DEBUG(("SSH2 public key file imported successfully")); return TRUE; } silc_hash_table_free(fields); return FALSE; } /* Import public key */ SILC_PKCS_IMPORT_PUBLIC_KEY(silc_pkcs_ssh_import_public_key) { SilcSshPublicKey pubkey; int ret; ret = silc_ssh_public_key_decode(key, key_len, &pubkey); if (ret) { if (ret_alg) *ret_alg = pubkey->pkcs; if (ret_public_key) *ret_public_key = pubkey; } return ret; } /* Export public key file */ SILC_PKCS_EXPORT_PUBLIC_KEY_FILE(silc_pkcs_ssh_export_public_key_file) { SilcSshPublicKey pubkey = public_key; SilcHashTableList htl; SilcBufferStruct buf, fields; unsigned char *key, *data; SilcUInt32 key_len; char *field, *value, tmp[1024], tmp2[1024 + 24]; int i, j, c; SILC_LOG_DEBUG(("Encoding %s public key file", pubkey->type == SILC_SSH_KEY_SSH2 ? "SSH2" : "OpenSSH")); /* Export key */ key = silc_pkcs_ssh_export_public_key(pkcs, stack, pubkey, &key_len); if (!key) return NULL; /* Base64 encode the key data */ if (pubkey->type == SILC_SSH_KEY_SSH2) data = silc_base64_encode_file(stack, key, key_len); else data = silc_base64_encode(stack, key, key_len); if (!data) return NULL; silc_sfree(stack, key); key = data; key_len = strlen(data); memset(&buf, 0, sizeof(buf)); memset(&fields, 0, sizeof(fields)); memset(tmp, 0, sizeof(tmp)); memset(tmp2, 0, sizeof(tmp2)); switch (pubkey->type) { case SILC_SSH_KEY_SSH2: /* RFC 4716 style public key file */ if (pubkey->fields) { /* Encode public key headers */ silc_hash_table_list(pubkey->fields, &htl); while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) { /* Wrap lines with over 72 characters */ silc_snprintf(tmp, sizeof(tmp), "%s: %s", field, value); for (i = 0, j = 0, c = 1; i < strlen(tmp); i++, c++) { if (c == 72) { tmp2[j++] = '\\'; tmp2[j++] = '\n'; i--; c = 0; continue; } tmp2[j++] = tmp[i]; } tmp2[j++] = '\n'; if (silc_buffer_sstrformat(stack, &fields, tmp2, SILC_STRFMT_END) < 0) { silc_buffer_spurge(stack, &fields); silc_sfree(stack, key); return NULL; } memset(tmp2, 0, sizeof(tmp2)); } silc_hash_table_list_reset(&htl); } /* Encode the file */ if (silc_buffer_sformat(stack, &buf, SILC_STR_UI32_STRING(SILC_SSH_PUBLIC_KEY_BEGIN), SILC_STR_UI32_STRING("\n"), SILC_STR_UI_XNSTRING(silc_buffer_data(&fields), silc_buffer_len(&fields)), SILC_STR_UI_XNSTRING(key, key_len), SILC_STR_UI32_STRING("\n"), SILC_STR_UI32_STRING(SILC_SSH_PUBLIC_KEY_END), SILC_STR_UI32_STRING("\n"), SILC_STR_END) < 0) { silc_buffer_spurge(stack, &fields); silc_sfree(stack, key); return NULL; } break; case SILC_SSH_KEY_OPENSSH: /* OpenSSH style public key file */ if (!strcmp(pubkey->pkcs->name, "rsa")) silc_snprintf(tmp, sizeof(tmp), "ssh-rsa "); else if (!strcmp(pubkey->pkcs->name, "dsa")) silc_snprintf(tmp, sizeof(tmp), "ssh-dss "); /* Get subject */ value = (char *)silc_ssh_public_key_get_field(pubkey, "Subject"); /* Encode the file */ if (silc_buffer_sformat(stack, &buf, SILC_STR_UI32_STRING(tmp), SILC_STR_UI_XNSTRING(key, key_len), SILC_STR_UI32_STRING(" "), SILC_STR_UI32_STRING(value), SILC_STR_UI32_STRING("\n"), SILC_STR_END) < 0) { silc_buffer_spurge(stack, &buf); silc_sfree(stack, key); return NULL; } break; default: silc_sfree(stack, key); return NULL; break; } silc_sfree(stack, key); key = silc_buffer_steal(&buf, ret_len); silc_buffer_spurge(stack, &fields); return key; } /* Export public key */ SILC_PKCS_EXPORT_PUBLIC_KEY(silc_pkcs_ssh_export_public_key) { return silc_ssh_public_key_encode(stack, public_key, ret_len); } /* Return key length in bits */ SILC_PKCS_PUBLIC_KEY_BITLEN(silc_pkcs_ssh_public_key_bitlen) { SilcSshPublicKey pubkey = public_key; return pubkey->pkcs->public_key_bitlen(pubkey->pkcs, pubkey->public_key); } /* Copy public key */ SILC_PKCS_PUBLIC_KEY_COPY(silc_pkcs_ssh_public_key_copy) { SilcSshPublicKey pubkey = public_key, new_pubkey; SilcHashTableList htl; char *field, *value; new_pubkey = silc_calloc(1, sizeof(*new_pubkey)); if (!new_pubkey) return NULL; new_pubkey->pkcs = pubkey->pkcs; new_pubkey->type = pubkey->type; new_pubkey->public_key = pubkey->pkcs->public_key_copy(pubkey->pkcs, pubkey->public_key); if (!new_pubkey->public_key) { silc_free(new_pubkey); return NULL; } if (pubkey->fields) { new_pubkey->fields = silc_ssh_allocate_fields(); if (!new_pubkey->fields) { pubkey->pkcs->public_key_free(pubkey->pkcs, pubkey->public_key); silc_free(new_pubkey); return NULL; } silc_hash_table_list(pubkey->fields, &htl); while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) silc_hash_table_add(new_pubkey->fields, strdup(field), strdup(value)); silc_hash_table_list_reset(&htl); } return new_pubkey; } /* Compare two public keys */ SILC_PKCS_PUBLIC_KEY_COMPARE(silc_pkcs_ssh_public_key_compare) { SilcSshPublicKey k1 = key1, k2 = key2; SilcHashTableList htl; char *field, *value, *value2; if (strcmp(k1->pkcs->name, k2->pkcs->name)) return FALSE; if (k1->fields && !k2->fields) return FALSE; if (!k1->fields && k2->fields) return FALSE; if (k1->fields && k2->fields) { if (silc_hash_table_count(k1->fields) != silc_hash_table_count(k2->fields)) return FALSE; silc_hash_table_list(k1->fields, &htl); while (silc_hash_table_get(&htl, (void *)&field, (void *)&value)) { value2 = (char *)silc_ssh_public_key_get_field(k2, field); if (!value2) return FALSE; if (strcmp(value, value2)) return FALSE; } silc_hash_table_list_reset(&htl); } return k1->pkcs->public_key_compare(k1->pkcs, k1->public_key, k2->public_key); } /* Free public key */ SILC_PKCS_PUBLIC_KEY_FREE(silc_pkcs_ssh_public_key_free) { silc_ssh_public_key_free(public_key); } /* Import private key file. Supports only OpenSSH (OpenSSL to be exact) private key files. */ SILC_PKCS_IMPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_import_private_key_file) { const SilcPKCSAlgorithm *alg; SilcSshPrivateKey privkey = NULL; SilcHashTable fields; SilcBufferStruct keybuf, line; unsigned char *data, iv[8], key[32]; SilcSshKeyType type; char *proctype, *dekinfo; SilcCipher des; SilcHash md5; int ret; SILC_LOG_DEBUG(("Parsing SSH2 private key file")); if (!ret_private_key) return FALSE; if (encoding == SILC_PKCS_FILE_BIN) return FALSE; silc_buffer_set(&keybuf, filedata, filedata_len); /* Check for private key markers */ if (!silc_ssh_parse_line(&keybuf, &line, TRUE)) { SILC_LOG_DEBUG(("Malformed SSH2 private key markers")); return FALSE; } if ((silc_buffer_len(&keybuf) < (strlen(SILC_SSH_RSA_BEGIN) + strlen(SILC_SSH_RSA_END))) || (strncmp(silc_buffer_data(&line), SILC_SSH_RSA_BEGIN, silc_buffer_len(&line)) && strncmp(silc_buffer_data(&line), SILC_SSH_DSA_BEGIN, silc_buffer_len(&line)))) { SILC_LOG_DEBUG(("Malformed SSH2 private key markers")); return FALSE; } /* Get PKCS algorithm */ if (!strncmp(silc_buffer_data(&line), SILC_SSH_RSA_BEGIN, silc_buffer_len(&line))) { alg = silc_pkcs_find_algorithm("rsa", "ssh"); if (!alg) { SILC_LOG_ERROR(("Unsupported PKCS algorithm rsa/ssh")); return FALSE; } } else if (!strncmp(silc_buffer_data(&line), SILC_SSH_DSA_BEGIN, silc_buffer_len(&line))) { alg = silc_pkcs_find_algorithm("dsa", "ssh"); if (!alg) { SILC_LOG_ERROR(("Unsupported PKCS algorithm dsa/ssh")); return FALSE; } } else return FALSE; type = SILC_SSH_KEY_OPENSSH; filedata = silc_buffer_data(&keybuf); /* Skip end marker */ if (!silc_buffer_strchr(&keybuf, '-', FALSE)) { SILC_LOG_DEBUG(("Malformed SSH2 private key markers")); return FALSE; } filedata_len = silc_buffer_data(&keybuf) - filedata; silc_buffer_set(&keybuf, filedata, filedata_len); /* Parse private key headers. They define how the private key has been encrypted. */ fields = silc_ssh_parse_headers(&keybuf); if (!fields) return FALSE; /* Skip empty line after headers */ if (silc_hash_table_count(fields) > 0) silc_ssh_parse_line(&keybuf, NULL, TRUE); filedata = silc_buffer_data(&keybuf); filedata_len = silc_buffer_len(&keybuf); /* Decode */ data = silc_base64_decode(NULL, filedata, filedata_len, &filedata_len); if (!data) { SILC_LOG_DEBUG(("Malformed SSH2 private key")); goto err; } filedata = data; SILC_LOG_DEBUG(("Private key is %s", (silc_hash_table_count(fields) ? "encrypted" : "not encrypted"))); if (silc_hash_table_count(fields) > 0 && passphrase) { /* Decrypt */ /* Get encryption info */ if (!silc_hash_table_find(fields, "Proc-Type", NULL, (void *)&proctype)) { SILC_LOG_ERROR(("Malformed SSH2 private key")); goto err; } if (strcmp(proctype, "4,ENCRYPTED")) { SILC_LOG_ERROR(("Malformed SSH2 private key")); goto err; } /* OpenSSH uses 3DES-EDE only */ if (!silc_hash_table_find(fields, "DEK-Info", NULL, (void *)&dekinfo)) { SILC_LOG_ERROR(("Malformed SSH2 private key")); goto err; } if (strncmp(dekinfo, "DES-EDE3-CBC", strlen("DES-EDE3-CBC"))) { SILC_LOG_ERROR(("Unsupported SSH2 private key cipher '%s'", dekinfo)); goto err; } /* Allocate cipher */ if (!silc_cipher_alloc("3des-168-cbc", &des)) { SILC_LOG_ERROR(("Unsupported algorithm 3des-168-cbc")); goto err; } /* Allocate hash */ if (!silc_hash_alloc("md5", &md5)) { SILC_LOG_ERROR(("Unsupported hash algorithm md5")); goto err; } /* Get IV from private key file */ dekinfo = strchr(dekinfo, ','); if (!dekinfo || strlen(dekinfo) < 16) { SILC_LOG_ERROR(("Malformed SSH2 private key")); goto err; } dekinfo++; silc_hex2data(dekinfo, iv, sizeof(iv), NULL); /* Generate key from passphrase and IV as salt. The passphrase is hashed with the IV, then rehashed with the previous hash, passphrase and the IV to produce the final key, which is the concatenation of the two hashes. */ silc_hash_init(md5); silc_hash_update(md5, passphrase, passphrase_len); silc_hash_update(md5, iv, 8); silc_hash_final(md5, key); silc_hash_init(md5); silc_hash_update(md5, key, 16); silc_hash_update(md5, passphrase, passphrase_len); silc_hash_update(md5, iv, 8); silc_hash_final(md5, key + 16); /* Decrypt */ silc_cipher_set_key(des, key, 192, FALSE); if (!silc_cipher_decrypt(des, filedata, filedata, filedata_len, iv)) { SILC_LOG_ERROR(("Malformed SSH2 private key")); silc_cipher_free(des); silc_hash_free(md5); goto err; } silc_cipher_free(des); silc_hash_free(md5); } /* Decode the private key */ ret = silc_pkcs_ssh_import_private_key(pkcs, alg, filedata, filedata_len, (void *)&privkey, ret_alg); silc_free(data); if (ret) { privkey->fields = fields; privkey->type = type; *ret_private_key = privkey; SILC_LOG_DEBUG(("SSH2 private key file imported successfully")); return TRUE; } err: if (fields) silc_hash_table_free(fields); return FALSE; } /* Import private key. The key format for RSA is PKCS#1 compliant and for DSA is equivalent to our DSA implementation, so we just simply call the algorithm specific import function to do the magic. */ SILC_PKCS_IMPORT_PRIVATE_KEY(silc_pkcs_ssh_import_private_key) { SilcSshPrivateKey privkey; int ret; if (!ret_private_key || !alg) return 0; /* Allocate SSH private key context */ privkey = silc_calloc(1, sizeof(*privkey)); if (!privkey) return 0; /* Import PKCS algorithm private key */ ret = alg->import_private_key(alg, key, key_len, &privkey->private_key); if (!ret) { silc_free(privkey); return 0; } privkey->pkcs = alg; privkey->type = SILC_SSH_KEY_OPENSSH; *ret_private_key = privkey; if (ret_alg) *ret_alg = alg; return ret; } /* Export private key file */ SILC_PKCS_EXPORT_PRIVATE_KEY_FILE(silc_pkcs_ssh_export_private_key_file) { SilcSshPrivateKey privkey = private_key; const SilcPKCSAlgorithm *alg = privkey->pkcs; SilcBufferStruct buf; unsigned char *key, *keyenc, ivdata[8], iv[16 + 1], enc[32]; SilcUInt32 key_len, pad_len; SilcCipher des = NULL; SilcHash md5 = NULL; SILC_LOG_DEBUG(("Encode SSH2 private key file")); /* Export the private key */ key = silc_pkcs_ssh_export_private_key(pkcs, stack, private_key, &key_len); if (!key) return NULL; memset(&buf, 0, sizeof(buf)); if (!strcmp(alg->name, "rsa")) { if (silc_buffer_sformat(stack, &buf, SILC_STR_ADVANCE, SILC_STR_UI32_STRING(SILC_SSH_RSA_BEGIN), SILC_STR_UI32_STRING("\n"), SILC_STR_END) < 0) goto err; } else if (!strcmp(alg->name, "dsa")) { if (silc_buffer_sformat(stack, &buf, SILC_STR_ADVANCE, SILC_STR_UI32_STRING(SILC_SSH_DSA_BEGIN), SILC_STR_UI32_STRING("\n"), SILC_STR_END) < 0) goto err; } else goto err; if (passphrase && strlen(passphrase) > 0) { /* Encrypt the key */ /* Allocate cipher */ if (!silc_cipher_alloc("3des-168-cbc", &des)) { SILC_LOG_ERROR(("Unsupported algorithm 3des-168-cbc")); goto err; } /* Allocate hash */ if (!silc_hash_alloc("md5", &md5)) { SILC_LOG_ERROR(("Unsupported hash algorithm md5")); goto err; } /* Generate IV */ silc_rng_get_rn_data(rng, sizeof(ivdata), ivdata, sizeof(ivdata)); silc_data2hex(ivdata, sizeof(ivdata), iv, sizeof(iv)); /* Encode header */ if (silc_buffer_sformat(stack, &buf, SILC_STR_ADVANCE, SILC_STR_UI32_STRING("Proc-Type: 4,ENCRYPTED\n"), SILC_STR_UI32_STRING("DEK-Info: DES-EDE3-CBC,"), SILC_STR_UI32_STRING(iv), SILC_STR_UI32_STRING("\n\n"), SILC_STR_END) < 0) goto err; /* Generate key from passphrase and IV as salt. The passphrase is hashed with the IV, then rehashed with the previous hash, passphrase and the IV to produce the final key, which is the concatenation of the two hashes. */ silc_hash_init(md5); silc_hash_update(md5, passphrase, passphrase_len); silc_hash_update(md5, ivdata, 8); silc_hash_final(md5, enc); silc_hash_init(md5); silc_hash_update(md5, enc, 16); silc_hash_update(md5, passphrase, passphrase_len); silc_hash_update(md5, ivdata, 8); silc_hash_final(md5, enc + 16); /* Pad */ pad_len = 8 - (key_len % 8); if (pad_len) { keyenc = silc_smalloc(stack, (key_len + pad_len) * sizeof(*keyenc)); if (!key) goto err; memset(keyenc + key_len, 'F', pad_len); memcpy(keyenc, key, key_len); } else { keyenc = silc_memdup(key, key_len); if (!keyenc) goto err; } /* Encrypt */ silc_cipher_set_key(des, enc, 192, TRUE); silc_cipher_encrypt(des, keyenc, keyenc, key_len + pad_len, ivdata); silc_sfree(stack, key); key = keyenc; key_len += pad_len; silc_cipher_free(des); silc_hash_free(md5); } /* Base64 encode */ keyenc = silc_base64_encode_file(stack, key, key_len); if (!keyenc) goto err; silc_sfree(stack, key); key = keyenc; key_len = strlen(keyenc); /* Encode rest of the public key */ if (!strcmp(alg->name, "rsa")) { if (silc_buffer_sformat(stack, &buf, SILC_STR_ADVANCE, SILC_STR_DATA(key, key_len), SILC_STR_UI32_STRING("\n"), SILC_STR_UI32_STRING(SILC_SSH_RSA_END), SILC_STR_UI32_STRING("\n"), SILC_STR_END) < 0) goto err; } else if (!strcmp(alg->name, "dsa")) { if (silc_buffer_sformat(stack, &buf, SILC_STR_ADVANCE, SILC_STR_DATA(key, key_len), SILC_STR_UI32_STRING("\n"), SILC_STR_UI32_STRING(SILC_SSH_DSA_END), SILC_STR_UI32_STRING("\n"), SILC_STR_END) < 0) goto err; } silc_sfree(stack, key); key = silc_buffer_steal(&buf, ret_len); return key; err: if (des) silc_cipher_free(des); if (md5) silc_hash_free(md5); silc_sfree(stack, key); return NULL; } /* Export private key */ SILC_PKCS_EXPORT_PRIVATE_KEY(silc_pkcs_ssh_export_private_key) { SilcSshPrivateKey privkey = private_key; const SilcPKCSAlgorithm *alg = privkey->pkcs; SILC_LOG_DEBUG(("Encode SSH2 private key")); /* Export PKCS algorithm private key */ if (alg->export_private_key) return alg->export_private_key(alg, stack, privkey->private_key, ret_len); return NULL; } /* Return key length in bits */ SILC_PKCS_PRIVATE_KEY_BITLEN(silc_pkcs_ssh_private_key_bitlen) { SilcSshPrivateKey privkey = private_key; return privkey->pkcs->private_key_bitlen(privkey->pkcs, privkey->private_key); } /* Free private key */ SILC_PKCS_PRIVATE_KEY_FREE(silc_pkcs_ssh_private_key_free) { SilcSshPrivateKey privkey = private_key; privkey->pkcs->private_key_free(privkey->pkcs, privkey->private_key); if (privkey->fields) silc_hash_table_free(privkey->fields); silc_free(privkey); } /* Encrypt */ SILC_PKCS_ENCRYPT(silc_pkcs_ssh_encrypt) { SilcSshPublicKey pubkey = public_key; if (!pubkey->pkcs->encrypt) { encrypt_cb(FALSE, NULL, 0, context); return NULL; } return pubkey->pkcs->encrypt(pubkey->pkcs, pubkey->public_key, src, src_len, rng, encrypt_cb, context); } /* Decrypt */ SILC_PKCS_DECRYPT(silc_pkcs_ssh_decrypt) { SilcSshPrivateKey privkey = private_key; if (!privkey->pkcs->decrypt) { decrypt_cb(FALSE, NULL, 0, context); return NULL; } return privkey->pkcs->decrypt(privkey->pkcs, privkey->private_key, src, src_len, decrypt_cb, context); } /* Sign */ SILC_PKCS_SIGN(silc_pkcs_ssh_sign) { SilcSshPrivateKey privkey = private_key; if (!privkey->pkcs->sign) { sign_cb(FALSE, NULL, 0, context); return NULL; } return privkey->pkcs->sign(privkey->pkcs, privkey->private_key, src, src_len, compute_hash, hash, rng, sign_cb, context); } /* Verify */ SILC_PKCS_VERIFY(silc_pkcs_ssh_verify) { SilcSshPublicKey pubkey = public_key; if (!pubkey->pkcs->verify) { verify_cb(FALSE, context); return NULL; } return pubkey->pkcs->verify(pubkey->pkcs, pubkey->public_key, signature, signature_len, data, data_len, hash, rng, verify_cb, context); } /************************** SSH2 PKCS RSA Alg API ***************************/ /* The SSH2 RSA PKCS Algorithm API. We implement here only the necessary parts of the API and the common code is used from PKCS#1 Algorithm API in silccrypt/silcpkcs1.c. Basically everything else is PKCS#1 except the format of the public key. */ /* Import RSA public key. Both RFC 4716 and OpenSSH have same format. */ SILC_PKCS_ALG_IMPORT_PUBLIC_KEY(silc_ssh_rsa_import_public_key) { SilcBufferStruct alg_key; RsaPublicKey *pubkey; unsigned char *n, *e; SilcUInt32 n_len, e_len; int ret; SILC_LOG_DEBUG(("Import public key")); if (!ret_public_key) return 0; /* Allocate RSA public key */ *ret_public_key = pubkey = silc_calloc(1, sizeof(*pubkey)); if (!pubkey) return 0; /* Parse SSH2 RSA public key */ silc_buffer_set(&alg_key, key, key_len); ret = silc_buffer_unformat(&alg_key, SILC_STR_UI32_NSTRING(&e, &e_len), SILC_STR_UI32_NSTRING(&n, &n_len), SILC_STR_END); if (ret < 0) goto err; if (!n_len || !e_len) goto err; /* Get MP integers */ silc_mp_init(&pubkey->n); silc_mp_init(&pubkey->e); silc_mp_bin2mp(n, n_len, &pubkey->n); silc_mp_bin2mp(e, e_len, &pubkey->e); /* Set key length */ pubkey->bits = ((silc_mp_sizeinbase(&pubkey->n, 2) + 7) / 8) * 8; return ret; err: silc_free(pubkey); return 0; } /* Export RSA public key. Both RFC 4716 and OpenSSH have same format. */ SILC_PKCS_ALG_EXPORT_PUBLIC_KEY(silc_ssh_rsa_export_public_key) { RsaPublicKey *pubkey = public_key; SilcBufferStruct alg_key; unsigned char *n = NULL, *e = NULL, *ret; SilcUInt32 n_len, e_len; SILC_LOG_DEBUG(("Export public key")); /* Encode MP integers */ n = silc_mp_mp2bin(&pubkey->n, 0, &n_len); if (!n) goto err; e = silc_mp_mp2bin(&pubkey->e, 0, &e_len); if (!e) goto err; /* Encode SSH2 RSA public key */ memset(&alg_key, 0, sizeof(alg_key)); if (silc_buffer_sformat(stack, &alg_key, SILC_STR_UI_INT(e_len), SILC_STR_DATA(e, e_len), SILC_STR_UI_INT(n_len), SILC_STR_DATA(n, n_len), SILC_STR_END) < 0) goto err; silc_free(n); silc_free(e); ret = silc_buffer_steal(&alg_key, ret_len); return ret; err: silc_free(n); silc_free(e); return NULL; } /************************** SSH2 PKCS DSA Alg API ***************************/ /* The SSH2 DSA PKCS Algorithm API. We implement here only the necessary parts of the API and the common code is used from DSS Algorithm API in silccrypt/dsa.c. */ /* Import DSA public key. Both RFC 4716 and OpenSSH have same format. */ SILC_PKCS_ALG_IMPORT_PUBLIC_KEY(silc_ssh_dsa_import_public_key) { SilcBufferStruct alg_key; DsaPublicKey *pubkey; unsigned char *p, *q, *g, *y; SilcUInt32 p_len, q_len, g_len, y_len; int ret; SILC_LOG_DEBUG(("Import public key")); if (!ret_public_key) return 0; /* Allocate DSA public key */ *ret_public_key = pubkey = silc_calloc(1, sizeof(*pubkey)); if (!pubkey) return 0; /* Parse SSH2 DSA public key */ silc_buffer_set(&alg_key, key, key_len); ret = silc_buffer_unformat(&alg_key, SILC_STR_UI32_NSTRING(&p, &p_len), SILC_STR_UI32_NSTRING(&q, &q_len), SILC_STR_UI32_NSTRING(&g, &g_len), SILC_STR_UI32_NSTRING(&y, &y_len), SILC_STR_END); if (ret < 0) goto err; if (!p_len || !q_len || !g_len || !y_len) goto err; /* Get MP integers */ silc_mp_init(&pubkey->p); silc_mp_init(&pubkey->q); silc_mp_init(&pubkey->g); silc_mp_init(&pubkey->y); silc_mp_bin2mp(p, p_len, &pubkey->p); silc_mp_bin2mp(q, q_len, &pubkey->q); silc_mp_bin2mp(g, g_len, &pubkey->g); silc_mp_bin2mp(y, y_len, &pubkey->y); /* Set key length */ pubkey->bits = ((silc_mp_sizeinbase(&pubkey->p, 2) + 7) / 8) * 8; return ret; err: silc_free(pubkey); return 0; } /* Export DSA public key. Both RFC 4716 and OpenSSH have same format. */ SILC_PKCS_ALG_EXPORT_PUBLIC_KEY(silc_ssh_dsa_export_public_key) { DsaPublicKey *pubkey = public_key; SilcBufferStruct alg_key; unsigned char *p = NULL, *q = NULL, *g = NULL, *y = NULL, *ret; SilcUInt32 p_len, q_len, g_len, y_len; SILC_LOG_DEBUG(("Export public key")); /* Encode MP integers */ p = silc_mp_mp2bin(&pubkey->p, 0, &p_len); if (!p) goto err; q = silc_mp_mp2bin(&pubkey->q, 0, &q_len); if (!q) goto err; g = silc_mp_mp2bin(&pubkey->g, 0, &g_len); if (!g) goto err; y = silc_mp_mp2bin(&pubkey->y, 0, &y_len); if (!y) goto err; /* Encode SSH2 DSA public key */ memset(&alg_key, 0, sizeof(alg_key)); if (silc_buffer_sformat(stack, &alg_key, SILC_STR_UI_INT(p_len), SILC_STR_DATA(p, p_len), SILC_STR_UI_INT(q_len), SILC_STR_DATA(q, q_len), SILC_STR_UI_INT(g_len), SILC_STR_DATA(g, g_len), SILC_STR_UI_INT(y_len), SILC_STR_DATA(y, y_len), SILC_STR_END) < 0) goto err; silc_free(p); silc_free(q); silc_free(g); silc_free(y); ret = silc_buffer_steal(&alg_key, ret_len); return ret; err: silc_free(p); silc_free(q); silc_free(g); silc_free(y); return NULL; }