| /* |
| * Copyright (C) 2017 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.apksig.internal.pkcs7; |
| |
| import static com.android.apksig.internal.asn1.Asn1DerEncoder.ASN1_DER_NULL; |
| import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA1; |
| import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA256; |
| import static com.android.apksig.internal.oid.OidConstants.OID_SIG_DSA; |
| import static com.android.apksig.internal.oid.OidConstants.OID_SIG_EC_PUBLIC_KEY; |
| import static com.android.apksig.internal.oid.OidConstants.OID_SIG_RSA; |
| import static com.android.apksig.internal.oid.OidConstants.OID_SIG_SHA256_WITH_DSA; |
| import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_DIGEST_ALG; |
| import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_SIGNATURE_ALG; |
| |
| import com.android.apksig.internal.apk.v1.DigestAlgorithm; |
| import com.android.apksig.internal.asn1.Asn1Class; |
| import com.android.apksig.internal.asn1.Asn1Field; |
| import com.android.apksig.internal.asn1.Asn1OpaqueObject; |
| import com.android.apksig.internal.asn1.Asn1Type; |
| import com.android.apksig.internal.util.Pair; |
| |
| import java.security.InvalidKeyException; |
| import java.security.PublicKey; |
| import java.security.Signature; |
| import java.security.SignatureException; |
| |
| /** |
| * PKCS #7 {@code AlgorithmIdentifier} as specified in RFC 5652. |
| */ |
| @Asn1Class(type = Asn1Type.SEQUENCE) |
| public class AlgorithmIdentifier { |
| |
| @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) |
| public String algorithm; |
| |
| @Asn1Field(index = 1, type = Asn1Type.ANY, optional = true) |
| public Asn1OpaqueObject parameters; |
| |
| public AlgorithmIdentifier() {} |
| |
| public AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters) { |
| this.algorithm = algorithmOid; |
| this.parameters = parameters; |
| } |
| |
| /** |
| * Returns the PKCS #7 {@code DigestAlgorithm} to use when signing using the specified digest |
| * algorithm. |
| */ |
| public static AlgorithmIdentifier getSignerInfoDigestAlgorithmOid( |
| DigestAlgorithm digestAlgorithm) { |
| switch (digestAlgorithm) { |
| case SHA1: |
| return new AlgorithmIdentifier(OID_DIGEST_SHA1, ASN1_DER_NULL); |
| case SHA256: |
| return new AlgorithmIdentifier(OID_DIGEST_SHA256, ASN1_DER_NULL); |
| } |
| throw new IllegalArgumentException("Unsupported digest algorithm: " + digestAlgorithm); |
| } |
| |
| /** |
| * Returns the JCA {@link Signature} algorithm and PKCS #7 {@code SignatureAlgorithm} to use |
| * when signing with the specified key and digest algorithm. |
| */ |
| public static Pair<String, AlgorithmIdentifier> getSignerInfoSignatureAlgorithm( |
| PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException { |
| String keyAlgorithm = publicKey.getAlgorithm(); |
| String jcaDigestPrefixForSigAlg; |
| switch (digestAlgorithm) { |
| case SHA1: |
| jcaDigestPrefixForSigAlg = "SHA1"; |
| break; |
| case SHA256: |
| jcaDigestPrefixForSigAlg = "SHA256"; |
| break; |
| default: |
| throw new IllegalArgumentException( |
| "Unexpected digest algorithm: " + digestAlgorithm); |
| } |
| if ("RSA".equalsIgnoreCase(keyAlgorithm)) { |
| return Pair.of( |
| jcaDigestPrefixForSigAlg + "withRSA", |
| new AlgorithmIdentifier(OID_SIG_RSA, ASN1_DER_NULL)); |
| } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { |
| AlgorithmIdentifier sigAlgId; |
| switch (digestAlgorithm) { |
| case SHA1: |
| sigAlgId = |
| new AlgorithmIdentifier(OID_SIG_DSA, ASN1_DER_NULL); |
| break; |
| case SHA256: |
| // DSA signatures with SHA-256 in SignedData are accepted by Android API Level |
| // 21 and higher. However, there are two ways to specify their SignedData |
| // SignatureAlgorithm: dsaWithSha256 (2.16.840.1.101.3.4.3.2) and |
| // dsa (1.2.840.10040.4.1). The latter works only on API Level 22+. Thus, we use |
| // the former. |
| sigAlgId = |
| new AlgorithmIdentifier(OID_SIG_SHA256_WITH_DSA, ASN1_DER_NULL); |
| break; |
| default: |
| throw new IllegalArgumentException( |
| "Unexpected digest algorithm: " + digestAlgorithm); |
| } |
| return Pair.of(jcaDigestPrefixForSigAlg + "withDSA", sigAlgId); |
| } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { |
| return Pair.of( |
| jcaDigestPrefixForSigAlg + "withECDSA", |
| new AlgorithmIdentifier(OID_SIG_EC_PUBLIC_KEY, ASN1_DER_NULL)); |
| } else { |
| throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); |
| } |
| } |
| |
| public static String getJcaSignatureAlgorithm( |
| String digestAlgorithmOid, |
| String signatureAlgorithmOid) throws SignatureException { |
| // First check whether the signature algorithm OID alone is sufficient |
| String result = OID_TO_JCA_SIGNATURE_ALG.get(signatureAlgorithmOid); |
| if (result != null) { |
| return result; |
| } |
| |
| // Signature algorithm OID alone is insufficient. Need to combine digest algorithm OID |
| // with signature algorithm OID. |
| String suffix; |
| if (OID_SIG_RSA.equals(signatureAlgorithmOid)) { |
| suffix = "RSA"; |
| } else if (OID_SIG_DSA.equals(signatureAlgorithmOid)) { |
| suffix = "DSA"; |
| } else if (OID_SIG_EC_PUBLIC_KEY.equals(signatureAlgorithmOid)) { |
| suffix = "ECDSA"; |
| } else { |
| throw new SignatureException( |
| "Unsupported JCA Signature algorithm" |
| + " . Digest algorithm: " + digestAlgorithmOid |
| + ", signature algorithm: " + signatureAlgorithmOid); |
| } |
| String jcaDigestAlg = getJcaDigestAlgorithm(digestAlgorithmOid); |
| // Canonical name for SHA-1 with ... is SHA1with, rather than SHA1. Same for all other |
| // SHA algorithms. |
| if (jcaDigestAlg.startsWith("SHA-")) { |
| jcaDigestAlg = "SHA" + jcaDigestAlg.substring("SHA-".length()); |
| } |
| return jcaDigestAlg + "with" + suffix; |
| } |
| |
| public static String getJcaDigestAlgorithm(String oid) |
| throws SignatureException { |
| String result = OID_TO_JCA_DIGEST_ALG.get(oid); |
| if (result == null) { |
| throw new SignatureException("Unsupported digest algorithm: " + oid); |
| } |
| return result; |
| } |
| } |