diff --git a/src/ccm.ml b/src/ccm.ml index 0b63e4f2..c06f1d84 100644 --- a/src/ccm.ml +++ b/src/ccm.ml @@ -5,7 +5,6 @@ let (<+>) = Cs.(<+>) let block_size = 16 let flags bit6 len1 len2 = - assert (len1 < 8 && len2 < 8); let byte = Cstruct.create 1 and data = bit6 lsl 6 + len1 lsl 3 + len2 in Cstruct.set_uint8 byte 0 data ; @@ -32,10 +31,7 @@ let format nonce adata q t (* mac len *) = let small_q = 15 - n in (* first byte (flags): *) (* reserved | adata | (t - 2) / 2 | q - 1 *) - let b6 = match adata with - | Some _ -> 1 - | None -> 0 - in + let b6 = if Cstruct.len adata = 0 then 0 else 1 in let flag = flags b6 ((t - 2) / 2) (small_q - 1) in (* first octet block: 0 : flags @@ -76,18 +72,15 @@ let gen_ctr nonce i = let pre, _, q = gen_ctr_prefix nonce in pre <+> encode_len q i -let prepare_header nonce adata tlen plen = - let ada = match adata with - | Some x -> gen_adata x - | None -> Cstruct.empty - in +let prepare_header nonce adata plen tlen = + let ada = if Cstruct.len adata = 0 then Cstruct.empty else gen_adata adata in format nonce adata plen tlen <+> ada type mode = Encrypt | Decrypt -let crypto_core ~cipher ~mode ~key ~nonce ~maclen ?adata data = +let crypto_core ~cipher ~mode ~key ~nonce ~maclen ?(adata = Cstruct.empty) data = let datalen = Cstruct.len data in - let cbcheader = prepare_header nonce adata maclen datalen in + let cbcheader = prepare_header nonce adata datalen maclen in let target = Cstruct.create datalen in let blkprefix, blkpreflen, preflen = gen_ctr_prefix nonce in @@ -148,17 +141,20 @@ let crypto_t t nonce cipher key = cipher ~key ctr ctr ; Cs.xor_into ctr t (Cstruct.len t) +let valid_nonce nonce = + let nsize = Cstruct.len nonce in + if nsize < 7 || nsize > 13 then + invalid_arg "CCM: nonce length not between 7 and 13: %d" nsize + let generation_encryption ~cipher ~key ~nonce ~maclen ?adata data = + valid_nonce nonce; let cdata, t = crypto_core ~cipher ~mode:Encrypt ~key ~nonce ~maclen ?adata data in crypto_t t nonce cipher key ; cdata <+> t let decryption_verification ~cipher ~key ~nonce ~maclen ?adata data = - let () = - let nsize = Cstruct.len nonce in - if nsize < 7 || nsize > 13 then - invalid_arg "CCM: nonce length %d" nsize in - if Cstruct.len data <= maclen then + valid_nonce nonce; + if Cstruct.len data < maclen then None else let pclen = Cstruct.len data - maclen in diff --git a/src/cipher_block.ml b/src/cipher_block.ml index 3ef3a8d7..9bddb2a1 100644 --- a/src/cipher_block.ml +++ b/src/cipher_block.ml @@ -341,7 +341,8 @@ module Modes2 = struct BE.set_uint64 _cs 0 a; BE.set_uint64 _cs 8 b; _cs let counter ~hkey iv = match len iv with - | 12 -> let (w1, w2) = BE.(get_uint64 iv 0, BE.get_uint32 iv 8) in + | 0 -> invalid_arg "GCM: invalid IV of length 0" + | 12 -> let (w1, w2) = BE.get_uint64 iv 0, BE.get_uint32 iv 8 in (w1, Int64.(shift_left (of_int32 w2) 32 |> add 1L)) | _ -> CTR.ctr_of_cstruct @@ GHASH.digesti ~key:hkey @@ iter2 iv (pack64s 0L (bits64 iv)) diff --git a/src/mirage_crypto.mli b/src/mirage_crypto.mli index 630a4c23..a468e9c3 100644 --- a/src/mirage_crypto.mli +++ b/src/mirage_crypto.mli @@ -390,11 +390,17 @@ module Cipher_block : sig val encrypt : key:key -> iv:Cstruct.t -> ?adata:Cstruct.t -> Cstruct.t -> result (** [encrypt ~key ~iv ?adata msg] is the {{!result}[result]} containing [msg] encrypted under [key], with [iv] as the initialization vector, - and the authentication tag computed over both [adata] and [msg]. *) + and the authentication tag computed over both [adata] and [msg]. + + @raise Invalid_argument if the length [iv] is 0. + *) val decrypt : key:key -> iv:Cstruct.t -> ?adata:Cstruct.t -> Cstruct.t -> result (** [decrypt ~key ~iv ?adata msg] is the result containing the inversion - of [encrypt] and the same authentication tag. *) + of [encrypt] and the same authentication tag. + + @raise Invalid_argument if the length [iv] is 0. + *) end (** {e Counter with CBC-MAC} mode. *) diff --git a/tests/test_cipher.ml b/tests/test_cipher.ml index 5d8df23a..a2e87133 100644 --- a/tests/test_cipher.ml +++ b/tests/test_cipher.ml @@ -323,10 +323,102 @@ let ccm_cases = ~maclen: 8 ] +let ccm_regressions = + let open Cipher_block.AES.CCM in + let no_vs_empty_ad _ = + (* as reported in https://github.com/mirleft/ocaml-nocrypto/issues/166 *) + (* see RFC 3610 Section 2.1, AD of length 0 should be same as no AD *) + let key = of_secret ~maclen:16 (vx "000102030405060708090a0b0c0d0e0f") + and nonce = vx "0001020304050607" + and plaintext = Cstruct.of_string "hello" + in + assert_cs_equal ~msg:"CCM no vs empty ad" + (encrypt ~key ~nonce plaintext) + (encrypt ~adata:Cstruct.empty ~key ~nonce plaintext) + and short_nonce_enc _ = + (* as reported in https://github.com/mirleft/ocaml-nocrypto/issues/167 *) + (* valid nonce sizes for CCM are 7..13 (L can be 2..8, nonce is 15 - L)*) + (* see RFC3610 Section 2.1 *) + let key = of_secret ~maclen:16 (vx "000102030405060708090a0b0c0d0e0f") + and nonce = Cstruct.empty + and plaintext = Cstruct.of_string "hello" + in + assert_raises ~msg:"CCM with short nonce raises" + (Invalid_argument "Mirage_crypto: CCM: nonce length not between 7 and 13: 0") + (fun () -> encrypt ~key ~nonce plaintext) + and short_nonce_enc2 _ = + let key = of_secret ~maclen:16 (vx "000102030405060708090a0b0c0d0e0f") + and nonce = vx "00" + and plaintext = Cstruct.of_string "hello" + in + assert_raises ~msg:"CCM with short nonce raises" + (Invalid_argument "Mirage_crypto: CCM: nonce length not between 7 and 13: 1") + (fun () -> encrypt ~key ~nonce plaintext) + and short_nonce_enc3 _ = + let key = of_secret ~maclen:16 (vx "000102030405060708090a0b0c0d0e0f") + and nonce = vx "000102030405" + and plaintext = Cstruct.of_string "hello" + in + assert_raises ~msg:"CCM with short nonce raises" + (Invalid_argument "Mirage_crypto: CCM: nonce length not between 7 and 13: 6") + (fun () -> encrypt ~key ~nonce plaintext) + and long_nonce_enc _ = + let key = of_secret ~maclen:16 (vx "000102030405060708090a0b0c0d0e0f") + and nonce = vx "000102030405060708090a0b0c0d" + and plaintext = Cstruct.of_string "hello" + in + assert_raises ~msg:"CCM with short nonce raises" + (Invalid_argument "Mirage_crypto: CCM: nonce length not between 7 and 13: 14") + (fun () -> encrypt ~key ~nonce plaintext) + and enc_dec_empty_message _ = + (* as reported in https://github.com/mirleft/ocaml-nocrypto/issues/168 *) + let key = of_secret ~maclen:16 (vx "000102030405060708090a0b0c0d0e0f") + and nonce = vx "0001020304050607" + and adata = Cstruct.of_string "hello" + and p = Cstruct.empty + in + let cipher = encrypt ~adata ~key ~nonce p in + match decrypt ~key ~nonce ~adata cipher with + | Some x -> assert_cs_equal ~msg:"CCM decrypt of empty message" p x + | None -> assert_failure "decryption broken" + in + [ + test_case no_vs_empty_ad ; + test_case short_nonce_enc ; + test_case short_nonce_enc2 ; + test_case short_nonce_enc3 ; + test_case long_nonce_enc ; + test_case enc_dec_empty_message ; + ] + +let gcm_regressions = + let open Cipher_block.AES.GCM in + let msg = vx "000102030405060708090a0b0c0d0e0f" in + let key = of_secret msg + and iv = Cstruct.empty + in + let iv_zero_length_enc _ = + (* reported in https://github.com/mirleft/ocaml-nocrypto/issues/169 *) + assert_raises ~msg:"GCM with iv of length 0" + (Invalid_argument "Mirage_crypto: GCM: invalid IV of length 0") + (fun () -> encrypt ~key ~iv msg) + and iv_zero_length_dec _ = + assert_raises ~msg:"GCM with iv of 0" + (Invalid_argument "Mirage_crypto: GCM: invalid IV of length 0") + (fun () -> decrypt ~key ~iv msg) + in + [ + test_case iv_zero_length_enc ; + test_case iv_zero_length_dec ; + ] + + let suite = [ "AES-ECB" >::: [ "SP 300-38A" >::: aes_ecb_cases ] ; "AES-CBC" >::: [ "SP 300-38A" >::: aes_cbc_cases ] ; "AES-CTR" >::: [ "SP 300-38A" >::: aes_ctr_cases; ] ; "AES-GCM" >::: gcm_cases ; "AES-CCM" >::: ccm_cases ; + "AES-CCM-REGRESSION" >::: ccm_regressions ; + "AES-GCM-REGRESSION" >::: gcm_regressions ; ]