~fkooman/www.tuxed.net

13feb3f7680723e9ab9c3a889eeeb5f25cc97490 — François Kooman 5 years ago
initial commit
A  => .gitignore +2 -0
@@ 1,2 @@
/vendor
/output

A  => composer.json +7 -0
@@ 1,7 @@
{
    "require": {
        "fkooman/tpl-twig": "^1.3",
        "michelf/php-markdown": "^1.6",
        "suin/php-rss-writer": "^1.4"
    }
}

A  => composer.lock +304 -0
@@ 1,304 @@
{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "hash": "78d82725917a839c4369911194693290",
    "content-hash": "050fab5d628e4670e41e020c559d18d5",
    "packages": [
        {
            "name": "fkooman/tpl",
            "version": "2.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/fkooman/php-lib-tpl.git",
                "reference": "d6abca430ff050890f93eb353158b8ed3dc91bf4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/fkooman/php-lib-tpl/zipball/d6abca430ff050890f93eb353158b8ed3dc91bf4",
                "reference": "d6abca430ff050890f93eb353158b8ed3dc91bf4",
                "shasum": ""
            },
            "require": {
                "php": ">= 5.3.0"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "fkooman\\Tpl\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache-2.0"
            ],
            "authors": [
                {
                    "name": "François Kooman",
                    "email": "fkooman@tuxed.net"
                }
            ],
            "description": "Simple Template Abstraction Library",
            "time": "2016-02-04 10:00:40"
        },
        {
            "name": "fkooman/tpl-twig",
            "version": "1.3.2",
            "source": {
                "type": "git",
                "url": "https://github.com/fkooman/php-lib-tpl-twig.git",
                "reference": "6e2187ef357d75f9f446b382f110cd7d7d290fc8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/fkooman/php-lib-tpl-twig/zipball/6e2187ef357d75f9f446b382f110cd7d7d290fc8",
                "reference": "6e2187ef357d75f9f446b382f110cd7d7d290fc8",
                "shasum": ""
            },
            "require": {
                "ext-gettext": "*",
                "ext-spl": "*",
                "fkooman/tpl": "^2.1.0",
                "php": ">= 5.4",
                "twig/extensions": "^1.3.0",
                "twig/twig": "^1.20"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "fkooman\\Tpl\\Twig\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache-2.0"
            ],
            "authors": [
                {
                    "name": "François Kooman",
                    "email": "fkooman@tuxed.net"
                }
            ],
            "description": "Twig for Simple Template Abstraction Library",
            "time": "2016-04-11 11:53:32"
        },
        {
            "name": "michelf/php-markdown",
            "version": "1.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/michelf/php-markdown.git",
                "reference": "156e56ee036505ec637d761ee62dc425d807183c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/michelf/php-markdown/zipball/156e56ee036505ec637d761ee62dc425d807183c",
                "reference": "156e56ee036505ec637d761ee62dc425d807183c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-lib": "1.4.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Michelf": ""
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Michel Fortin",
                    "email": "michel.fortin@michelf.ca",
                    "homepage": "https://michelf.ca/",
                    "role": "Developer"
                },
                {
                    "name": "John Gruber",
                    "homepage": "https://daringfireball.net/"
                }
            ],
            "description": "PHP Markdown",
            "homepage": "https://michelf.ca/projects/php-markdown/",
            "keywords": [
                "markdown"
            ],
            "time": "2015-12-24 01:37:31"
        },
        {
            "name": "suin/php-rss-writer",
            "version": "1.4.0",
            "source": {
                "type": "git",
                "url": "https://github.com/suin/php-rss-writer.git",
                "reference": "a7dd2bbc287a05b266406d3afa298d44ebf115f3"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/suin/php-rss-writer/zipball/a7dd2bbc287a05b266406d3afa298d44ebf115f3",
                "reference": "a7dd2bbc287a05b266406d3afa298d44ebf115f3",
                "shasum": ""
            },
            "require": {
                "php": ">=5.4.0"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "Suin\\RSSWriter": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Hidehito Nozawa aka Suin",
                    "email": "suinyeze@gmail.com"
                }
            ],
            "description": "Yet another simple RSS writer library for PHP 5.4 or later.",
            "homepage": "https://github.com/suin/php-rss-writer",
            "keywords": [
                "feed",
                "generator",
                "php",
                "rss",
                "writer"
            ],
            "time": "2016-03-19 06:15:37"
        },
        {
            "name": "twig/extensions",
            "version": "v1.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/twigphp/Twig-extensions.git",
                "reference": "449e3c8a9ffad7c2479c7864557275a32b037499"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/449e3c8a9ffad7c2479c7864557275a32b037499",
                "reference": "449e3c8a9ffad7c2479c7864557275a32b037499",
                "shasum": ""
            },
            "require": {
                "twig/twig": "~1.20|~2.0"
            },
            "require-dev": {
                "symfony/translation": "~2.3"
            },
            "suggest": {
                "symfony/translation": "Allow the time_diff output to be translated"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Twig_Extensions_": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                }
            ],
            "description": "Common additional features for Twig that do not directly belong in core",
            "homepage": "http://twig.sensiolabs.org/doc/extensions/index.html",
            "keywords": [
                "i18n",
                "text"
            ],
            "time": "2015-08-22 16:38:35"
        },
        {
            "name": "twig/twig",
            "version": "v1.24.1",
            "source": {
                "type": "git",
                "url": "https://github.com/twigphp/Twig.git",
                "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/twigphp/Twig/zipball/3566d311a92aae4deec6e48682dc5a4528c4a512",
                "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.7"
            },
            "require-dev": {
                "symfony/debug": "~2.7",
                "symfony/phpunit-bridge": "~2.7"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.24-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Twig_": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com",
                    "homepage": "http://fabien.potencier.org",
                    "role": "Lead Developer"
                },
                {
                    "name": "Armin Ronacher",
                    "email": "armin.ronacher@active-4.com",
                    "role": "Project Founder"
                },
                {
                    "name": "Twig Team",
                    "homepage": "http://twig.sensiolabs.org/contributors",
                    "role": "Contributors"
                }
            ],
            "description": "Twig, the flexible, fast, and secure template language for PHP",
            "homepage": "http://twig.sensiolabs.org",
            "keywords": [
                "templating"
            ],
            "time": "2016-05-30 09:11:59"
        }
    ],
    "packages-dev": [],
    "aliases": [],
    "minimum-stability": "stable",
    "stability-flags": [],
    "prefer-stable": false,
    "prefer-lowest": false,
    "platform": [],
    "platform-dev": []
}

A  => generate.php +95 -0
@@ 1,95 @@
<?php

require_once 'vendor/autoload.php';

use Michelf\MarkdownExtra;
use fkooman\Tpl\Twig\TwigTemplateManager;

$postDir = sprintf('%s/posts', __DIR__);
$outputDir = sprintf('%s/output', __DIR__);
$templateDir = sprintf('%s/views', __DIR__);

$blogTitle = "François' Weblog";
$blogDescription = 'Just another boring weblog.';
$blogUrl = 'https://www.tuxed.net/fkooman/blog/';
$blogAuthor = 'François Kooman';
$blogAuthorTwitter = 'fkooman';
$blogAuthorMail = 'fkooman@tuxed.net';

$blogPosts = [];

foreach (glob(sprintf('%s/*.md', $postDir)) as $postFile) {
    $postInfo = [];

    // obtain postInfo
    $f = fopen($postFile, 'r');
    $line = fgets($f);
    if (0 !== strpos($line, '---')) {
        throw new Exception('invalid file!');
    }
    $line = fgets($f);
    do {
        $xx = explode(':', $line);
        $postInfo[trim($xx[0])] = trim($xx[1]);
        $line = fgets($f);
    } while (0 !== strpos($line, '---'));

    // read rest of the post         
    $buffer = '';
    while (!feof($f)) {
        $buffer .= fgets($f);
    }

    fclose($f);
    $postOutputFile = basename($postFile, '.md').'.html';

    $parser = new MarkdownExtra();

    $blogPost = [
        'htmlContent' => $parser->transform($buffer),
        'published' => $postInfo['published'],
        'title' => $postInfo['title'],
        'modified' => isset($postInfo['modified']) ? $postInfo['modified'] : null,
        'fileName' => $postOutputFile,
    ];
    $postsList[] = $blogPost;
}

usort($postsList, function ($a, $b) {
    return strtotime($a['published']) < strtotime($b['published']);
});

$tpl = new TwigTemplateManager([$templateDir]);
$indexPage = $tpl->render(
    'index',
    [
        'postsList' => $postsList,
        'pageTitle' => 'Index',
        'blogTitle' => $blogTitle,
        'blogDescription' => $blogDescription,
        'blogAuthor' => $blogAuthor,
        'blogAuthorMail' => $blogAuthorMail,
        'blogAuthorTwitter' => $blogAuthorTwitter,
    ]
);

foreach ($postsList as $post) {
    $postPage = $tpl->render(
        'post',
        [
            'blogTitle' => $blogTitle,
            'pageTitle' => $post['title'],
            'post' => $post,
            'blogAuthor' => $blogAuthor,
            'blogAuthorMail' => $blogAuthorMail,
            'blogAuthorTwitter' => $blogAuthorTwitter,
        ]
    );
    file_put_contents($outputDir.'/'.$post['fileName'], $postPage);
}

file_put_contents($outputDir.'/index.html', $indexPage);

// copy css
@mkdir($outputDir.'/css');
copy(__DIR__.'/screen.css', $outputDir.'/css/screen.css');

A  => posts/apache_php_fpm.md +204 -0
@@ 1,204 @@
---
title: Apache and PHP-FPM
published: 2015-11-01
modified: 2015-11-02
---

There is lots of crappy information out there about deploying PHP with Apache, 
or nginx. It is really hard to distill what is really a safe configuration and
what works. Combining this with a safe TLS configuration nears the 
impossible.

### PHP-FPM

Configuring PHP-FPM is not that difficult, actually, one could keep the 
defaults and that will work pretty well.

```
$ sudo dnf -y install php-fpm
```

I only change the configuration not to use a socket, but listen on TCP 
instead. There are some more tweaks you can perform, but to get it working 
reasonably well that is not needed yet.

```
$ sudo sed -i "s|listen = /run/php-fpm/www.sock|listen = [::]:9000|" /etc/php-fpm.d/www.conf
$ sudo sed -i "s/listen.allowed_clients = 127.0.0.1/listen.allowed_clients = 127.0.0.1,::1/" /etc/php-fpm.d/www.conf
```

You possibly have to update the `listen.allowed_clients` if you use a 
separate VM or container for the web server.

Do not forget to enable and start PHP-FPM.

```
$ sudo systemctl enable php-fpm
$ sudo systemctl start php-fpm
```

That should be all for PHP-FPM.

### Apache

We start simple, with a HTTP server serving a PHP application using PHP-FPM. 

```
<VirtualHost www.example.org:80>
    ServerName www.example.org

    ErrorLog logs/www.example.org_error_log
    TransferLog logs/www.example.org_access_log
    CustomLog logs/www.example.org_combined_log combined
    LogLevel warn

    DocumentRoot /usr/share/my-php-app/web

    <Directory "/usr/share/my-php-app/web">
        Options -MultiViews

        #Require local
        Require all granted

        AllowOverride none
    </Directory>

    # Pass through the "Authorization" header
    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1

    # Some request are handled by Apache directly
    ProxyPass      "/css/" !
    ProxyPass      "/img/" !
    ProxyPass      "/js/" !
    ProxyPassMatch "^/robots.txt$" !
    ProxyPassMatch "^/favicon.ico$" !

    # The rest goes to PHP-FPM...
    ProxyPass      "/" fcgi://[::1]:9000/usr/share/php-my-app/web/index.php/
</VirtualHost>
```

### Apache TLS
Basically, this means that we remove the current contents of the 
`VirtualHost` block and use it to rewrite to HTTPS instead and move
the PHP-FPM stuff to the new TLS `VirtualHost`.

```
<VirtualHost www.example.org:80>
    ServerName www.example.org

    ErrorLog logs/www.example.org_error_log
    TransferLog logs/www.example.org_access_log
    CustomLog logs/www.example.org_combined_log combined
    LogLevel warn

    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteCond %{ENV:HTTPS} !=on
    RewriteRule .* https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
</VirtualHost>
```
Now, we create a new TLS `VirtualHost` that contains the stuff from
the previous section and some extra TLS configuration options.

```
<VirtualHost www.example.org:443>
    ServerName www.example.org

    ErrorLog logs/www.example.org_ssl_error_log
    TransferLog logs/www.example.org_ssl_access_log
    CustomLog logs/www.example.org_ssl_combined_log combined
    LogLevel warn

    DocumentRoot /usr/share/php-my-app/web

    SSLEngine on
    SSLCertificateFile /etc/pki/tls/certs/www.example.org.crt
    #SSLCertificateChainFile /etc/pki/tls/certs/www.example.org-chain.crt
    SSLCertificateKeyFile /etc/pki/tls/private/www.example.org.key

    SSLProtocol             all -SSLv3 -TLSv1
    SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
    SSLHonorCipherOrder     on
    SSLCompression          off

    # OCSP Stapling, only in httpd 2.3.3 and later
    SSLUseStapling          on
    SSLStaplingResponderTimeout 5
    SSLStaplingReturnResponderErrors off

    # HSTS (mod_headers is required) (15768000 seconds = 6 months)
    Header always set Strict-Transport-Security "max-age=15768000"

    <Directory "/usr/share/php-my-app/web">
        Options -MultiViews

        Require all granted
        AllowOverride none
    </Directory>

    # Pass through the "Authorization" header
    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1

    # Some request are handled by Apache directly
    ProxyPass      "/css/" !
    ProxyPass      "/img/" !
    ProxyPass      "/js/" !
    ProxyPassMatch "^/robots.txt$" !
    ProxyPassMatch "^/favicon.ico$" !

    # The rest goes to PHP-FPM...
    ProxyPass      "/" fcgi://[::1]:9000/usr/share/php-my-app/web/index.php/
</VirtualHost>
```

Now we still need to generate the key and certificate and optionally have them
signed by some CA. The following commands make this very easy:

```
# Generate the private key
$ sudo openssl genrsa -out /etc/pki/tls/private/www.example.org.key 2048
$ sudo chmod 600 /etc/pki/tls/private/www.example.org.key

# Create the CSR (optionally, send this to CA to have signed)
$ sudo openssl req -subj "/CN=www.example.org" -sha256 -new -key /etc/pki/tls/private/www.example.org.key -out www.example.org.csr

# Create the (self signed) certificate and install it
$ sudo openssl req -subj "/CN=www.example.org" -sha256 -new -x509 -key /etc/pki/tls/private/www.example.org.key -out /etc/pki/tls/certs/www.example.org.crt
```

If you want to have the certificate signed by a CA, use the CSR generated 
above and send it to the CA. Once you get a certificate back, overwrite the 
self signed certificate in `/etc/pki/tls/certs/www.example.org.crt` 
and make sure to also configure the `SSLCertificateChainFile`.

Next you can just place the two `VirtualHost` sections above in one 
file, put it in `/etc/httpd/conf.d/www.example.org.conf` and enable 
and start Apache.

```
$ sudo systemctl enable httpd
$ sudo systemctl start httpd
```

### Unanswered Questions

This stuff is so complex that there are still some issues that I do not know
how to solve. Hopefully this list will become smaller over time.

- The URL passed to PHP-FPM is still "URL encoded", for example `%20` is not 
  converted back to `SPACE`;
- Should we actually have the `RewriteCond` rules in the HTTP `VirtualHost` 
  section? It MUST always be rewritten?

### Resources

These resources are a MUST. Make sure to take note of everything that is 
mentioned there! Do not trust what I write here without thinking for yourself 
and making sure you understand everything.

- [PHP: The Right Way](http://www.phptherightway.com/#servers_and_deployment) (Servers and Deployment)
- [Mozilla SSL Configuration Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/)
- [SSL Server Test](https://www.ssllabs.com/ssltest/) (Qualys)
- [SSL Decoder](https://ssldecoder.org/) (Remy van Elst)  
- [Earlier blog post on HTTPS](https.html)

A  => posts/as_discovery.md +74 -0
@@ 1,74 @@
---
title: OAuth 2.0 Authorization Server Discovery
published: 2015-07-31
---
    
### Introduction
    
Currently an OAuth client is supposed to know the Authorization Server (AS) 
that is used by a particular Resource Server (RS). This blog post proposes a
discovery mechanism where the client only needs to know the location of the RS.
    
The RS chooses the AS it will use. There is no point for the client to know 
what the AS is beforehand. It can also change at any point at the 
RS's discretion. As far as I know there is no standardized way of doing this.
    
### Proposal
    
Currently, the `WWW-Authenticate` header is sent back by the RS
if no `Authorization` header is provided by the client or if it uses
an invalid or expired token. If the client sends an "unauthenticated" request,
i.e. without the `Authorization` header, like shown below: 

```
POST /endpoint HTTP/1.1
Host: rs.example.org
Content-Type: application/x-www-form-urlencoded

foo=bar
```

In most cases this will not succeed, although in some it might if out-of-band
authorization was already establish, e.g. using Kerberos in enterprise 
networks. However, in most scenarios this request will not be allowed. 
Currently the response would be like this:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer 
    realm="Phubble"
```

Now, the OAuth Bearer specification provides for specifying the OAuth scope 
required for performing a certain action, e.g. the scope 
`https://micropub.net/scope#create` is required to be able to 
perform the operation. This field is then included in the 
`WWW-Authenticate` header to tell the client which scope to
request:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer 
    realm="Phubble",
    scope="https://micropub.net/scope#create"
```

Now, for discovery of the AS I propose two new key-value pairs: 
`authorization_endpoint` and `token_endpoint`. This will
allow the client to select the correct AS:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer 
    realm="Phubble",
    scope="https://micropub.net/scope#create",
    authorization_endpoint="https://as.example.org/authorize",
    token_endpoint="https://as.example.org/token"
```

This proposal thus solves two issues: the discovery of the AS and the discovery
of the required scope for a particular action on the RS's endpoint.

### References

- [RFC 6750 "The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750)

A  => posts/group_communication_platform.md +142 -0
@@ 1,142 @@
---
title: Group Communication Platform
published: 2015-08-11
modified: 2015-08-12
---
    
**Update (2015-08-12)**: see 
[private-messaging-brainstorming](https://indiewebcamp.com/private-messaging-brainstorming) 
for a discussion on the topic of private messaging. It is in a way quite similar to 
the proposal below of "Inbox", but without OAuth.

### Introduction

This post will describe some typical features for a group communication 
platform with code name *Phubble*. As an example we will organize a 
birthday party for Alice. This post will describe what would be required of 
a "platform" to make it work for the IndieWeb.

### Alice's birthday party

Alice has a birthday in the near future. Bob, Eve and Mallory want to create a 
surprise party for Alice. Until now they used "Facebook Groups" to organize 
such an event among the friends.

Bob wants to coordinate the party with Eve and Mallory and have a secure way
of communicating among themselves without Alice, or anyone else finding out 
about it.

The group of friends contains three members:

- `https://bob.example/`
- `https://eve.example/`
- `https://mallory.example/`

Bob creates a private space `alice-bday-party` on 
*Phubble* and assigns the members to it.

*Phubble* will now have to figure out how to contact/notify the members 
by some mechanism to notify them they have been added to the private group. 
For example using an HTTP *inbox* or maybe email if an HTTP 
*inbox* is not listed on the member's homepage.

Bob then posts his first idea to the wall. Eve and Mallory will receive a
another notification saying that Bob posted a new message, possibly with the 
content of the message included, or maybe just a link.

Eve wants to add Peggy to the group. She adds her to the group configured in 
*Phubble* with the identity `https://peggy.example/`. It
should be possible for all members of a space to add new members. Only the 
creator can delete members.

*Phubble* sends Peggy a notification that she was added to the 
`alice-bday-party` space, and will also inform her of any future
activity. 

Peggy also has an idea and posts it to the space. Now Bob, Eve and Mallory will 
get a notification.

### Notifications

In order to notify a member (out of the blue) that he or she is a member of a 
(private) group space there needs to be a mechanism for doing this. Email has
long been the most reliable way to do this. Most users will publish their 
email address on their homepage, for example using the 
[h-card](https://en.wikipedia.org/wiki/HCard) microformat as 
promoted for the IndieWeb or the 
[rel="me"](http://microformats.org/wiki/relme) method. In 
addition below a mechanism using HTTP is proposed, HTTP *inbox*.

#### HTTP *inbox*

The user advertises a HTTP *inbox* on their homepage:

```
    <link rel="inbox" href="https://tuxed.net/inbox">
```

This endpoint accepts a HTTP POST containing a subject and a message:

```
POST /inbox HTTP/1.1
Host: tuxed.net
Content-Type: application/x-www-form-urlencoded

subject=New+message+in+%22Alice%27s+birthday%22+space&content=Lorem+ipsum+dolor+sit.
```

This request needs an OAuth 2.0 Bearer token to succeed. If non provided, the 
`inbox` endpoint will respond with some hints, as proposed by 
[OAuth 2.0 Authorization Server Discovery](as_discovery.html).

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer 
    realm="Inbox",
    authorization_endpoint="https://as.example.org/authorize",
    token_endpoint="https://as.example.org/token"
```

Now *Phubble*, as an OAuth client, knows where to obtain authorization. 
*Phubble* chooses its own "authorization server", e.g. one that supports 
client certificates, and uses its own URL, e.g. 
`https://phubble.example/` as its identity. Assuming the AS supports 
[Distributed IndieAuth](https://indiewebcamp.com/distributed-indieauth)
this should work perfectly well.

Once the access token has been obtained it can be sent in the POST request:

```
POST /inbox HTTP/1.1
Host: tuxed.net
Authorization: Bearer SFmrZYeCR9hCol2ORAusJbccHiHrp7MU
Content-Type: application/x-www-form-urlencoded

subject=New+message+in+%22Alice%27s+birthday%22+space&content=Lorem+ipsum+dolor+sit.
```

Now the response will show it succeeded:

```
    HTTP/1.1 201 Created
```

#### Email

Email is a safe fallback. The user's email address can be discovered from the 
homepage, for example by querying the `rel="me"` `link`
headers:

```
    <link rel="me" href="mailto:fkooman@tuxed.net">
```

This will be all that is needed to send notifications.

### Issues

- How does *Phubble* keep track of new users in the ACL that need to be 
notified that they are now member of the space?
- How do we deal with disabling notifications? Opt-in? Opt-out?
- Should we implement a distinction between "invited" and "member"? So only 
when people accept a membership they will start receiving notifications?

A  => posts/https.md +243 -0
@@ 1,243 @@
---
title: HTTPS
published: 2015-07-21
modified: 2015-07-21
---

**Update** (2015-07-21): fix the link to the Apache configuration file.
    
This document will not explain why to use HTTPS for your site, but assume you
are already convinced :-)
  
### Certificate Authority
    
So if you want to do HTTPS everyone focuses only on the certificate and the 
costs, but the costs seem to be going down or reach zero in some cases, 
although that can be treacherous in some cases, like StartSSL. Personally 
I've used [https://namecheap.com](https://namecheap.com), because at 
the time they were one of the cheaper ones that supported SHA-256 for signing 
the certificates. I do not recommend to use 
[StartSSL](https://startssl.com) because they have a ridiculous 
policy regarding revoking certificates. You have to pay to revoke. This is SO 
bad, and considering you may require revocation at some point because someone 
compromised your server... not a great prospect. You can also use [CAcert](http://cacert.org) (not recommended), or wait for [Let's Encrypt](https://letsencrypt.org/).
    

### Generating the signing request
    
Many tutorials only tell you to do the wrong thing. Either they are old, or 
obsolete or do crazy things. It is important to generate the private key on a 
physical device, e.g. your laptop, as virtual machines potentially have bad 
random due to the lack of entropy.
    
Below is the procedure I used for [IndieCert](https://indiecert.net). 
To generate the private key:
   
```
$ openssl genrsa -out indiecert.net.key 2048
```
    
Create a file `indiecert.net.cnf` containing the following:
    
```
[req]
prompt = no
distinguished_name = distinguished_name

[distinguished_name]
CN = www.indiecert.net

[v3_req]
subjectAltName = DNS:www.indiecert.net, DNS:indiecert.net
```
 
Now generate the CSR:
    
```
$ openssl req -sha256 -new -reqexts v3_req -config indiecert.net.cnf -key indiecert.net.key -out indiecert.net.csr
```
    
Because OpenSSL (and the config) is so tricky to get right, also here the output 
of the CSR in "human readable" form:

```
$ openssl req -in indiecert.net.csr -noout -text
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: CN=www.indiecert.net
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: <strong>(2048 bit)</strong>
                Modulus:
                    00:b8:fa:6b:12:e8:50:c8:22:db:ea:2e:1a:99:dc:
                    8d:45:ff:89:ac:c8:1d:6d:02:25:ff:17:fa:4b:67:
                    00:28:39:16:82:12:e1:82:52:ae:06:1b:2a:6f:2f:
                    af:bd:a5:41:46:91:86:81:67:02:50:fc:f8:44:a7:
                    67:66:e2:69:48:08:e1:25:8a:2d:c0:b1:8e:b7:05:
                    f3:7f:ab:68:0e:46:41:5a:f3:e2:dd:c8:60:70:c4:
                    9a:4b:e7:34:1b:8c:07:5d:da:72:42:1a:ee:8e:4b:
                    ce:ec:da:6e:3e:b7:b2:b9:d2:41:78:09:ad:4d:3a:
                    8a:ab:51:ec:32:9d:7b:ba:c5:3d:81:c4:11:78:8c:
                    e4:04:ef:67:24:88:f2:28:33:c8:71:1c:e2:c6:f2:
                    38:2e:57:6c:94:6f:f8:a9:fd:4d:4a:67:29:d9:2e:
                    3c:7e:11:1a:cf:39:d2:e2:89:11:38:6a:09:10:36:
                    8c:93:04:28:79:f7:a7:f4:5c:8f:f3:2e:2c:0a:a5:
                    90:74:cb:63:4a:c8:d9:d2:1d:ab:4b:6a:1e:eb:f1:
                    8e:85:f4:5b:90:1c:51:d5:df:b1:82:6c:b2:a6:d0:
                    7e:01:0b:44:ec:96:3e:2d:0f:6e:87:21:2d:70:26:
                    b6:3a:f5:81:e4:a8:2d:b4:ca:8a:d3:29:ad:0f:c3:
                    9d:49
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name: 
                DNS:www.indiecert.net, DNS:indiecert.net
    Signature Algorithm: <strong>sha256WithRSAEncryption</strong>
         52:f7:9d:14:b4:43:de:52:0b:6f:aa:ff:7a:32:cf:ca:5e:6c:
         09:94:32:02:77:8c:ed:03:07:6e:e6:d4:a8:12:74:21:fb:bc:
         a8:e5:ac:c4:af:6a:df:86:c0:05:07:3c:9e:53:de:ab:bb:37:
         55:2a:f3:f8:1d:fe:6e:92:21:44:bb:3e:c4:a9:fe:a4:4d:f4:
         68:1d:6b:fe:59:ea:95:d6:4f:2b:9f:cc:f9:0d:a2:7e:e0:96:
         8d:32:8b:1c:39:d4:b6:b2:6e:70:98:b2:c1:da:df:5f:72:e2:
         50:0a:54:08:05:f7:82:23:8f:89:4f:94:c4:0c:a1:7b:33:cc:
         ed:0f:5d:87:ed:98:64:e7:b2:ef:1f:12:08:6c:8a:6e:dc:d2:
         85:f9:77:ec:77:ce:53:63:a7:21:37:21:53:51:cb:7e:a8:d3:
         a5:e6:43:e2:96:de:10:83:e4:8a:8a:05:1d:5f:65:31:8d:d1:
         8c:8d:2f:9e:04:1c:9e:d5:c9:88:40:eb:7d:7d:34:8d:43:37:
         71:d9:fd:45:34:4a:b2:c2:80:0f:85:2d:ed:5c:0d:5d:ef:ae:
         3b:94:ea:3a:ea:3b:ad:f3:90:46:6e:a6:4a:d6:c7:57:36:3a:
         c2:71:ef:f7:d8:8d:cc:16:c1:2f:6f:ca:3f:bb:e0:2d:73:bc:
         04:59:89:07
```
    
It is important to make sure you have at least an 2048 bits public key, and
that SHA-256 was used for the signature. This will in most cases trigger the 
CA to also use SHA-256 if they still support SHA-1 as well.

Most CAs will override the CN and the Subject Alternative Name in most cases,
but it doesn't hurt to get it right yourself :-)

## CA procedure
    
The CA will now take this CSR and sign it and send you a signed certificate and
also in most cases a certificate chain:

```
$ openssl x509 -inform PEM -in www_indiecert_net.crt  -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            f1:ce:c2:0e:e7:b4:2f:d8:c4:a5:78:c5:e9:f5:4b:07
    Signature Algorithm: <strong>sha256WithRSAEncryption</strong>
        Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO RSA Domain Validation Secure Server CA
        Validity
            Not Before: Feb 18 00:00:00 2015 GMT
            Not After : Feb 18 23:59:59 2016 GMT
        Subject: <strong>OU=Domain Control Validated, OU=PositiveSSL, CN=www.indiecert.net</strong>
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:e1:4c:bd:f2:03:cb:cd:d9:33:b4:56:c4:a3:52:
                    2d:47:4e:1a:df:5a:8b:9e:75:01:51:29:9a:37:83:
                    63:d5:44:b4:6d:fa:b2:c1:a4:97:76:44:b1:f3:e6:
                    96:8f:40:40:85:fe:04:f6:04:65:ae:8d:e1:79:60:
                    32:eb:21:6f:8b:9c:85:2d:d9:38:aa:ea:7c:50:d0:
                    fd:25:29:a3:16:ef:c5:d1:ae:bc:0f:7d:82:41:8e:
                    cb:df:d2:da:41:4d:fd:2e:4c:4c:7f:32:aa:7a:10:
                    aa:73:99:21:f3:e1:a1:14:7b:5a:ca:f9:69:87:b1:
                    35:6f:86:56:6a:54:57:1d:8b:fd:1f:7a:56:d3:44:
                    67:54:99:8d:8c:70:2c:ba:4c:00:ff:6b:a4:0b:bf:
                    0e:c9:dc:b9:ea:bb:0c:9e:a5:02:b2:c9:34:4e:e2:
                    34:be:7f:e5:a5:e5:ed:d0:97:7f:6c:c0:aa:a9:b8:
                    24:76:78:12:49:e5:a5:f8:08:71:3f:55:d4:21:04:
                    7c:c0:5c:31:20:87:29:5e:a1:bd:b1:7d:63:e9:3f:
                    0e:f2:a8:fb:1f:d8:e8:51:0f:89:84:dc:5d:da:7a:
                    69:a5:cd:48:ba:39:63:d8:ae:39:29:cd:a7:8f:94:
                    06:9a:7f:da:c7:b6:f4:71:a1:58:03:ef:10:b4:22:
                    a1:7f
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier: 
                keyid:90:AF:6A:3A:94:5A:0B:D8:90:EA:12:56:73:DF:43:B4:3A:28:DA:E7

            X509v3 Subject Key Identifier: 
                FD:00:76:8D:04:A1:3E:B1:41:2B:49:8A:D1:CD:93:89:32:3C:38:B5
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Certificate Policies: 
                Policy: 1.3.6.1.4.1.6449.1.2.2.7
                  CPS: https://secure.comodo.com/CPS
                Policy: 2.23.140.1.2.1

            X509v3 CRL Distribution Points: 

                Full Name:
                  URI:http://crl.comodoca.com/COMODORSADomainValidationSecureServerCA.crl

            Authority Information Access: 
                CA Issuers - URI:http://crt.comodoca.com/COMODORSADomainValidationSecureServerCA.crt
                OCSP - URI:http://ocsp.comodoca.com

            X509v3 Subject Alternative Name: 
                <strong>DNS:www.indiecert.net, DNS:indiecert.net</strong>
    Signature Algorithm: <strong>sha256WithRSAEncryption</strong>
         69:c1:22:36:c1:2b:5d:43:34:c0:d7:a6:06:03:53:02:f4:85:
         ee:29:72:c2:82:37:56:af:ba:f8:1e:c9:2c:bf:da:fb:38:47:
         43:8d:c1:d3:94:48:b3:49:41:1c:f5:89:7c:97:23:88:0a:b3:
         cb:47:28:13:a2:a7:d2:d2:3c:40:5b:1b:8b:98:ae:70:4c:ea:
         67:77:e1:b8:d4:de:c7:0e:fd:09:ff:56:72:a8:30:eb:0d:0a:
         87:fe:2c:3f:9d:2e:7a:e3:de:47:22:79:dd:2a:58:da:38:78:
         14:2b:70:95:ee:8b:ce:9c:78:b0:ce:a7:cb:27:dd:98:36:f8:
         b4:f8:4c:44:35:b9:9d:d4:8c:cc:5b:c6:48:6e:25:12:e3:ce:
         9e:40:c7:c4:b9:d1:23:6b:93:83:e2:4e:29:7e:10:1a:31:72:
         d0:a0:24:97:3d:ea:b1:89:27:0b:49:0c:33:c7:ff:f2:e9:cb:
         4b:fe:a7:0a:10:c3:11:65:dc:f0:4a:07:32:63:d4:73:d5:30:
         77:9d:f4:fc:d3:51:04:11:51:af:8d:f6:37:d1:de:61:3c:74:
         5d:6a:64:f0:c6:99:45:21:1e:44:1c:01:61:99:3e:c1:a7:e4:
         a0:d1:39:f0:56:33:e6:7b:db:6d:22:73:c4:7f:d0:22:2e:54:
         93:0e:59:e4
```

### Installation
    
Now you can use the certificate, the key and the certificate chain and 
configure them in your web server. If you run your own server you SHOULD use
Mozilla's [configuration generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/) to make sure you configure your server in a secure way. 
    
If you use some virtual hosting provider, like e.g. [Uberspace.de](https://uberspace.de) you can probably upload your key, certificate and chain using SSH and instruct them to configure the certificate for you.

### Validation
    
This is where most people stop. They will never validate their configuration
and make sure SSLv3 is disabled, the chain is configured properly or the 
weak ciphers are disabled. 
        
Go to [SSL Server Test](https://www.ssllabs.com/ssltest/) provided
by Qualys. Enter your domain name and check the results. If you do not
get a rating A or A+ you are doing something wrong and should evaluate the 
results of the test. As an example, you can view the IndieCert [report](https://www.ssllabs.com/ssltest/analyze.html?d=indiecert.net&hideResults=on).
    
If you prefer a free software solution you can also look at [SSL Decoder](https://tls.so/). But it is advisable to also check using the Qualys tool mentioned above.

### Next Steps
    
Now that the basics are done, you should not stop here, but consider a few 
other things:
    
- [HTTP Strict Transport Security](https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security)
- [OCSP Stapling](https://wiki.mozilla.org/Security/Server_Side_TLS#OCSP_Stapling)
- [Add to HSTS preload list](https://hstspreload.appspot.com/)
- [Public Key Pinning](https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning)

### What did I do so far?

For IndieCert I followed most of these steps, but didn't get around to 
implementing Public Key Pinning yet. You can check the Apache configuration I 
use [here](https://github.com/fkooman/indiecert/blob/master/docker/indiecert.example-httpd.conf).

A  => posts/indiecert.md +265 -0
@@ 1,265 @@
---
title: Introducing IndieCert
published: 2015-02-02
modified: 2015-03-08
---

**More feedback is required before this document can be considered finished. See "Issues" section below.**
    
Authenticating to web servers with a client certificate, installed in the 
user's browser, is unfortunately not widely used. They are convenient and safe
to use once the initial hurdle of installing them is taken.
    
There is now a Proof of Concept instance! Check it out [here](https://indiecert.net)!
    
The main benefit is easy and secure authentication. There is no need to provide 
a password to any service, only the URL to your home page. So there is no 
chance you will leak your password to some service.
    
<video controls="controls" width="575">
    <source src="https://storage.tuxed.net/fkooman/public/upload/blog/indiecert_auth_flow.webm" type="video/webm">
    <source src="https://storage.tuxed.net/fkooman/public/upload/blog/indiecert_auth_flow.mp4" type="video/mp4">
</video>

In addition, this proposal describes a way to make the use of client certificates 
feasible:
    
1. A self-signed CA is used instead of a 'browser trusted' CA to issue the client certificates, this works without browser warnings;
2. Users authorize the certificates by publishing the certificate fingerprint on their home page allowing to claim ownership of a URL and by authenticating with the certificate directly;
3. Allow identity (certificate) linking using the user's home page address to allow registration of multiple fingerprints to support multiple devices and browsers.
 
A possible drawback is that many users do not have a home page anymore. They 
have a Facebook profile or a Twitter account, but no home page. However, this
is not relevant for [IndieWeb](https://indiewebcamp.com/) as all
users should have their own home page running on their own domain anyway :)
       
As a fallback it is possible to allow authenticating using existing social 
networks, similar to [IndieAuth](https://indieauth.com), instead 
of using the client certificates. Some browsers and operating systems 
unfortunately do not support easy client certificate enrollment.

<img src="https://storage.tuxed.net/fkooman/public/upload/blog/keep-calm-and-use-certificates.png" width="300" height="350" alt="keep calm and use certificates">

### Protocol for Relying Parties
    
The protocol follows the protocol proposed for IndieAuth. The protocol is 
based on the IndieAuth protocol and aims to be compatible with it.
    
#### Request Authentication
    
The service redirects the user to `https://indiecert.net/auth` to
start the authentication phase. Two parameters need to be specified:

- **redirect_uri**:
  - the URL the browser should be redirected back to after the authentication is successful. This MUST be a valid HTTPS URL;
- **me**:
  - the URL to the user's home page (see [Retrieving the User's Home Page](#retrieve_home_page));

##### Example
Below is an example of a browser redirect. The process can also be initiated 
with a `<form>` submit using the `GET` method.

```
HTTP/1.1 302 Found
Location: https://indiecert.net/auth?redirect_uri=https://example.org/callback&amp;me=https://tuxed.net/fkooman
```

IndieCert will at this point take care of the authentication and optional 
enrollment process if that was not already done.

#### Authentication Response
    
IndieCert will redirect the browser back to the `redirect_uri` 
specified in the authentication request after the user is authenticated at 
IndieCert.
    
##### Example

```
HTTP/1.1 302 Found
Location: https://example.org/callback?code=wW3OLXJZn35d7zFwg9YGmWti
```

#### Verification Request
    
The `code` parameter can now be used to request the claimed user 
identity. The following parameters are required:
    
<dl>
    <dt>`code`</dt>
    <dd>the code obtained in the authentication response query parameter `code`.</dd>
    <dt>`redirect_uri`</dt>
    <dd>the URL the browser was redirected back to after the successful authentication;</dd>
</dl>

Now a HTTP `POST` can be used to obtain the user's (normalized) home 
page URL:
    
```
POST /auth HTTP/1.1
Host: indiecert.net
Content-Type: application/x-www-form-urlencoded
Accept: application/json

redirect_uri=https%3A%2F%2Fexample.org%2Fcallback&amp;code=wW3OLXJZn35d7zFwg9YGmWti
```
    
The response will be formatted as JSON indicating the actual user home page in
the `me` parameter.
    
This is the identity that MUST be used by the relying party to identify the 
user as it could differ from the initial `me` specified by the user,
e.g.: redirects were followed to reach the user's home page.

```
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
    "me": "https://www.tuxed.net/fkooman/"
}
```
    
In case there was a failure in verifying the code, e.g. it was already used, or
not valid the following response can be expected:
    

```
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
    "error":"invalid_request"
}
```

### Protocol for the IndieCert Service
    
The IndieCert service has to deal with certificates, issuing, installing and
verifying them. The most convenient method is to use the HTML 
`<keygen>` tag. This makes the browser generate a private key
and certificate request (SPKAC) that is then sent to the service for signing. 
The service will sign this with the CA generated for this particular IndieCert
instance. The signed certificate is sent back to the client and (automatically)
imported in the browser certificate store. 
    
After this, the certificate can be used to authenticate to the IndieCert 
service, but it still needs to be linked to the URL of the user's home page. 
This can be done by using the `rel="me"` attribute in a 
`<link>` header tag or `<a>` body tag.

```
<link rel="me" href="ni://indiecert.net/sha-256;WXXyBZDo1pZYiLbrCNGFtSdSamqEvSJJBRzpx-MNIUA?ct=application/x-x509-user-cert">
```

Once the user has placed this HTML element on their home page the URL it will 
be found when the IndieCert instance will fetch the URL and extract all 
`<link>` and `<a>` tags to look for the 
`rel="me"` attribute. The fingerprint of the client certificate 
used to authenticate to IndieCert needs to be listed on the home page. 
After that, the URL of the home page will be used as an accepted identifier.

<video controls="controls" width="575">
    <source src="https://storage.tuxed.net/fkooman/public/upload/blog/indiecert_enroll.webm" type="video/webm">
    <source src="https://storage.tuxed.net/fkooman/public/upload/blog/indiecert_enroll.mp4" type="video/mp4">
</video>

#### Retrieving the User's Home Page
    
The user's home page MUST be fetched over HTTPS only, MUST follow redirects and 
MUST NOT have any HTTP URLs in its redirect path. If the URL does not start 
with `https://` it MUST be added by IndieCert. If a URL starts with
`http://` or is otherwise invalid it MUST be rejected. The final 
URL, the URL that returns a `200 OK` status, MUST be used in the 
response to the verification request, this is the normalized home page URL.
    
For example the user provides `tuxed.net/fkooman`. IndieCert makes
this `https://tuxed.net/fkooman` and starts the fetching process. It
is then redirected to `https://www.tuxed.net/fkooman` and then to
`https://www.tuxed.net/fkooman/`. The claimed identity thus becomes
`https://www.tuxed.net/fkooman/` and this value is returned in 
response to the verification request.
    
### Identity Linking
    
One of the benefits of publishing fingerprints on the user's home page is that 
additional client certificate fingerprints can easily be added to the home page
to allow those certificates to claim the same identity.

```
<!-- laptop -->
<link rel="me" href="ni://indiecert.net/sha-256;WXXyBZDo1pZYiLbrCNGFtSdSamqEvSJJBRzpx-MNIUA?ct=application/x-x509-user-cert">

<!-- phone -->
<link rel="me" href="ni://indiecert.net/sha-256;ThIaJ7TJQ1oAIKCGKe0BdBO3Bh8NzxZeyAa-WCTuzpU?ct=application/x-x509-user-cert">
```

Certificate revocation is done by removing the entry from the home page and it 
can no longer be used to claim the identity.
    
### Fingerprint Generation
    
The fingerprint of the certificate is generated by calculating the SHA-256 hash
over the DER encoded certificate and base64url encoding the resulting binary 
string according to RFC 6920 "Naming Things with Hashes" and  RFC 4648 
"The Base16, Base32, and Base64 Data Encodings". An example of calculating a
fingerprint in PHP:

```
<?php
$string = 'Hello World!';
echo rtrim(strtr(base64_encode(hash('SHA256', $string, true)), '+/', '-_'),'=');
```

### Distributed IndieCert
    
One of the important issues to solve is how to make this protocol distributed,
i.e.: how to allow multiple instances of IndieCert to be used by different 
services whilst allowing a smooth user experience.
 
The relying party can choose to join an existing IndieCert instance, or run 
their own if they don't trust any of the existing instances. The benefit of
running their own is better control and more trust and no requirement on third
parties for the authentication to their service.
    
Assuming they want to run their own instance that would require the user to
generate a new certificate and put the fingerprint on their home page as well. 
This results in additional overhead, for the user, but may be worth it from a
security perspective. And the process needs to be repeated once per IndieCert 
instance, per device or even per browser. This the greatest weakness of this 
proposal, that and adoption. If everyone runs their own IndieCert instance it 
will quickly become a management nightmare for the user if they have to keep 
track of all certificates in all their browsers on all their devices. This 
could be solved by a tool allowing users to easily add fingerprints to their 
website.
    
For the actual user experience, the user just selects the specific certificate 
for the specific IndieCert instance (it can be pre-selected by most browsers 
based on the CA) so that should not be a problem.
    
### Issues
    
There are a number of issues we have to solve before this document can be
considered stable:
     
- CSRF. User X can generate a code for his identity and trick user Y into
  going to a relying party with user X's code to work under user X's identity, 
  possibly storing private data under user X's identity instead of user Y's. We
  need to implement some kind of state generating/checking;
- Rate limiting on the verify endpoint;  
- Many browsers, many devices, many instances of IndieCert is a scalability nightmare for the user...
    
### References

- [IndieAuth](https://indieauth.com)
- [IndieWebCamp](https://indiewebcamp.com)
- [RelMeAuth](http://microformats.org/wiki/RelMeAuth)
- [Certificate Download Specification](https://wiki.mozilla.org/CA:Certificate_Download_Specification)
- [Keygen Tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen)
- [Naming Things with Hashes](http://tools.ietf.org/html/rfc6920) (RFC 6920)
- [The Base16, Base32, and Base64 Data Encodings](http://tools.ietf.org/html/rfc4648) (RFC 4648)
- [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749) (RFC 6749)

A  => posts/indiecert_nitrokey.md +118 -0
@@ 1,118 @@
---
title: IndieCert and Nitrokey
published: 2015-04-07
---
    
Finally I managed to get het Nitrokey working with IndieCert. It is not as 
smooth as expected and requires a fair bit of work, but here you can find the
steps required.
    
### Requirements
    
The documentation for Nitrokey seems scattered or lacking a bit. Below I will
describe what to do on the latest [Fedora](https://getfedora.org) 
(21) release.
    
#### PCSC
    
You need to install two packages to get started and recognize the Nitrokey:

```
$ sudo yum -y install opensc.x86_64 pcsc-lite.x86_64
```
 
Now you can make the PCSC daemon start on system boot

```
$ sudo systemctl enable pcscd.service
```
    
`pcscd` is *socket activated*, so no need to start it, it
will be activated when you plug in the Nitrokey. If you already plugged in the
stick remove it and plug it in again...
    
To check if everything is working use `openpgp-tool`:

```
$ openpgp-tool 
Using reader with a card: German Privacy Foundation Crypto Stick v2.0 (0000000000000) 00 00
Language:  de
Gender:    not applicable
$ 
```
 
This should be all!

#### Firefox
    
Next you need to enable the OpenSC `PKCS#11` driver in Firefox. 
The library to load is located at `/usr/lib64/opensc-pkcs11.so`. In
Firefox go to "Preferences" -&gt; "Advanced" -&gt; "Certificates" -&gt; 
"Security Devices" -&gt; "Load", and then enter this path in the 
"Module filename" box.
    
That should be all for Firefox!
    
### Approach
    
It doesn't seem possible to generate a self signed certificate on the Nitrokey,
it is possible to generate a private and public key on the device, and then
hook it up to OpenSSL somehow to generate a CSR, but I'm not sure if it is 
possible at that time to immediately generate a self signed certificate.
    
So, the next obvious choice would be to use the normal IndieCert flow and 
generate a certificate in the browser and export that. This is really not a 
good idea, but it seems the only thing possible right now.
    
So in order to do that, go to 
[https://indiecert.net/](https://indiecert.net/) and follow the 
normal flow to enroll. Once enrollment is done and the certificate is 
stored in the browser export it to a `PKCS#12` file. This can then
on the command line be imported in the stick.
    
You can export the certificate and private key by going to "Preferences" -&gt; 
"Advanced" -&gt; "Certificates" -&gt; "View Certificates" -&gt; 
"Your Certificates". Select the one generated by IndieCert and click 
"Backup...". Firefox will ask for a file path, I used 
`indiecert.p12` and a password, remember this password for later to
import the `PKSC#12` file in the Nitrokey.
      
We assume you exported the certificate to `indiecert.p12`. The 
default "Admin PIN" is `12345678`. The default "User PIN" is 
`123456`. Now import it in the key:

```
pkcs15-init --delete-objects privkey,pubkey --id 3 --store-private-key indiecert.p12 --format pkcs12 --auth-id 3 --verify-pin
```
    
This is the output, you will also be asked to enter both the "Admin PIN"
of the Nitrokey, and the password you provided when exporting the 
`PKCS#12` file in Firefox.

```
Using reader with a card: German Privacy Foundation Crypto Stick v2.0 (0000000000000) 00 00
User PIN required.
Please enter User PIN [Admin PIN]: 
Deleted 2 objects
error:23076071:PKCS12 routines:PKCS12_parse:mac verify failure
Please enter passphrase to unlock secret key: 
Importing 1 certificates:
  0: /CN=4fad073b801ab6bf0bc21efc0092c625
```
 
This now makes it possible to use it in Firefox!

<a href="https://storage.tuxed.net/fkooman/public/upload/blog/nitrokey_firefox_big.png">
    <img src="https://storage.tuxed.net/fkooman/public/upload/blog/nitrokey_firefox_small.png" width="575" height="323">
</a>

### Thanks

Special thanks to [elf Pavlik](https://wwelves.org/perpetual-tripper/) for the 
motivation and [@gamamb](https://twitter.com/gamamb) for providing the Nitrokey 
for testing!

### References

- [Nitrokey](https://nitrokey.com/) 
- [IndieCert](https://indiecert.net/)

A  => posts/nm_12_openvpn.md +50 -0
@@ 1,50 @@
---
title: OpenVPN and NetworkManager 1.2
published: 2016-05-15
---

Doing a new round of tests for OpenVPN client support I decided to test how
well Fedora 24 Beta and Ubuntu 16.04 work. They both have NetworkManager 
1.2 which brings a lot of improvements to the OpenVPN plugin, particularly 
when importing configurations. Particularly I was testing the way imports from
[eduvpn](https://github.com/eduvpn), a managed VPN service worked.

It turned out it works pretty well, with a minor issue that is already fixed in 
the development branch of NetworkManager. Ubuntu has some issues with DNS
servers provided over the VPN.

Importing a configuration using NetworkManager 1.2 resulted in a 
small [issue](https://bugzilla.gnome.org/show_bug.cgi?id=739519)
with `comp-lzo` that was fixed the same day, for release in a next 
point release of NetworkManager 1.2. In the case of eduvpn, the server pushed 
`comp-lzo`:

```
comp-lzo no
push "comp-lzo no"
```

The client had the following:

```
comp-lzo no
```

The issue was that OpenVPN import in NetworkManager saw `comp-lzo no` as having 
compression *disabled*, which is only kind of correct: having this option, even
if it is set to `no` allows the server to override it. Even if the
server again overrides it with `no` it still does not work when 
`comp-lzo` is missing: 

```
WARNING: 'comp-lzo' is present in remote config but missing in local config, remote='comp-lzo'
```

Using `comp-lzo yes` in the client configuration allows for the 
import to work correctly and the VPN to work perfectly on Fedora. 

On Ubuntu 
there is an additional issue with DNS, particularly in the part that integrates with `dnsmasq`. It was [reported](https://bugs.launchpad.net/ubuntu/+source/network-manager/+bug/1211110) almost 3 years ago, but hasn't been fixed yet.

The work-around is not difficult, but still cumbersome and requires `root`. Disable `dnsmasq` for 
NetworkManager which is used by default on Ubuntu by modifying `/etc/NetworkManager/NetworkManager.conf`. Add a `#` in front of the `dns=dnsmasq` line. Then restart NetworkManager, or simply reboot the system. That should be all!

A  => posts/owncloud_distributions.md +73 -0
@@ 1,73 @@
---
title: ownCloud and distributions
published: 2016-03-30
---

I want to respond in more detail to the response to my [Tweet](https://twitter.com/ownClouders/status/714797692657000453) to [@ownClouders](https://twitter.com/ownClouders) earlier. I wrote:

> Relevant for the @ownClouders and Debian/Fedora packaging discussion: 
> [http://enricozini.org/blog/2014/debian/debops/](http://enricozini.org/blog/2014/debian/debops/)

This article is written by a software developer who takes Debian as 
a base operating system and develops his software targeting Debian stable. It
brings him many benefit like "free" security updates and bug fixes in
the operating system and the libraries he needs that are already available in
Debian. That way he can focus on just his software and not the dependencies, 
or worry about moving targets.

### Responses

> @fkooman it's great if you have the time to support old platforms... But for 
> a big project, it is a huge investment with minor benefit

The recommended target distribution to install the latest version of ownCloud 
9.0 on [is](https://doc.owncloud.org/server/9.0/admin_manual/installation/system_requirements.html) 
Red Hat Enterprise Linux 7. This distribution is using PHP 5.4, which is still
supported by ownCloud. This is, at least in the PHP world, a very old platform
that is being supported.

About the benefits of distribution packaging: I think the most visible benefit of providing 
official distribution packages would be for the 
community and the users. Only having to do `yum install owncloud` 
and `yum update` to have a working setup without worrying about 
verifying signatures and keeping it up to date is not a minor benefit! But also 
for the developers there are some big benefits:

* Quality: by using the official packaging guidelines and untangling the 
  dependencies in separate packages there is an incentive to use high quality 
  dependencies that can easily be packaged and to limit the number of 
  dependencies. Dependencies are not free even if they only seem a
  `composer require` away;
* Testing: on most PHP packages in Fedora the included test suite in run during 
  build on the exact same version of the components they will later run on 
  ([example](https://apps.fedoraproject.org/packages/php-guzzlehttp-guzzle/sources/spec/));
* Maintenance: by separating the application in its components maintenance 
  becomes easier. When fixing a bug in a dependency it is easier to just update
  the package for the dependency, leaving the rest alone;
* Collaboration: some of the work of packaging can be shared among other 
  packagers who require the same dependencies, many are already packaged. As a 
  result more users than just the ownCloud users can benefit of the work;

To me, these are convincing reasons to package the (web) applications I
develop immediately for Fedora/CentOS. Initially it is a little more work, but 
later on the costs reduce substantially if you only have to worry about your 
application and not the dependencies once they are packaged and reliable.

> @fkooman and sadly, volunteers seem not interested, neither do customers 
> want to pay for it....

Volunteers 
[were](https://www.happyassassin.net/2015/08/29/looking-for-new-maintainer-for-fedora-epel-owncloud-packages/) 
[interested](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816376), but got 
demotivated by the problems they had to deal with.

Given the reasons above for choosing distribution packaging, I think customers
*are* paying for it already, considering it is really QA and software 
hygiene. I'm pretty sure enterprise customers would really like to have 
software they can install on a server and leave alone for a year or longer 
without requiring complicated upgrade processes, while only installing the 
occasional bug fix. Just for this reason I think it is important that the 
developers of the software themselves take on the packaging for the supported 
distributions and follow the platform conventions as much as possible to get
the highest possible quality software, and in the process bring extra value to 
other projects running on the same platform.

A  => posts/proposed_changes_to_indieauth_protocol.md +89 -0
@@ 1,89 @@
---
title: Proposed Changes to IndieAuth Protocol
published: 2015-03-04
modified: 2015-03-06
---

**Update (2015-03-06)**: Aaron Parecki replied to this proposal 
[here](https://aaronparecki.com/articles/2015/03/05/1/re-proposed-changes-to-indieauth-protocol). 
I agree with his reply as it turns out IndieAuth is also used for 
authorization in addition to authentication.

### Introduction

This post proposes a few minimal changes to the IndieAuth protocol as well as 
their rationale. These changes were inspired by creating an alternative but 
mostly compatible IndieAuth implementation called IndieCert, using client 
certificates to authenticate users, or actually browsers. More information 
on IndieCert can be found in this [blog](indiecert.html) post.
    
#### Optional `client_id`
    
Currently the protocol expects the use of the `client_id` 
parameter in the authentication request. This does not seem necessary as it
is never used, just a remnant of OAuth 2.0. The `redirect_uri` is 
enough to establish the relying party's identity. So the proposal is to make
`client_id` support OPTIONAL both for relying party as well as the
IndieAuth services.
    
#### CSRF protection

It is advisable to implement CSRF protection. The `state` parameter
can be used for that, or a new parameter, like `csrf_token`, as 
proposed by "SaferWeb: OAuth2.a or Let's Just Fix It" MUST be implemented.
    
This will protect against an attacker obtaining a `code` and
tricking the user's browser to go to the relying party's callback endpoint, 
thus gaining access to the server on the attacker's behalf and potentially
leaking private data to the attacker's account. 
    
The CSRF token (or state) needs to be saved in a browser session that is 
created when the user tries to login to the relying party. The CSRF token is 
compared to the token specified on the callback URL after the user grants 
permission to the relying party to obtain the user's identity.
    

#### Terminology: authentication instead of authorization
    
IndieAuth is actually used for *authentication* and not
*authorization* as is mentioned on the "Distributed IndieAuth" page.
This should be modified to talk about authentication instead. Also the name of 
the `authorization_endpoint` should be changed to 
`authentication_endpoint`.
    
#### Additional "verify" endpoint
    
The "auth" endpoint in IndieAuth is currently used both for initiating the 
authentication (GET) as well as verifying the authentication code (POST). In 
order to support the option to ask for user consent before the identity of the
user is released, it makes sense to have a separate verification endpoint as 
the POST on the authentication endpoint will be used for a form submit.
    
This is also relevant in the case of "Distributed IndieAuth" where the 
user mentions the `authorization_endpoint`, as a link relation on
their home page. The proposal is to also allow for (optionally) defining a 
`verification_endpoint` link relationship on the user's homepage. 
For example:

```
<link rel="authentication_endpoint" href="https://indiecert.net/auth">
<link rel="verification_endpoint" href="https://indiecert.net/verify">
```

#### Content negotiation for "verify" endpoint
    
Currently the IndieAuth protocol uses a 
`application/x-www-form-urlencoded` formatted *response* 
instead of the currently more popular `application/json` response 
format, e.g. in OAuth 2.0. It is proposed to support "Content Negotiation" by 
checking the HTTP `Accept` header in the verification request and 
returning either `application/x-www-form-urlencoded` or 
`application/json` depending on the `Accept` header. The 
default can be either of those, but both MUST be supported.

### References

- [IndieAuth](https://indieauth.com)
- [IndieCert](https://indiecert.net)
- [Distributed IndieAuth](https://indiewebcamp.com/distributed-indieauth)
- [SaferWeb: OAuth2.a or Let's Just Fix It](http://homakov.blogspot.de/2012/08/saferweb-oauth2a-or-lets-just-fix-it.html)

A  => posts/wireless_router_vpn.md +38 -0
@@ 1,38 @@
---
title: Wireless Routers and VPNs
published: 2015-07-30
---

I've been playing around with [OpenWrt](https://openwrt.org) and 
[Freifunk](http://freifunk.net/). There are special OpenWrt images 
for Freifunk. They are slightly modified vanilla OpenWrt 
images. They include for instance [OpenVPN](https://openvpn.net/). 
The interesting approach here is that all traffic from the access point will be 
routed over the VPN, thus eliminating the liability for the provider of the 
access point: the traffic of the users on the Freifunk access point cannot 
(easily) be linked back to the ISP of the user. Especially in Germany this is
[important](http://www.zdnet.com/article/file-sharing-in-germany-could-the-cost-of-getting-caught-be-about-to-come-down/)
if you want to share your Internet connection.

The Freifunk images also support mesh networking, so it becomes possible to 
connect to Freifunk if your own router can connect to another Freifunk access
point and relay the traffic. This way it is possible to make a mesh, and only
one of the access points in the mesh needs to have an actual Internet 
connection that then can be shared. Brilliant!

I used the 
[TP-Link TL-WR703N](http://wiki.openwrt.org/toh/tp-link/tl-wr703n) 
for Freifunk. It is not "officially" supported, but it was very easy to 
generate an image for it. It is a bit tricky to get working as there is only
one ethernet port on the WR703N which is configured by default as the LAN 
port. It should be configured as the WAN port, perhaps I can modify the 
profile of the Freifunk firmware a bit to make it act as the WAN port by 
default.

### eduroam

This could also have a wider application, for example for [eduroam](https://eduroam.org).
Together with [eduVPN](https://wiki.surfnet.nl/display/eduvpn/eduVPN) it makes for a compelling use case to use 
for instance the WR703N, available for less than $25 to promote eduroam 
everywhere from your home to libraries and cafes around the city. Minimal 
investment, great benefit for students and researchers!

A  => screen.css +39 -0
@@ 1,39 @@
body {
    font-family: sans-serif;
    color: #444;
/*    font-size: 1em;*/
    line-height: 1.6;
    margin: 40px auto;
    max-width: 650px;
    padding: 0 10px;
}

a {
    color: #444;
}

h1, h2, h3 {
    line-height: 1.2;
}

blockquote {
    margin: 0;
    background-color: #eee;
    padding: 0.1em 1em;
}

code, pre {
    font-family: monospace;
    font-size: 14px;
    background-color: #eee;
}

small {
    color: gray;
    font-size: 80%;
}

pre {
    white-space: pre-wrap;
    padding: 1em;
}

A  => views/footer.twig +2 -0
@@ 1,2 @@
</body>
</html>

A  => views/header.twig +11 -0
@@ 1,11 @@
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ blogTitle|e }} - {{ pageTitle|e }}</title>
    <link rel="stylesheet" type="text/css" href="css/screen.css">
</head>
<body>
<h1>{{ blogTitle|e }}</h1>

A  => views/index.twig +18 -0
@@ 1,18 @@
{% include 'header.twig' %}
<p>{{ blogDescription|e }}</p>

<h2>{{ pageTitle|e }}</h2>
<ul>
{% for post in postsList %}
<li>
    [{{post.published|e }}] <a href="{{post.fileName|e }}">{{ post.title|e }}</a>
</li>
{% endfor %}
</ul>

<h3>Author</h3>
<p>
Mail <a href="mailto:{{ blogAuthorMail|e }}">{{ blogAuthor|e }}</a>, or Twitter <a href="https://twitter.com/{{ blogAuthorTwitter|e }}">@{{ blogAuthorTwitter|e }}</a>
</p>

{% include 'footer.twig' %}

A  => views/post.twig +18 -0
@@ 1,18 @@
{% include 'header.twig' %}
<p>&lt; <a href="index.html">Back to Index</a></p>

{% if post.published %}
<small>Published on {{ post.published|e }}{% if post.modified %}, last updated on {{ post.modified|e }}{% endif %}</small>
{% endif %}

<h2>{{ pageTitle|e }}</h2>

{{ post.htmlContent|raw }}

<h3>Author</h3>
<p>
Mail <a href="mailto:{{ blogAuthorMail|e }}">{{ blogAuthor|e }}</a>, or Twitter <a href="https://twitter.com/{{ blogAuthorTwitter|e }}">@{{ blogAuthorTwitter|e }}</a>
</p>

<p>&lt; <a href="index.html">Back to Index</a></p>
{% include 'footer.twig' %}