1 parent
fdbc9a9d1b
commit
024f244f52
10 files changed
+910
-3
No files matched your search
@@ -51,6 +51,14 @@ enum Action {
|
||||
output: PathBuf,
|
||||
},
|
||||
Schema,
|
||||
Certificate {
|
||||
#[arg(long)]
|
||||
project: PathBuf,
|
||||
#[arg(long, conflicts_with = "thumbprint")]
|
||||
create: bool,
|
||||
#[arg(long)]
|
||||
thumbprint: Option<String>,
|
||||
},
|
||||
}
|
||||
#[derive(Subcommand)]
|
||||
enum PlanAction {
|
||||
@@ -105,6 +113,22 @@ fn run(action: Action, cancel: &AtomicBool) -> Result<i32> {
|
||||
&std::path::absolute(output)?,
|
||||
)?)?,
|
||||
Action::Schema => print(&schemars::schema_for!(package_domain::Project))?,
|
||||
Action::Certificate {
|
||||
project,
|
||||
create,
|
||||
thumbprint,
|
||||
} => print(&infra::configure_developer_certificate(
|
||||
&project,
|
||||
if create {
|
||||
"create"
|
||||
} else if thumbprint.is_some() {
|
||||
"select"
|
||||
} else {
|
||||
"list"
|
||||
},
|
||||
thumbprint.as_deref(),
|
||||
cancel,
|
||||
)?)?,
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export type DeveloperCertificate = { thumbprint: string; subject: string; notAfter: string; sha256: string; trustedPeople: boolean };
|
||||
export type SigningReport = { certificate: DeveloperCertificate; trustVerification: string; cmsSignatureValid: boolean; winVerifyTrust: string };
|
||||
const esc = (value: unknown) => String(value ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!);
|
||||
export function signingStatus(signing: string, report?: SigningReport | null): string {
|
||||
if (signing !== 'selfSignedDevelopment') return '미서명';
|
||||
return report?.trustVerification === 'passed' ? '개발자 서명 완료 · 현재 PC 신뢰 확인' : '개발자 서명 완료 · 테스트 PC 신뢰 등록 필요';
|
||||
}
|
||||
export function renderSigningEditor(mode: string, thumbprint: string | null | undefined, certificates: DeveloperCertificate[], locked: boolean): string {
|
||||
const disabled = locked ? 'disabled' : '';
|
||||
return `<label class="field">서명 모드<select data-field="signing.mode" ${disabled}><option value="unsigned" ${mode === 'unsigned' ? 'selected' : ''}>미서명</option><option value="development" ${mode === 'development' ? 'selected' : ''}>개발자 서명 · 테스트용 자체 서명</option></select></label>
|
||||
${mode === 'development' ? `<div class="state-card"><span class="badge amber">테스트 전용</span><h2>개발자 서명</h2><p>Publisher와 일치하는 자체 서명 인증서로 MSIX를 서명합니다. 인증서는 현재 Windows 사용자 저장소에 보관하며 같은 인증서를 다음 빌드에도 재사용합니다.</p><p>최초 사용 시 인증서를 생성하거나 기존 인증서를 선택하세요. 생성한 인증서는 3개월간 유효하며 개인 키는 내보내지 않습니다.</p></div>
|
||||
<div class="actions"><button data-action="certificate-list" ${disabled}>인증서 목록 새로고침</button><button data-action="certificate-create" ${disabled}>테스트 인증서 생성</button></div>
|
||||
<label class="field">사용 가능한 인증서<select id="developer-certificate" ${disabled}><option value="">인증서를 선택하세요</option>${certificates.map(c => `<option value="${esc(c.thumbprint)}" ${c.thumbprint === thumbprint ? 'selected' : ''}>${esc(c.subject)} · ${esc(c.notAfter)} 만료 · ${esc(c.thumbprint)}</option>`).join('')}</select></label><button data-action="certificate-select" ${disabled}>선택한 인증서 사용</button>
|
||||
<div class="check-row"><span>저장된 인증서 지문</span><code>${esc(thumbprint || '선택되지 않음')}</code></div>
|
||||
<div class="callout">테스트 PC에서 설치하려면 공개 인증서를 신뢰하도록 등록해야 합니다. 빌드 결과의 development-signing.cer를 확인한 후 Install-TestCertificate.ps1을 관리자 권한으로 실행하세요. 인증서 신뢰 등록은 자동으로 수행하지 않습니다.</div><p class="muted">현재 기능은 MSIX 패키지를 서명합니다. 내부 EXE/DLL 개별 서명, 공인 서명 및 타임스탬프는 포함하지 않습니다. Windows 개발자 모드와 별개이며 일반 사용자 배포용이 아닙니다.</p>` : '<div class="state-card"><h2>미서명 패키지</h2><p>패키지 생성과 내용 검증만 수행합니다. 설치 테스트에는 서명이 필요합니다.</p></div>'}`;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { renderSigningEditor, signingStatus } from '../src/signing-editor.ts';
|
||||
|
||||
test('signed packages keep trust status separate from signing', () => {
|
||||
assert.equal(signingStatus('unsignedDevelopment'), '미서명');
|
||||
assert.match(signingStatus('selfSignedDevelopment', { trustVerification: 'notTrusted' }), /신뢰 등록 필요/);
|
||||
assert.match(signingStatus('selfSignedDevelopment', { trustVerification: 'passed' }), /현재 PC 신뢰 확인/);
|
||||
assert.match(signingStatus('selfSignedDevelopment'), /신뢰 등록 필요/);
|
||||
});
|
||||
|
||||
test('certificate selection escapes certificate data and locks actions during JSON editing', () => {
|
||||
const certificate = { thumbprint: 'ABC', subject: '<img src=x>', notAfter: '2027-01-01', trustedPeople: false };
|
||||
const html = renderSigningEditor('development', 'ABC', [certificate], true);
|
||||
assert.ok(html.includes('value="ABC" selected'));
|
||||
assert.ok(html.includes('<img src=x>'));
|
||||
assert.ok(!html.includes('<img src=x>'));
|
||||
assert.match(html, /data-action="certificate-create" disabled/);
|
||||
assert.ok(!renderSigningEditor('unsigned', null, [], false).includes('data-action="certificate-create"'));
|
||||
});
|
||||
Reference in new issue
Block a user