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