summaryrefslogtreecommitdiffstats
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/api/api.go64
-rw-r--r--internal/api/errorcode_string.go28
-rw-r--r--internal/cryptoutil/cryptoutil.go107
3 files changed, 199 insertions, 0 deletions
diff --git a/internal/api/api.go b/internal/api/api.go
new file mode 100644
index 0000000..7c8472a
--- /dev/null
+++ b/internal/api/api.go
@@ -0,0 +1,64 @@
+// Package api contains type definitions for the gob API.
+package api
+
+import "encoding/gob"
+
+type ErrorCode int
+
+//go:generate go tool stringer -type=ErrorCode
+const (
+ Ok ErrorCode = iota
+ Unauthenticated // missing credentials
+ PermissionDenied // invalid credentials, or login required
+ BadRequest // incorrect request format (try gob)
+ NotFound // requested resource was not found
+ Internal // server encountered an unexpected error
+)
+
+func (c ErrorCode) Error() string {
+ return c.String()
+}
+
+type Response struct {
+ Status ErrorCode
+ Err string
+ Ok any
+}
+
+type LoginResponse struct {
+ Token string
+}
+
+type ListNotesRequest struct{}
+
+type ListNotesResponse struct {
+ Notes []string
+}
+
+type CreateNoteRequestStream struct {
+ Chunk []byte
+}
+
+type CreateNoteResponse struct {
+ Name string
+}
+
+type ReadNoteRequest struct {
+ Note string
+}
+
+type ReadNoteResponseStream struct {
+ Chunk []byte
+}
+
+type DeleteNoteRequest struct{}
+
+type DeleteNoteResponse struct{}
+
+func init() {
+ gob.Register(&LoginResponse{})
+ gob.Register(&ListNotesResponse{})
+ gob.Register(&CreateNoteResponse{})
+ gob.Register(&ReadNoteResponseStream{})
+ gob.Register(&DeleteNoteResponse{})
+}
diff --git a/internal/api/errorcode_string.go b/internal/api/errorcode_string.go
new file mode 100644
index 0000000..2b4a294
--- /dev/null
+++ b/internal/api/errorcode_string.go
@@ -0,0 +1,28 @@
+// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT.
+
+package api
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[Ok-0]
+ _ = x[Unauthenticated-1]
+ _ = x[PermissionDenied-2]
+ _ = x[BadRequest-3]
+ _ = x[NotFound-4]
+ _ = x[Internal-5]
+}
+
+const _ErrorCode_name = "OkUnauthenticatedPermissionDeniedBadRequestNotFoundInternal"
+
+var _ErrorCode_index = [...]uint8{0, 2, 17, 33, 43, 51, 59}
+
+func (i ErrorCode) String() string {
+ if i < 0 || i >= ErrorCode(len(_ErrorCode_index)-1) {
+ return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _ErrorCode_name[_ErrorCode_index[i]:_ErrorCode_index[i+1]]
+}
diff --git a/internal/cryptoutil/cryptoutil.go b/internal/cryptoutil/cryptoutil.go
index 4ed600d..09ed996 100644
--- a/internal/cryptoutil/cryptoutil.go
+++ b/internal/cryptoutil/cryptoutil.go
@@ -13,6 +13,10 @@ import (
"crypto/subtle"
"encoding/hex"
"errors"
+ "fmt"
+ "hash"
+ "io"
+ "slices"
)
const (
@@ -169,3 +173,106 @@ func (k EncryptionKey) Decrypt(msg EncryptedMessage) ([]byte, error) {
cipher.NewCTR(block, nonce).XORKeyStream(msg, msg)
return msg, nil
}
+
+// An EncryptingWriter encrypts the output and writes it to the underlying
+// writer. It's very important to call .Flush() to write the MAC after the data
+// has been written.
+type EncryptingWriter struct {
+ w io.Writer
+ stream cipher.Stream
+ mac hash.Hash
+ buf []byte
+}
+
+// Writer returns a new writer that encrypts its output. Don't forget to call
+// .Flush() to write the MAC.
+func (k EncryptionKey) Writer(w io.Writer) (*EncryptingWriter, error) {
+ aesKey, hmacKey := k[:aesKeySize], k[aesKeySize:]
+ block, err := aes.NewCipher(aesKey)
+ if err != nil {
+ return nil, err
+ }
+ mac := hmac.New(sha512.New, hmacKey)
+ nonce := make([]byte, nonceSize)
+ rand.Read(nonce)
+ mac.Write(nonce)
+ if _, err := w.Write(nonce); err != nil {
+ return nil, err
+ }
+ return &EncryptingWriter{
+ w: w,
+ stream: cipher.NewCTR(block, nonce),
+ mac: mac,
+ }, nil
+}
+
+// Write encrypts buf and writes it to the underlying writer.
+func (w *EncryptingWriter) Write(buf []byte) (int, error) {
+ if len(w.buf) < len(buf) {
+ w.buf = slices.Grow(w.buf, len(buf)-len(w.buf))
+ }
+ w.buf = w.buf[:len(buf)]
+ // Encrypt-then-MAC
+ w.stream.XORKeyStream(w.buf, buf)
+ w.mac.Write(w.buf)
+ return w.w.Write(w.buf)
+}
+
+// Flush writes the MAC for the encrypted message. Flush must be called after
+// all data has been written.
+func (w *EncryptingWriter) Flush() error {
+ _, err := w.w.Write(w.mac.Sum(nil))
+ return err
+}
+
+// A DecryptingReader decrypts data from an underlying reader.
+type DecryptingReader struct {
+ r cipher.StreamReader
+}
+
+// Reader returns a new DecryptingReader. The data is processed in two passes,
+// first to verify the MAC, then the io.Seeker interface is used to reset the
+// reader for decryption.
+func (k EncryptionKey) Reader(r io.ReadSeeker) (*DecryptingReader, error) {
+ aesKey, hmacKey := k[:aesKeySize], k[aesKeySize:]
+ block, err := aes.NewCipher(aesKey)
+ if err != nil {
+ return nil, err
+ }
+ totalLen, err := r.Seek(0, io.SeekEnd)
+ if err != nil {
+ return nil, err
+ }
+ if totalLen < certSize {
+ return nil, errors.New("file too short")
+ }
+ dataLen := totalLen - certSize
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
+ return nil, err
+ }
+ mac := hmac.New(sha512.New, hmacKey)
+ if _, err := io.Copy(mac, &io.LimitedReader{R: r, N: dataLen}); err != nil {
+ return nil, err
+ }
+ expectedMAC := mac.Sum(nil)
+ sig := make([]byte, certSize)
+ if _, err := io.ReadFull(r, sig); err != nil {
+ return nil, err
+ }
+ if !hmac.Equal(sig, expectedMAC) {
+ return nil, fmt.Errorf("invalid mac (got %x, want %x)", sig, expectedMAC)
+ }
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
+ return nil, err
+ }
+ nonce := make([]byte, nonceSize)
+ if _, err := io.ReadFull(r, nonce); err != nil {
+ return nil, err
+ }
+ return &DecryptingReader{cipher.StreamReader{S: cipher.NewCTR(block, nonce), R: &io.LimitedReader{R: r, N: dataLen - nonceSize}}}, nil
+}
+
+// Read reads and decrypts data from the underlying reader.
+func (r *DecryptingReader) Read(buf []byte) (int, error) {
+ return r.r.Read(buf)
+}