Skip to content

Commit 104efe9

Browse files
authored
CA-405593: Do not write extraneous data into the host certificate file (#6263)
When installing host certificates, the parser used accepts string with random characters surrounding the PEM-encoded data. The ad-hoc parser used to read the host certificate file was unable to parse them. Because the PEM-encoded objects are copied as-is after validating them, the ad-hoc parser fails to read the file correctly when xapi restarts. This change fixes the issue by making sure that the written file's data has been sanitized, by using parsed datastructures instead of user-provided data. Parse, don't validate I've manually tested the fix on a host, before and after to validate that indeed now all the characters in between the PEM-encoded objects are stripped
2 parents 6949dbd + 8a6af5e commit 104efe9

File tree

3 files changed

+80
-49
lines changed

3 files changed

+80
-49
lines changed

ocaml/gencert/lib.ml

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ module D = Debug.Make (struct let name = "gencert_lib" end)
1717
open Api_errors
1818
open Rresult
1919

20-
type t_certificate = Leaf | Chain
21-
2220
let validate_private_key pkcs8_private_key =
2321
let ensure_rsa_key_length = function
2422
| `RSA priv ->
@@ -86,7 +84,7 @@ let validate_not_expired x ~error_not_yet ~error_expired ~error_invalid =
8684
_validate_not_expired ~now x ~error_not_yet ~error_expired ~error_invalid
8785
|> Rresult.R.reword_error @@ fun (`Msg (e, msgs)) -> Server_error (e, msgs)
8886

89-
let validate_certificate kind pem now private_key =
87+
let validate_pem_chain ~pem_leaf ~pem_chain now private_key =
9088
let ensure_keys_match private_key certificate =
9189
let public_key = X509.Certificate.public_key certificate in
9290
match (public_key, private_key) with
@@ -102,38 +100,55 @@ let validate_certificate kind pem now private_key =
102100
| _ ->
103101
Error (`Msg (server_certificate_signature_not_supported, []))
104102
in
105-
match kind with
106-
| Leaf ->
107-
_validate_not_expired ~now pem ~error_invalid:server_certificate_invalid
108-
~error_not_yet:server_certificate_not_valid_yet
109-
~error_expired:server_certificate_expired
110-
>>= ensure_keys_match private_key
111-
>>= ensure_sha256_signature_algorithm
112-
| Chain -> (
113-
let raw_pem = Cstruct.of_string pem in
114-
X509.Certificate.decode_pem_multiple raw_pem |> function
115-
| Ok (cert :: _) ->
116-
Ok cert
117-
| Ok [] ->
118-
D.info "Rejected certificate chain because it's empty." ;
119-
Error (`Msg (server_certificate_chain_invalid, []))
120-
| Error (`Msg err_msg) ->
121-
D.info {|Failed to validate certificate chain because "%s"|} err_msg ;
122-
Error (`Msg (server_certificate_chain_invalid, []))
123-
)
103+
let validate_chain pem_chain =
104+
let raw_pem = Cstruct.of_string pem_chain in
105+
X509.Certificate.decode_pem_multiple raw_pem |> function
106+
| Ok (_ :: _ as certs) ->
107+
Ok certs
108+
| Ok [] ->
109+
D.info "Rejected certificate chain because it's empty." ;
110+
Error (`Msg (server_certificate_chain_invalid, []))
111+
| Error (`Msg err_msg) ->
112+
D.info {|Failed to validate certificate chain because "%s"|} err_msg ;
113+
Error (`Msg (server_certificate_chain_invalid, []))
114+
in
115+
_validate_not_expired ~now pem_leaf ~error_invalid:server_certificate_invalid
116+
~error_not_yet:server_certificate_not_valid_yet
117+
~error_expired:server_certificate_expired
118+
>>= ensure_keys_match private_key
119+
>>= ensure_sha256_signature_algorithm
120+
>>= fun cert ->
121+
match Option.map validate_chain pem_chain with
122+
| None ->
123+
Ok (cert, None)
124+
| Some (Ok chain) ->
125+
Ok (cert, Some chain)
126+
| Some (Error msg) ->
127+
Error msg
124128

129+
(** Decodes the PEM-encoded objects (private key, leaf certificate, and
130+
certificate chain, reencodes them to make sure they are normalised, and
131+
finally it installs them as a server certificate to be ready to use by
132+
stunnel. It also ensures the objects maintian some cryptographic
133+
properties. *)
125134
let install_server_certificate ~pem_chain ~pem_leaf ~pkcs8_private_key
126135
~server_cert_path ~cert_gid =
127136
let now = Ptime_clock.now () in
128137
validate_private_key pkcs8_private_key >>= fun priv ->
129-
validate_certificate Leaf pem_leaf now priv >>= fun cert ->
138+
let pkcs8_private_key =
139+
X509.Private_key.encode_pem priv |> Cstruct.to_string
140+
in
141+
validate_pem_chain ~pem_leaf ~pem_chain now priv >>= fun (cert, chain) ->
142+
let pem_leaf = X509.Certificate.encode_pem cert |> Cstruct.to_string in
130143
Option.fold
131144
~none:(Ok [pkcs8_private_key; pem_leaf])
132-
~some:(fun pem_chain ->
133-
validate_certificate Chain pem_chain now priv >>= fun _ignored ->
145+
~some:(fun chain ->
146+
let pem_chain =
147+
X509.Certificate.encode_pem_multiple chain |> Cstruct.to_string
148+
in
134149
Ok [pkcs8_private_key; pem_leaf; pem_chain]
135150
)
136-
pem_chain
151+
chain
137152
>>= fun server_cert_components ->
138153
server_cert_components
139154
|> String.concat "\n\n"

ocaml/gencert/lib.mli

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,19 @@ val validate_not_expired :
4343
(** The following functions are exposed exclusively for unit-testing, please
4444
do not use them directly, they are not stable *)
4545

46-
type t_certificate = Leaf | Chain
47-
4846
val validate_private_key :
4947
string
5048
-> ( [> `RSA of Mirage_crypto_pk.Rsa.priv]
5149
, [> `Msg of string * string list]
5250
)
5351
Result.result
5452

55-
val validate_certificate :
56-
t_certificate
57-
-> string
53+
val validate_pem_chain :
54+
pem_leaf:string
55+
-> pem_chain:string option
5856
-> Ptime.t
5957
-> [> `RSA of Mirage_crypto_pk.Rsa.priv]
60-
-> (X509.Certificate.t, [> `Msg of string * string list]) Rresult.result
58+
-> ( X509.Certificate.t * X509.Certificate.t list option
59+
, [> `Msg of string * string list]
60+
)
61+
Result.t

ocaml/gencert/test_lib.ml

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ let invalid_keys_tests =
162162
)
163163
invalid_private_keys
164164

165-
let test_valid_cert ~kind cert time pkey =
166-
match validate_certificate kind cert time pkey with
165+
let test_valid_leaf_cert pem_leaf time pkey () =
166+
match validate_pem_chain ~pem_leaf ~pem_chain:None time pkey with
167167
| Ok _ ->
168168
()
169169
| Error (`Msg (_, msg)) ->
@@ -173,8 +173,8 @@ let test_valid_cert ~kind cert time pkey =
173173
msg
174174
)
175175

176-
let test_invalid_cert ~kind cert time pkey error reason =
177-
match validate_certificate kind cert time pkey with
176+
let test_invalid_cert pem_leaf time pkey error reason =
177+
match validate_pem_chain ~pem_leaf ~pem_chain:None time pkey with
178178
| Ok _ ->
179179
Alcotest.fail "Invalid certificate was validated without errors"
180180
| Error (`Msg msg) ->
@@ -203,9 +203,6 @@ let sign_leaf_cert host_name digest pkey_leaf =
203203
>>| Cstruct.to_string
204204

205205
let valid_leaf_cert_tests =
206-
let test_valid_leaf_cert cert time pkey () =
207-
test_valid_cert ~kind:Leaf cert time pkey
208-
in
209206
List.map
210207
(fun (name, pkey_leaf_name, time, digest) ->
211208
let cert_test =
@@ -222,15 +219,15 @@ let test_corrupt_leaf_cert (cert_name, pkey_name, time, error, reason) =
222219
let time = time_of_rfc3339 time in
223220
let test_cert =
224221
load_pkcs8 pkey_name >>| fun pkey ->
225-
let test () = test_invalid_cert ~kind:Leaf cert time pkey error reason in
222+
let test () = test_invalid_cert cert time pkey error reason in
226223
test
227224
in
228225
("Validation of a corrupted certificate", `Quick, test_cert)
229226

230227
let test_invalid_leaf_cert
231228
(name, pkey_leaf_name, pkey_expected_name, time, digest, error, reason) =
232229
let test_invalid_leaf_cert cert time pkey error reason () =
233-
test_invalid_cert ~kind:Leaf cert time pkey error reason
230+
test_invalid_cert cert time pkey error reason
234231
in
235232
let test_cert =
236233
load_pkcs8 pkey_leaf_name >>= fun pkey_leaf ->
@@ -245,17 +242,30 @@ let invalid_leaf_cert_tests =
245242
List.map test_corrupt_leaf_cert corrupt_certificates
246243
@ List.map test_invalid_leaf_cert invalid_leaf_certificates
247244

248-
let test_valid_cert_chain chain time pkey () =
249-
test_valid_cert ~kind:Chain chain time pkey
245+
let test_valid_cert_chain ~pem_leaf ~pem_chain time pkey () =
246+
match validate_pem_chain ~pem_leaf ~pem_chain:(Some pem_chain) time pkey with
247+
| Ok _ ->
248+
()
249+
| Error (`Msg (_, msg)) ->
250+
Alcotest.fail
251+
(Format.asprintf "Valid certificate chain could not be validated: %a"
252+
Fmt.(Dump.list string)
253+
msg
254+
)
250255

251-
let test_invalid_cert_chain cert time pkey error reason () =
252-
test_invalid_cert ~kind:Chain cert time pkey error reason
256+
let test_invalid_cert_chain pem_leaf pem_chain time pkey error reason () =
257+
match validate_pem_chain ~pem_leaf ~pem_chain:(Some pem_chain) time pkey with
258+
| Ok _ ->
259+
Alcotest.fail "Invalid certificate chain was validated without errors"
260+
| Error (`Msg msg) ->
261+
Alcotest.(check @@ pair string @@ list string)
262+
"Error must match" (error, reason) msg
253263

254264
let valid_chain_cert_tests =
255265
let time = time_of_rfc3339 "2020-02-01T00:00:00Z" in
256266
let test_cert =
257267
load_pkcs8 "pkey_rsa_4096" >>= fun pkey_root ->
258-
let pkey, chain =
268+
let pkey_leaf, chain =
259269
List.fold_left
260270
(fun (pkey_sign, chain_result) pkey ->
261271
let result =
@@ -267,8 +277,10 @@ let valid_chain_cert_tests =
267277
)
268278
(pkey_root, Ok []) key_chain
269279
in
280+
sign_leaf_cert host_name `SHA256 pkey_leaf >>= fun pem_leaf ->
270281
chain >>| X509.Certificate.encode_pem_multiple >>| Cstruct.to_string
271-
>>| fun chain -> test_valid_cert_chain chain time pkey
282+
>>| fun pem_chain ->
283+
test_valid_cert_chain ~pem_leaf ~pem_chain time pkey_leaf
272284
in
273285
[("Validation of a supported certificate chain", `Quick, test_cert)]
274286

@@ -277,8 +289,11 @@ let invalid_chain_cert_tests =
277289
(fun (chain_name, pkey_name, time, error, reason) ->
278290
let chain = load_test_data chain_name in
279291
let test_cert =
280-
load_pkcs8 pkey_name >>| fun pkey ->
281-
test_invalid_cert_chain chain (time_of_rfc3339 time) pkey error reason
292+
(* Need to load a valid key and leaf cert *)
293+
load_pkcs8 pkey_name >>= fun pkey ->
294+
sign_leaf_cert host_name `SHA256 pkey >>| fun cert ->
295+
test_invalid_cert_chain cert chain (time_of_rfc3339 time) pkey error
296+
reason
282297
in
283298
("Validation of an unsupported certificate chain", `Quick, test_cert)
284299
)

0 commit comments

Comments
 (0)