Merge changes Ie0408ecc,I80bbbd88
* changes: Add OWNERS for VerityUtilsTest.java Provide a method to verify a PKCS#7 signature
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "FrameworksCoreTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.internal.security."
|
||||
},
|
||||
{
|
||||
"include-annotation": "android.platform.test.annotations.Presubmit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ApkVerityTest",
|
||||
"file_patterns": ["VerityUtils\\.java"]
|
||||
|
||||
@@ -23,10 +23,28 @@ import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
||||
import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import com.android.internal.org.bouncycastle.cms.CMSException;
|
||||
import com.android.internal.org.bouncycastle.cms.CMSProcessableByteArray;
|
||||
import com.android.internal.org.bouncycastle.cms.CMSSignedData;
|
||||
import com.android.internal.org.bouncycastle.cms.SignerInformation;
|
||||
import com.android.internal.org.bouncycastle.cms.SignerInformationVerifier;
|
||||
import com.android.internal.org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||
import com.android.internal.org.bouncycastle.operator.OperatorCreationException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/** Provides fsverity related operations. */
|
||||
public abstract class VerityUtils {
|
||||
@@ -90,6 +108,91 @@ public abstract class VerityUtils {
|
||||
return (retval == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the signature over the fs-verity digest using the provided certificate.
|
||||
*
|
||||
* This method should only be used by any existing fs-verity use cases that require
|
||||
* PKCS#7 signature verification, if backward compatibility is necessary.
|
||||
*
|
||||
* Since PKCS#7 is too flexible, for the current specific need, only specific configuration
|
||||
* will be accepted:
|
||||
* <ul>
|
||||
* <li>Must use SHA256 as the digest algorithm
|
||||
* <li>Must use rsaEncryption as signature algorithm
|
||||
* <li>Must be detached / without content
|
||||
* <li>Must not include any signed or unsigned attributes
|
||||
* </ul>
|
||||
*
|
||||
* It is up to the caller to provide an appropriate/trusted certificate.
|
||||
*
|
||||
* @param signatureBlock byte array of a PKCS#7 detached signature
|
||||
* @param digest fs-verity digest with the common configuration using sha256
|
||||
* @param derCertInputStream an input stream of a X.509 certificate in DER
|
||||
* @return whether the verification succeeds
|
||||
*/
|
||||
public static boolean verifyPkcs7DetachedSignature(@NonNull byte[] signatureBlock,
|
||||
@NonNull byte[] digest, @NonNull InputStream derCertInputStream) {
|
||||
if (digest.length != 32) {
|
||||
Slog.w(TAG, "Only sha256 is currently supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
CMSSignedData signedData = new CMSSignedData(
|
||||
new CMSProcessableByteArray(toFormattedDigest(digest)),
|
||||
signatureBlock);
|
||||
|
||||
if (!signedData.isDetachedSignature()) {
|
||||
Slog.w(TAG, "Expect only detached siganture");
|
||||
return false;
|
||||
}
|
||||
if (!signedData.getCertificates().getMatches(null).isEmpty()) {
|
||||
Slog.w(TAG, "Expect no certificate in signature");
|
||||
return false;
|
||||
}
|
||||
if (!signedData.getCRLs().getMatches(null).isEmpty()) {
|
||||
Slog.w(TAG, "Expect no CRL in signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
X509Certificate trustedCert = (X509Certificate) CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(derCertInputStream);
|
||||
SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder()
|
||||
.build(trustedCert);
|
||||
|
||||
// Verify any signature with the trusted certificate.
|
||||
for (SignerInformation si : signedData.getSignerInfos().getSigners()) {
|
||||
// To be the most strict while dealing with the complicated PKCS#7 signature, reject
|
||||
// everything we don't need.
|
||||
if (si.getSignedAttributes() != null && si.getSignedAttributes().size() > 0) {
|
||||
Slog.w(TAG, "Unexpected signed attributes");
|
||||
return false;
|
||||
}
|
||||
if (si.getUnsignedAttributes() != null && si.getUnsignedAttributes().size() > 0) {
|
||||
Slog.w(TAG, "Unexpected unsigned attributes");
|
||||
return false;
|
||||
}
|
||||
if (!NISTObjectIdentifiers.id_sha256.getId().equals(si.getDigestAlgOID())) {
|
||||
Slog.w(TAG, "Unsupported digest algorithm OID: " + si.getDigestAlgOID());
|
||||
return false;
|
||||
}
|
||||
if (!PKCSObjectIdentifiers.rsaEncryption.getId().equals(si.getEncryptionAlgOID())) {
|
||||
Slog.w(TAG, "Unsupported encryption algorithm OID: "
|
||||
+ si.getEncryptionAlgOID());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (si.verify(verifier)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (CertificateException | CMSException | OperatorCreationException e) {
|
||||
Slog.w(TAG, "Error occurred during the PKCS#7 signature verification", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fs-verity digest for the file if enabled, otherwise returns null. The digest is a
|
||||
* hash of root hash of fs-verity's Merkle tree with extra metadata.
|
||||
@@ -110,6 +213,19 @@ public abstract class VerityUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public static byte[] toFormattedDigest(byte[] digest) {
|
||||
// Construct fsverity_formatted_digest used in fs-verity's built-in signature verification.
|
||||
ByteBuffer buffer = ByteBuffer.allocate(12 + digest.length); // struct size + sha256 size
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.put("FSVerity".getBytes(StandardCharsets.US_ASCII));
|
||||
buffer.putShort((short) 1); // FS_VERITY_HASH_ALG_SHA256
|
||||
buffer.putShort((short) digest.length);
|
||||
buffer.put(digest);
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
private static native int enableFsverityNative(@NonNull String filePath,
|
||||
@NonNull byte[] pkcs7Signature);
|
||||
private static native int measureFsverityNative(@NonNull String filePath,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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.android.internal.security;
|
||||
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/** A wrapper class of ContentSigner */
|
||||
class ContentSignerWrapper implements ContentSigner {
|
||||
private final ContentSigner mSigner;
|
||||
|
||||
ContentSignerWrapper(ContentSigner wrapped) {
|
||||
mSigner = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlgorithmIdentifier getAlgorithmIdentifier() {
|
||||
return mSigner.getAlgorithmIdentifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return mSigner.getOutputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature() {
|
||||
return mSigner.getSignature();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Bug component: 36824
|
||||
|
||||
per-file VerityUtilsTest.java = file:platform/system/security:/fsverity/OWNERS
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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.android.internal.security;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encoding;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cms.CMSException;
|
||||
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||
import org.bouncycastle.cms.CMSSignedData;
|
||||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||
import org.bouncycastle.cms.SignerInfoGenerator;
|
||||
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.DigestCalculator;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
@Presubmit
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class VerityUtilsTest {
|
||||
private static final byte[] SAMPLE_DIGEST = "12345678901234567890123456789012".getBytes();
|
||||
private static final byte[] FORMATTED_SAMPLE_DIGEST = toFormattedDigest(SAMPLE_DIGEST);
|
||||
|
||||
KeyPair mKeyPair;
|
||||
ContentSigner mContentSigner;
|
||||
X509CertificateHolder mCertificateHolder;
|
||||
byte[] mCertificateDerEncoded;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
||||
mContentSigner = newFsverityContentSigner(mKeyPair.getPrivate());
|
||||
mCertificateHolder =
|
||||
newX509CertificateHolder(mContentSigner, mKeyPair.getPublic(), "Someone");
|
||||
mCertificateDerEncoded = mCertificateHolder.getEncoded();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyAcceptCorrectDigest() throws Exception {
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
|
||||
|
||||
byte[] anotherDigest = Arrays.copyOf(SAMPLE_DIGEST, SAMPLE_DIGEST.length);
|
||||
anotherDigest[0] ^= (byte) 1;
|
||||
|
||||
assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
assertFalse(verifySignature(pkcs7Signature, anotherDigest, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDigestWithWrongSize() throws Exception {
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
|
||||
assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
|
||||
byte[] digestTooShort = Arrays.copyOfRange(SAMPLE_DIGEST, 0, SAMPLE_DIGEST.length - 1);
|
||||
assertFalse(verifySignature(pkcs7Signature, digestTooShort, mCertificateDerEncoded));
|
||||
|
||||
byte[] digestTooLong = Arrays.copyOfRange(SAMPLE_DIGEST, 0, SAMPLE_DIGEST.length + 1);
|
||||
assertFalse(verifySignature(pkcs7Signature, digestTooLong, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyAcceptGoodSignature() throws Exception {
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
|
||||
|
||||
byte[] anotherDigest = Arrays.copyOf(SAMPLE_DIGEST, SAMPLE_DIGEST.length);
|
||||
anotherDigest[0] ^= (byte) 1;
|
||||
byte[] anotherPkcs7Signature =
|
||||
generatePkcs7Signature(
|
||||
mContentSigner, mCertificateHolder, toFormattedDigest(anotherDigest));
|
||||
|
||||
assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
assertFalse(verifySignature(anotherPkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlyValidCertCanVerify() throws Exception {
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
|
||||
|
||||
var wrongKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
|
||||
var wrongContentSigner = newFsverityContentSigner(wrongKeyPair.getPrivate());
|
||||
var wrongCertificateHolder =
|
||||
newX509CertificateHolder(wrongContentSigner, wrongKeyPair.getPublic(), "Not Me");
|
||||
byte[] wrongCertificateDerEncoded = wrongCertificateHolder.getEncoded();
|
||||
|
||||
assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, wrongCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectSignatureWithContent() throws Exception {
|
||||
CMSSignedDataGenerator generator =
|
||||
newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
|
||||
byte[] pkcs7SignatureNonDetached =
|
||||
generatePkcs7SignatureInternal(
|
||||
generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ true);
|
||||
|
||||
assertFalse(
|
||||
verifySignature(pkcs7SignatureNonDetached, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectSignatureWithCertificate() throws Exception {
|
||||
CMSSignedDataGenerator generator =
|
||||
newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
|
||||
generator.addCertificate(mCertificateHolder);
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7SignatureInternal(
|
||||
generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
|
||||
|
||||
assertFalse(
|
||||
verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Ignore("No easy way to construct test data")
|
||||
@Test
|
||||
public void testRejectSignatureWithCRL() throws Exception {
|
||||
CMSSignedDataGenerator generator =
|
||||
newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
|
||||
|
||||
// The current bouncycastle version does not have an easy way to generate a CRL.
|
||||
// TODO: enable the test once this is doable, e.g. with X509v2CRLBuilder.
|
||||
// generator.addCRL(new X509CRLHolder(CertificateList.getInstance(new DERSequence(...))));
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7SignatureInternal(
|
||||
generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
|
||||
|
||||
assertFalse(
|
||||
verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectUnsupportedSignatureAlgorithms() throws Exception {
|
||||
var contentSigner = newFsverityContentSigner(mKeyPair.getPrivate(), "MD5withRSA", null);
|
||||
var certificateHolder =
|
||||
newX509CertificateHolder(contentSigner, mKeyPair.getPublic(), "Someone");
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7Signature(contentSigner, certificateHolder, FORMATTED_SAMPLE_DIGEST);
|
||||
|
||||
assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, certificateHolder.getEncoded()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectUnsupportedDigestAlgorithm() throws Exception {
|
||||
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||
generator.addSignerInfoGenerator(
|
||||
newSignerInfoGenerator(
|
||||
mContentSigner,
|
||||
mCertificateHolder,
|
||||
OIWObjectIdentifiers.idSHA1,
|
||||
true)); // directSignature
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7SignatureInternal(
|
||||
generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
|
||||
|
||||
assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectAnySignerInfoAttributes() throws Exception {
|
||||
var generator = new CMSSignedDataGenerator();
|
||||
generator.addSignerInfoGenerator(
|
||||
newSignerInfoGenerator(
|
||||
mContentSigner,
|
||||
mCertificateHolder,
|
||||
NISTObjectIdentifiers.id_sha256,
|
||||
false)); // directSignature
|
||||
byte[] pkcs7Signature =
|
||||
generatePkcs7SignatureInternal(
|
||||
generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
|
||||
|
||||
assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
|
||||
}
|
||||
|
||||
private static boolean verifySignature(
|
||||
byte[] pkcs7Signature, byte[] fsverityDigest, byte[] certificateDerEncoded) {
|
||||
return VerityUtils.verifyPkcs7DetachedSignature(
|
||||
pkcs7Signature, fsverityDigest, new ByteArrayInputStream(certificateDerEncoded));
|
||||
}
|
||||
|
||||
private static byte[] toFormattedDigest(byte[] digest) {
|
||||
return VerityUtils.toFormattedDigest(digest);
|
||||
}
|
||||
|
||||
private static byte[] generatePkcs7Signature(
|
||||
ContentSigner contentSigner, X509CertificateHolder certificateHolder, byte[] signedData)
|
||||
throws IOException, CMSException, OperatorCreationException {
|
||||
CMSSignedDataGenerator generator =
|
||||
newFsveritySignedDataGenerator(contentSigner, certificateHolder);
|
||||
return generatePkcs7SignatureInternal(generator, signedData, /* encapsulate */ false);
|
||||
}
|
||||
|
||||
private static byte[] generatePkcs7SignatureInternal(
|
||||
CMSSignedDataGenerator generator, byte[] signedData, boolean encapsulate)
|
||||
throws IOException, CMSException, OperatorCreationException {
|
||||
CMSSignedData cmsSignedData =
|
||||
generator.generate(new CMSProcessableByteArray(signedData), encapsulate);
|
||||
return cmsSignedData.toASN1Structure().getEncoded(ASN1Encoding.DL);
|
||||
}
|
||||
|
||||
private static CMSSignedDataGenerator newFsveritySignedDataGenerator(
|
||||
ContentSigner contentSigner, X509CertificateHolder certificateHolder)
|
||||
throws IOException, CMSException, OperatorCreationException {
|
||||
var generator = new CMSSignedDataGenerator();
|
||||
generator.addSignerInfoGenerator(
|
||||
newSignerInfoGenerator(
|
||||
contentSigner,
|
||||
certificateHolder,
|
||||
NISTObjectIdentifiers.id_sha256,
|
||||
true)); // directSignature
|
||||
return generator;
|
||||
}
|
||||
|
||||
private static SignerInfoGenerator newSignerInfoGenerator(
|
||||
ContentSigner contentSigner,
|
||||
X509CertificateHolder certificateHolder,
|
||||
ASN1ObjectIdentifier digestAlgorithmId,
|
||||
boolean directSignature)
|
||||
throws IOException, CMSException, OperatorCreationException {
|
||||
var provider =
|
||||
new BcDigestCalculatorProvider() {
|
||||
/**
|
||||
* Allow the caller to override the digest algorithm, especially when the
|
||||
* default does not work (i.e. BcDigestCalculatorProvider could return null).
|
||||
*
|
||||
* <p>For example, the current fs-verity signature has to use rsaEncryption for
|
||||
* the signature algorithm, but BcDigestCalculatorProvider will return null,
|
||||
* thus we need a way to override.
|
||||
*
|
||||
* <p>TODO: After bouncycastle 1.70, we can remove this override and just use
|
||||
* {@code JcaSignerInfoGeneratorBuilder#setContentDigest}.
|
||||
*/
|
||||
@Override
|
||||
public DigestCalculator get(AlgorithmIdentifier algorithm)
|
||||
throws OperatorCreationException {
|
||||
return super.get(new AlgorithmIdentifier(digestAlgorithmId));
|
||||
}
|
||||
};
|
||||
var builder =
|
||||
new JcaSignerInfoGeneratorBuilder(provider).setDirectSignature(directSignature);
|
||||
return builder.build(contentSigner, certificateHolder);
|
||||
}
|
||||
|
||||
private static ContentSigner newFsverityContentSigner(PrivateKey privateKey)
|
||||
throws OperatorCreationException {
|
||||
// fs-verity expects the signature to have rsaEncryption as the exact algorithm, so
|
||||
// override the default.
|
||||
return newFsverityContentSigner(
|
||||
privateKey, "SHA256withRSA", PKCSObjectIdentifiers.rsaEncryption);
|
||||
}
|
||||
|
||||
private static ContentSigner newFsverityContentSigner(
|
||||
PrivateKey privateKey,
|
||||
String signatureAlgorithm,
|
||||
ASN1ObjectIdentifier signatureAlgorithmIdOverride)
|
||||
throws OperatorCreationException {
|
||||
if (signatureAlgorithmIdOverride != null) {
|
||||
return new ContentSignerWrapper(
|
||||
new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey)) {
|
||||
@Override
|
||||
public AlgorithmIdentifier getAlgorithmIdentifier() {
|
||||
return new AlgorithmIdentifier(signatureAlgorithmIdOverride);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static X509CertificateHolder newX509CertificateHolder(
|
||||
ContentSigner contentSigner, PublicKey publicKey, String name) {
|
||||
// Time doesn't really matter, as we only care about the key.
|
||||
Instant now = Instant.now();
|
||||
|
||||
return new X509v3CertificateBuilder(
|
||||
new X500Name("CN=Issuer " + name),
|
||||
/* serial= */ BigInteger.valueOf(now.getEpochSecond()),
|
||||
new Date(now.minus(Duration.ofDays(1)).toEpochMilli()),
|
||||
new Date(now.plus(Duration.ofDays(1)).toEpochMilli()),
|
||||
new X500Name("CN=Subject " + name),
|
||||
SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()))
|
||||
.build(contentSigner);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user