unit frxPDFPFX; {$I frx.inc} interface uses {$IFNDEF Linux} Windows, {$ENDIF} SysUtils, Classes, Math, frxHelpers, frxASN1, frxPDFX509Certificate; type TfrxPKCS12Document = class private FContentInfo: AnsiString; FMacSalt: AnsiString; FMacDigest: AnsiString; FMacAlgorithmName: TOIDName; FMacIterations: Integer; FValidPassword: UTF8String; FPrivateKeys: TOwnObjList; FCertificates: TOwnObjList; FChain: TfrxX509Certificate; FChainLen: Integer; function GetCertificates(i: Integer): TfrxX509Certificate; function GetPrivateKeys(i: Integer): TfrxRSAPrivateKey; protected FDebugLog: TLogList; procedure Clear; procedure ClearMacData; procedure LoadMacData(MacData: TfrxASN1Container); procedure ProcessSafeBag(SafeBag: TfrxASN1Container); class procedure PartCopy(Source: AnsiString; var Dest: AnsiString; APos: Integer); class procedure PartCopy2(Source: AnsiString; var Dest: AnsiString; APos: Integer); class function NormalizePassword(Password: UTF8String): AnsiString; procedure ReadPrivateKey(BagId: TOIDName; BagValue, BagAttributes: TfrxASN1Container); procedure ReadCertificate(BagValue, BagAttributes: TfrxASN1Container); property Certificates[i: Integer]: TfrxX509Certificate read GetCertificates; property PrivateKeys[i: Integer]: TfrxRSAPrivateKey read GetPrivateKeys; public constructor Create(ADebugLog: TLogList = nil); destructor Destroy; override; procedure LoadFromFile(AFileName: string); procedure LoadFromStream(AStream: TStream); function IsCheckPassword(Password: UTF8String): Boolean; procedure Parse; class function DerivingKey(Password: UTF8String; Salt: AnsiString; ID: Byte; Algorithm: TOIDName; Iteration: Integer; Len: Cardinal): AnsiString; property Chain: TfrxX509Certificate read FChain; property ChainLen: Integer read FChainLen; end; implementation uses frxHash, frxCipher; const UnknownStructure = 'Unknown structure.'; function PKCS7ProcessData(Data: TfrxASN1Container): AnsiString; begin // Fix for using OCTET STRING as a Container like // [0] (1 elem) // OCTET STRING (1 elem) // OCTET STRING (1 elem) while (Data[0] is TfrxASN1Container) and (Data[0].IsTag(ASN1_TAG_OCTET_STRING)) do begin Data := TfrxASN1Container(Data[0]); RaiseIf(Data.IsEmpty, UnknownStructure); end; RaiseIf(Data[0].IsNot(ASN1_TAG_OCTET_STRING), UnknownStructure); Result := TfrxASN1Data(Data[0]).Data; end; function PKCS7ProcessEncryptedData(Content: TfrxASN1Container; Password: UTF8String): AnsiString; const InvalidEncryptedData = 'Invalid Encrypted Data.'; var Version: TfrxASN1Base; EncryptedData, EncryptedContentInfo: TfrxASN1Container; begin RaiseIf(Password = '', 'Empty Password'); // [0] RaiseIf(Content[0].IsNot(ASN1_TAG_SEQUENCE), InvalidEncryptedData); EncryptedData := Content[0] as TfrxASN1Container; // EncryptedData ::= SEQUENCE { // version Version, // encryptedContentInfo EncryptedContentInfo } RaiseIf(EncryptedData.Count < 2, InvalidEncryptedData); Version := EncryptedData[0]; RaiseIf(not (Version is TfrxASN1Integer), InvalidEncryptedData); RaiseIf(TfrxASN1Integer(Version).Value <> 0, 'Invalid Encryption Algorithm Version.'); // EncryptedContentInfo ::= SEQUENCE { // contentType ContentType, // contentEncryptionAlgorithm // ContentEncryptionAlgorithmIdentifier, // encryptedContent // [0] IMPLICIT EncryptedContent OPTIONAL } // EncryptedContent ::= OCTET STRING RaiseIf(not (EncryptedData[1] is TfrxASN1Container) or (TfrxASN1Container(EncryptedData[1]).Count < 3), InvalidEncryptedData); EncryptedContentInfo := EncryptedData[1] as TfrxASN1Container; Result := EncriptData(EncryptedContentInfo[1], EncryptedContentInfo[2], Password); end; function ExtractPKCS7Info(ContentInfo: TfrxASN1Container; Password: UTF8String; DebugLog: TLogList = nil): AnsiString; var ContentType: TfrxASN1Base; Content: TfrxASN1Container; begin // ContentInfo ::= SEQUENCE { // contentType ContentType, // content // [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL } RaiseIf(ContentInfo.IsEmpty, 'ContentInfo.IsEmpty: ' + UnknownStructure, DebugLog); // ContentType ::= OBJECT IDENTIFIER ContentType := ContentInfo[0]; RaiseIf(ContentType.IsNot(ASN1_TAG_OBJECT_ID), 'ContentType.IsNot(ASN1_TAG_OBJECT_ID): ' + UnknownStructure, DebugLog); if ContentInfo.Count >= 2 then if ContentInfo[1].IsTag(ASN1_TAG_EOC) and ContentInfo[1].IsClass(ASN1_CLASS_CONTEXT) and (ContentInfo[1] is TfrxASN1Container) and not TfrxASN1Container(ContentInfo[1]).IsEmpty then begin Content := ContentInfo[1] as TfrxASN1Container; case TfrxASN1ObjectID(ContentType).OIDName of OID_pkcs7_data: begin DebugLog.Add('OID_pkcs7_data'); Result := PKCS7ProcessData(Content); end; // OID_pkcs7_signedData: // OID_pkcs7_envelopedData: // OID_pkcs7_signedAndEnvelopedData: // OID_pkcs7_digestedData: OID_pkcs7_encryptedData: // The encrypted-data content type shall have ASN.1 type EncryptedData: begin DebugLog.Add('OID_pkcs7_encryptedData'); Result := PKCS7ProcessEncryptedData(Content, Password); end; else RaiseIf(True, 'TfrxASN1ObjectID(ContentType).OIDName: Unsupported document.', DebugLog); end end else RaiseIf(True, 'ExtractPKCS7Info: ' + UnknownStructure, DebugLog); end; function HMAC(const Key, Text: AnsiString; HashAlgorithm: TOIDName; DebugLog: TLogList = nil): AnsiString; var HC: TfrxHashClass; H: TfrxHash; PAD, WrkPAD: array [0 .. 63] of Byte; HashDigest: TfrxHashDigest; i: Integer; begin HC := OIDtoHashClass(HashAlgorithm); RaiseIf(HC = nil, 'Algorithm not supported', DebugLog); H := HC.Create; try FillChar(PAD, SizeOf(PAD), 0); if Length(Key) <= 64 then Move(Key[1], PAD, Length(Key)) else begin H.Initialize; H.UpdateByAnsi(Key); H.Finalize; H.MoveDigestTo(PAD); end; for i := 0 to 63 do WrkPAD[i] := PAD[i] xor $36; H.Initialize; H.Update(WrkPAD, 64); H.UpdateByAnsi(Text); H.Finalize; H.MoveDigestTo(HashDigest); for i := 0 to 63 do WrkPAD[i] := PAD[i] xor $5C; H.Initialize; H.Update(WrkPAD, 64); H.Update(HashDigest, H.DigestSize); H.Finalize; Result := H.DigestToAnsi; finally H.Free; end; end; { TPKCS12Document } procedure TfrxPKCS12Document.Clear; begin FValidPassword := ''; FContentInfo := ''; ClearMacData; FCertificates.Clear; FPrivateKeys.Clear; end; procedure TfrxPKCS12Document.ClearMacData; begin FMacSalt := ''; FMacAlgorithmName := OID_Undef; FMacDigest := ''; FMacIterations := 1; // Default end; constructor TfrxPKCS12Document.Create(ADebugLog: TLogList = nil); begin FDebugLog := ADebugLog; FCertificates := TOwnObjList.Create; FPrivateKeys := TOwnObjList.Create; end; class function TfrxPKCS12Document.DerivingKey(Password: UTF8String; Salt: AnsiString; ID: Byte; Algorithm: TOIDName; Iteration: Integer; Len: Cardinal): AnsiString; procedure FillData(out OutData: AnsiString; out OutLen: Integer; InData: AnsiString); var InLen: Integer; i: Integer; begin InLen := Length(InData); if InLen and $3F = 0 then begin OutLen := InLen; OutData := InData; end else begin OutLen := ((InLen shr 6) + 1) shl 6; SetLength(OutData, OutLen); for i := 0 to OutLen - 1 do OutData[i + 1] := InData[(i mod InLen) + 1]; end; end; var NormalizedPassword: AnsiString; i : Cardinal; j, SLen, PLen: Integer; V: Integer; D, S, P, A, B: AnsiString; H: TfrxHash; HC: TfrxHashClass; begin Result := ''; HC := OIDtoHashClass(Algorithm); if (Len = 0) or (HC = nil) then Exit; NormalizedPassword := NormalizePassword(PassWord); V := 64; SetLength(D, V); FillChar(D[1], V, ID); FillData(S, SLen, Salt); FillData(P, PLen, NormalizedPassword); SetLength(B, V); SetLength(Result, Len); H := HC.Create; try i := 0; while True do begin H.Initialize; H.Iterate(D + S + P, Iteration); A := H.DigestToAnsi; PartCopy(A, Result, i); Inc(i, H.DigestSize); if i >= Len then Break; j := 0; while j < V do begin PartCopy(A, B, j); Inc(j, H.DigestSize); end; j := 0; while j < SLen do begin PartCopy2(B, S, j); Inc(j, V); end; j := 0; while j < PLen do begin PartCopy2(B, P, j); Inc(j, V); end; end; finally H.Free; end; end; destructor TfrxPKCS12Document.Destroy; begin FPrivateKeys.Free; FCertificates.Free; inherited; end; function TfrxPKCS12Document.GetCertificates(i: Integer): TfrxX509Certificate; begin Result := TfrxX509Certificate(FCertificates[i]); end; function TfrxPKCS12Document.GetPrivateKeys(i: Integer): TfrxRSAPrivateKey; begin Result := TfrxRSAPrivateKey(FPrivateKeys[i]); end; function TfrxPKCS12Document.IsCheckPassword(Password: UTF8String): Boolean; var Key: AnsiString; Digest: AnsiString; HSize: Cardinal; begin HSize := OIDtoHashClass(FMacAlgorithmName).DigestSize; Key := DerivingKey(Password, FMacSalt, 3, FMacAlgorithmName, FMacIterations, HSize); try Digest := HMAC(Key, FContentInfo, FMacAlgorithmName, FDebugLog); except Result := False; Exit; end; Result := FMacDigest = Digest; if Result then FValidPassword := Password; end; procedure TfrxPKCS12Document.LoadFromFile(AFileName: string); var FS: TFileStream; begin FS := TFileStream.Create(AFileName, fmOpenRead); try LoadFromStream(FS); finally FS.Free; end; end; procedure TfrxPKCS12Document.LoadFromStream(AStream: TStream); var PFX: TfrxASN1Container; Version, AuthSafe, MacData: TfrxASN1Base; FASN1Doc: TfrxASN1Document; begin Clear; FASN1Doc := TfrxASN1Document.Create; try FASN1Doc.LoadFromStream(AStream); // PFX ::= SEQUENCE { // version INTEGER {v3(3)}(v3,...), // authSafe ContentInfo, // macData MacData OPTIONAL } RaiseIf((FASN1Doc.Count = 0) or FASN1Doc[0].IsNot(ASN1_TAG_SEQUENCE), 'ASN1 sequence not found.', FDebugLog); PFX := FASN1Doc[0] as TfrxASN1Container; RaiseIf(PFX.Count < 2, UnknownStructure); Version := PFX[0]; // version INTEGER {v3(3)}(v3,...) RaiseIf(Version.IsNot(ASN1_TAG_INTEGER) or (TfrxASN1Integer(Version).Value <> 3), 'Invalid version of document.', FDebugLog); AuthSafe := PFX[1]; // authSafe ContentInfo RaiseIf(AuthSafe.IsNot(ASN1_TAG_SEQUENCE), 'PKCS#7 information not found.', FDebugLog); FContentInfo := ExtractPKCS7Info(AuthSafe as TfrxASN1Container, '', FDebugLog); if PFX.Count > 2 then // macData MacData OPTIONAL begin MacData := PFX[2]; RaiseIf(MacData.IsNot(ASN1_TAG_SEQUENCE), 'MacData information not found.', FDebugLog); LoadMacData(MacData as TfrxASN1Container); end; finally FASN1Doc.Free; end; end; procedure TfrxPKCS12Document.LoadMacData(MacData: TfrxASN1Container); const UnknownMacDataStructure = 'Unknown MacData structure.'; UnknownDigestStructure = 'Unknown digest structure.'; var Mac, DigestAlgorithm: TfrxASN1Container; Digest, MacSalt, Iterations: TfrxASN1Base; begin RaiseIf(MacData.Count < 2, 'MacData.Count < 2: ' + UnknownMacDataStructure, FDebugLog); // MacData ::= SEQUENCE { // mac DigestInfo, // macSalt OCTET STRING, // iterations INTEGER DEFAULT 1 } ClearMacData; // MacData.Mac // DigestInfo ::= SEQUENCE { // digestAlgorithm DigestAlgorithmIdentifier, // digest Digest } RaiseIf(MacData[0].IsNot(ASN1_TAG_SEQUENCE), 'MacData[0].IsNot(ASN1_TAG_SEQUENCE): ' + UnknownMacDataStructure, FDebugLog); Mac := TfrxASN1Container(MacData[0]); RaiseIf(Mac.Count <> 2, 'Mac.Count <> 2: ' + UnknownDigestStructure, FDebugLog); // MacData.Mac.DigestAlgorithm RaiseIf(Mac[0].IsNot(ASN1_TAG_SEQUENCE), 'Mac[0].IsNot(ASN1_TAG_SEQUENCE)' + UnknownDigestStructure, FDebugLog); DigestAlgorithm := TfrxASN1Container(Mac[0]); RaiseIf((DigestAlgorithm.IsEmpty) or DigestAlgorithm[0].IsNot(ASN1_TAG_Object_ID), UnknownDigestStructure, FDebugLog); FMacAlgorithmName := TfrxASN1ObjectID(DigestAlgorithm[0]).OIDName; RaiseIf(FMacAlgorithmName <> OID_sha1, 'Unsupported digest algorithm.', FDebugLog); // MacData.Mac.Digest // Digest ::= OCTET STRING Digest := Mac[1]; RaiseIf(Digest.IsNot(ASN1_TAG_OCTET_STRING), 'Digest.IsNot(ASN1_TAG_OCTET_STRING): ' + UnknownDigestStructure, FDebugLog); FMacDigest := TfrxASN1Data(Digest).Data; // MacData.MacSalt MacSalt := MacData[1]; RaiseIf(MacSalt.IsNot(ASN1_TAG_OCTET_STRING), 'MacSalt.IsNot(ASN1_TAG_OCTET_STRING):' + UnknownMacDataStructure, FDebugLog); FMacSalt := TfrxASN1Data(MacSalt).Data; // MacData.Iterations if MacData.Count > 2 then begin Iterations := MacData[2]; RaiseIf(Iterations.IsNot(ASN1_TAG_INTEGER) or (TfrxASN1Integer(Iterations).IsOver64Bit), UnknownMacDataStructure, FDebugLog); FMacIterations := TfrxASN1Integer(Iterations).Value; end else // DEFAULT 1 FMacIterations := 1; end; class function TfrxPKCS12Document.NormalizePassword(Password: UTF8String): AnsiString; var Pass: WideString; i: Integer; WCh: Word; begin Pass := {$IFDEF Delphi12} UTF8ToWideString(Password); {$ELSE} UTF8Decode(Password); {$ENDIF} SetLength(Result, (Length(Pass) + 1) * 2); for i := 1 to Length(Pass) do begin WCh := Word(Pass[i]); Result[i shl 1 - 1] := AnsiChar(Byte(WCh shr 8)); Result[i shl 1] := AnsiChar(Byte(WCh and $FF)); end; FillChar(Result[Length(Result) - 1], 2, 0); end; procedure TfrxPKCS12Document.Parse; var AuthSafe, SafeContents: TfrxASN1Container; i, j: Integer; Item: TfrxASN1Base; Bag: AnsiString; Certificate: TfrxX509Certificate; begin Item := nil; // Remove warning try Item := ReadASN1Object(FContentInfo); except on ESignException do RaiseIf(True, 'Parse - ReadASN1Object: AuthenticatedSafe cannot loaded', FDebugLog); end; AuthSafe := TfrxASN1Container(Item); FChain := nil; FChainLen := 0; FDebugLog.Add('AuthSafe.Count: ' + IntToStr(AuthSafe.Count)); try for i := 0 to AuthSafe.Count - 1 do if AuthSafe[i].IsTag(ASN1_TAG_SEQUENCE) then begin FDebugLog.Add('AuthSafe[' + IntToStr(i) + '].IsTag(ASN1_TAG_SEQUENCE)'); Bag := ExtractPKCS7Info(TfrxASN1Container(AuthSafe[i]), FValidPassword, FDebugLog); Item := ReadASN1Object(Bag); try if Item.IsTag(ASN1_TAG_SEQUENCE) then begin // SafeContents ::= SEQUENCE OF SafeBag SafeContents := TfrxASN1Container(Item); FDebugLog.Add('SafeContents.Count: ' + IntToStr(SafeContents.Count)); for j := 0 to SafeContents.Count - 1 do if SafeContents[j].IsTag(ASN1_TAG_SEQUENCE) then begin FDebugLog.Add('SafeContents[' + IntToStr(j) + '].IsTag(ASN1_TAG_SEQUENCE)'); ProcessSafeBag(SafeContents[j] as TfrxASN1Container); end; end; finally Item.Free; end; end; RaiseIf(FCertificates.Count = 0, 'Certificates not found in pfx document', FDebugLog); RaiseIf(FPrivateKeys.Count = 0, 'Private key not found in pfx document', FDebugLog); for i := 0 to FPrivateKeys.Count - 1 do begin for j := 0 to FCertificates.Count - 1 do if Certificates[j].CheckPrivateKey(PrivateKeys[i]) then begin FChain := Certificates[j]; Break; end; if FChain <> nil then Break; end; RaiseIf(FChain = nil, 'Not found pair certificate and private key', FDebugLog); for i := 0 to FCertificates.Count - 1 do for j := 0 to FCertificates.Count - 1 do if (i <> j) and Certificates[i].CheckOwner(Certificates[j]) then Break; Certificate := FChain; while Certificate <> nil do begin Certificate := Certificate.Owner; Inc(FChainLen); end; finally AuthSafe.Free; end; end; class procedure TfrxPKCS12Document.PartCopy(Source: AnsiString; var Dest: AnsiString; APos: Integer); var MoveCount: Integer; begin MoveCount := Min(Length(Source), Length(Dest) - APos); if MoveCount > 0 then Move(Source[1], Dest[1 + APos], MoveCount); end; class procedure TfrxPKCS12Document.PartCopy2(Source: AnsiString; var Dest: AnsiString; APos: Integer); var i: Integer; W: Word; D, S: PByteArray; begin if Source = '' then Exit; if (APos < 0) or (Length(Dest) < Length(Source) + APos) then raise Exception.Create('Invalid parameters'); D := @Dest[APos + 1]; S := @Source[1]; W := 1; for i := Length(Source) - 1 downto 0 do begin W := D^[i] + S^[i] + W; D^[i] := Lo(W); W := Hi(W); end; end; procedure TfrxPKCS12Document.ProcessSafeBag(SafeBag: TfrxASN1Container); var BagId: TOIDName; BagValue, BagAttributes: TfrxASN1Container; i: Integer; begin // SafeBag ::= SEQUENCE { // bagId BAG-TYPE.&id ({PKCS12BagSet}) // bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), // bagAttributes SET OF PKCS12Attribute OPTIONAL } if (SafeBag.Count < 3) or SafeBag[0].IsNot(ASN1_TAG_OBJECT_ID) or not (SafeBag[1] is TfrxASN1Container) then Exit; BagId := TfrxASN1ObjectID(SafeBag[0]).OIDName; BagValue := TfrxASN1Container(SafeBag[1]); BagAttributes := nil; if SafeBag[2] is TfrxASN1Container then BagAttributes := SafeBag[2] as TfrxASN1Container; // PKCS12BagSet BAG-TYPE ::= { keyBag | pkcs8ShroudedKeyBag | certBag | crlBag | secretBag | safeContentsBag } case BagId of OID_keyBag, OID_pkcs_8ShroudedKeyBag: begin FDebugLog.Add('BagId: OID_keyBag, OID_pkcs_8ShroudedKeyBag'); ReadPrivateKey(BagId, BagValue, BagAttributes); end; OID_certBag: begin FDebugLog.Add('BagId: OID_certBag'); ReadCertificate(BagValue, BagAttributes); end; OID_crlBag: ; OID_secretBag: ; OID_safeContentsBag: // This recursive structure allows for arbitrary nesting of multiple KeyBags, // PKCS8ShroudedKeyBags, CertBags, CRLBags, and SecretBags within the top-level SafeContents. begin FDebugLog.Add('BagId: OID_safeContentsBag; BagValue.Count: ' + IntToStr(BagValue.Count)); for i := 0 to BagValue.Count - 1 do if BagValue[i] is TfrxASN1Container then begin FDebugLog.Add('BagValue[' + IntToStr(i) + '] is TfrxASN1Container'); ProcessSafeBag(BagValue[i] as TfrxASN1Container); end; end; end; end; procedure TfrxPKCS12Document.ReadCertificate(BagValue, BagAttributes: TfrxASN1Container); var CertBag, CertValue: TfrxASN1Container; Data: AnsiString; CertObj: TfrxASN1Base; Certificate: TfrxX509Certificate; begin // CertBag ::= SEQUENCE { // certId BAG-TYPE.&id ({CertTypes}), // certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId}) } // // x509Certificate BAG-TYPE ::= // {OCTET STRING IDENTIFIED BY {certTypes 1}} // -- DER-encoded X.509 certificate stored in OCTET STRING // sdsiCertificate BAG-TYPE ::= // {IA5String IDENTIFIED BY {certTypes 2}} // -- Base64-encoded SDSI certificate stored in IA5String // // CertTypes BAG-TYPE ::= { x509Certificate | sdsiCertificate } if BagValue.IsEmpty then begin FDebugLog.Add('BagValue.IsEmpty'); Exit; end; if not (BagValue[0] is TfrxASN1Container) then begin FDebugLog.Add('not (BagValue[0] is TfrxASN1Container)'); Exit; end; CertBag := TfrxASN1Container(BagValue[0]); // [0] if (CertBag.Count < 2) or not (CertBag[0] is TfrxASN1ObjectID) or (TfrxASN1ObjectID(CertBag[0]).OIDName <> OID_x509Certificate) or not (CertBag[1] is TfrxASN1Container) or (TfrxASN1Container(CertBag[1]).IsEmpty) then begin FDebugLog.Add('CertBag: Exit'); Exit; end; CertValue := TfrxASN1Container(CertBag[1]); if CertValue[0].IsNot(ASN1_TAG_OCTET_STRING) then begin FDebugLog.Add('CertValue[0].IsNot(ASN1_TAG_OCTET_STRING)'); Exit; end; Data := TfrxASN1Data(CertValue[0]).Data; if Data = '' then begin FDebugLog.Add('Data = ""'); Exit; end; try CertObj := ReadASN1Object(Data); except on Exception do begin CertObj := nil; FDebugLog.Add('CertObj: raise ?'); end; end; if CertObj = nil then Exit; try if not (CertObj is TfrxASN1Container) or ((CertObj as TfrxASN1Container).IsEmpty) or not ((CertObj as TfrxASN1Container)[0] is TfrxASN1Container) then begin FDebugLog.Add('CertObj: Exit'); Exit; end; Certificate := TfrxX509Certificate.Create; try Certificate.Load(CertObj as TfrxASN1Container); except on Exception do begin FDebugLog.Add('Certificate: raise ?'); FreeAndNil(Certificate); end; end; if Certificate <> nil then FCertificates.Add(Certificate); finally CertObj.Free; end; end; procedure TfrxPKCS12Document.ReadPrivateKey(BagId: TOIDName; BagValue, BagAttributes: TfrxASN1Container); var PK: TfrxRSAPrivateKey; begin PK := TfrxRSAPrivateKey.Create; try case BagId of OID_keyBag: // KeyBag ::= PrivateKeyInfo (PKCS #8) begin FDebugLog.Add('BagId: OID_keyBag'); PK.Read(BagValue, BagAttributes); end; OID_pkcs_8ShroudedKeyBag: // PKCS8ShroudedKeyBag ::= EncryptedPrivateKeyInfo (PKCS #8) begin FDebugLog.Add('BagId: OID_pkcs_8ShroudedKeyBag'); PK.ReadCrypted(BagValue, BagAttributes, FValidPassword); end; end; except on ESignException do begin FreeAndNil(PK); FDebugLog.Add('raise ?'); end; end; if PK <> nil then FPrivateKeys.Add(PK); end; end.