Header
for a saltpack message.
Refs: - saltpack.org/encryption-format-v2
- FORMAT_MAJOR_VERSION
The header format major version field value
- FORMAT_MINOR_VERSION
The header format minor version field value
- FORMAT_NAME
The header format name field value
- FORMAT_VERSION
Version header Array
- MODES
Mode names to numeric values
- MODE_NAMES
- PAYLOAD_KEY_BOX_NONCE_PREFIX
The nonce prefix used to create the recipients list
- PAYLOAD_NONCE_PREFIX
The nonce prefix used for the payload packets
- SENDER_KEY_SECRETBOX_NONCE
The nonce used to create the sender key secret box
- ephemeral_key RW
The RbNaCl::PrivateKey used only for this message to encrypt/sign its internals
- format_name R
The format name used in the header
- format_version R
The [major, minor] version tuple used in the header
- hide_recipients RW
Whether to include the recipients’ public key in the recipients tuples of the message.
- mode R
The mode being used; one of the keys of MODES
- payload_key RW
The random payload key
- recipients R
The public keys of each of the message’s recipients.
- sender_key RW
The RbNaCl::PrivateKey/PublicKey of the sender
generate( sender_key, *recipient_public_keys, hide_recipients: false )
Generate a header
def self::generate( sender_key, *recipient_public_keys, hide_recipients: false )
end
Create a new header with the given
def initialize( **fields )
@format_name = FORMAT_NAME
@format_version = FORMAT_VERSION
@mode = :encryption
@payload_key = RbNaCl::Random.random_bytes( RbNaCl::SecretBox.key_bytes )
@ephemeral_key = RbNaCl::PrivateKey.generate
@sender_key = nil
@recipients = []
@hide_recipients = true
@recipient_mac_keys = nil
@hash = nil
@data = nil
fields.each do |name, value|
self.public_send( "#{name}=", value )
end
end
parse( source, recipient_key )
Parse the (already once-decoded) data in the specified source
as a Saltpack
header and return it as a Saltpack::Header
. Raises a Saltpack::Error if the source
cannot be parsed.
def self::parse( source, recipient_key )
source = StringIO.new( source ) unless
source.respond_to?( :read ) || source.respond_to?( :readpartial )
unpacker = MessagePack::Unpacker.new( source )
self.log.debug "Unpacker is: %p" % [ unpacker ]
encoded_header = unpacker.read
header_hash = RbNaCl::Hash.sha512( encoded_header )
parts = MessagePack.unpack( encoded_header )
raise Saltpack::MalformedMessage, "header is not an Array" unless parts.is_a?( Array )
raise Saltpack::UnsupportedFormat, parts[0] unless
parts[0] == FORMAT_NAME
raise Saltpack::UnsupportedVersion, parts[1] unless
parts[1] == FORMAT_VERSION
return new( *parts, header_hash: header_hash )
rescue MessagePack::MalformedFormatError => err
self.log.error "%p while parsing the header: %s" % [ err.class, err.message ]
raise Saltpack::MalformedMessage, "malformed msgpack data: %s" % [ err.message ]
end
Return the header as a binary String.
def data
self.finalize
return @data
end
Calculate all the header values and freeze it.
def finalize
return if self.frozen?
header_parts = [
self.format_name,
self.format_version,
self.numeric_mode,
self.ephemeral_key.public_key.to_bytes,
self.sender_secretbox,
self.recipient_tuples( hide_recipients: self.hide_recipients ),
]
header_bytes = MessagePack.pack( header_parts )
@hash = RbNaCl::Hash.sha512( header_bytes )
@data = MessagePack.pack( header_bytes )
@recipient_mac_keys = self.recipients.map.with_index do |recipient_key, i|
Saltpack.calculate_recipient_hash(
@hash, i,
[recipient_key, self.sender_private_key],
[recipient_key, self.ephemeral_key]
)
end
self.freeze
end
Overloaded – also freeze the recipients when the header is frozen.
def freeze
@recipients.freeze
super
end
Return the SHA612 hash of the single-messagepacked header.
def hash
self.finalize
return @hash
end
Set the mode as either a Symbol or as an Integer.
def mode=( new_mode )
if MODES.key?( new_mode )
@mode = new_mode
elsif MODE_NAMES.key?( new_mode )
@mode = MODE_NAMES[ new_mode ]
else
raise ArgumentError, "invalid mode %p" % [ new_mode ]
end
end
Return the mode as an Integer.
def numeric_mode
return MODES[ self.mode ]
end
The MAC keys used to hash/validate message parts.
def recipient_mac_keys
self.finalize
return @recipient_mac_keys
end
recipient_tuples( hide_recipients: true )
Generate an Array of header tuples from the recipients
keys and return it. If hide_recipients
is true, don’t include the public keys in the tuples.
def recipient_tuples( hide_recipients: true )
return self.recipients.map.with_index do |recipient_key, i|
box = RbNaCl::Box.new( recipient_key, self.ephemeral_key )
nonce = PAYLOAD_KEY_BOX_NONCE_PREFIX + [ i ].pack( 'Q>' )
encrypted_key = box.encrypt( nonce, self.payload_key )
[ hide_recipients ? nil : recipient_key, encrypted_key ]
end
end
Return the sender_key
after checking to be sure the PrivateKey is available.
def sender_private_key
key = self.sender_key or raise Saltpack::KeyError, "sender key is not set"
raise Saltpack::KeyError, "sender private key not available" unless
key && key.respond_to?( :public_key )
return key
end
Return either the sender_key
, or the public half of the send_key if it’s a PrivateKey.
def sender_public_key
key = self.sender_key or raise Saltpack::KeyError, "sender key is not set"
return key.public_key if key.respond_to?( :public_key )
return key
end
Return the sender secretbox
def sender_secretbox
box = RbNaCl::SecretBox.new( self.payload_key )
return box.encrypt( SENDER_KEY_SECRETBOX_NONCE, self.sender_public_key )
end