1 /* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #include "verifier_func.h" 17 18 #include "securec.h" 19 20 #include "adaptor_algorithm.h" 21 #include "adaptor_log.h" 22 #include "adaptor_memory.h" 23 #include "attribute.h" 24 #include "buffer.h" 25 #include "pin_db.h" 26 27 typedef enum VerifierState { 28 VERIFIER_STATE_INIT = 0, 29 VERIFIER_STATE_WAIT_SYNC = 1, 30 VERIFIER_STATE_WAIT_ACK = 2, 31 VERIFIER_STATE_FINISH = 3, 32 } VerifierState; 33 34 typedef struct VerifierSchedule { 35 uint64_t scheduleId; 36 uint64_t templateId; 37 uint64_t timeStamp; 38 Buffer *selfUdid; 39 Buffer *peerUdid; 40 Buffer *peerPubKey; 41 Buffer *salt; 42 VerifierState state; 43 } VerifierSchedule; 44 45 static KeyPair *g_keyPair = NULL; 46 static Buffer *g_fwkPubKey = NULL; 47 static VerifierSchedule *g_verifierSchedule = NULL; 48 49 /* This is for example only, Should be implemented in trusted environment. */ GenerateVerifierKeyPair(void)50 ResultCode GenerateVerifierKeyPair(void) 51 { 52 DestroyKeyPair(g_keyPair); 53 g_keyPair = GenerateEd25519KeyPair(); 54 if (g_keyPair == NULL) { 55 LOG_ERROR("GenerateVerifierKeyPair fail!"); 56 return RESULT_GENERAL_ERROR; 57 } 58 LOG_INFO("GenerateVerifierKeyPair success"); 59 return RESULT_SUCCESS; 60 } 61 DestroyVerifierKeyPair(void)62 void DestroyVerifierKeyPair(void) 63 { 64 LOG_INFO("DestroyVerifierKeyPair"); 65 DestroyKeyPair(g_keyPair); 66 g_keyPair = NULL; 67 } 68 69 /* This is for example only, Should be implemented in trusted environment. */ DoGetVerifierExecutorInfo(PinExecutorInfo * pinExecutorInfo)70 ResultCode DoGetVerifierExecutorInfo(PinExecutorInfo *pinExecutorInfo) 71 { 72 if (pinExecutorInfo == NULL) { 73 LOG_ERROR("check param fail!"); 74 return RESULT_BAD_PARAM; 75 } 76 if (!IsEd25519KeyPairValid(g_keyPair)) { 77 LOG_ERROR("key pair not init!"); 78 return RESULT_NEED_INIT; 79 } 80 uint32_t pubKeyLen = ED25519_FIX_PUBKEY_BUFFER_SIZE; 81 if (GetBufferData(g_keyPair->pubKey, pinExecutorInfo->pubKey, &pubKeyLen) != RESULT_SUCCESS) { 82 LOG_ERROR("GetBufferData fail!"); 83 return RESULT_UNKNOWN; 84 } 85 pinExecutorInfo->esl = PIN_EXECUTOR_SECURITY_LEVEL; 86 pinExecutorInfo->maxTemplateAcl = PIN_CAPABILITY_LEVEL; 87 return RESULT_SUCCESS; 88 } 89 DoSetVerifierFwkParam(const uint8_t * fwkPubKey,uint32_t fwkPubKeySize)90 int32_t DoSetVerifierFwkParam(const uint8_t *fwkPubKey, uint32_t fwkPubKeySize) 91 { 92 if ((fwkPubKey == NULL) || (fwkPubKeySize != ED25519_FIX_PUBKEY_BUFFER_SIZE)) { 93 LOG_ERROR("DoSetVerifierFwkParam check param fail!"); 94 return RESULT_BAD_PARAM; 95 } 96 DestroyBuffer(g_fwkPubKey); 97 g_fwkPubKey = CreateBufferByData(fwkPubKey, fwkPubKeySize); 98 if (g_fwkPubKey == NULL) { 99 LOG_ERROR("DoSetVerifierFwkParam create fwkPubKey fail!"); 100 return RESULT_NO_MEMORY; 101 } 102 return RESULT_SUCCESS; 103 } 104 DestroyVerifierSchedule(void)105 static void DestroyVerifierSchedule(void) 106 { 107 if (g_verifierSchedule == NULL) { 108 return; 109 } 110 DestroyBuffer(g_verifierSchedule->selfUdid); 111 DestroyBuffer(g_verifierSchedule->peerUdid); 112 DestroyBuffer(g_verifierSchedule->peerPubKey); 113 DestroyBuffer(g_verifierSchedule->salt); 114 Free(g_verifierSchedule); 115 g_verifierSchedule= NULL; 116 } 117 InitVerifierSchedule(uint64_t scheduleId)118 static bool InitVerifierSchedule(uint64_t scheduleId) 119 { 120 g_verifierSchedule = Malloc(sizeof(VerifierSchedule)); 121 if (g_verifierSchedule == NULL) { 122 LOG_ERROR("malloc VerifierSchedule fail!"); 123 return false; 124 } 125 (void)memset_s(g_verifierSchedule, sizeof(VerifierSchedule), 0, sizeof(VerifierSchedule)); 126 g_verifierSchedule->scheduleId = scheduleId; 127 return true; 128 } 129 GetAuthInfoFromSchedule(uint64_t scheduleId,const uint8_t * extraInfo,uint32_t extraInfoSize)130 static int32_t GetAuthInfoFromSchedule(uint64_t scheduleId, const uint8_t *extraInfo, uint32_t extraInfoSize) 131 { 132 Attribute *attribute = NULL; 133 int32_t result = VerifyAndGetDataAttribute(scheduleId, &attribute, g_fwkPubKey, extraInfo, extraInfoSize); 134 IF_TRUE_LOGE_AND_RETURN_VAL(result != RESULT_SUCCESS, result); 135 136 result = RESULT_GENERAL_ERROR; 137 g_verifierSchedule->selfUdid = GetBufferFromAttribute(attribute, ATTR_LOCAL_UDID, CONST_FWK_UDID_SIZE); 138 if (g_verifierSchedule->selfUdid == NULL) { 139 LOG_ERROR("get self udid fail!"); 140 goto EXIT; 141 } 142 g_verifierSchedule->peerUdid = GetBufferFromAttribute(attribute, ATTR_PEER_UDID, CONST_FWK_UDID_SIZE); 143 if (g_verifierSchedule->peerUdid == NULL) { 144 LOG_ERROR("get peer udid fail!"); 145 goto EXIT; 146 } 147 g_verifierSchedule->peerPubKey = GetBufferFromAttribute( 148 attribute, ATTR_PUBLIC_KEY, ED25519_FIX_PUBKEY_BUFFER_SIZE); 149 if (g_verifierSchedule->peerPubKey == NULL) { 150 LOG_ERROR("get peer public key fail!"); 151 goto EXIT; 152 } 153 result = RESULT_SUCCESS; 154 155 EXIT: 156 FreeAttribute(&attribute); 157 return result; 158 } 159 SetVerifyAckDataSalt(Attribute * attribute)160 static bool SetVerifyAckDataSalt(Attribute *attribute) 161 { 162 if (g_verifierSchedule->salt != NULL) { 163 LOG_ERROR("get non null salt!"); 164 return false; 165 } 166 g_verifierSchedule->salt = CreateBufferBySize(CONST_KEK_SALT_SIZE); 167 if (g_verifierSchedule->salt == NULL) { 168 LOG_ERROR("create salt fail!"); 169 return false; 170 } 171 int32_t result = SecureRandom(g_verifierSchedule->salt->buf, g_verifierSchedule->salt->maxSize); 172 if (result != RESULT_SUCCESS) { 173 LOG_ERROR("random salt fail!"); 174 return false; 175 } 176 g_verifierSchedule->salt->contentSize = g_verifierSchedule->salt->maxSize; 177 if (SetBufferToAttribute(attribute, PIN_ATTR_KEK_SALT, g_verifierSchedule->salt) != RESULT_SUCCESS) { 178 LOG_ERROR("set salt fail!"); 179 return false; 180 } 181 return true; 182 } 183 SetVerifyAckDataPinParam(Attribute * attribute)184 static bool SetVerifyAckDataPinParam(Attribute *attribute) 185 { 186 uint64_t subType = 0; 187 if (GetSubType(g_verifierSchedule->templateId, &subType) != RESULT_SUCCESS) { 188 LOG_ERROR("GetSubType fail!"); 189 return false; 190 } 191 if (SetAttributeUint64(attribute, ATTR_PIN_SUB_TYPE, subType) != RESULT_SUCCESS) { 192 LOG_ERROR("set sub type fail!"); 193 return false; 194 } 195 196 Buffer *algoParam = CreateBufferBySize(CONST_SALT_LEN); 197 if (algoParam == NULL) { 198 LOG_ERROR("create algoParam fail!"); 199 return false; 200 } 201 algoParam->contentSize = algoParam->maxSize; 202 uint32_t algoVersion = 0; 203 int32_t result = DoGetAlgoParameter( 204 g_verifierSchedule->templateId, algoParam->buf, &(algoParam->contentSize), &algoVersion); 205 if (result != RESULT_SUCCESS) { 206 LOG_ERROR("DoGetAlgoParameter fail!"); 207 DestroyBuffer(algoParam); 208 return false; 209 } 210 if (SetBufferToAttribute(attribute, PIN_ATTR_ALGO_PARAM, algoParam) != RESULT_SUCCESS) { 211 LOG_ERROR("set algo param fail!"); 212 DestroyBuffer(algoParam); 213 return false; 214 } 215 DestroyBuffer(algoParam); 216 if (SetAttributeUint32(attribute, PIN_ATTR_ALGO_VERSION, algoVersion) != RESULT_SUCCESS) { 217 LOG_ERROR("set algo version fail!"); 218 return false; 219 } 220 return true; 221 } 222 GetVerifyAckData()223 static Attribute *GetVerifyAckData() 224 { 225 Attribute *attribute = GetAttributeDataBase(g_verifierSchedule->scheduleId, REMOTE_PIN_VERIFIER_ACK); 226 IF_TRUE_LOGE_AND_RETURN_VAL(attribute == NULL, NULL); 227 228 if (!SetVerifyAckDataSalt(attribute)) { 229 LOG_ERROR("SetVerifyAckDataSalt fail!"); 230 goto ERROR; 231 } 232 233 if (!SetVerifyAckDataPinParam(attribute)) { 234 LOG_ERROR("SetVerifyAckDataPinParam fail!"); 235 goto ERROR; 236 } 237 238 return attribute; 239 240 ERROR: 241 FreeAttribute(&attribute); 242 return NULL; 243 } 244 GetResultTlv(VerifierMsg * verifierMsg)245 static int32_t GetResultTlv(VerifierMsg *verifierMsg) 246 { 247 Attribute *attribute = GetAttributeDataBase(g_verifierSchedule->scheduleId, REMOTE_PIN_MSG_NONE); 248 IF_TRUE_LOGE_AND_RETURN_VAL(attribute == NULL, RESULT_GENERAL_ERROR); 249 250 int32_t result = RESULT_GENERAL_ERROR; 251 if (!SetResultDataInfo( 252 attribute, PinResultToFwkResult(verifierMsg->authResult), g_verifierSchedule->templateId, NULL)) { 253 LOG_ERROR("SetResultDataInfo fail"); 254 goto EXIT; 255 } 256 257 result = FormatTlvMsg(attribute, g_keyPair, verifierMsg->msgOut, &(verifierMsg->msgOutSize)); 258 if (result != RESULT_SUCCESS) { 259 LOG_ERROR("FormatTlvMsg fail"); 260 goto EXIT; 261 } 262 263 EXIT: 264 FreeAttribute(&attribute); 265 return result; 266 } 267 IsVeriferMsgValid(VerifierMsg * verifierMsg)268 static bool IsVeriferMsgValid(VerifierMsg *verifierMsg) 269 { 270 if (verifierMsg == NULL) { 271 LOG_ERROR("verifierMsg is null"); 272 return false; 273 } 274 if ((verifierMsg->msgIn == NULL) || (verifierMsg->msgInSize == 0)) { 275 LOG_ERROR("verifierMsg msgIn is invalid"); 276 return false; 277 } 278 if ((verifierMsg->msgOut == NULL) || (verifierMsg->msgOutSize == 0)) { 279 LOG_ERROR("verifierMsg msgOut is invalid"); 280 return false; 281 } 282 return true; 283 } 284 DoVerifierAuth(uint64_t scheduleId,uint64_t templateId,VerifierMsg * verifierMsg)285 int32_t DoVerifierAuth(uint64_t scheduleId, uint64_t templateId, VerifierMsg *verifierMsg) 286 { 287 LOG_INFO("DoVerifierAuth start %{public}x", (uint16_t)scheduleId); 288 if (!IsVeriferMsgValid(verifierMsg)) { 289 LOG_ERROR("check param fail!"); 290 return RESULT_BAD_PARAM; 291 } 292 DestroyVerifierSchedule(); 293 if (!InitVerifierSchedule(scheduleId)) { 294 LOG_ERROR("InitVerifierSchedule fail!"); 295 return RESULT_GENERAL_ERROR; 296 } 297 int32_t result = GetAuthInfoFromSchedule(scheduleId, verifierMsg->msgIn, verifierMsg->msgInSize); 298 if (result != RESULT_SUCCESS) { 299 LOG_ERROR("GetAuthInfoFromSchedule fail!"); 300 goto ERROR; 301 } 302 g_verifierSchedule->templateId = templateId; 303 g_verifierSchedule->state = VERIFIER_STATE_WAIT_SYNC; 304 305 PinCredentialInfos pinCredentialInfo; 306 result = DoQueryPinInfo(templateId, &pinCredentialInfo); 307 if (result != RESULT_SUCCESS) { 308 LOG_ERROR("DoQueryPinInfo fail!"); 309 goto ERROR; 310 } 311 if (pinCredentialInfo.freezeTime == 0) { 312 LOG_INFO("DoVerifierAuth success"); 313 verifierMsg->authResult = RESULT_SUCCESS; 314 verifierMsg->msgOutSize = 0; 315 return RESULT_SUCCESS; 316 } 317 318 LOG_ERROR("DoVerifierAuth locked"); 319 verifierMsg->authResult = RESULT_PIN_FREEZE; 320 result = GetResultTlv(verifierMsg); 321 if (result != RESULT_SUCCESS) { 322 LOG_ERROR("GetResultTlv fail!"); 323 goto ERROR; 324 } 325 return RESULT_SUCCESS; 326 327 ERROR: 328 DestroyVerifierSchedule(); 329 return result; 330 } 331 DoCancelVerifierAuth()332 int32_t DoCancelVerifierAuth() 333 { 334 LOG_INFO("DoCancelVerifierAuth start"); 335 DestroyVerifierSchedule(); 336 return RESULT_SUCCESS; 337 } 338 CheckCurrentSchedule(uint64_t scheduleId)339 static bool CheckCurrentSchedule(uint64_t scheduleId) 340 { 341 if (g_verifierSchedule == NULL) { 342 LOG_ERROR("schedule not exist"); 343 return false; 344 } 345 if (g_verifierSchedule->scheduleId != scheduleId) { 346 LOG_ERROR("schedule:%{public}x not match current:%{public}x", 347 (uint16_t)scheduleId, (uint16_t)(g_verifierSchedule->scheduleId)); 348 return false; 349 } 350 return true; 351 } 352 DoHandleCollectorSync(VerifierMsg * verifierMsg)353 static int32_t DoHandleCollectorSync(VerifierMsg *verifierMsg) 354 { 355 verifierMsg->isAuthEnd = false; 356 Attribute *dataIn = NULL; 357 int32_t result = VerifyAndGetDataAttribute(g_verifierSchedule->scheduleId, 358 &dataIn, g_verifierSchedule->peerPubKey, verifierMsg->msgIn, verifierMsg->msgInSize); 359 if (result != RESULT_SUCCESS) { 360 LOG_ERROR("VerifyAndGetDataAttribute fail"); 361 return result; 362 } 363 364 result = CheckAttributeDataBase( 365 dataIn, g_verifierSchedule->scheduleId, REMOTE_PIN_COLLECTOR_SYNC, &(g_verifierSchedule->timeStamp)); 366 FreeAttribute(&dataIn); 367 if (result != RESULT_SUCCESS) { 368 LOG_ERROR("CheckAttributeDataBase fail"); 369 return result; 370 } 371 372 Attribute *dataOut = GetVerifyAckData(); 373 if (dataOut == NULL) { 374 LOG_ERROR("GetVerifyAckData fail!"); 375 return RESULT_GENERAL_ERROR; 376 } 377 result = FormatTlvMsg(dataOut, g_keyPair, verifierMsg->msgOut, &(verifierMsg->msgOutSize)); 378 FreeAttribute(&dataOut); 379 if (result != RESULT_SUCCESS) { 380 LOG_ERROR("FormatTlvMsg fail!"); 381 return result; 382 } 383 g_verifierSchedule->state = VERIFIER_STATE_WAIT_ACK; 384 return result; 385 } 386 DestroyAesGcmParam(AesGcmParam * aesGcmParam)387 static void DestroyAesGcmParam(AesGcmParam *aesGcmParam) 388 { 389 DestroyBuffer(aesGcmParam->key); 390 aesGcmParam->key = NULL; 391 DestroyBuffer(aesGcmParam->iv); 392 aesGcmParam->iv = NULL; 393 DestroyBuffer(aesGcmParam->aad); 394 aesGcmParam->aad = NULL; 395 } 396 GetAesGcmParam(AesGcmParam * aesGcmParam,const Attribute * attribute)397 static bool GetAesGcmParam(AesGcmParam *aesGcmParam, const Attribute *attribute) 398 { 399 (void)memset_s(aesGcmParam, sizeof(AesGcmParam), 0, sizeof(AesGcmParam)); 400 aesGcmParam->aad = CreateBufferByData((const uint8_t *)CONST_KEK_AAD, CONST_KEK_AAD_SIZE); 401 if (aesGcmParam->aad == NULL) { 402 LOG_ERROR("create aad buffer fail"); 403 goto ERROR; 404 } 405 aesGcmParam->iv = GetBufferFromAttribute(attribute, PIN_ATTR_KEK_IV, AES_GCM_256_IV_SIZE); 406 if (aesGcmParam->iv == NULL) { 407 LOG_ERROR("create iv buffer fail"); 408 goto ERROR; 409 } 410 if (GetDistributeKey(g_verifierSchedule->peerUdid, g_verifierSchedule->salt, &(aesGcmParam->key)) != 411 RESULT_SUCCESS) { 412 LOG_ERROR("GetDistributeKey fail"); 413 goto ERROR; 414 } 415 return true; 416 417 ERROR: 418 DestroyAesGcmParam(aesGcmParam); 419 return false; 420 } 421 GetPinData(const Attribute * attribute)422 static Buffer *GetPinData(const Attribute *attribute) 423 { 424 AesGcmParam aesGcmParam = {}; 425 if (!GetAesGcmParam(&aesGcmParam, attribute)) { 426 LOG_ERROR("GetAesGcmParam fail"); 427 return NULL; 428 } 429 int32_t result = RESULT_GENERAL_ERROR; 430 Buffer *plainText = NULL; 431 Buffer *tag = NULL; 432 Buffer *cipherText = GetBufferFromAttribute(attribute, PIN_ATTR_KEK_SECRET, CONST_PIN_DATA_LEN); 433 if (cipherText == NULL) { 434 LOG_ERROR("GetBufferFromAttribute secret fail"); 435 goto EXIT; 436 } 437 tag = GetBufferFromAttribute(attribute, PIN_ATTR_KEK_TAG, AES_GCM_256_TAG_SIZE); 438 if (tag == NULL) { 439 LOG_ERROR("GetBufferFromAttribute tag fail"); 440 goto EXIT; 441 } 442 result = AesGcm256Decrypt(cipherText, &aesGcmParam, tag, &plainText); 443 if (result != RESULT_SUCCESS) { 444 LOG_ERROR("AesGcm256Decrypt fail"); 445 goto EXIT; 446 } 447 448 EXIT: 449 DestroyAesGcmParam(&aesGcmParam); 450 DestroyBuffer(cipherText); 451 DestroyBuffer(tag); 452 return plainText; 453 } 454 AuthPin(VerifierMsg * verifierMsg,Buffer * pinDataBuf)455 static int32_t AuthPin(VerifierMsg *verifierMsg, Buffer *pinDataBuf) 456 { 457 LOG_INFO("start"); 458 verifierMsg->isAuthEnd = true; 459 verifierMsg->authResult = RESULT_GENERAL_ERROR; 460 461 PinCredentialInfos pinCredentialInfo = {}; 462 ResultCode ret = DoQueryPinInfo(g_verifierSchedule->templateId, &pinCredentialInfo); 463 if (ret != RESULT_SUCCESS) { 464 LOG_ERROR("DoQueryPinInfo fail."); 465 verifierMsg->msgOutSize = 0; 466 return RESULT_SUCCESS; 467 } 468 469 if (pinCredentialInfo.freezeTime == 0) { 470 ResultCode compareRet = RESULT_COMPARE_FAIL; 471 ResultCode result = AuthPinById(pinDataBuf, g_verifierSchedule->templateId, NULL, &compareRet); 472 if (result != RESULT_SUCCESS) { 473 LOG_ERROR("AuthPinById fail!"); 474 } 475 verifierMsg->authResult = result != RESULT_SUCCESS ? result : compareRet; 476 } else { 477 LOG_ERROR("Pin is freezing."); 478 verifierMsg->authResult = RESULT_PIN_FREEZE; 479 } 480 481 ret = GetResultTlv(verifierMsg); 482 if (ret != RESULT_SUCCESS) { 483 LOG_ERROR("GetResultTlv fail!"); 484 verifierMsg->msgOutSize = 0; 485 } 486 return RESULT_SUCCESS; 487 } 488 DoHandleCollectorAck(VerifierMsg * verifierMsg)489 static int32_t DoHandleCollectorAck(VerifierMsg *verifierMsg) 490 { 491 Attribute *dataIn = NULL; 492 int32_t result = VerifyAndGetDataAttribute(g_verifierSchedule->scheduleId, 493 &dataIn, g_verifierSchedule->peerPubKey, verifierMsg->msgIn, verifierMsg->msgInSize); 494 if (result != RESULT_SUCCESS) { 495 LOG_ERROR("VerifyAndGetDataAttribute fail"); 496 return result; 497 } 498 499 result = CheckAttributeDataBase( 500 dataIn, g_verifierSchedule->scheduleId, REMOTE_PIN_COLLECTOR_ACK, &(g_verifierSchedule->timeStamp)); 501 if (result != RESULT_SUCCESS) { 502 LOG_ERROR("CheckAttributeDataBase fail"); 503 FreeAttribute(&dataIn); 504 return result; 505 } 506 507 Buffer *pinData = GetPinData(dataIn); 508 FreeAttribute(&dataIn); 509 if (pinData == NULL) { 510 LOG_ERROR("GetPinData fail"); 511 return RESULT_GENERAL_ERROR; 512 } 513 514 result = AuthPin(verifierMsg, pinData); 515 DestroyBuffer(pinData); 516 if (result != RESULT_SUCCESS) { 517 LOG_ERROR("AuthPin fail"); 518 } 519 DestroyVerifierSchedule(); 520 return result; 521 } 522 DoSendMessageToVerifier(uint64_t scheduleId,VerifierMsg * verifierMsg)523 int32_t DoSendMessageToVerifier(uint64_t scheduleId, VerifierMsg *verifierMsg) 524 { 525 LOG_INFO("DoSendMessageToVerifier start schedule:%{public}x", (uint16_t)scheduleId); 526 if (!CheckCurrentSchedule(scheduleId) || !IsVeriferMsgValid(verifierMsg)) { 527 LOG_ERROR("check param fail!"); 528 return RESULT_BAD_PARAM; 529 } 530 LOG_INFO("DoSendMessageToVerifier current state:%{public}d", g_verifierSchedule->state); 531 if (g_verifierSchedule->state == VERIFIER_STATE_WAIT_SYNC) { 532 return DoHandleCollectorSync(verifierMsg); 533 } 534 if (g_verifierSchedule->state == VERIFIER_STATE_WAIT_ACK) { 535 return DoHandleCollectorAck(verifierMsg); 536 } 537 return RESULT_GENERAL_ERROR; 538 } 539