341 lines
7.6 KiB
Ruby
341 lines
7.6 KiB
Ruby
#!/usr/bin/env ruby
|
|
|
|
require 'openssl'
|
|
require 'zlib'
|
|
require 'stringio'
|
|
require 'rubygems'
|
|
begin
|
|
require 'json'
|
|
rescue
|
|
abort('ERROR: plz2run #> gem install json')
|
|
end
|
|
|
|
require './huffman'
|
|
|
|
# usage: ./content_from_pem.rb 5286016419950084643.pem
|
|
|
|
class BitWriter
|
|
|
|
def initialize(stream)
|
|
@stream = stream
|
|
@byte = 0x00
|
|
@count = 8
|
|
end
|
|
|
|
def write(char)
|
|
if char == '1'
|
|
@byte |= 1 << @count
|
|
end
|
|
@count -= 1
|
|
if @count == -1
|
|
self.pad
|
|
end
|
|
end
|
|
|
|
def write_bits(string)
|
|
string.each_char do |c|
|
|
self.write(c)
|
|
end
|
|
end
|
|
|
|
def pad()
|
|
@count = 8
|
|
@stream.write(Array(@byte).pack('C'))
|
|
@byte = 0x00
|
|
end
|
|
end
|
|
|
|
class Children
|
|
|
|
attr_accessor :children, :written
|
|
|
|
def initialize()
|
|
@children = []
|
|
@written = false
|
|
end
|
|
|
|
def each()
|
|
@children.each do |child|
|
|
yield child
|
|
end
|
|
end
|
|
|
|
def collect()
|
|
@children.each do |child|
|
|
yield child
|
|
end
|
|
end
|
|
|
|
def length()
|
|
@children.length
|
|
end
|
|
def [](i)
|
|
@children[i]
|
|
end
|
|
|
|
def []=(i, val)
|
|
@children[i] = val
|
|
end
|
|
|
|
def <<(other)
|
|
@children << other
|
|
end
|
|
|
|
def join(str)
|
|
@children.join(str)
|
|
end
|
|
|
|
def signature
|
|
@children.sort! do |a, b|
|
|
a.path <=> b.path
|
|
end
|
|
"[" + @children.collect { |x| x.path + x.signature }.join("|") + "]"
|
|
end
|
|
end
|
|
|
|
class Node
|
|
attr_accessor :path, :children, :de_duped, :offset
|
|
|
|
def initialize(path)
|
|
@path = path
|
|
@children = Children.new
|
|
@sig = nil
|
|
@de_duped = false
|
|
@offset = ran_char(2)
|
|
end
|
|
|
|
def has_key?(key)
|
|
@children.each do |child|
|
|
if child.path == key
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
def get_child(name)
|
|
@children.each do |child|
|
|
if child.path == name
|
|
return child
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
def de_duped=(val)
|
|
@de_duped = val
|
|
@children.each do |child|
|
|
child.de_duped = true
|
|
end
|
|
end
|
|
|
|
def signature()
|
|
if @sig.nil?
|
|
@sig = @children.signature
|
|
end
|
|
@sig
|
|
end
|
|
|
|
def flatten()
|
|
flat = [self]
|
|
@children.each do |child|
|
|
flat += child.flatten
|
|
end
|
|
flat
|
|
end
|
|
|
|
def to_json(*a)
|
|
{
|
|
@path => @children
|
|
}.to_json(*a)
|
|
end
|
|
end
|
|
|
|
def akamai_hex_to_content_set(akamai_hex)
|
|
gzipped_hex = akamai_hex.gsub(":","").chomp("00")
|
|
gzipped_data = [gzipped_hex].pack("H*")
|
|
gzipped_data_io = StringIO.new(gzipped_data)
|
|
gz = Zlib::GzipReader.new(gzipped_data_io)
|
|
content_sets = gz.read.split("|")
|
|
begin
|
|
gz.close
|
|
rescue Zlib::GzipFile::NoFooter
|
|
end
|
|
return content_sets
|
|
end
|
|
|
|
def mk_hash(sgmts, parent)
|
|
segment = sgmts.shift
|
|
return parent if segment.nil?
|
|
unless parent.has_key?(segment)
|
|
parent.children << mk_hash(sgmts, Node.new(segment))
|
|
else
|
|
mk_hash(sgmts, parent.get_child(segment))
|
|
# else
|
|
# hash[segment].update(mk_hash(sgmts, hash[segment]))
|
|
end
|
|
return parent
|
|
end
|
|
|
|
def compress_prefix(parent)
|
|
parent.children.each do |child|
|
|
compress_prefix(child)
|
|
end
|
|
if parent.children.length == 1
|
|
puts "compressing #{parent.path} and #{parent.children[0].path}"
|
|
parent.path += "/" + parent.children[0].path
|
|
parent.children = parent.children[0].children
|
|
end
|
|
return parent
|
|
end
|
|
|
|
# given a tree of nodes, try and find branches that match the children of node.
|
|
# if found, replace those branches with node's children
|
|
def de_dupe(tree, node)
|
|
tree.flatten.each do |sub_tree|
|
|
if sub_tree.children == node.children
|
|
# nothing
|
|
elsif node.signature == sub_tree.signature
|
|
sub_tree.de_duped = true
|
|
sub_tree.children = node.children
|
|
puts "Found dupe! " + node.signature unless node.signature == "[]"
|
|
end
|
|
end
|
|
end
|
|
|
|
def de_dupe_driver(tree)
|
|
tree.flatten.each do |node|
|
|
de_dupe(tree, node) unless node.de_duped
|
|
end
|
|
end
|
|
|
|
# simulate random file offsets
|
|
def ran_char(val)
|
|
val = (0..val - 1).map {rand(256).chr}.join
|
|
return val
|
|
end
|
|
|
|
def binary_write(file, parent, string_huff, node_huff)
|
|
# file.write(parent.path)
|
|
# file.write("\0")
|
|
#offset to child node indicies
|
|
# not needed, can just go write to children indicies
|
|
#file.write(ran_char)
|
|
if parent.children.written
|
|
puts "not writing children of #{parent.path}"
|
|
return
|
|
end
|
|
|
|
# number of paths
|
|
length = parent.children.length.to_s
|
|
# path_count = (3 - length.length).times.collect { |i| "0" }.join + length
|
|
# file.write(path_count)
|
|
# puts "CHILD COUNT: " + parent.children.length.to_s
|
|
parent.children.each do |child|
|
|
# puts "PATH: " + child.path
|
|
# file.write(child.path)
|
|
# file.write("\0")
|
|
# index of path string
|
|
file.write_bits(string_huff.encode(child.path))
|
|
# offset to node
|
|
# index of node, that is.
|
|
file.write_bits(node_huff.encode(child))
|
|
end
|
|
# reserve null byte for end of node info
|
|
# 3 0s are reserved in our name huffman table to denote end of node
|
|
file.write_bits("000")
|
|
parent.children.each do |child|
|
|
binary_write(file, child, string_huff, node_huff)
|
|
child.children.written = true
|
|
end
|
|
end
|
|
|
|
def write_strings(file, strings)
|
|
string_io = StringIO.new()
|
|
strings.each_key do |string|
|
|
puts "STRING: " + string
|
|
string_io.write(string)
|
|
string_io.write("\0")
|
|
end
|
|
zlib = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, 15, Zlib::MAX_MEM_LEVEL)
|
|
file.write zlib.deflate(string_io.string, Zlib::FINISH)
|
|
end
|
|
|
|
def collect_strings(parent)
|
|
strings = {}
|
|
parent.flatten.each do |node|
|
|
strings[node.path] = [0, ran_char(1)] unless strings.has_key? node.path
|
|
strings[node.path][0] += 1
|
|
end
|
|
strings
|
|
end
|
|
|
|
def build_huffman_for_strings(parent)
|
|
nodes = parent.flatten.uniq
|
|
paths = nodes.collect {|node| node.path}
|
|
table = HuffmanEncoding.new paths
|
|
end
|
|
|
|
def build_huffman_for_nodes(parent)
|
|
nodes = parent.flatten
|
|
table = HuffmanEncoding.new nodes
|
|
end
|
|
|
|
if $0 == __FILE__
|
|
if ARGV.length == 0
|
|
cert_data = STDIN.read
|
|
|
|
cert = OpenSSL::X509::Certificate.new(cert_data)
|
|
content_hex = cert.extensions.detect {|ext| ext.oid == 'subjectKeyIdentifier' }
|
|
abort('ERROR: no X509v3 extension for subjectKeyIdentifier') unless content_hex
|
|
|
|
puts akamai_hex_to_content_set(content_hex.value).join("|")
|
|
end
|
|
|
|
ARGV.each do |arg|
|
|
next unless FileTest.file?(arg)
|
|
cert_data = File.read(arg)
|
|
|
|
cert = OpenSSL::X509::Certificate.new(cert_data)
|
|
content_hex = cert.extensions.detect {|ext| ext.oid == 'subjectKeyIdentifier' }
|
|
abort('ERROR: no X509v3 extension for subjectKeyIdentifier') unless content_hex
|
|
|
|
ext = File.extname(arg)
|
|
txt_name = File.basename(arg, ext) + ".txt"
|
|
json_name = File.basename(arg, ext) + ".json"
|
|
binary = File.open(File.basename(arg, ext) + ".bin", "w")
|
|
|
|
sets = akamai_hex_to_content_set(content_hex.value)
|
|
|
|
File.open(txt_name, "w+") do |file|
|
|
file.write(sets)
|
|
end
|
|
File.open(json_name, "w+") do |file|
|
|
parent = Node.new("")
|
|
sets.each do |set|
|
|
line = set.start_with?("/") ? set[1..-1] : set
|
|
|
|
# => ["content", "beta", "rhel", "server", "6", "$releasever", "$basearch", "scalablefilesystem", "debug"]
|
|
chunks = line.split("/")
|
|
parent = mk_hash(chunks, parent)
|
|
end
|
|
# prime the signatures
|
|
parent.signature
|
|
de_dupe_driver(parent)
|
|
parent = compress_prefix(parent)
|
|
|
|
string_huff = build_huffman_for_strings(parent)
|
|
node_huff = build_huffman_for_nodes(parent)
|
|
|
|
strings = collect_strings(parent)
|
|
write_strings(binary, strings)
|
|
bit_file = BitWriter.new binary
|
|
binary_write(bit_file, parent, string_huff, node_huff)
|
|
bit_file.pad
|
|
file.write(parent.to_json)
|
|
|
|
end
|
|
puts "Wrote:\n [%d] %s\n [%d] %s" % [File.size(txt_name), txt_name, File.size(json_name), json_name]
|
|
|
|
end
|
|
end
|