/* See LICENSE for licensing and NOTICE for copyright. */ package org.cryptacular.x509; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.AccessDescription; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.DistributionPoint; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.PolicyInformation; import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.cryptacular.EncodingException; /** * Reads X.509v3 extended properties from an {@link java.security.cert.X509Certificate} object. The available properties * are described in section 4.2 of RFC 2459, http://www.faqs.org/rfcs/rfc2459.html. * * @author Middleware Services */ public final class ExtensionReader { /** The X509Certificate whose extension fields will be read. */ private final X509Certificate certificate; /** * Creates a new instance that can read extension fields from the given X.509 certificate. * * @param cert Certificate to read. */ public ExtensionReader(final X509Certificate cert) { certificate = cert; } /** * Reads the value of the extension given by OID or name as defined in section 4.2 of RFC 2459. * * @param extensionOidOrName OID or extension name, e.g. 2.5.29.14 orSubjectK eyIdentifier. In the case of extension * name, the name is case-sensitive and follows the conventions in RFC 2459. * * @return Extension type containing data from requested extension field. * * @throws EncodingException On certificate field parse errors. */ public ASN1Encodable read(final String extensionOidOrName) throws EncodingException { if (extensionOidOrName == null) { throw new IllegalArgumentException("extensionOidOrName cannot be null."); } if (extensionOidOrName.contains(".")) { return read(ExtensionType.fromOid(extensionOidOrName)); } else { return read(ExtensionType.fromName(extensionOidOrName)); } } /** * Reads the value of the given certificate extension field. * * @param extension Extension to read from certificate. * * @return Extension type containing data from requested extension field. * * @throws EncodingException On certificate field parse errors. */ public ASN1Encodable read(final ExtensionType extension) { byte[] data = certificate.getExtensionValue(extension.getOid()); if (data == null) { return null; } try { ASN1Encodable der = ASN1Primitive.fromByteArray(data); if (der instanceof ASN1OctetString) { // Strip off octet string "wrapper" data = ((ASN1OctetString) der).getOctets(); der = ASN1Primitive.fromByteArray(data); } return der; } catch (Exception e) { throw new EncodingException("ASN.1 parse error", e); } } /** * Reads the value of the SubjectAlternativeName extension field of the certificate. * * @return Collection of subject alternative names or null if the certificate does not define this extension field. * Note that an empty collection of names is different from a null return value; in the former case the field * is defined but empty, whereas in the latter the field is not defined on the certificate. * * @throws EncodingException On certificate field parse errors. */ public GeneralNames readSubjectAlternativeName() throws EncodingException { try { return GeneralNames.getInstance(read(ExtensionType.SubjectAlternativeName)); } catch (RuntimeException e) { throw new EncodingException("GeneralNames parse error", e); } } /** * Reads the value of the <code>IssuerAlternativeName</code> extension field of the certificate. * * @return Collection of issuer alternative names or null if the certificate does not define this extension field. * Note that an empty collection of names is different from a null return value; in the former case the field * is defined but empty, whereas in the latter the field is not defined on the certificate. * * @throws EncodingException On certificate field parse errors. */ public GeneralNames readIssuerAlternativeName() throws EncodingException { try { return GeneralNames.getInstance(read(ExtensionType.IssuerAlternativeName)); } catch (RuntimeException e) { throw new EncodingException("GeneralNames parse error", e); } } /** * Reads the value of the <code>BasicConstraints</code> extension field of the certificate. * * @return Basic constraints defined on certificate or null if the certificate does not define the field. * * @throws EncodingException On certificate field parse errors. */ public BasicConstraints readBasicConstraints() throws EncodingException { try { return BasicConstraints.getInstance(read(ExtensionType.BasicConstraints)); } catch (RuntimeException e) { throw new EncodingException("BasicConstraints parse error", e); } } /** * Reads the value of the <code>CertificatePolicies</code> extension field of the certificate. * * @return List of certificate policies defined on certificate or null if the certificate does not define the field. * * @throws EncodingException On certificate field parse errors. */ public List<PolicyInformation> readCertificatePolicies() throws EncodingException { final ASN1Encodable data = read(ExtensionType.CertificatePolicies); if (data == null) { return null; } try { final ASN1Sequence sequence = ASN1Sequence.getInstance(data); final List<PolicyInformation> list = new ArrayList<>(sequence.size()); for (int i = 0; i < sequence.size(); i++) { list.add(PolicyInformation.getInstance(sequence.getObjectAt(i))); } return list; } catch (RuntimeException e) { throw new EncodingException("PolicyInformation parse error", e); } } /** * Reads the value of the <code>SubjectKeyIdentifier</code> extension field of the certificate. * * @return Subject key identifier. * * @throws EncodingException On certificate field parse errors. */ public SubjectKeyIdentifier readSubjectKeyIdentifier() throws EncodingException { try { return SubjectKeyIdentifier.getInstance(read(ExtensionType.SubjectKeyIdentifier)); } catch (RuntimeException e) { throw new EncodingException("SubjectKeyIdentifier parse error", e); } } /** * Reads the value of the <code>AuthorityKeyIdentifier</code> extension field of the certificate. * * @return Authority key identifier. * * @throws EncodingException On certificate field parse errors. */ public AuthorityKeyIdentifier readAuthorityKeyIdentifier() throws EncodingException { try { return AuthorityKeyIdentifier.getInstance(read(ExtensionType.AuthorityKeyIdentifier)); } catch (RuntimeException e) { throw new EncodingException("AuthorityKeyIdentifier parse error", e); } } /** * Reads the value of the <code>KeyUsage</code> extension field of the certificate. * * @return Key usage data or null if extension field is not defined. * * @throws EncodingException On certificate field parse errors. */ public KeyUsage readKeyUsage() throws EncodingException { try { return KeyUsage.getInstance(read(ExtensionType.KeyUsage)); } catch (RuntimeException e) { throw new EncodingException("KeyUsage parse error", e); } } /** * Reads the value of the <code>ExtendedKeyUsage</code> extension field of the certificate. * * @return List of supported extended key usages or null if extension is not defined. * * @throws EncodingException On certificate field parse errors. */ public List<KeyPurposeId> readExtendedKeyUsage() throws EncodingException { final ASN1Encodable data = read(ExtensionType.ExtendedKeyUsage); if (data == null) { return null; } try { final ASN1Sequence sequence = ASN1Sequence.getInstance(data); final List<KeyPurposeId> list = new ArrayList<>(sequence.size()); for (int i = 0; i < sequence.size(); i++) { list.add(KeyPurposeId.getInstance(sequence.getObjectAt(i))); } return list; } catch (RuntimeException e) { throw new EncodingException("KeyPurposeId parse error", e); } } /** * Reads the value of the <code>CRLDistributionPoints</code> extension field of the certificate. * * @return List of CRL distribution points or null if extension is not defined. * * @throws EncodingException On certificate field parse errors. */ public List<DistributionPoint> readCRLDistributionPoints() throws EncodingException { final ASN1Encodable data = read(ExtensionType.CRLDistributionPoints); if (data == null) { return null; } try { final ASN1Sequence sequence = ASN1Sequence.getInstance(data); final List<DistributionPoint> list = new ArrayList<>(sequence.size()); for (int i = 0; i < sequence.size(); i++) { list.add(DistributionPoint.getInstance(sequence.getObjectAt(i))); } return list; } catch (RuntimeException e) { throw new EncodingException("DistributionPoint parse error", e); } } /** * Reads the value of the <code>AuthorityInformationAccess</code> extension field of the certificate. * * @return List of access descriptions or null if extension is not defined. * * @throws EncodingException On certificate field parse errors. */ public List<AccessDescription> readAuthorityInformationAccess() throws EncodingException { final ASN1Encodable data = read(ExtensionType.AuthorityInformationAccess); if (data == null) { return null; } try { final ASN1Sequence sequence = ASN1Sequence.getInstance(data); final List<AccessDescription> list = new ArrayList<>(sequence.size()); for (int i = 0; i < sequence.size(); i++) { list.add(AccessDescription.getInstance(sequence.getObjectAt(i))); } return list; } catch (RuntimeException e) { throw new EncodingException("AccessDescription parse error", e); } } }