Webauthn credential verifiation with fido2.dll fro Yubico - delphi

I started to interface yubicos fido2.dll in Delphi and was able to interface it
according to the provided examples. Now I want to go a step further and use the
dll on an e.g. apache server to handle credential creation and assertion.
So.. for this I basically use the javascripts found on THE testing site https://webauthn.io/
Basically I wanted to mimic some server functions for credential creation. On the website one can setup
some properties - in my environment they come from the server.
Currently I have made the communication from Client to issue a credential initialization - server responses with a challange. The Key is queried and the browser creates credentials and sends it back to the server. This though where I have a problem with the data coming from the server aka I have a
problem decoding the attestationObject part.
So here is the credential init json from my server:
{"publicKey":{"challenge":"LFJYIdXJfYpB1GZS+PzEOD8DNcYmdia4mZp2z0J4QcE=","pubKeyCredParams":[{"alg":-7,"type":"public-key"},{"alg":-257,"type":"public-key"},{"alg":-8,"type":"public-key"}],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","userVerification":"required","requireResidentKey":false},"rp":{"id":"fidotest.com","name":"fidotest.com"},"user":{"id":"zVOUjBCxJNIbSSWNiGOv2\/kZP2UU8pPguVylFeiw4HE=","displayName":"test","name":"test"},"Timeout":60000,"attestation":"direct"}}
And the result from the server:
{"id":"MfcgyBxDxpq5S71fB45FFjecCGtvCepvb6IZexJpgaHyTPPsaz0srQyZc26HkE92eda7a2PmPIzvSpLbipktmw","rawId":"MfcgyBxDxpq5S71fB45FFjecCGtvCepvb6IZexJpgaHyTPPsaz0srQyZc26HkE92eda7a2PmPIzvSpLbipktmw","type":"public-key","response":{"attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAO8fbE8iQcMFYE4KBwL6HK6OxSReRKriXZDWhcfGRMFxAiB7mIPZ7n-fWas7aWkEkWd-9CWvd8ncRVCh3BBFIzMuRmN4NWOBWQLAMIICvDCCAaSgAwIBAgIEBMX-_DANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbTELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEmMCQGA1UEAwwdWXViaWNvIFUyRiBFRSBTZXJpYWwgODAwODQ3MzIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQc2Np2EaP17x-IXpULpl2A4zSFU5FYS9R_W3GcUyNcJCHk45m9tXNngkGQk1dmYUk8kUwuZyTfk5T8-n3qixgEo2wwajAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuMTATBgsrBgEEAYLlHAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBD4oBHzjApNFYAGFxEfntx9MAwGA1UdEwEB_wQCMAAwDQYJKoZIhvcNAQELBQADggEBAHcYTO91LRoF8wpThdwthvj6wGNxcLAiYqUZXPX-0Db-AGVODSkVvEVSmj-JXmrBzNQel3FW4AupOgbgrJmmcWWEBZyXSpRQtYcl2LTNU0-Iz9WbyHNN1wQJ9ybFwj608xBuoNRC0rG8wgYbMC4usyRadt3dYOVdQi0cfaksVB2VNKnw-ttQUWKoZsPHtuzFx8NlazLQBep1W2T0FCONFEG7x_l-ZcfNhT13azAbaurJ2J0_ff6H0PXJP6h-Obne4xfz0-8ujftWDUSh9oaiVRYf-tgam_tzOKyEU38V2liV11zMyHKWrXiK0AfyDgb58ky2HSrn_AgE5MW_oXg_CXdoYXV0aERhdGFYxNNxx2kdwL5GE4VmZm0_PerRaSEQdriBtCqmPBobyJXTRQAAAA34oBHzjApNFYAGFxEfntx9AEAx9yDIHEPGmrlLvV8HjkUWN5wIa28J6m9vohl7EmmBofJM8-xrPSytDJlzboeQT3Z51rtrY-Y8jO9KktuKmS2bpQECAyYgASFYIPVUDt7LCfuPyhdowBAhHCaRp-4acTmevkowvQhYxQluIlggCOL0rfuXgGze8yGX38sBXzsSMqxQxiskjsXia6UQvtQ","clientDataJSON":"eyJjaGFsbGVuZ2UiOiJMRkpZSWRYSmZZcEIxR1pTLVB6RU9EOEROY1ltZGlhNG1acDJ6MEo0UWNFIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9maWRvdGVzdC5jb20iLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"}}
Here is a console program that should verify the credentials:
(note you need to paste the above content to a textfile and load it in the console).
program VerifyCred;
{$APPTYPE CONSOLE}
uses
SysUtils,
Fido2 in '..\Fido2.pas',
Fido2dll in '..\Fido2dll.pas',
Fido2Json in '..\Fido2Json.pas',
windows,
classes,
cbor,
superobject;
function DoVerifyCred( credential : ISuperObject ) : string;
var clientData : ISuperObject;
s : string;
rawS : RawByteString;
credentialId : string;
rawId : TBytes;
credVerify : TFidoCredVerify;
cborItem : TCborMap;
sig : TBytes;
x5c : TBytes;
authData : TBytes;
fmt : string;
alg : integer;
i : integer;
aName : string;
res : boolean;
rawChallange : RawByteString;
credFMT : TFidoCredentialFmt;
challenge : TFidoChallenge;
authDataObj : TAuthData;
attStmt : TCborMap;
j : integer;
restBuf : TBytes;
begin
Result := '{"error":0,"msg":"Error parsing content"}';
s := credential.S['response.clientDataJSON'];
if s = '' then
exit;
ClientData := So( String(Base64URLDecode( s )) );
if clientData = nil then
exit;
rawChallange := Base64URLDecode(ClientData.S['challenge']);
if Length(rawChallange) <> sizeof(challenge) then
exit;
Move( rawChallange[1], challenge, sizeof(challenge));
clientData := SO( String( Base64URLDecode( credential.S['response.clientDataJSON'] ) ) );
s := credential.S['response.attestationObject'];
if s = '' then
exit;
// attestation object is a cbor encoded raw base64ulr encoded string
cborItem := TCborDecoding.DecodeBase64UrlEx(s, restBuf) as TCborMap;
if not Assigned(cborItem) then
exit;
try
alg := 0;
fmt := '';
sig := nil;
authData := nil;
x5c := nil;
for i := 0 to cborItem.Count - 1 do
begin
assert( cborItem.Names[i] is TCborUtf8String, 'CBOR type error');
aName := String((cborItem.Names[i] as TCborUtf8String).Value);
if SameText(aName, 'attStmt') then
begin
attStmt := cborItem.Values[i] as TCborMap;
for j := 0 to attStmt.Count - 1 do
begin
aName := String((attStmt.Names[j] as TCborUtf8String).Value);
if SameText(aName, 'alg')
then
alg := (attStmt.Values[j] as TCborNegIntItem).value
else if SameText(aName, 'sig')
then
sig := (attStmt.Values[j] as TCborByteString).ToBytes
else if SameText(aName, 'x5c')
then
x5c := ((attStmt.Values[j] as TCborArr)[0] as TCborByteString).ToBytes
end;
end
else if SameText(aName, 'authData')
then
authData := (cborItem.Values[i] as TCborByteString).ToBytes
else if SameText(aName, 'fmt')
then
fmt := String( (cborItem.Values[i] as TCborUtf8String).Value );
end;
finally
cborItem.Free;
end;
// check if anyhing is in place
if not (( alg = COSE_ES256 ) or (alg = COSE_EDDSA) or (alg= COSE_RS256)) then
raise Exception.Create('Unknown algorithm');
if Length(sig) = 0 then
raise Exception.Create('No sig field provided');
if Length(x5c) = 0 then
raise Exception.Create('No certificate');
if Length(authData) = 0 then
raise Exception.Create('Missing authdata');
credentialId := credential.S['id'];
s := credential.S['rawId'];
if s = '' then
raise Exception.Create('No Credential id found');
raws := Base64URLDecode( s );
SetLength( rawId, Length(rawS));
Move( rawS[1], rawId[0], Length(rawId));
authDataObj := nil;
if Length(restBuf) > 0 then
raise Exception.Create('Damend there is a rest buffer that should not be');
if Length(authDAta) > 0 then
authDataObj := TAuthData.Create( authData );
try
if fmt = 'packed'
then
credFmt := fmFido2
else if fmt = 'fido-u2f'
then
credFmt := fmU2F
else
credFmt := fmDef;
// now... verify the credentials
credVerify := TFidoCredVerify.Create( TFidoCredentialType(alg), credFmt,
authData, x5c, sig, FidoServer.RequireResidentKey,
authDataObj.UserVerified, 0) ;
try
// auth data seems bad!
res := credVerify.Verify(challenge);
if res then
begin
// -> save EVERYTHING to a database
end;
finally
credVerify.Free;
end;
finally
authDataObj.Free;
end;
// build result and generate a session
if res then
begin
// yeeeha we got a
Result := '{"success":true}';
end
else
Result := '{"success":false}';
end;
var credential: ISuperObject;
begin
try
with TStringList.Create do
try
LoadFromFile('D:\CredVerify_delphi.json');
credential := SO( Text );
finally
Free;
end;
Writeln( doVerifyCred(credential) );
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
The cbor and fido2 projects can be found on github.
I actually have a problem with the CBOR encoded attestationObject returned. If the resident key property is set the
attestation object is only 63 bytes long - and there are bytes left that are actually not encoded. So...
The cbor decoding there either fails or I get data back that does not conform the webauthn attestation object which should at
these positions return the credential id and the public key (which is then also cbor encoded). If the resident key property is false
as this is the case in the above statement the fido dll returns bad auth data. So... anyone has a clue what I'm doing wrong?
It should basically look like in the diagram but it ends either after 63 bytes which is in the mids of the credential id or it fails in the dll.

Turned out I misused the indy base64 routines. The base64 decoder did not work properly for a Unicodstring (I assumed that the string was converted to an ansistring...)
So instead I use the following decoder:
function Base64Decode( s : string ) : RawByteString;
var aWrapStream : TWrapMemoryStream;
sconvStr : UTF8String;
lStream : TMemoryStream;
begin
sConvStr := UTF8String( s );
aWrapStream := TWrapMemoryStream.Create( #sConvStr[1], Length(sConvStr) );
lStream := TMemoryStream.Create;
try
with TIdDecoderMIME.Create(nil) do
try
DecodeBegin(lStream);
Decode( aWrapStream );
DecodeEnd;
SetLength(Result, lStream.Size );
if lStream.Size > 0 then
Move( PByte(lStream.Memory)^, Result[1], lStream.Size);
finally
Free;
end;
finally
lStream.Free;
end;
aWrapStream.Free;
end;

Related

How to clear a String from memory properly?

I have followed those questions and answers to try to clear a String from memory:
Delphi : how to completely remove String from the memory
Creating string from byte array
In my case I did something like this:
function generateEncryption(Textdata : String) : String;
var
SaltB, IVb, Padtext, keybytes, datas, SaltIVMessage : TBytes;
Cipher: TDCP_rijndael;
keysize, TextDatasize, saltivmsglen : Integer;
OutData, keygen : String;
begin
keygen := 'MyTargetedKey';
keybytes := TEncoding.UTF8.GetBytes(keygen);
Cipher := TDCP_rijndael.Create(nil);
try
cipher.CipherMode := cmCBC;
cipher.Algorithm := EncryptionType;
cipher.BlockSize := 16;
Cipher.Init(keybytes[0], keysize, #IVb[0]);
TextDatasize := high(PadText) + 1;
SetLength(datas, TextDatasize);
Cipher.EncryptCBC(PadText[0], datas[0], TextDatasize);
saltivmsglen := Length(SaltB) + Length(IVb) + Length(datas);
SetLength(SaltIVMessage, saltivmsglen);
SaltIVMessage := AddSaltIVMessage(SaltB, IVb, datas);
OutData := TNetEncoding.Base64.EncodeBytesToString(SaltIVMessage);
Result := OutData;
finally
cipher.Burn;
cipher.Free;
// No success
FillChar(keybytes, Sizeof(keybytes), 0);
// No success either
//ZeroMemory(#keybytes[0], Length(keybytes));
//FillChar(keybytes, Length(keybytes), 0);
//keybytes := nil;
// Still no success
//UniqueString(keygen);
//ZeroMemory(Pointer(keygen), Length(keygen)*SizeOf(Char));
//keygen:='';
end;
end;
I used the GitHub repository "strings2" to extract the String from the executable to read its Strings in the memory. I can see MyTargetedKey as following:
:TNetEncoding.:2
System.NetEncoding
0123456789ABCDEF
MyTargetedKey
In my code I tried different things without success (see comments). What is the proper way to totally clear a String from memory to avoid the capture of my app key, or how to make it harder to capture it?

Delphi DataSnap upload xml file in body

in delphi 10 with the Datasnap component I am trying to declare a Post method that receives an XML file but I can't.
Does anybody know if Datasnap only can receive Json format type in the body?
(in contrary any example will be great)
Thanks in advance.
You can overcome this with your datasnap WebModuleUnit and your custom ufileUploader unit like this :
In WebModuleUnit :
procedure TWebModule1.WebModuleDefaultAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
if Request.InternalPathInfo.StartsWith('/UploadFile') then
Response.Content := ufileUploader.UploadFile(Request)
else if (Request.InternalPathInfo = '') or (Request.InternalPathInfo = '/')
then
Response.Content := ReverseString.Content
else
Response.SendRedirect(Request.InternalScriptName + '/');
end;
in your ufileUploader unit :
unit ufileUploader;
interface
uses Web.HTTPApp;
function UploadFile(ARequest: TWebRequest): string;
implementation
uses System.SysUtils, System.Classes, Web.ReqMulti;
function UploadFile(ARequest: TWebRequest): string;
const
DestPath = 'c:\';
var
i: integer;
LFileName: string;
LStream: TMemoryStream;
begin
if not TMultipartContentParser.CanParse(ARequest) then
begin
Result := 'Cannot parse request';
Exit;
end;
if ARequest.Files.Count < 1 then
begin
Result := 'No file sended';
Exit;
end;
LStream := TMemoryStream.Create;
try
// You have sended ARequest.Files.Count files
for i := 0 to ARequest.Files.Count - 1 do
begin
LFileName := string(ARequest.Files.Items[i].FileName);
LStream.Clear;
// Read the sended file stream
LStream.CopyFrom(ARequest.Files.Items[i].Stream,
ARequest.Files.Items[i].Stream.Size);
LStream.Position := 0;
// Do what you want with the stream
LStream.SaveToFile(DestPath + LFileName);
end;
Result := Format('%d files saved to %s ', [ARequest.Files.Count, DestPath]);
finally
FreeAndNil(LStream);
end;
end;
end.
This is not the answer you are hoping for, but I opened a case with Embarcadero for this exact problem and their response was:
Hello
My name is Steve Axtell. I am looking at this case.
I am sorry but Datasnap does not support XML. It only supports JSON,
hence the error message.
Regards
Steve Axtell Embarcadero Support ref:_00D30HwR._5005a28Y6yq:ref
The problem is in Datasnap.DSService.TDSRESTService.ProcessParameters:
// Look for more parameters in the body
if (Content <> nil) and (Length(Content) > 0) then
begin
if LBody = nil then
begin
LBodyArray := nil;
LBody := TJSONObject.ParseJSONValue(Content, 0);
LFreeBody := LBody;
if LBody = nil then
begin
//ParamArray.Free;
raise TDSServiceException.Create(SNoJSONValue);
end;
if (LBody is TJSONObject) and (TJSONObject(LBody).Count = 1) and
(TJSONObject(LBody).Pairs[0].JSonString.Value = PARAMETERS_PAIR) and
(TJSONObject(LBody).Pairs[0].JsonValue is TJSONArray) then
begin
LBodyArray := TJSONArray(TJSONObject(LBody).Pairs[0].JsonValue);
LBodyArrayIndex := 0;
end
end;
end;
If the body is not in JSON, it fails to process the REST request, and I've not found a way to force DataSnap to not look in the body for additional parameters.
Note that in my case, I'm not using TComponent but TDataModule for the server methods.

Trying to get Webauthn running

I've started a Fido2/WebAuthn project a while ago and tried to get microsofts webauthn implementation starting. For this in this project there exists a
translation of the webauthn.h file
(as today I only found refernces to this file in Mozilla and Chromium browsers code...).
Now ... I simply tried to create form with a button issuing a command to create credentials but
this call fails miserably with an access violation # $0000EA60 and I have no clue what that might cause. What might I do wrong???
Here the code for a button onClick handler.
uses Webauthn;
// just a test JSON object that I obtained from a browser request
const cClientData : UTF8String = '{' +
'"hashAlgorithm": "SHA-256",' +
'"challenge": "fzjg31IEKi6ZxKqsQ9S_XHG9WvdmcXPah5EXd11p1bU",' +
'"origin": "https:\/\/fidotest.com",' +
'"clientExtensions": {},' +
'"type": "webauthn.create"' +
'}';
procedure TfrmWebAuthnTest.btnCredentialClick(Sender: TObject);
var RpInformation : TWebAuthnRPEntityInformation; // _In_
UserInformation : TWebAuthUserEntityInformation; // _In_
PubKeyCredParams : TWebauthnCoseCredentialParameters; // _In_
WebAuthNClientData : TWebAuthnClientData; // _In_
WebAuthNMakeCredentialOptions : TWebAuthnAuthenticatorMakeCredentialOptions; // _In_opt_
pWebAuthNCredentialAttestation : PWEBAUTHN_CREDENTIAL_ATTESTATION; // _Outptr_result_maybenull_
hr : HRESULT;
coseParams : Array[0..1] of WEBAUTHN_COSE_CREDENTIAL_PARAMETER;
i : integer;
challenge : Array[0..31] of byte;
cancellationID : TGuid;
bufClientData : UTF8String;
begin
// ################################################
// #### relying party
FillChar(RpInformation, sizeof(RpInformation), 0);
RpInformation.dwVersion := WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION;
RpInformation.pwszId := 'fidotest.com';
RpInformation.pwszName := 'Sweet home localhost';
RpInformation.pwszIcon := nil;
// ################################################
// #### user information
FillChar(UserInformation, sizeof(UserInformation), 0);
UserInformation.dwVersion := WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION;
UserInformation.cbId := sizeof( challenge );
Randomize;
// create credentials
for i := 0 to Length(challenge) - 1 do
begin
challenge[i] := Byte( Random(High(byte) + 1) );
end;
UserInformation.pbId := #challenge[0];
UserInformation.pwszName := 'Mike';
UserInformation.pwszIcon := niL;
UserInformation.pwszDisplayName := 'Mike Rabat';
// ################################################
// #### Client data
bufClientData := Copy( cClientData, 1, Length(cClientData));
FillChar(WebAuthNClientData, sizeof(WebAuthNClientData), 0);
WebAuthNClientData.dwVersion := WEBAUTHN_CLIENT_DATA_CURRENT_VERSION;
WebAuthNClientData.cbClientDataJSON := Length(cClientData);
WebAuthNClientData.pbClientDataJSON := PAnsiChar(bufClientData);
WebAuthNClientData.pwszHashAlgId := WEBAUTHN_HASH_ALGORITHM_SHA_256;
// ################################################
// #### pub ked credential params
PubKeyCredParams.cCredentialParameters := sizeof(coseParams);
PubKeyCredParams.pCredentialParameters := #coseParams[0];
coseParams[0].dwVersion := WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION;
coseParams[0].pwszCredentialType := WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
coseParams[0].lAlg := WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256;
coseParams[1].dwVersion := WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION;
coseParams[1].pwszCredentialType := WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
coseParams[1].lAlg := WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256;
// ###########################################
// #### Fill in params
FillChar(WebAuthNMakeCredentialOptions, sizeof(WebAuthNMakeCredentialOptions), 0);
WebAuthNMakeCredentialOptions.dwVersion := WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION;
WebAuthNMakeCredentialOptions.dwTimeoutMilliseconds := 60000;
WebAuthNMakeCredentialOptions.bRequireResidentKey := False;
WebAuthNMakeCredentialOptions.dwAuthenticatorAttachment := WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
WebAuthNMakeCredentialOptions.dwUserVerificationRequirement := WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
WebAuthNMakeCredentialOptions.dwAttestationConveyancePreference := WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
// ###########################################
// #### Cancellation
assert( WebAuthNGetCancellationId(cancellationID) = S_OK, 'Cancellation ID failed');
WebAuthNMakeCredentialOptions.pCancellationId := #cancellationID;
// ###########################################
// #### do the magic
pWebAuthNCredentialAttestation := nil;
hr := WebAuthNAuthenticatorMakeCredential( Handle,
#RpInformation,
#UserInformation,
#PubKeyCredParams,
#WebAuthNClientData,
#WebAuthNMakeCredentialOptions,
pWebAuthNCredentialAttestation );
if hr = S_OK then
begin
// WriteCredAttest( pWebAuthNCredentialAttestation );
WebAuthNFreeCredentialAttestation( pWebAuthNCredentialAttestation );
memLog.Lines.Add('Finished');
end
else
begin
memLog.Lines.Add('Make Cred failed with: ' + WebAuthNGetErrorName( hr ));
end;
end;
I'm using Delphi2010 so all strings should be unicode except the JSON client data string.
After lengthy study of the C++ code of the Mozilla Browser I think I found the problem.
It was in the size field of the COSE_PARAMS structure.
// #### pub ked credential params
PubKeyCredParams.cCredentialParameters := Length(coseParams);// sizeof(coseParams);
PubKeyCredParams.pCredentialParameters := #coseParams[0];
Instead of the size in bytes they seem to expect the length of the array of the
attached coseParams. That misunderstanding led to the AV.

Delphi Can I have AutoLogin to shared maps on a server using TOpenDialog

A program running on computer "A" wants to download a file "F" in map "M" from computer "B".
I use the following preparaions:
dlgSelectImportFile.InitialDir := '\\192.168.1.59';
dlgSelectImportFile.Options :=[ofOldStyleDialog];
As computer "B" wants authorization I get the Windows-Security-dialog.
I want to avoid this by giving the correct username and password automatically.
I guess there is something in the API of this dialog that could help here but I have not found anything.
You can store the credentials in the Credential Vault, here is some sample code (uses Jedi Apilib):
procedure StoreCredentials(const Server: String; const Username: String; const Password: String);
var
CredTargetInfo: CREDENTIAL_TARGET_INFORMATION;
Creds: CREDENTIAL;
CredType: DWORD;
bRes: Boolean;
LastError: DWORD;
begin
CredType := CRED_TYPE_DOMAIN_PASSWORD;
ZeroMemory(#CredTargetInfo, sizeof(CredTargetInfo));
CredTargetInfo.TargetName := PChar(Server);
CredTargetInfo.CredTypeCount := 1;
CredTargetInfo.CredTypes := #CredType;
ZeroMemory(#Creds, sizeof(Creds));
Creds.TargetName := PChar(Server);
Creds.Type_ := CRED_TYPE_DOMAIN_PASSWORD;
Creds.CredentialBlobSize := ByteLength(Password);
Creds.CredentialBlob := PByte(PChar(Password));
Creds.UserName := PChar(Username);
Creds.Persist := CRED_PERSIST_ENTERPRISE;
bRes := CredWriteDomainCredentials(#CredTargetInfo,#Creds, 0);
if bRes then
begin
DbgOut('Successfully stored %s Credentials for %s', [Username, Server]);
end
else begin
LastError := GetLastError;
DbgOut('CredWriteDomainCredentials failed with %d (%s)', [LastError, SysErrorMessage(LastError)]);
end;
end;
Here's an example how to delete stored credentials:
procedure DeleteCredentials;
var
Count: DWORD;
Creds: PPCredentialArray;
i: Cardinal;
bRes: Boolean;
begin
DbgOut('Deleting Old Credentials');
if not CredEnumerate(nil, 0, Count, PCREDENTIAL(Creds)) then
Exit;
DbgOut('Found %d old credentials', [Count]);
try
for i := Count-1 downto 0 do
begin
bRes := CredDelete(Creds^[i]^.TargetName, Creds^[i]^.Type_, 0);
DbgOut('Deleting credential %d (%s to %s) returned %s', [i, Creds^[i]^.UserName, Creds^[i]^.TargetName, BoolToStr(bRes, True)]);
end;
finally
CredFree(Creds);
end;
end;
Note: the sample code is copy/pasted off an old project so things like DbgOut can simply be removed. The sample code was for a case where an Active Directory domain was used, it might need some changes for non domain situation.

Transfer of Large Stream from server to client fails

All,
I am working on a new datasnap project based on the example project located in C:\Users\Public\Documents\Embarcadero\Studio\18.0\Samples\Object Pascal\DataSnap\FireDAC_DBX.
I am trying to transfer a large stream (1,606,408 bytes) from datasnap server to client. I am running into what appears to be a common issue and that is that the entire stream does not make it to the client.
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): TStringStream;
begin
Result := TStringStream.Create;
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(Result, TFDStorageFormat.sfBinary);
Result.Position := 0;
// Result.SaveToFile('output.adb');
except
raise;
end;
end;
Here is my client code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
LStringStream := TStringStream.Create(FDStoredProcBPInfo.ParamByName('ReturnValue').asBlob);
//LStringStream.Clear;
//LStringStream.LoadFromFile('Output.adb');
try
if LStringStream <> nil then
begin
LStringStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(LStringStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
LStringStream.Free;
end;
end;
You will see the stream save and load code; that is how I determined that the server was getting the entire result set into the stream, and that the client could handle the entire result set and display it properly.
So smaller streams transfer just fine, but this big one, when examined in the ide debugger, does not start with the 65,66,68,83 characters and the load fails with the error, '[FireDAC][Stan]-710. Invalid binary storage format'.
I know from extended Googling that there are work-arounds for this, but I do not understand how to apply the workarounds to my case, with the use of Tfdstoredproc and TfdSchemaAdaptor components. I'm trying to stay with this coding scheme.
How do I adapt this code to correctly receive large streams?
Update 1:
Ok, I tried strings and Base64 encoding. It didn't work.
Client Code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
TempStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
try
TempStream := TStringStream.Create;
TIdDecoderMIME.DecodeStream(FDStoredProcBPInfo.ParamByName('ReturnValue').asString,TempStream);
if TempStream <> nil then
begin
TempStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(TempStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
TempStream.Free;
end;
end;
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): String;
var
TempStream : TMemoryStream;
OutputStr : String;
begin
Result := '';
TempStream := TMemoryStream.Create;
try
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(TempStream, TFDStorageFormat.sfBinary);
TempStream.Position := 0;
OutputStr := IdEncoderMIMEBPInfo.EncodeStream(TempStream);
Result := OutputStr
except
raise;
end;
finally
TempStream.Free;
end;
end;
The result is the same.

Resources