robocap-decryption-sdk 1.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.
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'securerandom'
5
+ require_relative 'config'
6
+ require_relative 'errors'
7
+
8
+ module Robocap
9
+ module SDK
10
+ module RSAOAEP
11
+ module_function
12
+
13
+ def cipher_len_for_key(key)
14
+ key.n.num_bytes
15
+ end
16
+
17
+ def wrap_key(plaintext, public_key, plain_len:, cipher_len: nil)
18
+ unless plaintext.bytesize == plain_len
19
+ raise ArgumentError, "plaintext must be #{plain_len} bytes (got #{plaintext.bytesize})"
20
+ end
21
+ expected = cipher_len || cipher_len_for_key(public_key)
22
+ ciphertext = public_key.encrypt(
23
+ plaintext,
24
+ rsa_padding_mode: 'oaep',
25
+ rsa_oaep_md: 'sha256',
26
+ rsa_mgf1_md: 'sha256',
27
+ )
28
+ unless ciphertext.bytesize == expected
29
+ raise ArgumentError, "RSA ciphertext length #{ciphertext.bytesize} != #{expected}"
30
+ end
31
+ ciphertext
32
+ end
33
+
34
+ def unwrap_key(ciphertext, private_key,
35
+ plain_len:, cipher_len: nil,
36
+ decode_error: ErrorCode::ERR_K2_DECODE,
37
+ length_error: ErrorCode::ERR_K2_PLAINTEXT_LENGTH)
38
+ expected = cipher_len || cipher_len_for_key(private_key)
39
+ unless ciphertext.bytesize == expected
40
+ raise Error.new(code: decode_error, message: "RSA ciphertext must be #{expected} bytes")
41
+ end
42
+ plaintext = begin
43
+ private_key.decrypt(
44
+ ciphertext,
45
+ rsa_padding_mode: 'oaep',
46
+ rsa_oaep_md: 'sha256',
47
+ rsa_mgf1_md: 'sha256',
48
+ )
49
+ rescue OpenSSL::PKey::PKeyError, OpenSSL::PKey::RSAError => exc
50
+ raise Error.new(code: decode_error, message: 'RSA-OAEP unwrap failed', detail: { reason: exc.message })
51
+ end
52
+ unless plaintext.bytesize == plain_len
53
+ raise Error.new(code: length_error, message: "Decrypted key length #{plaintext.bytesize} != #{plain_len}")
54
+ end
55
+ plaintext
56
+ end
57
+
58
+ def wrap_aes_key(device_aes, public_key)
59
+ wrap_key(
60
+ device_aes, public_key,
61
+ plain_len: Config::AES_KEY_BYTES,
62
+ cipher_len: Config::RSA_CIPHERTEXT_BYTES,
63
+ )
64
+ end
65
+
66
+ def unwrap_aes_key(ciphertext, private_key)
67
+ unwrap_key(
68
+ ciphertext, private_key,
69
+ plain_len: Config::AES_KEY_BYTES,
70
+ cipher_len: Config::RSA_CIPHERTEXT_BYTES,
71
+ )
72
+ end
73
+
74
+ def wrap_cek(cek, public_key)
75
+ wrap_key(
76
+ cek, public_key,
77
+ plain_len: Config::CEK_BYTES,
78
+ cipher_len: Config::RSA_2048_CIPHERTEXT_BYTES,
79
+ )
80
+ end
81
+
82
+ def unwrap_cek(ciphertext, private_key)
83
+ unwrap_key(
84
+ ciphertext, private_key,
85
+ plain_len: Config::CEK_BYTES,
86
+ cipher_len: Config::RSA_2048_CIPHERTEXT_BYTES,
87
+ decode_error: ErrorCode::ERR_CENC_CEKA_WRAP,
88
+ length_error: ErrorCode::ERR_CENC_CEKA_LENGTH,
89
+ )
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+ require 'tempfile'
6
+ require_relative 'errors'
7
+
8
+ module Robocap
9
+ module SDK
10
+ module VaultLayout
11
+ module_function
12
+
13
+ def ensure_private_dir(path)
14
+ path = Pathname(path)
15
+ FileUtils.mkdir_p(path)
16
+ File.chmod(0o700, path) unless Gem.win_platform?
17
+ end
18
+
19
+ def atomic_write_bytes(target, data)
20
+ target = Pathname(target)
21
+ tmp = nil
22
+ begin
23
+ ensure_private_dir(target.parent)
24
+ tmp = Tempfile.new(['.tmp_', ''], target.parent.to_s, binmode: true)
25
+ tmp.write(data)
26
+ tmp.flush
27
+ tmp.fsync rescue nil
28
+ tmp.close
29
+ File.rename(tmp.path, target.to_s)
30
+ tmp = nil
31
+ rescue SystemCallError => exc
32
+ raise Error.new(
33
+ code: ErrorCode::ERR_VAULT_IO,
34
+ message: "Failed to write #{target}",
35
+ detail: { path: target.to_s, reason: exc.message },
36
+ )
37
+ ensure
38
+ if tmp
39
+ tmp.close unless tmp.closed?
40
+ File.unlink(tmp.path) if File.exist?(tmp.path)
41
+ end
42
+ end
43
+ end
44
+
45
+ def atomic_write_text(target, text, encoding: 'UTF-8')
46
+ atomic_write_bytes(target, text.encode(encoding).b)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Robocap
4
+ module SDK
5
+ VERSION = '1.0.0'
6
+ end
7
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'sdk/version'
4
+ require_relative 'sdk/config'
5
+ require_relative 'sdk/errors'
6
+ require_relative 'sdk/rsa_key_meta'
7
+ require_relative 'sdk/rsa_oaep'
8
+ require_relative 'sdk/vault_layout'
9
+ require_relative 'sdk/key_vault'
10
+ require_relative 'sdk/ffmpeg_cli'
11
+ require_relative 'sdk/mp4_cenc'
12
+ require_relative 'sdk/ownership'
13
+ require_relative 'sdk/rsa_import'
14
+ require_relative 'sdk/rsa_delete'
15
+ require_relative 'sdk/decrypt_cenc'
16
+
17
+ module Robocap
18
+ module SDK
19
+ module_function
20
+
21
+ def import_rsa_key_version(**kwargs)
22
+ RsaImport.call(**kwargs)
23
+ end
24
+
25
+ def delete_rsa_key_version(**kwargs)
26
+ RsaDelete.call(**kwargs)
27
+ end
28
+
29
+ def delete_rsa_key_dir(**kwargs)
30
+ RsaDelete.call_with_dir(**kwargs)
31
+ end
32
+
33
+ def decrypt_cenc_mp4(**kwargs)
34
+ DecryptCenc.call(**kwargs)
35
+ end
36
+
37
+ def verify_customer_private_key(**kwargs)
38
+ Ownership.verify(**kwargs)
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: robocap-decryption-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Frodobots
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.20'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.20'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ description: Ruby port of the Robocap CENC decryption SDK. Reads the same on-disk
55
+ vault and MP4 format as the Python SDK.
56
+ executables:
57
+ - robocap-decryption-sdk
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - README.md
62
+ - exe/robocap-decryption-sdk
63
+ - lib/robocap/sdk.rb
64
+ - lib/robocap/sdk/cli.rb
65
+ - lib/robocap/sdk/config.rb
66
+ - lib/robocap/sdk/decrypt_cenc.rb
67
+ - lib/robocap/sdk/errors.rb
68
+ - lib/robocap/sdk/ffmpeg_cli.rb
69
+ - lib/robocap/sdk/key_vault.rb
70
+ - lib/robocap/sdk/mp4_cenc.rb
71
+ - lib/robocap/sdk/ownership.rb
72
+ - lib/robocap/sdk/rsa_delete.rb
73
+ - lib/robocap/sdk/rsa_import.rb
74
+ - lib/robocap/sdk/rsa_key_meta.rb
75
+ - lib/robocap/sdk/rsa_oaep.rb
76
+ - lib/robocap/sdk/vault_layout.rb
77
+ - lib/robocap/sdk/version.rb
78
+ homepage: https://github.com/frodobots-org/robocap-decryption-sdk
79
+ licenses:
80
+ - Nonstandard
81
+ metadata: {}
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.7.2
97
+ specification_version: 4
98
+ summary: Offline import of RSA keys and decrypt of CENC-encrypted MP4 files.
99
+ test_files: []