On Wed, Nov 03, 2010 at 02:27:49PM +0100, Peter Pöml wrote:
> > I know it's not really relevant, but probably interesting anyway… we were
> > rather hoping that the mirrors wouldn't have to communicate with a central
> > database, so I figured the easiest way to do it would just be to put an
> > expiration timestamp in the URL of ~10 secs or so, and also a signature
> > generated with a private key. The mirrors would just need to calculate the
> > signature themselves with the key, check the signatures match and the
> > expiration hasn't passed, and serve the file. Any failure would just result
> > in a 403. 10 lines of Python/Perl/PHP/Shell should do it, I'd think… the
> > simpler the better :)
> Good idea. 
> If you come up with a good solution for checking the timestamp for
> deployment on the mirrors, it would be cool to add the timestamp
> generation directly in MirrorBrain. If this is independant of user
> authentication and/or a specific user database (I think it would be),
> that could be an interesting feature also for other deployments. 
> Then all things that you need would be doable with MirrorBrain
> itself, without proxying, redirecting or scripting, right?
> I'm thinking of a simple further Apache directive like
>  MirrorBrainRedirectStampKey <key>
> which causes MB to append ?stamp=<timestamp_crypted_with_key> to
> the URLs that it returns to clients.
> Sounds like just a few lines of code. If you have an idea how to best
> use/verify the stamp on the mirrors, and we come up with a reusable
> procedure for that, I'd be happy to implement the server part.
> With that concept, the decision how long links are valid would be in the
> hand of the mirrors, I think, which I hope is okay. Server time needs to
> be synchronized decently.

I spent a little time playing with this. I basically implemented
creating such temporary links in MirrorBrain. 

A quick and easy way to implement this is adding two things to the URL:
the request time as seconds since epoch, and a MD5 hash from a string
concatenated of epoch time and a shared secret. 

Below is test output demonstrating the procedure.

MirrorBrain is configured with a directive
       MirrorBrainRedirectStampKey my_key

 # curl -sI
HTTP/1.1 302 Found

Note the two arguments appended to the Location.

The mirrors which receives these requests need to check two things:

1) Check if the "ticket" is a valid one, by hashing the timestamp and
   the shared secret -- here in shell code:

    # echo -n '1288879347 my_key' | md5sum
   e215bb55bbea2c133145330f9e061f5b  -

   That hash is the same as came with the URL, and thus the ticket
   is valid (or was valid in the past).

2) Check if the "ticket" is still fresh enough, simply by comparing to
   current time. Shell example:

    # echo $(( $(date +'%s') - 1288879347))     

   While I was typing, the ticked became 380 seconds old (because I'm
   slow :-), which makes it outdated, and the mirror would reject it,
   based on the policy that it must not be older than e.g. 30 seconds.

These two checks to be done on the mirrors can easily be destilled in a
script consisting of only a few lines. 

The fact that MD5 collisions can be found nowadays should be pretty
irrelevant considering the short-lived-ness of the tickets. That's one
reason why the mirrors should check for the age. The other reason is
because it allows using a one-way, non-revertible hash, instead of a
symmetric encryption which would be less trivial to implement (md5 is
available anywhere, while symmetric ciphers are not).

The question would be whether this satisfies your needs. If you want to
go this route, I'm happy to commit the change and fold it into the
planned release.

I'm attaching the MirrorBrain patch for reference.

Index: mod_mirrorbrain/mod_mirrorbrain.c
--- mod_mirrorbrain/mod_mirrorbrain.c	(revision 8193)
+++ mod_mirrorbrain/mod_mirrorbrain.c	(working copy)
@@ -232,6 +232,7 @@
     apr_array_header_t *exclude_ips;
     ap_regex_t *exclude_filemask;
     ap_regex_t *metalink_torrentadd_mask;
+    const char *stampkey;
 } mb_dir_conf;
 /* per-server configuration */
@@ -380,6 +381,7 @@
     new->exclude_ips = apr_array_make(p, 4, sizeof (char *));
     new->exclude_filemask = NULL;
     new->metalink_torrentadd_mask = NULL;
+    new->stampkey = NULL;
     return (void *) new;
@@ -409,6 +411,7 @@
     mrg->exclude_ips = apr_array_append(p, base->exclude_ips, add->exclude_ips);
     mrg->exclude_filemask = (add->exclude_filemask == NULL) ? base->exclude_filemask : add->exclude_filemask;
     mrg->metalink_torrentadd_mask = (add->metalink_torrentadd_mask == NULL) ? base->metalink_torrentadd_mask : add->metalink_torrentadd_mask;
+    cfgMergeString(stampkey);
     return (void *) mrg;
@@ -487,6 +490,14 @@
     return NULL;
+static const char *mb_cmd_redirect_stamp_key(cmd_parms *cmd, void *config, 
+                                             const char* arg1)
+    mb_dir_conf *cfg = (mb_dir_conf *) config;
+    cfg->stampkey = arg1;
+    return NULL;
 static const char *mb_cmd_debug(cmd_parms *cmd, void *config, int flag)
     mb_dir_conf *cfg = (mb_dir_conf *) config;
@@ -3111,8 +3122,21 @@
+    /* Build target URI */
+    if (cfg->stampkey) {
+        const char* epoch = apr_itoa(r->pool, apr_time_sec(r->request_time));
+        const char* epochkey = apr_pstrcat(r->pool, epoch, " ", cfg->stampkey, NULL);
+        const char* stamp = ap_md5(r->pool, (unsigned const char *)epochkey);
+        debugLog(r, cfg, "stamp: '%s' -> %s", epochkey, stamp);
+        uri = apr_pstrcat(r->pool, chosen->baseurl, filename, 
+                          "?time=", epoch,
+                          "&stamp=", stamp, NULL);
+    } else {
+        uri = apr_pstrcat(r->pool, chosen->baseurl, filename, NULL);
+    }
     /* Send it away: set a "Location:" header and 302 redirect. */
-    uri = apr_pstrcat(r->pool, chosen->baseurl, filename, NULL);
     debugLog(r, cfg, "Redirect to '%s'", uri);
     /* for _conditional_ logging, leave some mark */
@@ -3267,6 +3291,12 @@
                   "mirror can be found in the database. These mirrors are assumed to have "
                   "*all* files. (Or they could be configured per directory.)"),
+    AP_INIT_TAKE1("MirrorBrainRedirectStampKey", mb_cmd_redirect_stamp_key, NULL, 
+                  ACCESS_CONF,
+                  "Causes MirrorBrain to append a signed timestamp to redirection URLs. The "
+                  "argument is a string that defines the key to encrypt the timestamp with. "
+                  "Can be configured on directory-level."),
     /* to be used only in server context */
     AP_INIT_TAKE1("MirrorBrainDBDQuery", mb_cmd_dbd_query, NULL,

Attachment: pgpchtsOJesux.pgp
Description: PGP signature

Reply via email to