/**
 * /** The class encapsulates the ASN.1 DER decoding work with PolicyQualifierInfo structure (as
 * specified in RFC 3280 - Internet X.509 Public Key Infrastructure. Certificate and Certificate
 * Revocation List (CRL) Profile. http://www.ietf.org/rfc/rfc3280.txt):
 *
 * <pre>
 *    PolicyQualifierInfo ::= SEQUENCE {
 *        policyQualifierId  PolicyQualifierId,
 *        qualifier          ANY DEFINED BY policyQualifierId }
 *
 *    PolicyQualifierId ::=
 *        OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
 *
 * </pre>
 */
public final class PolicyQualifierInfo {
  // Contains only ASN1 DER decoder currently
  public static final ASN1Sequence ASN1 =
      new ASN1Sequence(new ASN1Type[] {ASN1Oid.getInstance(), ASN1Any.getInstance()}) {};
}
/**
 * The class encapsulates the ASN.1 DER encoding/decoding work with the Extension part of X.509
 * certificate (as specified in RFC 3280 - Internet X.509 Public Key Infrastructure. Certificate and
 * Certificate Revocation List (CRL) Profile. http://www.ietf.org/rfc/rfc3280.txt):
 *
 * <pre>
 *  Extension  ::=  SEQUENCE  {
 *       extnID      OBJECT IDENTIFIER,
 *       critical    BOOLEAN DEFAULT FALSE,
 *       extnValue   OCTET STRING
 *  }
 * </pre>
 */
public final class Extension {
  // critical constants
  public static final boolean CRITICAL = true;
  public static final boolean NON_CRITICAL = false;

  // constants: the extension OIDs
  // certificate extensions:
  static final int[] SUBJ_DIRECTORY_ATTRS = {2, 5, 29, 9};
  static final int[] SUBJ_KEY_ID = {2, 5, 29, 14};
  static final int[] KEY_USAGE = {2, 5, 29, 15};
  static final int[] PRIVATE_KEY_USAGE_PERIOD = {2, 5, 29, 16};
  static final int[] SUBJECT_ALT_NAME = {2, 5, 29, 17};
  static final int[] ISSUER_ALTERNATIVE_NAME = {2, 5, 29, 18};
  static final int[] BASIC_CONSTRAINTS = {2, 5, 29, 19};
  static final int[] NAME_CONSTRAINTS = {2, 5, 29, 30};
  static final int[] CRL_DISTR_POINTS = {2, 5, 29, 31};
  static final int[] CERTIFICATE_POLICIES = {2, 5, 29, 32};
  static final int[] POLICY_MAPPINGS = {2, 5, 29, 33};
  static final int[] AUTH_KEY_ID = {2, 5, 29, 35};
  static final int[] POLICY_CONSTRAINTS = {2, 5, 29, 36};
  static final int[] EXTENDED_KEY_USAGE = {2, 5, 29, 37};
  static final int[] FRESHEST_CRL = {2, 5, 29, 46};
  static final int[] INHIBIT_ANY_POLICY = {2, 5, 29, 54};
  static final int[] AUTHORITY_INFO_ACCESS = {1, 3, 6, 1, 5, 5, 7, 1, 1};
  static final int[] SUBJECT_INFO_ACCESS = {1, 3, 6, 1, 5, 5, 7, 1, 11};
  // crl extensions:
  static final int[] ISSUING_DISTR_POINT = {2, 5, 29, 28};
  // crl entry extensions:
  static final int[] CRL_NUMBER = {2, 5, 29, 20};
  static final int[] CERTIFICATE_ISSUER = {2, 5, 29, 29};
  static final int[] INVALIDITY_DATE = {2, 5, 29, 24};
  static final int[] REASON_CODE = {2, 5, 29, 21};
  static final int[] ISSUING_DISTR_POINTS = {2, 5, 29, 28};

  // the value of extnID field of the structure
  private final int[] extnID;
  private String extnID_str;
  // the value of critical field of the structure
  private final boolean critical;
  // the value of extnValue field of the structure
  private final byte[] extnValue;
  // the ASN.1 encoded form of Extension
  private byte[] encoding;
  // the raw (not decoded) value of extnValue field of the structure
  private byte[] rawExtnValue;
  // the decoded extension value
  protected ExtensionValue extnValueObject;
  // tells whether extension value has been decoded or not
  private boolean valueDecoded = false;

  public Extension(String extnID, boolean critical, ExtensionValue extnValueObject) {
    this.extnID_str = extnID;
    this.extnID = ObjectIdentifier.toIntArray(extnID);
    this.critical = critical;
    this.extnValueObject = extnValueObject;
    this.valueDecoded = true;
    this.extnValue = extnValueObject.getEncoded();
  }

  public Extension(String extnID, boolean critical, byte[] extnValue) {
    this.extnID_str = extnID;
    this.extnID = ObjectIdentifier.toIntArray(extnID);
    this.critical = critical;
    this.extnValue = extnValue;
  }

  public Extension(int[] extnID, boolean critical, byte[] extnValue) {
    this.extnID = extnID;
    this.critical = critical;
    this.extnValue = extnValue;
  }

  public Extension(String extnID, byte[] extnValue) {
    this(extnID, NON_CRITICAL, extnValue);
  }

  public Extension(int[] extnID, byte[] extnValue) {
    this(extnID, NON_CRITICAL, extnValue);
  }

  private Extension(
      int[] extnID,
      boolean critical,
      byte[] extnValue,
      byte[] rawExtnValue,
      byte[] encoding,
      ExtensionValue decodedExtValue) {
    this(extnID, critical, extnValue);
    this.rawExtnValue = rawExtnValue;
    this.encoding = encoding;
    this.extnValueObject = decodedExtValue;
    this.valueDecoded = (decodedExtValue != null);
  }

  /** Returns the value of extnID field of the structure. */
  public String getExtnID() {
    if (extnID_str == null) {
      extnID_str = ObjectIdentifier.toString(extnID);
    }
    return extnID_str;
  }

  /** Returns the value of critical field of the structure. */
  public boolean getCritical() {
    return critical;
  }

  /** Returns the value of extnValue field of the structure. */
  public byte[] getExtnValue() {
    return extnValue;
  }

  /** Returns the raw (undecoded octet string) value of extnValue field of the structure. */
  public byte[] getRawExtnValue() {
    if (rawExtnValue == null) {
      rawExtnValue = ASN1OctetString.getInstance().encode(extnValue);
    }
    return rawExtnValue;
  }

  /** Returns ASN.1 encoded form of this X.509 Extension value. */
  public byte[] getEncoded() {
    if (encoding == null) {
      encoding = Extension.ASN1.encode(this);
    }
    return encoding;
  }

  @Override
  public boolean equals(Object ext) {
    if (!(ext instanceof Extension)) {
      return false;
    }
    Extension extension = (Extension) ext;
    return Arrays.equals(extnID, extension.extnID)
        && (critical == extension.critical)
        && Arrays.equals(extnValue, extension.extnValue);
  }

  @Override
  public int hashCode() {
    return (Arrays.hashCode(extnID) * 37 + (critical ? 1 : 0)) * 37 + Arrays.hashCode(extnValue);
  }

  public ExtensionValue getDecodedExtensionValue() throws IOException {
    if (!valueDecoded) {
      decodeExtensionValue();
    }
    return extnValueObject;
  }

  public KeyUsage getKeyUsageValue() {
    if (!valueDecoded) {
      try {
        decodeExtensionValue();
      } catch (IOException ignored) {
      }
    }
    if (extnValueObject instanceof KeyUsage) {
      return (KeyUsage) extnValueObject;
    } else {
      return null;
    }
  }

  public BasicConstraints getBasicConstraintsValue() {
    if (!valueDecoded) {
      try {
        decodeExtensionValue();
      } catch (IOException ignored) {
      }
    }
    if (extnValueObject instanceof BasicConstraints) {
      return (BasicConstraints) extnValueObject;
    } else {
      return null;
    }
  }

  private void decodeExtensionValue() throws IOException {
    if (valueDecoded) {
      return;
    }
    valueDecoded = true;
    if (Arrays.equals(extnID, SUBJ_KEY_ID)) {
      extnValueObject = SubjectKeyIdentifier.decode(extnValue);
    } else if (Arrays.equals(extnID, KEY_USAGE)) {
      extnValueObject = new KeyUsage(extnValue);
    } else if (Arrays.equals(extnID, SUBJECT_ALT_NAME)) {
      extnValueObject = new AlternativeName(AlternativeName.SUBJECT, extnValue);
    } else if (Arrays.equals(extnID, ISSUER_ALTERNATIVE_NAME)) {
      extnValueObject = new AlternativeName(AlternativeName.SUBJECT, extnValue);
    } else if (Arrays.equals(extnID, BASIC_CONSTRAINTS)) {
      extnValueObject = new BasicConstraints(extnValue);
    } else if (Arrays.equals(extnID, NAME_CONSTRAINTS)) {
      extnValueObject = NameConstraints.decode(extnValue);
    } else if (Arrays.equals(extnID, CERTIFICATE_POLICIES)) {
      extnValueObject = CertificatePolicies.decode(extnValue);
    } else if (Arrays.equals(extnID, AUTH_KEY_ID)) {
      extnValueObject = AuthorityKeyIdentifier.decode(extnValue);
    } else if (Arrays.equals(extnID, POLICY_CONSTRAINTS)) {
      extnValueObject = new PolicyConstraints(extnValue);
    } else if (Arrays.equals(extnID, EXTENDED_KEY_USAGE)) {
      extnValueObject = new ExtendedKeyUsage(extnValue);
    } else if (Arrays.equals(extnID, INHIBIT_ANY_POLICY)) {
      extnValueObject = new InhibitAnyPolicy(extnValue);
    } else if (Arrays.equals(extnID, CERTIFICATE_ISSUER)) {
      extnValueObject = new CertificateIssuer(extnValue);
    } else if (Arrays.equals(extnID, CRL_DISTR_POINTS)) {
      extnValueObject = CRLDistributionPoints.decode(extnValue);
    } else if (Arrays.equals(extnID, CERTIFICATE_ISSUER)) {
      extnValueObject = new ReasonCode(extnValue);
    } else if (Arrays.equals(extnID, INVALIDITY_DATE)) {
      extnValueObject = new InvalidityDate(extnValue);
    } else if (Arrays.equals(extnID, REASON_CODE)) {
      extnValueObject = new ReasonCode(extnValue);
    } else if (Arrays.equals(extnID, CRL_NUMBER)) {
      extnValueObject = new CRLNumber(extnValue);
    } else if (Arrays.equals(extnID, ISSUING_DISTR_POINTS)) {
      extnValueObject = IssuingDistributionPoint.decode(extnValue);
    } else if (Arrays.equals(extnID, AUTHORITY_INFO_ACCESS)) {
      extnValueObject = InfoAccessSyntax.decode(extnValue);
    } else if (Arrays.equals(extnID, SUBJECT_INFO_ACCESS)) {
      extnValueObject = InfoAccessSyntax.decode(extnValue);
    }
  }

  public void dumpValue(StringBuilder sb, String prefix) {
    sb.append("OID: ").append(getExtnID()).append(", Critical: ").append(critical).append('\n');
    if (!valueDecoded) {
      try {
        decodeExtensionValue();
      } catch (IOException ignored) {
      }
    }
    if (extnValueObject != null) {
      extnValueObject.dumpValue(sb, prefix);
      return;
    }
    // else: dump unparsed hex representation
    sb.append(prefix);
    if (Arrays.equals(extnID, SUBJ_DIRECTORY_ATTRS)) {
      sb.append("Subject Directory Attributes Extension");
    } else if (Arrays.equals(extnID, SUBJ_KEY_ID)) {
      sb.append("Subject Key Identifier Extension");
    } else if (Arrays.equals(extnID, KEY_USAGE)) {
      sb.append("Key Usage Extension");
    } else if (Arrays.equals(extnID, PRIVATE_KEY_USAGE_PERIOD)) {
      sb.append("Private Key Usage Period Extension");
    } else if (Arrays.equals(extnID, SUBJECT_ALT_NAME)) {
      sb.append("Subject Alternative Name Extension");
    } else if (Arrays.equals(extnID, ISSUER_ALTERNATIVE_NAME)) {
      sb.append("Issuer Alternative Name Extension");
    } else if (Arrays.equals(extnID, BASIC_CONSTRAINTS)) {
      sb.append("Basic Constraints Extension");
    } else if (Arrays.equals(extnID, NAME_CONSTRAINTS)) {
      sb.append("Name Constraints Extension");
    } else if (Arrays.equals(extnID, CRL_DISTR_POINTS)) {
      sb.append("CRL Distribution Points Extension");
    } else if (Arrays.equals(extnID, CERTIFICATE_POLICIES)) {
      sb.append("Certificate Policies Extension");
    } else if (Arrays.equals(extnID, POLICY_MAPPINGS)) {
      sb.append("Policy Mappings Extension");
    } else if (Arrays.equals(extnID, AUTH_KEY_ID)) {
      sb.append("Authority Key Identifier Extension");
    } else if (Arrays.equals(extnID, POLICY_CONSTRAINTS)) {
      sb.append("Policy Constraints Extension");
    } else if (Arrays.equals(extnID, EXTENDED_KEY_USAGE)) {
      sb.append("Extended Key Usage Extension");
    } else if (Arrays.equals(extnID, INHIBIT_ANY_POLICY)) {
      sb.append("Inhibit Any-Policy Extension");
    } else if (Arrays.equals(extnID, AUTHORITY_INFO_ACCESS)) {
      sb.append("Authority Information Access Extension");
    } else if (Arrays.equals(extnID, SUBJECT_INFO_ACCESS)) {
      sb.append("Subject Information Access Extension");
    } else if (Arrays.equals(extnID, INVALIDITY_DATE)) {
      sb.append("Invalidity Date Extension");
    } else if (Arrays.equals(extnID, CRL_NUMBER)) {
      sb.append("CRL Number Extension");
    } else if (Arrays.equals(extnID, REASON_CODE)) {
      sb.append("Reason Code Extension");
    } else {
      sb.append("Unknown Extension");
    }
    sb.append('\n').append(prefix).append("Unparsed Extension Value:\n");
    sb.append(Array.toString(extnValue, prefix));
  }

  /** X.509 Extension encoder/decoder. */
  public static final ASN1Sequence ASN1 =
      new ASN1Sequence(
          new ASN1Type[] {
            ASN1Oid.getInstance(),
            ASN1Boolean.getInstance(),
            new ASN1OctetString() {
              @Override
              public Object getDecodedObject(BerInputStream in) throws IOException {
                // first - decoded octet string,
                // second - raw encoding of octet string
                return new Object[] {super.getDecodedObject(in), in.getEncoded()};
              }
            }
          }) {
        {
          setDefault(Boolean.FALSE, 1);
        }

        @Override
        protected Object getDecodedObject(BerInputStream in) throws IOException {
          Object[] values = (Object[]) in.content;

          int[] oid = (int[]) values[0];
          byte[] extnValue = (byte[]) ((Object[]) values[2])[0];
          byte[] rawExtnValue = (byte[]) ((Object[]) values[2])[1];

          ExtensionValue decodedExtValue = null;
          // decode Key Usage and Basic Constraints extension values
          if (Arrays.equals(oid, KEY_USAGE)) {
            decodedExtValue = new KeyUsage(extnValue);
          } else if (Arrays.equals(oid, BASIC_CONSTRAINTS)) {
            decodedExtValue = new BasicConstraints(extnValue);
          }

          return new Extension(
              (int[]) values[0],
              (Boolean) values[1],
              extnValue,
              rawExtnValue,
              in.getEncoded(),
              decodedExtValue);
        }

        @Override
        protected void getValues(Object object, Object[] values) {
          Extension ext = (Extension) object;
          values[0] = ext.extnID;
          values[1] = (ext.critical) ? Boolean.TRUE : Boolean.FALSE;
          values[2] = ext.extnValue;
        }
      };
}
/**
 * The class encapsulates the ASN.1 DER encoding/decoding work with PolicyInformation structure
 * which is a subpart of certificatePolicies (as specified in RFC 3280 - Internet X.509 Public Key
 * Infrastructure. Certificate and Certificate Revocation List (CRL) Profile.
 * http://www.ietf.org/rfc/rfc3280.txt):
 *
 * <pre>
 *  PolicyInformation ::= SEQUENCE {
 *       policyIdentifier   CertPolicyId,
 *       policyQualifiers   SEQUENCE SIZE (1..MAX) OF
 *                               PolicyQualifierInfo OPTIONAL
 *  }
 * </pre>
 *
 * TODO: This class is not fully implemented, implemented only work with OIDs.
 */
public class PolicyInformation {

  // the value of policyIdentifier field of the structure
  private String policyIdentifier;
  // the ASN.1 encoded form of PolicyInformation
  private byte[] encoding;

  /**
   * TODO
   *
   * @param policyIdentifier: String
   */
  public PolicyInformation(String policyIdentifier) {
    this.policyIdentifier = policyIdentifier;
  }

  /**
   * Returns the value of policyIdentifier field of the structure.
   *
   * @return policyIdentifier
   */
  public String getPolicyIdentifier() {
    return policyIdentifier;
  }

  /**
   * Returns ASN.1 encoded form of this X.509 PolicyInformation value.
   *
   * @return a byte array containing ASN.1 encode form.
   */
  public byte[] getEncoded() {
    if (encoding == null) {
      encoding = ASN1.encode(this);
    }
    return encoding;
  }

  /** Places the string representation of extension value into the StringBuffer object. */
  public void dumpValue(StringBuffer buffer) {
    buffer
        .append("Policy Identifier [") // $NON-NLS-1$
        .append(policyIdentifier)
        .append(']');
  }

  /** ASN.1 DER X.509 PolicyInformation encoder/decoder class. */
  public static final ASN1Sequence ASN1 =
      new ASN1Sequence(new ASN1Type[] {ASN1Oid.getInstance(), ASN1Any.getInstance()}) {
        {
          setOptional(1);
        }

        protected Object getDecodedObject(BerInputStream in) {
          Object[] values = (Object[]) in.content;
          return new PolicyInformation(ObjectIdentifier.toString((int[]) values[0]));
        }

        protected void getValues(Object object, Object[] values) {

          PolicyInformation pi = (PolicyInformation) object;

          values[0] = ObjectIdentifier.toIntArray(pi.policyIdentifier);
        }
      };
}