diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs
deleted file mode 100644
index bfdf786f..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Node.cs
+++ /dev/null
@@ -1,1612 +0,0 @@
-//+-------------------------------------------------------------------------------+
-//| Copyright (c) 2003 Liping Dai. All rights reserved. |
-//| Web: www.lipingshare.com |
-//| Email: lipingshare@yahoo.com |
-//| |
-//| Copyright and Permission Details: |
-//| ================================= |
-//| Permission is hereby granted, free of charge, to any person obtaining a copy |
-//| of this software and associated documentation files (the "Software"), to deal |
-//| in the Software without restriction, including without limitation the rights |
-//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the |
-//| Software, subject to the following conditions: |
-//| |
-//| 1. Redistributions of source code must retain the above copyright notice, this|
-//| list of conditions and the following disclaimer. |
-//| |
-//| 2. Redistributions in binary form must reproduce the above copyright notice, |
-//| this list of conditions and the following disclaimer in the documentation |
-//| and/or other materials provided with the distribution. |
-//| |
-//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, |
-//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
-//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
-//| A PARTICULAR PURPOSE. |
-//+-------------------------------------------------------------------------------+
-
-
-using System;
-using System.IO;
-using System.Collections;
-using System.Text;
-
-namespace LipingShare.LCLib.Asn1Processor
-{
- ///
- /// IAsn1Node interface.
- ///
- public interface IAsn1Node
- {
- ///
- /// Load data from Stream.
- ///
- ///
- /// true:Succeed; false:failed.
- bool LoadData(Stream xdata);
-
- ///
- /// Save node data into Stream.
- ///
- /// Stream.
- /// true:Succeed; false:failed.
- bool SaveData(Stream xdata);
-
- ///
- /// Get parent node.
- ///
- Asn1Node ParentNode { get; }
-
- ///
- /// Add child node at the end of children list.
- ///
- /// Asn1Node
- void AddChild(Asn1Node xdata);
-
- ///
- /// Insert a node in the children list before the pointed index.
- ///
- /// Asn1Node
- /// 0 based index.
- int InsertChild(Asn1Node xdata, int index);
-
- ///
- /// Insert a node in the children list before the pointed node.
- ///
- /// Asn1Node that will be instered in the children list.
- /// Index node.
- /// New node index.
- int InsertChild(Asn1Node xdata, Asn1Node indexNode);
-
- ///
- /// Insert a node in the children list after the pointed index.
- ///
- /// Asn1Node
- /// 0 based index.
- /// New node index.
- int InsertChildAfter(Asn1Node xdata, int index);
-
- ///
- /// Insert a node in the children list after the pointed node.
- ///
- /// Asn1Node that will be instered in the children list.
- /// Index node.
- /// New node index.
- int InsertChildAfter(Asn1Node xdata, Asn1Node indexNode);
-
- ///
- /// Remove a child from children node list by index.
- ///
- /// 0 based index.
- /// The Asn1Node just removed from the list.
- Asn1Node RemoveChild(int index);
-
- ///
- /// Remove the child from children node list.
- ///
- /// The node needs to be removed.
- ///
- Asn1Node RemoveChild(Asn1Node node);
-
- ///
- /// Get child node count.
- ///
- long ChildNodeCount { get; }
-
- ///
- /// Retrieve child node by index.
- ///
- /// 0 based index.
- /// 0 based index.
- Asn1Node GetChildNode(int index);
-
- ///
- /// Get descendant node by node path.
- ///
- /// relative node path that refer to current node.
- ///
- Asn1Node GetDescendantNodeByPath(string nodePath);
-
- ///
- /// Get/Set tag value.
- ///
- byte Tag{ get; set; }
-
- ///
- /// Get tag name.
- ///
- string TagName{ get; }
-
- ///
- /// Get data length. Not included the unused bits byte for BITSTRING.
- ///
- long DataLength{ get; }
-
- ///
- /// Get the length field bytes.
- ///
- long LengthFieldBytes{ get; }
-
- ///
- /// Get data offset.
- ///
- long DataOffset{ get; }
-
- ///
- /// Get unused bits for BITSTRING.
- ///
- byte UnusedBits{ get; }
-
- ///
- /// Get/Set node data by byte[], the data length field content and all the
- /// node in the parent chain will be adjusted.
- ///
- byte[] Data { get; set; }
-
- ///
- /// Get/Set parseEncapsulatedData. This property will be inherited by the
- /// child nodes when loading data.
- ///
- bool ParseEncapsulatedData { get; set; }
-
- ///
- /// Get the deepness of the node.
- ///
- long Deepness { get; }
-
- ///
- /// Get the path string of the node.
- ///
- string Path{ get; }
-
- ///
- /// Get the node and all the descendents text description.
- ///
- /// starting node.
- /// line length.
- ///
- string GetText(Asn1Node startNode, int lineLen);
-
- ///
- /// Retrieve the node description.
- ///
- /// true:Return hex string only;
- /// false:Convert to more readable string depending on the node tag.
- /// string
- string GetDataStr(bool pureHexMode);
-
- ///
- /// Get node label string.
- ///
- ///
- ///
- /// SHOW_OFFSET
- /// SHOW_DATA
- /// USE_HEX_OFFSET
- /// SHOW_TAG_NUMBER
- /// SHOW_PATH
- ///
- /// string
- string GetLabel(uint mask);
-
- ///
- /// Clone a new Asn1Node by current node.
- ///
- /// new node.
- Asn1Node Clone();
-
- ///
- /// Clear data and children list.
- ///
- void ClearAll();
- }
-
- ///
- /// Asn1Node, implemented IAsn1Node interface.
- ///
- public class Asn1Node : IAsn1Node
- {
- private long lengthFieldBytes;
- private byte[] data;
- private ArrayList childNodeList;
- private const int indentStep = 3;
- private bool isIndefiniteLength = false;
- private bool parseEncapsulatedData = true;
-
- ///
- /// Default Asn1Node text line length.
- ///
- public const int defaultLineLen = 80;
-
- ///
- /// Minium line length.
- ///
- public const int minLineLen = 60;
-
- private Asn1Node(Asn1Node parentNode, long dataOffset)
- {
- Init();
- Deepness = parentNode.Deepness + 1;
- this.ParentNode = parentNode;
- this.DataOffset = dataOffset;
- }
-
- private void Init()
- {
- childNodeList = new ArrayList();
- data = null;
- DataLength = 0;
- lengthFieldBytes = 0;
- UnusedBits = 0;
- Tag = Asn1Tag.SEQUENCE | Asn1TagClasses.CONSTRUCTED;
- childNodeList.Clear();
- Deepness = 0;
- ParentNode = null;
- }
-
- private string GetHexPrintingStr(Asn1Node startNode, string baseLine,
- string lStr, int lineLen)
- {
- var nodeStr = "";
- var iStr = GetIndentStr(startNode);
- var dataStr = Asn1Util.ToHexString(data);
- if (dataStr.Length > 0)
- {
- if (baseLine.Length + dataStr.Length < lineLen)
- {
- nodeStr += baseLine + "'" + dataStr + "'";
- }
- else
- {
- nodeStr += baseLine + FormatLineHexString(
- lStr,
- iStr.Length,
- lineLen,
- dataStr
- );
- }
- }
- else
- {
- nodeStr += baseLine;
- }
- return nodeStr + "\r\n";
- }
-
- private string FormatLineString(string lStr, int indent, int lineLen, string msg)
- {
- var retval = "";
- indent += indentStep;
- var realLen = lineLen - indent;
- var sLen = indent;
- int currentp;
- for (currentp = 0; currentp < msg.Length; currentp += realLen)
- {
- if (currentp+realLen > msg.Length)
- {
- retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') +
- "'" + msg.Substring(currentp, msg.Length - currentp) + "'";
- }
- else
- {
- retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') + "'" +
- msg.Substring(currentp, realLen) + "'";
- }
- }
- return retval;
- }
-
- private string FormatLineHexString(string lStr, int indent, int lineLen, string msg)
- {
- var retval = "";
- indent += indentStep;
- var realLen = lineLen - indent;
- var sLen = indent;
- int currentp;
- for (currentp = 0; currentp < msg.Length; currentp += realLen)
- {
- if (currentp+realLen > msg.Length)
- {
- retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') +
- msg.Substring(currentp, msg.Length - currentp);
- }
- else
- {
- retval += "\r\n" + lStr + Asn1Util.GenStr(sLen,' ') +
- msg.Substring(currentp, realLen);
- }
- }
- return retval;
- }
-
-
- //PublicMembers
-
- ///
- /// Constructor, initialize all the members.
- ///
- public Asn1Node()
- {
- Init();
- DataOffset = 0;
- }
-
- ///
- /// Get/Set isIndefiniteLength.
- ///
- public bool IsIndefiniteLength
- {
- get
- {
- return isIndefiniteLength;
- }
- set
- {
- isIndefiniteLength = value;
- }
- }
-
- ///
- /// Clone a new Asn1Node by current node.
- ///
- /// new node.
- public Asn1Node Clone()
- {
- var ms = new MemoryStream();
- this.SaveData(ms);
- ms.Position = 0;
- var node = new Asn1Node();
- node.LoadData(ms);
- return node;
- }
-
- ///
- /// Get/Set tag value.
- ///
- public byte Tag { get; set; }
-
- ///
- /// Load data from byte[].
- ///
- /// byte[]
- /// true:Succeed; false:failed.
- public bool LoadData(byte[] byteData)
- {
- var retval = true;
- try
- {
- var ms = new MemoryStream(byteData);
- ms.Position = 0;
- retval = LoadData(ms);
- ms.Close();
- }
- catch
- {
- retval = false;
- }
- return retval;
- }
-
- ///
- /// Retrieve all the node count in the node subtree.
- ///
- /// starting node.
- /// long integer node count in the node subtree.
- public static long GetDescendantNodeCount(Asn1Node node)
- {
- long count =0;
- count += node.ChildNodeCount;
- for (var i=0; i
- /// Load data from Stream. Start from current position.
- /// This function sets requireRecalculatePar to false then calls InternalLoadData
- /// to complish the task.
- ///
- /// Stream
- /// true:Succeed; false:failed.
- public bool LoadData(Stream xdata)
- {
- var retval = false;
- try
- {
- RequireRecalculatePar = false;
- retval = InternalLoadData(xdata);
- return retval;
- }
- finally
- {
- RequireRecalculatePar = true;
- RecalculateTreePar();
- }
- }
-
- ///
- /// Call SaveData and return byte[] as result instead stream.
- ///
- ///
- public byte[] GetRawData()
- {
- var ms = new MemoryStream();
- SaveData(ms);
- var retval = new byte[ms.Length];
- ms.Position = 0;
- ms.Read(retval, 0, (int)ms.Length);
- ms.Close();
- return retval;
- }
-
- ///
- /// Get if data is empty.
- ///
- public bool IsEmptyData
- {
- get
- {
- if (data == null) return true;
- if (data.Length < 1)
- return true;
- else
- return false;
- }
- }
-
- ///
- /// Save node data into Stream.
- ///
- /// Stream.
- /// true:Succeed; false:failed.
- public bool SaveData(Stream xdata)
- {
- var retval = true;
- var nodeCount = ChildNodeCount;
- xdata.WriteByte(Tag);
- var tmpLen = Asn1Util.DERLengthEncode(xdata, (ulong) DataLength);
- if ((Tag) == Asn1Tag.BIT_STRING)
- {
- xdata.WriteByte(UnusedBits);
- }
- if (nodeCount==0)
- {
- if (data != null)
- {
- xdata.Write(data, 0, data.Length);
- }
- }
- else
- {
- Asn1Node tempNode;
- int i;
- for (i=0; i
- /// Clear data and children list.
- ///
- public void ClearAll()
- {
- data = null;
- Asn1Node tempNode;
- for (var i=0; i
- /// Add child node at the end of children list.
- ///
- /// the node that will be add in.
- public void AddChild(Asn1Node xdata)
- {
- childNodeList.Add(xdata);
- RecalculateTreePar();
- }
-
- ///
- /// Insert a node in the children list before the pointed index.
- ///
- /// Asn1Node
- /// 0 based index.
- /// New node index.
- public int InsertChild(Asn1Node xdata, int index)
- {
- childNodeList.Insert(index, xdata);
- RecalculateTreePar();
- return index;
- }
-
- ///
- /// Insert a node in the children list before the pointed node.
- ///
- /// Asn1Node that will be instered in the children list.
- /// Index node.
- /// New node index.
- public int InsertChild(Asn1Node xdata, Asn1Node indexNode)
- {
- var index = childNodeList.IndexOf(indexNode);
- childNodeList.Insert(index, xdata);
- RecalculateTreePar();
- return index;
- }
-
- ///
- /// Insert a node in the children list after the pointed node.
- ///
- /// Asn1Node
- /// Index node.
- /// New node index.
- public int InsertChildAfter(Asn1Node xdata, Asn1Node indexNode)
- {
- var index = childNodeList.IndexOf(indexNode)+1;
- childNodeList.Insert(index, xdata);
- RecalculateTreePar();
- return index;
- }
-
- ///
- /// Insert a node in the children list after the pointed node.
- ///
- /// Asn1Node that will be instered in the children list.
- /// 0 based index.
- /// New node index.
- public int InsertChildAfter(Asn1Node xdata, int index)
- {
- var xindex = index+1;
- childNodeList.Insert(xindex, xdata);
- RecalculateTreePar();
- return xindex;
- }
-
- ///
- /// Remove a child from children node list by index.
- ///
- /// 0 based index.
- /// The Asn1Node just removed from the list.
- public Asn1Node RemoveChild(int index)
- {
- Asn1Node retval = null;
- if (index < (childNodeList.Count - 1))
- {
- retval = (Asn1Node) childNodeList[index+1];
- }
- childNodeList.RemoveAt(index);
- if (retval == null)
- {
- if (childNodeList.Count > 0)
- {
- retval = (Asn1Node) childNodeList[childNodeList.Count-1];
- }
- else
- {
- retval = this;
- }
- }
- RecalculateTreePar();
- return retval;
- }
-
- ///
- /// Remove the child from children node list.
- ///
- /// The node needs to be removed.
- ///
- public Asn1Node RemoveChild(Asn1Node node)
- {
- Asn1Node retval = null;
- var i = childNodeList.IndexOf(node);
- retval = RemoveChild(i);
- return retval;
- }
-
- ///
- /// Get child node count.
- ///
- public long ChildNodeCount
- {
- get
- {
- return childNodeList.Count;
- }
- }
-
- ///
- /// Retrieve child node by index.
- ///
- /// 0 based index.
- /// 0 based index.
- public Asn1Node GetChildNode(int index)
- {
- Asn1Node retval = null;
- if (index < ChildNodeCount)
- {
- retval = (Asn1Node) childNodeList[index];
- }
- return retval;
- }
-
-
-
- ///
- /// Get tag name.
- ///
- public string TagName
- {
- get
- {
- return Asn1Util.GetTagName(Tag);
- }
- }
-
- ///
- /// Get parent node.
- ///
- public Asn1Node ParentNode { get; private set; }
-
- ///
- /// Get the node and all the descendents text description.
- ///
- /// starting node.
- /// line length.
- ///
- public string GetText(Asn1Node startNode, int lineLen)
- {
- var nodeStr = "";
- var baseLine = "";
- var dataStr = "";
- const string lStr = " | | | ";
- string oid, oidName;
- switch (Tag)
- {
- case Asn1Tag.BIT_STRING:
- baseLine =
- string.Format("{0,6}|{1,6}|{2,7}|{3} {4} UnusedBits:{5} : ",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName,
- UnusedBits
- );
- dataStr = Asn1Util.ToHexString(data);
- if (baseLine.Length + dataStr.Length < lineLen)
- {
- if (dataStr.Length<1)
- {
- nodeStr += baseLine + "\r\n";
- }
- else
- {
- nodeStr += baseLine + "'" + dataStr + "'\r\n";
- }
- }
- else
- {
- nodeStr += baseLine + FormatLineHexString(
- lStr,
- GetIndentStr(startNode).Length,
- lineLen,
- dataStr + "\r\n"
- );
- }
- break;
- case Asn1Tag.OBJECT_IDENTIFIER:
- var xoid = new Oid();
- oid = xoid.Decode(new MemoryStream(data));
- oidName = "foo";//xoid.GetOidName(oid);
- nodeStr += string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5} [{6}]\r\n",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName,
- oidName,
- oid
- );
- break;
- case Asn1Tag.RELATIVE_OID:
- var xiod = new RelativeOid();
- oid = xiod.Decode(new MemoryStream(data));
- oidName = "";
- nodeStr += string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5} [{6}]\r\n",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName,
- oidName,
- oid
- );
- break;
- case Asn1Tag.PRINTABLE_STRING:
- case Asn1Tag.IA5_STRING:
- case Asn1Tag.UNIVERSAL_STRING:
- case Asn1Tag.VISIBLE_STRING:
- case Asn1Tag.NUMERIC_STRING:
- case Asn1Tag.UTC_TIME:
- case Asn1Tag.UTF8_STRING:
- case Asn1Tag.BMPSTRING:
- case Asn1Tag.GENERAL_STRING:
- case Asn1Tag.GENERALIZED_TIME:
- baseLine =
- string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName
- );
- if ( Tag == Asn1Tag.UTF8_STRING )
- {
- var unicode = new UTF8Encoding();
- dataStr = unicode.GetString(data);
- }
- else
- {
- dataStr = Asn1Util.BytesToString(data);
- }
- if (baseLine.Length + dataStr.Length < lineLen)
- {
- nodeStr += baseLine + "'" + dataStr + "'\r\n";
- }
- else
- {
- nodeStr += baseLine + FormatLineString(
- lStr,
- GetIndentStr(startNode).Length,
- lineLen,
- dataStr) + "\r\n";
- }
- break;
- case Asn1Tag.INTEGER:
- if (data != null && DataLength < 8)
- {
- nodeStr += string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5}\r\n",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName,
- Asn1Util.BytesToLong(data).ToString()
- );
- }
- else
- {
- baseLine =
- string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName
- );
- nodeStr += GetHexPrintingStr(startNode, baseLine, lStr, lineLen);
- }
- break;
- default:
- if ((Tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate
- {
- baseLine =
- string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName
- );
- dataStr = Asn1Util.BytesToString(data);
- if (baseLine.Length + dataStr.Length < lineLen)
- {
- nodeStr += baseLine + "'" + dataStr + "'\r\n";
- }
- else
- {
- nodeStr += baseLine + FormatLineString(
- lStr,
- GetIndentStr(startNode).Length,
- lineLen,
- dataStr) + "\r\n";
- }
- }
- else
- {
- baseLine =
- string.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
- DataOffset,
- DataLength,
- lengthFieldBytes,
- GetIndentStr(startNode),
- TagName
- );
- nodeStr += GetHexPrintingStr(startNode, baseLine, lStr, lineLen);
- }
- break;
- };
- if (childNodeList.Count >= 0)
- {
- nodeStr += GetListStr(startNode, lineLen);
- }
- return nodeStr;
- }
-
- ///
- /// Get the path string of the node.
- ///
- public string Path { get; private set; } = "";
-
- ///
- /// Retrieve the node description.
- ///
- /// true:Return hex string only;
- /// false:Convert to more readable string depending on the node tag.
- /// string
- public string GetDataStr(bool pureHexMode)
- {
- const int lineLen = 32;
- var dataStr = "";
- if (pureHexMode)
- {
- dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
- }
- else
- {
- switch (Tag)
- {
- case Asn1Tag.BIT_STRING:
- dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
- break;
- case Asn1Tag.OBJECT_IDENTIFIER:
- var xoid = new Oid();
- dataStr = xoid.Decode(new MemoryStream(data));
- break;
- case Asn1Tag.RELATIVE_OID:
- var roid = new RelativeOid();
- dataStr = roid.Decode(new MemoryStream(data));
- break;
- case Asn1Tag.PRINTABLE_STRING:
- case Asn1Tag.IA5_STRING:
- case Asn1Tag.UNIVERSAL_STRING:
- case Asn1Tag.VISIBLE_STRING:
- case Asn1Tag.NUMERIC_STRING:
- case Asn1Tag.UTC_TIME:
- case Asn1Tag.BMPSTRING:
- case Asn1Tag.GENERAL_STRING:
- case Asn1Tag.GENERALIZED_TIME:
- dataStr = Asn1Util.BytesToString(data);
- break;
- case Asn1Tag.UTF8_STRING:
- var utf8 = new UTF8Encoding();
- dataStr = utf8.GetString(data);
- break;
- case Asn1Tag.INTEGER:
- dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
- break;
- default:
- if ((Tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate
- {
- dataStr = Asn1Util.BytesToString(data);
- }
- else
- {
- dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
- }
- break;
- };
- }
- return dataStr;
- }
-
- ///
- /// Get node label string.
- ///
- ///
- ///
- /// SHOW_OFFSET
- /// SHOW_DATA
- /// USE_HEX_OFFSET
- /// SHOW_TAG_NUMBER
- /// SHOW_PATH
- ///
- /// string
- public string GetLabel(uint mask)
- {
- var nodeStr = "";
- var dataStr = "";
- var offsetStr = "";
- if ((mask & TagTextMask.USE_HEX_OFFSET) != 0)
- {
- if ((mask & TagTextMask.SHOW_TAG_NUMBER) != 0)
- offsetStr = string.Format("(0x{0:X2},0x{1:X6},0x{2:X4})", Tag, DataOffset, DataLength);
- else
- offsetStr = string.Format("(0x{0:X6},0x{1:X4})", DataOffset, DataLength);
- }
- else
- {
- if ((mask & TagTextMask.SHOW_TAG_NUMBER) != 0)
- offsetStr = string.Format("({0},{1},{2})", Tag, DataOffset, DataLength);
- else
- offsetStr = string.Format("({0},{1})", DataOffset, DataLength);
- }
- string oid, oidName;
- switch (Tag)
- {
- case Asn1Tag.BIT_STRING:
- if ((mask & TagTextMask.SHOW_OFFSET) != 0)
- {
- nodeStr += offsetStr;
- }
- nodeStr += " " + TagName + " UnusedBits: " + UnusedBits.ToString();
- if ((mask & TagTextMask.SHOW_DATA) != 0)
- {
- dataStr = Asn1Util.ToHexString(data);
- nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : "");
- }
- break;
- case Asn1Tag.OBJECT_IDENTIFIER:
- var xoid = new Oid();
- oid = xoid.Decode(data);
- oidName = "bar";// xoid.GetOidName(oid);
- if ((mask & TagTextMask.SHOW_OFFSET) != 0)
- {
- nodeStr += offsetStr;
- }
- nodeStr += " " + TagName;
- nodeStr += " : " + oidName;
- if ((mask & TagTextMask.SHOW_DATA) != 0)
- {
- nodeStr += ((oid.Length>0) ? " : '" + oid + "'" : "");
- }
- break;
- case Asn1Tag.RELATIVE_OID:
- var roid = new RelativeOid();
- oid = roid.Decode(data);
- oidName = "";
- if ((mask & TagTextMask.SHOW_OFFSET) != 0)
- {
- nodeStr += offsetStr;
- }
- nodeStr += " " + TagName;
- nodeStr += " : " + oidName;
- if ((mask & TagTextMask.SHOW_DATA) != 0)
- {
- nodeStr += ((oid.Length>0) ? " : '" + oid + "'" : "");
- }
- break;
- case Asn1Tag.PRINTABLE_STRING:
- case Asn1Tag.IA5_STRING:
- case Asn1Tag.UNIVERSAL_STRING:
- case Asn1Tag.VISIBLE_STRING:
- case Asn1Tag.NUMERIC_STRING:
- case Asn1Tag.UTC_TIME:
- case Asn1Tag.UTF8_STRING:
- case Asn1Tag.BMPSTRING:
- case Asn1Tag.GENERAL_STRING:
- case Asn1Tag.GENERALIZED_TIME:
- if ((mask & TagTextMask.SHOW_OFFSET) != 0)
- {
- nodeStr += offsetStr;
- }
- nodeStr += " " + TagName;
- if ((mask & TagTextMask.SHOW_DATA) != 0)
- {
- if ( Tag == Asn1Tag.UTF8_STRING )
- {
- var unicode = new UTF8Encoding();
- dataStr = unicode.GetString(data);
- }
- else
- {
- dataStr = Asn1Util.BytesToString(data);
- }
- nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : "");
- }
- break;
- case Asn1Tag.INTEGER:
- if ((mask & TagTextMask.SHOW_OFFSET) != 0)
- {
- nodeStr += offsetStr;
- }
- nodeStr += " " + TagName;
- if ((mask & TagTextMask.SHOW_DATA) != 0)
- {
- if (data != null && DataLength < 8)
- {
- dataStr = Asn1Util.BytesToLong(data).ToString();
- }
- else
- {
- dataStr = Asn1Util.ToHexString(data);
- }
- nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : "");
- }
- break;
- default:
- if ((mask & TagTextMask.SHOW_OFFSET) != 0)
- {
- nodeStr += offsetStr;
- }
- nodeStr += " " + TagName;
- if ((mask & TagTextMask.SHOW_DATA) != 0)
- {
- if ((Tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate
- {
- dataStr = Asn1Util.BytesToString(data);
- }
- else
- {
- dataStr = Asn1Util.ToHexString(data);
- }
- nodeStr += ((dataStr.Length>0) ? " : '" + dataStr + "'" : "");
- }
- break;
- };
- if ((mask & TagTextMask.SHOW_PATH) != 0)
- {
- nodeStr = "(" + Path + ") " + nodeStr;
- }
- return nodeStr;
- }
-
- ///
- /// Get data length. Not included the unused bits byte for BITSTRING.
- ///
- public long DataLength { get; private set; }
-
- ///
- /// Get the length field bytes.
- ///
- public long LengthFieldBytes
- {
- get
- {
- return lengthFieldBytes;
- }
- }
-
- ///
- /// Get/Set node data by byte[], the data length field content and all the
- /// node in the parent chain will be adjusted.
- ///
- /// It return all the child data for constructed node.
- ///
- public byte[] Data
- {
- get
- {
- var xdata = new MemoryStream();
- var nodeCount = ChildNodeCount;
- if (nodeCount==0)
- {
- if (data != null)
- {
- xdata.Write(data, 0, data.Length);
- }
- }
- else
- {
- Asn1Node tempNode;
- for (var i=0; i
- /// Get the deepness of the node.
- ///
- public long Deepness { get; private set; }
-
- ///
- /// Get data offset.
- ///
- public long DataOffset { get; private set; }
-
- ///
- /// Get unused bits for BITSTRING.
- ///
- public byte UnusedBits { get; set; }
-
-
- ///
- /// Get descendant node by node path.
- ///
- /// relative node path that refer to current node.
- ///
- public Asn1Node GetDescendantNodeByPath(string nodePath)
- {
- var retval = this;
- if (nodePath == null) return retval;
- nodePath = nodePath.TrimEnd().TrimStart();
- if (nodePath.Length<1) return retval;
- var route = nodePath.Split('/');
- try
- {
- for (var i = 1; i
- /// Get node by OID.
- ///
- /// OID.
- /// Starting node.
- /// Null or Asn1Node.
- static public Asn1Node GetDecendantNodeByOid(string oid, Asn1Node startNode)
- {
- Asn1Node retval = null;
- var xoid = new Oid();
- for (var i = 0; i
- /// Constant of tag field length.
- ///
- public const int TagLength = 1;
-
- ///
- /// Constant of unused bits field length.
- ///
- public const int BitStringUnusedFiledLength = 1;
-
- ///
- /// Tag text generation mask definition.
- ///
- public class TagTextMask
- {
- ///
- /// Show offset.
- ///
- public const uint SHOW_OFFSET = 0x01;
-
- ///
- /// Show decoded data.
- ///
- public const uint SHOW_DATA = 0x02;
-
- ///
- /// Show offset in hex format.
- ///
- public const uint USE_HEX_OFFSET = 0x04;
-
- ///
- /// Show tag.
- ///
- public const uint SHOW_TAG_NUMBER = 0x08;
-
- ///
- /// Show node path.
- ///
- public const uint SHOW_PATH = 0x10;
- }
-
- ///
- /// Set/Get requireRecalculatePar. RecalculateTreePar function will not do anything
- /// if it is set to false.
- ///
- protected bool RequireRecalculatePar { get; set; } = true;
-
- //ProtectedMembers
-
- ///
- /// Find root node and recalculate entire tree length field,
- /// path, offset and deepness.
- ///
- protected void RecalculateTreePar()
- {
- if (!RequireRecalculatePar) return;
- Asn1Node rootNode;
- for (rootNode = this; rootNode.ParentNode != null;)
- {
- rootNode = rootNode.ParentNode;
- }
- ResetBranchDataLength(rootNode);
- rootNode.DataOffset = 0;
- rootNode.Deepness = 0;
- var subOffset = rootNode.DataOffset + TagLength + rootNode.lengthFieldBytes;
- ResetChildNodePar(rootNode, subOffset);
- }
-
- ///
- /// Recursively set all the node data length.
- ///
- ///
- /// node data length.
- protected static long ResetBranchDataLength(Asn1Node node)
- {
- long retval = 0;
- long childDataLength = 0;
- if (node.ChildNodeCount < 1)
- {
- if (node.data != null)
- childDataLength += node.data.Length;
- }
- else
- {
- for (var i=0; i
- /// Encode the node data length field and set lengthFieldBytes and dataLength.
- ///
- /// The node needs to be reset.
- protected static void ResetDataLengthFieldWidth(Asn1Node node)
- {
- var tempStream = new MemoryStream();
- Asn1Util.DERLengthEncode(tempStream, (ulong) node.DataLength);
- node.lengthFieldBytes = tempStream.Length;
- tempStream.Close();
- }
-
- ///
- /// Recursively set all the child parameters, except dataLength.
- /// dataLength is set by ResetBranchDataLength.
- ///
- /// Starting node.
- /// Starting node offset.
- protected void ResetChildNodePar(Asn1Node xNode, long subOffset)
- {
- int i;
- if (xNode.Tag == Asn1Tag.BIT_STRING)
- {
- subOffset++;
- }
- Asn1Node tempNode;
- for (i=0; i
- /// Generate all the child text from childNodeList.
- ///
- /// Starting node.
- /// Line length.
- /// Text string.
- protected string GetListStr(Asn1Node startNode, int lineLen)
- {
- var nodeStr = "";
- int i;
- Asn1Node tempNode;
- for (i=0; i
- /// Generate the node indent string.
- ///
- /// The node.
- /// Text string.
- protected string GetIndentStr(Asn1Node startNode)
- {
- var retval = "";
- long startLen = 0;
- if (startNode!=null)
- {
- startLen = startNode.Deepness;
- }
- for (long i = 0; i
- /// Decode ASN.1 encoded node Stream data.
- ///
- /// Stream data.
- /// true:Succeed, false:Failed.
- protected bool GeneralDecode(Stream xdata)
- {
- var retval = false;
- long nodeMaxLen;
- nodeMaxLen = xdata.Length - xdata.Position;
- Tag = (byte) xdata.ReadByte();
- long start, end;
- start = xdata.Position;
- DataLength = Asn1Util.DerLengthDecode(xdata, ref isIndefiniteLength);
- if (DataLength < 0) return retval; // Node data length can not be negative.
- end = xdata.Position;
- lengthFieldBytes = end - start;
- if (nodeMaxLen < (DataLength + TagLength + lengthFieldBytes))
- {
- return retval;
- }
- if ( ParentNode == null || ((ParentNode.Tag & Asn1TagClasses.CONSTRUCTED) == 0))
- {
- if ((Tag & Asn1Tag.TAG_MASK)<=0 || (Tag & Asn1Tag.TAG_MASK)>0x1E) return retval;
- }
- if (Tag == Asn1Tag.BIT_STRING)
- {
- // First byte of BIT_STRING is unused bits.
- // BIT_STRING data does not include this byte.
-
- // Fixed by Gustaf Björklund.
- if (DataLength < 1) return retval; // We cannot read less than 1 - 1 bytes.
-
- UnusedBits = (byte) xdata.ReadByte();
- data = new byte[DataLength-1];
- xdata.Read(data, 0, (int)(DataLength-1) );
- }
- else
- {
- data = new byte[DataLength];
- xdata.Read(data, 0, (int)(DataLength) );
- }
- retval = true;
- return retval;
- }
-
- ///
- /// Decode ASN.1 encoded complex data type Stream data.
- ///
- /// Stream data.
- /// true:Succeed, false:Failed.
- protected bool ListDecode(Stream xdata)
- {
- var retval = false;
- var originalPosition = xdata.Position;
- long childNodeMaxLen;
- try
- {
- childNodeMaxLen = xdata.Length - xdata.Position;
- Tag = (byte) xdata.ReadByte();
- long start, end, offset;
- start = xdata.Position;
- DataLength = Asn1Util.DerLengthDecode(xdata, ref isIndefiniteLength);
- if (DataLength<0 || childNodeMaxLen
- /// Set the node data and recalculate the entire tree parameters.
- ///
- /// byte[] data.
- protected void SetData(byte[] xdata)
- {
- if (childNodeList.Count > 0)
- {
- throw new Exception("Constructed node can't hold simple data.");
- }
- else
- {
- data = xdata;
- if (data != null)
- DataLength = data.Length;
- else
- DataLength = 0;
- RecalculateTreePar();
- }
- }
-
- ///
- /// Load data from Stream. Start from current position.
- ///
- /// Stream
- /// true:Succeed; false:failed.
- protected bool InternalLoadData(Stream xdata)
- {
- var retval = true;
- ClearAll();
- byte xtag;
- var curPosition = xdata.Position;
- xtag = (byte) xdata.ReadByte();
- xdata.Position = curPosition;
- var maskedTag = xtag & Asn1Tag.TAG_MASK;
- if (((xtag & Asn1TagClasses.CONSTRUCTED) != 0)
- || (parseEncapsulatedData
- && ((maskedTag == Asn1Tag.BIT_STRING)
- || (maskedTag == Asn1Tag.EXTERNAL)
- || (maskedTag == Asn1Tag.GENERAL_STRING)
- || (maskedTag == Asn1Tag.GENERALIZED_TIME)
- || (maskedTag == Asn1Tag.GRAPHIC_STRING)
- || (maskedTag == Asn1Tag.IA5_STRING)
- || (maskedTag == Asn1Tag.OCTET_STRING)
- || (maskedTag == Asn1Tag.PRINTABLE_STRING)
- || (maskedTag == Asn1Tag.SEQUENCE)
- || (maskedTag == Asn1Tag.SET)
- || (maskedTag == Asn1Tag.T61_STRING)
- || (maskedTag == Asn1Tag.UNIVERSAL_STRING)
- || (maskedTag == Asn1Tag.UTF8_STRING)
- || (maskedTag == Asn1Tag.VIDEOTEXT_STRING)
- || (maskedTag == Asn1Tag.VISIBLE_STRING)))
- )
- {
- if (!ListDecode(xdata))
- {
- if (!GeneralDecode(xdata))
- {
- retval = false;
- }
- }
- }
- else
- {
- if (!GeneralDecode(xdata)) retval = false;
- }
- return retval;
- }
-
- ///
- /// Get/Set parseEncapsulatedData. This property will be inherited by the
- /// child nodes when loading data.
- ///
- public bool ParseEncapsulatedData
- {
- get
- {
- return parseEncapsulatedData;
- }
- set
- {
- if (parseEncapsulatedData == value) return;
- var tmpData = Data;
- parseEncapsulatedData = value;
- ClearAll();
- if ((Tag & Asn1TagClasses.CONSTRUCTED) != 0 || parseEncapsulatedData)
- {
- var ms = new MemoryStream(tmpData);
- ms.Position = 0;
- var isLoaded = true;
- while(ms.Position < ms.Length)
- {
- var tempNode = new Asn1Node();
- tempNode.ParseEncapsulatedData = parseEncapsulatedData;
- if (!tempNode.LoadData(ms))
- {
- ClearAll();
- isLoaded = false;
- break;
- }
- AddChild(tempNode);
- }
- if (!isLoaded)
- {
- Data = tmpData;
- }
- }
- else
- {
- Data = tmpData;
- }
- }
- }
-
- }
-
-}
-
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs
deleted file mode 100644
index db1c5fb7..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Parser.cs
+++ /dev/null
@@ -1,201 +0,0 @@
-//+-------------------------------------------------------------------------------+
-//| Copyright (c) 2003 Liping Dai. All rights reserved. |
-//| Web: www.lipingshare.com |
-//| Email: lipingshare@yahoo.com |
-//| |
-//| Copyright and Permission Details: |
-//| ================================= |
-//| Permission is hereby granted, free of charge, to any person obtaining a copy |
-//| of this software and associated documentation files (the "Software"), to deal |
-//| in the Software without restriction, including without limitation the rights |
-//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the |
-//| Software, subject to the following conditions: |
-//| |
-//| 1. Redistributions of source code must retain the above copyright notice, this|
-//| list of conditions and the following disclaimer. |
-//| |
-//| 2. Redistributions in binary form must reproduce the above copyright notice, |
-//| this list of conditions and the following disclaimer in the documentation |
-//| and/or other materials provided with the distribution. |
-//| |
-//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, |
-//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
-//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
-//| A PARTICULAR PURPOSE. |
-//+-------------------------------------------------------------------------------+
-
-using System;
-using System.IO;
-
-namespace LipingShare.LCLib.Asn1Processor
-{
- ///
- /// ASN.1 encoded data parser.
- /// This a higher level class which unilized Asn1Node class functionality to
- /// provide functions for ASN.1 encoded files.
- ///
- public class Asn1Parser
- {
- private Asn1Node rootNode = new Asn1Node();
-
- ///
- /// Get/Set parseEncapsulatedData. Reloading data is required after this property is reset.
- ///
- bool ParseEncapsulatedData
- {
- get
- {
- return rootNode.ParseEncapsulatedData;
- }
- set
- {
- rootNode.ParseEncapsulatedData = value;
- }
- }
-
- ///
- /// Constructor.
- ///
- public Asn1Parser()
- {
- }
-
- ///
- /// Get raw ASN.1 encoded data.
- ///
- public byte[] RawData { get; private set; }
-
- ///
- /// Load ASN.1 encoded data from a file.
- ///
- /// File name.
- public void LoadData(string fileName)
- {
- var fs = new FileStream(fileName, FileMode.Open);
- RawData = new byte[fs.Length];
- fs.Read(RawData, 0, (int)fs.Length);
- fs.Close();
- var ms = new MemoryStream(RawData);
- LoadData(ms);
- }
-
- ///
- /// Load PEM formated file.
- ///
- /// PEM file name.
- public void LoadPemData(string fileName)
- {
- var fs = new FileStream(fileName, FileMode.Open);
- var data = new byte[fs.Length];
- fs.Read(data, 0, data.Length);
- fs.Close();
- var dataStr = Asn1Util.BytesToString(data);
- if (Asn1Util.IsPemFormated(dataStr))
- {
- var ms = Asn1Util.PemToStream(dataStr);
- ms.Position = 0;
- LoadData(ms);
- }
- else
- {
- throw new Exception("It is a invalid PEM file: " + fileName);
- }
- }
-
- ///
- /// Load ASN.1 encoded data from Stream.
- ///
- /// Stream data.
- public void LoadData(Stream stream)
- {
- stream.Position = 0;
- if (!rootNode.LoadData(stream))
- {
- throw new Exception("Failed to load data.");
- }
- RawData = new byte[stream.Length];
- stream.Position = 0;
- stream.Read(RawData, 0, RawData.Length);
- }
-
- ///
- /// Save data into a file.
- ///
- /// File name.
- public void SaveData(string fileName)
- {
- var fs = new FileStream(fileName, FileMode.Create);
- rootNode.SaveData(fs);
- fs.Close();
- }
-
- ///
- /// Get root node.
- ///
- public Asn1Node RootNode
- {
- get
- {
- return rootNode;
- }
- }
-
- ///
- /// Get a node by path string.
- ///
- /// Path string.
- /// Asn1Node or null.
- public Asn1Node GetNodeByPath(string nodePath)
- {
- return rootNode.GetDescendantNodeByPath(nodePath);
- }
-
- ///
- /// Get a node by OID.
- ///
- /// OID string.
- /// Asn1Node or null.
- public Asn1Node GetNodeByOid(string oid)
- {
- return Asn1Node.GetDecendantNodeByOid(oid, rootNode);
- }
-
- ///
- /// Generate node text header. This method is used by GetNodeText to put heading.
- ///
- /// Line length.
- /// Header string.
- static public string GetNodeTextHeader(int lineLen)
- {
- var header = string.Format("Offset| Len |LenByte|\r\n");
- header += "======+======+=======+" + Asn1Util.GenStr(lineLen+10, '=') + "\r\n";
- return header;
- }
-
- ///
- /// Generate the root node text description.
- ///
- /// Text string.
- public override string ToString()
- {
- return GetNodeText(rootNode, 100);
- }
-
- ///
- /// Generate node text description. It uses GetNodeTextHeader to generate
- /// the heading and Asn1Node.GetText to generate the node text.
- ///
- /// Target node.
- /// Line length.
- /// Text string.
- public static string GetNodeText(Asn1Node node, int lineLen)
- {
- var nodeStr = GetNodeTextHeader(lineLen);
- nodeStr +=node.GetText(node, lineLen);
- return nodeStr;
- }
-
- }
-}
-
-
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs
deleted file mode 100644
index f770a7a9..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Tag.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-//+-------------------------------------------------------------------------------+
-//| Copyright (c) 2003 Liping Dai. All rights reserved. |
-//| Web: www.lipingshare.com |
-//| Email: lipingshare@yahoo.com |
-//| |
-//| Copyright and Permission Details: |
-//| ================================= |
-//| Permission is hereby granted, free of charge, to any person obtaining a copy |
-//| of this software and associated documentation files (the "Software"), to deal |
-//| in the Software without restriction, including without limitation the rights |
-//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the |
-//| Software, subject to the following conditions: |
-//| |
-//| 1. Redistributions of source code must retain the above copyright notice, this|
-//| list of conditions and the following disclaimer. |
-//| |
-//| 2. Redistributions in binary form must reproduce the above copyright notice, |
-//| this list of conditions and the following disclaimer in the documentation |
-//| and/or other materials provided with the distribution. |
-//| |
-//| THE SOFTWARE PRODUCT IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, |
-//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
-//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
-//| A PARTICULAR PURPOSE. |
-//+-------------------------------------------------------------------------------+
-
-using System;
-
-namespace LipingShare.LCLib.Asn1Processor
-{
- ///
- /// Define ASN.1 tag constants.
- ///
- ///
- public class Asn1Tag
- {
- ///
- /// Tag mask constant value.
- ///
- public const byte TAG_MASK = 0x1F;
-
- ///
- /// Constant value.
- ///
- public const byte BOOLEAN = 0x01;
-
- ///
- /// Constant value.
- ///
- public const byte INTEGER = 0x02;
-
- ///
- /// Constant value.
- ///
- public const byte BIT_STRING = 0x03;
-
- ///
- /// Constant value.
- ///
- public const byte OCTET_STRING = 0x04;
-
- ///
- /// Constant value.
- ///
- public const byte TAG_NULL = 0x05;
-
- ///
- /// Constant value.
- ///
- public const byte OBJECT_IDENTIFIER = 0x06;
-
- ///
- /// Constant value.
- ///
- public const byte OBJECT_DESCRIPTOR = 0x07;
-
- ///
- /// Constant value.
- ///
- public const byte EXTERNAL = 0x08;
-
- ///
- /// Constant value.
- ///
- public const byte REAL = 0x09;
-
- ///
- /// Constant value.
- ///
- public const byte ENUMERATED = 0x0a;
-
- ///
- /// Constant value.
- ///
- public const byte UTF8_STRING = 0x0c;
-
- ///
- /// Relative object identifier.
- ///
- public const byte RELATIVE_OID = 0x0d;
-
- ///
- /// Constant value.
- ///
- public const byte SEQUENCE = 0x10;
-
- ///
- /// Constant value.
- ///
- public const byte SET = 0x11;
-
- ///
- /// Constant value.
- ///
- public const byte NUMERIC_STRING = 0x12;
-
- ///
- /// Constant value.
- ///
- public const byte PRINTABLE_STRING = 0x13;
-
- ///
- /// Constant value.
- ///
- public const byte T61_STRING = 0x14;
-
- ///
- /// Constant value.
- ///
- public const byte VIDEOTEXT_STRING = 0x15;
-
- ///
- /// Constant value.
- ///
- public const byte IA5_STRING = 0x16;
-
- ///
- /// Constant value.
- ///
- public const byte UTC_TIME = 0x17;
-
- ///
- /// Constant value.
- ///
- public const byte GENERALIZED_TIME = 0x18;
-
- ///
- /// Constant value.
- ///
- public const byte GRAPHIC_STRING = 0x19;
-
- ///
- /// Constant value.
- ///
- public const byte VISIBLE_STRING = 0x1a;
-
- ///
- /// Constant value.
- ///
- public const byte GENERAL_STRING = 0x1b;
-
- ///
- /// Constant value.
- ///
- public const byte UNIVERSAL_STRING = 0x1C;
-
- ///
- /// Constant value.
- ///
- public const byte BMPSTRING = 0x1E; /* 30: Basic Multilingual Plane/Unicode string */
-
- ///
- /// Constructor.
- ///
- public Asn1Tag()
- {
- }
- };
-
- ///
- /// Define ASN.1 tag class constants.
- ///
- ///
- public class Asn1TagClasses
- {
- ///
- /// Constant value.
- ///
- public const byte CLASS_MASK = 0xc0;
-
- ///
- /// Constant value.
- ///
- public const byte UNIVERSAL = 0x00;
-
- ///
- /// Constant value.
- ///
- public const byte CONSTRUCTED = 0x20;
-
- ///
- /// Constant value.
- ///
- public const byte APPLICATION = 0x40;
-
- ///
- /// Constant value.
- ///
- public const byte CONTEXT_SPECIFIC = 0x80;
-
- ///
- /// Constant value.
- ///
- public const byte PRIVATE = 0xc0;
-
- ///
- /// Constructor.
- ///
- public Asn1TagClasses()
- {
- }
- };
-
-}
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs
deleted file mode 100644
index c131420e..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/Asn1Util.cs
+++ /dev/null
@@ -1,733 +0,0 @@
-//+-------------------------------------------------------------------------------+
-//| Copyright (c) 2003 Liping Dai. All rights reserved. |
-//| Web: www.lipingshare.com |
-//| Email: lipingshare@yahoo.com |
-//| |
-//| Copyright and Permission Details: |
-//| ================================= |
-//| Permission is hereby granted, free of charge, to any person obtaining a copy |
-//| of this software and associated documentation files (the "Software"), to deal |
-//| in the Software without restriction, including without limitation the rights |
-//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the |
-//| Software, subject to the following conditions: |
-//| |
-//| 1. Redistributions of source code must retain the above copyright notice, this|
-//| list of conditions and the following disclaimer. |
-//| |
-//| 2. Redistributions in binary form must reproduce the above copyright notice, |
-//| this list of conditions and the following disclaimer in the documentation |
-//| and/or other materials provided with the distribution. |
-//| |
-//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, |
-//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
-//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
-//| A PARTICULAR PURPOSE. |
-//+-------------------------------------------------------------------------------+
-
-using System;
-using System.IO;
-
-namespace LipingShare.LCLib.Asn1Processor
-{
- ///
- /// Utility functions.
- ///
- public class Asn1Util
- {
-
- ///
- /// Check if the string is ASN.1 encoded hex string.
- ///
- /// The string.
- /// true:Yes, false:No.
- public static bool IsAsn1EncodedHexStr(string dataStr)
- {
- var retval = false;
- try
- {
- var data = HexStrToBytes(dataStr);
- if (data.Length > 0)
- {
- var node = new Asn1Node();
- retval = node.LoadData(data);
- }
- }
- catch
- {
- retval = false;
- }
- return retval;
- }
-
- ///
- /// Format a string to have certain line length and character group length.
- /// Sample result FormatString(xstr,32,2):
- /// 07 AE 0B E7 84 5A D4 6C 6A BD DF 8F 89 88 9E F1
- ///
- /// source string.
- /// line length.
- /// group length.
- ///
- public static string FormatString(string inStr, int lineLen, int groupLen)
- {
- var tmpCh = new char[inStr.Length*2];
- int i, c = 0, linec = 0;
- var gc = 0;
- for (i=0; i= groupLen && groupLen > 0)
- {
- tmpCh[c++] = ' ';
- gc = 0;
- }
- if (linec >= lineLen)
- {
- tmpCh[c++] = '\r';
- tmpCh[c++] = '\n';
- linec = 0;
- }
- }
- var retval = new string(tmpCh);
- retval = retval.TrimEnd('\0');
- retval = retval.TrimEnd('\n');
- retval = retval.TrimEnd('\r');
- return retval;
- }
-
- ///
- /// Generate a string by duplicating xch.
- ///
- /// duplicate times.
- /// the duplicated character.
- ///
- public static string GenStr(int len, char xch)
- {
- var ch = new char[len];
- for (var i = 0; i
- /// Convert byte array to a integer.
- ///
- ///
- ///
- public static long BytesToLong(byte[] bytes)
- {
- long tempInt = 0;
- for(var i=0; i
- /// Convert a ASCII byte array to string, also filter out the null characters.
- ///
- ///
- ///
- public static string BytesToString(byte[] bytes)
- {
- var retval = "";
- if (bytes == null || bytes.Length < 1) return retval;
- var cretval = new char[bytes.Length];
- for (int i=0, j=0; i
- /// Convert ASCII string to byte array.
- ///
- ///
- ///
- public static byte[] StringToBytes(string msg)
- {
- var retval = new byte[msg.Length];
- for (var i=0; i
- /// Compare source and target byte array.
- ///
- ///
- ///
- ///
- public static bool IsEqual(byte[] source, byte[] target)
- {
- if (source == null) return false;
- if (target == null) return false;
- if (source.Length != target.Length) return false;
- for (var i=0; i
- /// Constant hex digits array.
- ///
- static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
-
- ///
- /// Convert a byte array to hex string.
- ///
- /// source array.
- /// hex string.
- public static string ToHexString(byte[] bytes)
- {
- if (bytes == null) return "";
- var chars = new char[bytes.Length * 2];
- int b, i;
- for (i = 0; i < bytes.Length; i++)
- {
- b = bytes[i];
- chars[i * 2] = hexDigits[b >> 4];
- chars[i * 2 + 1] = hexDigits[b & 0xF];
- }
- return new string(chars);
- }
-
- ///
- /// Check if the character is a valid hex digits.
- ///
- /// source character.
- /// true:Valid, false:Invalid.
- public static bool IsValidHexDigits(char ch)
- {
- var retval = false;
- for (var i=0; i
- /// Get hex digits value.
- ///
- /// source character.
- /// hex digits value.
- public static byte GetHexDigitsVal(char ch)
- {
- byte retval = 0;
- for (var i=0; i
- /// Convert hex string to byte array.
- ///
- /// Source hex string.
- /// return byte array.
- public static byte[] HexStrToBytes(string hexStr)
- {
- hexStr = hexStr.Replace(" ", "");
- hexStr = hexStr.Replace("\r", "");
- hexStr = hexStr.Replace("\n", "");
- hexStr = hexStr.ToUpper();
- if ((hexStr.Length%2) != 0) throw new Exception("Invalid Hex string: odd length.");
- int i;
- for (i=0; i
- /// Check if the source string is a valid hex string.
- ///
- /// source string.
- /// true:Valid, false:Invalid.
- public static bool IsHexStr(string hexStr)
- {
- byte[] bytes = null;
- try
- {
- bytes = HexStrToBytes(hexStr);
- }
- catch
- {
- return false;
- }
- if (bytes == null || bytes.Length < 0)
- {
- return false;
- }
- else
- {
- return true;
- }
- }
-
- private const string PemStartStr = "-----BEGIN";
- private const string PemEndStr = "-----END";
- ///
- /// Check if the source string is PEM formated string.
- ///
- /// source string.
- /// true:Valid, false:Invalid.
- public static bool IsPemFormated(string pemStr)
- {
- byte[] data = null;
- try
- {
- data = PemToBytes(pemStr);
- }
- catch
- {
- return false;
- }
- return (data.Length > 0);
- }
-
- ///
- /// Check if a file is PEM formated.
- ///
- /// source file name.
- /// true:Yes, false:No.
- public static bool IsPemFormatedFile(string fileName)
- {
- var retval = false;
- try
- {
- var fs = new FileStream(fileName, FileMode.Open);
- var data = new byte[fs.Length];
- fs.Read(data, 0, data.Length);
- fs.Close();
- var dataStr = BytesToString(data);
- retval = IsPemFormated(dataStr);
- }
- catch
- {
- retval = false;
- }
- return retval;
- }
-
- ///
- /// Convert PEM formated string into and set the Stream position to 0.
- ///
- /// source string.
- /// output stream.
- public static Stream PemToStream(string pemStr)
- {
- var bytes = PemToBytes(pemStr);
- var retval = new MemoryStream(bytes);
- retval.Position = 0;
- return retval;
- }
-
- ///
- /// Convert PEM formated string into byte array.
- ///
- /// source string.
- /// output byte array.
- public static byte[] PemToBytes(string pemStr)
- {
- byte[] retval = null;
- var lines = pemStr.Split('\n');
- var base64Str = "";
- bool started = false, ended = false;
- var cline = "";
- for (var i = 0; i PemStartStr.Length)
- {
- if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
- {
- started = true;
- continue;
- }
- }
- if (cline.Length > PemEndStr.Length)
- {
- if (cline.Substring(0, PemEndStr.Length) == PemEndStr)
- {
- ended = true;
- break;
- }
- }
- if (started)
- {
- base64Str += lines[i];
- }
- }
- if (!(started && ended))
- {
- throw new Exception("'BEGIN'/'END' line is missing.");
- }
- base64Str = base64Str.Replace("\r", "");
- base64Str = base64Str.Replace("\n", "");
- base64Str = base64Str.Replace("\n", " ");
- retval = Convert.FromBase64String(base64Str);
- return retval;
- }
-
- ///
- /// Convert byte array to PEM formated string.
- ///
- ///
- ///
- public static string BytesToPem(byte[] data)
- {
- return BytesToPem(data, "");
- }
-
- ///
- /// Retrieve PEM file heading.
- ///
- /// source file name.
- /// heading string.
- public static string GetPemFileHeader(string fileName)
- {
- try
- {
- var fs = new FileStream(fileName, FileMode.Open);
- var data = new byte[fs.Length];
- fs.Read(data, 0, data.Length);
- fs.Close();
- var dataStr = BytesToString(data);
- return GetPemHeader(dataStr);
- }
- catch
- {
- return "";
- }
- }
-
- ///
- /// Retrieve PEM heading from a PEM formated string.
- ///
- /// source string.
- /// heading string.
- public static string GetPemHeader(string pemStr)
- {
- var lines = pemStr.Split('\n');
- var started = false;
- var cline = "";
- for (var i = 0; i PemStartStr.Length)
- {
- if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
- {
- started = true;
- var retstr = lines[i].Substring(PemStartStr.Length,
- lines[i].Length -
- PemStartStr.Length).Replace("-----","");
- return retstr.Replace("\r", "");
- }
- }
- else
- {
- continue;
- }
- }
- return "";
- }
-
- ///
- /// Convert byte array to PEM formated string and set the heading as pemHeader.
- ///
- /// source array.
- /// PEM heading.
- /// PEM formated string.
- public static string BytesToPem(byte[] data, string pemHeader)
- {
- if (pemHeader == null || pemHeader.Length<1)
- {
- pemHeader = "ASN.1 Editor Generated PEM File";
- }
- var retval = "";
- if (pemHeader.Length > 0 && pemHeader[0] != ' ')
- {
- pemHeader = " " + pemHeader;
- }
- retval = Convert.ToBase64String(data);
- retval = FormatString(retval, 64, 0);
- retval = "-----BEGIN"+ pemHeader +"-----\r\n" +
- retval +
- "\r\n-----END"+ pemHeader +"-----\r\n";
- return retval;
- }
-
- ///
- /// Calculate how many bits is enough to hold ivalue.
- ///
- /// source value.
- /// bits number.
- public static int BitPrecision(ulong ivalue)
- {
- if (ivalue == 0) return 0;
- int l = 0, h = 8 * 4; // 4: sizeof(ulong)
- while (h-l > 1)
- {
- var t = (int) (l+h)/2;
- if ((ivalue >> t) != 0)
- l = t;
- else
- h = t;
- }
- return h;
- }
-
- ///
- /// Calculate how many bytes is enough to hold the value.
- ///
- /// input value.
- /// bytes number.
- public static int BytePrecision(ulong value)
- {
- int i;
- for (i = 4; i > 0; --i) // 4: sizeof(ulong)
- if ((value >> (i-1)*8)!=0)
- break;
- return i;
- }
-
- ///
- /// ASN.1 DER length encoder.
- ///
- /// result output stream.
- /// source length.
- /// result bytes.
- public static int DERLengthEncode(Stream xdata, ulong length)
- {
- var i=0;
- if (length <= 0x7f)
- {
- xdata.WriteByte((byte)length);
- i++;
- }
- else
- {
- xdata.WriteByte((byte)(BytePrecision(length) | 0x80));
- i++;
- for (var j=BytePrecision((ulong)length); j>0; --j)
- {
- xdata.WriteByte((byte)(length >> (j-1)*8));
- i++;
- }
- }
- return i;
- }
-
- ///
- /// ASN.1 DER length decoder.
- ///
- /// Source stream.
- /// Output parameter.
- /// Output length.
- public static long DerLengthDecode(Stream bt, ref bool isIndefiniteLength)
- {
- isIndefiniteLength = false;
- long length = 0;
- byte b;
- b = (byte) bt.ReadByte();
- if ((b & 0x80)==0)
- {
- length = b;
- }
- else
- {
- long lengthBytes = b & 0x7f;
- if (lengthBytes == 0)
- {
- isIndefiniteLength = true;
- var sPos = bt.Position;
- return -2; // Indefinite length.
- }
- length = 0;
- while (lengthBytes-- > 0)
- {
- if ((length >> (8 * (4 - 1))) > 0) // 4: sizeof(long)
- {
- return -1; // Length overflow.
- }
- b = (byte) bt.ReadByte();
- length = (length << 8) | b;
- }
- }
- return length;
- }
-
- ///
- /// Decode tag value to return tag name.
- ///
- /// input tag.
- /// tag name.
- static public string GetTagName(byte tag)
- {
- var retval = "";
- if ((tag & Asn1TagClasses.CLASS_MASK) != 0)
- {
- switch (tag & Asn1TagClasses.CLASS_MASK)
- {
- case Asn1TagClasses.CONTEXT_SPECIFIC:
- retval += "CONTEXT SPECIFIC (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")";
- break;
- case Asn1TagClasses.APPLICATION:
- retval += "APPLICATION (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")";
- break;
- case Asn1TagClasses.PRIVATE:
- retval += "PRIVATE (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")";
- break;
- case Asn1TagClasses.CONSTRUCTED:
- retval += "CONSTRUCTED (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")";
- break;
- case Asn1TagClasses.UNIVERSAL:
- retval += "UNIVERSAL (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() +")";
- break;
- }
- }
- else
- {
- switch (tag & Asn1Tag.TAG_MASK)
- {
- case Asn1Tag.BOOLEAN:
- retval += "BOOLEAN";
- break;
- case Asn1Tag.INTEGER:
- retval += "INTEGER";
- break;
- case Asn1Tag.BIT_STRING:
- retval += "BIT STRING";
- break;
- case Asn1Tag.OCTET_STRING:
- retval += "OCTET STRING";
- break;
- case Asn1Tag.TAG_NULL:
- retval += "NULL";
- break;
- case Asn1Tag.OBJECT_IDENTIFIER:
- retval += "OBJECT IDENTIFIER";
- break;
- case Asn1Tag.OBJECT_DESCRIPTOR:
- retval += "OBJECT DESCRIPTOR";
- break;
- case Asn1Tag.RELATIVE_OID:
- retval += "RELATIVE-OID";
- break;
- case Asn1Tag.EXTERNAL:
- retval += "EXTERNAL";
- break;
- case Asn1Tag.REAL:
- retval += "REAL";
- break;
- case Asn1Tag.ENUMERATED:
- retval += "ENUMERATED";
- break;
- case Asn1Tag.UTF8_STRING:
- retval += "UTF8 STRING";
- break;
- case (Asn1Tag.SEQUENCE):
- retval += "SEQUENCE";
- break;
- case (Asn1Tag.SET):
- retval += "SET";
- break;
- case Asn1Tag.NUMERIC_STRING:
- retval += "NUMERIC STRING";
- break;
- case Asn1Tag.PRINTABLE_STRING:
- retval += "PRINTABLE STRING";
- break;
- case Asn1Tag.T61_STRING:
- retval += "T61 STRING";
- break;
- case Asn1Tag.VIDEOTEXT_STRING:
- retval += "VIDEOTEXT STRING";
- break;
- case Asn1Tag.IA5_STRING:
- retval += "IA5 STRING";
- break;
- case Asn1Tag.UTC_TIME:
- retval += "UTC TIME";
- break;
- case Asn1Tag.GENERALIZED_TIME:
- retval += "GENERALIZED TIME";
- break;
- case Asn1Tag.GRAPHIC_STRING:
- retval += "GRAPHIC STRING";
- break;
- case Asn1Tag.VISIBLE_STRING:
- retval += "VISIBLE STRING";
- break;
- case Asn1Tag.GENERAL_STRING:
- retval += "GENERAL STRING";
- break;
- case Asn1Tag.UNIVERSAL_STRING:
- retval += "UNIVERSAL STRING";
- break;
- case Asn1Tag.BMPSTRING:
- retval += "BMP STRING";
- break;
- default:
- retval += "UNKNOWN TAG";
- break;
- };
- }
- return retval;
- }
-
- ///
- /// Constructor.
- ///
- private Asn1Util()
- {
- //Private constructor.
- }
-
- }
-}
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs b/ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs
deleted file mode 100644
index 96d5a3a4..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/BinaryDump.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-//+-------------------------------------------------------------------------------+
-//| Copyright (c) 2003 Liping Dai. All rights reserved. |
-//| Web: www.lipingshare.com |
-//| Email: lipingshare@yahoo.com |
-//| |
-//| Copyright and Permission Details: |
-//| ================================= |
-//| Permission is hereby granted, free of charge, to any person obtaining a copy |
-//| of this software and associated documentation files (the "Software"), to deal |
-//| in the Software without restriction, including without limitation the rights |
-//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the |
-//| Software, subject to the following conditions: |
-//| |
-//| 1. Redistributions of source code must retain the above copyright notice, this|
-//| list of conditions and the following disclaimer. |
-//| |
-//| 2. Redistributions in binary form must reproduce the above copyright notice, |
-//| this list of conditions and the following disclaimer in the documentation |
-//| and/or other materials provided with the distribution. |
-//| |
-//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, |
-//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
-//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
-//| A PARTICULAR PURPOSE. |
-//+-------------------------------------------------------------------------------+
-
-namespace LCLib.Asn1Processor
-{
- ///
- /// Summary description for BinaryDump.
- ///
- public class BinaryDump
- {
- public byte[] Data { get; set; } = null;
-
- public int OffsetWidth { get; set; } = 3;
-
- public int DataWidth { get; set; } = 16;
-
-
- public BinaryDump()
- {
- }
-
- public static string Dump(byte[] data, int offsetWidth, int dataWidth)
- {
- var retval = "";
- var offset = 0;
- for (offset = 0; offset
- /// BinaryView class. It is used to calculate hex view parameters.
- ///
- public class BinaryView
- {
-
- ///
- /// Default constructor.
- ///
- public BinaryView()
- {
- CalculatePar();
- }
-
- ///
- /// Set hex view parameters. It calls to get the parameters.
- ///
- /// Parameters Definition:
- /// 000000 30 82 05 32 30 82 04 1A A0 03 02 01 02 02 0A 1F 0..20...........
- /// 000010 CE 8F 20 00 00 00 00 00 22 30 0D 06 09 2A 86 48 .. ....."0...*.H
- /// |----|offsetWidth |--dataWidth --|
- /// |----- hexWidth ---------------------------------------|
- /// |----- totalWidth -------------------------------------------------------|
- ///
- ///
- /// input
- /// input
- public void SetPar(int offsetWidth, int dataWidth)
- {
- OffsetWidth = offsetWidth;
- DataWidth = dataWidth;
- CalculatePar();
- }
-
- ///
- /// Constructor, it calls to set the parameters.
- ///
- /// input
- /// input
- public BinaryView(int offsetWidth, int dataWidth)
- {
- SetPar(offsetWidth, dataWidth);
- }
-
- ///
- /// Get offsetWidth.
- ///
- public int OffsetWidth { get; private set; } = 6;
-
- ///
- /// Get dataWidth.
- ///
- public int DataWidth { get; private set; } = 16;
-
- ///
- /// Get totalWidth.
- ///
- public int TotalWidth { get; private set; }
-
- ///
- /// Get hexWidth.
- ///
- public int HexWidth { get; private set; }
-
- ///
- /// Calculate hex view parameters.
- ///
- protected void CalculatePar()
- {
- TotalWidth = OffsetWidth + 2 + DataWidth*3 + ((DataWidth/8) - 1) + 1 + DataWidth;
- HexWidth = TotalWidth - DataWidth;
- }
-
- ///
- /// Generate hex view text string by calling .
- ///
- /// source byte array.
- /// output string.
- public string GenerateText(byte[] data)
- {
- return GetBinaryViewText(data, OffsetWidth, DataWidth);
- }
-
- ///
- /// Calculate the byte by offset.
- ///
- ///
- ///
- public void GetLocation(int byteOffset, ByteLocation loc)
- {
- var colOff = byteOffset - byteOffset/DataWidth * DataWidth;
- var line = byteOffset/DataWidth;
- var col = OffsetWidth + 2 + colOff * 3;
- var colLen = 3;
- var totOff = line * TotalWidth + line + col;
- var col2 = HexWidth + colOff;
- var totOff2 = line * TotalWidth + line + col2;
- var colLen2 = 1;
- loc.hexOffset = totOff;
- loc.hexColLen = colLen;
- loc.line = line;
- loc.chOffset = totOff2;
- loc.chColLen = colLen2;
- }
-
- ///
- /// Generate "Detail" hex view text.
- ///
- /// source byte array.
- /// offset text width.
- /// data text width
- /// detail hex view string.
- public static string GetBinaryViewText(byte[] data, int offsetWidth, int dataWidth)
- {
- var retval = "";
- var offForm = "{0:X"+offsetWidth+"} ";
- int i, lineStart, lineEnd;
- int line = 0, offset = 0;
- var totalWidth = offsetWidth + 2 + dataWidth*3 + ((dataWidth/8) - 1) + 1 + dataWidth;
- var hexWidth = totalWidth - dataWidth;
- var dumpStr = "";
- var lineStr = "";
- for (offset = 0; offset=data.Length) break;
- if ((i+1)%8==0 && i!=0 && (i+1)128)
- lineStr += '.';
- else
- lineStr += (char)data[i];
- }
- lineStr = lineStr.PadRight(totalWidth, ' ');
- dumpStr += lineStr + "\r\n";
- }
- retval = dumpStr;
- return retval;
- }
-
- }
-
- ///
- /// ByteLocation class is used by to transfer
- /// location parameters.
- ///
- public class ByteLocation
- {
- ///
- /// line number.
- ///
- public int line = 0;
-
- ///
- /// Hex encoded data length.
- ///
- public int hexColLen = 3;
-
- ///
- /// Hex encoded data offset.
- ///
- public int hexOffset = 0;
-
- ///
- /// Character length.
- ///
- public int chColLen = 1;
-
- ///
- /// Character offset.
- ///
- public int chOffset = 0;
-
- ///
- /// Constructor.
- ///
- public ByteLocation()
- {
- }
- }
-}
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs
deleted file mode 100644
index 53280f97..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/Oid.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-//+-------------------------------------------------------------------------------+
-//| Copyright (c) 2003 Liping Dai. All rights reserved. |
-//| Web: www.lipingshare.com |
-//| Email: lipingshare@yahoo.com |
-//| |
-//| Copyright and Permission Details: |
-//| ================================= |
-//| Permission is hereby granted, free of charge, to any person obtaining a copy |
-//| of this software and associated documentation files (the "Software"), to deal |
-//| in the Software without restriction, including without limitation the rights |
-//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the |
-//| Software, subject to the following conditions: |
-//| |
-//| 1. Redistributions of source code must retain the above copyright notice, this|
-//| list of conditions and the following disclaimer. |
-//| |
-//| 2. Redistributions in binary form must reproduce the above copyright notice, |
-//| this list of conditions and the following disclaimer in the documentation |
-//| and/or other materials provided with the distribution. |
-//| |
-//| THE SOFTWARE PRODUCT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, |
-//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
-//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
-//| A PARTICULAR PURPOSE. |
-//+-------------------------------------------------------------------------------+
-
-using System;
-using System.IO;
-
-namespace LipingShare.LCLib.Asn1Processor
-{
- ///
- /// Summary description for OID.
- /// This class is used to encode and decode OID strings.
- ///
- public class Oid
- {
- ///
- /// Encode OID string to byte array.
- ///
- /// source string.
- /// encoded array.
- public byte[] Encode(string oidStr)
- {
- var ms = new MemoryStream();
- Encode(ms, oidStr);
- ms.Position = 0;
- var retval = new byte[ms.Length];
- ms.Read(retval, 0, retval.Length);
- ms.Close();
- return retval;
- }
-
- ///
- /// Decode OID byte array to OID string.
- ///
- /// source byte array.
- /// result OID string.
- public string Decode(byte[] data)
- {
- var ms = new MemoryStream(data)
- {
- Position = 0
- };
- var retval = Decode(ms);
- ms.Close();
- return retval;
- }
-
- ///
- /// Encode OID string and put result into
- ///
- /// output stream.
- /// source OID string.
- public virtual void Encode(Stream bt, string oidStr) //TODO
- {
- var oidList = oidStr.Split('.');
- if (oidList.Length < 2) throw new Exception("Invalid OID string.");
- var values = new ulong[oidList.Length];
- for (var i = 0; i
- /// Decode OID and return OID string.
- ///
- /// source stream.
- /// result OID string.
- public virtual string Decode(Stream bt)
- {
- var retval = "";
- byte b;
- ulong v = 0;
- b = (byte) bt.ReadByte();
- retval += Convert.ToString(b/40);
- retval += "." + Convert.ToString(b%40);
- while (bt.Position < bt.Length)
- {
- try
- {
- DecodeValue(bt, ref v);
- retval += "." + v.ToString();
- }
- catch(Exception e)
- {
- throw new Exception("Failed to decode OID value: " + e.Message);
- }
- }
- return retval;
- }
-
- ///
- /// Default constructor
- ///
- public Oid()
- {
- }
-
- ///
- /// Encode single OID value.
- ///
- /// output stream.
- /// source value.
- protected void EncodeValue(Stream bt, ulong v)
- {
- for (var i=(Asn1Util.BitPrecision(v)-1)/7; i > 0; i--)
- {
- bt.WriteByte((byte)(0x80 | ((v >> (i*7)) & 0x7f)));
- }
- bt.WriteByte((byte)(v & 0x7f));
- }
-
- ///
- /// Decode single OID value.
- ///
- /// source stream.
- /// output value
- /// OID value bytes.
- protected int DecodeValue(Stream bt, ref ulong v)
- {
- byte b;
- var i=0;
- v = 0;
- while (true)
- {
- b = (byte) bt.ReadByte();
- i++;
- v <<= 7;
- v += (ulong) (b & 0x7f);
- if ((b & 0x80) == 0)
- return i;
- }
- }
-
- }
-}
-
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs b/ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs
deleted file mode 100644
index 5d8e2fa7..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/RelativeOid.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using System;
-using System.IO;
-
-namespace LipingShare.LCLib.Asn1Processor
-{
- ///
- /// Summary description for RelativeOid.
- ///
- public class RelativeOid : Oid
- {
- ///
- /// Constructor.
- ///
- public RelativeOid()
- {
- }
-
- ///
- /// Encode relative OID string and put result into
- ///
- /// output stream.
- /// source OID string.
- public override void Encode(Stream bt, string oidStr)
- {
- var oidList = oidStr.Split('.');
- var values = new ulong[oidList.Length];
- for (var i = 0; i
- /// Decode relative OID and return OID string.
- ///
- /// source stream.
- /// result OID string.
- public override string Decode(Stream bt)
- {
- var retval = "";
- ulong v = 0;
- var isFirst = true;
- while (bt.Position < bt.Length)
- {
- try
- {
- DecodeValue(bt, ref v);
- if (isFirst)
- {
- retval = v.ToString();
- isFirst = false;
- }
- else
- {
- retval += "." + v.ToString();
- }
- }
- catch(Exception e)
- {
- throw new Exception("Failed to decode OID value: " + e.Message);
- }
- }
- return retval;
- }
- }
-}
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor/Util.cs b/ExternalLibs/Asn1Processor/Asn1Processor/Util.cs
deleted file mode 100644
index aff94ef4..00000000
--- a/ExternalLibs/Asn1Processor/Asn1Processor/Util.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.IO;
-
-namespace LCLib.Asn1Processor
-{
- ///
- /// Summary description for Util.
- ///
- public class Asn1Util
- {
- public static int BytePrecision(ulong value)
- {
- int i;
- for (i=sizeof(ulong); i>0; --i)
- if ((value >> (i-1)*8)!=0)
- break;
- return i;
- }
-
- public static int DERLengthEncode(Stream xdata, ulong length)
- {
- var i=0;
- if (length <= 0x7f)
- {
- xdata.WriteByte((byte)length);
- i++;
- }
- else
- {
- xdata.WriteByte((byte)(BytePrecision(length) | 0x80));
- i++;
- for (var j=BytePrecision((ulong)length); j>0; --j)
- {
- xdata.WriteByte((byte)(length >> (j-1)*8));
- i++;
- }
- }
- return i;
- }
-
- public static long DerLengthDecode(Stream bt)
- {
- long length = 0;
- byte b;
- b = (byte) bt.ReadByte();
- if ((b & 0x80)==0)
- {
- length = b;
- }
- else
- {
- long lengthBytes = b & 0x7f;
- if (lengthBytes == 0)
- {
- throw new Exception("Indefinite length.");
- }
- length = 0;
- while (lengthBytes-- > 0)
- {
- if ((length >> (8*(sizeof(long)-1))) > 0)
- throw new Exception("Length overflow.");
- b = (byte) bt.ReadByte();
- length = (length << 8) | b;
- }
- }
- return length;
- }
-
- private Asn1Util()
- {
- }
-
- }
-}
diff --git a/ExternalLibs/Asn1Processor/Asn1Processor.Include.props b/ExternalLibs/AsnElt/AsnElt.Include.props
similarity index 57%
rename from ExternalLibs/Asn1Processor/Asn1Processor.Include.props
rename to ExternalLibs/AsnElt/AsnElt.Include.props
index 9e905360..c86484bc 100644
--- a/ExternalLibs/Asn1Processor/Asn1Processor.Include.props
+++ b/ExternalLibs/AsnElt/AsnElt.Include.props
@@ -1,10 +1,10 @@
- Asn1Processor\%(RecursiveDir)%(FileName)%(Extension)
+ AsnElt\%(FileName)%(Extension)
- Asn1Processor\%(RecursiveDir)%(FileName)%(Extension)
+ AsnElt\%(FileName)%(Extension)
\ No newline at end of file
diff --git a/ExternalLibs/AsnElt/AsnElt/AsnElt.cs b/ExternalLibs/AsnElt/AsnElt/AsnElt.cs
new file mode 100644
index 00000000..56a90d03
--- /dev/null
+++ b/ExternalLibs/AsnElt/AsnElt/AsnElt.cs
@@ -0,0 +1,2291 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * An AsnElt instance represents a decoded ASN.1 DER object. It is
+ * immutable.
+ */
+
+public class AsnElt {
+
+ /*
+ * Universal tag values.
+ */
+ public const int BOOLEAN = 1;
+ public const int INTEGER = 2;
+ public const int BIT_STRING = 3;
+ public const int OCTET_STRING = 4;
+ public const int NULL = 5;
+ public const int OBJECT_IDENTIFIER = 6;
+ public const int Object_Descriptor = 7;
+ public const int EXTERNAL = 8;
+ public const int REAL = 9;
+ public const int ENUMERATED = 10;
+ public const int EMBEDDED_PDV = 11;
+ public const int UTF8String = 12;
+ public const int RELATIVE_OID = 13;
+ public const int SEQUENCE = 16;
+ public const int SET = 17;
+ public const int NumericString = 18;
+ public const int PrintableString = 19;
+ public const int T61String = 20;
+ public const int TeletexString = 20;
+ public const int VideotexString = 21;
+ public const int IA5String = 22;
+ public const int UTCTime = 23;
+ public const int GeneralizedTime = 24;
+ public const int GraphicString = 25;
+ public const int VisibleString = 26;
+ public const int GeneralString = 27;
+ public const int UniversalString = 28;
+ public const int CHARACTER_STRING = 29;
+ public const int BMPString = 30;
+
+ /*
+ * Tag classes.
+ */
+ public const int UNIVERSAL = 0;
+ public const int APPLICATION = 1;
+ public const int CONTEXT = 2;
+ public const int PRIVATE = 3;
+
+ /*
+ * Internal rules
+ * ==============
+ *
+ * Instances are immutable. They reference an internal buffer
+ * that they never modify. The buffer is never shown to the
+ * outside; when decoding and creating, copies are performed
+ * where necessary.
+ *
+ * If the instance was created by decoding, then:
+ * objBuf points to the array containing the complete object
+ * objOff start offset for the object header
+ * objLen complete object length
+ * valOff offset for the first value byte
+ * valLen value length (excluding the null-tag, if applicable)
+ * hasEncodedHeader is true
+ *
+ * If the instance was created from an explicit value or from
+ * sub-elements, then:
+ * objBuf contains the value, or is null
+ * objOff is 0
+ * objLen is -1, or contains the computed object length
+ * valOff is 0
+ * valLen is -1, or contains the computed value length
+ * hasEncodedHeader is false
+ *
+ * If objBuf is null, then the object is necessarily constructed
+ * (Sub is not null). If objBuf is not null, then the encoded
+ * value is known (the object may be constructed or primitive),
+ * and valOff/valLen identify the actual value within objBuf.
+ *
+ * Tag class and value, and sub-elements, are referenced from
+ * specific properties.
+ */
+
+ byte[] objBuf;
+ int objOff;
+ int objLen;
+ int valOff;
+ int valLen;
+ bool hasEncodedHeader;
+
+ AsnElt()
+ {
+ }
+
+ /*
+ * The tag class for this element.
+ */
+ int tagClass_;
+ public int TagClass {
+ get {
+ return tagClass_;
+ }
+ private set {
+ tagClass_ = value;
+ }
+ }
+
+ /*
+ * The tag value for this element.
+ */
+ int tagValue_;
+ public int TagValue {
+ get {
+ return tagValue_;
+ }
+ private set {
+ tagValue_ = value;
+ }
+ }
+
+ /*
+ * The sub-elements. This is null if this element is primitive.
+ * DO NOT MODIFY this array.
+ */
+ AsnElt[] sub_;
+ public AsnElt[] Sub {
+ get {
+ return sub_;
+ }
+ private set {
+ sub_ = value;
+ }
+ }
+
+ /*
+ * The "constructed" flag: true for an elements with sub-elements,
+ * false for a primitive element.
+ */
+ public bool Constructed {
+ get {
+ return Sub != null;
+ }
+ }
+
+ /*
+ * The value length. When the object is BER-encoded with an
+ * indefinite length, the value length includes all the sub-objects
+ * but NOT the formal null-tag marker.
+ */
+ public int ValueLength {
+ get {
+ if (valLen < 0) {
+ if (Constructed) {
+ int vlen = 0;
+ foreach (AsnElt a in Sub) {
+ vlen += a.EncodedLength;
+ }
+ valLen = vlen;
+ } else {
+ valLen = objBuf.Length;
+ }
+ }
+ return valLen;
+ }
+ }
+
+ /*
+ * The encoded object length (complete with header).
+ */
+ public int EncodedLength {
+ get {
+ if (objLen < 0) {
+ int vlen = ValueLength;
+ objLen = TagLength(TagValue)
+ + LengthLength(vlen) + vlen;
+ }
+ return objLen;
+ }
+ }
+
+ /*
+ * Check that this element is constructed. An exception is thrown
+ * if this is not the case.
+ */
+ public void CheckConstructed()
+ {
+ if (!Constructed) {
+ throw new AsnException("not constructed");
+ }
+ }
+
+ /*
+ * Check that this element is primitive. An exception is thrown
+ * if this is not the case.
+ */
+ public void CheckPrimitive()
+ {
+ if (Constructed) {
+ throw new AsnException("not primitive");
+ }
+ }
+
+ /*
+ * Get a sub-element. This method throws appropriate exceptions
+ * if this element is not constructed, or the requested index
+ * is out of range.
+ */
+ public AsnElt GetSub(int n)
+ {
+ CheckConstructed();
+ if (n < 0 || n >= Sub.Length) {
+ throw new AsnException("no such sub-object: n=" + n);
+ }
+ return Sub[n];
+ }
+
+ /*
+ * Check that the tag is UNIVERSAL with the provided value.
+ */
+ public void CheckTag(int tv)
+ {
+ CheckTag(UNIVERSAL, tv);
+ }
+
+ /*
+ * Check that the tag has the specified class and value.
+ */
+ public void CheckTag(int tc, int tv)
+ {
+ if (TagClass != tc || TagValue != tv) {
+ throw new AsnException("unexpected tag: " + TagString);
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains exactly
+ * 'n' sub-elements.
+ */
+ public void CheckNumSub(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length != n) {
+ throw new AsnException("wrong number of sub-elements: "
+ + Sub.Length + " (expected: " + n + ")");
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains at least
+ * 'n' sub-elements.
+ */
+ public void CheckNumSubMin(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length < n) {
+ throw new AsnException("not enough sub-elements: "
+ + Sub.Length + " (minimum: " + n + ")");
+ }
+ }
+
+ /*
+ * Check that this element is constructed and contains no more
+ * than 'n' sub-elements.
+ */
+ public void CheckNumSubMax(int n)
+ {
+ CheckConstructed();
+ if (Sub.Length > n) {
+ throw new AsnException("too many sub-elements: "
+ + Sub.Length + " (maximum: " + n + ")");
+ }
+ }
+
+ /*
+ * Get a string representation of the tag class and value.
+ */
+ public string TagString {
+ get {
+ return TagToString(TagClass, TagValue);
+ }
+ }
+
+ static string TagToString(int tc, int tv)
+ {
+ switch (tc) {
+ case UNIVERSAL:
+ break;
+ case APPLICATION:
+ return "APPLICATION:" + tv;
+ case CONTEXT:
+ return "CONTEXT:" + tv;
+ case PRIVATE:
+ return "PRIVATE:" + tv;
+ default:
+ return String.Format("INVALID:{0}/{1}", tc, tv);
+ }
+
+ switch (tv) {
+ case BOOLEAN: return "BOOLEAN";
+ case INTEGER: return "INTEGER";
+ case BIT_STRING: return "BIT_STRING";
+ case OCTET_STRING: return "OCTET_STRING";
+ case NULL: return "NULL";
+ case OBJECT_IDENTIFIER: return "OBJECT_IDENTIFIER";
+ case Object_Descriptor: return "Object_Descriptor";
+ case EXTERNAL: return "EXTERNAL";
+ case REAL: return "REAL";
+ case ENUMERATED: return "ENUMERATED";
+ case EMBEDDED_PDV: return "EMBEDDED_PDV";
+ case UTF8String: return "UTF8String";
+ case RELATIVE_OID: return "RELATIVE_OID";
+ case SEQUENCE: return "SEQUENCE";
+ case SET: return "SET";
+ case NumericString: return "NumericString";
+ case PrintableString: return "PrintableString";
+ case TeletexString: return "TeletexString";
+ case VideotexString: return "VideotexString";
+ case IA5String: return "IA5String";
+ case UTCTime: return "UTCTime";
+ case GeneralizedTime: return "GeneralizedTime";
+ case GraphicString: return "GraphicString";
+ case VisibleString: return "VisibleString";
+ case GeneralString: return "GeneralString";
+ case UniversalString: return "UniversalString";
+ case CHARACTER_STRING: return "CHARACTER_STRING";
+ case BMPString: return "BMPString";
+ default:
+ return String.Format("UNIVERSAL:" + tv);
+ }
+ }
+
+ /*
+ * Get the encoded length for a tag.
+ */
+ static int TagLength(int tv)
+ {
+ if (tv <= 0x1F) {
+ return 1;
+ }
+ int z = 1;
+ while (tv > 0) {
+ z ++;
+ tv >>= 7;
+ }
+ return z;
+ }
+
+ /*
+ * Get the encoded length for a length.
+ */
+ static int LengthLength(int len)
+ {
+ if (len < 0x80) {
+ return 1;
+ }
+ int z = 1;
+ while (len > 0) {
+ z ++;
+ len >>= 8;
+ }
+ return z;
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. Trailing garbage is not tolerated.
+ */
+ public static AsnElt Decode(byte[] buf)
+ {
+ return Decode(buf, 0, buf.Length, true);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. Trailing garbage is not tolerated.
+ */
+ public static AsnElt Decode(byte[] buf, int off, int len)
+ {
+ return Decode(buf, off, len, true);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. If 'exactLength' is true, then trailing garbage is
+ * not tolerated (it triggers an exception).
+ */
+ public static AsnElt Decode(byte[] buf, bool exactLength)
+ {
+ return Decode(buf, 0, buf.Length, exactLength);
+ }
+
+ /*
+ * Decode an ASN.1 object. The provided buffer is internally
+ * copied. If 'exactLength' is true, then trailing garbage is
+ * not tolerated (it triggers an exception).
+ */
+ public static AsnElt Decode(byte[] buf, int off, int len,
+ bool exactLength)
+ {
+ int tc, tv, valOff, valLen, objLen;
+ bool cons;
+ objLen = Decode(buf, off, len,
+ out tc, out tv, out cons,
+ out valOff, out valLen);
+ if (exactLength && objLen != len) {
+ throw new AsnException("trailing garbage");
+ }
+ byte[] nbuf = new byte[objLen];
+ Array.Copy(buf, off, nbuf, 0, objLen);
+ return DecodeNoCopy(nbuf, 0, objLen);
+ }
+
+ /*
+ * Internal recursive decoder. The provided array is NOT copied.
+ * Trailing garbage is ignored (caller should use the 'objLen'
+ * field to learn the total object length).
+ */
+ static AsnElt DecodeNoCopy(byte[] buf, int off, int len)
+ {
+ int tc, tv, valOff, valLen, objLen;
+ bool cons;
+ objLen = Decode(buf, off, len,
+ out tc, out tv, out cons,
+ out valOff, out valLen);
+ AsnElt a = new AsnElt();
+ a.TagClass = tc;
+ a.TagValue = tv;
+ a.objBuf = buf;
+ a.objOff = off;
+ a.objLen = objLen;
+ a.valOff = valOff;
+ a.valLen = valLen;
+ a.hasEncodedHeader = true;
+ if (cons) {
+ List subs = new List();
+ off = valOff;
+ int lim = valOff + valLen;
+ while (off < lim) {
+ AsnElt b = DecodeNoCopy(buf, off, lim - off);
+ off += b.objLen;
+ subs.Add(b);
+ }
+ a.Sub = subs.ToArray();
+ } else {
+ a.Sub = null;
+ }
+ return a;
+ }
+
+ /*
+ * Decode the tag and length, and get the value offset and length.
+ * Returned value if the total object length.
+ * Note: when an object has indefinite length, the terminated
+ * "null tag" will NOT be considered part of the "value length".
+ */
+ static int Decode(byte[] buf, int off, int maxLen,
+ out int tc, out int tv, out bool cons,
+ out int valOff, out int valLen)
+ {
+ int lim = off + maxLen;
+ int orig = off;
+
+ /*
+ * Decode tag.
+ */
+ CheckOff(off, lim);
+ tv = buf[off ++];
+ cons = (tv & 0x20) != 0;
+ tc = tv >> 6;
+ tv &= 0x1F;
+ if (tv == 0x1F) {
+ tv = 0;
+ for (;;) {
+ CheckOff(off, lim);
+ int c = buf[off ++];
+ if (tv > 0xFFFFFF) {
+ throw new AsnException(
+ "tag value overflow");
+ }
+ tv = (tv << 7) | (c & 0x7F);
+ if ((c & 0x80) == 0) {
+ break;
+ }
+ }
+ }
+
+ /*
+ * Decode length.
+ */
+ CheckOff(off, lim);
+ int vlen = buf[off ++];
+ if (vlen == 0x80) {
+ /*
+ * Indefinite length. This is not strict DER, but
+ * we allow it nonetheless; we must check that
+ * the value was tagged as constructed, though.
+ */
+ vlen = -1;
+ if (!cons) {
+ throw new AsnException("indefinite length"
+ + " but not constructed");
+ }
+ } else if (vlen > 0x80) {
+ int lenlen = vlen - 0x80;
+ CheckOff(off + lenlen - 1, lim);
+ vlen = 0;
+ while (lenlen -- > 0) {
+ if (vlen > 0x7FFFFF) {
+ throw new AsnException(
+ "length overflow");
+ }
+ vlen = (vlen << 8) + buf[off ++];
+ }
+ }
+
+ /*
+ * Length was decoded, so the value starts here.
+ */
+ valOff = off;
+
+ /*
+ * If length is indefinite then we must explore sub-objects
+ * to get the value length.
+ */
+ if (vlen < 0) {
+ for (;;) {
+ int tc2, tv2, valOff2, valLen2;
+ bool cons2;
+ int slen;
+
+ slen = Decode(buf, off, lim - off,
+ out tc2, out tv2, out cons2,
+ out valOff2, out valLen2);
+ if (tc2 == 0 && tv2 == 0) {
+ if (cons2 || valLen2 != 0) {
+ throw new AsnException(
+ "invalid null tag");
+ }
+ valLen = off - valOff;
+ off += slen;
+ break;
+ } else {
+ off += slen;
+ }
+ }
+ } else {
+ if (vlen > (lim - off)) {
+ throw new AsnException("value overflow");
+ }
+ off += vlen;
+ valLen = off - valOff;
+ }
+
+ return off - orig;
+ }
+
+ static void CheckOff(int off, int lim)
+ {
+ if (off >= lim) {
+ throw new AsnException("offset overflow");
+ }
+ }
+
+ /*
+ * Get a specific byte from the value. This provided offset is
+ * relative to the value start (first value byte has offset 0).
+ */
+ public int ValueByte(int off)
+ {
+ if (off < 0) {
+ throw new AsnException("invalid value offset: " + off);
+ }
+ if (objBuf == null) {
+ int k = 0;
+ foreach (AsnElt a in Sub) {
+ int slen = a.EncodedLength;
+ if ((k + slen) > off) {
+ return a.ValueByte(off - k);
+ }
+ }
+ } else {
+ if (off < valLen) {
+ return objBuf[valOff + off];
+ }
+ }
+ throw new AsnException(String.Format(
+ "invalid value offset {0} (length = {1})",
+ off, ValueLength));
+ }
+
+ /*
+ * Encode this object into a newly allocated array.
+ */
+ public byte[] Encode()
+ {
+ byte[] r = new byte[EncodedLength];
+ Encode(r, 0);
+ return r;
+ }
+
+ /*
+ * Encode this object into the provided array. Encoded object
+ * length is returned.
+ */
+ public int Encode(byte[] dst, int off)
+ {
+ return Encode(0, Int32.MaxValue, dst, off);
+ }
+
+ /*
+ * Encode this object into the provided array. Only bytes
+ * at offset between 'start' (inclusive) and 'end' (exclusive)
+ * are actually written. The number of written bytes is returned.
+ * Offsets are relative to the object start (first tag byte).
+ */
+ int Encode(int start, int end, byte[] dst, int dstOff)
+ {
+ /*
+ * If the encoded value is already known, then we just
+ * dump it.
+ */
+ if (hasEncodedHeader) {
+ int from = objOff + Math.Max(0, start);
+ int to = objOff + Math.Min(objLen, end);
+ int len = to - from;
+ if (len > 0) {
+ Array.Copy(objBuf, from, dst, dstOff, len);
+ return len;
+ } else {
+ return 0;
+ }
+ }
+
+ int off = 0;
+
+ /*
+ * Encode tag.
+ */
+ int fb = (TagClass << 6) + (Constructed ? 0x20 : 0x00);
+ if (TagValue < 0x1F) {
+ fb |= (TagValue & 0x1F);
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)fb;
+ }
+ off ++;
+ } else {
+ fb |= 0x1F;
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)fb;
+ }
+ off ++;
+ int k = 0;
+ for (int v = TagValue; v > 0; v >>= 7, k += 7);
+ while (k > 0) {
+ k -= 7;
+ int v = (TagValue >> k) & 0x7F;
+ if (k != 0) {
+ v |= 0x80;
+ }
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)v;
+ }
+ off ++;
+ }
+ }
+
+ /*
+ * Encode length.
+ */
+ int vlen = ValueLength;
+ if (vlen < 0x80) {
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)vlen;
+ }
+ off ++;
+ } else {
+ int k = 0;
+ for (int v = vlen; v > 0; v >>= 8, k += 8);
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)(0x80 + (k >> 3));
+ }
+ off ++;
+ while (k > 0) {
+ k -= 8;
+ if (start <= off && off < end) {
+ dst[dstOff ++] = (byte)(vlen >> k);
+ }
+ off ++;
+ }
+ }
+
+ /*
+ * Encode value. We must adjust the start/end window to
+ * make it relative to the value.
+ */
+ EncodeValue(start - off, end - off, dst, dstOff);
+ off += vlen;
+
+ /*
+ * Compute copied length.
+ */
+ return Math.Max(0, Math.Min(off, end) - Math.Max(0, start));
+ }
+
+ /*
+ * Encode the value into the provided buffer. Only value bytes
+ * at offsets between 'start' (inclusive) and 'end' (exclusive)
+ * are written. Actual number of written bytes is returned.
+ * Offsets are relative to the start of the value.
+ */
+ int EncodeValue(int start, int end, byte[] dst, int dstOff)
+ {
+ int orig = dstOff;
+ if (objBuf == null) {
+ int k = 0;
+ foreach (AsnElt a in Sub) {
+ int slen = a.EncodedLength;
+ dstOff += a.Encode(start - k, end - k,
+ dst, dstOff);
+ k += slen;
+ }
+ } else {
+ int from = Math.Max(0, start);
+ int to = Math.Min(valLen, end);
+ int len = to - from;
+ if (len > 0) {
+ Array.Copy(objBuf, valOff + from,
+ dst, dstOff, len);
+ dstOff += len;
+ }
+ }
+ return dstOff - orig;
+ }
+
+ /*
+ * Copy a value chunk. The provided offset ('off') and length ('len')
+ * define the chunk to copy; the offset is relative to the value
+ * start (first value byte has offset 0). If the requested window
+ * exceeds the value boundaries, an exception is thrown.
+ */
+ public void CopyValueChunk(int off, int len, byte[] dst, int dstOff)
+ {
+ int vlen = ValueLength;
+ if (off < 0 || len < 0 || len > (vlen - off)) {
+ throw new AsnException(String.Format(
+ "invalid value window {0}:{1}"
+ + " (value length = {2})", off, len, vlen));
+ }
+ EncodeValue(off, off + len, dst, dstOff);
+ }
+
+ /*
+ * Copy the value into the specified array. The value length is
+ * returned.
+ */
+ public int CopyValue(byte[] dst, int off)
+ {
+ return EncodeValue(0, Int32.MaxValue, dst, off);
+ }
+
+ /*
+ * Get a copy of the value as a freshly allocated array.
+ */
+ public byte[] CopyValue()
+ {
+ byte[] r = new byte[ValueLength];
+ EncodeValue(0, r.Length, r, 0);
+ return r;
+ }
+
+ /*
+ * Get the value. This may return a shared buffer, that MUST NOT
+ * be modified.
+ */
+ byte[] GetValue(out int off, out int len)
+ {
+ if (objBuf == null) {
+ /*
+ * We can modify objBuf because CopyValue()
+ * called ValueLength, thus valLen has been
+ * filled.
+ */
+ objBuf = CopyValue();
+ off = 0;
+ len = objBuf.Length;
+ } else {
+ off = valOff;
+ len = valLen;
+ }
+ return objBuf;
+ }
+
+ /*
+ * Interpret the value as a BOOLEAN.
+ */
+ public bool GetBoolean()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid BOOLEAN (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen != 1) {
+ throw new AsnException(String.Format(
+ "invalid BOOLEAN (length = {0})", vlen));
+ }
+ return ValueByte(0) != 0;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. An exception is thrown if
+ * the value does not fit in a 'long'.
+ */
+ public long GetInteger()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid INTEGER (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException("invalid INTEGER (length = 0)");
+ }
+ int v = ValueByte(0);
+ long x;
+ if ((v & 0x80) != 0) {
+ x = -1;
+ for (int k = 0; k < vlen; k ++) {
+ if (x < ((-1L) << 55)) {
+ throw new AsnException(
+ "integer overflow (negative)");
+ }
+ x = (x << 8) + (long)ValueByte(k);
+ }
+ } else {
+ x = 0;
+ for (int k = 0; k < vlen; k ++) {
+ if (x >= (1L << 55)) {
+ throw new AsnException(
+ "integer overflow (positive)");
+ }
+ x = (x << 8) + (long)ValueByte(k);
+ }
+ }
+ return x;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. An exception is thrown if
+ * the value is outside of the provided range.
+ */
+ public long GetInteger(long min, long max)
+ {
+ long v = GetInteger();
+ if (v < min || v > max) {
+ throw new AsnException("integer out of allowed range");
+ }
+ return v;
+ }
+
+ /*
+ * Interpret the value as an INTEGER. Return its hexadecimal
+ * representation (uppercase), preceded by a '0x' or '-0x'
+ * header, depending on the integer sign. The number of
+ * hexadecimal digits is even. Leading zeroes are returned (but
+ * one may remain, to ensure an even number of digits). If the
+ * integer has value 0, then 0x00 is returned.
+ */
+ public string GetIntegerHex()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid INTEGER (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException("invalid INTEGER (length = 0)");
+ }
+ StringBuilder sb = new StringBuilder();
+ byte[] tmp = CopyValue();
+ if (tmp[0] >= 0x80) {
+ sb.Append('-');
+ int cc = 1;
+ for (int i = tmp.Length - 1; i >= 0; i --) {
+ int v = ((~tmp[i]) & 0xFF) + cc;
+ tmp[i] = (byte)v;
+ cc = v >> 8;
+ }
+ }
+ int k = 0;
+ while (k < tmp.Length && tmp[k] == 0) {
+ k ++;
+ }
+ if (k == tmp.Length) {
+ return "0x00";
+ }
+ sb.Append("0x");
+ while (k < tmp.Length) {
+ sb.AppendFormat("{0:X2}", tmp[k ++]);
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Interpret the value as an OCTET STRING. The value bytes are
+ * returned. This method supports constructed values and performs
+ * the reassembly.
+ */
+ public byte[] GetOctetString()
+ {
+ int len = GetOctetString(null, 0);
+ byte[] r = new byte[len];
+ GetOctetString(r, 0);
+ return r;
+ }
+
+ /*
+ * Interpret the value as an OCTET STRING. The value bytes are
+ * written in dst[], starting at offset 'off', and the total value
+ * length is returned. If 'dst' is null, then no byte is written
+ * anywhere, but the total length is still returned. This method
+ * supports constructed values and performs the reassembly.
+ */
+ public int GetOctetString(byte[] dst, int off)
+ {
+ if (Constructed) {
+ int orig = off;
+ foreach (AsnElt ae in Sub) {
+ ae.CheckTag(AsnElt.OCTET_STRING);
+ off += ae.GetOctetString(dst, off);
+ }
+ return off - orig;
+ }
+ if (dst != null) {
+ return CopyValue(dst, off);
+ } else {
+ return ValueLength;
+ }
+ }
+
+ /*
+ * Interpret the value as a BIT STRING. The bits are returned,
+ * with the "ignored bits" cleared.
+ */
+ public byte[] GetBitString()
+ {
+ int bitLength;
+ return GetBitString(out bitLength);
+ }
+
+ /*
+ * Interpret the value as a BIT STRING. The bits are returned,
+ * with the "ignored bits" cleared. The actual bit length is
+ * written in 'bitLength'.
+ */
+ public byte[] GetBitString(out int bitLength)
+ {
+ if (Constructed) {
+ /*
+ * TODO: support constructed BIT STRING values.
+ */
+ throw new AsnException(
+ "invalid BIT STRING (constructed)");
+ }
+ int vlen = ValueLength;
+ if (vlen == 0) {
+ throw new AsnException(
+ "invalid BIT STRING (length = 0)");
+ }
+ int fb = ValueByte(0);
+ if (fb > 7 || (vlen == 1 && fb != 0)) {
+ throw new AsnException(String.Format(
+ "invalid BIT STRING (start = 0x{0:X2})", fb));
+ }
+ byte[] r = new byte[vlen - 1];
+ CopyValueChunk(1, vlen - 1, r, 0);
+ if (vlen > 1) {
+ r[r.Length - 1] &= (byte)(0xFF << fb);
+ }
+ bitLength = (r.Length << 3) - fb;
+ return r;
+ }
+
+ /*
+ * Interpret the value as a NULL.
+ */
+ public void CheckNull()
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid NULL (constructed)");
+ }
+ if (ValueLength != 0) {
+ throw new AsnException(String.Format(
+ "invalid NULL (length = {0})", ValueLength));
+ }
+ }
+
+ /*
+ * Interpret the value as an OBJECT IDENTIFIER, and return it
+ * (in decimal-dotted string format).
+ */
+ public string GetOID()
+ {
+ CheckPrimitive();
+ if (valLen == 0) {
+ throw new AsnException("zero-length OID");
+ }
+ int v = objBuf[valOff];
+ if (v >= 120) {
+ throw new AsnException(
+ "invalid OID: first byte = " + v);
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.Append(v / 40);
+ sb.Append('.');
+ sb.Append(v % 40);
+ long acc = 0;
+ bool uv = false;
+ for (int i = 1; i < valLen; i ++) {
+ v = objBuf[valOff + i];
+ if ((acc >> 56) != 0) {
+ throw new AsnException(
+ "invalid OID: integer overflow");
+ }
+ acc = (acc << 7) + (long)(v & 0x7F);
+ if ((v & 0x80) == 0) {
+ sb.Append('.');
+ sb.Append(acc);
+ acc = 0;
+ uv = false;
+ } else {
+ uv = true;
+ }
+ }
+ if (uv) {
+ throw new AsnException(
+ "invalid OID: truncated");
+ }
+ return sb.ToString();
+ }
+
+ /*
+ * Get the object value as a string. The string type is inferred
+ * from the tag.
+ */
+ public string GetString()
+ {
+ if (TagClass != UNIVERSAL) {
+ throw new AsnException(String.Format(
+ "cannot infer string type: {0}:{1}",
+ TagClass, TagValue));
+ }
+ return GetString(TagValue);
+ }
+
+ /*
+ * Get the object value as a string. The string type is provided
+ * (universal tag value). Supported string types include
+ * NumericString, PrintableString, IA5String, TeletexString
+ * (interpreted as ISO-8859-1), UTF8String, BMPString and
+ * UniversalString; the "time types" (UTCTime and GeneralizedTime)
+ * are also supported, though, in their case, the internal
+ * contents are not checked (they are decoded as PrintableString).
+ */
+ public string GetString(int type)
+ {
+ if (Constructed) {
+ throw new AsnException(
+ "invalid string (constructed)");
+ }
+ switch (type) {
+ case NumericString:
+ case PrintableString:
+ case IA5String:
+ case TeletexString:
+ case UTCTime:
+ case GeneralizedTime:
+ return DecodeMono(objBuf, valOff, valLen, type);
+ case UTF8String:
+ return DecodeUTF8(objBuf, valOff, valLen);
+ case BMPString:
+ return DecodeUTF16(objBuf, valOff, valLen);
+ case UniversalString:
+ return DecodeUTF32(objBuf, valOff, valLen);
+ default:
+ throw new AsnException(
+ "unsupported string type: " + type);
+ }
+ }
+
+ static string DecodeMono(byte[] buf, int off, int len, int type)
+ {
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ tc[i] = (char)buf[off + i];
+ }
+ VerifyChars(tc, type);
+ return new string(tc);
+ }
+
+ static string DecodeUTF8(byte[] buf, int off, int len)
+ {
+ /*
+ * Skip BOM.
+ */
+ if (len >= 3 && buf[off] == 0xEF
+ && buf[off + 1] == 0xBB && buf[off + 2] == 0xBF)
+ {
+ off += 3;
+ len -= 3;
+ }
+ char[] tc = null;
+ for (int k = 0; k < 2; k ++) {
+ int tcOff = 0;
+ for (int i = 0; i < len; i ++) {
+ int c = buf[off + i];
+ int e;
+ if (c < 0x80) {
+ e = 0;
+ } else if (c < 0xC0) {
+ throw BadByte(c, UTF8String);
+ } else if (c < 0xE0) {
+ c &= 0x1F;
+ e = 1;
+ } else if (c < 0xF0) {
+ c &= 0x0F;
+ e = 2;
+ } else if (c < 0xF8) {
+ c &= 0x07;
+ e = 3;
+ } else {
+ throw BadByte(c, UTF8String);
+ }
+ while (e -- > 0) {
+ if (++ i >= len) {
+ throw new AsnException(
+ "invalid UTF-8 string");
+ }
+ int d = buf[off + i];
+ if (d < 0x80 || d > 0xBF) {
+ throw BadByte(d, UTF8String);
+ }
+ c = (c << 6) + (d & 0x3F);
+ }
+ if (c > 0x10FFFF) {
+ throw BadChar(c, UTF8String);
+ }
+ if (c > 0xFFFF) {
+ c -= 0x10000;
+ int hi = 0xD800 + (c >> 10);
+ int lo = 0xDC00 + (c & 0x3FF);
+ if (tc != null) {
+ tc[tcOff] = (char)hi;
+ tc[tcOff + 1] = (char)lo;
+ }
+ tcOff += 2;
+ } else {
+ if (tc != null) {
+ tc[tcOff] = (char)c;
+ }
+ tcOff ++;
+ }
+ }
+ if (tc == null) {
+ tc = new char[tcOff];
+ }
+ }
+ VerifyChars(tc, UTF8String);
+ return new string(tc);
+ }
+
+ static string DecodeUTF16(byte[] buf, int off, int len)
+ {
+ if ((len & 1) != 0) {
+ throw new AsnException(
+ "invalid UTF-16 string: length = " + len);
+ }
+ len >>= 1;
+ if (len == 0) {
+ return "";
+ }
+ bool be = true;
+ int hi = buf[off];
+ int lo = buf[off + 1];
+ if (hi == 0xFE && lo == 0xFF) {
+ off += 2;
+ len --;
+ } else if (hi == 0xFF && lo == 0xFE) {
+ off += 2;
+ len --;
+ be = false;
+ }
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int b0 = buf[off ++];
+ int b1 = buf[off ++];
+ if (be) {
+ tc[i] = (char)((b0 << 8) + b1);
+ } else {
+ tc[i] = (char)((b1 << 8) + b0);
+ }
+ }
+ VerifyChars(tc, BMPString);
+ return new string(tc);
+ }
+
+ static string DecodeUTF32(byte[] buf, int off, int len)
+ {
+ if ((len & 3) != 0) {
+ throw new AsnException(
+ "invalid UTF-32 string: length = " + len);
+ }
+ len >>= 2;
+ if (len == 0) {
+ return "";
+ }
+ bool be = true;
+ if (buf[off] == 0x00
+ && buf[off + 1] == 0x00
+ && buf[off + 2] == 0xFE
+ && buf[off + 3] == 0xFF)
+ {
+ off += 4;
+ len --;
+ } else if (buf[off] == 0xFF
+ && buf[off + 1] == 0xFE
+ && buf[off + 2] == 0x00
+ && buf[off + 3] == 0x00)
+ {
+ off += 4;
+ len --;
+ be = false;
+ }
+
+ char[] tc = null;
+ for (int k = 0; k < 2; k ++) {
+ int tcOff = 0;
+ for (int i = 0; i < len; i ++) {
+ uint b0 = buf[off + 0];
+ uint b1 = buf[off + 1];
+ uint b2 = buf[off + 2];
+ uint b3 = buf[off + 3];
+ uint c;
+ if (be) {
+ c = (b0 << 24) | (b1 << 16)
+ | (b2 << 8) | b3;
+ } else {
+ c = (b3 << 24) | (b2 << 16)
+ | (b1 << 8) | b0;
+ }
+ if (c > 0x10FFFF) {
+ throw BadChar((int)c, UniversalString);
+ }
+ if (c > 0xFFFF) {
+ c -= 0x10000;
+ int hi = 0xD800 + (int)(c >> 10);
+ int lo = 0xDC00 + (int)(c & 0x3FF);
+ if (tc != null) {
+ tc[tcOff] = (char)hi;
+ tc[tcOff + 1] = (char)lo;
+ }
+ tcOff += 2;
+ } else {
+ if (tc != null) {
+ tc[tcOff] = (char)c;
+ }
+ tcOff ++;
+ }
+ }
+ if (tc == null) {
+ tc = new char[tcOff];
+ }
+ }
+ VerifyChars(tc, UniversalString);
+ return new string(tc);
+ }
+
+ static void VerifyChars(char[] tc, int type)
+ {
+ switch (type) {
+ case NumericString:
+ foreach (char c in tc) {
+ if (!IsNum(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case PrintableString:
+ case UTCTime:
+ case GeneralizedTime:
+ foreach (char c in tc) {
+ if (!IsPrintable(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case IA5String:
+ foreach (char c in tc) {
+ if (!IsIA5(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ case TeletexString:
+ foreach (char c in tc) {
+ if (!IsLatin1(c)) {
+ throw BadChar(c, type);
+ }
+ }
+ return;
+ }
+
+ /*
+ * For Unicode string types (UTF-8, BMP...).
+ */
+ for (int i = 0; i < tc.Length; i ++) {
+ int c = tc[i];
+ if (c >= 0xFDD0 && c <= 0xFDEF) {
+ throw BadChar(c, type);
+ }
+ if (c == 0xFFFE || c == 0xFFFF) {
+ throw BadChar(c, type);
+ }
+ if (c < 0xD800 || c > 0xDFFF) {
+ continue;
+ }
+ if (c > 0xDBFF) {
+ throw BadChar(c, type);
+ }
+ int hi = c & 0x3FF;
+ if (++ i >= tc.Length) {
+ throw BadChar(c, type);
+ }
+ c = tc[i];
+ if (c < 0xDC00 || c > 0xDFFF) {
+ throw BadChar(c, type);
+ }
+ int lo = c & 0x3FF;
+ c = 0x10000 + lo + (hi << 10);
+ if ((c & 0xFFFE) == 0xFFFE) {
+ throw BadChar(c, type);
+ }
+ }
+ }
+
+ static bool IsNum(int c)
+ {
+ return c == ' ' || (c >= '0' && c <= '9');
+ }
+
+ internal static bool IsPrintable(int c)
+ {
+ if (c >= 'A' && c <= 'Z') {
+ return true;
+ }
+ if (c >= 'a' && c <= 'z') {
+ return true;
+ }
+ if (c >= '0' && c <= '9') {
+ return true;
+ }
+ switch (c) {
+ case ' ': case '(': case ')': case '+':
+ case ',': case '-': case '.': case '/':
+ case ':': case '=': case '?': case '\'':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ static bool IsIA5(int c)
+ {
+ return c < 128;
+ }
+
+ static bool IsLatin1(int c)
+ {
+ return c < 256;
+ }
+
+ static AsnException BadByte(int c, int type)
+ {
+ return new AsnException(String.Format(
+ "unexpected byte 0x{0:X2} in string of type {1}",
+ c, type));
+ }
+
+ static AsnException BadChar(int c, int type)
+ {
+ return new AsnException(String.Format(
+ "unexpected character U+{0:X4} in string of type {1}",
+ c, type));
+ }
+
+ /*
+ * Decode the value as a date/time. Returned object is in UTC.
+ * Type of date is inferred from the tag value.
+ */
+ public DateTime GetTime()
+ {
+ if (TagClass != UNIVERSAL) {
+ throw new AsnException(String.Format(
+ "cannot infer date type: {0}:{1}",
+ TagClass, TagValue));
+ }
+ return GetTime(TagValue);
+ }
+
+ /*
+ * Decode the value as a date/time. Returned object is in UTC.
+ * The time string type is provided as parameter (UTCTime or
+ * GeneralizedTime).
+ */
+ public DateTime GetTime(int type)
+ {
+ bool isGen = false;
+ switch (type) {
+ case UTCTime:
+ break;
+ case GeneralizedTime:
+ isGen = true;
+ break;
+ default:
+ throw new AsnException(
+ "unsupported date type: " + type);
+ }
+ string s = GetString(type);
+ string orig = s;
+
+ /*
+ * UTCTime has format:
+ * YYMMDDhhmm[ss](Z|(+|-)hhmm)
+ *
+ * GeneralizedTime has format:
+ * YYYYMMDDhhmmss[.uu*][Z|(+|-)hhmm]
+ *
+ * Differences between the two types:
+ * -- UTCTime encodes year over two digits; GeneralizedTime
+ * uses four digits. UTCTime years map to 1950..2049 (00 is
+ * 2000).
+ * -- Seconds are optional with UTCTime, mandatory with
+ * GeneralizedTime.
+ * -- GeneralizedTime can have fractional seconds (optional).
+ * -- Time zone is optional for GeneralizedTime. However,
+ * a missing time zone means "local time" which depends on
+ * the locality, so this is discouraged.
+ *
+ * Some other notes:
+ * -- If there is a fractional second, then it must include
+ * at least one digit. This implementation processes the
+ * first three digits, and ignores the rest (if present).
+ * -- Time zone offset ranges from -23:59 to +23:59.
+ * -- The calendar computations are delegated to .NET's
+ * DateTime (and DateTimeOffset) so this implements a
+ * Gregorian calendar, even for dates before 1589. Year 0
+ * is not supported.
+ */
+
+ /*
+ * Check characters.
+ */
+ foreach (char c in s) {
+ if (c >= '0' && c <= '9') {
+ continue;
+ }
+ if (c == '.' || c == '+' || c == '-' || c == 'Z') {
+ continue;
+ }
+ throw BadTime(type, orig);
+ }
+
+ bool good = true;
+
+ /*
+ * Parse the time zone.
+ */
+ int tzHours = 0;
+ int tzMinutes = 0;
+ bool negZ = false;
+ bool noTZ = false;
+ if (s.EndsWith("Z")) {
+ s = s.Substring(0, s.Length - 1);
+ } else {
+ int j = s.IndexOf('+');
+ if (j < 0) {
+ j = s.IndexOf('-');
+ negZ = true;
+ }
+ if (j < 0) {
+ noTZ = true;
+ } else {
+ string t = s.Substring(j + 1);
+ s = s.Substring(0, j);
+ if (t.Length != 4) {
+ throw BadTime(type, orig);
+ }
+ tzHours = Dec2(t, 0, ref good);
+ tzMinutes = Dec2(t, 2, ref good);
+ if (tzHours < 0 || tzHours > 23
+ || tzMinutes < 0 || tzMinutes > 59)
+ {
+ throw BadTime(type, orig);
+ }
+ }
+ }
+
+ /*
+ * Lack of time zone is allowed only for GeneralizedTime.
+ */
+ if (noTZ && !isGen) {
+ throw BadTime(type, orig);
+ }
+
+ /*
+ * Parse the date elements.
+ */
+ if (s.Length < 4) {
+ throw BadTime(type, orig);
+ }
+ int year = Dec2(s, 0, ref good);
+ if (isGen) {
+ year = year * 100 + Dec2(s, 2, ref good);
+ s = s.Substring(4);
+ } else {
+ if (year < 50) {
+ year += 100;
+ }
+ year += 1900;
+ s = s.Substring(2);
+ }
+ int month = Dec2(s, 0, ref good);
+ int day = Dec2(s, 2, ref good);
+ int hour = Dec2(s, 4, ref good);
+ int minute = Dec2(s, 6, ref good);
+ int second = 0;
+ int millisecond = 0;
+ if (isGen) {
+ second = Dec2(s, 8, ref good);
+ if (s.Length >= 12 && s[10] == '.') {
+ s = s.Substring(11);
+ foreach (char c in s) {
+ if (c < '0' || c > '9') {
+ good = false;
+ break;
+ }
+ }
+ s += "0000";
+ millisecond = 10 * Dec2(s, 0, ref good)
+ + Dec2(s, 2, ref good) / 10;
+ } else if (s.Length != 10) {
+ good = false;
+ }
+ } else {
+ switch (s.Length) {
+ case 8:
+ break;
+ case 10:
+ second = Dec2(s, 8, ref good);
+ break;
+ default:
+ throw BadTime(type, orig);
+ }
+ }
+
+ /*
+ * Parsing is finished; if any error occurred, then
+ * the 'good' flag has been cleared.
+ */
+ if (!good) {
+ throw BadTime(type, orig);
+ }
+
+ /*
+ * Leap seconds are not supported by .NET, so we claim
+ * they do not occur.
+ */
+ if (second == 60) {
+ second = 59;
+ }
+
+ /*
+ * .NET implementation performs all the checks (including
+ * checks on month length depending on year, as per the
+ * proleptic Gregorian calendar).
+ */
+ try {
+ if (noTZ) {
+ DateTime dt = new DateTime(year, month, day,
+ hour, minute, second, millisecond,
+ DateTimeKind.Local);
+ return dt.ToUniversalTime();
+ }
+ TimeSpan tzOff = new TimeSpan(tzHours, tzMinutes, 0);
+ if (negZ) {
+ tzOff = tzOff.Negate();
+ }
+ DateTimeOffset dto = new DateTimeOffset(
+ year, month, day, hour, minute, second,
+ millisecond, tzOff);
+ return dto.UtcDateTime;
+ } catch (Exception e) {
+ throw BadTime(type, orig, e);
+ }
+ }
+
+ static int Dec2(string s, int off, ref bool good)
+ {
+ if (off < 0 || off >= (s.Length - 1)) {
+ good = false;
+ return -1;
+ }
+ char c1 = s[off];
+ char c2 = s[off + 1];
+ if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') {
+ good = false;
+ return -1;
+ }
+ return 10 * (c1 - '0') + (c2 - '0');
+ }
+
+ static AsnException BadTime(int type, string s)
+ {
+ return BadTime(type, s, null);
+ }
+
+ static AsnException BadTime(int type, string s, Exception e)
+ {
+ string tt = (type == UTCTime) ? "UTCTime" : "GeneralizedTime";
+ string msg = String.Format("invalid {0} string: '{1}'", tt, s);
+ if (e == null) {
+ return new AsnException(msg);
+ } else {
+ return new AsnException(msg, e);
+ }
+ }
+
+ /* =============================================================== */
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagValue, byte[] val)
+ {
+ return MakePrimitive(UNIVERSAL, tagValue, val, 0, val.Length);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagValue,
+ byte[] val, int off, int len)
+ {
+ return MakePrimitive(UNIVERSAL, tagValue, val, off, len);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(
+ int tagClass, int tagValue, byte[] val)
+ {
+ return MakePrimitive(tagClass, tagValue, val, 0, val.Length);
+ }
+
+ /*
+ * Create a new element for a primitive value. The provided buffer
+ * is internally copied.
+ */
+ public static AsnElt MakePrimitive(int tagClass, int tagValue,
+ byte[] val, int off, int len)
+ {
+ byte[] nval = new byte[len];
+ Array.Copy(val, off, nval, 0, len);
+ return MakePrimitiveInner(tagClass, tagValue, nval, 0, len);
+ }
+
+ /*
+ * Like MakePrimitive(), but the provided array is NOT copied.
+ * This is for other factory methods that already allocate a
+ * new array.
+ */
+ static AsnElt MakePrimitiveInner(int tagValue, byte[] val)
+ {
+ return MakePrimitiveInner(UNIVERSAL, tagValue,
+ val, 0, val.Length);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagValue,
+ byte[] val, int off, int len)
+ {
+ return MakePrimitiveInner(UNIVERSAL, tagValue, val, off, len);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagClass, int tagValue, byte[] val)
+ {
+ return MakePrimitiveInner(tagClass, tagValue,
+ val, 0, val.Length);
+ }
+
+ static AsnElt MakePrimitiveInner(int tagClass, int tagValue,
+ byte[] val, int off, int len)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = new byte[len];
+ Array.Copy(val, off, a.objBuf, 0, len);
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = len;
+ a.hasEncodedHeader = false;
+ if (tagClass < 0 || tagClass > 3) {
+ throw new AsnException(
+ "invalid tag class: " + tagClass);
+ }
+ if (tagValue < 0) {
+ throw new AsnException(
+ "invalid tag value: " + tagValue);
+ }
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ a.Sub = null;
+ return a;
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer.
+ */
+ public static AsnElt MakeInteger(long x)
+ {
+ if (x >= 0) {
+ return MakeInteger((ulong)x);
+ }
+ int k = 1;
+ for (long w = x; w <= -(long)0x80; w >>= 8) {
+ k ++;
+ }
+ byte[] v = new byte[k];
+ for (long w = x; k > 0; w >>= 8) {
+ v[-- k] = (byte)w;
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer.
+ */
+ public static AsnElt MakeInteger(ulong x)
+ {
+ int k = 1;
+ for (ulong w = x; w >= 0x80; w >>= 8) {
+ k ++;
+ }
+ byte[] v = new byte[k];
+ for (ulong w = x; k > 0; w >>= 8) {
+ v[-- k] = (byte)w;
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer. The x[]
+ * array uses _unsigned_ big-endian encoding.
+ */
+ public static AsnElt MakeInteger(byte[] x)
+ {
+ int xLen = x.Length;
+ int j = 0;
+ while (j < xLen && x[j] == 0x00) {
+ j ++;
+ }
+ if (j == xLen) {
+ return MakePrimitiveInner(INTEGER, new byte[] { 0x00 });
+ }
+ byte[] v;
+ if (x[j] < 0x80) {
+ v = new byte[xLen - j];
+ Array.Copy(x, j, v, 0, v.Length);
+ } else {
+ v = new byte[1 + xLen - j];
+ Array.Copy(x, j, v, 1, v.Length - 1);
+ }
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a new INTEGER value for the provided integer. The x[]
+ * array uses _signed_ big-endian encoding.
+ */
+ public static AsnElt MakeIntegerSigned(byte[] x)
+ {
+ int xLen = x.Length;
+ if (xLen == 0) {
+ throw new AsnException(
+ "Invalid signed integer (empty)");
+ }
+ int j = 0;
+ if (x[0] >= 0x80) {
+ while (j < (xLen - 1)
+ && x[j] == 0xFF
+ && x[j + 1] >= 0x80)
+ {
+ j ++;
+ }
+ } else {
+ while (j < (xLen - 1)
+ && x[j] == 0x00
+ && x[j + 1] < 0x80)
+ {
+ j ++;
+ }
+ }
+ byte[] v = new byte[xLen - j];
+ Array.Copy(x, j, v, 0, v.Length);
+ return MakePrimitiveInner(INTEGER, v);
+ }
+
+ /*
+ * Create a BIT STRING from the provided value. The number of
+ * "unused bits" is set to 0.
+ */
+ public static AsnElt MakeBitString(byte[] buf)
+ {
+ return MakeBitString(buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBitString(byte[] buf, int off, int len)
+ {
+ byte[] tmp = new byte[len + 1];
+ Array.Copy(buf, off, tmp, 1, len);
+ return MakePrimitiveInner(BIT_STRING, tmp);
+ }
+
+ /*
+ * Create a BIT STRING from the provided value. The number of
+ * "unused bits" is specified.
+ */
+ public static AsnElt MakeBitString(int unusedBits, byte[] buf)
+ {
+ return MakeBitString(unusedBits, buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBitString(int unusedBits,
+ byte[] buf, int off, int len)
+ {
+ if (unusedBits < 0 || unusedBits > 7
+ || (unusedBits != 0 && len == 0))
+ {
+ throw new AsnException(
+ "Invalid number of unused bits in BIT STRING: "
+ + unusedBits);
+ }
+ byte[] tmp = new byte[len + 1];
+ tmp[0] = (byte)unusedBits;
+ Array.Copy(buf, off, tmp, 1, len);
+ if (len > 0) {
+ tmp[len - 1] &= (byte)(0xFF << unusedBits);
+ }
+ return MakePrimitiveInner(BIT_STRING, tmp);
+ }
+
+ /*
+ * Create an OCTET STRING from the provided value.
+ */
+ public static AsnElt MakeBlob(byte[] buf)
+ {
+ return MakeBlob(buf, 0, buf.Length);
+ }
+
+ public static AsnElt MakeBlob(byte[] buf, int off, int len)
+ {
+ return MakePrimitive(OCTET_STRING, buf, off, len);
+ }
+
+ /*
+ * Create a new constructed elements, by providing the relevant
+ * sub-elements.
+ */
+ public static AsnElt Make(int tagValue, params AsnElt[] subs)
+ {
+ return Make(UNIVERSAL, tagValue, subs);
+ }
+
+ /*
+ * Create a new constructed elements, by providing the relevant
+ * sub-elements.
+ */
+ public static AsnElt Make(int tagClass, int tagValue,
+ params AsnElt[] subs)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = null;
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = -1;
+ a.hasEncodedHeader = false;
+ if (tagClass < 0 || tagClass > 3) {
+ throw new AsnException(
+ "invalid tag class: " + tagClass);
+ }
+ if (tagValue < 0) {
+ throw new AsnException(
+ "invalid tag value: " + tagValue);
+ }
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ if (subs == null) {
+ a.Sub = new AsnElt[0];
+ } else {
+ a.Sub = new AsnElt[subs.Length];
+ Array.Copy(subs, 0, a.Sub, 0, subs.Length);
+ }
+ return a;
+ }
+
+ /*
+ * Create a SET OF: sub-elements are automatically sorted by
+ * lexicographic order of their DER encodings. Identical elements
+ * are merged.
+ */
+ public static AsnElt MakeSetOf(params AsnElt[] subs)
+ {
+ AsnElt a = new AsnElt();
+ a.objBuf = null;
+ a.objOff = 0;
+ a.objLen = -1;
+ a.valOff = 0;
+ a.valLen = -1;
+ a.hasEncodedHeader = false;
+ a.TagClass = UNIVERSAL;
+ a.TagValue = SET;
+ if (subs == null) {
+ a.Sub = new AsnElt[0];
+ } else {
+ SortedDictionary d =
+ new SortedDictionary(
+ COMPARER_LEXICOGRAPHIC);
+ foreach (AsnElt ax in subs) {
+ d[ax.Encode()] = ax;
+ }
+ AsnElt[] tmp = new AsnElt[d.Count];
+ int j = 0;
+ foreach (AsnElt ax in d.Values) {
+ tmp[j ++] = ax;
+ }
+ a.Sub = tmp;
+ }
+ return a;
+ }
+
+ static IComparer COMPARER_LEXICOGRAPHIC =
+ new ComparerLexicographic();
+
+ class ComparerLexicographic : IComparer {
+
+ public int Compare(byte[] x, byte[] y)
+ {
+ int xLen = x.Length;
+ int yLen = y.Length;
+ int cLen = Math.Min(xLen, yLen);
+ for (int i = 0; i < cLen; i ++) {
+ if (x[i] != y[i]) {
+ return (int)x[i] - (int)y[i];
+ }
+ }
+ return xLen - yLen;
+ }
+ }
+
+ /*
+ * Wrap an element into an explicit tag.
+ */
+ public static AsnElt MakeExplicit(int tagClass, int tagValue, AsnElt x)
+ {
+ return Make(tagClass, tagValue, x);
+ }
+
+ /*
+ * Wrap an element into an explicit CONTEXT tag.
+ */
+ public static AsnElt MakeExplicit(int tagValue, AsnElt x)
+ {
+ return Make(CONTEXT, tagValue, x);
+ }
+
+ /*
+ * Apply an implicit tag to a value. The source AsnElt object
+ * is unmodified; a new object is returned.
+ */
+ public static AsnElt MakeImplicit(int tagClass, int tagValue, AsnElt x)
+ {
+ if (x.Constructed) {
+ return Make(tagClass, tagValue, x.Sub);
+ }
+ AsnElt a = new AsnElt();
+ a.objBuf = x.GetValue(out a.valOff, out a.valLen);
+ a.objOff = 0;
+ a.objLen = -1;
+ a.hasEncodedHeader = false;
+ a.TagClass = tagClass;
+ a.TagValue = tagValue;
+ a.Sub = null;
+ return a;
+ }
+
+ public static AsnElt NULL_V = AsnElt.MakePrimitive(
+ NULL, new byte[0]);
+
+ public static AsnElt BOOL_TRUE = AsnElt.MakePrimitive(
+ BOOLEAN, new byte[] { 0xFF });
+ public static AsnElt BOOL_FALSE = AsnElt.MakePrimitive(
+ BOOLEAN, new byte[] { 0x00 });
+
+ /*
+ * Create an OBJECT IDENTIFIER from its string representation.
+ * This function tolerates extra leading zeros.
+ */
+ public static AsnElt MakeOID(string str)
+ {
+ List r = new List();
+ int n = str.Length;
+ long x = -1;
+ for (int i = 0; i < n; i ++) {
+ int c = str[i];
+ if (c == '.') {
+ if (x < 0) {
+ throw new AsnException(
+ "invalid OID (empty element)");
+ }
+ r.Add(x);
+ x = -1;
+ continue;
+ }
+ if (c < '0' || c > '9') {
+ throw new AsnException(String.Format(
+ "invalid character U+{0:X4} in OID",
+ c));
+ }
+ if (x < 0) {
+ x = 0;
+ } else if (x > ((Int64.MaxValue - 9) / 10)) {
+ throw new AsnException("OID element overflow");
+ }
+ x = x * (long)10 + (long)(c - '0');
+ }
+ if (x < 0) {
+ throw new AsnException(
+ "invalid OID (empty element)");
+ }
+ r.Add(x);
+ if (r.Count < 2) {
+ throw new AsnException(
+ "invalid OID (not enough elements)");
+ }
+ if (r[0] > 2 || r[1] > 40) {
+ throw new AsnException(
+ "invalid OID (first elements out of range)");
+ }
+
+ MemoryStream ms = new MemoryStream();
+ ms.WriteByte((byte)(40 * (int)r[0] + (int)r[1]));
+ for (int i = 2; i < r.Count; i ++) {
+ long v = r[i];
+ if (v < 0x80) {
+ ms.WriteByte((byte)v);
+ continue;
+ }
+ int k = -7;
+ for (long w = v; w != 0; w >>= 7, k += 7);
+ ms.WriteByte((byte)(0x80 + (int)(v >> k)));
+ for (k -= 7; k >= 0; k -= 7) {
+ int z = (int)(v >> k) & 0x7F;
+ if (k > 0) {
+ z |= 0x80;
+ }
+ ms.WriteByte((byte)z);
+ }
+ }
+ byte[] buf = ms.ToArray();
+ return MakePrimitiveInner(OBJECT_IDENTIFIER,
+ buf, 0, buf.Length);
+ }
+
+ /*
+ * Create a string of the provided type and contents. The string
+ * type is a universal tag value for one of the string or time
+ * types.
+ */
+ public static AsnElt MakeString(int type, string str)
+ {
+ VerifyChars(str.ToCharArray(), type);
+ byte[] buf;
+ switch (type) {
+ case NumericString:
+ case PrintableString:
+ case UTCTime:
+ case GeneralizedTime:
+ case IA5String:
+ case TeletexString:
+ buf = EncodeMono(str);
+ break;
+ case UTF8String:
+ buf = EncodeUTF8(str);
+ break;
+ case BMPString:
+ buf = EncodeUTF16(str);
+ break;
+ case UniversalString:
+ buf = EncodeUTF32(str);
+ break;
+ default:
+ throw new AsnException(
+ "unsupported string type: " + type);
+ }
+ return MakePrimitiveInner(type, buf);
+ }
+
+ static byte[] EncodeMono(string str)
+ {
+ byte[] r = new byte[str.Length];
+ int k = 0;
+ foreach (char c in str) {
+ r[k ++] = (byte)c;
+ }
+ return r;
+ }
+
+ /*
+ * Get the code point at offset 'off' in the string. Either one
+ * or two 'char' slots are used; 'off' is updated accordingly.
+ */
+ static int CodePoint(string str, ref int off)
+ {
+ int c = str[off ++];
+ if (c >= 0xD800 && c < 0xDC00 && off < str.Length) {
+ int d = str[off];
+ if (d >= 0xDC00 && d < 0xE000) {
+ c = ((c & 0x3FF) << 10)
+ + (d & 0x3FF) + 0x10000;
+ off ++;
+ }
+ }
+ return c;
+ }
+
+ static byte[] EncodeUTF8(string str)
+ {
+ int k = 0;
+ int n = str.Length;
+ MemoryStream ms = new MemoryStream();
+ while (k < n) {
+ int cp = CodePoint(str, ref k);
+ if (cp < 0x80) {
+ ms.WriteByte((byte)cp);
+ } else if (cp < 0x800) {
+ ms.WriteByte((byte)(0xC0 + (cp >> 6)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ } else if (cp < 0x10000) {
+ ms.WriteByte((byte)(0xE0 + (cp >> 12)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ } else {
+ ms.WriteByte((byte)(0xF0 + (cp >> 18)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 12) & 63)));
+ ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+ ms.WriteByte((byte)(0x80 + (cp & 63)));
+ }
+ }
+ return ms.ToArray();
+ }
+
+ static byte[] EncodeUTF16(string str)
+ {
+ byte[] buf = new byte[str.Length << 1];
+ int k = 0;
+ foreach (char c in str) {
+ buf[k ++] = (byte)(c >> 8);
+ buf[k ++] = (byte)c;
+ }
+ return buf;
+ }
+
+ static byte[] EncodeUTF32(string str)
+ {
+ int k = 0;
+ int n = str.Length;
+ MemoryStream ms = new MemoryStream();
+ while (k < n) {
+ int cp = CodePoint(str, ref k);
+ ms.WriteByte((byte)(cp >> 24));
+ ms.WriteByte((byte)(cp >> 16));
+ ms.WriteByte((byte)(cp >> 8));
+ ms.WriteByte((byte)cp);
+ }
+ return ms.ToArray();
+ }
+
+ /*
+ * Create a time value of the specified type (UTCTime or
+ * GeneralizedTime).
+ */
+ public static AsnElt MakeTime(int type, DateTime dt)
+ {
+ dt = dt.ToUniversalTime();
+ string str;
+ switch (type) {
+ case UTCTime:
+ int year = dt.Year;
+ if (year < 1950 || year >= 2050) {
+ throw new AsnException(String.Format(
+ "cannot encode year {0} as UTCTime",
+ year));
+ }
+ year = year % 100;
+ str = String.Format(
+ "{0:d2}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}Z",
+ year, dt.Month, dt.Day,
+ dt.Hour, dt.Minute, dt.Second);
+ break;
+ case GeneralizedTime:
+ str = String.Format(
+ "{0:d4}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}",
+ dt.Year, dt.Month, dt.Day,
+ dt.Hour, dt.Minute, dt.Second);
+ int millis = dt.Millisecond;
+ if (millis != 0) {
+ if (millis % 100 == 0) {
+ str = String.Format("{0}.{1:d1}",
+ str, millis / 100);
+ } else if (millis % 10 == 0) {
+ str = String.Format("{0}.{1:d2}",
+ str, millis / 10);
+ } else {
+ str = String.Format("{0}.{1:d3}",
+ str, millis);
+ }
+ }
+ str = str + "Z";
+ break;
+ default:
+ throw new AsnException(
+ "unsupported time type: " + type);
+ }
+ return MakeString(type, str);
+ }
+
+ /*
+ * Create a time value of the specified type (UTCTime or
+ * GeneralizedTime).
+ */
+ public static AsnElt MakeTime(int type, DateTimeOffset dto)
+ {
+ return MakeTime(type, dto.UtcDateTime);
+ }
+
+ /*
+ * Create a time value with an automatic type selection
+ * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+ * otherwise).
+ */
+ public static AsnElt MakeTimeAuto(DateTime dt)
+ {
+ dt = dt.ToUniversalTime();
+ return MakeTime((dt.Year >= 1950 && dt.Year <= 2049)
+ ? UTCTime : GeneralizedTime, dt);
+ }
+
+ /*
+ * Create a time value with an automatic type selection
+ * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+ * otherwise).
+ */
+ public static AsnElt MakeTimeAuto(DateTimeOffset dto)
+ {
+ return MakeTimeAuto(dto.UtcDateTime);
+ }
+}
+
+}
diff --git a/ExternalLibs/AsnElt/AsnElt/AsnException.cs b/ExternalLibs/AsnElt/AsnElt/AsnException.cs
new file mode 100644
index 00000000..852bd724
--- /dev/null
+++ b/ExternalLibs/AsnElt/AsnElt/AsnException.cs
@@ -0,0 +1,19 @@
+using System;
+using System.IO;
+
+namespace Asn1 {
+
+public class AsnException : IOException {
+
+ public AsnException(string message)
+ : base(message)
+ {
+ }
+
+ public AsnException(string message, Exception nested)
+ : base(message, nested)
+ {
+ }
+}
+
+}
diff --git a/ExternalLibs/AsnElt/AsnElt/AsnIO.cs b/ExternalLibs/AsnElt/AsnElt/AsnIO.cs
new file mode 100644
index 00000000..04883ece
--- /dev/null
+++ b/ExternalLibs/AsnElt/AsnElt/AsnIO.cs
@@ -0,0 +1,309 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+public static class AsnIO {
+
+ public static byte[] FindDER(byte[] buf)
+ {
+ return FindBER(buf, true);
+ }
+
+ public static byte[] FindBER(byte[] buf)
+ {
+ return FindBER(buf, false);
+ }
+
+ /*
+ * Find a BER/DER object in the provided buffer. If the data is
+ * not already in the right format, conversion to string then
+ * Base64 decoding is attempted; in the latter case, PEM headers
+ * are detected and skipped. In any case, the returned buffer
+ * must begin with a well-formed tag and length, corresponding to
+ * the object length.
+ *
+ * If 'strictDER' is true, then the function furthermore insists
+ * on the object to use a defined DER length.
+ *
+ * The returned buffer may be the source buffer itself, or a newly
+ * allocated buffer.
+ *
+ * On error, null is returned.
+ */
+ public static byte[] FindBER(byte[] buf, bool strictDER)
+ {
+ string pemType = null;
+ return FindBER(buf, strictDER, out pemType);
+ }
+
+ /*
+ * Find a BER/DER object in the provided buffer. If the data is
+ * not already in the right format, conversion to string then
+ * Base64 decoding is attempted; in the latter case, PEM headers
+ * are detected and skipped. In any case, the returned buffer
+ * must begin with a well-formed tag and length, corresponding to
+ * the object length.
+ *
+ * If 'strictDER' is true, then the function furthermore insists
+ * on the object to use a defined DER length.
+ *
+ * If the source was detected to use PEM, then the object type
+ * indicated by the PEM header is written in 'pemType'; otherwise,
+ * that variable is set to null.
+ *
+ * The returned buffer may be the source buffer itself, or a newly
+ * allocated buffer.
+ *
+ * On error, null is returned.
+ */
+ public static byte[] FindBER(byte[] buf,
+ bool strictDER, out string pemType)
+ {
+ pemType = null;
+
+ /*
+ * If it is already (from the outside) a BER object,
+ * return it.
+ */
+ if (LooksLikeBER(buf, strictDER)) {
+ return buf;
+ }
+
+ /*
+ * Convert the blob to a string. We support UTF-16 with
+ * and without a BOM, UTF-8 with and without a BOM, and
+ * ASCII-compatible encodings. Non-ASCII characters get
+ * truncated.
+ */
+ if (buf.Length < 3) {
+ return null;
+ }
+ string str = null;
+ if ((buf.Length & 1) == 0) {
+ if (buf[0] == 0xFE && buf[1] == 0xFF) {
+ // Starts with big-endian UTF-16 BOM
+ str = ConvertBi(buf, 2, true);
+ } else if (buf[0] == 0xFF && buf[1] == 0xFE) {
+ // Starts with little-endian UTF-16 BOM
+ str = ConvertBi(buf, 2, false);
+ } else if (buf[0] == 0) {
+ // First byte is 0 -> big-endian UTF-16
+ str = ConvertBi(buf, 0, true);
+ } else if (buf[1] == 0) {
+ // Second byte is 0 -> little-endian UTF-16
+ str = ConvertBi(buf, 0, false);
+ }
+ }
+ if (str == null) {
+ if (buf[0] == 0xEF
+ && buf[1] == 0xBB
+ && buf[2] == 0xBF)
+ {
+ // Starts with UTF-8 BOM
+ str = ConvertMono(buf, 3);
+ } else {
+ // Assumed ASCII-compatible mono-byte encoding
+ str = ConvertMono(buf, 0);
+ }
+ }
+ if (str == null) {
+ return null;
+ }
+
+ /*
+ * Try to detect a PEM header and footer; if we find both
+ * then we remove both, keeping only the characters that
+ * occur in between.
+ */
+ int p = str.IndexOf("-----BEGIN ");
+ int q = str.IndexOf("-----END ");
+ if (p >= 0 && q >= 0) {
+ p += 11;
+ int r = str.IndexOf((char)10, p) + 1;
+ int px = str.IndexOf('-', p);
+ if (px > 0 && px < r && r > 0 && r <= q) {
+ pemType = string.Copy(str.Substring(p, px - p));
+ str = str.Substring(r, q - r);
+ }
+ }
+
+ /*
+ * Convert from Base64.
+ */
+ try {
+ buf = Convert.FromBase64String(str);
+ if (LooksLikeBER(buf, strictDER)) {
+ return buf;
+ }
+ } catch {
+ // ignored: not Base64
+ }
+
+ /*
+ * Decoding failed.
+ */
+ return null;
+ }
+
+ /* =============================================================== */
+
+ /*
+ * Decode a tag; returned value is true on success, false otherwise.
+ * On success, 'off' is updated to point to the first byte after
+ * the tag.
+ */
+ static bool DecodeTag(byte[] buf, int lim, ref int off)
+ {
+ int p = off;
+ if (p >= lim) {
+ return false;
+ }
+ int v = buf[p ++];
+ if ((v & 0x1F) == 0x1F) {
+ do {
+ if (p >= lim) {
+ return false;
+ }
+ v = buf[p ++];
+ } while ((v & 0x80) != 0);
+ }
+ off = p;
+ return true;
+ }
+
+ /*
+ * Decode a BER length. Returned value is:
+ * -2 no decodable length
+ * -1 indefinite length
+ * 0+ definite length
+ * If a definite or indefinite length could be decoded, then 'off'
+ * is updated to point to the first byte after the length.
+ */
+ static int DecodeLength(byte[] buf, int lim, ref int off)
+ {
+ int p = off;
+ if (p >= lim) {
+ return -2;
+ }
+ int v = buf[p ++];
+ if (v < 0x80) {
+ off = p;
+ return v;
+ } else if (v == 0x80) {
+ off = p;
+ return -1;
+ }
+ v &= 0x7F;
+ if ((lim - p) < v) {
+ return -2;
+ }
+ int acc = 0;
+ while (v -- > 0) {
+ if (acc > 0x7FFFFF) {
+ return -2;
+ }
+ acc = (acc << 8) + buf[p ++];
+ }
+ off = p;
+ return acc;
+ }
+
+ /*
+ * Get the length, in bytes, of the object in the provided
+ * buffer. The object begins at offset 'off' but does not extend
+ * farther than offset 'lim'. If no such BER object can be
+ * decoded, then -1 is returned. The returned length includes
+ * that of the tag and length fields.
+ */
+ static int BERLength(byte[] buf, int lim, int off)
+ {
+ int orig = off;
+ if (!DecodeTag(buf, lim, ref off)) {
+ return -1;
+ }
+ int len = DecodeLength(buf, lim, ref off);
+ if (len >= 0) {
+ if (len > (lim - off)) {
+ return -1;
+ }
+ return off + len - orig;
+ } else if (len < -1) {
+ return -1;
+ }
+
+ /*
+ * Indefinite length: we must do some recursive exploration.
+ * End of structure is marked by a "null tag": object has
+ * total length 2 and its tag byte is 0.
+ */
+ for (;;) {
+ int slen = BERLength(buf, lim, off);
+ if (slen < 0) {
+ return -1;
+ }
+ off += slen;
+ if (slen == 2 && buf[off] == 0) {
+ return off - orig;
+ }
+ }
+ }
+
+ static bool LooksLikeBER(byte[] buf, bool strictDER)
+ {
+ return LooksLikeBER(buf, 0, buf.Length, strictDER);
+ }
+
+ static bool LooksLikeBER(byte[] buf, int off, int len, bool strictDER)
+ {
+ int lim = off + len;
+ int objLen = BERLength(buf, lim, off);
+ if (objLen != len) {
+ return false;
+ }
+ if (strictDER) {
+ DecodeTag(buf, lim, ref off);
+ return DecodeLength(buf, lim, ref off) >= 0;
+ } else {
+ return true;
+ }
+ }
+
+ static string ConvertMono(byte[] buf, int off)
+ {
+ int len = buf.Length - off;
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int v = buf[off + i];
+ if (v < 1 || v > 126) {
+ v = '?';
+ }
+ tc[i] = (char)v;
+ }
+ return new string(tc);
+ }
+
+ static string ConvertBi(byte[] buf, int off, bool be)
+ {
+ int len = buf.Length - off;
+ if ((len & 1) != 0) {
+ return null;
+ }
+ len >>= 1;
+ char[] tc = new char[len];
+ for (int i = 0; i < len; i ++) {
+ int b0 = buf[off + (i << 1) + 0];
+ int b1 = buf[off + (i << 1) + 1];
+ int v = be ? ((b0 << 8) + b1) : (b0 + (b1 << 8));
+ if (v < 1 || v > 126) {
+ v = '?';
+ }
+ tc[i] = (char)v;
+ }
+ return new string(tc);
+ }
+}
+
+}
diff --git a/ExternalLibs/AsnElt/AsnElt/AsnOID.cs b/ExternalLibs/AsnElt/AsnElt/AsnOID.cs
new file mode 100644
index 00000000..19739f5a
--- /dev/null
+++ b/ExternalLibs/AsnElt/AsnElt/AsnOID.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Asn1 {
+
+public class AsnOID {
+
+ static Dictionary OIDToName =
+ new Dictionary();
+ static Dictionary NameToOID =
+ new Dictionary();
+
+ static AsnOID()
+ {
+ /*
+ * From RFC 5280, PKIX1Explicit88 module.
+ */
+ Reg("1.3.6.1.5.5.7", "id-pkix");
+ Reg("1.3.6.1.5.5.7.1", "id-pe");
+ Reg("1.3.6.1.5.5.7.2", "id-qt");
+ Reg("1.3.6.1.5.5.7.3", "id-kp");
+ Reg("1.3.6.1.5.5.7.48", "id-ad");
+ Reg("1.3.6.1.5.5.7.2.1", "id-qt-cps");
+ Reg("1.3.6.1.5.5.7.2.2", "id-qt-unotice");
+ Reg("1.3.6.1.5.5.7.48.1", "id-ad-ocsp");
+ Reg("1.3.6.1.5.5.7.48.2", "id-ad-caIssuers");
+ Reg("1.3.6.1.5.5.7.48.3", "id-ad-timeStamping");
+ Reg("1.3.6.1.5.5.7.48.5", "id-ad-caRepository");
+
+ Reg("2.5.4", "id-at");
+ Reg("2.5.4.41", "id-at-name");
+ Reg("2.5.4.4", "id-at-surname");
+ Reg("2.5.4.42", "id-at-givenName");
+ Reg("2.5.4.43", "id-at-initials");
+ Reg("2.5.4.44", "id-at-generationQualifier");
+ Reg("2.5.4.3", "id-at-commonName");
+ Reg("2.5.4.7", "id-at-localityName");
+ Reg("2.5.4.8", "id-at-stateOrProvinceName");
+ Reg("2.5.4.10", "id-at-organizationName");
+ Reg("2.5.4.11", "id-at-organizationalUnitName");
+ Reg("2.5.4.12", "id-at-title");
+ Reg("2.5.4.46", "id-at-dnQualifier");
+ Reg("2.5.4.6", "id-at-countryName");
+ Reg("2.5.4.5", "id-at-serialNumber");
+ Reg("2.5.4.65", "id-at-pseudonym");
+ Reg("0.9.2342.19200300.100.1.25", "id-domainComponent");
+
+ Reg("1.2.840.113549.1.9", "pkcs-9");
+ Reg("1.2.840.113549.1.9.1", "id-emailAddress");
+
+ /*
+ * From RFC 5280, PKIX1Implicit88 module.
+ */
+ Reg("2.5.29", "id-ce");
+ Reg("2.5.29.35", "id-ce-authorityKeyIdentifier");
+ Reg("2.5.29.14", "id-ce-subjectKeyIdentifier");
+ Reg("2.5.29.15", "id-ce-keyUsage");
+ Reg("2.5.29.16", "id-ce-privateKeyUsagePeriod");
+ Reg("2.5.29.32", "id-ce-certificatePolicies");
+ Reg("2.5.29.33", "id-ce-policyMappings");
+ Reg("2.5.29.17", "id-ce-subjectAltName");
+ Reg("2.5.29.18", "id-ce-issuerAltName");
+ Reg("2.5.29.9", "id-ce-subjectDirectoryAttributes");
+ Reg("2.5.29.19", "id-ce-basicConstraints");
+ Reg("2.5.29.30", "id-ce-nameConstraints");
+ Reg("2.5.29.36", "id-ce-policyConstraints");
+ Reg("2.5.29.31", "id-ce-cRLDistributionPoints");
+ Reg("2.5.29.37", "id-ce-extKeyUsage");
+
+ Reg("2.5.29.37.0", "anyExtendedKeyUsage");
+ Reg("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth");
+ Reg("1.3.6.1.5.5.7.3.2", "id-kp-clientAuth");
+ Reg("1.3.6.1.5.5.7.3.3", "id-kp-codeSigning");
+ Reg("1.3.6.1.5.5.7.3.4", "id-kp-emailProtection");
+ Reg("1.3.6.1.5.5.7.3.8", "id-kp-timeStamping");
+ Reg("1.3.6.1.5.5.7.3.9", "id-kp-OCSPSigning");
+
+ Reg("2.5.29.54", "id-ce-inhibitAnyPolicy");
+ Reg("2.5.29.46", "id-ce-freshestCRL");
+ Reg("1.3.6.1.5.5.7.1.1", "id-pe-authorityInfoAccess");
+ Reg("1.3.6.1.5.5.7.1.11", "id-pe-subjectInfoAccess");
+ Reg("2.5.29.20", "id-ce-cRLNumber");
+ Reg("2.5.29.28", "id-ce-issuingDistributionPoint");
+ Reg("2.5.29.27", "id-ce-deltaCRLIndicator");
+ Reg("2.5.29.21", "id-ce-cRLReasons");
+ Reg("2.5.29.29", "id-ce-certificateIssuer");
+ Reg("2.5.29.23", "id-ce-holdInstructionCode");
+ Reg("2.2.840.10040.2", "WRONG-holdInstruction");
+ Reg("2.2.840.10040.2.1", "WRONG-id-holdinstruction-none");
+ Reg("2.2.840.10040.2.2", "WRONG-id-holdinstruction-callissuer");
+ Reg("2.2.840.10040.2.3", "WRONG-id-holdinstruction-reject");
+ Reg("2.5.29.24", "id-ce-invalidityDate");
+
+ /*
+ * These are the "right" OID. RFC 5280 mistakenly defines
+ * the first OID element as "2".
+ */
+ Reg("1.2.840.10040.2", "holdInstruction");
+ Reg("1.2.840.10040.2.1", "id-holdinstruction-none");
+ Reg("1.2.840.10040.2.2", "id-holdinstruction-callissuer");
+ Reg("1.2.840.10040.2.3", "id-holdinstruction-reject");
+
+ /*
+ * From PKCS#1.
+ */
+ Reg("1.2.840.113549.1.1", "pkcs-1");
+ Reg("1.2.840.113549.1.1.1", "rsaEncryption");
+ Reg("1.2.840.113549.1.1.7", "id-RSAES-OAEP");
+ Reg("1.2.840.113549.1.1.9", "id-pSpecified");
+ Reg("1.2.840.113549.1.1.10", "id-RSASSA-PSS");
+ Reg("1.2.840.113549.1.1.2", "md2WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.4", "md5WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.5", "sha1WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.11", "sha256WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.12", "sha384WithRSAEncryption");
+ Reg("1.2.840.113549.1.1.13", "sha512WithRSAEncryption");
+ Reg("1.3.14.3.2.26", "id-sha1");
+ Reg("1.2.840.113549.2.2", "id-md2");
+ Reg("1.2.840.113549.2.5", "id-md5");
+ Reg("1.2.840.113549.1.1.8", "id-mgf1");
+
+ /*
+ * From NIST: http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html
+ */
+ Reg("2.16.840.1.101.3", "csor");
+ Reg("2.16.840.1.101.3.4", "nistAlgorithms");
+ Reg("2.16.840.1.101.3.4.0", "csorModules");
+ Reg("2.16.840.1.101.3.4.0.1", "aesModule1");
+
+ Reg("2.16.840.1.101.3.4.1", "aes");
+ Reg("2.16.840.1.101.3.4.1.1", "id-aes128-ECB");
+ Reg("2.16.840.1.101.3.4.1.2", "id-aes128-CBC");
+ Reg("2.16.840.1.101.3.4.1.3", "id-aes128-OFB");
+ Reg("2.16.840.1.101.3.4.1.4", "id-aes128-CFB");
+ Reg("2.16.840.1.101.3.4.1.5", "id-aes128-wrap");
+ Reg("2.16.840.1.101.3.4.1.6", "id-aes128-GCM");
+ Reg("2.16.840.1.101.3.4.1.7", "id-aes128-CCM");
+ Reg("2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad");
+ Reg("2.16.840.1.101.3.4.1.21", "id-aes192-ECB");
+ Reg("2.16.840.1.101.3.4.1.22", "id-aes192-CBC");
+ Reg("2.16.840.1.101.3.4.1.23", "id-aes192-OFB");
+ Reg("2.16.840.1.101.3.4.1.24", "id-aes192-CFB");
+ Reg("2.16.840.1.101.3.4.1.25", "id-aes192-wrap");
+ Reg("2.16.840.1.101.3.4.1.26", "id-aes192-GCM");
+ Reg("2.16.840.1.101.3.4.1.27", "id-aes192-CCM");
+ Reg("2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad");
+ Reg("2.16.840.1.101.3.4.1.41", "id-aes256-ECB");
+ Reg("2.16.840.1.101.3.4.1.42", "id-aes256-CBC");
+ Reg("2.16.840.1.101.3.4.1.43", "id-aes256-OFB");
+ Reg("2.16.840.1.101.3.4.1.44", "id-aes256-CFB");
+ Reg("2.16.840.1.101.3.4.1.45", "id-aes256-wrap");
+ Reg("2.16.840.1.101.3.4.1.46", "id-aes256-GCM");
+ Reg("2.16.840.1.101.3.4.1.47", "id-aes256-CCM");
+ Reg("2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad");
+
+ Reg("2.16.840.1.101.3.4.2", "hashAlgs");
+ Reg("2.16.840.1.101.3.4.2.1", "id-sha256");
+ Reg("2.16.840.1.101.3.4.2.2", "id-sha384");
+ Reg("2.16.840.1.101.3.4.2.3", "id-sha512");
+ Reg("2.16.840.1.101.3.4.2.4", "id-sha224");
+ Reg("2.16.840.1.101.3.4.2.5", "id-sha512-224");
+ Reg("2.16.840.1.101.3.4.2.6", "id-sha512-256");
+
+ Reg("2.16.840.1.101.3.4.3", "sigAlgs");
+ Reg("2.16.840.1.101.3.4.3.1", "id-dsa-with-sha224");
+ Reg("2.16.840.1.101.3.4.3.2", "id-dsa-with-sha256");
+
+ Reg("1.2.840.113549", "rsadsi");
+ Reg("1.2.840.113549.2", "digestAlgorithm");
+ Reg("1.2.840.113549.2.7", "id-hmacWithSHA1");
+ Reg("1.2.840.113549.2.8", "id-hmacWithSHA224");
+ Reg("1.2.840.113549.2.9", "id-hmacWithSHA256");
+ Reg("1.2.840.113549.2.10", "id-hmacWithSHA384");
+ Reg("1.2.840.113549.2.11", "id-hmacWithSHA512");
+
+ /*
+ * From X9.57: http://oid-info.com/get/1.2.840.10040.4
+ */
+ Reg("1.2.840.10040.4", "x9algorithm");
+ Reg("1.2.840.10040.4", "x9cm");
+ Reg("1.2.840.10040.4.1", "dsa");
+ Reg("1.2.840.10040.4.3", "dsa-with-sha1");
+
+ /*
+ * From SEC: http://oid-info.com/get/1.3.14.3.2
+ */
+ Reg("1.3.14.3.2.2", "md4WithRSA");
+ Reg("1.3.14.3.2.3", "md5WithRSA");
+ Reg("1.3.14.3.2.4", "md4WithRSAEncryption");
+ Reg("1.3.14.3.2.12", "dsaSEC");
+ Reg("1.3.14.3.2.13", "dsaWithSHASEC");
+ Reg("1.3.14.3.2.27", "dsaWithSHA1SEC");
+
+ /*
+ * From Microsoft: http://oid-info.com/get/1.3.6.1.4.1.311.20.2
+ */
+ Reg("1.3.6.1.4.1.311.20.2", "ms-certType");
+ Reg("1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon");
+ Reg("1.3.6.1.4.1.311.20.2.3", "ms-UserPrincipalName");
+ Reg("1.3.6.1.4.1.311.20.2.3", "ms-UPN");
+ }
+
+ static void Reg(string oid, string name)
+ {
+ if (!OIDToName.ContainsKey(oid)) {
+ OIDToName.Add(oid, name);
+ }
+ string nn = Normalize(name);
+ if (NameToOID.ContainsKey(nn)) {
+ throw new Exception("OID name collision: " + nn);
+ }
+ NameToOID.Add(nn, oid);
+
+ /*
+ * Many names start with 'id-??-' and we want to support
+ * the short names (without that prefix) as aliases. But
+ * we must take care of some collisions on short names.
+ */
+ if (name.StartsWith("id-")
+ && name.Length >= 7 && name[5] == '-')
+ {
+ if (name.StartsWith("id-ad-")) {
+ Reg(oid, name.Substring(6) + "-IA");
+ } else if (name.StartsWith("id-kp-")) {
+ Reg(oid, name.Substring(6) + "-EKU");
+ } else {
+ Reg(oid, name.Substring(6));
+ }
+ }
+ }
+
+ static string Normalize(string name)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (char c in name) {
+ int d = (int)c;
+ if (d <= 32 || d == '-') {
+ continue;
+ }
+ if (d >= 'A' && d <= 'Z') {
+ d += 'a' - 'A';
+ }
+ sb.Append((char)c);
+ }
+ return sb.ToString();
+ }
+
+ public static string ToName(string oid)
+ {
+ return OIDToName.ContainsKey(oid) ? OIDToName[oid] : oid;
+ }
+
+ public static string ToOID(string name)
+ {
+ if (IsNumericOID(name)) {
+ return name;
+ }
+ string nn = Normalize(name);
+ if (!NameToOID.ContainsKey(nn)) {
+ throw new AsnException(
+ "unrecognized OID name: " + name);
+ }
+ return NameToOID[nn];
+ }
+
+ public static bool IsNumericOID(string oid)
+ {
+ /*
+ * An OID is in numeric format if:
+ * -- it contains only digits and dots
+ * -- it does not start or end with a dot
+ * -- it does not contain two consecutive dots
+ * -- it contains at least one dot
+ */
+ foreach (char c in oid) {
+ if (!(c >= '0' && c <= '9') && c != '.') {
+ return false;
+ }
+ }
+ if (oid.StartsWith(".") || oid.EndsWith(".")) {
+ return false;
+ }
+ if (oid.IndexOf("..") >= 0) {
+ return false;
+ }
+ if (oid.IndexOf('.') < 0) {
+ return false;
+ }
+ return true;
+ }
+}
+
+}
diff --git a/ExternalLibs/AsnElt/AsnElt/LICENSE.txt b/ExternalLibs/AsnElt/AsnElt/LICENSE.txt
new file mode 100644
index 00000000..8264cabc
--- /dev/null
+++ b/ExternalLibs/AsnElt/AsnElt/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Thomas Pornin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Src/Fido2/AttestationFormat/AndroidKey.cs b/Src/Fido2/AttestationFormat/AndroidKey.cs
index 57f76639..f36b324b 100644
--- a/Src/Fido2/AttestationFormat/AndroidKey.cs
+++ b/Src/Fido2/AttestationFormat/AndroidKey.cs
@@ -1,7 +1,9 @@
using System;
+using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
+using Asn1;
using PeterO.Cbor;
namespace Fido2NetLib.AttestationFormat
@@ -24,196 +26,130 @@ public static byte[] AttestationExtensionBytes(X509ExtensionCollection exts)
return null;
}
- public static byte[] GetASN1ObjectAtIndex(byte[] attExtBytes, int index)
- {
- // https://developer.android.com/training/articles/security-key-attestation#certificate_schema
- // This function returns an entry from the KeyDescription at index
- if (null == attExtBytes || 0 == attExtBytes.Length || attExtBytes.Length > ushort.MaxValue)
- throw new Fido2VerificationException("Invalid attExtBytes signature value");
- var offset = 0;
- var derSequence = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
-
- // expecting to start with 0x30 indicating SEQUENCE
- if (null == derSequence || 0x30 != derSequence[0])
- throw new Fido2VerificationException("attExtBytes signature not a valid DER sequence");
-
- // next is length of all the items in the sequence
- var dataLen = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
- if (null == dataLen)
- throw new Fido2VerificationException("attExtBytes signature has invalid length");
-
- // if data is more than 127 bytes, the length is encoded in long form
- // TODO : Why is longLen never used ?
- var longForm = (dataLen[0] > 0x7f);
- var longLen = 0;
- if (true == longForm)
- {
- var longLenByte = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
- if (null == longLenByte)
- throw new Fido2VerificationException("attExtBytes signature has invalid long form length");
- longLen = longLenByte[0];
- longLen &= (1 << 7);
- }
-
- // walk through each sequence entry until we get to the requested index
- for (var i = 0; i < index; i++)
- {
- var derId = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
- if (null == derId)
- throw new Fido2VerificationException("Ran out of bytes in attExtBytes sequence without finding the first octet string");
- var lenValue = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
- if (null == lenValue)
- throw new Fido2VerificationException("attExtBytes lenValue invalid");
- if (0 < lenValue[0])
- {
- var value = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, lenValue[0]);
- if (null == value)
- throw new Fido2VerificationException("Ran out of bytes in attExtBytes sequence without finding the first octet string");
- }
- }
- // skip the identifier of the requested item
- var asn1Id = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
- if (null == asn1Id)
- throw new Fido2VerificationException("Ran out of bytes in attExtBytes sequence without finding the first octet string");
- // get length of requested item
- var lenAsn1value = AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, 1);
- if (null == lenAsn1value)
- throw new Fido2VerificationException("lenAttestationChallenge version length invalid");
- // return byte array containing the requested item
- return AuthDataHelper.GetSizedByteArray(attExtBytes, ref offset, lenAsn1value[0]);
- }
-
public static byte[] GetAttestationChallenge(byte[] attExtBytes)
{
+ var keyDescription = AsnElt.Decode(attExtBytes);
// https://developer.android.com/training/articles/security-key-attestation#certificate_schema
// attestationChallenge at index 4
- return GetASN1ObjectAtIndex(attExtBytes, 4);
- }
-
- public static byte[] GetSoftwareEnforcedAuthorizationList(byte[] attExtBytes)
- {
- // https://developer.android.com/training/articles/security-key-attestation#certificate_schema
- // softwareEnforced AuthorizationList at index 6
- return GetASN1ObjectAtIndex(attExtBytes, 6);
- }
-
- public static byte[] GetTeeEnforcedAuthorizationList(byte[] attExtBytes)
- {
- // https://developer.android.com/training/articles/security-key-attestation#certificate_schema
- // teeEnforced AuthorizationList at index 7
- return GetASN1ObjectAtIndex(attExtBytes, 7);
+ return keyDescription.GetSub(4).GetOctetString();
}
public static bool FindAllApplicationsField(byte[] attExtBytes)
{
// https://developer.android.com/training/articles/security-key-attestation#certificate_schema
// check both software and tee enforced AuthorizationList objects for presense of "allApplications" tag, number 600
- var software = GetSoftwareEnforcedAuthorizationList(attExtBytes);
- var tee = GetTeeEnforcedAuthorizationList(attExtBytes);
- var ignore = -1;
- // allApplications is tag 600, and should not be found in either list
- return (GetDERTagValue(software, 600, ref ignore) && GetDERTagValue(tee, 600, ref ignore));
- }
+ var keyDescription = AsnElt.Decode(attExtBytes);
- public static bool GetDERTagValue(byte[] authList, int tag, ref int result)
- {
- // https://developer.android.com/training/articles/security-key-attestation#certificate_schema
- // walk authorizationList sequence looking for an item with requested tag
- // if tag is found, return true and set the result value to the found int value
- // the only two items we are expecting to find are purpose and origin
- // which are set of integer or integer, so int result is ok for now
- // if entire list is walked and tag is not found, return false
- for (var i = 0; i < authList.Length;)
+ var softwareEnforced = keyDescription.GetSub(6).Sub;
+ foreach (AsnElt s in softwareEnforced)
{
- // expecting to see first byte indicting the attribute is of class 2, and constructed value
- // first two bits are the class, expecting to see 10
- // third bit is primative (0) or constructed (1)
- if (false == ((authList[i] & 0xA0) == 0xA0))
- throw new Fido2VerificationException("Expected class 2 constructed ASN.1 value");
-
- int foundTag;
- // if the tag value is below 0x1F (11111), the value is stored in the remaining 5 bits of the first byte
- if (false == ((authList[i] & 0x1F) == 0x1F))
+ switch (s.TagValue)
{
- foundTag = (authList[i] & ~0xA0);
- i++;
- }
- // otherwise, if the tag value is 0x3FFF (11111111111111) or below
- // the value is stored in the lower 7 bits of the second byte, and the lower 7 bits of the third byte
- // this is signified by the high order bit set in the second byte, but not set in the third byte
- else if (((authList[i + 1] & 0x80) == 0x80) && ((authList[i + 2] & 0x80) == 0x0))
- {
- // take the lower 7 bits in the second byte, shift them left 7 positions
- // then add the lower 7 bits of the third byte to get the tag value
- // Welcome to Abstract Syntax Notation One
- foundTag = ((authList[i + 1] & ~0x80) << 7) + (authList[i + 2]);
- i += 3;
- }
- else
- {
- throw new Fido2VerificationException("Expecting ASN.1 tag less than 0x3FFF");
- }
- // if the tag we found is the one that we are looking for, get the value
- if (tag == foundTag)
- {
- // 5 bytes will be remaining if this a set (0x31), advance to the integer
- if (5 == authList[i] && 0x31 == authList[i + 1])
- i += 2;
- // for our purposes, we should see that there are 3 bytes remaining after this one
- // the second byte should be 2 indicating the value is an integer
- // and the third byte is the length in bytes, which again, for our purposes, should be one
- if (3 == authList[i] && 2 == authList[i + 1] && 1 == authList[i + 2])
- {
- // value is stored in the 4th byte, no need to go any further, we have our result
- result = authList[i + 3];
+ case 600:
return true;
- }
- else
- {
- throw new Fido2VerificationException("Unexpected byte sequence found fetching ASN.1 integer value");
- }
+ default:
+ break;
}
+ }
- // if we didn't find the tag we were looking for, advance the index
- // by the the size in bytes of the current tag plus one byte for the size
- else if (i < authList.Length)
+ var teeEnforced = keyDescription.GetSub(7).Sub;
+ foreach (AsnElt s in teeEnforced)
+ {
+ switch (s.TagValue)
{
- i += authList[i] + 1;
+ case 600:
+ return true;
+ default:
+ break;
}
}
- // ran out of bytes without finding the tag we were looking for
+
return false;
}
public static bool IsOriginGenerated(byte[] attExtBytes)
{
- var tagValue = -1;
+ long softwareEnforcedOriginValue = 0;
+ long teeEnforcedOriginValue = 0;
// https://developer.android.com/training/articles/security-key-attestation#certificate_schema
// origin tag is 702
- var result = GetDERTagValue(GetTeeEnforcedAuthorizationList(attExtBytes), 702, ref tagValue);
- return (0 == tagValue && true == result);
+ var keyDescription = AsnElt.Decode(attExtBytes);
+ var softwareEnforced = keyDescription.GetSub(6).Sub;
+ foreach (AsnElt s in softwareEnforced)
+ {
+ switch (s.TagValue)
+ {
+ case 702:
+ softwareEnforcedOriginValue = s.Sub[0].GetInteger();
+ break;
+ default:
+ break;
+ }
+ }
+
+ var teeEnforced = keyDescription.GetSub(7).Sub;
+ foreach (AsnElt s in teeEnforced)
+ {
+ switch (s.TagValue)
+ {
+ case 702:
+ teeEnforcedOriginValue = s.Sub[0].GetInteger();
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (0 == softwareEnforcedOriginValue && 0 == teeEnforcedOriginValue);
}
public static bool IsPurposeSign(byte[] attExtBytes)
{
- var tagValue = -1;
+ long softwareEnforcedPurposeValue = 2;
+ long teeEnforcedPurposeValue = 2;
// https://developer.android.com/training/articles/security-key-attestation#certificate_schema
// purpose tag is 1
- var result = GetDERTagValue(GetTeeEnforcedAuthorizationList(attExtBytes), 1, ref tagValue);
- return (2 == tagValue && true == result);
+ var keyDescription = AsnElt.Decode(attExtBytes);
+ var softwareEnforced = keyDescription.GetSub(6).Sub;
+ foreach (AsnElt s in softwareEnforced)
+ {
+ switch (s.TagValue)
+ {
+ case 1:
+ softwareEnforcedPurposeValue = s.Sub[0].Sub[0].GetInteger();
+ break;
+ default:
+ break;
+ }
+ }
+
+ var teeEnforced = keyDescription.GetSub(7).Sub;
+ foreach (AsnElt s in teeEnforced)
+ {
+ switch (s.TagValue)
+ {
+ case 1:
+ teeEnforcedPurposeValue = s.Sub[0].Sub[0].GetInteger();
+ break;
+ default:
+ break;
+ }
+ }
+
+ return (2 == softwareEnforcedPurposeValue && 2 == teeEnforcedPurposeValue);
}
public override void Verify()
{
// Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields
if (0 == attStmt.Keys.Count || 0 == attStmt.Values.Count)
- throw new Fido2VerificationException("Attestation format packed must have attestation statement");
+ throw new Fido2VerificationException("Attestation format android-key must have attestation statement");
if (null == Sig || CBORType.ByteString != Sig.Type || 0 == Sig.GetByteString().Length)
- throw new Fido2VerificationException("Invalid packed attestation signature");
+ throw new Fido2VerificationException("Invalid android-key attestation signature");
// 2a. Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
// using the attestation public key in attestnCert with the algorithm specified in alg
- if (null == X5c && CBORType.Array != X5c.Type && 0 == X5c.Count)
+ if (null == X5c || CBORType.Array != X5c.Type || 0 == X5c.Count)
throw new Fido2VerificationException("Malformed x5c in android-key attestation");
if (null == X5c.Values || 0 == X5c.Values.Count ||
@@ -234,24 +170,41 @@ public override void Verify()
}
if (null == Alg || CBORType.Number != Alg.Type || false == CryptoUtils.algMap.ContainsKey(Alg.AsInt32()))
- throw new Fido2VerificationException("Invalid attestation algorithm");
- if (true != androidKeyPubKey.VerifyData(Data,
- CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), androidKeyPubKey.KeySize),
- CryptoUtils.algMap[Alg.AsInt32()]))
- throw new Fido2VerificationException("Invalid android key signature");
+ throw new Fido2VerificationException("Invalid android key attestation algorithm");
+
+ byte[] ecsig;
+ try
+ {
+ ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), androidKeyPubKey.KeySize);
+ }
+ catch (Exception ex)
+ {
+ throw new Fido2VerificationException("Failed to decode android key attestation signature from ASN.1 encoded form", ex);
+ }
+
+ if (true != androidKeyPubKey.VerifyData(Data, ecsig, CryptoUtils.algMap[Alg.AsInt32()]))
+ throw new Fido2VerificationException("Invalid android key attestation signature");
// Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the attestedCredentialData in authenticatorData.
if (true != AuthData.AttestedCredentialData.CredentialPublicKey.Verify(Data, Sig.GetByteString()))
- throw new Fido2VerificationException("Invalid android key signature");
+ throw new Fido2VerificationException("Incorrect credentialPublicKey in android key attestation");
// Verify that in the attestation certificate extension data:
var attExtBytes = AttestationExtensionBytes(androidKeyCert.Extensions);
+ if (null == attExtBytes)
+ throw new Fido2VerificationException("Android key attestation certificate contains no AttestationRecord extension");
// 1. The value of the attestationChallenge field is identical to clientDataHash.
- var attestationChallenge = GetAttestationChallenge(attExtBytes);
- if (false == clientDataHash.SequenceEqual(attestationChallenge))
- throw new Fido2VerificationException("Mismatched between attestationChallenge and hashedClientDataJson verifying android key attestation certificate extension");
-
+ try
+ {
+ var attestationChallenge = GetAttestationChallenge(attExtBytes);
+ if (false == clientDataHash.SequenceEqual(attestationChallenge))
+ throw new Fido2VerificationException("Mismatch between attestationChallenge and hashedClientDataJson verifying android key attestation certificate extension");
+ }
+ catch (Exception)
+ {
+ throw new Fido2VerificationException("Malformed android key AttestationRecord extension verifying android key attestation certificate extension");
+ }
// 2. The AuthorizationList.allApplications field is not present, since PublicKeyCredential MUST be bound to the RP ID.
if (true == FindAllApplicationsField(attExtBytes))
throw new Fido2VerificationException("Found all applications field in android key attestation certificate extension");
diff --git a/Src/Fido2/AttestationFormat/FidoU2f.cs b/Src/Fido2/AttestationFormat/FidoU2f.cs
index a381a62c..4c8d4719 100644
--- a/Src/Fido2/AttestationFormat/FidoU2f.cs
+++ b/Src/Fido2/AttestationFormat/FidoU2f.cs
@@ -106,10 +106,16 @@ public override void Verify()
if (null == Sig || CBORType.ByteString != Sig.Type || 0 == Sig.GetByteString().Length)
throw new Fido2VerificationException("Invalid fido-u2f attestation signature");
- var ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize);
- if (null == ecsig)
- throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form");
-
+ byte[] ecsig;
+ try
+ {
+ ecsig = CryptoUtils.SigFromEcDsaSig(Sig.GetByteString(), pubKey.KeySize);
+ }
+ catch (Exception ex)
+ {
+ throw new Fido2VerificationException("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex);
+ }
+
var coseAlg = CredentialPublicKey[CBORObject.FromObject(COSE.KeyCommonParameter.Alg)].AsInt32();
var hashAlg = CryptoUtils.algMap[coseAlg];
diff --git a/Src/Fido2/AttestationFormat/Packed.cs b/Src/Fido2/AttestationFormat/Packed.cs
index 208797e5..a04128f5 100644
--- a/Src/Fido2/AttestationFormat/Packed.cs
+++ b/Src/Fido2/AttestationFormat/Packed.cs
@@ -37,10 +37,11 @@ public static bool IsValidPackedAttnCertSubject(string attnCertSubj)
var dictSubject = attnCertSubj.Split(new string[] { ", " }, StringSplitOptions.None)
.Select(part => part.Split('='))
.ToDictionary(split => split[0], split => split[1]);
- return (0 != dictSubject["C"].Length ||
- 0 != dictSubject["O"].Length ||
- 0 != dictSubject["OU"].Length ||
- 0 != dictSubject["CN"].Length ||
+
+ return (0 != dictSubject["C"].Length &&
+ 0 != dictSubject["O"].Length &&
+ 0 != dictSubject["OU"].Length &&
+ 0 != dictSubject["CN"].Length &&
"Authenticator Attestation" == dictSubject["OU"].ToString());
}
@@ -106,7 +107,7 @@ public override void Verify()
if (aaguid != null)
{
if (0 != AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid))
- throw new Fido2VerificationException("aaguid present in packed attestation but does not match aaguid from authData");
+ throw new Fido2VerificationException("aaguid present in packed attestation cert exts but does not match aaguid from authData");
}
// 2d. The Basic Constraints extension MUST have the CA component set to false
if (IsAttnCertCACert(attestnCert.Extensions))
diff --git a/Src/Fido2/AttestationFormat/Tpm.cs b/Src/Fido2/AttestationFormat/Tpm.cs
index bfe4479a..60649fdd 100644
--- a/Src/Fido2/AttestationFormat/Tpm.cs
+++ b/Src/Fido2/AttestationFormat/Tpm.cs
@@ -470,7 +470,7 @@ public override void Verify()
certInfo = new CertInfo(attStmt["certInfo"].GetByteString());
}
- if (null == certInfo || null == certInfo.ExtraData || 0 == certInfo.ExtraData.Length)
+ if (null == certInfo)
throw new Fido2VerificationException("CertInfo invalid parsing TPM format attStmt");
// Verify that magic is set to TPM_GENERATED_VALUE and type is set to TPM_ST_ATTEST_CERTIFY
@@ -580,7 +580,7 @@ public override void Verify()
// OID is 2.23.133.8.3
var EKU = EKUFromAttnCertExts(aikCert.Extensions, "2.23.133.8.3");
if (!EKU)
- throw new Fido2VerificationException("Invalid EKU on AIK certificate");
+ throw new Fido2VerificationException("aikCert EKU missing tcg-kp-AIKCertificate OID");
// The Basic Constraints extension MUST have the CA component set to false.
if (IsAttnCertCACert(aikCert.Extensions))
@@ -590,8 +590,8 @@ public override void Verify()
var aaguid = AaguidFromAttnCertExts(aikCert.Extensions);
if ((null != aaguid) &&
(!aaguid.SequenceEqual(Guid.Empty.ToByteArray())) &&
- (0 != new Guid(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid)))
- throw new Fido2VerificationException("aaguid malformed");
+ (0 != AttestedCredentialData.FromBigEndian(aaguid).CompareTo(AuthData.AttestedCredentialData.AaGuid)))
+ throw new Fido2VerificationException(string.Format("aaguid malformed, expected {0}, got {1}", AuthData.AttestedCredentialData.AaGuid, new Guid(aaguid)));
}
// If ecdaaKeyId is present, then the attestation type is ECDAA
else if (null != EcdaaKeyId)
@@ -738,9 +738,18 @@ public static (ushort size, byte[] name) NameFromTPM2BName(Memory ab, ref
{
name = AuthDataHelper.GetSizedByteArray(ab, ref offset, tpmAlgToDigestSizeMap[tpmalg]);
}
+ else
+ {
+ throw new Fido2VerificationException("TPM_ALG_ID found in TPM2B_NAME not acceptable hash algorithm");
+ }
}
+ else
+ {
+ throw new Fido2VerificationException("Invalid TPM_ALG_ID found in TPM2B_NAME");
+ }
+
if (totalSize != bytes.Length + name.Length)
- throw new Fido2VerificationException("Unexpected no name found in TPM2B_NAME");
+ throw new Fido2VerificationException("Unexpected extra bytes found in TPM2B_NAME");
return (size, name);
}
@@ -752,10 +761,10 @@ public CertInfo(byte[] certInfo)
var offset = 0;
Magic = AuthDataHelper.GetSizedByteArray(certInfo, ref offset, 4);
if (0xff544347 != BitConverter.ToUInt32(Magic.ToArray().Reverse().ToArray(), 0))
- throw new Fido2VerificationException("Bad magic number " + Magic.ToString());
+ throw new Fido2VerificationException("Bad magic number " + BitConverter.ToString(Magic).Replace("-",""));
Type = AuthDataHelper.GetSizedByteArray(certInfo, ref offset, 2);
if (0x8017 != BitConverter.ToUInt16(Type.ToArray().Reverse().ToArray(), 0))
- throw new Fido2VerificationException("Bad structure tag " + Type.ToString());
+ throw new Fido2VerificationException("Bad structure tag " + BitConverter.ToString(Type).Replace("-", ""));
QualifiedSigner = AuthDataHelper.GetSizedByteArray(certInfo, ref offset);
ExtraData = AuthDataHelper.GetSizedByteArray(certInfo, ref offset);
if (null == ExtraData || 0 == ExtraData.Length)
diff --git a/Src/Fido2/CryptoUtils.cs b/Src/Fido2/CryptoUtils.cs
index 5b606feb..fe128d1b 100644
--- a/Src/Fido2/CryptoUtils.cs
+++ b/Src/Fido2/CryptoUtils.cs
@@ -2,11 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
+using Asn1;
using Fido2NetLib.Objects;
-using LipingShare.LCLib.Asn1Processor;
namespace Fido2NetLib
{
@@ -56,109 +55,99 @@ public static HashAlgorithm GetHasher(HashAlgorithmName hashName)
{4, HashAlgorithmName.SHA1 },
{11, HashAlgorithmName.SHA256 },
{12, HashAlgorithmName.SHA384 },
- {13, HashAlgorithmName.SHA512 }
+ {13, HashAlgorithmName.SHA512 },
+ {(int) COSE.Algorithm.EdDSA, HashAlgorithmName.SHA512 }
};
- public static byte[] GetEcDsaSigValue(BinaryReader reader)
+ public static byte[] SigFromEcDsaSig(byte[] ecDsaSig, int keySize)
{
- // First byte should be DER int marker
- var derInt = reader.ReadByte();
- if (0x02 != derInt)
- throw new Fido2VerificationException("ECDsa signature coordinate sequence does not contain DER integer value"); // DER INTEGER
+ var decoded = AsnElt.Decode(ecDsaSig);
+ var r = decoded.Sub[0].GetOctetString();
+ var s = decoded.Sub[1].GetOctetString();
+
+ // .NET requires IEEE P-1363 fixed size unsigned big endian values for R and S
+ // ASN.1 requires storing positive integer values with any leading 0s removed
+ // Convert ASN.1 format to IEEE P-1363 format
+ // determine coefficient size
+ var coefficientSize = (int)Math.Ceiling((decimal)keySize / 8);
- // Second byte is length to read
- var len = reader.ReadByte();
+ // Create byte array to copy R into
+ var P1363R = new byte[coefficientSize];
- // a leading 0x00 is added to the content to indicate that the number is not negative...
- if (0x00 == reader.BaseStream.ReadByte())
+ if (0x0 == r[0] && (r[1] & (1 << 7)) != 0)
{
- // If the integer is positive but the high order bit is set to 1
- if ((reader.BaseStream.ReadByte() & (1 << 7)) != 0)
- {
- // we don't want to copy that leading 0x00, so reduce the length to read
- len--;
- }
- // back the stream up one byte from the high order bit check
- reader.BaseStream.Seek(-1, SeekOrigin.Current);
+ r.Skip(1).ToArray().CopyTo(P1363R, coefficientSize - r.Length + 1);
}
- // back the stream up one byte from the leading 0x00 check
else
- reader.BaseStream.Seek(-1, SeekOrigin.Current);
+ {
+ r.CopyTo(P1363R, coefficientSize - r.Length);
+ }
- // read the calculated number of bytes from the stream and return the result
- return reader.ReadBytes(len);
+ // Create byte array to copy S into
+ var P1363S = new byte[coefficientSize];
+
+ if (0x0 == s[0] && (s[1] & (1 << 7)) != 0)
+ {
+ s.Skip(1).ToArray().CopyTo(P1363S, coefficientSize - s.Length + 1);
+ }
+ else
+ {
+ s.CopyTo(P1363S, coefficientSize - s.Length);
+ }
+
+ // Concatenate R + S coordinates and return the raw signature
+ return P1363R.Concat(P1363S).ToArray();
}
- public static byte[] SigFromEcDsaSig(byte[] ecDsaSig, int keySize)
+ ///
+ /// Convert PEM formated string into byte array.
+ ///
+ /// source string.
+ /// output byte array.
+ public static byte[] PemToBytes(string pemStr)
{
- using (var stream = new MemoryStream(ecDsaSig, false))
+ const string PemStartStr = "-----BEGIN";
+ const string PemEndStr = "-----END";
+ byte[] retval = null;
+ var lines = pemStr.Split('\n');
+ var base64Str = "";
+ bool started = false, ended = false;
+ var cline = "";
+ for (var i = 0; i < lines.Length; i++)
{
- using (var reader = new BinaryReader(stream))
+ cline = lines[i].ToUpper();
+ if (cline == "")
+ continue;
+ if (cline.Length > PemStartStr.Length)
{
- // first byte should be DER sequence marker
- var derSequence = reader.ReadByte();
- if (0x30 != derSequence) throw new Fido2VerificationException("ECDsa signature not a valid DER sequence");
-
- // two forms of length, short form and long form
- // short form, one byte, bit 8 not set, rest of the bits indicate data length
- var dataLen = reader.ReadByte();
-
- // long form, first byte, bit 8 is set, rest of bits indicate the length of the data length
- // so if bit 8 is on...
- var longForm = (0 != (dataLen & (1 << 7)));
- if (true == longForm)
+ if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
{
- // rest of bits indicate the number of bytes containing the data length in long form
- var longLenBytes = (dataLen & ~(1 << 7));
-
- // we are expecting a single byte to hold the data length at the time of this writing
- if (1 != longLenBytes)
- throw new Fido2VerificationException("ECDsa signature has invalid long form data length bytes");
-
- // read the length of the data
- var longLen = reader.ReadBytes(longLenBytes)[0];
-
- // must be more than 127 bytes otherwise we'd be using the short form
- if (0x80 > longLen)
- throw new Fido2VerificationException("ECDsa signature has invalid long form data length");
-
- // sanity check the length
- if (ecDsaSig.Length != (reader.BaseStream.Position + longLen))
- throw new Fido2VerificationException("ECDsa signature has invalid long form length");
+ started = true;
+ continue;
+ }
+ }
+ if (cline.Length > PemEndStr.Length)
+ {
+ if (cline.Substring(0, PemEndStr.Length) == PemEndStr)
+ {
+ ended = true;
+ break;
}
-
- // Get R value
- var r = GetEcDsaSigValue(reader);
-
- // Get S value
- var s = GetEcDsaSigValue(reader);
-
- // make sure we are at the end
- if (reader.BaseStream.Position != reader.BaseStream.Length)
- throw new Fido2VerificationException("ECDsa signature has bytes leftover after parsing R and S values");
-
- // .NET requires IEEE P-1363 fixed size unsigned big endian values for R and S
- // ASN.1 requires storing positive integer values with any leading 0s removed
- // Convert ASN.1 format to IEEE P-1363 format
- // determine coefficient size
- var coefficientSize = (int)Math.Ceiling((decimal)keySize / 8);
-
- // Sanity check R and S value lengths
- if ((coefficientSize * 2) < (r.Length + s.Length))
- throw new Fido2VerificationException("ECDsa signature has invalid length for given curve key size");
-
- // Create byte array to copy R into
- var P1363R = new byte[coefficientSize];
- r.CopyTo(P1363R, coefficientSize - r.Length);
-
- // Create byte array to copy S into
- var P1363S = new byte[coefficientSize];
- s.CopyTo(P1363S, coefficientSize - s.Length);
-
- // Concatenate R + S coordinates and return the raw signature
- return P1363R.Concat(P1363S).ToArray();
+ }
+ if (started)
+ {
+ base64Str += lines[i];
}
}
+ if (!(started && ended))
+ {
+ throw new Exception("'BEGIN'/'END' line is missing.");
+ }
+ base64Str = base64Str.Replace("\r", "");
+ base64Str = base64Str.Replace("\n", "");
+ base64Str = base64Str.Replace("\n", " ");
+ retval = Convert.FromBase64String(base64Str);
+ return retval;
}
public static string CDPFromCertificateExts(X509ExtensionCollection exts)
@@ -168,49 +157,29 @@ public static string CDPFromCertificateExts(X509ExtensionCollection exts)
{
if (ext.Oid.Value.Equals("2.5.29.31")) // id-ce-CRLDistributionPoints
{
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- var asnData = new AsnEncodedData(ext.Oid, ext.RawData);
- cdp += asnData.Format(false).Split('=')[1];
- }
- else
- {
- var strCDP = Asn1Util.BytesToString(ext.RawData);
- strCDP = strCDP.Replace("\u0086.", "=");
- cdp += strCDP.Split('=')[1];
- }
+ var asnData = AsnElt.Decode(ext.RawData);
+ cdp = System.Text.Encoding.ASCII.GetString(asnData.Sub[0].Sub[0].Sub[0].Sub[0].GetOctetString());
}
}
return cdp;
}
public static bool IsCertInCRL(byte[] crl, X509Certificate2 cert)
{
- var asnParser = new Asn1Parser();
- var strCRL = Asn1Util.BytesToString(crl);
-
- if (Asn1Util.IsPemFormated(strCRL))
- {
- asnParser.LoadData(Asn1Util.PemToStream(strCRL));
- }
- else asnParser.LoadData(new MemoryStream(crl));
-
- if (7 > asnParser.RootNode.GetChildNode(0).ChildNodeCount)
+ var pemCRL = System.Text.Encoding.ASCII.GetString(crl);
+ var crlBytes = PemToBytes(pemCRL);
+ var asnData = AsnElt.Decode(crlBytes);
+ if (7 > asnData.Sub[0].Sub.Length)
return false; // empty CRL
- var revokedCertificates = asnParser.RootNode.GetChildNode(0).GetChildNode(5);
-
- // throw revoked certs into a list so someday we eventually cache CRLs
+ var revokedCertificates = asnData.Sub[0].Sub[5].Sub;
var revoked = new List();
- for (var i = 0; i < revokedCertificates.ChildNodeCount; i++)
+
+ foreach (AsnElt s in revokedCertificates)
{
- revoked.Add(Asn1Util.BytesToLong(revokedCertificates.GetChildNode(i)
- .GetChildNode(0)
- .Data
- .Reverse()
- .ToArray()));
+ revoked.Add(BitConverter.ToInt64(s.Sub[0].GetOctetString().Reverse().ToArray(), 0));
}
- return revoked.Contains(Asn1Util.BytesToLong(cert.GetSerialNumber()));
+ return revoked.Contains(BitConverter.ToInt64(cert.GetSerialNumber(), 0));
}
}
}
diff --git a/Src/Fido2/Fido2.csproj b/Src/Fido2/Fido2.csproj
index d41d58d6..42af5628 100644
--- a/Src/Fido2/Fido2.csproj
+++ b/Src/Fido2/Fido2.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/Test/Attestation/AndroidKey.cs b/Test/Attestation/AndroidKey.cs
new file mode 100644
index 00000000..f0056cdc
--- /dev/null
+++ b/Test/Attestation/AndroidKey.cs
@@ -0,0 +1,555 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using fido2_net_lib.Test;
+using Fido2NetLib.Objects;
+using PeterO.Cbor;
+using Xunit;
+using Asn1;
+using Fido2NetLib;
+
+namespace Test.Attestation
+{
+ public class AndroidKey : Fido2Tests.Attestation
+ {
+ public byte[] EncodeAttestationRecord()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime });
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) }));
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin });
+ return AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+ }
+ public AndroidKey()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", EncodeAttestationRecord(), false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ }
+ [Fact]
+ public async void TestAndroidKey()
+ {
+ var res = await MakeAttestationResponse();
+ Assert.Equal(string.Empty, res.ErrorMessage);
+ Assert.Equal("ok", res.Status);
+ Assert.Equal(_aaguid, res.Result.Aaguid);
+ Assert.Equal(_signCount, res.Result.Counter);
+ Assert.Equal("android-key", res.Result.CredType);
+ Assert.Equal(_credentialID, res.Result.CredentialId);
+ Assert.Null(res.Result.ErrorMessage);
+ Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey);
+ Assert.Null(res.Result.Status);
+ Assert.Equal("Test User", res.Result.User.DisplayName);
+ Assert.Equal(Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id);
+ Assert.Equal("testuser", res.Result.User.Name);
+ }
+
+ [Fact]
+ public void TestAndroidKeySigNull()
+ {
+ _attestationObject["attStmt"].Set("sig", null);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android-key attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeySigNotByteString()
+ {
+ _attestationObject["attStmt"].Set("sig", "walrus");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android-key attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeySigByteStringZeroLen()
+ {
+ _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[0]));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android-key attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyMissingX5c()
+ {
+ _attestationObject["attStmt"].Set("x5c", null);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message);
+ }
+ [Fact]
+ public void TestAndroidKeyX5cNotArray()
+ {
+ _attestationObject["attStmt"].Set("x5c", "boomerang");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cValueNotByteString()
+ {
+ _attestationObject["attStmt"].Set("x5c", "x".ToArray());
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cValueZeroLengthByteString()
+ {
+ _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in android-key attestation", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyInvalidPublicKey()
+ {
+ var attestnCert = _attestationObject["attStmt"]["x5c"].Values.FirstOrDefault().GetByteString();
+ attestnCert[0] ^= 0xff;
+ var X5c = CBORObject.NewArray().Add(CBORObject.FromObject(attestnCert));
+ _attestationObject["attStmt"].Set("x5c", X5c);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.StartsWith("Failed to extract public key from android key: ", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyMissingAlg()
+ {
+ _attestationObject["attStmt"].Remove("alg");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyAlgNull()
+ {
+ _attestationObject["attStmt"].Set("alg", null);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyAlgNaN()
+ {
+ _attestationObject["attStmt"].Set("alg", "invalid alg");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyAlgNotInMap()
+ {
+ _attestationObject["attStmt"].Set("alg", -1);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android key attestation algorithm", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeySigNotASN1()
+ {
+ _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[] { 0xf1, 0xd0 }));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Failed to decode android key attestation signature from ASN.1 encoded form", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyBadSig()
+ {
+ var sig = _attestationObject["attStmt"]["sig"].GetByteString();
+ sig[sig.Length - 1] ^= 0xff;
+ _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(sig));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid android key attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertMissingAttestationRecordExt()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Android key attestation certificate contains no AttestationRecord extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordExtMalformed()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", new byte[] { 0x0 }, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed android key AttestationRecord extension verifying android key attestation certificate extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordAllApplicationsSoftware()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var allApplications = AsnElt.MakeExplicit(600, AsnElt.Make(AsnElt.SEQUENCE, null));
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime, allApplications });
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) }));
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin });
+ var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Found all applications field in android key attestation certificate extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordAllApplicationsTee()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime });
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) }));
+ var allApplications = AsnElt.MakeExplicit(600, AsnElt.Make(AsnElt.SEQUENCE, null));
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin, allApplications });
+ var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Found all applications field in android key attestation certificate extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordOriginSoftware()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime });
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) }));
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(3));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin });
+ var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Found origin field not set to KM_ORIGIN_GENERATED in android key attestation certificate extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordOriginTeeTee()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(3));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime, origin });
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(2) }));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose });
+ var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Found origin field not set to KM_ORIGIN_GENERATED in android key attestation certificate extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordPurposeSoftware()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(1) }));
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime, purpose });
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { origin });
+ var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAndroidKeyX5cCertAttestationRecordPurposeTee()
+ {
+ var attestationVersion = AsnElt.MakeInteger(3);
+ var attestationSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var keymasterVersion = AsnElt.MakeInteger(2);
+ var keymasterSecurityLevel = AsnElt.Make(AsnElt.UNIVERSAL, AsnElt.MakeBlob(new byte[] { 0 }));
+ var attestationChallenge = AsnElt.MakeBlob(_clientDataHash);
+ var uniqueId = AsnElt.MakeBlob(_credentialID);
+ var creationDateTime = AsnElt.MakeExplicit(701, AsnElt.MakeInteger(DateTimeOffset.Now.ToUnixTimeSeconds()));
+ var softwareEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { creationDateTime });
+ var purpose = AsnElt.MakeExplicit(1, AsnElt.MakeSetOf(new AsnElt[] { AsnElt.MakeInteger(1) }));
+ var origin = AsnElt.MakeExplicit(702, AsnElt.MakeInteger(0));
+ var teeEnforced = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] { purpose, origin });
+ var attRecord = AsnElt.Make(AsnElt.SEQUENCE, new AsnElt[] {
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced
+ }).Encode();
+
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-key");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=AndroidKeyTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(new X509Extension("1.3.6.1.4.1.11129.2.1.17", attRecord, false));
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+
+ byte[] signature = SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES256)
+ .Add("x5c", X5c)
+ .Add("sig", signature));
+ }
+ }
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Found purpose field not set to KM_PURPOSE_SIGN in android key attestation certificate extension", ex.Result.Message);
+ }
+ }
+}
diff --git a/Test/Attestation/AndroidSafetyNet.cs b/Test/Attestation/AndroidSafetyNet.cs
new file mode 100644
index 00000000..25e5fefe
--- /dev/null
+++ b/Test/Attestation/AndroidSafetyNet.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using fido2_net_lib.Test;
+using PeterO.Cbor;
+
+namespace Test.Attestation
+{
+ class AndroidSafetyNet : Fido2Tests.Attestation
+ {
+ public AndroidSafetyNet()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "android-safetynet");
+ }
+ }
+}
diff --git a/Test/Attestation/FidoU2f.cs b/Test/Attestation/FidoU2f.cs
new file mode 100644
index 00000000..9531a75d
--- /dev/null
+++ b/Test/Attestation/FidoU2f.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using fido2_net_lib.Test;
+using Fido2NetLib;
+using Fido2NetLib.Objects;
+using PeterO.Cbor;
+using Xunit;
+
+namespace Test.Attestation
+{
+ public class FidoU2f : Fido2Tests.Attestation
+ {
+ public FidoU2f()
+ {
+ _aaguid = Guid.Empty;
+ _attestationObject.Add("fmt", "fido-u2f");
+ X509Certificate2 attestnCert;
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP256))
+ {
+ var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ using (attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData));
+ var ecparams = ecdsaAtt.ExportParameters(true);
+
+ _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(COSE.KeyType.EC2, COSE.Algorithm.ES256, COSE.EllipticCurve.P256, ecparams.Q.X, ecparams.Q.Y);
+
+ var x = _credentialPublicKey.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString();
+ var y = _credentialPublicKey.GetCBORObject()[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString();
+ var publicKeyU2F = new byte[1] { 0x4 }.Concat(x).Concat(y).ToArray();
+
+ var verificationData = new byte[1] { 0x00 };
+ verificationData = verificationData
+ .Concat(_rpIdHash)
+ .Concat(_clientDataHash)
+ .Concat(_credentialID)
+ .Concat(publicKeyU2F.ToArray())
+ .ToArray();
+
+ byte[] signature = Fido2Tests.SignData(COSE.KeyType.EC2, COSE.Algorithm.ES256, verificationData, ecdsaAtt, null, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap().Add("x5c", X5c).Add("sig", signature));
+ }
+ }
+
+ }
+ [Fact]
+ public async void TestU2f()
+ {
+ var res = await MakeAttestationResponse();
+ Assert.Equal(string.Empty, res.ErrorMessage);
+ Assert.Equal("ok", res.Status);
+ Assert.Equal(_aaguid, res.Result.Aaguid);
+ Assert.Equal(_signCount, res.Result.Counter);
+ Assert.Equal("fido-u2f", res.Result.CredType);
+ Assert.Equal(_credentialID, res.Result.CredentialId);
+ Assert.Null(res.Result.ErrorMessage);
+ Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey);
+ Assert.Null(res.Result.Status);
+ Assert.Equal("Test User", res.Result.User.DisplayName);
+ Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id);
+ Assert.Equal("testuser", res.Result.User.Name);
+ }
+ [Fact]
+ public void TestU2fWithAaguid()
+ {
+ _aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Aaguid was not empty parsing fido-u2f atttestation statement", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fMissingX5c()
+ {
+ _attestationObject["attStmt"].Set("x5c", null);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fX5cNotArray()
+ {
+ _attestationObject["attStmt"].Set("x5c", "boomerang");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fX5cCountNotOne()
+ {
+ _attestationObject["attStmt"]
+ .Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])).Add(CBORObject.FromObject(new byte[0])));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in fido - u2f attestation", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fX5cValueNotByteString()
+ {
+ _attestationObject["attStmt"].Set("x5c", "x".ToArray());
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in fido-u2f attestation", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fX5cValueZeroLengthByteString()
+ {
+ _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in fido-u2f attestation", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fAttCertNotP256()
+ {
+ using (var ecdsaAtt = ECDsa.Create(ECCurve.NamedCurves.nistP384))
+ {
+ var attRequest = new CertificateRequest("CN=U2FTesting, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US", ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(
+ new X509BasicConstraintsExtension(false, false, 0, false));
+
+ using (var attestnCert = attRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(2)))
+ {
+ _attestationObject["attStmt"].Set("x5c", CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData)));
+ ;
+ }
+ }
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Attestation certificate public key is not an Elliptic Curve (EC) public key over the P-256 curve", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fSigNull()
+ {
+ _attestationObject["attStmt"].Set("sig", null);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fSigNotByteString()
+ {
+ _attestationObject["attStmt"].Set("sig", "walrus");
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fSigByteStringZeroLen()
+ {
+ _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[0]));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fSigNotASN1()
+ {
+ _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(new byte[] { 0xf1, 0xd0 }));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Failed to decode fido-u2f attestation signature from ASN.1 encoded form", ex.Result.Message);
+ }
+ [Fact]
+ public void TestU2fBadSig()
+ {
+ var sig = _attestationObject["attStmt"]["sig"].GetByteString();
+ sig[sig.Length - 1] ^= 0xff;
+ _attestationObject["attStmt"].Set("sig", CBORObject.FromObject(sig));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid fido-u2f attestation signature", ex.Result.Message);
+ }
+ }
+}
diff --git a/Test/Attestation/None.cs b/Test/Attestation/None.cs
new file mode 100644
index 00000000..b0b24865
--- /dev/null
+++ b/Test/Attestation/None.cs
@@ -0,0 +1,51 @@
+using System.Linq;
+using fido2_net_lib.Test;
+using Fido2NetLib;
+using Fido2NetLib.Objects;
+using PeterO.Cbor;
+using Xunit;
+
+namespace Test.Attestation
+{
+ public class None : Fido2Tests.Attestation
+ {
+ public None()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "none");
+ }
+ [Fact]
+ public void TestNone()
+ {
+ Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param)
+ {
+ _attestationObject.Add("attStmt", CBORObject.NewMap());
+ _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(param);
+ Fido2.CredentialMakeResult res = null;
+
+ res = await MakeAttestationResponse();
+
+ Assert.Equal(string.Empty, res.ErrorMessage);
+ Assert.Equal("ok", res.Status);
+ Assert.Equal(_aaguid, res.Result.Aaguid);
+ Assert.Equal(_signCount, res.Result.Counter);
+ Assert.Equal("none", res.Result.CredType);
+ Assert.Equal(_credentialID, res.Result.CredentialId);
+ Assert.Null(res.Result.ErrorMessage);
+ Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey);
+ Assert.Null(res.Result.Status);
+ Assert.Equal("Test User", res.Result.User.DisplayName);
+ Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id);
+ Assert.Equal("testuser", res.Result.User.Name);
+ _attestationObject = CBORObject.NewMap().Add("fmt", "none");
+ });
+ }
+ [Fact]
+ public void TestNoneWithAttStmt()
+ {
+ _attestationObject.Add("attStmt", CBORObject.NewMap().Add("foo", "bar"));
+ _credentialPublicKey = Fido2Tests.MakeCredentialPublicKey(Fido2Tests._validCOSEParameters[0]);
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Attestation format none should have no attestation statement", ex.Result.Message);
+ }
+ }
+}
diff --git a/Test/Attestation/Packed.cs b/Test/Attestation/Packed.cs
new file mode 100644
index 00000000..41189f14
--- /dev/null
+++ b/Test/Attestation/Packed.cs
@@ -0,0 +1,1068 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using fido2_net_lib.Test;
+using Fido2NetLib;
+using Fido2NetLib.Objects;
+using PeterO.Cbor;
+using Xunit;
+
+namespace Test.Attestation
+{
+ public class Packed : Fido2Tests.Attestation
+ {
+ public Packed()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "packed");
+ }
+ [Fact]
+ public void TestSelf()
+ {
+ Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param)
+ {
+ var crv = (param.Length == 3) ? (COSE.EllipticCurve)param[2] : COSE.EllipticCurve.Reserved;
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], crv);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature));
+
+ var res = await MakeAttestationResponse();
+
+ Assert.Equal(string.Empty, res.ErrorMessage);
+ Assert.Equal("ok", res.Status);
+ Assert.Equal(_aaguid, res.Result.Aaguid);
+ Assert.Equal(_signCount, res.Result.Counter);
+ Assert.Equal("packed", res.Result.CredType);
+ Assert.Equal(_credentialID, res.Result.CredentialId);
+ Assert.Null(res.Result.ErrorMessage);
+ Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey);
+ Assert.Null(res.Result.Status);
+ Assert.Equal("Test User", res.Result.User.DisplayName);
+ Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id);
+ Assert.Equal("testuser", res.Result.User.Name);
+ _attestationObject = CBORObject.NewMap().Add("fmt", "packed");
+ });
+ }
+ [Fact]
+ public void TestSelfAlgMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", COSE.Algorithm.ES384)
+ .Add("sig", signature));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Algorithm mismatch between credential public key and authenticator data in self attestation statement", ex.Result.Message);
+ }
+ [Fact]
+ public void TestSelfBadSig()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 }));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Failed to validate signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestMissingAlg()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("sig", signature));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestAlgNaN()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", "invalid alg")
+ .Add("sig", signature));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid packed attestation algorithm", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestSigNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", null));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid packed attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestSigNotByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", "walrus"));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid packed attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestSigByteStringZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], (COSE.EllipticCurve)param[2]);
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", CBORObject.FromObject(new byte[0])));
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid packed attestation signature", ex.Result.Message);
+ }
+
+ [Fact]
+ public void TestFull()
+ {
+ Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param)
+ {
+ if (COSE.KeyType.OKP == (COSE.KeyType)param[0])
+ {
+ return;
+ }
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ Fido2.CredentialMakeResult res = null;
+
+ switch ((COSE.KeyType)param[0])
+ {
+ case COSE.KeyType.EC2:
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ switch (curve)
+ {
+ case COSE.EllipticCurve.P384:
+ eCCurve = ECCurve.NamedCurves.nistP384;
+ break;
+ case COSE.EllipticCurve.P521:
+ eCCurve = ECCurve.NamedCurves.nistP521;
+ break;
+ }
+
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ res = await MakeAttestationResponse();
+ }
+ }
+ break;
+ case COSE.KeyType.RSA:
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], COSE.EllipticCurve.Reserved, rsa: rsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ res = await MakeAttestationResponse();
+ }
+ }
+ break;
+ case COSE.KeyType.OKP:
+ {
+ var avr = new AssertionVerificationResult()
+ {
+ CredentialId = new byte[] { 0xf1, 0xd0 },
+ ErrorMessage = string.Empty,
+ Status = "ok",
+ };
+ }
+ break;
+ }
+ Assert.Equal(string.Empty, res.ErrorMessage);
+ Assert.Equal("ok", res.Status);
+ Assert.Equal(_aaguid, res.Result.Aaguid);
+ Assert.Equal(_signCount, res.Result.Counter);
+ Assert.Equal("packed", res.Result.CredType);
+ Assert.Equal(_credentialID, res.Result.CredentialId);
+ Assert.Null(res.Result.ErrorMessage);
+ Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey);
+ Assert.Null(res.Result.Status);
+ Assert.Equal("Test User", res.Result.User.DisplayName);
+ Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id);
+ Assert.Equal("testuser", res.Result.User.Name);
+ _attestationObject = CBORObject.NewMap().Add("fmt", "packed");
+ });
+ }
+
+ [Fact]
+ public void TestFullMissingX5c()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", null));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c array in packed attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullX5cNotArray()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", "boomerang"));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c array in packed attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullX5cCountNotOne()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], COSE.EllipticCurve.Reserved, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0])).Add(CBORObject.FromObject(new byte[0]))));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullX5cValueNotByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], COSE.EllipticCurve.Reserved, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", "x".ToArray()));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullX5cValueZeroLengthByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", CBORObject.NewArray().Add(CBORObject.FromObject(new byte[0]))));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c cert found in packed attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullX5cCertExpired()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-7);
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Packed signing certificate expired or not yet valid", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullX5cCertNotYetValid()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(1);
+ DateTimeOffset notAfter = notBefore.AddDays(7);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Packed signing certificate expired or not yet valid", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullInvalidAlg()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", 42)
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid attestation algorithm", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullInvalidSig()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", new byte[] { 0x30, 0x45, 0x02, 0x20, 0x11, 0x9b, 0x6f, 0xa8, 0x1c, 0xe1, 0x75, 0x9e, 0xbe, 0xf1, 0x52, 0xa6, 0x99, 0x40, 0x5e, 0xd6, 0x6a, 0xcc, 0x01, 0x33, 0x65, 0x18, 0x05, 0x00, 0x96, 0x28, 0x29, 0xbe, 0x85, 0x57, 0xb7, 0x1d, 0x02, 0x21, 0x00, 0x94, 0x50, 0x1d, 0xf1, 0x90, 0x03, 0xa4, 0x4d, 0xa4, 0xdf, 0x9f, 0xbb, 0xb5, 0xe4, 0xce, 0x91, 0x6b, 0xc3, 0x90, 0xe8, 0x38, 0x99, 0x66, 0x4f, 0xa5, 0xc4, 0x0c, 0xf3, 0xed, 0xe3, 0xda, 0x83 })
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid full packed signature", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullAttCertNotV3()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var rawAttestnCert = attestnCert.RawData;
+ rawAttestnCert[12] = 0x41;
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(rawAttestnCert))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Packed x5c attestation certificate not V3", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullAttCertSubject()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Not Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid attestation cert subject", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullAttCertAaguidNotMatchAuthdata()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ var notAsnEncodedAaguid = asnEncodedAaguid;
+ notAsnEncodedAaguid[3] = 0x42;
+ var notIdFidoGenCeAaguidExt = new X509Extension(oidIdFidoGenCeAaguid, asnEncodedAaguid, false);
+ attRequest.CertificateExtensions.Add(notIdFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("aaguid present in packed attestation cert exts but does not match aaguid from authData", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestFullAttCertCAFlagSet()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ X509Certificate2 root, attestnCert;
+ DateTimeOffset notBefore = DateTimeOffset.UtcNow;
+ DateTimeOffset notAfter = notBefore.AddDays(2);
+ var attDN = new X500DistinguishedName("CN=Testing, OU=Authenticator Attestation, O=FIDO2-NET-LIB, C=US");
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ var curve = (COSE.EllipticCurve)param[2];
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+ using (root = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+ attRequest.CertificateExtensions.Add(caExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ root,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(root.RawData));
+
+ var signature = SignData((COSE.KeyType)param[0], (COSE.Algorithm)param[1], curve, ecdsa: ecdsaAtt);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("alg", (COSE.Algorithm)param[1])
+ .Add("sig", signature)
+ .Add("x5c", X5c));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Attestion certificate has CA cert flag present", ex.Result.Message);
+ }
+ }
+ }
+ }
+}
diff --git a/Test/Attestation/Tpm.cs b/Test/Attestation/Tpm.cs
new file mode 100644
index 00000000..866273f6
--- /dev/null
+++ b/Test/Attestation/Tpm.cs
@@ -0,0 +1,8503 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using fido2_net_lib.Test;
+using Fido2NetLib;
+using Fido2NetLib.Objects;
+using PeterO.Cbor;
+using Xunit;
+using Fido2NetLib.AttestationFormat;
+
+namespace Test.Attestation
+{
+ public class Tpm : Fido2Tests.Attestation
+ {
+ private X500DistinguishedName attDN = new X500DistinguishedName("");
+ private byte[] asnEncodedSAN = new byte[] { 0x30, 0x53, 0xA4, 0x51, 0x30, 0x4F, 0x31, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x01, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x46, 0x46, 0x46, 0x46, 0x31, 0x44, 0x30, 0x30, 0x1F, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x02, 0x0C, 0x16, 0x46, 0x49, 0x44, 0x4F, 0x32, 0x2D, 0x4E, 0x45, 0x54, 0x2D, 0x4C, 0x49, 0x42, 0x2D, 0x54, 0x45, 0x53, 0x54, 0x2D, 0x54, 0x50, 0x4D, 0x30, 0x14, 0x06, 0x05, 0x67, 0x81, 0x05, 0x02, 0x03, 0x0C, 0x0B, 0x69, 0x64, 0x3A, 0x46, 0x31, 0x44, 0x30, 0x30, 0x30, 0x30, 0x32 };
+ private X509Certificate2 rootCert, attestnCert;
+ private DateTimeOffset notBefore, notAfter;
+ private X509EnhancedKeyUsageExtension tcgKpAIKCertExt;
+ private X509Extension aikCertSanExt;
+ private IEnumerable unique, exponent, curveId, kdf;
+ private byte[] type, tpmAlg;
+
+ public Tpm()
+ {
+ _attestationObject = CBORObject.NewMap().Add("fmt", "tpm");
+ unique = null;
+ exponent = null;
+ curveId = null;
+ kdf = null;
+ type = new byte[2];
+ tpmAlg = new byte[2];
+
+ notBefore = DateTimeOffset.UtcNow;
+ notAfter = notBefore.AddDays(2);
+ caExt = new X509BasicConstraintsExtension(true, true, 2, false);
+ notCAExt = new X509BasicConstraintsExtension(false, false, 0, false);
+ tcgKpAIKCertExt = new X509EnhancedKeyUsageExtension(
+ new OidCollection
+ {
+ new Oid("2.23.133.8.3")
+ },
+ false);
+
+ aikCertSanExt = new X509Extension(
+ "2.5.29.17",
+ asnEncodedSAN,
+ false);
+ }
+
+ [Fact]
+ public void TestTPM()
+ {
+ Fido2Tests._validCOSEParameters.ForEach(async delegate (object[] param)
+ {
+ if (COSE.KeyType.OKP == (COSE.KeyType)param[0])
+ {
+ return;
+ }
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ switch ((COSE.KeyType)param[0])
+ {
+ case COSE.KeyType.EC2:
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+
+ var curve = (COSE.EllipticCurve)param[2];
+ switch (curve)
+ {
+ case COSE.EllipticCurve.P384:
+ eCCurve = ECCurve.NamedCurves.nistP384;
+ break;
+ case COSE.EllipticCurve.P521:
+ eCCurve = ECCurve.NamedCurves.nistP521;
+ break;
+ }
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+
+ var ecparams = ecdsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X);
+ cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y);
+ cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]);
+
+ var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString();
+ var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString();
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = BitConverter
+ .GetBytes((UInt16)x.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(x)
+ .Concat(BitConverter.GetBytes((UInt16)y.Length)
+ .Reverse()
+ .ToArray())
+ .Concat(y);
+
+ var CoseCurveToTpm = new Dictionary
+ {
+ { 1, TpmEccCurve.TPM_ECC_NIST_P256},
+ { 2, TpmEccCurve.TPM_ECC_NIST_P384},
+ { 3, TpmEccCurve.TPM_ECC_NIST_P521},
+ };
+
+ curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32()]).Reverse().ToArray();
+ kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL);
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+ }
+ }
+ break;
+ case COSE.KeyType.RSA:
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+ }
+ }
+
+ break;
+ }
+ var res = await MakeAttestationResponse();
+
+ Assert.Equal(string.Empty, res.ErrorMessage);
+ Assert.Equal("ok", res.Status);
+ Assert.Equal(_aaguid, res.Result.Aaguid);
+ Assert.Equal(_signCount, res.Result.Counter);
+ Assert.Equal("tpm", res.Result.CredType);
+ Assert.Equal(_credentialID, res.Result.CredentialId);
+ Assert.Null(res.Result.ErrorMessage);
+ Assert.Equal(_credentialPublicKey.GetBytes(), res.Result.PublicKey);
+ Assert.Null(res.Result.Status);
+ Assert.Equal("Test User", res.Result.User.DisplayName);
+ Assert.Equal(System.Text.Encoding.UTF8.GetBytes("testuser"), res.Result.User.Id);
+ Assert.Equal("testuser", res.Result.User.Name);
+ _attestationObject = CBORObject.NewMap().Add("fmt", "tpm");
+ });
+ }
+
+ [Fact]
+ public void TestTPMSigNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", null)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM attestation signature", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMSigNotByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", "strawberries")
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM attestation signature", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMSigByteStringZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", CBORObject.FromObject(new byte[0]))
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM attestation signature", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMVersionNot2()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "3.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("FIDO2 only supports TPM 2.0", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", null));
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Missing or malformed pubArea", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaNotByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", "banana"));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Missing or malformed pubArea", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaByteStringZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", CBORObject.FromObject(new byte[0])));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Missing or malformed pubArea", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniqueNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+ var tpmalg = (TpmAlg)Enum.Parse(typeof(TpmAlg), BitConverter.ToUInt16(type.Reverse().ToArray(), 0).ToString());
+ var policy = new byte[] { 0x00 };
+ var pubArea
+ = type
+ .Concat(tpmAlg)
+ .Concat(new byte[] { 0x00, 0x00, 0x00, 0x00 })
+ .Concat(BitConverter.GetBytes((UInt16)policy.Length)
+ .Reverse()
+ .ToArray())
+ .Concat(policy)
+ .Concat(new byte[] { 0x00, 0x10 })
+ .Concat(new byte[] { 0x00, 0x10 })
+ .Concat(new byte[] { 0x80, 0x00 })
+ .Concat(BitConverter.GetBytes(exponent.ToArray()[0] + (exponent.ToArray()[1] << 8) + (exponent.ToArray()[2] << 16)));
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea.ToArray());
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Missing or malformed pubArea", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniqueByteStringZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ new byte[0] // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Missing or malformed pubArea", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniquePublicKeyMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.Reverse().ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Public key mismatch between pubArea and credentialPublicKey", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniqueExponentMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ new byte[] { 0x00, 0x01, 0x00 } , // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Public key exponent mismatch between pubArea and credentialPublicKey", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniqueXValueMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var alg = (COSE.Algorithm)param[1];
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+
+ var curve = (COSE.EllipticCurve)param[2];
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+
+ var ecparams = ecdsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X);
+ cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y);
+ cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]);
+
+ var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString().Reverse().ToArray();
+ var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString();
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = BitConverter
+ .GetBytes((UInt16)x.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(x)
+ .Concat(BitConverter.GetBytes((UInt16)y.Length)
+ .Reverse()
+ .ToArray())
+ .Concat(y);
+
+ var CoseCurveToTpm = new Dictionary
+ {
+ { 1, TpmEccCurve.TPM_ECC_NIST_P256},
+ { 2, TpmEccCurve.TPM_ECC_NIST_P384},
+ { 3, TpmEccCurve.TPM_ECC_NIST_P521},
+ };
+
+ curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32()]).Reverse().ToArray();
+ kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL);
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("X-coordinate mismatch between pubArea and credentialPublicKey", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniqueYValueMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var alg = (COSE.Algorithm)param[1];
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+
+ var curve = (COSE.EllipticCurve)param[2];
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+
+ var ecparams = ecdsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X);
+ cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y);
+ cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]);
+
+ var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString();
+ var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString().Reverse().ToArray();
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = BitConverter
+ .GetBytes((UInt16)x.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(x)
+ .Concat(BitConverter.GetBytes((UInt16)y.Length)
+ .Reverse()
+ .ToArray())
+ .Concat(y);
+
+ var CoseCurveToTpm = new Dictionary
+ {
+ { 1, TpmEccCurve.TPM_ECC_NIST_P256},
+ { 2, TpmEccCurve.TPM_ECC_NIST_P384},
+ { 3, TpmEccCurve.TPM_ECC_NIST_P521},
+ };
+
+ curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Crv)].AsInt32()]).Reverse().ToArray();
+ kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL);
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Y-coordinate mismatch between pubArea and credentialPublicKey", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaUniqueCurveMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[0];
+ var alg = (COSE.Algorithm)param[1];
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+
+ using (var ecdsaRoot = ECDsa.Create())
+ {
+ var rootRequest = new CertificateRequest(rootDN, ecdsaRoot, HashAlgorithmName.SHA256);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ ECCurve eCCurve = ECCurve.NamedCurves.nistP256;
+
+ var curve = (COSE.EllipticCurve)param[2];
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var ecdsaAtt = ECDsa.Create(eCCurve))
+ {
+ var attRequest = new CertificateRequest(attDN, ecdsaAtt, HashAlgorithmName.SHA256);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(ecdsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+
+ var ecparams = ecdsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.X, ecparams.Q.X);
+ cpk.Add(COSE.KeyTypeParameter.Y, ecparams.Q.Y);
+ cpk.Add(COSE.KeyTypeParameter.Crv, (COSE.EllipticCurve)param[2]);
+
+ var x = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.X)].GetByteString();
+ var y = cpk[CBORObject.FromObject(COSE.KeyTypeParameter.Y)].GetByteString();
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = BitConverter
+ .GetBytes((UInt16)x.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(x)
+ .Concat(BitConverter.GetBytes((UInt16)y.Length)
+ .Reverse()
+ .ToArray())
+ .Concat(y);
+
+ var CoseCurveToTpm = new Dictionary
+ {
+ { 1, TpmEccCurve.TPM_ECC_NIST_P256},
+ { 2, TpmEccCurve.TPM_ECC_NIST_P384},
+ { 3, TpmEccCurve.TPM_ECC_NIST_P521},
+ };
+
+ curveId = BitConverter.GetBytes((ushort)CoseCurveToTpm[2]).Reverse().ToArray();
+ kdf = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_NULL);
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_ECC).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, ecdsaAtt, null, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Curve mismatch between pubArea and credentialPublicKey", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", null)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoNotByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", "tomato")
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoByteStringZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", CBORObject.FromObject(new byte[0]))
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("CertInfo invalid parsing TPM format attStmt", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoBadMagic()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }, // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Bad magic number 474354FF", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoBadType()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }, // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Bad structure tag 1780", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoExtraDataZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)0)
+ .Reverse()
+ .ToArray();
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ new byte[0], // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Bad extraData in certInfo", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoTPM2BNameIsHandle()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(new byte[] { 0x00, 0x04 })
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Unexpected handle in TPM2B_NAME", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoTPM2BNoName()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(new byte[] { 0x00, 0x00 })
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Unexpected no name found in TPM2B_NAME", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoTPM2BExtraBytes()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length + 1)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea)
+ .Concat(new byte[] { 0x00 });
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Unexpected extra bytes found in TPM2B_NAME", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoTPM2BInvalidHashAlg()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(new byte[] { 0x00, 0x10 })
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("TPM_ALG_ID found in TPM2B_NAME not acceptable hash algorithm", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMCertInfoTPM2BInvalidTPMALGID()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(new byte[] { 0xff, 0xff })
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM_ALG_ID found in TPM2B_NAME", ex.Result.Message);
+ }
+ }
+ }
+
+
+ [Fact]
+ public void TestTPMAlgNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", null)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMAlgNotNumber()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", "kiwi")
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMAlgInvalid()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", 0)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Invalid TPM attestation algorithm", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMAlgMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", COSE.Algorithm.RS1)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Hash value mismatch extraData and attToBeSigned", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMPubAreaAttestedDataMismatch()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+
+ hashedPubArea[hashedPubArea.Length - 1] ^= 0xFF;
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", X5c)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Hash value mismatch attested and pubArea", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMMissingX5c()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", null)
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestX5cNotArray()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", "string")
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMX5cCountZero()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", CBORObject.NewArray())
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Neither x5c nor ECDAA were found in the TPM attestation statement", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMX5cValuesNull()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", CBORObject.NewArray().Add(null))
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMX5cValuesCountZero()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", CBORObject.NewArray().Add(CBORObject.Null))
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMFirstX5cValueNotByteString()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable tpm2bName = new byte[] { }
+ .Concat(tpm2bNameLen)
+ .Concat(tpmAlg)
+ .Concat(hashedPubArea);
+
+ var certInfo = CreateCertInfo(
+ new byte[] { 0x47, 0x43, 0x54, 0xff }.Reverse().ToArray(), // Magic
+ new byte[] { 0x17, 0x80 }.Reverse().ToArray(), // Type
+ new byte[] { 0x00, 0x01, 0x00 }, // QualifiedSIgner
+ extraData.ToArray(), // ExtraData
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Clock
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // ResetCount
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // RestartCount
+ new byte[] { 0x00 }, // Safe
+ new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // FirmwareVersion
+ tpm2bName.ToArray(), // TPM2BName
+ new byte[] { 0x00, 0x00 } // AttestedQualifiedNameBuffer
+ );
+
+ byte[] signature = Fido2Tests.SignData((COSE.KeyType)param[0], alg, certInfo, null, rsaAtt, null);
+
+ _attestationObject.Add("attStmt", CBORObject.NewMap()
+ .Add("ver", "2.0")
+ .Add("alg", alg)
+ .Add("x5c", "x".ToArray())
+ .Add("sig", signature)
+ .Add("certInfo", certInfo)
+ .Add("pubArea", pubArea));
+
+
+ var ex = Assert.ThrowsAsync(() => MakeAttestationResponse());
+ Assert.Equal("Malformed x5c in TPM attestation", ex.Result.Message);
+ }
+ }
+ }
+
+ [Fact]
+ public void TestTPMFirstX5cValueByteStringZeroLen()
+ {
+ var param = Fido2Tests._validCOSEParameters[3];
+
+ var alg = (COSE.Algorithm)param[1];
+ if (alg == COSE.Algorithm.ES256 || alg == COSE.Algorithm.PS256 || alg == COSE.Algorithm.RS256)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA256).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES384 || alg == COSE.Algorithm.PS384 || alg == COSE.Algorithm.RS384)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA384).Reverse().ToArray();
+ if (alg == COSE.Algorithm.ES512 || alg == COSE.Algorithm.PS512 || alg == COSE.Algorithm.RS512)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA512).Reverse().ToArray();
+ if (alg == COSE.Algorithm.RS1)
+ tpmAlg = BitConverter.GetBytes((ushort)Fido2NetLib.AttestationFormat.TpmAlg.TPM_ALG_SHA1).Reverse().ToArray();
+
+ using (RSA rsaRoot = RSA.Create())
+ {
+ RSASignaturePadding padding = RSASignaturePadding.Pss;
+ switch ((COSE.Algorithm)param[1]) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
+ {
+ case COSE.Algorithm.RS1:
+ case COSE.Algorithm.RS256:
+ case COSE.Algorithm.RS384:
+ case COSE.Algorithm.RS512:
+ padding = RSASignaturePadding.Pkcs1;
+ break;
+ }
+ var rootRequest = new CertificateRequest(rootDN, rsaRoot, HashAlgorithmName.SHA256, padding);
+ rootRequest.CertificateExtensions.Add(caExt);
+
+ using (rootCert = rootRequest.CreateSelfSigned(
+ notBefore,
+ notAfter))
+
+ using (var rsaAtt = RSA.Create())
+ {
+ var attRequest = new CertificateRequest(attDN, rsaAtt, HashAlgorithmName.SHA256, padding);
+
+ attRequest.CertificateExtensions.Add(notCAExt);
+
+ attRequest.CertificateExtensions.Add(idFidoGenCeAaguidExt);
+
+ attRequest.CertificateExtensions.Add(aikCertSanExt);
+
+ attRequest.CertificateExtensions.Add(tcgKpAIKCertExt);
+
+ byte[] serial = new byte[12];
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ rng.GetBytes(serial);
+ }
+ using (X509Certificate2 publicOnly = attRequest.Create(
+ rootCert,
+ notBefore,
+ notAfter,
+ serial))
+ {
+ attestnCert = publicOnly.CopyWithPrivateKey(rsaAtt);
+ }
+
+ var X5c = CBORObject.NewArray()
+ .Add(CBORObject.FromObject(attestnCert.RawData))
+ .Add(CBORObject.FromObject(rootCert.RawData));
+ var rsaparams = rsaAtt.ExportParameters(true);
+
+ var cpk = CBORObject.NewMap();
+ cpk.Add(COSE.KeyCommonParameter.KeyType, (COSE.KeyType)param[0]);
+ cpk.Add(COSE.KeyCommonParameter.Alg, (COSE.Algorithm)param[1]);
+ cpk.Add(COSE.KeyTypeParameter.N, rsaparams.Modulus);
+ cpk.Add(COSE.KeyTypeParameter.E, rsaparams.Exponent);
+
+ _credentialPublicKey = new CredentialPublicKey(cpk);
+
+ unique = rsaparams.Modulus;
+ exponent = rsaparams.Exponent;
+ type = BitConverter.GetBytes((ushort)TpmAlg.TPM_ALG_RSA).Reverse().ToArray();
+
+ var pubArea = CreatePubArea(
+ type, // Type
+ tpmAlg, // Alg
+ new byte[] { 0x00, 0x00, 0x00, 0x00 }, // Attributes
+ new byte[] { 0x00 }, // Policy
+ new byte[] { 0x00, 0x10 }, // Symmetric
+ new byte[] { 0x00, 0x10 }, // Scheme
+ new byte[] { 0x80, 0x00 }, // KeyBits
+ exponent?.ToArray(), // Exponent
+ curveId?.ToArray(), // CurveID
+ kdf?.ToArray(), // KDF
+ unique.ToArray() // Unique
+ );
+
+ byte[] data = new byte[_authData.Length + _clientDataHash.Length];
+ Buffer.BlockCopy(_authData, 0, data, 0, _authData.Length);
+ Buffer.BlockCopy(_clientDataHash, 0, data, _authData.Length, _clientDataHash.Length);
+
+ byte[] hashedData;
+ byte[] hashedPubArea;
+ var hashAlg = CryptoUtils.algMap[(int)alg];
+ using (var hasher = CryptoUtils.GetHasher(CryptoUtils.algMap[(int)alg]))
+ {
+ hashedData = hasher.ComputeHash(data);
+ hashedPubArea = hasher.ComputeHash(pubArea);
+ }
+ IEnumerable extraData = BitConverter
+ .GetBytes((UInt16)hashedData.Length)
+ .Reverse()
+ .ToArray()
+ .Concat(hashedData);
+
+ var tpmAlgToDigestSizeMap = new Dictionary
+ {
+ {TpmAlg.TPM_ALG_SHA1, (160/8) },
+ {TpmAlg.TPM_ALG_SHA256, (256/8) },
+ {TpmAlg.TPM_ALG_SHA384, (384/8) },
+ {TpmAlg.TPM_ALG_SHA512, (512/8) }
+ };
+
+ var tpm2bNameLen = BitConverter.GetBytes((UInt16)(tpmAlg.Length + hashedPubArea.Length)).Reverse().ToArray();
+
+ IEnumerable