升级签名和摘要计算
This commit is contained in:
parent
ac024c1176
commit
05546fa213
@ -192,6 +192,7 @@ Open:
|
||||
- `sql/`
|
||||
- `firmware/`
|
||||
- `scripts/`
|
||||
- 外层升级包实际包含的是 `payload.zip`,`payload.zip` 内部再包含上述 `payload/` 目录
|
||||
- `signature.sig` 需要能被配置的 PEM 公钥使用 `SM3withSM2` 软验签通过
|
||||
- 服务端需要配置 `tms.upgrade.signature-public-key-pem-path`(公钥 PEM 文件路径)
|
||||
- `manifest.json` 当前要求包含:
|
||||
@ -202,9 +203,11 @@ Open:
|
||||
- `minCompatibleVersion`
|
||||
- `description`
|
||||
- `entrypoints.execute`
|
||||
- `payloadSm3`
|
||||
- 可选 `entrypoints.precheck`
|
||||
- 可选 `entrypoints.verify`
|
||||
- 可选 `entrypoints.rollback`
|
||||
- `payloadSm3` 是整个 `payload.zip` 文件的 `SM3` 摘要;后端验签 `manifest.json` 后,会校验 `payload.zip` 摘要并解压出 `payload/` 供脚本执行
|
||||
|
||||
当前执行规则:
|
||||
- 同一时刻只允许一个升级任务处于 `RUNNING`
|
||||
|
||||
@ -19,6 +19,8 @@ public class UpgradeManifest {
|
||||
private String description;
|
||||
@Schema(description = "升级脚本入口")
|
||||
private Entrypoints entrypoints = new Entrypoints();
|
||||
@Schema(description = "payload.zip 文件 SM3 摘要")
|
||||
private String payloadSm3;
|
||||
|
||||
public String getPackageId() {
|
||||
return packageId;
|
||||
@ -76,6 +78,14 @@ public class UpgradeManifest {
|
||||
this.entrypoints = entrypoints;
|
||||
}
|
||||
|
||||
public String getPayloadSm3() {
|
||||
return payloadSm3;
|
||||
}
|
||||
|
||||
public void setPayloadSm3(String payloadSm3) {
|
||||
this.payloadSm3 = payloadSm3;
|
||||
}
|
||||
|
||||
public static class Entrypoints {
|
||||
private String execute;
|
||||
private String precheck;
|
||||
|
||||
@ -56,6 +56,7 @@ public class UpgradeTaskRunner {
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + task.getPackageFileId()));
|
||||
Path stagedDir = upgradePackageStagingService.stage(task.getTaskId(), Path.of(record.getStoragePath()));
|
||||
UpgradeManifest manifest = objectMapper.readValue(Files.readString(stagedDir.resolve("manifest.json")), UpgradeManifest.class);
|
||||
upgradePackageStagingService.extractPayloadZip(stagedDir);
|
||||
Path root = stagedDir.normalize().toAbsolutePath();
|
||||
|
||||
// 包内脚本按固定阶段执行;可选阶段未声明时跳过,execute 阶段必须存在。
|
||||
@ -137,6 +138,7 @@ public class UpgradeTaskRunner {
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package file not found: " + task.getPackageFileId()));
|
||||
Path stagedDir = upgradePackageStagingService.stage(task.getTaskId() + "-rollback", Path.of(record.getStoragePath()));
|
||||
UpgradeManifest manifest = objectMapper.readValue(Files.readString(stagedDir.resolve("manifest.json")), UpgradeManifest.class);
|
||||
upgradePackageStagingService.extractPayloadZip(stagedDir);
|
||||
String rollbackPath = manifest.getEntrypoints() == null ? "" : trim(manifest.getEntrypoints().getRollback());
|
||||
if (rollbackPath.isEmpty()) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade rollback script not found");
|
||||
|
||||
@ -16,7 +16,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@ -56,6 +58,9 @@ public class UpgradePackageService {
|
||||
validateProductType(manifest.getProductType());
|
||||
// 签名对象固定为 manifest.json 原始字节,具体算法由 UpgradePackageSignatureVerifier 实现。
|
||||
signatureVerifier.verify(manifest, stagedPackage.manifestPath(), stagedPackage.signaturePath());
|
||||
// 校验 payload.zip 摘要,确保已签名的 manifest 能约束实际载荷压缩包内容。
|
||||
validatePayloadZipSm3(stagedPackage.stagedDir(), manifest);
|
||||
upgradePackageStagingService.extractPayloadZip(stagedPackage.stagedDir());
|
||||
validateExecuteScript(stagedPackage.stagedDir(), manifest);
|
||||
|
||||
// 当前版本来自设备软件版本表;空版本允许继续预检,由前端展示“未记录版本”。
|
||||
@ -144,6 +149,24 @@ public class UpgradePackageService {
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePayloadZipSm3(Path stagedDir, UpgradeManifest manifest) {
|
||||
String expected = normalize(manifest.getPayloadSm3()).toLowerCase(Locale.ROOT);
|
||||
if (expected.isEmpty()) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 missing");
|
||||
}
|
||||
if (!expected.matches("[0-9a-f]{64}")) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 invalid");
|
||||
}
|
||||
Path root = stagedDir.normalize().toAbsolutePath();
|
||||
Path payloadZip = root.resolve("payload.zip").normalize().toAbsolutePath();
|
||||
if (!payloadZip.startsWith(root) || !Files.exists(payloadZip) || !Files.isRegularFile(payloadZip)) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade payload.zip not found");
|
||||
}
|
||||
if (!expected.equals(sm3Hex(payloadZip))) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package payload sm3 mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateExecuteScript(Path stagedDir, UpgradeManifest manifest) {
|
||||
Path executePath = stagedDir.resolve(normalize(manifest.getEntrypoints().getExecute())).normalize().toAbsolutePath();
|
||||
// execute 路径必须在 staging 目录内,避免 manifest 声明外部系统脚本。
|
||||
@ -166,6 +189,23 @@ public class UpgradePackageService {
|
||||
return normalize(value).toUpperCase();
|
||||
}
|
||||
|
||||
private String sm3Hex(Path path) {
|
||||
try {
|
||||
byte[] content = Files.readAllBytes(path);
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(content, 0, content.length);
|
||||
byte[] hash = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(hash, 0);
|
||||
StringBuilder builder = new StringBuilder(hash.length * 2);
|
||||
for (byte value : hash) {
|
||||
builder.append(String.format("%02x", value));
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (IOException ex) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade package checksum invalid");
|
||||
}
|
||||
}
|
||||
|
||||
private record StagedUpgradePackage(
|
||||
Path stagedDir,
|
||||
Path manifestPath,
|
||||
|
||||
@ -8,6 +8,8 @@ import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Comparator;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -26,18 +28,36 @@ public class UpgradePackageStagingService {
|
||||
Path taskDir = root.resolve(fileId).normalize().toAbsolutePath();
|
||||
try {
|
||||
Files.createDirectories(taskDir);
|
||||
unzip(packagePath, taskDir);
|
||||
unzip(packagePath, taskDir, "");
|
||||
return taskDir;
|
||||
} catch (IOException ex) {
|
||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to stage upgrade package");
|
||||
}
|
||||
}
|
||||
|
||||
private void unzip(Path zipFile, Path targetDir) throws IOException {
|
||||
public void extractPayloadZip(Path stagedDir) {
|
||||
Path root = stagedDir.normalize().toAbsolutePath();
|
||||
Path payloadZip = root.resolve("payload.zip").normalize().toAbsolutePath();
|
||||
if (!payloadZip.startsWith(root) || !Files.exists(payloadZip) || !Files.isRegularFile(payloadZip)) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "upgrade payload.zip not found");
|
||||
}
|
||||
try {
|
||||
deleteDirectoryIfExists(root.resolve("payload"));
|
||||
unzip(payloadZip, root, "payload/");
|
||||
} catch (IOException ex) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "failed to extract upgrade payload.zip");
|
||||
}
|
||||
}
|
||||
|
||||
private void unzip(Path zipFile, Path targetDir, String requiredEntryPrefix) throws IOException {
|
||||
try (InputStream inputStream = Files.newInputStream(zipFile);
|
||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||
String entryName = entry.getName();
|
||||
if (!requiredEntryPrefix.isEmpty() && !entryName.startsWith(requiredEntryPrefix)) {
|
||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "invalid upgrade payload entry");
|
||||
}
|
||||
Path target = targetDir.resolve(entry.getName()).normalize().toAbsolutePath();
|
||||
// 防止恶意 zip 条目通过 ../ 写出 staging 目录。
|
||||
if (!target.startsWith(targetDir)) {
|
||||
@ -53,6 +73,26 @@ public class UpgradePackageStagingService {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteDirectoryIfExists(Path directory) throws IOException {
|
||||
if (!Files.exists(directory)) {
|
||||
return;
|
||||
}
|
||||
try (Stream<Path> stream = Files.walk(directory)) {
|
||||
stream.sorted(Comparator.reverseOrder()).forEach(path -> {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
});
|
||||
} catch (IllegalStateException ex) {
|
||||
if (ex.getCause() instanceof IOException ioException) {
|
||||
throw ioException;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private String trim(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import com.cisd.tms.modules.upgrade.entity.UpgradeTaskEntity;
|
||||
import com.cisd.tms.modules.upgrade.repository.UpgradeTaskRepository;
|
||||
import com.cisd.tms.modules.upgrade.support.UpgradePackageStagingService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -19,6 +20,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -189,6 +191,17 @@ class UpgradeTaskRunnerTest {
|
||||
entrypoints.setRollback("payload/scripts/rollback.sh");
|
||||
}
|
||||
manifest.setEntrypoints(entrypoints);
|
||||
Map<String, byte[]> payloadFiles = new HashMap<>();
|
||||
if (includeOptionalScripts) {
|
||||
payloadFiles.put("payload/scripts/precheck.sh", "#!/bin/sh\necho precheck\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
payloadFiles.put("payload/scripts/verify.sh", "#!/bin/sh\necho verify\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
payloadFiles.put("payload/scripts/execute.sh", executeScript.getBytes(StandardCharsets.UTF_8));
|
||||
if (includeRollback) {
|
||||
payloadFiles.put("payload/scripts/rollback.sh", "#!/bin/sh\necho rollback\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
byte[] payloadZip = payloadZip(payloadFiles);
|
||||
manifest.setPayloadSm3(sm3Hex(payloadZip));
|
||||
|
||||
try (ZipOutputStream outputStream = new ZipOutputStream(Files.newOutputStream(packagePath))) {
|
||||
outputStream.putNextEntry(new ZipEntry("manifest.json"));
|
||||
@ -199,30 +212,9 @@ class UpgradeTaskRunnerTest {
|
||||
outputStream.write("signature".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/"));
|
||||
outputStream.putNextEntry(new ZipEntry("payload.zip"));
|
||||
outputStream.write(payloadZip);
|
||||
outputStream.closeEntry();
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/"));
|
||||
outputStream.closeEntry();
|
||||
|
||||
if (includeOptionalScripts) {
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/precheck.sh"));
|
||||
outputStream.write("#!/bin/sh\necho precheck\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/verify.sh"));
|
||||
outputStream.write("#!/bin/sh\necho verify\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/execute.sh"));
|
||||
outputStream.write(executeScript.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
|
||||
if (includeRollback) {
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/rollback.sh"));
|
||||
outputStream.write("#!/bin/sh\necho rollback\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
|
||||
FileRecordEntity entity = new FileRecordEntity();
|
||||
@ -233,6 +225,30 @@ class UpgradeTaskRunnerTest {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private byte[] payloadZip(Map<String, byte[]> payloadFiles) throws Exception {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream outputStream = new ZipOutputStream(bytes)) {
|
||||
for (Map.Entry<String, byte[]> entry : payloadFiles.entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||
outputStream.putNextEntry(new ZipEntry(entry.getKey()));
|
||||
outputStream.write(entry.getValue());
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
private String sm3Hex(byte[] content) {
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(content, 0, content.length);
|
||||
byte[] hash = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(hash, 0);
|
||||
StringBuilder builder = new StringBuilder(hash.length * 2);
|
||||
for (byte value : hash) {
|
||||
builder.append(String.format("%02x", value));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private UpgradeTaskEntity task(String taskId, String fileId, String taskType, String targetVersion) {
|
||||
UpgradeTaskEntity entity = new UpgradeTaskEntity();
|
||||
entity.setTaskId(taskId);
|
||||
|
||||
@ -15,6 +15,7 @@ import com.cisd.tms.modules.upgrade.support.UpgradePackageSignatureVerifier;
|
||||
import com.cisd.tms.modules.upgrade.support.UpgradePackageStagingService;
|
||||
import com.cisd.tms.modules.upgrade.support.UpgradeVersionComparator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -30,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemWriter;
|
||||
@ -167,6 +169,48 @@ class UpgradePackageServiceTest {
|
||||
Assertions.assertEquals("upgrade execute script not found", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectWhenPayloadSm3IsMissing() throws Exception {
|
||||
Path tempDir = Files.createTempDirectory("upgrade-preview-test");
|
||||
FileRecordEntity fileRecord = buildPackage(tempDir, "RECEIVER", "DIRECT", "V1.4.0", "V1.3.0", true, false);
|
||||
InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository();
|
||||
fileRepository.save(fileRecord);
|
||||
|
||||
UpgradePackageService service = new UpgradePackageService(
|
||||
fileRepository,
|
||||
new InMemoryDeviceSoftwareVersionRepository(),
|
||||
stagingService(tempDir.resolve("staging")),
|
||||
new PassThroughSignatureVerifier(),
|
||||
new UpgradeVersionComparator(),
|
||||
preset("DIRECT"),
|
||||
objectMapper
|
||||
);
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
||||
Assertions.assertEquals("upgrade package payload sm3 missing", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectWhenPayloadZipSm3DoesNotMatch() throws Exception {
|
||||
Path tempDir = Files.createTempDirectory("upgrade-preview-test");
|
||||
FileRecordEntity fileRecord = buildPackage(tempDir, "RECEIVER", "DIRECT", "V1.4.0", "V1.3.0", true, true, true);
|
||||
InMemoryFileRecordRepository fileRepository = new InMemoryFileRecordRepository();
|
||||
fileRepository.save(fileRecord);
|
||||
|
||||
UpgradePackageService service = new UpgradePackageService(
|
||||
fileRepository,
|
||||
new InMemoryDeviceSoftwareVersionRepository(),
|
||||
stagingService(tempDir.resolve("staging")),
|
||||
new PassThroughSignatureVerifier(),
|
||||
new UpgradeVersionComparator(),
|
||||
preset("DIRECT"),
|
||||
objectMapper
|
||||
);
|
||||
|
||||
BizException exception = Assertions.assertThrows(BizException.class, () -> service.preview(fileRecord.getFileId()));
|
||||
Assertions.assertEquals("upgrade package payload sm3 mismatch", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreviewUpgradePackageWithSoftSignatureVerification() throws Exception {
|
||||
Path tempDir = Files.createTempDirectory("upgrade-preview-test");
|
||||
@ -218,8 +262,37 @@ class UpgradePackageServiceTest {
|
||||
|
||||
private FileRecordEntity buildPackage(Path tempDir, String taskType, String productType, String version, String minCompatibleVersion, boolean includeExecute)
|
||||
throws IOException {
|
||||
return buildPackage(tempDir, taskType, productType, version, minCompatibleVersion, includeExecute, true);
|
||||
}
|
||||
|
||||
private FileRecordEntity buildPackage(
|
||||
Path tempDir,
|
||||
String taskType,
|
||||
String productType,
|
||||
String version,
|
||||
String minCompatibleVersion,
|
||||
boolean includeExecute,
|
||||
boolean includePayloadSm3
|
||||
) throws IOException {
|
||||
return buildPackage(tempDir, taskType, productType, version, minCompatibleVersion, includeExecute, includePayloadSm3, false);
|
||||
}
|
||||
|
||||
private FileRecordEntity buildPackage(
|
||||
Path tempDir,
|
||||
String taskType,
|
||||
String productType,
|
||||
String version,
|
||||
String minCompatibleVersion,
|
||||
boolean includeExecute,
|
||||
boolean includePayloadSm3,
|
||||
boolean corruptPayloadSm3
|
||||
) throws IOException {
|
||||
Path packagePath = tempDir.resolve("upgrade-package.zip");
|
||||
byte[] signatureBytes = "signature".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] executeScript = "#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] precheckScript = "#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] verifyScript = "#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] rollbackScript = "#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
UpgradeManifest manifest = new UpgradeManifest();
|
||||
manifest.setPackageId("PKG-001");
|
||||
@ -234,6 +307,14 @@ class UpgradePackageServiceTest {
|
||||
entrypoints.setVerify("payload/scripts/verify.sh");
|
||||
entrypoints.setRollback("payload/scripts/rollback.sh");
|
||||
manifest.setEntrypoints(entrypoints);
|
||||
byte[] payloadZip = payloadZip(Map.of(
|
||||
"payload/scripts/precheck.sh", precheckScript,
|
||||
"payload/scripts/verify.sh", verifyScript,
|
||||
"payload/scripts/rollback.sh", rollbackScript
|
||||
), includeExecute ? Map.of("payload/scripts/execute.sh", executeScript) : Map.of());
|
||||
if (includePayloadSm3) {
|
||||
manifest.setPayloadSm3(corruptPayloadSm3 ? "0".repeat(64) : sm3Hex(payloadZip));
|
||||
}
|
||||
|
||||
byte[] manifestBytes = objectMapper.writeValueAsBytes(manifest);
|
||||
|
||||
@ -246,27 +327,8 @@ class UpgradePackageServiceTest {
|
||||
outputStream.write(signatureBytes);
|
||||
outputStream.closeEntry();
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/"));
|
||||
outputStream.closeEntry();
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/"));
|
||||
outputStream.closeEntry();
|
||||
|
||||
if (includeExecute) {
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/execute.sh"));
|
||||
outputStream.write("#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/precheck.sh"));
|
||||
outputStream.write("#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/verify.sh"));
|
||||
outputStream.write("#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.closeEntry();
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/rollback.sh"));
|
||||
outputStream.write("#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.putNextEntry(new ZipEntry("payload.zip"));
|
||||
outputStream.write(payloadZip);
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
|
||||
@ -298,6 +360,9 @@ class UpgradePackageServiceTest {
|
||||
UpgradeManifest.Entrypoints entrypoints = new UpgradeManifest.Entrypoints();
|
||||
entrypoints.setExecute("payload/scripts/execute.sh");
|
||||
manifest.setEntrypoints(entrypoints);
|
||||
byte[] executeScript = "#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] payloadZip = payloadZip(Map.of("payload/scripts/execute.sh", executeScript), Map.of());
|
||||
manifest.setPayloadSm3(sm3Hex(payloadZip));
|
||||
|
||||
byte[] manifestBytes = objectMapper.writeValueAsBytes(manifest);
|
||||
byte[] signatureBytes = sign(manifestBytes, keyPair);
|
||||
@ -311,12 +376,8 @@ class UpgradePackageServiceTest {
|
||||
outputStream.write(signatureBytes);
|
||||
outputStream.closeEntry();
|
||||
|
||||
outputStream.putNextEntry(new ZipEntry("payload/"));
|
||||
outputStream.closeEntry();
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/"));
|
||||
outputStream.closeEntry();
|
||||
outputStream.putNextEntry(new ZipEntry("payload/scripts/execute.sh"));
|
||||
outputStream.write("#!/bin/sh\nexit 0\n".getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.putNextEntry(new ZipEntry("payload.zip"));
|
||||
outputStream.write(payloadZip);
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
|
||||
@ -349,6 +410,33 @@ class UpgradePackageServiceTest {
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
private byte[] payloadZip(Map<String, byte[]> required, Map<String, byte[]> optional) throws IOException {
|
||||
Map<String, byte[]> all = new HashMap<>();
|
||||
all.putAll(required);
|
||||
all.putAll(optional);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (ZipOutputStream outputStream = new ZipOutputStream(bytes)) {
|
||||
for (Map.Entry<String, byte[]> entry : all.entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||
outputStream.putNextEntry(new ZipEntry(entry.getKey()));
|
||||
outputStream.write(entry.getValue());
|
||||
outputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
private String sm3Hex(byte[] content) {
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(content, 0, content.length);
|
||||
byte[] hash = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(hash, 0);
|
||||
StringBuilder builder = new StringBuilder(hash.length * 2);
|
||||
for (byte value : hash) {
|
||||
builder.append(String.format("%02x", value));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String toPem(String type, byte[] content) throws Exception {
|
||||
StringWriter writer = new StringWriter();
|
||||
try (PemWriter pemWriter = new PemWriter(writer)) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user