【问题标题】:How do I call the default windows screen to choose a certificate?如何调用默认窗口屏幕来选择证书?
【发布时间】:2017-10-17 08:06:58
【问题描述】:

THTTPRIO 组件,在 HTTPWebNode 属性中,当您单击 ClientCertificate 时,Delphi 会打开一个表单来选择证书并将其信息加载到组件的属性中。这是windows屏幕吗?如果是,我该如何使用它?今天我使用 SecureBlackBox 将证书加载到组合框中,但我想知道是否可以使用此屏幕。 谢谢

更新

我能够使用 JWAPI 使用 ms 函数 CryptUIDlgSelectCertificateFromStore 来显示对话框。现在我遇到了函数结果 PCCERT_CONTEXT 结构的问题。

var
  P: Pointer;
  Context: PCCERT_CONTEXT;
  Issuer: DATA_BLOB;

  function GetDataBlobText(Data: DATA_BLOB): string;
  begin
    SetString(Result, PAnsiChar(Data.pbData), Data.cbData div SizeOf(AnsiChar));
  end;

begin
  P := CertOpenSystemStore(0, 'MY');
  Context := CryptUIDlgSelectCertificateFromStore(P, 0, PChar('test'), nil, CRYPTUI_SELECT_ISSUEDTO_COLUMN, 0, nil);
  if Context <> nil then
  begin
    Issuer := Context.pCertInfo.Issuer;
    ShowMessage((GetDataBlobText(Issuer)));
  end;
end;

ShowMessage 中的结果是:

更新2

感谢@RbMm。 获取 ASN 编码字段字符串(Issuer 和 Subject)

var
  P: Pointer;
  Context: PCCERT_CONTEXT;
  Subject: DATA_BLOB;
  SubjectStr: string;
  size : Cardinal;
begin
  P := CertOpenSystemStore(0, PAnsiChar('MY'));
  Context := CryptUIDlgSelectCertificateFromStore(P, 0, 'test', 'select certificate',
    CRYPTUI_SELECT_ISSUEDTO_COLUMN, 0, nil);
  if Context <> nil then
  begin
    Subject := Context.pCertInfo.Subject;
    size := CertNameToStr(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, @Subject, CERT_X500_NAME_STR, 0, 0);
    SetString(SubjectStr, PAnsiChar(Subject.pbData), size);
    CertNameToStr(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, @Subject, CERT_X500_NAME_STR, PAnsiChar(SubjectStr), size);
    Result := SubjectStr;
  end;

获取原始数据块的字符串(SerialNumber):

var
  SerialNumber: CRYPT_INTEGER_BLOB;
  size : Cardinal;
  s: PWideChar;
  ss: string;
begin
SerialNumber := Context.pCertInfo.SerialNumber;
CryptBinaryToStringW(SerialNumber.pbData, SerialNumber.cbData, CRYPT_STRING_HEX, nil, size);
s := AllocMem(SizeOf(Char) * size);
CryptBinaryToStringW(SerialNumber.pbData, SerialNumber.cbData, CRYPT_STRING_HEX, s, size);
ss := s;
showmessage(ss);
FreeMem(s, SizeOf(Char) * size);

【问题讨论】:

  • 使用任务管理器找出显示对话框的进程。使用调试器附加到进程。停止进程并查看堆栈跟踪。您应该能够获取所有 Microsoft 组件的符号,并且从那里您应该能够查看是否有可以使用的 API,或者是否没有。
  • 感谢@RbMm,我能够显示对话框。现在我在显示函数的结果时遇到了问题,即 PCCERT_CONTEXT 结构。我会用这个更新我的问题。

标签: windows delphi winapi certificate x509certificate


【解决方案1】:

证书中的所有数据块都经过编码。所以你需要解码它。通常使用CryptDecodeObjectEx api。但是对于Issuer(即CERT_NAME_BLOB)解码,您也可以使用CertNameToStrW。只有在将CERT_NAME_BLOB 结构中的编码名称转换为以空字符结尾的字符串后,您才能打印它。 c/c++上的代码示例:

void PrintIssuer(PCCERT_CONTEXT Context)
{
    CERT_NAME_BLOB Issuer = Context->pCertInfo->Issuer;

    // option #1
    if (ULONG len = CertNameToStrW(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &Issuer, CERT_X500_NAME_STR, 0, 0))
    {
        PWSTR sz = (PWSTR)alloca( len * sizeof(WCHAR));

        if (CertNameToStrW(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &Issuer, CERT_X500_NAME_STR, sz, len))
        {
            DbgPrint("%S\n", sz);
        }
    }

    // option #2 
    PCERT_NAME_INFO pcni;
    ULONG size;

    if (CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_NAME, Issuer.pbData, Issuer.cbData,
        CRYPT_DECODE_ALLOC_FLAG, 0, &pcni, &size))
    {
        if (DWORD cRDN = pcni->cRDN)
        {
            PCERT_RDN rgRDN = pcni->rgRDN;
            do 
            {
                if (DWORD cRDNAttr = rgRDN->cRDNAttr)
                {
                    PCERT_RDN_ATTR rgRDNAttr = rgRDN->rgRDNAttr;
                    do 
                    {
                        DbgPrint("ObjId = %s\n", rgRDNAttr->pszObjId);

                        switch (rgRDNAttr->dwValueType)
                        {
                        case CERT_RDN_PRINTABLE_STRING:
                            DbgPrint("Value = %s\n", rgRDNAttr->Value.pbData);
                            break;
                        }

                    } while (rgRDNAttr++, --cRDNAttr);
                }

            } while (rgRDN++, --cRDN);
        }

        LocalFree(pcni);
    }
}

和输出

CN=***
ObjId = 2.5.4.3
Value = ***

CN=Value=后面的字符串是一样的)

您可以注意到 "2.5.4.3"szOID_COMMON_NAME"CN"。所以第一个 api 是在颁发者名称之前附加 CN= 。第二个变体按原样返回你的名字和额外的ObjId = 2.5.4.3


SerialNumber 转换为可打印的字符串可以:

CRYPT_INTEGER_BLOB SerialNumber = Context->pCertInfo->SerialNumber;
DWORD cb = 0;
if (CryptBinaryToStringW(SerialNumber.pbData, SerialNumber.cbData, CRYPT_STRING_HEX, 0, &cb))
{
    PWSTR sz = (PWSTR)alloca( cb * sizeof(WCHAR));
    if (CryptBinaryToStringW(SerialNumber.pbData, SerialNumber.cbData, CRYPT_STRING_HEX, sz, &cb))
    {
        DbgPrint("%S\n", sz);
    }
}

【讨论】:

  • 谢谢@RbMm。我能够获得未编码的字段(颁发者和主题,或 CommonName),但是对于编码字段的第二个选项不起作用。我会用我所做的更新我的问题。
  • @RodrigoCaetano 发行者总是以 asn 编码。 CertNameToStrW 在内部调用 CryptDecodeObjectEx。这 2 个 api 只是以不同的形式返回结果。一个前缀发行者名称,另一个返回给你的 oid 字符串
  • 知道了。但是我可以使用第一个选项获得序列号(即)吗?当我使用它时,它返回给我一个空字符串。我需要的只是获取证书的标识符,以便在我的代码中使用。我可以使用 Delphi 组件 SecureBlackBox 自己获取证书,但我只想使用这个 dlg 让用户选择一个,然后在组件返回的证书列表中选择这个标识符。
  • 再次感谢男士们,对于错误信息,我很抱歉,但我是证书领域的新手
  • @RodrigoCaetano - SerialNumber 未编码。这是原始字节数据块。要将其转换为字符串,请使用 CryptBinaryToStringWCRYPT_STRING_HEX 标志
猜你喜欢
  • 2017-03-31
  • 1970-01-01
  • 1970-01-01
  • 2022-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多