potp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 572944d75dff4240e25ec32bb91202acd04ce07b2dea52e585fb765bf579076d
4
+ data.tar.gz: 633ba7f0dac49a1c66d71680a030571b32185806adf6e9e9111bc5561d937865
5
+ SHA512:
6
+ metadata.gz: 4e6b4d5efe9da1b692486e28a56fcf20f4c858a99bb7d870889c4cee21540fff2061589088ba01917333dcf83ec9d3293e4e9741d79d468fac039d5913b0bef2
7
+ data.tar.gz: 5ed1543a5214cb4be08e1b0b54d7ebff17833ed7442757bc7a5605356d52860967d136bf0ed2a0eb3629024797d65df313dd70ab198dd1b3397b412740744c5f
data/LICENSE ADDED
@@ -0,0 +1,52 @@
1
+ # BSD-2-clause license, extended by language use conditions
2
+
3
+ Copyright (C) 2024, Bertram Scharpf <software@bertram-scharpf.de>.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are
8
+ met:
9
+
10
+ * Redistributions of source code must retain the above copyright
11
+ notice, this list of conditions and the following disclaimer.
12
+
13
+ * Redistributions in binary form must reproduce the above copyright
14
+ notice, this list of conditions and the following disclaimer in
15
+ the documentation and/or other materials provided with the
16
+ distribution.
17
+
18
+ * Redistributions must not contain any clauses about anticipated
19
+ harassment or discrimination, nor must they be held in a so-called
20
+ "inclusive language". As far as German language is used, the
21
+ conditions mentioned below additionally apply.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
26
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
27
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+
35
+
36
+ ## Use of the German Language
37
+
38
+ Beim Gebrauch deutscher Sprache sind Weiterentwicklungen und
39
+ -verbreitungen nur gestattet unter Einhaltung sowie abermaligen
40
+ Einforderns folgender zusätzlicher Bedingungen:
41
+
42
+ * Keine Verwendung von sogenannter „geschlechtergerechter Sprache“,
43
+ also Anfügen von weiblichen Endungen mit Binnen-I, Sternchen,
44
+ Doppelpunkt, Unterstrich oder ähnlichem, oder Konstruktionen, die
45
+ den Sachverhalt falsch wiedergeben („Radfahrende“, „Studierende“).
46
+
47
+ * Keine Verwendung der „reformierten Rechtschreibung“ von 1996,
48
+ insbesondere Doppel-S am Silbenende, „plazieren“ mit T, sowie
49
+ Großschreibung von Wendungen wie „des weiteren“.
50
+
51
+
52
+ <!-- vim:set ft=markdown : -->
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # The Plain One Time Password Library
2
+
3
+ A ruby library for generating and validating one time passwords (HOTP & TOTP)
4
+ according to
5
+ [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226)
6
+ and
7
+ [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238).
8
+
9
+ POTP aims to be compatible with
10
+ [Google Authenticator](https://github.com/google/google-authenticator).
11
+
12
+ The Base32 format conforms to
13
+ [RFC 4648 Base32](http://en.wikipedia.org/wiki/Base32#Base_32_Encoding_per_§6)
14
+
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ sudo gem install potp
20
+ ```
21
+
22
+ If you like to run the executable (instead of writing a one-liner for
23
+ yourself), you have to install the `appl` gem.
24
+
25
+ ```bash
26
+ sudo gem install appl
27
+ ```
28
+
29
+
30
+ ## Library Usage
31
+
32
+ ### Time based (TOTP)
33
+
34
+ ```ruby
35
+ require "potp"
36
+
37
+ totp = POTP::TOTP.new "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
38
+ totp.now #=> "152201"
39
+
40
+ totp.verify "152201" #=> 1735417500 # ok, value is the timestamp
41
+ sleep 30
42
+ totp.verify "152201" #=> nil # not ok
43
+ ```
44
+
45
+ ### Counter based (HOTP)
46
+
47
+ ```ruby
48
+ hotp = POTP::HOTP.new "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
49
+ hotp.at 0 #=> "178748"
50
+ hotp.at 1 #=> "584373"
51
+ hotp.at 73 #=> "309764"
52
+
53
+ # OTP verifying with a counter
54
+ hotp.verify "309764", 73 #=> 73
55
+ hotp.verify "309764", 74 #=> nil
56
+ hotp.verify "309764", 70, retries: 2 #=> nil
57
+ hotp.verify "309764", 70, retries: 3 #=> 73
58
+ ```
59
+
60
+
61
+ ### Avoiding reuse of TOTP
62
+
63
+ ```ruby
64
+ require "potp"
65
+ totp = POTP::TOTP.new "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
66
+ code = totp.now #=> "054626"
67
+ last_verify = totp.verify code #=> 1735527390
68
+ totp.verify code, after: last_verify #=> nil
69
+ sleep 30
70
+ code = totp.now #=> "481150"
71
+ totp.verify code, after: last_verify #=> 1735527420
72
+ ```
73
+
74
+
75
+ ### Verifying a TOTP with drift
76
+
77
+ In case a user entered a code just after it has expired, you can allow
78
+ the token to remain valid.
79
+
80
+ ```ruby
81
+ totp = POTP::TOTP.new "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
82
+ now = Time.now - 30
83
+ code = totp.at now #=> "455335"
84
+ totp.verify code #=> nil
85
+ totp.verify code, drift_behind: 27 #=> 1735530510
86
+ ```
87
+
88
+
89
+ ### Generating a Base32 secret key
90
+
91
+ Returns a 160 bit (32 character) Base32 secret.
92
+
93
+ ```ruby
94
+ require "potp/random"
95
+ POTP::Base32.random #=> "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
96
+ ```
97
+
98
+
99
+ ### Generating QR codes for provisioning mobile apps
100
+
101
+ ```ruby
102
+ require "potp"
103
+
104
+ totp = POTP::TOTP.new "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
105
+ uri = totp.provisioning_uri name: "jdoe@example.net", issuer: "ACME Service"
106
+ #=> "otpauth://totp/ACME%20Service:jdoe%40example.net?secret=GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A&issuer=ACME%20Service"
107
+
108
+ hotp = POTP::HOTP.new "GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A"
109
+ uri = hotp.provisioning_uri name: "jdoe@example.net", issuer: "ACME Service", counter: 0
110
+ #=> "otpauth://hotp/ACME%20Service:jdoe%40example.net?secret=GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A&issuer=ACME%20Service&counter=0"
111
+
112
+ # Then, do something like this:
113
+ system *%w(qrencode -t xpm -s 1 -o), "qr.xpm", uri
114
+ ```
115
+
116
+
117
+ ## Executable Usage
118
+
119
+ Generates a time-based one-time password:
120
+
121
+ ```bash
122
+ potp --secret GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A
123
+ ```
124
+
125
+ Generates a counter-based one-time password:
126
+
127
+ ```bash
128
+ potp --hmac --secret GYS5L3N3E4AAYNMN562LW76TMWHQBJ4A --counter 42
129
+ ```
130
+
131
+ What you expect:
132
+
133
+ ```bash
134
+ potp --help
135
+ ```
136
+
137
+
138
+ ## Copyright
139
+
140
+ * (C) 2024 Bertram Scharpf <software@bertram-scharpf.de>
141
+ * License: [BSD-2-Clause+](./LICENSE)
142
+
data/bin/potp ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # potp -- The Plain One-Time Password Tool
5
+ #
6
+
7
+ begin
8
+ require "appl"
9
+ rescue LoadError
10
+ puts "This tool requires the `appl` gem."
11
+ exit 2
12
+ end
13
+ require "potp/foreign/supplement"
14
+ require "potp"
15
+
16
+
17
+ class POTP::Appl < Application
18
+
19
+ NAME = "potp"
20
+ VERSION = POTP::VERSION
21
+ SUMMARY = "Plain One Time Password Tool"
22
+ COPYRIGHT = "(C) 2024 Bertram Scharpf <software@bertram-scharpf.de>"
23
+ LICENSE = "BSD-2-Clause+"
24
+ AUTHOR = "Bertram Scharpf <software@bertram-scharpf.de>"
25
+
26
+ DESCRIPTION = <<~EOT
27
+ Generate and validate one time passwords (HOTP & TOTP)
28
+ according to [RFC 4226] and [RFC 6238].
29
+
30
+ Examples:
31
+
32
+ potp --secret p4ssword # Generates a time-based one-time password
33
+ potp --hmac --secret p4ssword --counter 42 # Generates a counter-based one-time password
34
+
35
+ EOT
36
+
37
+ attr_writer :secret, :counter, :digest
38
+ attr_bang :debug
39
+ def time! ; @mode = :time ; end
40
+ def hmac! ; @mode = :hmac ; end
41
+
42
+ define_option "t", :time!, true, "use time-based OTP according to RFC 6238"
43
+ alias_option "t", "time"
44
+
45
+ define_option "m", :hmac!, "use counter-based OTP according to RFC 4226"
46
+ alias_option "m", "hmac"
47
+
48
+ define_option "s", :secret=, "STR", "the shared secret"
49
+ alias_option "s", "secret"
50
+
51
+ define_option "d", :digest=, "ALG", "sha1", "algorithm for the digest"
52
+ alias_option "d", "digest"
53
+
54
+ define_option "c", :counter=, "NUM", 0,
55
+ "the counter for counter-based hmac OTP"
56
+ alias_option "c", "counter"
57
+
58
+ define_option "g", :debug!, "full Ruby error messages and backtrace"
59
+ alias_option "g", "debug"
60
+ define_option "h", :help, "show options"
61
+ alias_option "h", "help"
62
+ define_option "V", :version, "show version"
63
+ alias_option "V", "version"
64
+
65
+ def run
66
+ puts generate_output
67
+ rescue POTP::Base32::Invalid
68
+ raise "Secret must be in RFC4648 Base32 format - http://en.wikipedia.org/wiki/Base32#Base_32_Encoding_per_§6"
69
+ end
70
+
71
+ private
72
+
73
+ def generate_output
74
+ @secret.notempty? or raise "You must specify a --secret. See --help."
75
+ case @mode
76
+ when :time then (POTP::TOTP.new @secret, digest: @digest).now
77
+ when :hmac then (POTP::HOTP.new @secret, digest: @digest).at @counter
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ POTP::Appl.run
84
+
@@ -0,0 +1,140 @@
1
+ #
2
+ # potp/base32.rb -- Base32 Encoding
3
+ #
4
+
5
+ require "potp/foreign/supplement"
6
+
7
+
8
+ module POTP
9
+
10
+ class Base32
11
+
12
+ ORD_A, ORD_Z, ORD_a, ORD_z, ORD_2, ORD_7 = %w(A Z a z 2 7).map { |c| c.ord }
13
+ LAP = 26
14
+ DIFF_2 = ORD_2 - LAP
15
+
16
+ class << self
17
+
18
+ def encode bytes, width: nil, equals: true
19
+ res = [""]
20
+ scan_bufs bytes do |buf,r|
21
+ chunk = []
22
+ 8.times {
23
+ chunk.unshift buf & 0x1f
24
+ buf >>= 5
25
+ }
26
+ loop do
27
+ r -= 5
28
+ break unless r > 0
29
+ chunk.pop
30
+ end
31
+ chunk = chunk.map { |c| (c + (c < LAP ? ORD_A : DIFF_2)).chr }
32
+ if equals then
33
+ chunk.push "=" until chunk.length >= 8
34
+ end
35
+ res.last << chunk.join
36
+ if width and res.last.length > width then
37
+ res.push res.last.slice! width, 8
38
+ end
39
+ end
40
+ new res.join "\n"
41
+ end
42
+
43
+ private
44
+
45
+ def scan_bufs bytes
46
+ scan_blocks bytes do |s|
47
+ buf, r = 0, 40
48
+ 5.times {
49
+ buf <<= 8
50
+ if (b = s.shift) then
51
+ buf |= b
52
+ r -= 8
53
+ end
54
+ }
55
+ yield buf, r
56
+ end
57
+ end
58
+
59
+ def scan_blocks bytes
60
+ i = 0
61
+ loop do
62
+ s = bytes.byteslice i, 5
63
+ break unless s.notempty?
64
+ yield s.unpack "C*"
65
+ i += 5
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+
72
+ class Invalid < ArgumentError ; end
73
+
74
+ attr_reader :data
75
+
76
+ def initialize data
77
+ @data = data
78
+ end
79
+
80
+ def decode encoding: nil
81
+ res = ""
82
+ each_block { |buf,n|
83
+ chunk = []
84
+ 5.times {
85
+ chunk.unshift buf & 0xff
86
+ buf >>= 8
87
+ }
88
+ until n >= 40 do
89
+ chunk.pop
90
+ n += 8
91
+ end
92
+ res << (chunk.pack "C*")
93
+ }
94
+ encoding = Encoding.default_external if encoding == :default
95
+ res.force_encoding encoding if encoding
96
+ res
97
+ end
98
+
99
+ private
100
+
101
+ def each_block
102
+ buf, n = 0, 0
103
+ each_char do |c|
104
+ c -= case c
105
+ when ORD_A..ORD_Z then ORD_A
106
+ when ORD_a..ORD_z then ORD_a
107
+ when ORD_2..ORD_7 then DIFF_2
108
+ else raise Invalid, "Character '#{c}'."
109
+ end
110
+ buf <<= 5
111
+ buf |= c
112
+ n += 5
113
+ if n == 40 then
114
+ yield buf, n
115
+ buf, n = 0, 0
116
+ end
117
+ end
118
+ if n.nonzero? then
119
+ buf <<= 40 - n
120
+ yield buf, n
121
+ end
122
+ nil
123
+ end
124
+
125
+ def each_char
126
+ done = false
127
+ @data.each_char { |c|
128
+ case c
129
+ when "=" then done = true ; next
130
+ when "\n" then next
131
+ else raise Invalid, "Material after '=': '#{c}'" if done
132
+ end
133
+ yield c.ord
134
+ }
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
@@ -0,0 +1,21 @@
1
+ #
2
+ # potp/foreign/supplement.rb -- Addition usefull Ruby functions
3
+ #
4
+
5
+ # The purpose of this is simply to reduce dependencies.
6
+
7
+ begin
8
+ require "supplement____"
9
+ rescue LoadError
10
+ class NilClass ; def notempty? ; end ; end
11
+ class String ; def notempty? ; self unless empty? ; end ; end
12
+ class Array ; def notempty? ; self unless empty? ; end ; end
13
+ class <<Struct ; alias [] new ; end
14
+ class String
15
+ def starts_with? oth ; o = oth.to_str ; o.length if start_with? o ; end
16
+ def ends_with? oth ; o = oth.to_str ; length - o.length if end_with? o ; end
17
+ alias starts_with starts_with?
18
+ alias ends_with ends_with?
19
+ end
20
+ end
21
+
data/lib/potp/hotp.rb ADDED
@@ -0,0 +1,29 @@
1
+ #
2
+ # potp/hotp.rb -- HOTP class
3
+ #
4
+
5
+ require "potp/otp"
6
+
7
+
8
+ module POTP
9
+
10
+ class HOTP < OTP
11
+
12
+ SCHEME = "hotp"
13
+
14
+ def verify input, counter, retries: 0
15
+ while retries >= 0 do
16
+ return counter if super input, counter
17
+ counter += 1
18
+ retries -= 1
19
+ end
20
+ end
21
+
22
+ def provisioning_uri counter: 0, **kwargs
23
+ super
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
data/lib/potp/otp.rb ADDED
@@ -0,0 +1,93 @@
1
+ #
2
+ # potp/otp.rb -- OTP class
3
+ #
4
+
5
+ require "potp/foreign/supplement"
6
+ require "openssl"
7
+ require "potp/base32"
8
+
9
+
10
+ module POTP
11
+
12
+ class OTP
13
+
14
+ attr_reader :secret, :digits, :digest
15
+
16
+ DEFAULT_DIGITS = 6
17
+
18
+ def initialize secret, digits: nil, digest: nil, **kwargs
19
+ @secret = secret
20
+ @digits = digits || DEFAULT_DIGITS # Google Authenticate only supports 6 currently
21
+ @digest = digest || "sha1" # Google Authenticate only supports SHA1 currently
22
+ end
23
+
24
+ def at data
25
+ hmac = build_digest data
26
+ code = hmac[ (hmac.last & 0x0f), 4].inject do |c,e| c <<= 8 ; c |= e end
27
+ code &= 0x7fffffff
28
+ s = ""
29
+ @digits.times { code, d = code.divmod 10 ; s << d.to_s }
30
+ s.reverse!
31
+ s
32
+ end
33
+
34
+ def verify input, data
35
+ String === input or raise ArgumentError, "`otp` has to be a String"
36
+ time_constant_compare input, (at data)
37
+ end
38
+
39
+ # https://github.com/google/google-authenticator/wiki/Key-Uri-Format
40
+ # Example additional parameter: image: "https://example.com/icon.png"
41
+ def provisioning_uri name:, issuer: nil, **kwargs
42
+ label = [ issuer, name||""].map { |x| x&.tr ":", "_" }
43
+ parameters = {
44
+ **kwargs,
45
+ digits: (@digits unless @digits == DEFAULT_DIGITS),
46
+ algorithm: (@digest.upcase unless @digest.downcase == "sha1"),
47
+ issuer: issuer,
48
+ secret: @secret,
49
+ }
50
+ build_uri label, parameters
51
+ end
52
+
53
+ private
54
+
55
+ def int_to_bytestring i, padding = 8
56
+ i >= 0 or raise ArgumentError, "#int_to_bytestring requires a positive number"
57
+ result = []
58
+ while i != 0 or padding > 0 do
59
+ c = (i & 0xff).chr
60
+ result.unshift c
61
+ i >>= 8
62
+ padding -= 1
63
+ end
64
+ result.join
65
+ end
66
+
67
+ def time_constant_compare a, b
68
+ a.notempty? and b.notempty? and a == b
69
+ end
70
+
71
+ def build_digest input
72
+ d = OpenSSL::Digest.new @digest
73
+ s = (Base32.new @secret).decode
74
+ i = int_to_bytestring input.to_i
75
+ (OpenSSL::HMAC.digest d, s, i).bytes
76
+ end
77
+
78
+ def build_uri label, parameters
79
+ label.compact!
80
+ label = label.map! { |s| url_encode s }.join ":"
81
+ parameters.reject! { |_,v| v.nil? }
82
+ parameters = parameters.keys.reverse.map { |k| "#{k}=#{url_encode parameters[ k]}" }.join "&"
83
+ "otpauth://#{self.class::SCHEME}/#{label}?#{parameters}"
84
+ end
85
+
86
+ def url_encode str
87
+ str.to_s.gsub %r/([^a-zA-Z0-9_.-])/ do |c| "%%%02X" % c.ord end
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+
@@ -0,0 +1,26 @@
1
+ #
2
+ # potp/random.rb -- Generate random secrets
3
+ #
4
+
5
+ require "potp/base32"
6
+ require "securerandom"
7
+
8
+
9
+ module POTP
10
+
11
+ class Base32
12
+
13
+ class <<self
14
+
15
+ # A byte length of 20 means 160 bits and results in 32 character long base32 value.
16
+ def random byte_length = 20
17
+ rand_bytes = SecureRandom.random_bytes byte_length
18
+ (encode rand_bytes).data
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
data/lib/potp/totp.rb ADDED
@@ -0,0 +1,61 @@
1
+ #
2
+ # potp/totp.rb -- TOTP class
3
+ #
4
+
5
+ require "potp/otp"
6
+
7
+
8
+ module POTP
9
+
10
+ class TOTP < OTP
11
+
12
+ SCHEME = "totp"
13
+
14
+ DEFAULT_INTERVAL = 30
15
+
16
+ attr_reader :interval
17
+
18
+ # @option options [Integer] interval (30) the time interval in seconds for OTP
19
+ # This defaults to 30 which is standard.
20
+ def initialize secret, interval: nil, **kwargs
21
+ @interval = interval || DEFAULT_INTERVAL
22
+ super secret, **kwargs
23
+ end
24
+
25
+ def at time ; super (timeint time) / @interval ; end
26
+ def now ; at Time.now ; end
27
+
28
+ def verify input, drift_ahead: nil, drift_behind: nil, after: nil, at: nil
29
+ fin = now = timeint at||Time.now
30
+ now -= drift_behind if drift_behind
31
+ fin += drift_ahead if drift_ahead
32
+ if after then
33
+ after += @interval
34
+ now = after if now < after
35
+ end
36
+ now -= now % @interval
37
+ while now < fin do
38
+ return now if super input, now
39
+ now += @interval
40
+ end
41
+ nil
42
+ end
43
+
44
+ def provisioning_uri **kwargs
45
+ super **kwargs, period: (@interval unless @interval == TOTP::DEFAULT_INTERVAL)
46
+ end
47
+
48
+ private
49
+
50
+ def timeint time
51
+ case time
52
+ when Integer then time
53
+ when Time then time.utc.to_i
54
+ else time.to_i
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,11 @@
1
+ #
2
+ # potp/version.rb -- Version number
3
+ #
4
+
5
+
6
+ module POTP
7
+
8
+ VERSION = "1.0.0".freeze
9
+
10
+ end
11
+
data/lib/potp.rb ADDED
@@ -0,0 +1,11 @@
1
+ #
2
+ # potp.rb -- The whole potp package
3
+ #
4
+
5
+ require "potp/totp"
6
+ require "potp/hotp"
7
+ require "potp/version"
8
+
9
+ module POTP
10
+ end
11
+
data/potp.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ #
2
+ # potp.gemspec -- Gem Specification
3
+ #
4
+
5
+ require "./lib/potp/version"
6
+
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = "potp"
10
+ s.version = POTP::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.required_ruby_version = ">= 3.1"
13
+ s.summary = "Plain One Time Password Tool"
14
+ s.description = <<~EOT
15
+ A Ruby library for generating and verifying one time passwords,
16
+ both HOTP and TOTP, and includes QR Code provisioning.
17
+ EOT
18
+ s.license = "LicenseRef-LICENSE"
19
+ s.authors = ["Bertram Scharpf"]
20
+ s.email = ["<software@bertram-scharpf.de>"]
21
+ s.homepage = "https://github.com/BertramScharpf/ruby-potp"
22
+
23
+ s.requirements = "Just Ruby and some more if you like"
24
+ unless :full_dependecies then
25
+ s.add_dependency "supplement", "~>2", ">=2.10"
26
+ s.add_dependency "appl", "~>1"
27
+ end
28
+
29
+ s.require_paths = %w(lib)
30
+ s.extensions = %w()
31
+ s.files = Dir[ "lib/**/*.rb", "bin/*", ]
32
+ s.executables = %w(potp)
33
+ s.extra_rdoc_files = %w(LICENSE README.md potp.gemspec)
34
+ end
35
+
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: potp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Bertram Scharpf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ A Ruby library for generating and verifying one time passwords,
15
+ both HOTP and TOTP, and includes QR Code provisioning.
16
+ email:
17
+ - "<software@bertram-scharpf.de>"
18
+ executables:
19
+ - potp
20
+ extensions: []
21
+ extra_rdoc_files:
22
+ - LICENSE
23
+ - README.md
24
+ - potp.gemspec
25
+ files:
26
+ - LICENSE
27
+ - README.md
28
+ - bin/potp
29
+ - lib/potp.rb
30
+ - lib/potp/base32.rb
31
+ - lib/potp/foreign/supplement.rb
32
+ - lib/potp/hotp.rb
33
+ - lib/potp/otp.rb
34
+ - lib/potp/random.rb
35
+ - lib/potp/totp.rb
36
+ - lib/potp/version.rb
37
+ - potp.gemspec
38
+ homepage: https://github.com/BertramScharpf/ruby-potp
39
+ licenses:
40
+ - LicenseRef-LICENSE
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '3.1'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements:
57
+ - Just Ruby and some more if you like
58
+ rubygems_version: 3.5.23
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Plain One Time Password Tool
62
+ test_files: []