Revisão | 530 (tree) |
---|---|
Hora | 2020-11-24 05:14:15 |
Autor | derekwildstar |
Documentação atualizada
XmlDSigCreate concluída!
@@ -33,7 +33,12 @@ | ||
33 | 33 | //: @Param(ADestination é um PByte que vai receber o conteúdo de AOrigin em seu |
34 | 34 | //: final. ADestination pode ser um PByte de tamanho zero (zero elementos)) |
35 | 35 | //: @Param(ADestinationSize é a quantidade de bytes que ADestination está |
36 | -//: apontando antes deste procedure ser executado) | |
36 | +//: apontando antes deste procedure ser executado. Chamadas subsequentes a | |
37 | +//: AppendBytes utilizarão este parâmetro para saber a quantidade de bytes | |
38 | +//: existentes em ADestination e assim poder realizar a concatenação | |
39 | +//: corretamente, portanto é responsabilitade do programador incrementar uma | |
40 | +//: variável que será usada neste parâmetro com a quantidade de bytes escritos | |
41 | +//: em uma chamada anterior a AppendBytes) | |
37 | 42 | procedure AppendBytes(const AOrigin: PByte; AOriginSize: DWORD; var ADestination: PByte; ADestinationSize: DWORD); overload; |
38 | 43 | |
39 | 44 | //: Anexa os bytes contidos em AOrigin ao final dos bytes contidos em |
@@ -18,25 +18,29 @@ | ||
18 | 18 | |
19 | 19 | TXDSCreateArguments = record |
20 | 20 | SignatureType: TXDSSignatureType; |
21 | + CertificateContext: PCCERT_CONTEXT; | |
22 | + AddCertificate: TXDSAddCertificate; | |
21 | 23 | InputFileName: String; |
22 | 24 | InputData: PByte; |
23 | 25 | InputSize: DWORD; |
24 | 26 | InputCharSet: TXDSCharSet; |
25 | - ReferenceUri: String; | |
26 | - ReferenceId: String; | |
27 | + OutputCharSet: TXDSCharSet; | |
28 | + SignatureId: String; | |
27 | 29 | SignatureLocation: String; |
28 | - SignatureId: String; | |
29 | - CertificateContext: PCCERT_CONTEXT; | |
30 | 30 | SignatureCanonicalizationMethod: TXDSCanonicalizationMethod; |
31 | + SignatureHash: TXDSHash; | |
32 | + ReferenceId: String; | |
33 | + ReferenceUri: String; | |
31 | 34 | ReferenceCanonicalizationMethod: TXDSCanonicalizationMethod; |
32 | - SignatureHash: TXDSHash; | |
33 | - DigestHash: TXDSHash; | |
34 | - AddCertificate: TXDSAddCertificate; | |
35 | + ReferenceHash: TXDSHash; | |
36 | + KeyInfoId: String; | |
35 | 37 | AddKeyValue: Boolean; |
36 | - KeyInfoId: String; | |
37 | 38 | end; |
38 | 39 | |
39 | 40 | TXDSCreateResults = record |
41 | + OutputData: PByte; | |
42 | + OutputSize: DWORD; | |
43 | + OutputCharSet: TXDSCharSet; | |
40 | 44 | ErrorCode: HRESULT; |
41 | 45 | ErrorMessage: String; |
42 | 46 | end; |
@@ -59,6 +63,15 @@ | ||
59 | 63 | SysUtils, Math, KRK.Rtl.Win.NCrypt, KRK.Rtl.Win.BCrypt, KRK.Rtl.Common.FileUtils, |
60 | 64 | KRK.Rtl.Sys.System; |
61 | 65 | |
66 | +// Esta é a função de callback usada para consolidar o resultado da assinatura | |
67 | +// digital. | |
68 | +function WriteXml(pvCallbackState: PVOID; const pbData: PBYTE; cbData: ULONG): HRESULT; WINAPI; | |
69 | +begin | |
70 | + AppendBytes(pbData,cbData,TXDSCreateResults(pvCallbackState^).OutputData,TXDSCreateResults(pvCallbackState^).OutputSize); | |
71 | + TXDSCreateResults(pvCallbackState^).OutputSize := TXDSCreateResults(pvCallbackState^).OutputSize + cbData; | |
72 | + Result := S_OK; | |
73 | +end; | |
74 | + | |
62 | 75 | function XmlDSigCreate(const AArguments: TXDSCreateArguments; out AResults: TXDSCreateResults): Boolean; |
63 | 76 | const |
64 | 77 | ENVELOPED_TRANSFORM: CRYPT_XML_ALGORITHM = (cbSize: SizeOf(CRYPT_XML_ALGORITHM); |
@@ -66,6 +79,7 @@ | ||
66 | 79 | Encoded: (dwCharset: CRYPT_XML_CHARSET_AUTO; |
67 | 80 | cbData: 0; |
68 | 81 | pbData: nil)); |
82 | + TRUEBOOL: BOOL = True; | |
69 | 83 | var |
70 | 84 | Signature: HCRYPTXML; |
71 | 85 | Reference: HCRYPTXML; |
@@ -103,10 +117,10 @@ | ||
103 | 117 | SignatureCanonicalizationMethod: CRYPT_XML_ALGORITHM; |
104 | 118 | ReferenceCanonicalizationMethod: CRYPT_XML_ALGORITHM; |
105 | 119 | SignatureHashAlgorithm: CRYPT_XML_ALGORITHM; |
106 | - DigestHashAlgorithm: CRYPT_XML_ALGORITHM; | |
120 | + ReferenceHashAlgorithm: CRYPT_XML_ALGORITHM; | |
107 | 121 | OIDInformation: PCCRYPT_OID_INFO; |
108 | 122 | CNGAlgorithmId: array [0..1] of LPCWSTR; |
109 | - OpenToEncodeProperties: array [0..0] of CRYPT_XML_PROPERTY; | |
123 | + EncodeProperties: array [0..0] of CRYPT_XML_PROPERTY; | |
110 | 124 | ReferenceFlags: DWORD; |
111 | 125 | ReferenceUri: PChar; |
112 | 126 | TransformationAlgorithms: array [0..1] of CRYPT_XML_ALGORITHM; |
@@ -120,17 +134,32 @@ | ||
120 | 134 | Result := False; |
121 | 135 | ZeroMemory(@AResults,SizeOf(TXDSCreateResults)); |
122 | 136 | |
137 | + // Abaixo estão as variáveis que são usadas em CleanUp. Elas precisam ser | |
138 | + // inicializadas aqui antes de qualquer chamada a CleanUp | |
139 | + Signature := nil; | |
140 | + ZeroMemory(@InputFile,SizeOf(CRYPT_XML_BLOB)); | |
141 | + FreeInputFile := False; | |
142 | + MustFreePrivateKey := False; | |
143 | + PrivateKey := 0; | |
144 | + CertificateChainContext := nil; | |
145 | + | |
146 | + if AArguments.OutputCharSet = csNone then | |
147 | + begin | |
148 | + AResults.ErrorCode := 1; | |
149 | + AResults.ErrorMessage := 'XmlDSigCreate: O charset de saída não foi informado'; | |
150 | + CleanUp; // Não vai fazer nada, mas vamos manter o padrão pra deixar claro. | |
151 | + Exit; | |
152 | + end; | |
153 | + | |
123 | 154 | if not Assigned(AArguments.CertificateContext) then |
124 | 155 | begin |
125 | - AResults.ErrorCode := 1; | |
156 | + AResults.ErrorCode := 2; | |
126 | 157 | AResults.ErrorMessage := 'XmlDSigCreate: Um certificado não foi informado. Não é possível continuar'; |
127 | 158 | CleanUp; // Não vai fazer nada, mas vamos manter o padrão pra deixar claro. |
128 | 159 | end |
129 | 160 | else |
130 | 161 | begin |
131 | - PrivateKey := 0; | |
132 | 162 | PrivateKeyType := 0; |
133 | - MustFreePrivateKey := False; | |
134 | 163 | // Obtém a chave privada e o tipo da chave do certificado. Neste ponto, |
135 | 164 | // ainda não há solicitação de senha ou pin. Isso só será solicitado no |
136 | 165 | // momento em que a chave privada for efetivamente usada, isto é, ao chamar |
@@ -150,8 +179,6 @@ | ||
150 | 179 | begin |
151 | 180 | // Neste ponto já temos PrivateKey e PrivateKeyType preenchidas! |
152 | 181 | |
153 | - CertificateChainContext := nil; | |
154 | - | |
155 | 182 | // Essa variável seve para configurar os critérios de pesquisa que |
156 | 183 | // CertGetCertificateChain usa para encontrar os certificados da cadeia de |
157 | 184 | // certificados. Aqui, não adicionamos nenhum critério especial, tudo está |
@@ -197,7 +224,7 @@ | ||
197 | 224 | cmExclusiveC14NC: SignatureCanonicalizationMethod.wszAlgorithm := wszURI_CANONICALIZATION_EXSLUSIVE_C14NC; |
198 | 225 | else |
199 | 226 | begin |
200 | - AResults.ErrorCode := 2; | |
227 | + AResults.ErrorCode := 3; | |
201 | 228 | AResults.ErrorMessage := 'XmlDSigCreate: O método de canonicalização da assinatura digital não foi escolhido'; |
202 | 229 | CleanUp; |
203 | 230 | Exit; |
@@ -245,7 +272,7 @@ | ||
245 | 272 | hSHA512: CNGAlgorithmId[0] := BCRYPT_SHA512_ALGORITHM; |
246 | 273 | else |
247 | 274 | begin |
248 | - AResults.ErrorCode := 3; | |
275 | + AResults.ErrorCode := 4; | |
249 | 276 | AResults.ErrorMessage := 'XmlDSigCreate: O algoritmo de hash da assinatura digital não foi informado'; |
250 | 277 | CleanUp; |
251 | 278 | Exit; |
@@ -269,14 +296,14 @@ | ||
269 | 296 | |
270 | 297 | // Configurando o algoritmo de hash do resumo criptográfico (digest) da |
271 | 298 | // referência. Isso consiste em complementar a definição da variável |
272 | - // DigestHashAlgorithm, selecionando o algorítmo adequado de acordo com | |
273 | - // o argumento escolhido | |
274 | - ZeroMemory(@DigestHashAlgorithm,SizeOf(CRYPT_XML_ALGORITHM)); | |
275 | - DigestHashAlgorithm.cbSize := SizeOf(CRYPT_XML_ALGORITHM); | |
299 | + // ReferenceHashAlgorithm, selecionando o algorítmo adequado de acordo | |
300 | + // com o argumento escolhido | |
301 | + ZeroMemory(@ReferenceHashAlgorithm,SizeOf(CRYPT_XML_ALGORITHM)); | |
302 | + ReferenceHashAlgorithm.cbSize := SizeOf(CRYPT_XML_ALGORITHM); | |
276 | 303 | |
277 | 304 | // Aqui eu reaproveitei CNGAlgorithmId, já que é um array de LPCWSTR. |
278 | 305 | // Usei o primeiro item deste array |
279 | - case AArguments.DigestHash of | |
306 | + case AArguments.ReferenceHash of | |
280 | 307 | hSHA1: CNGAlgorithmId[0] := BCRYPT_SHA1_ALGORITHM; |
281 | 308 | hSHA256: CNGAlgorithmId[0] := BCRYPT_SHA256_ALGORITHM; |
282 | 309 | hSHA384: CNGAlgorithmId[0] := BCRYPT_SHA384_ALGORITHM; |
@@ -283,8 +310,8 @@ | ||
283 | 310 | hSHA512: CNGAlgorithmId[0] := BCRYPT_SHA512_ALGORITHM; |
284 | 311 | else |
285 | 312 | begin |
286 | - AResults.ErrorCode := 4; | |
287 | - AResults.ErrorMessage := 'XmlDSigCreate: O algoritmo de hash do resumo criptográfico (digest) não foi informado'; | |
313 | + AResults.ErrorCode := 5; | |
314 | + AResults.ErrorMessage := 'XmlDSigCreate: O algoritmo de hash do resumo criptográfico (digest) da referência não foi informado'; | |
288 | 315 | CleanUp; |
289 | 316 | Exit; |
290 | 317 | end; |
@@ -303,11 +330,8 @@ | ||
303 | 330 | Exit; |
304 | 331 | end; |
305 | 332 | |
306 | - DigestHashAlgorithm.wszAlgorithm := AlgorithmInfo.wszAlgorithmURI; | |
333 | + ReferenceHashAlgorithm.wszAlgorithm := AlgorithmInfo.wszAlgorithmURI; | |
307 | 334 | |
308 | - ZeroMemory(@InputFile,SizeOf(CRYPT_XML_BLOB)); | |
309 | - FreeInputFile := False; | |
310 | - | |
311 | 335 | case AArguments.InputCharSet of |
312 | 336 | csNone: InputFile.dwCharset := CRYPT_XML_CHARSET_AUTO; |
313 | 337 | csUTF8: InputFile.dwCharset := CRYPT_XML_CHARSET_UTF8; |
@@ -329,7 +353,7 @@ | ||
329 | 353 | end |
330 | 354 | else |
331 | 355 | begin |
332 | - AResults.ErrorCode := 5; | |
356 | + AResults.ErrorCode := 6; | |
333 | 357 | AResults.ErrorMessage := 'XmlDSigCreate: Não foi informado um arquivo de entrada ou dados a serem assinados. Não é possível continuar'; |
334 | 358 | CleanUp; |
335 | 359 | Exit; |
@@ -355,7 +379,6 @@ | ||
355 | 379 | ZeroMemory(@TransformationAlgorithms,SizeOf(TransformationAlgorithms)); |
356 | 380 | TransformationAlgorithmsCount := 0; |
357 | 381 | |
358 | - Signature := nil; | |
359 | 382 | ReferenceUri := ''; |
360 | 383 | |
361 | 384 | case AArguments.SignatureType of |
@@ -370,7 +393,7 @@ | ||
370 | 393 | stEnveloped: begin |
371 | 394 | if InputFile.dwCharset = CRYPT_XML_CHARSET_AUTO then |
372 | 395 | begin |
373 | - AResults.ErrorCode := 6; | |
396 | + AResults.ErrorCode := 7; | |
374 | 397 | AResults.ErrorMessage := 'XmlDSigCreate: O charset do xml de entrada não foi informado. Não é possível continuar'; |
375 | 398 | CleanUp; |
376 | 399 | Exit; |
@@ -384,16 +407,16 @@ | ||
384 | 407 | // ignoro, caso a memória do array não seja inicializada desta |
385 | 408 | // forma, a função CryptXmlOpenToEncode mais adiante não funciona |
386 | 409 | // de jeito nenhum, retornando o código de erro para "parâmetro |
387 | - // inválido". | |
388 | - ZeroMemory(@OpenToEncodeProperties,SizeOf(OpenToEncodeProperties)); | |
410 | + // incorreto". | |
411 | + ZeroMemory(@EncodeProperties,SizeOf(EncodeProperties)); | |
389 | 412 | |
390 | - OpenToEncodeProperties[0].dwPropId := CRYPT_XML_PROPERTY_SIGNATURE_LOCATION; | |
391 | - OpenToEncodeProperties[0].pvValue := @AArguments.SignatureLocation; | |
392 | - OpenToEncodeProperties[0].cbValue := SizeOf(LPCWSTR); | |
413 | + EncodeProperties[0].dwPropId := CRYPT_XML_PROPERTY_SIGNATURE_LOCATION; | |
414 | + EncodeProperties[0].pvValue := @AArguments.SignatureLocation; | |
415 | + EncodeProperties[0].cbValue := SizeOf(LPCWSTR); | |
393 | 416 | end |
394 | 417 | else |
395 | 418 | begin |
396 | - AResults.ErrorCode := 7; | |
419 | + AResults.ErrorCode := 8; | |
397 | 420 | AResults.ErrorMessage := 'XmlDSigCreate: Não foi informado o nó onde o nó de assinatura deverá ser incluído. Não é possível continuar'; |
398 | 421 | CleanUp; |
399 | 422 | Exit; |
@@ -401,22 +424,28 @@ | ||
401 | 424 | |
402 | 425 | // A função CryptXmlOpenToEncode cria o elemento de assinatura |
403 | 426 | // <Signature> com o id indicado em AArguments.SignatureId, |
404 | - // utilizando as propriedades incluídas no array | |
405 | - // OpenToEncodeProperties no arquivo xml contido em InputFile. No | |
406 | - // final da execução, Signature conterá um ponteiro para o elemento | |
407 | - // de assinatura, o qual pode ser usado em várias outras funções | |
408 | - // CryptXML | |
427 | + // utilizando as propriedades incluídas no array EncodeProperties no | |
428 | + // arquivo xml contido em InputFile. No final da execução, Signature | |
429 | + // conterá um ponteiro para o elemento de assinatura, o qual pode | |
430 | + // ser usado em várias outras funções CryptXML | |
409 | 431 | |
410 | - // Abaixo OpenToEncodeProperties é o array de propriedades, que no | |
411 | - // caso só tem um elemento. O endereço de um array estático é o | |
412 | - // endereço de seu primeiro elemento. CryptXmlOpenToEncode pode | |
413 | - // falhar quando em OpenToEncodeProperties.pvValue não existe o | |
414 | - // indicador do nó onde deve ser colocado o nó <Signature> ou quando | |
415 | - // aquilo que foi informado não existe no xml de entrada | |
432 | + // Abaixo EncodeProperties é o array de propriedades, que no caso só | |
433 | + // tem um elemento. O endereço de um array estático é o endereço de | |
434 | + // seu primeiro elemento. CryptXmlOpenToEncode pode falhar quando em | |
435 | + // EncodeProperties.pvValue não existe o indicador do nó onde deve | |
436 | + // ser colocado o nó <Signature> ou quando aquilo que foi informado | |
437 | + // não existe no xml de entrada. Se você estiver se perguntando | |
438 | + // porque eu usei um array de uma posição ao invéz de usar uma | |
439 | + // variável simples, a explicação é também simples: para deixar | |
440 | + // claro que ali podem ser usadas mais de uma propriedade e que para | |
441 | + // isso basta usar um array estático com mais posições, indicando no | |
442 | + // parâmetro cProperty a quantidade de elementos neste array que | |
443 | + // contém propriedades válidas, em suma, é apenas para que eu me | |
444 | + // lembre no futuro que isso é possível | |
416 | 445 | AResults.ErrorCode := CryptXmlOpenToEncode(nil |
417 | 446 | ,0 |
418 | 447 | ,PChar(AArguments.SignatureId) |
419 | - ,@OpenToEncodeProperties | |
448 | + ,@EncodeProperties | |
420 | 449 | ,1 |
421 | 450 | ,@InputFile |
422 | 451 | ,@Signature); |
@@ -442,7 +471,7 @@ | ||
442 | 471 | stEnveloping: begin |
443 | 472 | if AArguments.ReferenceUri = '' then |
444 | 473 | begin |
445 | - AResults.ErrorCode := 8; | |
474 | + AResults.ErrorCode := 9; | |
446 | 475 | AResults.ErrorMessage := 'XmlDSigCreate: Não foi informado o URI da referência. Ele é obrigatório no formato ENCODING. Não é possível continuar'; |
447 | 476 | CleanUp; |
448 | 477 | Exit; |
@@ -488,7 +517,7 @@ | ||
488 | 517 | end; |
489 | 518 | else |
490 | 519 | begin |
491 | - AResults.ErrorCode := 9; | |
520 | + AResults.ErrorCode := 10; | |
492 | 521 | AResults.ErrorMessage := 'XmlDSigCreate: O tipo de assinatura a realizar não foi informado'; |
493 | 522 | CleanUp; |
494 | 523 | Exit; |
@@ -530,7 +559,7 @@ | ||
530 | 559 | ,PChar(AArguments.ReferenceId) |
531 | 560 | ,ReferenceUri |
532 | 561 | ,nil |
533 | - ,@DigestHashAlgorithm | |
562 | + ,@ReferenceHashAlgorithm | |
534 | 563 | ,TransformationAlgorithmsCount |
535 | 564 | ,@TransformationAlgorithms |
536 | 565 | ,@Reference); |
@@ -560,7 +589,7 @@ | ||
560 | 589 | |
561 | 590 | // Cria em formato binário uma string que contém a codificação em |
562 | 591 | // Base64 dentro do nó <Base64> |
563 | - StringToBinary('<Base64>' + BytesToString(BTSP) + '</Base64>',InputFileBase64.pbData,InputFileBase64.cbData); | |
592 | + StringToBinary('<Base64 xmlns="http://www.w3.org/2000/09/xmldsig#">' + BytesToString(BTSP) + '</Base64>',InputFileBase64.pbData,InputFileBase64.cbData); | |
564 | 593 | try |
565 | 594 | // O CharSet precisa corresponder a aquilo que está em |
566 | 595 | // InputFileBase64.pbData. Se o conteudo for ANSI, use UTF8, se for |
@@ -607,7 +636,10 @@ | ||
607 | 636 | ZeroMemory(@CertificateChain,SizeOf(CertificateChain)); |
608 | 637 | |
609 | 638 | ZeroMemory(@KeyInfoParam,SizeOf(CRYPT_XML_KEYINFO_PARAM)); |
610 | - KeyInfoParam.wszId := PChar(AArguments.KeyInfoId); | |
639 | + | |
640 | + if AArguments.KeyInfoId <> '' then | |
641 | + KeyInfoParam.wszId := PChar(AArguments.KeyInfoId); | |
642 | + | |
611 | 643 | KeyInfoParam.rgCertificate := @CertificateChain; |
612 | 644 | |
613 | 645 | // De acordo com a opção de adição de certificados, devemos adicionar |
@@ -646,32 +678,43 @@ | ||
646 | 678 | ,@SignatureHashAlgorithm |
647 | 679 | ,@SignatureCanonicalizationMethod); |
648 | 680 | |
681 | + if Failed(AResults.ErrorCode) then | |
682 | + begin | |
683 | + AResults.ErrorMessage := 'CryptXmlSign: Erro 0x' + IntToHex(AResults.ErrorCode,8) + 'h ao executar'; | |
684 | + CleanUp; | |
685 | + Exit; | |
686 | + end; | |
649 | 687 | |
650 | - ontem passou em stEnveloped! continue | |
688 | + // Agora vamos salvar o resultado em AResults. A propriedade abaixo | |
689 | + // serve para produzir o cabeçalho no topo do xml. | |
690 | + // <?xml version="1.0" encoding="utf-8" standalone="yes"?> | |
651 | 691 | |
652 | -{ | |
653 | - Result := CryptXmlSign(hSig | |
654 | - ,hCryptProvOrNCryptKey | |
655 | - ,dwKeySpec | |
656 | - ,dwSignFlags | |
657 | - ,CRYPT_XML_KEYINFO_SPEC_PARAM | |
658 | - ,@KeyInfoParam | |
659 | - ,@xmlAlg_SignatureMethod | |
660 | - ,@xmlAlg_CanonicalizationMethod); | |
661 | -} | |
692 | + ZeroMemory(@EncodeProperties,SizeOf(EncodeProperties)); | |
693 | + EncodeProperties[0].dwPropId := CRYPT_XML_PROPERTY_DOC_DECLARATION; | |
694 | + EncodeProperties[0].pvValue := @TRUEBOOL; | |
695 | + EncodeProperties[0].cbValue := SizeOf(BOOL); | |
662 | 696 | |
697 | + AResults.ErrorCode := CryptXmlEncode(Signature | |
698 | + ,CRYPT_XML_CHARSET(AArguments.OutputCharSet) | |
699 | + ,@EncodeProperties | |
700 | + ,1 | |
701 | + ,@AResults | |
702 | + ,WriteXml); | |
703 | + | |
663 | 704 | if Failed(AResults.ErrorCode) then |
664 | 705 | begin |
665 | - AResults.ErrorMessage := 'CryptXmlSign: Erro 0x' + IntToHex(AResults.ErrorCode,8) + 'h ao executar'; | |
706 | + AResults.ErrorMessage := 'CryptXmlEncode: Erro 0x' + IntToHex(AResults.ErrorCode,8) + 'h ao executar'; | |
666 | 707 | CleanUp; |
667 | 708 | Exit; |
668 | 709 | end; |
669 | - | |
670 | 710 | end; |
671 | 711 | end; |
672 | 712 | |
673 | - // Limpa tudo que tiver sobrado na memória | |
713 | + // Caso o fluxo chegue neste ponto, significa que tudo ocorreu bem, neste | |
714 | + // caso limpa tudo que tiver sobrado na memória e configura o resultado como | |
715 | + // true | |
674 | 716 | CleanUp; |
717 | + Result := True; | |
675 | 718 | end; |
676 | 719 | end; |
677 | 720 |