// The MIT License // // Copyright (c) 2006-2008 DevDefined Limited. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Text; using System.Diagnostics; using System.Globalization; using System.Collections.Generic; using System.Security.Cryptography; namespace MonoRailsOAuth.Core.KeyInterop { class AsnKeyBuilder { public class AsnMessage { private byte[] m_octets; private String m_format; public int Length { get { if (null == m_octets) { return 0; } return m_octets.Length; } // set { m_length = value; } } public AsnMessage(byte[] octets, String format) { m_octets = octets; m_format = format; } public byte[] GetBytes() { if (null == m_octets) { return new byte[] { }; } return m_octets; } public String GetFormat() { return m_format; } } public class AsnType { // Constructors // No default - must specify tag and data public AsnType(byte tag, byte octet) { m_raw = false; m_tag = new byte[] { tag }; m_octets = new byte[] { octet }; } public AsnType(byte tag, byte[] octets) { m_raw = false; m_tag = new byte[] { tag }; m_octets = octets; } public AsnType(byte tag, byte[] length, byte[] octets) { m_raw = true; m_tag = new byte[] { tag }; m_length = length; m_octets = octets; } private bool m_raw; private bool Raw { get { return m_raw; } set { m_raw = value; } } // Setters and Getters private byte[] m_tag; public byte[] Tag { get { if (null == m_tag) return EMPTY; return m_tag; } // set { m_tag = value; } } private byte[] m_length; public byte[] Length { get { if (null == m_length) return EMPTY; return m_length; } // set { m_length = value; } } private byte[] m_octets; public byte[] Octets { get { if (null == m_octets) { return EMPTY; } return m_octets; } set { m_octets = value; } } // Methods public byte[] GetBytes() { // Created raw by user // return the bytes.... if (true == m_raw) { return Concatenate( new byte[][] { m_tag, m_length, m_octets } ); } SetLength(); // Special case // Null does not use length if (0x05 == m_tag[0]) { return Concatenate( new byte[][] { m_tag, m_octets } ); } return Concatenate( new byte[][] { m_tag, m_length, m_octets } ); } private void SetLength() { if (null == m_octets) { m_length = ZERO; return; } // Special case // Null does not use length if (0x05 == m_tag[0]) { m_length = EMPTY; return; } byte[] length = null; // Length: 0 <= l < 0x80 if (m_octets.Length < 0x80) { length = new byte[1]; length[0] = (byte)m_octets.Length; } // 0x80 < length <= 0xFF else if (m_octets.Length <= 0xFF) { length = new byte[2]; length[0] = 0x81; length[1] = (byte)((m_octets.Length & 0xFF)); } // // We should almost never see these... // // 0xFF < length <= 0xFFFF else if (m_octets.Length <= 0xFFFF) { length = new byte[3]; length[0] = 0x82; length[1] = (byte)((m_octets.Length & 0xFF00) >> 8); length[2] = (byte)((m_octets.Length & 0xFF)); } // 0xFFFF < length <= 0xFFFFFF else if (m_octets.Length <= 0xFFFFFF) { length = new byte[4]; length[0] = 0x83; length[1] = (byte)((m_octets.Length & 0xFF0000) >> 16); length[2] = (byte)((m_octets.Length & 0xFF00) >> 8); length[3] = (byte)((m_octets.Length & 0xFF)); } // 0xFFFFFF < length <= 0xFFFFFFFF else { length = new byte[5]; length[0] = 0x84; length[1] = (byte)((m_octets.Length & 0xFF000000) >> 24); length[2] = (byte)((m_octets.Length & 0xFF0000) >> 16); length[3] = (byte)((m_octets.Length & 0xFF00) >> 8); length[4] = (byte)((m_octets.Length & 0xFF)); } m_length = length; } private byte[] Concatenate(byte[][] values) { // Nothing in, nothing out if (IsEmpty(values)) return new byte[] { }; int length = 0; foreach (byte[] b in values) { if (null != b) length += b.Length; } byte[] cated = new byte[length]; int current = 0; foreach (byte[] b in values) { if (null != b) { Array.Copy(b, 0, cated, current, b.Length); current += b.Length; } } return cated; } }; private static byte[] ZERO = new byte[] { 0 }; private static byte[] EMPTY = new byte[] { }; // PublicKeyInfo (X.509 compatible) message /// /// Returns the AsnMessage representing the X.509 PublicKeyInfo. /// /// The DSA key to be encoded. /// Returns the AsnType representing the /// X.509 PublicKeyInfo. /// /// /// public static AsnMessage PublicKeyToX509(DSAParameters publicKey) { // Value Type cannot be null // Debug.Assert(null != publicKey); /* * * SEQUENCE // PrivateKeyInfo * +- SEQUENCE // AlgorithmIdentifier * | +- OID // 1.2.840.10040.4.1 * | +- SEQUENCE // DSS-Params (Optional Parameters) * | +- INTEGER (P) * | +- INTEGER (Q) * | +- INTEGER (G) * +- BITSTRING // PublicKey * +- INTEGER(Y) // DSAPublicKey Y * */ // DSA Parameters AsnType p = CreateIntegerPos(publicKey.P); AsnType q = CreateIntegerPos(publicKey.Q); AsnType g = CreateIntegerPos(publicKey.G); // Sequence - DSA-Params AsnType dssParams = CreateSequence(new AsnType[] { p, q, g }); // OID - packed 1.2.840.10040.4.1 // { 0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x01 } AsnType oid = CreateOid("1.2.840.10040.4.1"); // Sequence AsnType algorithmID = CreateSequence(new AsnType[] { oid, dssParams }); // Public Key Y AsnType y = CreateIntegerPos(publicKey.Y); AsnType key = CreateBitString(y); // Sequence 'A' AsnType publicKeyInfo = CreateSequence(new AsnType[] { algorithmID, key }); return new AsnMessage(publicKeyInfo.GetBytes(), "X.509"); } // PublicKeyInfo (X.509 compatible) message /// /// Returns the AsnMessage representing the X.509 PublicKeyInfo. /// /// The RSA key to be encoded. /// Returns the AsnType representing the /// X.509 PublicKeyInfo. /// /// /// public static AsnMessage PublicKeyToX509(RSAParameters publicKey) { // Value Type cannot be null // Debug.Assert(null != publicKey); /* * * SEQUENCE // PrivateKeyInfo * +- SEQUENCE // AlgorithmIdentifier * +- OID // 1.2.840.113549.1.1.1 * +- Null // Optional Parameters * +- BITSTRING // PrivateKey * +- SEQUENCE // RSAPrivateKey * +- INTEGER(N) // N * +- INTEGER(E) // E * */ // OID - packed 1.2.840.113549.1.1.1 // { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 } AsnType oid = CreateOid("1.2.840.113549.1.1.1"); AsnType algorithmID = CreateSequence(new AsnType[] { oid, CreateNull() }); AsnType n = CreateIntegerPos(publicKey.Modulus); AsnType e = CreateIntegerPos(publicKey.Exponent); AsnType key = CreateBitString( CreateSequence(new AsnType[] { n, e }) ); AsnType publicKeyInfo = CreateSequence(new AsnType[] { algorithmID, key }); return new AsnMessage(publicKeyInfo.GetBytes(), "X.509"); } // PKCS #8, Section 6 (PrivateKeyInfo) message // !!!!!!!!!!!!!!! Unencrypted !!!!!!!!!!!!!!! /// /// Returns AsnMessage representing the unencrypted /// PKCS #8 PrivateKeyInfo. /// /// The DSA key to be encoded. /// Returns the AsnType representing the unencrypted /// PKCS #8 PrivateKeyInfo. /// /// /// public static AsnMessage PrivateKeyToPKCS8(DSAParameters privateKey) { // Value Type cannot be null // Debug.Assert(null != privateKey); /* * * SEQUENCE // PrivateKeyInfo * +- INTEGER(0) // Version (v1998) * +- SEQUENCE // AlgorithmIdentifier * | +- OID // 1.2.840.10040.4.1 * | +- SEQUENCE // DSS-Params (Optional Parameters) * | +- INTEGER (P) * | +- INTEGER (Q) * | +- INTEGER (G) * +- OCTETSTRING // PrivateKey * +- INTEGER(X) // DSAPrivateKey X * */ // Version - 0 (v1998) AsnType version = CreateInteger(ZERO); // Domain Parameters AsnType p = CreateIntegerPos(privateKey.P); AsnType q = CreateIntegerPos(privateKey.Q); AsnType g = CreateIntegerPos(privateKey.G); AsnType dssParams = CreateSequence(new AsnType[] { p, q, g }); // OID - packed 1.2.840.10040.4.1 // { 0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x01 } AsnType oid = CreateOid("1.2.840.10040.4.1"); // AlgorithmIdentifier AsnType algorithmID = CreateSequence(new AsnType[] { oid, dssParams }); // Private Key X AsnType x = CreateIntegerPos(privateKey.X); AsnType key = CreateOctetString(x); // Sequence AsnType privateKeyInfo = CreateSequence(new AsnType[] { version, algorithmID, key }); return new AsnMessage(privateKeyInfo.GetBytes(), "PKCS#8"); } // PKCS #8, Section 6 (PrivateKeyInfo) message // !!!!!!!!!!!!!!! Unencrypted !!!!!!!!!!!!!!! /// /// Returns AsnMessage representing the unencrypted /// PKCS #8 PrivateKeyInfo. /// /// The RSA key to be encoded. /// Returns the AsnType representing the unencrypted /// PKCS #8 PrivateKeyInfo. /// /// /// public static AsnMessage PrivateKeyToPKCS8(RSAParameters privateKey) { // Value Type cannot be null // Debug.Assert(null != privateKey); /* * * SEQUENCE // PublicKeyInfo * +- INTEGER(0) // Version - 0 (v1998) * +- SEQUENCE // AlgorithmIdentifier * +- OID // 1.2.840.113549.1.1.1 * +- NULL // Optional Parameters * +- OCTETSTRING // PrivateKey * +- SEQUENCE // RSAPrivateKey * +- INTEGER(0) // Version - 0 (v1998) * +- INTEGER(N) * +- INTEGER(E) * +- INTEGER(D) * +- INTEGER(P) * +- INTEGER(Q) * +- INTEGER(DP) * +- INTEGER(DQ) * +- INTEGER(Inv Q) * */ AsnType n = CreateIntegerPos(privateKey.Modulus); AsnType e = CreateIntegerPos(privateKey.Exponent); AsnType d = CreateIntegerPos(privateKey.D); AsnType p = CreateIntegerPos(privateKey.P); AsnType q = CreateIntegerPos(privateKey.Q); AsnType dp = CreateIntegerPos(privateKey.DP); AsnType dq = CreateIntegerPos(privateKey.DQ); AsnType iq = CreateIntegerPos(privateKey.InverseQ); // Version - 0 (v1998) AsnType version = CreateInteger(new byte[] { 0 }); // octstring = OCTETSTRING(SEQUENCE(INTEGER(0)INTEGER(N)...)) AsnType key = CreateOctetString( CreateSequence(new AsnType[] { version, n, e, d, p, q, dp, dq, iq }) ); // OID - packed 1.2.840.113549.1.1.1 // { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 } AsnType algorithmID = CreateSequence(new AsnType[] { CreateOid("1.2.840.113549.1.1.1"), CreateNull() } ); // PrivateKeyInfo AsnType privateKeyInfo = CreateSequence(new AsnType[] { version, algorithmID, key }); return new AsnMessage(privateKeyInfo.GetBytes(), "PKCS#8"); } /// /// An ordered collection of one or more types. /// Returns the AsnType representing an ASN.1 encoded sequence. /// If the AsnType is null, an empty sequence (length 0) /// is returned. /// /// An AsnType consisting of /// a single value to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded sequence. /// /// /// /// /// /// /// /// public static AsnType CreateSequence(AsnType value) { // Should be at least 1... Debug.Assert(!IsEmpty(value)); // One or more required if (IsEmpty(value)) { throw new ArgumentException("A sequence requires at least one value."); } // Sequence: Tag 0x30 (16, Universal, Constructed) return new AsnType(0x30, value.GetBytes()); } /// /// An ordered collection of one or more types. /// Returns the AsnType representing an ASN.1 encoded sequence. /// If the AsnType is null, an /// empty sequence (length 0) is returned. /// /// An array of AsnType consisting of /// the values in the collection to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded Set. /// /// /// /// /// /// /// /// public static AsnType CreateSequence(AsnType[] values) { // Should be at least 1... Debug.Assert(!IsEmpty(values)); // One or more required if (IsEmpty(values)) { throw new ArgumentException("A sequence requires at least one value."); } // Sequence: Tag 0x30 (16, Universal, Constructed) return new AsnType((0x10 | 0x20), Concatenate(values)); } /// /// An ordered collection zero, one or more types. /// Returns the AsnType representing an ASN.1 encoded sequence. /// If the AsnType value is null,an /// empty sequence (length 0) is returned. /// /// An AsnType consisting of /// a single value to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded sequence. /// /// /// /// /// /// /// /// public static AsnType CreateSequenceOf(AsnType value) { // From the ASN.1 Mailing List if (IsEmpty(value)) { return new AsnType(0x30, EMPTY); } // Sequence: Tag 0x30 (16, Universal, Constructed) return new AsnType(0x30, value.GetBytes()); } /// /// An ordered collection zero, one or more types. /// Returns the AsnType representing an ASN.1 encoded sequence. /// If the AsnType array is null or the array is 0 length, /// an empty sequence (length 0) is returned. /// /// An AsnType consisting of /// the values in the collection to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded sequence. /// /// /// /// /// /// /// /// public static AsnType CreateSequenceOf(AsnType[] values) { // From the ASN.1 Mailing List if (IsEmpty(values)) { return new AsnType(0x30, EMPTY); } // Sequence: Tag 0x30 (16, Universal, Constructed) return new AsnType(0x30, Concatenate(values)); } /// /// An ordered sequence of zero, one or more bits. Returns /// the AsnType representing an ASN.1 encoded bit string. /// If octets is null or length is 0, an empty (0 length) /// bit string is returned. /// /// A MSB (big endian) byte[] representing the /// bit string to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded bit string. /// /// /// /// /// /// /// /// public static AsnType CreateBitString(byte[] octets) { // BitString: Tag 0x03 (3, Universal, Primitive) return CreateBitString(octets, 0); } /// /// An ordered sequence of zero, one or more bits. Returns /// the AsnType representing an ASN.1 encoded bit string. /// unusedBits is applied to the end of the bit string, /// not the start of the bit string. unusedBits must be less than 8 /// (the size of an octet). Refer to ITU X.680, Section 32. /// If octets is null or length is 0, an empty (0 length) /// bit string is returned. /// /// A MSB (big endian) byte[] representing the /// bit string to be encoded. /// The number of unused trailing binary /// digits in the bit string to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded bit string. /// /// /// /// /// /// /// /// public static AsnType CreateBitString(byte[] octets, uint unusedBits) { if (IsEmpty(octets)) { // Empty octet string return new AsnType(0x03, EMPTY); } if (!(unusedBits < 8)) { throw new ArgumentException("Unused bits must be less than 8."); } byte[] b = Concatenate(new byte[] { (byte)unusedBits }, octets); // BitString: Tag 0x03 (3, Universal, Primitive) return new AsnType(0x03, b); } /// /// An ordered sequence of zero, one or more bits. Returns /// the AsnType representing an ASN.1 encoded bit string. /// If value is null, an empty (0 length) bit string is /// returned. /// /// An AsnType object to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded bit string. /// /// /// /// /// /// /// /// public static AsnType CreateBitString(AsnType value) { if (IsEmpty(value)) { return new AsnType(0x03, EMPTY); } // BitString: Tag 0x03 (3, Universal, Primitive) return CreateBitString(value.GetBytes(), 0x00); } /// /// An ordered sequence of zero, one or more bits. Returns /// the AsnType representing an ASN.1 encoded bit string. /// If value is null, an empty (0 length) bit string is /// returned. /// /// An AsnType object to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded bit string. /// /// /// /// /// /// /// /// public static AsnType CreateBitString(AsnType[] values) { if (IsEmpty(values)) { return new AsnType(0x03, EMPTY); } // BitString: Tag 0x03 (3, Universal, Primitive) return CreateBitString(Concatenate(values), 0x00); } /// /// An ordered sequence of zero, one or more bits. Returns /// the AsnType representing an ASN.1 encoded bit string. /// If octets is null or length is 0, an empty (0 length) /// bit string is returned. /// If conversion fails, the bit string returned is a partial /// bit string. The partial bit string ends at the octet before the /// point of failure (it does not include the octet which could /// not be parsed, or subsequent octets). /// /// A MSB (big endian) byte[] representing the /// bit string to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded bit string. /// /// /// /// /// /// /// public static AsnType CreateBitString(String value) { if (IsEmpty(value)) { return CreateBitString(EMPTY); } // Any unused bits? int lstrlen = value.Length; int unusedBits = 8 - (lstrlen % 8); if (8 == unusedBits) { unusedBits = 0; } for (int i = 0; i < unusedBits; i++) { value += "0"; } // Determine number of octets int loctlen = (lstrlen + 7) / 8; List octets = new List(); for (int i = 0; i < loctlen; i++) { String s = value.Substring(i * 8, 8); byte b = 0x00; try { b = Convert.ToByte(s, 2); } catch (FormatException /*e*/) { unusedBits = 0; break; } catch (OverflowException /*e*/) { unusedBits = 0; break; } octets.Add(b); } // BitString: Tag 0x03 (3, Universal, Primitive) return CreateBitString(octets.ToArray(), (uint)unusedBits); } /// /// An ordered sequence of zero, one or more octets. Returns /// the ASN.1 encoded octet string. If octets is null or length /// is 0, an empty (0 length) octet string is returned. /// /// A MSB (big endian) byte[] representing the /// octet string to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded octet string. /// /// /// /// /// /// /// public static AsnType CreateOctetString(byte[] value) { if (IsEmpty(value)) { // Empty octet string return new AsnType(0x04, EMPTY); } // OctetString: Tag 0x04 (4, Universal, Primitive) return new AsnType(0x04, value); } /// /// An ordered sequence of zero, one or more octets. Returns /// the byte[] representing an ASN.1 encoded octet string. /// If octets is null or length is 0, an empty (0 length) /// o ctet string is returned. /// /// An AsnType object to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded octet string. /// /// /// /// /// /// public static AsnType CreateOctetString(AsnType value) { if (IsEmpty(value)) { // Empty octet string return new AsnType(0x04, 0x00); } // OctetString: Tag 0x04 (4, Universal, Primitive) return new AsnType(0x04, value.GetBytes()); } /// /// An ordered sequence of zero, one or more octets. Returns /// the byte[] representing an ASN.1 encoded octet string. /// If octets is null or length is 0, an empty (0 length) /// o ctet string is returned. /// /// An AsnType object to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded octet string. /// /// /// /// /// /// /// public static AsnType CreateOctetString(AsnType[] values) { if (IsEmpty(values)) { // Empty octet string return new AsnType(0x04, 0x00); } // OctetString: Tag 0x04 (4, Universal, Primitive) return new AsnType(0x04, Concatenate(values)); } /// /// An ordered sequence of zero, one or more bits. Returns /// the AsnType representing an ASN.1 encoded octet string. /// If octets is null or length is 0, an empty (0 length) /// octet string is returned. /// If conversion fails, the bit string returned is a partial /// bit string. The partial octet string ends at the octet before the /// point of failure (it does not include the octet which could /// not be parsed, or subsequent octets). /// /// A string representing the /// octet string to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded octet string. /// /// /// /// /// /// /// public static AsnType CreateOctetString(String value) { if (IsEmpty(value)) { return CreateOctetString(EMPTY); } // Determine number of octets int len = (value.Length + 255) / 256; List octets = new List(); for (int i = 0; i < len; i++) { String s = value.Substring(i * 2, 2); byte b = 0x00; try { b = Convert.ToByte(s, 16); } catch (FormatException /*e*/) { break; } catch (OverflowException /*e*/) { break; } octets.Add(b); } // OctetString: Tag 0x04 (4, Universal, Primitive) return CreateOctetString(octets.ToArray()); } /// /// Returns the AsnType representing a ASN.1 encoded /// integer. The octets pass through this method are not modified. /// If octets is null or zero length, the method returns an /// AsnType equivalent to CreateInteger(byte[]{0}).. /// /// A MSB (big endian) byte[] representing the /// integer to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded integer. /// /// ASN.1 encoded 0: /// CreateInteger(null) /// CreateInteger(new byte[]{0x00}) /// CreateInteger(new byte[]{0x00, 0x00}) /// /// /// ASN.1 encoded 1: /// CreateInteger(new byte[]{0x01}) /// /// /// public static AsnType CreateInteger(byte[] value) { // Is it better to add a '0', or silently // drop the Integer? Dropping integers // is probably not te best choice... if (IsEmpty(value)) { return CreateInteger(ZERO); } return new AsnType(0x02, value); } /// /// Returns the AsnType representing a positive ASN.1 encoded /// integer. If the high bit of most significant byte is set, /// the method prepends a 0x00 to octets before assigning the /// value to ensure the resulting integer is interpreted as /// positive in the application. /// If octets is null or zero length, the method returns an /// AsnType equivalent to CreateInteger(byte[]{0}).. /// /// A MSB (big endian) byte[] representing the /// integer to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded positive integer. /// /// ASN.1 encoded 0: /// CreateIntegerPos(null) /// CreateIntegerPos(new byte[]{0x00}) /// CreateIntegerPos(new byte[]{0x00, 0x00}) /// /// /// ASN.1 encoded 1: /// CreateInteger(new byte[]{0x01}) /// /// /// public static AsnType CreateIntegerPos(byte[] value) { byte[] i = null, d = Duplicate(value); if (IsEmpty(d)) { d = ZERO; } // Mediate the 2's compliment representation. // If the first byte has its high bit set, we will // add the additional byte of 0x00 if (d.Length > 0 && d[0] > 0x7F) { i = new byte[d.Length + 1]; i[0] = 0x00; Array.Copy(d, 0, i, 1, value.Length); } else { i = d; } // Integer: Tag 0x02 (2, Universal, Primitive) return CreateInteger(i); } /// /// Returns the negative ASN.1 encoded integer. If the high /// bit of most significant byte is set, the integer is already /// considered negative. /// If the high bit of most significant byte /// is not set, the integer will be 2's complimented /// to form a negative integer. /// If octets is null or zero length, the method returns an /// AsnType equivalent to CreateInteger(byte[]{0}).. /// /// A MSB (big endian) byte[] representing the /// integer to be encoded. /// Returns the negative ASN.1 encoded integer. /// /// ASN.1 encoded 0: /// CreateIntegerNeg(null) /// CreateIntegerNeg(new byte[]{0x00}) /// CreateIntegerNeg(new byte[]{0x00, 0x00}) /// /// /// ASN.1 encoded -1 (2's compliment 0xFF): /// CreateIntegerNeg(new byte[]{0x01}) /// /// /// ASN.1 encoded -2 (2's compliment 0xFE): /// CreateIntegerNeg(new byte[]{0x02}) /// /// /// ASN.1 encoded -1: /// CreateIntegerNeg(new byte[]{0xFF}) /// CreateIntegerNeg(new byte[]{0xFF,0xFF}) /// Note: already negative since the high bit is set. /// /// ASN.1 encoded -255 (2's compliment 0xFF, 0x01): /// CreateIntegerNeg(new byte[]{0x00,0xFF}) /// /// /// ASN.1 encoded -255 (2's compliment 0xFF, 0xFF, 0x01): /// CreateIntegerNeg(new byte[]{0x00,0x00,0xFF}) /// /// /// public static AsnType CreateIntegerNeg(byte[] value) { // Is it better to add a '0', or silently // drop the Integer? Dropping integers // is probably not te best choice... if (IsEmpty(value)) { return CreateInteger(ZERO); } // No Trimming // The byte[] may be that way for a reason if (IsZero(value)) { return CreateInteger(value); } // // At this point, we know we have at least 1 octet // // Is this integer already negative? if (value[0] >= 0x80) // Pass through with no modifications { return CreateInteger(value); } // No need to Duplicate - Compliment2s // performs the action byte[] c = Compliment2s(value); return CreateInteger(c); } /// /// Returns the AsnType representing an ASN.1 encoded null. /// /// Returns the AsnType representing an ASN.1 /// encoded null. public static AsnType CreateNull() { return new AsnType(0x05, new byte[] { 0x00 }); } /// /// Removes leading 0x00 octets from the byte[] octets. This /// method may return an empty byte array (0 length). /// /// An array of octets to trim. /// A byte[] with leading 0x00 octets removed. public static byte[] TrimStart(byte[] octets) { if (IsEmpty(octets) || IsZero(octets)) { return new byte[] { }; } byte[] d = Duplicate(octets); // Position of the first non-zero value int pos = 0; foreach (byte b in d) { if (0 != b) { break; } pos++; } // Nothing to trim if (pos == d.Length) { return octets; } // Allocate trimmed array byte[] t = new byte[d.Length - pos]; // Copy Array.Copy(d, pos, t, 0, t.Length); return t; } /// /// Removes trailing 0x00 octets from the byte[] octets. This /// method may return an empty byte array (0 length). /// /// An array of octets to trim. /// A byte[] with trailing 0x00 octets removed. public static byte[] TrimEnd(byte[] octets) { if (IsEmpty(octets) || IsZero(octets)) { return EMPTY; } byte[] d = Duplicate(octets); Array.Reverse(d); d = TrimStart(d); Array.Reverse(d); return d; } /// /// Returns the AsnType representing an ASN.1 encoded OID. /// If conversion fails, the result is a partial conversion /// up to the point of failure. If the oid string is null or /// not well formed, an empty byte[] is returned. /// /// The string representing the object /// identifier to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded object identifier. /// The following assigns the encoded AsnType /// for a RSA key to oid: /// AsnType oid = CreateOid("1.2.840.113549.1.1.1") /// /// public static AsnType CreateOid(String value) { // Punt? if (IsEmpty(value)) return null; String[] tokens = value.Split(new Char[] { ' ', '.' }); // Punt? if (IsEmpty(tokens)) return null; // Parsing/Manipulation of the arc value UInt64 a = 0; // One or more strings are available List arcs = new List(); foreach (String t in tokens) { // No empty or ill-formed strings... if (t.Length == 0) { break; } try { a = Convert.ToUInt64(t, CultureInfo.InvariantCulture); } catch (FormatException /*e*/) { break; } catch (OverflowException /*e*/) { break; } arcs.Add(a); } // Punt? if (0 == arcs.Count) return null; // Octets to be returned to caller List octets = new List(); // Guard the case of a small list // The list has at least 1 item... if (arcs.Count >= 1) { a = arcs[0] * 40; } if (arcs.Count >= 2) { a += arcs[1]; } octets.Add((byte)(a)); // Add remaining arcs (subidentifiers) for (int i = 2; i < arcs.Count; i++) { // Scratch list builder for this arc List temp = new List(); // The current arc (subidentifier) UInt64 arc = arcs[i]; // Build the arc (subidentifier) byte array // The array is built in reverse (LSB to MSB). do { // Each entry is formed from the low 7 bits (0x7F). // Set high bit of all entries (0x80) per X.680. We // will unset the high bit of the final byte later. temp.Add((byte)(0x80 | (arc & 0x7F))); arc >>= 7; } while (0 != arc); // Grab resulting array. Because of the do/while, // there is at least one value in the array. byte[] t = temp.ToArray(); // Unset high bit of byte t[0] // t[0] will be LSB after the array is reversed. t[0] = (byte)(0x7F & t[0]); // MSB first... Array.Reverse(t); // Add to the resulting array foreach (byte b in t) { octets.Add(b); } } return CreateOid(octets.ToArray()); } /// /// Returns the AsnType representing an ASN.1 encoded OID. /// If conversion fails, the result is a partial conversion /// (up to the point of failure). If octets is null, an /// empty byte[] is returned. /// /// The packed byte[] representing the object /// identifier to be encoded. /// Returns the AsnType representing an ASN.1 /// encoded object identifier. /// The following assigns the encoded AsnType for a RSA /// key to oid: /// // Packed 1.2.840.113549.1.1.1 /// byte[] rsa = new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; /// AsnType = CreateOid(rsa) /// /// public static AsnType CreateOid(byte[] value) { // Punt... if (IsEmpty(value)) { return null; } // OID: Tag 0x06 (6, Universal, Primitive) return new AsnType(0x06, value); } private static byte[] Compliment1s(byte[] value) { if (IsEmpty(value)) { return EMPTY; } // Make a copy of octet array byte[] c = Duplicate(value); for (int i = c.Length - 1; i >= 0; i--) { // Compliment c[i] = (byte)~c[i]; } return c; } private static byte[] Compliment2s(byte[] value) { if (IsEmpty(value)) { return EMPTY; } // 2s Compliment of 0 is 0 if (IsZero(value)) { return Duplicate(value); } // Make a copy of octet array byte[] d = Duplicate(value); int carry = 1; for (int i = d.Length - 1; i >= 0; i--) { // Compliment d[i] = (byte)~d[i]; // Add int j = d[i] + carry; // Write Back d[i] = (byte)(j & 0xFF); // Determine Next Carry if (0x100 == (j & 0x100)) { carry = 1; } else { carry = 0; } } // Carry Array (we may need to carry out of 'd' byte[] c = null; if (1 == carry) { c = new byte[d.Length + 1]; // Sign Extend.... c[0] = (byte)0xFF; Array.Copy(d, 0, c, 1, d.Length); } else { c = d; } return c; } private static byte[] Concatenate(AsnType[] values) { // Nothing in, nothing out if (IsEmpty(values)) return new byte[] { }; int length = 0; foreach (AsnType t in values) { if (null != t) { length += t.GetBytes().Length; } } byte[] cated = new byte[length]; int current = 0; foreach (AsnType t in values) { if (null != t) { byte[] b = t.GetBytes(); Array.Copy(b, 0, cated, current, b.Length); current += b.Length; } } return cated; } private static byte[] Concatenate(byte[] first, byte[] second) { return Concatenate(new byte[][] { first, second }); } private static byte[] Concatenate(byte[][] values) { // Nothing in, nothing out if (IsEmpty(values)) return new byte[] { }; int length = 0; foreach (byte[] b in values) { if (null != b) { length += b.Length; } } byte[] cated = new byte[length]; int current = 0; foreach (byte[] b in values) { if (null != b) { Array.Copy(b, 0, cated, current, b.Length); current += b.Length; } } return cated; } private static byte[] Duplicate(byte[] b) { if (IsEmpty(b)) { return EMPTY; } byte[] d = new byte[b.Length]; Array.Copy(b, d, b.Length); return d; } private static bool IsZero(byte[] octets) { if (IsEmpty(octets)) { return false; } bool allZeros = true; for (int i = 0; i < octets.Length; i++) { if (0 != octets[i]) { allZeros = false; break; } } return allZeros; } private static bool IsEmpty(byte[] octets) { if (null == octets || 0 == octets.Length) { return true; } return false; } private static bool IsEmpty(String s) { if (null == s || 0 == s.Length) { return true; } return false; } private static bool IsEmpty(String[] strings) { if (null == strings || 0 == strings.Length) return true; return false; } private static bool IsEmpty(AsnType value) { if (null == value) { return true; } return false; } private static bool IsEmpty(AsnType[] values) { if (null == values || 0 == values.Length) return true; return false; } private static bool IsEmpty(byte[][] arrays) { if (null == arrays || 0 == arrays.Length) return true; return false; } } }