본문 바로가기

3.구현/VC++

실행파일 인증서 서명과 검증

가끔씩 파일 무결정을 지원해야한다. 일반적인 무결성 지원은 파일에 대한 해시 값을 만들어서 저장해두고 나중에 이 값과 비교하는 방식이다. 이 경우 보안상 이슈가 있다. 즉 원래 변경된 파일에 대한 해시값을 같이 만들어 놓았다면, 변경된 파일을 해시값과 비교해도 같기에 의미가 없어진다. 이럴때 사용하는 방법 중에 하나가 전자서명이다.

자세한 알고리즘과 작동 방식에 대해서는 다른 곳에서 찾으면 많이 나오기 때문에 여기서는 다루지 않는다. 혹시 내가 시간이 아주 많이(?) 남는다면 나중에 정리할 수도 있을지도 모른다.

추가로 여기서 다루는 내용은 오로지 Windows에서만 가능하다. 다른 플랫폼이 필요하시다면 죄송하지만, 다른 곳을 찾기 바란다.

작성: http://ospace.tistory.com/,2011.12.23 (ospace114@empal.com)

서명하는 방법

서명하기 위해서는 인증서가 필요하다. 혹은 공인 CA(Certificate Autherity)에서 요청해서 발급 받을 수 있다. 물론 돈이 필요하다. 돈만 있으면 다된다. 우리나라 좋은나라! ㅡ.ㅡ;;;;

나는 돈이 많지 않다. 이런 테스트를 위해 쓸 돈이 없다. 아마 대부분 나와 비슷할 것 같으니~ 사설 인증서를 만들자.

일단 사설 인증서를 만들기위한 툴이 필요하다. 아마도 Visual Studio에 들어 있을거라 구입한다. orz

돈이 없으시다면 SDK를 받으시면 된다. 간혹 VS에 없기도 한다. 다 싫으시면 인터넷 검색하면 나올지도.. ^^;

적당하게(?) 경로를 넣어주고 아래 명령을 입력하면 암호 입력하라는 창이 뜬다. 생성할 키의 암호를 입력한다.

makecert -sv "mycert.pvk" -n "CN=Ospace" mycert.cer

PVK은 개인키 파일이고, CER은 인증서이다. 다음으로 SPC파일을 만들자.

cert2spc mycert.cer mycert.spc

키 생성은 끝났다. 아주 쉽다.
마자막으로 한가지 더 사설 인증서를 사용하려면 윈도우즈에 알려줘야 한다. 사설 인증서를 허용하달라고~~
SetReg 명령으로 간단하게 해결한다. 옵션을 살펴보면 여러가지 설정이 있으니 참조하기 바란다.

setreg -q 1 TRUE

이제야 본론으로 들어와서 서명해보자.

signcode -spc mycert.spc -v mycert.pvk 파일명.확장자

만약 돈을 내고 CA에서 인증서를 받았다면, -t로 인증기관을 지정하면된다. 예를 들어 아래와 같이...

signcode -spc mycert.spc -v mycert.pvk -t http://timestamp.yessign.or.kr/codeSign -n "Ospace" -i "http://ospace.tistory.com" 파일명.확장자

중요한 것은 -t 옵션이고 -i은 추가 정보를 얻기위한 URL, -n은 서명하는 파일에 대한 간략한 표현정도 이다.
근데 signcode가 없다구요. 아마 signtool로 되어 있을 것입니다. 이러면 힘들어지는데...
Signcode은 그냥 실행하면 마법사 창이 뜨면서 쉽게 사용했는데, 물론 도스창으로 명령 입력하는 것을 선호하지만...
Signtool은 사용법을 잘 모르겠네요. 내장된 인증서를 사용한다고 하는데 파일을 지정할수 없나?

사인 검증하기

이젠 서명을 했으니 서명한 파일을 검증하는 방법을 알아보자. 당연히 검증이 없다면 서명한 이유가 없다.
명령어로 간단하게 확인해보자.

chktrust 파일명.확장자

그럼 서명정보를 뛰우면서 실행여부를 확인할 것이다. 만약 Windows7인 경우 설치파일인 경우 운영체제에서 자동으로 검증창을 뛰어준다.
검증하는 것은 간단하다. 이거 뭐~ 누어서 떡먹기?

실행 파일에서 인증서 검증하기

조금 어려운 문제로 실행 중에 인증서를 검증할 수 있을까? 현재 인증서 정보를 실행 파일 내에 포함되어 있다.
간혹 닭과 달걀의 문제로 실행 파일 자체적으로 인증서를 검증할 수 없다고 하는 경우가 있다. 왜냐하면 서명하면서 추가정보를 파일에 추가함으로서 이미 파일이 변경되었다고 한다. 물론 맞는 말이다. 그러나 해결 방법은 있다. 이건 건너뛰고...
실행파일이 인증서를 검증하려면 API를 사용할 것이고 이런 API가 있는지 확인하면 된다. 다행이 있다. 땡스 빌~! 아니 발머?
WinVerifyTrust 함수를 사용하면 간단하게 해결된다.

//-------------------------------------------------------------------
// Copyright (C) Microsoft.  All rights reserved.
// Example of verifying the embedded signature of a PE file by using
// the WinVerifyTrust function.

#define _UNICODE 1
#define UNICODE 1

#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <Softpub.h>
#include <wincrypt.h>
#include <wintrust.h>

// Link with the Wintrust.lib file.
#pragma comment (lib, "wintrust")

BOOL VerifyEmbeddedSignature(LPCWSTR pwszSourceFile)
{
    LONG lStatus;
    DWORD dwLastError;

    // Initialize the WINTRUST_FILE_INFO structure.

    WINTRUST_FILE_INFO FileData;
    memset(&FileData, 0, sizeof(FileData));
    FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
    FileData.pcwszFilePath = pwszSourceFile;
    FileData.hFile = NULL;
    FileData.pgKnownSubject = NULL;

    /*
    WVTPolicyGUID specifies the policy to apply on the file
    WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks:

    1) The certificate used to sign the file chains up to a root
    certificate located in the trusted root certificate store. This
    implies that the identity of the publisher has been verified by
    a certification authority.

    2) In cases where user interface is displayed (which this example
    does not do), WinVerifyTrust will check for whether the 
    end entity certificate is stored in the trusted publisher store, 
    implying that the user trusts content from this publisher.

    3) The end entity certificate has sufficient permission to sign
    code, as indicated by the presence of a code signing EKU or no
    EKU.
    */

    GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    WINTRUST_DATA WinTrustData;

    // Initialize the WinVerifyTrust input data structure.

    // Default all fields to 0.
    memset(&WinTrustData, 0, sizeof(WinTrustData));

    WinTrustData.cbStruct = sizeof(WinTrustData);

    // Use default code signing EKU.
    WinTrustData.pPolicyCallbackData = NULL;

    // No data to pass to SIP.
    WinTrustData.pSIPClientData = NULL;

    // Disable WVT UI.
    WinTrustData.dwUIChoice = WTD_UI_NONE;

    // No revocation checking.
    WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;

    // Verify an embedded signature on a file.
    WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;

    // Default verification.
    WinTrustData.dwStateAction = 0;

    // Not applicable for default verification of embedded signature.
    WinTrustData.hWVTStateData = NULL;

    // Not used.
    WinTrustData.pwszURLReference = NULL;

    // This is not applicable if there is no UI because it changes
    // the UI to accommodate running applications instead of
    // installing applications.
    WinTrustData.dwUIContext = 0;

    // Set pFile.
    WinTrustData.pFile = &FileData;

    // WinVerifyTrust verifies signatures as specified by the GUID
    // and Wintrust_Data.
    lStatus = WinVerifyTrust(
        NULL,
        &WVTPolicyGUID,
        &WinTrustData);

    switch (lStatus)
    {
        case ERROR_SUCCESS:
            /*
            Signed file:
                - Hash that represents the subject is trusted.

                - Trusted publisher without any verification errors.

                - UI was disabled in dwUIChoice. No publisher or
                    time stamp chain errors.

                - UI was enabled in dwUIChoice and the user clicked
                    "Yes" when asked to install and run the signed
                    subject.
            */
            wprintf_s(L"The file \"%s\" is signed and the signature "
                L"was verified.\n",
                pwszSourceFile);
            break;

        case TRUST_E_NOSIGNATURE:
            // The file was not signed or had a signature
            // that was not valid.

            // Get the reason for no signature.
            dwLastError = GetLastError();
            if (TRUST_E_NOSIGNATURE == dwLastError ||
                    TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError ||
                    TRUST_E_PROVIDER_UNKNOWN == dwLastError)
            {
                // The file was not signed.
                wprintf_s(L"The file \"%s\" is not signed.\n",
                    pwszSourceFile);
            }
            else
            {
                // The signature was not valid or there was an error
                // opening the file.
                wprintf_s(L"An unknown error occurred trying to "
                    L"verify the signature of the \"%s\" file.\n",
                    pwszSourceFile);
            }

            break;

        case TRUST_E_EXPLICIT_DISTRUST:
            // The hash that represents the subject or the publisher
            // is not allowed by the admin or user.
            wprintf_s(L"The signature is present, but specifically "
                L"disallowed.\n");
            break;

        case TRUST_E_SUBJECT_NOT_TRUSTED:
            // The user clicked "No" when asked to install and run.
            wprintf_s(L"The signature is present, but not "
                L"trusted.\n");
            break;

        case CRYPT_E_SECURITY_SETTINGS:
            /*
            The hash that represents the subject or the publisher
            was not explicitly trusted by the admin and the
            admin policy has disabled user trust. No signature,
            publisher or time stamp errors.
            */
            wprintf_s(L"CRYPT_E_SECURITY_SETTINGS - The hash "
                L"representing the subject or the publisher wasn't "
                L"explicitly trusted by the admin and admin policy "
                L"has disabled user trust. No signature, publisher "
                L"or timestamp errors.\n");
            break;

        default:
            // The UI was disabled in dwUIChoice or the admin policy
            // has disabled user trust. lStatus contains the
            // publisher or time stamp chain error.
            wprintf_s(L"Error is: 0x%x.\n",
                lStatus);
            break;
    }

    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    if(argc > 1)
    {
        VerifyEmbeddedSignature(argv[1]);
    }

    return 0;
}

참고

[1] http://msdn.microsoft.com/en-us/library/aa382384.aspx

결론

이를 이용하면 파일에 대한 무결성을 쉽게 구현할 수 있다. 복잡한 해시함수를 사용하여 이를 검증하기 위해 다시 해시를 만들어서 비교하는 그런 작업이 사라진다. 파일도 별도 존재하지 않기 때문에 관리하기도 편하다. 아무튼 쉽게 사용할 수 있다는 점이 마음에 든다.

실행 프로그램이 스스로 자신을 검증하는 방법도 간단하다. 실행프로그램 실행 초기에 위의 WinVerifyTrust 함수를 이용하여 현재 실행 프로세스 파일명을 획득하여 이를 인자로 검증하면 된다. 뭐 간단하다.

마지막으로 그럼 설치 프로그램은 어떻게 하나요? 이것도 간단. 앞의 작업의 연장선이다.

모두 즐프~

2011.ospace.

반응형