Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Samsung Knox TEE integrity status #15

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

salvogiangri
Copy link
Contributor

This PR adds the ability to retrieve and show Samsung's specific device status info, this is done by using their own keystore API's to generate a "Knox protected" key. Additional code refactoring are welcome.


@salvogiangri
Copy link
Contributor Author

salvogiangri commented Jun 17, 2023

The main problem here is that keystore2 will prevent generating a SAK key if the app doesn't have one of those runtime permissions declared and granted:

<!-- android.Manifest.permission.KNOX_CCM_KEYSTORE -->
<permission
    android:label="@string/permlab_mdmCcm" 
    android:description="@string/permdesc_mdmCcm"
    android:name="com.samsung.android.knox.permission.KNOX_CCM_KEYSTORE" 
    android:permissionGroup="com.sec.enterprise.permission-group.mdm"
    android:protectionLevel="signature" />

<!-- android.Manifest.permission.KNOX_TIMA_KEYSTORE -->
<permission
    android:label="@string/permlab_mdmKeystore" 
    android:description="@string/permdesc_mdmKeystore"
    android:name="com.samsung.android.knox.permission.KNOX_TIMA_KEYSTORE"
    android:permissionGroup="com.sec.enterprise.permission-group.mdm"
    android:protectionLevel="signature" />

<!-- android.Manifest.permission.KNOX_TIMA_KEYSTORE_PER_APP -->
<permission
    android:label="@string/permlab_mdmKeystorePerApp" 
    android:description="@string/permdesc_mdmKeystorePerApp"
    android:name="com.samsung.android.knox.permission.KNOX_TIMA_KEYSTORE_PER_APP"
    android:permissionGroup="com.sec.enterprise.permission-group.mdm"
    android:protectionLevel="signature" />

<!-- android.Manifest.permission.SAMSUNG_KEYSTORE_PERMISSION -->
<permission
    android:name="com.samsung.android.security.permission.SAMSUNG_KEYSTORE_PERMISSION" 
    android:protectionLevel="signatureOrSystem" />

...and I've currently haven't found a way to grant SAMSUNG_KEYSTORE_PERMISSION permission without manually pushing the built apk to /system/priv-app, if you're interested on merging this I'd be great to get some help on how to get around this.

@vvb2060
Copy link
Owner

vvb2060 commented Jun 17, 2023

Thanks for your PR! I'm a bit surprised that Samsung has another system (even a different root of trust).
In AOSP, ID attestation also require privileges (READ_PRIVILEGED_PHONE_STATE). My idea is to support parsing them, and the generation can be done by other apps (e.g. test dpc). Because making the app privileged itself conflicts with integrity attestation.

Maybe use adb shell, which has more privileges than app and doesn't need unlock bootloader. This is in my plan, but for now I want to merge the parsing part first, hope you don't mind. Can you upload the saved pkipath file for me to test?

@vvb2060
Copy link
Owner

vvb2060 commented Jun 17, 2023

I did not find the public key published by Samsung on the Internet, can you provide the source?
https://docs.samsungknox.com/dev/knox-attestation/about-attestation.htm

@salvogiangri
Copy link
Contributor Author

salvogiangri commented Jun 17, 2023

Can you upload the saved pkipath file for me to test?

Sure! Here is it a52sxqeea-KeyAttestation.zip

I'm a bit surprised that Samsung has another system (even a different root of trust).

SKeymaster doesn't differs much from AOSP keymaster, Samsung enables Knox-specific code when asked via passing additional KeyParameters to keymaster as I discovered here:

private KeyParameter[] constructAttestationArguments(AttestParameterSpec spec) throws IllegalArgumentException, NullPointerException {
    if (spec.getChallenge() == null) {
        throw new IllegalArgumentException("The challenge cannot be null");
    }

    ArrayList<KeyParameter> args = new ArrayList<>();

    args.add(makeBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, spec.getChallenge()));

    if (spec.isDeviceAttestation()) {
        args.add(makeBytes(KeymasterDefs.KM_TAG_SAMSUNG_ATTESTATION_ROOT, SAMSUNG_ATTESTESTATION_DEVICE_IDS_ROOT.getBytes()));
    } else {
        args.add(makeBytes(KeymasterDefs.KM_TAG_SAMSUNG_ATTESTATION_ROOT, SAMSUNG_ATTESTESTATION_ROOT.getBytes()));
    }

    X500Principal certificateSubject = spec.getCertificateSubject();
    if (certificateSubject != null && !TextUtils.isEmpty(certificateSubject.getName("RFC1779"))) {
        args.add(makeBytes(KeymasterDefs.KM_TAG_SAMSUNG_CERTIFICATE_SUBJECT, certificateSubject.getName("RFC1779").getBytes()));
    }

    if (spec.isVerifiableIntegrity()) {
        args.add(makeBool(KeymasterDefs.KM_TAG_SAMSUNG_ATTEST_INTEGRITY));
        
        Application application = ActivityThread.currentApplication();
        if (application == null) {
            Log.w(TAG, "can not found application");
        } else {
            String packageName = spec.getPackageName();
            if (TextUtils.isEmpty(packageName)) {
                packageName = application.getPackageName();
            }
            
            byte[] bytesAuthPkg = getBytesAuthenticatePackage(packageName, application);
            if (bytesAuthPkg == null) {
                Log.w(TAG, "Auth package byte is null");
            } else {
                args.add(makeBytes(KeymasterDefs.KM_TAG_SAMSUNG_AUTHENTICATE_PACKAGE, bytesAuthPkg));
            }
        }
    }

    if (spec.isDevicePropertiesAttestationIncluded()) {
        args.add(makeBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, Build.BRAND.getBytes(StandardCharsets.UTF_8)));
        args.add(makeBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, Build.DEVICE.getBytes(StandardCharsets.UTF_8)));
        args.add(makeBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, Build.PRODUCT.getBytes(StandardCharsets.UTF_8)));
        args.add(makeBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER, Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8)));
        args.add(makeBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, Build.MODEL.getBytes(StandardCharsets.UTF_8)));
    }

    return (KeyParameter[]) args.toArray(new KeyParameter[args.size()]);
}

@salvogiangri
Copy link
Contributor Author

I did not find the public key published by Samsung on the Internet, can you provide the source?

I was able to get it thanks to the debug logs in your app:

Log.w(AppApplication.TAG, Base64.encodeToString(rootPublicKey, Base64.NO_WRAP));

I've added it to hide the "not trusted" header in the app.

@vvb2060
Copy link
Owner

vvb2060 commented Jun 17, 2023

Due to the significance of root of trust, the app can only accept official public data, such as Google.
Also, if using AOSP API, is the root certificate signed by Google?

@salvogiangri
Copy link
Contributor Author

salvogiangri commented Jun 17, 2023

Due to the significance of root of trust, the app can only accept official public data, such as Google.

Afaict SAK is only used by Samsung Knox powered apps (except for Samsung Pay/Wallet and Pass), mostly to verify whether or not the device is tampered (the key generation fails when ICD status is abnormal unless isVerifyIntegrity is true), probably the reason why Samsung didn't publish their public key anywhere.

Also, if using AOSP API, is the root certificate signed by Google?

Yes (a52sxqeea-KeyAttestation.zip).

@vvb2060
Copy link
Owner

vvb2060 commented Jun 17, 2023

I guess it might be in the internal Samsung Knox SDK doc, if you can confirm the public key is the same for all Samsung devices, I will add it.

@salvogiangri
Copy link
Contributor Author

I guess it might be in the internal Samsung Knox SDK doc, if you can confirm the public key is the same for all Samsung devices, I will add it.

Tested myself on two devices (Galaxy A52s 5G with Android 13, Galaxy A71 with Android 11) and the public key is the same.

@vvb2060
Copy link
Owner

vvb2060 commented Nov 15, 2023

critical(false) 1.3.6.1.4.1.236.11.3.23.7 value = Sequence
    Tagged [CONTEXT 0]
        PrintableString(Sat Jun 17 18:20:41 GMT+02:00 2023) 
    Tagged [CONTEXT 5]
        Sequence
            Tagged [CONTEXT 0]
                DER Enumerated(1)
            Tagged [CONTEXT 1]
                DER Enumerated(1)
            Tagged [CONTEXT 2]
                DER Enumerated(1)
            Tagged [CONTEXT 3]
                DER Enumerated(1)
            Tagged [CONTEXT 4]
                DER Enumerated(0)
            Tagged [CONTEXT 5]
                Sequence
                    Tagged [CONTEXT 0]
                        DER Enumerated(1048592)
                    Tagged [CONTEXT 1]
                        PrintableString(io.github.vvb2060.keyattestation) 
                    Tagged [CONTEXT 2]
                        PrintableString(6oIZkx9nsdHbEgbCgmbuQfWJf5kUUPBVexPiK8OQSZ8=) 
                    Tagged [CONTEXT 3]
                        DER Enumerated(1048592)
    Tagged [CONTEXT 6]
        DER Octet String[32] 

The data cannot be fully parsed.

@salvogiangri
Copy link
Contributor Author

salvogiangri commented Nov 15, 2023

Are you talking about the 6th entry? I didn't include that on purpose as it isn't relevant

private static final int AUTH_RESULT = 5;

private AuthResult mAuthResult = null;

public IntegrityStatus(ASN1Primitive asn1Encodable) {
    ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;

    Enumeration<?> seqEnum = sequence.getObjects();
    while (seqEnum.hasMoreElements()) {
        ASN1TaggedObject derObj = (ASN1TaggedObject) seqEnum.nextElement();

        switch (derObj.getTagNo()) {
            // ...
            case AUTH_RESULT:
                mAuthResult = new AuthResult(derObj.getObject());
                break;
        }
    }
}

@Override
public String toString() {
    // ...
        .append("Caller auth (with PROCA) status:").append('\n');
    sb.append(mAuthResult == null ? "Not performed" : mAuthResult.toString());
    return sb.toString();
}

This is how the AuthResult class looks like:

package com.android.server.knox.dar;

import android.util.Log;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import java.security.cert.CertificateParsingException;
import java.util.Enumeration;

public class AuthResult {
    private static final String TAG = "AuthResult";

    private static final int CALLER_AUTH_RESULT = 0;
    private static final int CALLING_PACKAGE = 1;
    private static final int CALLING_PACKAGE_SIGS = 2;
    private static final int CALLING_PACKAGE_AUTH_RESULT = 3;

    public static final int STATUS_NORMAL = 0;
    public static final int STATUS_ABNORMAL = 1;
    public static final int STATUS_NOT_SUPPORT = 2;

    private int mCallerAuthResult = -1;
    private byte[] mCallingPackage = new byte[]{0};
    private byte[] mCallingPackageSigs = new byte[]{0};
    private int mCallingPackageAuthResult = -1;

    public AuthResult(ASN1Primitive asn1Encodable) throws CertificateParsingException {
        if (!(asn1Encodable instanceof ASN1Sequence)) {
            throw new CertificateParsingException("Expected sequence for root of trust, found " + asn1Encodable.getClass().getName());
        }

        ASN1Sequence sequence = (ASN1Sequence) asn1Encodable;
        Enumeration seqEnum = sequence.getObjects();
        while (seqEnum.hasMoreElements()) {
            ASN1TaggedObject derObj = (ASN1TaggedObject) seqEnum.nextElement();

            switch (derObj.getTagNo()) {
                case CALLER_AUTH_RESULT:
                    mCallerAuthResult = ((ASN1Enumerated) derObj.getObject()).getValue().intValue();
                    break;
                case CALLING_PACKAGE:
                    mCallingPackage = Asn1Utils.getByteArrayFromAsn1(derObj);
                    break;
                case CALLING_PACKAGE_SIGS:
                    mCallingPackageSigs = Asn1Utils.getByteArrayFromAsn1(derObj);
                    break;
                case CALLING_PACKAGE_AUTH_RESULT:
                    mCallingPackageAuthResult = ((ASN1Enumerated) derObj.getObject()).getValue().intValue();
                    break;
                default:
                    Log.e(TAG, "invalid tag no : " + derObj.getTagNo());
                    break;
            }
        }
    }

    public int getCallerAuthResult() {
        return mCallerAuthResult;
    }

    public byte[] getCallingPackage() {
        return mCallingPackage;
    }

    public byte[] getCallingPackageSigs() {
        return mCallingPackageSigs;
    }

    public int getCallingPackageAuthResult() {
        return mCallingPackageAuthResult;
    }

    public String statusToString(int status, boolean isCallingPackageAuthResult) {
        switch (status) {
            case STATUS_NORMAL:
                return "Normal";
            case STATUS_ABNORMAL:
                return "Abnormal";
            case STATUS_NOT_SUPPORT:
                return "Not support";
            default:
                if (isCallingPackageAuthResult) {
                    return "Not support";
                }
                return Integer.toHexString(status);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("        Caller Auth Result : ")
                .append(statusToString(mCallerAuthResult, false))
                .append("\n        Calling Package : ")
                .append(new String(mCallingPackage))
                .append("\n        Calling Package Signatures : ")
                .append(new String(mCallingPackageSigs))
                .append("\n        Calling Package Auth Result : ")
                .append(statusToString(mCallingPackageAuthResult, true));
        return sb.toString();
    }
}

@salvogiangri
Copy link
Contributor Author

@vvb2060
Copy link
Owner

vvb2060 commented Nov 15, 2023

    Tagged [CONTEXT 6]
        DER Octet String[32] 

what about this?

PKCS7 does not maintain the order of certificates in a certification path. Set unique subject to reduce sorting issues.
@salvogiangri
Copy link
Contributor Author

typedef struct KnoxTEEProperties {
    ASN1_PRINTABLESTRING *challenge;
    ACCESSOR *creator;
    ACCESSOR_SET *administrators;
    ACCESSOR_SET *accessors;
    ASN1_PRINTABLESTRING *id_attest;
    INTEGRITY_STATUS *integrity;
    ASN1_OCTET_STRING *attestation_record_hash;
} KNOX_TEE_PROPERTIES;

ASN1_SEQUENCE(KNOX_TEE_PROPERTIES) = {
    ASN1_EXP_OPT(KNOX_TEE_PROPERTIES, challenge, ASN1_PRINTABLESTRING, 0),
    ASN1_EXP_OPT(KNOX_TEE_PROPERTIES, creator, ACCESSOR, 1),
    ASN1_EXP_SET_OF_OPT(KNOX_TEE_PROPERTIES, administrators, ACCESSOR, 2),
    ASN1_EXP_SET_OF_OPT(KNOX_TEE_PROPERTIES, accessors, ACCESSOR, 3),
    ASN1_EXP_OPT(KNOX_TEE_PROPERTIES, id_attest, ASN1_PRINTABLESTRING, 4),
    ASN1_EXP_OPT(KNOX_TEE_PROPERTIES, integrity, INTEGRITY_STATUS, 5),
    ASN1_EXP_OPT(KNOX_TEE_PROPERTIES, attestation_record_hash, ASN1_OCTET_STRING, 6),
} ASN1_SEQUENCE_END(KNOX_TEE_PROPERTIES)

So Tagged [CONTEXT 0] is simply the challenge, while Tagged [CONTEXT 6] is internally referenced as ATN_BLOB_HASH, looks like a SHA256 digest of the TA requesting the integrity status data from ICCC

@salvogiangri
Copy link
Contributor Author

Updated the PR to adapt to the latest source changes, also did some minor code cleanup. However, it looks like the app generated attest key feature isn't working correctly (signature error:error decoding signature bytes.)


a54xnaeea-KeyAttestation.zip

@vvb2060
Copy link
Owner

vvb2060 commented Nov 16, 2023

You are in app attest key mode, it looks like Samsung does not support it, you need to remove this feature for Samsung service.

@salvogiangri
Copy link
Contributor Author

Added a toggle to switch between SAK and GAK, this will also disable app generated attest key feature when SAK is enabled.

@chiteroman
Copy link

chiteroman commented Dec 25, 2023

@blackmesa123 Samsung uses Google private key to sign the leaf certificate? Or it uses Samsung custom one?

EDIT: They use their private key :(

Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
@salvogiangri
Copy link
Contributor Author

@blackmesa123 Samsung uses Google private key to sign the leaf certificate? Or it uses Samsung custom one?

EDIT: They use their private key :(

r11sxeea-KeyAttestation.zip

@vvb2060
Copy link
Owner

vvb2060 commented Jan 21, 2024

ASN1_PRINTABLESTRING challenge

public Builder(String alias, byte[] challenge)

What happens if fill it with a random byte array? This is a non-printable string.

ACCESSOR *creator;
ACCESSOR_SET *administrators;
ACCESSOR_SET *accessors;
ASN1_PRINTABLESTRING *id_attest;

Have they been deprecated and removed? I only find them in KnoxKeyInfo

@vvb2060
Copy link
Owner

vvb2060 commented Jan 22, 2024

https://docs.samsungknox.com/devref/knox-sdk/reference/com/samsung/android/knox/integrity/EnhancedAttestationPolicy.html

nonce
A nonce value that must be unique for each request. Nonce length can be 32 bytes string. Alphanumeric and underscore(_), dash(-), dot(.) characters are allowed for nonce.

public static final int ERROR_INVALID_NONCE
Since: API level 29 KNOX 3.4
Invalid nonce error occurred. Nonce length can be 32 bytes string. Alphanumeric and underscore(_), dash(-), dot(.) characters are allowed for nonce. Possible value for errorCode.

byte[] challenge It just follows the aosp api

@vvb2060 vvb2060 force-pushed the master branch 2 times, most recently from a92bc7a to 26db9eb Compare January 23, 2024 03:58
@salvogiangri
Copy link
Contributor Author

ACCESSOR *creator;
ACCESSOR_SET *administrators;
ACCESSOR_SET *accessors;
ASN1_PRINTABLESTRING *id_attest;

Have they been deprecated and removed? I only find them in KnoxKeyInfo

Seems like it. The keymaster TA source code I checked, which is part of the Samsung 2022 leak, is based off a very old version (4.2.19, used in Android 11 Samsung OS), each of these values are respectly set via the KM_TAG_KNOX_CREATOR_ID, KM_TAG_KNOX_ACCESSOR_ID, KM_TAG_KNOX_ADMINISTRATOR_ID and KM_TAG_ATTESTATION_ID_DEVICE tags

@salvogiangri
Copy link
Contributor Author

About the recent pushes in master branch:

All the rest seems okay

@vvb2060
Copy link
Owner

vvb2060 commented Jan 23, 2024

195fedd: legacy SAK root public keys are missing, can be added with BlackMesa123@21c54c1

I'm a little hesitant to add them, they were deprecated probably because of private key leak. Perhaps they should be considered revoked instead of legacy.

Also, I'd like to add them after testing that the certificate chain samples can be parsed properly.

@salvogiangri
Copy link
Contributor Author

I'm a little hesitant to add them, they were deprecated probably because of private key leak. Perhaps they should be considered revoked instead of legacy.

I don't think Samsung certificates ever got leaked, afaict SAK v2 cert has started to appear on devices shipping with Android 10 onwards, either because they implemented KeyMaster 4 or ICCC v4. SAK v1 is still a thing on legacy devices such as Galaxy S10 and Samsung apps/services that use SAK (Samsung Health, Knox Matrix) still accepts those certs

https://github.com/corsicanu/random_dumps/releases?q=sak%2C&expanded=true
https://github.com/corsicanu/random_dumps/releases?q=sakv2%2C&expanded=true

@vvb2060
Copy link
Owner

vvb2060 commented Jan 23, 2024

What is the difference between sakmv1 and sakv1? Do they still sign certs without tags 1-4 (creator,administrators,accessors,id_attest) like v2?

@salvogiangri
Copy link
Contributor Author

What is the difference between sakmv1 and sakv1? Do they still sign certs without tags 1-4 (creator,administrators,accessors,id_attest) like v2?

Certificate probably doesn't matters, what matters is the Knox SDK the device is running on (Android version). I couldn't find any mention to these tags from Android 12 onwards and I think this is the reason: https://docs.samsungknox.com/dev/knox-sdk/features/mdm-providers/keystores/tima-ccm-keystores/deprecation-of-tima-ccm-keystore-support/

These tags are controlled via the com.samsung.android.knox.keystore.KnoxKeyGenParameterSpec class (https://github.com/corsicanu/random_dumps/releases/download/20221212203608/A505FDDS9CVJ1-app_frame.zip)

@salvogiangri
Copy link
Contributor Author

salvogiangri commented Jan 24, 2024

SAKm_V1 cert seems to be used on Samsung JDM devices (eg. SM-T505)

@vvb2060
Copy link
Owner

vvb2060 commented Jan 30, 2024

hldr4@6180e50 🤦

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants