Delphi 使用fido2.dll对Yubico进行Webauthn凭据验证

Delphi 使用fido2.dll对Yubico进行Webauthn凭据验证,delphi,webauthn,yubico,cbor,Delphi,Webauthn,Yubico,Cbor,我开始在Delphi中连接yubicos fido2.dll,并能够连接它 根据提供的示例。现在我想更进一步,使用 例如apache服务器上的dll,用于处理凭据创建和断言 所以。。为此,我基本上使用了测试站点上的javascripts 基本上,我想模拟一些服务器功能来创建凭证。在网站上可以设置 一些属性-在我的环境中,它们来自服务器 目前,我已经与客户机进行了通信,以发出凭证初始化-服务器响应,并发出质疑。查询密钥,浏览器创建凭据并将其发送回服务器。虽然在这里,我对来自服务器的数据有问题,也就

我开始在Delphi中连接yubicos fido2.dll,并能够连接它 根据提供的示例。现在我想更进一步,使用 例如apache服务器上的dll,用于处理凭据创建和断言

所以。。为此,我基本上使用了测试站点上的javascripts

基本上,我想模拟一些服务器功能来创建凭证。在网站上可以设置 一些属性-在我的环境中,它们来自服务器

目前,我已经与客户机进行了通信,以发出凭证初始化-服务器响应,并发出质疑。查询密钥,浏览器创建凭据并将其发送回服务器。虽然在这里,我对来自服务器的数据有问题,也就是说,我有一个 解码DetectionObject部分时出现问题

下面是来自我的服务器的凭证init json:

{"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"}}
以及来自服务器的结果:

{"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"}}
以下是应验证凭据的控制台程序: (注意,您需要将上述内容粘贴到文本文件中,并将其加载到控制台中)

程序验证cred;
{$APPTYPE控制台}
使用
SysUtils,
“..\Fido2.pas”中的Fido2,
“..\Fido2dll.pas”中的Fido2dll,
“..\Fido2Json.pas”中的Fido2Json,
窗户,
班级,
cbor,
超客体;
函数DoVerifyCred(凭证:ISuperObject):字符串;
var clientData:ISuperObject;
s:字符串;
rawS:RawByteString;
证书:字符串;
rawId:TBytes;
验证:TFidoCredVerify;
cborItem:TCborMap;
sig:TBytes;
x5c:t字节;
authData:TBytes;
fmt:字符串;
alg:整数;
i:整数;
aName:字符串;
res:布尔型;
rawChallange:RawByteString;
credFMT:tfidoccredentialfmt;
挑战:挑战;
authDataObj:TAuthData;
ATTSMT:TCborMap;
j:整数;
restBuf:TBytes;
开始
结果:='{“错误”:0,“消息”:“分析内容时出错”}';
s:=credential.s['response.clientDataJSON'];
如果s='',那么
出口
ClientData:=So(字符串(Base64URLDecode));
如果clientData=nil,则
出口
rawChallange:=Base64URLDecode(ClientData.S['challenge']);
如果长度(rawChallange)大小(挑战),则
出口
移动(rawChallange[1],挑战,sizeof(挑战));
clientData:=SO(字符串(Base64URLDecode(credential.S['response.clientDataJSON']));
s:=credential.s['response.detectionobject'];
如果s='',那么
出口
//证明对象是cbor编码的原始base64ulr编码字符串
cborItem:=TCborDecoding.DecodeBase64UrlEx(s,restBuf)作为TCborMap;
如果未分配(cborItem),则
出口
尝试
alg:=0;
fmt:='';
sig:=nil;
authData:=nil;
x5c:=零;
对于i:=0到cborItem.Count-1 do
开始
assert(cborItem.Names[i]是TCborUtf8String,'CBOR type error');
aName:=字符串((cborItem.Names[i]作为TCborUtf8String.Value);
如果SameText(aName,'attStmt'),那么
开始
ATTSMT:=cborItem.Values[i]作为TCborMap;
对于j:=0到attStmt.Count-1 do
开始
aName:=字符串((attStmt.Names[j]作为TCborUtf8String.Value);
如果SameText(aName,'alg')
然后
alg:=(ATTSMT.Values[j]作为TCBORNAGINTIEM.value
否则如果SameText(aName,'sig')
然后
sig:=(attStmt.Values[j]作为TCborByteString).ToBytes
如果SameText(aName,'x5c'),则为else
然后
x5c:=((attStmt.Values[j]作为TCborArr)[0]作为TCborByteString)。ToBytes
结束;
结束
如果SameText(aName,'authData')
然后
authData:=(cborItem.Values[i]作为TCborByteString).ToBytes
如果SameText(aName,'fmt')
然后
fmt:=字符串((cborItem.Values[i]作为TCborUtf8String.Value);
结束;
最后
免费;
结束;
//检查是否有任何东西到位
如果不是((alg=COSE_ES256)或(alg=COSE_EDDSA)或(alg=COSE_RS256)),则
引发异常。创建('未知算法');
如果长度(sig)=0,则
引发异常。创建('未提供sig字段');
如果长度(x5c)=0,则
引发异常。创建('无证书');
如果长度(authData)=0,则
引发异常。创建('缺少authdata');
credentialId:=凭证.S['id'];
s:=credential.s['rawId'];
如果s='',那么
引发异常。创建('未找到凭据id');
raws:=Base64URLDecode;
设置长度(rawId,长度(rawS));
移动(rawS[1],rawId[0],长度(rawId));
authDataObj:=nil;
如果长度(restBuf)>0,则
rease Exception.Create('Damend存在一个不应该存在的rest缓冲区');
如果长度(authDAta)>0,则
authDataObj:=TAuthData.Create(authData);
尝试
如果fmt=‘打包’
然后
credFmt:=fmFido2
如果fmt='fido-u2f'
然后
credFmt:=fmU2F
其他的
credFmt:=fmDef;
//现在。。。验证凭据
credVerify:=TfidCredVerify.Create(TfidCredentialType(alg)、credFmt、,
authData、x5c、sig、FidoServer.RequireResidentKey、,
authDataObj.UserVerified,0);
尝试
//验证数据似乎不好!
res:=确认验证(质询);
如果是的话
开始
//->将所有内容保存到数据库中
结束;
最后
免费的;
结束;
最后
authDataObj.Free;
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.

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;