Skip to content

Commit f880e39

Browse files
authored
encrypt token session using aes-gcm if cpu support it or ChaCha20 (#248)
Harsha's improvement to use binary encoding instead of json encoding
1 parent 25fa2f3 commit f880e39

File tree

3 files changed

+133
-24
lines changed

3 files changed

+133
-24
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/minio/minio-go/v7 v7.0.5-0.20200807085956-d7db33ea7618
2222
github.com/minio/operator v0.0.0-20200806194125-c2ff646f4af1
2323
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
24+
github.com/secure-io/sio-go v0.3.1
2425
github.com/stretchr/testify v1.6.1
2526
github.com/unrolled/secure v1.0.7
2627
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de

pkg/auth/token.go

Lines changed: 128 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@
1717
package auth
1818

1919
import (
20+
"bytes"
2021
"crypto/aes"
2122
"crypto/cipher"
22-
"crypto/rand"
23+
"crypto/hmac"
2324
"crypto/sha1"
25+
"crypto/sha256"
2426
"encoding/base64"
2527
"errors"
2628
"fmt"
2729
"io"
30+
"io/ioutil"
2831
"log"
2932
"net/http"
3033
"strings"
@@ -33,13 +36,17 @@ import (
3336
"github.com/minio/console/models"
3437
"github.com/minio/console/pkg/auth/token"
3538
"github.com/minio/minio-go/v7/pkg/credentials"
39+
"github.com/secure-io/sio-go/sioutil"
40+
"golang.org/x/crypto/chacha20"
41+
"golang.org/x/crypto/chacha20poly1305"
3642
"golang.org/x/crypto/pbkdf2"
3743
)
3844

3945
var (
4046
errNoAuthToken = errors.New("session token missing")
4147
errReadingToken = errors.New("session token internal data is malformed")
4248
errClaimsFormat = errors.New("encrypted session token claims not in the right format")
49+
errorGeneric = errors.New("an error has occurred")
4350
)
4451

4552
// derivedKey is the key used to encrypt the session token claims, its derived using pbkdf on CONSOLE_PBKDF_PASSPHRASE with CONSOLE_PBKDF_SALT
@@ -102,9 +109,10 @@ func NewEncryptedTokenForClient(credentials *credentials.Value, actions []string
102109
// returns a base64 encoded ciphertext
103110
func encryptClaims(accessKeyID, secretAccessKey, sessionToken string, actions []string) (string, error) {
104111
payload := []byte(fmt.Sprintf("%s#%s#%s#%s", accessKeyID, secretAccessKey, sessionToken, strings.Join(actions, ",")))
105-
ciphertext, err := encrypt(payload)
112+
ciphertext, err := encrypt(payload, []byte{})
106113
if err != nil {
107-
return "", err
114+
log.Println(err)
115+
return "", errorGeneric
108116
}
109117
return base64.StdEncoding.EncodeToString(ciphertext), nil
110118
}
@@ -116,7 +124,7 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
116124
log.Println(err)
117125
return nil, errClaimsFormat
118126
}
119-
plaintext, err := decrypt(decoded)
127+
plaintext, err := decrypt(decoded, []byte{})
120128
if err != nil {
121129
log.Println(err)
122130
return nil, errClaimsFormat
@@ -136,37 +144,137 @@ func decryptClaims(ciphertext string) (*DecryptedClaims, error) {
136144
}, nil
137145
}
138146

139-
// Encrypt a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
140-
func encrypt(plaintext []byte) ([]byte, error) {
141-
block, _ := aes.NewCipher(derivedKey)
142-
gcm, err := cipher.NewGCM(block)
147+
const (
148+
aesGcm = 0x00
149+
c20p1305 = 0x01
150+
)
151+
152+
// Encrypt a blob of data using AEAD scheme, AES-GCM if the executing CPU
153+
// provides AES hardware support, otherwise will use ChaCha20-Poly1305
154+
// with a pbkdf2 derived key, this function should be used to encrypt a session
155+
// or data key provided as plaintext.
156+
//
157+
// The returned ciphertext data consists of:
158+
// iv | AEAD ID | nonce | encrypted data
159+
// 32 1 12 ~ len(data)
160+
func encrypt(plaintext, associatedData []byte) ([]byte, error) {
161+
iv, err := sioutil.Random(32) // 32 bit IV
143162
if err != nil {
144163
return nil, err
145164
}
146-
nonce := make([]byte, gcm.NonceSize())
147-
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
165+
var algorithm byte
166+
if sioutil.NativeAES() {
167+
algorithm = aesGcm
168+
} else {
169+
algorithm = c20p1305
170+
}
171+
var aead cipher.AEAD
172+
switch algorithm {
173+
case aesGcm:
174+
mac := hmac.New(sha256.New, derivedKey)
175+
mac.Write(iv)
176+
sealingKey := mac.Sum(nil)
177+
178+
var block cipher.Block
179+
block, err = aes.NewCipher(sealingKey)
180+
if err != nil {
181+
return nil, err
182+
}
183+
aead, err = cipher.NewGCM(block)
184+
if err != nil {
185+
return nil, err
186+
}
187+
case c20p1305:
188+
var sealingKey []byte
189+
sealingKey, err = chacha20.HChaCha20(derivedKey, iv)
190+
if err != nil {
191+
return nil, err
192+
}
193+
aead, err = chacha20poly1305.New(sealingKey)
194+
if err != nil {
195+
return nil, err
196+
}
197+
}
198+
nonce, err := sioutil.Random(aead.NonceSize())
199+
if err != nil {
148200
return nil, err
149201
}
150-
cipherText := gcm.Seal(nonce, nonce, plaintext, nil)
151-
return cipherText, nil
202+
203+
sealedBytes := aead.Seal(nil, nonce, plaintext, associatedData)
204+
205+
// ciphertext = iv | AEAD ID | nonce | sealed bytes
206+
207+
var buf bytes.Buffer
208+
buf.Write(iv)
209+
buf.WriteByte(algorithm)
210+
buf.Write(nonce)
211+
buf.Write(sealedBytes)
212+
213+
return buf.Bytes(), nil
152214
}
153215

154-
// Decrypts a blob of data using AEAD (AES-GCM) with a pbkdf2 derived key
155-
func decrypt(data []byte) ([]byte, error) {
156-
block, err := aes.NewCipher(derivedKey)
157-
if err != nil {
216+
// Decrypts a blob of data using AEAD scheme AES-GCM if the executing CPU
217+
// provides AES hardware support, otherwise will use ChaCha20-Poly1305with
218+
// and a pbkdf2 derived key
219+
func decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
220+
var (
221+
iv [32]byte
222+
algorithm [1]byte
223+
nonce [12]byte // This depends on the AEAD but both used ciphers have the same nonce length.
224+
)
225+
226+
r := bytes.NewReader(ciphertext)
227+
if _, err := io.ReadFull(r, iv[:]); err != nil {
228+
return nil, err
229+
}
230+
if _, err := io.ReadFull(r, algorithm[:]); err != nil {
231+
return nil, err
232+
}
233+
if _, err := io.ReadFull(r, nonce[:]); err != nil {
158234
return nil, err
159235
}
160-
gcm, err := cipher.NewGCM(block)
236+
237+
var aead cipher.AEAD
238+
switch algorithm[0] {
239+
case aesGcm:
240+
mac := hmac.New(sha256.New, derivedKey)
241+
mac.Write(iv[:])
242+
sealingKey := mac.Sum(nil)
243+
block, err := aes.NewCipher(sealingKey[:])
244+
if err != nil {
245+
return nil, err
246+
}
247+
aead, err = cipher.NewGCM(block)
248+
if err != nil {
249+
return nil, err
250+
}
251+
case c20p1305:
252+
sealingKey, err := chacha20.HChaCha20(derivedKey, iv[:])
253+
if err != nil {
254+
return nil, err
255+
}
256+
aead, err = chacha20poly1305.New(sealingKey)
257+
if err != nil {
258+
return nil, err
259+
}
260+
default:
261+
return nil, fmt.Errorf("invalid algorithm: %v", algorithm)
262+
}
263+
264+
if len(nonce) != aead.NonceSize() {
265+
return nil, fmt.Errorf("invalid nonce size %d, expected %d", len(nonce), aead.NonceSize())
266+
}
267+
268+
sealedBytes, err := ioutil.ReadAll(r)
161269
if err != nil {
162270
return nil, err
163271
}
164-
nonceSize := gcm.NonceSize()
165-
nonce, cipherText := data[:nonceSize], data[nonceSize:]
166-
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
272+
273+
plaintext, err := aead.Open(nil, nonce[:], sealedBytes, associatedData)
167274
if err != nil {
168275
return nil, err
169276
}
277+
170278
return plaintext, nil
171279
}
172280

pkg/auth/token_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ func TestNewJWTWithClaimsForClient(t *testing.T) {
3636
funcAssert := assert.New(t)
3737
// Test-1 : NewEncryptedTokenForClient() is generated correctly without errors
3838
function := "NewEncryptedTokenForClient()"
39-
jwt, err := NewEncryptedTokenForClient(creds, []string{""})
40-
if err != nil || jwt == "" {
39+
token, err := NewEncryptedTokenForClient(creds, []string{""})
40+
if err != nil || token == "" {
4141
t.Errorf("Failed on %s:, error occurred: %s", function, err)
4242
}
43-
// saving jwt for future tests
44-
goodToken = jwt
43+
// saving token for future tests
44+
goodToken = token
4545
// Test-2 : NewEncryptedTokenForClient() throws error because of empty credentials
4646
if _, err = NewEncryptedTokenForClient(nil, []string{""}); err != nil {
4747
funcAssert.Equal("provided credentials are empty", err.Error())

0 commit comments

Comments
 (0)