]> Sergey Matveev's repositories - mmc.git/blob - common.go
Verify SPKI hash
[mmc.git] / common.go
1 // mmc -- Mattermost client
2 // Copyright (C) 2023-2024 Sergey Matveev <stargrave@stargrave.org>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU Affero General Public License for more details.
13 //
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 package mmc
18
19 import (
20         "crypto/sha256"
21         "crypto/x509"
22         "encoding/hex"
23         "errors"
24         "os"
25         "strings"
26         "time"
27
28         "github.com/davecgh/go-spew/spew"
29         "github.com/mattermost/mattermost-server/v6/model"
30         "go.cypherpunks.ru/recfile"
31 )
32
33 const (
34         PerPage    = 100
35         OutRec     = "out.rec"
36         OutRecLock = "out.rec.lock"
37         Last       = "last"
38 )
39
40 var SleepTime = 250 * time.Millisecond
41
42 type Post struct {
43         P *model.Post
44         E string
45 }
46
47 func PostToRec(w *recfile.Writer, users map[string]*model.User, post Post) error {
48         _, err := w.RecordStart()
49         if err != nil {
50                 return err
51         }
52         created := time.Unix(post.P.CreateAt/1000, 0)
53         user := users[post.P.UserId]
54         sender := "unknown"
55         if user != nil {
56                 sender = user.Username
57         }
58         fields := []recfile.Field{
59                 {Name: "Id", Value: post.P.Id},
60                 {Name: "Created", Value: created.Format("2006-01-02 15:04:05")},
61                 {Name: "Sender", Value: sender},
62         }
63         if post.E != model.WebsocketEventPosted {
64                 fields = append(fields, recfile.Field{Name: "Event", Value: post.E})
65         }
66         if post.P.RootId != "" {
67                 fields = append(fields, recfile.Field{Name: "RootId", Value: post.P.RootId})
68         }
69         if post.P.Metadata != nil {
70                 for _, fi := range post.P.Metadata.Files {
71                         fields = append(
72                                 fields,
73                                 recfile.Field{Name: "File", Value: fi.Id},
74                                 recfile.Field{Name: "FileName", Value: fi.Name},
75                         )
76                 }
77         }
78         if _, err = w.WriteFields(fields...); err != nil {
79                 return err
80         }
81         _, err = w.WriteFieldMultiline("Text", strings.Split(post.P.Message, "\n"))
82         return err
83 }
84
85 func GetUsers(c *model.Client4, debugFd *os.File) (map[string]*model.User, error) {
86         users := make(map[string]*model.User)
87         for n := 0; ; n++ {
88                 time.Sleep(SleepTime)
89                 page, resp, err := c.GetUsers(n, PerPage, "")
90                 if err != nil {
91                         if debugFd != nil {
92                                 spew.Fdump(debugFd, resp)
93                         }
94                         return nil, err
95                 }
96                 if debugFd != nil {
97                         spew.Fdump(debugFd, page)
98                 }
99                 for _, u := range page {
100                         users[u.Id] = u
101                 }
102                 if len(page) < PerPage {
103                         break
104                 }
105         }
106         return users, nil
107 }
108
109 func GetEntrypoint() string {
110         s := os.Getenv("MMC_ENTRYPOINT")
111         if s == "" {
112                 return "http://mm.invalid"
113         }
114         return s
115 }
116
117 func GetSPKIHash() string {
118         s := os.Getenv("MMC_SPKI")
119         if s == "" {
120                 return "deadbeef"
121         }
122         return s
123 }
124
125 func NewVerifyPeerCertificate(hashExpected string) func(
126         rawCerts [][]byte, verifiedChains [][]*x509.Certificate,
127 ) error {
128         return func(
129                 rawCerts [][]byte, verifiedChains [][]*x509.Certificate,
130         ) error {
131                 cer, err := x509.ParseCertificate(rawCerts[0])
132                 if err != nil {
133                         return err
134                 }
135                 spki := cer.RawSubjectPublicKeyInfo
136                 hsh := sha256.Sum256(spki)
137                 if hashExpected != hex.EncodeToString(hsh[:]) {
138                         return errors.New("server certificate's SPKI hash mismatch")
139                 }
140                 return nil
141         }
142 }