saro-dat 1.0.0 → 4.3.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
  SHA256:
3
- metadata.gz: 8a79bc8cd93aba476a9f69fef214dd4a0ba9275a4bc93bebb4e1e4e78f36bb19
4
- data.tar.gz: 8a16197d29b030c965f8a9e30d80fc7decad1d94be60272b3ef22734e1eeb042
3
+ metadata.gz: 1bc8964419d2aa39030dbec0e990d8a22d1fa29a8f8eec15235f69e70fa81054
4
+ data.tar.gz: e2fb45d1bbb981ea6745fab4661db4f1db41063fe73bc147cd33276c60e4a0fa
5
5
  SHA512:
6
- metadata.gz: b9fa121db64bb0f59429e543ef49efa921afdd8a87eeb52a232352a9bc09dcd77f33898fa89dbe1031adc1d28bdd575f8c997deaf15265c804668a4e9ac2ea7b
7
- data.tar.gz: fc15628f82a197628bb41e686392d7b815bb1b39d2146fbd27bab85209ab8351362239dd6c98aa2de3ef214db943e6b0112b0c0a20de693481241e2e788da8b9
6
+ metadata.gz: c306baa43850bc77ebd33a2c1717f1aaac63ca0c98b538da28cb8ca064e0ed475b0c2e866fee5b47700a49009c085621c5b21f1b989041028e0a481112092590
7
+ data.tar.gz: c75ede02ecff59c9e18a85116247eb3bae95c594940f8fedec457434a8f9c9dc9690667d1be103cb4abf03b18c483997a9f0fe4b6adbbb3f3e19c644ad9e66e2
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  /.bundle/
3
3
  /vendor/bundle/
4
+
4
5
  /Gemfile.lock
5
6
  /saro-dat-*.gem
6
7
 
data/.idea/saro-dat.iml CHANGED
@@ -13,8 +13,9 @@
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.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" />
16
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v4.0.12, rbenv: 4.0.5) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.3.7, rbenv: 4.0.5) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="logger (v1.7.0, rbenv: 4.0.5) [gem]" level="application" />
18
19
  <orderEntry type="library" scope="PROVIDED" name="minitest (v5.27.0, rbenv: 4.0.5) [gem]" level="application" />
19
20
  <orderEntry type="library" scope="PROVIDED" name="openssl (v4.0.2, rbenv: 4.0.5) [gem]" level="application" />
20
21
  <orderEntry type="library" scope="PROVIDED" name="parallel (v2.1.0, 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-1.0.0.gem
12
+ gem push saro-dat-4.3.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
@@ -4,22 +4,27 @@
4
4
 
5
5
  ### [DAT Run Online](https://dat.saro.me)
6
6
 
7
- ### [What is DAT](https://dat.saro.me/--/intro)
7
+ ### [What is DAT](https://dat.saro.me/intro)
8
8
 
9
- ### [Example](https://dat.saro.me/--/libs/gems-saro-dat)
9
+ ### [Example](https://dat.saro.me/libs/gems-saro-dat)
10
10
 
11
- ## support signature algorithm
12
- | name | algorithm |
13
- |--------|------------|
14
- | P256 | secp256r1 |
15
- | P384 | secp384r1 |
16
- | P521 | secp521r1 |
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
- ## support crypto algorithm
19
- | name | algorithm |
20
- |------------|-----------------------------|
21
- | AES128GCMN | aes-128-gcm n(nonce + body) |
22
- | AES256GCMN | aes-256-cbc n(nonce + body) |
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: orSsnyfSfREmqzSp25qVE0XEXHBa1FgprEq75OBss5MH61jnWXMYypt4GOOLC6ZF29XUJapVv5X3p2gzBG5fLREFRqJdMaKrZoFi
33
- Secure: 8SkeUWIcp3Q2I104LgCpDZ4CZ6Uwd21j2QPerHtw5RRN2kPns6wwxjdipCCt5l9QdforbqqjQu5BfevfeC1id6OlBjPPHcF9oMpb
37
+ Plain: CcMjua0RA8I27be6W2lYMBmk5OcPrV8mu3ybrVwBOB2pEPbtfrvH0h0Z0VBhG1ID4zu51sDKxZZGFoku9TzrcPJLbb0ObRiF3NIF
38
+ Secure: EEYEOjriFkidJ8lKpYPkt1fwS01sZuJ7ysgCWd7XyarZfP6yrxkR9rciiJSUpuPZlBt0moFlKb0n2ZDivhvxLNjmO1eT8KjzhLlA
34
39
 
35
40
  --- 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
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
- 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
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
  ```
@@ -7,8 +7,8 @@ require_relative 'util'
7
7
  module Saro
8
8
  module Dat
9
9
  class DatCryptoAlgorithm
10
- AES128GCMN = "AES128GCMN"
11
- AES256GCMN = "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
- "AES128GCMN" => { name: "aes-128-gcm", length: 16 },
20
- "AES256GCMN" => { name: "aes-256-gcm", length: 32 }
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 DatCryptoKey
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, raw)
45
- new(algorithm, raw)
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
- 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
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
@@ -7,53 +7,80 @@ require_relative 'util'
7
7
  module Saro
8
8
  module Dat
9
9
  class DatCertificate
10
- attr_reader :cid, :signature_key, :crypto_key, :dat_issue_begin, :dat_issue_end, :dat_ttl
10
+ attr_reader :cid, :signature_key, :crypto_key, :dat_issuance_start_seconds, :dat_issuance_end_seconds, :dat_ttl_seconds
11
11
 
12
- def initialize(cid, signature_key, crypto_key, dat_issue_begin, dat_issue_end, dat_ttl)
12
+ def initialize(cid, dat_issuance_start_seconds, dat_issuance_duration_seconds, dat_ttl_seconds, signature_key, crypto_key)
13
13
  @cid = cid
14
+ @dat_issuance_start_seconds = dat_issuance_start_seconds
15
+ @dat_issuance_end_seconds = dat_issuance_start_seconds + dat_issuance_duration_seconds
16
+ @dat_ttl_seconds = dat_ttl_seconds
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(option)
21
+ def exports(verify_only = false)
22
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)
23
+ dat_issuance_start_seconds = @dat_issuance_start_seconds.to_s
24
+ dat_issuance_duration_seconds = (@dat_issuance_end_seconds - @dat_issuance_start_seconds).to_s
25
+ dat_ttl_seconds = @dat_ttl_seconds.to_s
26
+ signature_algorithm = @signature_key.algorithm
27
+ crypto_algorithm = @crypto_key.algorithm
28
+ signature_key = @signature_key.exports(verify_only)
29
+ crypto_key = @crypto_key.exports
27
30
 
28
- "#{cid_hex}.#{sig_alg}.#{sig_key}.#{cry_alg}.#{cry_key}.#{@dat_issue_begin}.#{@dat_issue_end}.#{@dat_ttl}"
31
+ "#{cid_hex}.#{dat_issuance_start_seconds}.#{dat_issuance_duration_seconds}.#{dat_ttl_seconds}.#{signature_algorithm}.#{crypto_algorithm}.#{signature_key}.#{crypto_key}"
32
+ end
33
+
34
+ def self.generate(cid, dat_issuance_start_seconds, dat_issuance_duration_seconds, dat_ttl_seconds, signature_algorithm, crypto_algorithm)
35
+ new(
36
+ cid, dat_issuance_start_seconds, dat_issuance_duration_seconds, dat_ttl_seconds,
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
- split = format_str.split(".")
33
- raise ArgumentError, "Invalid Certificate format" if split.length != 8
43
+ parts = format_str.split(".")
44
+ raise ArgumentError, "Invalid Certificate format" if parts.length != 8
34
45
 
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]))
46
+ cid = parts[0].to_i(16)
47
+ dat_issuance_start_seconds = parts[1].to_i
48
+ dat_issuance_duration_seconds = parts[2].to_i
49
+ dat_ttl_seconds = parts[3].to_i
50
+ signature_algorithm = parts[4]
51
+ crypto_algorithm = parts[5]
52
+ signature_key = Saro::Dat::DatSignature.imports(signature_algorithm, parts[6])
53
+ crypto_key = Saro::Dat::DatCrypto.imports(crypto_algorithm, 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, dat_issuance_start_seconds, dat_issuance_duration_seconds, dat_ttl_seconds, signature_key, crypto_key)
43
56
  end
44
57
 
45
- def issuable?
58
+ def issuable
46
59
  now = Time.now.to_i
47
- has_signing_key? && @dat_issue_begin <= now && now <= @dat_issue_end
60
+ signable && @dat_issuance_start_seconds <= now && now <= @dat_issuance_end_seconds
48
61
  end
49
62
 
50
- def expired?
51
- Time.now.to_i > (@dat_issue_end + @dat_ttl)
63
+ def expired
64
+ Time.now.to_i > (@dat_issuance_end_seconds + @dat_ttl_seconds)
52
65
  end
53
66
 
54
- def has_signing_key?
55
- @signature_key.has_signing_key?
67
+ def signable
68
+ @signature_key.signable
56
69
  end
70
+
71
+ def pair
72
+ @signature_key.pair
73
+ end
74
+
75
+ def support_verify_only
76
+ @signature_key.support_verify_only
77
+ end
78
+
79
+ # For Ruby conventions
80
+ alias_method :issuable?, :issuable
81
+ alias_method :expired?, :expired
82
+ alias_method :signable?, :signable
83
+ alias_method :pair?, :pair
57
84
  end
58
85
  end
59
86
  end
@@ -17,29 +17,32 @@ module Saro
17
17
  end
18
18
 
19
19
  def import_certificates(input_certs, clear: false)
20
+ renew_count = 0
20
21
  @lock.with_write_lock do
21
22
  certificates = clear ? [] : @certificates.dup
22
-
23
+
23
24
  before_cids = Set.new(certificates.map(&:cid))
24
25
  seen_cids = Set.new
25
26
 
26
27
  input_certs.each do |cert|
27
28
  raise ArgumentError, "Duplicate CID: #{cert.cid}" if seen_cids.include?(cert.cid)
28
29
  seen_cids.add(cert.cid)
29
- next if cert.expired?
30
+ next if cert.expired
30
31
  next if before_cids.include?(cert.cid)
31
32
 
32
33
  certificates << cert
34
+ renew_count += 1
33
35
  end
34
36
 
35
- certificates.sort_by!(&:dat_issue_end)
37
+ certificates.sort_by!(&:dat_issuance_end_seconds)
36
38
 
37
39
  # Find latest issuable certificate as issuer
38
- issuer = certificates.reverse_each.find(&:issuable?)
40
+ issuer = certificates.reverse_each.find(&:issuable)
39
41
 
40
42
  @issuer = issuer
41
43
  @certificates = certificates
42
44
  end
45
+ renew_count
43
46
  end
44
47
 
45
48
  def imports(format_str, clear: false)
@@ -52,9 +55,9 @@ module Saro
52
55
  import_certificates(certs, clear: clear)
53
56
  end
54
57
 
55
- def exports(option)
58
+ def exports(verify_only = false)
56
59
  @lock.with_read_lock do
57
- @certificates.map { |cert| cert.exports(option) }.join("\n")
60
+ @certificates.map { |cert| cert.exports(verify_only) }.join("\n")
58
61
  end
59
62
  end
60
63
 
@@ -82,7 +85,7 @@ module Saro
82
85
 
83
86
  def self._issue(cert, plain, secure)
84
87
  now = Time.now.to_i
85
- expire = now + cert.dat_ttl
88
+ expire = now + cert.dat_ttl_seconds
86
89
  cid_hex = cert.cid.to_s(16)
87
90
 
88
91
  plain_bytes = plain.is_a?(String) ? plain.encode('utf-8') : (plain || "".b)
@@ -6,25 +6,25 @@ require_relative 'util'
6
6
  module Saro
7
7
  module Dat
8
8
  class DatSignatureAlgorithm
9
- P256 = "P256"
10
- P384 = "P384"
11
- P521 = "P521"
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
- "P256" => { curve: "prime256v1", hash: "SHA256" },
26
- "P384" => { curve: "secp384r1", hash: "SHA384" },
27
- "P521" => { curve: "secp521r1", hash: "SHA512" }
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
- 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
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,68 @@ module Saro
92
71
 
93
72
  def self.generate(algorithm)
94
73
  config = Saro::Dat.get_signature_config(algorithm)
95
- key = OpenSSL::PKey::EC.generate(config[:curve])
96
- new(algorithm, key, key, config)
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, format_str)
83
+ def self.imports(algorithm, base64_str)
100
84
  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
85
+ bytes_data = Saro::Dat::Util.decode_base64_url(base64_str)
106
86
 
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)
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
- new(algorithm, signing_key, verifying_key, config)
112
+ new(algorithm, signing_key, verifying_key, config)
113
+ end
133
114
  end
134
115
 
135
- def exports(option)
136
- rv_parts = ["", ""]
116
+ def exports(verify_only = false)
117
+ if verify_only && !support_verify_only
118
+ raise ArgumentError, "#{config[:name]} does not supported verifying only key"
119
+ end
137
120
 
138
- if [DatSignatureKeyExportOption::PAIR, DatSignatureKeyExportOption::SIGNING].include?(option)
139
- if @signing_key&.private_key
121
+ if @config[:name] == "HMAC"
122
+ Saro::Dat::Util.encode_base64_url_str(@verifying_key)
123
+ else
124
+ if verify_only || !@signing_key&.private_key
125
+ public_bytes = @verifying_key.public_key.to_octet_string(:uncompressed)
126
+ Saro::Dat::Util.encode_base64_url_str(public_bytes)
127
+ else
140
128
  d_value = @signing_key.private_key
141
- # Ensure fixed length padding
142
129
  curve_size = (@signing_key.group.degree + 7) / 8
143
130
  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"
131
+
132
+ public_bytes = @verifying_key.public_key.to_octet_string(:uncompressed)
133
+ Saro::Dat::Util.encode_base64_url_str(d_bytes + public_bytes)
147
134
  end
148
135
  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
136
  end
165
137
 
166
138
  def sign(body)
@@ -168,10 +140,13 @@ module Saro
168
140
  body = body.encode('utf-8') if body.is_a?(String) && body.encoding != Encoding::BINARY
169
141
  raise ArgumentError, "Sign Error - body is empty" if body.nil? || body.empty?
170
142
 
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)
143
+ if @config[:name] == "HMAC"
144
+ OpenSSL::HMAC.digest(@config[:hash], @signing_key, body)
145
+ else
146
+ digest = OpenSSL::Digest.new(@config[:hash])
147
+ signature_der = @signing_key.dsa_sign_asn1(digest.digest(body))
148
+ der_to_raw_signature(signature_der)
149
+ end
175
150
  end
176
151
 
177
152
  def verify(body, signature)
@@ -184,19 +159,37 @@ module Saro
184
159
  signature
185
160
  end
186
161
 
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
162
+ if @config[:name] == "HMAC"
163
+ begin
164
+ actual_sig = OpenSSL::HMAC.digest(@config[:hash], @verifying_key, body)
165
+ # Use fixed-time comparison if possible
166
+ return actual_sig == sig_bytes
167
+ rescue StandardError
168
+ return false
169
+ end
170
+ else
171
+ begin
172
+ der_sig = raw_to_der_signature(sig_bytes)
173
+ digest = OpenSSL::Digest.new(@config[:hash])
174
+ @verifying_key.dsa_verify_asn1(digest.digest(body), der_sig)
175
+ rescue StandardError
176
+ false
177
+ end
193
178
  end
194
179
  end
195
180
 
196
- def has_signing_key?
181
+ def signable
197
182
  !@signing_key.nil?
198
183
  end
199
184
 
185
+ def pair
186
+ @config[:name] == "ECDSA"
187
+ end
188
+
189
+ def support_verify_only
190
+ @config[:name] == "ECDSA"
191
+ end
192
+
200
193
  private
201
194
 
202
195
  def der_to_raw_signature(signature_der)
data/lib/saro-dat.rb CHANGED
@@ -6,6 +6,7 @@ require_relative 'saro/dat/signature'
6
6
  require_relative 'saro/dat/dat_certificate'
7
7
  require_relative 'saro/dat/dat'
8
8
  require_relative 'saro/dat/dat_manager'
9
+ require_relative 'saro/dat/cms_manager'
9
10
 
10
11
  module Saro
11
12
  module Dat
data/saro-dat.gemspec CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "saro-dat"
5
- spec.version = "1.0.0"
5
+ spec.version = "4.3.0"
6
6
  spec.authors = ["marker"]
7
7
  spec.email = ["j@saro.me"]
8
8
 
9
9
  spec.summary = "DAT (Data Access Token) Ruby implementation"
10
10
  spec.description = "Ported from Python dat library"
11
- spec.homepage = "https://dat.saro.me/--/libs/gems-saro-dat"
11
+ spec.homepage = "https://dat.saro.me/libs/gems-saro-dat"
12
12
  spec.license = "MIT"
13
13
  spec.required_ruby_version = ">= 2.7.0"
14
14
 
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency "concurrent-ruby", "~> 1.3.6"
31
31
  spec.add_dependency "openssl", "~> 4.0.2"
32
32
  spec.add_dependency "base64"
33
+ spec.add_dependency "logger"
33
34
 
34
35
  spec.add_development_dependency "minitest", "~> 5.0"
35
36
  spec.add_development_dependency "benchmark"
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: 1.0.0
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - marker
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: logger
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: minitest
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -106,7 +120,6 @@ files:
106
120
  - ".idea/saro-dat.iml"
107
121
  - ".ruby-version"
108
122
  - Gemfile
109
- - Gemfile.lock
110
123
  - LICENSE
111
124
  - PUBLISH.md
112
125
  - README.md
@@ -118,11 +131,11 @@ files:
118
131
  - lib/saro/dat/signature.rb
119
132
  - lib/saro/dat/util.rb
120
133
  - saro-dat.gemspec
121
- homepage: https://dat.saro.me/--/libs/gems-saro-dat
134
+ homepage: https://dat.saro.me/libs/gems-saro-dat
122
135
  licenses:
123
136
  - MIT
124
137
  metadata:
125
- homepage_uri: https://dat.saro.me/--/libs/gems-saro-dat
138
+ homepage_uri: https://dat.saro.me/libs/gems-saro-dat
126
139
  source_code_uri: https://github.com/saro-lab/dat-ruby
127
140
  changelog_uri: https://github.com/saro-lab/dat-ruby/blob/main/CHANGELOG.md
128
141
  keywords: dat, distributed, access, token, web, session, security, authentication
@@ -140,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
153
  - !ruby/object:Gem::Version
141
154
  version: '0'
142
155
  requirements: []
143
- rubygems_version: 4.0.10
156
+ rubygems_version: 4.0.12
144
157
  specification_version: 4
145
158
  summary: DAT (Data Access Token) Ruby implementation
146
159
  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