saro-dat 1.0.0 → 4.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 +4 -4
- data/.gitignore +1 -0
- data/.idea/saro-dat.iml +1 -1
- data/PUBLISH.md +8 -2
- data/README.md +66 -37
- data/lib/saro/dat/crypto.rb +12 -15
- data/lib/saro/dat/dat.rb +3 -1
- data/lib/saro/dat/dat_certificate.rb +45 -22
- data/lib/saro/dat/dat_manager.rb +4 -4
- data/lib/saro/dat/signature.rb +87 -102
- data/saro-dat.gemspec +1 -1
- metadata +2 -3
- data/Gemfile.lock +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1981b6c7f0f207f6388f95f4cafaee56a44c49209b0961567f8f37dc3cb2187
|
|
4
|
+
data.tar.gz: 0e96927af41f3fb6c387a5ffceec5065f8ff6d2e723b8ed6dce41068e7c523fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c4c8b7d3730883f2056841f0018ae4783223ffe417c35861a62ae56d6f0010a5b3d9687adbd6aeb7a87f358d9182af396df007313a493e0073681b4e4b474d72
|
|
7
|
+
data.tar.gz: 7af21459544e31f18c9edcae209fcb3181cdc40d6609e80b65ff8c4b002502c1687e7631e4ae2cd224d01a017447ee421a5ee754602e11d95f4616a792c06169
|
data/.gitignore
CHANGED
data/.idea/saro-dat.iml
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<orderEntry type="sourceFolder" forTests="false" />
|
|
14
14
|
<orderEntry type="library" scope="PROVIDED" name="base64 (v0.3.0, rbenv: 4.0.5) [gem]" level="application" />
|
|
15
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.
|
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v4.0.12, rbenv: 4.0.5) [gem]" level="application" />
|
|
17
17
|
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.6, rbenv: 4.0.5) [gem]" level="application" />
|
|
18
18
|
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.27.0, rbenv: 4.0.5) [gem]" level="application" />
|
|
19
19
|
<orderEntry type="library" scope="PROVIDED" name="openssl (v4.0.2, rbenv: 4.0.5) [gem]" level="application" />
|
data/PUBLISH.md
CHANGED
|
@@ -9,7 +9,7 @@ bundle install
|
|
|
9
9
|
```
|
|
10
10
|
gem build saro-dat.gemspec
|
|
11
11
|
gem signin
|
|
12
|
-
gem push saro-dat-
|
|
12
|
+
gem push saro-dat-4.0.0.gem
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## install
|
|
@@ -23,10 +23,16 @@ source ~/.zshrc
|
|
|
23
23
|
# - mac
|
|
24
24
|
|
|
25
25
|
rbenv install -l
|
|
26
|
-
rbenv install 2.7.8
|
|
27
26
|
rbenv install 4.0.5
|
|
28
27
|
rbenv local 4.0.5
|
|
29
28
|
rbenv rehash
|
|
30
29
|
|
|
31
30
|
ruby -v
|
|
32
31
|
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## install test
|
|
35
|
+
```
|
|
36
|
+
bundle add saro-dat
|
|
37
|
+
bundle install
|
|
38
|
+
```
|
data/README.md
CHANGED
|
@@ -8,18 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
### [Example](https://dat.saro.me/--/libs/gems-saro-dat)
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
|
11
|
+
## Support algorithm
|
|
12
|
+
### Signature
|
|
13
|
+
| name | note |
|
|
14
|
+
|-----------------|-----------------------|
|
|
15
|
+
| ECDSA-P256 | = secp256r1 |
|
|
16
|
+
| ECDSA-P384 | = secp384r1 |
|
|
17
|
+
| ECDSA-P521 | = secp521r1 |
|
|
18
|
+
| HMAC-SHA256-MFS | = 256Bit Fixed Secret |
|
|
19
|
+
| HMAC-SHA384-MFS | = 384Bit Fixed Secret |
|
|
20
|
+
| HMAC-SHA512-MFS | = 512Bit Fixed Secret |
|
|
21
|
+
- MFS : Maximum(Same Bit) Fixed Secret
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
| name |
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
+
### Crypto
|
|
24
|
+
| name | note |
|
|
25
|
+
|------------|-------------------------------|
|
|
26
|
+
| IV-AES128-GCM | (IV=NONCE:96BIT) + AES128 GCM |
|
|
27
|
+
| IV-AES256-GCM | (IV=NONCE:96BIT) + AES256 GCM |
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
# Performance
|
|
@@ -29,34 +34,58 @@
|
|
|
29
34
|
```
|
|
30
35
|
Testing started at ...
|
|
31
36
|
Performance Test (Plain, Secure)
|
|
32
|
-
Plain:
|
|
33
|
-
Secure:
|
|
37
|
+
Plain: CcMjua0RA8I27be6W2lYMBmk5OcPrV8mu3ybrVwBOB2pEPbtfrvH0h0Z0VBhG1ID4zu51sDKxZZGFoku9TzrcPJLbb0ObRiF3NIF
|
|
38
|
+
Secure: EEYEOjriFkidJ8lKpYPkt1fwS01sZuJ7ysgCWd7XyarZfP6yrxkR9rciiJSUpuPZlBt0moFlKb0n2ZDivhvxLNjmO1eT8KjzhLlA
|
|
34
39
|
|
|
35
40
|
--- Multi-Thread ---
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
HMAC-SHA256-MFS IV-AES128-GCM Issue * 10000 : 141ms
|
|
42
|
+
HMAC-SHA256-MFS IV-AES128-GCM Parse * 10000 : 148ms
|
|
43
|
+
HMAC-SHA256-MFS IV-AES256-GCM Issue * 10000 : 126ms
|
|
44
|
+
HMAC-SHA256-MFS IV-AES256-GCM Parse * 10000 : 161ms
|
|
45
|
+
HMAC-SHA384-MFS IV-AES128-GCM Issue * 10000 : 162ms
|
|
46
|
+
HMAC-SHA384-MFS IV-AES128-GCM Parse * 10000 : 155ms
|
|
47
|
+
HMAC-SHA384-MFS IV-AES256-GCM Issue * 10000 : 126ms
|
|
48
|
+
HMAC-SHA384-MFS IV-AES256-GCM Parse * 10000 : 146ms
|
|
49
|
+
HMAC-SHA512-MFS IV-AES128-GCM Issue * 10000 : 124ms
|
|
50
|
+
HMAC-SHA512-MFS IV-AES128-GCM Parse * 10000 : 132ms
|
|
51
|
+
HMAC-SHA512-MFS IV-AES256-GCM Issue * 10000 : 128ms
|
|
52
|
+
HMAC-SHA512-MFS IV-AES256-GCM Parse * 10000 : 149ms
|
|
53
|
+
ECDSA-P256 IV-AES128-GCM Issue * 10000 : 171ms
|
|
54
|
+
ECDSA-P256 IV-AES128-GCM Parse * 10000 : 188ms
|
|
55
|
+
ECDSA-P256 IV-AES256-GCM Issue * 10000 : 158ms
|
|
56
|
+
ECDSA-P256 IV-AES256-GCM Parse * 10000 : 188ms
|
|
57
|
+
ECDSA-P384 IV-AES128-GCM Issue * 10000 : 260ms
|
|
58
|
+
ECDSA-P384 IV-AES128-GCM Parse * 10000 : 422ms
|
|
59
|
+
ECDSA-P384 IV-AES256-GCM Issue * 10000 : 259ms
|
|
60
|
+
ECDSA-P384 IV-AES256-GCM Parse * 10000 : 415ms
|
|
61
|
+
ECDSA-P521 IV-AES128-GCM Issue * 10000 : 307ms
|
|
62
|
+
ECDSA-P521 IV-AES128-GCM Parse * 10000 : 475ms
|
|
63
|
+
ECDSA-P521 IV-AES256-GCM Issue * 10000 : 311ms
|
|
64
|
+
ECDSA-P521 IV-AES256-GCM Parse * 10000 : 481ms
|
|
48
65
|
|
|
49
66
|
--- Single-Thread ---
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
HMAC-SHA256-MFS IV-AES128-GCM Issue * 10000 : 64ms
|
|
68
|
+
HMAC-SHA256-MFS IV-AES128-GCM Parse * 10000 : 65ms
|
|
69
|
+
HMAC-SHA256-MFS IV-AES256-GCM Issue * 10000 : 60ms
|
|
70
|
+
HMAC-SHA256-MFS IV-AES256-GCM Parse * 10000 : 68ms
|
|
71
|
+
HMAC-SHA384-MFS IV-AES128-GCM Issue * 10000 : 66ms
|
|
72
|
+
HMAC-SHA384-MFS IV-AES128-GCM Parse * 10000 : 83ms
|
|
73
|
+
HMAC-SHA384-MFS IV-AES256-GCM Issue * 10000 : 64ms
|
|
74
|
+
HMAC-SHA384-MFS IV-AES256-GCM Parse * 10000 : 67ms
|
|
75
|
+
HMAC-SHA512-MFS IV-AES128-GCM Issue * 10000 : 62ms
|
|
76
|
+
HMAC-SHA512-MFS IV-AES128-GCM Parse * 10000 : 67ms
|
|
77
|
+
HMAC-SHA512-MFS IV-AES256-GCM Issue * 10000 : 66ms
|
|
78
|
+
HMAC-SHA512-MFS IV-AES256-GCM Parse * 10000 : 68ms
|
|
79
|
+
ECDSA-P256 IV-AES128-GCM Issue * 10000 : 192ms
|
|
80
|
+
ECDSA-P256 IV-AES128-GCM Parse * 10000 : 403ms
|
|
81
|
+
ECDSA-P256 IV-AES256-GCM Issue * 10000 : 188ms
|
|
82
|
+
ECDSA-P256 IV-AES256-GCM Parse * 10000 : 404ms
|
|
83
|
+
ECDSA-P384 IV-AES128-GCM Issue * 10000 : 1049ms
|
|
84
|
+
ECDSA-P384 IV-AES128-GCM Parse * 10000 : 2157ms
|
|
85
|
+
ECDSA-P384 IV-AES256-GCM Issue * 10000 : 1060ms
|
|
86
|
+
ECDSA-P384 IV-AES256-GCM Parse * 10000 : 2155ms
|
|
87
|
+
ECDSA-P521 IV-AES128-GCM Issue * 10000 : 1374ms
|
|
88
|
+
ECDSA-P521 IV-AES128-GCM Parse * 10000 : 2428ms
|
|
89
|
+
ECDSA-P521 IV-AES256-GCM Issue * 10000 : 1368ms
|
|
90
|
+
ECDSA-P521 IV-AES256-GCM Parse * 10000 : 2417ms
|
|
62
91
|
```
|
data/lib/saro/dat/crypto.rb
CHANGED
|
@@ -7,8 +7,8 @@ require_relative 'util'
|
|
|
7
7
|
module Saro
|
|
8
8
|
module Dat
|
|
9
9
|
class DatCryptoAlgorithm
|
|
10
|
-
AES128GCMN = "
|
|
11
|
-
AES256GCMN = "
|
|
10
|
+
AES128GCMN = "IV-AES128-GCM"
|
|
11
|
+
AES256GCMN = "IV-AES256-GCM"
|
|
12
12
|
|
|
13
13
|
def self.all
|
|
14
14
|
[AES128GCMN, AES256GCMN]
|
|
@@ -16,8 +16,8 @@ module Saro
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
CRYPTO_CONFIG = {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"IV-AES128-GCM" => { name: "aes-128-gcm", length: 16 },
|
|
20
|
+
"IV-AES256-GCM" => { name: "aes-256-gcm", length: 32 }
|
|
21
21
|
}.freeze
|
|
22
22
|
|
|
23
23
|
def self.get_crypto_config(algorithm)
|
|
@@ -26,7 +26,7 @@ module Saro
|
|
|
26
26
|
raise ArgumentError, "Unsupported DAT Crypto Algorithm: #{algorithm}"
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
class
|
|
29
|
+
class DatCrypto
|
|
30
30
|
attr_reader :algorithm
|
|
31
31
|
|
|
32
32
|
def initialize(algorithm, key_bytes, config = nil)
|
|
@@ -41,12 +41,13 @@ module Saro
|
|
|
41
41
|
new(algorithm, key_bytes, config)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def self.imports(algorithm,
|
|
45
|
-
|
|
44
|
+
def self.imports(algorithm, base64_str)
|
|
45
|
+
key_bytes = Saro::Dat::Util.decode_base64_url(base64_str)
|
|
46
|
+
new(algorithm, key_bytes)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def exports
|
|
49
|
-
@key_bytes
|
|
50
|
+
Saro::Dat::Util.encode_base64_url_str(@key_bytes)
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def encrypt(data)
|
|
@@ -87,13 +88,9 @@ module Saro
|
|
|
87
88
|
cipher.iv = nonce
|
|
88
89
|
cipher.auth_tag = tag
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
res
|
|
94
|
-
rescue OpenSSL::Cipher::CipherError => e
|
|
95
|
-
raise ArgumentError, "Decryption failed: #{e.message}"
|
|
96
|
-
end
|
|
91
|
+
res = cipher.update(ciphertext) + cipher.final
|
|
92
|
+
res.force_encoding('BINARY')
|
|
93
|
+
res
|
|
97
94
|
end
|
|
98
95
|
end
|
|
99
96
|
end
|
data/lib/saro/dat/dat.rb
CHANGED
|
@@ -38,11 +38,13 @@ module Saro
|
|
|
38
38
|
new(value)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def expired
|
|
41
|
+
def expired
|
|
42
42
|
return true unless @format
|
|
43
43
|
Time.now.to_i > @expire
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
alias_method :expired?, :expired
|
|
47
|
+
|
|
46
48
|
def body_string
|
|
47
49
|
return "" unless @dat.include?('.')
|
|
48
50
|
@dat.rpartition('.').first
|
|
@@ -9,51 +9,74 @@ module Saro
|
|
|
9
9
|
class DatCertificate
|
|
10
10
|
attr_reader :cid, :signature_key, :crypto_key, :dat_issue_begin, :dat_issue_end, :dat_ttl
|
|
11
11
|
|
|
12
|
-
def initialize(cid,
|
|
12
|
+
def initialize(cid, issued_at, issuance_duration, dat_ttl, signature_key, crypto_key)
|
|
13
13
|
@cid = cid
|
|
14
|
+
@dat_issue_begin = issued_at
|
|
15
|
+
@dat_issue_end = issued_at + issuance_duration
|
|
16
|
+
@dat_ttl = dat_ttl
|
|
14
17
|
@signature_key = signature_key
|
|
15
18
|
@crypto_key = crypto_key
|
|
16
|
-
@dat_issue_begin = dat_issue_begin
|
|
17
|
-
@dat_issue_end = dat_issue_end
|
|
18
|
-
@dat_ttl = dat_ttl
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def exports(
|
|
21
|
+
def exports(verify_only = false)
|
|
22
22
|
cid_hex = @cid.to_s(16)
|
|
23
|
+
issued_at = @dat_issue_begin.to_s
|
|
24
|
+
issuance_duration = (@dat_issue_end - @dat_issue_begin).to_s
|
|
25
|
+
ttl = @dat_ttl.to_s
|
|
23
26
|
sig_alg = @signature_key.algorithm
|
|
24
|
-
sig_key = @signature_key.exports(option)
|
|
25
27
|
cry_alg = @crypto_key.algorithm
|
|
26
|
-
|
|
28
|
+
sig_key = @signature_key.exports(verify_only)
|
|
29
|
+
cry_key = @crypto_key.exports
|
|
30
|
+
|
|
31
|
+
"#{cid_hex}.#{issued_at}.#{issuance_duration}.#{ttl}.#{sig_alg}.#{cry_alg}.#{sig_key}.#{cry_key}"
|
|
32
|
+
end
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
def self.generate(cid, issued_at, issuance_duration, dat_ttl, signature_algorithm, crypto_algorithm)
|
|
35
|
+
new(
|
|
36
|
+
cid, issued_at, issuance_duration, dat_ttl,
|
|
37
|
+
Saro::Dat::DatSignature.generate(signature_algorithm),
|
|
38
|
+
Saro::Dat::DatCrypto.generate(crypto_algorithm)
|
|
39
|
+
)
|
|
29
40
|
end
|
|
30
41
|
|
|
31
42
|
def self.imports(format_str)
|
|
32
|
-
|
|
33
|
-
raise ArgumentError, "Invalid Certificate format" if
|
|
43
|
+
parts = format_str.split(".")
|
|
44
|
+
raise ArgumentError, "Invalid Certificate format" if parts.length != 8
|
|
34
45
|
|
|
35
|
-
cid =
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
cid = parts[0].to_i(16)
|
|
47
|
+
issued_at = parts[1].to_i
|
|
48
|
+
issuance_duration = parts[2].to_i
|
|
49
|
+
ttl = parts[3].to_i
|
|
50
|
+
sig_algo = parts[4]
|
|
51
|
+
cry_algo = parts[5]
|
|
52
|
+
signature_key = Saro::Dat::DatSignature.imports(sig_algo, parts[6])
|
|
53
|
+
crypto_key = Saro::Dat::DatCrypto.imports(cry_algo, parts[7])
|
|
38
54
|
|
|
39
|
-
new(
|
|
40
|
-
cid, sig_key, cry_key,
|
|
41
|
-
split[5].to_i, split[6].to_i, split[7].to_i
|
|
42
|
-
)
|
|
55
|
+
new(cid, issued_at, issuance_duration, ttl, signature_key, crypto_key)
|
|
43
56
|
end
|
|
44
57
|
|
|
45
|
-
def issuable
|
|
58
|
+
def issuable
|
|
46
59
|
now = Time.now.to_i
|
|
47
|
-
|
|
60
|
+
signable && @dat_issue_begin <= now && now <= @dat_issue_end
|
|
48
61
|
end
|
|
49
62
|
|
|
50
|
-
def expired
|
|
63
|
+
def expired
|
|
51
64
|
Time.now.to_i > (@dat_issue_end + @dat_ttl)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
|
-
def
|
|
55
|
-
@signature_key.
|
|
67
|
+
def signable
|
|
68
|
+
@signature_key.signable
|
|
56
69
|
end
|
|
70
|
+
|
|
71
|
+
def pair
|
|
72
|
+
@signature_key.pair
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# For Ruby conventions
|
|
76
|
+
alias_method :issuable?, :issuable
|
|
77
|
+
alias_method :expired?, :expired
|
|
78
|
+
alias_method :signable?, :signable
|
|
79
|
+
alias_method :pair?, :pair
|
|
57
80
|
end
|
|
58
81
|
end
|
|
59
82
|
end
|
data/lib/saro/dat/dat_manager.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Saro
|
|
|
26
26
|
input_certs.each do |cert|
|
|
27
27
|
raise ArgumentError, "Duplicate CID: #{cert.cid}" if seen_cids.include?(cert.cid)
|
|
28
28
|
seen_cids.add(cert.cid)
|
|
29
|
-
next if cert.expired
|
|
29
|
+
next if cert.expired
|
|
30
30
|
next if before_cids.include?(cert.cid)
|
|
31
31
|
|
|
32
32
|
certificates << cert
|
|
@@ -35,7 +35,7 @@ module Saro
|
|
|
35
35
|
certificates.sort_by!(&:dat_issue_end)
|
|
36
36
|
|
|
37
37
|
# Find latest issuable certificate as issuer
|
|
38
|
-
issuer = certificates.reverse_each.find(&:issuable
|
|
38
|
+
issuer = certificates.reverse_each.find(&:issuable)
|
|
39
39
|
|
|
40
40
|
@issuer = issuer
|
|
41
41
|
@certificates = certificates
|
|
@@ -52,9 +52,9 @@ module Saro
|
|
|
52
52
|
import_certificates(certs, clear: clear)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def exports(
|
|
55
|
+
def exports(verify_only = false)
|
|
56
56
|
@lock.with_read_lock do
|
|
57
|
-
@certificates.map { |cert| cert.exports(
|
|
57
|
+
@certificates.map { |cert| cert.exports(verify_only) }.join("\n")
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
data/lib/saro/dat/signature.rb
CHANGED
|
@@ -6,25 +6,25 @@ require_relative 'util'
|
|
|
6
6
|
module Saro
|
|
7
7
|
module Dat
|
|
8
8
|
class DatSignatureAlgorithm
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
HMAC_SHA256_MFS = "HMAC-SHA256-MFS"
|
|
10
|
+
HMAC_SHA384_MFS = "HMAC-SHA384-MFS"
|
|
11
|
+
HMAC_SHA512_MFS = "HMAC-SHA512-MFS"
|
|
12
|
+
P256 = "ECDSA-P256"
|
|
13
|
+
P384 = "ECDSA-P384"
|
|
14
|
+
P521 = "ECDSA-P521"
|
|
12
15
|
|
|
13
16
|
def self.all
|
|
14
|
-
[P256, P384, P521]
|
|
17
|
+
[HMAC_SHA256_MFS, HMAC_SHA384_MFS, HMAC_SHA512_MFS, P256, P384, P521]
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
|
|
18
|
-
class DatSignatureKeyExportOption
|
|
19
|
-
PAIR = "PAIR"
|
|
20
|
-
SIGNING = "SIGNING"
|
|
21
|
-
VERIFYING = "VERIFYING"
|
|
22
|
-
end
|
|
23
|
-
|
|
24
21
|
SIGNATURE_CONFIG = {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
22
|
+
"HMAC-SHA256-MFS" => { name: "HMAC", hash: "SHA256", hmac_len: 32 },
|
|
23
|
+
"HMAC-SHA384-MFS" => { name: "HMAC", hash: "SHA384", hmac_len: 48 },
|
|
24
|
+
"HMAC-SHA512-MFS" => { name: "HMAC", hash: "SHA512", hmac_len: 64 },
|
|
25
|
+
"ECDSA-P256" => { name: "ECDSA", curve: "prime256v1", hash: "SHA256", private_len: 32, public_len: 65 },
|
|
26
|
+
"ECDSA-P384" => { name: "ECDSA", curve: "secp384r1", hash: "SHA384", private_len: 48, public_len: 97 },
|
|
27
|
+
"ECDSA-P521" => { name: "ECDSA", curve: "secp521r1", hash: "SHA512", private_len: 66, public_len: 133 }
|
|
28
28
|
}.freeze
|
|
29
29
|
|
|
30
30
|
def self.get_signature_config(algorithm)
|
|
@@ -33,13 +33,7 @@ module Saro
|
|
|
33
33
|
raise ArgumentError, "Unsupported DAT Crypto Algorithm: #{algorithm}"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
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
|
|
36
|
+
class DatSignature
|
|
43
37
|
attr_reader :algorithm, :signing_key, :verifying_key
|
|
44
38
|
|
|
45
39
|
def initialize(algorithm, signing_key, verifying_key, config = nil)
|
|
@@ -51,13 +45,6 @@ module Saro
|
|
|
51
45
|
|
|
52
46
|
private_class_method def self.create_ec_key(curve_name, priv_bn = nil, pub_octet = nil)
|
|
53
47
|
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
48
|
group = OpenSSL::PKey::EC::Group.new(curve_name)
|
|
62
49
|
pub_octet ||= group.generator.mul(priv_bn).to_octet_string(:uncompressed)
|
|
63
50
|
|
|
@@ -69,14 +56,6 @@ module Saro
|
|
|
69
56
|
])
|
|
70
57
|
OpenSSL::PKey::EC.new(asn1.to_der)
|
|
71
58
|
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
59
|
spki = OpenSSL::ASN1::Sequence.new([
|
|
81
60
|
OpenSSL::ASN1::Sequence.new([
|
|
82
61
|
OpenSSL::ASN1::ObjectId.new("id-ecPublicKey"),
|
|
@@ -92,75 +71,64 @@ module Saro
|
|
|
92
71
|
|
|
93
72
|
def self.generate(algorithm)
|
|
94
73
|
config = Saro::Dat.get_signature_config(algorithm)
|
|
95
|
-
|
|
96
|
-
|
|
74
|
+
if config[:name] == "HMAC"
|
|
75
|
+
key = OpenSSL::Random.random_bytes(config[:hmac_len])
|
|
76
|
+
new(algorithm, key, key, config)
|
|
77
|
+
else
|
|
78
|
+
key = OpenSSL::PKey::EC.generate(config[:curve])
|
|
79
|
+
new(algorithm, key, key, config)
|
|
80
|
+
end
|
|
97
81
|
end
|
|
98
82
|
|
|
99
|
-
def self.imports(algorithm,
|
|
83
|
+
def self.imports(algorithm, base64_str)
|
|
100
84
|
config = Saro::Dat.get_signature_config(algorithm)
|
|
101
|
-
|
|
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
|
|
85
|
+
bytes_data = Saro::Dat::Util.decode_base64_url(base64_str)
|
|
109
86
|
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
signing_key =
|
|
120
|
-
verifying_key =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
87
|
+
if config[:name] == "HMAC"
|
|
88
|
+
if bytes_data.bytesize != config[:hmac_len]
|
|
89
|
+
raise ArgumentError, "Invalid HMAC key length: expected #{config[:hmac_len]}, got #{bytes_data.bytesize}"
|
|
90
|
+
end
|
|
91
|
+
new(algorithm, bytes_data, bytes_data, config)
|
|
92
|
+
else
|
|
93
|
+
private_len = config[:private_len]
|
|
94
|
+
public_len = config[:public_len]
|
|
95
|
+
|
|
96
|
+
signing_key = nil
|
|
97
|
+
verifying_key = nil
|
|
98
|
+
|
|
99
|
+
if bytes_data.bytesize == private_len + public_len
|
|
100
|
+
private_bytes = bytes_data[0, private_len]
|
|
101
|
+
public_bytes = bytes_data[private_len, public_len]
|
|
102
|
+
|
|
103
|
+
d_value = OpenSSL::BN.new(private_bytes, 2)
|
|
104
|
+
signing_key = create_ec_key(config[:curve], d_value, public_bytes)
|
|
105
|
+
verifying_key = signing_key
|
|
106
|
+
elsif bytes_data.bytesize == public_len
|
|
107
|
+
verifying_key = create_ec_key(config[:curve], nil, bytes_data)
|
|
108
|
+
else
|
|
109
|
+
raise ArgumentError, "Invalid ECDSA key length"
|
|
127
110
|
end
|
|
128
|
-
elsif !signing_key
|
|
129
|
-
raise ArgumentError, "Invalid DAT Signature Key Format: No keys found"
|
|
130
|
-
end
|
|
131
111
|
|
|
132
|
-
|
|
112
|
+
new(algorithm, signing_key, verifying_key, config)
|
|
113
|
+
end
|
|
133
114
|
end
|
|
134
115
|
|
|
135
|
-
def exports(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if
|
|
116
|
+
def exports(verify_only = false)
|
|
117
|
+
if @config[:name] == "HMAC"
|
|
118
|
+
Saro::Dat::Util.encode_base64_url_str(@verifying_key)
|
|
119
|
+
else
|
|
120
|
+
if verify_only || !@signing_key&.private_key
|
|
121
|
+
public_bytes = @verifying_key.public_key.to_octet_string(:uncompressed)
|
|
122
|
+
Saro::Dat::Util.encode_base64_url_str(public_bytes)
|
|
123
|
+
else
|
|
140
124
|
d_value = @signing_key.private_key
|
|
141
|
-
# Ensure fixed length padding
|
|
142
125
|
curve_size = (@signing_key.group.degree + 7) / 8
|
|
143
126
|
d_bytes = d_value.to_s(2).rjust(curve_size, "\x00".b)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
|
|
128
|
+
public_bytes = @verifying_key.public_key.to_octet_string(:uncompressed)
|
|
129
|
+
Saro::Dat::Util.encode_base64_url_str(d_bytes + public_bytes)
|
|
147
130
|
end
|
|
148
131
|
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
132
|
end
|
|
165
133
|
|
|
166
134
|
def sign(body)
|
|
@@ -168,10 +136,13 @@ module Saro
|
|
|
168
136
|
body = body.encode('utf-8') if body.is_a?(String) && body.encoding != Encoding::BINARY
|
|
169
137
|
raise ArgumentError, "Sign Error - body is empty" if body.nil? || body.empty?
|
|
170
138
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
139
|
+
if @config[:name] == "HMAC"
|
|
140
|
+
OpenSSL::HMAC.digest(@config[:hash], @signing_key, body)
|
|
141
|
+
else
|
|
142
|
+
digest = OpenSSL::Digest.new(@config[:hash])
|
|
143
|
+
signature_der = @signing_key.dsa_sign_asn1(digest.digest(body))
|
|
144
|
+
der_to_raw_signature(signature_der)
|
|
145
|
+
end
|
|
175
146
|
end
|
|
176
147
|
|
|
177
148
|
def verify(body, signature)
|
|
@@ -184,19 +155,33 @@ module Saro
|
|
|
184
155
|
signature
|
|
185
156
|
end
|
|
186
157
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
158
|
+
if @config[:name] == "HMAC"
|
|
159
|
+
begin
|
|
160
|
+
actual_sig = OpenSSL::HMAC.digest(@config[:hash], @verifying_key, body)
|
|
161
|
+
# Use fixed-time comparison if possible
|
|
162
|
+
return actual_sig == sig_bytes
|
|
163
|
+
rescue StandardError
|
|
164
|
+
return false
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
begin
|
|
168
|
+
der_sig = raw_to_der_signature(sig_bytes)
|
|
169
|
+
digest = OpenSSL::Digest.new(@config[:hash])
|
|
170
|
+
@verifying_key.dsa_verify_asn1(digest.digest(body), der_sig)
|
|
171
|
+
rescue StandardError
|
|
172
|
+
false
|
|
173
|
+
end
|
|
193
174
|
end
|
|
194
175
|
end
|
|
195
176
|
|
|
196
|
-
def
|
|
177
|
+
def signable
|
|
197
178
|
!@signing_key.nil?
|
|
198
179
|
end
|
|
199
180
|
|
|
181
|
+
def pair
|
|
182
|
+
@config[:name] == "ECDSA"
|
|
183
|
+
end
|
|
184
|
+
|
|
200
185
|
private
|
|
201
186
|
|
|
202
187
|
def der_to_raw_signature(signature_der)
|
data/saro-dat.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: saro-dat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- marker
|
|
@@ -106,7 +106,6 @@ files:
|
|
|
106
106
|
- ".idea/saro-dat.iml"
|
|
107
107
|
- ".ruby-version"
|
|
108
108
|
- Gemfile
|
|
109
|
-
- Gemfile.lock
|
|
110
109
|
- LICENSE
|
|
111
110
|
- PUBLISH.md
|
|
112
111
|
- README.md
|
|
@@ -140,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
140
139
|
- !ruby/object:Gem::Version
|
|
141
140
|
version: '0'
|
|
142
141
|
requirements: []
|
|
143
|
-
rubygems_version: 4.0.
|
|
142
|
+
rubygems_version: 4.0.12
|
|
144
143
|
specification_version: 4
|
|
145
144
|
summary: DAT (Data Access Token) Ruby implementation
|
|
146
145
|
test_files: []
|
data/Gemfile.lock
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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
|