diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | composer.json | 7 | ||||
-rw-r--r-- | composer.lock | 304 | ||||
-rw-r--r-- | generate.php | 95 | ||||
-rw-r--r-- | posts/apache_php_fpm.md | 204 | ||||
-rw-r--r-- | posts/as_discovery.md | 74 | ||||
-rw-r--r-- | posts/group_communication_platform.md | 142 | ||||
-rw-r--r-- | posts/https.md | 243 | ||||
-rw-r--r-- | posts/indiecert.md | 265 | ||||
-rw-r--r-- | posts/indiecert_nitrokey.md | 118 | ||||
-rw-r--r-- | posts/nm_12_openvpn.md | 50 | ||||
-rw-r--r-- | posts/owncloud_distributions.md | 73 | ||||
-rw-r--r-- | posts/proposed_changes_to_indieauth_protocol.md | 89 | ||||
-rw-r--r-- | posts/wireless_router_vpn.md | 38 | ||||
-rw-r--r-- | screen.css | 39 | ||||
-rw-r--r-- | views/footer.twig | 2 | ||||
-rw-r--r-- | views/header.twig | 11 | ||||
-rw-r--r-- | views/index.twig | 18 | ||||
-rw-r--r-- | views/post.twig | 18 |
19 files changed, 1792 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28d0a93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor +/output diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f91f571 --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "fkooman/tpl-twig": "^1.3", + "michelf/php-markdown": "^1.6", + "suin/php-rss-writer": "^1.4" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..4a817a2 --- /dev/null +++ b/composer.lock @@ -0,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": [] +} diff --git a/generate.php b/generate.php new file mode 100644 index 0000000..f1c9c40 --- /dev/null +++ b/generate.php @@ -0,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'); diff --git a/posts/apache_php_fpm.md b/posts/apache_php_fpm.md new file mode 100644 index 0000000..38fb4ea --- /dev/null +++ b/posts/apache_php_fpm.md @@ -0,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) diff --git a/posts/as_discovery.md b/posts/as_discovery.md new file mode 100644 index 0000000..547455c --- /dev/null +++ b/posts/as_discovery.md @@ -0,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) diff --git a/posts/group_communication_platform.md b/posts/group_communication_platform.md new file mode 100644 index 0000000..c81c159 --- /dev/null +++ b/posts/group_communication_platform.md @@ -0,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? diff --git a/posts/https.md b/posts/https.md new file mode 100644 index 0000000..a2db107 --- /dev/null +++ b/posts/https.md @@ -0,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). diff --git a/posts/indiecert.md b/posts/indiecert.md new file mode 100644 index 0000000..8f5fedc --- /dev/null +++ b/posts/indiecert.md @@ -0,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&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&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) diff --git a/posts/indiecert_nitrokey.md b/posts/indiecert_nitrokey.md new file mode 100644 index 0000000..95fafb7 --- /dev/null +++ b/posts/indiecert_nitrokey.md @@ -0,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" -> "Advanced" -> "Certificates" -> +"Security Devices" -> "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" -> +"Advanced" -> "Certificates" -> "View Certificates" -> +"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/) diff --git a/posts/nm_12_openvpn.md b/posts/nm_12_openvpn.md new file mode 100644 index 0000000..e5a2e77 --- /dev/null +++ b/posts/nm_12_openvpn.md @@ -0,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! diff --git a/posts/owncloud_distributions.md b/posts/owncloud_distributions.md new file mode 100644 index 0000000..b479678 --- /dev/null +++ b/posts/owncloud_distributions.md @@ -0,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. diff --git a/posts/proposed_changes_to_indieauth_protocol.md b/posts/proposed_changes_to_indieauth_protocol.md new file mode 100644 index 0000000..f1a6c28 --- /dev/null +++ b/posts/proposed_changes_to_indieauth_protocol.md @@ -0,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) diff --git a/posts/wireless_router_vpn.md b/posts/wireless_router_vpn.md new file mode 100644 index 0000000..4beafad --- /dev/null +++ b/posts/wireless_router_vpn.md @@ -0,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! diff --git a/screen.css b/screen.css new file mode 100644 index 0000000..749dc5f --- /dev/null +++ b/screen.css @@ -0,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; +} diff --git a/views/footer.twig b/views/footer.twig new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/views/footer.twig @@ -0,0 +1,2 @@ +</body> +</html> diff --git a/views/header.twig b/views/header.twig new file mode 100644 index 0000000..619c466 --- /dev/null +++ b/views/header.twig @@ -0,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> diff --git a/views/index.twig b/views/index.twig new file mode 100644 index 0000000..bef8660 --- /dev/null +++ b/views/index.twig @@ -0,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' %} diff --git a/views/post.twig b/views/post.twig new file mode 100644 index 0000000..3e903e7 --- /dev/null +++ b/views/post.twig @@ -0,0 +1,18 @@ +{% include 'header.twig' %} +<p>< <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>< <a href="index.html">Back to Index</a></p> +{% include 'footer.twig' %} |