encrypt.go (view raw)
1//
2// Copyright (c) 2024 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "crypto/rand"
21 "encoding/base64"
22 "fmt"
23 "io"
24 "strings"
25 "time"
26
27 "golang.org/x/crypto/nacl/box"
28 "humungus.tedunangst.com/r/webs/gencache"
29)
30
31type boxSecKey struct {
32 key *[32]byte
33}
34type boxPubKey struct {
35 key *[32]byte
36}
37
38func encryptString(plain string, seckey boxSecKey, pubkey boxPubKey) (string, error) {
39 var nonce [24]byte
40 rand.Read(nonce[:])
41 out := box.Seal(nil, []byte(plain), &nonce, pubkey.key, seckey.key)
42
43 var sb strings.Builder
44 b64 := base64.NewEncoder(base64.StdEncoding, &sb)
45 b64.Write(nonce[:])
46 b64.Write(out)
47 b64.Close()
48 return sb.String(), nil
49}
50
51func decryptString(encmsg string, seckey boxSecKey, pubkey boxPubKey) (string, error) {
52 var buf bytes.Buffer
53 b64 := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encmsg))
54 io.Copy(&buf, b64)
55 data := buf.Bytes()
56 if len(data) < 24 {
57 return "", fmt.Errorf("not enough data")
58 }
59 var nonce [24]byte
60 copy(nonce[:], data)
61 data = data[24:]
62 out, ok := box.Open(nil, data, &nonce, pubkey.key, seckey.key)
63 if !ok {
64 return "", fmt.Errorf("error decrypting chonk")
65 }
66 return string(out), nil
67}
68
69func b64tokey(s string) (*[32]byte, error) {
70 var buf bytes.Buffer
71 b64 := base64.NewDecoder(base64.StdEncoding, strings.NewReader(s))
72 n, _ := io.Copy(&buf, b64)
73 if n != 32 {
74 return nil, fmt.Errorf("bad key size")
75 }
76 var key [32]byte
77 copy(key[:], buf.Bytes())
78 return &key, nil
79}
80
81func tob64(data []byte) string {
82 var sb strings.Builder
83 b64 := base64.NewEncoder(base64.StdEncoding, &sb)
84 b64.Write(data)
85 b64.Close()
86 return sb.String()
87}
88
89func newChatKeys() (boxPubKey, boxSecKey) {
90 pub, sec, _ := box.GenerateKey(rand.Reader)
91 return boxPubKey{pub}, boxSecKey{sec}
92}
93
94var chatkeys = gencache.New(gencache.Options[string, boxPubKey]{Fill: func(xonker string) (boxPubKey, bool) {
95 data := getxonker(xonker, chatKeyProp)
96 if data == "" {
97 dlog.Printf("hitting the webs for missing chatkey: %s", xonker)
98 j, err := GetJunk(readyLuserOne, xonker)
99 if err != nil {
100 ilog.Printf("error getting %s: %s", xonker, err)
101 when := time.Now().UTC().Format(dbtimeformat)
102 stmtSaveXonker.Exec(xonker, "failed", chatKeyProp, when)
103 return boxPubKey{}, true
104 }
105 allinjest(originate(xonker), j)
106 data = getxonker(xonker, chatKeyProp)
107 if data == "" {
108 ilog.Printf("key not found after ingesting")
109 when := time.Now().UTC().Format(dbtimeformat)
110 stmtSaveXonker.Exec(xonker, "failed", chatKeyProp, when)
111 return boxPubKey{}, true
112 }
113 }
114 if data == "failed" {
115 ilog.Printf("lookup previously failed chatkey %s", xonker)
116 return boxPubKey{}, true
117 }
118 var pubkey boxPubKey
119 var err error
120 pubkey.key, err = b64tokey(data)
121 if err != nil {
122 ilog.Printf("error decoding %s pubkey: %s", xonker, err)
123 }
124 return pubkey, true
125}, Limit: 512})
126
127func getchatkey(xonker string) (boxPubKey, bool) {
128 pubkey, _ := chatkeys.Get(xonker)
129 return pubkey, pubkey.key != nil
130}