cryptosphere 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Celluloid](https://github.com/tarcieri/cryptosphere/raw/master/logo.png)
|
2
|
+
================
|
3
|
+
[![Build Status](http://travis-ci.org/tarcieri/cryptosphere.png)](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:
|