Class: Equity::Controller::Key
- Inherits:
-
Object
- Object
- Equity::Controller::Key
- Defined in:
- lib/equity/controller/key.rb
Constant Summary collapse
- LENGTH =
Length, in bits, of generated keys.
2048
- DEFAULT_IDENTITY_PATH =
Default file system path where the user’s key is stored. This path is the private half of the key. The public key can be found by appending “.pub” to this path.
File.join(ENV['HOME'], ".equity", "id_dsa")
- PERSONAL_AUTHORIZED_KEYS_PATH =
Array of paths that contain the lists of public keys that identify people who are authorized to control equity.
File.join(ENV['HOME'], ".equity", "authorized_keys")
- SYSTEM_AUTHORIZED_KEYS_PATH =
"/etc/equity/authorized_keys"
- SIGNATURE_SEPARATOR =
Byte sequence that separates the data and the signature in signed data.
0.chr
Class Method Summary collapse
-
.authorized_keys ⇒ Object
Returns an array of keys which are authorized to control equity.
-
.generate ⇒ Object
Generates a new DSA key pair and stores it in a pair of files at the default identity path.
-
.signature_key(data_with_sig) ⇒ Object
Returns the public half of the key that signed
data_with_sig
. -
.verify(data_with_sig) ⇒ Object
Verifies the cryptographic signature appended to
data_with_sig
.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Two keys are equal if they have the same public key.
-
#authorized? ⇒ Boolean
Returns true if this key is authorized to control equity.
-
#initialize(path_or_pem = DEFAULT_IDENTITY_PATH) ⇒ Key
constructor
Loads a key from disk or a string.
-
#paths ⇒ Object
Returns an array of paths to all files associated with this key.
-
#private? ⇒ Boolean
Returns true if this key includes the private half of the key.
-
#sign(data) ⇒ Object
Appends a cryptographic signature to
data
.
Constructor Details
#initialize(path_or_pem = DEFAULT_IDENTITY_PATH) ⇒ Key
Loads a key from disk or a string. If the loaded key includes the private half of the key and the key has been loaded from disk, the file’s permissions are verified to ensure that only the owner can access it. Raises a SecurityError if the file’s permissions are unsafe.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/equity/controller/key.rb', line 48 def initialize(path_or_pem = DEFAULT_IDENTITY_PATH) # Try to interpret path_or_pem as a PEM string first. begin @dsa = OpenSSL::PKey::DSA.new(path_or_pem) rescue # That didn't work. It must be a path. @path = path_or_pem @dsa = OpenSSL::PKey::DSA.new(IO.read(@path)) # If this is a private key, make sure nobody else can read it. if @dsa.private? mode = File.stat(@path).mode unless mode & 077 == 0 raise SecurityError, "unsafe permissions on #{@path} - must not allow any access by group or world" end end end end |
Class Method Details
.authorized_keys ⇒ Object
Returns an array of keys which are authorized to control equity. Raises a SecurityError if either of the authorized key files are writable by group or world, or if a private key is found in the files.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/equity/controller/key.rb', line 126 def self. # Return cached keys if they've already been loaded. return @authorized_keys if @authorized_keys # Read the keys and verify file permissions. pem = "" if File.exist?(PERSONAL_AUTHORIZED_KEYS_PATH) mode = File.stat(PERSONAL_AUTHORIZED_KEYS_PATH).mode unless mode & 022 == 0 raise SecurityError, "unsafe permissions on #{PERSONAL_AUTHORIZED_KEYS_PATH} - must not be writable by group or world" end pem = IO.read(PERSONAL_AUTHORIZED_KEYS_PATH) + "\n" end if File.exist?(SYSTEM_AUTHORIZED_KEYS_PATH) mode = File.stat(SYSTEM_AUTHORIZED_KEYS_PATH).mode unless mode & 022 == 0 raise SecurityError, "unsafe permissions on #{SYSTEM_AUTHORIZED_KEYS_PATH} - must not be writable by group or world" end pem += IO.read(SYSTEM_AUTHORIZED_KEYS_PATH) end # Instantiate the keys and verify that they're all public. pems = pem.split(/-----END DSA PUBLIC KEY-----/) pems.pop pems.collect! do |pem| key = new(pem + "-----END DSA PUBLIC KEY-----") if key.private? raise SecurityError, "private key found in authorized keys - only public keys should be authorized" end key end # Cache the loaded keys. @authorized_keys = pems end |
.generate ⇒ Object
Generates a new DSA key pair and stores it in a pair of files at the default identity path. Returns the generated key.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/equity/controller/key.rb', line 27 def self.generate # Generate a DSA key pair. dsa = OpenSSL::PKey::DSA.generate(LENGTH) # Write the private key to disk. FileUtils.mkdir_p(File.dirname(DEFAULT_IDENTITY_PATH)) File.open(DEFAULT_IDENTITY_PATH, "w") do |io| io.chmod(0600) io.write(dsa.to_pem) end # Write the public key to disk. File.open(DEFAULT_IDENTITY_PATH + ".pub", "w") do |io| io.write(dsa.public_key.to_pem) end # Return a Key object with the newly generated key. new end |
.signature_key(data_with_sig) ⇒ Object
Returns the public half of the key that signed data_with_sig
.
170 171 172 173 |
# File 'lib/equity/controller/key.rb', line 170 def self.signature_key(data_with_sig) data, sig, public_pem = data_with_sig.split(/#{SIGNATURE_SEPARATOR}/) new(public_pem) end |
.verify(data_with_sig) ⇒ Object
Verifies the cryptographic signature appended to data_with_sig
. If the signature is legitimate, the data is returned. Otherwise this method returns false
.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/equity/controller/key.rb', line 101 def self.verify(data_with_sig) data, sig, public_pem = data_with_sig.split(/#{SIGNATURE_SEPARATOR}/) sig = Base64.decode64(sig) dsa = OpenSSL::PKey::DSA.new(public_pem) sig_r = sig[0,20].unpack("H*")[0].to_i(16) sig_s = sig[20,20].unpack("H*")[0].to_i(16) a1sig = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(sig_r), OpenSSL::ASN1::Integer(sig_s) ]) if dsa.verify(OpenSSL::Digest::DSS1.new, a1sig.to_der, data) data else false end end |
Instance Method Details
#==(other) ⇒ Object
Two keys are equal if they have the same public key.
165 166 167 |
# File 'lib/equity/controller/key.rb', line 165 def ==(other) @dsa.public_key.to_s == other.instance_eval {@dsa.public_key.to_s} end |
#authorized? ⇒ Boolean
Returns true if this key is authorized to control equity.
119 120 121 |
# File 'lib/equity/controller/key.rb', line 119 def self.class..include?(self) end |
#paths ⇒ Object
Returns an array of paths to all files associated with this key.
67 68 69 70 71 72 73 74 |
# File 'lib/equity/controller/key.rb', line 67 def paths return [] if @path.nil? if @dsa.private? [@path, @path + ".pub"] else [@path + ".pub"] end end |
#private? ⇒ Boolean
Returns true if this key includes the private half of the key.
160 161 162 |
# File 'lib/equity/controller/key.rb', line 160 def private? @dsa.private? end |
#sign(data) ⇒ Object
Appends a cryptographic signature to data
.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/equity/controller/key.rb', line 77 def sign(data) sig = @dsa.sign(OpenSSL::Digest::DSS1.new, data) a1sig = OpenSSL::ASN1.decode( sig ) sig_r = a1sig.value[0].value.to_s(2) sig_s = a1sig.value[1].value.to_s(2) if sig_r.length > 20 || sig_s.length > 20 raise OpenSSL::PKey::DSAError, "bad sig size" end sig_r = "\0" * ( 20 - sig_r.length ) + sig_r if sig_r.length < 20 sig_s = "\0" * ( 20 - sig_s.length ) + sig_s if sig_s.length < 20 return [ data, Base64.encode64(sig_r + sig_s), @dsa.public_key.to_pem ].join(SIGNATURE_SEPARATOR) end |