yubioath 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8eb587a6db8969e2eda9a0d2d803ac5588436469
4
- data.tar.gz: 7876610c35706ce8c4df01d98eb896aa1ef999a2
3
+ metadata.gz: 8cafd4ebf2444b9e960b25d326c7357d7d31583f
4
+ data.tar.gz: f2a66945a9c2355e24dd1717b58cf57c522473e0
5
5
  SHA512:
6
- metadata.gz: b0c6e55417ec96be82a2783399416b056930399d54ffcf48d8c72433fa1f6183da3f98edecc33b459250b44296f539ab8338aa54fd62bc37e6899a43577e211b
7
- data.tar.gz: 77237c1030bc425b2dd2128d7777dae8e76a67086843542058f03f097f509ddcca15d171eebfe25853c29d6419db69998d628138b27428e915b0ba746f80dc0c
6
+ metadata.gz: 90d0ee58ddb0904823589b38a142905f5321dfcd00bd0b5692e5d16aa9e778373e0391935fff8d497201031e0aa18562b709cec0e3c55282c9a11df076d90ac5
7
+ data.tar.gz: 4b447d74069a87c974425b6920c3b52d15e3cb4b8096355b906f849e655955266edad2cd22119e1b1c10a263828143e1c4d927c76ca28ea6528cfa67ecc0c99a
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.2
1
+ 2.2.0
data/exe/yubioath ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path(File.join(__dir__, '..', 'lib'))
3
+ require 'cli'
4
+ CLI.start
data/lib/card.rb CHANGED
@@ -1,13 +1,24 @@
1
1
  require 'smartcard'
2
+ require 'yubioath'
2
3
 
3
4
  class Card
4
- def initialize
5
- Context.new do |context|
5
+ def initialize(name:)
6
+ @name = name
7
+ end
8
+
9
+ def yubioath
10
+ tap do |card|
11
+ yield YubiOATH.new(card)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def tap(&block)
18
+ Context.tap do |context|
6
19
  begin
7
- fail 'Unable to find exactly ONE smart card' unless context.readers.count == 1
8
- name = context.readers.first
9
- card = context.card(name)
10
- yield card
20
+ card = context.card(@name)
21
+ block.call(card)
11
22
  ensure
12
23
  card.disconnect unless card.nil?
13
24
  end
@@ -15,9 +26,9 @@ class Card
15
26
  end
16
27
 
17
28
  class Context
18
- def initialize
29
+ def self.tap(&block)
19
30
  context = Smartcard::PCSC::Context.new
20
- yield context
31
+ block.call(context)
21
32
  ensure
22
33
  context.release
23
34
  end
data/lib/cli.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'thor'
2
+ require 'card'
3
+
4
+ class CLI < Thor
5
+ package_name 'yubioath'
6
+
7
+ desc 'list', 'list current OTP tokens'
8
+ def list
9
+ card.yubioath do |yubioath|
10
+ all = yubioath.calculate_all(timestamp: Time.now)
11
+
12
+ STDOUT.puts
13
+ STDOUT.puts 'YubiOATH Tokens:'
14
+ STDOUT.puts '----------------'
15
+
16
+ all[:codes].each_with_index do |token, index|
17
+ STDOUT.puts "#{index+1}. #{token.name}: #{token.code}"
18
+ end
19
+ end
20
+ end
21
+
22
+ desc 'token NAME', 'get the current OTP value for NAME'
23
+ def token(name)
24
+ card.yubioath do |yubioath|
25
+ token = yubioath.calculate(name: name, timestamp: Time.now)
26
+ STDOUT.print token.code
27
+ end
28
+ end
29
+
30
+ desc 'add NAME SECRET', 'add a new OTP secret'
31
+ def add(name, secret)
32
+ card.yubioath do |yubioath|
33
+ response = yubioath.put(name: name, secret: secret, algorithm: 'SHA256', type: 'totp', digits: 6)
34
+ throw unless response.success?
35
+ end
36
+ end
37
+
38
+ desc 'delete NAME', 'remove the OTP token called NAME'
39
+ def delete(name)
40
+ card.yubioath do |yubioath|
41
+ response = yubioath.send(name: name)
42
+ throw unless response.success?
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def card
49
+ @card ||= Card.new(name: 'Yubico Yubikey NEO OTP+CCID')
50
+ end
51
+ end
data/lib/yubioath.rb CHANGED
@@ -1,4 +1,65 @@
1
- module YubiOATH
2
- CLA = 0x00
1
+ require 'bindata'
2
+ require 'yubioath/instructions'
3
+ require 'yubioath/response'
4
+
5
+ class YubiOATH
3
6
  AID = [0xA0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
7
+
8
+ def initialize(card)
9
+ @card = card
10
+ select(AID)
11
+ end
12
+
13
+ def calculate(name:, timestamp:)
14
+ data = Calculate::Request::Data.new(name: name, timestamp: timestamp.to_i / 30)
15
+ request = Calculate::Request.new(data: data.to_binary_s)
16
+ response = Response.read(@card.transmit(request.to_binary_s))
17
+ throw unless response.success?
18
+ Calculate::Response.read(response.data)
19
+ end
20
+
21
+ def calculate_all(timestamp:)
22
+ data = CalculateAll::Request::Data.new(timestamp: timestamp.to_i / 30)
23
+ request = CalculateAll::Request.new(data: data.to_binary_s)
24
+ response = Response.read(@card.transmit(request.to_binary_s))
25
+ throw unless response.success?
26
+ CalculateAll::Response.read(response.data)
27
+ end
28
+
29
+ def delete(name:)
30
+ data = Delete::Request::Data.new(name: name)
31
+ request = Delete::Request.new(data: data.to_binary_s)
32
+ Response.read(@card.transmit(request.to_binary_s))
33
+ end
34
+
35
+ def list
36
+ request = List::Request.new.to_binary_s
37
+ response = Response.read(@card.transmit(request))
38
+ throw unless response.success?
39
+ List::Response.read(response.data)
40
+ end
41
+
42
+ def put(name:, secret:, algorithm:, type:, digits:)
43
+ data = Put::Request::Data.new(
44
+ name: name,
45
+ key_algorithm: (Put::ALGORITHMS[algorithm] | Put::TYPES[type]),
46
+ digits: digits,
47
+ secret: secret,
48
+ )
49
+ request = Put::Request.new(data: data.to_binary_s)
50
+ Response.read(@card.transmit(request.to_binary_s))
51
+ end
52
+
53
+ def reset
54
+ Response.read(@card.transmit(Reset::Request.new.to_binary_s))
55
+ end
56
+
57
+ private
58
+
59
+ def select(aid)
60
+ request = Select::Request.new(aid: aid).to_binary_s
61
+ response = Response.read(@card.transmit(request))
62
+ throw unless response.success?
63
+ Select::Response.read(response.data)
64
+ end
4
65
  end
@@ -0,0 +1,39 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class Calculate
5
+ class Request < BinData::Record
6
+ uint8 :cla, value: 0x00
7
+ uint8 :ins, value: 0xA2
8
+ uint8 :p1, value: 0x00
9
+ uint8 :p2, value: 0x01
10
+ uint8 :data_length, value: -> { data.length }
11
+ string :data
12
+
13
+ class Data < BinData::Record
14
+ uint8 :name_tag, value: 0x71
15
+ uint8 :name_length, value: -> { name.length }
16
+ string :name
17
+
18
+ uint8 :challenge_tag, value: 0x74
19
+ uint8 :challenge_length, value: 8
20
+ uint64be :timestamp
21
+ end
22
+ end
23
+
24
+ class Response < BinData::Record
25
+ class Code < BinData::Record
26
+ uint8 :digits
27
+ uint32be :response
28
+
29
+ def to_s
30
+ (response % 10**digits).to_s.rjust(digits, '0')
31
+ end
32
+ end
33
+
34
+ uint8 :response_tag
35
+ uint8 :response_length
36
+ code :code, read_length: :response_length
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class CalculateAll
5
+ class Request < BinData::Record
6
+ uint8 :cla, value: 0x00
7
+ uint8 :ins, value: 0xA4
8
+ uint8 :p1, value: 0x00
9
+ uint8 :p2, value: 0x01
10
+ uint8 :data_length, value: -> { data.length }
11
+ string :data
12
+
13
+ class Data < BinData::Record
14
+ uint8 :challenge_tag, value: 0x74
15
+ uint8 :challenge_length, value: 8
16
+ uint64be :timestamp
17
+ end
18
+ end
19
+
20
+ class Response < BinData::Record
21
+ class Code < BinData::Record
22
+ uint8 :digits
23
+ uint32be :response
24
+
25
+ def to_s
26
+ (response % 10**digits).to_s.rjust(digits, '0')
27
+ end
28
+ end
29
+
30
+ array :codes, read_until: :eof do
31
+ uint8 :name_tag
32
+ uint8 :name_length
33
+ string :name, read_length: :name_length
34
+
35
+ uint8 :response_tag
36
+ uint8 :response_length
37
+ code :code, read_length: :response_length
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class Delete
5
+ class Request < BinData::Record
6
+ uint8 :cla, value: 0x00
7
+ uint8 :ins, value: 0x02
8
+ uint8 :p1, value: 0x00
9
+ uint8 :p2, value: 0x00
10
+ uint8 :data_length, value: -> { data.length }
11
+ string :data
12
+
13
+ class Data < BinData::Record
14
+ uint8 :name_tag, value: 0x71
15
+ uint8 :name_length, value: -> { name.length }
16
+ string :name
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ require 'yubioath/calculate'
2
+ require 'yubioath/calculate_all'
3
+ require 'yubioath/delete'
4
+ require 'yubioath/list'
5
+ require 'yubioath/put'
6
+ require 'yubioath/reset'
7
+ require 'yubioath/select'
@@ -0,0 +1,20 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class List
5
+ class Request < BinData::Record
6
+ uint8 :cla, value: 0x00
7
+ uint8 :ins, value: 0xA1
8
+ uint8 :p1, value: 0x00
9
+ uint8 :p2, value: 0x00
10
+ end
11
+
12
+ class Response < BinData::Record
13
+ array :codes, read_until: :eof do
14
+ uint8 :name_tag
15
+ uint8 :name_length
16
+ string :name, read_length: :name_length
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class Put
5
+ ALGORITHMS = {'SHA1' => 0x01, 'SHA256' => 0x02}
6
+ TYPES = {'hotp' => 0x10, 'totp' => 0x20}
7
+
8
+ class Request < BinData::Record
9
+ uint8 :cla, value: 0x00
10
+ uint8 :ins, value: 0x01
11
+ uint8 :p1, value: 0x00
12
+ uint8 :p2, value: 0x00
13
+ uint8 :data_length, value: -> { data.length }
14
+ string :data
15
+
16
+ class Data < BinData::Record
17
+ uint8 :name_tag, value: 0x71
18
+ uint8 :name_length, value: -> { name.length }
19
+ string :name
20
+
21
+ uint8 :key_tag, value: 0x73
22
+ uint8 :key_length, value: -> { secret.length + 2 }
23
+ uint8 :key_algorithm
24
+
25
+ uint8 :digits
26
+ string :secret
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class Reset
5
+ class Request < BinData::Record
6
+ uint8 :cla, value: 0x00
7
+ uint8 :ins, value: 0x04
8
+ uint8 :p1, value: 0xDE
9
+ uint8 :p2, value: 0xAD
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class Response < BinData::Record
5
+ count_bytes_remaining :data_length
6
+ string :data, read_length: -> { data_length - 2 }
7
+ array :success, type: :uint8, initial_length: 2
8
+
9
+ def success?
10
+ success == [0x90, 0x00]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ require 'bindata'
2
+
3
+ class YubiOATH
4
+ class Select
5
+ class Request < BinData::Record
6
+ uint8 :cla, value: 0x00
7
+ uint8 :ins, value: 0xA4
8
+ uint8 :p1, value: 0x04
9
+ uint8 :p2, value: 0x00
10
+ uint8 :aid_length, value: -> { aid.length }
11
+ array :aid, type: :uint8
12
+ end
13
+
14
+ class Response < BinData::Record
15
+ uint8 :version_tag, assert: 0x79
16
+ uint8 :version_length
17
+ array :version, type: :uint8, initial_length: :version_length
18
+
19
+ uint8 :name_tag, assert: 0x71
20
+ uint8 :name_length
21
+ array :name, type: :uint8, initial_length: :name_length
22
+ end
23
+ end
24
+ end
data/yubioath.gemspec CHANGED
@@ -4,20 +4,22 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'yubioath'
7
- spec.version = '0.0.1'
7
+ spec.version = '0.1.0'
8
8
  spec.authors = ['James Ottaway']
9
- spec.email = ['git@james.ottaway.io']
9
+ spec.email = ['yubioath@james.ottaway.io']
10
10
  spec.summary = 'Securely manage your 2FA tokens using your Yubikey NEO'
11
11
  spec.homepage = 'https://github.com/jamesottaway/yubioath'
12
12
  spec.license = 'MIT'
13
13
 
14
14
  spec.files = `git ls-files -z`.split("\x0")
15
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.bindir = "exe"
16
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
16
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
18
  spec.require_paths = ['lib']
18
19
 
20
+ spec.add_dependency 'thor', '~> 0.19.1'
19
21
  spec.add_dependency 'bindata', '~> 2.1'
20
- spec.add_dependency 'smartcard', '~> 0.5'
22
+ spec.add_dependency 'smartcard', '~> 0.5.5'
21
23
 
22
24
  spec.add_development_dependency 'bundler', '~> 1.7'
23
25
  spec.add_development_dependency 'rake', '~> 10.0'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yubioath
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Ottaway
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2015-03-16 00:00:00.000000000 Z
11
+ date: 2015-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.19.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bindata
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -30,14 +44,14 @@ dependencies:
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '0.5'
47
+ version: 0.5.5
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '0.5'
54
+ version: 0.5.5
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -68,8 +82,9 @@ dependencies:
68
82
  version: '10.0'
69
83
  description:
70
84
  email:
71
- - git@james.ottaway.io
72
- executables: []
85
+ - yubioath@james.ottaway.io
86
+ executables:
87
+ - yubioath
73
88
  extensions: []
74
89
  extra_rdoc_files: []
75
90
  files:
@@ -80,8 +95,19 @@ files:
80
95
  - LICENSE.txt
81
96
  - README.md
82
97
  - Rakefile
98
+ - exe/yubioath
83
99
  - lib/card.rb
100
+ - lib/cli.rb
84
101
  - lib/yubioath.rb
102
+ - lib/yubioath/calculate.rb
103
+ - lib/yubioath/calculate_all.rb
104
+ - lib/yubioath/delete.rb
105
+ - lib/yubioath/instructions.rb
106
+ - lib/yubioath/list.rb
107
+ - lib/yubioath/put.rb
108
+ - lib/yubioath/reset.rb
109
+ - lib/yubioath/response.rb
110
+ - lib/yubioath/select.rb
85
111
  - yubioath.gemspec
86
112
  homepage: https://github.com/jamesottaway/yubioath
87
113
  licenses: