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 @@ ...@@ -41,6 +41,7 @@
#include <openssl/pem.h> #include <openssl/pem.h>
#include <cache/cache.h> #include <cache/cache.h>
#include <vcl.h>
#include "vcc_crypto_if.h" #include "vcc_crypto_if.h"
...@@ -131,6 +132,223 @@ fini(void) ...@@ -131,6 +132,223 @@ fini(void)
#define EVP_MD_CTX_new() EVP_MD_CTX_create() #define EVP_MD_CTX_new() EVP_MD_CTX_create()
#endif #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 { struct vmod_crypto_verifier {
unsigned magic; unsigned magic;
#define VMOD_CRYPTO_VERIFIER_MAGIC 0x32c81a57 #define VMOD_CRYPTO_VERIFIER_MAGIC 0x32c81a57
...@@ -146,16 +364,20 @@ struct vmod_crypto_verifier_task { ...@@ -146,16 +364,20 @@ struct vmod_crypto_verifier_task {
VCL_VOID VCL_VOID
vmod_verifier__init(VRT_CTX, vmod_verifier__init(VRT_CTX,
struct vmod_crypto_verifier **vcvp, const char *vcl_name, VCL_ENUM md_s, struct vmod_crypto_verifier **vcvp, const char *vcl_name,
VCL_STRING pem) struct VARGS(verifier__init) *args)
{ {
struct vmod_crypto_verifier *vcv; 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; EVP_PKEY *pkey;
BIO *bio;
if (md == NULL) { 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; return;
} }
...@@ -189,20 +411,15 @@ vmod_verifier__init(VRT_CTX, ...@@ -189,20 +411,15 @@ vmod_verifier__init(VRT_CTX,
goto err_digest; goto err_digest;
} }
bio = BIO_new_mem_buf(pem, -1); if (args->valid_pem)
if (bio == NULL) { pkey = pkey_pem(ctx, args->pem);
VRT_fail(ctx, "key bio failed"); else if (args->valid_key)
goto err_digest; pkey = pkey_blob(ctx, args->key);
} else
INCOMPL();
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); if (pkey == NULL)
if (pkey == NULL) {
VRT_fail(ctx, "read public key failed, error 0x%lx",
ERR_get_error());
BIO_free_all(bio);
goto err_digest; goto err_digest;
}
BIO_free_all(bio);
if (EVP_DigestVerifyInit(vcv->evpctx, NULL, md, NULL, pkey) !=1) { if (EVP_DigestVerifyInit(vcv->evpctx, NULL, md, NULL, pkey) !=1) {
VRT_fail(ctx, "EVP_DigestVerifyInit failed, error 0x%lx", VRT_fail(ctx, "EVP_DigestVerifyInit failed, error 0x%lx",
...@@ -210,7 +427,9 @@ vmod_verifier__init(VRT_CTX, ...@@ -210,7 +427,9 @@ vmod_verifier__init(VRT_CTX,
EVP_PKEY_free(pkey); EVP_PKEY_free(pkey);
goto err_digest; goto err_digest;
} }
EVP_PKEY_free(pkey);
if (args->valid_pem)
EVP_PKEY_free(pkey);
*vcvp = vcv; *vcvp = vcv;
return; return;
......
...@@ -20,15 +20,23 @@ SYNOPSIS ...@@ -20,15 +20,23 @@ SYNOPSIS
import crypto [as name] [from "path"] 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 DESCRIPTION
...@@ -59,42 +67,85 @@ Example ...@@ -59,42 +67,85 @@ Example
} }
} -start } -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( new xverifier = crypto.verifier(
ENUM {md_null, md4, md5, sha1, sha224, sha256, sha384, sha512, ripemd160, rmd160, whirlpool} digest, 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 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 Alternatively to _key_, the _pem_ argument may be used to pass a
automatically determined from _pem_. Typically supported methods PEM-encoded public key specification. Use of the _pem_ argument is
comprise RSA and DSA. deprecated.
Either the _key_ or the _pem_ argument must be given.
.. _vmod_crypto.verifier.update: .. _xverifier.update():
BOOL xverifier.update(STRING) BOOL xverifier.update(STRING)
----------------------------- -----------------------------
Add strings to the data to be verfied with the verifier object. 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) BOOL xverifier.update_blob(BLOB)
-------------------------------- --------------------------------
Add a blob to the data to be verified with the verifier object. Add a blob to the data to be verified with the verifier object.
.. _vmod_crypto.verifier.reset: .. _xverifier.reset():
BOOL xverifier.reset() BOOL xverifier.reset()
---------------------- ----------------------
...@@ -102,7 +153,7 @@ BOOL xverifier.reset() ...@@ -102,7 +153,7 @@ BOOL xverifier.reset()
Reset the verfication state as if previous calls to the update methods Reset the verfication state as if previous calls to the update methods
had not happened. had not happened.
.. _vmod_crypto.verifier.valid: .. _xverifier.valid():
BOOL xverifier.valid(BLOB signature) BOOL xverifier.valid(BLOB signature)
------------------------------------ ------------------------------------
......
...@@ -38,20 +38,49 @@ Example ...@@ -38,20 +38,49 @@ Example
} }
} -start } -start
$Object key(PRIV_TASK)
$Object verifier(ENUM {md_null, md4, md5, sha1, sha224, Create a generic key object. The algorithm gets defined by the method
sha256, sha384, sha512, ripemd160, rmd160, whirlpool} digest, called upon it.
STRING pem)
Create an object to verify signatures created using _digest_ and Any methods on `crypto.key()`_ may only be used in ``sub vcl_init {}``.
_pem_.
$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 The cryptographic method to be used and the key length are
automatically determined from _pem_. Typically supported methods automatically determined from _pem_. Typically supported methods
comprise RSA and DSA. 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) $Method BOOL .update(STRANDS)
Add strings to the data to be verfied with the verifier object. Add strings to the data to be verfied with the verifier object.
......
...@@ -10,7 +10,8 @@ varnish v1 -vcl+backend { ...@@ -10,7 +10,8 @@ varnish v1 -vcl+backend {
import blob; import blob;
sub vcl_init { sub vcl_init {
new v = crypto.verifier(sha256, {" new k = crypto.key();
k.pem_pubkey({"
-----BEGIN PUBLIC KEY----- -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0l+1tg+ioDHojDA9/LJA MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0l+1tg+ioDHojDA9/LJA
SBq0D2oWd6jgXkJg9GJI7uFShWwKvKHNjRirkx3Ozk9xCZveOwj4LGGOXPRgGBbn SBq0D2oWd6jgXkJg9GJI7uFShWwKvKHNjRirkx3Ozk9xCZveOwj4LGGOXPRgGBbn
...@@ -21,6 +22,18 @@ bWDL9Nva2ncJ7LnUZy9QEwCGg+KsK9J1vVPG0u1/ORuVc3fVsnKQvRvq0pPZbQWp ...@@ -21,6 +22,18 @@ bWDL9Nva2ncJ7LnUZy9QEwCGg+KsK9J1vVPG0u1/ORuVc3fVsnKQvRvq0pPZbQWp
0wIDAQAB 0wIDAQAB
-----END PUBLIC KEY----- -----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 # note: always verifying against the same signature is
# not a realistic use case. # not a realistic use case.
new sig = blob.blob(BASE64URLNOPAD, new sig = blob.blob(BASE64URLNOPAD,
...@@ -30,6 +43,13 @@ bWDL9Nva2ncJ7LnUZy9QEwCGg+KsK9J1vVPG0u1/ORuVc3fVsnKQvRvq0pPZbQWp ...@@ -30,6 +43,13 @@ bWDL9Nva2ncJ7LnUZy9QEwCGg+KsK9J1vVPG0u1/ORuVc3fVsnKQvRvq0pPZbQWp
set resp.http.up = v.update(req.http.data); set resp.http.up = v.update(req.http.data);
if (v.valid(sig.get())) { if (v.valid(sig.get())) {
set resp.status = 200; 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 { } else {
set resp.status = 400; set resp.status = 400;
} }
...@@ -41,9 +61,11 @@ client c1 { ...@@ -41,9 +61,11 @@ client c1 {
rxresp rxresp
expect resp.status == 200 expect resp.status == 200
expect resp.http.up == true expect resp.http.up == true
expect resp.http.up2 == true
txreq -hdr "data: bad" txreq -hdr "data: bad"
rxresp rxresp
expect resp.status == 400 expect resp.status == 400
expect resp.http.up == true expect resp.http.up == true
expect resp.http.up2 == <undef>
} -run } -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