1 /*
2  * Copyright (C) 2018 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 android.content.pm;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.pm.SigningDetails.SignatureSchemeVersion;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.util.ArraySet;
26 
27 import java.security.PublicKey;
28 import java.util.Collection;
29 
30 /**
31  * Information pertaining to the signing certificates used to sign a package.
32  */
33 public final class SigningInfo implements Parcelable {
34 
35     @NonNull
36     private final SigningDetails mSigningDetails;
37 
SigningInfo()38     public SigningInfo() {
39         mSigningDetails = SigningDetails.UNKNOWN;
40     }
41 
42     /**
43      * Creates a new instance of information used to sign the APK.
44      *
45      * @param schemeVersion version of signing schema.
46      * @param apkContentsSigners signing certificates.
47      * @param publicKeys for the signing certificates.
48      * @param signingCertificateHistory All signing certificates the package has proven it is
49      *                                  authorized to use.
50      *
51      * @see
52      * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing
53      * schemas</a>
54      */
55     @FlaggedApi(Flags.FLAG_ARCHIVING)
SigningInfo(@ignatureSchemeVersion int schemeVersion, @Nullable Collection<Signature> apkContentsSigners, @Nullable Collection<PublicKey> publicKeys, @Nullable Collection<Signature> signingCertificateHistory)56     public SigningInfo(@SignatureSchemeVersion int schemeVersion,
57             @Nullable Collection<Signature> apkContentsSigners,
58             @Nullable Collection<PublicKey> publicKeys,
59             @Nullable Collection<Signature> signingCertificateHistory) {
60         if (schemeVersion <= 0 || apkContentsSigners == null) {
61             mSigningDetails = SigningDetails.UNKNOWN;
62             return;
63         }
64         Signature[] signatures = apkContentsSigners != null && !apkContentsSigners.isEmpty()
65                 ? apkContentsSigners.toArray(new Signature[apkContentsSigners.size()])
66                 : null;
67         Signature[] pastSignatures =
68                 signingCertificateHistory != null && !signingCertificateHistory.isEmpty()
69                 ? signingCertificateHistory.toArray(new Signature[signingCertificateHistory.size()])
70                 : null;
71         if (Signature.areExactArraysMatch(signatures, pastSignatures)) {
72             pastSignatures = null;
73         }
74         ArraySet<PublicKey> keys =
75                 publicKeys != null && !publicKeys.isEmpty() ? new ArraySet<>(publicKeys) : null;
76         mSigningDetails = new SigningDetails(signatures, schemeVersion, keys, pastSignatures);
77     }
78 
79     /**
80      * @hide only packagemanager should be populating this
81      */
SigningInfo(SigningDetails signingDetails)82     public SigningInfo(SigningDetails signingDetails) {
83         mSigningDetails = new SigningDetails(signingDetails);
84     }
85 
SigningInfo(SigningInfo orig)86     public SigningInfo(SigningInfo orig) {
87         mSigningDetails = new SigningDetails(orig.mSigningDetails);
88     }
89 
SigningInfo(Parcel source)90     private SigningInfo(Parcel source) {
91         mSigningDetails = SigningDetails.CREATOR.createFromParcel(source);
92     }
93 
94     /**
95      * Although relatively uncommon, packages may be signed by more than one signer, in which case
96      * their identity is viewed as being the set of all signers, not just any one.
97      */
hasMultipleSigners()98     public boolean hasMultipleSigners() {
99         return mSigningDetails.getSignatures() != null
100                 && mSigningDetails.getSignatures().length > 1;
101     }
102 
103     /**
104      * APK Signature Scheme v3 enables packages to provide a proof-of-rotation record that the
105      * platform verifies, and uses, to allow the use of new signing certificates.  This is only
106      * available to packages that are not signed by multiple signers.  In the event of a change to a
107      * new signing certificate, the package's past signing certificates are presented as well.  Any
108      * check of a package's signing certificate should also include a search through its entire
109      * signing history, since it could change to a new signing certificate at any time.
110      */
hasPastSigningCertificates()111     public boolean hasPastSigningCertificates() {
112         return mSigningDetails.getPastSigningCertificates() != null
113                 && mSigningDetails.getPastSigningCertificates().length > 0;
114     }
115 
116     /**
117      * Returns the signing certificates this package has proven it is authorized to use. This
118      * includes both the signing certificate associated with the signer of the package and the past
119      * signing certificates it included as its proof of signing certificate rotation.  Signing
120      * certificates are returned in the order of rotation with the original signing certificate at
121      * index 0, and the current signing certificate at the last index. This method is the preferred
122      * replacement for the {@code GET_SIGNATURES} flag used with {@link
123      * PackageManager#getPackageInfo(String, int)}. When determining if a package is signed by a
124      * desired certificate, the returned array should be checked to determine if it is one of the
125      * entries.
126      *
127      * <note>
128      *     This method returns null if the package is signed by multiple signing certificates, as
129      *     opposed to being signed by one current signer and also providing the history of past
130      *     signing certificates.  {@link #hasMultipleSigners()} may be used to determine if this
131      *     package is signed by multiple signers.  Packages which are signed by multiple signers
132      *     cannot change their signing certificates and their {@code Signature} array should be
133      *     checked to make sure that every entry matches the looked-for signing certificates.
134      * </note>
135      */
getSigningCertificateHistory()136     public Signature[] getSigningCertificateHistory() {
137         if (hasMultipleSigners()) {
138             return null;
139         } else if (!hasPastSigningCertificates()) {
140 
141             // this package is only signed by one signer with no history, return it
142             return mSigningDetails.getSignatures();
143         } else {
144 
145             // this package has provided proof of past signing certificates, include them
146             return mSigningDetails.getPastSigningCertificates();
147         }
148     }
149 
150     /**
151      * Returns the signing certificates used to sign the APK contents of this application.  Not
152      * including any past signing certificates the package proved it is authorized to use.
153      * <note>
154      *     This method should not be used unless {@link #hasMultipleSigners()} returns true,
155      *     indicating that {@link #getSigningCertificateHistory()} cannot be used, otherwise {@link
156      *     #getSigningCertificateHistory()} should be preferred.
157      * </note>
158      */
getApkContentsSigners()159     public Signature[] getApkContentsSigners() {
160         return mSigningDetails.getSignatures();
161     }
162 
163     /**
164      * Returns the version of signing schema used to sign the APK.
165      *
166      * @see
167      * <a href="https://source.android.com/docs/security/features/apksigning#schemes">APK signing
168      * schemas</a>
169      */
170     @FlaggedApi(Flags.FLAG_ARCHIVING)
getSchemeVersion()171     public @SignatureSchemeVersion int getSchemeVersion() {
172         return mSigningDetails.getSignatureSchemeVersion();
173     }
174 
175     /**
176      * Returns the public keys for the signing certificates.
177      */
178     @FlaggedApi(Flags.FLAG_ARCHIVING)
getPublicKeys()179     public @NonNull Collection<PublicKey> getPublicKeys() {
180         return mSigningDetails.getPublicKeys();
181     }
182 
183     @Override
describeContents()184     public int describeContents() {
185         return 0;
186     }
187 
188     @Override
writeToParcel(Parcel dest, int parcelableFlags)189     public void writeToParcel(Parcel dest, int parcelableFlags) {
190         mSigningDetails.writeToParcel(dest, parcelableFlags);
191     }
192 
193     /**
194      *  @hide
195      */
196     @NonNull
getSigningDetails()197     public SigningDetails getSigningDetails() {
198         return mSigningDetails;
199     }
200 
201     public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
202             new Parcelable.Creator<SigningInfo>() {
203         @Override
204         public SigningInfo createFromParcel(Parcel source) {
205             return new SigningInfo(source);
206         }
207 
208         @Override
209         public SigningInfo[] newArray(int size) {
210             return new SigningInfo[size];
211         }
212     };
213 }
214