Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server certificate validation #4015

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis-xs-opam.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -ex

PACKAGE="xapi"
PINS="xapi:. xapi-cli-protocol:. xapi-client:. xapi-consts:. xapi-database:. xapi-datamodel:. xapi-types:. xe:."
BASE_REMOTE="https://github.com/xapi-project/xs-opam.git"
BASE_REMOTE_BRANCH="feature/REQ-453/master"

wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-docker.sh
wget https://raw.githubusercontent.com/xapi-project/xs-opam/master/tools/xs-opam-ci.env
Expand Down
23 changes: 23 additions & 0 deletions ocaml/idl/datamodel_errors.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,29 @@ let _ =
error Api_errors.crl_corrupt ["name"]
~doc:"The specified CRL is corrupt or unreadable." ();

error Api_errors.server_certificate_key_invalid []
~doc:"Provided key is not in a pem-encoded PKCS#8 format." ();
error Api_errors.server_certificate_key_algorithm_not_supported ["algorithm_oid"]
~doc:"Provided key uses an unsupported algorithm." ();
error Api_errors.server_certificate_key_rsa_length_not_supported ["length"]
~doc:"Provided RSA key does not have a length between 2048 and 4096." ();
error Api_errors.server_certificate_key_rsa_multi_not_supported []
~doc:"Provided RSA key is using more than 2 primes, expecting only 2." ();

error Api_errors.server_certificate_invalid []
~doc:"Provided certificate is not in a pem-encoded X509." ();
error Api_errors.server_certificate_key_mismatch []
~doc:"Provided key does not match the provided certificate's public key." ();
error Api_errors.server_certificate_not_valid_yet ["now"; "not_before"]
~doc:"Provided certificate is not valid yet." ();
error Api_errors.server_certificate_expired ["now"; "not_after"]
~doc:"Provided certificate has expired." ();
error Api_errors.server_certificate_signature_not_supported []
~doc:"Provided certificate is not using the SHA256 (SHA2) signature algorithm." ();

error Api_errors.server_certificate_chain_invalid []
~doc:"Provided intermediate certificates are not in a pem-encoded X509." ();

error Api_errors.vmpp_has_vm []
~doc:"There is at least one VM assigned to this protection policy." ();
error Api_errors.vmpp_archive_more_frequent_than_backup []
Expand Down
19 changes: 6 additions & 13 deletions ocaml/tests/dune
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,14 @@ let coverage_rewriter =

let () = Printf.ksprintf Jbuild_plugin.V1.send {|

(executables
(names suite_alcotest)
(test
(name suite_alcotest)
(package xapi)
(flags (:standard -warn-error +a-3-4-6-9-27-28-29-52))
(libraries xapi_internal alcotest stdext threads)
(libraries xapi_internal alcotest nocrypto.unix stdext threads)
(preprocess (pps ppx_deriving_rpc ppx_sexp_conv %s))
)

(alias
(name runtest)
(package xapi)
(deps
(:x suite_alcotest.exe)
(source_tree test_data)
)
(action (run %%{x}))
(deps (source_tree test_data))
(action (run %%{test} --color=always))
)

(alias
Expand Down
1 change: 1 addition & 0 deletions ocaml/tests/suite_alcotest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ let () =
; "Test_vgpu_type", Test_vgpu_type.test
; "Test_storage_migrate_state", Test_storage_migrate_state.test
; "Test_bios_strings", Test_bios_strings.test
; "Test_certificates", Test_certificates.test
] @ Test_guest_agent.tests
@ Test_nm.tests
@ Test_xenopsd_metadata.tests
Expand Down
206 changes: 206 additions & 0 deletions ocaml/tests/test_certificates.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
(** This module tests the PKI validation
*)
open Certificates
open Api_errors

(* Initialize RNG for testing certificates *)
let () = Nocrypto_entropy_unix.initialize ()

let time_of_rfc3339 date =
match Ptime.of_rfc3339 date with
| Ok (time, _, _) -> time
| Error _ -> raise (Failure ("Date is in the wrong format: " ^ date))

let valid_from = time_of_rfc3339 "2020-01-01T00:00:00+00:00"

let valid_until = time_of_rfc3339 "2021-01-01T00:00:00+00:00"

let host_name = X509.Distinguished_name.Relative_distinguished_name.singleton (DC "localhost")

let load_test_data file = Stdext.Unixext.string_of_file @@ "test_data/certificates/" ^ file ^ ".pem"

let valid_private_keys = ["pkey_rsa_2048"; "pkey_rsa_4096"]

(* ( file_name, error_type, error_message list ) *)
let invalid_private_keys =
[ "pkey_rsa_1024", server_certificate_key_rsa_length_not_supported,
[ "1024" ]
; "pkey_rsa_8192", server_certificate_key_rsa_length_not_supported,
[ "8192" ]
; "pkey_rsa_n3_2048", server_certificate_key_rsa_multi_not_supported, []
; "pkey_ed25519", server_certificate_key_algorithm_not_supported,
[ "1.3.101.112" ]
; "pkey_bogus", server_certificate_key_invalid, []
]

(* ( description, leaf_private_key, time_of_validation, signature_algorithm ) *)
let valid_leaf_certificates =
[ "Valid, SHA256, matches key",
"pkey_rsa_2048",
"2020-02-01T00:00:00+00:00",
`SHA256 ]

(* ( description, leaf_private_key, expected_private_key, time_of_validation,
signature_algorithm, error_type, error_message list ) *)
let invalid_leaf_certificates =
[ "Not valid yet, SHA256, matching key",
"pkey_rsa_2048",
"pkey_rsa_2048",
"2019-01-01T00:00:00+00:00",
`SHA256,
server_certificate_not_valid_yet,
[ "2019-01-01T00:00:00-00:00"; "2020-01-01T00:00:00-00:00" ]
; "Expired, SHA256, matching key",
"pkey_rsa_2048",
"pkey_rsa_2048",
"2022-01-01T00:00:00+00:00",
`SHA256,
server_certificate_expired,
[ "2022-01-01T00:00:00-00:00"; "2021-01-01T00:00:00-00:00" ]
; "Valid, SHA256, keys do not match",
"pkey_rsa_2048",
"pkey_rsa_4096",
"2020-02-01T00:00:00+00:00",
`SHA256,
server_certificate_key_mismatch,
[]
; "Valid, SHA1, matching keys",
"pkey_rsa_2048",
"pkey_rsa_2048",
"2020-02-01T00:00:00+00:00",
`SHA1,
server_certificate_signature_not_supported,
[]
]

(* ( certificate_name, leaf_private_key, time_of_validation, error_tye,
error_message list ) *)
let corrupt_certificates =
[ "cert_bogus",
"pkey_rsa_2048",
"2020-02-01T00:00:00+00:00",
server_certificate_invalid,
[]
]

let key_chain = List.init 3 (fun _ -> `RSA (Nocrypto.Rsa.generate 2048))

(* ( certificate_name, leaf_private_key, time_of_validation, error_tye,
error_message list ) *)
let corrupt_chain_certificates =
[ "cert_bogus",
"pkey_rsa_2048",
"2020-02-01T00:00:00+00:00",
server_certificate_chain_invalid,
[]
]

let server_error err reason =
Server_error (err, reason)

let test_valid_key key_name () =
let _priv = validate_private_key (load_test_data key_name) in
()

let test_invalid_key key_name error reason () =
let key = load_test_data key_name in
Alcotest.check_raises ""
(server_error error reason)
(fun () -> let _priv = validate_private_key key in ())

let valid_keys_tests =
List.map (fun name ->
"Validation of a supported key: " ^ name, `Quick, test_valid_key name)
valid_private_keys

let invalid_keys_tests =
List.map (fun (name, error, reason) ->
"Validation of an unsupported key: " ^ name,
`Quick, test_invalid_key name error reason)
invalid_private_keys

let test_valid_cert cert time pkey () =
validate_certificate Leaf cert time pkey

let test_invalid_cert cert time pkey error reason () =
Alcotest.check_raises ""
(server_error error reason)
(fun () -> validate_certificate Leaf cert time pkey)

let load_pkcs8 name =
match X509.Private_key.decode_pem (Cstruct.of_string (load_test_data name)) with
| Ok pkey ->
pkey
| Error (`Msg msg) ->
raise (Failure
(Printf.sprintf "Could not load private key with name '%s': %s" name msg))

let sign_cert host_name ~pkey_sign digest pkey_leaf =
let csr = X509.Signing_request.create [host_name] ~digest pkey_leaf in
X509.Signing_request.sign csr ~valid_from ~valid_until ~digest pkey_sign [host_name]

let sign_leaf_cert host_name digest pkey_leaf =
let pkey_sign = load_pkcs8 "pkey_rsa_4096" in
sign_cert host_name ~pkey_sign digest pkey_leaf
|> X509.Certificate.encode_pem
|> Cstruct.to_string

let valid_leaf_cert_tests =
List.map (fun (name, pkey_leaf_name, time, digest) ->
let pkey_leaf = load_pkcs8 pkey_leaf_name in
let cert = sign_leaf_cert host_name digest pkey_leaf in
"Validation of a supported certificate: " ^ name,
`Quick, test_valid_cert cert (time_of_rfc3339 time) pkey_leaf)
valid_leaf_certificates

let corrupt_leaf_cert_tests =
List.map (fun (cert_name, pkey_name, time, error, reason) ->
let cert = load_test_data cert_name in
let pkey = load_pkcs8 pkey_name in
let time = time_of_rfc3339 time in
let test () = Alcotest.check_raises ""
(server_error error reason)
(fun () -> validate_certificate Leaf cert time pkey)
in
"Validation of a corrupted certificate", `Quick, test)
corrupt_certificates

let invalid_leaf_cert_tests =
List.map (fun (name, pkey_leaf_name, pkey_expected_name, time, digest, error, reason) ->
let pkey_leaf = load_pkcs8 pkey_leaf_name in
let pkey_expected = load_pkcs8 pkey_expected_name in
let cert = sign_leaf_cert host_name digest pkey_leaf in
"Validation of an unsupported certificate: " ^ name,
`Quick, test_invalid_cert cert (time_of_rfc3339 time) pkey_expected error reason)
invalid_leaf_certificates
@ corrupt_leaf_cert_tests

let test_valid_cert_chain chain time pkey () =
validate_certificate Chain chain time pkey

let test_invalid_cert_chain cert time pkey error reason () =
Alcotest.check_raises ""
(server_error error reason)
(fun () -> validate_certificate Chain cert time pkey)

let valid_chain_cert_tests =
let time = time_of_rfc3339 "2020-02-01T00:00:00+00:00" in
let pkey, chain = List.fold_left (fun (pkey_sign, chain) pkey ->
let cert = sign_cert host_name ~pkey_sign `SHA256 pkey in
pkey, cert :: chain
) ((load_pkcs8 "pkey_rsa_4096"), []) key_chain in
let chain = Cstruct.to_string (X509.Certificate.encode_pem_multiple chain) in
["Validation of a supported certificate chain",
`Quick, test_valid_cert_chain chain time pkey]

let invalid_chain_cert_tests =
List.map (fun (chain_name, pkey_name, time, error, reason) ->
let chain = load_test_data chain_name in
let pkey = load_pkcs8 pkey_name in
"Validation of an unsupported certificate chain",
`Quick, test_invalid_cert_chain chain (time_of_rfc3339 time) pkey error reason)
corrupt_chain_certificates

let test = valid_keys_tests @ invalid_keys_tests @
valid_leaf_cert_tests @ invalid_leaf_cert_tests @
valid_chain_cert_tests @ invalid_chain_cert_tests
3 changes: 3 additions & 0 deletions ocaml/tests/test_data/certificates/cert_bogus.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
Z9Gx2cmqSUZm
-----END CERTIFICATE-----
3 changes: 3 additions & 0 deletions ocaml/tests/test_data/certificates/pkey_bogus.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
deadbeef
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions ocaml/tests/test_data/certificates/pkey_ed25519.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIFSWIyNtBhwaMZ8UZZrrKtlufSZry496g/0BTj0UBcRe
-----END PRIVATE KEY-----
16 changes: 16 additions & 0 deletions ocaml/tests/test_data/certificates/pkey_rsa_1024.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKekmcudq8FP0iD6
eOnOrHhPF80cUuFDPSSP08q8NVMIcqX93OaX7ItLK5FJBCfXp4dBlWOA6k31/fog
ZqhdU3u40/yYOoqwcX69+ycofdhuy/ndBGtJ17pCKLwEz9JDywoJAB5UhTidAOv8
XL+pGZXMjhAgOv7yevWEYkcIotlFAgMBAAECgYEAoC0bbXtXEjtMEIqBYRRBoiog
GbBIWEdAcmCiAgFWCeH2eTdYC1N5hZXrKMPVHqRkw00ciZgxRxAU/RpcO93/1pi+
rNkQC4MMYVUgWe/r6WeQVHX2WO1ZrrLWprJSWtl5Mk4EEVw24w/J5Gam5pXrXxXr
ayJXsI8clKlVP1z7JDkCQQDUhfsjZ1+z22YfN2CbuBjueLCFeW+BnN6nVAuqmlpB
X5vBYcMLSnkEJi8Jqi9EG0Y0MMsFfXj2GTrTC6vxIed/AkEAyfAymTtA0mEcSQjn
cW4+x6BEy2r3+R12f93+5pA9fZERgv7JDA0qw7hjN1nNaa/aSfcj7uC8dHEV9bo5
6ZUBOwJAXh53+lXtrFmeAHUl89C0Oeh5RKNjHqO6d6BhCk0Ra6JL+HizSGzS62N9
sWoNRLxPew2g2+2VhGfoBU9F9qRtQQJBALhNDZEGICoh0TPi+YBv8sI0xrS0wu7M
cgtPa8W4HTbNwKLCsHDcT4xja8Hnpu7vPRXGhjgNSlInp7gmJ5bElGMCQQDNccPn
tnx0CPT4owj4t0U5lDYIpQTjRCtnz3Xi92NNrGL7BwL7c7fnwi57PN+2D+AI3HJr
OBTFz4BrXXvf12sH
-----END PRIVATE KEY-----
28 changes: 28 additions & 0 deletions ocaml/tests/test_data/certificates/pkey_rsa_2048.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDoqW6HB+8bVBr5
K/BHSi0oXK5frd3JfQXPj5UpOyu95KWeEAeZqQVJX61WssoXkRkLplB3/M9O7VcN
i8gg7tgmEdfJSPZf8XTkm7S1N/1bP9C3asGplX09mDJxOq4b1o5nVgYx7scrSAEw
pdVD85OhJzgdJc/bHOotJ/WkZLTrr55tv3N1B9B0c6AiL3+kCv2ab8RpSOobRKJa
fnZ74TO96f1J298lhGBHa/YTxvI7yxQBSkyqr+QdOwURYZpNbJBwqN+zkLPVyw6V
u2OpPerlTYKlaNAnHFsEdmsB5quw85Pu5qiKgBkYlxq59SZLazxKprS7rZOxmwGO
5vIArMXnAgMBAAECggEAAVPmM5MSBD/on39msaF0vIQ64oTlfR7fh520JjYWO9Ew
OMSypmfWheKr5JDEonnLgMuELgsdS8AC/SrNC5MRXLZBWv61mTqRhjR1RYt6QKkO
kd1kGYmm21EhCJBjKfmBeol9I4/PRyn0B8kKfIbJUuQcZ94tX3y8wWE2/6creSTW
o1KsIph+91chg7Eitvlji5La79OosvsArkYSmwiIkmXggCY/C+qy+Z9mRLvw/yS9
fcC4Y2MRba4G+J35rLap4rMHeo3wI5Y+224UYNFJjhLIqWF+6lwoe0At2XOOonI0
+eAZYQoHTmmv1RUiZafhnYX+5lcWtA489HaQ/Gx+UQKBgQD58ywwpykH4TSerUC5
sxmkWP5p2nJFgTRDEBEiHMPk3cT3Ixn84LiHvWBlsgzxdyIpSkAU33OAsSvlbiHA
tn1sPWzmVJgvdbEYq3W+GeQFRYqr2BVpXGJ//0RMReU/uQf3Zo/EcOMSnun/xfw0
RCNzRGNJq1lElHLjI0wgrxAgHQKBgQDuSyIBmAxChtvaj94RKQANTX1TPweQ/5Ko
V5nndvn8GVWxQaA1qkZxpSsgCJlU5xCGcRO0TErjmD05NRU2NYE2btfqmN4IH372
IyDQwN0QgArkYRF8y1wwTUy/KI+8vHphnCNhRHUWmdodKhHxfNKNMlZLWOS1ASHz
gHeZ4bYm0wKBgQDkY9sBKf1IAfR++XinNow7rEXsm3rHH+SNUuUU8ovdWPddi2l3
tC9peRXYVvWUvnMsn3gmmneEJGAj6JpW6Wl5ujcD0FCvLS+Bh0qzRh0g5AZuN6h4
+b3Xj3zs5QExVPV+juzXZv9WSHcxC8wERi7OaLMA4d1T2QGRovMw+kYV0QKBgQCL
G00kxRATHgrmEio+tkKTgXLxz8+o86k3oLv1hVz1sAx4R6kw6s/os9TzBFIOYD0j
Xot0Kz1kk5DZ/oT9ekqN7jMZflfPQiOpqGQVuSDT6wtwusGsqkKM5/MvfIw8T4LZ
zH5S39XCddoAQHjtEkMPrhz2K1SH3btmHM61BDCDhQKBgBF+Jk3MJHt6tob1WKkX
IxAEsCc/Gh5/lsIinQHRVpwpkQJP3livhhIRSunJlKXa4K9AITn09uJUbVLYlzJS
3meTHcDn2sHv4DeVePDZ6ER5YfMaciZtLvHX+ocKdgQMOfHcck5uV2thzE4m0F2P
sS8WJNFdh0WIl3gZHvKPGc3n
-----END PRIVATE KEY-----
52 changes: 52 additions & 0 deletions ocaml/tests/test_data/certificates/pkey_rsa_4096.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
V1q6H96p7LGCK7f1aS63n4lnYmdRM2hL
-----END PRIVATE KEY-----
Loading