首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何验证yubico otp响应

如何验证yubico otp响应
EN

Stack Overflow用户
提问于 2022-04-09 14:29:11
回答 2查看 288关注 0票数 0

我想在我的应用程序中使用yubico OTP作为第二个因素。Yubico文档:https://developers.yubico.com/OTP/

下面是一个c#(.net 6)示例,它通过控制台读取OTP (您需要按used上的按钮,然后将otp用作rest服务请求的参数)。此示例基于版本2.0或验证服务(https://api.yubico.com/wsapi/2.0/verify)

代码语言:javascript
复制
using System.Security.Cryptography;

//Sample for validating OTP based on https://developers.yubico.com/OTP/OTPs_Explained.html
//Sample request: https://api.yubico.com/wsapi/2.0/verify?otp=vvvvvvcucrlcietctckflvnncdgckubflugerlnr&id=87&timeout=8&sl=50&nonce=askjdnkajsndjkasndkjsnad

// The yubico api clientid.
// You can open an api key here: https://upgrade.yubico.com/getapikey/
string yubicoCredentialClientId = "87";
// This is currently not required. Should be used to verify the response but its unclear whether this is possible or not.
// string yubicoCredentionPrivateKey = "";

string yubikeyValidationUrl = $"https://api.yubico.com/wsapi/2.0/verify?";
string nonce = "";

//Create a nonce
using (var random = RandomNumberGenerator.Create())
{
    var tmpNonce = new byte[16];
    random.GetBytes(tmpNonce);
    nonce = BitConverter.ToString(tmpNonce).Replace("-", "");
}

//Get the OTP from yubikey
System.Console.WriteLine("Press yubikey button and then enter");
var otp = Console.ReadLine();
System.Console.WriteLine(otp);
string validationParameter = $"otp={otp}&id={yubicoCredentialClientId}&nonce={nonce}";

HttpClient client = new HttpClient();
var url = $"{yubikeyValidationUrl}{validationParameter}";
System.Console.WriteLine(url);
var result = client.GetAsync(url).Result;

System.Console.WriteLine(result.StatusCode);
string respnse = result.Content.ReadAsStringAsync().Result;
System.Console.WriteLine(respnse);
if (respnse.ToLower().Contains("status=ok"))
    System.Console.WriteLine("OTP succsessful validated");
else
    System.Console.WriteLine("OTP invalid");

当我使用由yubikey生成的有效OTP时,所有这些都可以正常工作,甚至返回status=OK作为响应的一部分。

问:我是否可以使用yubico私钥来验证响应?如果不是,这种身份验证似乎容易受到中间攻击的人的攻击。

附带问题:请求需要一个api id,我甚至通过https://upgrade.yubico.com/getapikey/创建了一个id,但是我可以只使用任何id,请求都是一样的。这是故意的吗?如果是,首先这个id参数的意义是什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-04-09 17:58:32

实际上,这里有一些文档:https://developers.yubico.com/OTP/Specifications/OTP_validation_protocol.html

必须为参数创建一个hmac-sha1 1,然后必须将此签名作为附加参数添加。

代码语言:javascript
复制
//Create the signature based on https://developers.yubico.com/OTP/Specifications/OTP_validation_protocol.html
//Prepare the parameters to be signed (Ordered alphabetically)
string signatureParameters = $"id={yubicoCredentialClientId}&nonce={nonce}&otp={otp}";

//Create the key based on the api key string
byte[] base64AsByte = Convert.FromBase64String(yubicoCredentionPrivateKey);

string signature = "";
using (var hmac = new HMACSHA1(base64AsByte))
{
    //Create the hmacsha1
    var signatureAsByte = hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureParameters));
    signature = Convert.ToBase64String(signatureAsByte);
}
//Add the signature 
signatureParameters+=$"&h={signature}";

然后,这样一个url看起来如下(签名是h参数的一部分):

代码语言:javascript
复制
https://api.yubico.com/wsapi/2.0/verify?id=42&nonce=5FB3D5377640BA3FB8955AF98D6B71EC&otp=foobar&h=XXVw+vqc3k//qFGG6+WbP96xXis=

完整示例

下面是一个完整的自包含示例,如何在.net应用程序中使用Yubikey OTP (包括签名的验证)

执行下列步骤:

创建请求key的参数

    • 创建一个当前的
    • 从yubikey
    • 获取OTP使用API
    • 签署参数

status

  • Compare

  • 调用从yubico

  • Check otp
  • 检查返回的服务,返回带有构建signature

的返回签名。

代码语言:javascript
复制
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

//Sample for validating OTP based on https://developers.yubico.com/OTP/OTPs_Explained.html
//Sample request: "https://api.yubico.com/wsapi/2.0/verify?id=87&nonce=44D4185490BA8E77E58A38A98CF501E9&otp=cccccxxxvulhlletkijhrtifrintlerfbnbhtdnikl&h=f9Ht4a08iaFQYQBI5E0XUni3Pss="
//Sample response: h=TC/RXXcVqPWkFr4JPlf29nWEnig=\r\nt=2022-04-09T18:58:34Z0336\r\notp=ccxxxxxtbbvulhlletkijhrtifrintlerfbnbhtdnikl\r\nnonce=44D41854DDDA8E77E58A38A98CF501E9\r\nsl=100\r\nstatus=OK\r\n\r\n"

// The yubico api clientid. You can open an api key here: https://upgrade.yubico.com/getapikey/
string yubicoApiClientId = "REPLACEWITHCLIENTID";
// This is currently not required. 
string yubicoApiPrivateKey = "REPLACEWITHAPIKEY";
string yubikeyValidationUrl = $"https://api.yubico.com/wsapi/2.0/verify?";
string nonce = "";

//Create the key based on the api key string
byte[] privateKey = Convert.FromBase64String(yubicoApiPrivateKey);

//Create a nonce
using (var random = RandomNumberGenerator.Create())
{
    var tmpNonce = new byte[16];
    random.GetBytes(tmpNonce);
    nonce = BitConverter.ToString(tmpNonce).Replace("-", "");
}

//Get the OTP from yubikey (usb stick)
System.Console.WriteLine("Press yubikey button");
var otp = Console.ReadLine();

//Create the signature based on https://developers.yubico.com/OTP/Specifications/OTP_validation_protocol.html
//Prepare the parameters to be signed (Ordered alphabetically)
string verifyParameters = $"id={yubicoApiClientId}&nonce={nonce}&otp={otp}";

string signature = "";
using (var hmac = new HMACSHA1(privateKey))
{
    //Create the hmacsha1
    var signatureAsByte = hmac.ComputeHash(Encoding.UTF8.GetBytes(verifyParameters));
    signature = Convert.ToBase64String(signatureAsByte);
}
//Add the signature 
verifyParameters += $"&h={signature}";

HttpClient client = new HttpClient();
var url = $"{yubikeyValidationUrl}{verifyParameters}";
System.Console.WriteLine(url);
var result = client.GetAsync(url).Result;

System.Console.WriteLine($"http statuscode: {result.StatusCode}");
string response = result.Content.ReadAsStringAsync().Result;
System.Console.WriteLine(response);
Match m = Regex.Match(response, "status=\\w*", RegexOptions.IgnoreCase);
if (m.Success)
    Console.WriteLine($"OTP Status: {m.Value}");

//Verify signature based on https://developers.yubico.com/OTP/Specifications/OTP_validation_protocol.html
//The response contains a signature (h parameter) which was signed with the same private key
//This means we can just calculate the hmacsha1 again (Without the h parameter and with ordering of the parameter)
//and then compare the returned signature with the created siganture
var lines = response.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None).ToList();
var returnedSignature = String.Empty;

string returnParameterToCheck = String.Empty;
foreach (var item in lines.OrderBy(x => x))
{
    if (!string.IsNullOrEmpty(item) && !item.StartsWith("h="))
        returnParameterToCheck += $"&{item}";

    if (!string.IsNullOrEmpty(item) && item.StartsWith("h="))
        returnedSignature = item.Replace("h=", "");
}
//Remove the first unnecessary '&' character
returnParameterToCheck = returnParameterToCheck.Remove(0, 1);

var signatureToCompare = String.Empty;
using (var hmac1 = new HMACSHA1(privateKey))
{
    signatureToCompare = Convert.ToBase64String(hmac1.ComputeHash(Encoding.UTF8.GetBytes(returnParameterToCheck)));
}

if (returnedSignature == signatureToCompare)
    System.Console.WriteLine("Signatures are equal");
else
    System.Console.WriteLine("Signatures are not equal");
票数 0
EN

Stack Overflow用户

发布于 2022-05-30 22:10:14

(我显然没有足够的声誉,所以我只允许发表“答案”)

@Manuel

我用不同的语言在网络上看到了这个例子,但是没有一个对我来说是正确的。

无论我使用什么物理密钥,返回状态总是status=OK

我可以访问由50个yubikeys 5 nfc组成的框,如果我使用您的例子,状态将是可以的。

如果我篡改了ID,我会得到类似NO_SUCH_CLIENTBAD_SIGNATURE等的响应。

因此,某些参数匹配是很重要的,但是实际的OTP并不是其中的一部分。

我可以在https://upgrade.yubico.com/getapikey注册一个ID和秘密

在您的代码中进行验证,它将是status=OK

然后,抓住一个全新的yubikey,并尝试它和状态将仍然是好的。

我试着用我的ID和秘密验证我的同事的一个yubikey,你可以猜到,status=OK

所以我唯一能证明的就是我拥有“尤比基”。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71808961

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档