Viewing: gss_crypto.c
// SPDX-License-Identifier: BSD-3-Clause
/*
* Modifications for Lustre
*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
*
* Author: Eric Mei <ericm@clusterfs.com>
*/
/*
* linux/net/sunrpc/gss_krb5_mech.c
*
* Copyright (c) 2001 The Regents of the University of Michigan.
* All rights reserved.
*
* Andy Adamson <andros@umich.edu>
* J. Bruce Fields <bfields@umich.edu>
*/
#define DEBUG_SUBSYSTEM S_SEC
#include <obd.h>
#include <obd_support.h>
#include "gss_internal.h"
#include "gss_crypto.h"
int gss_keyblock_init(struct gss_keyblock *kb, const char *alg_name,
const int alg_mode)
{
int rc;
kb->kb_tfm = crypto_alloc_sync_skcipher(alg_name, alg_mode, 0);
if (IS_ERR(kb->kb_tfm)) {
rc = PTR_ERR(kb->kb_tfm);
kb->kb_tfm = NULL;
CERROR("failed to alloc tfm: %s, mode %d: rc = %d\n", alg_name,
alg_mode, rc);
return rc;
}
rc = crypto_sync_skcipher_setkey(kb->kb_tfm, kb->kb_key.data,
kb->kb_key.len);
if (rc) {
CERROR("failed to set %s key, len %d, rc = %d\n", alg_name,
kb->kb_key.len, rc);
return rc;
}
return 0;
}
void gss_keyblock_free(struct gss_keyblock *kb)
{
rawobj_free(&kb->kb_key);
if (kb->kb_tfm)
crypto_free_sync_skcipher(kb->kb_tfm);
}
int gss_keyblock_dup(struct gss_keyblock *new, struct gss_keyblock *kb)
{
return rawobj_dup(&new->kb_key, &kb->kb_key);
}
int gss_get_bytes(char **ptr, const char *end, void *res, size_t len)
{
char *p, *q;
p = *ptr;
q = p + len;
if (q > end || q < p)
return -EINVAL;
memcpy(res, p, len);
*ptr = q;
return 0;
}
int gss_get_rawobj(char **ptr, const char *end, rawobj_t *res)
{
char *p, *q;
__u32 len;
p = *ptr;
if (gss_get_bytes(&p, end, &len, sizeof(len)))
return -EINVAL;
q = p + len;
if (q > end || q < p)
return -EINVAL;
/* Support empty objects */
if (len != 0) {
OBD_ALLOC_LARGE(res->data, len);
if (!res->data)
return -ENOMEM;
} else {
res->len = len;
res->data = NULL;
return 0;
}
res->len = len;
memcpy(res->data, p, len);
*ptr = q;
return 0;
}
int gss_get_keyblock(char **ptr, const char *end,
struct gss_keyblock *kb, __u32 keysize)
{
char *buf;
int rc;
OBD_ALLOC_LARGE(buf, keysize);
if (buf == NULL)
return -ENOMEM;
rc = gss_get_bytes(ptr, end, buf, keysize);
if (rc) {
OBD_FREE_LARGE(buf, keysize);
return rc;
}
kb->kb_key.len = keysize;
kb->kb_key.data = buf;
return 0;
}
/*
* Should be used for buffers allocated with k/vmalloc().
*
* Dispose of @sgt with gss_teardown_sgtable().
*
* @prealloc_sg is to avoid memory allocation inside sg_alloc_table()
* in cases where a single sg is sufficient. No attempt to reduce the
* number of sgs by squeezing physically contiguous pages together is
* made though, for simplicity.
*
* This function is copied from the ceph filesystem code.
*/
int gss_setup_sgtable(struct sg_table *sgt, struct scatterlist *prealloc_sg,
const void *buf, unsigned int buf_len)
{
struct scatterlist *sg;
const bool is_vmalloc = is_vmalloc_addr(buf);
unsigned int off = offset_in_page(buf);
unsigned int chunk_cnt = 1;
unsigned int chunk_len = PAGE_ALIGN(off + buf_len);
int i;
int rc;
if (buf_len == 0) {
memset(sgt, 0, sizeof(*sgt));
return -EINVAL;
}
if (is_vmalloc) {
chunk_cnt = chunk_len >> PAGE_SHIFT;
chunk_len = PAGE_SIZE;
}
if (chunk_cnt > 1) {
rc = sg_alloc_table(sgt, chunk_cnt, GFP_NOFS);
if (rc)
return rc;
} else {
WARN_ON_ONCE(chunk_cnt != 1);
sg_init_table(prealloc_sg, 1);
sgt->sgl = prealloc_sg;
sgt->nents = sgt->orig_nents = 1;
}
for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) {
struct page *page;
unsigned int len = min(chunk_len - off, buf_len);
if (is_vmalloc)
page = vmalloc_to_page(buf);
else
page = virt_to_page(buf);
sg_set_page(sg, page, len, off);
off = 0;
buf += len;
buf_len -= len;
}
WARN_ON_ONCE(buf_len != 0);
return 0;
}
void gss_teardown_sgtable(struct sg_table *sgt)
{
if (sgt->orig_nents > 1)
sg_free_table(sgt);
}
int gss_crypt_generic(struct crypto_sync_skcipher *tfm, int decrypt,
const void *iv, const void *in, void *out, size_t length)
{
struct scatterlist sg;
struct sg_table sg_out;
__u8 local_iv[16] = {0};
__u32 ret = -EINVAL;
SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm);
LASSERT(tfm);
if (length % crypto_sync_skcipher_blocksize(tfm) != 0) {
CERROR("output length %zu mismatch blocksize %d\n",
length, crypto_sync_skcipher_blocksize(tfm));
goto out;
}
if (crypto_sync_skcipher_ivsize(tfm) > ARRAY_SIZE(local_iv)) {
CERROR("iv size too large %d\n",
crypto_sync_skcipher_ivsize(tfm));
goto out;
}
if (iv)
memcpy(local_iv, iv, crypto_sync_skcipher_ivsize(tfm));
if (in != out)
memmove(out, in, length);
ret = gss_setup_sgtable(&sg_out, &sg, out, length);
if (ret != 0)
goto out;
skcipher_request_set_sync_tfm(req, tfm);
skcipher_request_set_callback(req, 0, NULL, NULL);
skcipher_request_set_crypt(req, sg_out.sgl, sg_out.sgl, length, local_iv);
if (decrypt)
ret = crypto_skcipher_decrypt(req);
else
ret = crypto_skcipher_encrypt(req);
gss_teardown_sgtable(&sg_out);
out:
return ret;
}
int gss_digest_hash(struct ahash_request *req,
rawobj_t *hdr, int msgcnt, rawobj_t *msgs,
int iovcnt, struct bio_vec *iovs)
{
struct scatterlist sg[1];
struct sg_table sgt;
int rc = 0;
int i;
for (i = 0; i < msgcnt; i++) {
if (msgs[i].len == 0)
continue;
rc = gss_setup_sgtable(&sgt, sg, msgs[i].data, msgs[i].len);
if (rc)
return rc;
ahash_request_set_crypt(req, sgt.sgl, NULL, msgs[i].len);
rc = crypto_ahash_update(req);
gss_teardown_sgtable(&sgt);
if (rc)
return rc;
}
for (i = 0; i < iovcnt; i++) {
if (iovs[i].bv_len == 0)
continue;
sg_init_table(sg, 1);
sg_set_page(&sg[0], iovs[i].bv_page, iovs[i].bv_len,
iovs[i].bv_offset);
ahash_request_set_crypt(req, sg, NULL, iovs[i].bv_len);
rc = crypto_ahash_update(req);
if (rc)
return rc;
}
if (hdr) {
rc = gss_setup_sgtable(&sgt, sg, hdr->data, hdr->len);
if (rc)
return rc;
ahash_request_set_crypt(req, sgt.sgl, NULL, hdr->len);
rc = crypto_ahash_update(req);
gss_teardown_sgtable(&sgt);
if (rc)
return rc;
}
return rc;
}
int gss_digest_hash_compat(struct ahash_request *req,
rawobj_t *hdr, int msgcnt, rawobj_t *msgs,
int iovcnt, struct bio_vec *iovs)
{
struct scatterlist sg[1];
struct sg_table sgt;
int rc = 0;
int i;
for (i = 0; i < msgcnt; i++) {
if (msgs[i].len == 0)
continue;
rc = gss_setup_sgtable(&sgt, sg, msgs[i].data, msgs[i].len);
if (rc)
return rc;
ahash_request_set_crypt(req, sgt.sgl, NULL, msgs[i].len);
rc = crypto_ahash_update(req);
gss_teardown_sgtable(&sgt);
if (rc)
return rc;
}
for (i = 0; i < iovcnt; i++) {
if (iovs[i].bv_len == 0)
continue;
sg_init_table(sg, 1);
sg_set_page(&sg[0], iovs[i].bv_page, iovs[i].bv_len,
iovs[i].bv_offset);
ahash_request_set_crypt(req, sg, NULL, iovs[i].bv_len);
rc = crypto_ahash_update(req);
if (rc)
return rc;
}
if (hdr) {
rc = gss_setup_sgtable(&sgt, sg, &(hdr->len), sizeof(hdr->len));
if (rc)
return rc;
ahash_request_set_crypt(req, sgt.sgl, NULL, sizeof(hdr->len));
rc = crypto_ahash_update(req);
gss_teardown_sgtable(&sgt);
if (rc)
return rc;
}
return rc;
}
int gss_add_padding(rawobj_t *msg, int msg_buflen, int blocksize)
{
int padding;
padding = (blocksize - (msg->len & (blocksize - 1))) &
(blocksize - 1);
if (!padding)
return 0;
if (msg->len + padding > msg_buflen) {
CERROR("bufsize %u too small: datalen %u, padding %u\n",
msg_buflen, msg->len, padding);
return -EINVAL;
}
memset(msg->data + msg->len, padding, padding);
msg->len += padding;
return 0;
}
int gss_crypt_rawobjs(struct crypto_sync_skcipher *tfm, __u8 *iv,
int inobj_cnt, rawobj_t *inobjs, rawobj_t *outobj,
int enc)
{
struct scatterlist src;
struct scatterlist dst;
struct sg_table sg_dst;
struct sg_table sg_src;
__u8 *buf;
__u32 datalen = 0;
int i, rc;
SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm);
ENTRY;
buf = outobj->data;
skcipher_request_set_sync_tfm(req, tfm);
skcipher_request_set_callback(req, 0, NULL, NULL);
for (i = 0; i < inobj_cnt; i++) {
LASSERT(buf + inobjs[i].len <= outobj->data + outobj->len);
rc = gss_setup_sgtable(&sg_src, &src, inobjs[i].data,
inobjs[i].len);
if (rc != 0)
RETURN(rc);
rc = gss_setup_sgtable(&sg_dst, &dst, buf,
outobj->len - datalen);
if (rc != 0) {
gss_teardown_sgtable(&sg_src);
RETURN(rc);
}
skcipher_request_set_crypt(req, sg_src.sgl, sg_dst.sgl, src.length, iv);
if (enc)
rc = crypto_skcipher_encrypt(req);
else
rc = crypto_skcipher_decrypt(req);
gss_teardown_sgtable(&sg_src);
gss_teardown_sgtable(&sg_dst);
if (rc) {
CERROR("encrypt error %d\n", rc);
RETURN(rc);
}
datalen += inobjs[i].len;
buf += inobjs[i].len;
}
outobj->len = datalen;
RETURN(0);
}