Skip to content

Commit 215d887

Browse files
committed
Implement auth tag verification
1 parent f5e415f commit 215d887

File tree

5 files changed

+73
-2
lines changed

5 files changed

+73
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# IDEs
2+
.idea
3+
.vscode

jwe/jwe_config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type JWEConfig struct {
1515
encryptionPaths map[string]string
1616
decryptionPaths map[string]string
1717
encryptionKeyFingerprint string
18+
enableHmacVerification bool
1819
}
1920

2021
func (config *JWEConfig) GetDecryptionKey() *rsa.PrivateKey {

jwe/jwe_config_builder.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type JWEConfigBuilder struct {
1717
encryptionPaths map[string]string
1818
decryptionPaths map[string]string
1919
encryptionKeyFingerprint string
20+
enableHmacVerification bool
2021
}
2122

2223
func NewJWEConfigBuilder() *JWEConfigBuilder {
@@ -74,6 +75,11 @@ func (cb *JWEConfigBuilder) WithEncryptedValueFieldName(encryptedValueFieldName
7475
return cb
7576
}
7677

78+
func (cb *JWEConfigBuilder) WithHmacVerificationEnabled(enableHmacVerification bool) *JWEConfigBuilder {
79+
cb.enableHmacVerification = enableHmacVerification
80+
return cb
81+
}
82+
7783
func (cb *JWEConfigBuilder) computeKeyFingerprint() {
7884
derEncoded, err := x509.MarshalPKIXPublicKey(cb.encryptionKey)
7985
if err != nil {
@@ -100,5 +106,6 @@ func (cb *JWEConfigBuilder) Build() *JWEConfig {
100106
encryptionPaths: cb.encryptionPaths,
101107
decryptionPaths: cb.decryptionPaths,
102108
encryptionKeyFingerprint: cb.encryptionKeyFingerprint,
109+
enableHmacVerification: cb.enableHmacVerification,
103110
}
104111
}

jwe/jwe_object.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package jwe
22

33
import (
44
"crypto"
5+
"crypto/hmac"
56
"crypto/rand"
67
"crypto/rsa"
78
"crypto/sha256"
9+
"crypto/subtle"
10+
"encoding/binary"
811
"errors"
12+
"fmt"
913
"strings"
1014

1115
"github.com/mastercard/client-encryption-go/aes_encryption"
@@ -15,7 +19,7 @@ import (
1519
const (
1620
A128CBC_HS256 = "A128CBC-HS256"
1721
A256GCM = "A256GCM"
18-
A128GCM = "A128GCM"
22+
A128GCM = "A128GCM"
1923
A192GCM = "A192GCM"
2024
)
2125

@@ -84,6 +88,11 @@ func (jweObject JWEObject) Decrypt(config JWEConfig) (string, error) {
8488
}
8589
return string(plainText), nil
8690
case A128CBC_HS256:
91+
if config.enableHmacVerification {
92+
if err := verifyAuthTagCbc(cek, nonce, cipherText, authTag, aad); err != nil {
93+
return "", err
94+
}
95+
}
8796
plainText, err := aes_encryption.AesCbcDecrypt(cipherText, cek[16:], nonce, authTag)
8897
if err != nil {
8998
return "", err
@@ -95,6 +104,31 @@ func (jweObject JWEObject) Decrypt(config JWEConfig) (string, error) {
95104
}
96105
}
97106

107+
func verifyAuthTagCbc(cek, nonce, cipherText, authTag, aad []byte) error {
108+
macKeyLen := len(cek) / 2
109+
if macKeyLen == 0 {
110+
return errors.New("invalid cek length for auth tag verification")
111+
}
112+
macKey := cek[:macKeyLen]
113+
114+
al := make([]byte, 8)
115+
binary.BigEndian.PutUint64(al, uint64(len(aad)*8))
116+
117+
mac := hmac.New(sha256.New, macKey)
118+
mac.Write(aad)
119+
mac.Write(nonce)
120+
mac.Write(cipherText)
121+
mac.Write(al)
122+
expected := mac.Sum(nil)
123+
124+
expectedAuthTag := expected[:len(authTag)]
125+
if subtle.ConstantTimeCompare(authTag, expectedAuthTag) != 1 {
126+
return errors.New("invalid authentication tag")
127+
}
128+
129+
return nil
130+
}
131+
98132
func (jweObject JWEObject) Serialize() string {
99133
return strings.Join([]string{jweObject.Aad, jweObject.EncryptedKey, jweObject.Iv, jweObject.CipherText, jweObject.AuthTag}, ".")
100134
}

jwe/jwe_object_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jwe_test
22

33
import (
4+
"strings"
45
"testing"
56

67
"github.com/mastercard/client-encryption-go/jwe"
@@ -12,7 +13,7 @@ const (
1213
encryptedPayload256Gcm = "eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.8c6vxeZOUBS8A9SXYUSrRnfl1ht9xxciB7TAEv84etZhQQ2civQKso-htpa2DWFBSUm-UYlxb6XtXNXZxuWu-A0WXjwi1K5ZAACc8KUoYnqPldEtC9Q2bhbQgc_qZF_GxeKrOZfuXc9oi45xfVysF_db4RZ6VkLvY2YpPeDGEMX_nLEjzqKaDz_2m0Ae_nknr0p_Nu0m5UJgMzZGR4Sk1DJWa9x-WJLEyo4w_nRDThOjHJshOHaOU6qR5rdEAZr_dwqnTHrjX9Qm9N9gflPGMaJNVa4mvpsjz6LJzjaW3nJ2yCoirbaeJyCrful6cCiwMWMaDMuiBDPKa2ovVTy0Sw.w0Nkjxl0T9HHNu4R.suRZaYu6Ui05Z3-vsw.akknMr3Dl4L0VVTGPUszcA"
1314
encryptedPayload128Gcm = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.WtvYljbsjdEv-Ttxx1p6PgyIrOsLpj1FMF9NQNhJUAHlKchAo5QImgEgIdgJE7HC2KfpNcHiQVqKKZq_y201FVzpicDkNzlPJr5kIH4Lq-oC5iP0agWeou9yK5vIxFRP__F_B8HSuojBJ3gDYT_KdYffUIHkm_UysNj4PW2RIRlafJ6RKYanVzk74EoKZRG7MIr3pTU6LIkeQUW41qYG8hz6DbGBOh79Nkmq7Oceg0ZwCn1_MruerP-b15SGFkuvOshStT5JJp7OOq82gNAOkMl4fylEj2-vADjP7VSK8GlqrA7u9Tn-a4Q28oy0GOKr1Z-HJgn_CElknwkUTYsWbg.PKl6_kvZ4_4MjmjW.AH6pGFkn7J49hBQcwg.zdyD73TcuveImOy4CRnVpw"
1415
encryptedPayload192Gcm = "eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.FWC8PVaZoR2TRKwKO4syhSJReezVIvtkxU_yKh4qODNvlVr8t8ttvySJ-AjM8xdI6vNyIg9jBMWASG4cE49jT9FYuQ72fP4R-Td4vX8wpB8GonQj40yLqZyfRLDrMgPR20RcQDW2ThzLXsgI55B5l5fpwQ9Nhmx8irGifrFWOcJ_k1dUSBdlsHsYxkjRKMENu5x4H6h12gGZ21aZSPtwAj9msMYnKLdiUbdGmGG_P8a6gPzc9ih20McxZk8fHzXKujjukr_1p5OO4o1N4d3qa-YI8Sns2fPtf7xPHnwi1wipmCC6ThFLU80r3173RXcpyZkF8Y3UacOS9y1f8eUfVQ.JRE7kZLN4Im1Rtdb.eW_lJ-U330n0QHqZnQ._r5xYVvMCrvICwLz4chjdw"
15-
encryptedPayloadCbc = "eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.5bsamlChk0HR3Nqg2UPJ2Fw4Y0MvC2pwWzNv84jYGkOXyqp1iwQSgETGaplIa7JyLg1ZWOqwNHEx3N7gsN4nzwAnVgz0eta6SsoQUE9YQ-5jek0COslUkoqIQjlQYJnYur7pqttDibj87fcw13G2agle5fL99j1QgFPjNPYqH88DMv481XGFa8O3VfJhW93m73KD2gvE5GasOPOkFK9wjKXc9lMGSgSArp3Awbc_oS2Cho_SbsvuEQwkhnQc2JKT3IaSWu8yK7edNGwD6OZJLhMJzWJlY30dUt2Eqe1r6kMT0IDRl7jHJnVIr2Qpe56CyeZ9V0aC5RH1mI5dYk4kHg.yI0CS3NdBrz9CCW2jwBSDw.6zr2pOSmAGdlJG0gbH53Eg.UFgf3-P9UjgMocEu7QA_vQ"
16+
encryptedPayloadCbc = "eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.5bsamlChk0HR3Nqg2UPJ2Fw4Y0MvC2pwWzNv84jYGkOXyqp1iwQSgETGaplIa7JyLg1ZWOqwNHEx3N7gsN4nzwAnVgz0eta6SsoQUE9YQ-5jek0COslUkoqIQjlQYJnYur7pqttDibj87fcw13G2agle5fL99j1QgFPjNPYqH88DMv481XGFa8O3VfJhW93m73KD2gvE5GasOPOkFK9wjKXc9lMGSgSArp3Awbc_oS2Cho_SbsvuEQwkhnQc2JKT3IaSWu8yK7edNGwD6OZJLhMJzWJlY30dUt2Eqe1r6kMT0IDRl7jHJnVIr2Qpe56CyeZ9V0aC5RH1mI5dYk4kHg.yI0CS3NdBrz9CCW2jwBSDw.6zr2pOSmAGdlJG0gbH53Eg.UFgf3-P9UjgMocEu7QA_vQ"
1617
)
1718

1819
func TestJWEObject(t *testing.T) {
@@ -132,3 +133,28 @@ func TestDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIsCbcEncrypted(t *testi
132133
assert.Nil(t, err)
133134
assert.Equal(t, "bar", decryptedPayload)
134135
}
136+
137+
func TestDecrypt_ShouldReturnError_WhenAuthTagIsInvalid(t *testing.T) {
138+
jweObject, err := jwe.ParseJWEObject(encryptedPayloadCbc)
139+
assert.Nil(t, err)
140+
141+
// Tamper with the auth tag while keeping it base64url valid
142+
jweObject.AuthTag = strings.Repeat("A", len(jweObject.AuthTag))
143+
144+
decryptionKeyPath := "../testdata/keys/pkcs8/test_key_pkcs8-2048.der"
145+
certificatePath := "../testdata/certificates/test_certificate-2048.der"
146+
147+
decryptionKey, err := utils.LoadUnencryptedDecryptionKey(decryptionKeyPath)
148+
assert.Nil(t, err)
149+
certificate, err := utils.LoadEncryptionCertificate(certificatePath)
150+
assert.Nil(t, err)
151+
152+
cb := jwe.NewJWEConfigBuilder()
153+
jweConfig := cb.WithDecryptionKey(decryptionKey).
154+
WithCertificate(certificate).
155+
Build()
156+
157+
decryptedPayload, err := jweObject.Decrypt(*jweConfig)
158+
assert.Empty(t, decryptedPayload)
159+
assert.NotNil(t, err)
160+
}

0 commit comments

Comments
 (0)