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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a79bc8cd93aba476a9f69fef214dd4a0ba9275a4bc93bebb4e1e4e78f36bb19
4
- data.tar.gz: 8a16197d29b030c965f8a9e30d80fc7decad1d94be60272b3ef22734e1eeb042
3
+ metadata.gz: a1981b6c7f0f207f6388f95f4cafaee56a44c49209b0961567f8f37dc3cb2187
4
+ data.tar.gz: 0e96927af41f3fb6c387a5ffceec5065f8ff6d2e723b8ed6dce41068e7c523fd
5
5
  SHA512:
6
- metadata.gz: b9fa121db64bb0f59429e543ef49efa921afdd8a87eeb52a232352a9bc09dcd77f33898fa89dbe1031adc1d28bdd575f8c997deaf15265c804668a4e9ac2ea7b
7
- data.tar.gz: fc15628f82a197628bb41e686392d7b815bb1b39d2146fbd27bab85209ab8351362239dd6c98aa2de3ef214db943e6b0112b0c0a20de693481241e2e788da8b9
6
+ metadata.gz: c4c8b7d3730883f2056841f0018ae4783223ffe417c35861a62ae56d6f0010a5b3d9687adbd6aeb7a87f358d9182af396df007313a493e0073681b4e4b474d72
7
+ data.tar.gz: 7af21459544e31f18c9edcae209fcb3181cdc40d6609e80b65ff8c4b002502c1687e7631e4ae2cd224d01a017447ee421a5ee754602e11d95f4616a792c06169
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,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.10, 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
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-1.0.0.gem
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
- ## 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
@@ -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, signature_key, crypto_key, dat_issue_begin, dat_issue_end, dat_ttl)
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(option)
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
- cry_key = Saro::Dat::Util.encode_base64_url_str(@crypto_key.exports)
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
- "#{cid_hex}.#{sig_alg}.#{sig_key}.#{cry_alg}.#{cry_key}.#{@dat_issue_begin}.#{@dat_issue_end}.#{@dat_ttl}"
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
- 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
+ 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
- has_signing_key? && @dat_issue_begin <= now && now <= @dat_issue_end
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 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
+ # 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
@@ -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(option)
55
+ def exports(verify_only = false)
56
56
  @lock.with_read_lock do
57
- @certificates.map { |cert| cert.exports(option) }.join("\n")
57
+ @certificates.map { |cert| cert.exports(verify_only) }.join("\n")
58
58
  end
59
59
  end
60
60
 
@@ -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,64 @@ 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
106
-
107
- signing_key = nil
108
- verifying_key = nil
85
+ bytes_data = Saro::Dat::Util.decode_base64_url(base64_str)
109
86
 
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 = ["", ""]
137
-
138
- if [DatSignatureKeyExportOption::PAIR, DatSignatureKeyExportOption::SIGNING].include?(option)
139
- if @signing_key&.private_key
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
- 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"
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
- 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)
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
- 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
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 has_signing_key?
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
@@ -2,7 +2,7 @@
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.0.0"
6
6
  spec.authors = ["marker"]
7
7
  spec.email = ["j@saro.me"]
8
8
 
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.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.10
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