msid 0.1.0 → 0.2.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: bf25c3ca34367f6073a84e93f267d835f34f56775664a23dc913d2143f9b5b61
4
- data.tar.gz: 94ca6e504098733c75dd7017a42343d5ee41af696c9acc95f7b44824d9b15ba4
3
+ metadata.gz: a66c2777ff91a9d45dadccdfeed8d8428ce55b153ab5dfb47fbe933042ac918c
4
+ data.tar.gz: 417965adb88e1fb02a1907f786d7fe09119958ed5ce5707e010399eb35c9f334
5
5
  SHA512:
6
- metadata.gz: 7652b3c1bce0ad68794440ce3a6d8a572d038ae38ef4c22e47af028e1a40f0aac148a969c9255e4dd7e1adec0605eaf84c76dcb9d305ba8346d93ab36f4fa952
7
- data.tar.gz: 1675bb45cc39e7060bb3eecdd0ff64b1362b6761c0304e384759695a24b61a62cf078bd826a0d9e0a081475664403e924c832e6fa2daf18f03fefeaa6b4c8fa1
6
+ metadata.gz: a427b6baad02240f442fea21a66ec814404be918c2070c0006521e91387ad4b89f8ec26a649f4bb4bf2e157f40814b243e45855571adaab3822b8ac487a24ed3
7
+ data.tar.gz: 2cd36016dafbf2a3da4e0f9a421e23bc29d62ad858de5dd0ca96fd811a6410249c2a72d9ee0b6d408c782fe3fae2bb1a47aa021f0baf0e12117cc3f402b7ca91
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- msid (0.1.0)
4
+ msid (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,169 +1,41 @@
1
1
  # msid
2
2
 
3
- `msid` is a Ruby gem for generating a unique and secure machine fingerprint ID. It gathers a wide range of system identifiers to create a hash that is highly specific to the machine it's run on.
3
+ `msid` generates a **unique, secure, and consistent machine fingerprint** for Ruby applications. It gathers hardware identifiers (CPU, Mac Address, Disk UUIDs, etc.) to create a specific SHA-256 hash.
4
4
 
5
- This is useful for licensing, device identification, or any scenario where you need to reliably identify a specific machine.
6
-
7
- ## How It Works
8
-
9
- `msid` collects the following system information:
10
- - Hostname
11
- - All MAC addresses
12
- - CPU model and core count
13
- - Total RAM
14
- - OS and kernel information
15
- - Hardware Serial Number
16
- - Hardware UUID
17
- - Motherboard Serial Number
18
- - Root disk volume UUID
19
- - System Model Identifier
20
- - GPU Information
21
- - BIOS/Firmware Information
22
- - Serial numbers of all physical disks
23
-
24
- It then combines these components into a single string and uses the SHA-256 algorithm to produce a consistent, unique fingerprint.
25
-
26
- ## Use Cases
27
-
28
- Here are some practical scenarios where `msid` can be valuable:
29
-
30
- ### Software Licensing
31
- - **Offline License Activation**: Bind software licenses to specific machines without requiring constant internet validation.
32
- - **Trial Period Management**: Track installations to prevent users from repeatedly installing trial versions.
33
- - **License Auditing**: Keep track of which machines have your software installed for compliance purposes.
34
-
35
- ### Security Applications
36
- - **Multi-factor Authentication**: Add an extra layer of security by validating not just "something you know" (password) but also "something you have" (the specific device).
37
- - **Session Security**: Detect when a user's session moves to a different machine, potentially indicating session hijacking.
38
- - **Fraud Detection**: Flag accounts that suddenly access your service from multiple different machines in rapid succession.
39
-
40
- ### System Management
41
- - **Asset Tracking**: Inventory management for IT departments to track hardware across an organization.
42
- - **Configuration Management**: Associate specific configurations or settings with particular machines.
43
- - **Environment-specific Features**: Enable or disable features based on the hardware environment.
44
-
45
- ### DevOps and Deployment
46
- - **Environment Fingerprinting**: Distinguish between development, staging, and production environments.
47
- - **Deployment Validation**: Ensure that deployments only happen from authorized build machines.
48
- - **Infrastructure Auditing**: Track which servers or virtual machines are running your applications.
49
-
50
- ### Usage Analytics
51
- - **Device Demographics**: Gather anonymous statistics about what kinds of machines are running your software.
52
- - **Installation Metrics**: Track unique installations versus reinstallations.
53
- - **Hardware Compatibility**: Identify hardware configurations where your software performs well or poorly.
5
+ Useful for licensing, analytics, and security.
54
6
 
55
7
  ## Installation
56
8
 
57
- Add this line to your application's Gemfile:
58
-
59
9
  ```ruby
60
10
  gem 'msid'
61
11
  ```
62
12
 
63
- And then execute:
64
-
65
- $ bundle install
66
-
67
- Or install it yourself as:
68
-
69
- $ gem install msid
70
-
71
13
  ## Usage
72
14
 
73
- ### From Ruby
74
-
75
- You can generate the machine ID from your Ruby code.
76
-
77
15
  ```ruby
78
16
  require 'msid'
79
17
 
80
- # Generate the default ID
81
- machine_id = Msid.generate
82
- puts machine_id
18
+ # Generate Machine ID
19
+ Msid.generate
83
20
  # => "a1b2c3d4..."
84
- ```
85
-
86
- #### Using a Salt
87
-
88
- For enhanced security, you can provide a `salt`. This is useful if you want to generate different IDs for different applications on the same machine.
89
-
90
- ```ruby
91
- require 'msid'
92
-
93
- # Generate an ID with a salt
94
- app_specific_id = Msid.generate(salt: 'my-super-secret-app-key')
95
- puts app_specific_id
96
- # => "e5f6g7h8..." (different from the one without a salt)
97
- ```
98
-
99
- ### With IRB (for local development)
100
-
101
- If you are developing the gem locally, you can test it with `irb`. From the root directory of the gem, run `irb` with the `-I` flag to add the `lib` directory to Ruby's load path.
102
21
 
103
- ```sh
104
- $ irb -I lib
105
- ```
106
-
107
- Then, inside `irb`, you can require and use the gem:
108
-
109
- ```ruby
110
- irb(main):001:0> require 'msid'
111
- => true
112
- irb(main):002:0> Msid.generate
113
- => "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
114
- ```
115
-
116
- ### In a Rails Application
22
+ # With Salt (Recommended for App-Specific IDs)
23
+ Msid.generate(salt: 'my-secret-app-key')
117
24
 
118
- To use `msid` in a Rails application, add it to your `Gemfile`.
119
-
120
- If you are developing the gem locally, you can use the `path` option:
121
- ```ruby
122
- # Gemfile
123
- gem 'msid', path: '/path/to/your/msid/gem'
25
+ # Inspect Gathered Data (New in v0.2.0)
26
+ Msid.info
27
+ # => {:hostname=>"MacBook-Pro.local", :cpu_model=>"Apple M1", ...}
124
28
  ```
125
29
 
126
- If the gem is on RubyGems, add it normally:
127
- ```ruby
128
- # Gemfile
129
- gem 'msid'
130
- ```
131
-
132
- Then run `bundle install`.
133
-
134
- Now you can use it anywhere in your Rails application, including the Rails console:
135
-
136
- ```sh
137
- $ rails console
138
- ```
139
-
140
- ```ruby
141
- # In Rails console
142
- irb(main):001:0> Msid.generate
143
- => "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
144
-
145
- # For extra security, you can use a Rails secret as a salt
146
- irb(main):002:0> Msid.generate(salt: Rails.application.credentials.secret_key_base)
147
- => "f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e2"
148
- ```
149
-
150
- ### From the Command Line
151
-
152
- The gem provides a command-line executable to easily retrieve the machine ID.
30
+ ### CLI
153
31
 
154
32
  ```sh
155
33
  $ msid
156
- a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
34
+ a1b2c3d4...
157
35
  ```
158
36
 
159
- ## Development
160
-
161
- After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests.
162
-
163
37
  ## Contributing
164
-
165
- Bug reports and pull requests are welcome on GitHub at https://github.com/davidesantangelo/msid.
38
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/davidesantangelo/msid).
166
39
 
167
40
  ## License
168
-
169
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
41
+ MIT License.
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Msid
6
+ # Base class for platform-specific data sources.
7
+ # Subclasses should implement methods to gather system information.
8
+ class Source
9
+ # @return [Hash] A hash containing all gathered system information.
10
+ def gather
11
+ {
12
+ hostname: hostname,
13
+ mac_addresses: mac_addresses,
14
+ cpu_model: cpu_model,
15
+ cpu_cores: cpu_cores,
16
+ memory: memory,
17
+ os_kernel: os_kernel,
18
+ serial_number: serial_number,
19
+ hardware_uuid: hardware_uuid,
20
+ board_serial: board_serial,
21
+ disk_uuid: disk_uuid,
22
+ model_identifier: model_identifier,
23
+ gpu_model: gpu_model,
24
+ bios_version: bios_version,
25
+ disk_serials: disk_serials
26
+ }
27
+ end
28
+
29
+ protected
30
+
31
+ # Executes a shell command and returns its stripped stdout.
32
+ # Returns nil if the command fails or an error occurs.
33
+ def run(command)
34
+ stdout, _stderr, status = Open3.capture3(command)
35
+ status.success? ? stdout.strip : nil
36
+ rescue StandardError
37
+ nil
38
+ end
39
+
40
+ # Normalize a string/array for consistent ID generation
41
+ def normalize(value)
42
+ case value
43
+ when Array
44
+ value.compact.map { |v| normalize(v) }.reject(&:empty?).sort.join(',')
45
+ when String
46
+ value.strip
47
+ else
48
+ value
49
+ end
50
+ end
51
+
52
+ # Abstract methods to be implemented by subclasses
53
+ %i[hostname mac_addresses cpu_model cpu_cores memory os_kernel
54
+ serial_number hardware_uuid board_serial disk_uuid
55
+ model_identifier gpu_model bios_version disk_serials].each do |method|
56
+ define_method(method) { nil }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msid/source'
4
+ require 'socket'
5
+
6
+ module Msid
7
+ module Sources
8
+ # MacOS specific data source implementation.
9
+ class Darwin < Source
10
+ def hostname
11
+ Socket.gethostname
12
+ end
13
+
14
+ def mac_addresses
15
+ run("ifconfig -a | grep -o -E '([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}'")&.split("\n")
16
+ end
17
+
18
+ def cpu_model
19
+ sysctl('machdep.cpu.brand_string')
20
+ end
21
+
22
+ def cpu_cores
23
+ sysctl('hw.ncpu')
24
+ end
25
+
26
+ def memory
27
+ sysctl('hw.memsize')
28
+ end
29
+
30
+ def os_kernel
31
+ "#{RUBY_PLATFORM}-#{run('uname -r')}"
32
+ end
33
+
34
+ def serial_number
35
+ # system_profiler is slow, try ioreg first?
36
+ # For stability, sticking to system_profiler but ideally we'd cache this if called multiple times.
37
+ # However, for a one-off generation, a single call is fine.
38
+ # We can optimize by asking for *only* the specific data type.
39
+ run("system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}'")
40
+ end
41
+
42
+ def hardware_uuid
43
+ run("system_profiler SPHardwareDataType | awk '/Hardware UUID/ {print $3}'")
44
+ end
45
+
46
+ def board_serial
47
+ # macOS doesn't easily expose this to user space without root or private entitlements sometimes.
48
+ nil
49
+ end
50
+
51
+ def disk_uuid
52
+ run("diskutil info / | awk '/Volume UUID/{print $3}'")
53
+ end
54
+
55
+ def model_identifier
56
+ sysctl('hw.model') # Faster than system_profiler
57
+ end
58
+
59
+ def gpu_model
60
+ run("system_profiler SPDisplaysDataType | grep 'Chipset Model:' | awk -F': ' '{print $2}'")
61
+ end
62
+
63
+ def bios_version
64
+ run("system_profiler SPHardwareDataType | awk '/Boot ROM Version/ {print $4}'")
65
+ end
66
+
67
+ def disk_serials
68
+ run("system_profiler SPStorageDataType | grep 'Serial Number:' | awk '{print $3}'")&.split("\n")
69
+ end
70
+
71
+ private
72
+
73
+ def sysctl(key)
74
+ run("sysctl -n #{key}")
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msid/source'
4
+ require 'socket'
5
+
6
+ module Msid
7
+ module Sources
8
+ # Linux specific data source implementation.
9
+ class Linux < Source
10
+ def hostname
11
+ Socket.gethostname
12
+ end
13
+
14
+ def mac_addresses
15
+ # More reliable than parsing `ip` command output
16
+ Dir.glob('/sys/class/net/*/address').map { |f| File.read(f).strip }
17
+ rescue StandardError
18
+ []
19
+ end
20
+
21
+ def cpu_model
22
+ run("grep 'model name' /proc/cpuinfo | uniq | awk -F': ' '{print $2}'")
23
+ end
24
+
25
+ def cpu_cores
26
+ run('nproc')
27
+ end
28
+
29
+ def memory
30
+ run("grep MemTotal /proc/meminfo | awk '{print $2}'")
31
+ end
32
+
33
+ def os_kernel
34
+ "#{RUBY_PLATFORM}-#{run('uname -r')}"
35
+ end
36
+
37
+ def serial_number
38
+ run('cat /sys/class/dmi/id/product_serial')
39
+ end
40
+
41
+ def hardware_uuid
42
+ run('cat /sys/class/dmi/id/product_uuid')
43
+ end
44
+
45
+ def board_serial
46
+ run('cat /sys/class/dmi/id/board_serial')
47
+ end
48
+
49
+ def disk_uuid
50
+ device = run("df / | tail -n1 | awk '{print $1}'")
51
+ run("lsblk -no UUID #{device}") if device
52
+ end
53
+
54
+ def model_identifier
55
+ run('cat /sys/class/dmi/id/product_name')
56
+ end
57
+
58
+ def gpu_model
59
+ run("lspci | grep -i 'vga\\|3d\\|2d' | head -n 1 | awk -F': ' '{print $3}'")
60
+ end
61
+
62
+ def bios_version
63
+ vendor = run('cat /sys/class/dmi/id/bios_vendor')
64
+ version = run('cat /sys/class/dmi/id/bios_version')
65
+ "#{vendor}-#{version}" if vendor && version
66
+ end
67
+
68
+ def disk_serials
69
+ run('lsblk -d -o serial -n')&.split("\n")
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'msid/source'
4
+ require 'socket'
5
+
6
+ module Msid
7
+ module Sources
8
+ # Windows specific data source implementation.
9
+ class Windows < Source
10
+ def hostname
11
+ Socket.gethostname
12
+ end
13
+
14
+ def mac_addresses
15
+ run('getmac /v /fo csv | findstr /V "Disconnected"')
16
+ &.lines
17
+ &.map { |line| line.split(',')[2]&.gsub('"', '')&.strip }
18
+ end
19
+
20
+ def cpu_model
21
+ wmic('cpu get name')&.gsub('Name=', '')
22
+ end
23
+
24
+ def cpu_cores
25
+ wmic('cpu get NumberOfCores')
26
+ end
27
+
28
+ def memory
29
+ wmic('ComputerSystem get TotalPhysicalMemory')
30
+ end
31
+
32
+ def os_kernel
33
+ "#{RUBY_PLATFORM}-#{run('ver')}"
34
+ end
35
+
36
+ def serial_number
37
+ wmic('bios get serialnumber')
38
+ end
39
+
40
+ def hardware_uuid
41
+ wmic('csproduct get uuid')
42
+ end
43
+
44
+ def board_serial
45
+ wmic('baseboard get serialnumber')
46
+ end
47
+
48
+ def disk_uuid
49
+ run('vol c: | findstr "Serial Number"')&.split&.last
50
+ end
51
+
52
+ def model_identifier
53
+ wmic('csproduct get name')
54
+ end
55
+
56
+ def gpu_model
57
+ wmic('path win32_videocontroller get name')
58
+ end
59
+
60
+ def bios_version
61
+ run('wmic bios get manufacturer,version /format:list')
62
+ end
63
+
64
+ def disk_serials
65
+ wmic('diskdrive get serialnumber')&.split("\n")
66
+ end
67
+
68
+ private
69
+
70
+ # Helper to run wmic commands and strip headers
71
+ def wmic(query)
72
+ # WMIC output often contains the header repeated or extra whitespace.
73
+ # This is a basic filter based on the original implementation's logic (using findstr /V).
74
+ # We'll rely on the specific parsing logic from original which used findstr /V "HeaderName"
75
+ # Since we want to be generic here, let's keep it simple:
76
+ # Original: run('wmic cpu get NumberOfCores | findstr /V "NumberOfCores"')
77
+ # So we just construct that command.
78
+
79
+ # Extract the property name from the query "cpu get NumberOfCores" -> "NumberOfCores"
80
+ # This is valid for simple "get X" queries.
81
+ property = query.split.last
82
+ run("wmic #{query} | findstr /V \"#{property}\"")
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/msid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Msid
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/msid.rb CHANGED
@@ -1,248 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'msid/version'
4
- require 'socket'
4
+ require 'msid/source'
5
5
  require 'digest'
6
- require 'open3'
7
6
 
8
7
  module Msid
9
8
  class Error < StandardError; end
10
9
 
11
- # Generates a unique machine fingerprint ID.
12
- #
13
- # The fingerprint is created by collecting various system identifiers,
14
- # combining them into a single string, and then hashing it with SHA-256.
15
- # This makes the ID highly unique to the machine it's generated on.
16
10
  class Generator
17
11
  class << self
18
- # Gathers a collection of machine-specific identifiers.
19
- # @return [Array<String>] a list of identifiers.
20
- def gather_components
21
- [
22
- hostname,
23
- all_mac_addresses,
24
- cpu_info,
25
- cpu_cores,
26
- total_memory,
27
- os_info,
28
- serial_number,
29
- hardware_uuid,
30
- baseboard_serial,
31
- disk_uuid,
32
- system_model,
33
- gpu_info,
34
- bios_info,
35
- all_disk_serials
36
- ].compact.reject(&:empty?)
37
- end
38
-
39
12
  # Generates the unique machine ID.
40
13
  # @param salt [String, nil] An optional salt to add to the fingerprint.
41
14
  # @return [String] The SHA-256 hash representing the machine ID.
42
15
  def generate(salt: nil)
43
16
  components = gather_components
44
- raise Error, 'Could not gather any machine identifiers.' if components.empty?
17
+ raise Error, 'Could not gather any machine identifiers.' if components.values.reject { |v| v.nil? || v.empty? }.empty?
45
18
 
46
- components.push(salt.to_s) if salt
19
+ data_string = components.values.compact.reject(&:empty?).join(':')
20
+ data_string += ":#{salt}" if salt
47
21
 
48
- data_string = components.join(':')
49
22
  Digest::SHA256.hexdigest(data_string)
50
23
  end
51
24
 
52
- private
53
-
54
- # Executes a shell command and returns its stripped stdout.
55
- # Returns nil if the command fails or an error occurs.
56
- def run_command(command)
57
- stdout, _stderr, status = Open3.capture3(command)
58
- status.success? ? stdout.strip : nil
59
- rescue StandardError
60
- nil
61
- end
62
-
63
- # @return [String, nil] The machine's hostname.
64
- def hostname
65
- Socket.gethostname
66
- rescue StandardError
67
- nil
68
- end
69
-
70
- # @return [String, nil] A sorted, concatenated string of all MAC addresses.
71
- def all_mac_addresses
72
- macs = case RUBY_PLATFORM
73
- when /darwin/ # macOS
74
- run_command("ifconfig -a | grep -o -E '([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}'")&.split("\n")
75
- when /linux/
76
- # Read from /sys/class/net, which is more reliable than parsing `ip` command output
77
- begin
78
- Dir.glob('/sys/class/net/*/address').map { |f| File.read(f).strip }
79
- rescue StandardError
80
- []
81
- end
82
- when /mswin|mingw/ # Windows
83
- run_command('getmac /v /fo csv | findstr /V "Disconnected"')
84
- &.lines
85
- &.map { |line| line.split(',')[2]&.gsub('"', '')&.strip }
86
- else
87
- []
88
- end
89
- macs&.compact&.reject(&:empty?)&.sort&.join(',')
90
- end
91
-
92
- # @return [String, nil] CPU model information.
93
- def cpu_info
94
- case RUBY_PLATFORM
95
- when /darwin/
96
- run_command('sysctl -n machdep.cpu.brand_string')
97
- when /linux/
98
- run_command("grep 'model name' /proc/cpuinfo | uniq | awk -F': ' '{print $2}'")
99
- when /mswin|mingw/
100
- run_command('wmic cpu get name /format:list | findstr "Name="')&.gsub('Name=', '')
101
- end
102
- end
103
-
104
- # @return [String, nil] Number of CPU cores.
105
- def cpu_cores
106
- case RUBY_PLATFORM
107
- when /darwin/
108
- run_command('sysctl -n hw.ncpu')
109
- when /linux/
110
- run_command('nproc')
111
- when /mswin|mingw/
112
- run_command('wmic cpu get NumberOfCores | findstr /V "NumberOfCores"')
113
- end
114
- end
115
-
116
- # @return [String, nil] Total physical memory in bytes or KB.
117
- def total_memory
118
- case RUBY_PLATFORM
119
- when /darwin/
120
- run_command('sysctl -n hw.memsize')
121
- when /linux/
122
- run_command("grep MemTotal /proc/meminfo | awk '{print $2}'")
123
- when /mswin|mingw/
124
- run_command('wmic ComputerSystem get TotalPhysicalMemory | findstr /V "TotalPhysicalMemory"')
125
- end
126
- end
127
-
128
- # @return [String, nil] Operating system and kernel information.
129
- def os_info
130
- kernel_info = case RUBY_PLATFORM
131
- when /mswin|mingw/
132
- run_command('ver')
133
- else
134
- run_command('uname -r')
135
- end
136
- "#{RUBY_PLATFORM}-#{kernel_info}"
25
+ # Returns a hash of all gathered components.
26
+ # @return [Hash]
27
+ def info
28
+ gather_components
137
29
  end
138
30
 
139
- # @return [String, nil] The machine's hardware serial number.
140
- def serial_number
141
- case RUBY_PLATFORM
142
- when /darwin/
143
- run_command("system_profiler SPHardwareDataType | awk '/Serial Number/{print $4}'")
144
- when /linux/
145
- run_command('cat /sys/class/dmi/id/product_serial')
146
- when /mswin|mingw/
147
- run_command('wmic bios get serialnumber | findstr /V "SerialNumber"')
148
- end
149
- end
150
-
151
- # @return [String, nil] The machine's hardware UUID.
152
- def hardware_uuid
153
- case RUBY_PLATFORM
154
- when /darwin/
155
- run_command("system_profiler SPHardwareDataType | awk '/Hardware UUID/ {print $3}'")
156
- when /linux/
157
- run_command('cat /sys/class/dmi/id/product_uuid')
158
- when /mswin|mingw/
159
- run_command('wmic csproduct get uuid | findstr /V "UUID"')
160
- end
161
- end
162
-
163
- # @return [String, nil] The machine's baseboard serial number.
164
- def baseboard_serial
165
- case RUBY_PLATFORM
166
- when /darwin/
167
- nil # Not easily available/distinct from system serial on macOS
168
- when /linux/
169
- run_command('cat /sys/class/dmi/id/board_serial')
170
- when /mswin|mingw/
171
- run_command('wmic baseboard get serialnumber | findstr /V "SerialNumber"')
172
- end
173
- end
174
-
175
- # @return [String, nil] The UUID of the root volume.
176
- def disk_uuid
177
- case RUBY_PLATFORM
178
- when /darwin/
179
- run_command("diskutil info / | awk '/Volume UUID/{print $3}'")
180
- when /linux/
181
- device = run_command("df / | tail -n1 | awk '{print $1}'")
182
- run_command("lsblk -no UUID #{device}") if device
183
- when /mswin|mingw/
184
- run_command('vol c: | findstr "Serial Number"')&.split&.last
185
- end
186
- end
187
-
188
- # @return [String, nil] The machine's system model identifier.
189
- def system_model
190
- case RUBY_PLATFORM
191
- when /darwin/
192
- run_command("system_profiler SPHardwareDataType | awk '/Model Identifier/ {print $3}'")
193
- when /linux/
194
- run_command('cat /sys/class/dmi/id/product_name')
195
- when /mswin|mingw/
196
- run_command('wmic csproduct get name | findstr /V "Name"')
197
- end
198
- end
199
-
200
- # @return [String, nil] The machine's GPU information.
201
- def gpu_info
202
- case RUBY_PLATFORM
203
- when /darwin/
204
- run_command("system_profiler SPDisplaysDataType | grep 'Chipset Model:' | awk -F': ' '{print $2}'")
205
- when /linux/
206
- run_command("lspci | grep -i 'vga\\|3d\\|2d' | head -n 1 | awk -F': ' '{print $3}'")
207
- when /mswin|mingw/
208
- run_command('wmic path win32_videocontroller get name | findstr /V "Name"')
209
- end
210
- end
211
-
212
- # @return [String, nil] The machine's BIOS/firmware information.
213
- def bios_info
214
- case RUBY_PLATFORM
215
- when /darwin/
216
- run_command("system_profiler SPHardwareDataType | awk '/Boot ROM Version/ {print $4}'")
217
- when /linux/
218
- vendor = run_command('cat /sys/class/dmi/id/bios_vendor')
219
- version = run_command('cat /sys/class/dmi/id/bios_version')
220
- "#{vendor}-#{version}" if vendor && version
221
- when /mswin|mingw/
222
- run_command('wmic bios get manufacturer,version /format:list')
223
- end
224
- end
31
+ private
225
32
 
226
- # @return [String, nil] A sorted, concatenated string of all physical disk serial numbers.
227
- def all_disk_serials
228
- serials = case RUBY_PLATFORM
229
- when /darwin/
230
- run_command("system_profiler SPStorageDataType | grep 'Serial Number:' | awk '{print $3}'")&.split("\n")
231
- when /linux/
232
- run_command('lsblk -d -o serial -n')&.split("\n")
233
- when /mswin|mingw/
234
- run_command('wmic diskdrive get serialnumber | findstr /V "SerialNumber"')&.split("\n")
235
- else
236
- []
237
- end
238
- serials&.compact&.map(&:strip)&.reject(&:empty?)&.sort&.join(',')
33
+ def gather_components
34
+ @components ||= source.gather
35
+ end
36
+
37
+ def source
38
+ @source ||= case RUBY_PLATFORM
39
+ when /darwin/
40
+ require 'msid/sources/darwin'
41
+ Msid::Sources::Darwin.new
42
+ when /linux/
43
+ require 'msid/sources/linux'
44
+ Msid::Sources::Linux.new
45
+ when /mswin|mingw/
46
+ require 'msid/sources/windows'
47
+ Msid::Sources::Windows.new
48
+ else
49
+ raise Error, "Unsupported platform: #{RUBY_PLATFORM}"
50
+ end
239
51
  end
240
52
  end
241
53
  end
242
54
 
243
55
  # Shortcut for Msid::Generator.generate
244
- # @param salt [String, nil] An optional salt to add to the fingerprint.
245
56
  def self.generate(salt: nil)
246
57
  Generator.generate(salt: salt)
247
58
  end
59
+
60
+ # Shortcut for Msid::Generator.info
61
+ def self.info
62
+ Generator.info
63
+ end
248
64
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: msid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davide Santangelo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-27 00:00:00.000000000 Z
11
+ date: 2025-12-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Creates a unique and secure machine fingerprint ID by gathering various
14
14
  system hardware and software identifiers. Designed to be difficult to replicate
@@ -25,6 +25,10 @@ files:
25
25
  - README.md
26
26
  - Rakefile
27
27
  - lib/msid.rb
28
+ - lib/msid/source.rb
29
+ - lib/msid/sources/darwin.rb
30
+ - lib/msid/sources/linux.rb
31
+ - lib/msid/sources/windows.rb
28
32
  - lib/msid/version.rb
29
33
  - msid.gemspec
30
34
  homepage: https://github.com/davidesantangelo/msid