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 }