Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ph-security/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
<dependency>
<groupId>com.helger.commons</groupId>
<artifactId>ph-bc</artifactId>
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.helger.commons</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Copyright (C) 2014-2026 Philip Helger (www.helger.com)
* philip[at]helger[dot]com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.helger.security.crl;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import org.jspecify.annotations.NonNull;

import com.helger.collection.commons.CommonsArrayList;
import com.helger.collection.commons.ICommonsList;

/**
* Parser for the small DER subset used by the X.509 CRL Distribution Points extension.
*
* @author GT
*/
final class CRLDistributionPointParser
{
private static final int TAG_OCTET_STRING = 0x04;
private static final int TAG_SEQUENCE = 0x30;
private static final int TAG_DISTRIBUTION_POINT_NAME = 0xa0;
private static final int TAG_FULL_NAME = 0xa0;
private static final int TAG_URI = 0x86;

private CRLDistributionPointParser ()
{}

@NonNull
public static ICommonsList <String> parse (final byte @NonNull [] aExtensionValue)
{
try
{
final DERReader aOuterReader = new DERReader (aExtensionValue);
final DERValue aExtensionOctets = aOuterReader.readExpected (TAG_OCTET_STRING);
aOuterReader.requireEnd ();

final DERReader aExtensionReader = aExtensionOctets.createReader ();
final DERValue aDistributionPointsSequence = aExtensionReader.readExpected (TAG_SEQUENCE);
aExtensionReader.requireEnd ();

final ICommonsList <String> ret = new CommonsArrayList <> ();
final DERReader aDistributionPointsReader = aDistributionPointsSequence.createReader ();
while (aDistributionPointsReader.hasRemaining ())
{
final DERValue aDistributionPoint = aDistributionPointsReader.readExpected (TAG_SEQUENCE);
_readDistributionPoint (aDistributionPoint, ret);
}
return ret;
}
catch (final IOException ex)
{
throw new UncheckedIOException ("Failed to decode the X.509 CRL Distribution Points extension", ex);
}
}

private static void _readDistributionPoint (@NonNull final DERValue aDistributionPoint,
@NonNull final ICommonsList <String> aTarget) throws IOException
{
final DERReader aFieldsReader = aDistributionPoint.createReader ();
while (aFieldsReader.hasRemaining ())
{
final DERValue aField = aFieldsReader.read ();
if (aField.getTag () == TAG_DISTRIBUTION_POINT_NAME)
_readDistributionPointName (aField, aTarget);
}
}

private static void _readDistributionPointName (@NonNull final DERValue aDistributionPointName,
@NonNull final ICommonsList <String> aTarget) throws IOException
{
final DERReader aNameReader = aDistributionPointName.createReader ();
final DERValue aName = aNameReader.read ();
aNameReader.requireEnd ();

if (aName.getTag () == TAG_FULL_NAME)
{
final DERReader aGeneralNamesReader = aName.createReader ();
while (aGeneralNamesReader.hasRemaining ())
{
final DERValue aGeneralName = aGeneralNamesReader.read ();
if (aGeneralName.getTag () == TAG_URI)
aTarget.add (_readIA5String (aGeneralName.getValue ()).trim ());
}
}
}

@NonNull
private static String _readIA5String (final byte @NonNull [] aValue) throws IOException
{
for (final byte b : aValue)
if ((b & 0x80) != 0)
throw new IOException ("IA5String contains a non-ASCII byte");
return new String (aValue, StandardCharsets.US_ASCII);
}

private static final class DERReader
{
private final byte [] m_aData;
private int m_nPosition;

DERReader (final byte @NonNull [] aData)
{
m_aData = aData;
}

public boolean hasRemaining ()
{
return m_nPosition < m_aData.length;
}

public void requireEnd () throws IOException
{
if (hasRemaining ())
throw new IOException ("Unexpected trailing DER data");
}

@NonNull
public DERValue readExpected (final int nExpectedTag) throws IOException
{
final DERValue ret = read ();
if (ret.getTag () != nExpectedTag)
throw new IOException ("Expected DER tag " + nExpectedTag + " but found " + ret.getTag ());
return ret;
}

@NonNull
public DERValue read () throws IOException
{
final int nTag = _readUnsignedByte ();
if ((nTag & 0x1f) == 0x1f)
throw new IOException ("High-tag-number DER values are not supported");

final int nLength = _readLength ();
if (nLength > m_aData.length - m_nPosition)
throw new IOException ("DER value length exceeds the available data");

final byte [] aValue = Arrays.copyOfRange (m_aData, m_nPosition, m_nPosition + nLength);
m_nPosition += nLength;
return new DERValue (nTag, aValue);
}

private int _readLength () throws IOException
{
final int nFirst = _readUnsignedByte ();
if ((nFirst & 0x80) == 0)
return nFirst;

final int nLengthBytes = nFirst & 0x7f;
if (nLengthBytes == 0)
throw new IOException ("Indefinite-length encoding is not valid DER");
if (nLengthBytes > 4 || nLengthBytes > m_aData.length - m_nPosition)
throw new IOException ("Invalid DER length field");
if ((m_aData[m_nPosition] & 0xff) == 0)
throw new IOException ("DER length has a redundant leading zero");

long nLength = 0;
for (int i = 0; i < nLengthBytes; ++i)
nLength = (nLength << 8) | _readUnsignedByte ();
if (nLength < 128 || nLength > Integer.MAX_VALUE)
throw new IOException ("Invalid DER length value");
return (int) nLength;
}

private int _readUnsignedByte () throws IOException
{
if (!hasRemaining ())
throw new IOException ("Unexpected end of DER data");
return m_aData[m_nPosition++] & 0xff;
}
}

private static final class DERValue
{
private final int m_nTag;
private final byte [] m_aValue;

DERValue (final int nTag, final byte @NonNull [] aValue)
{
m_nTag = nTag;
m_aValue = aValue;
}

public int getTag ()
{
return m_nTag;
}

public byte @NonNull [] getValue ()
{
return m_aValue;
}

@NonNull
public DERReader createReader ()
{
return new DERReader (m_aValue);
}
}
}
83 changes: 9 additions & 74 deletions ph-security/src/main/java/com/helger/security/crl/CRLHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,12 @@
*/
package com.helger.security.crl;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;

import org.bouncycastle.asn1.ASN1IA5String;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -46,8 +34,7 @@
import com.helger.collection.commons.ICommonsList;

/**
* Helper class to deal with CRLs. This class requires BouncyCastle to be in the
* classpath.
* Helper class to deal with CRLs.
*
* @author Philip Helger
* @since 11.2.0
Expand All @@ -56,6 +43,7 @@
public final class CRLHelper
{
private static final Logger LOGGER = LoggerFactory.getLogger (CRLHelper.class);
private static final String CRL_DISTRIBUTION_POINTS_OID = "2.5.29.31";

private CRLHelper ()
{}
Expand Down Expand Up @@ -104,67 +92,14 @@ public static X509CRL convertToCRL (final byte @NonNull @Nonempty [] aCRLBytes)
public static ICommonsList <String> getAllDistributionPoints (@NonNull final X509Certificate aCert)
{
ValueEnforcer.notNull (aCert, "Certificate");
final ICommonsList <String> ret = new CommonsArrayList <> ();
final byte [] aExtensionValue = aCert.getExtensionValue (CRL_DISTRIBUTION_POINTS_OID);
if (aExtensionValue == null)
return new CommonsArrayList <> ();

// Gets the DER-encoded OCTET string for the extension value for
// CRLDistributionPoints
final byte [] aExtensionValue = aCert.getExtensionValue (Extension.cRLDistributionPoints.getId ());
if (aExtensionValue != null)
{
// crlDPExtensionValue is encoded in ASN.1 format.
try (final ASN1InputStream aAsn1IS = new ASN1InputStream (aExtensionValue))
{
// DER (Distinguished Encoding Rules) is one of ASN.1 encoding rules
// defined in ITU-T X.690, 2002, specification.
// ASN.1 encoding rules can be used to encode any data object into a
// binary file. Read the object in octets.
final CRLDistPoint aDistPoint;
try
{
final DEROctetString aCrlDEROctetString = (DEROctetString) aAsn1IS.readObject ();
// Get Input stream in octets
try (final ASN1InputStream aAsn1InOctets = new ASN1InputStream (aCrlDEROctetString.getOctets ()))
{
final ASN1Primitive aCrlDERObject = aAsn1InOctets.readObject ();
aDistPoint = CRLDistPoint.getInstance (aCrlDERObject);
}
}
catch (final IOException e)
{
throw new UncheckedIOException (e);
}

// Loop through ASN1Encodable DistributionPoints
for (final DistributionPoint aDP : aDistPoint.getDistributionPoints ())
{
// get ASN1Encodable DistributionPointName
final DistributionPointName aDPName = aDP.getDistributionPoint ();
if (aDPName != null && aDPName.getType () == DistributionPointName.FULL_NAME)
{
// Create ASN1Encodable General Names
final GeneralName [] aGenNames = GeneralNames.getInstance (aDPName.getName ()).getNames ();
// Look for a URI
for (final GeneralName aGenName : aGenNames)
{
if (aGenName.getTagNo () == GeneralName.uniformResourceIdentifier)
{
// DERIA5String contains an ascii string.
// A IA5String is a restricted character string type in the
// ASN.1 notation
final String sURL = ASN1IA5String.getInstance (aGenName.getName ()).getString ().trim ();
if (LOGGER.isDebugEnabled ())
LOGGER.debug ("Found CRL URL '" + sURL + "' in certificate");
ret.add (sURL);
}
}
}
}
}
catch (final IOException ex)
{
throw new UncheckedIOException (ex);
}
}
final ICommonsList <String> ret = CRLDistributionPointParser.parse (aExtensionValue);
if (LOGGER.isDebugEnabled ())
for (final String sURL : ret)
LOGGER.debug ("Found CRL URL '" + sURL + "' in certificate");
return ret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package com.helger.security.oscp;

import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.jspecify.annotations.Nullable;

import com.helger.base.id.IHasIntID;
Expand All @@ -30,12 +29,12 @@
*/
public enum EOCSPResponseStatus implements ISuccessIndicator, IHasIntID
{
SUCCESSFUL (OCSPResponseStatus.SUCCESSFUL),
MALFORMED_REQUEST (OCSPResponseStatus.MALFORMED_REQUEST),
INTERNAL_ERROR (OCSPResponseStatus.INTERNAL_ERROR),
TRY_LATER (OCSPResponseStatus.TRY_LATER),
SIG_REQUIRED (OCSPResponseStatus.SIG_REQUIRED),
UNAUTHORIZED (OCSPResponseStatus.UNAUTHORIZED);
SUCCESSFUL (0),
MALFORMED_REQUEST (1),
INTERNAL_ERROR (2),
TRY_LATER (3),
SIG_REQUIRED (5),
UNAUTHORIZED (6);

private final int m_nValue;

Expand Down
Loading
Loading