From d69cebd8c703f0a1f6839944f1c949bce350b02e Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Tue, 13 Oct 2015 00:49:00 +0800 Subject: Bug 1199089 - add support for duo-security --- Bugzilla/DuoWeb.pm | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 Bugzilla/DuoWeb.pm (limited to 'Bugzilla/DuoWeb.pm') diff --git a/Bugzilla/DuoWeb.pm b/Bugzilla/DuoWeb.pm new file mode 100644 index 000000000..4fb28df9e --- /dev/null +++ b/Bugzilla/DuoWeb.pm @@ -0,0 +1,193 @@ +# https://github.com/duosecurity/duo_perl +# +# Copyright (c) 2012, Duo Security, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package Bugzilla::DuoWeb; + +use strict; +use warnings; + +use MIME::Base64; +use Digest::HMAC_SHA1 qw(hmac_sha1_hex); + +my $DUO_PREFIX = 'TX'; +my $APP_PREFIX = 'APP'; +my $AUTH_PREFIX = 'AUTH'; + +my $DUO_EXPIRE = 300; +my $APP_EXPIRE = 3600; + +my $IKEY_LEN = 20; +my $SKEY_LEN = 40; +my $AKEY_LEN = 40; + +our $ERR_USER = 'ERR|The username passed to sign_request() is invalid.'; +our $ERR_IKEY = 'ERR|The Duo integration key passed to sign_request() is invalid.'; +our $ERR_SKEY = 'ERR|The Duo secret key passed to sign_request() is invalid.'; +our $ERR_AKEY = "ERR|The application secret key passed to sign_request() must be at least $AKEY_LEN characters."; +our $ERR_UNKNOWN = 'ERR|An unknown error has occurred.'; + + +sub _sign_vals { + my ($key, $vals, $prefix, $expire) = @_; + + my $exp = time + $expire; + + my $val = join '|', @{$vals}, $exp; + my $b64 = encode_base64($val, ''); + my $cookie = "$prefix|$b64"; + + my $sig = hmac_sha1_hex($cookie, $key); + + return "$cookie|$sig"; +} + + +sub _parse_vals { + my ($key, $val, $prefix, $ikey) = @_; + + my $ts = time; + + if (not defined $val) { + return ''; + } + + my @parts = split /\|/, $val; + if (scalar(@parts) != 3) { + return ''; + } + my ($u_prefix, $u_b64, $u_sig) = @parts; + + my $sig = hmac_sha1_hex("$u_prefix|$u_b64", $key); + + if (hmac_sha1_hex($sig, $key) ne hmac_sha1_hex($u_sig, $key)) { + return ''; + } + + if ($u_prefix ne $prefix) { + return ''; + } + + my @cookie_parts = split /\|/, decode_base64($u_b64); + if (scalar(@cookie_parts) != 3) { + return ''; + } + my ($user, $u_ikey, $exp) = @cookie_parts; + + if ($u_ikey ne $ikey) { + return ''; + } + + if ($ts >= $exp) { + return ''; + } + + return $user; +} + +=pod + Generate a signed request for Duo authentication. + The returned value should be passed into the Duo.init() call! + in the rendered web page used for Duo authentication. + + Arguments: + + ikey -- Duo integration key + skey -- Duo secret key + akey -- Application secret key + username -- Primary-authenticated username +=cut + +sub sign_request { + my ($ikey, $skey, $akey, $username) = @_; + + if (not $username) { + return $ERR_USER; + } + + if (index($username, '|') != -1) { + return $ERR_USER; + } + + if (not $ikey or length $ikey != $IKEY_LEN) { + return $ERR_IKEY; + } + + if (not $skey or length $skey != $SKEY_LEN) { + return $ERR_SKEY; + } + + if (not $akey or length $akey < $AKEY_LEN) { + return $ERR_AKEY; + } + + my $vals = [ $username, $ikey ]; + + my $duo_sig = _sign_vals($skey, $vals, $DUO_PREFIX, $DUO_EXPIRE); + my $app_sig = _sign_vals($akey, $vals, $APP_PREFIX, $APP_EXPIRE); + + if (not $duo_sig or not $app_sig) { + return $ERR_UNKNOWN; + } + + return "$duo_sig:$app_sig"; +} + +=pod + + Validate the signed response returned from Duo. + + Returns the username of the authenticated user, or '' (empty + string) if secondary authentication was denied. + + Arguments: + + ikey -- Duo integration key + skey -- Duo secret key + akey -- Application secret key + sig_response -- The signed response POST'ed to the server + +=cut + +sub verify_response { + my ($ikey, $skey, $akey, $sig_response) = @_; + + if (not defined $sig_response) { + return ''; + } + + my ($auth_sig, $app_sig) = split /:/, $sig_response; + my $auth_user = _parse_vals($skey, $auth_sig, $AUTH_PREFIX, $ikey); + my $app_user = _parse_vals($akey, $app_sig, $APP_PREFIX, $ikey); + + if ($auth_user ne $app_user) { + return ''; + } + + return $auth_user; +} +1; -- cgit v1.2.3-24-g4f1b