Closed SilleBille closed 4 years ago
Posted by jmagne on 2018-05-29:
Looks like the attached test file is not being found by the system for me..
Posted by awood on 2018-05-30:
If you can't get the test working, I'll attach the output.
Full encoded extension: 30090603551d1504020a00 Encoded CRL Reason: 0a00 Reason value: 0
is the output line that demonstrates the problem. Basically it just shows that an enumeration value of zero isn't being encoded like the others and ultimately that causes a DER decoding error in the JDK classes.
I think the problem is in the putEnumerated method of DerOutputStream.
Posted by jmagne on 2018-05-30:
Thanks for the additional attachment, but I was referring to the fact that when I try to download the file, they system is telling me it's not found.
Posted by awood on 2018-05-30:
import org.mozilla.jss.JSSProvider;
import org.mozilla.jss.netscape.security.util.BitArray;
import org.mozilla.jss.netscape.security.util.DerInputStream;
import org.mozilla.jss.netscape.security.util.DerValue;
import org.mozilla.jss.netscape.security.x509.AuthorityKeyIdentifierExtension;
import org.mozilla.jss.netscape.security.x509.CRLExtensions;
import org.mozilla.jss.netscape.security.x509.CRLNumberExtension;
import org.mozilla.jss.netscape.security.x509.CRLReasonExtension;
import org.mozilla.jss.netscape.security.x509.Extension;
import org.mozilla.jss.netscape.security.x509.KeyIdentifier;
import org.mozilla.jss.netscape.security.x509.RevocationReason;
import org.mozilla.jss.netscape.security.x509.RevokedCertImpl;
import org.mozilla.jss.netscape.security.x509.RevokedCertificate;
import org.mozilla.jss.netscape.security.x509.X500Name;
import org.mozilla.jss.netscape.security.x509.X509CRLImpl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/** Class to demonstrate DER encoding failure when using an ASN.1 enumerated type with a value of zero.
*
* RFC 5280's section 5.3.1 lists the valid values for certificate revocation codes:
*
* CRLReason ::= ENUMERATED {
* unspecified (0),
* keyCompromise (1),
* cACompromise (2),
* affiliationChanged (3),
* superseded (4),
* cessationOfOperation (5),
* certificateHold (6),
* -- value 7 is not used
* removeFromCRL (8),
* privilegeWithdrawn (9),
* aACompromise (10) }
*
*/
public class EnumerationZeroTest {
static {
System.loadLibrary("jss4");
Security.addProvider(new JSSProvider());
}
private EnumerationZeroTest() {
}
public static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
/**
* Calculate the KeyIdentifier for an RSAPublicKey and place it in an AuthorityKeyIdentifier extension.
*
* Java encodes RSA public keys using the SubjectPublicKeyInfo type described in RFC 5280.
* <pre>
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING }
*
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL }
* </pre>
*
* A KeyIdentifier is a SHA-1 digest of the subjectPublicKey bit string from the ASN.1 above.
*
* param key the RSAPublicKey to use
* return an AuthorityKeyIdentifierExtension based on the key
* throws IOException if we can't construct a MessageDigest object.
*/
public static AuthorityKeyIdentifierExtension buildAuthorityKeyIdentifier(RSAPublicKey key)
throws IOException {
try {
MessageDigest d = MessageDigest.getInstance("SHA-1");
byte[] encodedKey = key.getEncoded();
DerInputStream s = new DerValue(encodedKey).toDerInputStream();
// Skip the first item in the sequence, AlgorithmIdentifier.
// The parameter, startLen, is required for skipSequence although it's unused.
s.skipSequence(0);
// Get the subjectPublicKey bit string
BitArray b = s.getUnalignedBitString();
byte[] digest = d.digest(b.toByteArray());
KeyIdentifier ki = new KeyIdentifier(digest);
return new AuthorityKeyIdentifierExtension(ki, null, null);
}
catch (NoSuchAlgorithmException e) {
throw new IOException("Could not find SHA1 implementation", e);
}
}
/**
* Output the DER encoding of a CRLExtension for examination
*/
public static void outputExtension(CRLReasonExtension ext) throws Exception {
ByteArrayOutputStream resultBytesOut = new ByteArrayOutputStream();
ext.encode(resultBytesOut);
byte[] encodedBytes = resultBytesOut.toByteArray();
System.out.print("Full encoded extension: " + toHex(encodedBytes));
Extension reasonExt = new Extension(new DerValue(encodedBytes));
System.out.print("\tEncoded CRL Reason: " + toHex(reasonExt.getExtensionValue()));
DerValue reasonValue = new DerValue(reasonExt.getExtensionValue());
System.out.println("\tReason value: " + reasonValue.getEnumerated());
}
/**
* Build a CRL using JSS
* param useZero whether or not to try creating a CRLEntry with the reason set to "unspecified"
* return an X509CRL object
* throws Exception if anything goes wrong
*/
public static X509CRL buildCrl(boolean useZero) throws Exception {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair kp = generator.generateKeyPair();
List<RevokedCertificate> revokedCerts = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 7 is an unused value in the enumeration
if (i == 7 || (i == 0 && !useZero)) {
continue;
}
CRLReasonExtension reasonExt = new CRLReasonExtension(RevocationReason.fromInt(i));
outputExtension(reasonExt);
CRLExtensions entryExtensions = new CRLExtensions();
entryExtensions.add(reasonExt);
revokedCerts.add(
new RevokedCertImpl(BigInteger.valueOf((long) i), new Date(), entryExtensions));
}
CRLExtensions crlExtensions = new CRLExtensions();
crlExtensions.add(new CRLNumberExtension(BigInteger.ONE));
crlExtensions.add(buildAuthorityKeyIdentifier((RSAPublicKey) kp.getPublic()));
X500Name issuer = new X500Name("CN=Test");
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 365);
Date until = calendar.getTime();
X509CRLImpl crlImpl = new X509CRLImpl(
issuer,
now,
until,
revokedCerts.toArray(new RevokedCertificate[] {}),
crlExtensions
);
crlImpl.sign(kp.getPrivate(), "SHA256withRSA");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509CRL) cf.generateCRL(new ByteArrayInputStream(crlImpl.getEncoded()));
}
catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
try {
X509CRL crl = buildCrl(false);
System.out.println(crl.toString());
buildCrl(true); // will throw exception
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Posted by jmagne on 2018-05-30:
Proposed simple patch to address this:
Was able to test the encoding to see that is matches what we expect for a enum value of zero: as follows:
reason 0: 300a0603551d1504030a0100 reason 08:: 300a0603551d1504030a0108
I didn't get far enough to test it with the sun classes, but should be easy to see if they accept it.
diff --git a/base/util/src/netscape/security/util/DerOutputStream.java b/base/util/src/netscape/security/util/DerOutputStream.java index df16d0b38..25bd3b81f 100644 --- a/base/util/src/netscape/security/util/DerOutputStream.java +++ b/base/util/src/netscape/security/util/DerOutputStream.java -205,7 +205,7 public class DerOutputStream write((byte) i); } } else {
Posted by mharmsen on 2018-06-22:
commit 06eacad918e745d632067deea398f14ce9da29ac (HEAD -> JSS_4_4_BRANCH, origin/JSS_4_4_BRANCH)
Author: Jack Magne
Posted by jmagne on 2018-06-22:
Last comment is incorrect. This patch did not make it in the first commit of the other ticket since it was not a blocker and we wanted some more testing on it.
This issue was migrated from Pagure Issue #3025.Originally filed by awood on 2018-05-24
Creating a CRL entry with a CRL Reason of "unspecified" (which has a value of 0) results in an encoding error when the JDK attempts to generate the CRL due to the way JSS encodes ASN.1 enumerations in DerOutputStream. Instead of encoding the enumeration as 0x0A0100, the value portion of the TLV is omitted and it's encoded as 0x0A00.
From what I can tell from X.690 (https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf), "The encoding of an enumerated value shall be that of the integer value with which it is associated" and for integers "The encoding of an integer value shall be primitive. The contents octets shall consist of one or more octets."
Steps to Reproduce:
% javac -cp ~/.m2/repository/org/mozilla/jss/4.4.0/jss-4.4.0.jar EnumerationZeroTest.java
% java -cp ~/.m2/repository/org/mozilla/jss/4.4.0/jss-4.4.0.jar:. -Djava.library.path=/usr/lib64/jss EnumerationZeroTest