#include <linux/err.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/sunrpc/auth.h>
#include <linux/sunrpc/gss_krb5.h>
#include <linux/sunrpc/xdr.h>
#include "auth_gss_internal.h"
#include "gss_krb5_internal.h"
#if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
# define RPCDBG_FACILITY RPCDBG_AUTH
#endif
static struct gss_api_mech gss_kerberos_mech;
static const u32 gss_krb5_enctypes[] = {
KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192,
KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128,
KRB5_ENCTYPE_CAMELLIA256_CTS_CMAC,
KRB5_ENCTYPE_CAMELLIA128_CTS_CMAC,
KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1_96,
KRB5_ENCTYPE_AES128_CTS_HMAC_SHA1_96,
};
static char gss_krb5_enctype_priority_list[64];
static void gss_krb5_prepare_enctype_priority_list(void)
{
size_t total, i;
char buf[16];
char *sep;
int n;
sep = "";
gss_krb5_enctype_priority_list[0] = '\0';
for (total = 0, i = 0; i < ARRAY_SIZE(gss_krb5_enctypes); i++) {
if (!crypto_krb5_find_enctype(gss_krb5_enctypes[i]))
continue;
n = sprintf(buf, "%s%u", sep, gss_krb5_enctypes[i]);
if (n < 0)
break;
if (total + n >= sizeof(gss_krb5_enctype_priority_list))
break;
strcat(gss_krb5_enctype_priority_list, buf);
sep = ",";
total += n;
}
}
static int
gss_krb5_import_ctx_v2(struct krb5_ctx *ctx, gfp_t gfp_mask)
{
struct krb5_buffer TK = {
.len = ctx->krb5e->key_len,
.data = ctx->Ksess,
};
int ret;
ctx->initiator_enc_aead =
crypto_krb5_prepare_encryption(ctx->krb5e, &TK,
KG_USAGE_INITIATOR_SEAL,
gfp_mask);
if (IS_ERR(ctx->initiator_enc_aead)) {
ret = PTR_ERR(ctx->initiator_enc_aead);
goto out_free;
}
ctx->acceptor_enc_aead =
crypto_krb5_prepare_encryption(ctx->krb5e, &TK,
KG_USAGE_ACCEPTOR_SEAL,
gfp_mask);
if (IS_ERR(ctx->acceptor_enc_aead)) {
ret = PTR_ERR(ctx->acceptor_enc_aead);
goto out_free;
}
ctx->initiator_sign_shash =
crypto_krb5_prepare_checksum(ctx->krb5e, &TK,
KG_USAGE_INITIATOR_SIGN,
gfp_mask);
if (IS_ERR(ctx->initiator_sign_shash)) {
ret = PTR_ERR(ctx->initiator_sign_shash);
goto out_free;
}
ctx->acceptor_sign_shash =
crypto_krb5_prepare_checksum(ctx->krb5e, &TK,
KG_USAGE_ACCEPTOR_SIGN,
gfp_mask);
if (IS_ERR(ctx->acceptor_sign_shash)) {
ret = PTR_ERR(ctx->acceptor_sign_shash);
goto out_free;
}
return 0;
out_free:
crypto_free_shash(ctx->acceptor_sign_shash);
crypto_free_shash(ctx->initiator_sign_shash);
crypto_free_aead(ctx->acceptor_enc_aead);
crypto_free_aead(ctx->initiator_enc_aead);
return ret;
}
static int
gss_import_v2_context(const void *p, const void *end, struct krb5_ctx *ctx,
gfp_t gfp_mask)
{
u64 seq_send64;
int keylen;
u32 time32;
int ret;
p = simple_get_bytes(p, end, &ctx->flags, sizeof(ctx->flags));
if (IS_ERR(p))
goto out_err;
ctx->initiate = ctx->flags & KRB5_CTX_FLAG_INITIATOR;
p = simple_get_bytes(p, end, &time32, sizeof(time32));
if (IS_ERR(p))
goto out_err;
ctx->endtime = (time64_t)time32;
p = simple_get_bytes(p, end, &seq_send64, sizeof(seq_send64));
if (IS_ERR(p))
goto out_err;
atomic64_set(&ctx->seq_send64, seq_send64);
p = simple_get_bytes(p, end, &ctx->enctype, sizeof(ctx->enctype));
if (IS_ERR(p))
goto out_err;
ctx->krb5e = crypto_krb5_find_enctype(ctx->enctype);
if (!ctx->krb5e) {
dprintk("gss_kerberos_mech: unsupported krb5 enctype %u\n",
ctx->enctype);
p = ERR_PTR(-EINVAL);
goto out_err;
}
keylen = ctx->krb5e->key_len;
p = simple_get_bytes(p, end, ctx->Ksess, keylen);
if (IS_ERR(p))
goto out_err;
if (p != end) {
p = ERR_PTR(-EINVAL);
goto out_err;
}
ctx->mech_used.data = kmemdup(gss_kerberos_mech.gm_oid.data,
gss_kerberos_mech.gm_oid.len, gfp_mask);
if (unlikely(ctx->mech_used.data == NULL)) {
p = ERR_PTR(-ENOMEM);
goto out_err;
}
ctx->mech_used.len = gss_kerberos_mech.gm_oid.len;
ret = gss_krb5_import_ctx_v2(ctx, gfp_mask);
if (ret) {
p = ERR_PTR(ret);
goto out_free;
}
return 0;
out_free:
kfree(ctx->mech_used.data);
out_err:
return PTR_ERR(p);
}
static int
gss_krb5_import_sec_context(const void *p, size_t len, struct gss_ctx *ctx_id,
time64_t *endtime, gfp_t gfp_mask)
{
const void *end = (const void *)((const char *)p + len);
struct krb5_ctx *ctx;
int ret;
ctx = kzalloc_obj(*ctx, gfp_mask);
if (ctx == NULL)
return -ENOMEM;
ret = gss_import_v2_context(p, end, ctx, gfp_mask);
memzero_explicit(&ctx->Ksess, sizeof(ctx->Ksess));
if (ret) {
kfree(ctx);
return ret;
}
ctx_id->internal_ctx_id = ctx;
if (endtime)
*endtime = ctx->endtime;
return 0;
}
static void
gss_krb5_delete_sec_context(void *internal_ctx)
{
struct krb5_ctx *kctx = internal_ctx;
crypto_free_shash(kctx->acceptor_sign_shash);
crypto_free_shash(kctx->initiator_sign_shash);
crypto_free_aead(kctx->acceptor_enc_aead);
crypto_free_aead(kctx->initiator_enc_aead);
kfree(kctx->mech_used.data);
kfree(kctx);
}
u32 gss_krb5_errno_to_status(int err)
{
switch (err) {
case 0:
return GSS_S_COMPLETE;
case -EBADMSG:
return GSS_S_BAD_SIG;
case -EPROTO:
return GSS_S_DEFECTIVE_TOKEN;
default:
return GSS_S_FAILURE;
}
}
static u32 gss_krb5_get_mic(struct gss_ctx *gctx, struct xdr_buf *text,
struct xdr_netobj *token)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
return gss_krb5_get_mic_v2(kctx, text, token);
}
static u32 gss_krb5_verify_mic(struct gss_ctx *gctx,
struct xdr_buf *message_buffer,
struct xdr_netobj *read_token)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
return gss_krb5_verify_mic_v2(kctx, message_buffer, read_token);
}
static u32 gss_krb5_wrap(struct gss_ctx *gctx, int offset,
struct xdr_buf *buf, struct page **pages)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
return gss_krb5_wrap_v2(kctx, offset, buf, pages);
}
static u32 gss_krb5_unwrap(struct gss_ctx *gctx, int offset,
int len, struct xdr_buf *buf)
{
struct krb5_ctx *kctx = gctx->internal_ctx_id;
return gss_krb5_unwrap_v2(kctx, offset, len, buf,
&gctx->slack, &gctx->align);
}
static const struct gss_api_ops gss_kerberos_ops = {
.gss_import_sec_context = gss_krb5_import_sec_context,
.gss_get_mic = gss_krb5_get_mic,
.gss_verify_mic = gss_krb5_verify_mic,
.gss_wrap = gss_krb5_wrap,
.gss_unwrap = gss_krb5_unwrap,
.gss_delete_sec_context = gss_krb5_delete_sec_context,
};
static struct pf_desc gss_kerberos_pfs[] = {
[0] = {
.pseudoflavor = RPC_AUTH_GSS_KRB5,
.qop = GSS_C_QOP_DEFAULT,
.service = RPC_GSS_SVC_NONE,
.name = "krb5",
},
[1] = {
.pseudoflavor = RPC_AUTH_GSS_KRB5I,
.qop = GSS_C_QOP_DEFAULT,
.service = RPC_GSS_SVC_INTEGRITY,
.name = "krb5i",
.datatouch = true,
},
[2] = {
.pseudoflavor = RPC_AUTH_GSS_KRB5P,
.qop = GSS_C_QOP_DEFAULT,
.service = RPC_GSS_SVC_PRIVACY,
.name = "krb5p",
.datatouch = true,
},
};
MODULE_ALIAS("rpc-auth-gss-krb5");
MODULE_ALIAS("rpc-auth-gss-krb5i");
MODULE_ALIAS("rpc-auth-gss-krb5p");
MODULE_ALIAS("rpc-auth-gss-390003");
MODULE_ALIAS("rpc-auth-gss-390004");
MODULE_ALIAS("rpc-auth-gss-390005");
MODULE_ALIAS("rpc-auth-gss-1.2.840.113554.1.2.2");
static struct gss_api_mech gss_kerberos_mech = {
.gm_name = "krb5",
.gm_owner = THIS_MODULE,
.gm_oid = { 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" },
.gm_ops = &gss_kerberos_ops,
.gm_pf_num = ARRAY_SIZE(gss_kerberos_pfs),
.gm_pfs = gss_kerberos_pfs,
.gm_upcall_enctypes = gss_krb5_enctype_priority_list,
};
static int __init init_kerberos_module(void)
{
int status;
gss_krb5_prepare_enctype_priority_list();
status = gss_mech_register(&gss_kerberos_mech);
if (status)
printk("Failed to register kerberos gss mechanism!\n");
return status;
}
static void __exit cleanup_kerberos_module(void)
{
gss_mech_unregister(&gss_kerberos_mech);
}
MODULE_DESCRIPTION("Sun RPC Kerberos 5 module");
MODULE_LICENSE("GPL");
module_init(init_kerberos_module);
module_exit(cleanup_kerberos_module);