code.pfad.fr/gohmekit@v0.2.1/pairing/crypto/session.go (about)

     1  package crypto
     2  
     3  import (
     4  	"crypto/cipher"
     5  	"encoding/binary"
     6  	"fmt"
     7  )
     8  
     9  func NewSession(sharedKey [32]byte, isAccessory bool) (*Session, error) {
    10  	salt := []byte("Control-Salt")
    11  	readKey := []byte("Control-Read-Encryption-Key")
    12  	writeKey := []byte("Control-Write-Encryption-Key")
    13  	if isAccessory {
    14  		writeKey, readKey = readKey, writeKey
    15  	}
    16  
    17  	sess := &Session{}
    18  	aeadGenerator := hkdfChacha20poly1305Generator(sharedKey[:], salt)
    19  	var err error
    20  	sess.readAEAD, err = aeadGenerator(readKey)
    21  	if err != nil {
    22  		return nil, err
    23  	}
    24  
    25  	sess.writeAEAD, err = aeadGenerator(writeKey)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	return sess, nil
    31  }
    32  
    33  type Session struct {
    34  	readAEAD   cipher.AEAD
    35  	readCount  uint64
    36  	readRemain []byte
    37  
    38  	writeAEAD  cipher.AEAD
    39  	writeCount uint64
    40  }
    41  
    42  func (s *Session) Seal(plaintext []byte) []byte {
    43  	if len(plaintext) == 0 {
    44  		return nil
    45  	}
    46  
    47  	var encrypted []byte
    48  	offset := 0
    49  	for len(plaintext)-offset > 1024 {
    50  		encrypted = append(encrypted, s.sealChunk(plaintext[offset:offset+1024])...)
    51  		offset += 1024
    52  	}
    53  
    54  	return append(encrypted, s.sealChunk(plaintext[offset:])...)
    55  }
    56  
    57  func (s *Session) sealChunk(plaintext []byte) []byte {
    58  	nonce := make([]byte, 12)
    59  	binary.LittleEndian.PutUint64(nonce[4:], s.writeCount)
    60  	s.writeCount++
    61  
    62  	// apparently in chacha20poly1305, ciphertext and plaintext have the same length
    63  	// https://go.googlesource.com/crypto/+/master/chacha20poly1305/chacha20poly1305.go
    64  	dst := make([]byte, 2, 2+len(plaintext)+s.writeAEAD.Overhead())
    65  
    66  	binary.LittleEndian.PutUint16(dst, uint16(len(plaintext)))
    67  
    68  	return s.writeAEAD.Seal(dst, nonce, plaintext, dst[:2])
    69  }
    70  
    71  // Open will decrypt and authenticate an incoming message.
    72  // If not enough bytes are present, it will return (nil,nil), but
    73  // keep the provided bytes in memory, to use them on the next call.
    74  func (s *Session) Open(p []byte) ([]byte, error) {
    75  	s.readRemain = append(s.readRemain, p...)
    76  
    77  	var dst []byte
    78  	i, b, err := s.openChunk(s.readRemain)
    79  	for b != nil && err == nil {
    80  		s.readRemain = s.readRemain[i:]
    81  		dst = append(dst, b...)
    82  		i, b, err = s.openChunk(s.readRemain)
    83  	}
    84  	s.readRemain = s.readRemain[i:]
    85  	return dst, err
    86  }
    87  
    88  func (s *Session) openChunk(p []byte) (int, []byte, error) {
    89  	if len(p) < 2+s.readAEAD.Overhead() {
    90  		// surely not enought data
    91  		return 0, nil, nil
    92  	}
    93  
    94  	length := binary.LittleEndian.Uint16(p)
    95  	if length > 1024 {
    96  		return 0, nil, fmt.Errorf("frame too large: %d", length)
    97  	}
    98  
    99  	cipherLength := int(length) + s.readAEAD.Overhead()
   100  	if len(p) < 2+cipherLength {
   101  		// not enought data yet
   102  		return 0, nil, nil
   103  	}
   104  
   105  	nonce := make([]byte, 12)
   106  	binary.LittleEndian.PutUint64(nonce[4:], s.readCount)
   107  	s.readCount++
   108  
   109  	out, err := s.readAEAD.Open(nil, nonce, p[2:cipherLength+2], p[:2])
   110  	return 2 + cipherLength, out, err
   111  }