Question

Assume a XMLDSig document SignedInfo as follow:

<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
  <Reference URI="#object">
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
    <DigestValue>OPnpF/ZNLDxJ/I+1F3iHhlmSwgo=</DigestValue>
  </Reference>
</SignedInfo>

The SHA1 of the above SignedInfo node is

5a c8 ef ab 04 5a 9a 46 fe 00 1a c5 8c 25 36 46 ff 88 dc 6a

To generate the SignatureValue in Windows CryptoAPI, I run this:

var hProv: HCRYPTPROV;
    hHash: HCRYPTHASH;
    hKey: HCRYPTKEY;
    s: string;
    B, bSign: TBytes;
    bPrefix, bPubKeyModulus, bExp: TBytes;
    iFFLen, iPos, iSize, iHashSize: Cardinal;
    PubKey: TRsaPubKey;
begin
  s := '<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">' + #$0A +
       '  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>' + #$0A +
       '  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>' + #$0A +
       '  <Reference URI="#object">' + #$0A +
       '    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>' + #$0A +
       '    <DigestValue>OPnpF/ZNLDxJ/I+1F3iHhlmSwgo=</DigestValue>' + #$0A +
       '  </Reference>' + #$0A +
       '</SignedInfo>';

  B := TEncoding.ANSI.GetBytes(s);

  {get context for crypt default provider}
  Win32Check(CryptAcquireContext(hProv, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT));
  try
    Win32Check(CryptGenKey(hProv, AT_SIGNATURE, RSA1024BIT_KEY or CRYPT_EXPORTABLE or ALG_SID_RSA_PKCS, hKey));

    {create hash-object (SHA algorithm)}
    Win32Check(CryptCreateHash(hProv, CALG_SHA1, 0, 0, hHash));

    Win32Check(CryptHashData(hHash, @B[0], Length(B), 0));

    // Obtain hash size
    iSize := SizeOf(iSize);
    SetLength(B, iSize);
    Win32Check(CryptGetHashParam(hHash, HP_HASHSIZE, @B[0], iSize, 0));
    Move(B[0], iSize, iSize);

    // obtain hash value
    SetLength(B, iSize);
    Win32Check(CryptGetHashParam(hHash, HP_HASHVAL, @B[0], iSize, 0));

    Print('Hex: ' + ToHex(B));
    Print('Base64: ' + ToBase64(B));

    // Signature of Hash
    Win32Check(CryptSignHash(hHash, AT_SIGNATURE, nil, 0, nil, iSize));
    SetLength(bSign, iSize);
    Win32Check(CryptSignHash(hHash, AT_SIGNATURE, nil, 0, @bSign[0], iSize));

    Print;
    Print('Signature: ');
    Print(ToBase64(Reverse(bSign)));

    // Public key
    Win32Check(CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, nil, iSize));

    SetLength(B, iSize);
    Win32Check(CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, @B[0], iSize));

    Print;
    Print('Public Key: ');
    Print(ToHexASCII(B));

    Move(B[SizeOf(TPublicKeyStruc)], PubKey, SizeOf(PubKey));

    // Public key: Get Modulus
    SetLength(bPubKeyModulus, PubKey.bitlen div 8);
    Move(B[SizeOf(TPublicKeyStruc) + SizeOf(TRsaPubKey)], bPubKeyModulus[0], Length(bPubKeyModulus));
    Print;
    Print('Modulus: ');
    Print(ToHex(bPubKeyModulus));
    Print;
    Print(ToBase64(bPubKeyModulus));

    // Public key: Get Exponent
    Print;
    Print('Exponent: ');
    Print(IntToStr(PubKey.pubexp));
    SetLength(bExp, SizeOf(PubKey.pubexp));
    Move(PubKey.pubexp, bExp[0], Length(bExp));
    while bExp[Length(bExp) - 1] = 0 do
      SetLength(bExp, Length(bExp) - 1);
    Print('Base64: ' + ToBase64(bExp));

    Print;
    if CryptVerifySignature(hHash, @bSign[0], Length(bSign), hKey, nil, 0) then
      Print('signature verified')
    else
      RaiseLastOSError;

    Win32Check(CryptDestroyKey(hKey));

    {destroy hash-object}
    Win32Check(CryptDestroyHash(hHash));
  finally
    {release the context for crypt default provider}
    Win32Check(CryptReleaseContext(hProv, 0));
  end;
end;

I am able to generate a signature and verify it using CryptoAPI that I am not sure if I am performing according to XMLDSig specification.

To further verify if I am doing it right, I try the online verifier: http://www.aleksey.com/xmlsec/xmldsig-verifier.html

I replace the modulus and SignatureValue of the following XMLDSig document and verify it with the online verifier:

<?xml version="1.0" encoding="UTF-8"?>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
  <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
  <Reference URI="#object">
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
    <DigestValue>OPnpF/ZNLDxJ/I+1F3iHhlmSwgo=</DigestValue>
  </Reference>
</SignedInfo>
<SignatureValue>nihUFQg4mDhLgecvhIcKb9Gz8VRTOlw+adiZOBBXgK4JodEe5aFfCqm8WcRIT8GLLXSk8PsUP4//SsKqUBQkpotcAqQAhtz2v9kCWdoUDnAOtFZkd/CnsZ1sge0ndha40wWDV+nOWyJxkYgicvB8POYtSmldLLepPGMz+J7/Uws=</SignatureValue>
<KeyInfo>
  <KeyValue>
    <RSAKeyValue><Modulus>4IlzOY3Y9fXoh3Y5f06wBbtTg94Pt6vcfcd1KQ0FLm0S36aGJtTSb6pYKfyX7PqCUQ8wgL6xUJ5GRPEsu9gyz8ZobwfZsGCsvu40CWoT9fcFBZPfXro1Vtlh/xl/yYHm+Gzqh0Bw76xtLHSfLfpVOrmZdwKmSFKMTvNXOFd0V18=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
  </KeyValue>
</KeyInfo>
<Object xmlns="http://www.w3.org/2000/09/xmldsig#" Id="object">some text
  with spaces and CR-LF.</Object>
</Signature>

And obviously it fail.

I think I didn't generate the signature value correctly.

Was it helpful?

Solution 2

I got it work finally. It was the endian problem at last. Microsoft CryptoAPI present modulus and signature value in Little Endian bit string. While the XMLDSIG specification presents in Big Endian bit string.

After obtaining modulus and signature value from CryptoAPI functions, just convert it to Big Endian bit string by reversing the bytes orders:

function Reverse(A: TBytes): TBytes;
var B: Byte;
    i: integer;
begin
  SetLength(Result, Length(A));
  i := Length(A) - 1;
  for B in A do begin
    Result[i] := B;
    Dec(i);
  end;
end;

Print(ToBase64(reverse(bSign)));
Print(ToBase64(reverse(bPubKeyModulus)));

OTHER TIPS

If you really need to verify XMLDSig document in native code and not in .NET I would recommend you to use MSXML 5.0. See the code example to verify signature and another one to create the signature.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top