| /* |
| * Copyright (C) 2016 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.apksigner.core.internal.apk.v1; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.security.InvalidKeyException; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.PrivateKey; |
| import java.security.PublicKey; |
| import java.security.SignatureException; |
| import java.security.cert.CertificateEncodingException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Base64; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| |
| import org.bouncycastle.asn1.ASN1InputStream; |
| import org.bouncycastle.asn1.ASN1ObjectIdentifier; |
| import org.bouncycastle.asn1.DERNull; |
| import org.bouncycastle.asn1.DEROutputStream; |
| import org.bouncycastle.asn1.x509.AlgorithmIdentifier; |
| import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; |
| import org.bouncycastle.cert.jcajce.JcaCertStore; |
| import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; |
| import org.bouncycastle.cms.CMSException; |
| import org.bouncycastle.cms.CMSProcessableByteArray; |
| import org.bouncycastle.cms.CMSSignatureEncryptionAlgorithmFinder; |
| import org.bouncycastle.cms.CMSSignedData; |
| import org.bouncycastle.cms.CMSSignedDataGenerator; |
| import org.bouncycastle.cms.DefaultCMSSignatureEncryptionAlgorithmFinder; |
| import org.bouncycastle.cms.SignerInfoGeneratorBuilder; |
| import org.bouncycastle.operator.ContentSigner; |
| import org.bouncycastle.operator.OperatorCreationException; |
| import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; |
| import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; |
| |
| import com.android.apksigner.core.internal.jar.ManifestWriter; |
| import com.android.apksigner.core.internal.jar.SignatureFileWriter; |
| import com.android.apksigner.core.internal.util.Pair; |
| |
| /** |
| * APK signer which uses JAR signing (aka v1 signing scheme). |
| * |
| * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File">Signed JAR File</a> |
| */ |
| public abstract class V1SchemeSigner { |
| |
| public static final String MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF"; |
| |
| private static final Attributes.Name ATTRIBUTE_NAME_CREATED_BY = |
| new Attributes.Name("Created-By"); |
| private static final String ATTRIBUTE_DEFALT_VALUE_CREATED_BY = "1.0 (Android apksigner)"; |
| private static final String ATTRIBUTE_VALUE_MANIFEST_VERSION = "1.0"; |
| private static final String ATTRIBUTE_VALUE_SIGNATURE_VERSION = "1.0"; |
| |
| private static final Attributes.Name SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME = |
| new Attributes.Name("X-Android-APK-Signed"); |
| |
| /** |
| * Signer configuration. |
| */ |
| public static class SignerConfig { |
| /** Name. */ |
| public String name; |
| |
| /** Private key. */ |
| public PrivateKey privateKey; |
| |
| /** |
| * Certificates, with the first certificate containing the public key corresponding to |
| * {@link #privateKey}. |
| */ |
| public List<X509Certificate> certificates; |
| |
| /** |
| * Digest algorithm used for the signature. |
| */ |
| public DigestAlgorithm signatureDigestAlgorithm; |
| |
| /** |
| * Digest algorithm used for digests of JAR entries and MANIFEST.MF. |
| */ |
| public DigestAlgorithm contentDigestAlgorithm; |
| } |
| |
| /** Hidden constructor to prevent instantiation. */ |
| private V1SchemeSigner() {} |
| |
| /** |
| * Gets the JAR signing digest algorithm to be used for signing an APK using the provided key. |
| * |
| * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see |
| * AndroidManifest.xml minSdkVersion attribute) |
| * |
| * @throws InvalidKeyException if the provided key is not suitable for signing APKs using |
| * JAR signing (aka v1 signature scheme) |
| */ |
| public static DigestAlgorithm getSuggestedSignatureDigestAlgorithm( |
| PublicKey signingKey, int minSdkVersion) throws InvalidKeyException { |
| String keyAlgorithm = signingKey.getAlgorithm(); |
| if ("RSA".equalsIgnoreCase(keyAlgorithm)) { |
| // Prior to API Level 18, only SHA-1 can be used with RSA. |
| if (minSdkVersion < 18) { |
| return DigestAlgorithm.SHA1; |
| } |
| return DigestAlgorithm.SHA256; |
| } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { |
| // Prior to API Level 21, only SHA-1 can be used with DSA |
| if (minSdkVersion < 21) { |
| return DigestAlgorithm.SHA1; |
| } else { |
| return DigestAlgorithm.SHA256; |
| } |
| } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { |
| if (minSdkVersion < 18) { |
| throw new InvalidKeyException( |
| "ECDSA signatures only supported for minSdkVersion 18 and higher"); |
| } |
| // Prior to API Level 21, only SHA-1 can be used with ECDSA |
| if (minSdkVersion < 21) { |
| return DigestAlgorithm.SHA1; |
| } else { |
| return DigestAlgorithm.SHA256; |
| } |
| } else { |
| throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); |
| } |
| } |
| |
| /** |
| * Returns the JAR signing digest algorithm to be used for JAR entry digests. |
| * |
| * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see |
| * AndroidManifest.xml minSdkVersion attribute) |
| */ |
| public static DigestAlgorithm getSuggestedContentDigestAlgorithm(int minSdkVersion) { |
| return (minSdkVersion >= 18) ? DigestAlgorithm.SHA256 : DigestAlgorithm.SHA1; |
| } |
| |
| /** |
| * Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm. |
| */ |
| public static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm) { |
| String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); |
| try { |
| return MessageDigest.getInstance(jcaAlgorithm); |
| } catch (NoSuchAlgorithmException e) { |
| throw new RuntimeException("Failed to obtain " + jcaAlgorithm + " MessageDigest", e); |
| } |
| } |
| |
| /** |
| * Returns the JCA {@link MessageDigest} algorithm corresponding to the provided digest |
| * algorithm. |
| */ |
| public static String getJcaMessageDigestAlgorithm(DigestAlgorithm digestAlgorithm) { |
| return digestAlgorithm.getJcaMessageDigestAlgorithm(); |
| } |
| |
| /** |
| * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's |
| * manifest. |
| */ |
| public static boolean isJarEntryDigestNeededInManifest(String entryName) { |
| // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File |
| |
| // Entries outside of META-INF must be listed in the manifest. |
| if (!entryName.startsWith("META-INF/")) { |
| return true; |
| } |
| // Entries in subdirectories of META-INF must be listed in the manifest. |
| if (entryName.indexOf('/', "META-INF/".length()) != -1) { |
| return true; |
| } |
| |
| // Ignored file names (case-insensitive) in META-INF directory: |
| // MANIFEST.MF |
| // *.SF |
| // *.RSA |
| // *.DSA |
| // *.EC |
| // SIG-* |
| String fileNameLowerCase = |
| entryName.substring("META-INF/".length()).toLowerCase(Locale.US); |
| if (("manifest.mf".equals(fileNameLowerCase)) |
| || (fileNameLowerCase.endsWith(".sf")) |
| || (fileNameLowerCase.endsWith(".rsa")) |
| || (fileNameLowerCase.endsWith(".dsa")) |
| || (fileNameLowerCase.endsWith(".ec")) |
| || (fileNameLowerCase.startsWith("sig-"))) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of |
| * JAR entries which need to be added to the APK as part of the signature. |
| * |
| * @param signerConfigs signer configurations, one for each signer. At least one signer config |
| * must be provided. |
| * |
| * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or |
| * cannot be used in general |
| * @throws SignatureException if an error occurs when computing digests of generating |
| * signatures |
| */ |
| public static List<Pair<String, byte[]>> sign( |
| List<SignerConfig> signerConfigs, |
| DigestAlgorithm jarEntryDigestAlgorithm, |
| Map<String, byte[]> jarEntryDigests, |
| List<Integer> apkSigningSchemeIds, |
| byte[] sourceManifestBytes) |
| throws InvalidKeyException, CertificateEncodingException, SignatureException { |
| if (signerConfigs.isEmpty()) { |
| throw new IllegalArgumentException("At least one signer config must be provided"); |
| } |
| OutputManifestFile manifest = |
| generateManifestFile(jarEntryDigestAlgorithm, jarEntryDigests, sourceManifestBytes); |
| |
| return signManifest(signerConfigs, jarEntryDigestAlgorithm, apkSigningSchemeIds, manifest); |
| } |
| |
| /** |
| * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of |
| * JAR entries which need to be added to the APK as part of the signature. |
| * |
| * @param signerConfigs signer configurations, one for each signer. At least one signer config |
| * must be provided. |
| * |
| * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or |
| * cannot be used in general |
| * @throws SignatureException if an error occurs when computing digests of generating |
| * signatures |
| */ |
| public static List<Pair<String, byte[]>> signManifest( |
| List<SignerConfig> signerConfigs, |
| DigestAlgorithm digestAlgorithm, |
| List<Integer> apkSigningSchemeIds, |
| OutputManifestFile manifest) |
| throws InvalidKeyException, CertificateEncodingException, SignatureException { |
| if (signerConfigs.isEmpty()) { |
| throw new IllegalArgumentException("At least one signer config must be provided"); |
| } |
| |
| // For each signer output .SF and .(RSA|DSA|EC) file, then output MANIFEST.MF. |
| List<Pair<String, byte[]>> signatureJarEntries = |
| new ArrayList<>(2 * signerConfigs.size() + 1); |
| byte[] sfBytes = |
| generateSignatureFile(apkSigningSchemeIds, digestAlgorithm, manifest); |
| for (SignerConfig signerConfig : signerConfigs) { |
| String signerName = signerConfig.name; |
| byte[] signatureBlock; |
| try { |
| signatureBlock = generateSignatureBlock(signerConfig, sfBytes); |
| } catch (InvalidKeyException e) { |
| throw new InvalidKeyException( |
| "Failed to sign using signer \"" + signerName + "\"", e); |
| } catch (CertificateEncodingException e) { |
| throw new CertificateEncodingException( |
| "Failed to sign using signer \"" + signerName + "\"", e); |
| } catch (SignatureException e) { |
| throw new SignatureException( |
| "Failed to sign using signer \"" + signerName + "\"", e); |
| } |
| signatureJarEntries.add(Pair.of("META-INF/" + signerName + ".SF", sfBytes)); |
| PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); |
| String signatureBlockFileName = |
| "META-INF/" + signerName + "." |
| + publicKey.getAlgorithm().toUpperCase(Locale.US); |
| signatureJarEntries.add( |
| Pair.of(signatureBlockFileName, signatureBlock)); |
| } |
| signatureJarEntries.add(Pair.of(MANIFEST_ENTRY_NAME, manifest.contents)); |
| return signatureJarEntries; |
| } |
| |
| /** |
| * Returns the names of JAR entries which this signer will produce as part of v1 signature. |
| */ |
| public static Set<String> getOutputEntryNames(List<SignerConfig> signerConfigs) { |
| Set<String> result = new HashSet<>(2 * signerConfigs.size() + 1); |
| for (SignerConfig signerConfig : signerConfigs) { |
| String signerName = signerConfig.name; |
| result.add("META-INF/" + signerName + ".SF"); |
| PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); |
| String signatureBlockFileName = |
| "META-INF/" + signerName + "." |
| + publicKey.getAlgorithm().toUpperCase(Locale.US); |
| result.add(signatureBlockFileName); |
| } |
| result.add(MANIFEST_ENTRY_NAME); |
| return result; |
| } |
| |
| /** |
| * Generated and returns the {@code META-INF/MANIFEST.MF} file based on the provided (optional) |
| * input {@code MANIFEST.MF} and digests of JAR entries covered by the manifest. |
| */ |
| public static OutputManifestFile generateManifestFile( |
| DigestAlgorithm jarEntryDigestAlgorithm, |
| Map<String, byte[]> jarEntryDigests, |
| byte[] sourceManifestBytes) { |
| Manifest sourceManifest = null; |
| if (sourceManifestBytes != null) { |
| try { |
| sourceManifest = new Manifest(new ByteArrayInputStream(sourceManifestBytes)); |
| } catch (IOException e) { |
| throw new IllegalArgumentException("Failed to parse source MANIFEST.MF", e); |
| } |
| } |
| ByteArrayOutputStream manifestOut = new ByteArrayOutputStream(); |
| Attributes mainAttrs = new Attributes(); |
| // Copy the main section from the source manifest (if provided). Otherwise use defaults. |
| if (sourceManifest != null) { |
| mainAttrs.putAll(sourceManifest.getMainAttributes()); |
| } else { |
| mainAttrs.put(Attributes.Name.MANIFEST_VERSION, ATTRIBUTE_VALUE_MANIFEST_VERSION); |
| mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, ATTRIBUTE_DEFALT_VALUE_CREATED_BY); |
| } |
| |
| try { |
| ManifestWriter.writeMainSection(manifestOut, mainAttrs); |
| } catch (IOException e) { |
| throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); |
| } |
| |
| List<String> sortedEntryNames = new ArrayList<>(jarEntryDigests.keySet()); |
| Collections.sort(sortedEntryNames); |
| SortedMap<String, byte[]> invidualSectionsContents = new TreeMap<>(); |
| String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm); |
| for (String entryName : sortedEntryNames) { |
| byte[] entryDigest = jarEntryDigests.get(entryName); |
| Attributes entryAttrs = new Attributes(); |
| entryAttrs.putValue( |
| entryDigestAttributeName, |
| Base64.getEncoder().encodeToString(entryDigest)); |
| ByteArrayOutputStream sectionOut = new ByteArrayOutputStream(); |
| byte[] sectionBytes; |
| try { |
| ManifestWriter.writeIndividualSection(sectionOut, entryName, entryAttrs); |
| sectionBytes = sectionOut.toByteArray(); |
| manifestOut.write(sectionBytes); |
| } catch (IOException e) { |
| throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); |
| } |
| invidualSectionsContents.put(entryName, sectionBytes); |
| } |
| |
| OutputManifestFile result = new OutputManifestFile(); |
| result.contents = manifestOut.toByteArray(); |
| result.mainSectionAttributes = mainAttrs; |
| result.individualSectionsContents = invidualSectionsContents; |
| return result; |
| } |
| |
| public static class OutputManifestFile { |
| public byte[] contents; |
| public SortedMap<String, byte[]> individualSectionsContents; |
| public Attributes mainSectionAttributes; |
| } |
| |
| private static byte[] generateSignatureFile( |
| List<Integer> apkSignatureSchemeIds, |
| DigestAlgorithm manifestDigestAlgorithm, |
| OutputManifestFile manifest) { |
| Manifest sf = new Manifest(); |
| Attributes mainAttrs = sf.getMainAttributes(); |
| mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION); |
| mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, ATTRIBUTE_DEFALT_VALUE_CREATED_BY); |
| if (!apkSignatureSchemeIds.isEmpty()) { |
| // Add APK Signature Scheme v2 (and newer) signature stripping protection. |
| // This attribute indicates that this APK is supposed to have been signed using one or |
| // more APK-specific signature schemes in addition to the standard JAR signature scheme |
| // used by this code. APK signature verifier should reject the APK if it does not |
| // contain a signature for the signature scheme the verifier prefers out of this set. |
| StringBuilder attrValue = new StringBuilder(); |
| for (int id : apkSignatureSchemeIds) { |
| if (attrValue.length() > 0) { |
| attrValue.append(", "); |
| } |
| attrValue.append(String.valueOf(id)); |
| } |
| mainAttrs.put( |
| SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME, |
| attrValue.toString()); |
| } |
| |
| // Add main attribute containing the digest of MANIFEST.MF. |
| MessageDigest md = getMessageDigestInstance(manifestDigestAlgorithm); |
| mainAttrs.putValue( |
| getManifestDigestAttributeName(manifestDigestAlgorithm), |
| Base64.getEncoder().encodeToString(md.digest(manifest.contents))); |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| try { |
| SignatureFileWriter.writeMainSection(out, mainAttrs); |
| } catch (IOException e) { |
| throw new RuntimeException("Failed to write in-memory .SF file", e); |
| } |
| String entryDigestAttributeName = getEntryDigestAttributeName(manifestDigestAlgorithm); |
| for (Map.Entry<String, byte[]> manifestSection |
| : manifest.individualSectionsContents.entrySet()) { |
| String sectionName = manifestSection.getKey(); |
| byte[] sectionContents = manifestSection.getValue(); |
| byte[] sectionDigest = md.digest(sectionContents); |
| Attributes attrs = new Attributes(); |
| attrs.putValue( |
| entryDigestAttributeName, |
| Base64.getEncoder().encodeToString(sectionDigest)); |
| |
| try { |
| SignatureFileWriter.writeIndividualSection(out, sectionName, attrs); |
| } catch (IOException e) { |
| throw new RuntimeException("Failed to write in-memory .SF file", e); |
| } |
| } |
| |
| // A bug in the java.util.jar implementation of Android platforms up to version 1.6 will |
| // cause a spurious IOException to be thrown if the length of the signature file is a |
| // multiple of 1024 bytes. As a workaround, add an extra CRLF in this case. |
| if ((out.size() > 0) && ((out.size() % 1024) == 0)) { |
| try { |
| SignatureFileWriter.writeSectionDelimiter(out); |
| } catch (IOException e) { |
| throw new RuntimeException("Failed to write to ByteArrayOutputStream", e); |
| } |
| } |
| |
| return out.toByteArray(); |
| } |
| |
| private static byte[] generateSignatureBlock( |
| SignerConfig signerConfig, byte[] signatureFileBytes) |
| throws InvalidKeyException, CertificateEncodingException, SignatureException { |
| JcaCertStore certs = new JcaCertStore(signerConfig.certificates); |
| X509Certificate signerCert = signerConfig.certificates.get(0); |
| String jcaSignatureAlgorithm = |
| getJcaSignatureAlgorithm( |
| signerCert.getPublicKey(), signerConfig.signatureDigestAlgorithm); |
| try { |
| ContentSigner signer = |
| new JcaContentSignerBuilder(jcaSignatureAlgorithm) |
| .build(signerConfig.privateKey); |
| CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); |
| gen.addSignerInfoGenerator( |
| new SignerInfoGeneratorBuilder( |
| new JcaDigestCalculatorProviderBuilder().build(), |
| SignerInfoSignatureAlgorithmFinder.INSTANCE) |
| .setDirectSignature(true) |
| .build(signer, new JcaX509CertificateHolder(signerCert))); |
| gen.addCertificates(certs); |
| |
| CMSSignedData sigData = |
| gen.generate(new CMSProcessableByteArray(signatureFileBytes), false); |
| |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) { |
| DEROutputStream dos = new DEROutputStream(out); |
| dos.writeObject(asn1.readObject()); |
| } |
| return out.toByteArray(); |
| } catch (OperatorCreationException | CMSException | IOException e) { |
| throw new SignatureException("Failed to generate signature", e); |
| } |
| } |
| |
| /** |
| * Chooser of SignatureAlgorithm for PKCS #7 CMS SignerInfo. |
| */ |
| private static class SignerInfoSignatureAlgorithmFinder |
| implements CMSSignatureEncryptionAlgorithmFinder { |
| private static final SignerInfoSignatureAlgorithmFinder INSTANCE = |
| new SignerInfoSignatureAlgorithmFinder(); |
| |
| private static final AlgorithmIdentifier DSA = |
| new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, DERNull.INSTANCE); |
| |
| private final CMSSignatureEncryptionAlgorithmFinder mDefault = |
| new DefaultCMSSignatureEncryptionAlgorithmFinder(); |
| |
| @Override |
| public AlgorithmIdentifier findEncryptionAlgorithm(AlgorithmIdentifier id) { |
| // Use the default chooser, but replace dsaWithSha1 with dsa. This is because "dsa" is |
| // accepted by any Android platform whereas "dsaWithSha1" is accepted only since |
| // API Level 9. |
| id = mDefault.findEncryptionAlgorithm(id); |
| if (id != null) { |
| ASN1ObjectIdentifier oid = id.getAlgorithm(); |
| if (X9ObjectIdentifiers.id_dsa_with_sha1.equals(oid)) { |
| return DSA; |
| } |
| } |
| |
| return id; |
| } |
| } |
| |
| private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) { |
| switch (digestAlgorithm) { |
| case SHA1: |
| return "SHA1-Digest"; |
| case SHA256: |
| return "SHA-256-Digest"; |
| default: |
| throw new IllegalArgumentException( |
| "Unexpected content digest algorithm: " + digestAlgorithm); |
| } |
| } |
| |
| private static String getManifestDigestAttributeName(DigestAlgorithm digestAlgorithm) { |
| switch (digestAlgorithm) { |
| case SHA1: |
| return "SHA1-Digest-Manifest"; |
| case SHA256: |
| return "SHA-256-Digest-Manifest"; |
| default: |
| throw new IllegalArgumentException( |
| "Unexpected content digest algorithm: " + digestAlgorithm); |
| } |
| } |
| |
| private static String getJcaSignatureAlgorithm( |
| PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException { |
| String keyAlgorithm = publicKey.getAlgorithm(); |
| String digestPrefixForSigAlg; |
| switch (digestAlgorithm) { |
| case SHA1: |
| digestPrefixForSigAlg = "SHA1"; |
| break; |
| case SHA256: |
| digestPrefixForSigAlg = "SHA256"; |
| break; |
| default: |
| throw new IllegalArgumentException( |
| "Unexpected digest algorithm: " + digestAlgorithm); |
| } |
| if ("RSA".equalsIgnoreCase(keyAlgorithm)) { |
| return digestPrefixForSigAlg + "withRSA"; |
| } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { |
| return digestPrefixForSigAlg + "withDSA"; |
| } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { |
| return digestPrefixForSigAlg + "withECDSA"; |
| } else { |
| throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); |
| } |
| } |
| } |