diff --git a/src/scam/p15_ldap.c b/src/scam/p15_ldap.c index 54930a8e..fde01fc1 100644 --- a/src/scam/p15_ldap.c +++ b/src/scam/p15_ldap.c @@ -24,13 +24,13 @@ #endif #if defined(HAVE_OPENSSL) && defined(HAVE_LDAP) #include +#include #include #include #ifdef HAVE_UNISTD_H #include #endif -#include -#include +#include #include #include #include @@ -38,20 +38,27 @@ #include #include #include +#include +#include "cert_support.h" #include "scam.h" typedef struct _scam_method_data { struct sc_context *ctx; struct sc_card *card; struct sc_pkcs15_card *p15card; - scldap_context *lctx; int card_locked; struct sc_pkcs15_object *objs[32]; struct sc_pkcs15_cert_info *cinfo; struct sc_pkcs15_object *prkey, *pin; + + scldap_context *lctx; + char *scldap_entry; } scam_method_data; +#define MAX_PATHLEN 10 +#define MAX_ENTRYLEN 256 + const char *p15_ldap_usage(void) { static char buf[500]; @@ -180,6 +187,11 @@ int p15_ldap_init(scam_context * sctx, int argc, const char **argv) return SCAM_FAILED; } scldap_parse_arguments(&data->lctx, argc, argv); + data->scldap_entry = (char *) malloc(MAX_ENTRYLEN); + if (!data->scldap_entry) { + return SCAM_FAILED; + } + memset(data->scldap_entry, 0, MAX_ENTRYLEN); return SCAM_SUCCESS; } @@ -212,41 +224,211 @@ int p15_ldap_qualify(scam_context * sctx, unsigned char *password) return SCAM_SUCCESS; } +static int copy_result(scldap_result * lresult, unsigned char **result, unsigned long *resultlen) +{ + if (!lresult) + return -1; + *result = NULL; + *resultlen = 0; + *result = (unsigned char *) malloc(lresult->result[0].datalen + 1); + if (!*result) + return -1; + memset(*result, 0, lresult->result[0].datalen + 1); + memcpy(*result, lresult->result[0].data, lresult->result[0].datalen); + *resultlen = lresult->result[0].datalen; + return 0; +} + +static void modify_base(scam_context * sctx, const char *entry, char *dn) +{ + scam_method_data *data = (scam_method_data *) sctx->method_data; + char approx_entry[MAX_ENTRYLEN]; + int entrynum = -1; + + if (!sctx || !entry || !dn) + return; + entrynum = scldap_get_entry(data->lctx, entry); + if (entrynum < 0) { + return; + } + snprintf(approx_entry, MAX_ENTRYLEN, "%s %s approx base", data->p15card->label, data->p15card->manufacturer_id); + if (scldap_approx_base_by_dn(data->lctx, approx_entry, dn, &data->lctx->entry[entrynum].base) < 0) { + return; + } + sc_debug(data->ctx, "modify_base: %s\n", data->lctx->entry[entrynum].base); +} + int p15_ldap_auth(scam_context * sctx, int argc, const char **argv, const char *user, const char *password) { scam_method_data *data = (scam_method_data *) sctx->method_data; struct sc_pkcs15_cert *p15cert = NULL; + scCertificate *CardCert = NULL, *Cert = NULL, *CACerts[MAX_PATHLEN]; + X509 *currcert = NULL; u8 random_data[20], chg[256]; - int r, err = SCAM_FAILED, chglen; - EVP_PKEY *pubkey = NULL; - X509 *cert = NULL; - unsigned char *ptr = NULL; + scldap_result *lresult = NULL; + int r = 0, i = 0, err = SCAM_FAILED, chglen; if (!sctx->method_data) { return SCAM_FAILED; } + for (i = 0; i < MAX_PATHLEN; i++) { + CACerts[i] = NULL; + } + Cert = certAlloc(); + if (!Cert) { + goto end; + } + CardCert = certAlloc(); + if (!CardCert) { + goto end; + } r = sc_pkcs15_read_certificate(data->p15card, data->cinfo, &p15cert); if (r != SC_SUCCESS) { scam_print_msg(sctx, "sc_pkcs15_read_certificate: %s\n", sc_strerror(r)); goto end; } - cert = X509_new(); - ptr = p15cert->data; - if (!d2i_X509(&cert, &ptr, p15cert->data_len)) { - scam_log_msg(sctx, "Invalid certificate. (user %s)\n", user); + + /* FIXME */ + CardCert->len = p15cert->data_len; + CardCert->buf = (unsigned char *) malloc(CardCert->len); + if (!CardCert->buf) { + scam_print_msg(sctx, "out of memory\n", sc_strerror(r)); goto end; } - pubkey = X509_get_pubkey(cert); - if (!pubkey || pubkey->type != EVP_PKEY_RSA) { + memcpy(CardCert->buf, p15cert->data, p15cert->data_len); + + /* Parse user certificate just once into a x509 structure + and not for each individual operation */ + if (!(CardCert->cert = certParseCertificate(CardCert->buf, CardCert->len))) { + scam_print_msg(sctx, "certParseCertificate failed: invalid certificate.\n"); + goto end; + } + snprintf(data->scldap_entry, MAX_ENTRYLEN, "%s %s auth certificate", data->p15card->label, data->p15card->manufacturer_id); + modify_base(sctx, data->scldap_entry, certGetSubject(CardCert->cert)); + r = scldap_search(data->lctx, data->scldap_entry, &lresult, 1, user); + if ((r < 0) || (copy_result(lresult, &Cert->buf, &Cert->len) < 0)) { + scam_print_msg(sctx, "Search failed: no auth certificate found.\n"); + goto end; + } + scldap_free_result(lresult); + lresult = NULL; + if (memcmp(CardCert->buf, Cert->buf, Cert->len) != 0) { + scam_print_msg(sctx, "Certificate comparing failed.\n"); + goto end; + } + certFree(CardCert); + CardCert = NULL; + + /* Parse user certificate just once into a x509 structure + and not for each individual operation */ + if (!(Cert->cert = certParseCertificate(Cert->buf, Cert->len))) { + scam_print_msg(sctx, "certParseCertificate failed: invalid certificate.\n"); + goto end; + } + /* Do not accept self signed user certificates or certificates + without issuer or subject fields */ + if (certIsSelfSigned(Cert->cert) != 0) { + scam_print_msg(sctx, "certIsSelfSigned failed: certificate is not signed by a CA.\n"); + goto end; + } + /* We want an encipherment key */ + if ((r = certCheckKeyUsage(Cert->cert, DATA_ENCIPHERMENT)) < 1) { + scam_print_msg(sctx, "certCheckKeyUsage failed: certificate cannot be used for encipherment.\n"); + if (r == -1) { + scam_log_msg(sctx, "KeyUsage check failed (user %s).\n", user); + } else { + scam_log_msg(sctx, "Wrong certificate type (user %s).\n", user); + } + goto end; + } + if (!(Cert->pubkey = certParsePublicKey(Cert->cert))) { + scam_print_msg(sctx, "certParsePublicKey failed: invalid public key in certificate.\n"); + scam_log_msg(sctx, "Invalid public key (user %s).\n", user); + goto end; + } + if (Cert->pubkey->type != EVP_PKEY_RSA) { scam_log_msg(sctx, "Invalid public key. (user %s)\n", user); goto end; } - chglen = RSA_size(pubkey->pkey.rsa); + chglen = RSA_size(Cert->pubkey->pkey.rsa); if (chglen > sizeof(chg)) { scam_print_msg(sctx, "RSA key too big.\n"); goto end; } + /* Parse issuer from each one certificate + in turn when you get them from the LDAP serer and stuff them + all into the chain when we have other certificates than FINEID + until issuer == subject. */ + do { + char *distpoint = NULL; + + /* Find empty slot */ + for (i = 0; i < MAX_PATHLEN; i++) { + if (!(CACerts[i])) { + break; + } + } + + /* Path length exceeded */ + if (i == MAX_PATHLEN) { + goto end; + } + CACerts[i] = certAlloc(); + if (!(CACerts[i])) { + goto end; + } + snprintf(data->scldap_entry, MAX_ENTRYLEN, "%s %s ca certificate", data->p15card->label, data->p15card->manufacturer_id); + modify_base(sctx, data->scldap_entry, certGetIssuer(Cert->cert)); + r = scldap_search(data->lctx, data->scldap_entry, &lresult, 1, NULL); + if ((r < 0) || (copy_result(lresult, &CACerts[i]->buf, &CACerts[i]->len) < 0)) { + scam_print_msg(sctx, "Search failed: no CA certificate.\n"); + goto end; + } + scldap_free_result(lresult); + lresult = NULL; + + /* Parse CA certificate into a x509 structure */ + if (!(CACerts[i]->cert = certParseCertificate(CACerts[i]->buf, CACerts[i]->len))) { + scam_print_msg(sctx, "certParseCertificate failed: invalid CA certificate.\n"); + goto end; + } + distpoint = certGetCRLDistributionPoint(Cert->cert); + snprintf(data->scldap_entry, MAX_ENTRYLEN, "%s %s crl", data->p15card->label, data->p15card->manufacturer_id); + + if (scldap_is_valid_url(distpoint)) { + if (scldap_url_to_entry(data->lctx, data->scldap_entry, distpoint) < 0) { + scam_print_msg(sctx, "scldap_url_to_entry failed: invalid CRL.\n"); + free(distpoint); + distpoint = NULL; + goto end; + } + } + free(distpoint); + distpoint = NULL; + r = scldap_search(data->lctx, data->scldap_entry, &lresult, 1, NULL); + if ((r < 0) || (copy_result(lresult, &CACerts[i]->crlbuf, &CACerts[i]->crllen) < 0)) { + scam_print_msg(sctx, "Search failed: CRL not found.\n"); + goto end; + } + scldap_free_result(lresult); + lresult = NULL; + + if (!(CACerts[i]->crl = certParseCRL(CACerts[i]->crlbuf, CACerts[i]->crllen))) { + scam_print_msg(sctx, "certParseCRL failed: invalid CRL.\n"); + scam_log_msg(sctx, "Could not parse CA CRL.\n"); + goto end; + } + currcert = CACerts[i]->cert; + } while (!certIsSelfSigned(currcert)); + + if ((r = certVerifyCAChain(CACerts, Cert->cert)) != 0) { + scam_print_msg(sctx, "certVerifyCAChain failed: certificate has invalid information.\n"); + scam_log_msg(sctx, "certVerifyCAChain failed: %s.\n", certError((unsigned long) r)); + goto end; + } + certFreeAll(CACerts); + r = scrandom_get_data(random_data, sizeof(random_data)); if (r < 0) { scam_log_msg(sctx, "scrandom_get_data failed.\n"); @@ -277,17 +459,18 @@ int p15_ldap_auth(scam_context * sctx, int argc, const char **argv, goto end; } - r = RSA_verify(NID_sha1, random_data, 20, chg, chglen, pubkey->pkey.rsa); + r = RSA_verify(NID_sha1, random_data, 20, chg, chglen, Cert->pubkey->pkey.rsa); if (r != 1) { scam_print_msg(sctx, "Signature verification failed.\n"); goto end; } err = SCAM_SUCCESS; end: - if (pubkey) - EVP_PKEY_free(pubkey); - if (cert) - X509_free(cert); + if (CardCert) + certFree(CardCert); + if (Cert) + certFree(Cert); + certFreeAll(CACerts); if (p15cert) sc_pkcs15_free_certificate(p15cert); return err; @@ -300,6 +483,10 @@ void p15_ldap_deinit(scam_context * sctx) if (!sctx->method_data) { return; } + if (data->scldap_entry) { + free(data->scldap_entry); + } + data->scldap_entry = NULL; if (data->lctx) { scldap_free_parameters(data->lctx); }