SP_REST_*, tham số, thuật toán, tên bảng/thuộc tính là hằng trích trực tiếp. Phần so khớp/đếm/khóa nằm trong stored procedure MySQL (⚠️).CLOUD_CERTIFICATE_ATTR (phân biệt bằng ATTR_TYPE_ID: PASSCODE=3, OTP_PASSCODE=22), lưu thô. SAD = bảng riêng SAD_MANAGER, cột SAD lưu bản băm. OTP của owner (login) khác PIN/OTP của credential — ở CLOUD_CERTIFICATE_OWNER_ATTR. Nối nhau qua CLOUD_CERTIFICATE_ID; CLOUD_AGREEMENT nối owner↔RP↔cert.Utils.generateOneTimePasswordpublic static String generateOneTimePassword(int len) {
String digits = "0123456789";
Random r = new Random(); // ⚠️ java.util.Random (không phải SecureRandom)
char[] buf = new char[len];
for (int i = 0; i < len; i++)
buf[i] = digits.charAt(r.nextInt(digits.length()));
return new String(buf); // chuỗi SỐ
}
credentials/issue → eSignCloud)// vn.mobileid.esigncloud.kernel.PrepareCertificateForSignCloud (chỉ với authMode = EXPLICIT_PIN)
int len = getPasscodeLength(); // cấu hình LENGHT_OF_OTP_PASSCODE
String pin = Utils.generateOneTimePassword(len); // sinh PIN
database.updateCloudCertificateAttributes(
cloudCertificateID, CloudCertificateAttributeType.PASSCODE.id, pin, null, modifiedBy); // lưu attr PASSCODE(3)
EmailReq req = new EmailReq(smtpProps, ownerEmail, subject, bodyChuaPin); // EMAIL PIN + CTS
sendEndpoint(req);
changePassphrase/resetPassphrase.credentials/sendOTP)// vn.mobileid.rssp.restful.api.proccess.API_SendOTP
int len = rsspProps.getOtpPassCodeLength(resource);
String otp = Utils.generateOneTimePassword(len); // sinh OTP mỗi lần
Attribute attr = new Attribute(cloudCertificateID, CloudCertificateAttributeType.OTP_PASSCODE.id);
attr.setValue(otp); // ⚠️ lưu PLAINTEXT, KHÔNG băm — attr OTP_PASSCODE(22)
resource.updateCloudCertificateAttribute(attr);
String body = Utils.makeNotificationMessage(template, otp, rsspProps.getOtpPassCodeTimeOutDuration());
EmailReq req = new EmailReq(rp.getSmtpProperties(), cert.getAuthorisationEmail(), subject, body);
API_Utils.sendEndpointRequest(resource, req, endpointUuid, EndpointFunction.SEND_EMAIL); // hoặc SMS
otpPassCodeTimeOutDuration) — sinh lại mỗi lần ký.credentials/authorize (API_Authorize.verify)AuthMode mode = AuthMode.valueOf(cloudCertificate.getAuthModeID());
switch (mode) {
case EXPLICIT_PIN: {
String code = ((RequestAuthorize) request).getAuthorizeCode(); // PIN client nộp (field authorizeCode)
DatabaseResponse dbResp = resource.authorizePassphrase(cloudCertificateID, code, modifiedBy);
if (dbResp.getResponseCode() != ResponseCodeType.SUCCESS.code) {
// PASSPHRASE_INVALID -> setRemainingCounter + throw "Passphrase is invalid"
// BLOCKED -> setTempLockoutDuration(getOtpPassCodeUnblockDuration) + throw "...has been blocked..."
// NOT_AVAILABLE -> throw "The passphrase ... is not avaialble ..."
}
break;
}
case EXPLICIT_OTP_SMS: case EXPLICIT_OTP_EMAIL:
// resource.authorizeOtpPassCode(...) -> "OTP is invalid" / "OTP was expired" / blocked
break;
case IMPLICIT_TSE:
API_Utils.pushNotification(...); // đẩy app, chờ xác nhận
return ResponseCodeType.REQUEST_WAITING_USER_CONFIRM;
}
// vn.mobileid.rssp.database.DatabaseResourceStorage
public DatabaseResponse authorizePassphrase(long cloudCertificateID, String passphrase,
int maxCounter, int modifiedBy) {
CallableStatement cs = conn("SQL_WRITE_ONLY_POOL")
.prepareCall("{ call SP_REST_CLOUD_CERTIFICATE_CHECK_PASSCODE(?,?,?,?,?,?) }");
cs.setLong (1, cloudCertificateID);
cs.setString(2, passphrase); // PIN thô
cs.setInt (3, maxCounter);
cs.setInt (4, modifiedBy);
cs.registerOutParameter(5, ...); // pRESPONSE_CODE_NAME
cs.registerOutParameter(6, ...); // pREMAINING_COUNTER
cs.execute();
res.setResponseCode(Integer.parseInt(cs.getString("pRESPONSE_CODE_NAME")));
res.setRemainingCounter(cs.getInt("pREMAINING_COUNTER"));
return res; // ⚠️ so khớp + đếm + khóa nằm TRONG SP
}
Verify OTP tương tự: { call SP_REST_CLOUD_CERTIFICATE_CHECK_OTP_PASSCODE(?,?,?,?,?,?,?) } — IN: pCLOUD_CERTIFICATE_ID, pOTP_PASSCODE, pMAX_COUNTER_FOR_PASSCODE, pTIMEOUT_DURATION_PASSCODE, pMODIFIED_BY; OUT: pREMAINING_COUNTER, pRESPONSE_CODE_NAME.
Utils.randomToken// vn.mobileid.rssp.common.utils.Utils — đây là "token ban đầu" của SAD
public static String randomToken(int len) {
String A = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+/";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++)
sb.append(A.charAt((int)(Math.random() * A.length()))); // ⚠️ Math.random() (không phải SecureRandom)
return sb.toString();
}
// Đây chính là chuỗi TRẢ VỀ CLIENT trong ResponseAuthorize.SAD (vd "y7B0UAgXMnPfi+Cg...").
// DB KHÔNG lưu chuỗi này — chỉ lưu bản băm của nó (mục 3.4).
RSSPUtils.hashSADpublic static String hashSAD(long clientInfo, int relyingPartyID, long cloudCertificateID, String sad) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(Long.toHexString(clientInfo).getBytes()); // hex(clientInfo) = RequesterInfo.hashCode()
baos.write(Long.toHexString((long) relyingPartyID).getBytes()); // hex(relyingPartyID)
baos.write(Long.toHexString(cloudCertificateID).getBytes()); // hex(cloudCertificateID)
baos.write(sad.getBytes()); // token gốc (từ mục 3.1)
byte[] digest = Crypto.hashData(baos.toByteArray(), "SHA-256");
return Utils.base64Encode(digest);
}
// SAD_luuDB = Base64( SHA-256( hex(clientInfo) ‖ hex(relyingPartyID) ‖ hex(cloudCertificateID) ‖ token ) )
credentials/authorize (sau khi verify PIN/OTP)// khung processRequest() ở API_ExtendTransaction, dùng lại cho API_Authorize
this.preVerify(request);
ResponseCodeType rc = this.verify(request, responseUnauthorized); // authorize -> check PIN/OTP (mục 2.4)
if (rc == ResponseCodeType.SUCCESS) {
String token = Utils.randomToken(len); // TOKEN GỐC (mục 3.1)
API_Utils.checkAndStorageSad(resource, relyingParty, cert, requesterInfo,
cloudLogID, numSignature, token, hashTobeSignProperties); // lưu SAD (băm)
((ResponseAuthorize) response).setSAD(token); // TRẢ TOKEN GỐC cho client
((ResponseAuthorize) response).setExpiresIn(rsspProps.getSadExpiresIn());
}
insertSADManagerByIDCallableStatement cs = conn.prepareCall("{ call SP_REST_SAD_MANAGER_INSERT(?,?,?,?,?,?,?,?,?,?,?) }");
cs.setLong (1, sm.getRelyingPartyID());
cs.setLong (2, sm.getCloudCertificateID());
cs.setLong (3, sm.getClientInfo()); // = RequesterInfo.hashCode()
cs.setString(4, sm.getClientInfoValue()...); // RequesterInfo (JSON)
cs.setLong (5, sm.getCloudLogID());
cs.setString(6, RSSPUtils.hashSAD(sm)); // ★ CỘT SAD = BĂM (không lưu token gốc)
cs.setInt (7, sm.getRemainingCounter()); // = sadMaxCounter
cs.setTimestamp(8, sm.getExpiredDt()); // = now + sadExpiresIn
cs.setInt (9, sm.getNumSignature());
cs.setString(10, sm.getHashTobeSignProperties()...);// DTBS/R (JSON) — ràng buộc SCAL2
cs.setInt (11, modifiedBy);
signatures/signHash (authorizeSAD)public DatabaseResponse authorizeSAD(long cloudCertificateID, long clientInfo, int relyingPartyID,
String token, int maxCounter, int modifiedBy) {
String pSad = RSSPUtils.hashSAD(clientInfo, relyingPartyID, cloudCertificateID, token); // băm lại token client nộp
CallableStatement cs = conn("SQL_WRITE_ONLY_POOL")
.prepareCall("{ call SP_REST_CLOUD_CERTIFICATE_CHECK_SAD(?,?,?,?,?,?,?,?,?,?) }");
cs.setLong ("pCLOUD_CERTIFICATE_ID", cloudCertificateID);
cs.setLong (2, clientInfo);
cs.setLong (3, relyingPartyID);
cs.setString("pSAD", pSad); // ★ đưa HASH cho SP (không phải token gốc)
cs.setInt ("pMAX_COUNTER_FOR_SAD", maxCounter);
cs.setInt ("pMODIFIED_BY", modifiedBy);
cs.registerOutParameter("pSAD_REMAINING_COUNTER", ...);
cs.registerOutParameter("pRESPONSE_CODE_NAME", ...);
cs.registerOutParameter("pNUM_SIGNATURE", ...);
cs.registerOutParameter("pHASH_TOBE_SIGN_PROPERTIES", ...); // DTBS/R đã ghim lúc authorize
cs.execute();
// đọc responseCode, remaining, numSignature, hashTobeSign ... (⚠️ so bằng+đếm+hết hạn trong SP)
}
// sau đó API_SignHash ràng buộc tài liệu (SCAL2):
if (!Utils.checkByteArrayTwoEquals(hashTobeSign.getHash(), request.getDocumentDigests().getHashes()))
throw new RSSP_Exception(..., "Hash array in credentials/authorize it not matched with Hash array in signatures/signHash");
// hợp lệ -> HsmCryptoFactory.create(...).restore(...).sign(...) -> ký ở HSM
Math.random() — nên đổi SecureRandom khi tự xây.owner/create → agreements/assign → credentials/issue
└─ API_CertificateIssue → API_Utils.enrollCertificate
└─ [eSignCloud] PrepareCertificateForSignCloud
→ generateOneTimePassword (PIN) → lưu attr PASSCODE(3) → EMAIL PIN + CTS
credentials/authorize { credentialID, numSignatures, authorizeCode = PIN, [documentDigests] }
→ API_Authorize.verify: authorizePassphrase → SP_REST_CLOUD_CERTIFICATE_CHECK_PASSCODE (verify PIN)
→ processRequest: randomToken → checkAndStorageSad(hashSAD → SAD_MANAGER_INSERT) → setSAD(token)
⇐ { SAD = token gốc, expiresIn }
signatures/signHash { credentialID, SAD, documentDigests, signAlgo }
→ authorizeSAD: hashSAD(token) → SP_REST_CLOUD_CERTIFICATE_CHECK_SAD (verify SAD)
→ checkByteArrayTwoEquals (bind hash) → HSM RsspKey.sign
⇐ { signatures[], remainingSigningCounter }
credentials/sendOTP { credentialID }
→ API_SendOTP: generateOneTimePassword (OTP) → lưu attr OTP_PASSCODE(22) → gửi SMS/email
credentials/authorize { credentialID, numSignatures, authorizeCode = OTP, [documentDigests] }
→ API_Authorize.verify: authorizeOtpPassCode → SP_REST_CLOUD_CERTIFICATE_CHECK_OTP_PASSCODE
→ (giống 4.2) cấp SAD
signatures/signHash → (giống 4.2)
credentials/changePassphrase { oldPassphrase, newPassphrase } → SP_REST_CLOUD_CERTIFICATE_CHANGE_PASSCODE
credentials/resetPassphrase { requestType=request→gửi OTP; confirm→OTP+PIN mới } → SP ..._RESET_PASSCODE
credentials/verifyPassphrase { authorizeCode = PIN } → authorizePassphrase (chỉ kiểm, không ký)
credentials/authorize — EXPLICIT_PIN (nhận SAD token gốc)credentials/authorize — EXPLICIT_OTP (SMS/EMAIL)credentials/authorize — IMPLICIT_TSE (blocking + push app)signatures/signHash — dùng lại token gốc để kýcredentials/issue — phát hành CTS + sinh PINcredentials/authorize (sau khi verify PIN/OTP OK)// randomToken() — sinh token gốc (Utils.randomToken)
String randomToken(int len) {
String A = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+/";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++)
sb.append(A.charAt((int)(Math.random() * A.length()))); // ⚠️ Math.random()
return sb.toString();
}
token = randomToken() // token gốc, ngẫu nhiên -> TRẢ CHO CLIENT
sad_hash = SHA256( hex(clientInfo) + hex(rpID) + hex(certID) + token ) // băm có salt
DB.insert(SAD_MANAGER) {
SAD = sad_hash // ★ chỉ lưu BĂM (không lưu token gốc)
EXPIRED_DT = now + sadExpiresIn // ~300s
REMAINING = sadMaxCounter
NUM_SIGNATURE, HASH_TOBE_SIGN // hash tài liệu (SCAL2)
}
return { SAD: token } // client nhận TOKEN GỐC
signatures/signHashtoken = request.SAD // client gửi lại token gốc
sad_hash = SHA256( hex(clientInfo) + hex(rpID) + hex(certID) + token ) // băm LẠI y hệt
row = DB.check(SAD_MANAGER, sad_hash) // SP_CHECK_SAD: so bằng + hạn + lượt + khóa
if !row.ok -> throw invalid / expired / blocked / not_exist
if SCAL2 && row.HASH_TOBE_SIGN != request.hashes -> throw "hash not matched"
HSM.sign(request.hashes) // hợp lệ -> ký
row.REMAINING-- // giảm lượt
SecureRandom.| Chủ đề | JAR → class → method |
|---|---|
| Sinh PIN/OTP | RSSP_Restful_Common → Utils.generateOneTimePassword; PIN: eSignCloud → PrepareCertificateForSignCloud; OTP: RSSP_Restful_API → API_SendOTP |
| Sinh token SAD | RSSP_Restful_Common → Utils.randomToken |
| Verify PIN/OTP | RSSP_Restful_API → v2.API_Authorize.verify; RSSP_WebApp_own_classes → DatabaseResourceStorage.authorizePassphrase / authorizeOtpPassCode |
| Sinh/băm SAD | RSSP_Restful_API → v2.API_ExtendTransaction.processRequest + API_Utils.checkAndStorageSad; RSSP_Restful_Core → RSSPUtils.hashSAD |
| Lưu/Verify SAD | RSSP_WebApp_own_classes → DatabaseResourceStorage.insertSADManagerByID / authorizeSAD; RSSP_Restful_API → v2.API_SignHash |
{ call SP_... } là hết phần Java; logic so khớp/đếm/khóa ở stored procedure MySQL — cần dump SP để xác nhận 100%.