EV (Extended Validation) 코드 서명 자동화


82

최근에 DigiCert EV 코드 서명 인증서를 구입했습니다. signtool.exe를 사용하여 .exe 파일에 서명 할 수 있습니다. 그러나 파일에 서명 할 때마다 SafeNet eToken 암호를 입력하라는 메시지가 표시됩니다.

사용자 개입없이 암호를 어딘가에 저장 / 캐싱하여이 프로세스를 어떻게 자동화 할 수 있습니까?


" SafeNet eToken 5110 또는 유사한 암호화 하드웨어 토큰의 암호 프롬프트는 얼마나 안전합니까? "라는 질문은 다소 관련이 있습니다. 답변을 받으면 암호 입력 자동화 여부를 평가하는 사람들이 관심을 가져야합니다. 내가 현재 그 토큰이나 유사한 토큰을 소유하고있는 누군가가 이것을 읽는다면, 당신이 그것을 "해킹"하고 그 질문에 답할 수 있다면 대단히 감사 할 것입니다. :)
gbr

안타깝게도 저에게 효과가 있었고 가장 많은 투표를 얻은 답변이 답변 목록 끝에 표시되므로 시간을 낭비하지 말고 Simon Mourier 답변 stackoverflow.com/a/26126701/27194
NDepend 팀의 Patrick

이러한 솔루션을 시도하기 전에 미리 알려드립니다. 하드웨어 토큰에는 "남은 토큰 암호 재시도"카운터가 있습니다 (SafeNet 인증 클라이언트에서 확인할 수 있음). 실험 할 때 명백한 이유로 0에 도달하지 않도록하십시오. 그렇지 않으면 하드웨어 토큰이 영구적으로 잠기고 새 토큰을 주문해야합니다! 어려운 방법으로 배웠습니다 ...
Sundae

불행히도 Simon의 대답은 더 이상 작동하지 않습니다 ( 대답에 대한 내 의견 참조 ). 그리고 Austin의 대답은 효과가있을뿐만 아니라 어쨌든 imo가 더 좋습니다.
Martin Prikryl

답변:


64

로그인 대화 상자 AFAIK를 우회 할 수있는 방법은 없지만 SafeNet 인증 클라이언트를 구성하여 로그인 세션 당 한 번만 요청하도록 할 수 있습니다.

여기에 SAC 문서 ( \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chm' Client Settings', ' Enabling Client Logon' 장 에서 설치 한 후 발견)를 인용합니다 .

단일 로그온이 활성화되면 사용자는 각 컴퓨터 세션 동안 토큰 암호를 한 번만 요청하여 여러 응용 프로그램에 액세스 할 수 있습니다. 이렇게하면 사용자가 각 응용 프로그램에 개별적으로 로그온 할 필요가 없습니다.

기본적으로 비활성화되어있는이 기능을 활성화하려면 SAC 고급 설정으로 이동하여 "단일 로그온 활성화"상자를 선택하십시오.

여기에 이미지 설명 입력

컴퓨터를 다시 시작하면 토큰 암호를 한 번만 입력하라는 메시지가 표시됩니다. 이 총, 그래서 우리의 경우, 우리는 각 빌드마다 서명하는 200 개 이상의 바이너리가 필수 .

그렇지 않으면 다음은 로그온 대화 상자 (아마 관리자로 실행해야 함)에 자동으로 응답 할 수있는 작은 C # 콘솔 샘플 코드 (m1st0 one에 해당)입니다 (콘솔 프로젝트 ( UIAutomationClient.dllUIAutomationTypes.dll) 에서 참조해야 함 ).

using System;
using System.Windows.Automation;

namespace AutoSafeNetLogon {
   class Program {
      static void Main(string[] args) {
         SatisfyEverySafeNetTokenPasswordRequest("YOUR_TOKEN_PASSWORD");
      }


      static void SatisfyEverySafeNetTokenPasswordRequest(string password) {
         int count = 0;
         Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, (sender, e) =>
         {
            var element = sender as AutomationElement;
            if (element.Current.Name == "Token Logon") {
               WindowPattern pattern = (WindowPattern)element.GetCurrentPattern(WindowPattern.Pattern);
               pattern.WaitForInputIdle(10000);
               var edit = element.FindFirst(TreeScope.Descendants, new AndCondition(
                   new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit),
                   new PropertyCondition(AutomationElement.NameProperty, "Token Password:")));

               var ok = element.FindFirst(TreeScope.Descendants, new AndCondition(
                   new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button),
                   new PropertyCondition(AutomationElement.NameProperty, "OK")));

               if (edit != null && ok != null) {
                  count++;
                  ValuePattern vp = (ValuePattern)edit.GetCurrentPattern(ValuePattern.Pattern);
                  vp.SetValue(password);
                  Console.WriteLine("SafeNet window (count: " + count + " window(s)) detected. Setting password...");

                  InvokePattern ip = (InvokePattern)ok.GetCurrentPattern(InvokePattern.Pattern);
                  ip.Invoke();
               } else {
                  Console.WriteLine("SafeNet window detected but not with edit and button...");
               }
            }
         });

         do {
            // press Q to quit...
            ConsoleKeyInfo k = Console.ReadKey(true);
            if (k.Key == ConsoleKey.Q)
               break;
         }
         while (true);
         Automation.RemoveAllEventHandlers();
      }
   }
}

10
이것은 DigiCert의 공식 답변이 아닐 수도 있지만 답변이 형편없고 이것은 굉장합니다! 도와 주셔서 감사합니다!
lordjeb

2
정답을 원하시면 +1하세요. 사용자 입력 등을 자동화하는 스크립트를 개발하는 사람들이 실제로 암호를 갖는 목적을 무너 뜨리고, 그들이 알아야 할 것은이 옵션이 어디에 있는지뿐이라는 사실에 놀랐습니다. 발행인은 바이너리가 서명 될 때마다 암호를 입력 할 수 없다는 것을 개발자가 이해하기 때문에이 옵션이 사라지지 않을 것입니다.
dyasta 16.01.30

3
TeamCity에서 작동 함을 확인할 수 있습니다 (TeamCity Windows 서비스에 "서비스가 데스크톱과 상호 작용하도록 허용"확인란이 선택되어있는 경우). 또한 다른 스레드에서 암호 입력 프로세스를 실행하고 빌드 시스템에서 "대화 형 서비스 감지"서비스를 비활성화해야했습니다. 서명을 수행하고 위와 같이 암호 입력을 처리하는 signtool 주위에 C # 래퍼를 만들었습니다. 이 작업을 수행하기 위해 얼마나 많은 장애물을 통과해야하는지 믿을 수 없지만 같은 보트에있는 다른 사람은 위에서 설명한 C # 방법에 초점을 맞 춥니 다.
Alan Spark

1
참고로 시만텍 EV 인증서도 SafeNet을 사용합니다. 이 프로세스를 중심으로 버벅 거림 솔루션을 구축해야했지만 답변을 읽고 콘솔 앱을 구현 한 후 빌드 프로세스에 큰 도움이되었습니다. 감사합니다. 잘못 설계된 코드 서명 프로세스에 대한 훌륭한 솔루션입니다.
Zoltan

1
이 유용한 솔루션을 한동안 성공적으로 사용하고 있습니다. 그러나 이제 Windows 8 이상용 SafeNet 클라이언트 9.0.34 x64가있는 Windows 10 Pro 2004가있는 새 컴퓨터에서 설정하면 더 이상 작동하지 않습니다. 새 암호 프롬프트는 어디에 있습니까? 이전과 같은 사용자 지정 SafeNet 프롬프트 대신 Windows 내장형으로 보입니다. 그리고 새 프롬프트의 암호 상자는 자동화 할 수 없습니다 ( AutomationElement트리에 표시 되지 않음 ). 어떻게 든 해결할 수 있을지 모르겠습니다. 그러나이 질문을 다시 방문하고 @Austin의 답변을 찾으면 어쨌든 더 나은 해결책이라고 생각합니다.
Martin Prikryl 2010 년

34

이 스레드에 이미있는 답변을 확장하면 Microsoft의 표준 signtool 프로그램을 사용하여 토큰 암호를 제공 할 수 있습니다.

0. 고급보기에서 SafeNet 클라이언트 열기

설치 경로는 다를 수 있지만 나에게 SafeNet 클라이언트는 다음 위치에 설치됩니다. C:\Program Files\SafeNet\Authentication\SAC\x64\SACTools.exe

오른쪽 상단의 톱니 바퀴 아이콘을 클릭하여 "고급보기"를 엽니 다. SafeNet 고급보기

1. SafeNet 클라이언트에서 파일로 공용 인증서 내보내기 인증서를 파일로 내보내기

2. 개인 키 컨테이너 이름 찾기
개인 키 컨테이너 이름

3. 독자 이름 찾기 독자 이름

4. 모두 함께 포맷

eToken CSP에는 컨테이너 이름에서 토큰 암호를 구문 분석하는 숨겨진 (또는 적어도 널리 알려지지 않은) 기능이 있습니다.

형식은 다음 중 하나입니다.

[]=name
[reader]=name
[{{password}}]=name
[reader{{password}}]=name

어디:

  • reader SafeNet 클라이언트 UI의 "리더 이름"입니다.
  • password 당신의 토큰 비밀번호입니다
  • name SafeNet 클라이언트 UI의 "컨테이너 이름"입니다.

두 개 이상의 리더가 연결되어있는 경우 리더 이름을 지정해야합니다. 리더가 하나뿐이므로 확인할 수 없습니다.

5. signtool에 정보 전달

  • /f certfile.cer
  • /csp "eToken Base Cryptographic Provider"
  • /k "<value from step 4>"
  • 필요한 기타 signtool 플래그

다음과 같은 signtool 명령의 예

signtool sign /f mycert.cer /csp "eToken Base Cryptographic Provider" /k "[{{TokenPasswordHere}}]=KeyContainerNameHere" myfile.exe

이 답변에서 가져온 일부 이미지 : https://stackoverflow.com/a/47894907/5420193


2
슬프게도 내가 파고와 자신의 "SignTool에서"를 구현 이틀 만이 위대한 발견 작동합니다 : D 들으
루카스 Koten

경고 :이 솔루션은 잘못된 암호를 두 번 입력해도 하드웨어 토큰에서 잠길 수 있습니다! 잘못된 암호로 명령을 한 번만 실행 한 후 암호 재시도 횟수가 15에서 3으로 줄었습니다. "etokensign.exe"솔루션은 정상적으로 작동하는 것 같습니다. 한 번의 잘못된 암호 시도 후 남은 암호 카운터가 15에서 14로 감소했습니다.
순대

1
놀랍게도 Sectigo에서 제공하는 SafeNet USB 동글을 사용하면 완벽하게 작동했습니다.
JacobJ

1
내가 아는 한,이 구문은 어디에도 공개적으로 문서화되어 있지 않습니다. IDA Pro에서 드라이버 바이너리를 리버스 엔지니어링하여이 기능을 찾았습니다.
Austin Morton

1
이것은 GUI 프롬프트에 가짜 암호 입력을하는 다른 답변보다 낫습니다. 무엇보다도 이것은 비대화 형 Windows 세션에서도 작동합니다. 그리고 다른 답변 은 최신 도구에서 작동하지 않는 것 같습니다 .
Martin Prikryl 2010 년

19

이 답변을 확장하면 다음을 사용하여 자동화 할 수 있습니다. CryptAcquireContextCryptSetProvParam 을 사용하여 토큰 PIN을 프로그래밍 방식으로 입력하고 CryptUIWizDigitalSign 을 사용하여 프로그래밍 방식으로 서명을 수행하여 . 인증서 파일 (SafeNet 인증 클라이언트에서 인증서를 마우스 오른쪽 버튼으로 클릭하고 "내보내기 ..."를 선택하여 내보냄), 개인 키 컨테이너 이름 (SafeNet 인증 클라이언트에 있음)을 입력으로 사용하는 콘솔 앱 (아래 코드)을 만들었습니다. 토큰 PIN, 타임 스탬프 URL 및 서명 할 파일의 경로. 이 콘솔 앱은 USB 토큰이 연결된 TeamCity 빌드 에이전트에서 호출 할 때 작동했습니다.

사용 예 :
etokensign.exe c:\CodeSigning.cert CONTAINER PIN http://timestamp.digicert.com C:\program.exe

암호:

#include <windows.h>
#include <cryptuiapi.h>
#include <iostream>
#include <string>

const std::wstring ETOKEN_BASE_CRYPT_PROV_NAME = L"eToken Base Cryptographic Provider";

std::string utf16_to_utf8(const std::wstring& str)
{
    if (str.empty())
    {
        return "";
    }

    auto utf8len = ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), NULL, 0, NULL, NULL);
    if (utf8len == 0)
    {
        return "";
    }

    std::string utf8Str;
    utf8Str.resize(utf8len);
    ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), &utf8Str[0], utf8Str.size(), NULL, NULL);

    return utf8Str;
}

struct CryptProvHandle
{
    HCRYPTPROV Handle = NULL;
    CryptProvHandle(HCRYPTPROV handle = NULL) : Handle(handle) {}
    ~CryptProvHandle() { if (Handle) ::CryptReleaseContext(Handle, 0); }
};

HCRYPTPROV token_logon(const std::wstring& containerName, const std::string& tokenPin)
{
    CryptProvHandle cryptProv;
    if (!::CryptAcquireContext(&cryptProv.Handle, containerName.c_str(), ETOKEN_BASE_CRYPT_PROV_NAME.c_str(), PROV_RSA_FULL, CRYPT_SILENT))
    {
        std::wcerr << L"CryptAcquireContext failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
        return NULL;
    }

    if (!::CryptSetProvParam(cryptProv.Handle, PP_SIGNATURE_PIN, reinterpret_cast<const BYTE*>(tokenPin.c_str()), 0))
    {
        std::wcerr << L"CryptSetProvParam failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
        return NULL;
    }

    auto result = cryptProv.Handle;
    cryptProv.Handle = NULL;
    return result;
}

int wmain(int argc, wchar_t** argv)
{
    if (argc < 6)
    {
        std::wcerr << L"usage: etokensign.exe <certificate file path> <private key container name> <token PIN> <timestamp URL> <path to file to sign>\n";
        return 1;
    }

    const std::wstring certFile = argv[1];
    const std::wstring containerName = argv[2];
    const std::wstring tokenPin = argv[3];
    const std::wstring timestampUrl = argv[4];
    const std::wstring fileToSign = argv[5];

    CryptProvHandle cryptProv = token_logon(containerName, utf16_to_utf8(tokenPin));
    if (!cryptProv.Handle)
    {
        return 1;
    }

    CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO extInfo = {};
    extInfo.dwSize = sizeof(extInfo);
    extInfo.pszHashAlg = szOID_NIST_sha256; // Use SHA256 instead of default SHA1

    CRYPT_KEY_PROV_INFO keyProvInfo = {};
    keyProvInfo.pwszContainerName = const_cast<wchar_t*>(containerName.c_str());
    keyProvInfo.pwszProvName = const_cast<wchar_t*>(ETOKEN_BASE_CRYPT_PROV_NAME.c_str());
    keyProvInfo.dwProvType = PROV_RSA_FULL;

    CRYPTUI_WIZ_DIGITAL_SIGN_CERT_PVK_INFO pvkInfo = {};
    pvkInfo.dwSize = sizeof(pvkInfo);
    pvkInfo.pwszSigningCertFileName = const_cast<wchar_t*>(certFile.c_str());
    pvkInfo.dwPvkChoice = CRYPTUI_WIZ_DIGITAL_SIGN_PVK_PROV;
    pvkInfo.pPvkProvInfo = &keyProvInfo;

    CRYPTUI_WIZ_DIGITAL_SIGN_INFO signInfo = {};
    signInfo.dwSize = sizeof(signInfo);
    signInfo.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE;
    signInfo.pwszFileName = fileToSign.c_str();
    signInfo.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_PVK;
    signInfo.pSigningCertPvkInfo = &pvkInfo;
    signInfo.pwszTimestampURL = timestampUrl.c_str();
    signInfo.pSignExtInfo = &extInfo;

    if (!::CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, NULL, NULL, &signInfo, NULL))
    {
        std::wcerr << L"CryptUIWizDigitalSign failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
        return 1;
    }

    std::wcout << L"Successfully signed " << fileToSign << L"\n";
    return 0;
}

인증서를 파일로 내보내기 :
인증서를 파일로 내보내기

개인 키 컨테이너 이름 :
개인 키 컨테이너 이름


2
이것은 받아 들여진 대답이어야합니다, 그것은 매력처럼 작동합니다!
shawn

1
이것은 완벽 해요. 특히이 도구를 사용하여 더미 파일에 서명하면된다는 사실을 깨달은 후. "단일 로그온"이 활성화 된 경우 (SafeNet 드라이버) 모든 후속 단계는 표준 signtool에서 작동합니다. 이는 다른 도구를 사용하는 VSTO (Office 추가 기능)에 서명하는 데 매우 유용하며 빌드 스크립트 / 프로세스에 최소한의 변경 만 필요함을 의미합니다.
Stefan Egli 2018 년

이 답변은 avzhatkin에서 제공하는 것에 대한 좋은 추가입니다. 이 시점에서 코드는 signtools.exe를 대체하는 데 가깝습니다. 이 프로그램은 교차 서명을 지원해야합니다. 다행히도 이제 교차 서명 을 수행하는 또 다른 SO 게시물이 있습니다 .
sdc

이 대답은 결국 나를 가장 많이 도왔습니다. VS2017에서 빌드 할 때 외부 참조를 놓쳤지만 여기에 제안 된대로 pragma 주석을 추가 할 때 Bamboo (Atlassian의 CI / CD)에 서명 할 수있었습니다.
HTBR

11

빌드 프로세스를 자동화하는 데 도움이되는 베타 도구를 만들었습니다.

클라이언트-서버 윈도우 애플리케이션입니다. EV 토큰이 삽입 된 컴퓨터에서 서버를 시작할 수 있습니다. 서버 측 애플리케이션 시작시 토큰의 비밀번호를 입력하십시오. 그런 다음 원격으로 파일에 서명 할 수 있습니다. 클라이언트 측 응용 프로그램은 signtool.exe를 완전히 대체하므로 기존 빌드 스크립트를 사용할 수 있습니다.

여기에있는 소스 코드 : https://github.com/SirAlex/RemoteSignTool

편집 : 우리는 빌드 서버에서 지난 반기 24x7 코드 서명에이 도구를 성공적으로 사용했습니다. 모두 잘 작동합니다.


1
이 접근 방식은 얼마나 안전합니까? HTTP로 서명 서버에 연결할 수있는 사람은 누구나 EV 인증서로 원하는 바이너리에 서명 할 수 있다는 의미가 아닙니까?
Gene Pavlovsky

6

실제로 Windows에서는 완전히 프로그래밍 방식으로 토큰 암호를 지정할 수 있습니다. 이는 "\\. \ AKS ifdh 0"형식의 토큰 이름 또는 인증 클라이언트 응용 프로그램의 인증 속성에서 볼 수있는 일부 guid 인 토큰 컨테이너 이름을 사용하여 CRYPT_SILENT 플래그가 있는 컨텍스트 ( CryptAcquireContext )를 생성하여 수행 할 수 있습니다 . 그런 다음 PP_SIGNATURE_PIN 매개 변수와 함께 CryptSetProvParam 을 사용하여 토큰 암호를 지정해야합니다. 그 후에 프로세스는 해당 토큰의 인증서를 사용하여 파일에 서명 할 수 있습니다.
참고 : 컨텍스트를 생성하면 현재 프로세스에 대해 완전히 작동하는 것처럼 보이므로 다른 Crypto API 함수 나 다른 것으로 전달할 필요가 없습니다. 그러나 더 많은 노력이 필요한 상황을 발견하면 자유롭게 언급하십시오.
편집 : 코드 샘플 추가

HCRYPTPROV OpenToken(const std::wstring& TokenName, const std::string& TokenPin)
{
    const wchar_t DefProviderName[] = L"eToken Base Cryptographic Provider";

    HCRYPTPROV hProv = NULL;
    // Token naming can be found in "eToken Software Developer's Guide"
    // Basically you can either use "\\.\AKS ifdh 0" form
    // Or use token's default container name, which looks like "ab-c0473610-8e6f-4a6a-ae2c-af944d09e01c"
    if(!CryptAcquireContextW(&hProv, TokenName.c_str(), DefProviderName, PROV_RSA_FULL, CRYPT_SILENT))
    {
        DWORD Error = GetLastError();
        //TracePrint("CryptAcquireContext for token %ws failed, error 0x%08X\n", TokenName.c_str(), Error);
        return NULL;
    }
    if(!CryptSetProvParam(hProv, PP_SIGNATURE_PIN, (BYTE*)TokenPin.c_str(), 0))
    {
        DWORD Error = GetLastError();
        //TracePrint("Token %ws unlock failed, error 0x%08X\n", TokenName.c_str(), Error);
        CryptReleaseContext(hProv, 0);
        return NULL;
    }
    else
    {
        //TracePrint("Unlocked token %ws\n", TokenName.c_str());
        return hProv;
    }
}

1
흥미 롭군. 당신이 IMHO (코드를 제공, 설명을 향상 등)을 그 위에 정교해야 약속 보인다
사이먼 Mourier

전체 예제를 게시하십시오. 이것은 정말 유용하게 들립니다
dten

추가 세부 사항에 감사드립니다. 이것이 당신이 언급 한 가이드입니까? read.pudn.com/downloads128/ebook/549477/eToken_SDK_3_50[1].pdf
dten

나는 그것이 내가 가지고 있던 정확한 버전이 아니라고 생각하지만 다른 사용 시나리오에 대해 컨텍스트 생성 및 PIN 제공에 대한 유사한 정보를 포함하는 것 같습니다.
avzhatkin

이 함수를 OpenToken (L "\\\\. \\ AKS ifdh 0", <token password>)이라고 부르는 것 같습니다. 잘 작동했습니다!
Michael Haephrati

5

내가 사용 AutoHotkey를을 스크립트 다음을 사용하여 암호 입력을 자동화 할 수 있습니다. 우리는 개발자가이 스크립트를 실행하여 바이너리를 Windows 상자에 보내서 서명하고 반환 할 수 있도록 웹 기반 프런트 엔드를 만들려고 노력해 왔습니다.

  Loop
  {   
    Sleep 2000

    if (WinExist("Token Logon"))
    {   
      WinActivate ; use the window found above
      SendInput [your_password]
      SendInput {Enter}
    }   
    if (WinExist("DigiCert Certificate Utility for Windows©"))
    {   
      WinActivate ; use the window found above
      SendInput [your_password]
      SendInput {Enter}
    }   
  } 

내가 공유 한 내용이 완전히 안전하지는 않지만 각 개발자의 서명 키를 구입하거나 릴리스 된 소프트웨어의 서명을 승인 할 서명 관리자의 작업을 할당해야하는이 문제도 발생했습니다. 품질 보증을 통과하고 출시 승인을 받으면 공식적으로 서명 할 수있는 더 나은 안전한 프로세스라고 생각합니다. 그러나 소규모 회사의 요구에 따라 다른 자동화 된 방식으로 수행해야 할 수도 있습니다.

필자는 원래 Linux (EV 인증서 이전)에서 osslsigncode 를 사용 하여 Windows 실행 파일의 서명을 자동화했습니다 (개발자의 용이성과 협업을 위해 많은 작업을 수행하는 Linux 서버가 있었기 때문에). 나는 osslsigncode 개발자에게 연락하여 그가 DigiCert SafeNet 토큰을 Linux에서 볼 수 있기 때문에 다른 방식으로 자동화하는 데 도움을 줄 수 있는지 확인했습니다. 그의 답변은 희망을 주었지만 진행 상황이 확실하지 않아 도움을 드리기 위해 더 많은 시간을 할애 할 수 없었습니다.


다른 답변을 참조하십시오. 세션 당 한 번만 잠금을 해제하는 옵션이 있으며 대부분의 사용자에게 충분합니다.
dyasta

5

signtool.exe sign / fd sha256 / f "signing.cer"/ csp "eToken Base Cryptographic Provider"/ kc "[{{token password here}}] = 컨테이너 이름 여기" "ConsoleApp1.exe"

Signtool에 Microsoft Windows SDK 10 사용


1
훌륭한! 이 한 줄짜리 줄은 다른 모든 대답을 부끄럽게 만듭니다 (처음에는 위의 draketb의 대답에서 지침을 찾을 때까지 "컨테이너 이름"을 알아내는 방법에 대해 의아해했지만).
댄 Z

5

https://chocolatey.org/docs/installation 설치 (관리 명령 프롬프트에서 하나의 명령을 사용하여 수행 할 수 있음)

(다시 시작 명령 프롬프트)

각 설치에 대한 choco의 지속적인 프롬프트를 억제하십시오.

choco feature enable -n=allowGlobalConfirmation

다음 명령을 사용하여 Python을 설치합니다.

choco install python

(다시 시작 명령 프롬프트) 추가 python 모듈 설치 :

pip install pypiwin32

다음 텍스트를에 저장 disableAutoprompt.py:

import pywintypes
import win32con
import win32gui
import time



DIALOG_CAPTION = 'Token Logon'
DIALOG_CLASS = '#32770'
PASSWORD_EDIT_ID = 0x3ea
TOKEN_PASSWORD_FILE = 'password.txt'
SLEEP_TIME = 10


def get_token_password():
    password = getattr(get_token_password, '_password', None)
    if password is None:
        with open(TOKEN_PASSWORD_FILE, 'r') as f:
            password = get_token_password._password = f.read()

    return password

def enumHandler(hwnd, lParam):
    if win32gui.IsWindowVisible(hwnd):
        if win32gui.GetWindowText(hwnd) == DIALOG_CAPTION and win32gui.GetClassName(hwnd) == DIALOG_CLASS:
            print('Token logon dialog has been detected, trying to enter password...')
            try:
                ed_hwnd = win32gui.GetDlgItem(hwnd, PASSWORD_EDIT_ID)
                win32gui.SendMessage(ed_hwnd, win32con.WM_SETTEXT, None, get_token_password())
                win32gui.PostMessage(ed_hwnd, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0)
                print('Success.')
            except Exception as e:
                print('Fail: {}'.format(str(e)))
                return False

    return True


def main():
    while True:
        try:
            win32gui.EnumWindows(enumHandler, None)
            time.sleep(SLEEP_TIME)
        except pywintypes.error as e:
            if e.winerror != 0:
                raise e


if __name__ == '__main__':
    print('Token unlocker has been started...')
    print('DO NOT CLOSE THE WINDOW!')
    main()

암호를 passwd.txt에 저장하고 실행 후

python disableAutoprompt.py

에서 SafeNet Authentication Client- 구성> Client Settings> Advanced>Enable Single Log On 옵션이 암호 메시지의 양을 최소화하는 것을 가능하게 할 수 있지만 완전히를 비활성화하지 않습니다 (버전 10.4.26.0에서 테스트)

C # 애플리케이션 (예 : https://github.com/ganl/safenetpass )은 잠금 화면에서 작동하지 않지만이 Python 스크립트에서는 작동합니다.


이 스크립트는 굉장했고, Yubikey 동글을 사용하여 내 필요에 쉽게 적용 할 수있었습니다. 그러나 Windows 10은 그것을 깨뜨립니다. Windows 10이 XAML로 변경되어 win32gui.xxxx () 함수가 작동하지 않습니다. /한숨. 감사합니다 Microsoft. 이것이 우리가 좋은 것을 가질 수없는 이유입니다.
John Rocha

2

Digicert에서 답변을 받았습니다.

불행히도 EV 코드 서명 인증서의 보안 중 일부는 매번 비밀번호를 입력해야한다는 것입니다. 자동화 할 방법이 없습니다.


우리는 동일한 응답을 얻었지만 솔루션을 찾고 있지만 사용할 수있는시기가 없습니다. 그들은이 SO 게시물을 알고 있지만 그것이 얼마나 많은 문제인지 깨닫기를 바랍니다.
Alan Spark


2

필자의 경우 Digicert는 이미 EV 인증서가있는 경우 CI에 대한 표준 (OV) 인증서를 무료로 발급합니다.

이것이 해결책이 아니라는 것을 알고 있지만 서버 (클라우드 서버)에 토큰을 넣을 수 없다면 이것이 갈 길입니다.


1

내가하는 방법은 다음과 같습니다.

  1. 토큰 열기

    PCCERT_CONTEXT cert = OpenToken (SAFENET_TOKEN, EV_PASS);

  2. 필요한 경우 토큰, 루트 / 교차 인증서 및 메모리에로드 된 EV 인증서를 사용 하여 파일에 서명합니다 .

    HRESULT 시간 = SignAppxPackage (cert, FILETOSIGN);

SignerSignEx2 () 사용 :

파일은 LoadLibrary () 및 GetProcAddress ()를 사용하여 메모리에로드해야하는 SignerSignEx2 ()를 사용하여 서명됩니다.

// Type definition for invoking SignerSignEx2 via GetProcAddress
typedef HRESULT(WINAPI *SignerSignEx2Function)(
    DWORD,
    PSIGNER_SUBJECT_INFO,
    PSIGNER_CERT,
    PSIGNER_SIGNATURE_INFO,
    PSIGNER_PROVIDER_INFO,
    DWORD,
    PCSTR,
    PCWSTR,
    PCRYPT_ATTRIBUTES,
    PVOID,
    PSIGNER_CONTEXT *,
    PVOID,
    PVOID);

// Load the SignerSignEx2 function from MSSign32.dll
HMODULE msSignModule = LoadLibraryEx(
    L"MSSign32.dll",
    NULL,
    LOAD_LIBRARY_SEARCH_SYSTEM32);

if (msSignModule)
{
    SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>(
        GetProcAddress(msSignModule, "SignerSignEx2"));
    if (SignerSignEx2)
    {
        hr = SignerSignEx2(
            signerParams.dwFlags,
            signerParams.pSubjectInfo,
            signerParams.pSigningCert,
            signerParams.pSignatureInfo,
            signerParams.pProviderInfo,
            signerParams.dwTimestampFlags,
            signerParams.pszAlgorithmOid,
            signerParams.pwszTimestampURL,
            signerParams.pCryptAttrs,
            signerParams.pSipData,
            signerParams.pSignerContext,
            signerParams.pCryptoPolicy,
            signerParams.pReserved);
    }
    else
    {
        DWORD lastError = GetLastError();
        hr = HRESULT_FROM_WIN32(lastError);
    }

    FreeLibrary(msSignModule);
}
else
{
    DWORD lastError = GetLastError();
    hr = HRESULT_FROM_WIN32(lastError);
}

// Free any state used during app package signing
if (sipClientData.pAppxSipState)
{
    sipClientData.pAppxSipState->Release();
}

타임 스탬핑

또한 서명 된 파일에 타임 스탬프를 지정하고 연결하는 타임 스탬프 권한을 사용하여이를 수행해야합니다.

이는 현재 날짜와 시간에 대한 URL을 통해 타임 스탬프 서버를 안전하게 확인함으로써 수행됩니다. 각 서명 기관에는 자체 타임 스탬프 서버가 있습니다. 타임 스탬프는 코드 서명 프로세스의 추가 단계이지만 EV 코드 서명의 경우 서명 된 PE에 보안 계층을 추가하는 요구 사항입니다. 따라서 코드에 사용자가 인터넷에 연결되어 있는지 확인하십시오.

DWORD dwReturnedFlag;
if (InternetGetConnectedState(&dwReturnedFlag,0) == NULL) // use https://docs.microsoft.com/en-us/windows/desktop/api/netlistmgr/nf-netlistmgr-inetworklistmanager-getconnectivity
{
    wprintf(L"Certificate can't be dated with no Internet connection\n");
    return 1;
}

파일에서 인증서로드

std::tuple<DWORD, DWORD, std::string> GetCertificateFromFile
(const wchar_t*                         FileName
    , std::shared_ptr<const CERT_CONTEXT>*   ResultCert)
{
    std::vector<unsigned char> vecAsn1CertBuffer;
    auto tuple_result = ReadFileToVector(FileName, &vecAsn1CertBuffer);

    if (std::get<0>(tuple_result) != 0)
    {
        return tuple_result;
    }

    return GetCertificateFromMemory(vecAsn1CertBuffer, ResultCert);
}

메모리에 인증서로드

std::tuple<DWORD, DWORD, std::string> GetCertificateFromMemory
(const std::vector<unsigned char>&      CertData
    , std::shared_ptr<const CERT_CONTEXT>*   ResultCert)
{
    const CERT_CONTEXT* crtResultCert = ::CertCreateCertificateContext
    (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
        , &CertData[0]
        , static_cast<DWORD>(CertData.size()));
    if (crtResultCert == NULL)
    {
        return std::make_tuple(E_FAIL
            , ::GetLastError()
            , "CertCreateCertificateContext");
    }

    *ResultCert = std::shared_ptr<const CERT_CONTEXT>(crtResultCert
        , ::CertFreeCertificateContext);
    return std::make_tuple(0, 0, "");
}

하드웨어 토큰에 액세스 한 후 인증서를로드 한 후로드합니다.

std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
        signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);

마지막으로 서명은 다음 함수에서 수행됩니다.

HRESULT SignAppxPackage(
    _In_ PCCERT_CONTEXT signingCertContext,
    _In_ LPCWSTR packageFilePath)
{
    HRESULT hr = S_OK;
    if (PathFileExists(CertAuthority_ROOT))
    {
        wprintf(L"Cross Certificate '%s' was found\n", CertAuthority_ROOT);
    }
    else
    {
        wprintf(L"Error: Cross Certificate '%s' was not found\n", CertAuthority_ROOT);
        return 3;
    }
    DWORD dwReturnedFlag;
    if (InternetGetConnectedState(&dwReturnedFlag,0) == NULL) 
    {
        wprintf(L"Certificate can't be dated with no Internet connection\n");
        return 1;
    }
    if (PathFileExists(CertAuthority_RSA))
    {
        wprintf(L"Cross Certificate '%s' was found\n", CertAuthority_RSA);
    }
    else
    {
        wprintf(L"Error: Cross Certificate '%s' was not found\n", CertAuthority_RSA);
        return 2;
    }
    if (PathFileExists(CROSSCERTPATH))
    {
        wprintf(L"Microsoft Cross Certificate '%s' was found\n", CROSSCERTPATH);

    }
    else
    {
        wprintf(L"Error: Microsoft Cross Certificate '%s' was not found\n", CROSSCERTPATH);
        return 3;
    }
    // Initialize the parameters for SignerSignEx2
    DWORD signerIndex = 0;

    SIGNER_FILE_INFO fileInfo = {};
    fileInfo.cbSize = sizeof(SIGNER_FILE_INFO);
    fileInfo.pwszFileName = packageFilePath;

    SIGNER_SUBJECT_INFO subjectInfo = {};
    subjectInfo.cbSize = sizeof(SIGNER_SUBJECT_INFO);
    subjectInfo.pdwIndex = &signerIndex;
    subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE;
    subjectInfo.pSignerFileInfo = &fileInfo;

    SIGNER_CERT_STORE_INFO certStoreInfo = {};
    certStoreInfo.cbSize = sizeof(SIGNER_CERT_STORE_INFO);
    certStoreInfo.dwCertPolicy = SIGNER_CERT_POLICY_STORE;// SIGNER_CERT_POLICY_CHAIN_NO_ROOT;
    certStoreInfo.pSigningCert = signingCertContext;

    // Issuer: 'CertAuthority RSA Certification Authority'
    // Subject 'CertAuthority RSA Extended Validation Code Signing CA'
    auto fileCertAuthorityRsaEVCA = CertAuthority_RSA;
    std::shared_ptr<const CERT_CONTEXT> certCertAuthorityRsaEVCA;
    auto tuple_result = GetCertificateFromFile(fileCertAuthorityRsaEVCA, &certCertAuthorityRsaEVCA);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    std::shared_ptr<const CERT_CONTEXT> certCertEV;
    std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
        signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);
    tuple_result = GetCertificateFromMemory(dataCertEV, &certCertEV);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    // Issuer:  'Microsoft Code Verification Root'
    // Subject: 'CertAuthority RSA Certification Authority'
    auto fileCertCross = CertAuthority_ROOT;
    std::shared_ptr<const CERT_CONTEXT> certCertCross;
    tuple_result = GetCertificateFromFile(fileCertCross, &certCertCross);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    //certificate 1 Issuer  : '<Certificate Provider> RSA Certification Authority'
    //              Subject : '<Certificate Provider> Extended Validation Code Signing CA'
    //
    //certificate 2 Issuer  : '<Certificate Provider> Extended Validation Code Signing CA'
    //              Subject : '<Your company / entity name>'
    //
    //certificate 3 Issuer  : 'Microsoft Code Verification Root'
    //              Subject : '<Certificate Provider> Certification Authority'

    std::vector<std::shared_ptr<const CERT_CONTEXT> > certs;
    certs.push_back(certCertAuthorityRsaEVCA);
    certs.push_back(certCertEV);
    certs.push_back(certCertCross);

    std::shared_ptr<void> resultStore;
    tuple_result = FormMemoryCertStore(certs, CERT_STORE_ADD_NEW, &resultStore);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    certStoreInfo.hCertStore = resultStore.get();
    //--------------------------------------------------------------------

    SIGNER_CERT cert = {};
    cert.cbSize = sizeof(SIGNER_CERT);
    cert.dwCertChoice = SIGNER_CERT_STORE;
    cert.pCertStoreInfo = &certStoreInfo;

    // The algidHash of the signature to be created must match the
    // hash algorithm used to create the app package
    SIGNER_SIGNATURE_INFO signatureInfo = {};
    signatureInfo.cbSize = sizeof(SIGNER_SIGNATURE_INFO);
    signatureInfo.algidHash = CALG_SHA_256;
    signatureInfo.dwAttrChoice = SIGNER_NO_ATTR;

    SIGNER_SIGN_EX2_PARAMS signerParams = {};
    signerParams.pSubjectInfo = &subjectInfo;
    signerParams.pSigningCert = &cert;
    signerParams.pSignatureInfo = &signatureInfo;
    signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_RFC3161;
    signerParams.pszAlgorithmOid = szOID_NIST_sha256;
    //signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_AUTHENTICODE;
    //signerParams.pszAlgorithmOid = NULL;
    signerParams.pwszTimestampURL = TIMESTAMPURL;

    APPX_SIP_CLIENT_DATA sipClientData = {};
    sipClientData.pSignerParams = &signerParams;
    signerParams.pSipData = &sipClientData;

    // Type definition for invoking SignerSignEx2 via GetProcAddress
    typedef HRESULT(WINAPI *SignerSignEx2Function)(
        DWORD,
        PSIGNER_SUBJECT_INFO,
        PSIGNER_CERT,
        PSIGNER_SIGNATURE_INFO,
        PSIGNER_PROVIDER_INFO,
        DWORD,
        PCSTR,
        PCWSTR,
        PCRYPT_ATTRIBUTES,
        PVOID,
        PSIGNER_CONTEXT *,
        PVOID,
        PVOID);

    // Load the SignerSignEx2 function from MSSign32.dll
    HMODULE msSignModule = LoadLibraryEx(
        L"MSSign32.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_SYSTEM32);

    if (msSignModule)
    {
        SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>(
            GetProcAddress(msSignModule, "SignerSignEx2"));
        if (SignerSignEx2)
        {
            hr = SignerSignEx2(
                signerParams.dwFlags,
                signerParams.pSubjectInfo,
                signerParams.pSigningCert,
                signerParams.pSignatureInfo,
                signerParams.pProviderInfo,
                signerParams.dwTimestampFlags,
                signerParams.pszAlgorithmOid,
                signerParams.pwszTimestampURL,
                signerParams.pCryptAttrs,
                signerParams.pSipData,
                signerParams.pSignerContext,
                signerParams.pCryptoPolicy,
                signerParams.pReserved);
        }
        else
        {
            DWORD lastError = GetLastError();
            hr = HRESULT_FROM_WIN32(lastError);
        }

        FreeLibrary(msSignModule);
    }
    else
    {
        DWORD lastError = GetLastError();
        hr = HRESULT_FROM_WIN32(lastError);
    }

    // Free any state used during app package signing
    if (sipClientData.pAppxSipState)
    {
        sipClientData.pAppxSipState->Release();
    }

    return hr;
}

내가 쓴이 기사를 보십시오 .


1

나는 globalsign 인증서를 사용하고 있으며 똑같은 말을 잘했습니다.

표준 EV 코드 서명으로 서명을 스크립팅하는 것은 불가능하며 HSM 플랫폼 사용을 장려하고 있습니다.

... 내 예산을 훨씬 초과합니다. 그들이 말한 것과는 반대로 나는 성공했습니다.

"C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe" sign /fd sha256 /f "MyCertificate.cer" /csp "eToken Base Cryptographic Provider" /kc "[{{TokenPassword}}]=ContainerTame" "FileToSign"

=>이 명령은 다음 오류를 반환합니다.

Error information: "CryptExportPublicKeyInfoEx failed" (87/0x57)

나는이 문제를 정말로 이해하지 못한다. 그러나 다른 시간에 다음 명령을 실행하면 작동합니다.

"C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\SignTool.exe" sign /tr http://timestamp.globalsign.com/scripts/timestamp.dll "MyFileToSign" 
Done Adding Additional Store
Successfully signed: MyFileToSign

이것은 teamcity 빌드 내에서 작동하며 teamcity 빌드 에이전트에 활성 계정 로그인이 필요하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.