sample-plugin: TLS Keying Material Exporter [RFC-5705] demonstration plug-in

A simple plug-in with a corresponding HTTP server and client which can authenticate
an HTTP user based on the authentication already done via an established OpenVPN
connection

[DS: Renamed the module at commit time from sso to keyingmaterialexporter to
     avoid confusion with other Single-Sign-On solutions.  Updated documentation
     and commits accordingly.  Added --pull to the client config]

Signed-off-by: Daniel Kubec <niel@rtfm.cz>
Signed-off-by: David Sommerseth <davids@redhat.com>
Acked-by: David Sommerseth <davids@redhat.com>
This commit is contained in:
Daniel Kubec 2015-04-05 00:10:37 +02:00 committed by David Sommerseth
parent 84604e0bae
commit f7ef7522f5
Notes: David Sommerseth 2015-10-10 10:46:13 +02:00
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Forgot to add Message-ID and URLs to the commit message.

Message-Id: CAEW7GUuRt5Tuw+n4CHie+pTrr3FhH2kM0hQ20y3f_LXS8yvDoQ@mail.gmail.com
URL: http://article.gmane.org/gmane.network.openvpn.devel/9581

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (GNU/Linux)

iEYEARECAAYFAlYYzNMACgkQDC186MBRfrrvYwCdFCjovO6dGxhmbuXa+L66a9X4
+rYAnR0AplVKpExLnOjxhjVEDMsU2cS+
=6PPU
-----END PGP SIGNATURE-----
7 changed files with 449 additions and 0 deletions

View file

@ -0,0 +1,68 @@
OpenVPN plugin examples. Daniel Kubec <niel@rtfm.cz>
Examples provided:
keyingmaterialexporter.c -- Example based on TLS Keying Material Exporters over HTTP [RFC-5705]
(openvpn/doc/keying-material-exporter.txt)
This example demonstrates authenticating a user over HTTP who have already
established an OpenVPN connecting using the --keying-material-exporter
feature.
Requires:
OpenVPN RFC-5705 Support, OpenSSL >= 1.0.1
Files:
http-server.py -- Example HTTP Server listen 0.0.0.0:8080
http-client.py -- Example HTTP Client connect 10.8.0.1:8080 [GET /$SESSIONID]
server.ovpn -- Example HTTP SSO VPN Server configuration
client.ovpn -- Example HTTP SSO VPN Client configuration
keyingmaterialexporter.c,
keyingmaterialexporter.so -- Example OpenVPN Client and Server plugin
To build:
./build keyingmaterialexporter
To use in OpenVPN:
Enter openvpn/sample/sample-plugins/keyingmaterialexporter directory
and in separate terminals, start these four processes:
$ openvpn --config ./server.ovpn
$ openvpn --config ./client.ovpn
$ ./http-server.py
$ ./http-client.py
Test:
openvpn --config ./server.ovpn
##############################
PLUGIN SSO: app session created
PLUGIN_CALL: POST ./keyingmaterialexporter.so/PLUGIN_TLS_VERIFY status=0
PLUGIN SSO: app session key: a5885abc84d361803f58ede1ef9c0adf99e720cd
PLUGIN SSO: app session file: /tmp/openvpn_sso_a5885abc84d361803f58ede1ef9c0adf99e720cd
PLUGIN SSO: app session user: Test-Client
openvpn --config ./client.ovpn
##############################
PLUGIN SSO: app session created
PLUGIN_CALL: POST ./keyingmaterialexporter.so/PLUGIN_TLS_VERIFY status=0
PLUGIN SSO: app session key: a5885abc84d361803f58ede1ef9c0adf99e720cd
PLUGIN SSO: app session file: /tmp/openvpn_sso_user
PLUGIN_CALL: POST ./keyingmaterialexporter.so/PLUGIN_TLS_FINAL status=0
HTTP_SERVER:
http-server.py
################
http server started
session file: /tmp/openvpn_sso_a5885abc84d361803f58ede1ef9c0adf99e720cd
10.8.0.1 - - [02/Apr/2015 15:03:33] "GET /a5885abc84d361803f58ede1ef9c0adf99e720cd HTTP/1.1" 200 -
session user: Test-Client
session key: a5885abc84d361803f58ede1ef9c0adf99e720cd
HTTP_SERVER:
http-client.py
<html><body><h1>Greetings Test-Client. You are authorized</h1></body></html>

View file

@ -0,0 +1,15 @@
#!/bin/sh
#
# Build an OpenVPN plugin module on *nix. The argument should
# be the base name of the C source file (without the .c).
#
# This directory is where we will look for openvpn-plugin.h
CPPFLAGS="${CPPFLAGS:--I../../..}"
CC="${CC:-gcc}"
CFLAGS="${CFLAGS:--O2 -Wall -g}"
$CC $CPPFLAGS $CFLAGS -fPIC -c $1.c && \
$CC $CFLAGS -fPIC -shared $LDFLAGS -Wl,-soname,$1.so -o $1.so $1.o -lc

View file

@ -0,0 +1,18 @@
tls-client
pull
keying-material-exporter "EXPORTER_SSO_TEST" 16
reneg-sec 0
ca ../../sample-keys/ca.crt
cert ../../sample-keys/client.crt
key ../../sample-keys/client.key
plugin ./keyingmaterialexporter.so
remote 127.0.0.1 1194
proto udp
dev tun
nobind
verb 4

View file

@ -0,0 +1,20 @@
#!/usr/bin/python
import sys
import os
import httplib
f = '/tmp/openvpn_sso_user'
with open (f, "r") as myfile:
session_key = myfile.read().replace('\n', '')
conn = httplib.HTTPConnection("10.8.0.1:8080")
conn.request("GET", "/" + session_key)
r1 = conn.getresponse()
if r1.status == 200:
body = r1.read().rstrip()
print body
elif r1.status == 404:
print "Authentication failed"
else:
print r1.status, r1.reason

View file

@ -0,0 +1,41 @@
#!/usr/bin/python
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import os
class ExampleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
session_key = os.path.basename(self.path)
file = '/tmp/openvpn_sso_' + session_key
print 'session file: ' + file
try:
f = open(file)
#send code 200 response
self.send_response(200)
#send header first
self.send_header('Content-type','text-html')
self.end_headers()
#send file content to client
user = f.read().rstrip()
print 'session user: ' + user
print 'session key: ' + session_key
self.wfile.write('<html><body><h1>Greetings ' + user \
+ '. You are authorized' \
'</h1>' \
'</body></html>')
f.close()
return
except IOError:
self.send_error(404, 'authentication failed')
def run():
#ip and port of servr
#by default http server port is 80
server_address = ('0.0.0.0', 8080)
httpd = HTTPServer(server_address, ExampleHTTPRequestHandler)
print('http server started')
httpd.serve_forever()
print('http server stopped')
if __name__ == '__main__':
run()

View file

@ -0,0 +1,269 @@
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This file implements a Sample (HTTP) SSO OpenVPN plugin module
*
* See the README file for build instructions.
*/
#define ENABLE_CRYPTO
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "openvpn-plugin.h"
#ifndef MAXPATH
#define MAXPATH 1024
#endif
#define ovpn_err(fmt, ...) \
plugin->log(PLOG_ERR, "SSO", fmt , ## __VA_ARGS__)
#define ovpn_dbg(fmt, ...) \
plugin->log(PLOG_DEBUG, "SSO", fmt , ## __VA_ARGS__)
#define ovpn_note(fmt, ...) \
plugin->log(PLOG_NOTE, "SSO", fmt , ## __VA_ARGS__)
enum endpoint { CLIENT = 1, SERVER = 2 };
struct plugin {
plugin_log_t log;
enum endpoint type;
int mask;
};
struct session {
char user[48];
char key [48];
};
/*
* Given an environmental variable name, search
* the envp array for its value, returning it
* if found or NULL otherwise.
*/
static const char *
get_env(const char *name, const char *envp[])
{
if (envp)
{
int i;
const int namelen = strlen (name);
for (i = 0; envp[i]; ++i)
{
if (!strncmp (envp[i], name, namelen))
{
const char *cp = envp[i] + namelen;
if (*cp == '=')
return cp + 1;
}
}
}
return NULL;
}
OPENVPN_EXPORT int
openvpn_plugin_open_v3 (const int version,
struct openvpn_plugin_args_open_in const *args,
struct openvpn_plugin_args_open_return *rv)
{
struct plugin *plugin = calloc (1, sizeof(*plugin));
plugin->type = get_env ("remote_1", args->envp) ? CLIENT : SERVER;
plugin->log = args->callbacks->plugin_log;
plugin->mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_FINAL);
plugin->mask |= OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY);
ovpn_note("vpn endpoint type=%s",plugin->type == CLIENT ? "client":"server");
rv->type_mask = plugin->mask;
rv->handle = (void *)plugin;
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
static void
session_user_set(struct session *sess, X509 *x509)
{
int fn_nid;
ASN1_OBJECT *fn;
ASN1_STRING *val;
X509_NAME *x509_name;
X509_NAME_ENTRY *ent;
const char *objbuf;
x509_name = X509_get_subject_name (x509);
int i, n = X509_NAME_entry_count (x509_name);
for (i = 0; i < n; ++i)
{
if (!(ent = X509_NAME_get_entry (x509_name, i)))
continue;
if (!(fn = X509_NAME_ENTRY_get_object (ent)))
continue;
if (!(val = X509_NAME_ENTRY_get_data (ent)))
continue;
if ((fn_nid = OBJ_obj2nid (fn)) == NID_undef)
continue;
if (!(objbuf = OBJ_nid2sn (fn_nid)))
continue;
/* bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8 requires this workaround */
unsigned char *buf = (unsigned char *)1;
if (ASN1_STRING_to_UTF8 (&buf, val) <= 0)
continue;
if (!strncasecmp(objbuf, "CN", 2))
snprintf(sess->user, sizeof(sess->user) - 1, (char *)buf);
OPENSSL_free (buf);
}
}
static int
tls_verify(struct openvpn_plugin_args_func_in const *args)
{
struct plugin *plugin = (struct plugin *)args->handle;
struct session *sess = (struct session *)args->per_client_context;
/* we store cert subject for the server end point only */
if (plugin->type != SERVER)
return OPENVPN_PLUGIN_FUNC_SUCCESS;
if (!args->current_cert) {
ovpn_err("this example plugin requires client certificate");
return OPENVPN_PLUGIN_FUNC_ERROR;
}
session_user_set(sess, args->current_cert);
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
static void
file_store(char *file, char *content)
{
FILE *f;
if (!(f = fopen(file, "w+")))
return;
fprintf(f, "%s", content);
fclose(f);
}
static void
server_store(struct openvpn_plugin_args_func_in const *args)
{
struct plugin *plugin = (struct plugin *)args->handle;
struct session *sess = (struct session *)args->per_client_context;
char file[MAXPATH];
snprintf(file, sizeof(file) - 1, "/tmp/openvpn_sso_%s", sess->key);
ovpn_note("app session file: %s", file);
file_store(file, sess->user);
}
static void
client_store(struct openvpn_plugin_args_func_in const *args)
{
struct plugin *plugin = (struct plugin *)args->handle;
struct session *sess = (struct session *)args->per_client_context;
char *file = "/tmp/openvpn_sso_user";
ovpn_note("app session file: %s", file);
file_store(file, sess->key);
}
static int
tls_final(struct openvpn_plugin_args_func_in const *args,
struct openvpn_plugin_args_func_return *rv)
{
struct plugin *plugin = (struct plugin *)args->handle;
struct session *sess = (struct session *)args->per_client_context;
const char *key;
if (!(key = get_env ("exported_keying_material", args->envp)))
return OPENVPN_PLUGIN_FUNC_ERROR;
snprintf(sess->key, sizeof(sess->key) - 1, "%s", key);
ovpn_note("app session key: %s", sess->key);
switch (plugin->type) {
case SERVER:
server_store(args);
break;
case CLIENT:
client_store(args);
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
ovpn_note("app session user: %s", sess->user);
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
OPENVPN_EXPORT int
openvpn_plugin_func_v3 (const int version,
struct openvpn_plugin_args_func_in const *args,
struct openvpn_plugin_args_func_return *rv)
{
switch(args->type) {
case OPENVPN_PLUGIN_TLS_VERIFY:
return tls_verify(args);
case OPENVPN_PLUGIN_TLS_FINAL:
return tls_final(args, rv);
}
return OPENVPN_PLUGIN_FUNC_SUCCESS;
}
OPENVPN_EXPORT void *
openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle)
{
struct plugin *plugin = (struct plugin *)handle;
struct session *sess = calloc (1, sizeof(*sess));
ovpn_note("app session created");
return (void *)sess;
}
OPENVPN_EXPORT void
openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *ctx)
{
struct plugin *plugin = (struct plugin *)handle;
struct session *sess = (struct session *)ctx;
ovpn_note("app session key: %s", sess->key);
ovpn_note("app session destroyed");
free (sess);
}
OPENVPN_EXPORT void
openvpn_plugin_close_v1 (openvpn_plugin_handle_t handle)
{
struct plugin *plugin = (struct plugin *)handle;
free (plugin);
}

View file

@ -0,0 +1,18 @@
tls-server
reneg-sec 0
keying-material-exporter "EXPORTER_SSO_TEST" 16
duplicate-cn
plugin ./keyingmaterialexporter.so
ca ../../sample-keys/ca.crt
cert ../../sample-keys/server.crt
key ../../sample-keys/server.key
dh ../../sample-keys/dh2048.pem
server 10.8.0.0 255.255.255.0
port 1194
proto udp
dev tun
verb 4