From 65ccb5a86f520f938da474da7141aef6063b745d Mon Sep 17 00:00:00 2001 From: swrup Date: Fri, 21 Nov 2025 19:07:36 +0100 Subject: [PATCH] add crypto.ml --- src/api_types.ml | 2 +- src/binary_formats.ml | 4 +- src/crypto.ml | 146 +++++++++++++++++++++++++++++++++++++++++ src/denomination.ml | 2 +- src/json.ml | 1 + src/signkey.ml | 2 +- src/types.ml | 147 ------------------------------------------ 7 files changed, 152 insertions(+), 152 deletions(-) create mode 100644 src/crypto.ml diff --git a/src/api_types.ml b/src/api_types.ml index 4ed5697a..7ffb0b7b 100644 --- a/src/api_types.ml +++ b/src/api_types.ml @@ -5,7 +5,7 @@ time - better types - issues with "never" = uint64_max *) -open Types +open Crypto module ErrorDetail = struct (* TODO GANA error codes diff --git a/src/binary_formats.ml b/src/binary_formats.ml index d2742054..059b0948 100644 --- a/src/binary_formats.ml +++ b/src/binary_formats.ml @@ -246,9 +246,9 @@ include UTIL (* -- Cryptographic primitives -- *) module DenominationHash = MK_SRC_HASH_32 (struct - type src = Types.RsaPublicKey.t + type src = Crypto.RsaPublicKey.t - let to_octets = Types.RsaPublicKey.to_octets + let to_octets = Crypto.RsaPublicKey.to_octets end) module ExchangePublicKeyP = struct diff --git a/src/crypto.ml b/src/crypto.ml new file mode 100644 index 00000000..3fe4171f --- /dev/null +++ b/src/crypto.ml @@ -0,0 +1,146 @@ +(* TODO key format + - check what is the exact format in GNUNET + - endianess issue? *) +module EddsaPublicKey = struct + (* EdDSA and ECDHE public keys always point on Curve25519 + and represented using the standard 256 bits Ed25519 compact format, + converted to Crockford Base32. *) + open Mirage_crypto_ec.Ed25519 + + type t = pub + + let to_octets t = pub_to_octets t + let of_octets t = pub_of_octets t + + let of_b32 s = + let open Syntax in + let* octets = B32.decode s in + match pub_of_octets octets with + | Error e -> Fmt.error "%a" Mirage_crypto_ec.pp_error e + | Ok pub -> Ok pub + + let to_b32 t = B32.encode (to_octets t) + let jsont = Jsont.of_of_string ~kind:"EddsaPublicKey" of_b32 ~enc:to_b32 +end + +module EddsaSignature : sig + type t + + val to_octets : t -> string + val sign : key:Mirage_crypto_ec.Ed25519.priv -> string -> t + val of_b32 : string -> (t, string) result + val to_b32 : t -> string + val jsont : t Jsont.t +end = struct + (* TODO key format + endianess issue? *) + (* EdDSA signatures are transmitted as 64-bytes base32 + binary-encoded objects with just the R and S values (base32_ binary-only). + + They are signature over a c-struct like `TALER_xxxPS` + with a purpose *) + type t = string + + let to_octets t = t + + let sign ~key s = + (* mirage_crypto: "The result is the concatenation of r and s, as specified in RFC 8032." *) + Mirage_crypto_ec.Ed25519.sign ~key s + + let check_size t = + match String.length t = 64 with + | false -> Error "EddsaSignature: invalid string length" + | true -> Ok () + + let of_b32 s = + let open Syntax in + let* t = B32.decode s in + let+ () = check_size t in + t + + let to_b32 = B32.encode + let jsont = Jsont.of_of_string ~kind:"EddsaSignature" of_b32 ~enc:to_b32 +end + +module RsaPublicKey = struct + module Binary = struct + (* https://www.gnupg.org/documentation/manuals/gcrypt/MPI-formats.html + format: { uint16_be: n size; uint16_be: e size; n; e} + + integer in big-endian format (MSB first) + leading zeroes are stripped unless they are required to keep a value positive + no 0-termination *) + + let rev_string len s = String.init len (fun i -> s.[len - 1 - i]) + + (* todo: need to strip leading zeros or something? *) + (* reverse bytes because Z.of_bits reads bytes in little endian *) + let z_of_bits_be src pos len = + String.sub src pos len |> rev_string len |> Z.of_bits + + let to_octets ({ n; e } : Mirage_crypto_pk.Rsa.pub) = + let n_len = Z.size n in + let e_len = Z.size e in + let len = 4 + n_len + e_len in + let b = Bytes.make len '\x00' in + Bytes.set_uint16_be b 0 n_len; + Bytes.set_uint16_be b 2 e_len; + let n = Z.to_bits n |> rev_string n_len in + let e = Z.to_bits e |> rev_string e_len in + Bytes.blit_string n 0 b 4 n_len; + Bytes.blit_string e 0 b (4 + n_len) e_len; + Bytes.unsafe_to_string b + + let of_octets = + let check = function + | false -> Error "RsaPublicKey.of_octets: invalid data" + | true -> Ok () + in + fun s -> + let open Syntax in + let len = String.length s in + let* () = check (len >= 4) in + let n_len = String.get_uint16_be s 0 in + let e_len = String.get_uint16_be s 2 in + let* () = check (len = n_len + e_len + 4) in + let n = z_of_bits_be s 4 n_len in + let e = z_of_bits_be s (4 + n_len) e_len in + Mirage_crypto_pk.Rsa.pub ~n ~e |> unwrap_err_msg + end + + include Binary + open Mirage_crypto_pk + + type t = Rsa.pub + + let of_b32 s = + let open Syntax in + let* s = B32.decode s in + let* v = of_octets s in + let+ v = Rsa.pub ~n:v.n ~e:v.e |> unwrap_err_msg in + v + + let to_b32 t = B32.encode (to_octets t) + let jsont = Jsont.of_of_string ~kind:"RsaPublicKey" of_b32 ~enc:to_b32 +end + +module RsaSignature : sig + type t + + val to_octets : t -> string + val sign : key:Mirage_crypto_pk.Rsa.priv -> string -> t + val of_b32 : string -> (t, string) result + val to_b32 : t -> string + val jsont : t Jsont.t +end = struct + type t = string + + let to_octets t = t + + (* TODO rsa sign *) + let sign ~key s = + Mirage_crypto_pk.Rsa.decrypt ~crt_hardening:true ~mask:`Yes ~key s + + let of_b32 s = B32.decode s + let to_b32 t = B32.encode t + let jsont = Jsont.of_of_string ~kind:"RsaSignature" of_b32 ~enc:to_b32 +end diff --git a/src/denomination.ml b/src/denomination.ml index 5e0a1eb9..073ab646 100644 --- a/src/denomination.ml +++ b/src/denomination.ml @@ -1,4 +1,4 @@ -open Types +open Crypto type t = { pub: RsaPublicKey.t; diff --git a/src/json.ml b/src/json.ml index 8a925111..af03ef53 100644 --- a/src/json.ml +++ b/src/json.ml @@ -1,5 +1,6 @@ open Types open Api_types +open Crypto let encode_exn jsont v = Jsont_bytesrw.encode_string jsont v |> Result.get_ok let encode jsont v = Jsont_bytesrw.encode_string jsont v diff --git a/src/signkey.ml b/src/signkey.ml index 67a14a6d..01095f00 100644 --- a/src/signkey.ml +++ b/src/signkey.ml @@ -1,4 +1,4 @@ -open Types +open Crypto type t = { pub: EddsaPublicKey.t; diff --git a/src/types.ml b/src/types.ml index 4eab7a54..3c20c4db 100644 --- a/src/types.ml +++ b/src/types.ml @@ -3,150 +3,3 @@ module Amount = struct let jsont = Jsont.of_of_string ~kind:"Amount" of_string ~enc:to_string end - -(* TODO key format - - check what is the exact format in GNUNET - - endianess issue? *) -module EddsaPublicKey = struct - (* EdDSA and ECDHE public keys always point on Curve25519 - and represented using the standard 256 bits Ed25519 compact format, - converted to Crockford Base32. *) - open Mirage_crypto_ec.Ed25519 - - type t = pub - - let to_octets t = pub_to_octets t - let of_octets t = pub_of_octets t - - let of_b32 s = - let open Syntax in - let* octets = B32.decode s in - match pub_of_octets octets with - | Error e -> Fmt.error "%a" Mirage_crypto_ec.pp_error e - | Ok pub -> Ok pub - - let to_b32 t = B32.encode (to_octets t) - let jsont = Jsont.of_of_string ~kind:"EddsaPublicKey" of_b32 ~enc:to_b32 -end - -module EddsaSignature : sig - type t - - val to_octets : t -> string - val sign : key:Mirage_crypto_ec.Ed25519.priv -> string -> t - val of_b32 : string -> (t, string) result - val to_b32 : t -> string - val jsont : t Jsont.t -end = struct - (* TODO key format - endianess issue? *) - (* EdDSA signatures are transmitted as 64-bytes base32 - binary-encoded objects with just the R and S values (base32_ binary-only). - - They are signature over a c-struct like `TALER_xxxPS` + with a purpose *) - type t = string - - let to_octets t = t - - let sign ~key s = - (* mirage_crypto: "The result is the concatenation of r and s, as specified in RFC 8032." *) - Mirage_crypto_ec.Ed25519.sign ~key s - - let check_size t = - match String.length t = 64 with - | false -> Error "EddsaSignature: invalid string length" - | true -> Ok () - - let of_b32 s = - let open Syntax in - let* t = B32.decode s in - let+ () = check_size t in - t - - let to_b32 = B32.encode - let jsont = Jsont.of_of_string ~kind:"EddsaSignature" of_b32 ~enc:to_b32 -end - -module RsaPublicKey = struct - module Binary = struct - (* https://www.gnupg.org/documentation/manuals/gcrypt/MPI-formats.html - format: { uint16_be: n size; uint16_be: e size; n; e} - - integer in big-endian format (MSB first) - leading zeroes are stripped unless they are required to keep a value positive - no 0-termination *) - - let rev_string len s = String.init len (fun i -> s.[len - 1 - i]) - - (* todo: need to strip leading zeros or something? *) - (* reverse bytes because Z.of_bits reads bytes in little endian *) - let z_of_bits_be src pos len = - String.sub src pos len |> rev_string len |> Z.of_bits - - let to_octets ({ n; e } : Mirage_crypto_pk.Rsa.pub) = - let n_len = Z.size n in - let e_len = Z.size e in - let len = 4 + n_len + e_len in - let b = Bytes.make len '\x00' in - Bytes.set_uint16_be b 0 n_len; - Bytes.set_uint16_be b 2 e_len; - let n = Z.to_bits n |> rev_string n_len in - let e = Z.to_bits e |> rev_string e_len in - Bytes.blit_string n 0 b 4 n_len; - Bytes.blit_string e 0 b (4 + n_len) e_len; - Bytes.unsafe_to_string b - - let of_octets = - let check = function - | false -> Error "RsaPublicKey.of_octets: invalid data" - | true -> Ok () - in - fun s -> - let open Syntax in - let len = String.length s in - let* () = check (len >= 4) in - let n_len = String.get_uint16_be s 0 in - let e_len = String.get_uint16_be s 2 in - let* () = check (len = n_len + e_len + 4) in - let n = z_of_bits_be s 4 n_len in - let e = z_of_bits_be s (4 + n_len) e_len in - Mirage_crypto_pk.Rsa.pub ~n ~e |> unwrap_err_msg - end - - include Binary - open Mirage_crypto_pk - - type t = Rsa.pub - - let of_b32 s = - let open Syntax in - let* s = B32.decode s in - let* v = of_octets s in - let+ v = Rsa.pub ~n:v.n ~e:v.e |> unwrap_err_msg in - v - - let to_b32 t = B32.encode (to_octets t) - let jsont = Jsont.of_of_string ~kind:"RsaPublicKey" of_b32 ~enc:to_b32 -end - -module RsaSignature : sig - type t - - val to_octets : t -> string - val sign : key:Mirage_crypto_pk.Rsa.priv -> string -> t - val of_b32 : string -> (t, string) result - val to_b32 : t -> string - val jsont : t Jsont.t -end = struct - type t = string - - let to_octets t = t - - (* TODO rsa sign *) - let sign ~key s = - Mirage_crypto_pk.Rsa.decrypt ~crt_hardening:true ~mask:`Yes ~key s - - let of_b32 s = B32.decode s - let to_b32 t = B32.encode t - let jsont = Jsont.of_of_string ~kind:"RsaSignature" of_b32 ~enc:to_b32 -end