From patchwork Thu Aug 22 15:08:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773701 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 9AA1AC3DA4A for ; Thu, 22 Aug 2024 15:10:07 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9RN-0007TW-Pz; Thu, 22 Aug 2024 11:09:34 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9R6-0007Ox-1T for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:16 -0400 Received: from mail-pj1-x102a.google.com ([2607:f8b0:4864:20::102a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9R4-00050O-7h for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:15 -0400 Received: by mail-pj1-x102a.google.com with SMTP id 98e67ed59e1d1-2d3b36f5366so698795a91.0 for ; Thu, 22 Aug 2024 08:09:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339352; x=1724944152; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=XK1RrmnAAcLJEruO+a0NiCbV8+je+zVBS/hWa0cHNWg=; b=btqkDlnf0pAUI6V5DTSDKLosmiqvUHUhhRF6e/tzn+n6BGkFKuZKhdQufr0rThNWZi p7pXI6cIL9bRfeizuXDRbfEwRVVBW/bt5AVOGBLhGXa4Ml1QtuRUVL0NYqI1pYOG4VDz CvlDwz20V1NO6loZ1AOxlJY/mLn2h4TMqBTfy7Ex10PbEPwDeXSd+U2EjUXMzfYNd57S TNUYaxrWh5BPK7ufA0mXgtTN9wiLp/PivSyiG5YX/UV/CMZ+XmYujMly/F0/9qmDRr0D Os7RLebSmDOvTXc0M9ZZvLyEtVHX2hxT6xrwiW3THCJh6/aqsFIv5fjnyzGEilPhQqow tpBw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339352; x=1724944152; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=XK1RrmnAAcLJEruO+a0NiCbV8+je+zVBS/hWa0cHNWg=; b=G8mLjJhwGTVxRYA3ZPZDFxAoabd+arHtlOmmhub8afLWjw30cejf5CtdmwcYpgBzFX /4EK7Iqsgco51bdlRVb19e5FSrTnS1xS6SoA4UhKpYM4MMup7zJpCvQIyUeTBlvQ057N 4/vGS1nuxUmys0QSehjyXsz3LvmbnpFaAja+L24Aax5EU+MrDmXu0J3cqqgLActaUJq0 Ziqkb6iYWrkKWFpPb4qwFsWydYz8N7jXAletbuTEUk0ej6i1V1v1cv9R+pYFA8xtaL4o 5h6ZN7An4NYN0aPnBf0EPpJLz2XtqOcIfYTp/VivSNRwwFP4ulZPAS32s2VEZK7ehXBf 2qgA== X-Gm-Message-State: AOJu0YxZjgcJjT92KjW6LKtZlXmdu0wd8B5sN17IXOUyv9rxF4ylxxBS MJoWe9ajfSNjl/8dHUIw+8nMRz6P5QxDyN/O2eJeDgz4rqSsu1d1Qmq2/vEk X-Google-Smtp-Source: AGHT+IHG0jXqivqvKmSSe2RzYuqQ5q50Okm/jOt+3NKEUgDMBpJPgHBeJ45sDnyBxSO9n+SwKS9/rA== X-Received: by 2002:a17:90a:640b:b0:2c9:8105:483 with SMTP id 98e67ed59e1d1-2d5e9a3fb15mr6482103a91.14.1724339352543; Thu, 22 Aug 2024 08:09:12 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:12 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 1/8] crypto: Define macros for hash algorithm digest lengths Date: Thu, 22 Aug 2024 21:08:42 +0600 Message-Id: <20240822150849.21759-2-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::102a; envelope-from=dorjoychy111@gmail.com; helo=mail-pj1-x102a.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Signed-off-by: Dorjoy Chowdhury Reviewed-by: Daniel P. Berrangé --- crypto/hash.c | 14 +++++++------- include/crypto/hash.h | 8 ++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crypto/hash.c b/crypto/hash.c index b0f8228bdc..8087f5dae6 100644 --- a/crypto/hash.c +++ b/crypto/hash.c @@ -23,13 +23,13 @@ #include "hashpriv.h" static size_t qcrypto_hash_alg_size[QCRYPTO_HASH_ALG__MAX] = { - [QCRYPTO_HASH_ALG_MD5] = 16, - [QCRYPTO_HASH_ALG_SHA1] = 20, - [QCRYPTO_HASH_ALG_SHA224] = 28, - [QCRYPTO_HASH_ALG_SHA256] = 32, - [QCRYPTO_HASH_ALG_SHA384] = 48, - [QCRYPTO_HASH_ALG_SHA512] = 64, - [QCRYPTO_HASH_ALG_RIPEMD160] = 20, + [QCRYPTO_HASH_ALG_MD5] = QCRYPTO_HASH_DIGEST_LEN_MD5, + [QCRYPTO_HASH_ALG_SHA1] = QCRYPTO_HASH_DIGEST_LEN_SHA1, + [QCRYPTO_HASH_ALG_SHA224] = QCRYPTO_HASH_DIGEST_LEN_SHA224, + [QCRYPTO_HASH_ALG_SHA256] = QCRYPTO_HASH_DIGEST_LEN_SHA256, + [QCRYPTO_HASH_ALG_SHA384] = QCRYPTO_HASH_DIGEST_LEN_SHA384, + [QCRYPTO_HASH_ALG_SHA512] = QCRYPTO_HASH_DIGEST_LEN_SHA512, + [QCRYPTO_HASH_ALG_RIPEMD160] = QCRYPTO_HASH_DIGEST_LEN_RIPEMD160, }; size_t qcrypto_hash_digest_len(QCryptoHashAlgorithm alg) diff --git a/include/crypto/hash.h b/include/crypto/hash.h index 54d87aa2a1..a113cc3b04 100644 --- a/include/crypto/hash.h +++ b/include/crypto/hash.h @@ -23,6 +23,14 @@ #include "qapi/qapi-types-crypto.h" +#define QCRYPTO_HASH_DIGEST_LEN_MD5 16 +#define QCRYPTO_HASH_DIGEST_LEN_SHA1 20 +#define QCRYPTO_HASH_DIGEST_LEN_SHA224 28 +#define QCRYPTO_HASH_DIGEST_LEN_SHA256 32 +#define QCRYPTO_HASH_DIGEST_LEN_SHA384 48 +#define QCRYPTO_HASH_DIGEST_LEN_SHA512 64 +#define QCRYPTO_HASH_DIGEST_LEN_RIPEMD160 20 + /* See also "QCryptoHashAlgorithm" defined in qapi/crypto.json */ /** From patchwork Thu Aug 22 15:08:43 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773708 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 3E7D4C531DF for ; Thu, 22 Aug 2024 15:11:38 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9RR-0007eS-2N; Thu, 22 Aug 2024 11:09:37 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9RD-0007Sr-Nk for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:23 -0400 Received: from mail-pg1-x52f.google.com ([2607:f8b0:4864:20::52f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9RB-000527-On for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:23 -0400 Received: by mail-pg1-x52f.google.com with SMTP id 41be03b00d2f7-7ae3d7222d4so737132a12.3 for ; Thu, 22 Aug 2024 08:09:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339360; x=1724944160; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2cUhzbDO1c4TVr/5fHme4bDVdctPUWA406Qmu6O3nZE=; b=UyQdEYgp/OaUJPvLPJ9X/6+OX6fYhYbgE1sK9rjQZtCWVUSo8xSwJ/zx8urt43Catg ytDieAnvxQWmWyPCYBAyUChitZ4LtpQKmox+te0nEY8lhWIOFZiLZuvIPJ33Cmfn5p3s y3cTQxhn0egQ4/zb0D67Nh0Wz0CWz+MlA1i2xmVArlRJXXvSaShoO+dFiuCFwKis1OE8 uD81PGIGSzYAleYCWhAZxsSSZU/KDVoLw2LMJUexz1Zjzk2Art0CfBWjJQVYmkQCUwXg wVn2jxNQBZT4kUqB9eytkXug9hCYVPhLjvJuzcnGg1w23Bo1UUTZHrjKs54gXVPgFfpn 8lpA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339360; x=1724944160; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=2cUhzbDO1c4TVr/5fHme4bDVdctPUWA406Qmu6O3nZE=; b=jQdp6JTkIWxfyMzX2xEiD7ELPv8nLz8cS0koE1p7sq0Kbsh2USSFW2d2UFlhIbafcE r95UphuBEIiksBNDFpsZU5bVad0rL3CmQ5EIaOizJ+xR4TdYv1UQPHE4yqU8z+FhcwaM kgko1tOrrBj3XbzAnT9vcluaSqQbTX6J1v8d4ngmMRgjC4W0lvTUElLceuvFES+rZrpo 8hn2E4sPxJsSRn+5vBPLjIrrDKm8AToxtAV60CpGjT45jTJ1DIc0XpDw7oWzDEDkr2jj BRLjXeWdeE2WiE5REKRLEuVw66F7YjITG+GbPamI6tvu/XfgYuuOh3L5Q8TSLw9E5dXq jkAg== X-Gm-Message-State: AOJu0YzX2RB6sBBJgOk4J1eUhWuMkgvSlPJViA/70riYmfLqKB50N9pi BjZ7A5ye4rU87x/DTKILKtyFcH0vl6a/jAr+20Tt5F0pPf0f9MyvWiAjGg== X-Google-Smtp-Source: AGHT+IHky4Et50cZ+iuguauLB28SxkMo5O6AdajkaYSf2qzZO9zdpcBCUAU5dqtchRlKn8o387WWBg== X-Received: by 2002:a17:90a:ba96:b0:2d1:ba43:1303 with SMTP id 98e67ed59e1d1-2d5e9b9a03bmr6526231a91.21.1724339359905; Thu, 22 Aug 2024 08:09:19 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:19 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 2/8] crypto: Support SHA384 hash when using glib Date: Thu, 22 Aug 2024 21:08:43 +0600 Message-Id: <20240822150849.21759-3-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::52f; envelope-from=dorjoychy111@gmail.com; helo=mail-pg1-x52f.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org QEMU requires minimum glib version 2.66.0 as per the root meson.build file and per glib documentation[1] G_CHECKSUM_SHA384 is available since 2.51. [1] https://docs.gtk.org/glib/enum.ChecksumType.html Reviewed-by: Daniel P. Berrangé Signed-off-by: Dorjoy Chowdhury --- crypto/hash-glib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/hash-glib.c b/crypto/hash-glib.c index 82de9db705..18e64faa9c 100644 --- a/crypto/hash-glib.c +++ b/crypto/hash-glib.c @@ -29,7 +29,7 @@ static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG__MAX] = { [QCRYPTO_HASH_ALG_SHA1] = G_CHECKSUM_SHA1, [QCRYPTO_HASH_ALG_SHA224] = -1, [QCRYPTO_HASH_ALG_SHA256] = G_CHECKSUM_SHA256, - [QCRYPTO_HASH_ALG_SHA384] = -1, + [QCRYPTO_HASH_ALG_SHA384] = G_CHECKSUM_SHA384, [QCRYPTO_HASH_ALG_SHA512] = G_CHECKSUM_SHA512, [QCRYPTO_HASH_ALG_RIPEMD160] = -1, }; From patchwork Thu Aug 22 15:08:44 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773703 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id A0268C531DF for ; Thu, 22 Aug 2024 15:10:07 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9RV-0007lT-AD; Thu, 22 Aug 2024 11:09:42 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9RQ-0007dq-0z for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:36 -0400 Received: from mail-pj1-x102f.google.com ([2607:f8b0:4864:20::102f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9RI-00052O-H5 for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:35 -0400 Received: by mail-pj1-x102f.google.com with SMTP id 98e67ed59e1d1-2d3b595c18dso1547410a91.0 for ; Thu, 22 Aug 2024 08:09:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339367; x=1724944167; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=GeAwo6rJ9Ssp7fNXqJixt1EPPvNs6AeUb8bCCXYa7xY=; b=hB8yH1v1XsMIB+frIz8vnQpASVGzCH3uSy4Laj3zgqx4PE7PUVvnvKyGC7Bw88tGcj gbNM6x1LUqbzeYx20oO+GeiQ1qyuodwWQiUFfInTon3NOnLEv/2bjHfNtguhj4w3o9/w Tb+E0xMdbcydWHQgxYYHsEvCoHSPXUek4/wDJWD8tRY85CqClKdrCyP5iUcbXX3qSXvl o5WkvKtMWJT5m8o4tBE5hdW/qIODTjL+N9H8iBF5dqIP+RUF6wMDbe5HPmbbQQoyUIPJ 8Org8LkXVShYlnuQwZzJLlv23Rruqe5nBA9np5RIA427LF4AwlbcbQJrjfb8S5ANnuYP ulaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339367; x=1724944167; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=GeAwo6rJ9Ssp7fNXqJixt1EPPvNs6AeUb8bCCXYa7xY=; b=eq5rhUMZTQPajDbZ9HZjH0O62jFCIWtya+jGgEi3tHIOlnm9rRrL6XLogCSHcTDqC2 T4VYXg6GaoIZIE8aCBFL33f1AbSYqd3hUIqSojy6whYUlbDxu1ZPEST8pEbBclrvvBOI DzXKKTsoddlNONan0zE6ujeRq+fj8FSUvq/PxuCYIGmnJYgc96GUVixSLWzW/aG53nj2 +YI9w6+ivnbEjyN433yBYfmPwSH69itBltGD8yKeIjHCKGKSfNmojKxLj72FVaCH7B/l uIJTLLWjCnlNKO7VjBxy8U7CxntCAZkhenmwNs3gXzv+a4mbavZIX2PL54gs7t7kM3/D L3Mw== X-Gm-Message-State: AOJu0YykvI3R+eY2YdpTuaC7/mSEKqy7bouJ7tyurgB50mnwzjqVjRh8 N9GiAjYUHLkZhXAOaAUPPd/FLHs1lWmqcjCci1EhGvysK6hDpvEATCP+yQ== X-Google-Smtp-Source: AGHT+IHTPFPRE9v35Z52EqGf1ZXU39olEwYHZBhKEzYW9SlscFqfSPhB8wCeUIhlzUDUnCsjDUtNqA== X-Received: by 2002:a17:90a:70c4:b0:2c9:5a71:1500 with SMTP id 98e67ed59e1d1-2d60a747c55mr5165797a91.0.1724339366847; Thu, 22 Aug 2024 08:09:26 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:26 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 3/8] crypto: Introduce x509 utils Date: Thu, 22 Aug 2024 21:08:44 +0600 Message-Id: <20240822150849.21759-4-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::102f; envelope-from=dorjoychy111@gmail.com; helo=mail-pj1-x102f.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org An utility function for getting fingerprint from X.509 certificate has been introduced. Implementation only provided using gnutls. Signed-off-by: Dorjoy Chowdhury --- crypto/meson.build | 4 ++ crypto/x509-utils.c | 75 +++++++++++++++++++++++++++++++++++++ include/crypto/x509-utils.h | 22 +++++++++++ 3 files changed, 101 insertions(+) create mode 100644 crypto/x509-utils.c create mode 100644 include/crypto/x509-utils.h diff --git a/crypto/meson.build b/crypto/meson.build index c46f9c22a7..735635de1f 100644 --- a/crypto/meson.build +++ b/crypto/meson.build @@ -24,6 +24,10 @@ crypto_ss.add(files( 'rsakey.c', )) +if gnutls.found() + crypto_ss.add(files('x509-utils.c')) +endif + if nettle.found() crypto_ss.add(nettle, files('hash-nettle.c', 'hmac-nettle.c', 'pbkdf-nettle.c')) if hogweed.found() diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c new file mode 100644 index 0000000000..593eb8968b --- /dev/null +++ b/crypto/x509-utils.c @@ -0,0 +1,75 @@ +/* + * X.509 certificate related helpers + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "crypto/x509-utils.h" +#include +#include +#include + +static const int qcrypto_to_gnutls_hash_alg_map[QCRYPTO_HASH_ALG__MAX] = { + [QCRYPTO_HASH_ALG_MD5] = GNUTLS_DIG_MD5, + [QCRYPTO_HASH_ALG_SHA1] = GNUTLS_DIG_SHA1, + [QCRYPTO_HASH_ALG_SHA224] = GNUTLS_DIG_SHA224, + [QCRYPTO_HASH_ALG_SHA256] = GNUTLS_DIG_SHA256, + [QCRYPTO_HASH_ALG_SHA384] = GNUTLS_DIG_SHA384, + [QCRYPTO_HASH_ALG_SHA512] = GNUTLS_DIG_SHA512, + [QCRYPTO_HASH_ALG_RIPEMD160] = GNUTLS_DIG_RMD160, +}; + +int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size, + QCryptoHashAlgorithm alg, + uint8_t *result, + size_t *resultlen, + Error **errp) +{ + int ret; + gnutls_x509_crt_t crt; + gnutls_datum_t datum = {.data = cert, .size = size}; + + if (alg >= G_N_ELEMENTS(qcrypto_to_gnutls_hash_alg_map)) { + error_setg(errp, "Unknown hash algorithm"); + return -1; + } + + if (result == NULL) { + error_setg(errp, "No valid buffer given"); + return -1; + } + + gnutls_x509_crt_init(&crt); + + if (gnutls_x509_crt_import(crt, &datum, GNUTLS_X509_FMT_PEM) != 0) { + error_setg(errp, "Failed to import certificate"); + goto cleanup; + } + + ret = gnutls_hash_get_len(qcrypto_to_gnutls_hash_alg_map[alg]); + if (*resultlen < ret) { + error_setg(errp, + "Result buffer size %zu is smaller than hash %d", + *resultlen, ret); + goto cleanup; + } + + if (gnutls_x509_crt_get_fingerprint(crt, + qcrypto_to_gnutls_hash_alg_map[alg], + result, resultlen) != 0) { + error_setg(errp, "Failed to get fingerprint from certificate"); + goto cleanup; + } + + return 0; + + cleanup: + gnutls_x509_crt_deinit(crt); + return -1; +} diff --git a/include/crypto/x509-utils.h b/include/crypto/x509-utils.h new file mode 100644 index 0000000000..4210dfbcfc --- /dev/null +++ b/include/crypto/x509-utils.h @@ -0,0 +1,22 @@ +/* + * X.509 certificate related helpers + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#ifndef QCRYPTO_X509_UTILS_H +#define QCRYPTO_X509_UTILS_H + +#include "crypto/hash.h" + +int qcrypto_get_x509_cert_fingerprint(uint8_t *cert, size_t size, + QCryptoHashAlgorithm hash, + uint8_t *result, + size_t *resultlen, + Error **errp); + +#endif From patchwork Thu Aug 22 15:08:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773705 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 5D05AC3DA4A for ; Thu, 22 Aug 2024 15:10:43 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9Rf-00082P-Hd; Thu, 22 Aug 2024 11:09:52 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9RW-0007xa-PX for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:43 -0400 Received: from mail-pj1-x102c.google.com ([2607:f8b0:4864:20::102c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9RP-00052U-Le for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:39 -0400 Received: by mail-pj1-x102c.google.com with SMTP id 98e67ed59e1d1-2cd5d6b2581so690514a91.2 for ; Thu, 22 Aug 2024 08:09:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339374; x=1724944174; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=izWLyVz2FVNcSSORRePgMUPNA6D0gLoIH3Mfk9kM1/8=; b=aeAczvvT/GxjKbptPi7Gru02JlSH8ZufJJu9oO4AsvnoAls07O4zL2xYJzBtbLgnax DrbAoChGDY0uGJ6TSo8dZBge7YqdfKXtIhGADcX7CvKlUSoSW83u9Nl0kBJNQC5B1teU c/fl1nBNpo/KBZY+mQHf29mPg+VV5xK6Lw8IXXUeUaTV6VRxxLQtaSJL1h3FWAFycuF+ ZVkFmrWy2zNefEFWW9txWUflyjczn+NtYMw9gfhrDfV8etawFba4l6Bs2WhoBZhoRX9H 0udcCEqpQECyJPnOuJlY0wWzK91VaBCmV2Y+daPOCFvW/9WWp9qtrw4jyNiUfqvybqXD 3uWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339374; x=1724944174; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=izWLyVz2FVNcSSORRePgMUPNA6D0gLoIH3Mfk9kM1/8=; b=w0lzwWrAplamOwXXhM+KLHcjWrFH3tklIUTNVt6FtdC7cGMlsSZclc9Cx05a3vP6EN nhKzcRKbSroE9KAjSRZ0ljqp7FjbOY+hd5rHLwijwqTnyCM8OfcEUCeBEcor28UTvqVN znj9bxXmE7Tfyzpsz0Zw9kg4h7Aikki4Cp31oeDrFBE+7zWiLzvz2qAUtPfVASrKFeQn P2+sR+TTHptZwvDYLR5++mHx+ZfwRqNuASGHSlolBc/hYjPw37kernusnLbEn1tGRSYG XABP68a24hmk0yp4efnwpXFNHDokmYXiQoc1MVsPLOzQOsUvB25/5+0qRL4muek9/nfP ItQQ== X-Gm-Message-State: AOJu0YyqMoRgjUTB3u+7SXfPLc23etzqttztEBFp55jG1lIZTFhZOdWJ IKKRFlDsRt+bDjHClYpMFwnhx5ufWb36GeVKCmnaQy09b7xGbEVVkqwJ8w== X-Google-Smtp-Source: AGHT+IHAh1yPpoMahcNChjTSTiZGqxHznp4ZblG7PnOWaTkJxVD46yrWRaioh9zh2MkS5CkkeV4B6w== X-Received: by 2002:a17:90b:3b45:b0:2cc:ff56:5be1 with SMTP id 98e67ed59e1d1-2d5e99c52d1mr6633857a91.7.1724339373755; Thu, 22 Aug 2024 08:09:33 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:33 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 4/8] tests/lcitool: Update libvirt-ci and add libcbor dependency Date: Thu, 22 Aug 2024 21:08:45 +0600 Message-Id: <20240822150849.21759-5-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::102c; envelope-from=dorjoychy111@gmail.com; helo=mail-pj1-x102c.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org libcbor dependecy is necessary for adding virtio-nsm and nitro-enclave machine support in the following commits. libvirt-ci has already been updated with the dependency upstream and this commit updates libvirt-ci submodule in QEMU to latest upstream. Also the libcbor dependency has been added to tests/lcitool/projects/qemu.yml. Signed-off-by: Dorjoy Chowdhury Reviewed-by: Daniel P. Berrangé --- .gitlab-ci.d/cirrus/macos-13.vars | 2 +- .gitlab-ci.d/cirrus/macos-14.vars | 2 +- scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml | 1 + scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml | 1 + tests/docker/dockerfiles/alpine.docker | 1 + tests/docker/dockerfiles/debian-amd64-cross.docker | 1 + tests/docker/dockerfiles/debian-arm64-cross.docker | 1 + tests/docker/dockerfiles/debian-armel-cross.docker | 1 + tests/docker/dockerfiles/debian-armhf-cross.docker | 1 + tests/docker/dockerfiles/debian-i686-cross.docker | 1 + tests/docker/dockerfiles/debian-mips64el-cross.docker | 1 + tests/docker/dockerfiles/debian-mipsel-cross.docker | 1 + tests/docker/dockerfiles/debian-ppc64el-cross.docker | 1 + tests/docker/dockerfiles/debian-s390x-cross.docker | 1 + tests/docker/dockerfiles/debian.docker | 1 + tests/docker/dockerfiles/fedora.docker | 1 + tests/docker/dockerfiles/opensuse-leap.docker | 3 ++- tests/docker/dockerfiles/ubuntu2204.docker | 1 + tests/lcitool/libvirt-ci | 2 +- tests/lcitool/projects/qemu.yml | 1 + 20 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.d/cirrus/macos-13.vars b/.gitlab-ci.d/cirrus/macos-13.vars index ac3fa3a847..b10b7f34de 100644 --- a/.gitlab-ci.d/cirrus/macos-13.vars +++ b/.gitlab-ci.d/cirrus/macos-13.vars @@ -11,6 +11,6 @@ MAKE='/opt/homebrew/bin/gmake' NINJA='/opt/homebrew/bin/ninja' PACKAGING_COMMAND='brew' PIP3='/opt/homebrew/bin/pip3' -PKGS='bash bc bison bzip2 capstone ccache cmocka ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 xorriso zlib zstd' +PKGS='bash bc bison bzip2 capstone ccache cmocka ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libcbor libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 xorriso zlib zstd' PYPI_PKGS='PyYAML numpy pillow sphinx sphinx-rtd-theme tomli' PYTHON='/opt/homebrew/bin/python3' diff --git a/.gitlab-ci.d/cirrus/macos-14.vars b/.gitlab-ci.d/cirrus/macos-14.vars index 24cfec3b89..2bbab50ca0 100644 --- a/.gitlab-ci.d/cirrus/macos-14.vars +++ b/.gitlab-ci.d/cirrus/macos-14.vars @@ -11,6 +11,6 @@ MAKE='/opt/homebrew/bin/gmake' NINJA='/opt/homebrew/bin/ninja' PACKAGING_COMMAND='brew' PIP3='/opt/homebrew/bin/pip3' -PKGS='bash bc bison bzip2 capstone ccache cmocka ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 xorriso zlib zstd' +PKGS='bash bc bison bzip2 capstone ccache cmocka ctags curl dbus diffutils dtc flex gcovr gettext git glib gnu-sed gnutls gtk+3 gtk-vnc jemalloc jpeg-turbo json-c libcbor libepoxy libffi libgcrypt libiscsi libnfs libpng libslirp libssh libtasn1 libusb llvm lzo make meson mtools ncurses nettle ninja pixman pkg-config python3 rpm2cpio sdl2 sdl2_image snappy socat sparse spice-protocol swtpm tesseract usbredir vde vte3 xorriso zlib zstd' PYPI_PKGS='PyYAML numpy pillow sphinx sphinx-rtd-theme tomli' PYTHON='/opt/homebrew/bin/python3' diff --git a/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml b/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml index 71a0f0c433..4fb3c7555f 100644 --- a/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml +++ b/scripts/ci/setup/ubuntu/ubuntu-2204-aarch64.yaml @@ -35,6 +35,7 @@ packages: - libcacard-dev - libcap-ng-dev - libcapstone-dev + - libcbor-dev - libcmocka-dev - libcurl4-gnutls-dev - libdaxctl-dev diff --git a/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml b/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml index d8de967b18..d79399caf3 100644 --- a/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml +++ b/scripts/ci/setup/ubuntu/ubuntu-2204-s390x.yaml @@ -35,6 +35,7 @@ packages: - libcacard-dev - libcap-ng-dev - libcapstone-dev + - libcbor-dev - libcmocka-dev - libcurl4-gnutls-dev - libdaxctl-dev diff --git a/tests/docker/dockerfiles/alpine.docker b/tests/docker/dockerfiles/alpine.docker index 54b9721997..a0136181d3 100644 --- a/tests/docker/dockerfiles/alpine.docker +++ b/tests/docker/dockerfiles/alpine.docker @@ -45,6 +45,7 @@ RUN apk update && \ libaio-dev \ libbpf-dev \ libcap-ng-dev \ + libcbor-dev \ libdrm-dev \ libepoxy-dev \ libffi-dev \ diff --git a/tests/docker/dockerfiles/debian-amd64-cross.docker b/tests/docker/dockerfiles/debian-amd64-cross.docker index 136c3a79a1..5c5a753aca 100644 --- a/tests/docker/dockerfiles/debian-amd64-cross.docker +++ b/tests/docker/dockerfiles/debian-amd64-cross.docker @@ -92,6 +92,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:amd64 \ libcap-ng-dev:amd64 \ libcapstone-dev:amd64 \ + libcbor-dev:amd64 \ libcmocka-dev:amd64 \ libcurl4-gnutls-dev:amd64 \ libdaxctl-dev:amd64 \ diff --git a/tests/docker/dockerfiles/debian-arm64-cross.docker b/tests/docker/dockerfiles/debian-arm64-cross.docker index 233f6ee1de..7724b5f7fb 100644 --- a/tests/docker/dockerfiles/debian-arm64-cross.docker +++ b/tests/docker/dockerfiles/debian-arm64-cross.docker @@ -92,6 +92,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:arm64 \ libcap-ng-dev:arm64 \ libcapstone-dev:arm64 \ + libcbor-dev:arm64 \ libcmocka-dev:arm64 \ libcurl4-gnutls-dev:arm64 \ libdaxctl-dev:arm64 \ diff --git a/tests/docker/dockerfiles/debian-armel-cross.docker b/tests/docker/dockerfiles/debian-armel-cross.docker index 8476fc8cce..5e9c4002db 100644 --- a/tests/docker/dockerfiles/debian-armel-cross.docker +++ b/tests/docker/dockerfiles/debian-armel-cross.docker @@ -95,6 +95,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:armel \ libcap-ng-dev:armel \ libcapstone-dev:armel \ + libcbor-dev:armel \ libcmocka-dev:armel \ libcurl4-gnutls-dev:armel \ libdaxctl-dev:armel \ diff --git a/tests/docker/dockerfiles/debian-armhf-cross.docker b/tests/docker/dockerfiles/debian-armhf-cross.docker index f26385e0b9..d64c2a2cbd 100644 --- a/tests/docker/dockerfiles/debian-armhf-cross.docker +++ b/tests/docker/dockerfiles/debian-armhf-cross.docker @@ -92,6 +92,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:armhf \ libcap-ng-dev:armhf \ libcapstone-dev:armhf \ + libcbor-dev:armhf \ libcmocka-dev:armhf \ libcurl4-gnutls-dev:armhf \ libdaxctl-dev:armhf \ diff --git a/tests/docker/dockerfiles/debian-i686-cross.docker b/tests/docker/dockerfiles/debian-i686-cross.docker index 3fe8ee623d..5b548c764b 100644 --- a/tests/docker/dockerfiles/debian-i686-cross.docker +++ b/tests/docker/dockerfiles/debian-i686-cross.docker @@ -95,6 +95,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:i386 \ libcap-ng-dev:i386 \ libcapstone-dev:i386 \ + libcbor-dev:i386 \ libcmocka-dev:i386 \ libcurl4-gnutls-dev:i386 \ libdaxctl-dev:i386 \ diff --git a/tests/docker/dockerfiles/debian-mips64el-cross.docker b/tests/docker/dockerfiles/debian-mips64el-cross.docker index 2862785692..c375921c2d 100644 --- a/tests/docker/dockerfiles/debian-mips64el-cross.docker +++ b/tests/docker/dockerfiles/debian-mips64el-cross.docker @@ -94,6 +94,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:mips64el \ libcap-ng-dev:mips64el \ libcapstone-dev:mips64el \ + libcbor-dev:mips64el \ libcmocka-dev:mips64el \ libcurl4-gnutls-dev:mips64el \ libdaxctl-dev:mips64el \ diff --git a/tests/docker/dockerfiles/debian-mipsel-cross.docker b/tests/docker/dockerfiles/debian-mipsel-cross.docker index 0d559ae4ba..370f8d9c90 100644 --- a/tests/docker/dockerfiles/debian-mipsel-cross.docker +++ b/tests/docker/dockerfiles/debian-mipsel-cross.docker @@ -94,6 +94,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:mipsel \ libcap-ng-dev:mipsel \ libcapstone-dev:mipsel \ + libcbor-dev:mipsel \ libcmocka-dev:mipsel \ libcurl4-gnutls-dev:mipsel \ libdaxctl-dev:mipsel \ diff --git a/tests/docker/dockerfiles/debian-ppc64el-cross.docker b/tests/docker/dockerfiles/debian-ppc64el-cross.docker index 8c1dcec9cf..393d0300c6 100644 --- a/tests/docker/dockerfiles/debian-ppc64el-cross.docker +++ b/tests/docker/dockerfiles/debian-ppc64el-cross.docker @@ -92,6 +92,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:ppc64el \ libcap-ng-dev:ppc64el \ libcapstone-dev:ppc64el \ + libcbor-dev:ppc64el \ libcmocka-dev:ppc64el \ libcurl4-gnutls-dev:ppc64el \ libdaxctl-dev:ppc64el \ diff --git a/tests/docker/dockerfiles/debian-s390x-cross.docker b/tests/docker/dockerfiles/debian-s390x-cross.docker index 72668e0315..cbe590080d 100644 --- a/tests/docker/dockerfiles/debian-s390x-cross.docker +++ b/tests/docker/dockerfiles/debian-s390x-cross.docker @@ -92,6 +92,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev:s390x \ libcap-ng-dev:s390x \ libcapstone-dev:s390x \ + libcbor-dev:s390x \ libcmocka-dev:s390x \ libcurl4-gnutls-dev:s390x \ libdaxctl-dev:s390x \ diff --git a/tests/docker/dockerfiles/debian.docker b/tests/docker/dockerfiles/debian.docker index 42bd0067d1..631fe6b671 100644 --- a/tests/docker/dockerfiles/debian.docker +++ b/tests/docker/dockerfiles/debian.docker @@ -41,6 +41,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev \ libcap-ng-dev \ libcapstone-dev \ + libcbor-dev \ libcmocka-dev \ libcurl4-gnutls-dev \ libdaxctl-dev \ diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker index ecdefaff1a..a01c004cfa 100644 --- a/tests/docker/dockerfiles/fedora.docker +++ b/tests/docker/dockerfiles/fedora.docker @@ -61,6 +61,7 @@ exec "$@"\n' > /usr/bin/nosync && \ libbpf-devel \ libcacard-devel \ libcap-ng-devel \ + libcbor-devel \ libcmocka-devel \ libcurl-devel \ libdrm-devel \ diff --git a/tests/docker/dockerfiles/opensuse-leap.docker b/tests/docker/dockerfiles/opensuse-leap.docker index 66143621fe..751919c1ec 100644 --- a/tests/docker/dockerfiles/opensuse-leap.docker +++ b/tests/docker/dockerfiles/opensuse-leap.docker @@ -4,7 +4,7 @@ # # https://gitlab.com/libvirt/libvirt-ci -FROM registry.opensuse.org/opensuse/leap:15.5 +FROM registry.opensuse.org/opensuse/leap:15.6 RUN zypper update -y && \ zypper install -y \ @@ -46,6 +46,7 @@ RUN zypper update -y && \ libbz2-devel \ libcacard-devel \ libcap-ng-devel \ + libcbor-devel \ libcmocka-devel \ libcurl-devel \ libdrm-devel \ diff --git a/tests/docker/dockerfiles/ubuntu2204.docker b/tests/docker/dockerfiles/ubuntu2204.docker index 3a7de6a318..ecd5c4fd08 100644 --- a/tests/docker/dockerfiles/ubuntu2204.docker +++ b/tests/docker/dockerfiles/ubuntu2204.docker @@ -41,6 +41,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ libcacard-dev \ libcap-ng-dev \ libcapstone-dev \ + libcbor-dev \ libcmocka-dev \ libcurl4-gnutls-dev \ libdaxctl-dev \ diff --git a/tests/lcitool/libvirt-ci b/tests/lcitool/libvirt-ci index 789b4601bc..a8253ca565 160000 --- a/tests/lcitool/libvirt-ci +++ b/tests/lcitool/libvirt-ci @@ -1 +1 @@ -Subproject commit 789b4601bce4e01f43fdb6ad4ce5ab4e46674440 +Subproject commit a8253ca5656c34c1d91f2c1170bd0cbf117358f1 diff --git a/tests/lcitool/projects/qemu.yml b/tests/lcitool/projects/qemu.yml index 252e871f80..35878951b6 100644 --- a/tests/lcitool/projects/qemu.yml +++ b/tests/lcitool/projects/qemu.yml @@ -42,6 +42,7 @@ packages: - libc-static - libcacard - libcap-ng + - libcbor - libcurl - libdrm - libepoxy From patchwork Thu Aug 22 15:08:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773707 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7AD9BC3DA4A for ; Thu, 22 Aug 2024 15:11:36 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9Rm-0008Kt-UQ; Thu, 22 Aug 2024 11:09:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9Rl-0008Bz-FA for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:57 -0400 Received: from mail-pg1-x536.google.com ([2607:f8b0:4864:20::536]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9RZ-000532-P7 for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:53 -0400 Received: by mail-pg1-x536.google.com with SMTP id 41be03b00d2f7-7b594936e9bso705236a12.1 for ; Thu, 22 Aug 2024 08:09:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339382; x=1724944182; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=m84Z+YKohRlbxvvjKuB5nf7rs+yXUWPqNnfhfR7bp0U=; b=f2iGPnj9XOaQc7d3P9cYDhFqPY7kvTBXY7r9HHVQ4rWObh4rWkC+l0TCdH7HLVhXjw s4T51ijjaNLzO/dFAM4YEMHg1b9RQY3MBVSzcXKxJK8acwexKM2DlB+vr+I2yW0ozw6X Jpu08NBLAXmAcYFxn4B8syDsNCv7G1CDkbCK9ziFxlDO1VT64LFpWFDNOPOkoAOpc1Ws 2lh7nozfaRMXgEXRNCmwX5Kmv4RNQOAZ8/hpeNui6L0wR0kgeBrbz040UIzNgOpAJJs9 B0NyPv426t6gLEfJvvw+F/hgtkJpcPc1XsKubJbTv68oPtxwsV4YAHkVIBVaERL4eXjw 2MZw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339382; x=1724944182; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=m84Z+YKohRlbxvvjKuB5nf7rs+yXUWPqNnfhfR7bp0U=; b=WnsiyRnLK0HJXeuR57HGWBAeaynhgS75DAEQMjDhRixb7RcueZwRri4e/CamS7iC6i dK39R0FbfLnz3TjFU3mcCAQqI8cakDDwrP9vaoQ3R2JEDGgPHsdF/t9k2gJDu1HPmLZP JChuju4k2AZbOw+G06/ILUctGHJUG+GTlmvOfGg4BMpqUPnpyiS4Q06C2mRNv17+s7U/ l4esCgOme+a2+UeZ/wshVK60VIltVucGVXwIZfD031Q7E3Nttalj6GhvMiMKl4bv6Cj9 gsRCCt/aasJ/QzCZPZOkczNbJvZzjkscfHYiYTCCoVrYyhGAoecyAoJcS15/lPEWlPGC ZQ/Q== X-Gm-Message-State: AOJu0Yx9RoUB8f99ZeudLvnEqpupkbA5Rvn3v+S6SrAqHnKdhzcHegLH QIcVMhryVzilAjIk53B8vcWgVhDrNuMl4121kYEwOSITAElpAe/Z5A7qXw== X-Google-Smtp-Source: AGHT+IHrmsO1x5W1lbD10C5d/jxtN+auEnrq39Soa6cTa4NZyHa65rOAUFDurdSD8besWvms6CCUQg== X-Received: by 2002:a17:90a:4ca5:b0:2d3:babf:f9a3 with SMTP id 98e67ed59e1d1-2d5e99f9c55mr5996698a91.2.1724339381482; Thu, 22 Aug 2024 08:09:41 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:41 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 5/8] device/virtio-nsm: Support for Nitro Secure Module device Date: Thu, 22 Aug 2024 21:08:46 +0600 Message-Id: <20240822150849.21759-6-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::536; envelope-from=dorjoychy111@gmail.com; helo=mail-pg1-x536.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Nitro Secure Module (NSM)[1] device is used in AWS Nitro Enclaves[2] for stripped down TPM functionality like cryptographic attestation. The requests to and responses from NSM device are CBOR[3] encoded. This commit adds support for NSM device in QEMU. Although related to AWS Nitro Enclaves, the virito-nsm device is independent and can be used in other machine types as well. The libcbor[4] library has been used for the CBOR encoding and decoding functionalities. [1] https://lists.oasis-open.org/archives/virtio-comment/202310/msg00387.html [2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html [3] http://cbor.io/ [4] https://libcbor.readthedocs.io/en/latest/ Signed-off-by: Dorjoy Chowdhury --- MAINTAINERS | 10 + hw/virtio/Kconfig | 5 + hw/virtio/cbor-helpers.c | 326 ++++++ hw/virtio/meson.build | 6 + hw/virtio/virtio-nsm-pci.c | 73 ++ hw/virtio/virtio-nsm.c | 1638 ++++++++++++++++++++++++++++++ include/hw/virtio/cbor-helpers.h | 46 + include/hw/virtio/virtio-nsm.h | 59 ++ meson.build | 2 + 9 files changed, 2165 insertions(+) create mode 100644 hw/virtio/cbor-helpers.c create mode 100644 hw/virtio/virtio-nsm-pci.c create mode 100644 hw/virtio/virtio-nsm.c create mode 100644 include/hw/virtio/cbor-helpers.h create mode 100644 include/hw/virtio/virtio-nsm.h diff --git a/MAINTAINERS b/MAINTAINERS index 3584d6a6c6..da4f698137 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2340,6 +2340,16 @@ F: include/sysemu/rng*.h F: backends/rng*.c F: tests/qtest/virtio-rng-test.c +virtio-nsm +M: Alexander Graf +M: Dorjoy Chowdhury +S: Maintained +F: hw/virtio/cbor-helpers.c +F: hw/virtio/virtio-nsm.c +F: hw/virtio/virtio-nsm-pci.c +F: include/hw/virtio/cbor-helpers.h +F: include/hw/virtio/virtio-nsm.h + vhost-user-stubs M: Alex Bennée S: Maintained diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig index aa63ff7fd4..29fee32035 100644 --- a/hw/virtio/Kconfig +++ b/hw/virtio/Kconfig @@ -6,6 +6,11 @@ config VIRTIO_RNG default y depends on VIRTIO +config VIRTIO_NSM + bool + default y + depends on VIRTIO + config VIRTIO_IOMMU bool default y diff --git a/hw/virtio/cbor-helpers.c b/hw/virtio/cbor-helpers.c new file mode 100644 index 0000000000..a0e58d6862 --- /dev/null +++ b/hw/virtio/cbor-helpers.c @@ -0,0 +1,326 @@ +/* + * QEMU CBOR helpers + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "hw/virtio/cbor-helpers.h" + +bool qemu_cbor_map_add(cbor_item_t *map, cbor_item_t *key, cbor_item_t *value) +{ + bool success = false; + struct cbor_pair pair = (struct cbor_pair) { + .key = cbor_move(key), + .value = cbor_move(value) + }; + + success = cbor_map_add(map, pair); + if (!success) { + cbor_incref(pair.key); + cbor_incref(pair.value); + } + + return success; +} + +bool qemu_cbor_array_push(cbor_item_t *array, cbor_item_t *value) +{ + bool success = false; + + success = cbor_array_push(array, cbor_move(value)); + if (!success) { + cbor_incref(value); + } + + return success; +} + +bool qemu_cbor_add_bool_to_map(cbor_item_t *map, const char *key, bool value) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_build_bool(value); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_uint8_to_map(cbor_item_t *map, const char *key, + uint8_t value) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_build_uint8(value); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_map_to_map(cbor_item_t *map, const char *key, + size_t nested_map_size, + cbor_item_t **nested_map) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_new_definite_map(nested_map_size); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + *nested_map = value_cbor; + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_bytestring_to_map(cbor_item_t *map, const char *key, + uint8_t *arr, size_t len) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_build_bytestring(arr, len); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_bytestring_or_null_to_map(cbor_item_t *map, const char *key, + uint8_t *arr, size_t len) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + if (len) { + value_cbor = cbor_build_bytestring(arr, len); + } else { + value_cbor = cbor_new_null(); + } + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_string_to_map(cbor_item_t *map, const char *key, + const char *value) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_build_string(value); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_uint8_array_to_map(cbor_item_t *map, const char *key, + uint8_t *arr, size_t len) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_new_definite_array(len); + if (!value_cbor) { + goto cleanup; + } + + for (int i = 0; i < len; ++i) { + cbor_item_t *tmp = cbor_build_uint8(arr[i]); + if (!tmp) { + goto cleanup; + } + if (!qemu_cbor_array_push(value_cbor, tmp)) { + cbor_decref(&tmp); + goto cleanup; + } + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_uint8_key_bytestring_to_map(cbor_item_t *map, uint8_t key, + uint8_t *buf, size_t len) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_uint8(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_build_bytestring(buf, len); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +bool qemu_cbor_add_uint64_to_map(cbor_item_t *map, const char *key, + uint64_t value) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + + key_cbor = cbor_build_string(key); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_build_uint64(value); + if (!value_cbor) { + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index 621fc65454..1fe7cb4d72 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -54,6 +54,9 @@ specific_virtio_ss.add(when: 'CONFIG_VIRTIO_PMEM', if_true: files('virtio-pmem.c specific_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock.c')) specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock.c')) specific_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c')) +if libcbor.found() + specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor]) +endif specific_virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c')) specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_SCMI', if_true: files('vhost-user-scmi.c')) specific_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_SCMI'], if_true: files('vhost-user-scmi-pci.c')) @@ -70,6 +73,9 @@ virtio_pci_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto-pc virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT_HOST', if_true: files('virtio-input-host-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT', if_true: files('virtio-input-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng-pci.c')) +if libcbor.found() + virtio_pci_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm-pci.c', 'cbor-helpers.c'), libcbor]) +endif virtio_pci_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_9P', if_true: files('virtio-9p-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio-scsi-pci.c')) diff --git a/hw/virtio/virtio-nsm-pci.c b/hw/virtio/virtio-nsm-pci.c new file mode 100644 index 0000000000..dca797315a --- /dev/null +++ b/hw/virtio/virtio-nsm-pci.c @@ -0,0 +1,73 @@ +/* + * AWS Nitro Secure Module (NSM) device + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" + +#include "hw/virtio/virtio-pci.h" +#include "hw/virtio/virtio-nsm.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "qom/object.h" + +typedef struct VirtIONsmPCI VirtIONsmPCI; + +#define TYPE_VIRTIO_NSM_PCI "virtio-nsm-pci-base" +DECLARE_INSTANCE_CHECKER(VirtIONsmPCI, VIRTIO_NSM_PCI, + TYPE_VIRTIO_NSM_PCI) + +struct VirtIONsmPCI { + VirtIOPCIProxy parent_obj; + VirtIONSM vdev; +}; + +static void virtio_nsm_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) +{ + VirtIONsmPCI *vnsm = VIRTIO_NSM_PCI(vpci_dev); + DeviceState *vdev = DEVICE(&vnsm->vdev); + + virtio_pci_force_virtio_1(vpci_dev); + + if (!qdev_realize(vdev, BUS(&vpci_dev->bus), errp)) { + return; + } +} + +static void virtio_nsm_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); + + k->realize = virtio_nsm_pci_realize; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static void virtio_nsm_initfn(Object *obj) +{ + VirtIONsmPCI *dev = VIRTIO_NSM_PCI(obj); + + virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev), + TYPE_VIRTIO_NSM); +} + +static const VirtioPCIDeviceTypeInfo virtio_nsm_pci_info = { + .base_name = TYPE_VIRTIO_NSM_PCI, + .generic_name = "virtio-nsm-pci", + .instance_size = sizeof(VirtIONsmPCI), + .instance_init = virtio_nsm_initfn, + .class_init = virtio_nsm_pci_class_init, +}; + +static void virtio_nsm_pci_register(void) +{ + virtio_pci_types_register(&virtio_nsm_pci_info); +} + +type_init(virtio_nsm_pci_register) diff --git a/hw/virtio/virtio-nsm.c b/hw/virtio/virtio-nsm.c new file mode 100644 index 0000000000..7e212eee1a --- /dev/null +++ b/hw/virtio/virtio-nsm.c @@ -0,0 +1,1638 @@ +/* + * AWS Nitro Secure Module (NSM) device + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/guest-random.h" +#include "qapi/error.h" + +#include "crypto/hash.h" +#include "hw/virtio/virtio.h" +#include "hw/virtio/virtio-nsm.h" +#include "hw/virtio/cbor-helpers.h" +#include "standard-headers/linux/virtio_ids.h" + +#define NSM_PCR_DATA_REQ_MAX_SIZE 512 + +enum NSMResponseTypes { + NSM_SUCCESS = 0, + NSM_INVALID_ARGUMENT = 1, + NSM_INVALID_INDEX = 2, + NSM_READONLY_INDEX = 3, + NSM_INVALID_OPERATION = 4, + NSM_BUFFER_TOO_SMALL = 5, + NSM_INPUT_TOO_LARGE = 6, + NSM_INTERNAL_ERROR = 7, +}; + +static const char *error_string(enum NSMResponseTypes type) +{ + const char *str; + switch (type) { + case NSM_INVALID_ARGUMENT: + str = "InvalidArgument"; + break; + case NSM_INVALID_INDEX: + str = "InvalidIndex"; + break; + case NSM_READONLY_INDEX: + str = "ReadOnlyIndex"; + break; + case NSM_INVALID_OPERATION: + str = "InvalidOperation"; + break; + case NSM_BUFFER_TOO_SMALL: + str = "BufferTooSmall"; + break; + case NSM_INPUT_TOO_LARGE: + str = "InputTooLarge"; + break; + default: + str = "InternalError"; + break; + } + + return str; +} + +/* + * Error response structure: + * + * { + * Map(1) { + * key = String("Error"), + * value = String(error_name) + * } + * } + * + * where error_name can be one of the following: + * InvalidArgument + * InvalidIndex + * InvalidResponse + * ReadOnlyIndex + * InvalidOperation + * BufferTooSmall + * InputTooLarge + * InternalError + */ + +static bool error_response(struct iovec *response, enum NSMResponseTypes error, + Error **errp) +{ + cbor_item_t *root; + size_t len; + bool r = false; + + root = cbor_new_definite_map(1); + if (!root) { + goto err; + } + + if (!qemu_cbor_add_string_to_map(root, "Error", error_string(error))) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + error_setg(errp, "Response buffer is small for %s response", + error_string(error)); + goto out; + } + response->iov_len = len; + r = true; + + out: + if (root) { + cbor_decref(&root); + } + return r; + + err: + error_setg(errp, "Failed to initialize %s response", error_string(error)); + goto out; +} + +/* + * GetRandom response structure: + * + * { + * Map(1) { + * key = String("GetRandom"), + * value = Map(1) { + * key = String("random"), + * value = Byte_String() + * } + * } + * } + */ +static bool handle_GetRandom(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root, *nested_map; + size_t len; + uint8_t rnd[64]; + bool r = false; + + root = cbor_new_definite_map(1); + if (!root) { + goto err; + } + + if (!qemu_cbor_add_map_to_map(root, "GetRandom", 1, &nested_map)) { + goto err; + } + + qemu_guest_getrandom_nofail(rnd, 64); + + if (!qemu_cbor_add_bytestring_to_map(nested_map, "random", rnd, 64)) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto out; + } + + response->iov_len = len; + r = true; + + out: + if (root) { + cbor_decref(&root); + } + return r; + + err: + error_setg(errp, "Failed to initialize GetRandom response"); + goto out; +} + +/* + * DescribeNSM response structure: + * + * { + * Map(1) { + * key = String("DescribeNSM"), + * value = Map(7) { + * key = String("digest"), + * value = String("SHA384"), + * key = String("max_pcrs"), + * value = Uint8(32), + * key = String("module_id"), + * value = String("i-1234-enc5678"), + * key = String("locked_pcrs"), + * value = Array(), + * key = String("version_major"), + * value = Uint8(1), + * key = String("version_minor"), + * value = Uint8(0), + * key = String("version_patch"), + * value = Uint8(0) + * } + * } + * } + */ +static bool handle_DescribeNSM(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root, *nested_map; + uint16_t locked_pcrs_cnt = 0; + uint8_t locked_pcrs_ind[NSM_MAX_PCRS]; + size_t len; + bool r = false; + + root = cbor_new_definite_map(1); + if (!root) { + goto err; + } + + if (!qemu_cbor_add_map_to_map(root, "DescribeNSM", 7, &nested_map)) { + goto err; + } + + if (!qemu_cbor_add_string_to_map(nested_map, "digest", vnsm->digest)) { + goto err; + } + + if (!qemu_cbor_add_uint8_to_map(nested_map, "max_pcrs", vnsm->max_pcrs)) { + goto err; + } + + if (!qemu_cbor_add_string_to_map(nested_map, "module_id", + vnsm->module_id)) { + goto err; + } + + for (uint8_t i = 0; i < NSM_MAX_PCRS; ++i) { + if (vnsm->pcrs[i].locked) { + locked_pcrs_ind[locked_pcrs_cnt++] = i; + } + } + if (!qemu_cbor_add_uint8_array_to_map(nested_map, "locked_pcrs", + locked_pcrs_ind, locked_pcrs_cnt)) { + goto err; + } + + if (!qemu_cbor_add_uint8_to_map(nested_map, "version_major", + vnsm->version_major)) { + goto err; + } + + if (!qemu_cbor_add_uint8_to_map(nested_map, "version_minor", + vnsm->version_minor)) { + goto err; + } + + if (!qemu_cbor_add_uint8_to_map(nested_map, "version_patch", + vnsm->version_patch)) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto out; + } + + response->iov_len = len; + r = true; + + out: + if (root) { + cbor_decref(&root); + } + return r; + + err: + error_setg(errp, "Failed to initialize DescribeNSM response"); + goto out; +} + +/* + * DescribePCR request structure: + * + * { + * Map(1) { + * key = String("DescribePCR"), + * value = Map(1) { + * key = String("index"), + * value = Uint8(pcr) + * } + * } + * } + */ +typedef struct NSMDescribePCRReq { + uint8_t index; +} NSMDescribePCRReq; + +static enum NSMResponseTypes get_nsm_describe_pcr_req( + uint8_t *req, size_t len, + NSMDescribePCRReq *nsm_req) +{ + size_t size; + uint8_t *str; + struct cbor_pair *pair; + cbor_item_t *item = NULL; + struct cbor_load_result result; + enum NSMResponseTypes r = NSM_INVALID_ARGUMENT; + + item = cbor_load(req, len, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + goto cleanup; + } + + pair = cbor_map_handle(item); + if (!cbor_isa_map(pair->value) || cbor_map_size(pair->value) != 1) { + goto cleanup; + } + + pair = cbor_map_handle(pair->value); + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 5 || memcmp(str, "index", 5) != 0) { + goto cleanup; + } + if (!cbor_isa_uint(pair->value) || + cbor_int_get_width(pair->value) != CBOR_INT_8) { + goto cleanup; + } + + nsm_req->index = cbor_get_uint8(pair->value); + r = NSM_SUCCESS; + goto cleanup; + + cleanup: + if (item) { + cbor_decref(&item); + } + return r; +} + +/* + * DescribePCR response structure: + * + * { + * Map(1) { + * key = String("DescribePCR"), + * value = Map(2) { + * key = String("data"), + * value = Byte_String(), + * key = String("lock"), + * value = Bool() + * } + * } + * } + */ +static bool handle_DescribePCR(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root = NULL; + cbor_item_t *nested_map; + size_t len; + NSMDescribePCRReq nsm_req; + enum NSMResponseTypes type; + struct PCRInfo *pcr; + bool r = false; + + type = get_nsm_describe_pcr_req(request->iov_base, request->iov_len, + &nsm_req); + if (type != NSM_SUCCESS) { + if (error_response(response, type, errp)) { + r = true; + } + goto out; + } + if (nsm_req.index >= vnsm->max_pcrs) { + if (error_response(response, NSM_INVALID_INDEX, errp)) { + r = true; + } + goto out; + } + pcr = &(vnsm->pcrs[nsm_req.index]); + + root = cbor_new_definite_map(1); + if (!root) { + goto err; + } + + if (!qemu_cbor_add_map_to_map(root, "DescribePCR", 2, &nested_map)) { + goto err; + } + + if (!qemu_cbor_add_bytestring_to_map(nested_map, "data", pcr->data, + QCRYPTO_HASH_DIGEST_LEN_SHA384)) { + goto err; + } + + if (!qemu_cbor_add_bool_to_map(nested_map, "lock", pcr->locked)) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto out; + } + + response->iov_len = len; + r = true; + + out: + if (root) { + cbor_decref(&root); + } + return r; + + err: + error_setg(errp, "Failed to initialize DescribePCR response"); + goto out; +} + +/* + * ExtendPCR request structure: + * + * { + * Map(1) { + * key = String("ExtendPCR"), + * value = Map(2) { + * key = String("index"), + * value = Uint8(pcr), + * key = String("data"), + * value = Byte_String(data), + * } + * } + * } + */ +typedef struct NSMExtendPCRReq { + uint8_t index; + uint16_t data_len; + uint8_t data[NSM_PCR_DATA_REQ_MAX_SIZE]; +} NSMExtendPCRReq; + +static bool get_nsm_extend_pcr_req(uint8_t *req, size_t len, + NSMExtendPCRReq *nsm_req) +{ + cbor_item_t *item = NULL; + size_t size ; + uint8_t *str; + struct cbor_pair *pair; + struct cbor_load_result result; + enum NSMResponseTypes r = NSM_INVALID_ARGUMENT; + + item = cbor_load(req, len, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + goto cleanup; + } + + pair = cbor_map_handle(item); + if (!cbor_isa_map(pair->value) || cbor_map_size(pair->value) != 2) { + goto cleanup; + } + pair = cbor_map_handle(pair->value); + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 5 || memcmp(str, "index", 5) != 0) { + goto cleanup; + } + if (!cbor_isa_uint(pair->value) || + cbor_int_get_width(pair->value) != CBOR_INT_8) { + goto cleanup; + } + nsm_req->index = cbor_get_uint8(pair->value); + + /* let's move forward to the next key value pair */ + pair++; + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 4 || memcmp(str, "data", 4) != 0) { + goto cleanup; + } + if (!cbor_isa_bytestring(pair->value)) { + goto cleanup; + } + str = cbor_bytestring_handle(pair->value); + size = cbor_bytestring_length(pair->value); + if (!str || size == 0) { + goto cleanup; + } + if (size > NSM_PCR_DATA_REQ_MAX_SIZE) { + r = NSM_INPUT_TOO_LARGE; + goto cleanup; + } + + nsm_req->data_len = size; + memcpy(nsm_req->data, str, size); + r = NSM_SUCCESS; + goto cleanup; + + cleanup: + if (item) { + cbor_decref(&item); + } + return r; +} + +/* + * ExtendPCR response structure: + * + * { + * Map(1) { + * key = String("ExtendPCR"), + * value = Map(1) { + * key = String("data"), + * value = Byte_String() + * } + * } + * } + */ +static bool handle_ExtendPCR(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root = NULL; + cbor_item_t *nested_map; + size_t len; + NSMExtendPCRReq nsm_req; + enum NSMResponseTypes type; + struct PCRInfo *pcr; + bool r = false; + + type = get_nsm_extend_pcr_req(request->iov_base, request->iov_len, + &nsm_req); + if (type != NSM_SUCCESS) { + if (error_response(response, type, errp)) { + r = true; + } + goto out; + } + if (nsm_req.index >= vnsm->max_pcrs) { + if (error_response(response, NSM_INVALID_INDEX, errp)) { + r = true; + } + goto out; + } + + pcr = &(vnsm->pcrs[nsm_req.index]); + + if (pcr->locked) { + if (error_response(response, NSM_READONLY_INDEX, errp)) { + r = true; + } + goto out; + } + + if (!vnsm->extend_pcr(vnsm, nsm_req.index, nsm_req.data, + nsm_req.data_len)) { + if (error_response(response, NSM_INTERNAL_ERROR, errp)) { + r = true; + } + goto out; + } + + root = cbor_new_definite_map(1); + if (!root) { + goto err; + } + + if (!qemu_cbor_add_map_to_map(root, "ExtendPCR", 1, &nested_map)) { + goto err; + } + + if (!qemu_cbor_add_bytestring_to_map(nested_map, "data", pcr->data, + QCRYPTO_HASH_DIGEST_LEN_SHA384)) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto out; + } + + response->iov_len = len; + r = true; + + out: + if (root) { + cbor_decref(&root); + } + return r; + + err: + error_setg(errp, "Failed to initialize DescribePCR response"); + goto out; +} + +/* + * LockPCR request structure: + * + * { + * Map(1) { + * key = String("LockPCR"), + * value = Map(1) { + * key = String("index"), + * value = Uint8(pcr) + * } + * } + * } + */ +typedef struct NSMLockPCRReq { + uint8_t index; +} NSMLockPCRReq; + +static enum NSMResponseTypes get_nsm_lock_pcr_req(uint8_t *req, size_t len, + NSMLockPCRReq *nsm_req) +{ + cbor_item_t *item = NULL; + size_t size; + uint8_t *str; + struct cbor_pair *pair; + struct cbor_load_result result; + enum NSMResponseTypes r = NSM_INVALID_ARGUMENT; + + item = cbor_load(req, len, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + goto cleanup; + } + + pair = cbor_map_handle(item); + if (!cbor_isa_map(pair->value) || cbor_map_size(pair->value) != 1) { + goto cleanup; + } + pair = cbor_map_handle(pair->value); + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 5 || memcmp(str, "index", 5) != 0) { + goto cleanup; + } + if (!cbor_isa_uint(pair->value) || + cbor_int_get_width(pair->value) != CBOR_INT_8) { + goto cleanup; + } + + nsm_req->index = cbor_get_uint8(pair->value); + r = NSM_SUCCESS; + goto cleanup; + + cleanup: + if (item) { + cbor_decref(&item); + } + return r; +} + +/* + * LockPCR success response structure: + * { + * String("LockPCR") + * } + */ +static bool handle_LockPCR(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root = NULL; + size_t len; + NSMLockPCRReq nsm_req; + enum NSMResponseTypes type; + struct PCRInfo *pcr; + bool r = false; + + type = get_nsm_lock_pcr_req(request->iov_base, request->iov_len, &nsm_req); + if (type != NSM_SUCCESS) { + if (error_response(response, type, errp)) { + r = true; + } + goto cleanup; + } + if (nsm_req.index >= vnsm->max_pcrs) { + if (error_response(response, NSM_INVALID_INDEX, errp)) { + r = true; + } + goto cleanup; + } + + pcr = &(vnsm->pcrs[nsm_req.index]); + + if (pcr->locked) { + if (error_response(response, NSM_READONLY_INDEX, errp)) { + r = true; + } + goto cleanup; + } + + pcr->locked = true; + + root = cbor_build_string("LockPCR"); + if (!root) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto cleanup; + } + + response->iov_len = len; + r = true; + goto cleanup; + + err: + error_setg(errp, "Failed to initialize LockPCR response"); + + cleanup: + if (root) { + cbor_decref(&root); + } + return r; +} + +/* + * LockPCRs request structure: + * + * { + * Map(1) { + * key = String("LockPCRs"), + * value = Map(1) { + * key = String("range"), + * value = Uint8(pcr) + * } + * } + * } + */ +typedef struct NSMLockPCRsReq { + uint16_t range; +} NSMLockPCRsReq; + +static enum NSMResponseTypes get_nsm_lock_pcrs_req(uint8_t *req, size_t len, + NSMLockPCRsReq *nsm_req) +{ + cbor_item_t *item = NULL; + size_t size; + uint8_t *str; + struct cbor_pair *pair; + struct cbor_load_result result; + enum NSMResponseTypes r = NSM_INVALID_ARGUMENT; + + item = cbor_load(req, len, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + goto cleanup; + } + + pair = cbor_map_handle(item); + if (!cbor_isa_map(pair->value) || cbor_map_size(pair->value) != 1) { + goto cleanup; + } + pair = cbor_map_handle(pair->value); + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 5 || memcmp(str, "range", 5) != 0) { + goto cleanup; + } + if (!cbor_isa_uint(pair->value) || + cbor_int_get_width(pair->value) != CBOR_INT_8) { + goto cleanup; + } + + nsm_req->range = cbor_get_uint8(pair->value); + r = NSM_SUCCESS; + goto cleanup; + + cleanup: + if (item) { + cbor_decref(&item); + } + return r; +} + +/* + * LockPCRs success response structure: + * { + * String("LockPCRs") + * } + */ +static bool handle_LockPCRs(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root = NULL; + size_t len; + NSMLockPCRsReq nsm_req; + enum NSMResponseTypes type; + bool r = false; + + type = get_nsm_lock_pcrs_req(request->iov_base, request->iov_len, &nsm_req); + if (type != NSM_SUCCESS) { + if (error_response(response, type, errp)) { + r = true; + } + goto cleanup; + } + if (nsm_req.range >= vnsm->max_pcrs) { + if (error_response(response, NSM_INVALID_INDEX, errp)) { + r = true; + } + goto cleanup; + } + + for (int i = 0; i <= nsm_req.range; ++i) { + vnsm->pcrs[i].locked = true; + } + + root = cbor_build_string("LockPCRs"); + if (!root) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto cleanup; + } + + response->iov_len = len; + r = true; + goto cleanup; + + err: + error_setg(errp, "Failed to initialize response"); + + cleanup: + if (root) { + cbor_decref(&root); + } + return r; +} + +/* + * Attestation request structure: + * + * Map(1) { + * key = String("Attestation"), + * value = Map(3) { + * key = String("user_data"), + * value = Byte_String() || null, + * key = String("nonce"), + * value = Byte_String() || null, + * key = String("public_key"), + * value = Byte_String() || null, + * } + * } + * } + */ +typedef struct NSMAttestationReq { + uint16_t user_data_len; + uint8_t user_data[NSM_USER_DATA_MAX_SIZE]; + + uint16_t nonce_len; + uint8_t nonce[NSM_NONCE_MAX_SIZE]; + + uint16_t public_key_len; + uint8_t public_key[NSM_PUBLIC_KEY_MAX_SIZE]; +} NSMAttestationReq; + +static enum NSMResponseTypes get_nsm_attestation_req(uint8_t *req, size_t len, + NSMAttestationReq *nsm_req) +{ + cbor_item_t *item = NULL; + size_t size; + uint8_t *str; + struct cbor_pair *pair; + struct cbor_load_result result; + enum NSMResponseTypes r = NSM_INVALID_ARGUMENT; + + item = cbor_load(req, len, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + goto cleanup; + } + + pair = cbor_map_handle(item); + if (!cbor_isa_map(pair->value) || cbor_map_size(pair->value) != 3) { + goto cleanup; + } + pair = cbor_map_handle(pair->value); + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 9 || memcmp(str, "user_data", 9) != 0) { + goto cleanup; + } + + if (cbor_isa_bytestring(pair->value)) { + str = cbor_bytestring_handle(pair->value); + size = cbor_bytestring_length(pair->value); + if (!str || size == 0) { + goto cleanup; + } + if (size > NSM_USER_DATA_MAX_SIZE) { + r = NSM_INPUT_TOO_LARGE; + goto cleanup; + } + memcpy(nsm_req->user_data, str, size); + nsm_req->user_data_len = size; + } else if (cbor_is_null(pair->value)) { + nsm_req->user_data_len = 0; + } else { + goto cleanup; + } + + /* let's move forward */ + pair++; + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 5 || memcmp(str, "nonce", 5) != 0) { + goto cleanup; + } + + if (cbor_isa_bytestring(pair->value)) { + str = cbor_bytestring_handle(pair->value); + size = cbor_bytestring_length(pair->value); + if (!str || size == 0) { + goto cleanup; + } + if (size > NSM_NONCE_MAX_SIZE) { + r = NSM_INPUT_TOO_LARGE; + goto cleanup; + } + memcpy(nsm_req->nonce, str, size); + nsm_req->nonce_len = size; + } else if (cbor_is_null(pair->value)) { + nsm_req->nonce_len = 0; + } else { + goto cleanup; + } + + /* let's move forward */ + pair++; + if (!cbor_isa_string(pair->key)) { + goto cleanup; + } + str = cbor_string_handle(pair->key); + size = cbor_string_length(pair->key); + if (!str || size != 10 || memcmp(str, "public_key", 10) != 0) { + goto cleanup; + } + + if (cbor_isa_bytestring(pair->value)) { + str = cbor_bytestring_handle(pair->value); + size = cbor_bytestring_length(pair->value); + if (!str || size == 0) { + goto cleanup; + } + if (size > NSM_PUBLIC_KEY_MAX_SIZE) { + r = NSM_INPUT_TOO_LARGE; + goto cleanup; + } + memcpy(nsm_req->public_key, str, size); + nsm_req->public_key_len = size; + } else if (cbor_is_null(pair->value)) { + nsm_req->public_key_len = 0; + } else { + goto cleanup; + } + + r = NSM_SUCCESS; + + cleanup: + if (item) { + cbor_decref(&item); + } + return r; +} + +static bool add_protected_header_to_cose(cbor_item_t *cose) +{ + cbor_item_t *map = NULL; + cbor_item_t *key = NULL; + cbor_item_t *value = NULL; + cbor_item_t *bs = NULL; + size_t len; + bool r = false; + size_t buf_len = 4096; + g_autofree uint8_t *buf = g_malloc(buf_len); + + map = cbor_new_definite_map(1); + if (!map) { + goto cleanup; + } + key = cbor_build_uint8(1); + if (!key) { + goto cleanup; + } + value = cbor_new_int8(); + if (!value) { + goto cleanup; + } + cbor_mark_negint(value); + /* we don't actually sign the data, so we use -1 as the 'alg' value */ + cbor_set_uint8(value, 0); + + if (!qemu_cbor_map_add(map, key, value)) { + goto cleanup; + } + + len = cbor_serialize(map, buf, buf_len); + if (len == 0) { + goto cleanup_map; + } + + bs = cbor_build_bytestring(buf, len); + if (!bs) { + goto cleanup_map; + } + if (!qemu_cbor_array_push(cose, bs)) { + cbor_decref(&bs); + goto cleanup_map; + } + r = true; + goto cleanup_map; + + cleanup: + if (key) { + cbor_decref(&key); + } + if (value) { + cbor_decref(&value); + } + + cleanup_map: + if (map) { + cbor_decref(&map); + } + return r; +} + +static bool add_unprotected_header_to_cose(cbor_item_t *cose) +{ + cbor_item_t *map = cbor_new_definite_map(0); + if (!map) { + goto cleanup; + } + if (!qemu_cbor_array_push(cose, map)) { + goto cleanup; + } + + return true; + + cleanup: + if (map) { + cbor_decref(&map); + } + return false; +} + +static bool add_ca_bundle_to_payload(cbor_item_t *map) +{ + cbor_item_t *key_cbor = NULL; + cbor_item_t *value_cbor = NULL; + cbor_item_t *bs = NULL; + uint8_t zero[64] = {0}; + + key_cbor = cbor_build_string("cabundle"); + if (!key_cbor) { + goto cleanup; + } + value_cbor = cbor_new_definite_array(1); + if (!value_cbor) { + goto cleanup; + } + bs = cbor_build_bytestring(zero, 64); + if (!bs) { + goto cleanup; + } + if (!qemu_cbor_array_push(value_cbor, bs)) { + cbor_decref(&bs); + goto cleanup; + } + if (!qemu_cbor_map_add(map, key_cbor, value_cbor)) { + goto cleanup; + } + + return true; + + cleanup: + if (key_cbor) { + cbor_decref(&key_cbor); + } + if (value_cbor) { + cbor_decref(&value_cbor); + } + return false; +} + +static bool add_payload_to_cose(cbor_item_t *cose, VirtIONSM *vnsm) +{ + cbor_item_t *root = NULL; + cbor_item_t *nested_map; + cbor_item_t *bs = NULL; + size_t locked_cnt; + uint8_t ind[NSM_MAX_PCRS]; + size_t payload_map_size = 8; + size_t len; + struct PCRInfo *pcr; + uint8_t zero[64] = {0}; + bool r = false; + size_t buf_len = 16384; + g_autofree uint8_t *buf = g_malloc(buf_len); + + if (vnsm->public_key_len > 0) { + payload_map_size++; + } + + root = cbor_new_definite_map(payload_map_size); + if (!root) { + goto cleanup; + } + if (!qemu_cbor_add_string_to_map(root, "module_id", vnsm->module_id)) { + goto cleanup; + } + if (!qemu_cbor_add_string_to_map(root, "digest", vnsm->digest)) { + goto cleanup; + } + if (!qemu_cbor_add_uint64_to_map(root, "timestamp", + (uint64_t) time(NULL) * 1000)) { + goto cleanup; + } + + locked_cnt = 0; + for (uint8_t i = 0; i < NSM_MAX_PCRS; ++i) { + if (vnsm->pcrs[i].locked) { + ind[locked_cnt++] = i; + } + } + if (!qemu_cbor_add_map_to_map(root, "pcrs", locked_cnt, &nested_map)) { + goto cleanup; + } + for (uint8_t i = 0; i < locked_cnt; ++i) { + pcr = &(vnsm->pcrs[ind[i]]); + if (!qemu_cbor_add_uint8_key_bytestring_to_map( + nested_map, ind[i], + pcr->data, + QCRYPTO_HASH_DIGEST_LEN_SHA384)) { + goto cleanup; + } + } + if (!qemu_cbor_add_bytestring_to_map(root, "certificate", zero, 64)) { + goto cleanup; + } + if (!add_ca_bundle_to_payload(root)) { + goto cleanup; + } + if (vnsm->public_key_len > 0 && + !qemu_cbor_add_bytestring_to_map(root, "public_key", vnsm->public_key, + vnsm->public_key_len)) { + goto cleanup; + } + if (!qemu_cbor_add_bytestring_or_null_to_map(root, "user_data", + vnsm->user_data, + vnsm->user_data_len)) { + goto cleanup; + } + if (!qemu_cbor_add_bytestring_or_null_to_map(root, "nonce", vnsm->nonce, + vnsm->nonce_len)) { + goto cleanup; + } + len = cbor_serialize(root, buf, buf_len); + if (len == 0) { + goto cleanup; + } + + bs = cbor_build_bytestring(buf, len); + if (!bs) { + goto cleanup; + } + if (!qemu_cbor_array_push(cose, bs)) { + cbor_decref(&bs); + goto cleanup; + } + + r = true; + + cleanup: + if (root) { + cbor_decref(&root); + } + return r; +} + +static bool add_signature_to_cose(cbor_item_t *cose) +{ + cbor_item_t *bs = NULL; + uint8_t zero[64] = {0}; + + /* we don't actually sign the data, so we just put 64 zero bytes */ + bs = cbor_build_bytestring(zero, 64); + if (!bs) { + goto cleanup; + } + + if (!qemu_cbor_array_push(cose, bs)) { + goto cleanup; + } + + return true; + + cleanup: + if (bs) { + cbor_decref(&bs); + } + return false; +} + +/* + * Attestation response structure: + * + * { + * Map(1) { + * key = String("Attestation"), + * value = Map(1) { + * key = String("document"), + * value = Byte_String() + * } + * } + * } + * + * The document is a serialized COSE sign1 blob of the structure: + * { + * Array(4) { + * [0] { ByteString() }, // serialized protected header + * [1] { Map(0) }, // 0 length map + * [2] { ByteString() }, // serialized payload + * [3] { ByteString() }, // signature + * } + * } + * + * where [0] protected header is a serialized CBOR blob of the structure: + * { + * Map(1) { + * key = Uint8(1) // alg + * value = NegativeInt8() // Signing algorithm + * } + * } + * + * [2] payload is serialized CBOR blob of the structure: + * { + * Map(9/8/7) { + * [0] { key = String("module_id"), value = String(module_id) }, + * [1] { key = String("digest"), value = String("SHA384") }, + * [2] { + * key = String("timestamp"), + * value = Uint64(unix epoch of when document was created) + * }, + * [3] { + * key = String("pcrs"), + * value = Map(locked_pcr_cnt) { + * key = Uint8(pcr_index), + * value = ByteString(pcr_data) + * }, + * }, + * [4] { + * key = String("certificate"), + * value = ByteString(Signing certificate) + * }, + * [5] { key = String("cabundle"), value = Array(N) { ByteString()... } }, + * [6] { key = String("public_key"), value = ByteString() }, // optional + * [7] { key = String("user_data"), value = ByteString() }, // optional + * [8] { key = String("nonce"), value = ByteString() }, // optional + * } + * } + */ +static bool handle_Attestation(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp) +{ + cbor_item_t *root = NULL; + cbor_item_t *cose = NULL; + cbor_item_t *nested_map; + size_t len; + NSMAttestationReq nsm_req; + enum NSMResponseTypes type; + bool r = false; + size_t buf_len = 16384; + g_autofree uint8_t *buf = g_malloc(buf_len); + + type = get_nsm_attestation_req(request->iov_base, request->iov_len, + &nsm_req); + if (type != NSM_SUCCESS) { + if (error_response(response, type, errp)) { + r = true; + } + goto out; + } + + cose = cbor_new_definite_array(4); + if (!cose) { + goto err; + } + if (!add_protected_header_to_cose(cose)) { + goto err; + } + if (!add_unprotected_header_to_cose(cose)) { + goto err; + } + + if (nsm_req.public_key_len > 0) { + memcpy(vnsm->public_key, nsm_req.public_key, nsm_req.public_key_len); + vnsm->public_key_len = nsm_req.public_key_len; + } + if (nsm_req.user_data_len > 0) { + memcpy(vnsm->user_data, nsm_req.user_data, nsm_req.user_data_len); + vnsm->user_data_len = nsm_req.user_data_len; + } + if (nsm_req.nonce_len > 0) { + memcpy(vnsm->nonce, nsm_req.nonce, nsm_req.nonce_len); + vnsm->nonce_len = nsm_req.nonce_len; + } + + if (!add_payload_to_cose(cose, vnsm)) { + goto err; + } + + if (!add_signature_to_cose(cose)) { + goto err; + } + + len = cbor_serialize(cose, buf, buf_len); + if (len == 0) { + goto err; + } + + root = cbor_new_definite_map(1); + if (!root) { + goto err; + } + if (!qemu_cbor_add_map_to_map(root, "Attestation", 1, &nested_map)) { + goto err; + } + if (!qemu_cbor_add_bytestring_to_map(nested_map, "document", buf, len)) { + goto err; + } + + len = cbor_serialize(root, response->iov_base, response->iov_len); + if (len == 0) { + if (error_response(response, NSM_BUFFER_TOO_SMALL, errp)) { + r = true; + } + goto out; + } + + response->iov_len = len; + r = true; + + out: + if (root) { + cbor_decref(&root); + } + if (cose) { + cbor_decref(&cose); + } + return r; + + err: + error_setg(errp, "Failed to initialize Attestation response"); + goto out; +} + +enum CBOR_ROOT_TYPE { + CBOR_ROOT_TYPE_STRING = 0, + CBOR_ROOT_TYPE_MAP = 1, +}; + +struct nsm_cmd { + char name[16]; + /* + * There are 2 types of request + * 1) String(); "GetRandom", "DescribeNSM" + * 2) Map(1) { key: String(), value: ... } + */ + enum CBOR_ROOT_TYPE root_type; + bool (*response_fn)(VirtIONSM *vnsm, struct iovec *request, + struct iovec *response, Error **errp); +}; + +const struct nsm_cmd nsm_cmds[] = { + { "GetRandom", CBOR_ROOT_TYPE_STRING, handle_GetRandom }, + { "DescribeNSM", CBOR_ROOT_TYPE_STRING, handle_DescribeNSM }, + { "DescribePCR", CBOR_ROOT_TYPE_MAP, handle_DescribePCR }, + { "ExtendPCR", CBOR_ROOT_TYPE_MAP, handle_ExtendPCR }, + { "LockPCR", CBOR_ROOT_TYPE_MAP, handle_LockPCR }, + { "LockPCRs", CBOR_ROOT_TYPE_MAP, handle_LockPCRs }, + { "Attestation", CBOR_ROOT_TYPE_MAP, handle_Attestation }, +}; + +static const struct nsm_cmd *get_nsm_request_cmd(uint8_t *buf, size_t len) +{ + size_t size; + uint8_t *req; + enum CBOR_ROOT_TYPE root_type; + struct cbor_load_result result; + cbor_item_t *item = cbor_load(buf, len, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + goto cleanup; + } + + if (cbor_isa_string(item)) { + size = cbor_string_length(item); + req = cbor_string_handle(item); + root_type = CBOR_ROOT_TYPE_STRING; + } else if (cbor_isa_map(item) && cbor_map_size(item) == 1) { + struct cbor_pair *handle = cbor_map_handle(item); + if (cbor_isa_string(handle->key)) { + size = cbor_string_length(handle->key); + req = cbor_string_handle(handle->key); + root_type = CBOR_ROOT_TYPE_MAP; + } else { + goto cleanup; + } + } else { + goto cleanup; + } + + if (size == 0 || req == NULL) { + goto cleanup; + } + + for (int i = 0; i < ARRAY_SIZE(nsm_cmds); ++i) { + if (nsm_cmds[i].root_type == root_type && + strlen(nsm_cmds[i].name) == size && + memcmp(nsm_cmds[i].name, req, size) == 0) { + cbor_decref(&item); + return &nsm_cmds[i]; + } + } + + cleanup: + if (item) { + cbor_decref(&item); + } + return NULL; +} + +static bool get_nsm_request_response(VirtIONSM *vnsm, struct iovec *req, + struct iovec *resp, Error **errp) +{ + const struct nsm_cmd *cmd; + + cmd = get_nsm_request_cmd(req->iov_base, req->iov_len); + + if (cmd == NULL) { + if (error_response(resp, NSM_INVALID_OPERATION, errp)) { + return true; + } + error_setg(errp, "Failed to initialize InvalidOperation response"); + return false; + } + + return cmd->response_fn(vnsm, req, resp, errp); +} + +static void handle_input(VirtIODevice *vdev, VirtQueue *vq) +{ + g_autofree VirtQueueElement *out_elem = NULL; + g_autofree VirtQueueElement *in_elem = NULL; + VirtIONSM *vnsm = VIRTIO_NSM(vdev); + Error *err = NULL; + + out_elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); + if (!out_elem) { + /* nothing in virtqueue */ + return; + } + + if (out_elem->out_num != 1) { + virtio_error(vdev, "Expected one request buffer first in virtqueue"); + goto cleanup; + } + + in_elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); + if (!in_elem) { + virtio_error(vdev, "Expected response buffer after request buffer " + "in virtqueue"); + goto cleanup; + } + if (in_elem->in_num != 1) { + virtio_error(vdev, "Expected one response buffer after request buffer " + "in virtqueue"); + goto cleanup; + } + + if (!get_nsm_request_response(vnsm, out_elem->out_sg, in_elem->in_sg, + &err)) { + error_report_err(err); + virtio_error(vdev, "Failed to get NSM request response"); + goto cleanup; + } + + virtqueue_push(vq, out_elem, 0); + virtqueue_push(vq, in_elem, in_elem->in_sg->iov_len); + virtio_notify(vdev, vq); + return; + + cleanup: + if (out_elem) { + virtqueue_detach_element(vq, out_elem, 0); + } + if (in_elem) { + virtqueue_detach_element(vq, in_elem, 0); + } + return; +} + +static uint64_t get_features(VirtIODevice *vdev, uint64_t f, Error **errp) +{ + return f; +} + +static bool extend_pcr(VirtIONSM *vnsm, int ind, uint8_t *data, uint16_t len) +{ + Error *err = NULL; + struct PCRInfo *pcr = &(vnsm->pcrs[ind]); + size_t digest_len = QCRYPTO_HASH_DIGEST_LEN_SHA384; + uint8_t result[QCRYPTO_HASH_DIGEST_LEN_SHA384]; + uint8_t *ptr = result; + struct iovec iov[2] = { + { .iov_base = pcr->data, .iov_len = QCRYPTO_HASH_DIGEST_LEN_SHA384 }, + { .iov_base = data, .iov_len = len }, + }; + + if (qcrypto_hash_bytesv(QCRYPTO_HASH_ALG_SHA384, iov, 2, &ptr, &digest_len, + &err) < 0) { + return false; + } + + memcpy(pcr->data, result, QCRYPTO_HASH_DIGEST_LEN_SHA384); + return true; +} + +static void lock_pcr(VirtIONSM *vnsm, int ind) +{ + vnsm->pcrs[ind].locked = true; +} + +static void virtio_nsm_device_realize(DeviceState *dev, Error **errp) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIONSM *vnsm = VIRTIO_NSM(dev); + + vnsm->max_pcrs = NSM_MAX_PCRS; + vnsm->digest = (char *) "SHA384"; + if (vnsm->module_id == NULL) { + vnsm->module_id = (char *) "i-234-enc5678"; + } + vnsm->version_major = 1; + vnsm->version_minor = 0; + vnsm->version_patch = 0; + vnsm->extend_pcr = extend_pcr; + vnsm->lock_pcr = lock_pcr; + + virtio_init(vdev, VIRTIO_ID_NITRO_SEC_MOD, 0); + + vnsm->vq = virtio_add_queue(vdev, 2, handle_input); +} + +static void virtio_nsm_device_unrealize(DeviceState *dev) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + + virtio_del_queue(vdev, 0); + virtio_cleanup(vdev); +} + +static const VMStateDescription vmstate_virtio_nsm = { + .name = "virtio-nsm", + .minimum_version_id = 1, + .version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_VIRTIO_DEVICE, + VMSTATE_END_OF_LIST() + }, +}; + +static Property virtio_nsm_properties[] = { + DEFINE_PROP_STRING("module-id", VirtIONSM, module_id), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_nsm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + + device_class_set_props(dc, virtio_nsm_properties); + dc->vmsd = &vmstate_virtio_nsm; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + vdc->realize = virtio_nsm_device_realize; + vdc->unrealize = virtio_nsm_device_unrealize; + vdc->get_features = get_features; +} + +static const TypeInfo virtio_nsm_info = { + .name = TYPE_VIRTIO_NSM, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VirtIONSM), + .class_init = virtio_nsm_class_init, +}; + +static void virtio_register_types(void) +{ + type_register_static(&virtio_nsm_info); +} + +type_init(virtio_register_types) diff --git a/include/hw/virtio/cbor-helpers.h b/include/hw/virtio/cbor-helpers.h new file mode 100644 index 0000000000..fe3a2077fb --- /dev/null +++ b/include/hw/virtio/cbor-helpers.h @@ -0,0 +1,46 @@ +/* + * QEMU CBOR helpers + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#ifndef QEMU_VIRTIO_CBOR_HELPERS_H +#define QEMU_VIRTIO_CBOR_HELPERS_H + +#include + +bool qemu_cbor_map_add(cbor_item_t *map, cbor_item_t *key, cbor_item_t *value); + +bool qemu_cbor_array_push(cbor_item_t *array, cbor_item_t *value); + +bool qemu_cbor_add_bool_to_map(cbor_item_t *map, const char *key, bool value); + +bool qemu_cbor_add_uint8_to_map(cbor_item_t *map, const char *key, + uint8_t value); + +bool qemu_cbor_add_map_to_map(cbor_item_t *map, const char *key, + size_t nested_map_size, + cbor_item_t **nested_map); + +bool qemu_cbor_add_bytestring_to_map(cbor_item_t *map, const char *key, + uint8_t *arr, size_t len); + +bool qemu_cbor_add_bytestring_or_null_to_map(cbor_item_t *map, const char *key, + uint8_t *arr, size_t len); + +bool qemu_cbor_add_string_to_map(cbor_item_t *map, const char *key, + const char *value); + +bool qemu_cbor_add_uint8_array_to_map(cbor_item_t *map, const char *key, + uint8_t *arr, size_t len); + +bool qemu_cbor_add_uint8_key_bytestring_to_map(cbor_item_t *map, uint8_t key, + uint8_t *buf, size_t len); + +bool qemu_cbor_add_uint64_to_map(cbor_item_t *map, const char *key, + uint64_t value); +#endif diff --git a/include/hw/virtio/virtio-nsm.h b/include/hw/virtio/virtio-nsm.h new file mode 100644 index 0000000000..fdeb6fd815 --- /dev/null +++ b/include/hw/virtio/virtio-nsm.h @@ -0,0 +1,59 @@ +/* + * AWS Nitro Secure Module (NSM) device + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#ifndef QEMU_VIRTIO_NSM_H +#define QEMU_VIRTIO_NSM_H + +#include "crypto/hash.h" +#include "hw/virtio/virtio.h" +#include "qom/object.h" + +#define NSM_MAX_PCRS 32 +#define NSM_USER_DATA_MAX_SIZE 512 +#define NSM_NONCE_MAX_SIZE 512 +#define NSM_PUBLIC_KEY_MAX_SIZE 1024 + +#define TYPE_VIRTIO_NSM "virtio-nsm-device" +OBJECT_DECLARE_SIMPLE_TYPE(VirtIONSM, VIRTIO_NSM) +#define VIRTIO_NSM_GET_PARENT_CLASS(obj) \ + OBJECT_GET_PARENT_CLASS(obj, TYPE_VIRTIO_NSM) + +struct PCRInfo { + bool locked; + uint8_t data[QCRYPTO_HASH_DIGEST_LEN_SHA384]; +}; + +struct VirtIONSM { + VirtIODevice parent_obj; + + /* Only one vq - guest puts request and response buffers on it */ + VirtQueue *vq; + + /* NSM State */ + uint16_t max_pcrs; + struct PCRInfo pcrs[NSM_MAX_PCRS]; + char *digest; + char *module_id; + uint8_t version_major; + uint8_t version_minor; + uint8_t version_patch; + + uint16_t public_key_len; + uint8_t public_key[NSM_PUBLIC_KEY_MAX_SIZE]; + uint16_t user_data_len; + uint8_t user_data[NSM_USER_DATA_MAX_SIZE]; + uint16_t nonce_len; + uint8_t nonce[NSM_NONCE_MAX_SIZE]; + + bool (*extend_pcr)(VirtIONSM *vnsm, int ind, uint8_t *data, uint16_t len); + void (*lock_pcr)(VirtIONSM *vnsm, int ind); +}; + +#endif diff --git a/meson.build b/meson.build index fbda17c987..b78a833803 100644 --- a/meson.build +++ b/meson.build @@ -1661,6 +1661,8 @@ if (have_system or have_tools) and (virgl.found() or opengl.found()) endif have_vhost_user_gpu = have_vhost_user_gpu and virgl.found() and opengl.found() and gbm.found() +libcbor = dependency('libcbor', version: '>=0.7.0', required: false) + gnutls = not_found gnutls_crypto = not_found if get_option('gnutls').enabled() or (get_option('gnutls').auto() and have_system) From patchwork Thu Aug 22 15:08:47 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773706 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id E5340C52D7C for ; Thu, 22 Aug 2024 15:11:36 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9Ro-0008VI-QX; Thu, 22 Aug 2024 11:10:00 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9Rm-0008L9-WE for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:59 -0400 Received: from mail-pg1-x52a.google.com ([2607:f8b0:4864:20::52a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9Rg-00053M-HQ for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:09:58 -0400 Received: by mail-pg1-x52a.google.com with SMTP id 41be03b00d2f7-7cd8afc9ff3so1382133a12.0 for ; Thu, 22 Aug 2024 08:09:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339388; x=1724944188; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=jJEbNg0nOptldUe0A9tiWpoxdcQnm26BHvU8e3rFvX8=; b=KnstvTdgtVxgZtEZfCqxcw7RpbTL4PYPaJv9RKoV5HKhVOfezt4Cm2lA3cUUJlYU5c 7eNliLQ9jeqJ3XCKqxfNHE9Cri1fQA3YyCzA2s9ZpnOz/Lc3uAiDVLUHtGBTL1NvBIMX 0ynkReFVCo4CyzOhURXvX4yZs2oIM50XYDFBn14sPGmJ6yzT7kd3XGdoRrQUgKq11MrT 36Q+FXJyVssZYYFQeI5za3VD4eTF20xZLDUtNC6205KfFh5OxxGZk0CsDFj7PItZ2RRX O6OKlt5jsTMkUh3XcKjObWlbKqvqnhPrpHY6Imkcu1A742LamdjJzfkKlgRy9+4ALHiq GcOg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339388; x=1724944188; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=jJEbNg0nOptldUe0A9tiWpoxdcQnm26BHvU8e3rFvX8=; b=QHnO91SlPbrVz/BG5uEKet/D41P6wdok81AAX/MmXDNX+XaUNd4vyV4OFlXaG5JHsb Nzfn1w7zpjrx9tLrgkczkfl0ob9sBCpNAkQMYtR2b0mEFifg5TRwooezZ6xKo1uFqh0S cWxioDeMTwTHmjZSTGQheIWKO9y7qt11hySOxu3pNt6iFS9X6QDD2jfew7ddf1QogcGd tpxFIQfV046vEXCJRC44Nlwv4OcAg/BUBBy7juyAQtEFTg6gzdQQXhn3coB3jYS9QJWE WZ73e7Sx12SMcAJdC53IWpOvVaFTKTYyES+h9wc7szWLy6HvPZX8q4aK2aIr5YtjIL5E 3UAQ== X-Gm-Message-State: AOJu0Ywisa0tzCuAx94AZTsQFi3aqmGuYkLaLctlYzlXmQnMUwph4jI7 ocUsflzOH3HZ4LFW3ZY+VI4SXVuF7Wnt6V598TTk7PHhv6lFYrGzUuiOAQ== X-Google-Smtp-Source: AGHT+IHttOWluDS1n5aEMZSCWpgedhA8IX5NuHcTzcYVfstyTRZc2PoQ76n/rz8KJl0hAGu6nhGhjw== X-Received: by 2002:a17:90a:70c4:b0:2c9:5a71:1500 with SMTP id 98e67ed59e1d1-2d60a747c55mr5167333a91.0.1724339388475; Thu, 22 Aug 2024 08:09:48 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:48 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 6/8] hw/core: Add Enclave Image Format (EIF) related helpers Date: Thu, 22 Aug 2024 21:08:47 +0600 Message-Id: <20240822150849.21759-7-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::52a; envelope-from=dorjoychy111@gmail.com; helo=mail-pg1-x52a.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org An EIF (Enclave Image Format)[1] file is used to boot an AWS nitro enclave[2] virtual machine. The EIF file contains the necessary kernel, cmdline, ramdisk(s) sections to boot. Some helper functions have been introduced for extracting the necessary sections from an EIF file and then writing them to temporary files as well as computing SHA384 hashes from the section data. These will be used in the following commit to add support for nitro-enclave machine type in QEMU. [1] https://github.com/aws/aws-nitro-enclaves-image-format [2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html Signed-off-by: Dorjoy Chowdhury --- hw/core/eif.c | 719 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/core/eif.h | 22 ++ 2 files changed, 741 insertions(+) create mode 100644 hw/core/eif.c create mode 100644 hw/core/eif.h diff --git a/hw/core/eif.c b/hw/core/eif.c new file mode 100644 index 0000000000..2cfd5c911e --- /dev/null +++ b/hw/core/eif.c @@ -0,0 +1,719 @@ +/* + * EIF (Enclave Image Format) related helpers + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/bswap.h" +#include "qapi/error.h" +#include "crypto/hash.h" +#include "crypto/x509-utils.h" +#include /* for crc32 */ +#include + +#include "hw/core/eif.h" + +#define MAX_SECTIONS 32 + +/* members are ordered according to field order in .eif file */ +typedef struct EifHeader { + uint8_t magic[4]; /* must be .eif in ascii i.e., [46, 101, 105, 102] */ + uint16_t version; + uint16_t flags; + uint64_t default_memory; + uint64_t default_cpus; + uint16_t reserved; + uint16_t section_cnt; + uint64_t section_offsets[MAX_SECTIONS]; + uint64_t section_sizes[MAX_SECTIONS]; + uint32_t unused; + uint32_t eif_crc32; +} QEMU_PACKED EifHeader; + +/* members are ordered according to field order in .eif file */ +typedef struct EifSectionHeader { + /* + * 0 = invalid, 1 = kernel, 2 = cmdline, 3 = ramdisk, 4 = signature, + * 5 = metadata + */ + uint16_t section_type; + uint16_t flags; + uint64_t section_size; +} QEMU_PACKED EifSectionHeader; + +enum EifSectionTypes { + EIF_SECTION_INVALID = 0, + EIF_SECTION_KERNEL = 1, + EIF_SECTION_CMDLINE = 2, + EIF_SECTION_RAMDISK = 3, + EIF_SECTION_SIGNATURE = 4, + EIF_SECTION_METADATA = 5, + EIF_SECTION_MAX = 6, +}; + +static const char *section_type_to_string(uint16_t type) +{ + const char *str; + switch (type) { + case EIF_SECTION_INVALID: + str = "invalid"; + break; + case EIF_SECTION_KERNEL: + str = "kernel"; + break; + case EIF_SECTION_CMDLINE: + str = "cmdline"; + break; + case EIF_SECTION_RAMDISK: + str = "ramdisk"; + break; + case EIF_SECTION_SIGNATURE: + str = "signature"; + break; + case EIF_SECTION_METADATA: + str = "metadata"; + break; + default: + str = "unknown"; + break; + } + + return str; +} + +static bool read_eif_header(FILE *f, EifHeader *header, uint32_t *crc, + Error **errp) +{ + size_t got; + size_t header_size = sizeof(*header); + + got = fread(header, 1, header_size, f); + if (got != header_size) { + error_setg(errp, "Failed to read EIF header"); + return false; + } + + if (memcmp(header->magic, ".eif", 4) != 0) { + error_setg(errp, "Invalid EIF image. Magic mismatch."); + return false; + } + + /* Exclude header->eif_crc32 field from CRC calculation */ + *crc = crc32(*crc, (uint8_t *)header, header_size - 4); + + header->version = be16_to_cpu(header->version); + header->flags = be16_to_cpu(header->flags); + header->default_memory = be64_to_cpu(header->default_memory); + header->default_cpus = be64_to_cpu(header->default_cpus); + header->reserved = be16_to_cpu(header->reserved); + header->section_cnt = be16_to_cpu(header->section_cnt); + + for (int i = 0; i < MAX_SECTIONS; ++i) { + header->section_offsets[i] = be64_to_cpu(header->section_offsets[i]); + } + + for (int i = 0; i < MAX_SECTIONS; ++i) { + header->section_sizes[i] = be64_to_cpu(header->section_sizes[i]); + } + + header->unused = be32_to_cpu(header->unused); + header->eif_crc32 = be32_to_cpu(header->eif_crc32); + return true; +} + +static bool read_eif_section_header(FILE *f, EifSectionHeader *section_header, + uint32_t *crc, Error **errp) +{ + size_t got; + size_t section_header_size = sizeof(*section_header); + + got = fread(section_header, 1, section_header_size, f); + if (got != section_header_size) { + error_setg(errp, "Failed to read EIF section header"); + return false; + } + + *crc = crc32(*crc, (uint8_t *)section_header, section_header_size); + + section_header->section_type = be16_to_cpu(section_header->section_type); + section_header->flags = be16_to_cpu(section_header->flags); + section_header->section_size = be64_to_cpu(section_header->section_size); + return true; +} + +/* + * Upon success, the caller is responsible for unlinking and freeing *tmp_path. + */ +static bool get_tmp_file(const char *template, char **tmp_path, Error **errp) +{ + int tmp_fd; + + *tmp_path = NULL; + tmp_fd = g_file_open_tmp(template, tmp_path, NULL); + if (tmp_fd < 0 || *tmp_path == NULL) { + error_setg(errp, "Failed to create temporary file for template %s", + template); + return false; + } + + close(tmp_fd); + return true; +} + +static void safe_fclose(FILE *f) +{ + if (f) { + fclose(f); + } +} + +static void safe_unlink(char *f) +{ + if (f) { + unlink(f); + } +} + +/* + * Upon success, the caller is reponsible for unlinking and freeing *kernel_path + */ +static bool read_eif_kernel(FILE *f, uint64_t size, char **kernel_path, + uint8_t *kernel, uint32_t *crc, Error **errp) +{ + size_t got; + FILE *tmp_file = NULL; + + *kernel_path = NULL; + if (!get_tmp_file("eif-kernel-XXXXXX", kernel_path, errp)) { + goto cleanup; + } + + tmp_file = fopen(*kernel_path, "wb"); + if (tmp_file == NULL) { + error_setg_errno(errp, errno, "Failed to open temporary file %s", + *kernel_path); + goto cleanup; + } + + got = fread(kernel, 1, size, f); + if ((uint64_t) got != size) { + error_setg(errp, "Failed to read EIF kernel section data"); + goto cleanup; + } + + got = fwrite(kernel, 1, size, tmp_file); + if ((uint64_t) got != size) { + error_setg(errp, "Failed to write EIF kernel section data to temporary" + " file"); + goto cleanup; + } + + *crc = crc32(*crc, kernel, size); + fclose(tmp_file); + + return true; + + cleanup: + safe_fclose(tmp_file); + + safe_unlink(*kernel_path); + g_free(*kernel_path); + *kernel_path = NULL; + + return false; +} + +static bool read_eif_cmdline(FILE *f, uint64_t size, char *cmdline, + uint32_t *crc, Error **errp) +{ + size_t got = fread(cmdline, 1, size, f); + if ((uint64_t) got != size) { + error_setg(errp, "Failed to read EIF cmdline section data"); + return false; + } + + *crc = crc32(*crc, (uint8_t *)cmdline, size); + return true; +} + +static bool read_eif_ramdisk(FILE *eif, FILE *initrd, uint64_t size, + uint8_t *ramdisk, uint32_t *crc, Error **errp) +{ + size_t got; + + got = fread(ramdisk, 1, size, eif); + if ((uint64_t) got != size) { + error_setg(errp, "Failed to read EIF ramdisk section data"); + return false; + } + + got = fwrite(ramdisk, 1, size, initrd); + if ((uint64_t) got != size) { + error_setg(errp, "Failed to write EIF ramdisk data to temporary file"); + return false; + } + + *crc = crc32(*crc, ramdisk, size); + return true; +} + +static bool get_signature_fingerprint_sha384(FILE *eif, uint64_t size, + uint8_t *sha384, + uint32_t *crc, + Error **errp) +{ + size_t got; + g_autofree uint8_t *sig = NULL; + g_autofree uint8_t *cert = NULL; + cbor_item_t *item = NULL; + cbor_item_t *pcr0 = NULL; + size_t len; + size_t hash_len = QCRYPTO_HASH_DIGEST_LEN_SHA384; + struct cbor_pair *pair; + struct cbor_load_result result; + bool ret = false; + + sig = g_malloc(size); + got = fread(sig, 1, size, eif); + if ((uint64_t) got != size) { + error_setg(errp, "Failed to read EIF signature section data"); + goto cleanup; + } + + *crc = crc32(*crc, sig, size); + + item = cbor_load(sig, size, &result); + if (!item || result.error.code != CBOR_ERR_NONE) { + error_setg(errp, "Failed to load signature section data as CBOR"); + goto cleanup; + } + if (!cbor_isa_array(item) || cbor_array_size(item) < 1) { + error_setg(errp, "Invalid signature CBOR"); + goto cleanup; + } + pcr0 = cbor_array_get(item, 0); + if (!pcr0) { + error_setg(errp, "Failed to get PCR0 signature"); + goto cleanup; + } + if (!cbor_isa_map(pcr0) || cbor_map_size(pcr0) != 2) { + error_setg(errp, "Invalid signature CBOR"); + goto cleanup; + } + pair = cbor_map_handle(pcr0); + if (!cbor_isa_string(pair->key) || cbor_string_length(pair->key) != 19 || + memcmp(cbor_string_handle(pair->key), "signing_certificate", 19) != 0) { + error_setg(errp, "Invalid signautre CBOR"); + goto cleanup; + } + if (!cbor_isa_array(pair->value)) { + error_setg(errp, "Invalid signature CBOR"); + goto cleanup; + } + len = cbor_array_size(pair->value); + if (len == 0) { + error_setg(errp, "Invalid signature CBOR"); + goto cleanup; + } + cert = g_malloc(len); + for (int i = 0; i < len; ++i) { + cbor_item_t *tmp = cbor_array_get(pair->value, i); + if (!tmp) { + error_setg(errp, "Invalid signature CBOR"); + goto cleanup; + } + if (!cbor_isa_uint(tmp) || cbor_int_get_width(tmp) != CBOR_INT_8) { + cbor_decref(&tmp); + error_setg(errp, "Invalid signature CBOR"); + goto cleanup; + } + cert[i] = cbor_get_uint8(tmp); + cbor_decref(&tmp); + } + + if (qcrypto_get_x509_cert_fingerprint(cert, len, QCRYPTO_HASH_ALG_SHA384, + sha384, &hash_len, errp)) { + goto cleanup; + } + + ret = true; + + cleanup: + if (pcr0) { + cbor_decref(&pcr0); + } + if (item) { + cbor_decref(&item); + } + return ret; +} + +/* Expects file to have offset 0 before this function is called */ +static long get_file_size(FILE *f, Error **errp) +{ + long size; + + if (fseek(f, 0, SEEK_END) != 0) { + error_setg_errno(errp, errno, "Failed to seek to the end of file"); + return -1; + } + + size = ftell(f); + if (size == -1) { + error_setg_errno(errp, errno, "Failed to get offset"); + return -1; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + error_setg_errno(errp, errno, "Failed to seek back to the start"); + return -1; + } + + return size; +} + +static bool get_SHA384_digest(GList *list, uint8_t *digest, Error **errp) +{ + size_t digest_len = QCRYPTO_HASH_DIGEST_LEN_SHA384; + size_t list_len = g_list_length(list); + struct iovec *iovec_list = g_malloc(list_len * sizeof(struct iovec)); + bool ret = true; + GList *l; + int i; + + for (i = 0, l = list; l != NULL; l = l->next, i++) { + iovec_list[i] = *(struct iovec *) l->data; + } + + if (qcrypto_hash_bytesv(QCRYPTO_HASH_ALG_SHA384, iovec_list, list_len, + &digest, &digest_len, errp) < 0) { + ret = false; + } + + g_free(iovec_list); + return ret; +} + +static void free_iovec(struct iovec *iov) +{ + if (iov) { + g_free(iov->iov_base); + g_free(iov); + } +} + +/* + * Upon success, the caller is reponsible for unlinking and freeing + * *kernel_path, *initrd_path and freeing *cmdline. + */ +bool read_eif_file(const char *eif_path, const char *machine_initrd, + char **kernel_path, char **initrd_path, char **cmdline, + uint8_t *image_sha384, uint8_t *bootstrap_sha384, + uint8_t *app_sha384, uint8_t *fingerprint_sha384, + bool *signature_found, Error **errp) +{ + FILE *f = NULL; + FILE *machine_initrd_f = NULL; + FILE *initrd_path_f = NULL; + long machine_initrd_size; + uint32_t crc = 0; + EifHeader eif_header; + bool seen_sections[EIF_SECTION_MAX] = {false}; + /* kernel + ramdisks + cmdline sha384 hash */ + GList *iov_PCR0 = NULL; + /* kernel + boot ramdisk + cmdline sha384 hash */ + GList *iov_PCR1 = NULL; + /* application ramdisk(s) hash */ + GList *iov_PCR2 = NULL; + uint8_t *ptr = NULL; + struct iovec *iov_ptr = NULL; + + *signature_found = false; + *kernel_path = *initrd_path = *cmdline = NULL; + + f = fopen(eif_path, "rb"); + if (f == NULL) { + error_setg_errno(errp, errno, "Failed to open %s", eif_path); + goto cleanup; + } + + if (!read_eif_header(f, &eif_header, &crc, errp)) { + goto cleanup; + } + + if (eif_header.version < 4) { + error_setg(errp, "Expected EIF version 4 or greater"); + goto cleanup; + } + + if (eif_header.flags != 0) { + error_setg(errp, "Expected EIF flags to be 0"); + goto cleanup; + } + + if (eif_header.section_cnt > MAX_SECTIONS) { + error_setg(errp, "EIF header section count must not be greater than " + "%d but found %d", MAX_SECTIONS, eif_header.section_cnt); + goto cleanup; + } + + for (int i = 0; i < eif_header.section_cnt; ++i) { + EifSectionHeader hdr; + uint16_t section_type; + + if (fseek(f, eif_header.section_offsets[i], SEEK_SET) != 0) { + error_setg_errno(errp, errno, "Failed to offset to %lu in EIF file", + eif_header.section_offsets[i]); + goto cleanup; + } + + if (!read_eif_section_header(f, &hdr, &crc, errp)) { + goto cleanup; + } + + if (hdr.flags != 0) { + error_setg(errp, "Expected EIF section header flags to be 0"); + goto cleanup; + } + + if (eif_header.section_sizes[i] != hdr.section_size) { + error_setg(errp, "EIF section size mismatch between header and " + "section header: header %lu, section header %lu", + eif_header.section_sizes[i], + hdr.section_size); + goto cleanup; + } + + section_type = hdr.section_type; + + switch (section_type) { + case EIF_SECTION_KERNEL: + if (seen_sections[EIF_SECTION_KERNEL]) { + error_setg(errp, "Invalid EIF image. More than 1 kernel " + "section"); + goto cleanup; + } + + ptr = g_malloc(hdr.section_size); + + iov_ptr = g_malloc(sizeof(struct iovec)); + iov_ptr->iov_base = ptr; + iov_ptr->iov_len = hdr.section_size; + + iov_PCR0 = g_list_append(iov_PCR0, iov_ptr); + iov_PCR1 = g_list_append(iov_PCR1, iov_ptr); + + if (!read_eif_kernel(f, hdr.section_size, kernel_path, ptr, &crc, + errp)) { + goto cleanup; + } + + break; + case EIF_SECTION_CMDLINE: + { + uint64_t size; + uint8_t *cmdline_copy; + if (seen_sections[EIF_SECTION_CMDLINE]) { + error_setg(errp, "Invalid EIF image. More than 1 cmdline " + "section"); + goto cleanup; + } + size = hdr.section_size; + *cmdline = g_malloc(size + 1); + if (!read_eif_cmdline(f, size, *cmdline, &crc, errp)) { + goto cleanup; + } + (*cmdline)[size] = '\0'; + + /* + * We make a copy of '*cmdline' for putting it in iovecs so that + * we can easily free all the iovec entries later as we cannot + * free '*cmdline' which is used by the caller. + */ + cmdline_copy = g_memdup2(*cmdline, size); + + iov_ptr = g_malloc(sizeof(struct iovec)); + iov_ptr->iov_base = cmdline_copy; + iov_ptr->iov_len = size; + + iov_PCR0 = g_list_append(iov_PCR0, iov_ptr); + iov_PCR1 = g_list_append(iov_PCR1, iov_ptr); + break; + } + case EIF_SECTION_RAMDISK: + { + if (!seen_sections[EIF_SECTION_RAMDISK]) { + /* + * If this is the first time we are seeing a ramdisk section, + * we need to create the initrd temporary file. + */ + if (!get_tmp_file("eif-initrd-XXXXXX", initrd_path, errp)) { + goto cleanup; + } + initrd_path_f = fopen(*initrd_path, "wb"); + if (initrd_path_f == NULL) { + error_setg_errno(errp, errno, "Failed to open file %s", + *initrd_path); + goto cleanup; + } + } + + ptr = g_malloc(hdr.section_size); + + iov_ptr = g_malloc(sizeof(struct iovec)); + iov_ptr->iov_base = ptr; + iov_ptr->iov_len = hdr.section_size; + + iov_PCR0 = g_list_append(iov_PCR0, iov_ptr); + /* + * If it's the first ramdisk, we need to hash it into bootstrap + * i.e., iov_PCR1, otherwise we need to hash it into app i.e., + * iov_PCR2. + */ + if (!seen_sections[EIF_SECTION_RAMDISK]) { + iov_PCR1 = g_list_append(iov_PCR1, iov_ptr); + } else { + iov_PCR2 = g_list_append(iov_PCR2, iov_ptr); + } + + if (!read_eif_ramdisk(f, initrd_path_f, hdr.section_size, ptr, + &crc, errp)) { + goto cleanup; + } + + break; + } + case EIF_SECTION_SIGNATURE: + *signature_found = true; + if (!get_signature_fingerprint_sha384(f, hdr.section_size, + fingerprint_sha384, &crc, + errp)) { + goto cleanup; + } + break; + default: + /* other sections including invalid or unknown sections */ + { + uint8_t *buf; + size_t got; + uint64_t size = hdr.section_size; + buf = g_malloc(size); + got = fread(buf, 1, size, f); + if ((uint64_t) got != size) { + g_free(buf); + error_setg(errp, "Failed to read EIF %s section data", + section_type_to_string(section_type)); + goto cleanup; + } + crc = crc32(crc, buf, size); + g_free(buf); + break; + } + } + + if (section_type < EIF_SECTION_MAX) { + seen_sections[section_type] = true; + } + } + + if (!seen_sections[EIF_SECTION_KERNEL]) { + error_setg(errp, "Invalid EIF image. No kernel section."); + goto cleanup; + } + if (!seen_sections[EIF_SECTION_CMDLINE]) { + error_setg(errp, "Invalid EIF image. No cmdline section."); + goto cleanup; + } + if (!seen_sections[EIF_SECTION_RAMDISK]) { + error_setg(errp, "Invalid EIF image. No ramdisk section."); + goto cleanup; + } + + if (eif_header.eif_crc32 != crc) { + error_setg(errp, "CRC mismatch. Expected %u but header has %u.", + crc, eif_header.eif_crc32); + goto cleanup; + } + + /* + * Let's append the initrd file from "-initrd" option if any. Although + * we pass the crc pointer to read_eif_ramdisk, it is not useful anymore. + * We have already done the crc mismatch check above this code. + */ + if (machine_initrd) { + machine_initrd_f = fopen(machine_initrd, "rb"); + if (machine_initrd_f == NULL) { + error_setg_errno(errp, errno, "Failed to open initrd file %s", + machine_initrd); + goto cleanup; + } + + machine_initrd_size = get_file_size(machine_initrd_f, errp); + if (machine_initrd_size == -1) { + goto cleanup; + } + + ptr = g_malloc(machine_initrd_size); + + iov_ptr = g_malloc(sizeof(struct iovec)); + iov_ptr->iov_base = ptr; + iov_ptr->iov_len = machine_initrd_size; + + iov_PCR0 = g_list_append(iov_PCR0, iov_ptr); + iov_PCR2 = g_list_append(iov_PCR2, iov_ptr); + + if (!read_eif_ramdisk(machine_initrd_f, initrd_path_f, + machine_initrd_size, ptr, &crc, errp)) { + goto cleanup; + } + } + + if (!get_SHA384_digest(iov_PCR0, image_sha384, errp)) { + goto cleanup; + } + if (!get_SHA384_digest(iov_PCR1, bootstrap_sha384, errp)) { + goto cleanup; + } + if (!get_SHA384_digest(iov_PCR2, app_sha384, errp)) { + goto cleanup; + } + + /* + * We only need to free iov_PCR0 entries because iov_PCR1 and + * iov_PCR2 iovec entries are subsets of iov_PCR0 iovec entries. + */ + g_list_free_full(iov_PCR0, (GDestroyNotify) free_iovec); + g_list_free(iov_PCR1); + g_list_free(iov_PCR2); + fclose(f); + fclose(initrd_path_f); + safe_fclose(machine_initrd_f); + return true; + + cleanup: + g_list_free_full(iov_PCR0, (GDestroyNotify) free_iovec); + g_list_free(iov_PCR1); + g_list_free(iov_PCR2); + + safe_fclose(f); + safe_fclose(initrd_path_f); + safe_fclose(machine_initrd_f); + + safe_unlink(*kernel_path); + g_free(*kernel_path); + *kernel_path = NULL; + + safe_unlink(*initrd_path); + g_free(*initrd_path); + *initrd_path = NULL; + + g_free(*cmdline); + *cmdline = NULL; + + return false; +} diff --git a/hw/core/eif.h b/hw/core/eif.h new file mode 100644 index 0000000000..fed3cb5514 --- /dev/null +++ b/hw/core/eif.h @@ -0,0 +1,22 @@ +/* + * EIF (Enclave Image Format) related helpers + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#ifndef HW_CORE_EIF_H +#define HW_CORE_EIF_H + +bool read_eif_file(const char *eif_path, const char *machine_initrd, + char **kernel_path, char **initrd_path, + char **kernel_cmdline, uint8_t *image_sha384, + uint8_t *bootstrap_sha384, uint8_t *app_sha384, + uint8_t *fingerprint_sha384, bool *signature_found, + Error **errp); + +#endif + From patchwork Thu Aug 22 15:08:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773704 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 154EBC52D7C for ; Thu, 22 Aug 2024 15:10:35 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9Rq-0000AV-Bb; Thu, 22 Aug 2024 11:10:02 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9Ro-0008Vt-Sz for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:10:00 -0400 Received: from mail-pj1-x1029.google.com ([2607:f8b0:4864:20::1029]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9Rl-00053g-Jl for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:10:00 -0400 Received: by mail-pj1-x1029.google.com with SMTP id 98e67ed59e1d1-2d3c5f769d6so669268a91.3 for ; Thu, 22 Aug 2024 08:09:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339396; x=1724944196; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=fk9kEBvjTnQ5i2HjwjK6rHdXtVSY94Nibsoxtw66GYo=; b=R8AcNynHWYuBtSIgVI93YyQ1V33meGiM3sJ67S4NTlEbqjJJyKzRpxfAxTnCGfbxOA g/QQncqNEAMGpCh5F3BPW/i3+jQpQtkiwfdw8pG1J1ipF/yEbLWlYCnlxQ+ua6C9puW3 cniWzxgDVhkpVHTx9d7xI4msfhDxFFfD33ylLr3kcuB+o8G/C4s5Fi04NwqO48sS9O8G cgNHfMfZwtbWTIFph8dJdeB5daHnoaxSxgNcIbJwQK0LsbGq+k3qyS7SvZ6W3+0fclcw kAU6AgmIA7I+BI7V2mnJR5QvxQ1/a+SpXnh/DBxyEvlD8g94A/4EfCuvB2OqqjBdCSEG e/Dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339396; x=1724944196; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=fk9kEBvjTnQ5i2HjwjK6rHdXtVSY94Nibsoxtw66GYo=; b=BlSHN544EmD4Bk6I0OUNN06CiAPHxTHEFfYuDl42WggSNq5BifV3Sgly3bH4mfO5q9 y8+Cxi5taCHf9r+DcxYQZg2u8MwFM75goNQANdPrqns+TxYxgt1P9KXOV8Nd4UF9ZEQa FUuWGkYPUS2AYWWdvuTSCph1YR2dvTm76MIfQy4OuhVoO/DPrBZYoSOfhDcT2iyUZhnp 1Jbg432lqHNJycjRzneVF9tITscDKEMc85gk07CVWx85N/f97w2JgA/ETOMrVjC6MNqK YQjQLX9G3CIi2KzjID+5Pfkj4euOk2TMWeESBwu9wJogCtNYvocLXHr1WwPYdZMce0PS KW0A== X-Gm-Message-State: AOJu0YzlDwB4vJXu2gjIjMNJ3wEzjHowhuvL+uQE9WRM2E/nuAT7rkG8 3IZEbT5AAwsglJ9ycnDfLTfaAFp1zVjCow6RTzGhVvmjMd0Rv67+zhCGxg== X-Google-Smtp-Source: AGHT+IGemNPOKQWonuPX9Itwn3mvc8Ndz9aM9vN9GHciM5MifVe8crIo7zsyEGURYYx0pACn7pfr4g== X-Received: by 2002:a17:90a:ad7:b0:2d3:c0d4:2c33 with SMTP id 98e67ed59e1d1-2d616ae9acfmr2373062a91.17.1724339395668; Thu, 22 Aug 2024 08:09:55 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:09:55 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 7/8] machine/nitro-enclave: New machine type for AWS Nitro Enclaves Date: Thu, 22 Aug 2024 21:08:48 +0600 Message-Id: <20240822150849.21759-8-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::1029; envelope-from=dorjoychy111@gmail.com; helo=mail-pj1-x1029.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org AWS nitro enclaves[1] is an Amazon EC2[2] feature that allows creating isolated execution environments, called enclaves, from Amazon EC2 instances which are used for processing highly sensitive data. Enclaves have no persistent storage and no external networking. The enclave VMs are based on the Firecracker microvm with a vhost-vsock device for communication with the parent EC2 instance that spawned it and a Nitro Secure Module (NSM) device for cryptographic attestation. The parent instance VM always has CID 3 while the enclave VM gets a dynamic CID. An EIF (Enclave Image Format)[3] file is used to boot an AWS nitro enclave virtual machine. This commit adds support for AWS nitro enclave emulation using a new machine type option '-M nitro-enclave'. This new machine type is based on the 'microvm' machine type, similar to how real nitro enclave VMs are based on Firecracker microvm. For nitro-enclave to boot from an EIF file, the kernel and ramdisk(s) are extracted into a temporary kernel and a temporary initrd file which are then hooked into the regular x86 boot mechanism along with the extracted cmdline. The EIF file path should be provided using the '-kernel' QEMU option. In QEMU, the vsock emulation for nitro enclave is added using vhost-user- vsock as opposed to vhost-vsock. vhost-vsock doesn't support sibling VM communication which is needed for nitro enclaves. So for the vsock communication to CID 3 to work, another process that does the vsock emulation in userspace must be run, for example, vhost-device-vsock[4] from rust-vmm, with necessary vsock communication support in another guest VM with CID 3. Using vhost-user-vsock also enables the possibility to implement some proxying support in the vhost-user-vsock daemon that will forward all the packets to the host machine instead of CID 3 so that users of nitro-enclave can run the necessary applications in their host machine instead of running another whole VM with CID 3. The following mandatory nitro-enclave machine option has been added related to the vhost-user-vsock device. - 'vsock': The chardev id from the '-chardev' option for the vhost-user-vsock device. AWS Nitro Enclaves have built-in Nitro Secure Module (NSM) device which has been added using the virtio-nsm device added in a previous commit. In Nitro Enclaves, all the PCRs start in a known zero state and the first 16 PCRs are locked from boot and reserved. The PCR0, PCR1, PCR2 and PCR8 contain the SHA384 hashes related to the EIF file used to boot the VM for validation. The following optional nitro-enclave machine options have been added related to the NSM device. - 'id': Enclave identifier, reflected in the module-id of the NSM device. If not provided, a default id will be set. - 'parent-role': Parent instance IAM role ARN, reflected in PCR3 of the NSM device. - 'parent-id': Parent instance identifier, reflected in PCR4 of the NSM device. [1] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html [2] https://aws.amazon.com/ec2/ [3] https://github.com/aws/aws-nitro-enclaves-image-format [4] https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-vsock Signed-off-by: Dorjoy Chowdhury --- MAINTAINERS | 9 + backends/hostmem-memfd.c | 2 - configs/devices/i386-softmmu/default.mak | 1 + hw/core/machine.c | 71 ++--- hw/core/meson.build | 3 + hw/i386/Kconfig | 6 + hw/i386/meson.build | 3 + hw/i386/microvm.c | 6 +- hw/i386/nitro_enclave.c | 355 +++++++++++++++++++++++ include/hw/boards.h | 2 + include/hw/i386/microvm.h | 2 + include/hw/i386/nitro_enclave.h | 62 ++++ include/sysemu/hostmem.h | 2 + 13 files changed, 488 insertions(+), 36 deletions(-) create mode 100644 hw/i386/nitro_enclave.c create mode 100644 include/hw/i386/nitro_enclave.h diff --git a/MAINTAINERS b/MAINTAINERS index da4f698137..aa7846107e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1877,6 +1877,15 @@ F: hw/i386/microvm.c F: include/hw/i386/microvm.h F: pc-bios/bios-microvm.bin +nitro-enclave +M: Alexander Graf +M: Dorjoy Chowdhury +S: Maintained +F: hw/core/eif.c +F: hw/core/eif.h +F: hw/i386/nitro_enclave.c +F: include/hw/i386/nitro_enclave.h + Machine core M: Eduardo Habkost M: Marcel Apfelbaum diff --git a/backends/hostmem-memfd.c b/backends/hostmem-memfd.c index 6a3c89a12b..9f890a813e 100644 --- a/backends/hostmem-memfd.c +++ b/backends/hostmem-memfd.c @@ -18,8 +18,6 @@ #include "qapi/error.h" #include "qom/object.h" -#define TYPE_MEMORY_BACKEND_MEMFD "memory-backend-memfd" - OBJECT_DECLARE_SIMPLE_TYPE(HostMemoryBackendMemfd, MEMORY_BACKEND_MEMFD) diff --git a/configs/devices/i386-softmmu/default.mak b/configs/devices/i386-softmmu/default.mak index 448e3e3b1b..4faf2f0315 100644 --- a/configs/devices/i386-softmmu/default.mak +++ b/configs/devices/i386-softmmu/default.mak @@ -29,3 +29,4 @@ # CONFIG_I440FX=n # CONFIG_Q35=n # CONFIG_MICROVM=n +# CONFIG_NITRO_ENCLAVE=n diff --git a/hw/core/machine.c b/hw/core/machine.c index 27dcda0248..b4662b2795 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -998,6 +998,39 @@ void machine_add_audiodev_property(MachineClass *mc) "Audiodev to use for default machine devices"); } +static bool create_default_memdev(MachineState *ms, const char *path, + Error **errp) +{ + Object *obj; + MachineClass *mc = MACHINE_GET_CLASS(ms); + bool r = false; + + obj = object_new(path ? TYPE_MEMORY_BACKEND_FILE : TYPE_MEMORY_BACKEND_RAM); + if (path) { + if (!object_property_set_str(obj, "mem-path", path, errp)) { + goto out; + } + } + if (!object_property_set_int(obj, "size", ms->ram_size, errp)) { + goto out; + } + object_property_add_child(object_get_objects_root(), mc->default_ram_id, + obj); + /* Ensure backend's memory region name is equal to mc->default_ram_id */ + if (!object_property_set_bool(obj, "x-use-canonical-path-for-ramblock-id", + false, errp)) { + goto out; + } + if (!user_creatable_complete(USER_CREATABLE(obj), errp)) { + goto out; + } + r = object_property_set_link(OBJECT(ms), "memory-backend", obj, errp); + +out: + object_unref(obj); + return r; +} + static void machine_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); @@ -1017,6 +1050,8 @@ static void machine_class_init(ObjectClass *oc, void *data) */ mc->numa_mem_align_shift = 23; + mc->create_default_memdev = create_default_memdev; + object_class_property_add_str(oc, "kernel", machine_get_kernel, machine_set_kernel); object_class_property_set_description(oc, "kernel", @@ -1410,38 +1445,6 @@ MemoryRegion *machine_consume_memdev(MachineState *machine, return ret; } -static bool create_default_memdev(MachineState *ms, const char *path, Error **errp) -{ - Object *obj; - MachineClass *mc = MACHINE_GET_CLASS(ms); - bool r = false; - - obj = object_new(path ? TYPE_MEMORY_BACKEND_FILE : TYPE_MEMORY_BACKEND_RAM); - if (path) { - if (!object_property_set_str(obj, "mem-path", path, errp)) { - goto out; - } - } - if (!object_property_set_int(obj, "size", ms->ram_size, errp)) { - goto out; - } - object_property_add_child(object_get_objects_root(), mc->default_ram_id, - obj); - /* Ensure backend's memory region name is equal to mc->default_ram_id */ - if (!object_property_set_bool(obj, "x-use-canonical-path-for-ramblock-id", - false, errp)) { - goto out; - } - if (!user_creatable_complete(USER_CREATABLE(obj), errp)) { - goto out; - } - r = object_property_set_link(OBJECT(ms), "memory-backend", obj, errp); - -out: - object_unref(obj); - return r; -} - const char *machine_class_default_cpu_type(MachineClass *mc) { if (mc->valid_cpu_types && !mc->valid_cpu_types[1]) { @@ -1545,7 +1548,9 @@ void machine_run_board_init(MachineState *machine, const char *mem_path, Error * machine_class->default_ram_id); return; } - if (!create_default_memdev(current_machine, mem_path, errp)) { + + if (!machine_class->create_default_memdev(current_machine, mem_path, + errp)) { return; } } diff --git a/hw/core/meson.build b/hw/core/meson.build index a3d9bab9f4..5437a94490 100644 --- a/hw/core/meson.build +++ b/hw/core/meson.build @@ -24,6 +24,9 @@ system_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c')) system_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c')) system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c')) system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('sysbus-fdt.c')) +if libcbor.found() and gnutls.found() + system_ss.add(when: 'CONFIG_NITRO_ENCLAVE', if_true: [files('eif.c'), zlib, libcbor, gnutls]) +endif system_ss.add(files( 'cpu-sysemu.c', diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index f4a33b6c08..63271bf915 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -129,6 +129,12 @@ config MICROVM select USB_XHCI_SYSBUS select I8254 +config NITRO_ENCLAVE + default y + depends on MICROVM + select VHOST_USER_VSOCK + select VIRTIO_NSM + config X86_IOMMU bool depends on PC diff --git a/hw/i386/meson.build b/hw/i386/meson.build index 03aad10df7..1ddd7a83be 100644 --- a/hw/i386/meson.build +++ b/hw/i386/meson.build @@ -15,6 +15,9 @@ i386_ss.add(when: 'CONFIG_AMD_IOMMU', if_true: files('amd_iommu.c'), if_false: files('amd_iommu-stub.c')) i386_ss.add(when: 'CONFIG_I440FX', if_true: files('pc_piix.c')) i386_ss.add(when: 'CONFIG_MICROVM', if_true: files('x86-common.c', 'microvm.c', 'acpi-microvm.c', 'microvm-dt.c')) +if libcbor.found() and gnutls.found() + i386_ss.add(when: 'CONFIG_NITRO_ENCLAVE', if_true: files('nitro_enclave.c')) +endif i386_ss.add(when: 'CONFIG_Q35', if_true: files('pc_q35.c')) i386_ss.add(when: 'CONFIG_VMMOUSE', if_true: files('vmmouse.c')) i386_ss.add(when: 'CONFIG_VMPORT', if_true: files('vmport.c')) diff --git a/hw/i386/microvm.c b/hw/i386/microvm.c index 40edcee7af..869c177642 100644 --- a/hw/i386/microvm.c +++ b/hw/i386/microvm.c @@ -283,6 +283,7 @@ static void microvm_devices_init(MicrovmMachineState *mms) static void microvm_memory_init(MicrovmMachineState *mms) { + MicrovmMachineClass *mmc = MICROVM_MACHINE_GET_CLASS(mms); MachineState *machine = MACHINE(mms); X86MachineState *x86ms = X86_MACHINE(mms); MemoryRegion *ram_below_4g, *ram_above_4g; @@ -328,7 +329,7 @@ static void microvm_memory_init(MicrovmMachineState *mms) rom_set_fw(fw_cfg); if (machine->kernel_filename != NULL) { - x86_load_linux(x86ms, fw_cfg, 0, true); + mmc->x86_load_linux(x86ms, fw_cfg, 0, true); } if (mms->option_roms) { @@ -637,9 +638,12 @@ GlobalProperty microvm_properties[] = { static void microvm_class_init(ObjectClass *oc, void *data) { X86MachineClass *x86mc = X86_MACHINE_CLASS(oc); + MicrovmMachineClass *mmc = MICROVM_MACHINE_CLASS(oc); MachineClass *mc = MACHINE_CLASS(oc); HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc); + mmc->x86_load_linux = x86_load_linux; + mc->init = microvm_machine_state_init; mc->family = "microvm_i386"; diff --git a/hw/i386/nitro_enclave.c b/hw/i386/nitro_enclave.c new file mode 100644 index 0000000000..7dbeee530f --- /dev/null +++ b/hw/i386/nitro_enclave.c @@ -0,0 +1,355 @@ +/* + * AWS nitro-enclave machine + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "qom/object_interfaces.h" + +#include "chardev/char.h" +#include "hw/sysbus.h" +#include "hw/core/eif.h" +#include "hw/i386/x86.h" +#include "hw/i386/microvm.h" +#include "hw/i386/nitro_enclave.h" +#include "hw/virtio/virtio-mmio.h" +#include "hw/virtio/virtio-nsm.h" +#include "hw/virtio/vhost-user-vsock.h" +#include "sysemu/hostmem.h" + +static BusState *find_free_virtio_mmio_bus(void) +{ + BusChild *kid; + BusState *bus = sysbus_get_default(); + + QTAILQ_FOREACH(kid, &bus->children, sibling) { + DeviceState *dev = kid->child; + if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MMIO)) { + VirtIOMMIOProxy *mmio = VIRTIO_MMIO(OBJECT(dev)); + VirtioBusState *mmio_virtio_bus = &mmio->bus; + BusState *mmio_bus = &mmio_virtio_bus->parent_obj; + if (QTAILQ_EMPTY(&mmio_bus->children)) { + return mmio_bus; + } + } + } + + return NULL; +} + +static void vhost_user_vsock_init(NitroEnclaveMachineState *nems) +{ + DeviceState *dev = qdev_new(TYPE_VHOST_USER_VSOCK); + VHostUserVSock *vsock = VHOST_USER_VSOCK(dev); + BusState *bus; + + if (!nems->vsock) { + error_report("A valid chardev id for vhost-user-vsock device must be " + "provided using the 'vsock' machine option"); + exit(1); + } + + bus = find_free_virtio_mmio_bus(); + if (!bus) { + error_report("Failed to find bus for vhost-user-vsock device"); + exit(1); + } + + Chardev *chardev = qemu_chr_find(nems->vsock); + if (!chardev) { + error_report("Failed to find chardev with id %s", nems->vsock); + exit(1); + } + + vsock->conf.chardev.chr = chardev; + + qdev_realize_and_unref(dev, bus, &error_fatal); +} + +static void virtio_nsm_init(NitroEnclaveMachineState *nems) +{ + DeviceState *dev = qdev_new(TYPE_VIRTIO_NSM); + VirtIONSM *vnsm = VIRTIO_NSM(dev); + BusState *bus = find_free_virtio_mmio_bus(); + + if (!bus) { + error_report("Failed to find bus for virtio-nsm device."); + exit(1); + } + + qdev_prop_set_string(dev, "module-id", nems->id); + + qdev_realize_and_unref(dev, bus, &error_fatal); + nems->vnsm = vnsm; +} + +static void nitro_enclave_devices_init(NitroEnclaveMachineState *nems) +{ + vhost_user_vsock_init(nems); + virtio_nsm_init(nems); +} + +static void nitro_enclave_machine_state_init(MachineState *machine) +{ + NitroEnclaveMachineClass *ne_class = + NITRO_ENCLAVE_MACHINE_GET_CLASS(machine); + NitroEnclaveMachineState *ne_state = NITRO_ENCLAVE_MACHINE(machine); + + ne_class->parent_init(machine); + nitro_enclave_devices_init(ne_state); +} + +static void nitro_enclave_machine_reset(MachineState *machine, + ShutdownCause reason) +{ + NitroEnclaveMachineClass *ne_class = + NITRO_ENCLAVE_MACHINE_GET_CLASS(machine); + NitroEnclaveMachineState *ne_state = NITRO_ENCLAVE_MACHINE(machine); + + ne_class->parent_reset(machine, reason); + + memset(ne_state->vnsm->pcrs, 0, sizeof(ne_state->vnsm->pcrs)); + + /* PCR0 */ + ne_state->vnsm->extend_pcr(ne_state->vnsm, 0, ne_state->image_sha384, + QCRYPTO_HASH_DIGEST_LEN_SHA384); + /* PCR1 */ + ne_state->vnsm->extend_pcr(ne_state->vnsm, 1, ne_state->bootstrap_sha384, + QCRYPTO_HASH_DIGEST_LEN_SHA384); + /* PCR2 */ + ne_state->vnsm->extend_pcr(ne_state->vnsm, 2, ne_state->app_sha384, + QCRYPTO_HASH_DIGEST_LEN_SHA384); + /* PCR3 */ + if (ne_state->parent_role) { + ne_state->vnsm->extend_pcr(ne_state->vnsm, 3, + (uint8_t *) ne_state->parent_role, + strlen(ne_state->parent_role)); + } + /* PCR4 */ + if (ne_state->parent_id) { + ne_state->vnsm->extend_pcr(ne_state->vnsm, 4, + (uint8_t *) ne_state->parent_id, + strlen(ne_state->parent_id)); + } + /* PCR8 */ + if (ne_state->signature_found) { + ne_state->vnsm->extend_pcr(ne_state->vnsm, 8, + ne_state->fingerprint_sha384, + QCRYPTO_HASH_DIGEST_LEN_SHA384); + } + + /* First 16 PCRs are locked from boot and reserved for nitro enclave */ + for (int i = 0; i < 16; ++i) { + ne_state->vnsm->lock_pcr(ne_state->vnsm, i); + } +} + +static void nitro_enclave_machine_initfn(Object *obj) +{ + MicrovmMachineState *mms = MICROVM_MACHINE(obj); + X86MachineState *x86ms = X86_MACHINE(obj); + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + nems->id = g_strdup("i-234-enc5678"); + + /* AWS nitro enclaves have PCIE and ACPI disabled */ + mms->pcie = ON_OFF_AUTO_OFF; + x86ms->acpi = ON_OFF_AUTO_OFF; +} + +static void x86_load_eif(X86MachineState *x86ms, FWCfgState *fw_cfg, + int acpi_data_size, bool pvh_enabled) +{ + Error *err = NULL; + char *eif_kernel, *eif_initrd, *eif_cmdline; + MachineState *machine = MACHINE(x86ms); + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(x86ms); + + if (!read_eif_file(machine->kernel_filename, machine->initrd_filename, + &eif_kernel, &eif_initrd, &eif_cmdline, + nems->image_sha384, nems->bootstrap_sha384, + nems->app_sha384, nems->fingerprint_sha384, + &(nems->signature_found), &err)) { + error_report_err(err); + exit(1); + } + + g_free(machine->kernel_filename); + machine->kernel_filename = eif_kernel; + g_free(machine->initrd_filename); + machine->initrd_filename = eif_initrd; + + /* + * If kernel cmdline argument was provided, let's concatenate it to the + * extracted EIF kernel cmdline. + */ + if (machine->kernel_cmdline != NULL) { + char *cmd = g_strdup_printf("%s %s", eif_cmdline, + machine->kernel_cmdline); + g_free(eif_cmdline); + g_free(machine->kernel_cmdline); + machine->kernel_cmdline = cmd; + } else { + machine->kernel_cmdline = eif_cmdline; + } + + x86_load_linux(x86ms, fw_cfg, 0, true); + + unlink(machine->kernel_filename); + unlink(machine->initrd_filename); + return; +} + +static bool create_memfd_backend(MachineState *ms, const char *path, + Error **errp) +{ + Object *obj; + MachineClass *mc = MACHINE_GET_CLASS(ms); + bool r = false; + + obj = object_new(TYPE_MEMORY_BACKEND_MEMFD); + if (!object_property_set_int(obj, "size", ms->ram_size, errp)) { + goto out; + } + object_property_add_child(object_get_objects_root(), mc->default_ram_id, + obj); + + if (!user_creatable_complete(USER_CREATABLE(obj), errp)) { + goto out; + } + r = object_property_set_link(OBJECT(ms), "memory-backend", obj, errp); + +out: + object_unref(obj); + return r; +} + +static char *nitro_enclave_get_vsock_chardev_id(Object *obj, Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + return g_strdup(nems->vsock); +} + +static void nitro_enclave_set_vsock_chardev_id(Object *obj, const char *value, + Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + g_free(nems->vsock); + nems->vsock = g_strdup(value); +} + +static char *nitro_enclave_get_id(Object *obj, Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + return g_strdup(nems->id); +} + +static void nitro_enclave_set_id(Object *obj, const char *value, + Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + g_free(nems->id); + nems->id = g_strdup(value); +} + +static char *nitro_enclave_get_parent_role(Object *obj, Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + return g_strdup(nems->parent_role); +} + +static void nitro_enclave_set_parent_role(Object *obj, const char *value, + Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + g_free(nems->parent_role); + nems->parent_role = g_strdup(value); +} + +static char *nitro_enclave_get_parent_id(Object *obj, Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + return g_strdup(nems->parent_id); +} + +static void nitro_enclave_set_parent_id(Object *obj, const char *value, + Error **errp) +{ + NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); + + g_free(nems->parent_id); + nems->parent_id = g_strdup(value); +} + +static void nitro_enclave_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + MicrovmMachineClass *mmc = MICROVM_MACHINE_CLASS(oc); + NitroEnclaveMachineClass *nemc = NITRO_ENCLAVE_MACHINE_CLASS(oc); + + mmc->x86_load_linux = x86_load_eif; + + mc->family = "nitro_enclave_i386"; + mc->desc = "AWS Nitro Enclave"; + + nemc->parent_init = mc->init; + mc->init = nitro_enclave_machine_state_init; + + nemc->parent_reset = mc->reset; + mc->reset = nitro_enclave_machine_reset; + + mc->create_default_memdev = create_memfd_backend; + + object_class_property_add_str(oc, NITRO_ENCLAVE_VSOCK_CHARDEV_ID, + nitro_enclave_get_vsock_chardev_id, + nitro_enclave_set_vsock_chardev_id); + object_class_property_set_description(oc, NITRO_ENCLAVE_VSOCK_CHARDEV_ID, + "Set chardev id for vhost-user-vsock " + "device"); + + object_class_property_add_str(oc, NITRO_ENCLAVE_ID, nitro_enclave_get_id, + nitro_enclave_set_id); + object_class_property_set_description(oc, NITRO_ENCLAVE_ID, + "Set enclave identifier"); + + object_class_property_add_str(oc, NITRO_ENCLAVE_PARENT_ROLE, + nitro_enclave_get_parent_role, + nitro_enclave_set_parent_role); + object_class_property_set_description(oc, NITRO_ENCLAVE_PARENT_ROLE, + "Set parent instance IAM role ARN"); + + object_class_property_add_str(oc, NITRO_ENCLAVE_PARENT_ID, + nitro_enclave_get_parent_id, + nitro_enclave_set_parent_id); + object_class_property_set_description(oc, NITRO_ENCLAVE_PARENT_ID, + "Set parent instance identifier"); +} + +static const TypeInfo nitro_enclave_machine_info = { + .name = TYPE_NITRO_ENCLAVE_MACHINE, + .parent = TYPE_MICROVM_MACHINE, + .instance_size = sizeof(NitroEnclaveMachineState), + .instance_init = nitro_enclave_machine_initfn, + .class_size = sizeof(NitroEnclaveMachineClass), + .class_init = nitro_enclave_class_init, +}; + +static void nitro_enclave_machine_init(void) +{ + type_register_static(&nitro_enclave_machine_info); +} +type_init(nitro_enclave_machine_init); diff --git a/include/hw/boards.h b/include/hw/boards.h index 48ff6d8b93..c268e7f005 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -308,6 +308,8 @@ struct MachineClass { int64_t (*get_default_cpu_node_id)(const MachineState *ms, int idx); ram_addr_t (*fixup_ram_size)(ram_addr_t size); uint64_t smbios_memory_device_size; + bool (*create_default_memdev)(MachineState *ms, const char *path, + Error **errp); }; /** diff --git a/include/hw/i386/microvm.h b/include/hw/i386/microvm.h index fad97a891d..b9ac34a3ef 100644 --- a/include/hw/i386/microvm.h +++ b/include/hw/i386/microvm.h @@ -78,6 +78,8 @@ struct MicrovmMachineClass { X86MachineClass parent; HotplugHandler *(*orig_hotplug_handler)(MachineState *machine, DeviceState *dev); + void (*x86_load_linux)(X86MachineState *x86ms, FWCfgState *fw_cfg, + int acpi_data_size, bool pvh_enabled); }; struct MicrovmMachineState { diff --git a/include/hw/i386/nitro_enclave.h b/include/hw/i386/nitro_enclave.h new file mode 100644 index 0000000000..687c88cb54 --- /dev/null +++ b/include/hw/i386/nitro_enclave.h @@ -0,0 +1,62 @@ +/* + * AWS nitro-enclave machine + * + * Copyright (c) 2024 Dorjoy Chowdhury + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#ifndef HW_I386_NITRO_ENCLAVE_H +#define HW_I386_NITRO_ENCLAVE_H + +#include "crypto/hash.h" +#include "hw/i386/microvm.h" +#include "qom/object.h" +#include "hw/virtio/virtio-nsm.h" + +/* Machine type options */ +#define NITRO_ENCLAVE_VSOCK_CHARDEV_ID "vsock" +#define NITRO_ENCLAVE_ID "id" +#define NITRO_ENCLAVE_PARENT_ROLE "parent-role" +#define NITRO_ENCLAVE_PARENT_ID "parent-id" + +struct NitroEnclaveMachineClass { + MicrovmMachineClass parent; + + void (*parent_init)(MachineState *state); + void (*parent_reset)(MachineState *machine, ShutdownCause reason); +}; + +struct NitroEnclaveMachineState { + MicrovmMachineState parent; + + /* Machine type options */ + char *vsock; + /* Enclave identifier */ + char *id; + /* Parent instance IAM role ARN */ + char *parent_role; + /* Parent instance identifier */ + char *parent_id; + + /* Machine state */ + VirtIONSM *vnsm; + + /* kernel + ramdisks + cmdline sha384 hash */ + uint8_t image_sha384[QCRYPTO_HASH_DIGEST_LEN_SHA384]; + /* kernel + boot ramdisk + cmdline sha384 hash */ + uint8_t bootstrap_sha384[QCRYPTO_HASH_DIGEST_LEN_SHA384]; + /* application ramdisk(s) hash */ + uint8_t app_sha384[QCRYPTO_HASH_DIGEST_LEN_SHA384]; + /* certificate fingerprint hash */ + uint8_t fingerprint_sha384[QCRYPTO_HASH_DIGEST_LEN_SHA384]; + bool signature_found; +}; + +#define TYPE_NITRO_ENCLAVE_MACHINE MACHINE_TYPE_NAME("nitro-enclave") +OBJECT_DECLARE_TYPE(NitroEnclaveMachineState, NitroEnclaveMachineClass, + NITRO_ENCLAVE_MACHINE) + +#endif diff --git a/include/sysemu/hostmem.h b/include/sysemu/hostmem.h index de47ae59e4..67f45abe39 100644 --- a/include/sysemu/hostmem.h +++ b/include/sysemu/hostmem.h @@ -39,6 +39,8 @@ OBJECT_DECLARE_TYPE(HostMemoryBackend, HostMemoryBackendClass, */ #define TYPE_MEMORY_BACKEND_FILE "memory-backend-file" +#define TYPE_MEMORY_BACKEND_MEMFD "memory-backend-memfd" + /** * HostMemoryBackendClass: From patchwork Thu Aug 22 15:08:49 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dorjoy Chowdhury X-Patchwork-Id: 13773709 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0C958C52D7C for ; Thu, 22 Aug 2024 15:11:58 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sh9Ry-0000uv-5A; Thu, 22 Aug 2024 11:10:10 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sh9Rv-0000ao-Hf for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:10:07 -0400 Received: from mail-pf1-x42b.google.com ([2607:f8b0:4864:20::42b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sh9Rs-0005D6-IY for qemu-devel@nongnu.org; Thu, 22 Aug 2024 11:10:06 -0400 Received: by mail-pf1-x42b.google.com with SMTP id d2e1a72fcca58-71439adca73so593553b3a.0 for ; Thu, 22 Aug 2024 08:10:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724339403; x=1724944203; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=C+tKt5A2xkc4zSdNubRVhA8MqjwLBWMFSUXPn4nm9SI=; b=NUwDqWKaZ96Lyz6Zw1rIPOuy8gjGH6prDaChBI3bI48AVqIVUenvMva/nOX98aJmBt obt8SeSpO7EyBKO53a9sEEHvC3MgsjKH4RglTS4+3YN7luj3BQ97QQiBMP2Ae401Ei8k bxss2xrZ4HhWtISm/fIHyJjRO7imIyNErlOg/LlMxHj9A2NrAMRkn0qh4Zk8E2/gAG5p LoBqVIV1WEHEUZPS57CTD6S4JeLKZhRT8Zsrgz10uN6E8jz85LBVCtkZzOn4+cUe+SAA CXrgclJBlC4h0Ikr1sDZV6+WXa7zB1grkb5V0lw5i0nAY8JunW7uUGGlcjJJwqFNkecv RyJw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724339403; x=1724944203; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=C+tKt5A2xkc4zSdNubRVhA8MqjwLBWMFSUXPn4nm9SI=; b=O0dVyOvgfu5M5cKPI7GR6ohM4CCDW8MERT7o7kmf1Jr7OsqCjC9Yqw20UjeJ0Ek2HB sy9pPrJTRkNflFhdn+/lBYEslGFmQIujGwgy8mLCT+Jaas5K5i5tUuuCj0eGBilZKoBf nA/csgtzQnLszgAaGp8cjuH2MxlMG6VOFhVrA1xdUQEdHli+KdMb2PamnjUNnLhZNe0Q mQsEZsizKNNBpv+9W5/3TKvmaJz7GRcNXKmTqEorRwLaFPPhMJNxdFbCMHv1xDBfpH4f XKo3z7RcDnJRTUO+yERM6omKFaHlvk2YFc1ayBJGaMGDJsvETwzWTJypho7ClzMM6BAG ouIQ== X-Gm-Message-State: AOJu0Ywx7irOQZxRzKqTh4k5qj0wZy2jxxqOMcaSC9LLTlXXpezzDprU j4zdaDyJQaZ6ZuwoAm3jvZCHcczSmKDKjN4loJPq4s0Q0AFF5H7D/c0JAQ== X-Google-Smtp-Source: AGHT+IHG44E7WUwshdv0brLX+eGXs6E91pzQe8j/zIkhF/rOrnFHHqxWmNgFsQKCXCnqPyJ9tq/CmA== X-Received: by 2002:a17:90a:ce14:b0:2cf:c9ab:e727 with SMTP id 98e67ed59e1d1-2d5e9ec96b5mr6515368a91.31.1724339402624; Thu, 22 Aug 2024 08:10:02 -0700 (PDT) Received: from localhost.localdomain ([103.103.35.175]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-2d613919fd2sm1991567a91.13.2024.08.22.08.09.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 22 Aug 2024 08:10:02 -0700 (PDT) From: Dorjoy Chowdhury To: qemu-devel@nongnu.org Cc: graf@amazon.com, agraf@csgraf.de, stefanha@redhat.com, pbonzini@redhat.com, slp@redhat.com, richard.henderson@linaro.org, eduardo@habkost.net, mst@redhat.com, marcel.apfelbaum@gmail.com, berrange@redhat.com, philmd@linaro.org Subject: [PATCH v5 8/8] docs/nitro-enclave: Documentation for nitro-enclave machine type Date: Thu, 22 Aug 2024 21:08:49 +0600 Message-Id: <20240822150849.21759-9-dorjoychy111@gmail.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20240822150849.21759-1-dorjoychy111@gmail.com> References: <20240822150849.21759-1-dorjoychy111@gmail.com> MIME-Version: 1.0 Received-SPF: pass client-ip=2607:f8b0:4864:20::42b; envelope-from=dorjoychy111@gmail.com; helo=mail-pf1-x42b.google.com X-Spam_score_int: 15 X-Spam_score: 1.5 X-Spam_bar: + X-Spam_report: (1.5 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_ENVFROM_END_DIGIT=0.25, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Signed-off-by: Dorjoy Chowdhury --- docs/system/i386/nitro-enclave.rst | 85 ++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 docs/system/i386/nitro-enclave.rst diff --git a/docs/system/i386/nitro-enclave.rst b/docs/system/i386/nitro-enclave.rst new file mode 100644 index 0000000000..966691c396 --- /dev/null +++ b/docs/system/i386/nitro-enclave.rst @@ -0,0 +1,85 @@ +'nitro-enclave' virtual machine (``nitro-enclave``) +=================================================== + +``nitro-enclave`` is a machine type which emulates an ``AWS nitro enclave`` +virtual machine. `AWS nitro enclaves`_ is an `Amazon EC2`_ feature that allows +creating isolated execution environments, called enclaves, from Amazon EC2 +instances which are used for processing highly sensitive data. Enclaves have +no persistent storage and no external networking. The enclave VMs are based +on Firecracker microvm with a vhost-vsock device for communication with the +parent EC2 instance that spawned it and a Nitro Secure Module (NSM) device +for cryptographic attestation. The parent instance VM always has CID 3 while +the enclave VM gets a dynamic CID. Enclaves use an EIF (`Enclave Image Format`_) +file which contains the necessary kernel, cmdline and ramdisk(s) to boot. + +In QEMU, ``nitro-enclave`` is a machine type based on ``microvm`` similar to how +``AWS nitro enclaves`` are based on ``Firecracker`` microvm. This is useful for +local testing of EIF files using QEMU instead of running real AWS Nitro Enclaves +which can be difficult for debugging due to its roots in security. The vsock +device emulation is done using vhost-user-vsock which means another process that +can do the userspace emulation, like `vhost-device-vsock`_ from rust-vmm crate, +must be run alongside nitro-enclave for the vsock communication to work. + +``libcbor`` and ``gnutls`` are required dependencies for nitro-enclave machine +support to be added when building QEMU from source. + +.. _AWS nitro enlaves: https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html +.. _Amazon EC2: https://aws.amazon.com/ec2/ +.. _Enclave Image Format: https://github.com/aws/aws-nitro-enclaves-image-format +.. _vhost-device-vsock: https://github.com/rust-vmm/vhost-device/tree/main/vhost-device-vsock + +Using the nitro-enclave machine type +------------------------------ + +Machine-specific options +~~~~~~~~~~~~~~~~~~~~~~~~ + +It supports the following machine-specific options: + +- nitro-enclave.vsock=string (required) (Id of the chardev from '-chardev' option that vhost-user-vsock device will use) +- nitro-enclave.id=string (optional) (Set enclave identifier) +- nitro-enclave.parent-role=string (optional) (Set parent instance IAM role ARN) +- nitro-enclave.parent-id=string (optional) (Set parent instance identifier) + + +Running a nitro-enclave VM +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First, run vhost-device-vsock (or a similar tool that supports vhost-user-vsock) + + $ vhost-device-vsock \ + --vm guest-cid=4,uds-path=/tmp/vm4.vsock,socket=/tmp/vhost4.socket \ + --vm guest-cid=3,uds-path=/tmp/vm3.vsock,socket=/tmp/vhost3.socket + +Then, run the parent VM that has the necessary vsock communication support. + + $ qemu-system-x86_64 -machine q35,memory-backend=mem0 -enable-kvm -m 8G \ + -nic user,model=virtio -drive file=test_vm.qcow2,media=disk,if=virtio \ + --display sdl -object memory-backend-memfd,id=mem0,size=8G \ + -chardev socket,id=char0,reconnect=0,path=/tmp/vhost3.socket \ + -device vhost-user-vsock-pci,chardev=char0 + +Inside this VM the necessary applications should be run so that the nitro-enclave +VM applications' vsock communication works. For example, the nitro-enclave VM's +init process connects to CID 3 and sends a single byte hello heartbeat (0xB7) to +let the parent VM know that it booted expecting a heartbeat (0xB7) response. + +Now run the nitro-enclave VM using the following command where ``hello.eif`` is +an EIF file you would use to spawn a real AWS nitro enclave virtual machine: + + $ qemu-system-x86_64 -M nitro-enclave,vsock=c,id=hello-world \ + -kernel hello-world.eif -nographic -m 4G --enable-kvm -cpu host \ + -chardev socket,id=c,path=/tmp/vhost4.socket + +In this example, the nitro-enclave VM has CID 4. + + +Limitations +----------- + +AWS nitro enclave emulation support in QEMU requires users to run vhost-device-vsock +or similar tool for vhost-user-vsock support and another VM with CID 3 with necessary +vsock communication support. Requirement of running another VM and necessary applications +inside it can be lifted if some proxying support is added to vhost-device-vsock to +forward all packets to the host machine, in which case, users can run the necessary +applications in the host machine instead of the parent VM with CID 3.