//
// ASN1Convert.cs: Abstract Syntax Notation 1 convertion routines
//
// Authors:
//	Sebastien Pouliot  <sebastien@ximian.com>
//	Jesper Pedersen  <jep@itplus.dk>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
// (C) 2004 IT+ A/S (http://www.itplus.dk)
// Copyright (C) 2004-2007 Novell, Inc (http://www.novell.com)
//
// 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.Collections;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;

namespace Mono.Security
{

	// References:
	// a.	ITU ASN.1 standards (free download)
	//	http://www.itu.int/ITU-T/studygroups/com17/languages/

#if INSIDE_CORLIB
	internal
#else
	public
#endif
	static class ASN1Convert
	{
		// RFC3280, section 4.2.1.5
		// CAs conforming to this profile MUST always encode certificate
		// validity dates through the year 2049 as UTCTime; certificate validity
		// dates in 2050 or later MUST be encoded as GeneralizedTime.

		// Under 1.x this API requires a Local datetime to be provided
		// Under 2.0 it will also accept a Utc datetime
		static public ASN1 FromDateTime(DateTime dt)
		{
			if (dt.Year < 2050)
			{
				// UTCTIME
				return new ASN1(0x17, Encoding.ASCII.GetBytes(
					dt.ToUniversalTime().ToString("yyMMddHHmmss",
					CultureInfo.InvariantCulture) + "Z"));
			}
			else
			{
				// GENERALIZEDTIME
				return new ASN1(0x18, Encoding.ASCII.GetBytes(
					dt.ToUniversalTime().ToString("yyyyMMddHHmmss",
					CultureInfo.InvariantCulture) + "Z"));
			}
		}

		static public ASN1 FromInt32(Int32 value)
		{
			byte[] integer = BitConverterLE.GetBytes(value);
			Array.Reverse(integer);
			int x = 0;
			while ((x < integer.Length) && (integer[x] == 0x00))
				x++;
			ASN1 asn1 = new ASN1(0x02);
			switch (x)
			{
				case 0:
					asn1.Value = integer;
					break;
				case 4:
					asn1.Value = new byte[1];
					break;
				default:
					byte[] smallerInt = new byte[4 - x];
					Buffer.BlockCopy(integer, x, smallerInt, 0, smallerInt.Length);
					asn1.Value = smallerInt;
					break;
			}
			return asn1;
		}

		static public ASN1 FromOid(string oid)
		{
			if (oid == null)
				throw new ArgumentNullException("oid");

			return new ASN1(CryptoConfig.EncodeOID(oid));
		}

		static public ASN1 FromUnsignedBigInteger(byte[] big)
		{
			if (big == null)
				throw new ArgumentNullException("big");

			// check for numbers that could be interpreted as negative (first bit)
			if (big[0] >= 0x80)
			{
				// in thie cas we add a new, empty, byte (position 0) so we're
				// sure this will always be interpreted an unsigned integer.
				// However we can't feed it into RSAParameters or DSAParameters
				int length = big.Length + 1;
				byte[] uinteger = new byte[length];
				Buffer.BlockCopy(big, 0, uinteger, 1, length - 1);
				big = uinteger;
			}
			return new ASN1(0x02, big);
		}

		static public int ToInt32(ASN1 asn1)
		{
			if (asn1 == null)
				throw new ArgumentNullException("asn1");
			if (asn1.Tag != 0x02)
				throw new FormatException("Only integer can be converted");

			int x = 0;
			for (int i = 0; i < asn1.Value.Length; i++)
				x = (x << 8) + asn1.Value[i];
			return x;
		}

		// Convert a binary encoded OID to human readable string representation of 
		// an OID (IETF style). Based on DUMPASN1.C from Peter Gutmann.
		static public string ToOid(ASN1 asn1)
		{
			if (asn1 == null)
				throw new ArgumentNullException("asn1");

			byte[] aOID = asn1.Value;
			StringBuilder sb = new StringBuilder();
			// Pick apart the OID
			byte x = (byte)(aOID[0] / 40);
			byte y = (byte)(aOID[0] % 40);
			if (x > 2)
			{
				// Handle special case for large y if x = 2
				y += (byte)((x - 2) * 40);
				x = 2;
			}
			sb.Append(x.ToString(CultureInfo.InvariantCulture));
			sb.Append(".");
			sb.Append(y.ToString(CultureInfo.InvariantCulture));
			ulong val = 0;
			for (x = 1; x < aOID.Length; x++)
			{
				val = ((val << 7) | ((byte)(aOID[x] & 0x7F)));
				if (!((aOID[x] & 0x80) == 0x80))
				{
					sb.Append(".");
					sb.Append(val.ToString(CultureInfo.InvariantCulture));
					val = 0;
				}
			}
			return sb.ToString();
		}

		static public DateTime ToDateTime(ASN1 time)
		{
			if (time == null)
				throw new ArgumentNullException("time");

			string t = Encoding.ASCII.GetString(time.Value);
			// to support both UTCTime and GeneralizedTime (and not so common format)
			string mask = null;
			int year;
			switch (t.Length)
			{
				case 11:
					// illegal format, still it's supported for compatibility
					mask = "yyMMddHHmmZ";
					break;
				case 13:
					// RFC3280: 4.1.2.5.1  UTCTime
					year = Convert.ToInt16(t.Substring(0, 2), CultureInfo.InvariantCulture);
					// Where YY is greater than or equal to 50, the 
					// year SHALL be interpreted as 19YY; and 
					// Where YY is less than 50, the year SHALL be 
					// interpreted as 20YY.
					if (year >= 50)
						t = "19" + t;
					else
						t = "20" + t;
					mask = "yyyyMMddHHmmssZ";
					break;
				case 15:
					mask = "yyyyMMddHHmmssZ"; // GeneralizedTime
					break;
				case 17:
					// another illegal format (990630000000+1000), again supported for compatibility
					year = Convert.ToInt16(t.Substring(0, 2), CultureInfo.InvariantCulture);
					string century = (year >= 50) ? "19" : "20";
					// ASN.1 (see ITU X.680 section 43.3) deals with offset differently than .NET
					char sign = (t[12] == '+') ? '-' : '+';
					t = String.Format("{0}{1}{2}{3}{4}:{5}{6}", century, t.Substring(0, 12), sign,
						t[13], t[14], t[15], t[16]);
					mask = "yyyyMMddHHmmsszzz";
					break;
			}
			return DateTime.ParseExact(t, mask, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
		}
	}
}
