tem_drm 0.0.1

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,2 @@
1
+ v0.0.1. Put together a gem from previous code.
2
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2007 Massachusetts Institute of Technology
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ bin/drm_wrap.rb
2
+ bin/drm_key2proc.rb
3
+ bin/drm_unwrap.rb
4
+ bin/drm_proxy.rb
5
+ LICENSE
6
+ test/kick_vlc.xspf
7
+ lib/drm/unwrapper.rb
8
+ lib/drm/wrapper.rb
9
+ lib/drm/tem.rb
10
+ lib/drm/io_procs.rb
11
+ lib/drm/metadata.rb
12
+ lib/drm/http_procs.rb
13
+ lib/drm/drm.rb
14
+ README
15
+ CHANGELOG
16
+ Manifest
data/README ADDED
@@ -0,0 +1,3 @@
1
+ Proof-of-concept DRM (digital rights management) for the TEM.
2
+
3
+ Commands will be documented soon.
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ require 'drm'
3
+
4
+ require 'rubygems'
5
+ gem 'tem_ruby'
6
+ require 'tem_ruby'
7
+
8
+ unless ARGV.length == 2
9
+ print "Usage: #{$0} key_file proc_file\n"
10
+ exit
11
+ end
12
+
13
+ $terminal = Tem::SCard::JCOPRemoteTerminal.new
14
+ unless $terminal.connect
15
+ $terminal.disconnect
16
+ $terminal = Tem::SCard::PCSCTerminal.new
17
+ $terminal.connect
18
+ end
19
+ $javacard = Tem::SCard::JavaCard.new($terminal)
20
+ $tem = Tem::Session.new($javacard)
21
+ tem_pubek = $tem.pubek
22
+
23
+ key_fname = ARGV[0]
24
+ proc_fname = ARGV[1]
25
+
26
+ key = File.open(key_fname, 'rb') { |f| f.read }
27
+ proc = DRM::Tem.tem_proc_for_filekey key, tem_pubek
28
+ File.open(proc_fname, 'wb') { |f| f.write proc.to_yaml_str }
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+ require 'socket'
3
+ require 'thread'
4
+ require 'time'
5
+
6
+ require 'rubygems'
7
+ gem 'tem_ruby'
8
+ require 'tem_ruby'
9
+
10
+ require 'drm.rb'
11
+
12
+ class Socket
13
+ def self.pack_sockaddr_in6(port, address, options = {})
14
+ raise 'wrong address format' unless address.length == 16
15
+ [28, Socket::AF_INET6, options[:flowinfo] || 0, port].pack('CCnN') + address.pack('C*') + [0].pack('N')
16
+ end
17
+ end
18
+
19
+ class DRM::Proxy
20
+ def initialize(conf)
21
+ @key_prefix = conf[:key_prefix]
22
+
23
+ @tcp4 = Socket.new Socket::AF_INET, Socket::SOCK_STREAM, Socket::PF_UNSPEC
24
+ @tcp4.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
25
+ # @tcp4.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
26
+ @tcp4.bind Socket.pack_sockaddr_in(conf[:port], '0.0.0.0')
27
+ @tcp4.listen 10
28
+ @tcp4_thread = Thread.new(@tcp4) { |s| sock_loop s }
29
+
30
+ begin
31
+ @tcp6 = Socket.new Socket::AF_INET6, Socket::SOCK_STREAM, Socket::PF_UNSPEC
32
+ @tcp6.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true
33
+ # @tcp6.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true
34
+ @tcp6.bind Socket.pack_sockaddr_in6(conf[:port], Array.new(16, 0))
35
+ @tcp6.listen 10
36
+ @tcp6_thread = Thread.new(@tcp6) { |s| sock_loop s }
37
+ rescue
38
+ puts "Borg IPv6 setup failed: #{$!}"
39
+ @tcp6 = nil
40
+ @tcp6_thread = nil
41
+ end
42
+ end
43
+
44
+ def sock_loop(socket)
45
+ while true do
46
+ begin
47
+ client_sock, client_sockaddr = socket.accept
48
+ print "Accepted client with address #{client_sockaddr.unpack('C*').join(', ')}\n"
49
+ Thread.new(client_sock) do |s|
50
+ begin
51
+ serve_client s
52
+ rescue
53
+ print "Unexpected exception #{$!}\n"
54
+ print $!.backtrace.join("\n") + "\n"
55
+ end
56
+ end
57
+ rescue
58
+ print "Unexpected exception #{$!}\n"
59
+ print $!.backtrace.join("\n") + "\n"
60
+ end
61
+ end
62
+ end
63
+
64
+ def serve_client(socket)
65
+ headers_string = ''
66
+ while true do
67
+ hdr = socket.recv 4096
68
+ headers_string += hdr
69
+ break if headers_string =~ /(\r\n){2}/
70
+ end
71
+
72
+ headers_array = headers_string.split("\r\n")
73
+ request = Hash[*([:method, :uri, :protocol].zip(headers_array.shift.split(' ')).reject { |va| va.any? { |vm| vm.nil? }}.flatten)]
74
+ headers = Hash[*(headers_array.map { |line| line.split(':', 2).map { |e| e.strip } }.flatten)]
75
+
76
+ serve_request request, headers, socket
77
+ socket.close unless socket.closed?
78
+ end
79
+
80
+ def serve_request(request, headers, socket)
81
+ pp request
82
+ pp headers
83
+
84
+ raise "I don't know how to do #{request[:method]}" unless request[:method] == 'GET'
85
+
86
+ pp request[:uri]
87
+ match_data = request[:uri].match(/^\/\?url\=(.*)/)
88
+ unless match_data.nil?
89
+ real_uri = URI.unescape(match_data[1])
90
+ drm_uri = URI.escape(real_uri + '.drm-enc')
91
+ pp [real_uri, drm_uri]
92
+ else
93
+ # unencrypted file
94
+ end
95
+
96
+ begin
97
+ read_proc = DRM::HttpProcs.read_proc drm_uri
98
+
99
+ unwrapper = DRM::Unwrapper.new read_proc
100
+ metadata = unwrapper.metadata
101
+ rescue URI::InvalidURIError
102
+ metadata = nil
103
+ end
104
+
105
+ if metadata.nil?
106
+ send_headers(socket, 404)
107
+ return
108
+ end
109
+
110
+ pp metadata
111
+
112
+ key_fname = @key_prefix + File.basename(real_uri) + '.drm-tem'
113
+ proc = Tem::SecPack.new_from_yaml_str(File.open(key_fname, 'rb') { |f| f.read })
114
+ subkey_proc = DRM::Tem.subkey_proc_for_tem_proc(proc)
115
+
116
+ data_queue = Queue.new
117
+ write_proc = Proc.new do |data|
118
+ data_queue.enq data
119
+ end
120
+
121
+ length = metadata.length
122
+ if headers['Range']
123
+ # parse content range, send in relevant stuff
124
+ units, ranges_str = *headers['Range'].split('=', 2)
125
+ if units != 'bytes'
126
+ send_headers(socket, 416, 0, 'text/html', 'Content-Range' => "0-#{length - 1}/#{length}")
127
+ return
128
+ end
129
+
130
+ ranges = ranges_str.split(',').map do |srange|
131
+ # break up and deal with negative values
132
+ srange_ends = srange.split('-',2).map! { |s| (s.nil? or s.empty?) ? -1 : s.to_i }
133
+ if srange_ends[0] < 0
134
+ srange_ends = [length - srange_ends[1], length - 1]
135
+ elsif srange_ends[1] < 0
136
+ srange_ends[1] = length - 1
137
+ end
138
+ # clamp
139
+ srange_ends.map! { |i| (i < length) ? i : length - 1 }
140
+ end
141
+
142
+ # cheat and reply w/ first range 'cause multi-part responses are hard
143
+ send_headers(socket, 206, ranges[0][1] + 1 - ranges[0][0], metadata.mime_type, 'Accept-Ranges' => 'bytes', 'ETag' => metadata.etag, 'Content-Range' => "bytes #{ranges[0][0]}-#{ranges[0][1]}/#{length}")
144
+ Thread.new do
145
+
146
+ unwrapper.unwrap ranges[0][0], ranges[0][1], read_proc, write_proc, subkey_proc
147
+ data_queue.enq nil
148
+ end
149
+ else
150
+ send_headers(socket, 200, length, metadata.mime_type, 'Accept-Ranges' => 'bytes', 'ETag' => metadata.etag)
151
+ Thread.new do
152
+ unwrapper.unwrap 0, length, read_proc, write_proc, subkey_proc
153
+ data_queue.enq nil
154
+ end
155
+ end
156
+
157
+ loop do
158
+ data = data_queue.deq
159
+ break if data.nil?
160
+
161
+ sent_data = 0
162
+ sent_data += socket.send(data[sent_data..-1], 0) while sent_data < data.length
163
+ end
164
+ end
165
+
166
+ def send_headers(socket, status, length = 0, mime_type = 'text/html', headers = {})
167
+ reason = headers[:reason] || {200 => 'OK', 404 => 'File not found'}[status]
168
+
169
+ response_line = "HTTP/1.1 #{status} #{reason}\r\n"
170
+
171
+ response_headers = { 'Connection' => 'close', 'Server' => 'Rubylicious/1.0', 'Date' => Time.now.rfc2822,
172
+ 'Content-Type' => mime_type, 'Content-Length' => length }.merge headers
173
+
174
+ pp response_headers
175
+ socket.send response_line, 0
176
+ socket.send response_headers.to_a.map { |k,v| k.to_s + ': ' + v.to_s }.join("\r\n") + "\r\n\r\n", 0
177
+ end
178
+ end
179
+
180
+ STDOUT.sync = true
181
+ if File.exists? 'drm_proxy.yml'
182
+ conf = File.open('drm_proxy.yml', 'r') { |f| YAML::load f }
183
+ else
184
+ conf = { :port => 8080, :key_prefix => './files/' }
185
+ File.open('drm_proxy.yml', 'w') { |f| YAML::dump conf, f }
186
+ end
187
+
188
+ def connect_tem
189
+ creation_proc = Kernel.proc { $terminal = Tem::SCard::JCOPRemoteTerminal.new }
190
+ connect_proc = Kernel.proc { creation_proc.call(); $terminal.connect }
191
+
192
+ unless connect_proc.call()
193
+ $terminal.disconnect
194
+ creation_proc = Kernel.proc { $terminal = Tem::SCard::PCSCTerminal.new }
195
+ connect_proc.call()
196
+ end
197
+ $javacard = Tem::SCard::JavaCard.new($terminal)
198
+ $tem = Tem::Session.new($javacard)
199
+ class <<$tem
200
+ def issue_apdu(*args)
201
+ loop do
202
+ begin
203
+ return super(*args)
204
+ rescue
205
+ print $! + "\n"
206
+ print $!.backtrace.join("\n") + "\n"
207
+ connect_proc.call()
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ connect_tem
215
+
216
+ server = DRM::Proxy.new conf
217
+
218
+ print "Listening on port #{conf[:port]}\n"
219
+ sleep 1 while true
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ require 'drm'
3
+
4
+ require 'rubygems'
5
+ gem 'tem_ruby'
6
+ require 'tem_ruby'
7
+
8
+ unless ARGV.length == 3
9
+ print "Usage: #{$0} encrypted_file key_file decrypted_file\n"
10
+ exit
11
+ end
12
+
13
+ encrypted_fname = ARGV[0]
14
+ key_fname = ARGV[1]
15
+ decrypted_fname = ARGV[2]
16
+ begin
17
+ read_proc = DRM::HttpProcs.read_proc encrypted_fname
18
+ pp 'http ok'
19
+ rescue URI::InvalidURIError
20
+ read_proc = DRM::IoProcs.read_proc File.open(encrypted_fname, 'rb')
21
+ end
22
+
23
+ if key_fname =~ /\.drm\-tem$/
24
+ proc = Tem::SecPack.new_from_yaml_str(File.open(key_fname, 'rb') { |f| f.read })
25
+ subkey_proc = DRM::Tem.subkey_proc_for_tem_proc(proc)
26
+
27
+ $terminal = Tem::SCard::JCOPRemoteTerminal.new
28
+ unless $terminal.connect
29
+ $terminal.disconnect
30
+ $terminal = Tem::SCard::PCSCTerminal.new
31
+ $terminal.connect
32
+ end
33
+ $javacard = Tem::SCard::JavaCard.new($terminal)
34
+ $tem = Tem::Session.new($javacard)
35
+ elsif key_fname =~ /\.drm\-key$/
36
+ key = File.open(key_fname, 'rb') { |f| f.read }
37
+ subkey_proc = Proc.new do |metadata, key_index|
38
+ metadata.subkey_from_filekey key_index, key
39
+ end
40
+ else
41
+ print "Unknown key file format in #{key_fname} (need .drm-tem and .drm-key)\n"
42
+ exit
43
+ end
44
+
45
+ File.open(decrypted_fname, 'wb') do |outfile|
46
+ unwrapper = DRM::Unwrapper.new read_proc
47
+ unwrapper.unwrap 0, unwrapper.metadata.length - 1, read_proc, DRM::IoProcs.write_proc(outfile), subkey_proc
48
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ require 'drm'
3
+
4
+ unless ARGV.length == 1
5
+ print "Usage: #{$0} file\n"
6
+ exit
7
+ end
8
+
9
+ original_fname = ARGV[0]
10
+ encrypted_fname = ARGV[0] + '.drm-enc'
11
+ key_fname = ARGV[0] + '.drm-key'
12
+
13
+ in_length = File.stat(original_fname).size
14
+ wrapper = DRM::Wrapper.new in_length, 'audio/mp3'
15
+
16
+ File.open(key_fname, 'wb') { |f| f.write wrapper.filekey }
17
+
18
+ File.open(original_fname, 'rb') do |infile|
19
+ File.open(encrypted_fname, 'wb') do |outfile|
20
+ wrapper.wrap DRM::IoProcs.read_proc(infile), DRM::IoProcs.write_proc(outfile)
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # the DRM namespace
2
+ module DRM
3
+ end
4
+
5
+ require 'drm/metadata.rb'
6
+ require 'drm/wrapper.rb'
7
+ require 'drm/unwrapper.rb'
8
+
9
+ require 'drm/io_procs.rb'
10
+ require 'drm/http_procs.rb'
11
+ require 'drm/tem.rb'
@@ -0,0 +1,17 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module DRM::HttpProcs
5
+ # produces a read_proc(length, offset) for the given HTTP +url+
6
+ def self.read_proc(url)
7
+ uri = URI.parse url
8
+ Proc.new do |length, offset|
9
+ request = Net::HTTP::Get.new uri.path
10
+ request.set_range(offset, length)
11
+ response = Net::HTTP.start(uri.host, uri.port) { |http| http.request request }
12
+ # content_range_str = nil
13
+ # response.each_header { |key, value| content_range_str = value and break if key.downcase == 'content-range' }
14
+ response.body
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module DRM::IoProcs
2
+ # produces a read_proc(length, offset) for the given +io+ instance of IO
3
+ def self.read_proc(io)
4
+ Proc.new do |length, offset|
5
+ io.seek offset
6
+ io.read length
7
+ end
8
+ end
9
+
10
+ # produces a write_proc(data) for the given +io+ instance of IO
11
+ def self.write_proc(io)
12
+ Proc.new do |data|
13
+ io.write data
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,119 @@
1
+ require 'digest'
2
+ require 'openssl'
3
+ require 'yaml'
4
+
5
+ class DRM::Metadata
6
+ # internal constructor; access through new_for_file and new_from_hash
7
+ def initialize(data_hash)
8
+ @data = data_hash
9
+ end
10
+
11
+ # metadata object from a hash obtained using to_hash
12
+ def self.new_from_hash(data_hash)
13
+ # TODO: some validation to ensure the hash contains the metadata fields
14
+ self.new data_hash
15
+ end
16
+
17
+ # metadata object from a string obtained using to_yaml_str
18
+ def self.new_from_yaml_str(yaml_str)
19
+ data_hash = YAML.load yaml_str
20
+ # TODO: verify we're actually dealing with a hash
21
+ self.new_from_hash data_hash
22
+ end
23
+
24
+ # new metadata object for a content file
25
+ def self.new_for_file(length, mime_type, options = {})
26
+ # storage strategy
27
+ data_hash = { :length => length, :mime_type => mime_type }
28
+ blocksize = options[:block_size]
29
+ if blocksize.nil?
30
+ blocksize = 1 << 16
31
+ # TODO: adaptive block size
32
+ end
33
+ data_hash[:block_size] = blocksize
34
+
35
+ # encryption strategy
36
+ keys = options[:keys] || 8
37
+ # TODO: adaptive number of keys
38
+ data_hash[:keys] = keys
39
+ data_hash[:blocks] = (length + blocksize - 1) / blocksize
40
+ data_hash[:blocks_per_key] = (data_hash[:blocks] + keys - 1) / keys
41
+
42
+ # serial number
43
+ data_hash[:etag] = Digest::SHA1.hexdigest((0...32).map { |i| rand(256) }.pack('C*'))
44
+
45
+ self.new data_hash
46
+ end
47
+
48
+ # creates a new file key
49
+ def self.new_filekey
50
+ (0...32).map { |i| rand * 256 }.pack('C*')
51
+ end
52
+
53
+ # converts the metadata to an easy-to-serialize hash
54
+ def to_hash
55
+ @data
56
+ end
57
+
58
+ # converts the metadata to an easy-to-serialize to_yaml_str
59
+ def to_yaml_str
60
+ self.to_hash.to_yaml.to_s
61
+ end
62
+
63
+ # computes the index of the subkey to be used for decoding a block
64
+ def subkey_index(block_index)
65
+ block_index / @data[:blocks_per_key]
66
+ end
67
+
68
+ # computers a subkey from a file key (this will usually happen on a TEM)
69
+ def subkey_from_filekey(subkey_index, filekey)
70
+ Digest::SHA1.digest(filekey + [subkey_index].pack('N'))
71
+ end
72
+
73
+ # the number of blocks in the content file
74
+ def blocks
75
+ @data[:blocks]
76
+ end
77
+
78
+ # the size of a content block
79
+ def block_size
80
+ @data[:block_size]
81
+ end
82
+
83
+ # the length of the content
84
+ def length
85
+ @data[:length]
86
+ end
87
+
88
+ # the MIME type of the content
89
+ def mime_type
90
+ @data[:mime_type]
91
+ end
92
+
93
+ # the ETag of the document
94
+ def etag
95
+ @data[:etag]
96
+ end
97
+
98
+ # encrypts a content block
99
+ def encrypt_block(block_data, block_index, subkey)
100
+ cipher = OpenSSL::Cipher::Cipher.new 'aes-128-ecb'
101
+ cipher.encrypt
102
+ cipher.key = subkey
103
+ cipher.iv = [block_index].pack('N') * 4
104
+ eblock = cipher.update(block_data)
105
+ return eblock
106
+ end
107
+
108
+ # decrypts a content block
109
+ def decrypt_block(crypted_data, block_index, subkey)
110
+ cipher = OpenSSL::Cipher::Cipher.new 'aes-128-ecb'
111
+ cipher.decrypt
112
+ cipher.key = subkey
113
+ cipher.iv = [block_index].pack('N') * 4
114
+ dblock = cipher.update(crypted_data)
115
+ # compensate for openssl being retarded
116
+ dblock += cipher.update([0].pack('C') * 16)
117
+ return dblock
118
+ end
119
+ end
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ gem 'tem_ruby'
3
+ require 'tem_ruby'
4
+
5
+ module DRM::Tem
6
+ # produces a TEM procedure which yields the subkeys for +filekey+
7
+ # the procedure is sealed with the TEM public key +pubek+
8
+ def self.tem_proc_for_filekey(filekey, pubek)
9
+ key_proc = Tem::Session.assemble do |p|
10
+ p.ldbc filekey.length + 4
11
+ p.outnew
12
+ p.mdfxb :from => :filekey, :size => filekey.length + 4, :to => (1 << 16) - 1
13
+ p.halt
14
+ p.label :filekey
15
+ p.immed :ubyte, filekey.unpack('C*')
16
+ p.label :key_index
17
+ p.filler :ubyte, 2
18
+ p.filler :ushort, 2
19
+ p.label :end_of_hash
20
+ p.stack
21
+ p.extra 8
22
+ end
23
+ key_proc.seal pubek, :filekey, :key_index
24
+ return key_proc
25
+ end
26
+
27
+ # produces a subkey_proc(metadata, index) that works by calling a TEM with +tem_proc+
28
+ # +tem_proc+ must be obtained by calling tem_proc_for_filekey
29
+ def self.subkey_proc_for_tem_proc(tem_proc)
30
+ Proc.new do |metadata, index|
31
+ index_str = [0, 0] + Tem::Session.to_tem_ushort(index)
32
+ tem_proc.body[tem_proc.label_address(:key_index), index_str.length] = index_str
33
+ # TODO: more elegant solution than $tem
34
+ $tem.execute(tem_proc).pack('C*')
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ class DRM::Unwrapper
2
+ # new unwrapper for a DRM-wrapped file
3
+ # read_proc(length, offset) reads length bytes at offset from the DRM-wrapped file
4
+ def initialize(read_proc)
5
+ @data_offset = [0].pack('N').length
6
+ metadata_lenstr = read_proc.call @data_offset, 0
7
+ metadata_str = read_proc.call metadata_lenstr.unpack('N').first, @data_offset
8
+ @data_offset += metadata_str.length
9
+ @metadata = DRM::Metadata.new_from_yaml_str metadata_str
10
+ end
11
+
12
+ # the DRM metadata for the wrapped file
13
+ attr_reader :metadata
14
+
15
+ # unwraps bytes +start_byte+..+end_byte+ from the content of a DRM-wrapped file
16
+ # read_proc(length, offset) reads length bytes at offset from the file to be wrapped
17
+ # write_proc(data) appends the given data to the file containing the wrapped content
18
+ # subkey_proc(metadata, index) produces the +index+th DRM subkey for the contents identified by +metadata+
19
+ def unwrap(start_byte, end_byte, read_proc, write_proc, subkey_proc)
20
+ block_size = @metadata.block_size
21
+ start_block = start_byte / block_size
22
+ end_block = end_byte / block_size
23
+
24
+ in_offset = @data_offset + start_block * block_size
25
+ old_ski = nil
26
+ subkey = nil
27
+ start_block.upto(end_block) do |block_index|
28
+ # read
29
+ crypted_data = read_proc.call block_size, in_offset
30
+ in_offset += crypted_data.length
31
+ # decrypt
32
+ ski = @metadata.subkey_index(block_index)
33
+ subkey = subkey_proc.call @metadata, ski unless ski == old_ski
34
+ old_ski = ski
35
+ block_data = @metadata.decrypt_block crypted_data, block_index, subkey
36
+ # write
37
+ write_proc.call block_data[((block_index == start_block) ? start_byte % block_size : 0)..((block_index == end_block) ? end_byte % block_size : block_size - 1)]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ class DRM::Wrapper
2
+ # new wrapper for a contents file
3
+ # the file has +length+ bytes and its MIME type is +mime_type+
4
+ def initialize(length, mime_type)
5
+ @filekey = DRM::Metadata.new_filekey
6
+ @metadata = DRM::Metadata.new_for_file length, mime_type
7
+ end
8
+
9
+ # the key used to wrap the file
10
+ attr_reader :filekey
11
+ # the DRM metadata for the wrapped file
12
+ attr_reader :metadata
13
+
14
+ # wraps a content file with DRM protection
15
+ # read_proc(length, offset) reads length bytes at offset from the file to be wrapped
16
+ # write_proc(data) appends the given data to the file containing the wrapped content
17
+ def wrap(read_proc, write_proc)
18
+ # push the metadata
19
+ metadata_str = @metadata.to_yaml_str
20
+ metadata_lenstr = [metadata_str.length].pack('N')
21
+ write_proc.call metadata_lenstr
22
+ write_proc.call metadata_str
23
+
24
+ # push the data
25
+ block_size = @metadata.block_size
26
+ length = @metadata.length
27
+ in_offset = 0
28
+ 0.upto(@metadata.blocks - 1) do |block_index|
29
+ # read
30
+ read_size = (block_size <= length - in_offset) ? block_size : length - in_offset
31
+ block_data = read_proc.call read_size, in_offset
32
+ in_offset += read_size
33
+ # encrypt
34
+ subkey = @metadata.subkey_from_filekey(metadata.subkey_index(block_index), @filekey)
35
+ crypted_data = @metadata.encrypt_block block_data, block_index, subkey
36
+ # write
37
+ write_proc.call crypted_data
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,53 @@
1
+
2
+ # Gem::Specification for Tem_drm-0.0.1
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{tem_drm}
7
+ s.version = "0.0.1"
8
+
9
+ s.specification_version = 2 if s.respond_to? :specification_version=
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Victor Costan"]
13
+ s.date = %q{2008-06-11}
14
+ s.description = %q{Personal DRM (Digital Rights Management) relying on the TEM.}
15
+ s.email = %q{victor@costan.us}
16
+ s.executables = ["drm_wrap.rb", "drm_key2proc.rb", "drm_unwrap.rb", "drm_proxy.rb"]
17
+ s.extra_rdoc_files = ["bin/drm_wrap.rb", "bin/drm_key2proc.rb", "bin/drm_unwrap.rb", "bin/drm_proxy.rb", "LICENSE", "lib/drm/unwrapper.rb", "lib/drm/wrapper.rb", "lib/drm/tem.rb", "lib/drm/io_procs.rb", "lib/drm/metadata.rb", "lib/drm/http_procs.rb", "lib/drm/drm.rb", "README", "CHANGELOG"]
18
+ s.files = ["bin/drm_wrap.rb", "bin/drm_key2proc.rb", "bin/drm_unwrap.rb", "bin/drm_proxy.rb", "LICENSE", "test/kick_vlc.xspf", "lib/drm/unwrapper.rb", "lib/drm/wrapper.rb", "lib/drm/tem.rb", "lib/drm/io_procs.rb", "lib/drm/metadata.rb", "lib/drm/http_procs.rb", "lib/drm/drm.rb", "README", "CHANGELOG", "Manifest", "tem_drm.gemspec"]
19
+ s.has_rdoc = true
20
+ s.homepage = %q{http://tem.rubyforge.org}
21
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tem_drm", "--main", "README"]
22
+ s.require_paths = ["lib"]
23
+ s.rubyforge_project = %q{tem}
24
+ s.rubygems_version = %q{1.1.1}
25
+ s.summary = %q{Personal DRM (Digital Rights Management) relying on the TEM.}
26
+
27
+ s.add_dependency(%q<tem_ruby>, [">= 0.9.0"])
28
+ end
29
+
30
+
31
+ # # Original Rakefile source (requires the Echoe gem):
32
+ #
33
+ # require 'rubygems'
34
+ # gem 'echoe'
35
+ # require 'echoe'
36
+ #
37
+ # Echoe.new('tem_drm') do |p|
38
+ # p.project = 'tem' # rubyforge project
39
+ #
40
+ # p.author = 'Victor Costan'
41
+ # p.email = 'victor@costan.us'
42
+ # p.summary = 'Personal DRM (Digital Rights Management) relying on the TEM.'
43
+ # p.url = 'http://tem.rubyforge.org'
44
+ # p.dependencies = ['tem_ruby >=0.9.0']
45
+ #
46
+ # p.need_tar_gz = false
47
+ # p.rdoc_pattern = /^(lib|bin|tasks|ext)|^BUILD|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
48
+ # end
49
+ #
50
+ # if $0 == __FILE__
51
+ # Rake.application = Rake::Application.new
52
+ # Rake.application.run
53
+ # end
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <playlist version="0" xmlns="http://xspf.org/ns/0/">
3
+ <title>kick_vlc.xspf</title>
4
+ <location>file:///Users/victor/Documents/workspace/tem_drm/kick_vlc.xspf</location>
5
+ <trackList>
6
+ <track>
7
+ <location>http://localhost:8080/?url=http:%252F%252Fweb.mit.edu%252Fcostan%252Fwww%252Fbooks%252FTocarte.Toa.mp3</location>
8
+ <title>Tocarte Toa</title>
9
+ </track>
10
+ </trackList>
11
+ </playlist>
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tem_drm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Victor Costan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-11 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: tem_ruby
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.0
23
+ version:
24
+ description: Personal DRM (Digital Rights Management) relying on the TEM.
25
+ email: victor@costan.us
26
+ executables:
27
+ - drm_wrap.rb
28
+ - drm_key2proc.rb
29
+ - drm_unwrap.rb
30
+ - drm_proxy.rb
31
+ extensions: []
32
+
33
+ extra_rdoc_files:
34
+ - bin/drm_wrap.rb
35
+ - bin/drm_key2proc.rb
36
+ - bin/drm_unwrap.rb
37
+ - bin/drm_proxy.rb
38
+ - LICENSE
39
+ - lib/drm/unwrapper.rb
40
+ - lib/drm/wrapper.rb
41
+ - lib/drm/tem.rb
42
+ - lib/drm/io_procs.rb
43
+ - lib/drm/metadata.rb
44
+ - lib/drm/http_procs.rb
45
+ - lib/drm/drm.rb
46
+ - README
47
+ - CHANGELOG
48
+ files:
49
+ - bin/drm_wrap.rb
50
+ - bin/drm_key2proc.rb
51
+ - bin/drm_unwrap.rb
52
+ - bin/drm_proxy.rb
53
+ - LICENSE
54
+ - test/kick_vlc.xspf
55
+ - lib/drm/unwrapper.rb
56
+ - lib/drm/wrapper.rb
57
+ - lib/drm/tem.rb
58
+ - lib/drm/io_procs.rb
59
+ - lib/drm/metadata.rb
60
+ - lib/drm/http_procs.rb
61
+ - lib/drm/drm.rb
62
+ - README
63
+ - CHANGELOG
64
+ - Manifest
65
+ - tem_drm.gemspec
66
+ has_rdoc: true
67
+ homepage: http://tem.rubyforge.org
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --line-numbers
71
+ - --inline-source
72
+ - --title
73
+ - Tem_drm
74
+ - --main
75
+ - README
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project: tem
93
+ rubygems_version: 1.1.1
94
+ signing_key:
95
+ specification_version: 2
96
+ summary: Personal DRM (Digital Rights Management) relying on the TEM.
97
+ test_files: []
98
+