From 57922fa5cc21f455226d4725d6b6d303710b8534 Mon Sep 17 00:00:00 2001 From: pjd Date: Wed, 27 Jul 2005 21:43:37 +0000 Subject: Add GEOM_ELI class which provides GEOM providers encryption. For features list and usage see manual page: geli(8). Sponsored by: Wheel Sp. z o.o. http://www.wheel.pl MFC after: 1 week --- sys/geom/eli/g_eli.c | 1138 +++++++++++++++++++++++++++++++++++++++++++ sys/geom/eli/g_eli.h | 366 ++++++++++++++ sys/geom/eli/g_eli_crypto.c | 276 +++++++++++ sys/geom/eli/g_eli_ctl.c | 639 ++++++++++++++++++++++++ sys/geom/eli/g_eli_key.c | 179 +++++++ sys/geom/eli/pkcs5v2.c | 123 +++++ sys/geom/eli/pkcs5v2.h | 36 ++ 7 files changed, 2757 insertions(+) create mode 100644 sys/geom/eli/g_eli.c create mode 100644 sys/geom/eli/g_eli.h create mode 100644 sys/geom/eli/g_eli_crypto.c create mode 100644 sys/geom/eli/g_eli_ctl.c create mode 100644 sys/geom/eli/g_eli_key.c create mode 100644 sys/geom/eli/pkcs5v2.c create mode 100644 sys/geom/eli/pkcs5v2.h (limited to 'sys/geom') diff --git a/sys/geom/eli/g_eli.c b/sys/geom/eli/g_eli.c new file mode 100644 index 0000000..d837ce4 --- /dev/null +++ b/sys/geom/eli/g_eli.c @@ -0,0 +1,1138 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + + +MALLOC_DEFINE(M_ELI, "eli data", "GEOM_ELI Data"); + +SYSCTL_DECL(_kern_geom); +SYSCTL_NODE(_kern_geom, OID_AUTO, eli, CTLFLAG_RW, 0, "GEOM_ELI stuff"); +u_int g_eli_debug = 1; +TUNABLE_INT("kern.geom.eli.debug", &g_eli_debug); +SYSCTL_UINT(_kern_geom_eli, OID_AUTO, debug, CTLFLAG_RW, &g_eli_debug, 0, + "Debug level"); +static u_int g_eli_tries = 3; +TUNABLE_INT("kern.geom.eli.tries", &g_eli_tries); +SYSCTL_UINT(_kern_geom_eli, OID_AUTO, tries, CTLFLAG_RW, &g_eli_tries, 0, + "Number of tries when asking for passphrase"); +static u_int g_eli_visible_passphrase = 0; +TUNABLE_INT("kern.geom.eli.visible_passphrase", &g_eli_visible_passphrase); +SYSCTL_UINT(_kern_geom_eli, OID_AUTO, visible_passphrase, CTLFLAG_RW, + &g_eli_visible_passphrase, 0, + "Turn on echo when entering passphrase (debug purposes only!!)"); +u_int g_eli_overwrites = 5; +SYSCTL_UINT(_kern_geom_eli, OID_AUTO, overwrites, CTLFLAG_RW, &g_eli_overwrites, + 0, "Number of overwrites on-disk keys when destroying"); +static u_int g_eli_threads = 1; +TUNABLE_INT("kern.geom.eli.threads", &g_eli_threads); +SYSCTL_UINT(_kern_geom_eli, OID_AUTO, threads, CTLFLAG_RW, &g_eli_threads, 0, + "Number of threads doing crypto work"); + +static int g_eli_do_taste = 0; + +static int g_eli_destroy_geom(struct gctl_req *req, struct g_class *mp, + struct g_geom *gp); +static int g_eli_crypto_run(struct g_eli_worker *wr, struct bio *bp); + +static g_taste_t g_eli_taste; +static g_dumpconf_t g_eli_dumpconf; + +struct g_class g_eli_class = { + .name = G_ELI_CLASS_NAME, + .version = G_VERSION, + .ctlreq = g_eli_config, + .taste = g_eli_taste, + .destroy_geom = g_eli_destroy_geom +}; + + +/* + * Code paths: + * BIO_READ: + * g_eli_start -> g_io_request -> g_eli_read_done -> g_eli_crypto_run -> g_eli_crypto_read_done -> g_io_deliver + * BIO_WRITE: + * g_eli_start -> g_eli_crypto_run -> g_eli_crypto_write_done -> g_io_request -> g_eli_write_done -> g_io_deliver + */ + + +/* + * EAGAIN from crypto(9) means, that we were probably balanced to another crypto + * accelerator or something like this. + * The function updates the SID and rerun the operation. + */ +static int +g_eli_crypto_rerun(struct cryptop *crp) +{ + struct g_eli_softc *sc; + struct g_eli_worker *wr; + struct bio *bp; + int error; + + bp = (struct bio *)crp->crp_opaque; + sc = bp->bio_to->geom->softc; + LIST_FOREACH(wr, &sc->sc_workers, w_next) { + if (wr->w_number == bp->bio_pflags) + break; + } + KASSERT(wr != NULL, ("Invalid worker (%u).", bp->bio_pflags)); + G_ELI_DEBUG(1, "Reruning crypto %s request (sid: %ju -> %ju).", + bp->bio_cmd == BIO_READ ? "READ" : "WRITE", (uintmax_t)wr->w_sid, + (uintmax_t)crp->crp_sid); + wr->w_sid = crp->crp_sid; + crp->crp_etype = 0; + error = crypto_dispatch(crp); + if (error == 0) + return (0); + G_ELI_DEBUG(1, "%s: crypto_dispatch() returned %d.", __func__, error); + crp->crp_etype = error; + return (error); +} + +/* + * The function is called afer reading encrypted data from the provider. + */ +static void +g_eli_read_done(struct bio *bp) +{ + struct g_eli_softc *sc; + struct bio *pbp; + int error; + + G_ELI_LOGREQ(2, bp, "Request done."); + pbp = bp->bio_parent; + if (pbp->bio_error == 0) + pbp->bio_error = bp->bio_error; + g_destroy_bio(bp); + if (pbp->bio_error != 0) { + G_ELI_LOGREQ(0, pbp, "%s() failed", __func__); + pbp->bio_completed = 0; + g_io_deliver(pbp, pbp->bio_error); + return; + } + sc = pbp->bio_to->geom->softc; + /* + * If we have hardware acceleration we can call g_eli_crypto_run() + * directly. If not, put it on the queue and wakeup worker thread, + * which will do the work for us, so we don't slow down g_up path. + */ + if (sc->sc_crypto == G_ELI_CRYPTO_HW) { + struct g_eli_worker *wr; + + wr = LIST_FIRST(&sc->sc_workers); + error = g_eli_crypto_run(wr, pbp); + if (error != 0) { + G_ELI_LOGREQ(0, pbp, + "g_eli_crypto_run() failed (error=%d).", error); + pbp->bio_completed = 0; + g_io_deliver(pbp, error); + } + } else { + mtx_lock(&sc->sc_queue_mtx); + bioq_insert_tail(&sc->sc_queue, pbp); + mtx_unlock(&sc->sc_queue_mtx); + wakeup(sc); + } +} + +/* + * The function is called after we read and decrypt data. + */ +static int +g_eli_crypto_read_done(struct cryptop *crp) +{ + struct bio *bp; + + if (crp->crp_etype == EAGAIN) { + if (g_eli_crypto_rerun(crp) == 0) + return (0); + } + bp = (struct bio *)crp->crp_opaque; + bp->bio_inbed++; + if (crp->crp_etype == 0) { + G_ELI_DEBUG(3, "Crypto READ request done (%d/%d).", + bp->bio_inbed, bp->bio_children); + bp->bio_completed += crp->crp_olen; + } else { + G_ELI_DEBUG(1, "Crypto READ request failed (%d/%d) error=%d.", + bp->bio_inbed, bp->bio_children, crp->crp_etype); + if (bp->bio_error == 0) + bp->bio_error = crp->crp_etype; + } + /* + * Do we have all sectors already? + */ + if (bp->bio_inbed < bp->bio_children) + return (0); + free(bp->bio_driver2, M_ELI); + bp->bio_driver2 = NULL; + if (bp->bio_error != 0) { + G_ELI_LOGREQ(0, bp, "Crypto READ request failed (error=%d).", + bp->bio_error); + bp->bio_completed = 0; + } + /* + * Read is finished, send it up. + */ + g_io_deliver(bp, bp->bio_error); + return (0); +} + +/* + * The function is called after we encrypt and write data. + */ +static void +g_eli_write_done(struct bio *bp) +{ + struct bio *pbp; + + G_ELI_LOGREQ(2, bp, "Request done."); + pbp = bp->bio_parent; + if (pbp->bio_error == 0) + pbp->bio_error = bp->bio_error; + free(pbp->bio_driver2, M_ELI); + pbp->bio_driver2 = NULL; + if (pbp->bio_error == 0) + pbp->bio_completed = pbp->bio_length; + else { + G_ELI_LOGREQ(0, pbp, "Crypto READ request failed (error=%d).", + pbp->bio_error); + pbp->bio_completed = 0; + } + g_destroy_bio(bp); + /* + * Write is finished, send it up. + */ + g_io_deliver(pbp, pbp->bio_error); +} + +/* + * The function is called after data encryption. + */ +static int +g_eli_crypto_write_done(struct cryptop *crp) +{ + struct g_geom *gp; + struct g_consumer *cp; + struct bio *bp, *cbp; + + if (crp->crp_etype == EAGAIN) { + if (g_eli_crypto_rerun(crp) == 0) + return (0); + } + bp = (struct bio *)crp->crp_opaque; + bp->bio_inbed++; + if (crp->crp_etype == 0) { + G_ELI_DEBUG(3, "Crypto WRITE request done (%d/%d).", + bp->bio_inbed, bp->bio_children); + } else { + G_ELI_DEBUG(1, "Crypto WRITE request failed (%d/%d) error=%d.", + bp->bio_inbed, bp->bio_children, crp->crp_etype); + if (bp->bio_error == 0) + bp->bio_error = crp->crp_etype; + } + /* + * All sectors are already encrypted? + */ + if (bp->bio_inbed < bp->bio_children) + return (0); + bp->bio_inbed = 0; + bp->bio_children = 1; + cbp = bp->bio_driver1; + bp->bio_driver1 = NULL; + if (bp->bio_error != 0) { + G_ELI_LOGREQ(0, bp, "Crypto WRITE request failed (error=%d).", + bp->bio_error); + free(bp->bio_driver2, M_ELI); + bp->bio_driver2 = NULL; + g_destroy_bio(cbp); + g_io_deliver(bp, bp->bio_error); + return (0); + } + cbp->bio_data = bp->bio_driver2; + cbp->bio_done = g_eli_write_done; + gp = bp->bio_to->geom; + cp = LIST_FIRST(&gp->consumer); + cbp->bio_to = cp->provider; + G_ELI_LOGREQ(2, cbp, "Sending request."); + /* + * Send encrypted data to the provider. + */ + g_io_request(cbp, cp); + return (0); +} + +/* + * This function should never be called, but GEOM made as it set ->orphan() + * method for every geom. + */ +static void +g_eli_orphan_spoil_assert(struct g_consumer *cp) +{ + + panic("Function %s() called for %s.", __func__, cp->geom->name); +} + +static void +g_eli_orphan(struct g_consumer *cp) +{ + struct g_eli_softc *sc; + + g_topology_assert(); + sc = cp->geom->softc; + if (sc == NULL) + return; + g_eli_destroy(sc, 1); +} + +static void +g_eli_start(struct bio *bp) +{ + struct g_eli_softc *sc; + struct bio *cbp; + + sc = bp->bio_to->geom->softc; + KASSERT(sc != NULL, + ("Provider's error should be set (error=%d)(device=%s).", + bp->bio_to->error, bp->bio_to->name)); + G_ELI_LOGREQ(2, bp, "Request received."); + + switch (bp->bio_cmd) { + case BIO_READ: + case BIO_WRITE: + break; + case BIO_DELETE: + /* + * We could eventually support BIO_DELETE request. + * It could be done by overwritting requested sector with + * random data g_eli_overwrites number of times. + */ + case BIO_GETATTR: + default: + g_io_deliver(bp, EOPNOTSUPP); + return; + } + cbp = g_clone_bio(bp); + if (cbp == NULL) { + g_io_deliver(bp, ENOMEM); + return; + } + if (bp->bio_cmd == BIO_READ) { + struct g_consumer *cp; + + cbp->bio_done = g_eli_read_done; + cp = LIST_FIRST(&sc->sc_geom->consumer); + cbp->bio_to = cp->provider; + G_ELI_LOGREQ(2, bp, "Sending request."); + /* + * Read encrypted data from provider. + */ + g_io_request(cbp, cp); + } else /* if (bp->bio_cmd == BIO_WRITE) */ { + struct g_eli_worker *wr; + int error; + + bp->bio_driver1 = cbp; + wr = LIST_FIRST(&sc->sc_workers); + /* + * If we have hardware acceleration we can call + * g_eli_crypto_run() directly. If not, put it on the queue and + * wakeup worker thread, which will do the work for us, so we + * don't slow down g_down path. + */ + if (sc->sc_crypto == G_ELI_CRYPTO_HW) { + error = g_eli_crypto_run(wr, bp); + if (error != 0) { + G_ELI_LOGREQ(0, bp, + "g_eli_crypto_run() failed (error=%d).", + error); + g_destroy_bio(cbp); + bp->bio_completed = 0; + g_io_deliver(bp, error); + } + } else { + mtx_lock(&sc->sc_queue_mtx); + bioq_insert_tail(&sc->sc_queue, bp); + mtx_unlock(&sc->sc_queue_mtx); + wakeup(sc); + } + } +} + +/* + * This is the main function for kernel worker thread when we don't have + * hardware acceleration and we have to do cryptography in software. + * Dedicated thread is needed, so we don't slow down g_up/g_down GEOM + * threads with crypto work. + */ +static void +g_eli_worker(void *arg) +{ + struct g_eli_softc *sc; + struct g_eli_worker *wr; + struct bio *bp; + int error; + + wr = arg; + sc = wr->w_softc; + mtx_lock_spin(&sched_lock); + sched_prio(curthread, PRIBIO); + mtx_unlock_spin(&sched_lock); + + G_ELI_DEBUG(1, "Thread %s started.", curthread->td_proc->p_comm); + + for (;;) { + mtx_lock(&sc->sc_queue_mtx); + bp = bioq_takefirst(&sc->sc_queue); + if (bp == NULL) { + if ((sc->sc_flags & G_ELI_FLAG_DESTROY) != 0) { + LIST_REMOVE(wr, w_next); + crypto_freesession(wr->w_sid); + free(wr, M_ELI); + G_ELI_DEBUG(1, "Thread %s exiting.", + curthread->td_proc->p_comm); + wakeup(&sc->sc_workers); + mtx_unlock(&sc->sc_queue_mtx); + kthread_exit(0); + } + msleep(sc, &sc->sc_queue_mtx, PRIBIO | PDROP, + "geli:w", 0); + continue; + } + mtx_unlock(&sc->sc_queue_mtx); + error = g_eli_crypto_run(wr, bp); + if (error != 0) { + G_ELI_LOGREQ(0, bp, + "g_eli_crypto_run() failed (error=%d).", error); + if (bp->bio_cmd == BIO_WRITE) { + g_destroy_bio(bp->bio_driver1); + bp->bio_driver1 = NULL; + } + bp->bio_driver2 = NULL; + bp->bio_completed = 0; + g_io_deliver(bp, error); + } + } +} + +/* + * Here we generate IV. It is unique for every sector. + */ +static void +g_eli_crypto_ivgen(struct g_eli_softc *sc, off_t offset, u_char *iv, + size_t size) +{ + u_char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX ctx; + + /* Copy precalculated SHA256 context for IV-Key. */ + bcopy(&sc->sc_ivctx, &ctx, sizeof(ctx)); + SHA256_Update(&ctx, (uint8_t *)&offset, sizeof(offset)); + SHA256_Final(hash, &ctx); + bcopy(hash, iv, size); +} + +/* + * This is the main function responsible for cryptography (ie. communication + * with crypto(9) subsystem). + */ +static int +g_eli_crypto_run(struct g_eli_worker *wr, struct bio *bp) +{ + struct g_eli_softc *sc; + struct cryptop *crp; + struct cryptodesc *crd; + struct uio *uio; + struct iovec *iov; + u_int i, nsec, add, secsize; + int err, error, flags; + size_t size; + u_char *p, *data; + + G_ELI_LOGREQ(3, bp, "%s", __func__); + + bp->bio_pflags = wr->w_number; + sc = wr->w_softc; + secsize = LIST_FIRST(&sc->sc_geom->provider)->sectorsize; + nsec = bp->bio_length / secsize; + + /* + * Calculate how much memory do we need. + * We need separate crypto operation for every single sector. + * It is much faster to calculate total amount of needed memory here and + * do the allocation once insteaf of allocate memory in pieces (many, + * many pieces). + */ + size = sizeof(*crp) * nsec; + size += sizeof(*crd) * nsec; + size += sizeof(*uio) * nsec; + size += sizeof(*iov) * nsec; + /* + * If we write the data we cannot destroy current bio_data content, + * so we need to allocate more memory for encrypted data. + */ + if (bp->bio_cmd == BIO_WRITE) + size += bp->bio_length; + p = malloc(size, M_ELI, M_NOWAIT | M_ZERO); + if (p == NULL) + return (ENOMEM); + + bp->bio_inbed = 0; + bp->bio_children = nsec; + bp->bio_driver2 = p; + + if (bp->bio_cmd == BIO_READ) + data = bp->bio_data; + else { + data = p; + p += bp->bio_length; + bcopy(bp->bio_data, data, bp->bio_length); + } + + error = 0; + for (i = 0, add = 0; i < nsec; i++, add += secsize) { + crp = (struct cryptop *)p; p += sizeof(*crp); + crd = (struct cryptodesc *)p; p += sizeof(*crd); + uio = (struct uio *)p; p += sizeof(*uio); + iov = (struct iovec *)p; p += sizeof(*iov); + + iov->iov_len = secsize; + iov->iov_base = data; + data += secsize; + + uio->uio_iov = iov; + uio->uio_iovcnt = 1; + uio->uio_segflg = UIO_SYSSPACE; + uio->uio_resid = secsize; + + crp->crp_sid = wr->w_sid; + crp->crp_ilen = secsize; + crp->crp_olen = secsize; + crp->crp_opaque = (void *)bp; + crp->crp_buf = (void *)uio; + if (bp->bio_cmd == BIO_WRITE) + crp->crp_callback = g_eli_crypto_write_done; + else /* if (bp->bio_cmd == BIO_READ) */ + crp->crp_callback = g_eli_crypto_read_done; + crp->crp_flags = CRYPTO_F_IOV | CRYPTO_F_CBIFSYNC | CRYPTO_F_REL; + crp->crp_desc = crd; + + crd->crd_skip = 0; + crd->crd_len = secsize; + crd->crd_flags = flags; + crd->crd_flags = + CRD_F_IV_EXPLICIT | CRD_F_IV_PRESENT | CRD_F_KEY_EXPLICIT; + if (bp->bio_cmd == BIO_WRITE) + crd->crd_flags |= CRD_F_ENCRYPT; + crd->crd_alg = sc->sc_algo; + crd->crd_key = sc->sc_datakey; + crd->crd_klen = sc->sc_keylen; + g_eli_crypto_ivgen(sc, bp->bio_offset + add, crd->crd_iv, + sizeof(crd->crd_iv)); + crd->crd_next = NULL; + + err = crypto_dispatch(crp); + if (err != 0) { + G_ELI_DEBUG(1, "crypto_dispatch() returned %d.", err); + bp->bio_children--; + if (error == 0) + error = err; + } + } + if (bp->bio_error == 0) + bp->bio_error = error; + if (bp->bio_children > 0) + error = 0; + else { + free(bp->bio_driver2, M_ELI); + bp->bio_driver2 = NULL; + } + return (error); +} + +int +g_eli_read_metadata(struct g_class *mp, struct g_provider *pp, + struct g_eli_metadata *md) +{ + struct g_geom *gp; + struct g_consumer *cp; + u_char *buf = NULL; + int error; + + g_topology_assert(); + + gp = g_new_geomf(mp, "eli:taste"); + gp->start = g_eli_start; + gp->access = g_std_access; + /* + * g_eli_read_metadata() is always called from the event thread. + * Our geom is created and destroyed in the same event, so there + * could be no orphan nor spoil event in the meantime. + */ + gp->orphan = g_eli_orphan_spoil_assert; + gp->spoiled = g_eli_orphan_spoil_assert; + cp = g_new_consumer(gp); + error = g_attach(cp, pp); + if (error != 0) + goto end; + error = g_access(cp, 1, 0, 0); + if (error != 0) + goto end; + g_topology_unlock(); + buf = g_read_data(cp, pp->mediasize - pp->sectorsize, pp->sectorsize, + &error); + g_topology_lock(); + if (error != 0) + goto end; + eli_metadata_decode(buf, md); +end: + if (buf != NULL) + g_free(buf); + if (cp->provider != NULL) { + if (cp->acr == 1) + g_access(cp, -1, 0, 0); + g_detach(cp); + } + g_destroy_consumer(cp); + g_destroy_geom(gp); + return (error); +} + +/* + * The function is called when we had last close on provider and user requested + * to close it when this situation occur. + */ +static void +g_eli_last_close(struct g_eli_softc *sc) +{ + struct g_geom *gp; + struct g_provider *pp; + char ppname[64]; + int error; + + g_topology_assert(); + gp = sc->sc_geom; + pp = LIST_FIRST(&gp->provider); + strlcpy(ppname, pp->name, sizeof(ppname)); + error = g_eli_destroy(sc, 1); + KASSERT(error == 0, ("Cannot detach %s on last close (error=%d).", + ppname, error)); + G_ELI_DEBUG(0, "Detached %s on last close.", ppname); +} + +int +g_eli_access(struct g_provider *pp, int dr, int dw, int de) +{ + struct g_eli_softc *sc; + struct g_geom *gp; + + gp = pp->geom; + sc = gp->softc; + + if (dw > 0) { + /* Someone is opening us for write, we need to remember that. */ + sc->sc_flags |= G_ELI_FLAG_WOPEN; + return (0); + } + /* Is this the last close? */ + if (pp->acr + dr > 0 || pp->acw + dw > 0 || pp->ace + de > 0) + return (0); + + /* + * Automatically detach on last close if requested. + */ + if ((sc->sc_flags & G_ELI_FLAG_RW_DETACH) || + (sc->sc_flags & G_ELI_FLAG_WOPEN)) { + g_eli_last_close(sc); + } + return (0); +} + +struct g_geom * +g_eli_create(struct gctl_req *req, struct g_class *mp, struct g_provider *bpp, + const struct g_eli_metadata *md, const u_char *mkey, int nkey) +{ + struct g_eli_softc *sc; + struct g_eli_worker *wr; + struct g_geom *gp; + struct g_provider *pp; + struct g_consumer *cp; + struct cryptoini cri; + u_int i, threads; + int error; + + G_ELI_DEBUG(1, "Creating device %s%s.", bpp->name, G_ELI_SUFFIX); + + gp = g_new_geomf(mp, "%s%s", bpp->name, G_ELI_SUFFIX); + gp->softc = NULL; /* for a moment */ + + sc = malloc(sizeof(*sc), M_ELI, M_WAITOK | M_ZERO); + gp->start = g_eli_start; + /* + * Spoiling cannot happen actually, because we keep provider open for + * writing all the time. + */ + gp->spoiled = g_eli_orphan_spoil_assert; + gp->orphan = g_eli_orphan; + /* + * If detach-on-last-close feature is not enabled, we can simply use + * g_std_access(). + */ + if (md->md_flags & G_ELI_FLAG_WO_DETACH) + gp->access = g_eli_access; + else + gp->access = g_std_access; + gp->dumpconf = g_eli_dumpconf; + + sc->sc_crypto = G_ELI_CRYPTO_SW; + sc->sc_flags = md->md_flags; + sc->sc_algo = md->md_algo; + sc->sc_nkey = nkey; + /* + * Remember the keys in our softc structure. + */ + bcopy(mkey, sc->sc_ivkey, sizeof(sc->sc_ivkey)); + mkey += sizeof(sc->sc_ivkey); + bcopy(mkey, sc->sc_datakey, sizeof(sc->sc_datakey)); + sc->sc_keylen = md->md_keylen; + + /* + * Precalculate SHA256 for IV generation. + * This is expensive operation and we can do it only once now or for + * every access to sector, so now will be much better. + */ + SHA256_Init(&sc->sc_ivctx); + SHA256_Update(&sc->sc_ivctx, sc->sc_ivkey, sizeof(sc->sc_ivkey)); + + gp->softc = sc; + sc->sc_geom = gp; + + bioq_init(&sc->sc_queue); + mtx_init(&sc->sc_queue_mtx, "geli:queue", NULL, MTX_DEF); + + pp = NULL; + cp = g_new_consumer(gp); + error = g_attach(cp, bpp); + if (error != 0) { + if (req != NULL) { + gctl_error(req, "Cannot attach to %s (error=%d).", + bpp->name, error); + } else { + G_ELI_DEBUG(1, "Cannot attach to %s (error=%d).", + bpp->name, error); + } + goto failed; + } + /* + * Keep provider open all the time, so we can run critical tasks, + * like Master Keys deletion, without wondering if we can open + * provider or not. + */ + error = g_access(cp, 1, 1, 1); + if (error != 0) { + if (req != NULL) { + gctl_error(req, "Cannot access %s (error=%d).", + bpp->name, error); + } else { + G_ELI_DEBUG(1, "Cannot access %s (error=%d).", + bpp->name, error); + } + goto failed; + } + + LIST_INIT(&sc->sc_workers); + + bzero(&cri, sizeof(cri)); + cri.cri_alg = sc->sc_algo; + cri.cri_klen = sc->sc_keylen; + cri.cri_key = sc->sc_datakey; + + threads = g_eli_threads; + /* There is really no need for too many worker threads. */ + if (threads > MAXCPU) { + G_ELI_DEBUG(0, "Reducing number of threads to %u.", MAXCPU); + threads = MAXCPU; + } + for (i = 0; i < threads; i++) { + wr = malloc(sizeof(*wr), M_ELI, M_WAITOK | M_ZERO); + wr->w_softc = sc; + wr->w_number = i; + + /* + * If this is the first pass, try to get hardware support. + * Use software cryptography, if we cannot get it. + */ + if (i == 0) { + error = crypto_newsession(&wr->w_sid, &cri, 1); + if (error == 0) { + sc->sc_crypto = G_ELI_CRYPTO_HW; + wr->w_proc = NULL; + LIST_INSERT_HEAD(&sc->sc_workers, wr, w_next); + break; + } + } + error = crypto_newsession(&wr->w_sid, &cri, 0); + if (error != 0) { + free(wr, M_ELI); + if (req != NULL) { + gctl_error(req, "Cannot setup crypto session " + "for %s (error=%d).", bpp->name, error); + } else { + G_ELI_DEBUG(1, "Cannot setup crypto session " + "for %s (error=%d).", bpp->name, error); + } + goto failed; + } + + /* + * If we cannot get hardware acceleration, create dedicated + * thread(s) and do the crypto work in there. + */ + + error = kthread_create(g_eli_worker, wr, &wr->w_proc, 0, 0, + "g_eli[%u] %s", i, bpp->name); + if (error != 0) { + crypto_freesession(wr->w_sid); + free(wr, M_ELI); + if (req != NULL) { + gctl_error(req, "Cannot create kernel " + "thread for %s (error=%d).", + bpp->name, error); + } else { + G_ELI_DEBUG(1, "Cannot create kernel " + "thread for %s (error=%d).", + bpp->name, error); + } + goto failed; + } + LIST_INSERT_HEAD(&sc->sc_workers, wr, w_next); + } + + /* + * Create decrypted provider. + */ + pp = g_new_providerf(gp, "%s%s", bpp->name, G_ELI_SUFFIX); + pp->sectorsize = md->md_sectorsize; + pp->mediasize = bpp->mediasize; + if ((sc->sc_flags & G_ELI_FLAG_ONETIME) == 0) + pp->mediasize -= bpp->sectorsize; + pp->mediasize -= (pp->mediasize % pp->sectorsize); + g_error_provider(pp, 0); + + G_ELI_DEBUG(0, "Device %s created.", pp->name); + G_ELI_DEBUG(0, " Cipher: %s", g_eli_algo2str(sc->sc_algo)); + G_ELI_DEBUG(0, "Key length: %u", sc->sc_keylen); + G_ELI_DEBUG(0, " Crypto: %s", + sc->sc_crypto == G_ELI_CRYPTO_SW ? "software" : "hardware"); + return (gp); +failed: + if (sc->sc_crypto == G_ELI_CRYPTO_SW) { + mtx_lock(&sc->sc_queue_mtx); + sc->sc_flags |= G_ELI_FLAG_DESTROY; + wakeup(sc); + /* + * Wait for kernel threads self destruction. + */ + while (!LIST_EMPTY(&sc->sc_workers)) { + msleep(&sc->sc_workers, &sc->sc_queue_mtx, PRIBIO, + "geli:destroy", 0); + } + } else if (sc->sc_crypto == G_ELI_CRYPTO_HW) { + wr = LIST_FIRST(&sc->sc_workers); + LIST_REMOVE(wr, w_next); + crypto_freesession(wr->w_sid); + free(wr, M_ELI); + } + mtx_destroy(&sc->sc_queue_mtx); + if (cp->provider != NULL) { + if (cp->acr == 1) + g_access(cp, -1, -1, -1); + g_detach(cp); + } + g_destroy_consumer(cp); + if (pp != NULL) + g_destroy_provider(pp); + g_destroy_geom(gp); + bzero(sc, sizeof(*sc)); + free(sc, M_ELI); + return (NULL); +} + +int +g_eli_destroy(struct g_eli_softc *sc, boolean_t force) +{ + struct g_eli_worker *wr; + struct g_geom *gp; + struct g_provider *pp; + + g_topology_assert(); + + if (sc == NULL) + return (ENXIO); + + gp = sc->sc_geom; + pp = LIST_FIRST(&gp->provider); + if (pp != NULL && (pp->acr != 0 || pp->acw != 0 || pp->ace != 0)) { + if (force) { + G_ELI_DEBUG(1, "Device %s is still open, so it " + "can't be definitely removed.", pp->name); + } else { + G_ELI_DEBUG(1, + "Device %s is still open (r%dw%de%d).", pp->name, + pp->acr, pp->acw, pp->ace); + return (EBUSY); + } + } + + /* + * When we do cryptography in software, we have kernel thread hanging + * around, so we need to destroy it first. + */ + if (sc->sc_crypto == G_ELI_CRYPTO_SW) { + mtx_lock(&sc->sc_queue_mtx); + sc->sc_flags |= G_ELI_FLAG_DESTROY; + wakeup(sc); + while (!LIST_EMPTY(&sc->sc_workers)) { + msleep(&sc->sc_workers, &sc->sc_queue_mtx, PRIBIO, + "geli:destroy", 0); + } + } else /* if (sc->sc_crypto == G_ELI_CRYPTO_HW) */ { + wr = LIST_FIRST(&sc->sc_workers); + LIST_REMOVE(wr, w_next); + crypto_freesession(wr->w_sid); + free(wr, M_ELI); + } + mtx_destroy(&sc->sc_queue_mtx); + gp->softc = NULL; + bzero(sc, sizeof(*sc)); + free(sc, M_ELI); + + if (pp == NULL || (pp->acr == 0 && pp->acw == 0 && pp->ace == 0)) + G_ELI_DEBUG(0, "Device %s destroyed.", gp->name); + g_wither_geom_close(gp, ENXIO); + + return (0); +} + +static int +g_eli_destroy_geom(struct gctl_req *req __unused, + struct g_class *mp __unused, struct g_geom *gp) +{ + struct g_eli_softc *sc; + + sc = gp->softc; + return (g_eli_destroy(sc, 0)); +} + +/* + * Tasting is only made on boot. + * We detect providers which should be attached before root is mounted. + */ +static struct g_geom * +g_eli_taste(struct g_class *mp, struct g_provider *pp, int flags __unused) +{ + struct g_eli_metadata md; + struct g_geom *gp; + struct hmac_ctx ctx; + char passphrase[256]; + u_char key[G_ELI_USERKEYLEN], mkey[G_ELI_DATAIVKEYLEN]; + u_int nkey, i; + int error; + + g_trace(G_T_TOPOLOGY, "%s(%s, %s)", __func__, mp->name, pp->name); + g_topology_assert(); + + if (!g_eli_do_taste || g_eli_tries == 0) + return (NULL); + + G_ELI_DEBUG(3, "Tasting %s.", pp->name); + + error = g_eli_read_metadata(mp, pp, &md); + if (error != 0) + return (NULL); + gp = NULL; + + if (strcmp(md.md_magic, G_ELI_MAGIC) != 0) + return (NULL); + if (md.md_version > G_ELI_VERSION) { + printf("geom_eli.ko module is too old to handle %s.\n", + pp->name); + return (NULL); + } + if (md.md_provsize != pp->mediasize) + return (NULL); + /* Should we attach it on boot? */ + if ((md.md_flags & G_ELI_FLAG_BOOT) == 0) + return (NULL); + if (md.md_keys == 0x00) { + G_ELI_DEBUG(0, "No valid keys on %s.", pp->name); + return (NULL); + } + + /* + * Ask for the passphrase no more than g_eli_tries times. + */ + for (i = 0; i < g_eli_tries; i++) { + printf("Enter passphrase for %s: ", pp->name); + gets(passphrase, sizeof(passphrase), g_eli_visible_passphrase); + KASSERT(md.md_iterations >= 0, ("md_iterations = %d for %s", + (int)md.md_iterations, pp->name)); + /* + * Prepare Derived-Key from the user passphrase. + */ + g_eli_crypto_hmac_init(&ctx, NULL, 0); + if (md.md_iterations == 0) { + g_eli_crypto_hmac_update(&ctx, md.md_salt, + sizeof(md.md_salt)); + g_eli_crypto_hmac_update(&ctx, passphrase, + strlen(passphrase)); + } else { + u_char dkey[G_ELI_USERKEYLEN]; + + pkcs5v2_genkey(dkey, sizeof(dkey), md.md_salt, + sizeof(md.md_salt), passphrase, md.md_iterations); + g_eli_crypto_hmac_update(&ctx, dkey, sizeof(dkey)); + bzero(dkey, sizeof(dkey)); + } + g_eli_crypto_hmac_final(&ctx, key, 0); + + /* + * Decrypt Master-Key. + */ + error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); + bzero(key, sizeof(key)); + if (error == -1) { + if (i == g_eli_tries - 1) { + i++; + break; + } + G_ELI_DEBUG(0, "Wrong key for %s. Tries left: %u.", + pp->name, g_eli_tries - i - 1); + /* Try again. */ + continue; + } else if (error > 0) { + G_ELI_DEBUG(0, "Cannot decrypt Master Key for %s (error=%d).", + pp->name, error); + return (NULL); + } + G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name); + break; + } + if (i == g_eli_tries) { + G_ELI_DEBUG(0, "Wrong key for %s. No tries left.", pp->name); + return (NULL); + } + + /* + * We have correct key, let's attach provider. + */ + gp = g_eli_create(NULL, mp, pp, &md, mkey, nkey); + bzero(mkey, sizeof(mkey)); + bzero(&md, sizeof(md)); + if (gp == NULL) { + G_ELI_DEBUG(0, "Cannot create device %s%s.", pp->name, + G_ELI_SUFFIX); + return (NULL); + } + return (gp); +} + +static void +g_eli_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, + struct g_consumer *cp, struct g_provider *pp) +{ + struct g_eli_softc *sc; + + g_topology_assert(); + sc = gp->softc; + if (sc == NULL) + return; + if (pp != NULL || cp != NULL) + return; /* Nothing here. */ + sbuf_printf(sb, "%s", indent); + switch (sc->sc_crypto) { + case G_ELI_CRYPTO_HW: + sbuf_printf(sb, "hardware"); + break; + case G_ELI_CRYPTO_SW: + sbuf_printf(sb, "software"); + break; + default: + sbuf_printf(sb, "UNKNOWN"); + break; + } + sbuf_printf(sb, "\n"); +} + +static void +g_eli_on_boot_start(void *dummy __unused) +{ + + /* This prevents from tasting when module is loaded after boot. */ + if (cold) { + G_ELI_DEBUG(1, "Start tasting."); + g_eli_do_taste = 1; + } else { + G_ELI_DEBUG(1, "Tasting not started."); + } +} +SYSINIT(geli_boot_start, SI_SUB_TUNABLES, SI_ORDER_ANY, g_eli_on_boot_start, NULL) + +static void +g_eli_on_boot_end(void *dummy __unused) +{ + + if (g_eli_do_taste) { + G_ELI_DEBUG(1, "Tasting no more."); + g_eli_do_taste = 0; + } +} +SYSINIT(geli_boot_end, SI_SUB_RUN_SCHEDULER, SI_ORDER_ANY, g_eli_on_boot_end, NULL) + +DECLARE_GEOM_CLASS(g_eli_class, g_eli); +MODULE_DEPEND(geom_eli, crypto, 1, 1, 1); +MODULE_DEPEND(geom_eli, cryptodev, 1, 1, 1); diff --git a/sys/geom/eli/g_eli.h b/sys/geom/eli/g_eli.h new file mode 100644 index 0000000..62bee40 --- /dev/null +++ b/sys/geom/eli/g_eli.h @@ -0,0 +1,366 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _G_ELI_H_ +#define _G_ELI_H_ + +#include +#include +#include +#include +#include +#ifdef _KERNEL +#include +#include +#include +#else +#include +#include +#endif +#ifndef _OpenSSL_ +#include +#endif + +#define G_ELI_CLASS_NAME "ELI" +#define G_ELI_MAGIC "GEOM::ELI" +#define G_ELI_SUFFIX ".eli" + +/* + * Version history: + * 0 - Initial version number. + */ +#define G_ELI_VERSION 0 + +/* Use random, onetime keys. */ +#define G_ELI_FLAG_ONETIME 0x00000001 +/* Ask for the passphrase from the kernel, before mounting root. */ +#define G_ELI_FLAG_BOOT 0x00000002 +/* Detach on last close, if we were open for writing. */ +#define G_ELI_FLAG_WO_DETACH 0x00000004 +/* Detach on last close. */ +#define G_ELI_FLAG_RW_DETACH 0x00000008 +/* Provider was open for writing. */ +#define G_ELI_FLAG_WOPEN 0x00010000 +/* Destroy device. */ +#define G_ELI_FLAG_DESTROY 0x00020000 + +#define SHA512_MDLEN 64 + +#define G_ELI_MAXMKEYS 2 +#define G_ELI_MAXKEYLEN 64 +#define G_ELI_USERKEYLEN G_ELI_MAXKEYLEN +#define G_ELI_DATAKEYLEN G_ELI_MAXKEYLEN +#define G_ELI_IVKEYLEN G_ELI_MAXKEYLEN +#define G_ELI_SALTLEN 64 +#define G_ELI_DATAIVKEYLEN (G_ELI_DATAKEYLEN + G_ELI_IVKEYLEN) +/* Data-Key, IV-Key, HMAC_SHA512(Derived-Key, Data-Key+IV-Key) */ +#define G_ELI_MKEYLEN (G_ELI_DATAIVKEYLEN + SHA512_MDLEN) + +#ifdef _KERNEL +extern u_int g_eli_debug; +extern u_int g_eli_overwrites; + +#define G_ELI_CRYPTO_HW 1 +#define G_ELI_CRYPTO_SW 2 + +#define G_ELI_DEBUG(lvl, ...) do { \ + if (g_eli_debug >= (lvl)) { \ + printf("GEOM_ELI"); \ + if (g_eli_debug > 0) \ + printf("[%u]", lvl); \ + printf(": "); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } \ +} while (0) +#define G_ELI_LOGREQ(lvl, bp, ...) do { \ + if (g_eli_debug >= (lvl)) { \ + printf("GEOM_ELI"); \ + if (g_eli_debug > 0) \ + printf("[%u]", lvl); \ + printf(": "); \ + printf(__VA_ARGS__); \ + printf(" "); \ + g_print_bio(bp); \ + printf("\n"); \ + } \ +} while (0) + +struct g_eli_worker { + struct g_eli_softc *w_softc; + struct proc *w_proc; + u_int w_number; + uint64_t w_sid; + LIST_ENTRY(g_eli_worker) w_next; +}; + +struct g_eli_softc { + struct g_geom *sc_geom; + u_int sc_crypto; + uint8_t sc_datakey[G_ELI_DATAKEYLEN]; + uint8_t sc_ivkey[G_ELI_IVKEYLEN]; + SHA256_CTX sc_ivctx; + u_int sc_algo; + u_int sc_keylen; + int sc_nkey; + uint32_t sc_flags; + + /* Only for software cryptography. */ + struct bio_queue_head sc_queue; + struct mtx sc_queue_mtx; + LIST_HEAD(, g_eli_worker) sc_workers; +}; +#define sc_name sc_geom->name +#endif /* _KERNEL */ + +struct g_eli_metadata { + char md_magic[16]; /* Magic value. */ + uint32_t md_version; /* Version number. */ + uint32_t md_flags; /* Additional flags. */ + uint16_t md_algo; /* Encryption algorithm. */ + uint16_t md_keylen; /* Key length. */ + uint64_t md_provsize; /* Provider's size. */ + uint32_t md_sectorsize; /* Sector size. */ + uint8_t md_keys; /* Available keys. */ + int32_t md_iterations; /* Number of iterations for PKCS#5v2 */ + uint8_t md_salt[G_ELI_SALTLEN]; /* Salt. */ + /* Encrypted master key (IV-key, Data-key, HMAC). */ + uint8_t md_mkeys[G_ELI_MAXMKEYS * G_ELI_MKEYLEN]; + u_char md_hash[16]; /* MD5 hash. */ +}; +#ifndef _OpenSSL_ +static __inline void +eli_metadata_encode(struct g_eli_metadata *md, u_char *data) +{ + MD5_CTX ctx; + u_char *p; + + p = data; + bcopy(md->md_magic, p, sizeof(md->md_magic)); p += sizeof(md->md_magic); + le32enc(p, md->md_version); p += sizeof(md->md_version); + le32enc(p, md->md_flags); p += sizeof(md->md_flags); + le16enc(p, md->md_algo); p += sizeof(md->md_algo); + le16enc(p, md->md_keylen); p += sizeof(md->md_keylen); + le64enc(p, md->md_provsize); p += sizeof(md->md_provsize); + le32enc(p, md->md_sectorsize); p += sizeof(md->md_sectorsize); + *p = md->md_keys; p += sizeof(md->md_keys); + le32enc(p, md->md_iterations); p += sizeof(md->md_iterations); + bcopy(md->md_salt, p, sizeof(md->md_salt)); p += sizeof(md->md_salt); + bcopy(md->md_mkeys, p, sizeof(md->md_mkeys)); p += sizeof(md->md_mkeys); + MD5Init(&ctx); + MD5Update(&ctx, data, p - data); + MD5Final(md->md_hash, &ctx); + bcopy(md->md_hash, p, sizeof(md->md_hash)); +} +static __inline int +eli_metadata_decode_v0(const u_char *data, struct g_eli_metadata *md) +{ + MD5_CTX ctx; + const u_char *p; + + p = data + sizeof(md->md_magic) + sizeof(md->md_version); + md->md_flags = le32dec(p); p += sizeof(md->md_flags); + md->md_algo = le16dec(p); p += sizeof(md->md_algo); + md->md_keylen = le16dec(p); p += sizeof(md->md_keylen); + md->md_provsize = le64dec(p); p += sizeof(md->md_provsize); + md->md_sectorsize = le32dec(p); p += sizeof(md->md_sectorsize); + md->md_keys = *p; p += sizeof(md->md_keys); + md->md_iterations = le32dec(p); p += sizeof(md->md_iterations); + bcopy(p, md->md_salt, sizeof(md->md_salt)); p += sizeof(md->md_salt); + bcopy(p, md->md_mkeys, sizeof(md->md_mkeys)); p += sizeof(md->md_mkeys); + MD5Init(&ctx); + MD5Update(&ctx, data, p - data); + MD5Final(md->md_hash, &ctx); + if (bcmp(md->md_hash, p, 16) != 0) + return (EINVAL); + return (0); +} +static __inline int +eli_metadata_decode(const u_char *data, struct g_eli_metadata *md) +{ + int error; + + bcopy(data, md->md_magic, sizeof(md->md_magic)); + md->md_version = le32dec(data + sizeof(md->md_magic)); + switch (md->md_version) { + case 0: + error = eli_metadata_decode_v0(data, md); + break; + default: + error = EINVAL; + break; + } + return (error); +} +#endif /* !_OpenSSL */ + +static __inline u_int +g_eli_str2algo(const char *name) +{ + + if (strcmp("null", name) == 0) + return (CRYPTO_NULL_CBC); + if (strcmp("aes", name) == 0) + return (CRYPTO_AES_CBC); + else if (strcmp("blowfish", name) == 0) + return (CRYPTO_BLF_CBC); + else if (strcmp("3des", name) == 0) + return (CRYPTO_3DES_CBC); + return (CRYPTO_ALGORITHM_MIN - 1); +} + +static __inline const char * +g_eli_algo2str(u_int algo) +{ + + switch (algo) { + case CRYPTO_NULL_CBC: + return ("NULL"); + case CRYPTO_AES_CBC: + return ("AES"); + case CRYPTO_BLF_CBC: + return ("Blowfish"); + case CRYPTO_3DES_CBC: + return ("3DES"); + } + return ("unknown"); +} + +static __inline void +eli_metadata_dump(const struct g_eli_metadata *md) +{ + static const char hex[] = "0123456789abcdef"; + char str[sizeof(md->md_mkeys) * 2 + 1]; + u_int i; + + printf(" magic: %s\n", md->md_magic); + printf(" version: %u\n", (u_int)md->md_version); + printf(" flags: 0x%x\n", (u_int)md->md_flags); + printf(" algo: %s\n", g_eli_algo2str(md->md_algo)); + printf(" keylen: %u\n", (u_int)md->md_keylen); + printf(" provsize: %ju\n", (uintmax_t)md->md_provsize); + printf("sectorsize: %u\n", (u_int)md->md_sectorsize); + printf(" keys: 0x%02x\n", (u_int)md->md_keys); + printf("iterations: %u\n", (u_int)md->md_iterations); + bzero(str, sizeof(str)); + for (i = 0; i < sizeof(md->md_salt); i++) { + str[i * 2] = hex[md->md_salt[i] >> 4]; + str[i * 2 + 1] = hex[md->md_salt[i] & 0x0f]; + } + printf(" Salt: %s\n", str); + bzero(str, sizeof(str)); + for (i = 0; i < sizeof(md->md_mkeys); i++) { + str[i * 2] = hex[md->md_mkeys[i] >> 4]; + str[i * 2 + 1] = hex[md->md_mkeys[i] & 0x0f]; + } + printf("Master Key: %s\n", str); + bzero(str, sizeof(str)); + for (i = 0; i < 16; i++) { + str[i * 2] = hex[md->md_hash[i] >> 4]; + str[i * 2 + 1] = hex[md->md_hash[i] & 0x0f]; + } + printf(" MD5 hash: %s\n", str); +} + +static __inline u_int +g_eli_keylen(u_int algo, u_int keylen) +{ + + switch (algo) { + case CRYPTO_NULL_CBC: + if (keylen == 0) + keylen = 64 * 8; + else { + if (keylen > 64 * 8) + keylen = 0; + } + return (keylen); + case CRYPTO_AES_CBC: + switch (keylen) { + case 0: + return (128); + case 128: + case 192: + case 256: + return (keylen); + default: + return (0); + } + case CRYPTO_BLF_CBC: + if (keylen == 0) + return (128); + if (keylen < 128 || keylen > 448) + return (0); + if ((keylen % 32) != 0) + return (0); + return (keylen); + case CRYPTO_3DES_CBC: + if (keylen == 0 || keylen == 192) + return (192); + return (0); + default: + return (0); + } +} + +#ifdef _KERNEL +int g_eli_read_metadata(struct g_class *mp, struct g_provider *pp, + struct g_eli_metadata *md); +struct g_geom *g_eli_create(struct gctl_req *req, struct g_class *mp, + struct g_provider *bpp, const struct g_eli_metadata *md, + const u_char *mkey, int nkey); +int g_eli_destroy(struct g_eli_softc *sc, boolean_t force); + +int g_eli_access(struct g_provider *pp, int dr, int dw, int de); +void g_eli_config(struct gctl_req *req, struct g_class *mp, const char *verb); +#endif + +void g_eli_mkey_hmac(unsigned char *mkey, const unsigned char *key); +int g_eli_mkey_decrypt(const struct g_eli_metadata *md, + const unsigned char *key, unsigned char *mkey, unsigned *nkeyp); +int g_eli_mkey_encrypt(unsigned algo, const unsigned char *key, unsigned keylen, + unsigned char *mkey); + +int g_eli_crypto_encrypt(u_int algo, u_char *data, size_t datasize, + const u_char *key, size_t keysize); +int g_eli_crypto_decrypt(u_int algo, u_char *data, size_t datasize, + const u_char *key, size_t keysize); + +struct hmac_ctx { + SHA512_CTX shactx; + u_char k_opad[128]; +}; + +void g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey, + size_t hkeylen); +void g_eli_crypto_hmac_update(struct hmac_ctx *ctx, const uint8_t *data, + size_t datasize); +void g_eli_crypto_hmac_final(struct hmac_ctx *ctx, uint8_t *md, size_t mdsize); +void g_eli_crypto_hmac(const uint8_t *hkey, size_t hkeysize, + const uint8_t *data, size_t datasize, uint8_t *md, size_t mdsize); +#endif /* !_G_ELI_H_ */ diff --git a/sys/geom/eli/g_eli_crypto.c b/sys/geom/eli/g_eli_crypto.c new file mode 100644 index 0000000..d4ec889 --- /dev/null +++ b/sys/geom/eli/g_eli_crypto.c @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#ifdef _KERNEL +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#define _OpenSSL_ +#endif +#include + +#ifdef _KERNEL +MALLOC_DECLARE(M_ELI); + +static int +g_eli_crypto_done(struct cryptop *crp) +{ + + crp->crp_opaque = (void *)crp; + wakeup(crp); + return (0); +} + +static int +g_eli_crypto_cipher(u_int algo, int enc, u_char *data, size_t datasize, + const u_char *key, size_t keysize) +{ + struct cryptoini cri; + struct cryptop *crp; + struct cryptodesc *crd; + struct uio *uio; + struct iovec *iov; + uint64_t sid; + u_char *p; + int error; + + bzero(&cri, sizeof(cri)); + cri.cri_alg = algo; + cri.cri_key = __DECONST(void *, key); + cri.cri_klen = keysize; + error = crypto_newsession(&sid, &cri, 0); + if (error != 0) + return (error); + p = malloc(sizeof(*crp) + sizeof(*crd) + sizeof(*uio) + sizeof(*iov), + M_ELI, M_NOWAIT | M_ZERO); + if (p == NULL) { + crypto_freesession(sid); + return (ENOMEM); + } + crp = (struct cryptop *)p; p += sizeof(*crp); + crd = (struct cryptodesc *)p; p += sizeof(*crd); + uio = (struct uio *)p; p += sizeof(*uio); + iov = (struct iovec *)p; p += sizeof(*iov); + + iov->iov_len = datasize; + iov->iov_base = data; + + uio->uio_iov = iov; + uio->uio_iovcnt = 1; + uio->uio_segflg = UIO_SYSSPACE; + uio->uio_resid = datasize; + + crd->crd_skip = 0; + crd->crd_len = datasize; + crd->crd_flags = CRD_F_IV_EXPLICIT | CRD_F_IV_PRESENT | CRD_F_KEY_EXPLICIT; + if (enc) + crd->crd_flags |= CRD_F_ENCRYPT; + crd->crd_alg = algo; + crd->crd_key = __DECONST(void *, key); + crd->crd_klen = keysize; + bzero(crd->crd_iv, sizeof(crd->crd_iv)); + crd->crd_next = NULL; + + crp->crp_sid = sid; + crp->crp_ilen = datasize; + crp->crp_olen = datasize; + crp->crp_opaque = NULL; + crp->crp_callback = g_eli_crypto_done; + crp->crp_buf = (void *)uio; + crp->crp_flags = CRYPTO_F_IOV | CRYPTO_F_CBIFSYNC | CRYPTO_F_REL; + crp->crp_desc = crd; + + error = crypto_dispatch(crp); + if (error == 0) { + while (crp->crp_opaque == NULL) + tsleep(crp, PRIBIO, "geli", hz / 5); + error = crp->crp_etype; + } + + free(crp, M_ELI); + crypto_freesession(sid); + return (error); +} +#else /* !_KERNEL */ +static int +g_eli_crypto_cipher(u_int algo, int enc, u_char *data, size_t datasize, + const u_char *key, size_t keysize) +{ + EVP_CIPHER_CTX ctx; + const EVP_CIPHER *type; + u_char iv[keysize]; + int outsize; + + switch (algo) { + case CRYPTO_NULL_CBC: + type = EVP_enc_null(); + break; + case CRYPTO_AES_CBC: + switch (keysize) { + case 128: + type = EVP_aes_128_cbc(); + break; + case 192: + type = EVP_aes_192_cbc(); + break; + case 256: + type = EVP_aes_256_cbc(); + break; + default: + return (EINVAL); + } + break; + case CRYPTO_BLF_CBC: + type = EVP_bf_cbc(); + break; + case CRYPTO_3DES_CBC: + type = EVP_des_ede3_cbc(); + break; + default: + return (EINVAL); + } + + EVP_CIPHER_CTX_init(&ctx); + + EVP_CipherInit_ex(&ctx, type, NULL, NULL, NULL, enc); + EVP_CIPHER_CTX_set_key_length(&ctx, keysize / 8); + EVP_CIPHER_CTX_set_padding(&ctx, 0); + bzero(iv, sizeof(iv)); + EVP_CipherInit_ex(&ctx, NULL, NULL, key, iv, enc); + + if (EVP_CipherUpdate(&ctx, data, &outsize, data, datasize) == 0) { + EVP_CIPHER_CTX_cleanup(&ctx); + return (EINVAL); + } + assert(outsize == (int)datasize); + + if (EVP_CipherFinal_ex(&ctx, data + outsize, &outsize) == 0) { + EVP_CIPHER_CTX_cleanup(&ctx); + return (EINVAL); + } + assert(outsize == 0); + + EVP_CIPHER_CTX_cleanup(&ctx); + return (0); +} +#endif /* !_KERNEL */ + +int +g_eli_crypto_encrypt(u_int algo, u_char *data, size_t datasize, + const u_char *key, size_t keysize) +{ + + return (g_eli_crypto_cipher(algo, 1, data, datasize, key, keysize)); +} + +int +g_eli_crypto_decrypt(u_int algo, u_char *data, size_t datasize, + const u_char *key, size_t keysize) +{ + + return (g_eli_crypto_cipher(algo, 0, data, datasize, key, keysize)); +} + +void +g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey, + size_t hkeylen) +{ + u_char k_ipad[128], key[128]; + SHA512_CTX lctx; + u_int i; + + bzero(key, sizeof(key)); + if (hkeylen == 0) + ; /* do nothing */ + else if (hkeylen <= 128) + bcopy(hkey, key, hkeylen); + else { + /* If key is longer than 128 bytes reset it to key = SHA512(key). */ + SHA512_Init(&lctx); + SHA512_Update(&lctx, hkey, hkeylen); + SHA512_Final(key, &lctx); + } + + /* XOR key with ipad and opad values. */ + for (i = 0; i < sizeof(key); i++) { + k_ipad[i] = key[i] ^ 0x36; + ctx->k_opad[i] = key[i] ^ 0x5c; + } + bzero(key, sizeof(key)); + /* Perform inner SHA512. */ + SHA512_Init(&ctx->shactx); + SHA512_Update(&ctx->shactx, k_ipad, sizeof(k_ipad)); +} + +void +g_eli_crypto_hmac_update(struct hmac_ctx *ctx, const uint8_t *data, + size_t datasize) +{ + + SHA512_Update(&ctx->shactx, data, datasize); +} + +void +g_eli_crypto_hmac_final(struct hmac_ctx *ctx, uint8_t *md, size_t mdsize) +{ + u_char digest[SHA512_MDLEN]; + SHA512_CTX lctx; + + SHA512_Final(digest, &ctx->shactx); + /* Perform outer SHA512. */ + SHA512_Init(&lctx); + SHA512_Update(&lctx, ctx->k_opad, sizeof(ctx->k_opad)); + bzero(ctx, sizeof(*ctx)); + SHA512_Update(&lctx, digest, sizeof(digest)); + SHA512_Final(digest, &lctx); + /* mdsize == 0 means "Give me the whole hash!" */ + if (mdsize == 0) + mdsize = SHA512_MDLEN; + bcopy(digest, md, mdsize); +} + +void +g_eli_crypto_hmac(const uint8_t *hkey, size_t hkeysize, const uint8_t *data, + size_t datasize, uint8_t *md, size_t mdsize) +{ + struct hmac_ctx ctx; + + g_eli_crypto_hmac_init(&ctx, hkey, hkeysize); + g_eli_crypto_hmac_update(&ctx, data, datasize); + g_eli_crypto_hmac_final(&ctx, md, mdsize); +} diff --git a/sys/geom/eli/g_eli_ctl.c b/sys/geom/eli/g_eli_ctl.c new file mode 100644 index 0000000..d2ffdd9 --- /dev/null +++ b/sys/geom/eli/g_eli_ctl.c @@ -0,0 +1,639 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + + +MALLOC_DECLARE(M_ELI); + + +static void +g_eli_ctl_attach(struct gctl_req *req, struct g_class *mp) +{ + struct g_eli_metadata md; + struct g_provider *pp; + const char *name; + u_char *key, mkey[G_ELI_DATAIVKEYLEN]; + int *nargs, *detach; + int keysize, error; + u_int nkey; + + g_topology_assert(); + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 1) { + gctl_error(req, "Invalid number of arguments."); + return; + } + + detach = gctl_get_paraml(req, "detach", sizeof(*detach)); + if (detach == NULL) { + gctl_error(req, "No '%s' argument.", "detach"); + return; + } + + name = gctl_get_asciiparam(req, "arg0"); + if (name == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + if (strncmp(name, "/dev/", strlen("/dev/")) == 0) + name += strlen("/dev/"); + pp = g_provider_by_name(name); + if (pp == NULL) { + gctl_error(req, "Provider %s is invalid.", name); + return; + } + error = g_eli_read_metadata(mp, pp, &md); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s (error=%d).", + name, error); + return; + } + if (md.md_keys == 0x00) { + bzero(&md, sizeof(md)); + gctl_error(req, "No valid keys on %s.", pp->name); + return; + } + + key = gctl_get_param(req, "key", &keysize); + if (key == NULL || keysize != G_ELI_USERKEYLEN) { + bzero(&md, sizeof(md)); + gctl_error(req, "No '%s' argument.", "key"); + return; + } + + error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); + bzero(key, keysize); + if (error == -1) { + bzero(&md, sizeof(md)); + gctl_error(req, "Wrong key for %s.", pp->name); + return; + } else if (error > 0) { + bzero(&md, sizeof(md)); + gctl_error(req, "Cannot decrypt Master Key for %s (error=%d).", + pp->name, error); + return; + } + G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name); + + if (*detach) + md.md_flags |= G_ELI_FLAG_WO_DETACH; + g_eli_create(req, mp, pp, &md, mkey, nkey); + bzero(mkey, sizeof(mkey)); + bzero(&md, sizeof(md)); +} + +static struct g_eli_softc * +g_eli_find_device(struct g_class *mp, const char *prov) +{ + struct g_eli_softc *sc; + struct g_geom *gp; + struct g_provider *pp; + struct g_consumer *cp; + + if (strncmp(prov, "/dev/", strlen("/dev/")) == 0) + prov += strlen("/dev/"); + LIST_FOREACH(gp, &mp->geom, geom) { + sc = gp->softc; + if (sc == NULL) + continue; + pp = LIST_FIRST(&gp->provider); + if (pp != NULL && strcmp(pp->name, prov) == 0) + return (sc); + cp = LIST_FIRST(&gp->consumer); + if (cp != NULL && cp->provider != NULL && + strcmp(cp->provider->name, prov) == 0) { + return (sc); + } + } + return (NULL); +} + +static void +g_eli_ctl_detach(struct gctl_req *req, struct g_class *mp) +{ + struct g_eli_softc *sc; + int *force, *last, *nargs, error; + const char *prov; + char param[16]; + u_int i; + + g_topology_assert(); + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs <= 0) { + gctl_error(req, "Missing device(s)."); + return; + } + force = gctl_get_paraml(req, "force", sizeof(*force)); + if (force == NULL) { + gctl_error(req, "No '%s' argument.", "force"); + return; + } + last = gctl_get_paraml(req, "last", sizeof(*last)); + if (last == NULL) { + gctl_error(req, "No '%s' argument.", "last"); + return; + } + + for (i = 0; i < (u_int)*nargs; i++) { + snprintf(param, sizeof(param), "arg%u", i); + prov = gctl_get_asciiparam(req, param); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", i); + return; + } + sc = g_eli_find_device(mp, prov); + if (sc == NULL) { + gctl_error(req, "No such device: %s.", prov); + return; + } + if (*last) { + sc->sc_flags |= G_ELI_FLAG_RW_DETACH; + sc->sc_geom->access = g_eli_access; + } else { + error = g_eli_destroy(sc, *force); + if (error != 0) { + gctl_error(req, + "Cannot destroy device %s (error=%d).", + sc->sc_name, error); + return; + } + } + } +} + +static void +g_eli_ctl_onetime(struct gctl_req *req, struct g_class *mp) +{ + struct g_eli_metadata md; + struct g_provider *pp; + const char *name; + intmax_t *keylen, *sectorsize; + u_char mkey[G_ELI_DATAIVKEYLEN]; + int *nargs, *detach; + + g_topology_assert(); + bzero(&md, sizeof(md)); + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 1) { + gctl_error(req, "Invalid number of arguments."); + return; + } + + detach = gctl_get_paraml(req, "detach", sizeof(*detach)); + if (detach == NULL) { + gctl_error(req, "No '%s' argument.", "detach"); + return; + } + + strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); + md.md_version = G_ELI_VERSION; + md.md_flags |= G_ELI_FLAG_ONETIME; + if (*detach) + md.md_flags |= G_ELI_FLAG_WO_DETACH; + + name = gctl_get_asciiparam(req, "algo"); + if (name == NULL) { + gctl_error(req, "No '%s' argument.", "algo"); + return; + } + md.md_algo = g_eli_str2algo(name); + if (md.md_algo < CRYPTO_ALGORITHM_MIN || + md.md_algo > CRYPTO_ALGORITHM_MAX) { + gctl_error(req, "Invalid '%s' argument.", "algo"); + return; + } + + keylen = gctl_get_paraml(req, "keylen", sizeof(*keylen)); + if (keylen == NULL) { + gctl_error(req, "No '%s' argument.", "keylen"); + return; + } + md.md_keylen = g_eli_keylen(md.md_algo, *keylen); + if (md.md_keylen == 0) { + gctl_error(req, "Invalid '%s' argument.", "keylen"); + return; + } + + /* Not important here. */ + md.md_provsize = 0; + /* Not important here. */ + bzero(md.md_salt, sizeof(md.md_salt)); + + md.md_keys = 0x01; + arc4rand(mkey, sizeof(mkey), 0); + + /* Not important here. */ + bzero(md.md_hash, sizeof(md.md_hash)); + + name = gctl_get_asciiparam(req, "arg0"); + if (name == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + if (strncmp(name, "/dev/", strlen("/dev/")) == 0) + name += strlen("/dev/"); + pp = g_provider_by_name(name); + if (pp == NULL) { + gctl_error(req, "Provider %s is invalid.", name); + return; + } + + sectorsize = gctl_get_paraml(req, "sectorsize", sizeof(*sectorsize)); + if (sectorsize == NULL) { + gctl_error(req, "No '%s' argument.", "sectorsize"); + return; + } + if (*sectorsize == 0) + md.md_sectorsize = pp->sectorsize; + else { + if (*sectorsize < 0 || (*sectorsize % pp->sectorsize) != 0) { + gctl_error(req, "Invalid sector size."); + return; + } + md.md_sectorsize = *sectorsize; + } + + g_eli_create(req, mp, pp, &md, mkey, -1); + bzero(mkey, sizeof(mkey)); + bzero(&md, sizeof(md)); +} + +static void +g_eli_ctl_setkey(struct gctl_req *req, struct g_class *mp) +{ + struct g_eli_softc *sc; + struct g_eli_metadata md; + struct g_provider *pp; + struct g_consumer *cp; + const char *name; + u_char *key, *mkeydst, *sector; + intmax_t *valp; + int nkey; + int keysize, error; + + g_topology_assert(); + + name = gctl_get_asciiparam(req, "arg0"); + if (name == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + sc = g_eli_find_device(mp, name); + if (sc == NULL) { + gctl_error(req, "Provider %s is invalid.", name); + return; + } + cp = LIST_FIRST(&sc->sc_geom->consumer); + pp = cp->provider; + + error = g_eli_read_metadata(mp, pp, &md); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s (error=%d).", + name, error); + return; + } + + valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "keyno"); + return; + } + if (*valp != -1) + nkey = *valp; + else + nkey = sc->sc_nkey; + if (nkey < 0 || nkey >= G_ELI_MAXMKEYS) { + gctl_error(req, "Invalid '%s' argument.", "keyno"); + return; + } + + key = gctl_get_param(req, "key", &keysize); + if (key == NULL || keysize != G_ELI_USERKEYLEN) { + bzero(&md, sizeof(md)); + gctl_error(req, "No '%s' argument.", "key"); + return; + } + + mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; + md.md_keys |= (1 << nkey); + + bcopy(sc->sc_ivkey, mkeydst, sizeof(sc->sc_ivkey)); + bcopy(sc->sc_datakey, mkeydst + sizeof(sc->sc_ivkey), + sizeof(sc->sc_datakey)); + + /* Encrypt Master Key with the new key. */ + error = g_eli_mkey_encrypt(md.md_algo, key, md.md_keylen, mkeydst); + bzero(key, sizeof(key)); + if (error != 0) { + bzero(&md, sizeof(md)); + gctl_error(req, "Cannot encrypt Master Key (error=%d).", error); + return; + } + + sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); + /* Store metadata with fresh key. */ + eli_metadata_encode(&md, sector); + bzero(&md, sizeof(md)); + error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, + pp->sectorsize); + bzero(sector, sizeof(sector)); + free(sector, M_ELI); + if (error != 0) { + gctl_error(req, "Cannot store metadata on %s (error=%d).", + pp->name, error); + return; + } + G_ELI_DEBUG(1, "Key %u changed on %s.", nkey, pp->name); +} + +static void +g_eli_ctl_delkey(struct gctl_req *req, struct g_class *mp) +{ + struct g_eli_softc *sc; + struct g_eli_metadata md; + struct g_provider *pp; + struct g_consumer *cp; + const char *name; + u_char *mkeydst, *sector; + intmax_t *valp; + size_t keysize; + int error, nkey, *all, *force; + u_int i; + + g_topology_assert(); + + nkey = 0; /* fixes causeless gcc warning */ + + name = gctl_get_asciiparam(req, "arg0"); + if (name == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + sc = g_eli_find_device(mp, name); + if (sc == NULL) { + gctl_error(req, "Provider %s is invalid.", name); + return; + } + cp = LIST_FIRST(&sc->sc_geom->consumer); + pp = cp->provider; + + error = g_eli_read_metadata(mp, pp, &md); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s (error=%d).", + name, error); + return; + } + + all = gctl_get_paraml(req, "all", sizeof(*all)); + if (all == NULL) { + gctl_error(req, "No '%s' argument.", "all"); + return; + } + + if (*all) { + mkeydst = md.md_mkeys; + keysize = sizeof(md.md_mkeys); + } else { + force = gctl_get_paraml(req, "force", sizeof(*force)); + if (force == NULL) { + gctl_error(req, "No '%s' argument.", "force"); + return; + } + + valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "keyno"); + return; + } + if (*valp != -1) + nkey = *valp; + else + nkey = sc->sc_nkey; + if (nkey < 0 || nkey >= G_ELI_MAXMKEYS) { + gctl_error(req, "Invalid '%s' argument.", "keyno"); + return; + } + if (!(md.md_keys & (1 << nkey)) && !*force) { + gctl_error(req, "Master Key %u is not set.", nkey); + return; + } + md.md_keys &= ~(1 << nkey); + if (md.md_keys == 0 && !*force) { + gctl_error(req, "This is the last Master Key. Use '-f' " + "flag if you really want to remove it."); + return; + } + mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; + keysize = G_ELI_MKEYLEN; + } + + sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); + for (i = 0; i <= g_eli_overwrites; i++) { + if (i == g_eli_overwrites) + bzero(mkeydst, keysize); + else + arc4rand(mkeydst, keysize, 0); + /* Store metadata with destroyed key. */ + eli_metadata_encode(&md, sector); + error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, + pp->sectorsize); + if (error != 0) { + G_ELI_DEBUG(0, "Cannot store metadata on %s " + "(error=%d).", pp->name, error); + } + } + bzero(&md, sizeof(md)); + bzero(sector, sizeof(sector)); + free(sector, M_ELI); + if (*all) + G_ELI_DEBUG(1, "All keys removed from %s.", pp->name); + else + G_ELI_DEBUG(1, "Key %d removed from %s.", nkey, pp->name); +} + +static int +g_eli_kill_one(struct g_eli_softc *sc) +{ + struct g_provider *pp; + struct g_consumer *cp; + u_char *sector; + int err, error = 0; + u_int i; + + g_topology_assert(); + + if (sc == NULL) + return (ENOENT); + + pp = LIST_FIRST(&sc->sc_geom->provider); + g_error_provider(pp, ENXIO); + + cp = LIST_FIRST(&sc->sc_geom->consumer); + pp = cp->provider; + + sector = malloc(pp->sectorsize, M_ELI, M_WAITOK); + for (i = 0; i <= g_eli_overwrites; i++) { + if (i == g_eli_overwrites) + bzero(sector, pp->sectorsize); + else + arc4rand(sector, pp->sectorsize, 0); + err = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, + pp->sectorsize); + if (err != 0) { + G_ELI_DEBUG(0, "Cannot erase metadata on %s " + "(error=%d).", pp->name, err); + if (error == 0) + error = err; + } + } + free(sector, M_ELI); + if (error == 0) + G_ELI_DEBUG(0, "%s has been killed.", pp->name); + g_eli_destroy(sc, 1); + return (error); +} + +static void +g_eli_ctl_kill(struct gctl_req *req, struct g_class *mp) +{ + int *all, *nargs; + int error; + + g_topology_assert(); + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + all = gctl_get_paraml(req, "all", sizeof(*all)); + if (all == NULL) { + gctl_error(req, "No '%s' argument.", "all"); + return; + } + if (!*all && *nargs == 0) { + gctl_error(req, "Too few arguments."); + return; + } + + if (*all) { + struct g_geom *gp, *gp2; + + LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2) { + error = g_eli_kill_one(gp->softc); + if (error != 0) + gctl_error(req, "Not fully done."); + } + } else { + struct g_eli_softc *sc; + const char *prov; + char param[16]; + int i; + + for (i = 0; i < *nargs; i++) { + snprintf(param, sizeof(param), "arg%u", i); + prov = gctl_get_asciiparam(req, param); + + sc = g_eli_find_device(mp, prov); + if (sc == NULL) { + G_ELI_DEBUG(1, "No such provider: %s.", prov); + continue; + } + error = g_eli_kill_one(sc); + if (error != 0) + gctl_error(req, "Not fully done."); + } + } +} + +void +g_eli_config(struct gctl_req *req, struct g_class *mp, const char *verb) +{ + uint32_t *version; + + g_topology_assert(); + + version = gctl_get_paraml(req, "version", sizeof(*version)); + if (version == NULL) { + gctl_error(req, "No '%s' argument.", "version"); + return; + } + if (*version != G_ELI_VERSION) { + gctl_error(req, "Userland and kernel parts are out of sync."); + return; + } + + if (strcmp(verb, "attach") == 0) + g_eli_ctl_attach(req, mp); + else if (strcmp(verb, "detach") == 0 || strcmp(verb, "stop") == 0) + g_eli_ctl_detach(req, mp); + else if (strcmp(verb, "onetime") == 0) + g_eli_ctl_onetime(req, mp); + else if (strcmp(verb, "setkey") == 0) + g_eli_ctl_setkey(req, mp); + else if (strcmp(verb, "delkey") == 0) + g_eli_ctl_delkey(req, mp); + else if (strcmp(verb, "kill") == 0) + g_eli_ctl_kill(req, mp); + else + gctl_error(req, "Unknown verb."); +} diff --git a/sys/geom/eli/g_eli_key.c b/sys/geom/eli/g_eli_key.c new file mode 100644 index 0000000..79a04e8 --- /dev/null +++ b/sys/geom/eli/g_eli_key.c @@ -0,0 +1,179 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#ifdef _KERNEL +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#include + + +/* + * Verify if the given 'key' is correct. + * Return 1 if it is correct and 0 otherwise. + */ +static int +g_eli_mkey_verify(const unsigned char *mkey, const unsigned char *key) +{ + const unsigned char *odhmac; /* On-disk HMAC. */ + unsigned char chmac[SHA512_MDLEN]; /* Calculated HMAC. */ + unsigned char hmkey[SHA512_MDLEN]; /* Key for HMAC. */ + + /* + * The key for HMAC calculations is: hmkey = HMAC_SHA512(Derived-Key, 0) + */ + g_eli_crypto_hmac(key, G_ELI_USERKEYLEN, "\x00", 1, hmkey, 0); + + odhmac = mkey + G_ELI_DATAIVKEYLEN; + + /* Calculate HMAC from Data-Key and IV-Key. */ + g_eli_crypto_hmac(hmkey, sizeof(hmkey), mkey, G_ELI_DATAIVKEYLEN, + chmac, 0); + + bzero(hmkey, sizeof(hmkey)); + + /* + * Compare calculated HMAC with HMAC from metadata. + * If two HMACs are equal, 'key' is correct. + */ + return (!bcmp(odhmac, chmac, SHA512_MDLEN)); +} + +/* + * Calculate HMAC from Data-Key and IV-Key. + */ +void +g_eli_mkey_hmac(unsigned char *mkey, const unsigned char *key) +{ + unsigned char hmkey[SHA512_MDLEN]; /* Key for HMAC. */ + unsigned char *odhmac; /* On-disk HMAC. */ + + /* + * The key for HMAC calculations is: hmkey = HMAC_SHA512(Derived-Key, 0) + */ + g_eli_crypto_hmac(key, G_ELI_USERKEYLEN, "\x00", 1, hmkey, 0); + + odhmac = mkey + G_ELI_DATAIVKEYLEN; + /* Calculate HMAC from Data-Key and IV-Key. */ + g_eli_crypto_hmac(hmkey, sizeof(hmkey), mkey, G_ELI_DATAIVKEYLEN, + odhmac, 0); + + bzero(hmkey, sizeof(hmkey)); +} + +/* + * Find and decrypt Master Key encrypted with 'key'. + * Return decrypted Master Key number in 'nkeyp' if not NULL. + * Return 0 on success, > 0 on failure, -1 on bad key. + */ +int +g_eli_mkey_decrypt(const struct g_eli_metadata *md, const unsigned char *key, + unsigned char *mkey, unsigned *nkeyp) +{ + unsigned char tmpmkey[G_ELI_MKEYLEN]; + unsigned char enckey[SHA512_MDLEN]; /* Key for encryption. */ + const unsigned char *mmkey; + int bit, error, nkey; + + if (nkeyp != NULL) + *nkeyp = -1; + + /* + * The key for encryption is: enckey = HMAC_SHA512(Derived-Key, 1) + */ + g_eli_crypto_hmac(key, G_ELI_USERKEYLEN, "\x01", 1, enckey, 0); + + mmkey = md->md_mkeys; + nkey = 0; + for (nkey = 0; nkey < G_ELI_MAXMKEYS; nkey++, mmkey += G_ELI_MKEYLEN) { + bit = (1 << nkey); + if ((md->md_keys & bit) == 0) + continue; + bcopy(mmkey, tmpmkey, G_ELI_MKEYLEN); + error = g_eli_crypto_decrypt(md->md_algo, tmpmkey, + G_ELI_MKEYLEN, enckey, md->md_keylen); + if (error != 0) { + bzero(tmpmkey, sizeof(tmpmkey)); + bzero(enckey, sizeof(enckey)); + return (error); + } + if (g_eli_mkey_verify(tmpmkey, key)) { + bcopy(tmpmkey, mkey, G_ELI_DATAIVKEYLEN); + bzero(tmpmkey, sizeof(tmpmkey)); + bzero(enckey, sizeof(enckey)); + if (nkeyp != NULL) + *nkeyp = nkey; + return (0); + } + } + bzero(enckey, sizeof(enckey)); + bzero(tmpmkey, sizeof(tmpmkey)); + return (-1); +} + +/* + * Encrypt the Master-Key and calculate HMAC to be able to verify it in the + * future. + */ +int +g_eli_mkey_encrypt(unsigned algo, const unsigned char *key, unsigned keylen, + unsigned char *mkey) +{ + unsigned char enckey[SHA512_MDLEN]; /* Key for encryption. */ + int error; + + /* + * To calculate HMAC, the whole key (G_ELI_USERKEYLEN bytes long) will + * be used. + */ + g_eli_mkey_hmac(mkey, key); + /* + * The key for encryption is: enckey = HMAC_SHA512(Derived-Key, 1) + */ + g_eli_crypto_hmac(key, G_ELI_USERKEYLEN, "\x01", 1, enckey, 0); + /* + * Encrypt the Master-Key and HMAC() result with the given key (this + * time only 'keylen' bits from the key are used). + */ + error = g_eli_crypto_encrypt(algo, mkey, G_ELI_MKEYLEN, enckey, keylen); + + bzero(enckey, sizeof(enckey)); + + return (error); +} diff --git a/sys/geom/eli/pkcs5v2.c b/sys/geom/eli/pkcs5v2.c new file mode 100644 index 0000000..5fcd047 --- /dev/null +++ b/sys/geom/eli/pkcs5v2.c @@ -0,0 +1,123 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#ifdef _KERNEL +#include +#include +#else +#include +#include +#include +#endif + +#include +#include + +static __inline void +xor(uint8_t *dst, const uint8_t *src, size_t size) +{ + + for (; size > 0; size--) + *dst++ ^= *src++; +} + +void +pkcs5v2_genkey(uint8_t *key, unsigned keylen, const uint8_t *salt, + size_t saltsize, const char *passphrase, u_int iterations) +{ + uint8_t md[SHA512_MDLEN], saltcount[saltsize + sizeof(uint32_t)]; + uint8_t *counter, *keyp; + u_int i, bsize, passlen; + uint32_t count; + + passlen = strlen(passphrase); + bzero(key, keylen); + bcopy(salt, saltcount, saltsize); + counter = saltcount + saltsize; + + keyp = key; + for (count = 1; keylen > 0; count++, keylen -= bsize, keyp += bsize) { + bsize = MIN(keylen, sizeof(md)); + + counter[0] = (count >> 24) & 0xff; + counter[1] = (count >> 16) & 0xff; + counter[2] = (count >> 8) & 0xff; + counter[3] = count & 0xff; + g_eli_crypto_hmac(passphrase, passlen, saltcount, + sizeof(saltcount), md, 0); + xor(keyp, md, bsize); + + for(i = 1; i < iterations; i++) { + g_eli_crypto_hmac(passphrase, passlen, md, sizeof(md), + md, 0); + xor(keyp, md, bsize); + } + } +} + +#ifndef _KERNEL +/* + * Return the number of microseconds needed for 'interations' iterations. + */ +static int +pkcs5v2_probe(int iterations) +{ + uint8_t key[G_ELI_USERKEYLEN], salt[G_ELI_SALTLEN]; + uint8_t passphrase[] = "passphrase"; + struct rusage start, end; + int usecs; + + getrusage(RUSAGE_SELF, &start); + pkcs5v2_genkey(key, sizeof(key), salt, sizeof(salt), passphrase, + iterations); + getrusage(RUSAGE_SELF, &end); + + usecs = end.ru_utime.tv_sec - start.ru_utime.tv_sec; + usecs *= 1000000; + usecs += end.ru_utime.tv_usec - start.ru_utime.tv_usec; + return (usecs); +} + +/* + * Return the number of iterations which takes 'usecs' microseconds. + */ +int +pkcs5v2_calculate(int usecs) +{ + int iterations, v; + + for (iterations = 1; ; iterations <<= 1) { + v = pkcs5v2_probe(iterations); + if (v > 2000000) + break; + } + return (((intmax_t)iterations * (intmax_t)usecs) / v); +} +#endif /* !_KERNEL */ diff --git a/sys/geom/eli/pkcs5v2.h b/sys/geom/eli/pkcs5v2.h new file mode 100644 index 0000000..83691ac --- /dev/null +++ b/sys/geom/eli/pkcs5v2.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 2005 Pawel Jakub Dawidek + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _PKCS5V2_H_ +#define _PKCS5V2_H_ +void pkcs5v2_genkey(uint8_t *key, unsigned keylen, const uint8_t *salt, + size_t saltsize, const char *passphrase, u_int iterations); +#ifndef _KERNEL +int pkcs5v2_calculate(int usecs); +#endif +#endif /* !_PKCS5V2_H_ */ -- cgit v1.1