Commit 06669fb5 authored by Geoff Simmons's avatar Geoff Simmons

Add custom encrypter VFPs.

parent 4e8a19a4
# looks like -*- vcl -*-
varnishtest "custom encryption VFPs"
# Enabled only if configure was invoked with --enable-set-salt. See
# the comment in encrypt.vtc.
feature cmd {test $ENABLE_SET_SALT = "yes"}
# Test data from the example in RFC 8188 ch 3.1
server s1 -repeat 3 {
rxreq
txresp -body {I am the walrus}
} -start
varnish v1 -arg "-p vsl_mask=+VfpAcct" -vcl+backend {
import ${vmod_ece};
import blob;
sub vcl_init {
# With all params set to defaults, the encrypter is
# just like the default ece_encrypt VFP.
new default_dup = ece.encrypter("default_dup");
ece.set_key("", blob.decode(BASE64URLNOPAD,
encoded="yqdlZ-tYemfogSmv7Ws5PQ"));
}
# Setting the salt from bereq header XYZZY-ECE-Salt.
sub vcl_backend_response {
set bereq.http.X-ECE-Key-ID = "";
set bereq.http.XYZZY-ECE-Salt = "I1BsxtFttlv3u/Oo94xnmw==";
set beresp.filters = "default_dup";
set beresp.uncacheable = true;
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.Content-Encoding == "aes128gcm"
expect resp.bodylen == 53
} -run
# The only way is to verify the result is to manually inspect the log file.
# See the comment in encrypt.vtc. The same log excerpt shown there should
# appear for this test as well.
logexpect l1 -v v1 -d 1 -g vxid -q "VfpAcct" {
expect 0 * Begin bereq
expect * = VfpAcct {^default_dup \d+ 53$}
expect * = End
} -run
varnish v1 -vcl+backend {
import ${vmod_ece};
sub vcl_init {
# Custom encrypter that uses another header to set the key.
new hdr = ece.encrypter("hdr", key_hdr="X-Set-Key-ID");
}
sub vcl_backend_response {
set bereq.http.XYZZY-ECE-Salt = "I1BsxtFttlv3u/Oo94xnmw==";
set beresp.filters = "hdr";
set beresp.uncacheable = true;
set beresp.do_stream = false;
}
}
logexpect l1 -v v1 -d 0 -g vxid -q "VfpAcct" {
expect 0 * Begin bereq
expect * = VfpAcct {^hdr \d+ 53$}
expect * = End
} -start
client c1 {
txreq -hdr "X-Set-Key-ID: "
rxresp
expect resp.status == 200
expect resp.http.Content-Encoding == "aes128gcm"
expect resp.bodylen == 53
} -run
logexpect l1 -wait
# Fetch fails if the wrong key header is used.
logexpect l1 -v v1 -d 0 -g vxid -q "FetchError" {
expect 0 * Begin bereq
expect * = FetchError {^ece encrypt: key id header X-Set-Key-ID: not found$}
expect * = End
} -start
client c1 {
txreq -hdr "X-ECE-Key-ID: "
rxresp
expect resp.status == 503
expect resp.reason == "Backend fetch failed"
} -run
logexpect l1 -wait
# Tests object finalization and DISCARD event.
varnish v1 -cli "vcl.discard vcl1"
varnish v1 -cli "vcl.list"
......@@ -2,7 +2,7 @@
varnishtest "encryption->decryption round trips"
server s1 {
server s1 -repeat 3 {
rxreq
txresp -body "I am the walrus"
......@@ -151,3 +151,130 @@ logexpect l1 -v v1 -d 1 -g vxid -q "VfpAcct" {
varnish v1 -expect ECE.vfp.ece_encrypt.ops == 6
varnish v1 -expect ECE.vfp.ece_decrypt.ops == 6
# Repeat the test with custom VFPs
varnish v1 -vcl+backend {
import ${vmod_ece};
sub vcl_init {
# Encrypter uses 128 byte record size.
new rs128 = ece.encrypter("rs128", rs=128B);
# Decrypter uses 8k chunk size.
new chunk8k = ece.decrypter("chunk8k", chunksz=8k);
}
sub vcl_backend_response {
set beresp.filters = "rs128 chunk8k";
set beresp.uncacheable = true;
}
}
logexpect l1 -v v1 -d 0 -g vxid -q "VfpAcct" {
expect 0 * Begin bereq
expect * = Debug {^chunk8k: record size 128$}
expect * = Debug {^chunk8k: chunk size 8192$}
expect * = VfpAcct {^chunk8k \d+ 15$}
expect 0 = VfpAcct {^rs128 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk8k: record size 128$}
expect * = Debug {^chunk8k: chunk size 8192$}
expect * = VfpAcct {^chunk8k \d+ 16$}
expect 0 = VfpAcct {^rs128 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk8k: record size 128$}
expect * = Debug {^chunk8k: chunk size 8192$}
expect * = VfpAcct {^chunk8k \d+ 269$}
expect * = VfpAcct {^rs128 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk8k: record size 128$}
expect * = Debug {^chunk8k: chunk size 8192$}
expect * = VfpAcct {^chunk8k \d+ 3067$}
expect * = VfpAcct {^rs128 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk8k: record size 128$}
expect * = Debug {^chunk8k: chunk size 8192$}
expect * = VfpAcct {^chunk8k \d+ 65536$}
expect * = VfpAcct {^rs128 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk8k: record size 128$}
expect * = Debug {^chunk8k: chunk size 8192$}
expect * = VfpAcct {^chunk8k \d+ 131072$}
expect * = VfpAcct {^rs128 \d+ \d+$}
expect * = End
} -start
client c1 -run
logexpect l1 -wait
varnish v1 -vcl+backend {
import ${vmod_ece};
sub vcl_init {
new rs815 = ece.encrypter("rs815", rs=815B);
new chunk4711 = ece.decrypter("chunk4711", chunksz=4711B);
}
sub vcl_backend_response {
set beresp.filters = "rs815 chunk4711";
set beresp.uncacheable = true;
}
}
logexpect l1 -v v1 -d 0 -g vxid -q "VfpAcct" {
expect 0 * Begin bereq
expect * = Debug {^chunk4711: record size 815$}
expect * = Debug {^chunk4711: chunk size 4075$}
expect * = VfpAcct {^chunk4711 \d+ 15$}
expect 0 = VfpAcct {^rs815 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk4711: record size 815$}
expect * = Debug {^chunk4711: chunk size 4075$}
expect * = VfpAcct {^chunk4711 \d+ 16$}
expect 0 = VfpAcct {^rs815 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk4711: record size 815$}
expect * = Debug {^chunk4711: chunk size 4075$}
expect * = VfpAcct {^chunk4711 \d+ 269$}
expect * = VfpAcct {^rs815 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk4711: record size 815$}
expect * = Debug {^chunk4711: chunk size 4075$}
expect * = VfpAcct {^chunk4711 \d+ 3067$}
expect * = VfpAcct {^rs815 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk4711: record size 815$}
expect * = Debug {^chunk4711: chunk size 4075$}
expect * = VfpAcct {^chunk4711 \d+ 65536$}
expect * = VfpAcct {^rs815 \d+ \d+$}
expect * = End
expect 0 * Begin bereq
expect * = Debug {^chunk4711: record size 815$}
expect * = Debug {^chunk4711: chunk size 4075$}
expect * = VfpAcct {^chunk4711 \d+ 131072$}
expect * = VfpAcct {^rs815 \d+ \d+$}
expect * = End
} -start
client c1 -run
logexpect l1 -wait
......@@ -35,6 +35,7 @@
struct vfp_settings {
unsigned magic;
#define VFP_SETTINGS_MAGIC 0x00b45435
const char *key_hdr;
size_t chunksz;
uint32_t rs;
};
......@@ -51,6 +52,13 @@ struct vfp_cfg {
void v_matchproto_(vfp_fini_f) vfp_common_fini(struct vfp_ctx *ctx,
struct vfp_entry *ent);
enum vfp_status v_matchproto_(vfp_init_f)
vfp_encrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent);
enum vfp_status v_matchproto_(vfp_pull_f)
vfp_encrypt_pull(struct vfp_ctx *ctx, struct vfp_entry *ent, void *ptr,
ssize_t *lenp);
enum vfp_status v_matchproto_(vfp_init_f)
vfp_decrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent);
......
......@@ -107,10 +107,11 @@ encrypt(struct vfp_ctx *ctx, struct ece *ece, enum vfp_status vp)
return (vp);
}
static enum vfp_status v_matchproto_(vfp_init_f)
enum vfp_status v_matchproto_(vfp_init_f)
vfp_encrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent)
{
struct ece *ece = NULL;
struct vfp_settings *settings;
struct ece_hdrbuf *hdrbuf;
const struct vfp_cfg *cfg;
const char *keyid;
......@@ -121,6 +122,8 @@ vfp_encrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent)
CHECK_OBJ_NOTNULL(ent, VFP_ENTRY_MAGIC);
AN(ent->vfp);
CAST_OBJ_NOTNULL(cfg, ent->vfp->priv1, VFP_CFG_MAGIC);
CHECK_OBJ_NOTNULL(cfg->settings, VFP_SETTINGS_MAGIC);
settings = cfg->settings;
/* XXX implement me */
if (http_GetStatus(ctx->resp) == 206)
......@@ -132,10 +135,9 @@ vfp_encrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent)
if (cfg->stats != NULL)
cfg->stats->ops++;
/* XXX make the key header configurable */
if (http_GetHdr(ctx->req, DEFAULT_KEY_HDR, &keyid) == 0)
if (http_GetHdr(ctx->req, settings->key_hdr, &keyid) == 0)
return (VERR(ctx, "key id header %s not found",
DEFAULT_KEY_HDR + 1));
settings->key_hdr + 1));
keyid_len = strlen(keyid);
if (keyid_len > MAX_ID_LEN)
return (VERR(ctx, "key id \"%.80s...\" too long "
......@@ -164,8 +166,7 @@ vfp_encrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent)
return (VERR(ctx, "%s", errmsg));
#endif
/* XXX make rs configurable */
ece->rs = DEFAULT_RS;
ece->rs = settings->rs;
encode_header(hdrbuf->hdr, ece->rs, (uint8_t)keyid_len,
(uint8_t *)keyid);
......@@ -200,7 +201,7 @@ vfp_encrypt_init(struct vfp_ctx *ctx, struct vfp_entry *ent)
return (VFP_OK);
}
static enum vfp_status v_matchproto_(vfp_pull_f)
enum vfp_status v_matchproto_(vfp_pull_f)
vfp_encrypt_pull(struct vfp_ctx *ctx, struct vfp_entry *ent, void *ptr,
ssize_t *lenp)
{
......@@ -267,6 +268,8 @@ vfp_encrypt_pull(struct vfp_ctx *ctx, struct vfp_entry *ent, void *ptr,
static struct vfp_settings default_settings = {
.magic = VFP_SETTINGS_MAGIC,
.key_hdr = DEFAULT_KEY_HDR,
.rs = DEFAULT_RS,
};
static struct vfp_cfg default_cfg = {
......
......@@ -49,6 +49,14 @@
/* minimum fetch_chunksize */
#define VARNISH_MIN_CHUNKSZ 4096
struct VPFX(ece_encrypter) {
unsigned magic;
#define ECE_ENCRYPTER_MAGIC 0x21cb655f
char *vcl_name;
struct vfp *vfp;
struct vfp_cfg *cfg;
};
struct VPFX(ece_decrypter) {
unsigned magic;
#define ECE_DECRYPTER_MAGIC 0x2a28a833
......@@ -160,14 +168,12 @@ VPFX(event)(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
NEEDLESS(return (0));
}
/* Object decrypter */
/* Custom VFPs */
VCL_VOID
vmod_decrypter__init(VRT_CTX, struct VPFX(ece_decrypter) **decp,
const char *vcl_name, struct vmod_priv *priv,
VCL_STRING name, VCL_BYTES chunksz, VCL_BYTES max_rs)
static int
crypter_init(VRT_CTX, const char *vcl_name, struct vmod_priv *priv,
VCL_STRING name, struct vfp **vfpp, struct vfp_cfg **cfgp)
{
struct VPFX(ece_decrypter) *dec;
struct vfp *vfp;
struct vfp_cfg *cfg;
struct vfp_settings *settings;
......@@ -175,15 +181,19 @@ vmod_decrypter__init(VRT_CTX, struct VPFX(ece_decrypter) **decp,
struct custom_vfp_entry *vfpe;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(decp);
AZ(*decp);
AN(vcl_name);
AN(priv);
AN(name);
AN(vfpp);
AN(cfgp);
if (name == NULL) {
VFAIL(ctx, "new %s: filter name is NULL", vcl_name);
return (-1);
}
if (*name == '\0') {
VFAIL(ctx, "new %s: filter name must be non-empty", vcl_name);
return;
return (-1);
}
if (strcmp(name, "ece_encrypt") == 0 || strcmp(name, "ece_decrypt") == 0
......@@ -195,9 +205,188 @@ vmod_decrypter__init(VRT_CTX, struct VPFX(ece_decrypter) **decp,
VFAIL(ctx,
"new %s: filter name %s already in use by another VFP",
vcl_name, name);
return (-1);
}
errno = 0;
vfp = malloc(sizeof(*vfp));
if (vfp == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for VFP: %s",
vcl_name, vstrerror(errno));
return (-1);
}
*vfpp = vfp;
errno = 0;
ALLOC_OBJ(cfg, VFP_CFG_MAGIC);
if (cfg == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for config: %s",
vcl_name, vstrerror(errno));
return (-1);
}
*cfgp = cfg;
errno = 0;
ALLOC_OBJ(settings, VFP_SETTINGS_MAGIC);
if (settings == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for settings: %s",
vcl_name, vstrerror(errno));
return (-1);
}
cfg->settings = settings;
errno = 0;
ALLOC_OBJ(vfpe, CUSTOM_VFP_MAGIC);
if (vfpe == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for VFP list entry: %s",
vcl_name, vstrerror(errno));
return (-1);
}
vfph = init_priv_vcl(priv);
AN(vfph);
vfp->name = strdup(name);
vfp->fini = vfp_common_fini;
vfp->priv1 = cfg;
VRT_AddVFP(ctx, vfp);
vfph = init_priv_vcl(priv);
vfpe->vfp = vfp;
VSLIST_INSERT_HEAD(vfph, vfpe, list);
return (0);
}
/* Object encrypter */
VCL_VOID
vmod_encrypter__init(VRT_CTX, struct VPFX(ece_encrypter) **encp,
const char *vcl_name, struct vmod_priv *priv,
VCL_STRING name, VCL_BYTES rs, VCL_STRING key_hdr)
{
struct VPFX(ece_encrypter) *enc;
struct vfp *vfp;
struct vfp_cfg *cfg;
size_t len;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(encp);
AZ(*encp);
AN(vcl_name);
AN(priv);
if (key_hdr == NULL) {
VFAIL(ctx, "new %s: key header name is NULL", vcl_name);
return;
}
if (*key_hdr == '\0') {
VFAIL(ctx, "new %s: key header name may not be empty",
vcl_name);
return;
}
len = strlen(key_hdr);
if (len > UINT8_MAX - 1) {
VFAIL(ctx, "new %s: key header name %.80s too long "
"(length %zu > max %u)", vcl_name, key_hdr, len,
UINT8_MAX - 1);
return;
}
/* Catches rs < 0. */
if (rs < MIN_RS) {
VFAIL(ctx, "new %s: rs %jd too small (must >= %d)", vcl_name,
(intmax_t)rs, MIN_RS);
return;
}
if (rs > INT_MAX) {
VFAIL(ctx, "new %s: rs %jd too large (must be <= %d)", vcl_name,
(intmax_t)rs, INT_MAX);
return;
}
/* just in case */
if (rs > UINT32_MAX) {
VFAIL(ctx, "new %s: rs %jd too large (must be <= %u)", vcl_name,
(intmax_t)rs, UINT32_MAX);
return;
}
if (crypter_init(ctx, vcl_name, priv, name, &vfp, &cfg) != 0)
return;
AN(vfp);
CHECK_OBJ_NOTNULL(cfg, VFP_CFG_MAGIC);
CHECK_OBJ_NOTNULL(cfg->settings, VFP_SETTINGS_MAGIC);
errno = 0;
cfg->settings->key_hdr = malloc(len + 3);
if (cfg->settings->key_hdr == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for header: %s",
vcl_name, vstrerror(errno));
return;
}
*((uint8_t *)TRUST_ME(cfg->settings->key_hdr)) = (uint8_t)(len + 1);
sprintf(TRUST_ME(cfg->settings->key_hdr) + 1, "%s:", key_hdr);
cfg->settings->rs = (uint32_t)rs;
errno = 0;
ALLOC_OBJ(enc, ECE_ENCRYPTER_MAGIC);
if (enc == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for object: %s",
vcl_name, vstrerror(errno));
return;
}
vfp->init = vfp_encrypt_init;
vfp->pull = vfp_encrypt_pull;
enc->vfp = vfp;
enc->vcl_name = strdup(vcl_name);
enc->cfg = cfg;
*encp = enc;
}
VCL_VOID
vmod_encrypter__fini(struct VPFX(ece_encrypter) **encp)
{
struct VPFX(ece_encrypter) *enc;
struct vfp_cfg *cfg;
if (encp == NULL || *encp == NULL)
return;
TAKE_OBJ_NOTNULL(enc, encp, ECE_ENCRYPTER_MAGIC);
if (enc->vcl_name != NULL)
free(enc->vcl_name);
if (enc->cfg != NULL) {
CHECK_OBJ(enc->cfg, VFP_CFG_MAGIC);
cfg = enc->cfg;
if (cfg->settings != NULL) {
CHECK_OBJ(cfg->settings, VFP_SETTINGS_MAGIC);
if (cfg->settings->key_hdr != NULL)
free(TRUST_ME(cfg->settings->key_hdr));
FREE_OBJ(cfg->settings);
}
if (cfg->vsc_seg != NULL)
VSC_ece_Destroy(&cfg->vsc_seg);
FREE_OBJ(cfg);
}
FREE_OBJ(enc);
}
/* Object decrypter */
VCL_VOID
vmod_decrypter__init(VRT_CTX, struct VPFX(ece_decrypter) **decp,
const char *vcl_name, struct vmod_priv *priv,
VCL_STRING name, VCL_BYTES chunksz, VCL_BYTES max_rs)
{
struct VPFX(ece_decrypter) *dec;
struct vfp *vfp;
struct vfp_cfg *cfg;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(decp);
AZ(*decp);
AN(vcl_name);
AN(priv);
/* This catches chunksz < 0 (VCL_BYTES is int64_t, hence signed). */
if (chunksz < VARNISH_MIN_CHUNKSZ) {
VFAIL(ctx, "new %s: chunksz %jd may not be < %d", vcl_name,
......@@ -230,6 +419,12 @@ vmod_decrypter__init(VRT_CTX, struct VPFX(ece_decrypter) **decp,
}
}
if (crypter_init(ctx, vcl_name, priv, name, &vfp, &cfg) != 0)
return;
AN(vfp);
CHECK_OBJ_NOTNULL(cfg, VFP_CFG_MAGIC);
CHECK_OBJ_NOTNULL(cfg->settings, VFP_SETTINGS_MAGIC);
errno = 0;
ALLOC_OBJ(dec, ECE_DECRYPTER_MAGIC);
if (dec == NULL) {
......@@ -238,56 +433,15 @@ vmod_decrypter__init(VRT_CTX, struct VPFX(ece_decrypter) **decp,
return;
}
errno = 0;
vfp = malloc(sizeof(*vfp));
if (vfp == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for VFP: %s",
vcl_name, vstrerror(errno));
return;
}
errno = 0;
ALLOC_OBJ(cfg, VFP_CFG_MAGIC);
if (cfg == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for config: %s",
vcl_name, vstrerror(errno));
return;
}
cfg->settings->chunksz = (size_t)chunksz;
cfg->settings->rs = (uint32_t)max_rs;
errno = 0;
ALLOC_OBJ(settings, VFP_SETTINGS_MAGIC);
if (settings == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for settings: %s",
vcl_name, vstrerror(errno));
return;
}
cfg->settings = settings;
errno = 0;
ALLOC_OBJ(vfpe, CUSTOM_VFP_MAGIC);
if (vfpe == NULL) {
VFAIL(ctx, "new %s: cannot allocate space for VFP list entry: %s",
vcl_name, vstrerror(errno));
return;
}
vfph = init_priv_vcl(priv);
AN(vfph);
settings->chunksz = (size_t)chunksz;
settings->rs = (uint32_t)max_rs;
vfp->name = strdup(name);
vfp->init = vfp_decrypt_init;
vfp->pull = vfp_decrypt_pull;
vfp->fini = vfp_common_fini;
vfp->priv1 = cfg;
VRT_AddVFP(ctx, vfp);
vfpe->vfp = vfp;
VSLIST_INSERT_HEAD(vfph, vfpe, list);
dec->vfp = vfp;
dec->vcl_name = strdup(vcl_name);
dec->cfg = cfg;
*decp = dec;
}
......
......@@ -56,6 +56,13 @@ Encryption and HTTP
XXX ...
$Object encrypter(PRIV_VCL, STRING name, BYTES rs=4096,
STRING key_hdr="X-ECE-Key-ID")
Create an encryption filter named ``name`` with custom parameters.
XXX ...
$Object decrypter(PRIV_VCL, STRING name, BYTES chunksz=16384,
BYTES max_rs=1048576)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment