1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
open Utils
(** {{: https://tools.ietf.org/html/rfc7516 } Link to RFC } *)
type t = {
header : Header.t;
cek : string;
iv : string;
payload : string;
aad : string option;
}
module RSA_OAEP = Mirage_crypto_pk.Rsa.OAEP (Mirage_crypto.Hash.SHA1)
let make_cek ( : Header.t) =
match header.enc with
| Some enc ->
let key_length = Jwa.enc_to_length enc in
Mirage_crypto_rng.generate (key_length / 8)
|> Cstruct.to_string |> U_Result.return
| None -> Error `Missing_enc
let make_iv ( : Header.t) =
match header.alg with
| `RSA_OAEP ->
Mirage_crypto_rng.generate Mirage_crypto.Cipher_block.AES.GCM.block_size
|> Cstruct.to_string |> U_Result.return
| `RSA1_5 ->
Mirage_crypto_rng.generate Mirage_crypto.Cipher_block.AES.CBC.block_size
|> Cstruct.to_string |> U_Result.return
| _ -> Error `Unsupported_alg
let make ~ payload =
let open U_Result.Infix in
make_cek header >>= fun cek ->
make_iv header >>= fun iv ->
let aad = None in
Ok { header; cek; iv; aad; payload }
let encrypt_payload ?enc ~cek ~iv ~aad payload =
let iv = Cstruct.of_string iv in
match enc with
| Some `A128CBC_HS256 ->
let hmac_key, aes_key =
Cstruct.(
split (of_string cek) Mirage_crypto.Cipher_block.AES.CBC.block_size)
in
let key = Mirage_crypto.Cipher_block.AES.CBC.of_secret aes_key in
Mirage_crypto.Cipher_block.AES.CBC.encrypt ~key ~iv
(Pkcs7.pad
(Cstruct.of_string payload)
Mirage_crypto.Cipher_block.AES.CBC.block_size)
|> fun data ->
let hmac_input =
let aal = Cstruct.create 8 in
Cstruct.BE.set_uint64 aal 0 Int64.(mul 8L (of_int (String.length aad)));
Cstruct.(concat [ of_string aad; iv; data; aal ])
in
let computed_auth_tag =
let full = Mirage_crypto.Hash.SHA256.hmac ~key:hmac_key hmac_input in
Cstruct.sub full 0 16 |> Cstruct.to_string
in
Ok (Cstruct.to_string data, computed_auth_tag)
| Some `A256GCM ->
let module GCM = Mirage_crypto.Cipher_block.AES.GCM in
let cek = Cstruct.of_string cek in
let key = GCM.of_secret cek in
let adata = Cstruct.of_string aad in
GCM.authenticate_encrypt ~key ~nonce:iv ~adata (Cstruct.of_string payload)
|> fun cdata ->
let cipher, tag_data =
Cstruct.split cdata (Cstruct.length cdata - GCM.tag_size)
in
let ciphertext = Cstruct.to_string cipher in
let tag_string = Cstruct.to_string tag_data in
Ok (ciphertext, tag_string)
| None -> Error `Missing_enc
| _ -> Error `Unsupported_enc
let encrypt_cek (type a) alg (cek : string) ~(jwk : a Jwk.t) =
let open U_Result.Infix in
(match jwk with
| Rsa_priv rsa -> Ok (Mirage_crypto_pk.Rsa.pub_of_priv rsa.key)
| Rsa_pub rsa -> Ok rsa.key
| Oct _ -> Error `Unsupported_kty
| Es256_priv _ -> Error `Unsupported_kty
| Es256_pub _ -> Error `Unsupported_kty
| Es512_priv _ -> Error `Unsupported_kty
| Es512_pub _ -> Error `Unsupported_kty)
>>= fun key ->
match alg with
| `RSA1_5 ->
let ecek =
cek |> Cstruct.of_string
|> Mirage_crypto_pk.Rsa.PKCS1.encrypt ~key
|> Cstruct.to_string
in
Ok ecek
| `RSA_OAEP ->
let cek = Cstruct.of_string cek in
let jek = RSA_OAEP.encrypt ~key cek |> Cstruct.to_string in
Ok jek
| _ -> Error `Invalid_alg
let encrypt (type a) ~(jwk : a Jwk.t) t =
let open U_Result.Infix in
let = Header.to_string t.header in
encrypt_cek t.header.alg t.cek ~jwk >|= U_Base64.url_encode_string
>>= fun ecek ->
let eiv = U_Base64.url_encode_string t.iv in
encrypt_payload ?enc:t.header.enc ~cek:t.cek ~iv:t.iv ~aad:header_string
t.payload
>>= fun (ciphertext, auth_tag) ->
Ok
(String.concat "."
[
header_string;
ecek;
eiv;
U_Base64.url_encode_string ciphertext;
U_Base64.url_encode_string auth_tag;
])
let decrypt_cek alg str ~(jwk : Jwk.priv Jwk.t) =
let of_opt_cstruct = function
| Some c -> Ok (Cstruct.to_string c)
| None -> Error `Decrypt_cek_failed
in
match (alg, jwk) with
| `RSA1_5, Jwk.Rsa_priv rsa ->
Utils.U_Base64.url_decode str
|> U_Result.map Cstruct.of_string
|> U_Result.map (Mirage_crypto_pk.Rsa.PKCS1.decrypt ~key:rsa.key)
|> U_Result.flat_map of_opt_cstruct
| `RSA_OAEP, Jwk.Rsa_priv rsa ->
Utils.U_Base64.url_decode str
|> U_Result.map Cstruct.of_string
|> U_Result.map (RSA_OAEP.decrypt ~key:rsa.key)
|> U_Result.flat_map of_opt_cstruct
| _ -> Error `Invalid_JWK
let decrypt_ciphertext enc ~cek ~iv ~auth_tag ~aad ciphertext =
let iv = Cstruct.of_string iv in
let open Utils.U_Result.Infix in
U_Base64.url_decode ciphertext >>= fun encrypted ->
let encrypted = Cstruct.of_string encrypted in
match enc with
| Some `A128CBC_HS256 ->
let hmac_key, aes_key = Cstruct.(split (of_string cek) 16) in
let key = Mirage_crypto.Cipher_block.AES.CBC.of_secret aes_key in
let hmac_input =
let aal = Cstruct.create 8 in
Cstruct.BE.set_uint64 aal 0 Int64.(mul 8L (of_int (String.length aad)));
Cstruct.(concat [ of_string aad; iv; encrypted; aal ])
in
let computed_auth_tag =
let full = Mirage_crypto.Hash.SHA256.hmac ~key:hmac_key hmac_input in
Cstruct.sub full 0 16 |> Cstruct.to_string
in
if not (String.equal computed_auth_tag auth_tag) then
Error (`Msg "invalid auth tag")
else
Mirage_crypto.Cipher_block.AES.CBC.decrypt ~key ~iv encrypted
|> Pkcs7.unpad
>>= fun data -> Ok (Cstruct.to_string data)
| Some `A256GCM ->
let module GCM = Mirage_crypto.Cipher_block.AES.GCM in
let cek = Cstruct.of_string cek in
let key = GCM.of_secret cek in
let adata = Cstruct.of_string aad in
let encrypted = Cstruct.append encrypted (Cstruct.of_string auth_tag) in
Mirage_crypto.Cipher_block.AES.GCM.authenticate_decrypt ~key ~nonce:iv
~adata encrypted
|> fun message ->
U_Opt.(
map (fun x -> Ok (Cstruct.to_string x)) message
|> get_with_default ~default:(Error (`Msg "invalid auth tag")))
| _ -> Error (`Msg "unsupported encryption")
let decrypt ~(jwk : Jwk.priv Jwk.t) jwe =
let open Utils.U_Result.Infix in
String.split_on_char '.' jwe |> function
| [ ; enc_cek; enc_iv; ciphertext; auth_tag ] ->
Header.of_string enc_header >>= fun ->
decrypt_cek header.Header.alg ~jwk enc_cek >>= fun cek ->
U_Base64.url_decode enc_iv >>= fun iv ->
U_Base64.url_decode auth_tag >>= fun auth_tag ->
decrypt_ciphertext header.Header.enc ~cek ~iv ~auth_tag ~aad:enc_header
ciphertext
>>= fun payload -> Ok { header; cek; iv; payload; aad = None }
| _ -> Error `Invalid_JWE