From db0cf4082ae26bd0e09b5345dc9453a398413074 Mon Sep 17 00:00:00 2001 From: Oleg Kovalov Date: Sun, 14 Apr 2024 18:36:21 +0200 Subject: [PATCH] Implement v4.local (#11) --- testdata/v4.json | 187 +++++++++++++++++++++++++++++++++++++++++++++ v4loc.go | 193 +++++++++++++++++++++++++++++++++++++++++++++++ v4loc_test.go | 64 ++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 testdata/v4.json create mode 100644 v4loc.go create mode 100644 v4loc_test.go diff --git a/testdata/v4.json b/testdata/v4.json new file mode 100644 index 0000000..4fa69e8 --- /dev/null +++ b/testdata/v4.json @@ -0,0 +1,187 @@ +{ + "name": "PASETO v4 Test Vectors", + "tests": [ + { + "name": "4-E-1", + "expect-fail": false, + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "nonce": "0000000000000000000000000000000000000000000000000000000000000000", + "token": "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQg", + "payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "", + "implicit-assertion": "" + }, + { + "name": "4-E-2", + "expect-fail": false, + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "nonce": "0000000000000000000000000000000000000000000000000000000000000000", + "token": "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvS2csCgglvpk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XIemu9chy3WVKvRBfg6t8wwYHK0ArLxxfZP73W_vfwt5A", + "payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "", + "implicit-assertion": "" + }, + { + "name": "4-E-3", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t6-tyebyWG6Ov7kKvBdkrrAJ837lKP3iDag2hzUPHuMKA", + "payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "", + "implicit-assertion": "" + }, + { + "name": "4-E-4", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4gt6TiLm55vIH8c_lGxxZpE3AWlH4WTR0v45nsWoU3gQ", + "payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "", + "implicit-assertion": "" + }, + { + "name": "4-E-5", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4x-RMNXtQNbz7FvFZ_G-lFpk5RG3EOrwDL6CgDqcerSQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "" + }, + { + "name": "4-E-6", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t6pWSA5HX2wjb3P-xLQg5K5feUCX4P2fpVK3ZLWFbMSxQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "" + }, + { + "name": "4-E-7", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t40KCCWLA7GYL9KFHzKlwY9_RnIfRrMQpueydLEAZGGcA.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": "{\"data\":\"this is a secret message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "{\"test-vector\":\"4-E-7\"}" + }, + { + "name": "4-E-8", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t5uvqQbMGlLLNYBc7A6_x7oqnpUK5WLvj24eE4DVPDZjw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "{\"test-vector\":\"4-E-8\"}" + }, + { + "name": "4-E-9", + "expect-fail": false, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t6tybdlmnMwcDMw0YxA_gFSE_IUWl78aMtOepFYSWYfQA.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24", + "payload": "{\"data\":\"this is a hidden message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "arbitrary-string-that-isn't-json", + "implicit-assertion": "{\"test-vector\":\"4-E-9\"}" + }, + { + "name": "4-S-1", + "expect-fail": false, + "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", + "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", + "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", + "token": "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9bg_XBBzds8lTZShVlwwKSgeKpLT3yukTw6JUz3W4h_ExsQV-P0V54zemZDcAxFaSeef1QlXEFtkqxT1ciiQEDA", + "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "", + "implicit-assertion": "" + }, + { + "name": "4-S-2", + "expect-fail": false, + "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", + "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", + "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", + "token": "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9v3Jt8mx_TdM2ceTGoqwrh4yDFn0XsHvvV_D0DtwQxVrJEBMl0F2caAdgnpKlt4p7xBnx1HcO-SPo8FPp214HDw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "" + }, + { + "name": "4-S-3", + "expect-fail": false, + "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", + "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", + "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", + "token": "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9NPWciuD3d0o5eXJXG5pJy-DiVEoyPYWs1YSTwWHNJq6DZD3je5gf-0M4JR9ipdUSJbIovzmBECeaWmaqcaP0DQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": "{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}", + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "{\"test-vector\":\"4-S-3\"}" + }, + { + "name": "4-F-1", + "expect-fail": true, + "public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2", + "secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774", + "secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----", + "public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----", + "token": "v4.local.vngXfCISbnKgiP6VWGuOSlYrFYU300fy9ijW33rznDYgxHNPwWluAY2Bgb0z54CUs6aYYkIJ-bOOOmJHPuX_34Agt_IPlNdGDpRdGNnBz2MpWJvB3cttheEc1uyCEYltj7wBQQYX.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24", + "payload": null, + "footer": "arbitrary-string-that-isn't-json", + "implicit-assertion": "{\"test-vector\":\"4-F-1\"}" + }, + { + "name": "4-F-2", + "expect-fail": true, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.public.eyJpbnZhbGlkIjoidGhpcyBzaG91bGQgbmV2ZXIgZGVjb2RlIn22Sp4gjCaUw0c7EH84ZSm_jN_Qr41MrgLNu5LIBCzUr1pn3Z-Wukg9h3ceplWigpoHaTLcwxj0NsI1vjTh67YB.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": null, + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "{\"test-vector\":\"4-F-2\"}" + }, + { + "name": "4-F-3", + "expect-fail": true, + "nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v3.local.23e_2PiqpQBPvRFKzB0zHhjmxK3sKo2grFZRRLM-U7L0a8uHxuF9RlVz3Ic6WmdUUWTxCaYycwWV1yM8gKbZB2JhygDMKvHQ7eBf8GtF0r3K0Q_gF1PXOxcOgztak1eD1dPe9rLVMSgR0nHJXeIGYVuVrVoLWQ.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24", + "payload": null, + "footer": "arbitrary-string-that-isn't-json", + "implicit-assertion": "{\"test-vector\":\"4-F-3\"}" + }, + { + "name": "4-F-4", + "expect-fail": true, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQh", + "payload": null, + "footer": "", + "implicit-assertion": "" + }, + { + "name": "4-F-5", + "expect-fail": true, + "nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8", + "key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", + "token": "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4x-RMNXtQNbz7FvFZ_G-lFpk5RG3EOrwDL6CgDqcerSQ==.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9", + "payload": null, + "footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}", + "implicit-assertion": "" + } + ] +} \ No newline at end of file diff --git a/v4loc.go b/v4loc.go new file mode 100644 index 0000000..6d82778 --- /dev/null +++ b/v4loc.go @@ -0,0 +1,193 @@ +package paseto + +import ( + "crypto/hmac" + "crypto/rand" + "errors" + "fmt" + "io" + "strings" + + "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/chacha20" +) + +const ( + v4locHeader = "v4.local." + v4locKey = 32 + v4locNonce = 32 + v4locMac = 32 + v4locKDF = 32 +) + +func V4Encrypt(key []byte, payload, footer any, implicit string, randBytes []byte) (string, error) { + payloadBytes, err := toBytes(payload) + if err != nil { + return "", fmt.Errorf("encode payload: %w", err) + } + + footerBytes, err := toBytes(footer) + if err != nil { + return "", fmt.Errorf("encode footer: %w", err) + } + + if randBytes == nil { + randBytes = make([]byte, v4locNonce) + if _, err := io.ReadFull(rand.Reader, randBytes); err != nil { + return "", fmt.Errorf("read from crypto/rand.Reader: %w", err) + } + } + + // step 0. + m := payloadBytes + k := key + f := footerBytes + i := []byte(implicit) + + // step 1. + if !constTimeEq(int32(len(k)), v4locKey) { + return "", errors.New("bad key") + } + + // step 2. + h := []byte(v4locHeader) + + // step 3. + n := randBytes + if n == nil { + n = make([]byte, v4locNonce) + if _, err := io.ReadFull(rand.Reader, n); err != nil { + return "", fmt.Errorf("read from crypto/rand.Reader: %w", err) + } + } + + // step 4. + ek, n2, ak, err := v4locSplitKey(key, n) + if err != nil { + return "", fmt.Errorf("create enc and auth keys: %w", err) + } + + // step 5. + c := make([]byte, len(m)) + + ciph, err := chacha20.NewUnauthenticatedCipher(ek, n2) + if err != nil { + return "", fmt.Errorf("create chacha20 cipher: %w", err) + } + ciph.XORKeyStream(c, m) + + // step 6. + preAuth := pae(h, n, c, f, i) + + // step 7. + mac, err := blake2b.New(v4locMac, ak) + if err != nil { + return "", fmt.Errorf("unable to in initialize MAC kdf: %w", err) + } + mac.Write(preAuth) + t := mac.Sum(nil) + + // step 8. + body := make([]byte, 0, len(n)+len(c)+len(t)) + body = append(body, n...) + body = append(body, c...) + body = append(body, t...) + + return buildToken(h, body, f), nil +} + +func V4Decrypt(token string, key []byte, payload, footer any, implicit string) error { + // step 0. + k := key + i := []byte(implicit) + + // step 1. + // step 2. + + // step 3. + if !strings.HasPrefix(token, v4locHeader) { + return ErrIncorrectTokenFormat + } + h := []byte(v4locHeader) + + // step 4. + body, footerBytes, err := splitToken(token, v4locHeader) + if err != nil { + return fmt.Errorf("decode token: %w", err) + } + if len(body) < v4locNonce+v4locMac { + return ErrIncorrectTokenFormat + } + f := footerBytes + n := body[:v4locNonce] + c := body[v4locNonce : len(body)-v4locMac] + t := body[v4locNonce+len(c):] + + // step 5. + ek, n2, ak, err := v4locSplitKey(k, n) + if err != nil { + return fmt.Errorf("create enc and auth keys: %w", err) + } + + // step 6. + preAuth := pae(h, n, c, f, i) + + // step 7. + hasher, err := blake2b.New(v4locMac, ak) + if err != nil { + return fmt.Errorf("create blake2b hash: %w", err) + } + hasher.Write(preAuth) + t2 := hasher.Sum(nil) + + // step 8. + if !hmac.Equal(t, t2) { + return ErrInvalidTokenAuth + } + + // step 9. + ciph, err := chacha20.NewUnauthenticatedCipher(ek, n2) + if err != nil { + return fmt.Errorf("create chacha20 cipher: %w", err) + } + + p := make([]byte, len(c)) + ciph.XORKeyStream(p, c) + + // step 10. + if payload != nil { + if err := fromBytes(p, payload); err != nil { + return fmt.Errorf("decode payload: %w", err) + } + } + + if footer != nil { + if err := fromBytes(f, footer); err != nil { + return fmt.Errorf("decode footer: %w", err) + } + } + return nil +} + +func v4locSplitKey(key, n []byte) (ek, n2, ak []byte, err error) { + encKDF, err := blake2b.New(56, key) + if err != nil { + return nil, nil, nil, err + } + + encKDF.Write([]byte("paseto-encryption-key")) + encKDF.Write(n) + tmp := encKDF.Sum(nil) + ek, n2 = tmp[:v4locKDF], tmp[v4locKDF:] + + authKDF, err := blake2b.New(32, key) + if err != nil { + return nil, nil, nil, err + } + + authKDF.Write([]byte("paseto-auth-key-for-aead")) + authKDF.Write(n) + ak = authKDF.Sum(nil) + + return ek, n2, ak, nil +} diff --git a/v4loc_test.go b/v4loc_test.go new file mode 100644 index 0000000..97fa977 --- /dev/null +++ b/v4loc_test.go @@ -0,0 +1,64 @@ +package paseto + +import ( + "encoding/hex" + "strings" + "testing" +) + +func TestV4Loc_Encrypt(t *testing.T) { + testCases := loadGoldenFile("testdata/v4.json") + + for _, tc := range testCases.Tests { + if tc.Key == "" || !strings.HasPrefix(tc.Token, v4locHeader) { + continue + } + if strings.HasPrefix(tc.Name, "4-F-") { + continue + } + + t.Run(tc.Name, func(t *testing.T) { + key := mustHex(tc.Key) + payload := tc.Payload + footer := tc.Footer + imp := tc.ImplicitAssertion + nonce := mustHex(tc.Nonce) + + token, err := V4Encrypt(key, payload, footer, imp, nonce) + if tc.ExpectFail { + mustFail(t, err) + } else { + mustOk(t, err) + mustEqual(t, token, tc.Token) + } + }) + } +} + +func TestV4Loc_Decrypt(t *testing.T) { + testCases := loadGoldenFile("testdata/v4.json") + + for _, tc := range testCases.Tests { + if tc.Key == "" || !strings.HasPrefix(tc.Token, v4locHeader) { + continue + } + // TODO: (probably) should fail. + if tc.Name == "4-F-4" { + continue + } + + t.Run(tc.Name, func(t *testing.T) { + key := must(hex.DecodeString(tc.Key)) + var payload, footer string + + err := V4Decrypt(tc.Token, key, &payload, &footer, tc.ImplicitAssertion) + if tc.ExpectFail { + mustFail(t, err) + } else { + mustOk(t, err) + mustEqual(t, payload, tc.Payload) + mustEqual(t, footer, tc.Footer) + } + }) + } +}