Commit deb37c73 authored by Lukas Buchs's avatar Lukas Buchs
Browse files

Android-SafetyNet: ctsProfileMatch

Allow decide in processCreate() if you want accept only devices with ctsProfileMatch (default) or basicIntegrity is suffisant.
No related merge requests found
Showing with 27 additions and 7 deletions
+27 -7
...@@ -72,6 +72,17 @@ class AndroidSafetyNet extends FormatBase { ...@@ -72,6 +72,17 @@ class AndroidSafetyNet extends FormatBase {
} }
} }
/**
* ctsProfileMatch: A stricter verdict of device integrity.
* If the value of ctsProfileMatch is true, then the profile of the device running your app matches
* the profile of a device that has passed Android compatibility testing and
* has been approved as a Google-certified Android device.
* @return bool
*/
public function ctsProfileMatch() {
return isset($this->_payload->ctsProfileMatch) ? !!$this->_payload->ctsProfileMatch : false;
}
/* /*
* returns the key certificate in PEM format * returns the key certificate in PEM format
...@@ -95,13 +106,13 @@ class AndroidSafetyNet extends FormatBase { ...@@ -95,13 +106,13 @@ class AndroidSafetyNet extends FormatBase {
// Verify that attestationCert is issued to the hostname "attest.android.com" // Verify that attestationCert is issued to the hostname "attest.android.com"
$certInfo = \openssl_x509_parse($this->getCertificatePem()); $certInfo = \openssl_x509_parse($this->getCertificatePem());
if (!\is_array($certInfo) || !$certInfo['subject'] || $certInfo['subject']['CN'] !== 'attest.android.com') { if (!\is_array($certInfo) || ($certInfo['subject']['CN'] ?? '') !== 'attest.android.com') {
throw new WebAuthnException('invalid certificate CN in JWS (' . $certInfo['subject']['CN']. ')', WebAuthnException::INVALID_DATA); throw new WebAuthnException('invalid certificate CN in JWS (' . ($certInfo['subject']['CN'] ?? '-'). ')', WebAuthnException::INVALID_DATA);
} }
// Verify that the ctsProfileMatch attribute in the payload of response is true. // Verify that the basicIntegrity attribute in the payload of response is true.
if (empty($this->_payload->ctsProfileMatch)) { if (empty($this->_payload->basicIntegrity)) {
throw new WebAuthnException('invalid ctsProfileMatch in payload', WebAuthnException::INVALID_DATA); throw new WebAuthnException('invalid basicIntegrity in payload', WebAuthnException::INVALID_DATA);
} }
// check certificate // check certificate
......
...@@ -286,10 +286,11 @@ class WebAuthn { ...@@ -286,10 +286,11 @@ class WebAuthn {
* @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin)
* @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button) * @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button)
* @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match * @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match
* @param bool $requireCtsProfileMatch false, if you don't want to check if the device is approved as a Google-certified Android device.
* @return \stdClass * @return \stdClass
* @throws WebAuthnException * @throws WebAuthnException
*/ */
public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) { public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true, $requireCtsProfileMatch=true) {
$clientDataHash = \hash('sha256', $clientDataJSON, true); $clientDataHash = \hash('sha256', $clientDataJSON, true);
$clientData = \json_decode($clientDataJSON); $clientData = \json_decode($clientDataJSON);
$challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge);
...@@ -330,6 +331,13 @@ class WebAuthn { ...@@ -330,6 +331,13 @@ class WebAuthn {
throw new WebAuthnException('invalid certificate signature', WebAuthnException::INVALID_SIGNATURE); throw new WebAuthnException('invalid certificate signature', WebAuthnException::INVALID_SIGNATURE);
} }
// Android-SafetyNet: if required, check for Compatibility Testing Suite (CTS).
if ($requireCtsProfileMatch && $attestationObject->getAttestationFormat() instanceof Attestation\Format\AndroidSafetyNet) {
if (!$attestationObject->getAttestationFormat()->ctsProfileMatch()) {
throw new WebAuthnException('invalid ctsProfileMatch: device is not approved as a Google-certified Android device.', WebAuthnException::ANDROID_NOT_TRUSTED);
}
}
// 15. If validation is successful, obtain a list of acceptable trust anchors // 15. If validation is successful, obtain a list of acceptable trust anchors
$rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null; $rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null;
if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) { if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) {
...@@ -483,7 +491,7 @@ class WebAuthn { ...@@ -483,7 +491,7 @@ class WebAuthn {
* Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder * Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder
* https://fidoalliance.org/metadata/ * https://fidoalliance.org/metadata/
* @param string $certFolder Folder path to save the certificates in PEM format. * @param string $certFolder Folder path to save the certificates in PEM format.
* @param bool $deleteCerts=true * @param bool $deleteCerts delete certificates in the target folder before adding the new ones.
* @return int number of cetificates * @return int number of cetificates
* @throws WebAuthnException * @throws WebAuthnException
*/ */
......
...@@ -20,6 +20,7 @@ class WebAuthnException extends \Exception { ...@@ -20,6 +20,7 @@ class WebAuthnException extends \Exception {
const CRYPTO_STRONG = 13; const CRYPTO_STRONG = 13;
const BYTEBUFFER = 14; const BYTEBUFFER = 14;
const CBOR = 15; const CBOR = 15;
const ANDROID_NOT_TRUSTED = 16;
public function __construct($message = "", $code = 0, $previous = null) { public function __construct($message = "", $code = 0, $previous = null) {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment