Skip to content

Commit

Permalink
Rework v1.local (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg authored Apr 14, 2024
1 parent b324c0e commit 3535a4f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 58 deletions.
9 changes: 7 additions & 2 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package paseto

import (
"bytes"
"crypto/subtle"
"encoding/base64"
"encoding/binary"
"encoding/json"
Expand Down Expand Up @@ -50,7 +51,7 @@ func fromBytes(data []byte, x any) error {
*f = append(*f, data...)
default:
if err := json.Unmarshal(data, x); err != nil {
return fmt.Errorf("%v: %w", err, ErrDataUnmarshal)
return fmt.Errorf("%w: %v", ErrDataUnmarshal, err)
}
}
return nil
Expand Down Expand Up @@ -89,7 +90,7 @@ func splitToken(token, header string) ([]byte, []byte, error) {
return payload, footer, nil
}

func buildToken(header string, body, footer []byte) string {
func buildToken(header, body, footer []byte) string {
size := len(header) + b64EncodedLen(len(body))
if len(footer) > 0 {
size += 1 + b64EncodedLen(len(footer))
Expand Down Expand Up @@ -124,3 +125,7 @@ func b64Encode(dst, src []byte) {
func b64EncodedLen(n int) int {
return base64.RawURLEncoding.EncodedLen(n)
}

func constTimeEq(x, y int32) bool {
return subtle.ConstantTimeEq(x, y) == 1
}
2 changes: 1 addition & 1 deletion common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func BenchmarkPAE(b *testing.B) {
var footerBytes []byte

pieces := [][]byte{
[]byte(v1LocHeader),
[]byte(v1locHeader),
nonce[:],
encryptedPayload[:],
footerBytes,
Expand Down
146 changes: 95 additions & 51 deletions v1loc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,23 @@ import (
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"errors"
"fmt"
"io"
"strings"

"golang.org/x/crypto/hkdf"
)

const (
v1LocHeader = "v1.local."
v1LocNonceSize = 32
v1LocNonceHalf = v1LocNonceSize / 2
v1LocMacSize = 48 // const for crypty.SHA384.Size()
v1locHeader = "v1.local."
v1locKey = 32
v1locNonce = 32
v1locNonceH = v1locNonce / 2
v1locMac = 48 // const for crypto.SHA384.Size()
)

func V1Encrypt(key []byte, payload, footer any, randBytes []byte) (string, error) {
if randBytes == nil {
randBytes = make([]byte, v1LocNonceSize)
if _, err := io.ReadFull(rand.Reader, randBytes); err != nil {
return "", fmt.Errorf("read from crypto/rand.Reader: %w", err)
}
}

payloadBytes, err := toBytes(payload)
if err != nil {
return "", fmt.Errorf("encode payload: %w", err)
Expand All @@ -37,76 +33,124 @@ func V1Encrypt(key []byte, payload, footer any, randBytes []byte) (string, error
return "", fmt.Errorf("encode footer: %w", err)
}

macN := hmac.New(sha512.New384, randBytes)
if _, err := macN.Write(payloadBytes); err != nil {
return "", fmt.Errorf("hash payload: %w", err)
m := payloadBytes
k := key
f := footerBytes

// step 1.
if !constTimeEq(int32(len(k)), v1locKey) {
return "", errors.New("bad key")
}
nonce := macN.Sum(nil)[:v1LocNonceSize]

encKey, authKey, err := v1locSplitKey(key, nonce[:v1LocNonceHalf])
// step 2.
h := []byte(v1locHeader)

// step 3.
b := randBytes
if b == nil {
b = make([]byte, v1locNonce)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return "", fmt.Errorf("read from crypto/rand.Reader: %w", err)
}
}

// step 4.
hash := hmac.New(sha512.New384, b)
hash.Write(m)
n := hash.Sum(nil)[:v1locNonce]

// step 5.
ek, ak, err := v1locSplitKey(k, n[:v1locNonceH])
if err != nil {
return "", fmt.Errorf("create enc and auth keys: %w", err)
}

block, err := aes.NewCipher(encKey)
// step 6.
block, err := aes.NewCipher(ek)
if err != nil {
return "", fmt.Errorf("create aes cipher: %w", err)
}
c := make([]byte, len(m))
ciph := cipher.NewCTR(block, n[v1locNonceH:])
ciph.XORKeyStream(c, m)

encryptedPayload := make([]byte, len(payloadBytes))
cipher.NewCTR(block, nonce[v1LocNonceHalf:]).
XORKeyStream(encryptedPayload, payloadBytes)
// step 7.
preAuth := pae(h, n, c, f)

h := hmac.New(sha512.New384, authKey)
if _, err := h.Write(pae([]byte(v1LocHeader), nonce, encryptedPayload, footerBytes)); err != nil {
return "", fmt.Errorf("create signature: %w", err)
}
mac := h.Sum(nil)
// step 8.
hasher := hmac.New(sha512.New384, ak)
hasher.Write(preAuth)
t := hasher.Sum(nil)

body := make([]byte, 0, len(nonce)+len(encryptedPayload)+len(mac))
body = append(body, nonce...)
body = append(body, encryptedPayload...)
body = append(body, mac...)
// step 9.
body := make([]byte, 0, len(n)+len(c)+len(t))
body = append(body, n...)
body = append(body, c...)
body = append(body, t...)

return buildToken(v1LocHeader, body, footerBytes), nil
return buildToken(h, body, f), nil
}

func V1Decrypt(token string, key []byte, payload, footer any) error {
data, footerBytes, err := splitToken(token, v1LocHeader)
// step 0.
k := key

// step 1.
if !constTimeEq(int32(len(k)), v1locKey) {
return errors.New("bad key")
}

// step 2.
// TODO: ?

// step 3.
if !strings.HasPrefix(token, v1locHeader) {
return ErrIncorrectTokenFormat
}
h := []byte(v1locHeader)

// step 4.
data, footerBytes, err := splitToken(token, v1locHeader)
if err != nil {
return fmt.Errorf("decode token: %w", err)
}
if len(data) < v1LocNonceSize+v1LocMacSize {
if len(data) < v1locNonce+v1locMac {
return ErrIncorrectTokenFormat
}
f := footerBytes

pivot := len(data) - v1LocMacSize
nonce := data[:v1LocNonceSize]
encryptedPayload, mac := data[v1LocNonceSize:pivot], data[pivot:]
pivot := len(data) - v1locMac
n := data[:v1locNonce]
c, t := data[v1locNonce:pivot], data[pivot:]

encKey, authKey, err := v1locSplitKey(key, nonce[:v1LocNonceHalf])
// step 5.
ek, ak, err := v1locSplitKey(k, n[:v1locNonceH])
if err != nil {
return fmt.Errorf("create enc and auth keys: %w", err)
}

body := pae([]byte(v1LocHeader), nonce, encryptedPayload, footerBytes)
h := hmac.New(sha512.New384, authKey)
if _, err := h.Write(body); err != nil {
return fmt.Errorf("create signature: %w", err)
}
// step 6.
preAuth := pae(h, n, c, f)

// step 7.
hasher := hmac.New(sha512.New384, ak)
hasher.Write(preAuth)
t2 := hasher.Sum(nil)

if !hmac.Equal(h.Sum(nil), mac) {
// step 8.
if !hmac.Equal(t2, t) {
return ErrInvalidTokenAuth
}

block, err := aes.NewCipher(encKey)
// step 9.
block, err := aes.NewCipher(ek)
if err != nil {
return fmt.Errorf("create aes cipher: %w", err)
}

decryptedPayload := make([]byte, len(encryptedPayload))
cipher.NewCTR(block, nonce[v1LocNonceHalf:]).
XORKeyStream(decryptedPayload, encryptedPayload)
decryptedPayload := make([]byte, len(c))
ciph := cipher.NewCTR(block, n[v1locNonceH:])
ciph.XORKeyStream(decryptedPayload, c)

if payload != nil {
if err := fromBytes(decryptedPayload, payload); err != nil {
Expand All @@ -126,14 +170,14 @@ func v1locSplitKey(key, salt []byte) ([]byte, []byte, error) {
eReader := hkdf.New(sha512.New384, key, salt, []byte("paseto-encryption-key"))
aReader := hkdf.New(sha512.New384, key, salt, []byte("paseto-auth-key-for-aead"))

encKey := make([]byte, 32)
authKey := make([]byte, 32)
ek := make([]byte, 32)
ak := make([]byte, 32)

if _, err := io.ReadFull(eReader, encKey); err != nil {
if _, err := io.ReadFull(eReader, ek); err != nil {
return nil, nil, err
}
if _, err := io.ReadFull(aReader, authKey); err != nil {
if _, err := io.ReadFull(aReader, ak); err != nil {
return nil, nil, err
}
return encKey, authKey, nil
return ek, ak, nil
}
6 changes: 3 additions & 3 deletions v1loc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestV1Loc_Encrypt(t *testing.T) {
testCases := loadGoldenFile("testdata/v1.json")

for _, tc := range testCases.Tests {
if tc.Key == "" || !strings.HasPrefix(tc.Token, v1LocHeader) {
if tc.Key == "" || !strings.HasPrefix(tc.Token, v1locHeader) {
continue
}

Expand All @@ -30,8 +30,8 @@ func TestV1Loc_Encrypt(t *testing.T) {
func TestV1Loc_Decrypt(t *testing.T) {
testCases := loadGoldenFile("testdata/v1.json")

for _, tc := range testCases.Tests[:] {
if tc.Key == "" || !strings.HasPrefix(tc.Token, v1LocHeader) {
for _, tc := range testCases.Tests {
if tc.Key == "" || !strings.HasPrefix(tc.Token, v1locHeader) {
continue
}

Expand Down
2 changes: 1 addition & 1 deletion v2loc.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func V2Encrypt(key []byte, payload, footer any, randBytes []byte) (string, error
)
body := append(nonce, encryptedPayload...)

return buildToken(v2LocHeader, body, footerBytes), nil
return buildToken([]byte(v2LocHeader), body, footerBytes), nil
}

func V2Decrypt(token string, key []byte, payload, footer any) error {
Expand Down

0 comments on commit 3535a4f

Please sign in to comment.