saro-dat 1.0.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 +7 -0
- data/.gitignore +16 -0
- data/.idea/.gitignore +10 -0
- data/.idea/modules.xml +8 -0
- data/.idea/saro-dat.iml +22 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +39 -0
- data/LICENSE +21 -0
- data/PUBLISH.md +32 -0
- data/README.md +62 -0
- data/lib/saro/dat/crypto.rb +100 -0
- data/lib/saro/dat/dat.rb +77 -0
- data/lib/saro/dat/dat_certificate.rb +59 -0
- data/lib/saro/dat/dat_manager.rb +114 -0
- data/lib/saro/dat/signature.rb +226 -0
- data/lib/saro/dat/util.rb +42 -0
- data/lib/saro-dat.rb +14 -0
- data/saro-dat.gemspec +37 -0
- metadata +146 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8a79bc8cd93aba476a9f69fef214dd4a0ba9275a4bc93bebb4e1e4e78f36bb19
|
|
4
|
+
data.tar.gz: 8a16197d29b030c965f8a9e30d80fc7decad1d94be60272b3ef22734e1eeb042
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b9fa121db64bb0f59429e543ef49efa921afdd8a87eeb52a232352a9bc09dcd77f33898fa89dbe1031adc1d28bdd575f8c997deaf15265c804668a4e9ac2ea7b
|
|
7
|
+
data.tar.gz: fc15628f82a197628bb41e686392d7b815bb1b39d2146fbd27bab85209ab8351362239dd6c98aa2de3ef214db943e6b0112b0c0a20de693481241e2e788da8b9
|
data/.gitignore
ADDED
data/.idea/.gitignore
ADDED
data/.idea/modules.xml
ADDED
data/.idea/saro-dat.iml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
|
3
|
+
<component name="ModuleRunConfigurationManager">
|
|
4
|
+
<shared />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="NewModuleRootManager">
|
|
7
|
+
<content url="file://$MODULE_DIR$">
|
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
|
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
|
11
|
+
</content>
|
|
12
|
+
<orderEntry type="jdk" jdkName="rbenv: 4.0.5" jdkType="RUBY_SDK" />
|
|
13
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
14
|
+
<orderEntry type="library" scope="PROVIDED" name="base64 (v0.3.0, rbenv: 4.0.5) [gem]" level="application" />
|
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="benchmark (v0.5.0, rbenv: 4.0.5) [gem]" level="application" />
|
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v4.0.10, rbenv: 4.0.5) [gem]" level="application" />
|
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.6, rbenv: 4.0.5) [gem]" level="application" />
|
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.27.0, rbenv: 4.0.5) [gem]" level="application" />
|
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="openssl (v4.0.2, rbenv: 4.0.5) [gem]" level="application" />
|
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="parallel (v2.1.0, rbenv: 4.0.5) [gem]" level="application" />
|
|
21
|
+
</component>
|
|
22
|
+
</module>
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.5
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
saro-dat (1.0.0)
|
|
5
|
+
base64
|
|
6
|
+
concurrent-ruby (~> 1.3.6)
|
|
7
|
+
openssl (~> 4.0.2)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
base64 (0.3.0)
|
|
13
|
+
benchmark (0.5.0)
|
|
14
|
+
concurrent-ruby (1.3.6)
|
|
15
|
+
minitest (5.27.0)
|
|
16
|
+
openssl (4.0.2)
|
|
17
|
+
parallel (2.1.0)
|
|
18
|
+
|
|
19
|
+
PLATFORMS
|
|
20
|
+
arm64-darwin-25
|
|
21
|
+
ruby
|
|
22
|
+
|
|
23
|
+
DEPENDENCIES
|
|
24
|
+
benchmark
|
|
25
|
+
minitest (~> 5.0)
|
|
26
|
+
parallel
|
|
27
|
+
saro-dat!
|
|
28
|
+
|
|
29
|
+
CHECKSUMS
|
|
30
|
+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
31
|
+
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
|
|
32
|
+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
33
|
+
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
|
|
34
|
+
openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2
|
|
35
|
+
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
36
|
+
saro-dat (1.0.0)
|
|
37
|
+
|
|
38
|
+
BUNDLED WITH
|
|
39
|
+
4.0.10
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 SARO Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/PUBLISH.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## sync
|
|
2
|
+
```shell
|
|
3
|
+
rm -rf Gemfile.lock
|
|
4
|
+
bundle install
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
## publish
|
|
9
|
+
```
|
|
10
|
+
gem build saro-dat.gemspec
|
|
11
|
+
gem signin
|
|
12
|
+
gem push saro-dat-1.0.0.gem
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## install
|
|
16
|
+
```
|
|
17
|
+
# install
|
|
18
|
+
brew install rbenv ruby-build
|
|
19
|
+
|
|
20
|
+
# mac -
|
|
21
|
+
echo 'eval "$(rbenv init -)"' >> ~/.zshrc
|
|
22
|
+
source ~/.zshrc
|
|
23
|
+
# - mac
|
|
24
|
+
|
|
25
|
+
rbenv install -l
|
|
26
|
+
rbenv install 2.7.8
|
|
27
|
+
rbenv install 4.0.5
|
|
28
|
+
rbenv local 4.0.5
|
|
29
|
+
rbenv rehash
|
|
30
|
+
|
|
31
|
+
ruby -v
|
|
32
|
+
```
|
data/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# DAT - Distributed Access Token
|
|
2
|
+
|
|
3
|
+
## Document
|
|
4
|
+
|
|
5
|
+
### [DAT Run Online](https://dat.saro.me)
|
|
6
|
+
|
|
7
|
+
### [What is DAT](https://dat.saro.me/--/intro)
|
|
8
|
+
|
|
9
|
+
### [Example](https://dat.saro.me/--/libs/gems-saro-dat)
|
|
10
|
+
|
|
11
|
+
## support signature algorithm
|
|
12
|
+
| name | algorithm |
|
|
13
|
+
|--------|------------|
|
|
14
|
+
| P256 | secp256r1 |
|
|
15
|
+
| P384 | secp384r1 |
|
|
16
|
+
| P521 | secp521r1 |
|
|
17
|
+
|
|
18
|
+
## support crypto algorithm
|
|
19
|
+
| name | algorithm |
|
|
20
|
+
|------------|-----------------------------|
|
|
21
|
+
| AES128GCMN | aes-128-gcm n(nonce + body) |
|
|
22
|
+
| AES256GCMN | aes-256-cbc n(nonce + body) |
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Performance
|
|
26
|
+
- random plain and secure test
|
|
27
|
+
- mac mini m4 2024 basic (10 core)
|
|
28
|
+
- [test_bench.rb](test/test_bench.rb)
|
|
29
|
+
```
|
|
30
|
+
Testing started at ...
|
|
31
|
+
Performance Test (Plain, Secure)
|
|
32
|
+
Plain: orSsnyfSfREmqzSp25qVE0XEXHBa1FgprEq75OBss5MH61jnWXMYypt4GOOLC6ZF29XUJapVv5X3p2gzBG5fLREFRqJdMaKrZoFi
|
|
33
|
+
Secure: 8SkeUWIcp3Q2I104LgCpDZ4CZ6Uwd21j2QPerHtw5RRN2kPns6wwxjdipCCt5l9QdforbqqjQu5BfevfeC1id6OlBjPPHcF9oMpb
|
|
34
|
+
|
|
35
|
+
--- Multi-Thread ---
|
|
36
|
+
P256 AES128GCMN Issue * 10000 : 158ms
|
|
37
|
+
P256 AES128GCMN Parse * 10000 : 201ms
|
|
38
|
+
P256 AES256GCMN Issue * 10000 : 154ms
|
|
39
|
+
P256 AES256GCMN Parse * 10000 : 187ms
|
|
40
|
+
P384 AES128GCMN Issue * 10000 : 288ms
|
|
41
|
+
P384 AES128GCMN Parse * 10000 : 450ms
|
|
42
|
+
P384 AES256GCMN Issue * 10000 : 268ms
|
|
43
|
+
P384 AES256GCMN Parse * 10000 : 613ms
|
|
44
|
+
P521 AES128GCMN Issue * 10000 : 405ms
|
|
45
|
+
P521 AES128GCMN Parse * 10000 : 553ms
|
|
46
|
+
P521 AES256GCMN Issue * 10000 : 370ms
|
|
47
|
+
P521 AES256GCMN Parse * 10000 : 609ms
|
|
48
|
+
|
|
49
|
+
--- Single-Thread ---
|
|
50
|
+
P256 AES128GCMN Issue * 10000 : 200ms
|
|
51
|
+
P256 AES128GCMN Parse * 10000 : 415ms
|
|
52
|
+
P256 AES256GCMN Issue * 10000 : 199ms
|
|
53
|
+
P256 AES256GCMN Parse * 10000 : 406ms
|
|
54
|
+
P384 AES128GCMN Issue * 10000 : 1054ms
|
|
55
|
+
P384 AES128GCMN Parse * 10000 : 2151ms
|
|
56
|
+
P384 AES256GCMN Issue * 10000 : 1053ms
|
|
57
|
+
P384 AES256GCMN Parse * 10000 : 2163ms
|
|
58
|
+
P521 AES128GCMN Issue * 10000 : 1420ms
|
|
59
|
+
P521 AES128GCMN Parse * 10000 : 2414ms
|
|
60
|
+
P521 AES256GCMN Issue * 10000 : 1420ms
|
|
61
|
+
P521 AES256GCMN Parse * 10000 : 2458ms
|
|
62
|
+
```
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require_relative 'util'
|
|
6
|
+
|
|
7
|
+
module Saro
|
|
8
|
+
module Dat
|
|
9
|
+
class DatCryptoAlgorithm
|
|
10
|
+
AES128GCMN = "AES128GCMN"
|
|
11
|
+
AES256GCMN = "AES256GCMN"
|
|
12
|
+
|
|
13
|
+
def self.all
|
|
14
|
+
[AES128GCMN, AES256GCMN]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
CRYPTO_CONFIG = {
|
|
19
|
+
"AES128GCMN" => { name: "aes-128-gcm", length: 16 },
|
|
20
|
+
"AES256GCMN" => { name: "aes-256-gcm", length: 32 }
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
def self.get_crypto_config(algorithm)
|
|
24
|
+
config = CRYPTO_CONFIG[algorithm]
|
|
25
|
+
return config if config
|
|
26
|
+
raise ArgumentError, "Unsupported DAT Crypto Algorithm: #{algorithm}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class DatCryptoKey
|
|
30
|
+
attr_reader :algorithm
|
|
31
|
+
|
|
32
|
+
def initialize(algorithm, key_bytes, config = nil)
|
|
33
|
+
@config = config || Saro::Dat.get_crypto_config(algorithm)
|
|
34
|
+
@algorithm = algorithm
|
|
35
|
+
@key_bytes = key_bytes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.generate(algorithm)
|
|
39
|
+
config = Saro::Dat.get_crypto_config(algorithm)
|
|
40
|
+
key_bytes = OpenSSL::Random.random_bytes(config[:length])
|
|
41
|
+
new(algorithm, key_bytes, config)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.imports(algorithm, raw)
|
|
45
|
+
new(algorithm, raw)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def exports
|
|
49
|
+
@key_bytes
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def encrypt(data)
|
|
53
|
+
data = data.encode('utf-8') if data.is_a?(String) && data.encoding != Encoding::BINARY
|
|
54
|
+
return "".b if data.nil? || data.empty?
|
|
55
|
+
|
|
56
|
+
cipher = OpenSSL::Cipher.new(@config[:name])
|
|
57
|
+
cipher.encrypt
|
|
58
|
+
cipher.key = @key_bytes
|
|
59
|
+
nonce = OpenSSL::Random.random_bytes(12)
|
|
60
|
+
cipher.iv_len = 12
|
|
61
|
+
cipher.iv = nonce
|
|
62
|
+
|
|
63
|
+
ciphertext = cipher.update(data) + cipher.final
|
|
64
|
+
tag = cipher.auth_tag
|
|
65
|
+
|
|
66
|
+
nonce + ciphertext + tag
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def decrypt(data)
|
|
70
|
+
if data.is_a?(String) && data.encoding != Encoding::BINARY
|
|
71
|
+
data = Saro::Dat::Util.decode_base64_url(data)
|
|
72
|
+
end
|
|
73
|
+
return "".b if data.nil? || data.empty?
|
|
74
|
+
|
|
75
|
+
if data.length <= 12 + 16 # nonce(12) + tag(16)
|
|
76
|
+
raise ArgumentError, "Invalid data length"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
nonce = data[0, 12]
|
|
80
|
+
tag = data[-16, 16]
|
|
81
|
+
ciphertext = data[12...-16]
|
|
82
|
+
|
|
83
|
+
cipher = OpenSSL::Cipher.new(@config[:name])
|
|
84
|
+
cipher.decrypt
|
|
85
|
+
cipher.key = @key_bytes
|
|
86
|
+
cipher.iv_len = 12
|
|
87
|
+
cipher.iv = nonce
|
|
88
|
+
cipher.auth_tag = tag
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
res = cipher.update(ciphertext) + cipher.final
|
|
92
|
+
res.force_encoding('BINARY')
|
|
93
|
+
res
|
|
94
|
+
rescue OpenSSL::Cipher::CipherError => e
|
|
95
|
+
raise ArgumentError, "Decryption failed: #{e.message}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/saro/dat/dat.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'util'
|
|
4
|
+
|
|
5
|
+
module Saro
|
|
6
|
+
module Dat
|
|
7
|
+
class Dat
|
|
8
|
+
attr_reader :dat, :expire, :cid, :plain, :secure, :signature, :format
|
|
9
|
+
|
|
10
|
+
def initialize(dat_str)
|
|
11
|
+
@dat = dat_str || ''
|
|
12
|
+
@format = false
|
|
13
|
+
@expire = 0
|
|
14
|
+
@cid = 0
|
|
15
|
+
@plain = "".b
|
|
16
|
+
@secure = "".b
|
|
17
|
+
@signature = "".b
|
|
18
|
+
|
|
19
|
+
if !@dat.empty?
|
|
20
|
+
parts = @dat.split('.')
|
|
21
|
+
if parts.length == 5
|
|
22
|
+
begin
|
|
23
|
+
@expire = parts[0].to_i
|
|
24
|
+
@cid = parts[1].to_i(16)
|
|
25
|
+
@plain = Saro::Dat::Util.decode_base64_url(parts[2])
|
|
26
|
+
@secure = Saro::Dat::Util.decode_base64_url(parts[3])
|
|
27
|
+
@signature = Saro::Dat::Util.decode_base64_url(parts[4])
|
|
28
|
+
@format = (!@signature.empty? && @expire >= 0)
|
|
29
|
+
rescue StandardError
|
|
30
|
+
@format = false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.from_value(value)
|
|
37
|
+
return value if value.is_a?(Dat)
|
|
38
|
+
new(value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def expired?
|
|
42
|
+
return true unless @format
|
|
43
|
+
Time.now.to_i > @expire
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def body_string
|
|
47
|
+
return "" unless @dat.include?('.')
|
|
48
|
+
@dat.rpartition('.').first
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class DatPayload
|
|
53
|
+
attr_reader :plain_bytes, :secure_bytes
|
|
54
|
+
|
|
55
|
+
def initialize(plain, secure)
|
|
56
|
+
@plain_bytes = plain
|
|
57
|
+
@secure_bytes = secure
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def plain
|
|
61
|
+
@plain_bytes.force_encoding('utf-8')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def secure
|
|
65
|
+
@secure_bytes.force_encoding('utf-8')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
"#{Saro::Dat::Util.encode_base64_url_str(@plain_bytes)} #{Saro::Dat::Util.encode_base64_url_str(@secure_bytes)}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_unsafe_string
|
|
73
|
+
"#{plain} #{secure}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'crypto'
|
|
4
|
+
require_relative 'signature'
|
|
5
|
+
require_relative 'util'
|
|
6
|
+
|
|
7
|
+
module Saro
|
|
8
|
+
module Dat
|
|
9
|
+
class DatCertificate
|
|
10
|
+
attr_reader :cid, :signature_key, :crypto_key, :dat_issue_begin, :dat_issue_end, :dat_ttl
|
|
11
|
+
|
|
12
|
+
def initialize(cid, signature_key, crypto_key, dat_issue_begin, dat_issue_end, dat_ttl)
|
|
13
|
+
@cid = cid
|
|
14
|
+
@signature_key = signature_key
|
|
15
|
+
@crypto_key = crypto_key
|
|
16
|
+
@dat_issue_begin = dat_issue_begin
|
|
17
|
+
@dat_issue_end = dat_issue_end
|
|
18
|
+
@dat_ttl = dat_ttl
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def exports(option)
|
|
22
|
+
cid_hex = @cid.to_s(16)
|
|
23
|
+
sig_alg = @signature_key.algorithm
|
|
24
|
+
sig_key = @signature_key.exports(option)
|
|
25
|
+
cry_alg = @crypto_key.algorithm
|
|
26
|
+
cry_key = Saro::Dat::Util.encode_base64_url_str(@crypto_key.exports)
|
|
27
|
+
|
|
28
|
+
"#{cid_hex}.#{sig_alg}.#{sig_key}.#{cry_alg}.#{cry_key}.#{@dat_issue_begin}.#{@dat_issue_end}.#{@dat_ttl}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.imports(format_str)
|
|
32
|
+
split = format_str.split(".")
|
|
33
|
+
raise ArgumentError, "Invalid Certificate format" if split.length != 8
|
|
34
|
+
|
|
35
|
+
cid = split[0].to_i(16)
|
|
36
|
+
sig_key = Saro::Dat::DatSignatureKey.imports(split[1], split[2])
|
|
37
|
+
cry_key = Saro::Dat::DatCryptoKey.imports(split[3], Saro::Dat::Util.decode_base64_url(split[4]))
|
|
38
|
+
|
|
39
|
+
new(
|
|
40
|
+
cid, sig_key, cry_key,
|
|
41
|
+
split[5].to_i, split[6].to_i, split[7].to_i
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def issuable?
|
|
46
|
+
now = Time.now.to_i
|
|
47
|
+
has_signing_key? && @dat_issue_begin <= now && now <= @dat_issue_end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def expired?
|
|
51
|
+
Time.now.to_i > (@dat_issue_end + @dat_ttl)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def has_signing_key?
|
|
55
|
+
@signature_key.has_signing_key?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent-ruby'
|
|
4
|
+
require 'set'
|
|
5
|
+
require_relative 'dat_certificate'
|
|
6
|
+
require_relative 'dat'
|
|
7
|
+
require_relative 'signature'
|
|
8
|
+
require_relative 'util'
|
|
9
|
+
|
|
10
|
+
module Saro
|
|
11
|
+
module Dat
|
|
12
|
+
class DatManager
|
|
13
|
+
def initialize
|
|
14
|
+
@issuer = nil
|
|
15
|
+
@certificates = []
|
|
16
|
+
@lock = Concurrent::ReadWriteLock.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def import_certificates(input_certs, clear: false)
|
|
20
|
+
@lock.with_write_lock do
|
|
21
|
+
certificates = clear ? [] : @certificates.dup
|
|
22
|
+
|
|
23
|
+
before_cids = Set.new(certificates.map(&:cid))
|
|
24
|
+
seen_cids = Set.new
|
|
25
|
+
|
|
26
|
+
input_certs.each do |cert|
|
|
27
|
+
raise ArgumentError, "Duplicate CID: #{cert.cid}" if seen_cids.include?(cert.cid)
|
|
28
|
+
seen_cids.add(cert.cid)
|
|
29
|
+
next if cert.expired?
|
|
30
|
+
next if before_cids.include?(cert.cid)
|
|
31
|
+
|
|
32
|
+
certificates << cert
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
certificates.sort_by!(&:dat_issue_end)
|
|
36
|
+
|
|
37
|
+
# Find latest issuable certificate as issuer
|
|
38
|
+
issuer = certificates.reverse_each.find(&:issuable?)
|
|
39
|
+
|
|
40
|
+
@issuer = issuer
|
|
41
|
+
@certificates = certificates
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def imports(format_str, clear: false)
|
|
46
|
+
certs = []
|
|
47
|
+
format_str.strip.split("\n").each do |line|
|
|
48
|
+
line = line.strip
|
|
49
|
+
next if line.empty?
|
|
50
|
+
certs << Saro::Dat::DatCertificate.imports(line)
|
|
51
|
+
end
|
|
52
|
+
import_certificates(certs, clear: clear)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def exports(option)
|
|
56
|
+
@lock.with_read_lock do
|
|
57
|
+
@certificates.map { |cert| cert.exports(option) }.join("\n")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def issue(plain, secure)
|
|
62
|
+
issuer = @lock.with_read_lock { @issuer }
|
|
63
|
+
raise RuntimeError, "Invalid DAT: Signing Key Does Not Exist" unless issuer
|
|
64
|
+
|
|
65
|
+
self.class._issue(issuer, plain, secure)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parse(dat_input)
|
|
69
|
+
dat = Saro::Dat::Dat.from_value(dat_input)
|
|
70
|
+
raise ArgumentError, "Invalid DAT: Format" unless dat.format
|
|
71
|
+
|
|
72
|
+
certificate = @lock.with_read_lock do
|
|
73
|
+
@certificates.find { |c| c.cid == dat.cid }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
raise ArgumentError, "Invalid DAT: CID(Certificate ID) Not Found" unless certificate
|
|
77
|
+
|
|
78
|
+
self.class._parse(certificate, dat)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def self._issue(cert, plain, secure)
|
|
84
|
+
now = Time.now.to_i
|
|
85
|
+
expire = now + cert.dat_ttl
|
|
86
|
+
cid_hex = cert.cid.to_s(16)
|
|
87
|
+
|
|
88
|
+
plain_bytes = plain.is_a?(String) ? plain.encode('utf-8') : (plain || "".b)
|
|
89
|
+
plain_b64 = Saro::Dat::Util.encode_base64_url_str(plain_bytes)
|
|
90
|
+
|
|
91
|
+
encrypted_secure = cert.crypto_key.encrypt(secure)
|
|
92
|
+
secure_b64 = Saro::Dat::Util.encode_base64_url_str(encrypted_secure)
|
|
93
|
+
|
|
94
|
+
body = "#{expire}.#{cid_hex}.#{plain_b64}.#{secure_b64}"
|
|
95
|
+
signature = Saro::Dat::Util.encode_base64_url_str(cert.signature_key.sign(body))
|
|
96
|
+
|
|
97
|
+
"#{body}.#{signature}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self._parse(cert, dat_input)
|
|
101
|
+
dat = Saro::Dat::Dat.from_value(dat_input)
|
|
102
|
+
raise RuntimeError, "Invalid DAT: Format" unless dat.format
|
|
103
|
+
raise RuntimeError, "Invalid DAT: Expired" if dat.expired?
|
|
104
|
+
|
|
105
|
+
unless cert.signature_key.verify(dat.body_string, dat.signature)
|
|
106
|
+
raise RuntimeError, "Invalid DAT: Signature"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
decrypted_secure = cert.crypto_key.decrypt(dat.secure)
|
|
110
|
+
Saro::Dat::DatPayload.new(dat.plain, decrypted_secure)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require_relative 'util'
|
|
5
|
+
|
|
6
|
+
module Saro
|
|
7
|
+
module Dat
|
|
8
|
+
class DatSignatureAlgorithm
|
|
9
|
+
P256 = "P256"
|
|
10
|
+
P384 = "P384"
|
|
11
|
+
P521 = "P521"
|
|
12
|
+
|
|
13
|
+
def self.all
|
|
14
|
+
[P256, P384, P521]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class DatSignatureKeyExportOption
|
|
19
|
+
PAIR = "PAIR"
|
|
20
|
+
SIGNING = "SIGNING"
|
|
21
|
+
VERIFYING = "VERIFYING"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
SIGNATURE_CONFIG = {
|
|
25
|
+
"P256" => { curve: "prime256v1", hash: "SHA256" },
|
|
26
|
+
"P384" => { curve: "secp384r1", hash: "SHA384" },
|
|
27
|
+
"P521" => { curve: "secp521r1", hash: "SHA512" }
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
def self.get_signature_config(algorithm)
|
|
31
|
+
config = SIGNATURE_CONFIG[algorithm]
|
|
32
|
+
return config if config
|
|
33
|
+
raise ArgumentError, "Unsupported DAT Crypto Algorithm: #{algorithm}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
CURVE_OIDS = {
|
|
37
|
+
"prime256v1" => "1.2.840.10045.3.1.7",
|
|
38
|
+
"secp384r1" => "1.3.132.0.34",
|
|
39
|
+
"secp521r1" => "1.3.132.0.35"
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
class DatSignatureKey
|
|
43
|
+
attr_reader :algorithm, :signing_key, :verifying_key
|
|
44
|
+
|
|
45
|
+
def initialize(algorithm, signing_key, verifying_key, config = nil)
|
|
46
|
+
@algorithm = algorithm
|
|
47
|
+
@signing_key = signing_key
|
|
48
|
+
@verifying_key = verifying_key
|
|
49
|
+
@config = config || Saro::Dat.get_signature_config(algorithm)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private_class_method def self.create_ec_key(curve_name, priv_bn = nil, pub_octet = nil)
|
|
53
|
+
if priv_bn
|
|
54
|
+
# EC Private Key structure (SEC1)
|
|
55
|
+
# ECPrivateKey ::= SEQUENCE {
|
|
56
|
+
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
|
|
57
|
+
# privateKey OCTET STRING,
|
|
58
|
+
# parameters [0] EXPLICIT ECParameters {{ NamedCurve }} OPTIONAL,
|
|
59
|
+
# publicKey [1] EXPLICIT BIT STRING OPTIONAL
|
|
60
|
+
# }
|
|
61
|
+
group = OpenSSL::PKey::EC::Group.new(curve_name)
|
|
62
|
+
pub_octet ||= group.generator.mul(priv_bn).to_octet_string(:uncompressed)
|
|
63
|
+
|
|
64
|
+
asn1 = OpenSSL::ASN1::Sequence.new([
|
|
65
|
+
OpenSSL::ASN1::Integer.new(1),
|
|
66
|
+
OpenSSL::ASN1::OctetString.new(priv_bn.to_s(2).rjust((group.degree + 7) / 8, "\x00".b)),
|
|
67
|
+
OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::ObjectId.new(curve_name)], 0, :CONTEXT_SPECIFIC),
|
|
68
|
+
OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::BitString.new(pub_octet)], 1, :CONTEXT_SPECIFIC)
|
|
69
|
+
])
|
|
70
|
+
OpenSSL::PKey::EC.new(asn1.to_der)
|
|
71
|
+
elsif pub_octet
|
|
72
|
+
# SubjectPublicKeyInfo
|
|
73
|
+
# SEQUENCE {
|
|
74
|
+
# SEQUENCE {
|
|
75
|
+
# OBJECT IDENTIFIER id-ecPublicKey (1.2.840.10045.2.1)
|
|
76
|
+
# OBJECT IDENTIFIER namedCurve
|
|
77
|
+
# }
|
|
78
|
+
# BIT STRING publicKey
|
|
79
|
+
# }
|
|
80
|
+
spki = OpenSSL::ASN1::Sequence.new([
|
|
81
|
+
OpenSSL::ASN1::Sequence.new([
|
|
82
|
+
OpenSSL::ASN1::ObjectId.new("id-ecPublicKey"),
|
|
83
|
+
OpenSSL::ASN1::ObjectId.new(curve_name)
|
|
84
|
+
]),
|
|
85
|
+
OpenSSL::ASN1::BitString.new(pub_octet)
|
|
86
|
+
])
|
|
87
|
+
OpenSSL::PKey::EC.new(spki.to_der)
|
|
88
|
+
else
|
|
89
|
+
raise ArgumentError, "Either private key or public key must be provided"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.generate(algorithm)
|
|
94
|
+
config = Saro::Dat.get_signature_config(algorithm)
|
|
95
|
+
key = OpenSSL::PKey::EC.generate(config[:curve])
|
|
96
|
+
new(algorithm, key, key, config)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.imports(algorithm, format_str)
|
|
100
|
+
config = Saro::Dat.get_signature_config(algorithm)
|
|
101
|
+
parts = format_str.split("~", -1)
|
|
102
|
+
|
|
103
|
+
unless (1..2).cover?(parts.length)
|
|
104
|
+
raise ArgumentError, "Invalid DAT Signature Key Format: No keys found"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
signing_key = nil
|
|
108
|
+
verifying_key = nil
|
|
109
|
+
|
|
110
|
+
if parts[0] && !parts[0].empty?
|
|
111
|
+
d_bytes = Saro::Dat::Util.decode_base64_url(parts[0])
|
|
112
|
+
d_value = OpenSSL::BN.new(d_bytes, 2)
|
|
113
|
+
|
|
114
|
+
# Handle public key if provided in parts[1] for signing key as well
|
|
115
|
+
v_octet = if parts.length == 2 && parts[1] && !parts[1].empty?
|
|
116
|
+
Saro::Dat::Util.decode_base64_url(parts[1])
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
signing_key = create_ec_key(config[:curve], d_value, v_octet)
|
|
120
|
+
verifying_key = signing_key
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if parts.length == 2 && parts[1] && !parts[1].empty?
|
|
124
|
+
unless signing_key
|
|
125
|
+
public_bytes = Saro::Dat::Util.decode_base64_url(parts[1])
|
|
126
|
+
verifying_key = create_ec_key(config[:curve], nil, public_bytes)
|
|
127
|
+
end
|
|
128
|
+
elsif !signing_key
|
|
129
|
+
raise ArgumentError, "Invalid DAT Signature Key Format: No keys found"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
new(algorithm, signing_key, verifying_key, config)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def exports(option)
|
|
136
|
+
rv_parts = ["", ""]
|
|
137
|
+
|
|
138
|
+
if [DatSignatureKeyExportOption::PAIR, DatSignatureKeyExportOption::SIGNING].include?(option)
|
|
139
|
+
if @signing_key&.private_key
|
|
140
|
+
d_value = @signing_key.private_key
|
|
141
|
+
# Ensure fixed length padding
|
|
142
|
+
curve_size = (@signing_key.group.degree + 7) / 8
|
|
143
|
+
d_bytes = d_value.to_s(2).rjust(curve_size, "\x00".b)
|
|
144
|
+
rv_parts[0] = Saro::Dat::Util.encode_base64_url_str(d_bytes)
|
|
145
|
+
elsif option == DatSignatureKeyExportOption::SIGNING
|
|
146
|
+
raise ArgumentError, "Signature key is not supported - verifying only key"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if [DatSignatureKeyExportOption::PAIR, DatSignatureKeyExportOption::VERIFYING].include?(option)
|
|
151
|
+
# Uncompressed point: 0x04 + R + S
|
|
152
|
+
public_bytes = @verifying_key.public_key.to_octet_string(:uncompressed)
|
|
153
|
+
rv_parts[1] = Saro::Dat::Util.encode_base64_url_str(public_bytes)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
case option
|
|
157
|
+
when DatSignatureKeyExportOption::SIGNING
|
|
158
|
+
rv_parts[0]
|
|
159
|
+
when DatSignatureKeyExportOption::VERIFYING
|
|
160
|
+
"~#{rv_parts[1]}"
|
|
161
|
+
else
|
|
162
|
+
"#{rv_parts[0]}~#{rv_parts[1]}"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def sign(body)
|
|
167
|
+
raise ArgumentError, "Signature key is not supported - verifying only key" unless @signing_key
|
|
168
|
+
body = body.encode('utf-8') if body.is_a?(String) && body.encoding != Encoding::BINARY
|
|
169
|
+
raise ArgumentError, "Sign Error - body is empty" if body.nil? || body.empty?
|
|
170
|
+
|
|
171
|
+
digest = OpenSSL::Digest.new(@config[:hash])
|
|
172
|
+
signature_der = @signing_key.dsa_sign_asn1(digest.digest(body))
|
|
173
|
+
|
|
174
|
+
der_to_raw_signature(signature_der)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def verify(body, signature)
|
|
178
|
+
body = body.encode('utf-8') if body.is_a?(String) && body.encoding != Encoding::BINARY
|
|
179
|
+
return false if body.nil? || body.empty?
|
|
180
|
+
|
|
181
|
+
sig_bytes = if signature.is_a?(String) && signature.encoding != Encoding::BINARY
|
|
182
|
+
Saro::Dat::Util.decode_base64_url(signature)
|
|
183
|
+
else
|
|
184
|
+
signature
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
begin
|
|
188
|
+
der_sig = raw_to_der_signature(sig_bytes)
|
|
189
|
+
digest = OpenSSL::Digest.new(@config[:hash])
|
|
190
|
+
@verifying_key.dsa_verify_asn1(digest.digest(body), der_sig)
|
|
191
|
+
rescue StandardError
|
|
192
|
+
false
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def has_signing_key?
|
|
197
|
+
!@signing_key.nil?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def der_to_raw_signature(signature_der)
|
|
203
|
+
asn1 = OpenSSL::ASN1.decode(signature_der)
|
|
204
|
+
r = asn1.value[0].value
|
|
205
|
+
s = asn1.value[1].value
|
|
206
|
+
|
|
207
|
+
size = (@verifying_key.group.degree + 7) / 8
|
|
208
|
+
r_bytes = r.to_s(2).rjust(size, "\x00".b)
|
|
209
|
+
s_bytes = s.to_s(2).rjust(size, "\x00".b)
|
|
210
|
+
|
|
211
|
+
r_bytes + s_bytes
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def raw_to_der_signature(signature_raw)
|
|
215
|
+
size = signature_raw.length / 2
|
|
216
|
+
r = OpenSSL::BN.new(signature_raw[0, size], 2)
|
|
217
|
+
s = OpenSSL::BN.new(signature_raw[size, size], 2)
|
|
218
|
+
|
|
219
|
+
r_asn1 = OpenSSL::ASN1::Integer.new(r)
|
|
220
|
+
s_asn1 = OpenSSL::ASN1::Integer.new(s)
|
|
221
|
+
|
|
222
|
+
OpenSSL::ASN1::Sequence.new([r_asn1, s_asn1]).to_der
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Saro
|
|
6
|
+
module Dat
|
|
7
|
+
module Util
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def encode_base64_url(s)
|
|
11
|
+
return "".b if s.nil?
|
|
12
|
+
if s.is_a?(String)
|
|
13
|
+
return "".b if s.empty?
|
|
14
|
+
s = s.encode('utf-8') unless s.encoding == Encoding::BINARY
|
|
15
|
+
end
|
|
16
|
+
Base64.urlsafe_encode64(s, padding: false).b
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def encode_base64_url_str(s)
|
|
20
|
+
encode_base64_url(s).force_encoding('ascii')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def decode_base64_url(s)
|
|
24
|
+
return "".b if s.nil?
|
|
25
|
+
if s.is_a?(String)
|
|
26
|
+
return "".b if s.empty?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# More robust way for older Ruby
|
|
30
|
+
s = s.to_s.tr('-_', '+/')
|
|
31
|
+
rem = s.bytesize % 4
|
|
32
|
+
s += ("=" * (4 - rem)) if rem > 0
|
|
33
|
+
|
|
34
|
+
Base64.decode64(s).b
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def decode_base64_url_str(s)
|
|
38
|
+
decode_base64_url(s).force_encoding('utf-8')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/saro-dat.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'saro/dat/util'
|
|
4
|
+
require_relative 'saro/dat/crypto'
|
|
5
|
+
require_relative 'saro/dat/signature'
|
|
6
|
+
require_relative 'saro/dat/dat_certificate'
|
|
7
|
+
require_relative 'saro/dat/dat'
|
|
8
|
+
require_relative 'saro/dat/dat_manager'
|
|
9
|
+
|
|
10
|
+
module Saro
|
|
11
|
+
module Dat
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/saro-dat.gemspec
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = "saro-dat"
|
|
5
|
+
spec.version = "1.0.0"
|
|
6
|
+
spec.authors = ["marker"]
|
|
7
|
+
spec.email = ["j@saro.me"]
|
|
8
|
+
|
|
9
|
+
spec.summary = "DAT (Data Access Token) Ruby implementation"
|
|
10
|
+
spec.description = "Ported from Python dat library"
|
|
11
|
+
spec.homepage = "https://dat.saro.me/--/libs/gems-saro-dat"
|
|
12
|
+
spec.license = "MIT"
|
|
13
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
14
|
+
|
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/saro-lab/dat-ruby"
|
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/saro-lab/dat-ruby/blob/main/CHANGELOG.md"
|
|
18
|
+
|
|
19
|
+
spec.metadata["keywords"] = "dat, distributed, access, token, web, session, security, authentication"
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|_pypi)/}) }
|
|
25
|
+
end
|
|
26
|
+
spec.bindir = "exe"
|
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ["lib"]
|
|
29
|
+
|
|
30
|
+
spec.add_dependency "concurrent-ruby", "~> 1.3.6"
|
|
31
|
+
spec.add_dependency "openssl", "~> 4.0.2"
|
|
32
|
+
spec.add_dependency "base64"
|
|
33
|
+
|
|
34
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
35
|
+
spec.add_development_dependency "benchmark"
|
|
36
|
+
spec.add_development_dependency "parallel"
|
|
37
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: saro-dat
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- marker
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: concurrent-ruby
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.3.6
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 1.3.6
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: openssl
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 4.0.2
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 4.0.2
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: base64
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: benchmark
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: parallel
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
description: Ported from Python dat library
|
|
97
|
+
email:
|
|
98
|
+
- j@saro.me
|
|
99
|
+
executables: []
|
|
100
|
+
extensions: []
|
|
101
|
+
extra_rdoc_files: []
|
|
102
|
+
files:
|
|
103
|
+
- ".gitignore"
|
|
104
|
+
- ".idea/.gitignore"
|
|
105
|
+
- ".idea/modules.xml"
|
|
106
|
+
- ".idea/saro-dat.iml"
|
|
107
|
+
- ".ruby-version"
|
|
108
|
+
- Gemfile
|
|
109
|
+
- Gemfile.lock
|
|
110
|
+
- LICENSE
|
|
111
|
+
- PUBLISH.md
|
|
112
|
+
- README.md
|
|
113
|
+
- lib/saro-dat.rb
|
|
114
|
+
- lib/saro/dat/crypto.rb
|
|
115
|
+
- lib/saro/dat/dat.rb
|
|
116
|
+
- lib/saro/dat/dat_certificate.rb
|
|
117
|
+
- lib/saro/dat/dat_manager.rb
|
|
118
|
+
- lib/saro/dat/signature.rb
|
|
119
|
+
- lib/saro/dat/util.rb
|
|
120
|
+
- saro-dat.gemspec
|
|
121
|
+
homepage: https://dat.saro.me/--/libs/gems-saro-dat
|
|
122
|
+
licenses:
|
|
123
|
+
- MIT
|
|
124
|
+
metadata:
|
|
125
|
+
homepage_uri: https://dat.saro.me/--/libs/gems-saro-dat
|
|
126
|
+
source_code_uri: https://github.com/saro-lab/dat-ruby
|
|
127
|
+
changelog_uri: https://github.com/saro-lab/dat-ruby/blob/main/CHANGELOG.md
|
|
128
|
+
keywords: dat, distributed, access, token, web, session, security, authentication
|
|
129
|
+
rdoc_options: []
|
|
130
|
+
require_paths:
|
|
131
|
+
- lib
|
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: 2.7.0
|
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '0'
|
|
142
|
+
requirements: []
|
|
143
|
+
rubygems_version: 4.0.10
|
|
144
|
+
specification_version: 4
|
|
145
|
+
summary: DAT (Data Access Token) Ruby implementation
|
|
146
|
+
test_files: []
|