Skip to content

Commit 0fdf4b4

Browse files
committed
refactor: unify ChaCha20 variants and improve types
Parameterize ChaCha20/XChaCha20 encrypt, decrypt, wrap, and unwrap functions to eliminate duplication. Extract HMAC key validation helper in signing. Replace raw header tuple with ParsedHeader record type. Use labeled arguments and computed combinations in generators.
1 parent 6239ef5 commit 0fdf4b4

9 files changed

Lines changed: 223 additions & 246 deletions

File tree

src/gose/internal/jwe/content_encryption.gleam

Lines changed: 59 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,24 @@ pub fn encrypt_content(
125125
hash_for_aes_size(size),
126126
size,
127127
)
128-
jwa.ChaCha20Poly1305 -> encrypt_chacha20(cek, iv, aad, plaintext)
129-
jwa.XChaCha20Poly1305 -> encrypt_xchacha20(cek, iv, aad, plaintext)
128+
jwa.ChaCha20Poly1305 ->
129+
encrypt_chacha20_variant(
130+
cek:,
131+
iv:,
132+
aad:,
133+
plaintext:,
134+
using: aead.chacha20_poly1305,
135+
variant: "ChaCha20-Poly1305",
136+
)
137+
jwa.XChaCha20Poly1305 ->
138+
encrypt_chacha20_variant(
139+
cek:,
140+
iv:,
141+
aad:,
142+
plaintext:,
143+
using: aead.xchacha20_poly1305,
144+
variant: "XChaCha20-Poly1305",
145+
)
130146
}
131147
}
132148

@@ -151,8 +167,26 @@ pub fn decrypt_content(
151167
hash_for_aes_size(size),
152168
size,
153169
)
154-
jwa.ChaCha20Poly1305 -> decrypt_chacha20(cek, iv, aad, ciphertext, tag)
155-
jwa.XChaCha20Poly1305 -> decrypt_xchacha20(cek, iv, aad, ciphertext, tag)
170+
jwa.ChaCha20Poly1305 ->
171+
decrypt_chacha20_variant(
172+
cek:,
173+
iv:,
174+
aad:,
175+
ciphertext:,
176+
tag:,
177+
using: aead.chacha20_poly1305,
178+
variant: "ChaCha20-Poly1305",
179+
)
180+
jwa.XChaCha20Poly1305 ->
181+
decrypt_chacha20_variant(
182+
cek:,
183+
iv:,
184+
aad:,
185+
ciphertext:,
186+
tag:,
187+
using: aead.xchacha20_poly1305,
188+
variant: "XChaCha20-Poly1305",
189+
)
156190
}
157191
}
158192

@@ -189,78 +223,41 @@ fn decrypt_aes_gcm(
189223
|> result.replace_error(gose.CryptoError("AES-GCM decryption failed"))
190224
}
191225

192-
fn encrypt_chacha20(
193-
cek: BitArray,
194-
iv: BitArray,
195-
aad: BitArray,
196-
plaintext: BitArray,
197-
) -> Result(#(BitArray, BitArray), gose.GoseError) {
198-
use ctx <- result.try(
199-
aead.chacha20_poly1305(cek)
200-
|> result.replace_error(gose.CryptoError(
201-
"invalid key size for ChaCha20-Poly1305",
202-
)),
203-
)
204-
aead.seal_with_aad(ctx, nonce: iv, plaintext:, additional_data: aad)
205-
|> result.replace_error(gose.CryptoError(
206-
"ChaCha20-Poly1305 encryption failed",
207-
))
208-
}
209-
210-
fn decrypt_chacha20(
211-
cek: BitArray,
212-
iv: BitArray,
213-
aad: BitArray,
214-
ciphertext: BitArray,
215-
tag: BitArray,
216-
) -> Result(BitArray, gose.GoseError) {
217-
use ctx <- result.try(
218-
aead.chacha20_poly1305(cek)
219-
|> result.replace_error(gose.CryptoError(
220-
"invalid key size for ChaCha20-Poly1305",
221-
)),
222-
)
223-
aead.open_with_aad(ctx, nonce: iv, tag:, ciphertext:, additional_data: aad)
224-
|> result.replace_error(gose.CryptoError(
225-
"ChaCha20-Poly1305 decryption failed",
226-
))
227-
}
228-
229-
fn encrypt_xchacha20(
230-
cek: BitArray,
231-
iv: BitArray,
232-
aad: BitArray,
233-
plaintext: BitArray,
226+
fn encrypt_chacha20_variant(
227+
cek cek: BitArray,
228+
iv iv: BitArray,
229+
aad aad: BitArray,
230+
plaintext plaintext: BitArray,
231+
using cipher_fn: fn(BitArray) -> Result(aead.AeadContext, Nil),
232+
variant variant_name: String,
234233
) -> Result(#(BitArray, BitArray), gose.GoseError) {
235234
use ctx <- result.try(
236-
aead.xchacha20_poly1305(cek)
235+
cipher_fn(cek)
237236
|> result.replace_error(gose.CryptoError(
238-
"invalid key size for XChaCha20-Poly1305",
237+
"invalid key size for " <> variant_name,
239238
)),
240239
)
241240
aead.seal_with_aad(ctx, nonce: iv, plaintext:, additional_data: aad)
242-
|> result.replace_error(gose.CryptoError(
243-
"XChaCha20-Poly1305 encryption failed",
244-
))
241+
|> result.replace_error(gose.CryptoError(variant_name <> " encryption failed"))
245242
}
246243

247-
fn decrypt_xchacha20(
248-
cek: BitArray,
249-
iv: BitArray,
250-
aad: BitArray,
251-
ciphertext: BitArray,
252-
tag: BitArray,
244+
fn decrypt_chacha20_variant(
245+
cek cek: BitArray,
246+
iv iv: BitArray,
247+
aad aad: BitArray,
248+
ciphertext ciphertext: BitArray,
249+
tag tag: BitArray,
250+
using cipher_fn: fn(BitArray) -> Result(aead.AeadContext, Nil),
251+
variant variant_name: String,
253252
) -> Result(BitArray, gose.GoseError) {
254253
use ctx <- result.try(
255-
aead.xchacha20_poly1305(cek)
254+
cipher_fn(cek)
256255
|> result.replace_error(gose.CryptoError(
257-
"invalid key size for XChaCha20-Poly1305",
256+
"invalid key size for " <> variant_name,
258257
)),
259258
)
260259
aead.open_with_aad(ctx, nonce: iv, tag:, ciphertext:, additional_data: aad)
261-
|> result.replace_error(gose.CryptoError(
262-
"XChaCha20-Poly1305 decryption failed",
263-
))
260+
|> result.replace_error(gose.CryptoError(variant_name <> " decryption failed"))
264261
}
265262

266263
pub fn aes_cipher(

src/gose/internal/jwe/key_wrap.gleam

Lines changed: 43 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -116,70 +116,48 @@ pub fn unwrap_aes_gcm_kw(
116116
unwrap_aes_gcm(kek, encrypted_cek, iv, tag, size)
117117
}
118118

119-
pub fn wrap_chacha20(
120-
kek: BitArray,
121-
cek: BitArray,
122-
nonce: BitArray,
123-
) -> Result(#(BitArray, BitArray), gose.GoseError) {
124-
use ctx <- result.try(
125-
aead.chacha20_poly1305(kek)
126-
|> result.replace_error(gose.CryptoError(
127-
"invalid key size for ChaCha20-Poly1305 Key Wrap",
128-
)),
129-
)
130-
aead.seal(ctx, nonce:, plaintext: cek)
131-
|> result.replace_error(gose.CryptoError("ChaCha20-Poly1305 Key Wrap failed"))
132-
}
133-
134-
pub fn unwrap_chacha20(
135-
kek: BitArray,
136-
encrypted_cek: BitArray,
137-
nonce: BitArray,
138-
tag: BitArray,
139-
) -> Result(BitArray, gose.GoseError) {
140-
use ctx <- result.try(
141-
aead.chacha20_poly1305(kek)
142-
|> result.replace_error(gose.CryptoError(
143-
"invalid key size for ChaCha20-Poly1305 Key Unwrap",
144-
)),
145-
)
146-
aead.open(ctx, nonce:, tag:, ciphertext: encrypted_cek)
147-
|> result.replace_error(gose.CryptoError(
148-
"ChaCha20-Poly1305 Key Unwrap failed",
149-
))
119+
fn chacha20_variant_params(
120+
variant: jwa.ChaCha20Kw,
121+
) -> #(fn(BitArray) -> Result(aead.AeadContext, Nil), String) {
122+
case variant {
123+
jwa.C20PKw -> #(aead.chacha20_poly1305, "ChaCha20-Poly1305")
124+
jwa.XC20PKw -> #(aead.xchacha20_poly1305, "XChaCha20-Poly1305")
125+
}
150126
}
151127

152-
pub fn wrap_xchacha20(
153-
kek: BitArray,
154-
cek: BitArray,
155-
nonce: BitArray,
128+
fn wrap_chacha20_variant(
129+
kek kek: BitArray,
130+
cek cek: BitArray,
131+
nonce nonce: BitArray,
132+
using cipher_fn: fn(BitArray) -> Result(aead.AeadContext, Nil),
133+
variant variant_name: String,
156134
) -> Result(#(BitArray, BitArray), gose.GoseError) {
157135
use ctx <- result.try(
158-
aead.xchacha20_poly1305(kek)
136+
cipher_fn(kek)
159137
|> result.replace_error(gose.CryptoError(
160-
"invalid key size for XChaCha20-Poly1305 Key Wrap",
138+
"invalid key size for " <> variant_name <> " Key Wrap",
161139
)),
162140
)
163141
aead.seal(ctx, nonce:, plaintext: cek)
164-
|> result.replace_error(gose.CryptoError("XChaCha20-Poly1305 Key Wrap failed"))
142+
|> result.replace_error(gose.CryptoError(variant_name <> " Key Wrap failed"))
165143
}
166144

167-
pub fn unwrap_xchacha20(
168-
kek: BitArray,
169-
encrypted_cek: BitArray,
170-
nonce: BitArray,
171-
tag: BitArray,
145+
fn unwrap_chacha20_variant(
146+
kek kek: BitArray,
147+
encrypted_cek encrypted_cek: BitArray,
148+
nonce nonce: BitArray,
149+
tag tag: BitArray,
150+
using cipher_fn: fn(BitArray) -> Result(aead.AeadContext, Nil),
151+
variant variant_name: String,
172152
) -> Result(BitArray, gose.GoseError) {
173153
use ctx <- result.try(
174-
aead.xchacha20_poly1305(kek)
154+
cipher_fn(kek)
175155
|> result.replace_error(gose.CryptoError(
176-
"invalid key size for XChaCha20-Poly1305 Key Unwrap",
156+
"invalid key size for " <> variant_name <> " Key Unwrap",
177157
)),
178158
)
179159
aead.open(ctx, nonce:, tag:, ciphertext: encrypted_cek)
180-
|> result.replace_error(gose.CryptoError(
181-
"XChaCha20-Poly1305 Key Unwrap failed",
182-
))
160+
|> result.replace_error(gose.CryptoError(variant_name <> " Key Unwrap failed"))
183161
}
184162

185163
pub fn wrap_chacha20_by_variant(
@@ -188,10 +166,14 @@ pub fn wrap_chacha20_by_variant(
188166
nonce: BitArray,
189167
variant: jwa.ChaCha20Kw,
190168
) -> Result(#(BitArray, BitArray), gose.GoseError) {
191-
case variant {
192-
jwa.C20PKw -> wrap_chacha20(kek, cek, nonce)
193-
jwa.XC20PKw -> wrap_xchacha20(kek, cek, nonce)
194-
}
169+
let #(cipher_fn, variant_name) = chacha20_variant_params(variant)
170+
wrap_chacha20_variant(
171+
kek:,
172+
cek:,
173+
nonce:,
174+
using: cipher_fn,
175+
variant: variant_name,
176+
)
195177
}
196178

197179
pub fn unwrap_chacha20_by_variant(
@@ -201,10 +183,15 @@ pub fn unwrap_chacha20_by_variant(
201183
tag: BitArray,
202184
variant: jwa.ChaCha20Kw,
203185
) -> Result(BitArray, gose.GoseError) {
204-
case variant {
205-
jwa.C20PKw -> unwrap_chacha20(kek, encrypted_cek, nonce, tag)
206-
jwa.XC20PKw -> unwrap_xchacha20(kek, encrypted_cek, nonce, tag)
207-
}
186+
let #(cipher_fn, variant_name) = chacha20_variant_params(variant)
187+
unwrap_chacha20_variant(
188+
kek:,
189+
encrypted_cek:,
190+
nonce:,
191+
tag:,
192+
using: cipher_fn,
193+
variant: variant_name,
194+
)
208195
}
209196

210197
pub fn unwrap_chacha20_kw(

src/gose/internal/jws/signing.gleam

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,9 @@ pub fn compute_signature(
2121
let mat = jwk.material(key)
2222
case alg {
2323
jwa.JwsHmac(hmac_alg) -> {
24-
use secret <- result.try(
25-
jwk.material_octet_secret(mat)
26-
|> result.replace_error(gose.InvalidState(
27-
"HMAC algorithms require an octet key",
28-
)),
29-
)
30-
let #(hash_alg, min_size, alg_name) = resolve_hmac_params(hmac_alg)
31-
use _ <- result.try(key_helpers.validate_hmac_key_size(
24+
use #(hash_alg, secret) <- result.try(extract_validated_hmac_secret(
3225
key,
33-
min_size,
34-
alg_name,
26+
hmac_alg,
3527
))
3628
crypto.hmac(hash_alg, secret, message)
3729
|> result.replace_error(gose.CryptoError("HMAC computation failed"))
@@ -75,17 +67,9 @@ pub fn verify_signature(
7567
let mat = jwk.material(key)
7668
case alg {
7769
jwa.JwsHmac(hmac_alg) -> {
78-
use secret <- result.try(
79-
jwk.material_octet_secret(mat)
80-
|> result.replace_error(gose.InvalidState(
81-
"HMAC algorithms require an octet key",
82-
)),
83-
)
84-
let #(hash_alg, min_size, alg_name) = resolve_hmac_params(hmac_alg)
85-
use _ <- result.try(key_helpers.validate_hmac_key_size(
70+
use #(hash_alg, secret) <- result.try(extract_validated_hmac_secret(
8671
key,
87-
min_size,
88-
alg_name,
72+
hmac_alg,
8973
))
9074
hmac_verify(hash_alg, secret, message, signature)
9175
}
@@ -119,6 +103,25 @@ pub fn verify_signature(
119103
}
120104
}
121105

106+
fn extract_validated_hmac_secret(
107+
key: jwk.Jwk,
108+
hmac_alg: jwa.HmacAlg,
109+
) -> Result(#(hash.HashAlgorithm, BitArray), gose.GoseError) {
110+
use secret <- result.try(
111+
jwk.material_octet_secret(jwk.material(key))
112+
|> result.replace_error(gose.InvalidState(
113+
"HMAC algorithms require an octet key",
114+
)),
115+
)
116+
let #(hash_alg, min_size, alg_name) = resolve_hmac_params(hmac_alg)
117+
use _ <- result.try(key_helpers.validate_hmac_key_size(
118+
key,
119+
min_size,
120+
alg_name,
121+
))
122+
Ok(#(hash_alg, secret))
123+
}
124+
122125
fn hmac_verify(
123126
algorithm: hash.HashAlgorithm,
124127
key: BitArray,

src/gose/internal/utils.gleam

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,18 @@ pub fn ec_public_key_coordinates(
4949
let raw_point = ec.public_key_to_raw_point(public)
5050
let expected_size = 1 + coord_size * 2
5151
case bit_array.byte_size(raw_point) == expected_size, raw_point {
52-
True, <<0x04, rest:bits>> ->
53-
case bit_array.slice(rest, 0, coord_size) {
54-
Ok(x) ->
55-
bit_array.slice(rest, coord_size, coord_size)
56-
|> result.replace_error(gose.InvalidState("invalid raw point format"))
57-
|> result.map(fn(y) { #(x, y) })
58-
Error(_) -> Error(gose.InvalidState("invalid raw point format"))
59-
}
52+
True, <<0x04, rest:bits>> -> {
53+
let error = gose.InvalidState("invalid raw point format")
54+
use x <- result.try(
55+
bit_array.slice(rest, 0, coord_size)
56+
|> result.replace_error(error),
57+
)
58+
use y <- result.try(
59+
bit_array.slice(rest, coord_size, coord_size)
60+
|> result.replace_error(error),
61+
)
62+
Ok(#(x, y))
63+
}
6064
_, _ -> Error(gose.InvalidState("invalid raw point format"))
6165
}
6266
}

0 commit comments

Comments
 (0)