Module: MixinBot::Utils::Crypto

Included in:
MixinBot::Utils
Defined in:
lib/mixin_bot/utils/crypto.rb

Instance Method Summary collapse

Instance Method Details

#access_token(method, uri, body = '', **kwargs) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/mixin_bot/utils/crypto.rb', line 6

def access_token(method, uri, body = '', **kwargs)
  sig = Digest::SHA256.hexdigest(method + uri + body.to_s)
  iat = Time.now.utc.to_i
  exp = (Time.now.utc + (kwargs[:exp_in] || 600)).to_i
  scp = kwargs[:scp] || 'FULL'
  jti = SecureRandom.uuid
  uid = kwargs[:app_id] || MixinBot.config.app_id
  sid = kwargs[:session_id] || MixinBot.config.session_id
  private_key = kwargs[:private_key] || MixinBot.config.session_private_key

  payload = {
    uid:,
    sid:,
    iat:,
    exp:,
    jti:,
    sig:,
    scp:
  }

  if private_key.blank?
    raise ConfigurationNotValidError, 'private_key is required'
  elsif private_key.size == 64
    jwk = JOSE::JWK.from_okp [:Ed25519, private_key]
    jws = JOSE::JWS.from({ 'alg' => 'EdDSA' })
  else
    jwk = JOSE::JWK.from_pem private_key
    jws = JOSE::JWS.from({ 'alg' => 'RS512' })
  end

  jwt = JOSE::JWT.from payload
  JOSE::JWT.sign(jwk, jws, jwt).compact
end

#decrypt_pin(msg, shared_key:) ⇒ Object

decrypt the encrpted pin, just for test



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/mixin_bot/utils/crypto.rb', line 130

def decrypt_pin(msg, shared_key:)
  msg = Base64.urlsafe_decode64 msg
  iv = msg[0..15]
  cipher = msg[16..47]
  alg = 'AES-256-CBC'
  decode_cipher = OpenSSL::Cipher.new(alg)
  decode_cipher.decrypt
  decode_cipher.iv = iv
  decode_cipher.key = shared_key
  decode_cipher.update(cipher)
end

#encrypt_pin(pin, **kwargs) ⇒ Object

use timestamp(timestamp) for iterator as default: must be bigger than the previous, the first time must be greater than 0. After a new session created, it will be reset to 0.

Raises:



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/mixin_bot/utils/crypto.rb', line 143

def encrypt_pin(pin, **kwargs)
  pin = MixinBot.utils.decode_key pin

  shared_key = kwargs[:shared_key]
  raise ArgumentError, 'shared_key is required' if shared_key.blank?

  iterator ||= kwargs[:iterator] || Time.now.utc.to_i
  tszero = iterator % 0x100
  tsone = (iterator % 0x10000) >> 8
  tstwo = (iterator % 0x1000000) >> 16
  tsthree = (iterator % 0x100000000) >> 24
  tsstring = "#{tszero.chr}#{tsone.chr}#{tstwo.chr}#{tsthree.chr}\u0000\u0000\u0000\u0000"
  encrypt_content = pin + tsstring + tsstring
  pad_count = 16 - (encrypt_content.length % 16)
  padded_content =
    if pad_count.positive?
      encrypt_content + (pad_count.chr * pad_count)
    else
      encrypt_content
    end

  alg = 'AES-256-CBC'
  aes = OpenSSL::Cipher.new(alg)
  iv = OpenSSL::Cipher.new(alg).random_iv
  aes.encrypt
  aes.key = shared_key
  aes.iv = iv
  cipher = aes.update(padded_content)
  msg = iv + cipher
  Base64.urlsafe_encode64 msg, padding: false
end

#generate_ed25519_keyObject



40
41
42
43
44
45
46
# File 'lib/mixin_bot/utils/crypto.rb', line 40

def generate_ed25519_key
  ed25519_key = JOSE::JWA::Ed25519.keypair
  {
    private_key: Base64.urlsafe_encode64(ed25519_key[1], padding: false),
    public_key: Base64.urlsafe_encode64(ed25519_key[0], padding: false)
  }
end

#generate_public_key(key) ⇒ Object



56
57
58
59
60
61
62
63
# File 'lib/mixin_bot/utils/crypto.rb', line 56

def generate_public_key(key)
  point = JOSE::JWA::FieldElement.new(
    OpenSSL::BN.new(key.reverse, 2),
    JOSE::JWA::Edwards25519Point::L
  )

  (JOSE::JWA::Edwards25519Point.stdbase * point.x.to_i).encode
end

#generate_rsa_keyObject



48
49
50
51
52
53
54
# File 'lib/mixin_bot/utils/crypto.rb', line 48

def generate_rsa_key
  rsa_key = OpenSSL::PKey::RSA.new 1024
  {
    private_key: rsa_key.to_pem,
    public_key: rsa_key.public_key.to_pem
  }
end

#generate_trace_from_hash(hash, output_index = 0) ⇒ Object



118
119
120
121
122
123
124
125
126
127
# File 'lib/mixin_bot/utils/crypto.rb', line 118

def generate_trace_from_hash(hash, output_index = 0)
  md5 = Digest::MD5.new
  md5 << hash
  md5 << [output_index].pack('c*') if output_index.positive? && output_index < 256
  digest = md5.digest
  digest[6] = ((digest[6].ord & 0x0f) | 0x30).chr
  digest[8] = ((digest[8].ord & 0x3f) | 0x80).chr

  MixinBot::UUID.new(raw: digest).unpacked
end

#generate_unique_uuid(uuid_1, uuid_2) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/mixin_bot/utils/crypto.rb', line 95

def generate_unique_uuid(uuid_1, uuid_2)
  md5 = Digest::MD5.new
  md5 << [uuid_1, uuid_2].min
  md5 << [uuid_1, uuid_2].max
  digest = md5.digest
  digest6 = ((digest[6].ord & 0x0f) | 0x30).chr
  digest8 = ((digest[8].ord & 0x3f) | 0x80).chr
  cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]

  MixinBot::UUID.new(raw: cipher).unpacked
end

#sign(msg, key:) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/mixin_bot/utils/crypto.rb', line 65

def sign(msg, key:)
  msg = Digest::Blake3.digest msg

  pub = generate_public_key key

  y_point = JOSE::JWA::FieldElement.new(
    OpenSSL::BN.new(key.reverse, 2),
    JOSE::JWA::Edwards25519Point::L
  )

  key_digest = Digest::SHA512.digest key
  msg_digest = Digest::SHA512.digest(key_digest[-32...] + msg)

  z_point = JOSE::JWA::FieldElement.new(
    OpenSSL::BN.new(msg_digest[...64].reverse, 2),
    JOSE::JWA::Edwards25519Point::L
  )

  r_point = JOSE::JWA::Edwards25519Point.stdbase * z_point.x.to_i
  hram_digest = Digest::SHA512.digest(r_point.encode + pub + msg)

  x_point = JOSE::JWA::FieldElement.new(
    OpenSSL::BN.new(hram_digest[...64].reverse, 2),
    JOSE::JWA::Edwards25519Point::L
  )
  s_point = (x_point * y_point) + z_point

  r_point.encode + s_point.to_bytes(36)
end

#tip_public_key(key, counter: 0) ⇒ Object

Raises:



175
176
177
178
179
# File 'lib/mixin_bot/utils/crypto.rb', line 175

def tip_public_key(key, counter: 0)
  raise ArgumentError, 'invalid key' if key.size < 32

  (key[0...32].bytes + MixinBot::Utils.encode_uint_64(counter + 1)).pack('c*').unpack1('H*')
end

#unique_uuid(*uuids) ⇒ Object



107
108
109
110
111
112
113
114
115
116
# File 'lib/mixin_bot/utils/crypto.rb', line 107

def unique_uuid(*uuids)
  uuids = uuids.flatten.compact
  uuids.sort
  r = uuids.first
  uuids.each_with_index do |uuid, i|
    r = generate_unique_uuid(r, uuid) if i.positive?
  end

  r
end