diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..20806ea --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,112 @@ + +# Documentation + +## otp.lua +This file exposes the main part of the library in which the hotp and totp conventions are concieved. + +function otp.new(secret, digits, digest, ...) +Returns a new OTP object used to pass to other OTP functions. +required string secret - base32 encoded string in which OTP codes are generated based on +optional number digits default 6 - how many digits is returned from an OTP generation +optional number digest default "sha1" - the hash used for code generation +situational number {...}[1] - a second interval for timeblocks for generation verification + +function otp.generate_otp(instance, input) +Returns an OTP code. Returns nil if input < 0. +required object instance - otp instance from otp.new() +required input - a value > -1 + +function otp.byte_secret(instance) +Pads % 8 the secret key in instance state. Returns array of decoded secret base32 bytes. +required object instance - otp instance from otp.new() + +function otp.int_to_bytestring(i, padding) +Converts 4-byte integers into a binary string. +required number i - integer to convert +optional number padding default 8 - pad bytes out byte groups to multiples of this number + +## totp.lua +This file extends the file otp.lua in a polymorphism way. It exposes timeblock based code generation. + +functop totp.at(instance, for_time, counter_offset) +Returns a totp code at a specific time based on state. +required object instance - otp instance from otp.new() +required number for_time - time to generate code from in seconds +optional number counter_offset default 0 - skip this many timeblocks in code generation + +function totp.now(instance, override) +Returns a totp code at the current time based on os.time() ignored in favor for override. +required object instance - otp instance from otp.new() +optional number override default os.time() - replacement for os.time() + +function totp.valid_until(instance, for_time, valid_window) +Returns the time in seconds that the specified for_time will be invalidated. +required object instance - otp instance from otp.new() +required number for_time - time in seconds for finding out the next invalidation period +optional number valid_window default 0 - timeblocks a code should be active for + +function totp.verify(instance, key, for_time, valid_window) +Returns true if key is verified, or within the timeblock specified by valid_window from for_time. +required object instance - otp instance from otp.new() +required number key - otp key to verify +optional number for_time default os.time() - time in seconds to verify the current +optional number valid_window default 0 - timeblocks a code should be active for + +## hotp.lua +This file extends the file otp.lua in a polymorphism way. It exposes counter based code generation. + +function hotp.at(instance, counter) +Returns the current otp code at a specified counter count. +required object instance - otp instance from otp.new() +required number counter - counter to generate otp code from + +function hotp.verify(instance, key, counter) +Returns true if key is verified, or the key code matches the generated otp with counter. +required object instance - otp instance from otp.new() +required number key - otp key to verify +required number counter - counter to generate otp code from to match against key + +## util.lua +These are your general util functions for generating a OTP uri and internally used functions. + +LinearArray util.default_chars[32] +Base32 characters without 0 and 1 (for confusion sakes). + +string util.base_uri[17] +A format containing 3 string replacements for an otpauth uri. + +function util.build_args(arr) +Given key-value pairs in arr, creates a uri parameters section in the form of ?key=arr[key]&key2=arr[key2]. +required dictionary arr - key-value pairs of uri arguments + +function util.encode_url(url) +URL-encodes string url to be url-safe. +required string url - the url to make url-safe + +function build_uri(secret, name, initial_count, issuer_name, algorithm, digits, period) +Returns a uri built based on otp data for importing in 3rd party apps or to generate QR codes. +required string secret - base32 secret string for otp code generation +required string name - name to append to the issuer_name with a : +optional number initial_count default nil - if method is hotp, this is a counter, if not then should always be nil +required string issuer_name - the issuer of the otp code +required string algorithm - the hash algorithm used to generate the blob to hmac for the otp code +required number digits - how many digits an otp code should have +required number period - if method is totp, this the timeblock an otp is generated for + +function util.arr_reverse(tab) +Reverses a linear array. Returns a new table. +required LinearArray tab - a linear array to reverse the elements in + +function util.byte_arr_tostring(arr) +Converts a linear array of numbers into characters into a string. Returns a new table. +required LinearArray arr - a linear array of numbers to convert to char and put in a linear array + +function util.str_to_byte(str) +Converts a string into a linear array of bytes. Returns a new table. +required string str - string to break down into a byte array + +function util.random_base32(length, chars) +Given charset chars, generates and returns a random base32 string with a length of length. +optional number length default 16 - the length of the base32 string +optional LinearArray chars default util.default_chars - the base32 charset to use + diff --git a/LICENSE b/LICENSE index 30154c8..9182eaf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Cody Tilkins +Copyright (c) 2021 Cody Tilkins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4d01ba4..dac65e9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ A simple One Time Password (OTP) library in lua Compatible with Authy and Google Authenticator. Full support for QR code url is provided. +Copyright (c) 2021 Cody Tilkins MIT + ## Libraries Needed @@ -30,11 +32,6 @@ You will need to configure the paths of the requires of this library. I am guess When it comes down to it, this library will convert your integer numbers to string and do a comparison byte by byte. There is no need for expensive testing - nobody knows what is going on except the key holders and the key can't be reversed because we only send a small part of the hmac. That being said, there is no support for digits > 9 yet - as this is half an int's limit. -## Description - -This was actually a spawn off pyotp, but I would necessarily say the code was copied. Things in python aren't in lua, therefore I had to make the methods myself. However, credits will go to the module for providing a guideline of what to do. [Here](https://github.com/pyotp/pyotp) you can find pyotp and realize how different it really is. - - _____________ ## License @@ -58,5 +55,4 @@ To use this library, pick either TOTP or HOTP then use the provided files - givi * Add comments - there are lacking comments, should match up to COTP's style * Remove the dependancy on basexx - I have an implementation found in COTP and JOTP that can be ported. * bit32 isn't actually an external dependancy, but depending on the version you prefer, it is -* sha1 make sure we aren't infringing anything with this diff --git a/hotp.lua b/hotp.lua index 6defd3b..f78de2a 100644 --- a/hotp.lua +++ b/hotp.lua @@ -1,11 +1,34 @@ +--[[ +MIT License + +Copyright (c) 2021 Cody Tilkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] -- please point this to proper location local otp = require("otp") local hotp = {} -hotp.at = function(instance, count) - return otp.generate_otp(instance, count) +hotp.at = function(instance, counter) + return otp.generate_otp(instance, counter) end hotp.verify = function(instance, key, counter) diff --git a/otp.lua b/otp.lua index 59c469d..88f5fd3 100644 --- a/otp.lua +++ b/otp.lua @@ -1,3 +1,26 @@ +--[[ +MIT License + +Copyright (c) 2021 Cody Tilkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] -- please point these to proper location local basexx = require("basexx") @@ -39,6 +62,10 @@ end otp.generate_otp = function(instance, input) + if (input < 0) then + return nil + end + local hash = sha1.hmac_binary(otp.byte_secret(instance), otp.int_to_bytestring(input)) local offset = bit32.band(string.byte(hash:sub(-1, -1)), 0xF) + 1 @@ -76,14 +103,4 @@ otp.int_to_bytestring = function(i, padding) return string.rep('\0', math.max(0, (padding or 8) - #bytes)) .. util.byte_arr_tostring(util.arr_reverse(bytes)) end -otp.random_base32 = function(length, chars) - length = length or 16 - chars = chars or util.default_chars - local out = "" - for i=1, length do - out = out .. chars[math.random(1, #chars)] - end - return out -end - return otp diff --git a/test.lua b/test.lua index 8f959e3..fcc4805 100644 --- a/test.lua +++ b/test.lua @@ -1,3 +1,26 @@ +--[[ +MIT License + +Copyright (c) 2021 Cody Tilkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] ---------------------------------------------------------------- -- Initialization Stuff -- @@ -14,6 +37,7 @@ local DIGEST = "SHA1"; local OTP = require("otp") local TOTP = require("totp") local HOTP = require("hotp") +local UTIL = require("util") -- Create OTPData struct, which decides the environment @@ -74,7 +98,7 @@ math.random(1, 2) local base32_len = 16 -local base32_new_secret = OTP.random_base32(base32_len, OTP.util.default_chars) +local base32_new_secret = UTIL.random_base32(base32_len, OTP.util.default_chars) print("Generated BASE32 Secret: `" .. base32_new_secret .. "`") print("") -- line break for readability diff --git a/totp.lua b/totp.lua index 5da4490..5a53a3c 100644 --- a/totp.lua +++ b/totp.lua @@ -1,3 +1,26 @@ +--[[ +MIT License + +Copyright (c) 2021 Cody Tilkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] -- please point this to proper location local otp = require("otp") @@ -5,14 +28,11 @@ local otp = require("otp") local totp = {} totp.at = function(instance, for_time, counter_offset) - if (for_time == nil) then - error("No for_time supplied.") - end return otp.generate_otp(instance, totp.timecode(instance, tonumber(for_time)) + (counter_offset or 0)) end -totp.now = function(instance) - return otp.generate_otp(instance, totp.timecode(instance, os.time())) +totp.now = function(instance, override) + return otp.generate_otp(instance, totp.timecode(instance, override or os.time())) end totp.valid_until = function(instance, for_time, valid_window) diff --git a/util.lua b/util.lua index 490e63c..1743caa 100644 --- a/util.lua +++ b/util.lua @@ -1,3 +1,26 @@ +--[[ +MIT License + +Copyright (c) 2021 Cody Tilkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] local util = {} @@ -41,7 +64,7 @@ util.build_uri = function(secret, name, initial_count, issuer_name, algorithm, d algorithm = algorithm and string.upper(algorithm) or "" local url_args = { - secret = tostringsecret, + secret = tostring(secret), issuer = issuer_name, counter = tostring(initial_count), algorithm = algorithm, @@ -77,4 +100,14 @@ util.str_to_byte = function(str) return out end +util.random_base32 = function(length, chars) + length = length or 16 + chars = chars or util.default_chars + local out = "" + for i=1, length do + out = out .. chars[math.random(1, #chars)] + end + return out +end + return util