All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.io.PKCS7OutputStream Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2020 Guardsquare NV
 *
 * 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 proguard.io;

import java.io.*;
import java.security.cert.*;
import java.util.*;

/**
 * This class provides methods to write signature data in PKCS7 format
 * (= Cryptographic Message Syntax (CMS), part of the Public Key Cryptography
 * Standards) to an underlying DER output stream.
 * 

* This is for example the format of signature files like META-INF/CERT.RSA *

* You can view the resulting file with * openssl asn1parse -inform DER -in META-INF/CERT.RSA *

* You can check a signed jar with * jarsigner -verify -verbose example.jar * * @author Eric Lafortune */ class PKCS7OutputStream { private static final int PKCS7_VERSION = 1; private static final int SIGNER_VERSION = 1; private static final byte[] DATA_CONTENT = { 0x2a, (byte)0x86, 0x48, (byte)0x86, (byte)0xf7, 0xd, 0x1, 0x7, 0x1 }; // 1.2.840.113549.1.7.1 private static final byte[] SDATA_CONTENT = { 0x2a, (byte)0x86, 0x48, (byte)0x86, (byte)0xf7, 0xd, 0x1, 0x7, 0x2 }; // 1.2.840.113549.1.7.2 private static final byte[] SHA1_DIGEST_ALGORITHM_ID = { 0x2b, 0xe, 0x3, 0x2, 0x1a }; // 1.3.14.3.2.26 private static final byte[] SHA256_DIGEST_ALGORITHM_ID = { 0x60, (byte)0x86, 0x48, 0x1, 0x65, 0x3, 0x4, 0x2, 0x1 }; // 2.16.840.1.101.3.4.2.1 private static final byte[] SHA384_DIGEST_ALGORITHM_ID = { 0x60, (byte)0x86, 0x48, 0x1, 0x65, 0x3, 0x4, 0x2, 0x2 }; // 2.16.840.1.101.3.4.2.2 private static final byte[] SHA512_DIGEST_ALGORITHM_ID = { 0x60, (byte)0x86, 0x48, 0x1, 0x65, 0x3, 0x4, 0x2, 0x3 }; // 2.16.840.1.101.3.4.2.3 private static final byte[] RSA_ENCRYPTION_ALGORITHM_ID = { 0x2a, (byte)0x86, 0x48, (byte)0x86, (byte)0xf7, 0xd, 0x1, 0x1, 0x1 }; // 1.2.840.113549.1.1.1 private static final byte[] DH_ENCRYPTION_ALGORITHM_ID = { 0x2a, (byte)0x86, 0x48, (byte)0x86, (byte)0xf7, 0xd, 0x1, 0x3, 0x1 }; // 1.2.840.113549.1.3.1 private static final byte[] DSA_ENCRYPTION_ALGORITHM_ID = { 0x2a, (byte)0x86, 0x48, (byte)0xce, 0x38, 0x4, 0x1 }; // 1.2.840.10040.4.1 private static final byte[] EC_ENCRYPTION_ALGORITHM_ID = { 0x2a, (byte)0x86, 0x48, (byte)0xce, 0x3d, 0x2, 0x1 }; // 1.2.840.10045.2.1 private static final byte[] COMMON_NAME_OBJECT_ID = { 0x55, 0x04, 0x03 }; // 2.5.4.3 private static final byte[] SURNAME_OBJECT_ID = { 0x55, 0x04, 0x04 }; // 2.5.4.4 private static final byte[] SERIAL_NUMBER_OBJECT_ID = { 0x55, 0x04, 0x05 }; // 2.5.4.5 private static final byte[] COUNTRY_NAME_OBJECT_ID = { 0x55, 0x04, 0x06 }; // 2.5.4.6 private static final byte[] LOCALITY_NAME_OBJECT_ID = { 0x55, 0x04, 0x07 }; // 2.5.4.7 private static final byte[] STATE_NAME_OBJECT_ID = { 0x55, 0x04, 0x08 }; // 2.5.4.8 private static final byte[] STREET_ADDRESS_OBJECT_ID = { 0x55, 0x04, 0x09 }; // 2.5.4.9 private static final byte[] ORGANIZATION_NAME_OBJECT_ID = { 0x55, 0x04, 0x0a }; // 2.5.4.10 private static final byte[] UNIT_NAME_OBJECT_ID = { 0x55, 0x04, 0x0b }; // 2.5.4.11 private static final byte[] TITLE_OBJECT_ID = { 0x55, 0x04, 0x0c }; // 2.5.4.12 private static final byte[] GIVEN_NAME_OBJECT_ID = { 0x55, 0x04, 0x2a }; // 2.5.4.42 private static final byte[] EMAIL_ADDRESS_OBJECT_ID = { 0x2a, (byte)0x86, 0x48, (byte)0x86, (byte)0xf7, 0xd, 0x1, 0x9, 0x1 }; // 1.2.840.113549.1.9.1 private static final String COMMON_NAME_ATTRIBUTE = "CN"; private static final String SURNAME_ATTRIBUTE = "SN"; // Not supported by keytool. private static final String SERIAL_NUMBER_ATTRIBUTE = "SERIALNUMBER"; private static final String COUNTRY_NAME_ATTRIBUTE = "C"; private static final String LOCALITY_NAME_ATTRIBUTE = "L"; private static final String STATE_NAME_ATTRIBUTE = "ST"; private static final String STREET_ADDRESS_ATTRIBUTE = "STREET"; private static final String ORGANIZATION_NAME_ATTRIBUTE = "O"; private static final String UNIT_NAME_ATTRIBUTE = "OU"; private static final String TITLE_ATTRIBUTE = "T"; private static final String GIVEN_NAME_ATTRIBUTE = "GN"; // Not supported by keytool. private static final String EMAIL_ADDRESS_ATTRIBUTE = "EMAILADDRESS"; private final DEROutputStream derOutputStream; /** * Creates a new PKCS7OutputStream. * @param derOutputStream the DER output stream to which data will be * written. */ public PKCS7OutputStream(DEROutputStream derOutputStream) { this.derOutputStream = derOutputStream; } /** * Writes the given signature data. */ public void writeSignature(X509Certificate certificate, String digestAlgorithm, String encryptionAlgorithm, byte[] digitalSignatureBytes) throws IOException, CertificateEncodingException { derOutputStream.startSequence(); { // Write the content type. derOutputStream.writeObjectIdentifier(SDATA_CONTENT); derOutputStream.startImplicit(); { derOutputStream.startSequence(); // Write the PKCS7 version. derOutputStream.writeInteger(PKCS7_VERSION); // Write the signing algorithm. derOutputStream.startSet(); writeNullAttribute(encodedDigestAlgorithmOid(digestAlgorithm)); derOutputStream.endSet(); // Write the content type. derOutputStream.startSequence(); derOutputStream.writeObjectIdentifier(DATA_CONTENT); derOutputStream.endSequence(); // Write the encoded certificate. derOutputStream.writeImplicit(certificate.getEncoded()); // Write the signature itself. writeSignerSingleton(certificate, digestAlgorithm, encryptionAlgorithm, digitalSignatureBytes); derOutputStream.endSequence(); } derOutputStream.endImplicit(); } derOutputStream.endSequence(); } public void flush() throws IOException { derOutputStream.flush(); } public void close() throws IOException { derOutputStream.close(); } // Small utility methods. /** * Writes a set with a single signature for the given certificate. */ private void writeSignerSingleton(X509Certificate certificate, String digestAlgorithm, String encryptionAlgorithm, byte[] digitalSignatureBytes) throws IOException { derOutputStream.startSet(); writeSigner(certificate, digestAlgorithm, encryptionAlgorithm, digitalSignatureBytes); derOutputStream.endSet(); } /** * Writes a signature for the given certificate. */ private void writeSigner(X509Certificate certificate, String digestAlgorithm, String encryptionAlgorithm, byte[] digitalSignatureBytes) throws IOException { derOutputStream.startSequence(); // Write the signer version. derOutputStream.writeInteger(SIGNER_VERSION); // Write the relevant info from the certificate. writeSignerCertificateInfo(certificate); // Write the digest and signing algorithms (hardcoded). writeNullAttribute(encodedDigestAlgorithmOid(digestAlgorithm)); writeNullAttribute(encodedEncryptionAlgorithmOid(encryptionAlgorithm)); // Write the signature bytes themselves. derOutputStream.writeOctetString(digitalSignatureBytes); derOutputStream.endSequence(); } /** * Writes the certificate part of the signer info. */ private void writeSignerCertificateInfo(X509Certificate certificate) throws IOException { derOutputStream.startSequence(); { derOutputStream.startSequence(); // We need the distinguished name of the issuer, // which might be different from the name of the subject. String distinguishedName = certificate.getIssuerDN().getName(); // Apparently, the attribute sequence has to be in the reverse // order of the comma-separated list in the distinguished name. // The "jarsigner -verify" prints out "java.lang.SecurityException: // cannot verify signature block file META-INF/CERT" if the order, // the contents, or the even the string formats are wrong. // Start parsing at the end. int endIndex = distinguishedName.length(); do { // We have to strip any quotes from attribute values. // Quoted attribute values may contain commas. boolean quoted = distinguishedName.charAt(endIndex-1) == '"'; int commaEndIndex = quoted ? distinguishedName.lastIndexOf('"', endIndex-2) : endIndex - 1; // Extract the attribute name/value pair. int commaIndex = distinguishedName.lastIndexOf(',', commaEndIndex); String attribute = distinguishedName.substring(commaIndex+1, endIndex); // Extract the name and the value from the attribute. // Apparently, the value can contain unquoted control characters // (e.g. EMAILADDRESS=^[email protected]). int equalsIndex = attribute.indexOf('='); String attributeName = attribute.substring(0, equalsIndex).trim(); String attributeValue = quoted ? attribute.substring(equalsIndex+2, attribute.length()-1) : trimSpaces(attribute.substring(equalsIndex + 1)); // Write out the name and value. // Note that the email address needs to be written out as an // IA5 string. if (attributeName.equals(COMMON_NAME_ATTRIBUTE )) writeStringAttributeSingleton(COMMON_NAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(SURNAME_ATTRIBUTE )) writeStringAttributeSingleton(SURNAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(SERIAL_NUMBER_ATTRIBUTE )) writeStringAttributeSingleton(SERIAL_NUMBER_OBJECT_ID , attributeValue, false); else if (attributeName.equals(COUNTRY_NAME_ATTRIBUTE )) writeStringAttributeSingleton(COUNTRY_NAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(LOCALITY_NAME_ATTRIBUTE )) writeStringAttributeSingleton(LOCALITY_NAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(STATE_NAME_ATTRIBUTE )) writeStringAttributeSingleton(STATE_NAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(STREET_ADDRESS_ATTRIBUTE )) writeStringAttributeSingleton(STREET_ADDRESS_OBJECT_ID , attributeValue, false); else if (attributeName.equals(ORGANIZATION_NAME_ATTRIBUTE)) writeStringAttributeSingleton(ORGANIZATION_NAME_OBJECT_ID, attributeValue, false); else if (attributeName.equals(UNIT_NAME_ATTRIBUTE )) writeStringAttributeSingleton(UNIT_NAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(TITLE_ATTRIBUTE )) writeStringAttributeSingleton(TITLE_OBJECT_ID , attributeValue, false); else if (attributeName.equals(GIVEN_NAME_ATTRIBUTE )) writeStringAttributeSingleton(GIVEN_NAME_OBJECT_ID , attributeValue, false); else if (attributeName.equals(EMAIL_ADDRESS_ATTRIBUTE )) writeStringAttributeSingleton(EMAIL_ADDRESS_OBJECT_ID , attributeValue, true); endIndex = commaIndex; } while (endIndex > 0); derOutputStream.endSequence(); } // Certificate serial number. derOutputStream.writeInteger(certificate.getSerialNumber()); derOutputStream.endSequence(); } /** * Returns the given string with any leading or trailing spaces trimmed. */ private String trimSpaces(String string) { // Find the first non-space character. int startIndex = 0; while (startIndex < string.length() && string.charAt(startIndex) == ' ') { startIndex++; } // Find the last non-space character. int endIndex = string.length(); while (endIndex > startIndex && string.charAt(endIndex - 1) == ' ') { endIndex--; } return string.substring(startIndex, endIndex); } /** * Writes a X.509 Relative-Distinguished-Name (RDN, a set of attributes), * with the specified X.500 Attribute-Value-Assertion (AVA, an attribute). */ private void writeStringAttributeSingleton(byte[] attributeIdentifier, String attributeValue, boolean asIA5String) throws IOException { derOutputStream.startSet(); // Write just a single attribute. writeStringAttribute(attributeIdentifier, attributeValue, asIA5String); derOutputStream.endSet(); } /** * Writes the specified X.500 Attribute-Value-Assertion (AVA), * with string value. */ private void writeStringAttribute(byte[] attributeIdentifier, String attributeValue, boolean asIA5String) throws IOException { derOutputStream.startSequence(); derOutputStream.writeObjectIdentifier(attributeIdentifier); derOutputStream.writeString(attributeValue, asIA5String); derOutputStream.endSequence(); } /** * Writes the specified X.500 Attribute-Value-Assertion (AVA), * with a null value. */ private void writeNullAttribute(byte[] attributeName) throws IOException { derOutputStream.startSequence(); derOutputStream.writeObjectIdentifier(attributeName); derOutputStream.writeNull(); derOutputStream.endSequence(); } /** * Returns the encoded object ID of the specified digest algorithm. */ private byte[] encodedDigestAlgorithmOid(String digestAlgorithm) { return digestAlgorithm.equals("SHA") || digestAlgorithm.equals("SHA1") || digestAlgorithm.equals("SHA-1") ? SHA1_DIGEST_ALGORITHM_ID : digestAlgorithm.equals("SHA256") || digestAlgorithm.equals("SHA-256") ? SHA256_DIGEST_ALGORITHM_ID : digestAlgorithm.equals("SHA384") || digestAlgorithm.equals("SHA-384") ? SHA384_DIGEST_ALGORITHM_ID : digestAlgorithm.equals("SHA512") || digestAlgorithm.equals("SHA-512") ? SHA512_DIGEST_ALGORITHM_ID : throwNewIllegalArgumentException("Unsupported digest algorithm ["+digestAlgorithm+"]"); } /** * Returns the encoded object ID of the specified encryption algorithm. */ private byte[] encodedEncryptionAlgorithmOid(String encryptionAlgorithm) { return encryptionAlgorithm.equals("RSA") ? RSA_ENCRYPTION_ALGORITHM_ID : encryptionAlgorithm.equals("DH") || encryptionAlgorithm.equals("Diffie-Hellman") ? DH_ENCRYPTION_ALGORITHM_ID : encryptionAlgorithm.equals("DSA") ? DSA_ENCRYPTION_ALGORITHM_ID : encryptionAlgorithm.equals("EC") ? EC_ENCRYPTION_ALGORITHM_ID : throwNewIllegalArgumentException("Unsupported encryption algorithm ["+encryptionAlgorithm+"]"); } /** * Throws an IllegalArgumentException with the given message. */ private byte[] throwNewIllegalArgumentException(String message) { throw new IllegalArgumentException(message); } // /** // * Writes out encoded objects IDs of algorithms. // */ // public static void main(String[] args) // { // ObjectIdentifier[] oids = // { // AlgorithmId.SHA_oid, // AlgorithmId.SHA256_oid, // AlgorithmId.SHA384_oid, // AlgorithmId.SHA512_oid, // // AlgorithmId.RSAEncryption_oid, // AlgorithmId.DH_oid, // AlgorithmId.DSA_oid, // AlgorithmId.EC_oid, // }; // // try // { // Field field = ObjectIdentifier.class.getDeclaredField("encoding"); // field.setAccessible(true); // // for (int oidIndex = 0; oidIndex < oids.length; oidIndex++) // { // ObjectIdentifier oid = oids[oidIndex]; // // System.out.print("{ "); // byte[] bytes = (byte[])field.get(oid); // for (int byteIndex = 0; byteIndex < bytes.length; byteIndex++) // { // byte b = bytes[byteIndex]; // System.out.print((b < 0 ? "(byte)":"") + "0x" + Integer.toHexString(b & 0xff) + ", "); // } // System.out.println("}; // "+oid); // } // } // catch (IllegalAccessException e) // { // e.printStackTrace(); // } // catch (NoSuchFieldException e) // { // e.printStackTrace(); // } // } /** * Reads a certificate with java.security.cert and writes a copy with this class. * For example: * java proguard.io.PKCS7OutputStream META-INF/CERT.RSA copy.RSA * * Useful to debug incorrect writing of certificates. * For example: * openssl asn1parse -inform DER -in META-INF/CERT.RSA * openssl asn1parse -inform DER -in copy.RSA */ public static void main(String[] args) { String inputCertificateFileName = args[0]; String outputCertificateFileName = args[1]; try { System.out.println("Reading ["+inputCertificateFileName+"]"); InputStream inputStream = new FileInputStream(inputCertificateFileName); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); Collection certificates = certificateFactory.generateCertificates(inputStream); int counter = 0; Iterator iterator = certificates.iterator(); while (iterator.hasNext()) { X509Certificate cert509 = (X509Certificate) iterator.next(); System.out.println("IssuerDN: " + cert509.getIssuerDN()); System.out.println("SigAlgName: " + cert509.getSigAlgName()); System.out.println("Signature: " + Arrays.toString(cert509.getSignature())); String fileName = counter == 0 ? outputCertificateFileName : outputCertificateFileName + counter; System.out.println("Writing ["+fileName+"]"); OutputStream outputStream = new FileOutputStream(fileName); DEROutputStream derOutputStream = new DEROutputStream(outputStream); PKCS7OutputStream pkcs7OutputStream = new PKCS7OutputStream(derOutputStream); String sigAlgName = cert509.getSigAlgName(); int withIndex = sigAlgName.indexOf("with"); String digestAlgorithm = withIndex < 0 ? "SHA1" : sigAlgName.substring(0, withIndex); String encryptionAlgorithm = withIndex < 0 ? "RSA" : sigAlgName.substring(withIndex + 4); pkcs7OutputStream.writeSignature(cert509, digestAlgorithm, encryptionAlgorithm, new byte[cert509.getSignature().length]); pkcs7OutputStream.close(); System.out.println(); counter++; } inputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy