cryptosphere 0.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.
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.travis.yml +6 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +77 -0
- data/Rakefile +6 -0
- data/bin/csphere +4 -0
- data/cryptosphere.gemspec +24 -0
- data/lib/cryptosphere.rb +45 -0
- data/lib/cryptosphere/blobs/blob.rb +117 -0
- data/lib/cryptosphere/blobs/tree.rb +22 -0
- data/lib/cryptosphere/cli.rb +11 -0
- data/lib/cryptosphere/crypto/asymmetric_cipher.rb +73 -0
- data/lib/cryptosphere/crypto/kdf.rb +12 -0
- data/lib/cryptosphere/crypto/signature_algorithm.rb +17 -0
- data/lib/cryptosphere/head.rb +77 -0
- data/lib/cryptosphere/identity.rb +21 -0
- data/lib/cryptosphere/protocol/handshake.rb +37 -0
- data/lib/cryptosphere/version.rb +3 -0
- data/logo.png +0 -0
- data/spec/cryptosphere/blobs/blob_spec.rb +10 -0
- data/spec/cryptosphere/blobs/tree_spec.rb +10 -0
- data/spec/cryptosphere/head_spec.rb +21 -0
- data/spec/cryptosphere/identity_spec.rb +7 -0
- data/spec/cryptosphere/protocol/handshake_spec.rb +22 -0
- data/spec/fixtures/alice.key +27 -0
- data/spec/fixtures/bob.key +27 -0
- data/spec/spec_helper.rb +43 -0
- data/tasks/rspec.task +7 -0
- metadata +143 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Tony Arcieri
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+

|
2
|
+
================
|
3
|
+
[](http://travis-ci.org/tarcieri/cryptosphere)
|
4
|
+
|
5
|
+
> "I want people to see the truth... regardless of who they are... because
|
6
|
+
> without information, you cannot make informed decisions as a public" _-- Bradley Manning_
|
7
|
+
|
8
|
+
The Cryptosphere is a global peer-to-peer cryptosystem for publishing and
|
9
|
+
securely distributing content anonymously with no central point of failure.
|
10
|
+
The system is openly federated and anyone can join. To ensure quality service
|
11
|
+
and prevent abuse, the Cryptosphere uses an integrated cryptographically
|
12
|
+
secure reputation system which provides a distributed web of trust.
|
13
|
+
|
14
|
+
There are several systems with similar goals to the Cryptosphere, such as
|
15
|
+
MNet, Freenet, and Tahoe-LAFS. These systems serve as inspiration for the
|
16
|
+
Cryptosphere's design. The Cryptosphere is also heavily influenced by Git, the
|
17
|
+
distributed version control system.
|
18
|
+
|
19
|
+
For more information, please see the [project philosophy][philosophy]
|
20
|
+
page in the wiki.
|
21
|
+
|
22
|
+
Like the Cryptosphere? [Join the Google Group][google group]
|
23
|
+
We're also on IRC at #cryptosphere on irc.freenode.net
|
24
|
+
|
25
|
+
[philosophy]: https://github.com/tarcieri/cryptosphere/wiki/Philosophy
|
26
|
+
[google group]: https://groups.google.com/group/cryptosphere
|
27
|
+
|
28
|
+
### Is it any good?
|
29
|
+
|
30
|
+
[Yes.](http://news.ycombinator.com/item?id=3067434)
|
31
|
+
|
32
|
+
### Is It "Production Ready™"?
|
33
|
+
|
34
|
+
No, the Cryptosphere is still in an early development stage, and is not yet
|
35
|
+
ready for general usage.
|
36
|
+
|
37
|
+
Use Cases
|
38
|
+
---------
|
39
|
+
|
40
|
+
The Cryptosphere provides an encrypted storage system where only users with
|
41
|
+
the capability tokens for respective content are able to access it. Unlike
|
42
|
+
many other peer to systems, there is no global search system because all
|
43
|
+
content in the system is encrypted and therefore unsearchable.
|
44
|
+
|
45
|
+
This makes the Cryptosphere quite a bit different from many other P2P systems
|
46
|
+
which sought to publicize users content. Instead, the Cryptosphere tries to
|
47
|
+
keep your content as confidential as possible. This makes it useful for the
|
48
|
+
following things:
|
49
|
+
|
50
|
+
* Secure personal backups
|
51
|
+
* File sharing among small groups (ala Dropbox)
|
52
|
+
* Secure anonymous encrypted source control
|
53
|
+
* Censorship-proof anonymous web hosting
|
54
|
+
|
55
|
+
Suggested Reading
|
56
|
+
-----------------
|
57
|
+
|
58
|
+
* [Tahoe - The Least-Authority Filesystem (Tahoe-LAFS)](https://tahoe-lafs.org/~zooko/lafs.pdf)
|
59
|
+
* [A Distributed Decentralized Information Storage and Retrieval System (Freenet)](http://freenetproject.org/papers/ddisrs.pdf)
|
60
|
+
* [Efficient Sharing of Encrypted Data (GNUnet)](http://grothoff.org/christian/esed.pdf)
|
61
|
+
* [Samsara: Honor Among Thieves in Peer-to-Peer Storage](http://www.eecs.harvard.edu/~mema/courses/cs264/papers/samsara-sosp2003.pdf)
|
62
|
+
* [The Sybil Attack](http://research.microsoft.com/pubs/74220/IPTPS2002.pdf)
|
63
|
+
* [A Sybilproof Indirect Reciprocity Mechanism for Peer-to-Peer Networks](http://discovery.ucl.ac.uk/14962/1/14962.pdf)
|
64
|
+
* [Incentive-driven QoS in peer-to-peer overlays](http://discovery.ucl.ac.uk/19490/1/19490.pdf)
|
65
|
+
|
66
|
+
Contributing to the Cryptosphere
|
67
|
+
--------------------------------
|
68
|
+
|
69
|
+
* Fork this repository on github
|
70
|
+
* Make your changes and send me a pull request
|
71
|
+
* If I like them I'll merge them
|
72
|
+
|
73
|
+
License
|
74
|
+
-------
|
75
|
+
|
76
|
+
Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
|
77
|
+
LICENSE.txt for further details.
|
data/Rakefile
ADDED
data/bin/csphere
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/cryptosphere/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Tony Arcieri"]
|
6
|
+
gem.email = ["tony.arcieri@gmail.com"]
|
7
|
+
gem.description = "A decentralized globally distributed peer-to-peer data archive"
|
8
|
+
gem.summary = "The Cryptosphere is a P2P cryptosystem for publishing and securely distributing content anonymously with no central point of failure"
|
9
|
+
gem.homepage = "http://github.com/tarcieri/cryptosphere"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "cryptosphere"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Cryptosphere::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "celluloid"
|
19
|
+
gem.add_dependency "thor"
|
20
|
+
gem.add_dependency "hkdf"
|
21
|
+
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
gem.add_development_dependency "rspec"
|
24
|
+
end
|
data/lib/cryptosphere.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'openssl'
|
3
|
+
require 'cryptosphere/version'
|
4
|
+
|
5
|
+
require 'cryptosphere/crypto/asymmetric_cipher'
|
6
|
+
require 'cryptosphere/crypto/kdf'
|
7
|
+
require 'cryptosphere/crypto/signature_algorithm'
|
8
|
+
|
9
|
+
require 'cryptosphere/blobs/blob'
|
10
|
+
require 'cryptosphere/blobs/tree'
|
11
|
+
|
12
|
+
require 'cryptosphere/protocol/handshake'
|
13
|
+
|
14
|
+
require 'cryptosphere/cli'
|
15
|
+
require 'cryptosphere/head'
|
16
|
+
require 'cryptosphere/identity'
|
17
|
+
|
18
|
+
module Cryptosphere
|
19
|
+
# How large of a key to use for the pubkey cipher
|
20
|
+
PUBKEY_SIZE = 2048
|
21
|
+
|
22
|
+
# Secure random data source
|
23
|
+
def random_bytes(size)
|
24
|
+
OpenSSL::Random.random_bytes(size)
|
25
|
+
end
|
26
|
+
|
27
|
+
# 256-bit hash function
|
28
|
+
def self.hash_function
|
29
|
+
Digest::SHA256.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# 256-bit block cipher
|
33
|
+
def self.block_cipher
|
34
|
+
OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Request to do something we're incapable of
|
38
|
+
class CapabilityError < StandardError; end
|
39
|
+
|
40
|
+
# Signature doesn't match (potential data tampering)
|
41
|
+
class InvalidSignatureError < StandardError; end
|
42
|
+
|
43
|
+
# Implausible timestamps (i.e. ones from the future)
|
44
|
+
class InvalidTimestampError < StandardError; end
|
45
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Cryptosphere
|
5
|
+
class Blob
|
6
|
+
# Prefix added to the beginning of every Cryptosphere node
|
7
|
+
PREFIX = "blob::"
|
8
|
+
|
9
|
+
attr_reader :id, :key, :path
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :path
|
13
|
+
|
14
|
+
# Configure the Cryptosphere Node store
|
15
|
+
def setup(options = {})
|
16
|
+
unless options[:root]
|
17
|
+
raise ArgumentError, "no :root path given"
|
18
|
+
end
|
19
|
+
|
20
|
+
unless File.directory? options[:root]
|
21
|
+
raise ArgumentError, "no such directory: #{options[:root]}"
|
22
|
+
end
|
23
|
+
|
24
|
+
@path = File.expand_path("nodes", options[:root])
|
25
|
+
FileUtils.mkdir @path unless File.directory? @path
|
26
|
+
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create a node from a given object
|
31
|
+
def [](obj)
|
32
|
+
builder = Builder.new
|
33
|
+
builder << obj
|
34
|
+
builder.finish
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(id, key)
|
39
|
+
@id, @key = id, key
|
40
|
+
@path = File.join(self.class.path, @id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def decrypt
|
44
|
+
raise "can't decrypt node without key" unless @key
|
45
|
+
|
46
|
+
cipher = Cryptosphere.block_cipher
|
47
|
+
cipher.decrypt
|
48
|
+
cipher.key = @key[0...32]
|
49
|
+
cipher.iv = @key[32...64]
|
50
|
+
|
51
|
+
output = ''
|
52
|
+
|
53
|
+
File.open(@path, 'r') do |file|
|
54
|
+
while data = file.read(4096)
|
55
|
+
output << cipher.update(data)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
output << cipher.final
|
60
|
+
end
|
61
|
+
|
62
|
+
# Encrypt a node and insert it into the local store
|
63
|
+
class Builder
|
64
|
+
def initialize
|
65
|
+
@hash_function = Cryptosphere.hash_function
|
66
|
+
@hash_function << PREFIX
|
67
|
+
|
68
|
+
@file = Tempfile.new 'cryptosphere'
|
69
|
+
end
|
70
|
+
|
71
|
+
def write(data)
|
72
|
+
@hash_function << data
|
73
|
+
@file << data
|
74
|
+
end
|
75
|
+
alias_method :<<, :write
|
76
|
+
|
77
|
+
def finish
|
78
|
+
keys = Cryptosphere.kdf(@hash_function.digest, size: 64)
|
79
|
+
key, iv = keys[0...32], keys[32...64]
|
80
|
+
|
81
|
+
block_cipher = Cryptosphere.block_cipher
|
82
|
+
block_cipher.encrypt
|
83
|
+
block_cipher.key, block_cipher.iv = key, iv
|
84
|
+
|
85
|
+
@file.rewind
|
86
|
+
output = Tempfile.new 'cryptosphere'
|
87
|
+
|
88
|
+
begin
|
89
|
+
hash_function = Cryptosphere.hash_function
|
90
|
+
while plaintext = @file.read(4096)
|
91
|
+
ciphertext = block_cipher.update(plaintext)
|
92
|
+
output << ciphertext
|
93
|
+
hash_function << ciphertext
|
94
|
+
end
|
95
|
+
|
96
|
+
ciphertext = block_cipher.final
|
97
|
+
output << ciphertext
|
98
|
+
hash_function << ciphertext
|
99
|
+
output.close
|
100
|
+
|
101
|
+
node_id = hash_function.hexdigest
|
102
|
+
FileUtils.mv output.path, File.join(Blob.path, node_id)
|
103
|
+
|
104
|
+
Blob.new(node_id, key + iv)
|
105
|
+
rescue Exception
|
106
|
+
output.close rescue nil
|
107
|
+
output.unlink rescue nil
|
108
|
+
|
109
|
+
raise
|
110
|
+
end
|
111
|
+
ensure
|
112
|
+
@file.close rescue nil
|
113
|
+
@file.unlink rescue nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Cryptosphere
|
2
|
+
class Tree < Blob
|
3
|
+
def self.[](*entries)
|
4
|
+
new(entries)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(entries)
|
8
|
+
@entries = entries
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
@entries.sort_by { |e| e.name }.map(&:to_s).join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Individual entries in a tree
|
16
|
+
class Entry < Struct.new(:mode, :type, :id, :key, :name)
|
17
|
+
def to_s
|
18
|
+
"#{mode} #{type} #{id} #{key} #{name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Cryptosphere
|
4
|
+
# Asymmetric encryption cipher: 2048-bit RSA
|
5
|
+
class AsymmetricCipher
|
6
|
+
KEY_SIZE = 2048
|
7
|
+
|
8
|
+
def self.generate_key
|
9
|
+
OpenSSL::PKey::RSA.generate(KEY_SIZE).to_pem
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(key)
|
13
|
+
openssl_key = OpenSSL::PKey::RSA.new(key)
|
14
|
+
|
15
|
+
if openssl_key.private?
|
16
|
+
@private_key = openssl_key
|
17
|
+
@public_key = openssl_key.public_key
|
18
|
+
else
|
19
|
+
@private_key = nil
|
20
|
+
@public_key = openssl_key
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Serialize canonical private key with Distinguished Encoding Rules (DER)
|
25
|
+
def private_key
|
26
|
+
@private_key.to_der
|
27
|
+
end
|
28
|
+
|
29
|
+
# Serialize private key in Privacy Enhanced Mail (PEM) format
|
30
|
+
def private_key_pem
|
31
|
+
@private_key.to_pem
|
32
|
+
end
|
33
|
+
|
34
|
+
# Is a private key present?
|
35
|
+
def private_key?
|
36
|
+
!!@private_key
|
37
|
+
end
|
38
|
+
|
39
|
+
# Serialize canonical public key with Distinguished Encoding Rules (DER)
|
40
|
+
def public_key
|
41
|
+
@public_key.to_der
|
42
|
+
end
|
43
|
+
|
44
|
+
# Obtain the fingerprint for this public key
|
45
|
+
def public_key_fingerprint
|
46
|
+
Cryptosphere.kdf(public_key).unpack('H*').first.scan(/.{4}/).join(":")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Encrypt a value using the private key
|
50
|
+
# Value can be decrypted with the public key
|
51
|
+
def private_encrypt(plaintext)
|
52
|
+
@private_key.private_encrypt(plaintext)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Decrypt a value using the private key
|
56
|
+
# Ciphertext must be encrypted with public key
|
57
|
+
def private_decrypt(ciphertext)
|
58
|
+
@private_key.private_decrypt(ciphertext)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Encrypt a value using the public key
|
62
|
+
# Value can only be decrypted with the private key
|
63
|
+
def public_encrypt(plaintext)
|
64
|
+
@public_key.public_encrypt(plaintext)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Decrypt a value using the public key
|
68
|
+
# Ciphertext must be encrypted with private key
|
69
|
+
def public_decrypt(ciphertext)
|
70
|
+
@public_key.public_decrypt(ciphertext)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'hkdf'
|
2
|
+
|
3
|
+
module Cryptosphere
|
4
|
+
# Cryptographically secure key derivation function: HKDF (RFC 5869)
|
5
|
+
#
|
6
|
+
# Options:
|
7
|
+
# * size: how many bytes of output to generate (default 32, i.e. 256 bits)
|
8
|
+
def self.kdf(secret, options = {})
|
9
|
+
size = options[:size] || 32
|
10
|
+
HKDF.new(secret).next_bytes(size)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cryptosphere
|
2
|
+
# Sign the given message with a private key
|
3
|
+
def self.sign(key, message)
|
4
|
+
AsymmetricCipher.new(key).private_encrypt(kdf(message))
|
5
|
+
end
|
6
|
+
|
7
|
+
# Verify a message with the public key. Returns if the signature matches,
|
8
|
+
# and false if there's a mismatch
|
9
|
+
def self.verify(key, message, signature)
|
10
|
+
AsymmetricCipher.new(key).public_decrypt(signature) == kdf(message)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Verify a message, raising InvalidSignatureError on signature mismatch
|
14
|
+
def self.verify!(key, message, signature)
|
15
|
+
verify(key, message, signature) or raise InvalidSignatureError, "signature mismatch"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Cryptosphere
|
2
|
+
class Head
|
3
|
+
attr_reader :signing_key, :read_key, :verify_key, :timestamp
|
4
|
+
|
5
|
+
def self.generate
|
6
|
+
access_key = AsymmetricCipher.generate_key
|
7
|
+
read_key = Cryptosphere.random_bytes(32)
|
8
|
+
|
9
|
+
new(verify_key.to_der, read_key, signing_key.to_der)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(access_key, read_key = nil)
|
13
|
+
@signing_cipher = AsymmetricCipher.new(access_key)
|
14
|
+
@read_key = read_key
|
15
|
+
|
16
|
+
@id = @signing_cipher.public_key_fingerprint
|
17
|
+
@location = nil
|
18
|
+
@timestamp = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def location
|
22
|
+
raise CapabilityError, "can't read location" unless @read_key
|
23
|
+
@location
|
24
|
+
end
|
25
|
+
|
26
|
+
def move(location, timestamp = Time.now)
|
27
|
+
raise CapabilityError, "don't have write capability" unless @signing_cipher.private_key?
|
28
|
+
@location, @timestamp = location, timestamp
|
29
|
+
end
|
30
|
+
alias_method :location=, :move
|
31
|
+
|
32
|
+
def to_signed_message
|
33
|
+
cipher = Cryptosphere.block_cipher
|
34
|
+
cipher.encrypt
|
35
|
+
cipher.key = @read_key
|
36
|
+
cipher.iv = iv = cipher.random_iv
|
37
|
+
|
38
|
+
ciphertext = cipher.update(location)
|
39
|
+
ciphertext << cipher.final
|
40
|
+
|
41
|
+
message = [@timestamp.to_i, iv, ciphertext].pack("QA16A*")
|
42
|
+
signature = @signing_cipher.private_encrypt Cryptosphere.kdf(message)
|
43
|
+
|
44
|
+
[signature.size, signature, message].pack("nA*A*")
|
45
|
+
end
|
46
|
+
|
47
|
+
def update(signed_message)
|
48
|
+
signature_size, rest = signed_message.unpack("nA*")
|
49
|
+
signature, message = rest.unpack("A#{signature_size}A*")
|
50
|
+
|
51
|
+
if @signing_cipher.public_decrypt(signature) != Cryptosphere.kdf(message)
|
52
|
+
raise InvalidSignatureError, "signature does not match message"
|
53
|
+
end
|
54
|
+
|
55
|
+
timestamp, iv, ciphertext = message.unpack("QA16A*")
|
56
|
+
timestamp = Time.at(timestamp)
|
57
|
+
|
58
|
+
if timestamp > Time.now
|
59
|
+
raise InvalidTimestampError, "timestamp is in the future"
|
60
|
+
elsif @timestamp && timestamp < @timestamp
|
61
|
+
return false # we have a newer version
|
62
|
+
end
|
63
|
+
|
64
|
+
if @read_key
|
65
|
+
cipher = Cryptosphere.block_cipher
|
66
|
+
cipher.decrypt
|
67
|
+
cipher.key = @read_key
|
68
|
+
cipher.iv = iv
|
69
|
+
|
70
|
+
location = cipher.update(ciphertext)
|
71
|
+
location << cipher.final
|
72
|
+
|
73
|
+
@location = location
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cryptosphere
|
2
|
+
class Identity
|
3
|
+
attr_reader :id
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@cipher, :private_key, :public_key
|
7
|
+
|
8
|
+
def self.generate
|
9
|
+
new AsymmetricCipher.generate_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(private_key)
|
13
|
+
@cipher = AsymmetricCipher.new(private_key)
|
14
|
+
@id = @cipher.public_key_fingerprint
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#<Cryptosphere::Identity:#{id}>"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Cryptosphere
|
2
|
+
module Handshake
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def encode_request(sender, recipient)
|
6
|
+
# TODO: encrypt sender's public key
|
7
|
+
# Sure would be nice to have some Curve25519 here
|
8
|
+
message = sender.public_key
|
9
|
+
Cryptosphere.sign(sender.private_key, message) + message
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode_request(recipient, message)
|
13
|
+
bytes = PUBKEY_SIZE / 8
|
14
|
+
signature, message = message[0...bytes], message[bytes..-1]
|
15
|
+
|
16
|
+
# FIXME: this should be encrypted :(
|
17
|
+
sender_key = message
|
18
|
+
Cryptosphere.verify!(sender_key, message, signature)
|
19
|
+
|
20
|
+
sender_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def encode_response(sender, recipient, secret = Cryptosphere.random_bytes(32))
|
24
|
+
cipher = AsymmetricCipher.new(recipient.public_key)
|
25
|
+
message = cipher.public_encrypt(secret)
|
26
|
+
Cryptosphere.sign(sender.private_key, message) + message
|
27
|
+
end
|
28
|
+
|
29
|
+
def decode_response(recipient, sender, message)
|
30
|
+
bytes = PUBKEY_SIZE / 8
|
31
|
+
signature, message = message[0...bytes], message[bytes..-1]
|
32
|
+
Cryptosphere.verify!(sender.public_key, message, signature)
|
33
|
+
cipher = AsymmetricCipher.new(recipient.private_key)
|
34
|
+
cipher.private_decrypt(message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/logo.png
ADDED
Binary file
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cryptosphere::Tree do
|
4
|
+
it "creates nodes" do
|
5
|
+
file = Cryptosphere::Blob["file::foobar"]
|
6
|
+
foobar = Cryptosphere::Tree::Entry['-', 'file', file.id, file.key, "foobar"]
|
7
|
+
tree = Cryptosphere::Tree[foobar]
|
8
|
+
tree.to_s.should == "- file #{file.id} #{file.key} foobar"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cryptosphere::Head do
|
4
|
+
let(:read_key) { "X" * 32 }
|
5
|
+
let(:write_key) { example_private_key }
|
6
|
+
let(:verify_key) { example_public_key }
|
7
|
+
|
8
|
+
let(:reader) { Cryptosphere::Head.new(verify_key, read_key) }
|
9
|
+
let(:writer) { Cryptosphere::Head.new(write_key, read_key) }
|
10
|
+
|
11
|
+
let(:example_location) { "221B Baker Street" }
|
12
|
+
|
13
|
+
it "moves heads" do
|
14
|
+
writer.move(example_location)
|
15
|
+
message = writer.to_signed_message
|
16
|
+
message.length.should be < MAX_DATAGRAM_LENGTH
|
17
|
+
|
18
|
+
reader.update message
|
19
|
+
reader.location.should == example_location
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cryptosphere::Handshake do
|
4
|
+
let(:alice) { Cryptosphere::Identity.new(alice_private_key) }
|
5
|
+
let(:bob) { Cryptosphere::Identity.new(bob_private_key) }
|
6
|
+
let(:secret) { "X" * 32 }
|
7
|
+
|
8
|
+
it "encodes and decodes request packets" do
|
9
|
+
packet = Cryptosphere::Handshake.encode_request(alice, bob)
|
10
|
+
|
11
|
+
# FIXME: need elliptic curve crypto here for smaller packets
|
12
|
+
#packet.length.should be < MAX_DATAGRAM_LENGTH
|
13
|
+
public_key = Cryptosphere::Handshake.decode_request(bob, packet)
|
14
|
+
public_key.should eq alice.public_key
|
15
|
+
end
|
16
|
+
|
17
|
+
it "encodes and decodes response packets" do
|
18
|
+
packet = Cryptosphere::Handshake.encode_response(bob, alice, secret)
|
19
|
+
response = Cryptosphere::Handshake.decode_response(alice, bob, packet)
|
20
|
+
response.should eq secret
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpQIBAAKCAQEAzwQYjchBgWq7nStqQeTvRhC15kGJc2Aa3q3ytIt7iH3gQSH+
|
3
|
+
KbZKckGh184JnPoCEOUDHJ2klhpTkjf0kgAcx6TOUt9DdP63B3FpX22LG0PU4qbm
|
4
|
+
SxZ4QXsqhEhj2UyxMbkKbBE9lKx90K9SO7dDB797cghoD5Sh2T69y+4bg6etJpVv
|
5
|
+
x0oP+9/HUynQBwZqykHIXPyhcBGsaWcB52tmwwYVQCkhaOhQv3iI4Z+33M2gAbpr
|
6
|
+
cH83kJCnKVZlRnhrswsxUUQ5nrX66edNXztcz28Lj6r8bMrQlymd+1zFjVCX1h5e
|
7
|
+
h2n/DNVRU5LkeK3VPom7mQ/xZygdhvEksebsSwIDAQABAoIBACDyJ+44lqRAFke3
|
8
|
+
JxwBkUr3UdupRnTEMMKLsHqnUCuyzMPQ4yBEUKjKZTVxJvqCl12U9N/S/uScn/w/
|
9
|
+
R38M4YesZOGvgo7WEs7ub7SuPFtEelbv9OqyUsUpEuUmmC13FSQyrMPyInjM0uEp
|
10
|
+
Zc73JYXQJZdKWzVPlEp8v7v60woqlchFzFk9i+byhN40gKUx4Irm6UCeMRBlmPP8
|
11
|
+
6Ck3iUZw0I6NARF9tRNa0gn+nDoX7bBJfohsb5yLYvDHjv9ED2u4ivDyk5X0HvPP
|
12
|
+
nHCBFgtt5MK0WVdaaIKxXXPJaPCXVlpYTCLmwqjuVQslPZXzv0CAhO40ZvMnLfAn
|
13
|
+
xZ9MCjECgYEA+ysg7gE5WoWL/hPVS0JA5tKqn83f4tinWAnRgbPg7cUnFVrNI3qZ
|
14
|
+
riPvj/SdFQMrGEsnq6RH7hoBfKPIEImWETaNMgWvGEegs1JDzHXI40BUZBmm1ioZ
|
15
|
+
tCF0hmDibYckJBhU8BKSiM0wAx3QEbC/VzKsSto2mOICIidEyw+Wk6cCgYEA0v+K
|
16
|
+
MGhOusBxDDx/mRlW3/1+nCzyla+NDTRKcJaDGOArgU+AOqRcHuyiBdD25fyhs3JC
|
17
|
+
3GsxJmOvO+UrX2tFpNycEpmMR/XYPVWZlNbaoOp15/+ijx9OAOTMmjOQ26HUHhar
|
18
|
+
3jP5DLbodcnnuHCeTNpW8zTswbqnArRPYXZKBr0CgYEAmvJaWDmtFij42f+GP+1Z
|
19
|
+
eIxR8k/hZGJfqjI0ax17D3Pmzoe7sb16fTFyIo63MTVJKq2ChaLNNRgZ/rhTPdCD
|
20
|
+
IY9Tv54+DG5ztuxzIvkuuvL+nNouUEScosFYz2WJiiQqqZHRJGFwwLBEhEeqCp/N
|
21
|
+
CpAaNfs0X1BeHI+5IsQ1ElUCgYEAmuULnlPEkCZcFx6GkW7frtmaS65Xe3l/c9US
|
22
|
+
XKqxnN5cMbaaLPKhyfXvT5PC3L1kO6bC3Ks4TrVZW//1ojvOyaNGVAUyzVT2JLil
|
23
|
+
YXWE1CKq4eBxht31VoSgiwcV7ZZUcK42B45h42qXJnlNScIrA8I5mJsev211029o
|
24
|
+
4uSCnYUCgYEAlpfHrPIvHzjrgg2+d18hFmF1139zeAfuDVNpvRC2sjTZVAWfmyDj
|
25
|
+
9rlsBXPM8vg2AUojiKy4Q+CuhIcBKOpMAEgmU33sH6/rEZTPmQrztx4f1YJHHQjR
|
26
|
+
JyL+dplE4InNOiO2m8ymQA7JfRa4hC+VZnVUNxPl1TcB2yp/iMZyg8M=
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEAycdXNUdBLGbnyKt8M8LhT9sObVzvKY+WQJsdvxyPoROaS8aY
|
3
|
+
ZxFTLHxSbJpHp9Yl+A3ZZjoNdE0b6mc771S/PorY6SQc+KOGbDP7864fn5y+haYP
|
4
|
+
BT8qBB93wZdkCIQay5HnAS1xdIzAFCAsu3dKVgfxQt/+sFnSw/qtC1BuxCqXckDk
|
5
|
+
HVYcgkdQUXKh1prj240M/NfGjYIPfHnGvAKwhTKzJYVPSKDtgs432NcD5u8Oa+eU
|
6
|
+
yrA8PpaeXdwnndbjXbyvXwWc1sep77NHXkcZNpOqC9vKY+PHXbHvpV+pILeZxH7z
|
7
|
+
lE9mf/l0ucbmCH4EnLx+ZXTU2lZMTCqH2JwaRwIDAQABAoIBAF85A57REaCyr4+z
|
8
|
+
3dlPjqTw684QnY0vhejXSyJ1iBKr/ZTlE+cP9gB4ay11YXuDREfbwUzM+Kx590KX
|
9
|
+
lWFMzTPmspbTxBhSk41cuvo0ohfhEMhhpZUESf/IGevyVfLu5PZM1IdpurEV+0+E
|
10
|
+
H5gYo6wV83Vr3/W5bg2urxs3yg4odNH59f5RIi++q3YpIXctZErboh6nEGNoP4aK
|
11
|
+
NsCmPv+dTheryHbY29OBCqtFZWNma57gNViuuliDQYlgqbjvOGE2jEm6PmqOYuN/
|
12
|
+
/nRlgD0hFscJUGfnxjm7b2pu3J9hLosiJ7WCkKuA6QkFYBX8IbiCGPX0QB/uVS2P
|
13
|
+
bNgJaYECgYEA6mTjPZAE6NG1lErp5FRknuzm4R1Damm0SVYC3K6kkIimXBf4z0lk
|
14
|
+
TB/l0j5e7wS7b29eoQcylXoxLAS/tG3odEDyZXwncncWjYyvwOeonfxRCFxhKyEP
|
15
|
+
GV6ATz/arOUqjN7b39j6grtrNy+SLg6xpGwnue3wVUz4m4TENRtlSvMCgYEA3GDP
|
16
|
+
nNDNBqdsjzjSccCZXo6AL+TNDP0PkhzZNGRFXLn2kr2bmL2rdS7A8iYwbYsX0US/
|
17
|
+
WUTZAfTBaC2nWa4RyKLTLrwChKflccrz4a0EjTeZvheSy+HRyBMLrIg7bK+VaTxg
|
18
|
+
kgNKxfxMYb7L4sqMvgyG2XUoI70wOPUvRqAooF0CgYBOn162BLwQ2F8fCe3goApM
|
19
|
+
YMylECrP4/sMamR1X8Nlk+CxnXzhEw4olr6BQliXti+lFmdYflCSYTVjPYiMXh2N
|
20
|
+
+UcVkYqt05JmEp3ViB9ANyV7N3mEfQdSjCdf7dxNCGW4cPyx3ldRSMqS7UsMVfNy
|
21
|
+
YrEEpfJqunHdwLAL3E+izwKBgHLH4ZpVjlPG4938xG8G11rOcamAS+RV8cQyTlbh
|
22
|
+
Wtce7HQlWWNGdoUEIu58QYDsjy0p11fRag2AwzNVg/JLnWuYktGmjtE0+WY5RKjo
|
23
|
+
CpTavrKpaIXUSgUhFlV4ZysGkJVNIycpm8pezBGk5GBtCrz5nUqpoIxsvy8LVOI+
|
24
|
+
DKrpAoGBAL3g75JCxSuCxETK+bDns6h/t4YDmyxWsZOp+SZE4j3pYbILwQQr24l8
|
25
|
+
FVNsGkLiti5k2pgHB6jDAPEiZBzR9zXAoBxJaM5ga1XirdC4YkFVULP+vUflHbRz
|
26
|
+
jz8R+pzjVuUr+b/sTeudgh8FRofHcHi0fNVK7XnLgmQaj6mTArf5
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'cryptosphere'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
Root = File.expand_path("../tmp", __FILE__)
|
7
|
+
|
8
|
+
FileUtils.rm_rf File.join(Root, "*")
|
9
|
+
Cryptosphere::Blob.setup :root => Root
|
10
|
+
|
11
|
+
module KeyExamples
|
12
|
+
def load_fixture_private_key(name = 'alice.key')
|
13
|
+
File.read File.expand_path("../fixtures/#{name}", __FILE__)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_fixture_public_key(name = 'alice.key')
|
17
|
+
Cryptosphere::AsymmetricCipher.new(load_fixture_private_key(name)).public_key
|
18
|
+
end
|
19
|
+
|
20
|
+
def alice_private_key
|
21
|
+
@alice_private_key ||= load_fixture_private_key('alice.key')
|
22
|
+
end
|
23
|
+
alias_method :example_private_key, :alice_private_key
|
24
|
+
|
25
|
+
def alice_public_key
|
26
|
+
@alice_public_key ||= load_fixture_public_key('alice.key')
|
27
|
+
end
|
28
|
+
alias_method :example_public_key, :alice_public_key
|
29
|
+
|
30
|
+
def bob_private_key
|
31
|
+
@bob_private_key ||= load_fixture_private_key('bob.key')
|
32
|
+
end
|
33
|
+
|
34
|
+
def bob_public_key
|
35
|
+
@bob_public_key ||= load_fixture_public_key('bob.key')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
include KeyExamples
|
40
|
+
|
41
|
+
# Maximum datagram length the Cryptosphere protocol permits
|
42
|
+
# Taken from RFC 879 maximum size reassembly buffer
|
43
|
+
MAX_DATAGRAM_LENGTH = 512
|
data/tasks/rspec.task
ADDED
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cryptosphere
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tony Arcieri
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: celluloid
|
16
|
+
requirement: &70140313417880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70140313417880
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thor
|
27
|
+
requirement: &70140313417460 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70140313417460
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: hkdf
|
38
|
+
requirement: &70140313417040 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70140313417040
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70140313416620 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70140313416620
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70140313416200 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70140313416200
|
69
|
+
description: A decentralized globally distributed peer-to-peer data archive
|
70
|
+
email:
|
71
|
+
- tony.arcieri@gmail.com
|
72
|
+
executables:
|
73
|
+
- csphere
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- .rspec
|
79
|
+
- .travis.yml
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/csphere
|
85
|
+
- cryptosphere.gemspec
|
86
|
+
- lib/cryptosphere.rb
|
87
|
+
- lib/cryptosphere/blobs/blob.rb
|
88
|
+
- lib/cryptosphere/blobs/tree.rb
|
89
|
+
- lib/cryptosphere/cli.rb
|
90
|
+
- lib/cryptosphere/crypto/asymmetric_cipher.rb
|
91
|
+
- lib/cryptosphere/crypto/kdf.rb
|
92
|
+
- lib/cryptosphere/crypto/signature_algorithm.rb
|
93
|
+
- lib/cryptosphere/head.rb
|
94
|
+
- lib/cryptosphere/identity.rb
|
95
|
+
- lib/cryptosphere/protocol/handshake.rb
|
96
|
+
- lib/cryptosphere/version.rb
|
97
|
+
- logo.png
|
98
|
+
- spec/cryptosphere/blobs/blob_spec.rb
|
99
|
+
- spec/cryptosphere/blobs/tree_spec.rb
|
100
|
+
- spec/cryptosphere/head_spec.rb
|
101
|
+
- spec/cryptosphere/identity_spec.rb
|
102
|
+
- spec/cryptosphere/protocol/handshake_spec.rb
|
103
|
+
- spec/fixtures/alice.key
|
104
|
+
- spec/fixtures/bob.key
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/tmp/.gitkeep
|
107
|
+
- tasks/rspec.task
|
108
|
+
homepage: http://github.com/tarcieri/cryptosphere
|
109
|
+
licenses: []
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options: []
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.8.10
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: The Cryptosphere is a P2P cryptosystem for publishing and securely distributing
|
132
|
+
content anonymously with no central point of failure
|
133
|
+
test_files:
|
134
|
+
- spec/cryptosphere/blobs/blob_spec.rb
|
135
|
+
- spec/cryptosphere/blobs/tree_spec.rb
|
136
|
+
- spec/cryptosphere/head_spec.rb
|
137
|
+
- spec/cryptosphere/identity_spec.rb
|
138
|
+
- spec/cryptosphere/protocol/handshake_spec.rb
|
139
|
+
- spec/fixtures/alice.key
|
140
|
+
- spec/fixtures/bob.key
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/tmp/.gitkeep
|
143
|
+
has_rdoc:
|