TubitakCreditService.java
package io.mersel.dss.signer.api.services.timestamp.tubitak;
import io.mersel.dss.signer.api.dtos.TubitakCreditResponseDto;
import io.mersel.dss.signer.api.exceptions.TimestampException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* TÜBİTAK ESYA zaman damgası kontör sorgulama servisi.
*/
@Service
public class TubitakCreditService {
private static final Logger LOGGER = LoggerFactory.getLogger(TubitakCreditService.class);
private static final String IDENTITY_HEADER = "identity";
private static final String CREDIT_REQ_HEADER = "credit_req";
private static final String CREDIT_REQ_TIME_HEADER = "credit_req_time";
private static final String USER_AGENT_HEADER = "User-Agent";
private static final String TUBITAK_USER_AGENT = "UEKAE TSS Client";
private final String tspServerUrl;
private final String tspUserId;
private final String tspUserPassword;
private final boolean isTubitakTsp;
public TubitakCreditService(
@Value("${TS_SERVER_HOST:}") String tspServerUrl,
@Value("${TS_USER_ID:}") String tspUserId,
@Value("${TS_USER_PASSWORD:}") String tspUserPassword,
@Value("${IS_TUBITAK_TSP:false}") boolean isTubitakTsp) {
this.tspServerUrl = tspServerUrl;
this.tspUserId = tspUserId;
this.tspUserPassword = tspUserPassword;
this.isTubitakTsp = TubitakTspDetector.resolveTubitakTspMode(isTubitakTsp, tspServerUrl);
if (!isTubitakTsp && this.isTubitakTsp) {
LOGGER.info("IS_TUBITAK_TSP explicit olarak set edilmemiş, ancak TS_SERVER_HOST " +
"({}) KamuSM zaman damgası endpoint'i; kontör servisi TÜBİTAK modunda " +
"çalışacak.", tspServerUrl);
}
}
/**
* TÜBİTAK zaman damgası kontör bilgisini sorgular.
*
* @return Kontör bilgisi
* @throws TimestampException TÜBİTAK modu aktif değilse veya sorgulama başarısız olursa
*/
public TubitakCreditResponseDto checkCredit() {
if (!isTubitakTsp) {
throw new TimestampException(
"Kontör sorgulama sadece TÜBİTAK zaman damgası modu için kullanılabilir. " +
"IS_TUBITAK_TSP=true olarak ayarlayın.");
}
if (!StringUtils.hasText(tspServerUrl)) {
throw new TimestampException("Timestamp sunucu URL'si yapılandırılmamış.");
}
if (!StringUtils.hasText(tspUserId)) {
throw new TimestampException("Kullanıcı ID yapılandırılmamış.");
}
if (!StringUtils.hasText(tspUserPassword)) {
throw new TimestampException("Kullanıcı parolası yapılandırılmamış.");
}
try {
int customerId = Integer.parseInt(tspUserId);
// Timestamp (epoch millis)
long timestamp = System.currentTimeMillis();
// ÖNEMLİ: SHA1(customerID + timestamp) hesapla
String authString = String.valueOf(customerId) + String.valueOf(timestamp);
byte[] authHash = calculateSHA1(authString.getBytes());
// Bu hash'i şifrele
String authToken = TubitakAuthenticationHelper.encryptIdentity(
customerId,
tspUserPassword,
authHash
);
// HTTP request gönder
String creditInfo = sendCreditRequest(authToken, customerId, timestamp);
LOGGER.info("TÜBİTAK kontör sorgulaması başarılı. Müşteri ID: {}", customerId);
return new TubitakCreditResponseDto(
parseCreditFromResponse(creditInfo),
customerId,
creditInfo
);
} catch (NumberFormatException e) {
throw new TimestampException("Kullanıcı ID sayısal olmalı: " + tspUserId, e);
} catch (Exception e) {
LOGGER.error("TÜBİTAK kontör sorgulaması başarısız: {}", e.getMessage());
LOGGER.debug("Hata detayı", e);
throw new TimestampException("Kontör sorgulaması başarısız", e);
}
}
/**
* SHA1 hash hesaplar.
*/
private byte[] calculateSHA1(byte[] data) {
try {
java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
return digest.digest(data);
} catch (Exception e) {
throw new RuntimeException("SHA-1 hesaplama hatası", e);
}
}
/**
* Kontör sorgulama HTTP request'i gönderir.
*/
private String sendCreditRequest(String authToken, int customerId, long timestamp) throws Exception {
org.apache.http.client.methods.HttpPost httpPost =
new org.apache.http.client.methods.HttpPost(tspServerUrl);
httpPost.setHeader("Content-Type", "application/timestamp-query");
httpPost.setHeader(USER_AGENT_HEADER, TUBITAK_USER_AGENT);
httpPost.setHeader(IDENTITY_HEADER, authToken);
httpPost.setHeader(CREDIT_REQ_HEADER, String.valueOf(customerId));
httpPost.setHeader(CREDIT_REQ_TIME_HEADER, String.valueOf(timestamp));
httpPost.setEntity(new org.apache.http.entity.ByteArrayEntity(new byte[0]));
LOGGER.debug("TÜBİTAK kontör sorgulama request gönderiliyor: {}", tspServerUrl);
org.apache.http.impl.client.CloseableHttpClient httpClient =
org.apache.http.impl.client.HttpClients.createDefault();
try (org.apache.http.client.methods.CloseableHttpResponse response =
httpClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();
LOGGER.debug("HTTP response status: {}", statusCode);
if (statusCode != 200) {
throw new RuntimeException("HTTP error: " + statusCode);
}
// Response body'yi parse et
byte[] responseBytes = org.apache.http.util.EntityUtils.toByteArray(
response.getEntity());
// TÜBİTAK kontör bilgisini response'dan çıkar
// Response ASN.1 formatında olabilir veya özel format olabilir
return parseResponseForCredit(responseBytes);
} finally {
httpClient.close();
}
}
/**
* Response'dan kontör bilgisini parse eder.
*/
private String parseResponseForCredit(byte[] responseBytes) {
// TÜBİTAK plain text olarak kontör dönüyor
return new String(responseBytes).trim();
}
/**
* Response string'inden kontör sayısını çıkarır.
*/
private Long parseCreditFromResponse(String response) {
try {
// Response direkt sayı olarak geliyor
return Long.parseLong(response.trim());
} catch (Exception e) {
LOGGER.warn("Kontör bilgisi parse edilemedi: {}", e.getMessage());
return 0L;
}
}
/**
* Servisin kullanılabilir olup olmadığını kontrol eder.
*/
public boolean isAvailable() {
return isTubitakTsp &&
StringUtils.hasText(tspServerUrl) &&
StringUtils.hasText(tspUserId) &&
StringUtils.hasText(tspUserPassword);
}
}