Skip to content

Commit

Permalink
support letsencrypt (#56)
Browse files Browse the repository at this point in the history
* Add more serialization formats to JWS

* Add ES384 keys

* Allow creating JWK from x509 keys directly

* Allow adding extra headers

* Cleanup and add tests

* Add changes to CHANGES.md
  • Loading branch information
ulrikstrid authored Feb 16, 2023
1 parent ad829c5 commit 8959edd
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 87 deletions.
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
next
--------------
- Support all serialization formats, previously only the compact serialization was supported, now we support both general and flattened JSON format (by @ulrikstrid)
- Add support for ES384 (P-384 with SHA384) (by @ulrikstrid)
- Allow creating a JWK from X509 keys directly (by @ulrikstrid)
- Support extra headers (by @ulrikstrid)

0.8.2
--------------
- JWS now properly checks the signature. Reported by @nankeen and fixed by @ulrikstrid. CVE-2023-23928
Expand Down
44 changes: 34 additions & 10 deletions jose/Header.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ open Utils

type t = {
alg : Jwa.alg;
jku : string option;
jwk : Jwk.public Jwk.t option;
kid : string option;
x5t : string option;
x5t256 : string option;
typ : string option;
cty : string option;
enc : Jwa.enc option;
extra : (string * Yojson.Safe.t) list option;
}

let make_header ?typ ?alg ?enc (jwk : Jwk.priv Jwk.t) =
(* TODO: This is probably very slow *)
let remove_supported (l : (string * Yojson.Safe.t) list) =
l |> List.remove_assoc "alg" |> List.remove_assoc "jwk"
|> List.remove_assoc "kid" |> List.remove_assoc "x5t"
|> List.remove_assoc "x5t#256"
|> List.remove_assoc "typ" |> List.remove_assoc "cty"
|> List.remove_assoc "enc"

let make_header ?typ ?alg ?enc ?(extra = []) ?(jwk_header = false)
(jwk : Jwk.priv Jwk.t) =
let alg =
match alg with
| Some alg -> alg
Expand All @@ -21,28 +30,41 @@ let make_header ?typ ?alg ?enc (jwk : Jwk.priv Jwk.t) =
| Jwk.Rsa_priv _ -> `RS256
| Jwk.Oct _ -> `HS256
| Jwk.Es256_priv _ -> `ES256
| Jwk.Es384_priv _ -> `ES384
| Jwk.Es512_priv _ -> `ES512)
in
let kid =
match List.assoc_opt "kid" extra with
| Some kid -> Some (Yojson.Safe.Util.to_string kid)
| None -> Jwk.get_kid jwk
in
let extra = remove_supported extra in
{
alg;
jku = None;
jwk = None;
kid = Jwk.get_kid jwk;
jwk = (if jwk_header then Some (Jwk.pub_of_priv jwk) else None);
kid;
x5t = None;
x5t256 = None;
typ;
cty = None;
enc;
extra = (match extra with [] -> None | extra -> Some extra);
}

module Json = Yojson.Safe.Util

let get_extra_headers (json : Yojson.Safe.t) =
match json with
| `Assoc vals -> (
let extra = remove_supported vals in
match extra with [] -> None | extra -> Some extra)
| _ -> None (* TODO: raise here? *)

let of_json json =
try
Ok
{
alg = json |> Json.member "alg" |> Jwa.alg_of_json;
jku = json |> Json.member "jku" |> Json.to_string_option;
jwk =
json |> Json.member "jwk"
|> Json.to_option (fun jwk_json ->
Expand All @@ -56,6 +78,7 @@ let of_json json =
enc =
json |> Json.member "enc" |> Json.to_string_option
|> U_Opt.map Jwa.enc_of_string;
extra = get_extra_headers json;
}
with Json.Type_error (s, _) -> Error (`Msg s)

Expand All @@ -64,17 +87,18 @@ let to_json t =
[
RJson.to_json_string_opt "typ" t.typ;
Some ("alg", Jwa.alg_to_json t.alg);
RJson.to_json_string_opt "jku" t.jku;
U_Opt.map Jwk.to_pub_json t.jwk |> U_Opt.map (fun jwk -> ("jwk", jwk));
RJson.to_json_string_opt "kid" t.kid;
U_Opt.map Jwk.to_pub_json t.jwk |> U_Opt.map (fun jwk -> ("jwk", jwk));
RJson.to_json_string_opt "x5t" t.x5t;
RJson.to_json_string_opt "x5t#256" t.x5t256;
RJson.to_json_string_opt "cty" t.cty;
t.enc |> U_Opt.map Jwa.enc_to_string
t.enc
|> U_Opt.map Jwa.enc_to_string
|> U_Opt.map (fun enc -> ("enc", `String enc));
]
in
`Assoc (U_List.filter_map (fun x -> x) values)
let extra = Option.value ~default:[] t.extra in
`Assoc (U_List.filter_map (fun x -> x) values @ extra)

let of_string header_str =
U_Base64.url_decode header_str
Expand Down
73 changes: 57 additions & 16 deletions jose/Jose.mli
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Jwa : sig
[ `RS256 (** HMAC using SHA-256 *)
| `HS256 (** RSASSA-PKCS1-v1_5 using SHA-256 *)
| `ES256 (** ECDSA using P-256 and SHA-256 *)
| `ES384 (** ECDSA using P-384 and SHA-384 *)
| `ES512 (** ECDSA using P-521 and SHA-512 *)
| `RSA_OAEP (** RSAES OAEP using default parameters *)
| `RSA1_5 (** RSA PKCS 1 *)
Expand Down Expand Up @@ -79,6 +80,12 @@ module Jwk : sig
type pub_es256 = Mirage_crypto_ec.P256.Dsa.pub jwk
(** [es256] represents a private JWK with [kty] [`EC] and a [P256.priv] key *)

type priv_es384 = Mirage_crypto_ec.P384.Dsa.priv jwk
(** [es384] represents a public JWK with [kty] [`EC] and a [P384.pub] key *)

type pub_es384 = Mirage_crypto_ec.P384.Dsa.pub jwk
(** [es384] represents a private JWK with [kty] [`EC] and a [P384.priv] key *)

type priv_es512 = Mirage_crypto_ec.P521.Dsa.priv jwk
(** [es512] represents a public JWK with [kty] [`EC] and a [P512.pub] key *)

Expand All @@ -92,6 +99,8 @@ module Jwk : sig
| Rsa_pub : pub_rsa -> public t
| Es256_priv : priv_es256 -> priv t
| Es256_pub : pub_es256 -> public t
| Es384_priv : priv_es384 -> priv t
| Es384_pub : pub_es384 -> public t
| Es512_priv : priv_es512 -> priv t
| Es512_pub : pub_es512 -> public t

Expand Down Expand Up @@ -154,6 +163,16 @@ module Jwk : sig
(** [to_priv_pem t] takes a JWK and returns a result PEM string or a message
of what went wrong. *)

val of_priv_x509 :
?use:use ->
X509.Private_key.t ->
(priv t, [> `Msg of string | `Not_rsa ]) result

val of_pub_x509 :
?use:use ->
X509.Public_key.t ->
(public t, [> `Msg of string | `Not_rsa ]) result

val of_priv_json :
Yojson.Safe.t ->
( priv t,
Expand Down Expand Up @@ -234,29 +253,39 @@ end
module Header : sig
type t = {
alg : Jwa.alg;
jku : string option;
jwk : Jwk.public Jwk.t option;
kid : string option;
x5t : string option;
x5t256 : string option;
typ : string option;
cty : string option;
enc : Jwa.enc option;
extra : (string * Yojson.Safe.t) list option;
}
(** The [header] has the following properties: - [alg] Jwa - RS256 and none is
currently the only supported algs - [jku] JWK Set URL - [jwk] JSON Web Key
- [kid] Key ID - We currently always expect this to be there, this can
change in the future - [x5t] X.509 Certificate SHA-1 Thumbprint -
[x5t#S256] X.509 Certificate SHA-256 Thumbprint - [typ] Type - [cty]
Content Type Not implemented: - [x5u] X.509 URL - [x5c] X.509 Certficate
Chain - [crit] Critical
(** The [header] has the following properties:
- [alg] {! Jwa.alg }
- [jwk] JSON Web Key
- [kid] Key ID - We currently always expect this to be there, this can change in the future
- [x5t] X.509 Certificate SHA-1 Thumbprint -
- [x5t#S256] X.509 Certificate SHA-256 Thumbprint
- [typ] Type
- [cty] Content Type Not implemented
{{: https://tools.ietf.org/html/rfc7515#section-4.1 } Link to RFC }
{{: https://tools.ietf.org/html/rfc7515#section-4.1 } Link to RFC } *)
{{: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-header-parameters } Complete list of registered header parameters} *)

val make_header :
?typ:string -> ?alg:Jwa.alg -> ?enc:Jwa.enc -> Jwk.priv Jwk.t -> t
?typ:string ->
?alg:Jwa.alg ->
?enc:Jwa.enc ->
?extra:(string * Yojson.Safe.t) list ->
?jwk_header:bool ->
Jwk.priv Jwk.t ->
t
(** [make_header typ alg enc jwk] if [alg] is not provided it will be derived
from [jwk]. *)
from [jwk]. [jwk_header] decides if the jwk should be put in the header. *)

val of_string : string -> (t, [> `Msg of string ]) result
val to_string : t -> string
Expand All @@ -277,8 +306,12 @@ module Jws : sig
signature : signature;
}

val of_string : string -> (t, [> `Msg of string ]) result
val to_string : t -> string
type serialization = [ `Compact | `General | `Flattened ]

val of_string :
string -> (t, [> `Msg of string | `Not_json | `Not_supported ]) result

val to_string : ?serialization:serialization -> t -> string

val validate :
jwk:'a Jwk.t -> t -> (t, [> `Invalid_signature | `Msg of string ]) result
Expand Down Expand Up @@ -315,15 +348,23 @@ module Jwt : sig
val get_yojson_claim : t -> string -> Yojson.Safe.t option
val get_string_claim : t -> string -> string option
val get_int_claim : t -> string -> int option
val to_string : t -> string
val to_string : ?serialization:Jws.serialization -> t -> string

val of_string :
jwk:'a Jwk.t ->
string ->
(t, [> `Expired | `Invalid_signature | `Msg of string ]) result
( t,
[> `Expired
| `Invalid_signature
| `Msg of string
| `Not_json
| `Not_supported ] )
result
(** [of_string ~jwk jwt_string] parses and validates the encoded JWT string. *)

val unsafe_of_string : string -> (t, [> `Msg of string ]) result
val unsafe_of_string :
string -> (t, [> `Msg of string | `Not_json | `Not_supported ]) result

val to_jws : t -> Jws.t
val of_jws : Jws.t -> t

Expand Down
3 changes: 3 additions & 0 deletions jose/Jwa.ml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type alg =
[ `RS256 (** HMAC using SHA-256 *)
| `HS256 (** RSASSA-PKCS1-v1_5 using SHA-256 *)
| `ES256 (** ECDSA using P-256 and SHA-256 *)
| `ES384 (** ECDSA using P-384 and SHA-384 *)
| `ES512 (** ECDSA using P-521 and SHA-512 *)
| `RSA_OAEP (** RSAES OAEP using default parameters *)
| `RSA1_5 (** RSA PKCS 1 *)
Expand All @@ -30,6 +31,7 @@ let alg_to_string = function
| `RS256 -> "RS256"
| `HS256 -> "HS256"
| `ES256 -> "ES256"
| `ES384 -> "ES384"
| `ES512 -> "ES512"
| `RSA_OAEP -> "RSA-OAEP"
| `RSA1_5 -> "RSA1_5"
Expand All @@ -40,6 +42,7 @@ let alg_of_string = function
| "RS256" -> `RS256
| "HS256" -> `HS256
| "ES256" -> `ES256
| "ES384" -> `ES384
| "ES512" -> `ES512
| "RSA-OAEP" -> `RSA_OAEP
| "RSA1_5" -> `RSA1_5
Expand Down
2 changes: 2 additions & 0 deletions jose/Jwe.ml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ let encrypt_cek (type a) alg (cek : string) ~(jwk : a Jwk.t) =
| Oct _ -> Error `Unsupported_kty
| Es256_priv _ -> Error `Unsupported_kty
| Es256_pub _ -> Error `Unsupported_kty
| Es384_priv _ -> Error `Unsupported_kty
| Es384_pub _ -> Error `Unsupported_kty
| Es512_priv _ -> Error `Unsupported_kty
| Es512_pub _ -> Error `Unsupported_kty)
>>= fun key ->
Expand Down
Loading

0 comments on commit 8959edd

Please sign in to comment.