Created separate encoder for messages
This commit is contained in:
199
pkg/message/message.go
Normal file
199
pkg/message/message.go
Normal file
@@ -0,0 +1,199 @@
|
||||
// Package packet provides a standard message binary serialised data format and
|
||||
// message segmentation scheme which includes address.Sender cloaked public
|
||||
// key and address.Receiver private keys for generating a shared cipher and applying
|
||||
// to messages/message segments.
|
||||
package message
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
|
||||
"github.com/Indra-Labs/indra"
|
||||
"github.com/Indra-Labs/indra/pkg/ciph"
|
||||
"github.com/Indra-Labs/indra/pkg/key/address"
|
||||
"github.com/Indra-Labs/indra/pkg/key/prv"
|
||||
"github.com/Indra-Labs/indra/pkg/key/pub"
|
||||
"github.com/Indra-Labs/indra/pkg/key/sig"
|
||||
"github.com/Indra-Labs/indra/pkg/nonce"
|
||||
"github.com/Indra-Labs/indra/pkg/sha256"
|
||||
"github.com/Indra-Labs/indra/pkg/slice"
|
||||
log2 "github.com/cybriq/proc/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
log = log2.GetLogger(indra.PathBase)
|
||||
check = log.E.Chk
|
||||
)
|
||||
|
||||
// Message is the standard format for an encrypted, possibly segmented message
|
||||
// container with parameters for Reed Solomon Forward Error Correction and
|
||||
// contains previously seen cipher keys so the correspondent can free them.
|
||||
type Message struct {
|
||||
// Seq specifies the segment number of the message, 4 bytes long.
|
||||
Seq uint16
|
||||
// Length is the number of segments in the batch
|
||||
Length uint32
|
||||
// Parity is the ratio of redundancy. In each 256 segment
|
||||
Parity byte
|
||||
// Data is the message.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// GetOverhead returns the packet frame overhead given the settings found in the
|
||||
// packet.
|
||||
func (p *Message) GetOverhead() int {
|
||||
return Overhead
|
||||
}
|
||||
|
||||
// Overhead is the base overhead on a packet, use GetOverhead to add any extra
|
||||
// as found in a Message.
|
||||
const Overhead = slice.Uint16Len +
|
||||
slice.Uint32Len + 1 + SigEnd
|
||||
|
||||
// Packets is a slice of pointers to packets.
|
||||
type Packets []*Message
|
||||
|
||||
// sort.Interface implementation.
|
||||
|
||||
func (p Packets) Len() int { return len(p) }
|
||||
func (p Packets) Less(i, j int) bool { return p[i].Seq < p[j].Seq }
|
||||
func (p Packets) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// EP defines the parameters for creating a (split) packet given a set of keys,
|
||||
// cipher, and data. To, From, Blk and Data are required, Parity is optional,
|
||||
// set it to define a level of Reed Solomon redundancy on the split packets.
|
||||
// Seen should be populated to send a signal to the other side of keys that have
|
||||
// been seen at time of constructing this packet that can now be discarded as
|
||||
// they will not be used to generate a cipher again.
|
||||
type EP struct {
|
||||
To *address.Sender
|
||||
From *prv.Key
|
||||
Length int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// GetOverhead returns the amount of the message that will not be part of the
|
||||
// payload.
|
||||
func (ep EP) GetOverhead() int {
|
||||
return Overhead
|
||||
}
|
||||
|
||||
const (
|
||||
CheckEnd = 4
|
||||
NonceEnd = CheckEnd + nonce.IVLen
|
||||
AddressEnd = NonceEnd + address.Len
|
||||
SigEnd = AddressEnd + sig.Len
|
||||
)
|
||||
|
||||
// Encode creates a Message, encrypts the payload using the given private from
|
||||
// key and the public to key, serializes the form, signs the bytes and appends
|
||||
// the signature to the end.
|
||||
func Encode(ep EP) (pkt []byte, e error) {
|
||||
var blk cipher.Block
|
||||
if blk, e = ciph.GetBlock(ep.From, ep.To.Key); check(e) {
|
||||
return
|
||||
}
|
||||
nonc := nonce.New()
|
||||
var to address.Cloaked
|
||||
to, e = ep.To.GetCloak()
|
||||
Length := slice.NewUint32()
|
||||
slice.EncodeUint32(Length, ep.Length)
|
||||
// Concatenate the message pieces together into a single byte slice.
|
||||
pkt = slice.Cat(
|
||||
// f.Nonce[:], // 16 bytes \
|
||||
// f.To[:], // 8 bytes |
|
||||
make([]byte, SigEnd),
|
||||
Length, // 4 bytes
|
||||
ep.Data,
|
||||
)
|
||||
// Encrypt the encrypted part of the data.
|
||||
ciph.Encipher(blk, nonc, pkt[SigEnd:])
|
||||
// Sign the packet.
|
||||
var s sig.Bytes
|
||||
hash := sha256.Single(pkt[SigEnd:])
|
||||
if s, e = sig.Sign(ep.From, hash); check(e) {
|
||||
return
|
||||
}
|
||||
// Copy nonce, address, check and signature over top of the header.
|
||||
copy(pkt[CheckEnd:NonceEnd], nonc)
|
||||
copy(pkt[NonceEnd:AddressEnd], to)
|
||||
copy(pkt[AddressEnd:SigEnd], s)
|
||||
// last bot not least, the packet check header, which protects the
|
||||
// entire packet.
|
||||
checkBytes := sha256.Single(pkt[CheckEnd:])[:CheckEnd]
|
||||
copy(pkt[:CheckEnd], checkBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// GetKeys returns the To field of the message in order, checks the packet
|
||||
// checksum and recovers the public key signing it.
|
||||
//
|
||||
// After this, if the matching private key to the cloaked address returned is
|
||||
// found, it is combined with the public key to generate the cipher and the
|
||||
// entire packet should then be processed with ciph.Encipher (sans signature)
|
||||
// using the block cipher thus created from the shared secret, and the Decode
|
||||
// function will then decode a Message.
|
||||
func GetKeys(d []byte) (to address.Cloaked, from *pub.Key, e error) {
|
||||
pktLen := len(d)
|
||||
if pktLen < Overhead {
|
||||
// If this isn't checked the slice operations later can
|
||||
// hit bounds errors.
|
||||
e = fmt.Errorf("packet too small, min %d, got %d",
|
||||
Overhead, pktLen)
|
||||
log.E.Ln(e)
|
||||
return
|
||||
}
|
||||
to = d[NonceEnd:AddressEnd]
|
||||
// split off the signature and recover the public key
|
||||
var s sig.Bytes
|
||||
var chek []byte
|
||||
chek = d[:CheckEnd]
|
||||
s = d[AddressEnd:SigEnd]
|
||||
checkHash := sha256.Single(d[CheckEnd:])[:4]
|
||||
if string(chek) != string(checkHash[:4]) {
|
||||
e = fmt.Errorf("check failed: got '%v', expected '%v'",
|
||||
chek, checkHash[:4])
|
||||
return
|
||||
}
|
||||
hash := sha256.Single(d[SigEnd:])
|
||||
if from, e = s.Recover(hash); check(e) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Decode a packet and return the Message with encrypted payload and signer's
|
||||
// public key. This assumes GetKeys succeeded and the matching private key was
|
||||
// found.
|
||||
func Decode(d []byte, from *pub.Key, to *prv.Key) (f *Message, e error) {
|
||||
pktLen := len(d)
|
||||
if pktLen < Overhead {
|
||||
// If this isn't checked the slice operations later can
|
||||
// hit bounds errors.
|
||||
e = fmt.Errorf("packet too small, min %d, got %d",
|
||||
Overhead, pktLen)
|
||||
log.E.Ln(e)
|
||||
return
|
||||
}
|
||||
// Trim off the signature and hash, we already have the key and have
|
||||
// validated the checksum.
|
||||
|
||||
f = &Message{}
|
||||
// copy the nonce
|
||||
nonc := make(nonce.IV, nonce.IVLen)
|
||||
copy(nonc, d[CheckEnd:NonceEnd])
|
||||
var blk cipher.Block
|
||||
if blk, e = ciph.GetBlock(to, from); check(e) {
|
||||
return
|
||||
}
|
||||
// This decrypts the rest of the packet, which is encrypted for
|
||||
// security.
|
||||
data := d[SigEnd:]
|
||||
ciph.Encipher(blk, nonc, data)
|
||||
var length slice.Size32
|
||||
length, data = slice.Cut(data, slice.Uint32Len)
|
||||
f.Length = uint32(slice.DecodeUint32(length))
|
||||
f.Data = data
|
||||
// log.I.Ln("decode length", len(data), "length prefix", f.Length)
|
||||
return
|
||||
}
|
||||
62
pkg/message/message_test.go
Normal file
62
pkg/message/message_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Indra-Labs/indra/pkg/key/address"
|
||||
"github.com/Indra-Labs/indra/pkg/key/prv"
|
||||
"github.com/Indra-Labs/indra/pkg/key/pub"
|
||||
"github.com/Indra-Labs/indra/pkg/sha256"
|
||||
"github.com/Indra-Labs/indra/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestEncode_Decode(t *testing.T) {
|
||||
msgSize := 1382
|
||||
payload := make([]byte, msgSize)
|
||||
var e error
|
||||
var n int
|
||||
if n, e = rand.Read(payload); check(e) && n != msgSize {
|
||||
t.Error(e)
|
||||
}
|
||||
payload = append([]byte("payload"), payload...)
|
||||
pHash := sha256.Single(payload)
|
||||
var sp, rp *prv.Key
|
||||
var sP, rP *pub.Key
|
||||
if sp, rp, sP, rP, e = testutils.GenerateTestKeyPairs(); check(e) {
|
||||
t.FailNow()
|
||||
}
|
||||
addr := address.FromPubKey(rP)
|
||||
var pkt []byte
|
||||
params := EP{
|
||||
To: addr,
|
||||
From: sp,
|
||||
Data: payload,
|
||||
Length: msgSize,
|
||||
}
|
||||
if pkt, e = Encode(params); check(e) {
|
||||
t.Error(e)
|
||||
}
|
||||
var to address.Cloaked
|
||||
var from *pub.Key
|
||||
if to, from, e = GetKeys(pkt); check(e) {
|
||||
t.Error(e)
|
||||
}
|
||||
if !sP.ToBytes().Equals(from.ToBytes()) {
|
||||
t.Error(e)
|
||||
}
|
||||
rk := address.NewReceiver(rp)
|
||||
if !rk.Match(to) {
|
||||
t.Error("cloaked key incorrect")
|
||||
}
|
||||
var f *Message
|
||||
if f, e = Decode(pkt, from, rp); check(e) {
|
||||
t.Error(e)
|
||||
}
|
||||
dHash := sha256.Single(f.Data)
|
||||
if bytes.Compare(pHash, dHash) != 0 {
|
||||
t.Error(errors.New("encode/decode unsuccessful"))
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,11 @@ var (
|
||||
// GitRef is the gitref, as in refs/heads/branchname.
|
||||
GitRef = "refs/heads/main"
|
||||
// ParentGitCommit is the commit hash of the parent HEAD.
|
||||
ParentGitCommit = "57112689048c2adefde66958e5a50c19e0926331"
|
||||
ParentGitCommit = "461c4d70ab24685a7f3973a55c86bf840f6d022a"
|
||||
// BuildTime stores the time when the current binary was built.
|
||||
BuildTime = "2022-12-08T13:28:16+01:00"
|
||||
BuildTime = "2022-12-09T11:29:44+01:00"
|
||||
// SemVer lists the (latest) git tag on the build.
|
||||
SemVer = "v0.0.161"
|
||||
SemVer = "v0.0.162"
|
||||
// PathBase is the path base returned from runtime caller.
|
||||
PathBase = "/home/loki/src/github.com/Indra-Labs/indra/"
|
||||
// Major is the major number from the tag.
|
||||
@@ -25,7 +25,7 @@ var (
|
||||
// Minor is the minor number from the tag.
|
||||
Minor = 0
|
||||
// Patch is the patch version number from the tag.
|
||||
Patch = 161
|
||||
Patch = 162
|
||||
)
|
||||
|
||||
// Version returns a pretty printed version information string.
|
||||
|
||||
Reference in New Issue
Block a user