tem_drm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+