diff options
author | Mike Crute <mike@crute.us> | 2022-08-04 17:43:40 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2022-08-04 17:43:40 -0700 |
commit | f7b39e7e79e75396ccb0967b6c791f2e0264acef (patch) | |
tree | cc1be8e539b3f98a6bdfa95a5d51881d7b245ee7 | |
parent | 11b6cf3ce57b4e1706c8410ad7ccd499902fc4de (diff) | |
download | go-inform-f7b39e7e79e75396ccb0967b6c791f2e0264acef.tar.bz2 go-inform-f7b39e7e79e75396ccb0967b6c791f2e0264acef.tar.xz go-inform-f7b39e7e79e75396ccb0967b6c791f2e0264acef.zip |
Support AES GCM and snappy compression
-rw-r--r-- | go.mod | 5 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | inform/codec.go | 16 | ||||
-rw-r--r-- | inform/crypto.go | 50 | ||||
-rw-r--r-- | inform/inform.go | 56 |
5 files changed, 119 insertions, 10 deletions
@@ -0,0 +1,5 @@ | |||
1 | module code.crute.me/mcrute/go-inform | ||
2 | |||
3 | go 1.18 | ||
4 | |||
5 | require github.com/golang/snappy v0.0.4 | ||
@@ -0,0 +1,2 @@ | |||
1 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||
2 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||
diff --git a/inform/codec.go b/inform/codec.go index e5c9296..8147986 100644 --- a/inform/codec.go +++ b/inform/codec.go | |||
@@ -5,6 +5,8 @@ import ( | |||
5 | "encoding/binary" | 5 | "encoding/binary" |
6 | "errors" | 6 | "errors" |
7 | "io" | 7 | "io" |
8 | |||
9 | "github.com/golang/snappy" | ||
8 | ) | 10 | ) |
9 | 11 | ||
10 | type Codec struct { | 12 | type Codec struct { |
@@ -33,6 +35,7 @@ func (c *Codec) Unmarshal(fp io.Reader) (*InformWrapper, error) { | |||
33 | 35 | ||
34 | var dataLen int32 | 36 | var dataLen int32 |
35 | binary.Read(fp, binary.BigEndian, &dataLen) | 37 | binary.Read(fp, binary.BigEndian, &dataLen) |
38 | w.DataLength = dataLen | ||
36 | 39 | ||
37 | p := make([]byte, dataLen) | 40 | p := make([]byte, dataLen) |
38 | io.ReadFull(fp, p) | 41 | io.ReadFull(fp, p) |
@@ -42,12 +45,21 @@ func (c *Codec) Unmarshal(fp io.Reader) (*InformWrapper, error) { | |||
42 | return nil, errors.New("No key found") | 45 | return nil, errors.New("No key found") |
43 | } | 46 | } |
44 | 47 | ||
45 | u, err := Decrypt(p, iv, key) | 48 | u, err := Decrypt(p, iv, key, w) |
46 | if err != nil { | 49 | if err != nil { |
47 | return nil, err | 50 | return nil, err |
48 | } | 51 | } |
49 | 52 | ||
50 | w.Payload = u | 53 | if w.IsSnappyCompressed() { |
54 | w.Payload, err = snappy.Decode(nil, u) | ||
55 | if err != nil { | ||
56 | return nil, err | ||
57 | } | ||
58 | } else if w.IsZlibCompressed() { | ||
59 | return nil, errors.New("payload is zlib compressed, not supported") | ||
60 | } else { | ||
61 | w.Payload = u | ||
62 | } | ||
51 | 63 | ||
52 | return w, nil | 64 | return w, nil |
53 | } | 65 | } |
diff --git a/inform/crypto.go b/inform/crypto.go index 90118c1..4e82741 100644 --- a/inform/crypto.go +++ b/inform/crypto.go | |||
@@ -5,6 +5,7 @@ import ( | |||
5 | "crypto/aes" | 5 | "crypto/aes" |
6 | "crypto/cipher" | 6 | "crypto/cipher" |
7 | "crypto/rand" | 7 | "crypto/rand" |
8 | "encoding/binary" | ||
8 | "encoding/hex" | 9 | "encoding/hex" |
9 | "errors" | 10 | "errors" |
10 | ) | 11 | ) |
@@ -68,7 +69,54 @@ func Encrypt(payload []byte, key string) ([]byte, []byte, error) { | |||
68 | return ct, iv, nil | 69 | return ct, iv, nil |
69 | } | 70 | } |
70 | 71 | ||
71 | func Decrypt(payload, iv []byte, key string) ([]byte, error) { | 72 | func Decrypt(payload, iv []byte, key string, w *InformWrapper) ([]byte, error) { |
73 | if !w.IsEncrypted() { | ||
74 | return nil, errors.New("payload is not encrypted") | ||
75 | } | ||
76 | |||
77 | if w.IsGCMEncrypted() { | ||
78 | return decryptGCM(payload, iv, key, w) | ||
79 | } else { | ||
80 | return decryptCBC(payload, iv, key) | ||
81 | } | ||
82 | |||
83 | return nil, nil | ||
84 | } | ||
85 | |||
86 | func buildAuthData(w *InformWrapper, iv []byte) []byte { | ||
87 | ad := &bytes.Buffer{} | ||
88 | binary.Write(ad, binary.BigEndian, int32(PROTOCOL_MAGIC)) | ||
89 | binary.Write(ad, binary.BigEndian, int32(w.Version)) | ||
90 | ad.Write(w.MacAddr) | ||
91 | binary.Write(ad, binary.BigEndian, int16(w.Flags)) | ||
92 | ad.Write(iv) | ||
93 | binary.Write(ad, binary.BigEndian, int32(w.DataVersion)) | ||
94 | binary.Write(ad, binary.BigEndian, int32(w.DataLength)) | ||
95 | return ad.Bytes() | ||
96 | } | ||
97 | |||
98 | func decryptGCM(payload, iv []byte, key string, w *InformWrapper) ([]byte, error) { | ||
99 | block, err := decodeHexKey(key) | ||
100 | if err != nil { | ||
101 | return nil, err | ||
102 | } | ||
103 | |||
104 | mode, err := cipher.NewGCMWithNonceSize(block, 16) | ||
105 | if err != nil { | ||
106 | return nil, err | ||
107 | } | ||
108 | |||
109 | _, err = mode.Open(payload[:0], iv, payload, buildAuthData(w, iv)) | ||
110 | if err != nil { | ||
111 | return nil, err | ||
112 | } | ||
113 | |||
114 | // The last block always seems to be garbage, maybe it's padding or | ||
115 | // something else. I have not looked carefully at it. | ||
116 | return payload[:len(payload)-aes.BlockSize], nil | ||
117 | } | ||
118 | |||
119 | func decryptCBC(payload, iv []byte, key string) ([]byte, error) { | ||
72 | b := make([]byte, len(payload)) | 120 | b := make([]byte, len(payload)) |
73 | 121 | ||
74 | block, err := decodeHexKey(key) | 122 | block, err := decodeHexKey(key) |
diff --git a/inform/inform.go b/inform/inform.go index ac3b57d..59f969c 100644 --- a/inform/inform.go +++ b/inform/inform.go | |||
@@ -11,8 +11,10 @@ const ( | |||
11 | INFORM_VERSION int32 = 0 | 11 | INFORM_VERSION int32 = 0 |
12 | DATA_VERSION int32 = 1 | 12 | DATA_VERSION int32 = 1 |
13 | 13 | ||
14 | ENCRYPTED_FLAG = 1 | 14 | ENCRYPTED_FLAG = 1 |
15 | COMPRESSED_FLAG = 2 | 15 | ZLIB_COMPRESSED_FLAG = 2 |
16 | SNAPPY_COMPRESSED_FLAG = 4 | ||
17 | AES_GCM_FLAG = 8 | ||
16 | ) | 18 | ) |
17 | 19 | ||
18 | // Wrapper around an inform message, serializes directly into the wire | 20 | // Wrapper around an inform message, serializes directly into the wire |
@@ -22,6 +24,7 @@ type InformWrapper struct { | |||
22 | MacAddr []byte | 24 | MacAddr []byte |
23 | Flags int16 | 25 | Flags int16 |
24 | DataVersion int32 | 26 | DataVersion int32 |
27 | DataLength int32 | ||
25 | Payload []byte | 28 | Payload []byte |
26 | } | 29 | } |
27 | 30 | ||
@@ -86,7 +89,18 @@ func (i *InformWrapper) String() string { | |||
86 | fmt.Fprintf(b, "Mac Addr: \t%s\n", i.FormattedMac()) | 89 | fmt.Fprintf(b, "Mac Addr: \t%s\n", i.FormattedMac()) |
87 | fmt.Fprintf(b, "Flags: \t%d\n", i.Flags) | 90 | fmt.Fprintf(b, "Flags: \t%d\n", i.Flags) |
88 | fmt.Fprintf(b, " Encrypted: \t%t\n", i.IsEncrypted()) | 91 | fmt.Fprintf(b, " Encrypted: \t%t\n", i.IsEncrypted()) |
89 | fmt.Fprintf(b, " Compressed: \t%t\n", i.IsCompressed()) | 92 | fmt.Fprintf(b, " Compressed: \t%t", i.IsCompressed()) |
93 | if i.IsCompressed() { | ||
94 | if i.IsZlibCompressed() { | ||
95 | fmt.Fprintf(b, " (zlib)\n") | ||
96 | } else if i.IsSnappyCompressed() { | ||
97 | fmt.Fprintf(b, " (snappy)\n") | ||
98 | } else { | ||
99 | fmt.Fprintf(b, " (unknown)\n") | ||
100 | } | ||
101 | } else { | ||
102 | fmt.Fprintf(b, "\n") | ||
103 | } | ||
90 | fmt.Fprintf(b, "Data Version: \t%d\n", i.DataVersion) | 104 | fmt.Fprintf(b, "Data Version: \t%d\n", i.DataVersion) |
91 | fmt.Fprintf(b, "Payload: \t%q\n", i.Payload) | 105 | fmt.Fprintf(b, "Payload: \t%q\n", i.Payload) |
92 | 106 | ||
@@ -105,14 +119,42 @@ func (i *InformWrapper) SetEncrypted(e bool) { | |||
105 | } | 119 | } |
106 | } | 120 | } |
107 | 121 | ||
122 | func (i *InformWrapper) IsGCMEncrypted() bool { | ||
123 | return i.Flags&AES_GCM_FLAG != 0 | ||
124 | } | ||
125 | |||
126 | func (i *InformWrapper) SetGCMEncrypted(e bool) { | ||
127 | if e { | ||
128 | i.Flags |= AES_GCM_FLAG | ||
129 | } else { | ||
130 | i.Flags &= AES_GCM_FLAG | ||
131 | } | ||
132 | } | ||
133 | |||
108 | func (i *InformWrapper) IsCompressed() bool { | 134 | func (i *InformWrapper) IsCompressed() bool { |
109 | return i.Flags&COMPRESSED_FLAG != 0 | 135 | return i.IsZlibCompressed() || i.IsSnappyCompressed() |
136 | } | ||
137 | |||
138 | func (i *InformWrapper) IsZlibCompressed() bool { | ||
139 | return i.Flags&ZLIB_COMPRESSED_FLAG != 0 | ||
140 | } | ||
141 | |||
142 | func (i *InformWrapper) IsSnappyCompressed() bool { | ||
143 | return i.Flags&SNAPPY_COMPRESSED_FLAG != 0 | ||
144 | } | ||
145 | |||
146 | func (i *InformWrapper) SetSnappyCompressed(c bool) { | ||
147 | if c { | ||
148 | i.Flags |= SNAPPY_COMPRESSED_FLAG | ||
149 | } else { | ||
150 | i.Flags &= SNAPPY_COMPRESSED_FLAG | ||
151 | } | ||
110 | } | 152 | } |
111 | 153 | ||
112 | func (i *InformWrapper) SetCompressed(c bool) { | 154 | func (i *InformWrapper) SetZlibCompressed(c bool) { |
113 | if c { | 155 | if c { |
114 | i.Flags |= COMPRESSED_FLAG | 156 | i.Flags |= ZLIB_COMPRESSED_FLAG |
115 | } else { | 157 | } else { |
116 | i.Flags &= COMPRESSED_FLAG | 158 | i.Flags &= ZLIB_COMPRESSED_FLAG |
117 | } | 159 | } |
118 | } | 160 | } |