Files
lazarus-ccr/components/fpspreadsheet/source/crypto/fpsopendocument_crypto.pas

192 lines
5.6 KiB
ObjectPascal
Raw Permalink Normal View History

unit fpsOpenDocument_Crypto;
{$MODE ObjFPC}{$H+}
{.$DEFINE UNZIP_ABBREVIA} // Remove this define when zipper is fixed.
interface
uses
Classes, SysUtils,
{$IFDEF UNZIP_ABBREVIA}
ABUnzper,
{$ENDIF}
fpsTypes, fpsUtils, fpsOpenDocument;
type
TsSpreadOpenDocReaderCrypto = class(TsSpreadOpenDocReader)
private
function CalcPasswordHash(ADecryptionInfo: TsOpenDocManifestFileEntry;
APassword: String): TBytes;
protected
function Decrypt(AStream: TStream; ADecryptionInfo: TsOpenDocManifestFileEntry;
APassword: String; ADestStream: TStream; out AErrorMsg: String): Boolean; override;
function SupportsDecryption: Boolean; override;
{$IFDEF UNZIP_ABBREVIA}
function UnzipToStream(AStream: TStream; AZippedFile: String; ADestStream: TStream): Boolean; override;
{$ENDIF}
end;
var
sfidOpenDocument_Crypto: TsSpreadFormatID;
implementation
uses
zStream,
fpsReaderWriter, fpsCryptoProc;
{ Decompresses the source stream and stored the output in the destination stream. }
procedure Decompress(ASrcStream, ADestStream: TStream);
var
decompressor: TDecompressionStream;
begin
decompressor := TDecompressionStream.Create(ASrcStream, true);
try
ADestStream.CopyFrom(decompressor, 0); // 0 --> entire src stream
finally
decompressor.Free;
end;
end;
{-------------------------------------------------------------------------------
TsSpreadOpenDocReaderCrypto
-------------------------------------------------------------------------------}
{ AStream contains one encrypted xml file of the ods file structure. The method
decrypts the stream based on the information provided in ADecryptionInfo and
using the given (unhashed) user password. The output is stored in the
destination stream. }
function TsSpreadOpenDocReaderCrypto.Decrypt(AStream: TStream;
ADecryptionInfo: TsOpenDocManifestFileEntry; APassword: String;
ADestStream: TStream; out AErrorMsg: String): Boolean;
var
pwdHash: TBytes;
iv: TBytes;
tmpStream: TStream;
algorithm: String;
begin
Result := false;
algorithm := LowerCase(ADecryptionInfo.AlgorithmName);
if (algorithm = 'aes128-cbc') or (algorithm='aes192-cbc') or (algorithm='aes256-cbc') then
algorithm := 'aes'
else
algorithm := '';
if algorithm = '' then
exit;
tmpStream := TMemoryStream.Create;
try
// Calculated password hash
pwdHash := CalcPasswordHash(ADecryptionInfo, APassword);
// Decrypt
iv := DecodeBase64(ADecryptionInfo.InitializationVector);
case algorithm of
'aes':
AErrorMsg := Decrypt_AES_CBC(pwdHash[0], Length(pwdHash)*8, @iv[0], AStream, tmpStream);
else
AErrorMsg := 'Encryption method not supported.';
end;
if (AErrorMsg <> '') then
exit;
// Verify decrypted (but still compressed) stream
// OpenDocument-v1.2-part3, section 3.8.3: "The digest is build from the compressed unencrypted file"
tmpStream.Position := 0;
if not VerifyDecrypt(tmpStream, ADecryptionInfo.EncryptionData_CheckSum, ADecryptionInfo.EncryptionData_ChecksumType) then
AErrorMsg := 'Checksum error';
if AErrorMsg <> '' then
exit;
// Decompress the decrypted stream
Decompress(tmpStream, ADestStream);
ADestStream.Position := 0;
// Success!
Result := true;
finally
tmpStream.Free;
end;
end;
{ Calculates the hash value of the user-provided passwort.
Hash creation is determined by information stored in ADecryptionInfo. }
function TsSpreadOpenDocReaderCrypto.CalcPasswordHash(
ADecryptionInfo: TsOpenDocManifestFileEntry;
APassword: String): TBytes;
var
pwdHash: TBytes;
salt: TBytes;
numIterations: Integer;
keySize: Integer;
begin
Result := nil;
// Generate start key
case LowerCase(ADecryptionInfo.StartKeyGenerationName) of
'sha256': pwdHash := Calc_SHA256(APassword[1], Length(APassword));
else
raise EFpSpreadsheetReader.Create('Unsupported start key generator ' + ADecryptionInfo.StartKeyGenerationName);
end;
// Generate derived key
numIterations := ADecryptionInfo.IterationCount;
keySize := ADecryptionInfo.KeySize;
salt := DecodeBase64(ADecryptionInfo.Salt);
if LowerCase(ADecryptionInfo.KeyDerivationName) = 'pbkdf2' then
Result := PBKDF2_HMAC_SHA1(pwdHash, salt, numIterations, keySize)
else
raise EFpSpreadsheetReader.Create('Unsupported key generation method ' + ADecryptionInfo.KeyDerivationName);
end;
{ Tells the calling routine that this reader is able to decrypt ods files. }
function TsSpreadOpenDocReaderCrypto.SupportsDecryption: Boolean;
begin
Result := true;
end;
{$IFDEF UNZIP_ABBREVIA}
{ Extracts the specified file from the compressed stream (AStream) to the
ADestStream.
Uses the ABBREVIA library for this purpose (because FCL Stripper fails to
extract the encrypted file). }
function TsSpreadOpenDocReaderCrypto.UnzipToStream(AStream: TStream;
AZippedFile: String; ADestStream: TStream): Boolean;
var
unzipper: TABUnzipper;
begin
Result := false;
unzipper := TABUnzipper.Create(nil);
try
unzipper.Stream := AStream;
try
unzipper.ExtractToStream(AZippedFile, ADestStream);
ADestStream.Position := 0;
Result := true;
except
raise;
end;
finally
unzipper.Free;
end;
end;
{$ENDIF}
{==============================================================================}
initialization
{==============================================================================}
{ Registers this reader for fpSpreadsheet }
sfidOpenDocument_Crypto := RegisterSpreadFormat(sfUser,
TsSpreadOpenDocReaderCrypto, nil,
STR_FILEFORMAT_OPENDOCUMENT, 'ODS', [STR_OPENDOCUMENT_CALC_EXTENSION]
);
end.