-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdecode.go
220 lines (171 loc) · 5.36 KB
/
decode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package qrsecrets
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"io"
"github.com/go-compile/rome"
"github.com/pkg/errors"
"golang.org/x/crypto/argon2"
)
var (
// ErrNotContainer is returned if the content trying to be decoded is not
// a container
ErrNotContainer = errors.New("stream is not a container")
// ErrProtocolVersionSupport is returned if the version of the protocol
// in the container is not a supported version and can not be decoded
ErrProtocolVersionSupport = errors.New("protocol version is not supported")
// ErrCipherTextShort is returned when ciphertext is too short to be valid
ErrCipherTextShort = errors.New("ciphertext is too short to be valid")
)
// DecodeContainer will decode a container and decrypt it
func DecodeContainer(r io.Reader, priv rome.PrivateKey, masterKey []byte) (*Container, error) {
// Read magic number
magicNum := make([]byte, 3)
if _, err := io.ReadFull(r, magicNum); err != nil {
return nil, err
}
if !bytes.Equal(magicNum, magicNum) {
return nil, ErrNotContainer
}
version := make([]byte, 1)
if _, err := io.ReadFull(r, version); err != nil {
return nil, err
}
// Check if this is the right protocol version
// If we add new protocol versions this needs to be turned into a
// switch statement pointing to different decode functions for different
// versions
if version[0] != ProtocolVersion {
return nil, ErrProtocolVersionSupport
}
curve := make([]byte, 1)
if _, err := io.ReadFull(r, curve); err != nil {
return nil, err
}
hash := make([]byte, 1)
if _, err := io.ReadFull(r, hash); err != nil {
return nil, err
}
c := &Container{
version: version[0],
Curve: CurveID(curve[0]),
HashID: HashID(hash[0]),
}
metadata, err := decodeMetaDataSection(r, c, priv)
if err != nil {
return nil, err
}
c.MetaData = metadata
ciphertext, err := decodeCiphertextSection(r, metadata, masterKey)
if err != nil {
return nil, err
}
c.CipherText = ciphertext
return c, nil
}
func decodeMetaDataSection(r io.Reader, c *Container, priv rome.PrivateKey) (*SectionMetaData, error) {
cipherTextLen := make([]byte, 2)
if _, err := io.ReadFull(r, cipherTextLen); err != nil {
return nil, err
}
cipherText := make([]byte, binary.BigEndian.Uint16(cipherTextLen))
if _, err := io.ReadFull(r, cipherText); err != nil {
return nil, err
}
hash := HashIDToFunc(c.HashID)
if hash == nil {
return nil, ErrHashUnsupported
}
// TODO: add cipher option for decrypt
// decrypt the metadata section by using the private key and the peramaters in the container
plaintext, err := priv.Decrypt(cipherText, rome.CipherAES_GCM, hash(), rome.NewHKDF(hash, 32, nil))
if err != nil {
return nil, err
}
// create a new buffer of the plain text to read from
buf := bytes.NewBuffer(plaintext)
m := &SectionMetaData{}
// Get the salt from the newly created buffer
salt := make([]byte, 32)
if _, err := io.ReadFull(buf, salt); err != nil {
return nil, err
}
m.Salt = salt
memory := make([]byte, 4)
if _, err := io.ReadFull(buf, memory); err != nil {
return nil, err
}
m.ArgonMemory = binary.BigEndian.Uint32(memory)
iterations := make([]byte, 4)
if _, err := io.ReadFull(buf, iterations); err != nil {
return nil, err
}
m.ArgonIterations = binary.BigEndian.Uint32(iterations)
parallelism := make([]byte, 1)
if _, err := io.ReadFull(buf, parallelism); err != nil {
return nil, err
}
m.ArgonParallelism = parallelism[0]
keyLen := make([]byte, 4)
if _, err := io.ReadFull(buf, keyLen); err != nil {
return nil, err
}
m.ArgonKeyLen = binary.BigEndian.Uint32(keyLen)
padding := make([]byte, 4)
if _, err := io.ReadFull(buf, padding); err != nil {
return nil, err
}
m.PaddingSize = binary.BigEndian.Uint32(padding)
return m, nil
}
func decodeCiphertextSection(r io.Reader, m *SectionMetaData, masterKey []byte) (*SectionCipherText, error) {
// Get len of ciphertext
cipherTextLen := make([]byte, 8)
if _, err := io.ReadFull(r, cipherTextLen); err != nil {
return nil, err
}
// Check if ciphertext is long enough to fit the nonce
if binary.BigEndian.Uint64(cipherTextLen) <= 12 {
return nil, ErrCipherTextShort
}
// Read the nonce which is prepended to the ciphertext
nonce := make([]byte, 12)
if _, err := io.ReadFull(r, nonce); err != nil {
return nil, err
}
// Read the rest of the ciphertext
cipherText := make([]byte, binary.BigEndian.Uint64(cipherTextLen)-12)
if _, err := io.ReadFull(r, cipherText); err != nil {
return nil, err
}
// Derive the key with Argon2 again
key := argon2.Key(masterKey, m.Salt, m.ArgonIterations, m.ArgonMemory, m.ArgonParallelism, m.ArgonKeyLen)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
plaintext, err := aesgcm.Open(nil, nonce, cipherText, nil)
if err != nil {
return nil, err
}
// Make sure the padding params are valid
if len(plaintext) < int(m.PaddingSize) {
return nil, ErrCipherTextShort
}
c := &SectionCipherText{
Plaintext: plaintext[:len(plaintext)-int(m.PaddingSize)],
Padding: plaintext[len(plaintext)-int(m.PaddingSize):],
}
return c, nil
}
// UnmarshalContainer will decode the provided bytes into a container
func UnmarshalContainer(container []byte, priv rome.PrivateKey, masterKey []byte) (*Container, error) {
buf := bytes.NewBuffer(container)
return DecodeContainer(buf, priv, masterKey)
}