306 lines
10 KiB
Java
306 lines
10 KiB
Java
/*
|
|
* Copyright 2020 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 android.app.blob;
|
|
|
|
import static android.app.blob.XmlTags.ATTR_ALGO;
|
|
import static android.app.blob.XmlTags.ATTR_DIGEST;
|
|
import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
|
|
import static android.app.blob.XmlTags.ATTR_LABEL;
|
|
import static android.app.blob.XmlTags.ATTR_TAG;
|
|
|
|
import android.annotation.CurrentTimeMillisLong;
|
|
import android.annotation.NonNull;
|
|
import android.os.Parcel;
|
|
import android.os.Parcelable;
|
|
import android.util.Base64;
|
|
import android.util.IndentingPrintWriter;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
import com.android.internal.util.XmlUtils;
|
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
import org.xmlpull.v1.XmlSerializer;
|
|
|
|
import java.io.IOException;
|
|
import java.util.Arrays;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* An identifier to represent a blob.
|
|
*/
|
|
// TODO: use datagen tool?
|
|
public final class BlobHandle implements Parcelable {
|
|
/** @hide */
|
|
public static final String ALGO_SHA_256 = "SHA-256";
|
|
|
|
private static final String[] SUPPORTED_ALGOS = {
|
|
ALGO_SHA_256
|
|
};
|
|
|
|
private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters
|
|
private static final int LIMIT_BLOB_LABEL_LENGTH = 100; // characters
|
|
|
|
/**
|
|
* Cyrptographically secure hash algorithm used to generate hash of the blob this handle is
|
|
* representing.
|
|
*
|
|
* @hide
|
|
*/
|
|
@NonNull public final String algorithm;
|
|
|
|
/**
|
|
* Hash of the blob this handle is representing using {@link #algorithm}.
|
|
*
|
|
* @hide
|
|
*/
|
|
@NonNull public final byte[] digest;
|
|
|
|
/**
|
|
* Label of the blob that can be surfaced to the user.
|
|
* @hide
|
|
*/
|
|
@NonNull public final CharSequence label;
|
|
|
|
/**
|
|
* Time in milliseconds after which the blob should be invalidated and not
|
|
* allowed to be accessed by any other app, in {@link System#currentTimeMillis()} timebase.
|
|
*
|
|
* @hide
|
|
*/
|
|
@CurrentTimeMillisLong public final long expiryTimeMillis;
|
|
|
|
/**
|
|
* An opaque {@link String} associated with the blob.
|
|
*
|
|
* @hide
|
|
*/
|
|
@NonNull public final String tag;
|
|
|
|
private BlobHandle(String algorithm, byte[] digest, CharSequence label, long expiryTimeMillis,
|
|
String tag) {
|
|
this.algorithm = algorithm;
|
|
this.digest = digest;
|
|
this.label = label;
|
|
this.expiryTimeMillis = expiryTimeMillis;
|
|
this.tag = tag;
|
|
}
|
|
|
|
private BlobHandle(Parcel in) {
|
|
this.algorithm = in.readString();
|
|
this.digest = in.createByteArray();
|
|
this.label = in.readCharSequence();
|
|
this.expiryTimeMillis = in.readLong();
|
|
this.tag = in.readString();
|
|
}
|
|
|
|
/** @hide */
|
|
public static @NonNull BlobHandle create(@NonNull String algorithm, @NonNull byte[] digest,
|
|
@NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis,
|
|
@NonNull String tag) {
|
|
final BlobHandle handle = new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag);
|
|
handle.assertIsValid();
|
|
return handle;
|
|
}
|
|
|
|
/**
|
|
* Create a new blob identifier.
|
|
*
|
|
* <p> For two objects of {@link BlobHandle} to be considered equal, the following arguments
|
|
* must be equal:
|
|
* <ul>
|
|
* <li> {@code digest}
|
|
* <li> {@code label}
|
|
* <li> {@code expiryTimeMillis}
|
|
* <li> {@code tag}
|
|
* </ul>
|
|
*
|
|
* @param digest the SHA-256 hash of the blob this is representing.
|
|
* @param label a label indicating what the blob is, that can be surfaced to the user.
|
|
* The length of the label cannot be more than 100 characters. It is recommended
|
|
* to keep this brief. This may be truncated and ellipsized if it is too long
|
|
* to be displayed to the user.
|
|
* @param expiryTimeMillis the time in secs after which the blob should be invalidated and not
|
|
* allowed to be accessed by any other app,
|
|
* in {@link System#currentTimeMillis()} timebase or {@code 0} to
|
|
* indicate that there is no expiry time associated with this blob.
|
|
* @param tag an opaque {@link String} associated with the blob. The length of the tag
|
|
* cannot be more than 128 characters.
|
|
*
|
|
* @return a new instance of {@link BlobHandle} object.
|
|
*/
|
|
public static @NonNull BlobHandle createWithSha256(@NonNull byte[] digest,
|
|
@NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis,
|
|
@NonNull String tag) {
|
|
return create(ALGO_SHA_256, digest, label, expiryTimeMillis, tag);
|
|
}
|
|
|
|
/**
|
|
* Returns the SHA-256 hash of the blob that this object is representing.
|
|
*
|
|
* @see #createWithSha256(byte[], CharSequence, long, String)
|
|
*/
|
|
public @NonNull byte[] getSha256Digest() {
|
|
return digest;
|
|
}
|
|
|
|
/**
|
|
* Returns the label associated with the blob that this object is representing.
|
|
*
|
|
* @see #createWithSha256(byte[], CharSequence, long, String)
|
|
*/
|
|
public @NonNull CharSequence getLabel() {
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* Returns the expiry time in milliseconds of the blob that this object is representing, in
|
|
* {@link System#currentTimeMillis()} timebase.
|
|
*
|
|
* @see #createWithSha256(byte[], CharSequence, long, String)
|
|
*/
|
|
public @CurrentTimeMillisLong long getExpiryTimeMillis() {
|
|
return expiryTimeMillis;
|
|
}
|
|
|
|
/**
|
|
* Returns the opaque {@link String} associated with the blob this object is representing.
|
|
*
|
|
* @see #createWithSha256(byte[], CharSequence, long, String)
|
|
*/
|
|
public @NonNull String getTag() {
|
|
return tag;
|
|
}
|
|
|
|
@Override
|
|
public int describeContents() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|
dest.writeString(algorithm);
|
|
dest.writeByteArray(digest);
|
|
dest.writeCharSequence(label);
|
|
dest.writeLong(expiryTimeMillis);
|
|
dest.writeString(tag);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj == null || !(obj instanceof BlobHandle)) {
|
|
return false;
|
|
}
|
|
final BlobHandle other = (BlobHandle) obj;
|
|
return this.algorithm.equals(other.algorithm)
|
|
&& Arrays.equals(this.digest, other.digest)
|
|
&& this.label.toString().equals(other.label.toString())
|
|
&& this.expiryTimeMillis == other.expiryTimeMillis
|
|
&& this.tag.equals(other.tag);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(algorithm, Arrays.hashCode(digest), label, expiryTimeMillis, tag);
|
|
}
|
|
|
|
/** @hide */
|
|
public void dump(IndentingPrintWriter fout, boolean dumpFull) {
|
|
if (dumpFull) {
|
|
fout.println("algo: " + algorithm);
|
|
fout.println("digest: " + (dumpFull ? encodeDigest(digest) : safeDigest(digest)));
|
|
fout.println("label: " + label);
|
|
fout.println("expiryMs: " + expiryTimeMillis);
|
|
fout.println("tag: " + tag);
|
|
} else {
|
|
fout.println(toString());
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
public void assertIsValid() {
|
|
Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm);
|
|
Preconditions.checkByteArrayNotEmpty(digest, "digest");
|
|
Preconditions.checkStringNotEmpty(label, "label must not be null");
|
|
Preconditions.checkArgument(label.length() <= LIMIT_BLOB_LABEL_LENGTH, "label too long");
|
|
Preconditions.checkArgumentNonnegative(expiryTimeMillis,
|
|
"expiryTimeMillis must not be negative");
|
|
Preconditions.checkStringNotEmpty(tag, "tag must not be null");
|
|
Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long");
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "BlobHandle {"
|
|
+ "algo:" + algorithm + ","
|
|
+ "digest:" + safeDigest(digest) + ","
|
|
+ "label:" + label + ","
|
|
+ "expiryMs:" + expiryTimeMillis + ","
|
|
+ "tag:" + tag
|
|
+ "}";
|
|
}
|
|
|
|
/** @hide */
|
|
public static String safeDigest(@NonNull byte[] digest) {
|
|
final String digestStr = encodeDigest(digest);
|
|
return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2);
|
|
}
|
|
|
|
private static String encodeDigest(@NonNull byte[] digest) {
|
|
return Base64.encodeToString(digest, Base64.NO_WRAP);
|
|
}
|
|
|
|
/** @hide */
|
|
public boolean isExpired() {
|
|
return expiryTimeMillis != 0 && expiryTimeMillis < System.currentTimeMillis();
|
|
}
|
|
|
|
public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() {
|
|
@Override
|
|
public @NonNull BlobHandle createFromParcel(@NonNull Parcel source) {
|
|
return new BlobHandle(source);
|
|
}
|
|
|
|
@Override
|
|
public @NonNull BlobHandle[] newArray(int size) {
|
|
return new BlobHandle[size];
|
|
}
|
|
};
|
|
|
|
/** @hide */
|
|
public void writeToXml(@NonNull XmlSerializer out) throws IOException {
|
|
XmlUtils.writeStringAttribute(out, ATTR_ALGO, algorithm);
|
|
XmlUtils.writeByteArrayAttribute(out, ATTR_DIGEST, digest);
|
|
XmlUtils.writeStringAttribute(out, ATTR_LABEL, label);
|
|
XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis);
|
|
XmlUtils.writeStringAttribute(out, ATTR_TAG, tag);
|
|
}
|
|
|
|
/** @hide */
|
|
@NonNull
|
|
public static BlobHandle createFromXml(@NonNull XmlPullParser in) throws IOException {
|
|
final String algo = XmlUtils.readStringAttribute(in, ATTR_ALGO);
|
|
final byte[] digest = XmlUtils.readByteArrayAttribute(in, ATTR_DIGEST);
|
|
final CharSequence label = XmlUtils.readStringAttribute(in, ATTR_LABEL);
|
|
final long expiryTimeMs = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME);
|
|
final String tag = XmlUtils.readStringAttribute(in, ATTR_TAG);
|
|
|
|
return BlobHandle.create(algo, digest, label, expiryTimeMs, tag);
|
|
}
|
|
}
|