diff options
author | Sebastien Pouliot <sebastien@ximian.com> | 2003-03-16 03:41:50 +0300 |
---|---|---|
committer | Sebastien Pouliot <sebastien@ximian.com> | 2003-03-16 03:41:50 +0300 |
commit | 8e6623df7b256c204111a8566c68f3bd9cb70ac3 (patch) | |
tree | 7510feef44ab6556b4fe66b97be6008fffe01b0a /mcs/class/Mono.Security/Mono.Security.X509 | |
parent | a4a6e2334b06db7834e78871b6f961beb57923dd (diff) |
2003-03-15 Sebastien Pouliot <spouliot@videotron.ca>
* X501Name.cs: New. X.501 Distinguished Names stuff
* X509Builder.cs: New. Abstract class for building X.509 related
structures (like certificates and CRLs).
* X509Certificate.cs: New. A more complete class to handle X.509
certificates.
* X509CertificateBuilder.cs: New. Class to build X.509 certificates.
* X509Extension.cs: New. A base class for all X.509 extensions.
* X509Extensions.cs: New. X509Extension collection.
* X520Attributes.cs: New. X.520 attributes (mainly for X501 names)
svn path=/trunk/mcs/; revision=12574
Diffstat (limited to 'mcs/class/Mono.Security/Mono.Security.X509')
8 files changed, 1413 insertions, 0 deletions
diff --git a/mcs/class/Mono.Security/Mono.Security.X509/ChangeLog b/mcs/class/Mono.Security/Mono.Security.X509/ChangeLog new file mode 100644 index 00000000000..c8f9e2bae70 --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/ChangeLog @@ -0,0 +1,11 @@ +2003-03-15 Sebastien Pouliot <spouliot@videotron.ca> + + * X501Name.cs: New. X.501 Distinguished Names stuff + * X509Builder.cs: New. Abstract class for building X.509 related + structures (like certificates and CRLs). + * X509Certificate.cs: New. A more complete class to handle X.509 + certificates. + * X509CertificateBuilder.cs: New. Class to build X.509 certificates. + * X509Extension.cs: New. A base class for all X.509 extensions. + * X509Extensions.cs: New. X509Extension collection. + * X520Attributes.cs: New. X.520 attributes (mainly for X501 names)
\ No newline at end of file diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X501Name.cs b/mcs/class/Mono.Security/Mono.Security.X509/X501Name.cs new file mode 100644 index 00000000000..7fbbf213fac --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X501Name.cs @@ -0,0 +1,176 @@ +// +// X501Name.cs: X.501 Distinguished Names stuff +// +// Author: +// Sebastien Pouliot (spouliot@motus.com) +// +// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com) +// + +using System; +using System.Text; + +using Mono.Security; + +namespace Mono.Security.X509 { + + // References: + // 1. Information technology - Open Systems Interconnection - The Directory: Models + // http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I + // 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names + // http://www.ietf.org/rfc/rfc2253.txt + + /* + * Name ::= CHOICE { RDNSequence } + * + * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + * + * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue + */ + public class X501 { + + static byte[] countryName = { 0x55, 0x04, 0x06 }; + static byte[] organizationName = { 0x55, 0x04, 0x0A }; + static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B }; + static byte[] commonName = { 0x55, 0x04, 0x03 }; + static byte[] localityName = { 0x55, 0x04, 0x07 }; + static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 }; + static byte[] streetAddress = { 0x55, 0x04, 0x09 }; + static byte[] serialNumber = { 0x55, 0x04, 0x05 }; + static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 }; + static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 }; + static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 }; + + static public string ToString (ASN1 seq) + { + StringBuilder sb = new StringBuilder (); + for (int i = 0; i < seq.Count; i++) { + ASN1 entry = seq [i]; + ASN1 pair = entry [0]; + + ASN1 s = pair [1]; + if (s == null) + continue; + + ASN1 poid = pair [0]; + if (poid == null) + continue; + + if (poid.CompareValue (countryName)) + sb.Append ("C="); + else if (poid.CompareValue (organizationName)) + sb.Append ("O="); + else if (poid.CompareValue (organizationalUnitName)) + sb.Append ("OU="); + else if (poid.CompareValue (commonName)) + sb.Append ("CN="); + else if (poid.CompareValue (localityName)) + sb.Append ("L="); + else if (poid.CompareValue (stateOrProvinceName)) + sb.Append ("S="); // NOTE: RFC2253 uses ST= + else if (poid.CompareValue (streetAddress)) + sb.Append ("STREET="); + else if (poid.CompareValue (domainComponent)) + sb.Append ("DC="); + else if (poid.CompareValue (userid)) + sb.Append ("UID="); + else if (poid.CompareValue (email)) + sb.Append ("E="); // NOTE: Not part of RFC2253 + else { + // unknown OID + sb.Append ("OID."); // NOTE: Not present as RFC2253 + sb.Append (ASN1Convert.ToOID (poid)); + sb.Append ("="); + } + + string sValue = null; + // 16bits or 8bits string ? TODO not complete (+special chars!) + if (s.Tag == 0x1E) { + // BMPSTRING + StringBuilder sb2 = new StringBuilder (); + for (int j = 1; j < s.Value.Length; j+=2) + sb2.Append ((char) s.Value[j]); + sValue = sb2.ToString (); + } + else { + sValue = System.Text.Encoding.UTF8.GetString (s.Value); + // in some cases we must quote (") the value + // Note: this doesn't seems to conform to RFC2253 + char[] specials = { ',', '+', '"', '\\', '<', '>', ';' }; + if (sValue.IndexOfAny(specials, 0, sValue.Length) > 0) + sValue = "\"" + sValue + "\""; + else if (sValue.StartsWith (" ")) + sValue = "\"" + sValue + "\""; + else if (sValue.EndsWith (" ")) + sValue = "\"" + sValue + "\""; + } + + sb.Append (sValue); + + // separator (not on last iteration) + if (i < seq.Count - 1) + sb.Append (", "); + } + return sb.ToString (); + } + + static private X520.AttributeTypeAndValue GetAttributeFromOID (string attributeType) + { + switch (attributeType.ToUpper ().Trim ()) { + case "C": + return new X520.CountryName (); + case "O": + return new X520.OrganizationName (); + case "OU": + return new X520.OrganizationalUnitName (); + case "CN": + return new X520.CommonName (); + case "L": + return new X520.LocalityName (); + case "S": // Microsoft + case "ST": // RFC2253 + return new X520.StateOrProvinceName (); + case "DC": +// return streetAddress; + case "UID": +// return domainComponent; + default: + return null; + } + } + + static public ASN1 FromString (string rdn) + { + if (rdn == null) + throw new ArgumentNullException ("rdn"); + // get string from here to ',' or end of string + int start = 0; + int end = 0; + ASN1 asn1 = new ASN1 (0x30); + while (start < rdn.Length) { + end = rdn.IndexOf (',', end) + 1; + if (end == 0) + end = rdn.Length + 1; + string av = rdn.Substring (start, end - start - 1); + // get '=' position in substring + int equal = av.IndexOf ('='); + // get AttributeType + string attributeType = av.Substring (0, equal); + // get value + string attributeValue = av.Substring (equal + 1); + + X520.AttributeTypeAndValue atv = GetAttributeFromOID (attributeType); + atv.Value = attributeValue; + asn1.Add (new ASN1 (0x31, atv.GetBytes ())); + + // next part + start = end; + if (start != - 1) { + if (end > rdn.Length) + break; + } + } + return asn1; + } + } +} diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X509Builder.cs b/mcs/class/Mono.Security/Mono.Security.X509/X509Builder.cs new file mode 100644 index 00000000000..591fe67fa64 --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X509Builder.cs @@ -0,0 +1,122 @@ +using System; +using System.Security.Cryptography; + +using Mono.Security; + +namespace Mono.Security.X509 { + + public abstract class X509Builder { + + private const string defaultHash = "SHA1"; + private string hashName; + + public X509Builder () + { + hashName = defaultHash; + } + + protected abstract ASN1 ToBeSigned (string hashName); + + // move to PKCS1 + protected string GetOID (string hashName) + { + switch (hashName.ToLower ()) { + case "md2": + // md2withRSAEncryption (1 2 840 113549 1 1 2) + return "1.2.840.113549.1.1.2"; + case "md4": + // md4withRSAEncryption (1 2 840 113549 1 1 3) + return "1.2.840.113549.1.1.3"; + case "md5": + // md5withRSAEncryption (1 2 840 113549 1 1 4) + return "1.2.840.113549.1.1.4"; + case "sha1": + // sha1withRSAEncryption (1 2 840 113549 1 1 5) + return "1.2.840.113549.1.1.5"; + case "sha256": + // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } + return "1.2.840.113549.1.1.11"; + case "sha384": + // sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } + return "1.2.840.113549.1.1.12"; + case "sha512": + // sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } + return "1.2.840.113549.1.1.13"; + default: + throw new NotSupportedException ("Unknown hash algorithm " + hashName); + } + } + + public string Hash { + get { return hashName; } + set { + if (hashName == null) + hashName = defaultHash; + else + hashName = value; + } + } + + public virtual byte[] Sign (AsymmetricAlgorithm aa) + { + if (aa is RSA) + return Sign (aa as RSA); + else if (aa is DSA) + return Sign (aa as DSA); + else + throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString()); + } + + private byte[] Build (ASN1 tbs, string hashoid, byte[] signature) + { + ASN1 builder = new ASN1 (0x30); + builder.Add (tbs); + builder.Add (PKCS7.AlgorithmIdentifier (hashoid)); + // first byte of BITSTRING is the number of unused bits in the first byte + byte[] bitstring = new byte [signature.Length + 1]; + Array.Copy (signature, 0, bitstring, 1, signature.Length); + builder.Add (new ASN1 (0x03, bitstring)); + return builder.GetBytes (); + } + + public virtual byte[] Sign (RSA key) + { + string oid = GetOID (hashName); + ASN1 tbs = ToBeSigned (oid); + HashAlgorithm ha = HashAlgorithm.Create (hashName); + byte[] hash = ha.ComputeHash (tbs.GetBytes ()); + + RSAPKCS1SignatureFormatter pkcs1 = new RSAPKCS1SignatureFormatter (key); + pkcs1.SetHashAlgorithm (hashName); + byte[] signature = pkcs1.CreateSignature (hash); + + return Build (tbs, oid, signature); + } + + public virtual byte[] Sign (DSA key) + { + string oid = "1.2.840.10040.4.3"; + ASN1 tbs = ToBeSigned (oid); + HashAlgorithm ha = HashAlgorithm.Create (hashName); + if (!(ha is SHA1)) + throw new NotSupportedException ("Only SHA-1 is supported for DSA"); + byte[] hash = ha.ComputeHash (tbs.GetBytes ()); + + DSASignatureFormatter dsa = new DSASignatureFormatter (key); + dsa.SetHashAlgorithm (hashName); + byte[] rs = dsa.CreateSignature (hash); + + // split R and S + byte[] r = new byte [20]; + Array.Copy (rs, 0, r, 0, 20); + byte[] s = new byte [20]; + Array.Copy (rs, 20, s, 0, 20); + ASN1 signature = new ASN1 (0x30); + signature.Add (new ASN1 (0x02, r)); + signature.Add (new ASN1 (0x02, s)); + + // dsaWithSha1 (1 2 840 10040 4 3) + return Build (tbs, oid, signature.GetBytes ()); + } + } +} diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X509Certificate.cs b/mcs/class/Mono.Security/Mono.Security.X509/X509Certificate.cs new file mode 100644 index 00000000000..582e9cd3409 --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X509Certificate.cs @@ -0,0 +1,429 @@ +// +// X509Certificates.cs: Handles X.509 certificates. +// +// Author: +// Sebastien Pouliot (spouliot@motus.com) +// +// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com) +// + +using System; +using System.Security.Cryptography; +using SSCX = System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Mono.Security.X509 { + + // References: + // a. Internet X.509 Public Key Infrastructure Certificate and CRL Profile + // http://www.ietf.org/rfc/rfc3280.txt + // b. ITU ASN.1 standards (free download) + // http://www.itu.int/ITU-T/studygroups/com17/languages/ + + public class X509Certificate { + + private ASN1 decoder; + + private byte[] m_encodedcert; + private DateTime m_from; + private DateTime m_until; + private string m_issuername; + private string m_keyalgo; + private byte[] m_keyalgoparams; + private string m_subject; + private byte[] m_publickey; + private byte[] signature; + private string m_signaturealgo; + private byte[] m_signaturealgoparams; + + // from http://www.ietf.org/rfc/rfc2459.txt + // + //Certificate ::= SEQUENCE { + // tbsCertificate TBSCertificate, + // signatureAlgorithm AlgorithmIdentifier, + // signature BIT STRING } + // + //TBSCertificate ::= SEQUENCE { + // version [0] Version DEFAULT v1, + // serialNumber CertificateSerialNumber, + // signature AlgorithmIdentifier, + // issuer Name, + // validity Validity, + // subject Name, + // subjectPublicKeyInfo SubjectPublicKeyInfo, + // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + // -- If present, version shall be v2 or v3 + // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + // -- If present, version shall be v2 or v3 + // extensions [3] Extensions OPTIONAL + // -- If present, version shall be v3 -- } + private int version; + private byte[] serialnumber; + + private byte[] issuerUniqueID; + private byte[] subjectUniqueID; + private X509Extensions extensions; + + // that's were the real job is! + private void Parse (byte[] data) + { + string e = "Input data cannot be coded as a valid certificate."; + try { + decoder = new ASN1 (data); + // Certificate + if (decoder.Tag != 0x30) + throw new CryptographicException (e); + // Certificate / TBSCertificate + if (decoder [0].Tag != 0x30) + throw new CryptographicException (e); + + ASN1 tbsCertificate = decoder [0]; + + int tbs = 0; + // Certificate / TBSCertificate / Version + ASN1 v = decoder [0][tbs]; + version = 1; // DEFAULT v1 + if (v.Tag == 0xA0) { + // version (optional) is present only in v2+ certs + version += v.Value [0]; // zero based + tbs++; + } + + // Certificate / TBSCertificate / CertificateSerialNumber + ASN1 sn = decoder [0][tbs++]; + if (sn.Tag != 0x02) + throw new CryptographicException (e); + serialnumber = sn.Value; + Array.Reverse (serialnumber, 0, serialnumber.Length); + + // Certificate / TBSCertificate / AlgorithmIdentifier + ASN1 signatureAlgo = tbsCertificate.Element (tbs++, 0x30); + + ASN1 issuer = tbsCertificate.Element (tbs++, 0x30); + m_issuername = X501.ToString (issuer); + + ASN1 validity = tbsCertificate.Element (tbs++, 0x30); + ASN1 notBefore = validity [0]; + m_from = ASN1Convert.ToDateTime (notBefore); + ASN1 notAfter = validity [1]; + m_until = ASN1Convert.ToDateTime (notAfter); + + ASN1 subject = tbsCertificate.Element (tbs++, 0x30); + m_subject = X501.ToString (subject); + + ASN1 subjectPublicKeyInfo = tbsCertificate.Element (tbs++, 0x30); + + ASN1 algorithm = subjectPublicKeyInfo.Element (0, 0x30); + ASN1 algo = algorithm.Element (0, 0x06); + m_keyalgo = ASN1Convert.ToOID (algo); + // parameters ANY DEFINED BY algorithm OPTIONAL + // so we dont ask for a specific (Element) type and return DER + ASN1 parameters = algorithm [1]; + m_keyalgoparams = parameters.GetBytes (); + + ASN1 subjectPublicKey = subjectPublicKeyInfo.Element (1, 0x03); + // we must drop th first byte (which is the number of unused bits + // in the BITSTRING) + int n = subjectPublicKey.Length - 1; + m_publickey = new byte [n]; + Array.Copy (subjectPublicKey.Value, 1, m_publickey, 0, n); + + // signature processing + byte[] bitstring = decoder [2].Value; + // first byte contains unused bits in first byte + signature = new byte [bitstring.Length - 1]; + Array.Copy (bitstring, 1, signature, 0, signature.Length); + + algorithm = decoder [1]; + algo = algorithm.Element (0, 0x06); + m_signaturealgo = ASN1Convert.ToOID (algo); + parameters = algorithm [1]; + if (parameters != null) + m_signaturealgoparams = parameters.GetBytes (); + else + m_signaturealgoparams = null; + + // Certificate / TBSCertificate / issuerUniqueID + ASN1 issuerUID = tbsCertificate.Element (tbs, 0xA1); + if (issuerUID != null) { + tbs++; + issuerUniqueID = issuerUID.Value; + } + + // Certificate / TBSCertificate / subjectUniqueID + ASN1 subjectUID = tbsCertificate.Element (tbs, 0xA2); + if (subjectUID != null) { + tbs++; + subjectUniqueID = subjectUID.Value; + } + + // Certificate / TBSCertificate / Extensions + ASN1 extns = tbsCertificate.Element (tbs, 0xA3); + if ((extns != null) && (extns.Count == 1)) + extensions = new X509Extensions (extns [0]); + else + extensions = new X509Extensions (null); + + // keep a copy of the original data + m_encodedcert = (byte[]) data.Clone (); + } + catch { + throw new CryptographicException (e); + } + } + + // constructors + + public X509Certificate (byte[] data) + { + if (data != null) + Parse (data); + } + + private byte[] GetUnsignedBigInteger (byte[] integer) + { + if (integer [0] == 0x00) { + // this first byte is added so we're sure it's an unsigned integer + // however we can't feed it into RSAParameters or DSAParameters + int length = integer.Length - 1; + byte[] uinteger = new byte [length]; + Array.Copy (integer, 1, uinteger, 0, length); + return uinteger; + } + else + return integer; + } + + // public methods + + public DSA DSA { + get { + DSAParameters dsaParams = new DSAParameters (); + // for DSA m_publickey contains 1 ASN.1 integer - Y + ASN1 pubkey = new ASN1 (m_publickey); + if ((pubkey == null) || (pubkey.Tag != 0x02)) + return null; + dsaParams.Y = GetUnsignedBigInteger (pubkey.Value); + + ASN1 param = new ASN1 (m_keyalgoparams); + if ((param == null) || (param.Tag != 0x30) || (param.Count < 3)) + return null; + if ((param [0].Tag != 0x02) || (param [1].Tag != 0x02) || (param [2].Tag != 0x02)) + return null; + dsaParams.P = GetUnsignedBigInteger (param [0].Value); + dsaParams.Q = GetUnsignedBigInteger (param [1].Value); + dsaParams.G = GetUnsignedBigInteger (param [2].Value); + + // BUG: MS BCL 1.0 can't import a key which + // isn't the same size as the one present in + // the container. + DSACryptoServiceProvider dsa = new DSACryptoServiceProvider (dsaParams.Y.Length << 3); + dsa.ImportParameters (dsaParams); + return (DSA) dsa; + } + } + + public X509Extensions Extensions { + get { return extensions; } + } + + public byte[] Hash { + get { + HashAlgorithm hash = null; + switch (m_signaturealgo) { + case "1.2.840.113549.1.1.2": // MD2 with RSA encryption + // maybe someone installed MD2 ? + hash = HashAlgorithm.Create ("MD2"); + break; + case "1.2.840.113549.1.1.4": // MD5 with RSA encryption + hash = MD5.Create (); + break; + case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption + case "1.2.840.10040.4.3": // SHA1-1 with DSA + hash = SHA1.Create (); + break; + default: + return null; + } + try { + byte[] toBeSigned = decoder [0].GetBytes (); + return hash.ComputeHash (toBeSigned, 0, toBeSigned.Length); + } + catch { + return null; + } + } + } + + public virtual string IssuerName { + get { return m_issuername; } + } + + public virtual string KeyAlgorithm { + get { return m_keyalgo; } + } + + public virtual byte[] KeyAlgorithmParameters { + get { return m_keyalgoparams; } + } + + public virtual byte[] PublicKey { + get { return m_publickey; } + } + + public virtual RSA RSA { + get { + RSAParameters rsaParams = new RSAParameters (); + // for RSA m_publickey contains 2 ASN.1 integers + // the modulus and the public exponent + ASN1 pubkey = new ASN1 (m_publickey); + ASN1 modulus = pubkey [0]; + if ((modulus == null) || (modulus.Tag != 0x02)) + return null; + ASN1 exponent = pubkey [1]; + if (exponent.Tag != 0x02) + return null; + + rsaParams.Modulus = GetUnsignedBigInteger (modulus.Value); + rsaParams.Exponent = exponent.Value; + + // BUG: MS BCL 1.0 can't import a key which + // isn't the same size as the one present in + // the container. + int keySize = (rsaParams.Modulus.Length << 3); + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider (keySize); + rsa.ImportParameters (rsaParams); + return (RSA)rsa; + } + } + + public virtual byte[] RawData { + get { return (byte[]) m_encodedcert.Clone (); } + } + + public virtual byte[] SerialNumber { + get { return serialnumber; } + } + + public virtual byte[] Signature { + get { + switch (m_signaturealgo) { + case "1.2.840.113549.1.1.2": // MD2 with RSA encryption + case "1.2.840.113549.1.1.4": // MD5 with RSA encryption + case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption + return signature; + case "1.2.840.10040.4.3": // SHA-1 with DSA + ASN1 sign = new ASN1 (signature); + if ((sign == null) || (sign.Count != 2)) + return null; + // parts may be less than 20 bytes (i.e. first bytes were 0x00) + byte[] part1 = sign [0].Value; + byte[] part2 = sign [1].Value; + byte[] sig = new byte [40]; + Array.Copy (part1, 0, sig, (20 - part1.Length), part1.Length); + Array.Copy (part2, 0, sig, (40 - part2.Length), part2.Length); + return sig; + default: + throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo); + } + } + } + + public virtual string SignatureAlgorithm { + get { return m_signaturealgo; } + } + + public virtual byte[] SignatureAlgorithmParameters { + get { return m_signaturealgoparams; } + } + + public virtual string SubjectName { + get { return m_subject; } + } + + public virtual DateTime ValidFrom { + get { return m_from; } + } + + public virtual DateTime ValidUntil { + get { return m_until; } + } + + public int Version { + get { return version; } + } + + public bool IsCurrent { + get { return WasCurrent (DateTime.UtcNow); } + } + + public bool WasCurrent (DateTime date) + { + return ((date > ValidFrom) && (date <= ValidUntil)); + } + + private byte[] GetHash (string hashName) + { + byte[] toBeSigned = decoder [0].GetBytes (); + HashAlgorithm ha = HashAlgorithm.Create (hashName); + return ha.ComputeHash (toBeSigned); + } + + public bool VerifySignature (DSA dsa) + { + // signatureOID is check by both this.Hash and this.Signature + DSASignatureDeformatter v = new DSASignatureDeformatter (dsa); + // only SHA-1 is supported + v.SetHashAlgorithm ("SHA1"); + return v.VerifySignature (this.Hash, this.Signature); + } + + internal bool VerifySignature (RSA rsa) + { + RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa); + switch (m_signaturealgo) { + // MD2 with RSA encryption + case "1.2.840.113549.1.1.2": + // maybe someone installed MD2 ? + v.SetHashAlgorithm ("MD2"); + break; + // MD5 with RSA encryption + case "1.2.840.113549.1.1.4": + v.SetHashAlgorithm ("MD5"); + break; + // SHA-1 with RSA Encryption + case "1.2.840.113549.1.1.5": + v.SetHashAlgorithm ("SHA1"); + break; + default: + throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo); + } + return v.VerifySignature (this.Hash, this.Signature); + } + + public bool VerifySignature (AsymmetricAlgorithm aa) + { + if (aa is RSA) + return VerifySignature (aa as RSA); + else if (aa is DSA) + return VerifySignature (aa as DSA); + else + throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ()); + } + + public bool CheckSignature (byte[] hash, string hashAlgorithm, byte[] signature) + { + RSACryptoServiceProvider r = (RSACryptoServiceProvider) RSA; + return r.VerifyHash (hash, hashAlgorithm, signature); + } + + public bool IsSelfSigned { + get { + if (m_issuername == m_subject) + return VerifySignature (RSA); + else + return false; + } + } + } +}
\ No newline at end of file diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X509CertificateBuilder.cs b/mcs/class/Mono.Security/Mono.Security.X509/X509CertificateBuilder.cs new file mode 100644 index 00000000000..e66b4e094ca --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X509CertificateBuilder.cs @@ -0,0 +1,221 @@ +// +// X509CertificateBuilder.cs: Handles building of X.509 certificates. +// +// Author: +// Sebastien Pouliot (spouliot@motus.com) +// +// (C) 2003 Motus Technologies Inc. (http://www.motus.com) + +using System; +using System.Security.Cryptography; + +namespace Mono.Security.X509 { + // From RFC3280 + /* + * Certificate ::= SEQUENCE { + * tbsCertificate TBSCertificate, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING + * } + * TBSCertificate ::= SEQUENCE { + * version [0] Version DEFAULT v1, + * serialNumber CertificateSerialNumber, + * signature AlgorithmIdentifier, + * issuer Name, + * validity Validity, + * subject Name, + * subjectPublicKeyInfo SubjectPublicKeyInfo, + * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + * -- If present, version MUST be v2 or v3 + * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + * -- If present, version MUST be v2 or v3 + * extensions [3] Extensions OPTIONAL + * -- If present, version MUST be v3 -- + * } + * Version ::= INTEGER { v1(0), v2(1), v3(2) } + * CertificateSerialNumber ::= INTEGER + * Validity ::= SEQUENCE { + * notBefore Time, + * notAfter Time + * } + * Time ::= CHOICE { + * utcTime UTCTime, + * generalTime GeneralizedTime + * } + */ + public class X509CertificateBuilder : X509Builder { + + private byte version; + private byte[] sn; + private string issuer; + private DateTime notBefore; + private DateTime notAfter; + private string subject; + private AsymmetricAlgorithm aa; + private byte[] issuerUniqueID; + private byte[] subjectUniqueID; + private X509Extensions extensions; + + public X509CertificateBuilder () : this (3) {} + + public X509CertificateBuilder (byte version) + { + if (version > 3) + throw new ArgumentException ("Invalid certificate version"); + this.version = version; + extensions = new X509Extensions (); + } + + public byte Version { + get { return version; } + set { version = value; } + } + + public byte[] SerialNumber { + get { return sn; } + set { sn = value; } + } + + public string IssuerName { + get { return issuer; } + set { issuer = value; } + } + + public DateTime NotBefore { + get { return notBefore; } + set { notBefore = value; } + } + + public DateTime NotAfter { + get { return notAfter; } + set { notAfter = value; } + } + + public string SubjectName { + get { return subject; } + set { subject = value; } + } + + public AsymmetricAlgorithm SubjectPublicKey { + get { return aa; } + set { aa = value; } + } + + public byte[] IssuerUniqueID { + get { return issuerUniqueID; } + set { issuerUniqueID = value; } + } + + public byte[] SubjectUniqueID { + get { return subjectUniqueID; } + set { subjectUniqueID = value; } + } + + public X509Extensions Extensions { + get { return extensions; } + } + + + /* SubjectPublicKeyInfo ::= SEQUENCE { + * algorithm AlgorithmIdentifier, + * subjectPublicKey BIT STRING } + */ + private ASN1 SubjectPublicKeyInfo () + { + ASN1 keyInfo = new ASN1 (0x30); + if (aa is RSA) { + keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.113549.1.1.1")); + RSAParameters p = (aa as RSA).ExportParameters (false); + /* RSAPublicKey ::= SEQUENCE { + * modulus INTEGER, -- n + * publicExponent INTEGER } -- e + */ + ASN1 key = new ASN1 (0x30); + key.Add (ASN1Convert.FromUnsignedBigInteger (p.Modulus)); + key.Add (ASN1Convert.FromUnsignedBigInteger (p.Exponent)); + keyInfo.Add (new ASN1 (UniqueIdentifier (key.GetBytes ()))); + } + else if (aa is DSA) { + DSAParameters p = (aa as DSA).ExportParameters (false); + /* Dss-Parms ::= SEQUENCE { + * p INTEGER, + * q INTEGER, + * g INTEGER } + */ + ASN1 param = new ASN1 (0x30); + param.Add (ASN1Convert.FromUnsignedBigInteger (p.P)); + param.Add (ASN1Convert.FromUnsignedBigInteger (p.Q)); + param.Add (ASN1Convert.FromUnsignedBigInteger (p.G)); + keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.10040.4.1", param)); + ASN1 key = keyInfo.Add (new ASN1 (0x03)); + // DSAPublicKey ::= INTEGER -- public key, y + key.Add (ASN1Convert.FromUnsignedBigInteger (p.Y)); + } + else + throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ()); + return keyInfo; + } + + private byte[] UniqueIdentifier (byte[] id) + { + // UniqueIdentifier ::= BIT STRING + ASN1 uid = new ASN1 (0x03); + // first byte in a BITSTRING is the number of unused bits in the first byte + byte[] v = new byte [id.Length + 1]; + Array.Copy (id, 0, v, 1, id.Length); + uid.Value = v; + return uid.GetBytes (); + } + + protected override ASN1 ToBeSigned (string oid) + { + // TBSCertificate + ASN1 tbsCert = new ASN1 (0x30); + + if (version > 1) { + // TBSCertificate / [0] Version DEFAULT v1, + byte[] ver = { (byte)(version - 1) }; + ASN1 v = tbsCert.Add (new ASN1 (0xA0)); + v.Add (new ASN1 (0x02, ver)); + } + + // TBSCertificate / CertificateSerialNumber, + tbsCert.Add (new ASN1 (0x02, sn)); + + // TBSCertificate / AlgorithmIdentifier, + tbsCert.Add (PKCS7.AlgorithmIdentifier (oid)); + + // TBSCertificate / Name + tbsCert.Add (X501.FromString (issuer)); + + // TBSCertificate / Validity + ASN1 validity = tbsCert.Add (new ASN1 (0x30)); + // TBSCertificate / Validity / Time + validity.Add (ASN1Convert.FromDateTime (notBefore)); + // TBSCertificate / Validity / Time + validity.Add (ASN1Convert.FromDateTime (notAfter)); + + // TBSCertificate / Name + tbsCert.Add (X501.FromString (subject)); + + // TBSCertificate / SubjectPublicKeyInfo + ASN1 keyInfo = tbsCert.Add (SubjectPublicKeyInfo ()); + + if (version > 1) { + // TBSCertificate / [1] IMPLICIT UniqueIdentifier OPTIONAL + if (issuerUniqueID != null) + tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (issuerUniqueID))); + + // TBSCertificate / [2] IMPLICIT UniqueIdentifier OPTIONAL + if (subjectUniqueID != null) + tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (subjectUniqueID))); + + // TBSCertificate / [3] Extensions OPTIONAL + if ((version > 2) && (extensions.Count > 0)) + tbsCert.Add (new ASN1 (0xA3, extensions.GetBytes ())); + } + + return tbsCert; + } + } +} diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X509Extension.cs b/mcs/class/Mono.Security/Mono.Security.X509/X509Extension.cs new file mode 100644 index 00000000000..353ad31beb8 --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X509Extension.cs @@ -0,0 +1,136 @@ +// +// X509Extension.cs: Base class for all X.509 extensions. +// +// Author: +// Sebastien Pouliot (spouliot@motus.com) +// +// (C) 2003 Motus Technologies Inc. (http://www.motus.com) +// + +using System; +using System.Text; + +using Mono.Security; + +namespace Mono.Security.X509 { + /* + * Extension ::= SEQUENCE { + * extnID OBJECT IDENTIFIER, + * critical BOOLEAN DEFAULT FALSE, + * extnValue OCTET STRING + * } + */ + public class X509Extension { + + protected string extnOid; + protected bool extnCritical; + protected ASN1 extnValue; + + internal X509Extension () + { + extnCritical = false; + } + + public X509Extension (ASN1 asn1) + { + if ((asn1.Tag != 0x30) || (asn1.Count < 2)) + throw new ArgumentException ("Invalid X.509 extension"); + if (asn1[0].Tag != 0x06) + throw new ArgumentException ("Invalid X.509 extension"); + extnOid = ASN1Convert.ToOID (asn1 [0]); + extnCritical = ((asn1[1].Tag == 0x01) && (asn1[1].Value[0] == 0xFF)); + extnValue = asn1 [asn1.Count - 1]; // last element + Decode (); + } + + public X509Extension (X509Extension extension) : this () + { + if (extension == null) + throw new ArgumentNullException ("extension"); + if ((extension.Value.Tag != 0x04) || (extension.Value.Count != 0)) + throw new ArgumentException ("Invalid extension"); + extnOid = extension.OID; + extnCritical = extension.Critical; + extnValue = extension.Value; + Decode (); + } + + protected virtual void Decode () {} + + protected virtual void Encode () {} + + public ASN1 ASN1 { + get { + ASN1 extension = new ASN1 (0x30); + extension.Add (ASN1Convert.FromOID (extnOid)); + if (extnCritical) + extension.Add (new ASN1 (0x01, new byte [1] { 0x01 })); + ASN1 os = extension.Add (new ASN1 (0x04)); + Encode (); + os.Add (extnValue); + return extension; + } + } + + public string OID { + get { return extnOid; } + } + + public bool Critical { + get { return extnCritical; } + } + + // this gets overrided with more meaningful names + public virtual string Name { + get { return extnOid; } + } + + public ASN1 Value { + get { return extnValue; } + } + + public byte[] GetBytes () + { + return ASN1.GetBytes (); + } + + private void WriteLine (StringBuilder sb, int n, int pos) + { + byte[] value = extnValue.Value; + int p = pos; + StringBuilder preview = new StringBuilder (); + for (int j=0; j < 8; j++) { + if (j < n) { + sb.Append (value [p++].ToString ("X2")); + sb.Append (" "); + } + else + sb.Append (" "); + } + sb.Append (" "); + p = pos; + for (int j=0; j < n; j++) { + byte b = value [p++]; + if (b < 0x20) + sb.Append ("."); + else + sb.Append (Convert.ToChar (b)); + } + sb.Append (Environment.NewLine); + } + + public override string ToString () + { + StringBuilder sb = new StringBuilder (); + int div = (extnValue.Length >> 3); + int rem = (extnValue.Length - (div << 3)); + int x = 0; + for (int i=0; i < div; i++) { + WriteLine (sb, 8, x); + x += 8; + } + WriteLine (sb, rem, x); + return sb.ToString (); + } + } +} diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X509Extensions.cs b/mcs/class/Mono.Security/Mono.Security.X509/X509Extensions.cs new file mode 100644 index 00000000000..488ceed8725 --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X509Extensions.cs @@ -0,0 +1,105 @@ +// +// X509Extensions.cs: Handles X.509 extensions. +// +// Author: +// Sebastien Pouliot (spouliot@motus.com) +// +// (C) 2003 Motus Technologies Inc. (http://www.motus.com) +// + +using System; +using System.Collections; + +using Mono.Security; + +namespace Mono.Security.X509 { + /* + * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension + * + * Note: 1..MAX -> There shouldn't be 0 Extensions in the ASN1 structure + */ + public class X509Extensions : ICollection, IEnumerable { + + private ArrayList extensions; + private bool readOnly; + + public X509Extensions () + { + extensions = new ArrayList (); + } + + public X509Extensions (ASN1 asn1) : this () + { + readOnly = true; + if (asn1 == null) + return; + if (asn1.Tag != 0x30) + throw new Exception ("Invalid extensions format"); + for (int i=0; i < asn1.Count; i++) { + X509Extension extension = new X509Extension (asn1 [i]); + extensions.Add (extension); + } + } + + // ICollection + public int Count { + get { return extensions.Count; } + } + + // ICollection + public bool IsSynchronized { + get { return extensions.IsSynchronized; } + } + + // ICollection + public object SyncRoot { + get { return extensions.SyncRoot; } + } + + // ICollection + public void CopyTo (Array array, int index) + { + extensions.CopyTo (array, index); + } + + // IEnumerable + public IEnumerator GetEnumerator () + { + return extensions.GetEnumerator (); + } + + public X509Extension this [int index] { + get { return (X509Extension) extensions [index]; } + } + + public X509Extension this [string index] { + get { + for (int i=0; i < extensions.Count; i++) { + X509Extension extension = (X509Extension) extensions [i]; + if (extension.OID == index) + return extension; + } + return null; + } + } + + public void Add (X509Extension extension) + { + if (readOnly) + throw new NotSupportedException ("Extensions are read only"); + extensions.Add (extension); + } + + public byte[] GetBytes () + { + if (extensions.Count < 1) + return null; + ASN1 sequence = new ASN1 (0x30); + for (int i=0; i < extensions.Count; i++) { + X509Extension x = (X509Extension) extensions [i]; + sequence.Add (x.ASN1); + } + return sequence.GetBytes (); + } + } +} diff --git a/mcs/class/Mono.Security/Mono.Security.X509/X520Attributes.cs b/mcs/class/Mono.Security/Mono.Security.X509/X520Attributes.cs new file mode 100644 index 00000000000..98cd3ebf1ce --- /dev/null +++ b/mcs/class/Mono.Security/Mono.Security.X509/X520Attributes.cs @@ -0,0 +1,213 @@ +// +// X520.cs: X.520 related stuff (attributes, RDN) +// +// Author: +// Sebastien Pouliot (spouliot@motus.com) +// +// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com) +// + +using System; +using System.Text; + +using Mono.Security; + +namespace Mono.Security.X509 { + + // References: + // 1. Information technology - Open Systems Interconnection - The Directory: Selected attribute types + // http://www.itu.int/rec/recommendation.asp?type=folders&lang=e&parent=T-REC-X.520 + // 2. Internet X.509 Public Key Infrastructure Certificate and CRL Profile + // http://www.ietf.org/rfc/rfc3280.txt + + /* + * AttributeTypeAndValue ::= SEQUENCE { + * type AttributeType, + * value AttributeValue + * } + * + * AttributeType ::= OBJECT IDENTIFIER + * + * AttributeValue ::= ANY DEFINED BY AttributeType + */ + public class X520 { + + public abstract class AttributeTypeAndValue { + protected string oid; + protected string attrValue; + private int upperBound; + private byte inputEncoding; + protected byte defaultEncoding; + + public AttributeTypeAndValue (string oid, int upperBound) + { + inputEncoding = 0xFF; + defaultEncoding = 0xFF; + this.oid = oid; + this.upperBound = upperBound; + } + + public string Value { + get { return attrValue; } + set { attrValue = value; } + } + + public ASN1 ASN1 { + get { return GetASN1 (); } + } + + public ASN1 GetASN1 (byte encoding) + { + ASN1 asn1 = new ASN1 (0x30); + asn1.Add (ASN1Convert.FromOID (oid)); + switch (encoding) { + case 0x13: + // PRINTABLESTRING + asn1.Add (new ASN1 (0x13, Encoding.ASCII.GetBytes (attrValue))); + break; + case 0x1E: + // BMPSTRING + asn1.Add (new ASN1 (0x1E, Encoding.BigEndianUnicode.GetBytes (attrValue))); + break; + } + return asn1; + } + + public ASN1 GetASN1 () + { + byte encoding = inputEncoding; + if (encoding == 0xFF) + encoding = defaultEncoding; + if (encoding == 0xFF) + encoding = SelectBestEncoding (); + return GetASN1 (encoding); + } + + public byte[] GetBytes (byte encoding) + { + return GetASN1 (encoding) .GetBytes (); + } + + public byte[] GetBytes () + { + return GetASN1 () .GetBytes (); + } + + private byte SelectBestEncoding () + { + char[] notPrintableString = { '@', '_' }; + if (attrValue.IndexOfAny (notPrintableString) != -1) + return 0x1E; // BMPSTRING + else + return 0x13; // PRINTABLESTRING + } + } + + public class Name : AttributeTypeAndValue { + + public Name () : base ("2.5.4.41", 32768) {} + } + + public class CommonName : AttributeTypeAndValue { + + public CommonName () : base ("2.5.4.3", 64) {} + } + + public class LocalityName : AttributeTypeAndValue { + + public LocalityName () : base ("2.5.4.7", 128) {} + } + + public class StateOrProvinceName : AttributeTypeAndValue { + + public StateOrProvinceName () : base ("2.5.4.8", 128) {} + } + + public class OrganizationName : AttributeTypeAndValue { + + public OrganizationName () : base ("2.5.4.10", 64) {} + } + + public class OrganizationalUnitName : AttributeTypeAndValue { + + public OrganizationalUnitName () : base ("2.5.4.11", 64) {} + } + + /* -- Naming attributes of type X520Title + * id-at-title AttributeType ::= { id-at 12 } + * + * X520Title ::= CHOICE { + * teletexString TeletexString (SIZE (1..ub-title)), + * printableString PrintableString (SIZE (1..ub-title)), + * universalString UniversalString (SIZE (1..ub-title)), + * utf8String UTF8String (SIZE (1..ub-title)), + * bmpString BMPString (SIZE (1..ub-title)) + * } + */ + public class Title : AttributeTypeAndValue { + + public Title () : base ("2.5.4.12", 64) {} + } + + public class CountryName : AttributeTypeAndValue { + + public CountryName () : base ("2.5.4.6", 2) + { + defaultEncoding = 0x13; // PRINTABLESTRING + } + } + } + + /* From RFC3280 + * -- specifications of Upper Bounds MUST be regarded as mandatory + * -- from Annex B of ITU-T X.411 Reference Definition of MTS Parameter + * + * -- Upper Bounds + * + * ub-name INTEGER ::= 32768 + * ub-common-name INTEGER ::= 64 + * ub-locality-name INTEGER ::= 128 + * ub-state-name INTEGER ::= 128 + * ub-organization-name INTEGER ::= 64 + * ub-organizational-unit-name INTEGER ::= 64 + * ub-title INTEGER ::= 64 + * ub-serial-number INTEGER ::= 64 + * ub-match INTEGER ::= 128 + * ub-emailaddress-length INTEGER ::= 128 + * ub-common-name-length INTEGER ::= 64 + * ub-country-name-alpha-length INTEGER ::= 2 + * ub-country-name-numeric-length INTEGER ::= 3 + * ub-domain-defined-attributes INTEGER ::= 4 + * ub-domain-defined-attribute-type-length INTEGER ::= 8 + * ub-domain-defined-attribute-value-length INTEGER ::= 128 + * ub-domain-name-length INTEGER ::= 16 + * ub-extension-attributes INTEGER ::= 256 + * ub-e163-4-number-length INTEGER ::= 15 + * ub-e163-4-sub-address-length INTEGER ::= 40 + * ub-generation-qualifier-length INTEGER ::= 3 + * ub-given-name-length INTEGER ::= 16 + * ub-initials-length INTEGER ::= 5 + * ub-integer-options INTEGER ::= 256 + * ub-numeric-user-id-length INTEGER ::= 32 + * ub-organization-name-length INTEGER ::= 64 + * ub-organizational-unit-name-length INTEGER ::= 32 + * ub-organizational-units INTEGER ::= 4 + * ub-pds-name-length INTEGER ::= 16 + * ub-pds-parameter-length INTEGER ::= 30 + * ub-pds-physical-address-lines INTEGER ::= 6 + * ub-postal-code-length INTEGER ::= 16 + * ub-pseudonym INTEGER ::= 128 + * ub-surname-length INTEGER ::= 40 + * ub-terminal-id-length INTEGER ::= 24 + * ub-unformatted-address-length INTEGER ::= 180 + * ub-x121-address-length INTEGER ::= 16 + * + * -- Note - upper bounds on string types, such as TeletexString, are + * -- measured in characters. Excepting PrintableString or IA5String, a + * -- significantly greater number of octets will be required to hold + * -- such a value. As a minimum, 16 octets, or twice the specified + * -- upper bound, whichever is the larger, should be allowed for + * -- TeletexString. For UTF8String or UniversalString at least four + * -- times the upper bound should be allowed. + */ +} |