Received: by 2002:a25:c205:0:0:0:0:0 with SMTP id s5csp4827624ybf; Wed, 4 Mar 2020 11:28:37 -0800 (PST) X-Google-Smtp-Source: ADFU+vt7uPB7QTF5Q4WhCjKNrlVMXFyTfI8JR7dz6IkIvt5zBM8M8TENJTf752sezzqb7hEotXJ8 X-Received: by 2002:a05:6808:a08:: with SMTP id n8mr2914463oij.91.1583350117499; Wed, 04 Mar 2020 11:28:37 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1583350117; cv=none; d=google.com; s=arc-20160816; b=ElumrPAnOvzLtpr/xlMYbOCvySd8tFwmpa6qdpRg8022dY5LvBbaWoVJ7SlwdVi6Ce k/9StFQzdAvZhnmRVlY4BfrnCh8qzSe60MrCdUeoUxiyvAIEQvw8tIkfuaxGYx5vZVII bBXPoY9xiiVD6cgmLskBxasqcuJlJCaBP1VraQQWGgxFgSeKQ9g+mr3GAsPbWfutwqnx 0wN/IWqdE1Dv2JH4g2hdBgLOYOTvG4y5XQhuQl+us1guwKBmBoOoV+uEhG3ExNZxTbp8 WK0XNMlqtJP4S7PzP8fjykfQwe7XIHssfzmKy3VT4YO5KWMXhoKod5M4i28KIcLa4Y9U JU4Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=ddCmJnchG7ddEu+kg1f4rZXhrNk22lXtAjVoUaUZxJM=; b=kwrvNraSR96HcE8atWhEKfLnTp+QeJ+eSLJ2aIM4qwhXapUY5EHfvuNH+vEKuzD8Cq 6UA8U9VGh7BfkLGinTuYgm/o768W3VUFzeq+FgylJRpNQlwLQp37ZcthWa8Do9PM6IT6 jsjKwADGJR9hFbKP0Xz8YN7TFS1lnkbf843WEb9tNm6foJJjYBKj2/nD9CdyL29yncrz xeOUtErvLAd+HzMvZsaXySptW1JYew4+GC1QofhaXd+ZUM8ByiLhLTte8Xu2oJUpOmRx 0TNt/r6rveduNO++gajuDy1FlVFhw83Ie6uWgSdqdlOTlNAh/KkeK7t0yxEZaBSLIHsa xhGA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@redhat.com header.s=mimecast20190719 header.b=Wo5SLwzU; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=redhat.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id j20si85692oii.168.2020.03.04.11.28.26; Wed, 04 Mar 2020 11:28:37 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@redhat.com header.s=mimecast20190719 header.b=Wo5SLwzU; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=redhat.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729028AbgCDT2Q (ORCPT + 99 others); Wed, 4 Mar 2020 14:28:16 -0500 Received: from us-smtp-delivery-1.mimecast.com ([205.139.110.120]:57180 "EHLO us-smtp-1.mimecast.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727137AbgCDT2Q (ORCPT ); Wed, 4 Mar 2020 14:28:16 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1583350094; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=ddCmJnchG7ddEu+kg1f4rZXhrNk22lXtAjVoUaUZxJM=; b=Wo5SLwzUYM/FEr3a5sz1aWDDvmP07/M0j+SjG0aWa7VRQSg70TN5BcsyNONUtiRSCt34aW y5HJQRI0ukLjVywcKOkTkvkFl/5UCFJwIp0CrNTqla+Opz71ta0WKhLJMwQXNv+Udx3Btp 8E8aU/WwSqLjOQQJLSoPnFlh2VvocPU= Received: from mail-io1-f69.google.com (mail-io1-f69.google.com [209.85.166.69]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-86-hFBmL7FONcqWMBW4tPzevQ-1; Wed, 04 Mar 2020 14:28:10 -0500 X-MC-Unique: hFBmL7FONcqWMBW4tPzevQ-1 Received: by mail-io1-f69.google.com with SMTP id i62so2234134ioa.6 for ; Wed, 04 Mar 2020 11:28:10 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=ddCmJnchG7ddEu+kg1f4rZXhrNk22lXtAjVoUaUZxJM=; b=gxl2ToYkp4Lx+/F0PXPhcT532UMZYORn8XThNYVg5QHQRAoGv1AxfXr6cY5axgIcaI grPOeanBSFYwDudCrf9EXlp1Brvki9Z7PR7hzq4YsWMYXWsHu4u4GP/JF2D+RF4Hq3E/ lOg+BAO92LdToHz2RXUgNOIslFJJyppvNRhOQ65Oltat2bh8nNxHL/vaZtCIeNw3ZRAN cIAFeMD6hSYheri+/pTyeQyEU1z/FDREEbaJegfaYAd8Nz94c/qdy4po0LCbImawnZdE ayumICnUxY+6CRyBK6mNgIeri6lx9iLtO6YXxT5trZnDlIGexdMHWTuUWop4hoYozX5U dc9g== X-Gm-Message-State: ANhLgQ2p1ta2ByKt5lVp+TtwoUTaCdlq3V5BfrIIa6euew42jgKgigPM /1ZFkhkCpgoaP0l6RD/AdhvfM6H7b6cxjx24izYEwyMYW+ZpcKbcsCN2pNyjah5T8s9Vy2/PQXK ztwwLRqJZ7irIIKNE1MQ7UxsPt03pzYE6nQ7TIm4X X-Received: by 2002:a92:9a02:: with SMTP id t2mr4062369ili.11.1583350088788; Wed, 04 Mar 2020 11:28:08 -0800 (PST) X-Received: by 2002:a92:9a02:: with SMTP id t2mr4062321ili.11.1583350088213; Wed, 04 Mar 2020 11:28:08 -0800 (PST) MIME-Version: 1.0 References: <20200303233609.713348-1-jarkko.sakkinen@linux.intel.com> <20200303233609.713348-15-jarkko.sakkinen@linux.intel.com> In-Reply-To: <20200303233609.713348-15-jarkko.sakkinen@linux.intel.com> From: Nathaniel McCallum Date: Wed, 4 Mar 2020 14:27:57 -0500 Message-ID: Subject: Re: [PATCH v28 14/22] selftests/x86: Add a selftest for SGX To: Jarkko Sakkinen Cc: linux-kernel@vger.kernel.org, x86@kernel.org, linux-sgx@vger.kernel.org, akpm@linux-foundation.org, dave.hansen@intel.com, "Christopherson, Sean J" , Neil Horman , "Huang, Haitao" , andriy.shevchenko@linux.intel.com, tglx@linutronix.de, "Svahn, Kai" , bp@alien8.de, Josh Triplett , luto@kernel.org, kai.huang@intel.com, rientjes@google.com, cedric.xing@intel.com, Patrick Uiterwijk , linux-kselftest@vger.kernel.org Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Tue, Mar 3, 2020 at 6:39 PM Jarkko Sakkinen wrote: > > Add a selftest for SGX. It is a trivial test where a simple enclave > copies one 64-bit word of memory between two memory locations given to > the enclave as arguments. Use ENCLS[EENTER] to invoke the enclave. > > Cc: linux-sgx@vger.kernel.org > Cc: linux-kselftest@vger.kernel.org > Signed-off-by: Jarkko Sakkinen > --- > tools/testing/selftests/x86/sgx/.gitignore | 3 + > tools/testing/selftests/x86/sgx/Makefile | 48 ++ > tools/testing/selftests/x86/sgx/defines.h | 17 + > tools/testing/selftests/x86/sgx/encl.c | 20 + > tools/testing/selftests/x86/sgx/encl.lds | 34 ++ > .../selftests/x86/sgx/encl_bootstrap.S | 94 ++++ > tools/testing/selftests/x86/sgx/main.c | 247 +++++++++ > tools/testing/selftests/x86/sgx/sgx_call.S | 23 + > tools/testing/selftests/x86/sgx/sgx_call.h | 11 + > tools/testing/selftests/x86/sgx/sgxsign.c | 493 ++++++++++++++++++ > .../testing/selftests/x86/sgx/signing_key.pem | 39 ++ > 11 files changed, 1029 insertions(+) > create mode 100644 tools/testing/selftests/x86/sgx/.gitignore > create mode 100644 tools/testing/selftests/x86/sgx/Makefile > create mode 100644 tools/testing/selftests/x86/sgx/defines.h > create mode 100644 tools/testing/selftests/x86/sgx/encl.c > create mode 100644 tools/testing/selftests/x86/sgx/encl.lds > create mode 100644 tools/testing/selftests/x86/sgx/encl_bootstrap.S > create mode 100644 tools/testing/selftests/x86/sgx/main.c > create mode 100644 tools/testing/selftests/x86/sgx/sgx_call.S > create mode 100644 tools/testing/selftests/x86/sgx/sgx_call.h > create mode 100644 tools/testing/selftests/x86/sgx/sgxsign.c > create mode 100644 tools/testing/selftests/x86/sgx/signing_key.pem > > diff --git a/tools/testing/selftests/x86/sgx/.gitignore b/tools/testing/selftests/x86/sgx/.gitignore > new file mode 100644 > index 000000000000..98eb2d439606 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/.gitignore > @@ -0,0 +1,3 @@ > +encl.ss > +sgxsign > +test_sgx > diff --git a/tools/testing/selftests/x86/sgx/Makefile b/tools/testing/selftests/x86/sgx/Makefile > new file mode 100644 > index 000000000000..f838700029e2 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/Makefile > @@ -0,0 +1,48 @@ > +top_srcdir = ../../../../.. > + > +include ../../lib.mk > + > +ifndef OBJCOPY > +OBJCOPY := $(CROSS_COMPILE)objcopy > +endif > + > +INCLUDES := -I$(top_srcdir)/tools/include > +HOST_CFLAGS := -Wall -Werror -g $(INCLUDES) -fPIC -z noexecstack > +ENCL_CFLAGS := -Wall -Werror -static -nostdlib -nostartfiles -fPIC \ > + -fno-stack-protector -mrdrnd $(INCLUDES) > + > +TEST_CUSTOM_PROGS := $(OUTPUT)/test_sgx $(OUTPUT)/encl.bin $(OUTPUT)/encl.ss > + > +all: $(TEST_CUSTOM_PROGS) > + > +$(OUTPUT)/test_sgx: $(OUTPUT)/main.o $(OUTPUT)/sgx_call.o > + $(CC) $(HOST_CFLAGS) -o $@ $^ > + > +$(OUTPUT)/main.o: main.c > + $(CC) $(HOST_CFLAGS) -c $< -o $@ > + > +$(OUTPUT)/sgx_call.o: sgx_call.S > + $(CC) $(HOST_CFLAGS) -c $< -o $@ > + > +$(OUTPUT)/encl.bin: $(OUTPUT)/encl.elf $(OUTPUT)/sgxsign > + $(OBJCOPY) -O binary $< $@ > + > +$(OUTPUT)/encl.elf: encl.lds encl.c encl_bootstrap.S > + $(CC) $(ENCL_CFLAGS) -T $^ -o $@ > + > +$(OUTPUT)/encl.ss: $(OUTPUT)/encl.bin > + $(OUTPUT)/sgxsign signing_key.pem $(OUTPUT)/encl.bin $(OUTPUT)/encl.ss > + > +$(OUTPUT)/sgxsign: sgxsign.c > + $(CC) $(INCLUDES) -o $@ $< -lcrypto > + > +EXTRA_CLEAN := \ > + $(OUTPUT)/encl.bin \ > + $(OUTPUT)/encl.elf \ > + $(OUTPUT)/encl.ss \ > + $(OUTPUT)/sgx_call.o \ > + $(OUTPUT)/sgxsign \ > + $(OUTPUT)/test_sgx \ > + $(OUTPUT)/test_sgx.o \ > + > +.PHONY: clean > diff --git a/tools/testing/selftests/x86/sgx/defines.h b/tools/testing/selftests/x86/sgx/defines.h > new file mode 100644 > index 000000000000..87264f85cb9f > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/defines.h > @@ -0,0 +1,17 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright(c) 2016-19 Intel Corporation. > + */ > + > +#ifndef DEFINES_H > +#define DEFINES_H > + > +#include > + > +#define __aligned(x) __attribute__((__aligned__(x))) > +#define __packed __attribute__((packed)) > + > +#include "../../../../../arch/x86/kernel/cpu/sgx/arch.h" > +#include "../../../../../arch/x86/include/uapi/asm/sgx.h" > + > +#endif /* DEFINES_H */ > diff --git a/tools/testing/selftests/x86/sgx/encl.c b/tools/testing/selftests/x86/sgx/encl.c > new file mode 100644 > index 000000000000..ede915399742 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/encl.c > @@ -0,0 +1,20 @@ > +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) > +// Copyright(c) 2016-18 Intel Corporation. > + > +#include > +#include "defines.h" > + > +static void *memcpy(void *dest, const void *src, size_t n) > +{ > + size_t i; > + > + for (i = 0; i < n; i++) > + ((char *)dest)[i] = ((char *)src)[i]; > + > + return dest; > +} > + > +void encl_body(void *rdi, void *rsi) > +{ > + memcpy(rsi, rdi, 8); > +} > diff --git a/tools/testing/selftests/x86/sgx/encl.lds b/tools/testing/selftests/x86/sgx/encl.lds > new file mode 100644 > index 000000000000..9a56d3064104 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/encl.lds > @@ -0,0 +1,34 @@ > +OUTPUT_FORMAT(elf64-x86-64) > + > +SECTIONS > +{ > + . = 0; > + .tcs : { > + *(.tcs*) > + } > + > + . = ALIGN(4096); > + .text : { > + *(.text*) > + *(.rodata*) > + } > + > + . = ALIGN(4096); > + .data : { > + *(.data*) > + } > + > + /DISCARD/ : { > + *(.data*) > + *(.comment*) > + *(.note*) > + *(.debug*) > + *(.eh_frame*) > + } > +} > + > +ASSERT(!DEFINED(.altinstructions), "ALTERNATIVES are not supported in enclaves") > +ASSERT(!DEFINED(.altinstr_replacement), "ALTERNATIVES are not supported in enclaves") > +ASSERT(!DEFINED(.discard.retpoline_safe), "RETPOLINE ALTERNATIVES are not supported in enclaves") > +ASSERT(!DEFINED(.discard.nospec), "RETPOLINE ALTERNATIVES are not supported in enclaves") > +ASSERT(!DEFINED(.got.plt), "Libcalls are not supported in enclaves") > diff --git a/tools/testing/selftests/x86/sgx/encl_bootstrap.S b/tools/testing/selftests/x86/sgx/encl_bootstrap.S > new file mode 100644 > index 000000000000..d07f970ccdf9 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/encl_bootstrap.S > @@ -0,0 +1,94 @@ > +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ > +/* > + * Copyright(c) 2016-18 Intel Corporation. > + */ > + > + .macro ENCLU > + .byte 0x0f, 0x01, 0xd7 > + .endm > + > + .section ".tcs", "a" > + .balign 4096 > + > + .fill 1, 8, 0 # STATE (set by CPU) > + .fill 1, 8, 0 # FLAGS > + .quad encl_ssa # OSSA > + .fill 1, 4, 0 # CSSA (set by CPU) > + .fill 1, 4, 1 # NSSA > + .quad encl_entry # OENTRY > + .fill 1, 8, 0 # AEP (set by EENTER and ERESUME) > + .fill 1, 8, 0 # OFSBASE > + .fill 1, 8, 0 # OGSBASE > + .fill 1, 4, 0xFFFFFFFF # FSLIMIT > + .fill 1, 4, 0xFFFFFFFF # GSLIMIT > + .fill 4024, 1, 0 # Reserved > + > + .text > + > +encl_entry: > + # RBX contains the base address for TCS, which is also the first address > + # inside the enclave. By adding the value of le_stack_end to it, we get > + # the absolute address for the stack. > + lea (encl_stack)(%rbx), %rax > + xchg %rsp, %rax > + push %rax > + > + push %rcx # push the address after EENTER > + push %rbx # push the enclave base address > + > + call encl_body > + > + pop %rbx # pop the enclave base address > + > + # Restore XSAVE registers to a synthetic state. > + mov $0xFFFFFFFF, %rax > + mov $0xFFFFFFFF, %rdx > + lea (xsave_area)(%rbx), %rdi > + fxrstor (%rdi) > + > + # Clear GPRs. > + xor %rcx, %rcx > + xor %rdx, %rdx > + xor %rdi, %rdi > + xor %rsi, %rsi > + xor %r8, %r8 > + xor %r9, %r9 > + xor %r10, %r10 > + xor %r11, %r11 > + xor %r12, %r12 > + xor %r13, %r13 > + xor %r14, %r14 > + xor %r15, %r15 > + > + # Reset status flags. > + add %rdx, %rdx # OF = SF = AF = CF = 0; ZF = PF = 1 > + > + # Prepare EEXIT target by popping the address of the instruction after > + # EENTER to RBX. > + pop %rbx > + > + # Restore the caller stack. > + pop %rax > + mov %rax, %rsp > + > + # EEXIT > + mov $4, %rax > + enclu > + > + .section ".data", "aw" > + > +encl_ssa: > + .space 4096 > + > +xsave_area: > + .fill 1, 4, 0x037F # FCW > + .fill 5, 4, 0 > + .fill 1, 4, 0x1F80 # MXCSR > + .fill 1, 4, 0xFFFF # MXCSR_MASK > + .fill 123, 4, 0 > + .fill 1, 4, 0x80000000 # XCOMP_BV[63] = 1, compaction mode > + .fill 12, 4, 0 I find this much more readable: xsave_area: # Legacy .fill 1, 4, 0x037F # FCW .fill 5, 4, 0 .fill 1, 4, 0x1F80 # MXCSR .fill 1, 4, 0xFFFF # MXCSR_MASK .fill 60, 8, 0 # Header .fill 1, 8, 0 # XSTATE_BV .fill 1, 8, 1 << 63 # XCOMP_BV (compaction mode) .fill 6, 8, 0 Also, since people are likely to copy this code for their own enclaves, it would be helpful to document which flags are set in FCW and MXCSR. > + > + .balign 4096 > + .space 8192 > +encl_stack: > diff --git a/tools/testing/selftests/x86/sgx/main.c b/tools/testing/selftests/x86/sgx/main.c > new file mode 100644 > index 000000000000..48ed5fdfb3cb > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/main.c > @@ -0,0 +1,247 @@ > +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) > +// Copyright(c) 2016-18 Intel Corporation. > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include "defines.h" > +#include "sgx_call.h" > + > +#define PAGE_SIZE 4096 > + > +static const uint64_t MAGIC = 0x1122334455667788ULL; > + > +static bool encl_create(int dev_fd, unsigned long bin_size, > + struct sgx_secs *secs) > +{ > + struct sgx_enclave_create ioc; > + void *area; > + int rc; > + > + memset(secs, 0, sizeof(*secs)); > + secs->ssa_frame_size = 1; > + secs->attributes = SGX_ATTR_MODE64BIT; > + secs->xfrm = 3; > + > + for (secs->size = 4096; secs->size < bin_size; ) > + secs->size <<= 1; > + > + area = mmap(NULL, secs->size * 2, PROT_NONE, MAP_SHARED, dev_fd, 0); > + if (area == MAP_FAILED) { > + perror("mmap"); > + return false; > + } > + > + secs->base = ((uint64_t)area + secs->size - 1) & ~(secs->size - 1); > + > + munmap(area, secs->base - (uint64_t)area); > + munmap((void *)(secs->base + secs->size), > + (uint64_t)area + secs->size - secs->base); > + > + ioc.src = (unsigned long)secs; > + rc = ioctl(dev_fd, SGX_IOC_ENCLAVE_CREATE, &ioc); > + if (rc) { > + fprintf(stderr, "ECREATE failed rc=%d, err=%d.\n", rc, errno); > + munmap((void *)secs->base, secs->size); > + return false; > + } > + > + return true; > +} > + > +static bool encl_add_pages(int dev_fd, unsigned long offset, void *data, > + unsigned long length, uint64_t flags) > +{ > + struct sgx_enclave_add_pages ioc; > + struct sgx_secinfo secinfo; > + int rc; > + > + memset(&secinfo, 0, sizeof(secinfo)); > + secinfo.flags = flags; > + > + ioc.src = (uint64_t)data; > + ioc.offset = offset; > + ioc.length = length; > + ioc.secinfo = (unsigned long)&secinfo; > + ioc.flags = SGX_PAGE_MEASURE; > + > + rc = ioctl(dev_fd, SGX_IOC_ENCLAVE_ADD_PAGES, &ioc); > + if (rc) { > + fprintf(stderr, "EADD failed rc=%d.\n", rc); > + return false; > + } > + > + if (ioc.count != ioc.length) { > + fprintf(stderr, "Partially processed, update the test.\n"); > + return false; > + } > + > + return true; > +} > + > +#define SGX_REG_PAGE_FLAGS \ > + (SGX_SECINFO_REG | SGX_SECINFO_R | SGX_SECINFO_W | SGX_SECINFO_X) > + > +static bool encl_build(struct sgx_secs *secs, void *bin, > + unsigned long bin_size, struct sgx_sigstruct *sigstruct) > +{ > + struct sgx_enclave_init ioc; > + void *addr; > + int dev_fd; > + int rc; > + > + dev_fd = open("/dev/sgx/enclave", O_RDWR); > + if (dev_fd < 0) { > + fprintf(stderr, "Unable to open /dev/sgx\n"); > + return false; > + } > + > + if (!encl_create(dev_fd, bin_size, secs)) > + goto out_dev_fd; > + > + if (!encl_add_pages(dev_fd, 0, bin, PAGE_SIZE, SGX_SECINFO_TCS)) > + goto out_dev_fd; > + > + if (!encl_add_pages(dev_fd, PAGE_SIZE, bin + PAGE_SIZE, > + bin_size - PAGE_SIZE, SGX_REG_PAGE_FLAGS)) > + goto out_dev_fd; > + > + ioc.sigstruct = (uint64_t)sigstruct; > + rc = ioctl(dev_fd, SGX_IOC_ENCLAVE_INIT, &ioc); > + if (rc) { > + printf("EINIT failed rc=%d\n", rc); > + goto out_map; > + } > + > + addr = mmap((void *)secs->base, PAGE_SIZE, PROT_READ | PROT_WRITE, > + MAP_SHARED | MAP_FIXED, dev_fd, 0); > + if (addr == MAP_FAILED) { > + fprintf(stderr, "mmap() failed on TCS, errno=%d.\n", errno); > + return false; > + } > + > + addr = mmap((void *)(secs->base + PAGE_SIZE), bin_size - PAGE_SIZE, > + PROT_READ | PROT_WRITE | PROT_EXEC, > + MAP_SHARED | MAP_FIXED, dev_fd, 0); > + if (addr == MAP_FAILED) { > + fprintf(stderr, "mmap() failed, errno=%d.\n", errno); > + return false; > + } > + > + close(dev_fd); > + return true; > +out_map: > + munmap((void *)secs->base, secs->size); > +out_dev_fd: > + close(dev_fd); > + return false; > +} > + > +bool get_file_size(const char *path, off_t *bin_size) > +{ > + struct stat sb; > + int ret; > + > + ret = stat(path, &sb); > + if (ret) { > + perror("stat"); > + return false; > + } > + > + if (!sb.st_size || sb.st_size & 0xfff) { > + fprintf(stderr, "Invalid blob size %lu\n", sb.st_size); > + return false; > + } > + > + *bin_size = sb.st_size; > + return true; > +} > + > +bool encl_data_map(const char *path, void **bin, off_t *bin_size) > +{ > + int fd; > + > + fd = open(path, O_RDONLY); > + if (fd == -1) { > + fprintf(stderr, "open() %s failed, errno=%d.\n", path, errno); > + return false; > + } > + > + if (!get_file_size(path, bin_size)) > + goto err_out; > + > + *bin = mmap(NULL, *bin_size, PROT_READ, MAP_PRIVATE, fd, 0); > + if (*bin == MAP_FAILED) { > + fprintf(stderr, "mmap() %s failed, errno=%d.\n", path, errno); > + goto err_out; > + } > + > + close(fd); > + return true; > + > +err_out: > + close(fd); > + return false; > +} > + > +bool load_sigstruct(const char *path, void *sigstruct) > +{ > + int fd; > + > + fd = open(path, O_RDONLY); > + if (fd == -1) { > + fprintf(stderr, "open() %s failed, errno=%d.\n", path, errno); > + return false; > + } > + > + if (read(fd, sigstruct, sizeof(struct sgx_sigstruct)) != > + sizeof(struct sgx_sigstruct)) { > + fprintf(stderr, "read() %s failed, errno=%d.\n", path, errno); > + close(fd); > + return false; > + } > + > + close(fd); > + return true; > +} > + > +int main(int argc, char *argv[], char *envp[]) > +{ > + struct sgx_sigstruct sigstruct; > + struct sgx_secs secs; > + uint64_t result = 0; > + off_t bin_size; > + void *bin; > + > + if (!encl_data_map("encl.bin", &bin, &bin_size)) > + exit(1); > + > + if (!load_sigstruct("encl.ss", &sigstruct)) > + exit(1); > + > + if (!encl_build(&secs, bin, bin_size, &sigstruct)) > + exit(1); > + > + printf("Input: 0x%lx\n", MAGIC); > + > + sgx_call_eenter((void *)&MAGIC, &result, (void *)secs.base); > + if (result != MAGIC) { > + fprintf(stderr, "0x%lx != 0x%lx\n", result, MAGIC); > + exit(1); > + } > + > + printf("Output: 0x%lx\n", result); > + > + exit(0); > +} > diff --git a/tools/testing/selftests/x86/sgx/sgx_call.S b/tools/testing/selftests/x86/sgx/sgx_call.S > new file mode 100644 > index 000000000000..ca4c7893f9d9 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/sgx_call.S > @@ -0,0 +1,23 @@ > +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ > +/** > +* Copyright(c) 2016-18 Intel Corporation. > +*/ > + > + .text > + > + .macro ENCLU > + .byte 0x0f, 0x01, 0xd7 > + .endm > + > + .text > + > + .global sgx_call_eenter > +sgx_call_eenter: > + push %rbx > + mov $0x02, %rax > + mov %rdx, %rbx > + lea sgx_async_exit(%rip), %rcx > +sgx_async_exit: > + ENCLU > + pop %rbx > + ret > diff --git a/tools/testing/selftests/x86/sgx/sgx_call.h b/tools/testing/selftests/x86/sgx/sgx_call.h > new file mode 100644 > index 000000000000..bf72068ada23 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/sgx_call.h > @@ -0,0 +1,11 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright(c) 2016-19 Intel Corporation. > + */ > + > +#ifndef SGX_CALL_H > +#define SGX_CALL_H > + > +void sgx_call_eenter(void *rdi, void *rsi, void *entry); > + > +#endif /* SGX_CALL_H */ > diff --git a/tools/testing/selftests/x86/sgx/sgxsign.c b/tools/testing/selftests/x86/sgx/sgxsign.c > new file mode 100644 > index 000000000000..3d9007af40c9 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/sgxsign.c > @@ -0,0 +1,493 @@ > +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) > +// Copyright(c) 2016-18 Intel Corporation. > + > +#define _GNU_SOURCE > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include "defines.h" > + > +struct sgx_sigstruct_payload { > + struct sgx_sigstruct_header header; > + struct sgx_sigstruct_body body; > +}; > + > +static bool check_crypto_errors(void) > +{ > + int err; > + bool had_errors = false; > + const char *filename; > + int line; > + char str[256]; > + > + for ( ; ; ) { > + if (ERR_peek_error() == 0) > + break; > + > + had_errors = true; > + err = ERR_get_error_line(&filename, &line); > + ERR_error_string_n(err, str, sizeof(str)); > + fprintf(stderr, "crypto: %s: %s:%d\n", str, filename, line); > + } > + > + return had_errors; > +} > + > +static void exit_usage(const char *program) > +{ > + fprintf(stderr, > + "Usage: %s/sign-le \n", program); > + exit(1); > +} > + > +static inline const BIGNUM *get_modulus(RSA *key) > +{ > +#if OPENSSL_VERSION_NUMBER < 0x10100000L > + return key->n; > +#else > + const BIGNUM *n; > + > + RSA_get0_key(key, &n, NULL, NULL); > + return n; > +#endif > +} > + > +static RSA *load_sign_key(const char *path) > +{ > + FILE *f; > + RSA *key; > + > + f = fopen(path, "rb"); > + if (!f) { > + fprintf(stderr, "Unable to open %s\n", path); > + return NULL; > + } > + key = RSA_new(); > + if (!PEM_read_RSAPrivateKey(f, &key, NULL, NULL)) > + return NULL; > + fclose(f); > + > + if (BN_num_bytes(get_modulus(key)) != SGX_MODULUS_SIZE) { > + fprintf(stderr, "Invalid key size %d\n", > + BN_num_bytes(get_modulus(key))); > + RSA_free(key); > + return NULL; > + } > + > + return key; > +} > + > +static void reverse_bytes(void *data, int length) > +{ > + int i = 0; > + int j = length - 1; > + uint8_t temp; > + uint8_t *ptr = data; > + > + while (i < j) { > + temp = ptr[i]; > + ptr[i] = ptr[j]; > + ptr[j] = temp; > + i++; > + j--; > + } > +} > + > +enum mrtags { > + MRECREATE = 0x0045544145524345, > + MREADD = 0x0000000044444145, > + MREEXTEND = 0x00444E4554584545, > +}; > + > +static bool mrenclave_update(EVP_MD_CTX *ctx, const void *data) > +{ > + if (!EVP_DigestUpdate(ctx, data, 64)) { > + fprintf(stderr, "digest update failed\n"); > + return false; > + } > + > + return true; > +} > + > +static bool mrenclave_commit(EVP_MD_CTX *ctx, uint8_t *mrenclave) > +{ > + unsigned int size; > + > + if (!EVP_DigestFinal_ex(ctx, (unsigned char *)mrenclave, &size)) { > + fprintf(stderr, "digest commit failed\n"); > + return false; > + } > + > + if (size != 32) { > + fprintf(stderr, "invalid digest size = %u\n", size); > + return false; > + } > + > + return true; > +} > + > +struct mrecreate { > + uint64_t tag; > + uint32_t ssaframesize; > + uint64_t size; > + uint8_t reserved[44]; > +} __attribute__((__packed__)); > + > + > +static bool mrenclave_ecreate(EVP_MD_CTX *ctx, uint64_t blob_size) > +{ > + struct mrecreate mrecreate; > + uint64_t encl_size; > + > + for (encl_size = 0x1000; encl_size < blob_size; ) > + encl_size <<= 1; > + > + memset(&mrecreate, 0, sizeof(mrecreate)); > + mrecreate.tag = MRECREATE; > + mrecreate.ssaframesize = 1; > + mrecreate.size = encl_size; > + > + if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) > + return false; > + > + return mrenclave_update(ctx, &mrecreate); > +} > + > +struct mreadd { > + uint64_t tag; > + uint64_t offset; > + uint64_t flags; /* SECINFO flags */ > + uint8_t reserved[40]; > +} __attribute__((__packed__)); > + > +static bool mrenclave_eadd(EVP_MD_CTX *ctx, uint64_t offset, uint64_t flags) > +{ > + struct mreadd mreadd; > + > + memset(&mreadd, 0, sizeof(mreadd)); > + mreadd.tag = MREADD; > + mreadd.offset = offset; > + mreadd.flags = flags; > + > + return mrenclave_update(ctx, &mreadd); > +} > + > +struct mreextend { > + uint64_t tag; > + uint64_t offset; > + uint8_t reserved[48]; > +} __attribute__((__packed__)); > + > +static bool mrenclave_eextend(EVP_MD_CTX *ctx, uint64_t offset, uint8_t *data) > +{ > + struct mreextend mreextend; > + int i; > + > + for (i = 0; i < 0x1000; i += 0x100) { > + memset(&mreextend, 0, sizeof(mreextend)); > + mreextend.tag = MREEXTEND; > + mreextend.offset = offset + i; > + > + if (!mrenclave_update(ctx, &mreextend)) > + return false; > + > + if (!mrenclave_update(ctx, &data[i + 0x00])) > + return false; > + > + if (!mrenclave_update(ctx, &data[i + 0x40])) > + return false; > + > + if (!mrenclave_update(ctx, &data[i + 0x80])) > + return false; > + > + if (!mrenclave_update(ctx, &data[i + 0xC0])) > + return false; > + } > + > + return true; > +} > + > +/** > + * measure_encl - measure enclave > + * @path: path to the enclave > + * @mrenclave: measurement > + * > + * Calculates MRENCLAVE. Assumes that the very first page is a TCS page and > + * following pages are regular pages. Does not measure the contents of the > + * enclave as the signing tool is used at the moment only for the launch > + * enclave, which is pass-through (everything gets a token). > + */ > +static bool measure_encl(const char *path, uint8_t *mrenclave) > +{ > + FILE *file; > + struct stat sb; > + EVP_MD_CTX *ctx; > + uint64_t flags; > + uint64_t offset; > + uint8_t data[0x1000]; > + int rc; > + > + ctx = EVP_MD_CTX_create(); > + if (!ctx) > + return false; > + > + file = fopen(path, "rb"); > + if (!file) { > + perror("fopen"); > + EVP_MD_CTX_destroy(ctx); > + return false; > + } > + > + rc = stat(path, &sb); > + if (rc) { > + perror("stat"); > + goto out; > + } > + > + if (!sb.st_size || sb.st_size & 0xfff) { > + fprintf(stderr, "Invalid blob size %lu\n", sb.st_size); > + goto out; > + } > + > + if (!mrenclave_ecreate(ctx, sb.st_size)) > + goto out; > + > + for (offset = 0; offset < sb.st_size; offset += 0x1000) { > + if (!offset) > + flags = SGX_SECINFO_TCS; > + else > + flags = SGX_SECINFO_REG | SGX_SECINFO_R | > + SGX_SECINFO_W | SGX_SECINFO_X; > + > + if (!mrenclave_eadd(ctx, offset, flags)) > + goto out; > + > + rc = fread(data, 1, 0x1000, file); > + if (!rc) > + break; > + if (rc < 0x1000) > + goto out; > + > + if (!mrenclave_eextend(ctx, offset, data)) > + goto out; > + } > + > + if (!mrenclave_commit(ctx, mrenclave)) > + goto out; > + > + fclose(file); > + EVP_MD_CTX_destroy(ctx); > + return true; > +out: > + fclose(file); > + EVP_MD_CTX_destroy(ctx); > + return false; > +} > + > +/** > + * sign_encl - sign enclave > + * @sigstruct: pointer to SIGSTRUCT > + * @key: 3072-bit RSA key > + * @signature: byte array for the signature > + * > + * Calculates EMSA-PKCSv1.5 signature for the given SIGSTRUCT. The result is > + * stored in big-endian format so that it can be further passed to OpenSSL > + * libcrypto functions. > + */ > +static bool sign_encl(const struct sgx_sigstruct *sigstruct, RSA *key, > + uint8_t *signature) > +{ > + struct sgx_sigstruct_payload payload; > + unsigned int siglen; > + uint8_t digest[SHA256_DIGEST_LENGTH]; > + bool ret; > + > + memcpy(&payload.header, &sigstruct->header, sizeof(sigstruct->header)); > + memcpy(&payload.body, &sigstruct->body, sizeof(sigstruct->body)); > + > + SHA256((unsigned char *)&payload, sizeof(payload), digest); > + > + ret = RSA_sign(NID_sha256, digest, SHA256_DIGEST_LENGTH, signature, > + &siglen, key); > + > + return ret; > +} > + > +struct q1q2_ctx { > + BN_CTX *bn_ctx; > + BIGNUM *m; > + BIGNUM *s; > + BIGNUM *q1; > + BIGNUM *qr; > + BIGNUM *q2; > +}; > + > +static void free_q1q2_ctx(struct q1q2_ctx *ctx) > +{ > + BN_CTX_free(ctx->bn_ctx); > + BN_free(ctx->m); > + BN_free(ctx->s); > + BN_free(ctx->q1); > + BN_free(ctx->qr); > + BN_free(ctx->q2); > +} > + > +static bool alloc_q1q2_ctx(const uint8_t *s, const uint8_t *m, > + struct q1q2_ctx *ctx) > +{ > + ctx->bn_ctx = BN_CTX_new(); > + ctx->s = BN_bin2bn(s, SGX_MODULUS_SIZE, NULL); > + ctx->m = BN_bin2bn(m, SGX_MODULUS_SIZE, NULL); > + ctx->q1 = BN_new(); > + ctx->qr = BN_new(); > + ctx->q2 = BN_new(); > + > + if (!ctx->bn_ctx || !ctx->s || !ctx->m || !ctx->q1 || !ctx->qr || > + !ctx->q2) { > + free_q1q2_ctx(ctx); > + return false; > + } > + > + return true; > +} > + > +static bool calc_q1q2(const uint8_t *s, const uint8_t *m, uint8_t *q1, > + uint8_t *q2) > +{ > + struct q1q2_ctx ctx; > + > + if (!alloc_q1q2_ctx(s, m, &ctx)) { > + fprintf(stderr, "Not enough memory for Q1Q2 calculation\n"); > + return false; > + } > + > + if (!BN_mul(ctx.q1, ctx.s, ctx.s, ctx.bn_ctx)) > + goto out; > + > + if (!BN_div(ctx.q1, ctx.qr, ctx.q1, ctx.m, ctx.bn_ctx)) > + goto out; > + > + if (BN_num_bytes(ctx.q1) > SGX_MODULUS_SIZE) { > + fprintf(stderr, "Too large Q1 %d bytes\n", > + BN_num_bytes(ctx.q1)); > + goto out; > + } > + > + if (!BN_mul(ctx.q2, ctx.s, ctx.qr, ctx.bn_ctx)) > + goto out; > + > + if (!BN_div(ctx.q2, NULL, ctx.q2, ctx.m, ctx.bn_ctx)) > + goto out; > + > + if (BN_num_bytes(ctx.q2) > SGX_MODULUS_SIZE) { > + fprintf(stderr, "Too large Q2 %d bytes\n", > + BN_num_bytes(ctx.q2)); > + goto out; > + } > + > + BN_bn2bin(ctx.q1, q1); > + BN_bn2bin(ctx.q2, q2); > + > + free_q1q2_ctx(&ctx); > + return true; > +out: > + free_q1q2_ctx(&ctx); > + return false; > +} > + > +static bool save_sigstruct(const struct sgx_sigstruct *sigstruct, > + const char *path) > +{ > + FILE *f = fopen(path, "wb"); > + > + if (!f) { > + fprintf(stderr, "Unable to open %s\n", path); > + return false; > + } > + > + fwrite(sigstruct, sizeof(*sigstruct), 1, f); > + fclose(f); > + return true; > +} > + > +int main(int argc, char **argv) > +{ > + uint64_t header1[2] = {0x000000E100000006, 0x0000000000010000}; > + uint64_t header2[2] = {0x0000006000000101, 0x0000000100000060}; > + struct sgx_sigstruct ss; > + const char *program; > + int opt; > + RSA *sign_key; > + > + memset(&ss, 0, sizeof(ss)); > + ss.header.header1[0] = header1[0]; > + ss.header.header1[1] = header1[1]; > + ss.header.header2[0] = header2[0]; > + ss.header.header2[1] = header2[1]; > + ss.exponent = 3; > + > +#ifndef CONFIG_EINITTOKENKEY > + ss.body.attributes = SGX_ATTR_MODE64BIT; > +#else > + ss.body.attributes = SGX_ATTR_MODE64BIT | SGX_ATTR_EINITTOKENKEY; > +#endif > + ss.body.xfrm = 3, > + > + program = argv[0]; > + > + do { > + opt = getopt(argc, argv, ""); > + switch (opt) { > + case -1: > + break; > + default: > + exit_usage(program); > + } > + } while (opt != -1); > + > + argc -= optind; > + argv += optind; > + > + if (argc < 3) > + exit_usage(program); > + > + /* sanity check only */ > + if (check_crypto_errors()) > + exit(1); > + > + sign_key = load_sign_key(argv[0]); > + if (!sign_key) > + goto out; > + > + BN_bn2bin(get_modulus(sign_key), ss.modulus); > + > + if (!measure_encl(argv[1], ss.body.mrenclave)) > + goto out; > + > + if (!sign_encl(&ss, sign_key, ss.signature)) > + goto out; > + > + if (!calc_q1q2(ss.signature, ss.modulus, ss.q1, ss.q2)) > + goto out; > + > + /* convert to little endian */ > + reverse_bytes(ss.signature, SGX_MODULUS_SIZE); > + reverse_bytes(ss.modulus, SGX_MODULUS_SIZE); > + reverse_bytes(ss.q1, SGX_MODULUS_SIZE); > + reverse_bytes(ss.q2, SGX_MODULUS_SIZE); > + > + if (!save_sigstruct(&ss, argv[2])) > + goto out; > + exit(0); > +out: > + check_crypto_errors(); > + exit(1); > +} > diff --git a/tools/testing/selftests/x86/sgx/signing_key.pem b/tools/testing/selftests/x86/sgx/signing_key.pem > new file mode 100644 > index 000000000000..d76f21f19187 > --- /dev/null > +++ b/tools/testing/selftests/x86/sgx/signing_key.pem > @@ -0,0 +1,39 @@ > +-----BEGIN RSA PRIVATE KEY----- > +MIIG4wIBAAKCAYEApalGbq7Q+usM91CPtksu3D+b0Prc8gAFL6grM3mg85A5Bx8V > +cfMXPgtrw8EYFwQxDAvzZWwl+9VfOX0ECrFRBkOHcOiG0SnADN8+FLj1UiNUQwbp > +S6OzhNWuRcSbGraSOyUlVlV0yMQSvewyzGklOaXBe30AJqzIBc8QfdSxKuP8rs0Z > +ga6k/Bl73osrYKByILJTUUeZqjLERsE6GebsdzbWgKn8qVqng4ZS4yMNg6LeRlH3 > ++9CIPgg4jwpSLHcp7dq2qTIB9a0tGe9ayp+5FbucpB6U7ePold0EeRN6RlJGDF9k > +L93v8P5ykz5G5gYZ2g0K1X2sHIWV4huxPgv5PXgdyQYbK+6olqj0d5rjYuwX57Ul > +k6SroPS1U6UbdCjG5txM+BNGU0VpD0ZhrIRw0leQdnNcCO9sTJuInZrgYacSVJ7u > +mtB+uCt+uzUesc+l+xPRYA+9e14lLkZp7AAmo9FvL816XDI09deehJ3i/LmHKCRN > +tuqC5TprRjFwUr6dAgEDAoIBgG5w2Z8fNfycs0+LCnmHdJLVEotR6KFVWMpwHMz7 > +wKJgJgS/Y6FMuilc8oKAuroCy11dTO5IGVKOP3uorVx2NgQtBPXwWeDGgAiU1A3Q > +o4wXjYIEm4fCd63jyYPYZ2ckYXzDbjmOTdstYdPyzIhGGNEZK6eoqsRzMAPfYFPj > +IMdCqHSIu6vJw1K7p+myHOsVoWshjODaZnF3LYSA0WaZ8vokjwBxUxuRxQJZjJds > +s60XPtmL+qfgWtQFewoG4XL6GuD8FcXccynRRtzrLtFNPIl9BQfWfjBBhTC1/Te1 > +0Z6XbZvpdUTD9OfLB7SbR2OUFNpKQgriO0iYVdbW3cr7uu38Zwp4W1TX73DPjoi6 > +KNooP6SGWd4mRJW2+dUmSYS4QNG8eVVZswKcploEIXlAKRsOe4kzJJ1iETugIe85 > +uX8nd1WYEp65xwoRUg8hqng0MeyveVbXqNKuJG6tzNDt9kgFYo+hmC/oouAW2Dtc > +T9jdRAwKJXqA2Eg6OkgXCEv+kwKBwQDYaQiFMlFhsmLlqI+EzCUh7c941/cL7m6U > +7j98+8ngl0HgCEcrc10iJVCKakQW3YbPzAx3XkKTaGjWazvvrFarXIGlOud64B8a > +iWyQ7VdlnmZnNEdk+C83tI91OQeaTKqRLDGzKh29Ry/jL8Pcbazt+kDgxa0H7qJp > +roADUanLQuNkYubpbhFBh3xpa2EExaVq6rF7nIVsD8W9TrbmPKA4LgH7z0iy544D > +kVCNYsTjYDdUWP+WiSor8kCnnpjnN9sCgcEAw/eNezUD1UDf6OYFC9+5JZJFn4Tg > +mZMyN93JKIb199ffwnjtHUSjcyiWeesXucpzwtGbTcwQnDisSW4oneYKLSEBlBaq > +scqiUugyGZZOthFSCbdXYXMViK2vHrKlkse7GxVlROKcEhM/pRBrmjaGO8eWR+D4 > +FO2wCXzVs3KgV6j779frw0vC54oHOxc9+Lu1rSHp4i+600koyvL/zF6U/5tZXIvN > +YW2yoiQJnjCmVA1pwbwV6KAUTPDTMnBK+YjnAoHBAJBGBa4hi5Z27JkbCliIGMFJ > +NPs6pLKe9GNJf6in2+sPgUAFhMeiPhbDiwbxgrnpBIqICE+ULGJFmzmc0p/IOceT > +ARjR76dAFLxbnbXzj5kURETNhO36yiUjCk4mBRGIcbYddndxaSjaH+zKgpLzyJ6m > +1esuc1qfFvEfAAI2cTIsl5hB70ZJYNZaUvDyQK3ZGPHxy6e9rkgKg9OJz0QoatAe > +q/002yHvtAJg4F5B2JeVejg7VQ8GHB1MKxppu0TP5wKBwQCCpQj8zgKOKz/wmViy > +lSYZDC5qWJW7t3bP6TDFr06lOpUsUJ4TgxeiGw778g/RMaKB4RIz3WBoJcgw9BsT > +7rFza1ZiucchMcGMmswRDt8kC4wGejpA92Owc8oUdxkMhSdnY5jYlxK2t3/DYEe8 > +JFl9L7mFQKVjSSAGUzkiTGrlG1Kf5UfXh9dFBq98uilQfSPIwUaWynyM23CHTKqI > +Pw3/vOY9sojrnncWwrEUIG7is5vWfWPwargzSzd29YdRBe8CgcEAuRVewK/YeNOX > +B7ZG6gKKsfsvrGtY7FPETzLZAHjoVXYNea4LVZ2kn4hBXXlvw/4HD+YqcTt4wmif > +5JQlDvjNobUiKJZpzy7hklVhF7wZFl4pCF7Yh43q9iQ7gKTaeUG7MiaK+G8Zz8aY > +HW9rsiihbdZkccMvnPfO9334XMxl3HtBRzLstjUlbLB7Sdh+7tZ3JQidCOFNs5pE > +XyWwnASPu4tKfDahH1UUTp1uJcq/6716CSWg080avYxFcn75qqsb > +-----END RSA PRIVATE KEY----- > -- > 2.25.0 >