Initial commit

This commit is contained in:
Trevor Hall 2026-03-03 23:53:04 -05:00
commit 5584446828
37 changed files with 7962 additions and 0 deletions

520
Security/Asn1.cs Normal file
View file

@ -0,0 +1,520 @@
/*
* 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;
}
}
}

109
Security/RSAPublicKey.cs Normal file
View file

@ -0,0 +1,109 @@
/*
* 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.Numerics;
namespace SecuCore.Security
{
public class RSAPublicKey
{
public int keysize;
public byte[] keydat;
public byte[] modulusbytes;
public byte[] exponentbytes;
public RSAPublicKey(byte[] dat, int offset = 0)
{
keydat = dat;
int dp = offset;
Asn1Info ai = DerDecoder.GetAsn1Data(dat, ref dp);
if (ai.type != Asn1Type.SEQUENCE) throw new AsnException("Malformed RSA key");
ai = DerDecoder.GetAsn1Data(dat, ref dp);
if (ai.type != Asn1Type.INTEGER) throw new AsnException("Malformed RSA key");
modulusbytes = new byte[ai.length - 1];
Buffer.BlockCopy(dat, ai.contentOffset + 1, modulusbytes, 0, ai.length - 1);
keysize = modulusbytes.Length;
dp += ai.length;
ai = DerDecoder.GetAsn1Data(dat, ref dp);
if (ai.type != Asn1Type.INTEGER) throw new AsnException("Malformed RSA key");
exponentbytes = new byte[ai.length];
Buffer.BlockCopy(dat, ai.contentOffset, exponentbytes, 0, ai.length);
}
public byte[] EncryptData(byte[] data)
{
SecRNG sr = new SecRNG();
BigInteger modulus = new BigInteger(modulusbytes, true, true);
BigInteger exponent = new BigInteger(exponentbytes, true, true);
byte[] block = new byte[keysize];
byte[] brng = new byte[keysize];
int bp = 0;
while (bp < keysize)
{
sr.GetRandomBytes(brng, 0, brng.Length);
for(int i = 0; i < brng.Length; i++)
{
if (brng[i] > 2) block[bp++] = brng[i];
if (bp >= keysize) break;
}
}
block[0] = 0x00;
block[1] = 0x02;
block[block.Length - data.Length - 1] = 0x00;
Buffer.BlockCopy(data, 0, block, block.Length - data.Length, data.Length);
BigInteger B = new BigInteger(block, true, true);
BigInteger D = ModPow(B, exponent, modulus);
byte[] bigendian = D.ToByteArray(true, true);
return bigendian;
}
public byte[] DecryptSignature(byte[] signature)
{
BigInteger modulus = new BigInteger(modulusbytes, true, true);
BigInteger exponent = new BigInteger(exponentbytes, true, true);
BigInteger B = new BigInteger(signature, true, true);
BigInteger D = ModPow(B, exponent, modulus);
byte[] bigendian = D.ToByteArray(false, true);
int start = 0;
for(int i = 0; i < bigendian.Length; i++)
{
if(bigendian[i] == 0)
{
start = i + 1;
break;
}
}
byte[] cropped = new byte[bigendian.Length - start];
Buffer.BlockCopy(bigendian, start, cropped, 0, bigendian.Length - start);
return cropped;
}
private static BigInteger ModPow(BigInteger basenum, BigInteger exponent, BigInteger modulus)
{
if (modulus == 1) return 0;
BigInteger curPow = basenum % modulus;
BigInteger res = 1;
while (exponent > 0)
{
if ((exponent & 0x01) == 1) res = (res * curPow) % modulus;
exponent >>= 1;
curPow = (curPow * curPow) % modulus;
}
return res;
}
}
}

77
Security/SecRNG.cs Normal file
View file

@ -0,0 +1,77 @@
/*
* 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;
namespace SecuCore.Security
{
class SecRNG
{
private RStep stepper;
public SecRNG()
{
uint seed = BitConverter.ToUInt32(Guid.NewGuid().ToByteArray());
stepper = new RStep(seed);
}
public void GetRandomBytes(byte[] target, int offset, int length)
{
int ceil = offset + length - 1;
int i = offset;
while(i <= ceil)
{
uint nu = stepper.Next32();
if (i > ceil) break;
target[i++] = (byte)((nu >> 24) & 0xff);
if (i > ceil) break;
target[i++] = (byte)((nu >> 16) & 0xff);
if (i > ceil) break;
target[i++] = (byte)((nu >> 8) & 0xff);
if (i > ceil) break;
target[i++] = (byte)((nu) & 0xff);
}
}
}
public struct RStep
{
const uint iY = 842502087, iZ = 3579807591, iW = 273326509;
uint x;
uint y;
uint z;
uint w;
uint next;
public RStep(uint seed)
{
x = seed;
y = iY;
z = iZ;
w = iW;
next = 0;
Step();
}
public void Step()
{
uint t = (x ^ (x << 11));
x = y;
y = z;
z = w;
next = (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)));
}
public uint Next32()
{
Step();
return next;
}
}
}

53
Security/TrustedCA.cs Normal file
View file

@ -0,0 +1,53 @@
/*
* 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.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SecuCore.Security
{
class TrustedCA
{
public static Dictionary<string, X509Cert> TrustedCerts;
static TrustedCA()
{
TrustedCerts = new Dictionary<string, X509Cert>();
//GlobalSign
LoadForSubjectKeyIdent("607b661a450d97ca89502f7d04cd34a8fffcfd4b", "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==");
//DigiCert
LoadForSubjectKeyIdent("b13ec36903f8bf4701d498261a0802ef63642bc3", "MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCevEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K");
//UserTrust
LoadForSubjectKeyIdent("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");
}
private static void LoadForSubjectKeyIdent(string ski, string base64)
{
byte[] b64data = Convert.FromBase64String(base64);
X509Cert trusted = X509Cert.FromASN(b64data, 0);
TrustedCerts.Add(ski.ToLower(), trusted);
}
public static X509Cert GetTrustedCertificate(string subjectKeyIndentifier)
{
subjectKeyIndentifier = subjectKeyIndentifier.ToLower();
if (TrustedCerts.TryGetValue(subjectKeyIndentifier, out X509Cert trusted))
{
return trusted;
}
return null;
}
}
}

182
Security/X509Cert.cs Normal file
View file

@ -0,0 +1,182 @@
/*
* 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;
namespace SecuCore.Security
{
public class CertificateException : Exception
{
public CertificateException(string s = "") : base("Error parsing Certificate: " + s)
{
}
}
public class X509Cert
{
public int version;
public byte[] sourceData;
public byte[] serialNumber;
public string sigOid;
public string encOid;
public RDNSequence issuer;
public RDNSequence subject;
public DateTime notBefore;
public DateTime notAfter;
public byte[] publicKey;
public string altNames;
public string subjectKeyIdentifier;
public string authorityKeyIdentifier;
public string algorithmIdentifier;
public byte[] certificateHash;
public byte[] signature;
private RSAPublicKey rpk;
public void Dispose()
{
serialNumber = null;
publicKey = null;
certificateHash = null;
signature = null;
issuer = null;
subject = null;
rpk = null;
}
public RSAPublicKey GetRSAPublicKey()
{
if(rpk == null) rpk = new RSAPublicKey(publicKey, 0);
return rpk;
}
public static X509Cert FromASN(byte[] asnData, int offset)
{
X509Cert cert = new X509Cert();
cert.sourceData = asnData;
IndexedASNData iad = new IndexedASNData(asnData, offset);
if (!iad.Lookup(out Asn1Value av, 0, 0, 0)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.INTEGER) throw new AsnException("Malformed asn data");
cert.version = Asn1Tools.Btoi(asnData, av.offset, av.length);
if (!iad.Lookup(out av, 0, 0, 1)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.INTEGER) throw new AsnException("Malformed asn data");
cert.serialNumber = Asn1Tools.Cpy(asnData, av.offset, av.length);
if (!iad.Lookup(out av, 0, 0, 2, 0)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.OBJECT_IDENTIFIER) throw new AsnException("Malformed asn data");
cert.sigOid = Asn1Tools.Poid(asnData, av.offset, av.length);
if (!iad.Lookup(out av, 0, 0, 3)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.SEQUENCE) throw new AsnException("Malformed asn data");
cert.issuer = new RDNSequence(asnData, av.offset, av.length);
if (!iad.Lookup(out av, 0, 0, 4, 0)) throw new AsnException("Malformed asn data");
if(av.asnType == Asn1Type.GeneralizedTime)
{
cert.notBefore = Asn1Tools.Generalized(asnData, av.offset);
}
else if (av.asnType == Asn1Type.UTCTime)
{
cert.notBefore = Asn1Tools.Utc(asnData, av.offset);
}
else
{
throw new AsnException("Malformed asn data");
}
if (!iad.Lookup(out av, 0, 0, 4, 1)) throw new AsnException("Malformed asn data");
if (av.asnType == Asn1Type.GeneralizedTime)
{
cert.notAfter = Asn1Tools.Generalized(asnData, av.offset);
}
else if (av.asnType == Asn1Type.UTCTime)
{
cert.notAfter = Asn1Tools.Utc(asnData, av.offset);
}
else
{
throw new AsnException("Malformed asn data");
}
if (!iad.Lookup(out av, 0, 0, 5)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.SEQUENCE) throw new AsnException("Malformed asn data");
cert.subject = new RDNSequence(asnData, av.offset, av.length);
if (!iad.Lookup(out av, 0, 0, 6, 0, 0)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.OBJECT_IDENTIFIER) throw new AsnException("Malformed asn data");
cert.encOid = Asn1Tools.Poid(asnData, av.offset, av.length);
if (!iad.Lookup(out av, 0, 0, 6, 1)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.BIT_STRING) throw new AsnException("Malformed asn data");
cert.publicKey = Asn1Tools.Cpy(asnData, av.offset + 1, av.length - 1);
if (!iad.Lookup(out av, 0, 1, 0)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.OBJECT_IDENTIFIER) throw new AsnException("Malformed asn data");
cert.algorithmIdentifier = Asn1Tools.Poid(asnData, av.offset, av.length);
if(cert.algorithmIdentifier == "1.2.840.113549.1.1.11")
{
if (!iad.Lookup(out Asn1Value certOnly, 0, 0)) throw new AsnException("Malformed asn data");
ReadOnlySpan<byte> certspan = new ReadOnlySpan<byte>(asnData, certOnly.offset - 4, certOnly.length + 4);
cert.certificateHash = System.Security.Cryptography.SHA256.HashData(certspan);
}
if(cert.algorithmIdentifier == "1.2.840.113549.1.1.12")
{
if (!iad.Lookup(out Asn1Value certOnly, 0, 0)) throw new AsnException("Malformed asn data");
ReadOnlySpan<byte> certspan = new ReadOnlySpan<byte>(asnData, certOnly.offset - 4, certOnly.length + 4);
cert.certificateHash = System.Security.Cryptography.SHA384.HashData(certspan);
}
if(cert.algorithmIdentifier == "1.2.840.113549.1.1.5")
{
if (!iad.Lookup(out Asn1Value certOnly, 0, 0)) throw new AsnException("Malformed asn data");
ReadOnlySpan<byte> certspan = new ReadOnlySpan<byte>(asnData, certOnly.offset - 4, certOnly.length + 4);
cert.certificateHash = System.Security.Cryptography.SHA1.HashData(certspan);
}
if (!iad.Lookup(out av, 0, 2)) throw new AsnException("Malformed asn data");
if (av.asnType != Asn1Type.BIT_STRING) throw new AsnException("Malformed asn data");
cert.signature = Asn1Tools.Cpy(asnData, av.offset, av.length);
//find optional data
for (int i = 0; i < iad.AsnDataPairs.Count; i++)
{
KeyValuePair<Asn1Index, Asn1Value> pair = iad.AsnDataPairs[i];
if(pair.Value.asnType == Asn1Type.OBJECT_IDENTIFIER)
{
string oidstr = Asn1Tools.Poid(asnData, pair.Value.offset, pair.Value.length);
if (oidstr == "2.5.29.17") // subjectaltname
{
i++;
Asn1Value altnamesoct = iad.AsnDataPairs[i].Value;
cert.altNames = Asn1Tools.OctStr(asnData, altnamesoct.offset, altnamesoct.length);
}
else if (oidstr == "2.5.29.14") //subjectKeyIdentifier
{
i++;
Asn1Value subkeyid = iad.AsnDataPairs[i].Value;
cert.subjectKeyIdentifier = Asn1Tools.OctStr(asnData, subkeyid.offset, subkeyid.length);
}
else if (oidstr == "2.5.29.35") //authorityKeyIdentifier
{
i++;
Asn1Value authkeyid = iad.AsnDataPairs[i].Value;
cert.authorityKeyIdentifier = Asn1Tools.OctStr(asnData, authkeyid.offset, authkeyid.length);
}
}
}
return cert;
}
}
}

161
Security/X509CertChain.cs Normal file
View file

@ -0,0 +1,161 @@
/*
* 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;
namespace SecuCore.Security
{
public class X509CertChain
{
public List<X509Cert> certs;
public X509CertChain()
{
certs = new List<X509Cert>();
}
public void AddLink(X509Cert cert)
{
certs.Add(cert);
}
public bool Verify()
{
if (certs.Count <= 1) return true;
for(int i = 0; i < certs.Count; i++)
{
X509Cert certa = certs[i];
X509Cert certb = null;
if(i == certs.Count - 1)
{
if (HasTrustedRoot(certa, out certb)) {
if(certb == null)
{
//self signed root was already trusted
return true;
}
}
else
{
return false;
}
}
else
{
certb = certs[i + 1];
}
RSAPublicKey rpkb = certb.GetRSAPublicKey();
byte[] decrypted = rpkb.DecryptSignature(certa.signature);
byte[] actualhash = Asn1Tools.ParseHash(decrypted);
if(Asn1Tools.HashesEqual(certa.certificateHash, actualhash))
{
//passed
}
else
{
return false;
}
}
return true;
}
private bool HasTrustedRoot(X509Cert cert, out X509Cert root)
{
root = null;
string certificateIssuer = cert.issuer.ToString();
string certificateSubject = cert.subject.ToString();
if (certificateIssuer != certificateSubject)
{
if (!string.IsNullOrEmpty(cert.authorityKeyIdentifier))
{
X509Cert trusted = TrustedCA.GetTrustedCertificate(cert.authorityKeyIdentifier);
if (trusted != null)
{
root = trusted;
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
if (!string.IsNullOrEmpty(cert.subjectKeyIdentifier))
{
X509Cert trusted = TrustedCA.GetTrustedCertificate(cert.subjectKeyIdentifier);
if (trusted != null)
{
//we trust this
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
}
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)(65 + (input - 10));
}
}
public void Dispose()
{
if(certs is null)
{
//do nothing
}
else
{
certs.Clear();
certs = null;
}
}
}
}