코드 서명에는 키 관리 문제가 있습니다. 일반적인 설정은 릴리스를 서명해야 하는 모든 빌드 에이전트에 .pfx 파일을 복사하거나 USB 토큰을 꽂는 방식입니다. 결국 개인 키는 신뢰할 수 없는 빌드 스크립트를 실행하는 머신에 놓이게 되고, 무엇이 누구에 의해 서명되었는지 기록이 남지 않으며, 인증서를 교체하려면 모든 에이전트를 손봐야 합니다. sgcSign 2026.6은 새로운 서명 서버로 이 문제에 답합니다. TLS REST API를 통해 서명을 노출하는 자체 호스팅 데몬으로, 파이프라인과 개발자가 원격으로 서명하는 동안 서명 키는 정확히 한 곳에만 머무릅니다.
키는 서버를 절대 떠나지 않습니다. 더 나아가 서버는 하드웨어 토큰이나 클라우드 KMS를 프론트엔드로 둘 수 있으므로, 키가 파일로 존재하지조차 않게 할 수 있습니다. 모든 요청은 API 키로 인증되고, 속도 제한이 적용되며, 변조 방지 감사 로그에 기록됩니다. 이 글에서는 서버가 무엇을 서명할 수 있는지, 어떻게 구성하는지, 5분 빠른 시작, 그리고 서버를 호출하는 두 가지 방법, 즉 일반 curl과 번들로 제공되는 sgcsign 명령줄 클라이언트를 다룹니다.
무엇을 서명하는가
하나의 서버, 하나의 API, 여덟 가지 서명 형식. 동일한 엔드포인트 형태(POST /api/v1/sign/<format>)가 실행 파일과 문서를 모두 처리합니다.
- Authenticode — Windows PE 파일:
.exe,.dll,.sys,.msi,.cab,.ocx. 레거시 Windows를 위한 선택적 이중 SHA-1 + SHA-256 서명. - PAdES — 사유, 위치, 연락처, 서명자 이름을 포함한 PDF 문서.
- XAdES — XML, 봉투형(enveloped), 봉투화(enveloping) 또는 분리형(detached), eIDAS, FacturaE, FatturaPA, KSeF, Peppol, VeriFactu, TicketBAI 같은 국가별 전자 송장 프로파일 지원.
- CAdES — 임의의 페이로드에 대한 분리형 PKCS#7(
.p7s). - ClickOnce — 애플리케이션 및 배포 매니페스트.
- NuGet —
.nupkg패키지 서명. - VSIX — Visual Studio 확장 패키지.
- PowerShell —
.ps1/.psm1/.psd1스크립트(Authenticode SIP).
모든 형식은 선택적 RFC 3161 타임스탬프 URL을 받아들이므로, 서명 인증서가 만료된 후에도 서명이 유효하게 유지됩니다. 함께 제공되는 POST /api/v1/verify 엔드포인트는 기존 서명을 확인하고 서명자 주체, 유효성, 타임스탬프를 반환합니다.
플러그형 키 제공자
제공자(provider)는 서명 키에 대한 이름이 붙은 핸들입니다. 호출자는 각 요청에서 제공자의 이름을 지정하며, 키 자체는 보지 못합니다. 필요한 만큼 여러 개를 등록할 수 있으며, 동일한 서버에서 내부 도구용 로컬 PFX와 공개 릴리스용 클라우드 기반 EV 인증서를 함께 사용할 수 있습니다. 아홉 가지 제공자 유형이 지원됩니다.
- Windows Certificate Store(
WinCertStore) — 지문(thumbprint) 또는 주체(subject)로 인증서를 선택하며, 토큰에 등록된 EV 인증서에 이상적입니다. - PFX 및 PEM — 디스크상의 인증서 및 키 파일.
- PKCS#11 / HSM(
PKCS11) — YubiKey 같은 하드웨어 토큰 또는 네트워크 HSM. - AWS KMS, Azure Trusted Signing, Google Cloud KMS, HashiCorp Vault, Certum SimplySign — 개인 키가 클라우드 서비스에 존재하며 서버에 절대 도달하지 않습니다.
비밀 값은 구성 파일 밖에 둡니다. 이름이 _env로 끝나는 모든 매개변수는 환경 변수에서 읽어들이므로, PFX 비밀번호, 토큰 PIN 또는 클라우드 비밀 값은 디스크에 커밋되지 않고 서비스 환경에서 제공됩니다.
서버 구성하기
전체 서버는 하나의 JSON 파일 sgcSignServer.conf.json으로 구동됩니다(문서화된 샘플은 sgcSignServer.conf.sample.json으로 제공됩니다). 최상위 구조는 몇 개의 블록으로 이루어집니다.
{
"server": {
"listen": "0.0.0.0",
"port": 8443,
"tls": {
"enabled": true,
"cert_file": "certs/server.crt",
"key_file": "certs/server.key"
},
"max_upload_mb": 512,
"firewall": {
"enabled": true,
"whitelist": ["10.0.0.0/8"],
"brute_force": { "enabled": true, "max_attempts": 5, "ban_duration_sec": 900 },
"rate_limit": { "enabled": true, "max_connections_per_ip": 30, "time_window_sec": 60 }
}
},
"storage": { "sqlite_path": "data/sgcsignserver.db" },
"admin": {
"initial_user": "admin",
"initial_password_env": "SGCSIGN_ADMIN_INIT_PW",
"session_timeout_minutes": 60
},
"audit": { "retention_days": 365 },
"providers": [
{
"name": "pfx-build",
"type": "PFX",
"params": { "file": "certs/build.pfx", "password_env": "SGCSIGN_PFX_PW" }
},
{
"name": "ev-release",
"type": "AzureTS",
"params": {
"tenant_id": "00000000-0000-0000-0000-000000000000",
"client_id": "00000000-0000-0000-0000-000000000000",
"client_secret_env": "SGCSIGN_AZURE_TS_SECRET",
"account": "your-account",
"certificate_profile": "your-profile",
"endpoint": "https://eus.codesigning.azure.net/"
}
}
]
}
각 블록은 기능에 직접 대응됩니다. server는 바인드 주소, 포트(기본값 8443), TLS 인증서, 그리고 내장 방화벽(IP 허용 및 차단 목록, 무차별 대입 잠금, 연결 속도 제한, 경로 탐색 및 페이로드 보호)을 설정합니다. storage는 사용자, API 키, 감사 로그, 웹훅 큐를 보관하는 SQLite 데이터베이스를 가리킵니다. admin은 첫 사용자와 세션 지속 시간을 정의합니다. audit는 보존 기간을 설정합니다. providers는 위의 목록입니다.
부트스트랩 관리자 비밀번호는 파일에 저장되지 않습니다. 사용자 테이블이 비어 있는 최초 시작 시, 서버는 initial_password_env(기본값 SGCSIGN_ADMIN_INIT_PW)로 지정된 환경 변수를 읽어 관리자 계정을 생성합니다. 이후 모든 시작에서는 이 변수가 무시되므로, 오직 첫 로그인을 시드하는 데에만 사용됩니다.
5분 빠른 시작
서버를 설치하고(설정 마법사가 Windows 서비스를 등록하거나, sgcSignServer.exe --install을 실행), 다음을 진행합니다.
1. 테스트 코드 서명 인증서를 생성하고 PFX로 내보냅니다. 실제 배포에서는 CA가 발급한 인증서를 가져오거나, 이 단계를 건너뛰고 제공자를 토큰이나 클라우드 KMS로 가리키게 합니다.
$cert = New-SelfSignedCertificate -Type CodeSigningCert `
-Subject "CN=sgcSign Quickstart" -KeyAlgorithm RSA -KeyLength 3072 `
-CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(1)
$pw = ConvertTo-SecureString "QuickStartPFX!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -Password $pw `
-FilePath "C:\Program Files\sgcSign Server\certs\quickstart.pfx"
2. 비밀 값을 머신 환경 변수로 설정하여 서비스 계정이 읽을 수 있도록 합니다.
setx /M SGCSIGN_ADMIN_INIT_PW "ChangeMeNow!"
setx /M SGCSIGN_PFX_PW "QuickStartPFX!"
3. 하나의 PFX 제공자로 최소 구성을 작성합니다(localhost 테스트용으로만 TLS 끔).
{
"server": { "listen": "127.0.0.1", "port": 8443, "tls": { "enabled": false } },
"storage": { "sqlite_path": "data/sgcsignserver.db" },
"admin": { "initial_user": "admin", "initial_password_env": "SGCSIGN_ADMIN_INIT_PW" },
"audit": { "retention_days": 365 },
"providers": [
{ "name": "pfx-quickstart", "type": "PFX",
"params": { "file": "certs/quickstart.pfx", "password_env": "SGCSIGN_PFX_PW" } }
]
}
4. 서비스를 시작하고 수신 대기 중인지 확인합니다.
sc start sgcSignServer
sc query sgcSignServer
시작 시 멈춘다면, sgcSignServer.exe --console --config <path>로 포그라운드에서 실행하여 오류를 실시간으로 확인하십시오(누락된 SGCSIGN_ADMIN_INIT_PW나 읽을 수 없는 PFX가 흔한 원인입니다). --selftest-providers 플래그는 구성을 로드하고 모든 제공자를 초기화한 뒤 종료하므로, 운영에 들어가기 전에 키를 검증할 수 있습니다.
5. 로그인하고 API 키를 생성합니다. http://localhost:8443/admin을 열고 부트스트랩 비밀번호로 admin으로 로그인한 다음, API 키를 생성합니다. 평문 키는 생성 시 한 번만 표시되므로 즉시 복사하십시오. 키에는 접두사 sgcsk_가 붙습니다.
6. curl로 테스트 실행 파일을 서명합니다.
curl -X POST http://localhost:8443/api/v1/sign/authenticode ^
-H "X-API-Key: sgcsk_..." ^
-F file=@app.exe ^
-F provider=pfx-quickstart ^
-F hash=sha256 ^
--output app-signed.exe
이것이 전체 흐름입니다. 제공자를 구성하고, 키를 발급하고, 서명합니다. 여기서부터 자체 서명 PFX를 실제 인증서로 교체하고, TLS를 켜고, 방화벽을 CI 서브넷으로 잠그면 됩니다.
REST를 통한 서명
모든 서명 요청은 파일과 몇 개의 폼 필드를 포함한 POST입니다. 인증은 하나의 헤더로 이루어지며, X-API-Key: sgcsk_... 또는 Authorization: Bearer sgcsk_...입니다. 응답은 application/octet-stream으로 된 서명된 산출물이며, 서명자 주체와 서명 소요 시간은 X-Sgcsign-Signer-Subject 및 X-Sgcsign-Duration-Ms 헤더로 반환됩니다.
가시적인 서명 사유와 위치를 포함하여 PDF를 서명하기:
curl -X POST https://sign.acme.local:8443/api/v1/sign/pades \
-H "X-API-Key: sgcsk_..." \
-F file=@invoice.pdf \
-F provider=qualified-eu \
-F tsa_url=http://timestamp.digicert.com \
-F reason=Approved \
-F location="Madrid, Spain" \
-F signer_name="Acme Billing" \
-o invoice-signed.pdf
eIDAS XAdES 프로파일로 XML 송장을 서명하기:
curl -X POST https://sign.acme.local:8443/api/v1/sign/xades \
-H "X-API-Key: sgcsk_..." \
-F file=@invoice.xml \
-F provider=qualified-eu \
-F profile=eidas \
-F xades_type=enveloped \
-o invoice-signed.xml
서명 검증은 파일이 아니라 JSON을 반환합니다.
curl -X POST https://sign.acme.local:8443/api/v1/verify \
-H "X-API-Key: sgcsk_..." \
-F file=@app-signed.exe \
-F format=authenticode
{ "valid": true, "status": "valid",
"subject": "CN=Acme Inc., O=Acme, C=US",
"has_timestamp": true, "timestamp_time": "2026-06-15T13:01:17.000Z" }
sgcsign 명령줄 클라이언트
서버에는 네 가지 동사(sign, verify, keys, health)를 갖춘 작은 교차 형식 CLI인 sgcsign이 함께 제공됩니다. 서버 URL, API 키, 제공자는 플래그로 지정하거나 환경 변수 SGCSIGN_SERVER, SGCSIGN_APIKEY, SGCSIGN_PROVIDER에서 가져올 수 있으며, 이렇게 하면 CI에서 비밀 값이 명령줄에 노출되지 않습니다.
set SGCSIGN_SERVER=https://sign.acme.local:8443
set SGCSIGN_APIKEY=sgcsk_...
sgcsign sign -f authenticode -p pfx-build -o app-signed.exe app.exe ^
--tsa http://timestamp.digicert.com --desc "Acme Installer"
sgcsign verify -f authenticode app-signed.exe
Authenticode의 경우 대역폭을 절약하는 요령이 있습니다. 사전 해시 모드(--prehash, 기본적으로 켜짐)에서는 CLI가 PE 해시를 로컬에서 계산하고 해시만 업로드하므로, 수 메가바이트 크기의 바이너리 대신 수백 바이트만 전송됩니다. 서버는 해시를 서명하고 분리형 PKCS#7 블롭을 반환하며, 클라이언트는 이를 파일에 임베드합니다. 대역폭이 제한된 CI 에이전트에서 대형 설치 프로그램을 서명할 때, 이는 수 메가바이트 업로드를 아주 작은 업로드로 바꿔 줍니다.
보안 및 운영
서버는 실제 서명 키 앞에서 무인으로 실행되도록 설계되었으므로, 운영 표면은 암호화만큼이나 중요합니다.
/admin의 관리 콘솔: 대시보드, API 키, 제공자, 사용자, 프로젝트, 감사 추적을 모두 브라우저에서 처리합니다.- 키별 속도 제한과 일일 할당량을 갖춘 API 키. 한도에 도달한 키는
Retry-After헤더와 함께429를 받습니다. - 멀티 테넌트 프로젝트: 한 팀으로 범위가 지정된 키는 다른 팀의 제공자를 사용할 수 없습니다.
- 승인 워크플로(선택): 서명 요청이 운영자가 승인할 때까지 보류되는 2단계 흐름으로, 고가치 릴리스 인증서에 사용됩니다.
- 변조 방지 감사 로그: 모든 서명, 검증, 로그인, 구성 변경이 해시로 체인되므로, 기록은 사후에 변경될 수 없습니다.
/api/v1/metrics의 Prometheus 메트릭과/api/v1/health의 활성/준비 상태 프로브로, 모니터링 스택에 바로 사용할 수 있습니다.- 모든 서명 작업마다 JSON 이벤트를 POST하는 아웃바운드 웹훅으로, 서명을 Slack, SIEM 또는 릴리스 대시보드에 연결합니다.
/api/v1/openapi.json의 OpenAPI 3.1 명세와/api/v1/docs의 대화형 Swagger UI.- 선택적 ACME / Let's Encrypt 챌린지 서빙을 갖춘 TLS 1.2+, 그리고 IP 필터링과 남용 방지를 위한 내장 방화벽.
자체 호스팅하는 이유
서버의 핵심은 위험하게 키를 지닌 빌드 에이전트의 무리를 단 하나의 강화된 엔드포인트로 축소하는 것입니다. 서명 키는 오직 서버에만, 또는 서버가 프론트엔드로 두는 HSM이나 클라우드 KMS에만 존재하며, 빌드 스크립트를 실행하는 머신에는 절대 놓이지 않습니다. 접근은 할당량을 갖추고 프로젝트로 범위가 지정된 키별 API 토큰으로 통제되며, 모든 서명은 여러분이 통제하는 감사 로그에 기록됩니다. 인증서를 교체하거나, 손상된 에이전트의 키를 폐기하거나, 누가 무엇을 서명했는지 증명하는 일이 모두 여러 곳이 아닌 한 곳을 살펴보는 일이 됩니다.
제공 안내
sgcSign Server는 sgcSign 2026.6.0의 일부입니다. Windows 서비스 또는 콘솔 애플리케이션으로 실행되며, 위에 표시된 단일 JSON 파일로 구성되고, curl, 번들 sgcsign CLI, 또는 OpenAPI 설명을 통한 모든 HTTP 클라이언트로 구동됩니다.
질문, 의견 또는 설정에 도움이 필요하신가요? 문의하기. 코드를 작성한 사람들로부터 직접 답변을 받으실 수 있습니다.
