1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.apksig.internal.pkcs7;
18 
19 import static com.android.apksig.internal.asn1.Asn1DerEncoder.ASN1_DER_NULL;
20 import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA1;
21 import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA256;
22 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_DSA;
23 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_EC_PUBLIC_KEY;
24 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_RSA;
25 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_SHA256_WITH_DSA;
26 import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_DIGEST_ALG;
27 import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_SIGNATURE_ALG;
28 
29 import com.android.apksig.internal.apk.v1.DigestAlgorithm;
30 import com.android.apksig.internal.asn1.Asn1Class;
31 import com.android.apksig.internal.asn1.Asn1Field;
32 import com.android.apksig.internal.asn1.Asn1OpaqueObject;
33 import com.android.apksig.internal.asn1.Asn1Type;
34 import com.android.apksig.internal.util.Pair;
35 
36 import java.security.InvalidKeyException;
37 import java.security.PublicKey;
38 import java.security.Signature;
39 import java.security.SignatureException;
40 
41 /**
42  * PKCS #7 {@code AlgorithmIdentifier} as specified in RFC 5652.
43  */
44 @Asn1Class(type = Asn1Type.SEQUENCE)
45 public class AlgorithmIdentifier {
46 
47     @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER)
48     public String algorithm;
49 
50     @Asn1Field(index = 1, type = Asn1Type.ANY, optional = true)
51     public Asn1OpaqueObject parameters;
52 
AlgorithmIdentifier()53     public AlgorithmIdentifier() {}
54 
AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters)55     public AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters) {
56         this.algorithm = algorithmOid;
57         this.parameters = parameters;
58     }
59 
60     /**
61      * Returns the PKCS #7 {@code DigestAlgorithm} to use when signing using the specified digest
62      * algorithm.
63      */
getSignerInfoDigestAlgorithmOid( DigestAlgorithm digestAlgorithm)64     public static AlgorithmIdentifier getSignerInfoDigestAlgorithmOid(
65             DigestAlgorithm digestAlgorithm) {
66         switch (digestAlgorithm) {
67             case SHA1:
68                 return new AlgorithmIdentifier(OID_DIGEST_SHA1, ASN1_DER_NULL);
69             case SHA256:
70                 return new AlgorithmIdentifier(OID_DIGEST_SHA256, ASN1_DER_NULL);
71         }
72         throw new IllegalArgumentException("Unsupported digest algorithm: " + digestAlgorithm);
73     }
74 
75     /**
76      * Returns the JCA {@link Signature} algorithm and PKCS #7 {@code SignatureAlgorithm} to use
77      * when signing with the specified key and digest algorithm.
78      */
getSignerInfoSignatureAlgorithm( PublicKey publicKey, DigestAlgorithm digestAlgorithm)79     public static Pair<String, AlgorithmIdentifier> getSignerInfoSignatureAlgorithm(
80             PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException {
81         String keyAlgorithm = publicKey.getAlgorithm();
82         String jcaDigestPrefixForSigAlg;
83         switch (digestAlgorithm) {
84             case SHA1:
85                 jcaDigestPrefixForSigAlg = "SHA1";
86                 break;
87             case SHA256:
88                 jcaDigestPrefixForSigAlg = "SHA256";
89                 break;
90             default:
91                 throw new IllegalArgumentException(
92                         "Unexpected digest algorithm: " + digestAlgorithm);
93         }
94         if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
95             return Pair.of(
96                     jcaDigestPrefixForSigAlg + "withRSA",
97                     new AlgorithmIdentifier(OID_SIG_RSA, ASN1_DER_NULL));
98         } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
99             AlgorithmIdentifier sigAlgId;
100             switch (digestAlgorithm) {
101                 case SHA1:
102                     sigAlgId =
103                             new AlgorithmIdentifier(OID_SIG_DSA, ASN1_DER_NULL);
104                     break;
105                 case SHA256:
106                     // DSA signatures with SHA-256 in SignedData are accepted by Android API Level
107                     // 21 and higher. However, there are two ways to specify their SignedData
108                     // SignatureAlgorithm: dsaWithSha256 (2.16.840.1.101.3.4.3.2) and
109                     // dsa (1.2.840.10040.4.1). The latter works only on API Level 22+. Thus, we use
110                     // the former.
111                     sigAlgId =
112                             new AlgorithmIdentifier(OID_SIG_SHA256_WITH_DSA, ASN1_DER_NULL);
113                     break;
114                 default:
115                     throw new IllegalArgumentException(
116                             "Unexpected digest algorithm: " + digestAlgorithm);
117             }
118             return Pair.of(jcaDigestPrefixForSigAlg + "withDSA", sigAlgId);
119         } else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
120             return Pair.of(
121                     jcaDigestPrefixForSigAlg + "withECDSA",
122                     new AlgorithmIdentifier(OID_SIG_EC_PUBLIC_KEY, ASN1_DER_NULL));
123         } else {
124             throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
125         }
126     }
127 
getJcaSignatureAlgorithm( String digestAlgorithmOid, String signatureAlgorithmOid)128     public static String getJcaSignatureAlgorithm(
129             String digestAlgorithmOid,
130             String signatureAlgorithmOid) throws SignatureException {
131         // First check whether the signature algorithm OID alone is sufficient
132         String result = OID_TO_JCA_SIGNATURE_ALG.get(signatureAlgorithmOid);
133         if (result != null) {
134             return result;
135         }
136 
137         // Signature algorithm OID alone is insufficient. Need to combine digest algorithm OID
138         // with signature algorithm OID.
139         String suffix;
140         if (OID_SIG_RSA.equals(signatureAlgorithmOid)) {
141             suffix = "RSA";
142         } else if (OID_SIG_DSA.equals(signatureAlgorithmOid)) {
143             suffix = "DSA";
144         } else if (OID_SIG_EC_PUBLIC_KEY.equals(signatureAlgorithmOid)) {
145             suffix = "ECDSA";
146         } else {
147             throw new SignatureException(
148                     "Unsupported JCA Signature algorithm"
149                             + " . Digest algorithm: " + digestAlgorithmOid
150                             + ", signature algorithm: " + signatureAlgorithmOid);
151         }
152         String jcaDigestAlg = getJcaDigestAlgorithm(digestAlgorithmOid);
153         // Canonical name for SHA-1 with ... is SHA1with, rather than SHA1. Same for all other
154         // SHA algorithms.
155         if (jcaDigestAlg.startsWith("SHA-")) {
156             jcaDigestAlg = "SHA" + jcaDigestAlg.substring("SHA-".length());
157         }
158         return jcaDigestAlg + "with" + suffix;
159     }
160 
getJcaDigestAlgorithm(String oid)161     public static String getJcaDigestAlgorithm(String oid)
162             throws SignatureException {
163         String result = OID_TO_JCA_DIGEST_ALG.get(oid);
164         if (result == null) {
165             throw new SignatureException("Unsupported digest algorithm: " + oid);
166         }
167         return result;
168     }
169 }
170