nova 0.0.2

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +29 -0
  4. data/bin/nova +8 -0
  5. data/lib/generator/template/new_install/galaxy/some_star.rb +3 -0
  6. data/lib/generator/template/new_install/supernova.yml +16 -0
  7. data/lib/nova.rb +49 -0
  8. data/lib/nova/cli.rb +62 -0
  9. data/lib/nova/commands/server.rb +71 -0
  10. data/lib/nova/common.rb +17 -0
  11. data/lib/nova/common/event_handler.rb +165 -0
  12. data/lib/nova/common/event_handler/event.rb +147 -0
  13. data/lib/nova/common/features.rb +93 -0
  14. data/lib/nova/common/features/feature.rb +65 -0
  15. data/lib/nova/common/metadata.rb +65 -0
  16. data/lib/nova/common/metadata/data.rb +171 -0
  17. data/lib/nova/common/star_management.rb +164 -0
  18. data/lib/nova/constructor.rb +84 -0
  19. data/lib/nova/exceptions.rb +16 -0
  20. data/lib/nova/project.rb +199 -0
  21. data/lib/nova/remote.rb +10 -0
  22. data/lib/nova/remote/fake.rb +51 -0
  23. data/lib/nova/remote/fake/commands.rb +44 -0
  24. data/lib/nova/remote/fake/file_system.rb +76 -0
  25. data/lib/nova/remote/fake/operating_system.rb +52 -0
  26. data/lib/nova/remote/fake/platform.rb +89 -0
  27. data/lib/nova/star.rb +25 -0
  28. data/lib/nova/starbound.rb +14 -0
  29. data/lib/nova/starbound/client.rb +59 -0
  30. data/lib/nova/starbound/encryptor.rb +134 -0
  31. data/lib/nova/starbound/encryptors.rb +13 -0
  32. data/lib/nova/starbound/encryptors/openssl.rb +122 -0
  33. data/lib/nova/starbound/encryptors/plaintext.rb +64 -0
  34. data/lib/nova/starbound/encryptors/rbnacl.rb +67 -0
  35. data/lib/nova/starbound/protocol.rb +81 -0
  36. data/lib/nova/starbound/protocol/encryption.rb +48 -0
  37. data/lib/nova/starbound/protocol/exceptions.rb +38 -0
  38. data/lib/nova/starbound/protocol/messages.rb +116 -0
  39. data/lib/nova/starbound/protocol/packet.rb +267 -0
  40. data/lib/nova/starbound/protocol/socket.rb +231 -0
  41. data/lib/nova/starbound/server.rb +182 -0
  42. data/lib/nova/version.rb +5 -0
  43. data/spec/constructor_spec.rb +20 -0
  44. data/spec/local_spec.rb +26 -0
  45. data/spec/nova_spec.rb +7 -0
  46. data/spec/spec_helper.rb +19 -0
  47. data/spec/star/some_type.rb +27 -0
  48. data/spec/star_spec.rb +107 -0
  49. data/spec/starbound/encryptor_spec.rb +33 -0
  50. data/spec/starbound/openssl_encryptor_spec.rb +80 -0
  51. data/spec/starbound/packet_spec.rb +61 -0
  52. data/spec/starbound/plaintext_encryptor_spec.rb +27 -0
  53. data/spec/starbound/protocol_spec.rb +163 -0
  54. data/spec/starbound/rbnacl_encryptor_spec.rb +70 -0
  55. metadata +166 -0
@@ -0,0 +1,13 @@
1
+ require 'nova/starbound/encryptors/plaintext'
2
+ require 'nova/starbound/encryptors/openssl'
3
+ require 'nova/starbound/encryptors/rbnacl'
4
+
5
+ module Nova
6
+ module Starbound
7
+
8
+ # A module containing the encryptors that come with starbound.
9
+ module Encryptors
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,122 @@
1
+ require 'digest/sha2'
2
+
3
+ module Nova
4
+ module Starbound
5
+ module Encryptors
6
+
7
+ # Handles encryption using the OpenSSL library. Shares the
8
+ # shared secret using RSA public key encryption, creates a
9
+ # HMAC digest of the body using the shared secret as a key, and
10
+ # encrypts the body using AES-256-CBC encryption.
11
+ class OpenSSL < Encryptor
12
+
13
+ # The RSA key size for the key exchange.
14
+ RSA_KEY_SIZE = 4096
15
+
16
+ # The shared secret size, in bytes. If RSA_KEY_SIZE is 4096,
17
+ # this is 256.
18
+ SECRET_SIZE = RSA_KEY_SIZE / 16
19
+
20
+ encryptor_name "openssl/rsa-#{RSA_KEY_SIZE}/aes-256-cbc"
21
+ register! 1
22
+
23
+ # see Encryptor.available?
24
+ def self.available?
25
+ @_available ||= begin
26
+ require 'openssl'
27
+ true
28
+ rescue LoadError
29
+ false
30
+ end
31
+ end
32
+
33
+ # (see Encryptor#encrypt)
34
+ def encrypt(packet)
35
+ packet = packet.clone
36
+ cipher = ::OpenSSL::Cipher::AES256.new(:CBC)
37
+ cipher.encrypt
38
+ cipher.key = options[:shared_secret]
39
+
40
+ # we have to fit the packet's nonce size.
41
+ packet[:nonce] = cipher.iv = ::OpenSSL::Random.random_bytes(24)
42
+
43
+ encrypted = cipher.update(packet[:body]) + cipher.final
44
+
45
+ packet.body = hmac_digest(encrypted) + encrypted
46
+ packet
47
+ end
48
+
49
+ # (see Encryptor#decrypt)
50
+ def decrypt(packet)
51
+ packet = packet.clone
52
+ decipher = ::OpenSSL::Cipher::AES256.new(:CBC)
53
+ decipher.decrypt
54
+ decipher.key = options[:shared_secret]
55
+ decipher.iv = packet[:nonce]
56
+
57
+ digest = packet[:body][0..63]
58
+ actual_body = packet[:body][64..-1]
59
+
60
+ if hmac_digest(actual_body) != digest
61
+ raise EncryptorError, "Digest doesn't match the body."
62
+ end
63
+
64
+ packet.body = decipher.update(actual_body) +
65
+ decipher.final
66
+ packet
67
+ end
68
+
69
+ # (see Encryptor#private_key!)
70
+ def private_key!
71
+ options[:private] = ::OpenSSL::PKey::RSA.new(RSA_KEY_SIZE)
72
+ end
73
+
74
+ # If we have already recieved the other public key, we'll
75
+ # generate the secret here, and return the encrypted version
76
+ # of that secret here. Otherwise, we'll generate our private
77
+ # key and return that in DER format.
78
+ #
79
+ # @return [String]
80
+ def public_key
81
+ if options[:other_public]
82
+ options[:shared_secret] =
83
+ ::OpenSSL::Random.random_bytes(SECRET_SIZE)
84
+ options[:other_public].public_encrypt(
85
+ options[:shared_secret])
86
+ else
87
+ options[:public] ||= options[:private].public_key.to_der
88
+ end
89
+ end
90
+
91
+ # If we already have a public key, that means that the value
92
+ # that's passed to this method is the shared secret.
93
+ # Otherwise, it really is the public key of the other remote.
94
+ # If the passed value is a shared secret, it's decrypted with
95
+ # our private key and stored. If it's the other public key,
96
+ # it's instantized to a openssl RSA key, and stored.
97
+ #
98
+ # @return [void]
99
+ def other_public_key=(public_key)
100
+ if options[:public]
101
+ options[:shared_secret] =
102
+ options[:private].private_decrypt(public_key)
103
+ else
104
+ options[:other_public] =
105
+ ::OpenSSL::PKey::RSA.new(public_key)
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # Provides a digest of the data, using HMAC.
112
+ #
113
+ # @return [String]
114
+ def hmac_digest(body)
115
+ ::OpenSSL::HMAC.digest(::OpenSSL::Digest::SHA512.new,
116
+ options[:shared_secret], body.to_s)
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,64 @@
1
+ module Nova
2
+ module Starbound
3
+ module Encryptors
4
+
5
+ # The plaintext encryptor.
6
+ class Plaintext < Encryptor
7
+
8
+ encryptor_name "plaintext"
9
+ register! 0
10
+
11
+ # The random provider for this clas.
12
+ RANDOM = Random.new
13
+
14
+ # Whether or not this encryptor is available. Since it's
15
+ # plaintext, it's always available.
16
+ #
17
+ # @return [true]
18
+ def self.available?
19
+ true
20
+ end
21
+
22
+ # Whether or not this encryptor is plaintext. It is. This
23
+ # will always return true for this class.
24
+ #
25
+ # @return [true]
26
+ def self.plaintext?
27
+ true
28
+ end
29
+
30
+ # (see Encryptor#encrypt)
31
+ def encrypt(packet)
32
+ packet = packet.clone
33
+
34
+ packet[:nonce] = RANDOM.bytes(24)
35
+ packet
36
+ end
37
+
38
+ # (see Encryptor#decrypt)
39
+ def decrypt(packet)
40
+ packet = packet.clone
41
+
42
+ packet
43
+ end
44
+
45
+ # Does nothing.
46
+ #
47
+ # @return [nil]
48
+ def private_key!; end
49
+
50
+ # Does nothing.
51
+ #
52
+ # @return [String] an empty string.
53
+ def public_key; ""; end
54
+
55
+ # Does nothing.
56
+ #
57
+ # @param _ [String] the other "public" key.
58
+ # @return [nil]
59
+ def other_public_key=(_); end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,67 @@
1
+ module Nova
2
+ module Starbound
3
+ module Encryptors
4
+
5
+ # Provides encryption using RbNaCl.
6
+ class RbNaCl < Encryptor
7
+
8
+ encryptor_name "rbnacl/1.0.0"
9
+ register! 2
10
+
11
+ # (see Encryptor.available?)
12
+ def self.available?
13
+ @_available ||= begin
14
+ require 'rbnacl'
15
+ true
16
+ rescue LoadError
17
+ false
18
+ end
19
+ end
20
+
21
+ # (see Encryptor#encrypt)
22
+ def encrypt(packet)
23
+ packet = packet.clone
24
+ packet[:nonce] = Crypto::Random.random_bytes(24)
25
+ box = Crypto::Box.new(options[:public_key], options[:private_key])
26
+ enc = box.encrypt(packet[:nonce], packet[:body])
27
+
28
+ packet.body = enc
29
+ packet
30
+ end
31
+
32
+ # (see Encryptor#decrypt)
33
+ def decrypt(packet)
34
+ packet = packet.clone
35
+ box = Crypto::Box.new(options[:public_key], options[:private_key])
36
+ packet.body = box.decrypt(packet[:nonce], packet[:body])
37
+
38
+ packet
39
+ rescue Crypto::CryptoError => e
40
+ raise EncryptorError, e
41
+ end
42
+
43
+ # Generates a private key.
44
+ #
45
+ # @return [void]
46
+ def private_key!
47
+ options[:private_key] = Crypto::PrivateKey.generate
48
+ end
49
+
50
+ # Returns the public key for this remote.
51
+ #
52
+ # @return [String]
53
+ def public_key
54
+ options[:private_key].public_key.to_bytes
55
+ end
56
+
57
+ # Sets the other public key to the given value.
58
+ #
59
+ # @return [void]
60
+ def other_public_key=(public_key)
61
+ options[:public_key] = Crypto::PublicKey.new(public_key)
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,81 @@
1
+ require 'nova/starbound/protocol/exceptions'
2
+ require 'nova/starbound/protocol/encryption'
3
+ require 'nova/starbound/protocol/messages'
4
+ require 'nova/starbound/protocol/packet'
5
+ require 'nova/starbound/protocol/socket'
6
+
7
+ module Nova
8
+ module Starbound
9
+
10
+ # The basic Starbound protocol.
11
+ #
12
+ # @todo More testing.
13
+ class Protocol
14
+
15
+ include Protocol::Socket
16
+ include Protocol::Encryption
17
+ include Protocol::Messages
18
+
19
+ # The options that was passed to this protocol on
20
+ # initialization.
21
+ #
22
+ # @return [Hash<Symbol, Object>]
23
+ attr_reader :options
24
+
25
+ # The current state of the protocol. Known values:
26
+ # +:offline+ (default), +:handshake+, +:online+, +:closing+.
27
+ #
28
+ # @return [Symbol]
29
+ attr_reader :state
30
+
31
+ # Perform a handshake with the server. First sets the state to
32
+ # +:handshake+.
33
+ #
34
+ # @return [void]
35
+ def handshake
36
+ @state = :handshake
37
+ thread
38
+
39
+ if options[:type] == :client
40
+ message = send :protocol_version, Nova::VERSION
41
+ response = response_to message
42
+ check_versions response
43
+ handle_encryption
44
+ else
45
+
46
+ wait_for_protocol_version
47
+ handle_server_encryption
48
+ end
49
+
50
+ @state = :online
51
+ end
52
+
53
+ # Initialize the protocol.
54
+ #
55
+ # @param options [Hash] the options to initialize the protocol
56
+ # with.
57
+ def initialize(options = {})
58
+ @options = options
59
+ @state = :offline
60
+
61
+ super()
62
+ end
63
+
64
+ # Closes the connection.
65
+ #
66
+ # @return [void]
67
+ def close(code = :none)
68
+ @state = :closing
69
+
70
+ if code
71
+ send :close, Packet::CloseReasons[code].to_s
72
+ end
73
+
74
+ super()
75
+
76
+ @state = :offline
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,48 @@
1
+ module Nova
2
+ module Starbound
3
+ class Protocol
4
+
5
+ # The encryption used in the protocol.
6
+ module Encryption
7
+
8
+ # The provider used to encrypt the body.
9
+ #
10
+ # @return [Encryptor]
11
+ attr_writer :encryption_provider
12
+
13
+ # Initialize the encryption.
14
+ def initialize
15
+ @encryption_provider = Encryptors::Plaintext.new
16
+
17
+ super()
18
+ end
19
+
20
+ # Gets the encryption provider.
21
+ #
22
+ # @raise [NoEncryptionError] if {#state} isn't handshake,
23
+ # the encryption provider returns true on
24
+ # {Encryptor.plaintext?}, and +:allow_plaintext+ is false.
25
+ # @return [Encryptor]
26
+ def encryption_provider
27
+ raise NoEncryptionError if
28
+ @encryption_provider.class.plaintext? &&
29
+ !should_allow_plaintext?
30
+
31
+ @encryption_provider
32
+ end
33
+
34
+ private
35
+
36
+ # Whether or not plaintext is acceptable. Checks the state to
37
+ # see if it's +:handshake+ and the +:allow_plaintext+ option
38
+ # to return true or false.
39
+ #
40
+ # @return [Boolean]
41
+ def should_allow_plaintext?
42
+ state == :handshake || options.fetch(:allow_plaintext, false)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,38 @@
1
+ module Nova
2
+ module Starbound
3
+ # Any error that is related to the protocol should inherit this
4
+ # class.
5
+ class ProtocolError < StandardError; end
6
+
7
+ # When the protocol needs to exit from a connection, this is
8
+ # raised. Meant to be raised by a third party; not raised by this
9
+ # library.
10
+ class ExitError < StandardError; end
11
+
12
+ # Raised when the socket isn't using encryption, and we've
13
+ # already finished with the handshake, and if the
14
+ # +:allow_plaintext+ option isn't true. Used in
15
+ # {Protocol::Socket}.
16
+ class NoEncryptionError < ProtocolError; end
17
+
18
+ # Raised when the major version of the remote does not match the
19
+ # major version of this library. Used in {Protocol::Messages}.
20
+ class IncompatibleRemoteError < ProtocolError; end
21
+
22
+ # The remote closed the connection. Used in {Protocol::Socket}.
23
+ class RemoteClosedError < ProtocolError; end
24
+
25
+ # Raised when a string is unpacked but it doesn't match any
26
+ # struct defined here. Used in {Protocol::Packet}.
27
+ class NoStructError < ProtocolError; end
28
+
29
+ # Raised when a packet is sent when another packet was
30
+ # expected. Used in {Protocol::Packet}.
31
+ class UnacceptablePacketError < ProtocolError; end
32
+
33
+ # Raised if the encryptor runs into an error with encrypting
34
+ # or decrypting the packet. Used in {Encryptors::OpenSSL} and
35
+ # {Encryptors::RbNaCl}.
36
+ class EncryptorError < StandardError; end
37
+ end
38
+ end
@@ -0,0 +1,116 @@
1
+ module Nova
2
+ module Starbound
3
+ class Protocol
4
+
5
+ # Handles messages.
6
+ module Messages
7
+
8
+ # Checks the versions of the remote and us. If the major
9
+ # versions don't match, raise an error.
10
+ #
11
+ # @raise [IncompatibleRemoteError] if the major versions don't
12
+ # match.
13
+ # @return [void]
14
+ def check_versions(other_packet)
15
+ other_packet.expect(:protocol_version)
16
+ our_major = Nova::VERSION.split('.').first
17
+ their_major = other_packet.body.split('.').first
18
+
19
+ if our_major != their_major
20
+ raise IncompatibleRemoteError,
21
+ "Major versions do not match (our: #{our_major}, " +
22
+ "theirs: #{their_major})"
23
+ end
24
+ end
25
+
26
+ # Waits for the remote to send their protocol version, and
27
+ # then checks their version against ours, making sure the
28
+ # versions match.
29
+ #
30
+ # @raise [IncompatibleRemoteError] if the major versions don't
31
+ # match.
32
+ # @return [void]
33
+ def wait_for_protocol_version
34
+ pack = read
35
+
36
+ check_versions(pack)
37
+
38
+ respond_to pack, :protocol_version, Nova::VERSION
39
+ end
40
+
41
+ # Handles setting up encryption with the server. Sends the
42
+ # server the list of options we have, and waits for a
43
+ # response. The first 4 bytes of the public key correspond
44
+ # to the index of the encryptor, and the rest is the actual
45
+ # public key. It then sends our public key back, but doesn't
46
+ # wait for a response.
47
+ #
48
+ # @return [void]
49
+ def handle_encryption
50
+ sent = send :encryption_options,
51
+ Encryptor.sorted_encryptors.map(&:encryptor_name).join("\n")
52
+
53
+ response = response_to sent
54
+ response.expect(:public_key)
55
+
56
+ encryptor = matching_encryptor *response.body.split("\n", 2)
57
+
58
+ respond_to response, :public_key, encryptor.public_key
59
+ self.encryption_provider = encryptor
60
+ end
61
+
62
+ # Handles the server encryption. Reads a packet, splits the
63
+ # body by new lines, and searches our encryptors for a
64
+ # matching encryptor. Initializes it, and sends back data
65
+ # to the client containing which encryptor was used and our
66
+ # public key; waits for a response containing the client's
67
+ # public key, and then sets the encryption provider.
68
+ #
69
+ # @return [void]
70
+ def handle_server_encryption
71
+ enc_options = read
72
+ enc_options.expect(:encryption_options)
73
+ lines = enc_options.body.split("\n")
74
+
75
+ encs = Encryptor.sorted_encryptors.select do |e|
76
+ lines.include?(e.encryptor_name)
77
+ end
78
+
79
+ preferred = encs.first
80
+ index = lines.index(preferred.encryptor_name)
81
+ encryptor = preferred.new
82
+ encryptor.private_key!
83
+
84
+ data = preferred.encryptor_name + "\n"
85
+
86
+ out = respond_to enc_options, :public_key, data +
87
+ encryptor.public_key
88
+
89
+ pub_key = response_to out
90
+ pub_key.expect(:public_key)
91
+
92
+ encryptor.other_public_key = pub_key.body
93
+
94
+ self.encryption_provider = encryptor
95
+ end
96
+
97
+ private
98
+
99
+ # Handles selecting and setting up the encryption for this
100
+ # protocol, given the name from the remote.
101
+ #
102
+ # @return [Encryptor]
103
+ def matching_encryptor(name, body)
104
+ enc = Encryptor.encryptors.select { |e|
105
+ e.encryptor_name == name
106
+ }.first.new
107
+ enc.private_key!
108
+ enc.other_public_key = body
109
+
110
+ enc
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+ end