Commit 5afe33cd authored by Nils Goroll's avatar Nils Goroll

introduce $Object key() with the option to set RSA parameters

This enables definition of keys where the PEM form is not evailable,
for example from JSON Web Key (JWK) files.

Ref: https://tools.ietf.org/html/rfc7517#section-9.3
parent ac6dede4
......@@ -41,6 +41,7 @@
#include <openssl/pem.h>
#include <cache/cache.h>
#include <vcl.h>
#include "vcc_crypto_if.h"
......@@ -131,6 +132,223 @@ fini(void)
#define EVP_MD_CTX_new() EVP_MD_CTX_create()
#endif
/*
* ------------------------------------------------------------
* $Object key()
*/
struct VPFX(crypto_key) {
unsigned magic;
#define VMOD_CRYPTO_KEY_MAGIC 0x32c81a50
const char *vcl_name;
EVP_PKEY *pkey;
};
#define CRYPTO_KEY_BLOB 0x32c81a51
static void
key_free(void *ptr)
{
struct VPFX(crypto_key) *k;
CAST_OBJ_NOTNULL(k, ptr, VMOD_CRYPTO_KEY_MAGIC);
if (k->pkey != NULL)
EVP_PKEY_free(k->pkey);
memset(k, 0, sizeof *k);
}
VCL_VOID
vmod_key__init(VRT_CTX,
struct VPFX(crypto_key) **kp, const char *vcl_name,
struct vmod_priv *priv)
{
struct VPFX(crypto_key) *k;
AN(kp);
AZ(*kp);
assert(ctx->method == VCL_MET_INIT);
k = WS_Alloc(ctx->ws, sizeof *k);
INIT_OBJ(k, VMOD_CRYPTO_KEY_MAGIC);
k->vcl_name = vcl_name;
/* use PRIV_TASK to free key after vcl_init */
priv->priv = k;
priv->free = key_free;
*kp = k;
}
VCL_VOID
vmod_key__fini(struct VPFX(crypto_key) **kp)
{
*kp = NULL;
}
static int
key_ctx_ok(VRT_CTX)
{
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
if (ctx->method == VCL_MET_INIT)
return (1);
VRT_fail(ctx, "key methods can only be used in vcl_init {}");
return (0);
}
VCL_BLOB
vmod_key_use(VRT_CTX, struct VPFX(crypto_key) *k)
{
if (! key_ctx_ok(ctx))
return (NULL);
CHECK_OBJ_NOTNULL(k, VMOD_CRYPTO_KEY_MAGIC);
return (VRT_blob(ctx, "xkey.use()", k, sizeof *k, CRYPTO_KEY_BLOB));
}
EVP_PKEY *
pkey_blob(VRT_CTX, VCL_BLOB blob)
{
struct VPFX(crypto_key) *k;
if (blob && blob->type == CRYPTO_KEY_BLOB &&
blob->blob != NULL &&
blob->len == sizeof(*k)) {
CAST_OBJ_NOTNULL(k, TRUST_ME(blob->blob),
VMOD_CRYPTO_KEY_MAGIC);
return (k->pkey);
}
VRT_fail(ctx, "invalid key blob");
return (NULL);
}
/* to be freed by caller */
static EVP_PKEY *
pkey_pem(VRT_CTX, VCL_STRING pem)
{
EVP_PKEY *pkey;
BIO *bio;
ERR_clear_error();
bio = BIO_new_mem_buf(pem, -1);
if (bio == NULL) {
VRT_fail(ctx, "key bio failed");
return (NULL);
}
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
BIO_free_all(bio);
if (pkey != NULL)
return (pkey);
VRT_fail(ctx, "read public key failed, error 0x%lx",
ERR_get_error());
return (NULL);
}
VCL_VOID
vmod_key_pem_pubkey(VRT_CTX, struct VPFX(crypto_key) *k,
VCL_STRING pem)
{
if (! key_ctx_ok(ctx))
return;
CHECK_OBJ_NOTNULL(k, VMOD_CRYPTO_KEY_MAGIC);
if (k->pkey != NULL) {
VRT_fail(ctx, "xkey.pem_pubkey(): key already defined");
return;
}
k->pkey = pkey_pem(ctx, pem);
}
VCL_VOID
vmod_key_rsa(VRT_CTX, struct VPFX(crypto_key) *k, struct VARGS(key_rsa) *args) {
BIGNUM *n = NULL, *e = NULL, *d = NULL;
EVP_PKEY *pkey;
RSA *rsa;
if (! key_ctx_ok(ctx))
return;
CHECK_OBJ_NOTNULL(k, VMOD_CRYPTO_KEY_MAGIC);
if (k->pkey != NULL) {
VRT_fail(ctx, "xkey.rsa(): key already defined");
return;
}
AN(args);
ERR_clear_error();
if (args->n && args->n->len > 0)
n = BN_bin2bn(args->n->blob, args->n->len, NULL);
if (args->e && args->e->len > 0)
e = BN_bin2bn(args->e->blob, args->e->len, NULL);
if (args->valid_d && args->d && args->d->len > 0)
d = BN_bin2bn(args->d->blob, args->d->len, NULL);
if (n == NULL || e == NULL) {
VRT_fail(ctx, "xkey.rsa(): n and/or e missing, error 0x%lx",
ERR_get_error());
goto err_bn;
}
pkey = EVP_PKEY_new();
if (pkey == NULL) {
VRT_fail(ctx, "xkey.rsa(): pkey alloc failed, error 0x%lx",
ERR_get_error());
goto err_bn;
}
rsa = RSA_new();
if (rsa == NULL) {
VRT_fail(ctx, "xkey.rsa(): rsa alloc failed, error 0x%lx",
ERR_get_error());
goto err_pkey;
}
if (RSA_set0_key(rsa, n, e, d) != 1) {
VRT_fail(ctx, "xkey.rsa(): RSA_set0_key failed, error 0x%lx",
ERR_get_error());
goto err_rsa;
}
EVP_PKEY_assign_RSA(pkey, rsa);
k->pkey = pkey;
return;
err_rsa:
RSA_free(rsa);
err_pkey:
EVP_PKEY_free(pkey);
err_bn:
if (n != NULL) BN_free(n);
if (e != NULL) BN_free(e);
if (d != NULL) BN_free(d);
}
/*
* ------------------------------------------------------------
* $Object verfier()
*/
struct vmod_crypto_verifier {
unsigned magic;
#define VMOD_CRYPTO_VERIFIER_MAGIC 0x32c81a57
......@@ -146,16 +364,20 @@ struct vmod_crypto_verifier_task {
VCL_VOID
vmod_verifier__init(VRT_CTX,
struct vmod_crypto_verifier **vcvp, const char *vcl_name, VCL_ENUM md_s,
VCL_STRING pem)
struct vmod_crypto_verifier **vcvp, const char *vcl_name,
struct VARGS(verifier__init) *args)
{
struct vmod_crypto_verifier *vcv;
const EVP_MD *md = md_evp(md_parse(md_s));
const EVP_MD *md = md_evp(md_parse(args->digest));
EVP_PKEY *pkey;
BIO *bio;
if (md == NULL) {
VRT_fail(ctx, "digest %s not supported", md_s);
VRT_fail(ctx, "digest %s not supported", args->digest);
return;
}
if (args->valid_pem ^ args->valid_key == 0) {
VRT_fail(ctx, "Need either pem or key, but not both");
return;
}
......@@ -189,20 +411,15 @@ vmod_verifier__init(VRT_CTX,
goto err_digest;
}
bio = BIO_new_mem_buf(pem, -1);
if (bio == NULL) {
VRT_fail(ctx, "key bio failed");
goto err_digest;
}
if (args->valid_pem)
pkey = pkey_pem(ctx, args->pem);
else if (args->valid_key)
pkey = pkey_blob(ctx, args->key);
else
INCOMPL();
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
if (pkey == NULL) {
VRT_fail(ctx, "read public key failed, error 0x%lx",
ERR_get_error());
BIO_free_all(bio);
if (pkey == NULL)
goto err_digest;
}
BIO_free_all(bio);
if (EVP_DigestVerifyInit(vcv->evpctx, NULL, md, NULL, pkey) !=1) {
VRT_fail(ctx, "EVP_DigestVerifyInit failed, error 0x%lx",
......@@ -210,7 +427,9 @@ vmod_verifier__init(VRT_CTX,
EVP_PKEY_free(pkey);
goto err_digest;
}
EVP_PKEY_free(pkey);
if (args->valid_pem)
EVP_PKEY_free(pkey);
*vcvp = vcv;
return;
......
......@@ -20,15 +20,23 @@ SYNOPSIS
import crypto [as name] [from "path"]
:ref:`vmod_crypto.verifier`
:ref:`crypto.key()`
:ref:`vmod_crypto.verifier.update`
:ref:`xkey.use()`
:ref:`vmod_crypto.verifier.update_blob`
:ref:`xkey.pem_pubkey()`
:ref:`vmod_crypto.verifier.reset`
:ref:`xkey.rsa()`
:ref:`vmod_crypto.verifier.valid`
:ref:`crypto.verifier()`
:ref:`xverifier.update()`
:ref:`xverifier.update_blob()`
:ref:`xverifier.reset()`
:ref:`xverifier.valid()`
DESCRIPTION
......@@ -59,42 +67,85 @@ Example
}
} -start
.. _vmod_crypto.verifier:
.. _crypto.key():
new xkey = crypto.key()
-----------------------
Create a generic key object. The algorithm gets defined by the method
called upon it.
Any methods on `crypto.key()`_ may only be used in ``sub vcl_init {}``.
new xverifier = crypto.verifier(ENUM digest, STRING pem)
--------------------------------------------------------
.. _xkey.use():
BLOB xkey.use()
---------------
Wrap the key in a blob to be passed to `crypto.verifier()`_
.. _xkey.pem_pubkey():
VOID xkey.pem_pubkey(STRING)
----------------------------
Create a key from the PEM-encoded public key.
The cryptographic method to be used and the key length are
automatically determined from _pem_. Typically supported methods
comprise RSA and DSA.
Any error is fatal to vcl initialization.
.. _xkey.rsa():
VOID xkey.rsa(BLOB n, BLOB e, [BLOB d])
---------------------------------------
Create an RSA key from the parameters n, e, and optionally d.
Any error is fatal to vcl initialization.
.. _crypto.verifier():
new xverifier = crypto.verifier(ENUM digest, [STRING pem], [BLOB key])
----------------------------------------------------------------------
::
new xverifier = crypto.verifier(
ENUM {md_null, md4, md5, sha1, sha224, sha256, sha384, sha512, ripemd160, rmd160, whirlpool} digest,
STRING pem
[STRING pem],
[BLOB key]
)
Create an object to verify signatures created using _digest_ and
_pem_.
_key_.
The _pem_ argument is a PEM-encoded public key specification.
The _key_ argument should be a call to `xkey.use()`_ on the respective
`crypto.key()`_ object.
The cryptographic method to be used and the key length are
automatically determined from _pem_. Typically supported methods
comprise RSA and DSA.
Alternatively to _key_, the _pem_ argument may be used to pass a
PEM-encoded public key specification. Use of the _pem_ argument is
deprecated.
Either the _key_ or the _pem_ argument must be given.
.. _vmod_crypto.verifier.update:
.. _xverifier.update():
BOOL xverifier.update(STRING)
-----------------------------
Add strings to the data to be verfied with the verifier object.
.. _vmod_crypto.verifier.update_blob:
.. _xverifier.update_blob():
BOOL xverifier.update_blob(BLOB)
--------------------------------
Add a blob to the data to be verified with the verifier object.
.. _vmod_crypto.verifier.reset:
.. _xverifier.reset():
BOOL xverifier.reset()
----------------------
......@@ -102,7 +153,7 @@ BOOL xverifier.reset()
Reset the verfication state as if previous calls to the update methods
had not happened.
.. _vmod_crypto.verifier.valid:
.. _xverifier.valid():
BOOL xverifier.valid(BLOB signature)
------------------------------------
......
......@@ -38,20 +38,49 @@ Example
}
} -start
$Object key(PRIV_TASK)
$Object verifier(ENUM {md_null, md4, md5, sha1, sha224,
sha256, sha384, sha512, ripemd160, rmd160, whirlpool} digest,
STRING pem)
Create a generic key object. The algorithm gets defined by the method
called upon it.
Create an object to verify signatures created using _digest_ and
_pem_.
Any methods on `crypto.key()`_ may only be used in ``sub vcl_init {}``.
$Method BLOB .use()
Wrap the key in a blob to be passed to `crypto.verifier()`_
The _pem_ argument is a PEM-encoded public key specification.
$Method VOID .pem_pubkey(STRING)
Create a key from the PEM-encoded public key.
The cryptographic method to be used and the key length are
automatically determined from _pem_. Typically supported methods
comprise RSA and DSA.
Any error is fatal to vcl initialization.
$Method VOID .rsa(BLOB n, BLOB e, [BLOB d])
Create an RSA key from the parameters n, e, and optionally d.
Any error is fatal to vcl initialization.
$Object verifier(ENUM {md_null, md4, md5, sha1, sha224,
sha256, sha384, sha512, ripemd160, rmd160, whirlpool} digest,
[STRING pem], [BLOB key])
Create an object to verify signatures created using _digest_ and
_key_.
The _key_ argument should be a call to `xkey.use()`_ on the respective
`crypto.key()`_ object.
Alternatively to _key_, the _pem_ argument may be used to pass a
PEM-encoded public key specification. Use of the _pem_ argument is
deprecated.
Either the _key_ or the _pem_ argument must be given.
$Method BOOL .update(STRANDS)
Add strings to the data to be verfied with the verifier object.
......
......@@ -10,7 +10,8 @@ varnish v1 -vcl+backend {
import blob;
sub vcl_init {
new v = crypto.verifier(sha256, {"
new k = crypto.key();
k.pem_pubkey({"
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0l+1tg+ioDHojDA9/LJA
SBq0D2oWd6jgXkJg9GJI7uFShWwKvKHNjRirkx3Ozk9xCZveOwj4LGGOXPRgGBbn
......@@ -21,6 +22,18 @@ bWDL9Nva2ncJ7LnUZy9QEwCGg+KsK9J1vVPG0u1/ORuVc3fVsnKQvRvq0pPZbQWp
0wIDAQAB
-----END PUBLIC KEY-----
"});
/*
{"kty":"RSA","e":"AQAB","kid":"a0d11ba1-4ab5-4c66-aa93-9899451d1637","n":"0l-1tg-ioDHojDA9_LJASBq0D2oWd6jgXkJg9GJI7uFShWwKvKHNjRirkx3Ozk9xCZveOwj4LGGOXPRgGBbnAd8xjeZKJ4-MWBNQasfPsuqkm5CWWtYt5Td4zJ_jtDu0F7LPgpKL5G0va9FbdcdYbWDL9Nva2ncJ7LnUZy9QEwCGg-KsK9J1vVPG0u1_ORuVc3fVsnKQvRvq0pPZbQWp8HrTVZh8VyqA8IghVUsENfzenjPhesfff0pzwUC_PCwsWlbbDXGHQw59nQ-NvCZW1MSp-eU66dELZV9r_uTMrOZrHqg2O2rSCJsIwJUsS9SM852FkW7GWEHKMSU4NuBY0w"}
*/
new k2 = crypto.key();
k2.rsa(n = blob.decode(BASE64URLNOPAD,
encoded = "0l-1tg-ioDHojDA9_LJASBq0D2oWd6jgXkJg9GJI7uFShWwKvKHNjRirkx3Ozk9xCZveOwj4LGGOXPRgGBbnAd8xjeZKJ4-MWBNQasfPsuqkm5CWWtYt5Td4zJ_jtDu0F7LPgpKL5G0va9FbdcdYbWDL9Nva2ncJ7LnUZy9QEwCGg-KsK9J1vVPG0u1_ORuVc3fVsnKQvRvq0pPZbQWp8HrTVZh8VyqA8IghVUsENfzenjPhesfff0pzwUC_PCwsWlbbDXGHQw59nQ-NvCZW1MSp-eU66dELZV9r_uTMrOZrHqg2O2rSCJsIwJUsS9SM852FkW7GWEHKMSU4NuBY0w"),
e = blob.decode(BASE64URLNOPAD, encoded = "AQAB"));
new v = crypto.verifier(sha256, key=k.use());
new v2 = crypto.verifier(sha256, key=k2.use());
# note: always verifying against the same signature is
# not a realistic use case.
new sig = blob.blob(BASE64URLNOPAD,
......@@ -30,6 +43,13 @@ bWDL9Nva2ncJ7LnUZy9QEwCGg+KsK9J1vVPG0u1/ORuVc3fVsnKQvRvq0pPZbQWp
set resp.http.up = v.update(req.http.data);
if (v.valid(sig.get())) {
set resp.status = 200;
} else {
set resp.status = 400;
return (deliver);
}
set resp.http.up2 = v2.update(req.http.data);
if (v2.valid(sig.get())) {
set resp.status = 200;
} else {
set resp.status = 400;
}
......@@ -41,9 +61,11 @@ client c1 {
rxresp
expect resp.status == 200
expect resp.http.up == true
expect resp.http.up2 == true
txreq -hdr "data: bad"
rxresp
expect resp.status == 400
expect resp.http.up == true
expect resp.http.up2 == <undef>
} -run
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