Initial commit
This commit is contained in:
commit
5584446828
37 changed files with 7962 additions and 0 deletions
520
Security/Asn1.cs
Normal file
520
Security/Asn1.cs
Normal 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
109
Security/RSAPublicKey.cs
Normal 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
77
Security/SecRNG.cs
Normal 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
53
Security/TrustedCA.cs
Normal 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
182
Security/X509Cert.cs
Normal 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
161
Security/X509CertChain.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue