Class: Akero

Inherits:
Object
  • Object
show all
Defined in:
lib/akero.rb,
lib/akero.rb,
lib/akero/cli.rb,
lib/akero/version.rb,
lib/akero/benchmark.rb

Overview

Akero

Defined Under Namespace

Classes: Cli, Message

Constant Summary collapse

DEFAULT_RSA_BITS =
4096
DEFAULT_DIGEST =
OpenSSL::Digest::SHA512
VERSION =
'1.1.1'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rsa_bits = DEFAULT_RSA_BITS, digest = DEFAULT_DIGEST) ⇒ Akero

Create a new Akero instance.

Examples:

Create new Akero instance with default settings

Akero.new

Create new Akero instance with a 4096-bit key

Akero.new(4096)

Create new Akero instance with a 4096-bit key and SHA512 digest

Akero.new(4096, OpenSSL::Digest::SHA512)

Parameters:

  • rsa_bits (Integer) (defaults to: DEFAULT_RSA_BITS)

    RSA key length

  • digest (OpenSSL::Digest) (defaults to: DEFAULT_DIGEST)

    Signature digest



112
113
114
# File 'lib/akero.rb', line 112

def initialize(rsa_bits = DEFAULT_RSA_BITS, digest = DEFAULT_DIGEST)
  @key, @cert = generate_keypair(rsa_bits, digest) unless rsa_bits.nil?
end

Class Method Details

.fingerprint_from_cert(cert) ⇒ String

Extract fingerprint from an Akero public key.

Returns:

  • (String)

    Akero fingerprint

Raises:

  • (ERR_CERT_CORRUPT)


272
273
274
275
276
277
# File 'lib/akero.rb', line 272

def self.fingerprint_from_cert(cert)
  cert.extensions.map.each do |e|
    return "AK:#{e.value}" if e.oid == 'subjectKeyIdentifier'
  end
  raise ERR_CERT_CORRUPT
end

.load(private_key) ⇒ Akero

Load an Akero identity.

Examples:

Load previously stored private key

Akero.load(File.read('/tmp/alice.akr'))

Parameters:

  • private_key (String)

    Akero private key

Returns:

  • (Akero)

    New Akero instance



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/akero.rb', line 123

def self.load(private_key)
  inner = Base64.decode64(private_key[PKEY_HEADER.length..private_key.length - PKEY_FOOTER.length])
  if inner[0..63] != OpenSSL::Digest::SHA512.new(inner[64..-1]).digest
    raise ERR_PKEY_CORRUPT
  end
  cert_len = inner[64..65].unpack('S')[0]
  akero = Akero.new(nil)
  akero.instance_variable_set(:@cert, OpenSSL::X509::Certificate.new(inner[66..66 + cert_len]))
  akero.instance_variable_set(:@key, OpenSSL::PKey::RSA.new(inner[66 + cert_len..-1]))
  akero
end

.replate(msg, plates, reverse = false) ⇒ String

Swap the “license plates” on an ascii-armored message. This is done for user-friendliness, so stored Akero messages and keys can be easily identified at a glance.

Parameters:

  • msg (String)

    Message to be replated

  • plates (Array)

    Array of the two plates to swap

  • reverse (Boolean) (defaults to: false)

    Reverse the swap?

Returns:

  • (String)

    The replated message



264
265
266
267
# File 'lib/akero.rb', line 264

def self.replate(msg, plates, reverse = false)
  a, b = reverse ? [1, 0] : [0, 1]
  "-----BEGIN #{plates[b]}-----#{msg.strip[plates[a].length + 16..-(plates[a].length + 15)]}-----END #{plates[b]}-----\n"
end

Instance Method Details

#encrypt(to, plaintext, ascii_armor = true) ⇒ String

Sign->encrypt->sign a message for 1 or more recipients.

Only the listed recipients can decrypt the message-body but anyone can extract the sender’s public key.

Examples:

Alice encrypts a message to Bob

alice = Akero.new
bob = Akero.new
ciphertext = alice.encrypt(bob.public_key, "Hello Bob!")

Alice encrypts a message to Bob and Charlie

alice = Akero.new
bob = Akero.new
charlie = Akero.new
ciphertext = alice.encrypt([bob.public_key, charlie.public_key], "Hello Bob and Charlie!")

Parameters:

  • to (Array)

    Akero public keys of recipients

  • plaintext (String)

    The message to encrypt (binary safe)

  • ascii_armor (Boolean) (defaults to: true)

    Convert the output to base64?

Returns:

  • (String)

    Akero secret message



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/akero.rb', line 197

def encrypt(to, plaintext, ascii_armor = true)
  to = [to] unless to.is_a? Array
  to = to.map do |e|
    case e
    when String
      begin
        OpenSSL::X509::Certificate.new(Akero.replate(e, Akero::PLATE_CERT, true))
      rescue OpenSSL::X509::CertificateError
        raise ERR_INVALID_RECIPIENT_CERT
      end
    else
      raise ERR_INVALID_RECIPIENT
    end
  end
  out = _sign(_encrypt(to, _sign(plaintext, false)))
  ascii_armor ? Akero.replate(out.to_s, PLATE_CRYPTED) : out.to_der
end

#idString

Unique fingerprint of this Akero keypair.

Returns:

  • (String)

    Akero fingerprint



94
95
96
# File 'lib/akero.rb', line 94

def id
  Akero.fingerprint_from_cert(@cert)
end

#private_keyString

Private key (do not share this with anyone!)

Examples:

Save and load an Akero identity

alice = Akero.new
# Save
File.open('/tmp/alice.akr', 'w') { |f| f.write(alice.private_key) }
# Load
new_alice = Akero.load(File.read('/tmp/alice.akr'))

Returns:

  • (String)

    Private key (ascii armored)

See Also:



156
157
158
159
160
161
162
163
164
165
# File 'lib/akero.rb', line 156

def private_key
  # We do not use PKCS#12 ("PFX") for serialization here
  # because of http://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html
  cert_der = @cert.to_der
  out = [cert_der.length].pack('S')
  out << cert_der
  out << @key.to_der
  out.insert(0, OpenSSL::Digest::SHA512.new(out).digest)
  PKEY_HEADER + Base64.encode64(out) + PKEY_FOOTER
end

#public_keyString

Akero public key.

Share this with other Akero instances that you wish to receive encrypted messages from.

Returns:

  • (String)

    Public key (ascii armored)



141
142
143
# File 'lib/akero.rb', line 141

def public_key
  Akero.replate(@cert.to_s, Akero::PLATE_CERT)
end

#receive(ciphertext) ⇒ Akero::Message

Receive an Akero message.

Parameters:

  • ciphertext (String)

    Akero Message

Returns:

Raises:

  • (ERR_MSG_MALFORMED_BODY)


219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/akero.rb', line 219

def receive(ciphertext)
  if ciphertext.start_with? '-----BEGIN '
    ciphertext = Akero.replate(ciphertext, Akero::PLATE_CRYPTED, true)
  end
  begin
    body, signer_cert, body_type = verify(ciphertext, nil)
  rescue ArgumentError
    raise ERR_MSG_MALFORMED_ENV
  end

  case body_type.ord
  when 0x00
    # public message (signed)
    return Message.new(body, signer_cert, :signed)
  when 0x01
    # private message (signed, crypted, signed)
    signed_plaintext = _decrypt(body)
    plaintext, _verified_cert, _body_type = verify(signed_plaintext, signer_cert)
    msg = Message.new(plaintext, signer_cert, :encrypted)
    return msg
  end
  raise ERR_MSG_MALFORMED_BODY
end

#sign(plaintext, ascii_armor = true) ⇒ String

Sign a message.

Parameters:

  • plaintext (String)

    The message to sign (binary safe)

  • ascii_armor (Boolean) (defaults to: true)

    Convert the output in base64?

Returns:

  • (String)

    Akero signed message



172
173
174
175
# File 'lib/akero.rb', line 172

def sign(plaintext, ascii_armor = true)
  out = _sign(plaintext)
  ascii_armor ? Akero.replate(out.to_s, Akero::PLATE_SIGNED) : out.to_der
end