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.Constants.OID_RSA_ENCRYPTION; 20 import static com.android.apksig.internal.asn1.Asn1DerEncoder.ASN1_DER_NULL; 21 import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA1; 22 import static com.android.apksig.internal.oid.OidConstants.OID_DIGEST_SHA256; 23 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_DSA; 24 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_EC_PUBLIC_KEY; 25 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_RSA; 26 import static com.android.apksig.internal.oid.OidConstants.OID_SIG_SHA256_WITH_DSA; 27 import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_DIGEST_ALG; 28 import static com.android.apksig.internal.oid.OidConstants.OID_TO_JCA_SIGNATURE_ALG; 29 30 import com.android.apksig.internal.apk.v1.DigestAlgorithm; 31 import com.android.apksig.internal.asn1.Asn1Class; 32 import com.android.apksig.internal.asn1.Asn1Field; 33 import com.android.apksig.internal.asn1.Asn1OpaqueObject; 34 import com.android.apksig.internal.asn1.Asn1Type; 35 import com.android.apksig.internal.util.Pair; 36 37 import java.security.InvalidKeyException; 38 import java.security.PublicKey; 39 import java.security.Signature; 40 import java.security.SignatureException; 41 42 /** 43 * PKCS #7 {@code AlgorithmIdentifier} as specified in RFC 5652. 44 */ 45 @Asn1Class(type = Asn1Type.SEQUENCE) 46 public class AlgorithmIdentifier { 47 48 @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) 49 public String algorithm; 50 51 @Asn1Field(index = 1, type = Asn1Type.ANY, optional = true) 52 public Asn1OpaqueObject parameters; 53 AlgorithmIdentifier()54 public AlgorithmIdentifier() {} 55 AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters)56 public AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters) { 57 this.algorithm = algorithmOid; 58 this.parameters = parameters; 59 } 60 61 /** 62 * Returns the PKCS #7 {@code DigestAlgorithm} to use when signing using the specified digest 63 * algorithm. 64 */ getSignerInfoDigestAlgorithmOid( DigestAlgorithm digestAlgorithm)65 public static AlgorithmIdentifier getSignerInfoDigestAlgorithmOid( 66 DigestAlgorithm digestAlgorithm) { 67 switch (digestAlgorithm) { 68 case SHA1: 69 return new AlgorithmIdentifier(OID_DIGEST_SHA1, ASN1_DER_NULL); 70 case SHA256: 71 return new AlgorithmIdentifier(OID_DIGEST_SHA256, ASN1_DER_NULL); 72 } 73 throw new IllegalArgumentException("Unsupported digest algorithm: " + digestAlgorithm); 74 } 75 76 /** 77 * Returns the JCA {@link Signature} algorithm and PKCS #7 {@code SignatureAlgorithm} to use 78 * when signing with the specified key and digest algorithm. 79 */ getSignerInfoSignatureAlgorithm( PublicKey publicKey, DigestAlgorithm digestAlgorithm, boolean deterministicDsaSigning)80 public static Pair<String, AlgorithmIdentifier> getSignerInfoSignatureAlgorithm( 81 PublicKey publicKey, DigestAlgorithm digestAlgorithm, boolean deterministicDsaSigning) 82 throws InvalidKeyException { 83 String keyAlgorithm = publicKey.getAlgorithm(); 84 String jcaDigestPrefixForSigAlg; 85 switch (digestAlgorithm) { 86 case SHA1: 87 jcaDigestPrefixForSigAlg = "SHA1"; 88 break; 89 case SHA256: 90 jcaDigestPrefixForSigAlg = "SHA256"; 91 break; 92 default: 93 throw new IllegalArgumentException( 94 "Unexpected digest algorithm: " + digestAlgorithm); 95 } 96 if ("RSA".equalsIgnoreCase(keyAlgorithm) || OID_RSA_ENCRYPTION.equals(keyAlgorithm)) { 97 return Pair.of( 98 jcaDigestPrefixForSigAlg + "withRSA", 99 new AlgorithmIdentifier(OID_SIG_RSA, ASN1_DER_NULL)); 100 } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { 101 AlgorithmIdentifier sigAlgId; 102 switch (digestAlgorithm) { 103 case SHA1: 104 sigAlgId = 105 new AlgorithmIdentifier(OID_SIG_DSA, ASN1_DER_NULL); 106 break; 107 case SHA256: 108 // DSA signatures with SHA-256 in SignedData are accepted by Android API Level 109 // 21 and higher. However, there are two ways to specify their SignedData 110 // SignatureAlgorithm: dsaWithSha256 (2.16.840.1.101.3.4.3.2) and 111 // dsa (1.2.840.10040.4.1). The latter works only on API Level 22+. Thus, we use 112 // the former. 113 sigAlgId = 114 new AlgorithmIdentifier(OID_SIG_SHA256_WITH_DSA, ASN1_DER_NULL); 115 break; 116 default: 117 throw new IllegalArgumentException( 118 "Unexpected digest algorithm: " + digestAlgorithm); 119 } 120 String signingAlgorithmName = 121 jcaDigestPrefixForSigAlg + (deterministicDsaSigning ? "withDetDSA" : "withDSA"); 122 return Pair.of(signingAlgorithmName, sigAlgId); 123 } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { 124 return Pair.of( 125 jcaDigestPrefixForSigAlg + "withECDSA", 126 new AlgorithmIdentifier(OID_SIG_EC_PUBLIC_KEY, ASN1_DER_NULL)); 127 } else { 128 throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); 129 } 130 } 131 getJcaSignatureAlgorithm( String digestAlgorithmOid, String signatureAlgorithmOid)132 public static String getJcaSignatureAlgorithm( 133 String digestAlgorithmOid, 134 String signatureAlgorithmOid) throws SignatureException { 135 // First check whether the signature algorithm OID alone is sufficient 136 String result = OID_TO_JCA_SIGNATURE_ALG.get(signatureAlgorithmOid); 137 if (result != null) { 138 return result; 139 } 140 141 // Signature algorithm OID alone is insufficient. Need to combine digest algorithm OID 142 // with signature algorithm OID. 143 String suffix; 144 if (OID_SIG_RSA.equals(signatureAlgorithmOid)) { 145 suffix = "RSA"; 146 } else if (OID_SIG_DSA.equals(signatureAlgorithmOid)) { 147 suffix = "DSA"; 148 } else if (OID_SIG_EC_PUBLIC_KEY.equals(signatureAlgorithmOid)) { 149 suffix = "ECDSA"; 150 } else { 151 throw new SignatureException( 152 "Unsupported JCA Signature algorithm" 153 + " . Digest algorithm: " + digestAlgorithmOid 154 + ", signature algorithm: " + signatureAlgorithmOid); 155 } 156 String jcaDigestAlg = getJcaDigestAlgorithm(digestAlgorithmOid); 157 // Canonical name for SHA-1 with ... is SHA1with, rather than SHA1. Same for all other 158 // SHA algorithms. 159 if (jcaDigestAlg.startsWith("SHA-")) { 160 jcaDigestAlg = "SHA" + jcaDigestAlg.substring("SHA-".length()); 161 } 162 return jcaDigestAlg + "with" + suffix; 163 } 164 getJcaDigestAlgorithm(String oid)165 public static String getJcaDigestAlgorithm(String oid) 166 throws SignatureException { 167 String result = OID_TO_JCA_DIGEST_ALG.get(oid); 168 if (result == null) { 169 throw new SignatureException("Unsupported digest algorithm: " + oid); 170 } 171 return result; 172 } 173 } 174