Forum OpenACS Development: Re: Multi-Factor Authentication in OpenACS

Collapse
Posted by Claudio Pasolini on
Hi Neophytos,

I looked at your work and I wondered if it was possible to avoid the external libraries taht you used to generate the one-time password and the QRcode, so as to build a cleaner solution.

I think that with the help of NaviServer's ns_totp and the CLI qrencode it should be possible.

All the best,

Claudio

Collapse
Posted by Neofytos Dimitriou on
Hi Claudio, It should be possible but I haven't tried with ns_totp. I think Gustaf had done so way back when.
Collapse
Posted by Claudio Pasolini on

I wanted to try the generation of the QRcode, which I did withe the following code:

ad_proc -private mfa::generate_secret {} {
    Generate the secret Base32
} {
    package require base32
    
    set raw [ns_crypto::randombytes 20]
    return [base32::encode $raw]
}

# create a new secret
set secret [mfa::generate_secret]

# create URI otpauth
set issuer "OpenACS"
set account_name "Claudio@[ad_conn peeraddr]"
set uri "otpauth://totp/${issuer}:${account_name}?secret=$secret&issuer=$issuer&digits=6"

# creates QR code PNG 
set png_path /vagrant/alter-dev/www/resources/qr.png
exec qrencode -o $png_path $uri

Following is the code used to generate a TOTP based on the secret

ad_proc mfa::totp {
    -secret:required
    {-for_time ""}
    {-time_step 30}
    {-digits 6}
} {
    # generates current TOTP (6 digits, 30 seconds step)
} {
    package require base32
    
    if {$for_time eq ""} {
        set for_time [clock seconds]
    }

    set counter [expr {int($for_time / $time_step)}]
    set key [base32::decode $secret]

    return [ns_totp -key $key \
                    -time $for_time \
                    -interval $time_step \
                    -digits $digits \
                    -digest sha1]    
    
}

and finally the code to compare the generated TOTP with that generated by Google Authenticator

ad_proc mfa::verify {
    -secret:required
    -code:required
    {-time_step 30}
    {-skew 1}
    {-digits 6}
} {
    Compares the secret with the code entered by the user (skew ±1 for clock tolerance)
} {
    set code [string trim $code]
    for {set i -$skew} {$i <= $skew} {incr i} {
        set t [expr {[clock seconds] + $i * $time_step}]
	set totp_code [mfa::totp \
		 -secret    $secret \
		 -for_time  $t \
		 -time_step $time_step \
		 -digits    $digits]
        if {$totp_code eq $code} {
            return 1
        }
    }
    return 0
}

Claudio