SecuCore/Security/Asn1.cs

520 lines
16 KiB
C#
Raw Normal View History

2026-03-03 23:53:04 -05:00
/*
* Secucore
*
* Copyright (C) 2023 Trevor Hall
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace SecuCore.Security
{
public class AsnException : Exception
{
public AsnException(string s = "") : base( "Error parsing ASN: " + s){}
}
public enum Asn1Type
{
EndOfContent,
BOOLEAN,
INTEGER,
BIT_STRING,
OCTET_STRING,
NULL,
OBJECT_IDENTIFIER,
ObjectDescriptor,
EXTERNAL,
REAL,
ENUMERATED,
EMBEDDED_PDV,
UTF8String,
RELATIVE_OID,
TIME,
RESERVED,
SEQUENCE,
SET,
NumericString,
PrintableString,
T61String,
VideotexString,
IA5String,
UTCTime,
GeneralizedTime,
GraphicString,
VisibleString,
GeneralString,
UniversalString,
CHARACTER_STRING,
BMPString,
DATE,
TIME_OF_DAY,
DATE_TIME,
DURATION,
OID_URI,
RELATIVE_OID_URI
}
public enum AsnTagClass
{
Universal,
Application,
ContextSpecific,
Private
}
public struct Asn1Tag
{
public AsnTagClass tagClass;
public bool constructed;
public uint tagNumber;
public Asn1Tag(AsnTagClass atc, bool pc, uint tn)
{
tagClass = atc;
constructed = pc;
tagNumber = tn;
}
}
public struct Asn1Info
{
public Asn1Tag tag;
public Asn1Type type;
public int contentOffset;
public bool lengthIsDefinite;
public int length;
public Asn1Info(Asn1Tag atag, int coffset, bool lendefinite, int alen)
{
tag = atag;
type = (Asn1Type)(atag.tagNumber);
contentOffset = coffset;
lengthIsDefinite = lendefinite;
length = alen;
}
public byte[] GetData(byte[] parent, ref int pos)
{
pos += length;
byte[] dat = new byte[length];
Buffer.BlockCopy(parent, (int)contentOffset, dat, 0, (int)length);
return dat;
}
}
public static class DerDecoder {
public static Asn1Info GetAsn1Data(byte[] data, ref int offset)
{
byte first = data[offset++];
AsnTagClass atc = (AsnTagClass)((first >> 6) & 0x3);
bool constructed = (((first >> 5) & 0x1) == 1);
byte tagbits = ((byte)(first & 0x1f));
int tagnumber = 0;
//if constructed is true and tag is 17 (10001) DER doesnt encode the type in additional bytes
if (tagbits == 17 && constructed == false)
{
// long form
while (true)
{
byte tnb = data[offset++];
bool moreBytes = ((tnb >> 7) == 1);
int bval = (int)(tnb & 127);
tagnumber <<= 7;
tagnumber |= bval;
}
}
else
{
tagnumber = (int)tagbits;
}
Asn1Tag asn1Tag = new Asn1Tag(atc, constructed, (uint)tagnumber);
//read length octets
byte lfirst = data[offset++];
if (lfirst == 0xff) throw new AsnException("First length octet of 0xff is reserved");
bool definite = true;
int length = 0;
bool bit8 = ((lfirst >> 7) == 1);
int lenbits = (lfirst & 127);
if (bit8)
{
if (lenbits == 0)
{
//indefinite, parser will have to read until 2 EndOfContents octets are found
definite = false;
}
else
{
//definite, long
for (int i = 0; i < lenbits; i++)
{
byte next = data[offset++];
length <<= 8;
length |= next;
}
}
}
else
{
//definite short
length = lenbits;
}
return new Asn1Info(asn1Tag, offset, definite, length);
}
public static string ParseOid(byte[] data, int length, ref int offset)
{
List<int> idelements = new List<int>();
int endindex = offset + length;
byte first = data[offset++];
idelements.Add(first / 40);
idelements.Add(first % 40);
int currentvalue = 0;
while (offset < endindex)
{
byte oide = data[offset++];
currentvalue <<= 7;
currentvalue |= (oide & 127);
if (oide >> 7 == 0)
{
idelements.Add(currentvalue);
currentvalue = 0;
}
}
return string.Join('.', idelements);
}
}
public class Asn1Index : IComparable<int[]>, IEquatable<int[]>
{
private int[] data;
public Asn1Index(params int[] columns)
{
data = columns;
}
public int CompareTo(int[] other)
{
int cmp = -1;
for (int i = 0; i < data.Length; i++)
{
if (other.Length <= i) return cmp;
cmp = data[i].CompareTo(other[i]);
if (cmp != 0) return cmp;
}
return cmp;
}
public bool Equals(int[] other)
{
if (other.Length != this.data.Length) return false;
for (int i = 0; i < data.Length; i++)
{
if (!data[i].Equals(other[i])) return false;
}
return true;
}
public override string ToString()
{
return string.Join('.', data);
}
}
public class Asn1Value
{
public Asn1Type asnType;
public int offset;
public int length;
}
public class IndexedASNData
{
public List<KeyValuePair<Asn1Index, Asn1Value>> AsnDataPairs = new List<KeyValuePair<Asn1Index, Asn1Value>>();
public IndexedASNData(byte[] input, int offset)
{
X509Cert cert = new X509Cert();
int ofs = offset;
List<int> indexes = new List<int>();
Stack<int> endstack = new Stack<int>();
int curridx = 0;
int datend = input.Length;
int currdatend = datend;
while (ofs < datend)
{
Asn1Info ai = DerDecoder.GetAsn1Data(input, ref ofs);
if (ai.tag.tagClass == AsnTagClass.ContextSpecific)
{
ai = DerDecoder.GetAsn1Data(input, ref ofs);
}
if (ai.type == Asn1Type.SEQUENCE || ai.type == Asn1Type.SET)
{
int[] indints = new int[indexes.Count + 1];
indexes.CopyTo(indints);
indints[indints.Length - 1] = curridx;
Asn1Index aind = new Asn1Index(indints);
Asn1Value av = new Asn1Value()
{
asnType = ai.type,
length = ai.length,
offset = ofs
};
AsnDataPairs.Add(new KeyValuePair<Asn1Index, Asn1Value>(aind, av));
indexes.Add(curridx);
endstack.Push(currdatend);
curridx = 0;
currdatend = ofs + ai.length;
}
else
{
int[] indints = new int[indexes.Count + 1];
indexes.CopyTo(indints);
indints[indints.Length - 1] = curridx;
Asn1Index aind = new Asn1Index(indints);
Asn1Value av = new Asn1Value()
{
asnType = ai.type,
length = ai.length,
offset = ofs
};
ofs += ai.length;
AsnDataPairs.Add(new KeyValuePair<Asn1Index, Asn1Value>(aind, av));
while (ofs >= currdatend && endstack.Count > 0)
{
currdatend = endstack.Pop();
curridx = indexes[indexes.Count - 1];
indexes.RemoveAt(indexes.Count - 1);
}
curridx++;
}
}
}
public bool Lookup(out Asn1Value av, params int[] Index)
{
for(int i = 0; i < AsnDataPairs.Count; i++)
{
if(AsnDataPairs[i].Key.Equals(Index))
{
av = AsnDataPairs[i].Value;
return true;
}
}
av = null;
return false;
}
}
public class RDNSequence
{
public string country = "";
public string organization = "";
public string commonname = "";
public RDNSequence(byte[] data, int offset, int length)
{
Asn1Info ai;
while (true)
{
ai = DerDecoder.GetAsn1Data(data, ref offset);
if (ai.type != Asn1Type.SET) break;
ai = DerDecoder.GetAsn1Data(data, ref offset);
if (ai.type != Asn1Type.SEQUENCE) break;
ai = DerDecoder.GetAsn1Data(data, ref offset);
if (ai.type != Asn1Type.OBJECT_IDENTIFIER) break;
string oid = Asn1Tools.Poid(data, ai.contentOffset, ai.length);
offset += ai.length;
ai = DerDecoder.GetAsn1Data(data, ref offset);
if (ai.type != Asn1Type.PrintableString && ai.type != Asn1Type.UTF8String) break;
string strval = Asn1Tools.Strc(data, ai.contentOffset, ai.length);
offset += ai.length;
if (oid == "2.5.4.6") country = strval;
else if (oid == "2.5.4.10") organization = strval;
else if (oid == "2.5.4.3") commonname = strval;
}
}
public override string ToString()
{
return organization + " " + commonname + " " + country;
}
}
public class Asn1Tools
{
public static int Btoi(byte[] dat, int offset, int length)
{
int val = 0;
for (int i = offset; i < (offset + length); i++)
{
val <<= 8;
val |= dat[i];
}
return val;
}
public static byte[] Cpy(byte[] dat, int offset, int length)
{
byte[] copy = new byte[length];
Buffer.BlockCopy(dat, offset, copy, 0, length);
return copy;
}
public static string Poid(byte[] data, int offset, int length)
{
List<int> idelements = new List<int>();
int endindex = offset + length;
byte first = data[offset++];
idelements.Add(first / 40);
idelements.Add(first % 40);
int currentvalue = 0;
while (offset < endindex)
{
byte oide = data[offset++];
currentvalue <<= 7;
currentvalue |= (oide & 127);
if (oide >> 7 == 0)
{
idelements.Add(currentvalue);
currentvalue = 0;
}
}
return string.Join('.', idelements);
}
public static string Strc(byte[] data, int offset, int length)
{
return Encoding.ASCII.GetString(data, offset, length);
}
public static DateTime Utc(byte[] data, int offset)
{
DateTime dt = new DateTime();
int dp = offset;
int yy = ((data[dp + 0] - 48) * 10) + (data[dp + 1] - 48);
int mm = ((data[dp + 2] - 48) * 10) + (data[dp + 3] - 48);
int dd = ((data[dp + 4] - 48) * 10) + (data[dp + 5] - 48);
int thh = ((data[dp + 6] - 48) * 10) + (data[dp + 7] - 48);
int tmm = ((data[dp + 8] - 48) * 10) + (data[dp + 9] - 48);
int tss = ((data[dp + 10] - 48) * 10) + (data[dp + 11] - 48);
int year = 2000 + yy;
return new DateTime(year, mm, dd, thh, tmm, tss);
}
public static DateTime Generalized(byte[] data, int offset)
{
return DateTime.Now;
}
public static string OctStr(byte[] data, int offset, int length)
{
int dp = offset;
int de = offset + length;
StringBuilder sb = new StringBuilder();
while (dp < de)
{
Asn1Info ai = DerDecoder.GetAsn1Data(data, ref dp);
if (ai.type == Asn1Type.SEQUENCE)
{
continue;
}
else if(ai.type == Asn1Type.OCTET_STRING || (ai.type == Asn1Type.EndOfContent && ai.tag.tagClass == AsnTagClass.ContextSpecific))
{
byte[] hexdat = new byte[ai.length];
Buffer.BlockCopy(data, ai.contentOffset, hexdat, 0, ai.length);
sb.Append(ToHexString(hexdat));
dp += ai.length;
}
else if (ai.type == Asn1Type.INTEGER)
{
string strdat = Encoding.ASCII.GetString(data, ai.contentOffset, ai.length);
dp += ai.length;
sb.AppendLine(strdat);
}
else
{
break;
}
}
return sb.ToString();
}
public static string ToHexString(byte[] input)
{
char[] outchars = new char[input.Length * 2];
int outp = 0;
for (int i = 0; i < input.Length; i++)
{
outchars[outp++] = ToHexChar(input[i] / 16);
outchars[outp++] = ToHexChar(input[i] % 16);
}
return new string(outchars);
}
private static char ToHexChar(int input)
{
if (input < 0 || input > 15) throw new Exception("Hex conversion error");
if (input < 10)
{
return (char)(48 + input);
}
else
{
return (char)(97 + (input - 10));
}
}
public static bool HashesEqual(byte[] hasha, byte[] hashb)
{
if (hasha == null && hashb != null) return false;
if (hasha != null && hashb == null) return false;
if (hasha.Length != hashb.Length) return false;
for (int i = 0; i < hasha.Length; i++)
{
if (hasha[i] != hashb[i]) return false;
}
return true;
}
public static byte[] ParseHash(byte[] decrypted)
{
int idx = 0;
Asn1Info ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
if (ai.type != Asn1Type.SEQUENCE) return null;
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
if (ai.type != Asn1Type.SEQUENCE) return null;
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
if (ai.type != Asn1Type.OBJECT_IDENTIFIER) return null;
string oid = Asn1Tools.Poid(decrypted, ai.contentOffset, ai.length);
idx += ai.length;
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
if (ai.type != Asn1Type.NULL) return null;
ai = DerDecoder.GetAsn1Data(decrypted, ref idx);
if (ai.type != Asn1Type.OCTET_STRING) return null;
byte[] hash = Asn1Tools.Cpy(decrypted, ai.contentOffset, ai.length);
return hash;
}
}
}