1

I'm using this code from GitHub

https://github.com/as08/ClassicASP.TwoFactorAuthentication

I downloaded the demo site, installed what I needed on the server and everything works perfectly. The demo site has a lot of code so I broke it down into the two components that are shown on the Github page 1)Generating a secret key and QR code 2) Validating a verification code.

I made 2 very barebones basic asp pages

index.asp

<%
Dim TwoFA : Set TwoFA = Server.CreateObject("ClassicASP.TwoFactorAuthentication")
    
    TwoFA.SecretKeyLength(20)
    TwoFA.HashMode("SHA1")
    TwoFA.totpSize(6)
  RecoveryPasswordLength = 18
  
    Dim SecretKey, RecoveryPassword, GenerateQR
                    
    SecretKey = TwoFA.GenerateSecretKey()
    RecoveryPassword = TwoFA.RecoveryPassword(RecoveryPasswordLength)
  
  response.write("Secret Key: " & secretKey & "<br>")
  response.write("Recovery Password: " & RecoveryPassword & "<br />")
  
    ' Generate the QR code

    GenerateQR = "<img src=""https://chart.googleapis.com/chart" &_
    "?chs=320x320" &_
    "&chld=H|0" &_
    "&cht=qr" &_
    "&chl=" & Server.URLencode("otpauth://totp/test@test.com" &_ 
    "?secret=" & SecretKey &_ 
    "&issuer=examplesite.com" &_ 
    "&algorithm=SHA1" &_ 
    "&digits=6" &_ 
    "&period=30") & "&choe=UTF-8"" " &_
  "class=""img-fluid border-info border mt-4 QRframe"" " &_  
    "width=""320px"" height=""320px"">"

Set TwoFA = Nothing
%>
<%=GenerateQR%>

Validate.asp

<%
Dim TwoFA : Set TwoFA = Server.CreateObject("ClassicASP.TwoFactorAuthentication")

    TwoFA.SecretKeyLength(20)
    TwoFA.HashMode("SHA1")
    TwoFA.totpSize(6)
  TOTP = request("totp")
  
  response.write(totp & "<br />")

    If TwoFA.Verify("EDSLKFQENTEFPATYN5LAZ5BCGD2UOR4R",cStr(TOTP)) Then

        ' Valid Time-based One-time Password (TOTP)
    response.write("valid")

    Else

        ' Invalid TOTP
    response.write("invalid")

    End If

Set TwoFA = Nothing
%>

To test, I went to my index.asp page and generated a QRcode and set that up in Microsoft Authenticator. I then took the secret key and hardcoded it into the validate page's verify call. Then I went into Authenticator and got the code and tested it by going to validate.asp?totp=123456. No matter what I do, I can't get this to work. I always get the response.write("invalid") result.

Why is this not returning a valid response even though I'm typing in the right 6 digit code using the right secret key?

user692942
  • 16,398
  • 7
  • 76
  • 175
Damien
  • 4,093
  • 9
  • 39
  • 52
  • 2
    Please don’t delete a post ignore the reasons for it being closed in the first place and proceed to re-post the exact same question. Instead, [edit](https://stackoverflow.com/posts/71948700/edit) the [original](https://stackoverflow.com/q/71948700/692942) so it can be re-opened. This way you have lost the comment thread and the useful information that was gleaned which you’ve failed yet again to provide here! – user692942 Apr 21 '22 at 19:58
  • 1
    Deleted the post because I figured it would be stuck in limbo. Changed verbiage some and not sure how to make it clearer. I made the valid/invalid part clearer which addresses your issue. Other than that the comments wouldn’t make a difference since there were only two of them. – Damien Apr 21 '22 at 21:35

2 Answers2

2

I figured this out. I hope this can be helpful to someone in the future. Unfortunately, in the documentation it has the TOTP in a cStr(TOTP), thereby converting it to a string... but I removed the cStr() and in the request i forced it to be an integer by doing:

TOTP = request("totp")+0

Now it works!

Damien
  • 4,093
  • 9
  • 39
  • 52
  • 1
    At no point does the validate sample on the github repo link tell you to use `CStr()`. Glad you sorted the issue though by working through it, but consider using `CInt()` instead. – user692942 Apr 22 '22 at 00:05
  • Got the code from the validate.class.... in there they cstr(TOTP). Thanks for the help! – Damien Apr 22 '22 at 00:25
  • 2
    I created this COM DLL. It's a long time since I've looked at the code. But I recall using `cStr()` in cases where the the TOTP started with a 0, if you just use `Int()` or `cInt()`, it will format the TOTP ignoring the prefixed 0. I did a lot of testing and I'm a bit stumped as to why your solution works. I'll maybe have to revisit it unless you can provide a better explanation. Using `request("totp")+0` doesn't make much sense. – Adam Apr 22 '22 at 08:06
  • 1
    Was going to suggest that maybe the OP contacts the GitHub author but seen as though you are already here @Adam... – user692942 Apr 22 '22 at 08:09
  • Thinking out loud @Adam, would encoding (UTF-8 vs Windows-1252) affect how the `CStr(TOTP)` is interpreted? – user692942 Apr 22 '22 at 08:19
  • The `Verify` Boolean function in the C# code treats the TOTP as a string, so passing as a string or integer should make no difference (excluding the issue of ASP converting say 012345 into 12345 if you converting to an integer beforehand). So yeah, that's a bit of a head scratcher. – Adam Apr 22 '22 at 08:21
  • I'm not sure @user692942, I don't think character encoding would affect numeric values, but I could be wrong. – Adam Apr 22 '22 at 08:24
  • @Adam I was under the impression they exist at the same index point to avoid that sort of thing, it just seems like something that could cause an issue. But as for it working if interpreted as an integer, I have no idea. I think that may be a "red-herring". – user692942 Apr 22 '22 at 08:40
  • @Adam - You're right. It doesn't work if a code is presented with a leading zero since converting to an int removes the leading zero. But, passing it as a string doesn't work ever. – Damien Apr 22 '22 at 11:01
  • 1
    @Adam - I found the real culprit!!!! There's no way I can fix it but it's not the int issue. cStr() works just fine. The problem actually is that you only have 4 seconds to input the code from when the code is shown to you. If you wait till the countdown reaches 25 then time's up. Code no longer works. This is using Microsoft Authenticator. Not sure there's anything i can do to fix that. Thoughts? – Damien Apr 22 '22 at 11:23
  • 1
    @Damien. The C# DLL and and any authenticator apps should be using UTC without you having to adjust any settings either server side or app side, meaning they should be in sync (give or take a few milliseconds), and it has a 30 second tolerance, meaning you should have 60 seconds to enter the code. That's very odd. It's using the [OtpNet](https://github.com/kspearrin/Otp.NET) library. What app are you testing it with? I'll look into it more over the weekend. – Adam Apr 22 '22 at 11:34
  • @Adam - I'm using Microsoft Authenticator. – Damien Apr 22 '22 at 13:04
  • @Adam, just tried it with Google Authenticator. Same issue. Only works within the first few seconds. – Damien Apr 23 '22 at 02:48
  • @Adam, lastly, I tried it on your demo site and i get the same exact results. The code only works for the first 3 or 4 seconds. After that, the code will always return invalid. Please let me know if there's anything I can do to help. – Damien Apr 23 '22 at 02:53
  • Sounds like a time synchronisation issue on the server, check the server date time against UTC as @Adam suggests in [their answer](https://stackoverflow.com/a/71976764/692942). – user692942 Apr 25 '22 at 08:00
1

This isn't an answer as such, but it's too long and detailed to leave as a comment.

I've downloaded and run the demo from my GitHub repository, and used Microsoft authenticator, Google Authenticator and Authy. And they all work fine, including the 30 second tolerance. I even changed my time zone to various different ones (although that wouldn't account for the 4 second expiration you described, and they didn't. I was just covering all my bases).

All I can think to recommend is this, download and run this UTC.asp page:

<%=UTC_DateTime()%>
<script language="JScript" runat="server">
    
    // Return the current UTC date and time regardless of what timezone the server is set to
    
    function UTC_DateTime() {
        
        var date = new Date();
        
        // date.getUTCMonth() returns a value from 0 - 11 (?) so we need to  + 1
        
        var result = date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + date.getUTCHours() + ":" + date.getUTCMinutes() + ":" + date.getUTCSeconds();
        
        // Pad month/day/hour/minute/second values with a 0 if necessary
        
        return result.replace(/(\D)(\d)(?!\d)/g, "$10$2");
        
    }

</script>

Then compare it to a site like timeanddate.com, if the times aren't the same (give or take a few a few seconds), then there has to be something wrong with you servers time settings, it must be generating a UTC time that is out of sync. Some sort of NTP glitch?

If that's the case, I don't have an answer, hopefully someone does, but that has to be the problem given it's working fine for me. (I ran all apps on iOS and the demo on Windows 10 and Windows Server 2016 just to clarify)

Adam
  • 836
  • 2
  • 8
  • 13
  • Unfortunately, ran the code and the timeanddate.com matches exactly to the code provided (minus refresh seconds of course). I'm at a loss . Was really looking forward to having this code implemented. If you come up with anything please let me know. In the meantime, i'll give it a shot on another server in another state and let's see how the code runs on that server. I'll keep you updated. – Damien Apr 27 '22 at 14:36
  • So I tried it on another server entirely and it works perfectly. Still hoping that you can come up with a suggestion for a fix. I have no idea. Thanks a lot Adam, appreciate all your work! – Damien Apr 27 '22 at 15:01
  • From a Google search on fixing NTP sync issues: "Open Control Panel and go to Date and Time. Go to Internet Time > click the Change settings button. Select time.nist.gov as the Server > click the Update now button. Also, try using pool.ntp.org as the server, some users reported that this fixed the issue." – Adam Apr 28 '22 at 05:37
  • Unfortunately that didn't work. Worse yet, now I can't get it to work with the 4 seconds I had before. Now, it doesn't work at all. – Damien Apr 28 '22 at 11:16
  • 1
    @Damien, ah, I'm sorry about that, although that does indicate that it's almost certainly a sync issue. Have a look at [this Microsoft article](https://learn.microsoft.com/en-us/windows-server/networking/windows-time-service/windows-time-service-tools-and-settings), it has a lot of examples for fixing sync issues for Windows servers 2022 to 2012. – Adam Apr 28 '22 at 11:42
  • @Damien I think that pretty much proves that the issue is not with the code but the servers time synchronization to UTC. This should be the accepted answer. – user692942 May 03 '22 at 10:42
  • 1
    @user692942 - sorry... you are correct. Didn't realize i didn't accept it. Thanks :) – Damien May 04 '22 at 14:08