Projects
Eulaceura:Factory
shim
_service:obs_scm:Feature-shim-support-sm2-and-s...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:Feature-shim-support-sm2-and-sm3-algorithm.patch of Package shim
From e1f5fc87be6581b63550218d991c713ad0f23113 Mon Sep 17 00:00:00 2001 From: Huaxin Lu <luhuaxin1@huawei.com> Date: Mon, 7 Nov 2022 11:47:42 +0800 Subject: [PATCH 2/2] shim support sm2 and sm3 algorithm Co-authored-by: Yusong Gao <gaoyusong2@huawei.com> Signed-off-by: Yusong Gao <gaoyusong2@huawei.com> Signed-off-by: Huaxin Lu <luhuaxin1@huawei.com> --- Makefile | 5 ++- MokManager.c | 8 ++++ include/pe.h | 7 ++++ include/peimage.h | 3 ++ pe.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++ shim.c | 74 ++++++++++++++++++++++++++++++++-- shim.h | 20 ++++++++++ 7 files changed, 212 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 24ac314..9b8d7e8 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,9 @@ CFLAGS += -DENABLE_SHIM_CERT else TARGETS += $(MMNAME) $(FBNAME) endif +ifneq ($(origin ENABLE_SHIM_SM),undefined) +CFLAGS += -DENABLE_SHIM_SM +endif OBJS = shim.o globals.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o sbat_data.o sbat_var.o pe.o httpboot.o csv.o load-options.o KEYS = shim_cert.h ocsp.* ca.* shim.crt shim.csr shim.p12 shim.pem shim.key shim.cer ORIG_SOURCES = shim.c globals.c mok.c netboot.c replacements.c tpm.c errlog.c sbat.c pe.c httpboot.c shim.h version.h $(wildcard include/*.h) cert.S sbat_var.S @@ -163,7 +166,7 @@ Cryptlib/libcryptlib.a: $(MAKE) TOPDIR=$(TOPDIR) VPATH=$(TOPDIR)/Cryptlib -C Cryptlib -f $(TOPDIR)/Cryptlib/Makefile Cryptlib/OpenSSL/libopenssl.a: - for i in x509v3 x509 txt_db stack sha rsa rc4 rand pkcs7 pkcs12 pem ocsp objects modes md5 lhash kdf hmac evp err dso dh conf comp cmac buffer bn bio async/arch asn1 aes; do mkdir -p Cryptlib/OpenSSL/crypto/$$i; done + for i in x509v3 x509 txt_db stack sha rsa rc4 rand pkcs7 pkcs12 pem ocsp objects modes md5 lhash kdf hmac evp err dso dh conf comp cmac buffer bn bio async/arch asn1 aes ec sm3 sm2 ecdsa; do mkdir -p Cryptlib/OpenSSL/crypto/$$i; done $(MAKE) TOPDIR=$(TOPDIR) VPATH=$(TOPDIR)/Cryptlib/OpenSSL -C Cryptlib/OpenSSL -f $(TOPDIR)/Cryptlib/OpenSSL/Makefile lib/lib.a: | $(TOPDIR)/lib/Makefile $(wildcard $(TOPDIR)/include/*.[ch]) diff --git a/MokManager.c b/MokManager.c index ffcd6a6..8b7fd4b 100644 --- a/MokManager.c +++ b/MokManager.c @@ -1910,6 +1910,9 @@ static EFI_STATUS enroll_file(void *data, UINTN datasize, BOOLEAN hash) if (hash) { UINT8 sha256[SHA256_DIGEST_SIZE]; UINT8 sha1[SHA1_DIGEST_SIZE]; +#ifdef ENABLE_SHIM_SM + UINT8 sm3[SM3_DIGEST_SIZE]; +#endif SHIM_LOCK *shim_lock; PE_COFF_LOADER_IMAGE_CONTEXT context; @@ -1929,8 +1932,13 @@ static EFI_STATUS enroll_file(void *data, UINTN datasize, BOOLEAN hash) if (EFI_ERROR(efi_status)) goto out; +#ifdef ENABLE_SHIM_SM + efi_status = shim_lock->Hash(data, datasize, &context, sha256, + sha1, sm3); +#else efi_status = shim_lock->Hash(data, datasize, &context, sha256, sha1); +#endif if (EFI_ERROR(efi_status)) goto out; diff --git a/include/pe.h b/include/pe.h index ccc8798..93af091 100644 --- a/include/pe.h +++ b/include/pe.h @@ -28,10 +28,17 @@ handle_image (void *data, unsigned int datasize, EFI_PHYSICAL_ADDRESS *alloc_address, UINTN *alloc_pages); +#ifdef ENABLE_SHIM_SM +EFI_STATUS +generate_hash (char *data, unsigned int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash, UINT8 *sm3hash); +#else EFI_STATUS generate_hash (char *data, unsigned int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash); +#endif EFI_STATUS relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, diff --git a/include/peimage.h b/include/peimage.h index e97b29c..7a4f356 100644 --- a/include/peimage.h +++ b/include/peimage.h @@ -807,6 +807,9 @@ typedef struct { #define SHA1_DIGEST_SIZE 20 #define SHA256_DIGEST_SIZE 32 +#ifdef ENABLE_SHIM_SM +#define SM3_DIGEST_SIZE 32 +#endif #define WIN_CERT_TYPE_PKCS_SIGNED_DATA 0x0002 typedef struct { diff --git a/pe.c b/pe.c index ba3e2bb..f48d635 100644 --- a/pe.c +++ b/pe.c @@ -297,13 +297,24 @@ get_section_vma_by_name (char *name, size_t namesz, * Calculate the SHA1 and SHA256 hashes of a binary */ +#ifdef ENABLE_SHIM_SM +EFI_STATUS +generate_hash(char *data, unsigned int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, + UINT8 *sha1hash, UINT8 *sm3hash) +#else EFI_STATUS generate_hash(char *data, unsigned int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash) +#endif { unsigned int sha256ctxsize, sha1ctxsize; void *sha256ctx = NULL, *sha1ctx = NULL; +#ifdef ENABLE_SHIM_SM + unsigned int sm3ctxsize; + void *sm3ctx = NULL; +#endif char *hashbase; unsigned int hashsize; unsigned int SumOfBytesHashed, SumOfSectionBytes; @@ -327,12 +338,25 @@ generate_hash(char *data, unsigned int datasize, sha1ctxsize = Sha1GetContextSize(); sha1ctx = AllocatePool(sha1ctxsize); +#ifdef ENABLE_SHIM_SM + sm3ctxsize = Sm3GetContextSize(); + sm3ctx = AllocatePool(sm3ctxsize); +#endif + +#ifdef ENABLE_SHIM_SM + if (!sha256ctx || !sha1ctx || !sm3ctx) { +#else if (!sha256ctx || !sha1ctx) { +#endif perror(L"Unable to allocate memory for hash context\n"); return EFI_OUT_OF_RESOURCES; } +#ifdef ENABLE_SHIM_SM + if (!Sha256Init(sha256ctx) || !Sha1Init(sha1ctx) || !Sm3Init(sm3ctx)) { +#else if (!Sha256Init(sha256ctx) || !Sha1Init(sha1ctx)) { +#endif perror(L"Unable to initialise hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -344,8 +368,14 @@ generate_hash(char *data, unsigned int datasize, hashbase; check_size(data, datasize, hashbase, hashsize); +#ifdef ENABLE_SHIM_SM + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize)) || + !(Sm3Update(sm3ctx, hashbase, hashsize))) { +#else if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || !(Sha1Update(sha1ctx, hashbase, hashsize))) { +#endif perror(L"Unable to generate hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -357,8 +387,14 @@ generate_hash(char *data, unsigned int datasize, hashsize = (char *)context->SecDir - hashbase; check_size(data, datasize, hashbase, hashsize); +#ifdef ENABLE_SHIM_SM + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize)) || + !(Sm3Update(sm3ctx, hashbase, hashsize))) { +#else if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || !(Sha1Update(sha1ctx, hashbase, hashsize))) { +#endif perror(L"Unable to generate hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -375,8 +411,14 @@ generate_hash(char *data, unsigned int datasize, } check_size(data, datasize, hashbase, hashsize); +#ifdef ENABLE_SHIM_SM + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize)) || + !(Sm3Update(sm3ctx, hashbase, hashsize))) { +#else if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || !(Sha1Update(sha1ctx, hashbase, hashsize))) { +#endif perror(L"Unable to generate hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -505,8 +547,14 @@ generate_hash(char *data, unsigned int datasize, hashsize = (unsigned int) Section->SizeOfRawData; check_size(data, datasize, hashbase, hashsize); +#ifdef ENABLE_SHIM_SM + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize)) || + !(Sm3Update(sm3ctx, hashbase, hashsize))) { +#else if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || !(Sha1Update(sha1ctx, hashbase, hashsize))) { +#endif perror(L"Unable to generate hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -531,8 +579,14 @@ generate_hash(char *data, unsigned int datasize, } check_size(data, datasize, hashbase, hashsize); +#ifdef ENABLE_SHIM_SM + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize)) || + !(Sm3Update(sm3ctx, hashbase, hashsize))) { +#else if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || !(Sha1Update(sha1ctx, hashbase, hashsize))) { +#endif perror(L"Unable to generate hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -551,8 +605,14 @@ generate_hash(char *data, unsigned int datasize, check_size(data, datasize, hashbase, hashsize); +#ifdef ENABLE_SHIM_SM + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize)) || + !(Sm3Update(sm3ctx, hashbase, hashsize))) { +#else if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || !(Sha1Update(sha1ctx, hashbase, hashsize))) { +#endif perror(L"Unable to generate hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -562,8 +622,14 @@ generate_hash(char *data, unsigned int datasize, } #endif +#ifdef ENABLE_SHIM_SM + if (!(Sha256Final(sha256ctx, sha256hash)) || + !(Sha1Final(sha1ctx, sha1hash)) || + !(Sm3Final(sm3ctx, sm3hash))) { +#else if (!(Sha256Final(sha256ctx, sha256hash)) || !(Sha1Final(sha1ctx, sha1hash))) { +#endif perror(L"Unable to finalise hash\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; @@ -573,6 +639,10 @@ generate_hash(char *data, unsigned int datasize, dhexdumpat(sha1hash, SHA1_DIGEST_SIZE, 0); dprint(L"sha256 authenticode hash:\n"); dhexdumpat(sha256hash, SHA256_DIGEST_SIZE, 0); +#ifdef ENABLE_SHIM_SM + dprint(L"sm3 authenticode hash:\n"); + dhexdumpat(sm3hash, SM3_DIGEST_SIZE, 0); +#endif done: if (SectionHeader) @@ -581,6 +651,10 @@ done: FreePool(sha1ctx); if (sha256ctx) FreePool(sha256ctx); +#ifdef ENABLE_SHIM_SM + if (sm3ctx) + FreePool(sm3ctx); +#endif return efi_status; } @@ -1027,6 +1101,9 @@ EFI_STATUS verify_image(void *data, unsigned int datasize, EFI_STATUS efi_status; UINT8 sha1hash[SHA1_DIGEST_SIZE]; UINT8 sha256hash[SHA256_DIGEST_SIZE]; +#ifdef ENABLE_SHIM_SM + UINT8 sm3hash[SHA256_DIGEST_SIZE]; +#endif /* * The binary header contains relevant context and section pointers @@ -1042,8 +1119,13 @@ EFI_STATUS verify_image(void *data, unsigned int datasize, * in order to load it. */ if (secure_mode()) { +#ifdef ENABLE_SHIM_SM + efi_status = verify_buffer(data, datasize, + context, sha256hash, sha1hash, sm3hash); +#else efi_status = verify_buffer(data, datasize, context, sha256hash, sha1hash); +#endif if (EFI_ERROR(efi_status)) { if (verbose) console_print(L"Verification failed: %r\n", efi_status); @@ -1061,8 +1143,13 @@ EFI_STATUS verify_image(void *data, unsigned int datasize, * this is only useful for the TPM1.2 case. We should try to fix * this in a follow-up. */ +#ifdef ENABLE_SHIM_SM + efi_status = generate_hash(data, datasize, context, sha256hash, + sha1hash, sm3hash); +#else efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash); +#endif if (EFI_ERROR(efi_status)) return efi_status; @@ -1103,6 +1190,9 @@ handle_image (void *data, unsigned int datasize, int found_entry_point = 0; UINT8 sha1hash[SHA1_DIGEST_SIZE]; UINT8 sha256hash[SHA256_DIGEST_SIZE]; +#ifdef ENABLE_SHIM_SM + UINT8 sm3hash[SM3_DIGEST_SIZE]; +#endif /* * The binary header contains relevant context and section pointers @@ -1118,8 +1208,13 @@ handle_image (void *data, unsigned int datasize, * in order to load it. */ if (secure_mode ()) { +#ifdef ENABLE_SHIM_SM + efi_status = verify_buffer(data, datasize, &context, sha256hash, + sha1hash, sm3hash); +#else efi_status = verify_buffer(data, datasize, &context, sha256hash, sha1hash); +#endif if (EFI_ERROR(efi_status)) { if (verbose) @@ -1140,8 +1235,13 @@ handle_image (void *data, unsigned int datasize, * this is only useful for the TPM1.2 case. We should try to fix * this in a follow-up. */ +#ifdef ENABLE_SHIM_SM + efi_status = generate_hash(data, datasize, &context, sha256hash, + sha1hash, sm3hash); +#else efi_status = generate_hash(data, datasize, &context, sha256hash, sha1hash); +#endif if (EFI_ERROR(efi_status)) return efi_status; diff --git a/shim.c b/shim.c index fdd205e..400bd9a 100644 --- a/shim.c +++ b/shim.c @@ -458,11 +458,20 @@ BOOLEAN secure_mode (void) return TRUE; } +#ifdef ENABLE_SHIM_SM +static EFI_STATUS +verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig, + UINT8 *sha256hash, UINT8 *sha1hash, UINT8 *sm3hash) +#else static EFI_STATUS verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig, UINT8 *sha256hash, UINT8 *sha1hash) +#endif { EFI_STATUS efi_status; +#ifdef ENABLE_SHIM_SM + sm3hash = sm3hash; +#endif /* * Ensure that the binary isn't forbidden @@ -532,11 +541,17 @@ verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig, if (vendor_cert_size) { dprint("verifying against vendor_cert\n"); } +#ifdef ENABLE_SHIM_SM if (vendor_cert_size && - AuthenticodeVerify(sig->CertData, - sig->Hdr.dwLength - sizeof(sig->Hdr), - vendor_cert, vendor_cert_size, - sha256hash, SHA256_DIGEST_SIZE)) { + (AuthenticodeVerify(sig->CertData, sig->Hdr.dwLength - sizeof(sig->Hdr), + vendor_cert, vendor_cert_size, sha256hash, SHA256_DIGEST_SIZE) || + AuthenticodeVerify(sig->CertData, sig->Hdr.dwLength - sizeof(sig->Hdr), + vendor_cert, vendor_cert_size, sm3hash, SM3_DIGEST_SIZE))) { +#else + if (vendor_cert_size && + (AuthenticodeVerify(sig->CertData, sig->Hdr.dwLength - sizeof(sig->Hdr), + vendor_cert, vendor_cert_size, sha256hash, SHA256_DIGEST_SIZE))) { +#endif dprint(L"AuthenticodeVerify(vendor_cert) succeeded\n"); update_verification_method(VERIFIED_BY_CERT); tpm_measure_variable(L"Shim", SHIM_LOCK_GUID, @@ -558,10 +573,17 @@ verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig, /* * Check that the signature is valid and matches the binary */ +#ifdef ENABLE_SHIM_SM +EFI_STATUS +verify_buffer_authenticode (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash, UINT8 *sm3hash) +#else EFI_STATUS verify_buffer_authenticode (char *data, int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash) +#endif { EFI_STATUS ret_efi_status; size_t size = datasize; @@ -578,7 +600,12 @@ verify_buffer_authenticode (char *data, int datasize, */ drain_openssl_errors(); +#ifdef ENABLE_SHIM_SM + ret_efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash, sm3hash); +#else ret_efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash); +#endif + if (EFI_ERROR(ret_efi_status)) { dprint(L"generate_hash: %r\n", ret_efi_status); PrintErrors(); @@ -665,7 +692,11 @@ verify_buffer_authenticode (char *data, int datasize, dprint(L"Attempting to verify signature %d:\n", i++); +#ifdef ENABLE_SHIM_SM + efi_status = verify_one_signature(sig, sha256hash, sha1hash, sm3hash); +#else efi_status = verify_one_signature(sig, sha256hash, sha1hash); +#endif /* * If we didn't get EFI_SECURITY_VIOLATION from @@ -746,10 +777,17 @@ verify_buffer_sbat (char *data, int datasize, * Check that the signature is valid and matches the binary and that * the binary is permitted to load by SBAT. */ +#ifdef ENABLE_SHIM_SM +EFI_STATUS +verify_buffer (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash, UINT8 *sm3hash) +#else EFI_STATUS verify_buffer (char *data, int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash) +#endif { EFI_STATUS efi_status; @@ -757,7 +795,11 @@ verify_buffer (char *data, int datasize, if (EFI_ERROR(efi_status)) return efi_status; +#ifdef ENABLE_SHIM_SM + return verify_buffer_authenticode(data, datasize, context, sha256hash, sha1hash, sm3hash); +#else return verify_buffer_authenticode(data, datasize, context, sha256hash, sha1hash); +#endif } static int @@ -970,6 +1012,9 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) PE_COFF_LOADER_IMAGE_CONTEXT context; UINT8 sha1hash[SHA1_DIGEST_SIZE]; UINT8 sha256hash[SHA256_DIGEST_SIZE]; +#ifdef ENABLE_SHIM_SM + UINT8 sm3hash[SM3_DIGEST_SIZE]; +#endif if ((INT32)size < 0) return EFI_INVALID_PARAMETER; @@ -981,8 +1026,13 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) if (EFI_ERROR(efi_status)) goto done; +#ifdef ENABLE_SHIM_SM + efi_status = generate_hash(buffer, size, &context, + sha256hash, sha1hash, sm3hash); +#else efi_status = generate_hash(buffer, size, &context, sha256hash, sha1hash); +#endif if (EFI_ERROR(efi_status)) goto done; @@ -1002,16 +1052,27 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) goto done; } +#ifdef ENABLE_SHIM_SM + efi_status = verify_buffer(buffer, size, + &context, sha256hash, sha1hash, sm3hash); +#else efi_status = verify_buffer(buffer, size, &context, sha256hash, sha1hash); +#endif done: in_protocol = 0; return efi_status; } +#ifdef ENABLE_SHIM_SM +static EFI_STATUS shim_hash (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash, UINT8 *sm3hash) +#else static EFI_STATUS shim_hash (char *data, int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash) +#endif { EFI_STATUS efi_status; @@ -1019,8 +1080,13 @@ static EFI_STATUS shim_hash (char *data, int datasize, return EFI_INVALID_PARAMETER; in_protocol = 1; +#ifdef ENABLE_SHIM_SM + efi_status = generate_hash(data, datasize, context, + sha256hash, sha1hash, sm3hash); +#else efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash); +#endif in_protocol = 0; return efi_status; diff --git a/shim.h b/shim.h index b5272b9..b9aa982 100644 --- a/shim.h +++ b/shim.h @@ -208,6 +208,18 @@ EFI_STATUS IN UINT32 size ); +#ifdef ENABLE_SHIM_SM +typedef +EFI_STATUS +(*EFI_SHIM_LOCK_HASH) ( + IN char *data, + IN int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, + UINT8 *sha1hash, + UINT8 *sm3hash + ); +#else typedef EFI_STATUS (*EFI_SHIM_LOCK_HASH) ( @@ -217,6 +229,7 @@ EFI_STATUS UINT8 *sha256hash, UINT8 *sha1hash ); +#endif typedef EFI_STATUS @@ -271,10 +284,17 @@ extern UINT32 load_options_size; BOOLEAN secure_mode (void); +#ifdef ENABLE_SHIM_SM +EFI_STATUS +verify_buffer (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash, UINT8 *sm3hash); +#else EFI_STATUS verify_buffer (char *data, int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash); +#endif #ifndef SHIM_UNIT_TEST #define perror_(file, line, func, fmt, ...) ({ \ -- 2.33.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2