Commit 37232bcf authored by Geoff Simmons's avatar Geoff Simmons

Add the random() function.

parent 3f6619c9
Pipeline #190 skipped
......@@ -35,6 +35,9 @@ import gcrypt [from "path"] ;
<OBJ>.encrypt(BLOB plaintext [, BLOB iv] [, BLOB ctr])
<OBJ>.decrypt(BLOB plaintext [, BLOB iv] [, BLOB ctr])
BLOB gcrypt.random([ENUM {STRONG, VERY_STRONG, NONCE} quality]
[, BYTES n])
gcrypt.version()
gcrypt.gcrypt_version()
......@@ -44,8 +47,12 @@ DESCRIPTION
This Varnish Module (VMOD) provides access to the libgcrypt library of
cryptographic building blocks -- the same library used by the GNU
Privacy Guard cryptographic suite (GnuPG or GPG). The VMOD currently
only supports symmetric encryption with AES, and with the standard
modes of operation.
supports:
* symmetric encryption with AES, and with the standard modes of
operation
* generation of pseudo-random data
The VMOD uses the VCL data type BLOB for data that enter into
encryption operations -- plaintext, ciphertext, initialization vectors
......@@ -78,24 +85,32 @@ This is a simple usage example::
# into production VCL!
new k = blobcode.blob(HEX, "000102030405060708090a0b0c0d0e0f");
# Create an object for AES-128 in CTR mode with PKCS#7 padding,
# using the key just created, and with libgcrypt internal
# structures in secure memory.
new aes = gcrypt.symmetric(AES, CTR, PKCS7, key=k.get(),
secure=true);
# Create an object for AES-128 in CTR mode using the key just
# created, and with libgcrypt internal structures in secure memory.
new aes = gcrypt.symmetric(AES, CTR, key=k.get(), secure=true);
}
# Assume that a ciphertext to be decrypted is in the request
# header X-Msg, and the counter vector is in X-CTR, both
# hex-encoded.
# Assume that a plaintext to be encrypted is in the response
# header X-Msg. Assign the hex-encoded encrypted message to the
# response header X-Cipher, and the hex-encoded counter vector to
# the response header X-Ctr; and remove X-Msg from the response.
sub vcl_recv {
# Use the blobcode VMOD to decode the two headers, and to
# encode the decrypted plaintext in hex with lower-case
# digits, to be assigned to the request header X-Plain.
set req.http.X-Plain
# Use the blobcode VMOD to decode to convert the contents of
# X-Msg to a BLOB, and to encode the encrypted ciphertext in
# hex with lower-case digits. Use the random() function to
# generate a counter vector as a 128 bit nonce.
set resp.http.X-Cipher
= blobcode.encode(HEXLC,
aes.decrypt(blobcode.decode(HEX, req.http.X-Msg),
ctr=blobcode.decode(HEX, req.http.X-CTR)));
aes.encrypt(blobcode.decode(encoded=req.http.X-Msg),
ctr=gcrypt.random(NONCE, 16B)));
# Use the no-argument version of random() to retrieve the
# counter vector that was just generated, and use the
# blobcode VMOD to encode it as lower-case hex.
set resp.http.X-CTR = blobcode.encode(HEXLC, gcrypt.random());
# Remove the plaintext from the response header.
unset resp.http.X-Msg;
}
libgcrypt secure memory
......@@ -152,6 +167,7 @@ CONTENTS
* VOID init(ENUM {INIT_SECMEM,DISABLE_SECMEM,FINISH}, BYTES)
* symmetric(ENUM {AES,AES128,RIJNDAEL,RIJNDAEL128,AES192,RIJNDAEL192,AES256,RIJNDAEL256}, ENUM {ECB,CFB,CBC,OFB,CTR}, ENUM {PKCS7,ISO7816,X923,NONE}, BLOB, BOOL, BOOL)
* BLOB random(PRIV_TASK, ENUM {STRONG,VERY_STRONG,NONCE}, BYTES)
* STRING version()
* STRING gcrypt_version()
......@@ -478,6 +494,100 @@ Examples::
iv=blobcode.decode(BASE64, req.http.X-IV-256)));
}
.. _func_random:
random
------
::
BLOB random(PRIV_TASK, ENUM {STRONG,VERY_STRONG,NONCE} quality=0, BYTES n=0)
Return a BLOB containing ``n`` bytes of pseudo-random data. The
cryptographic strength of random number generation is determined by
the ``quality`` ENUM:
* ``NONCE`` (for "number used once"): Pseudo-random bytes for
applications where cryptographic security is not required. This
level is suitable, for example, for generation of initialization or
counter vectors when used with modes of operation that require
uniqueness, but do not have strong requirements for unpredictabilty.
* ``STRONG``: From the libgcrypt manual: "Use this level for session
keys and similar purposes". Most applications requiring
cryptographically secure pseudo-random data should use this level.
* ``VERY_STRONG``: From the libgcrypt manual: "Use this level for long
term key material".
*NOTE: At the VERY_STRONG level, the random() function may take
several minutes to execute.* This level should *not* be used when
performance is important; ``STRONG`` is sufficient for crypto-quality
randomness. If at all, ``VERY_STRONG`` might be used in a service that
occasionally generates new keys, on the understanding that this will
take quite some time (and it may be necessary to set long timeouts).
Consider using the ``NONCE`` level for applications that do not
require strong randomness. With ``NONCE``, performance is better,
nothing can be revealed about the internal state of the strong random
number generator, and entropy mixed into the strong generator is not
consumed.
If ``n`` is 0B, then ``random()`` returns the same BLOB that was
returned by its most recent invocation in the same client or backend
context. Since 0B is the default value of ``n``, it can be left out
for this purpose, and the ``quality`` ENUM can be left out as well. In
other words, calling ``random()`` with no arguments returns the same
BLOB that was returned with arguments in the same client or backend
scope.
This makes it possible, for example, to use ``random()`` with
appropriate arguments to generate an initialization or counter vector
in an encryption operation, and then call ``random()`` again with no
arguments to assign the same value to a header to be sent along with
the encrypted message, as illustrated below.
The ``random()`` function fails if:
* ``n`` is 0B, but ``random()`` was not previously called with ``n`` >
0B in the same client or backend context.
* ``n`` is greater than 0B, but the ``quality`` ENUM is not set.
* There is insufficient workspace for the BLOB to be returned.
If ``random()`` fails in ``vcl_init``, then the VCL load fails with an
error message. If it fails in any other subroutine, then it returns
NULL, and an error message is written to the log with the
``VCL_Error`` tag.
Example::
# Assume the aes192 object shown in the examples above: AES-192
# encryption with CBC mode.
# Encrypt the contents of the X-Msg response header, using the
# random() function to generate an initialization vector, which
# is sent in the reponse header X-IV.
sub vcl_resp {
# The length of the IV MUST match the block size of the
# cipher in use -- 64 bits (8 bytes) for AES. For CBC, the IV
# MUST be unpredictable, so we use quality level STRONG.
set resp.http.X-Encrypted
= blobcode.encode(BASE64,
aes192.encrypt(blobcode.decode(encoded=resp.http.X-Msg),
iv=gcrypt.random(STRONG, 8B)));
# Now call random() with no arguments to retrive the IV that
# was generated, to be sent in the base64-encoded response
# header X-IV.
set resp.http.X-IV = blobcode.encode(BASE64, gcrypt.random());
# Remove the plaintext from the response header.
unset resp.http.X-Msg;
}
.. _func_version:
version
......
# looks like -*- vcl -*-
varnishtest "random() at quality level VERY_STRONG"
# This test can take several minutes to run!
# The default timeout for varnishtest is very likely to be too short
# for this test, resulting in the test aborting and failing. For best
# results, use the -t parameter to set a longer timeout, for example:
# varnishtest -t 300
varnish v1 -vcl {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
backend b { .host = "${bad_ip}"; }
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.very_strong
= blobcode.encode(HEXUC, gcrypt.random(VERY_STRONG, 16B));
set resp.http.very_strong_task
= blobcode.encode(HEXUC, gcrypt.random());
return(deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.very_strong ~ "^[[:xdigit:]]{32}$"
expect resp.http.very_strong_task == resp.http.very_strong
} -run
......@@ -793,3 +793,57 @@ client c1 {
expect resp.http.c_j == "c58c18a02297685e11148e51cbde72e644262e6bc875a3270f207f9e3936c1dd"
expect resp.http.p_j == {{"encrypt" : "foo"}}
} -run
# Use random() to create an IV and a counter.
varnish v1 -vcl {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
new k1 = blobcode.blob(HEX, "000102030405060708090a0b0c0d0e0f");
new ctr = gcrypt.symmetric(AES, CTR, key=k1.get());
new cbc = gcrypt.symmetric(AES, CBC, key=k1.get(), cbc_cts=true);
new p1 = blobcode.blob(HEX, "000102030405060708090a0b0c0d0e0f");
}
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.ctr-ciphertext
= blobcode.encode(HEXLC,
ctr.encrypt(p1.get(),
ctr=gcrypt.random(NONCE, 16B)));
set resp.http.ctr-ctr
= blobcode.encode(HEXLC, gcrypt.random());
set resp.http.ctr-plaintext
= blobcode.encode(HEXLC,
ctr.decrypt(blobcode.decode(HEX, resp.http.ctr-ciphertext),
ctr=blobcode.decode(HEX, resp.http.ctr-ctr)));
set resp.http.cbc-ciphertext
= blobcode.encode(HEXLC,
cbc.encrypt(p1.get(),
iv=gcrypt.random(STRONG, 8B)));
set resp.http.cbc-iv
= blobcode.encode(HEXLC, gcrypt.random());
set resp.http.cbc-plaintext
= blobcode.encode(HEXLC,
cbc.decrypt(blobcode.decode(HEX, resp.http.cbc-ciphertext),
iv=blobcode.decode(HEX, resp.http.cbc-iv)));
return(deliver);
}
}
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.ctr-ciphertext ~ "^[[:xdigit:]]{32}$"
expect resp.http.ctr-ctr ~ "^[[:xdigit:]]{32}$"
expect resp.http.ctr-plaintext == "000102030405060708090a0b0c0d0e0f"
expect resp.http.cbc-ciphertext ~ "^[[:xdigit:]]{32}$"
expect resp.http.cbc-iv ~ "^[[:xdigit:]]{16}$"
expect resp.http.cbc-plaintext == "000102030405060708090a0b0c0d0e0f"
} -run
# looks like -*- vcl -*-
varnishtest "random()"
# Quality level VERY_STRONG is not tested here, because random() can
# take several minutes to run at that level. See
# src/tests.disabled/very_strong_random.vtc for a test of VERY_STRONG,
# which almost certainly requires a long timeout parameter for
# varnishtest.
varnish v1 -vcl {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
backend b { .host = "${bad_ip}"; }
sub vcl_recv {
return(synth(200));
}
sub vcl_synth {
set resp.http.nonce16
= blobcode.encode(HEXUC, gcrypt.random(NONCE, 16B));
set resp.http.nonce16-task
= blobcode.encode(HEXUC, gcrypt.random());
set resp.http.strong32
= blobcode.encode(HEXUC, gcrypt.random(STRONG, 32B));
set resp.http.strong32-task-noarg
= blobcode.encode(HEXUC, gcrypt.random());
set resp.http.strong32-task-n0
= blobcode.encode(HEXUC, gcrypt.random(n=0B));
set resp.http.strong32-task-enum
= blobcode.encode(HEXUC, gcrypt.random(VERY_STRONG));
set resp.http.strong32-task-n0-enum
= blobcode.encode(HEXUC, gcrypt.random(NONCE, n=0B));
set resp.http.error
= blobcode.encode(HEXUC, gcrypt.random(n=1B));
return(deliver);
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.nonce16 ~ "^[[:xdigit:]]{32}$"
expect resp.http.nonce16-task == resp.http.nonce16
expect resp.http.strong32 ~ "^[[:xdigit:]]{64}$"
expect resp.http.strong32-task-noarg == resp.http.strong32
expect resp.http.strong32-task-n0 == resp.http.strong32
expect resp.http.strong32-task-enum == resp.http.strong32
expect resp.http.strong32-task-n0-enum == resp.http.strong32
expect resp.http.error == ""
} -run
logexpect l1 -v v1 -d 1 -q "VCL_Error" {
expect 0 * Begin req
expect * = VCL_Error "^vmod gcrypt error: in gcrypt.random..: quality ENUM is NULL"
expect * = End
} -run
varnish v1 -errvcl {vmod gcrypt error: in gcrypt.random(): quality ENUM is NULL} {
import blobcode;
import gcrypt from "${vmod_topbuild}/src/.libs/libvmod_gcrypt.so";
backend b { .host = "${bad_ip}"; }
sub vcl_init {
gcrypt.init(FINISH);
new aes = gcrypt.symmetric(AES, ECB, NONE,
key=gcrypt.random(n=16B));
}
}
......@@ -54,6 +54,9 @@
#define VERR(ctx, fmt, ...) \
errmsg((ctx), "vmod gcrypt error: " fmt, __VA_ARGS__)
#define ERRNOMEM(ctx, msg) \
ERR((ctx), msg ", out of space")
#define VERRNOMEM(ctx, fmt, ...) \
VERR((ctx), fmt ", out of space", __VA_ARGS__)
......@@ -529,6 +532,69 @@ vmod_symmetric_decrypt(VRT_CTX, struct vmod_gcrypt_symmetric *symmetric,
return plaintext;
}
/* Function random */
VCL_BLOB
vmod_random(VRT_CTX, struct vmod_priv *rnd_task, VCL_ENUM qualitys, VCL_BYTES n)
{
struct vmod_priv *rnd_blob;
uintptr_t snap;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
AN(rnd_task);
assert(n >= 0);
if (n == 0) {
if (rnd_task->priv == NULL) {
ERR(ctx, "in gcrypt.random(): No random BLOB was "
"created previously in this task scope");
return NULL;
}
assert(rnd_task->len == sizeof(*rnd_blob));
assert(((VCL_BLOB)rnd_task->priv)->len > 0);
return (VCL_BLOB)rnd_task->priv;
}
if (qualitys == NULL) {
ERR(ctx, "in gcrypt.random(): quality ENUM is NULL");
return NULL;
}
snap = WS_Snapshot(ctx->ws);
if ((rnd_blob = WS_Alloc(ctx->ws, sizeof(*rnd_blob))) == NULL) {
ERRNOMEM(ctx, "in gcrypt.random(), allocating space for return "
"BLOB");
return NULL;
}
if ((rnd_blob->priv = WS_Alloc(ctx->ws, n)) == NULL) {
WS_Reset(ctx->ws, snap);
VERRNOMEM(ctx, "in gcrypt.random(), allocating space for %d "
"random bytes", n);
return NULL;
}
switch(qualitys[0]) {
case 'N':
AZ(strcmp(qualitys + 1, "ONCE"));
gcry_create_nonce(rnd_blob->priv, n);
break;
case 'S':
AZ(strcmp(qualitys + 1, "TRONG"));
gcry_randomize(rnd_blob->priv, n, GCRY_STRONG_RANDOM);
break;
case 'V':
AZ(strcmp(qualitys + 1, "ERY_STRONG"));
gcry_randomize(rnd_blob->priv, n, GCRY_VERY_STRONG_RANDOM);
break;
default:
WRONG("Illegal quality enum");
}
rnd_blob->len = n;
rnd_blob->free = NULL;
rnd_task->priv = rnd_blob;
rnd_task->len = sizeof(*rnd_blob);
rnd_task->free = NULL;
return rnd_blob;
}
VCL_STRING
vmod_version(VRT_CTX __attribute__((unused)))
{
......
......@@ -18,6 +18,9 @@ $Module gcrypt 3 access the libgcrypt cryptographic library
<OBJ>.encrypt(BLOB plaintext [, BLOB iv] [, BLOB ctr])
<OBJ>.decrypt(BLOB plaintext [, BLOB iv] [, BLOB ctr])
BLOB gcrypt.random([ENUM {STRONG, VERY_STRONG, NONCE} quality]
[, BYTES n])
gcrypt.version()
gcrypt.gcrypt_version()
......@@ -27,8 +30,12 @@ DESCRIPTION
This Varnish Module (VMOD) provides access to the libgcrypt library of
cryptographic building blocks -- the same library used by the GNU
Privacy Guard cryptographic suite (GnuPG or GPG). The VMOD currently
only supports symmetric encryption with AES, and with the standard
modes of operation.
supports:
* symmetric encryption with AES, and with the standard modes of
operation
* generation of pseudo-random data
The VMOD uses the VCL data type BLOB for data that enter into
encryption operations -- plaintext, ciphertext, initialization vectors
......@@ -61,24 +68,32 @@ This is a simple usage example::
# into production VCL!
new k = blobcode.blob(HEX, "000102030405060708090a0b0c0d0e0f");
# Create an object for AES-128 in CTR mode with PKCS#7 padding,
# using the key just created, and with libgcrypt internal
# structures in secure memory.
new aes = gcrypt.symmetric(AES, CTR, PKCS7, key=k.get(),
secure=true);
# Create an object for AES-128 in CTR mode using the key just
# created, and with libgcrypt internal structures in secure memory.
new aes = gcrypt.symmetric(AES, CTR, key=k.get(), secure=true);
}
# Assume that a ciphertext to be decrypted is in the request
# header X-Msg, and the counter vector is in X-CTR, both
# hex-encoded.
# Assume that a plaintext to be encrypted is in the response
# header X-Msg. Assign the hex-encoded encrypted message to the
# response header X-Cipher, and the hex-encoded counter vector to
# the response header X-Ctr; and remove X-Msg from the response.
sub vcl_recv {
# Use the blobcode VMOD to decode the two headers, and to
# encode the decrypted plaintext in hex with lower-case
# digits, to be assigned to the request header X-Plain.
set req.http.X-Plain
# Use the blobcode VMOD to decode to convert the contents of
# X-Msg to a BLOB, and to encode the encrypted ciphertext in
# hex with lower-case digits. Use the random() function to
# generate a counter vector as a 128 bit nonce.
set resp.http.X-Cipher
= blobcode.encode(HEXLC,
aes.decrypt(blobcode.decode(HEX, req.http.X-Msg),
ctr=blobcode.decode(HEX, req.http.X-CTR)));
aes.encrypt(blobcode.decode(encoded=req.http.X-Msg),
ctr=gcrypt.random(NONCE, 16B)));
# Use the no-argument version of random() to retrieve the
# counter vector that was just generated, and use the
# blobcode VMOD to encode it as lower-case hex.
set resp.http.X-CTR = blobcode.encode(HEXLC, gcrypt.random());
# Remove the plaintext from the response header.
unset resp.http.X-Msg;
}
libgcrypt secure memory
......@@ -428,6 +443,94 @@ Examples::
iv=blobcode.decode(BASE64, req.http.X-IV-256)));
}
$Function BLOB random(PRIV_TASK, ENUM {STRONG, VERY_STRONG, NONCE} quality=0,
BYTES n=0)
Return a BLOB containing ``n`` bytes of pseudo-random data. The
cryptographic strength of random number generation is determined by
the ``quality`` ENUM:
* ``NONCE`` (for "number used once"): Pseudo-random bytes for
applications where cryptographic security is not required. This
level is suitable, for example, for generation of initialization or
counter vectors when used with modes of operation that require
uniqueness, but do not have strong requirements for unpredictabilty.
* ``STRONG``: From the libgcrypt manual: "Use this level for session
keys and similar purposes". Most applications requiring
cryptographically secure pseudo-random data should use this level.
* ``VERY_STRONG``: From the libgcrypt manual: "Use this level for long
term key material".
*NOTE: At the VERY_STRONG level, the random() function may take
several minutes to execute.* This level should *not* be used when
performance is important; ``STRONG`` is sufficient for crypto-quality
randomness. If at all, ``VERY_STRONG`` might be used in a service that
occasionally generates new keys, on the understanding that this will
take quite some time (and it may be necessary to set long timeouts).
Consider using the ``NONCE`` level for applications that do not
require strong randomness. With ``NONCE``, performance is better,
nothing can be revealed about the internal state of the strong random
number generator, and entropy mixed into the strong generator is not
consumed.
If ``n`` is 0B, then ``random()`` returns the same BLOB that was
returned by its most recent invocation in the same client or backend
context. Since 0B is the default value of ``n``, it can be left out
for this purpose, and the ``quality`` ENUM can be left out as well. In
other words, calling ``random()`` with no arguments returns the same
BLOB that was returned with arguments in the same client or backend
scope.
This makes it possible, for example, to use ``random()`` with
appropriate arguments to generate an initialization or counter vector
in an encryption operation, and then call ``random()`` again with no
arguments to assign the same value to a header to be sent along with
the encrypted message, as illustrated below.
The ``random()`` function fails if:
* ``n`` is 0B, but ``random()`` was not previously called with ``n`` >
0B in the same client or backend context.
* ``n`` is greater than 0B, but the ``quality`` ENUM is not set.
* There is insufficient workspace for the BLOB to be returned.
If ``random()`` fails in ``vcl_init``, then the VCL load fails with an
error message. If it fails in any other subroutine, then it returns
NULL, and an error message is written to the log with the
``VCL_Error`` tag.
Example::
# Assume the aes192 object shown in the examples above: AES-192
# encryption with CBC mode.
# Encrypt the contents of the X-Msg response header, using the
# random() function to generate an initialization vector, which
# is sent in the reponse header X-IV.
sub vcl_resp {
# The length of the IV MUST match the block size of the
# cipher in use -- 64 bits (8 bytes) for AES. For CBC, the IV
# MUST be unpredictable, so we use quality level STRONG.
set resp.http.X-Encrypted
= blobcode.encode(BASE64,
aes192.encrypt(blobcode.decode(encoded=resp.http.X-Msg),
iv=gcrypt.random(STRONG, 8B)));
# Now call random() with no arguments to retrive the IV that
# was generated, to be sent in the base64-encoded response
# header X-IV.
set resp.http.X-IV = blobcode.encode(BASE64, gcrypt.random());
# Remove the plaintext from the response header.
unset resp.http.X-Msg;
}
$Function STRING version()
Returns the version string for this VMOD.
......
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