mirror of
https://github.com/nsacyber/HIRS.git
synced 2024-12-19 04:58:00 +00:00
Saving LDevID to local key files
This commit is contained in:
parent
8b92b31e5c
commit
b152a3421c
@ -236,10 +236,11 @@ public class AbstractProcessor {
|
||||
* @param platformCredentials the platform credentials used to generate the AC
|
||||
* @param device the device to which the attestation certificate is tied
|
||||
* @param isLDevID whether the certificate is a ldevid
|
||||
* @return whether the certificate was saved successfully
|
||||
* @throws {@link CertificateProcessingException} if error occurs in persisting the Attestation
|
||||
* Certificate
|
||||
*/
|
||||
public void saveAttestationCertificate(final CertificateRepository certificateRepository,
|
||||
public boolean saveAttestationCertificate(final CertificateRepository certificateRepository,
|
||||
final byte[] derEncodedAttestationCertificate,
|
||||
final EndorsementCredential endorsementCredential,
|
||||
final List<PlatformCredential> platformCredentials,
|
||||
@ -264,7 +265,7 @@ public class AbstractProcessor {
|
||||
generateCertificate = isLDevID ? policySettings.isIssueDevIdCertificate()
|
||||
: policySettings.isIssueAttestationCertificate();
|
||||
|
||||
if (issuedAc != null && (isLDevID ? policySettings.isDevIdExpirationFlag()
|
||||
if (issuedAc != null && issuedAc.size() > 0 && (isLDevID ? policySettings.isDevIdExpirationFlag()
|
||||
: policySettings.isGenerateOnExpiration())) {
|
||||
if (issuedAc.get(0).getEndValidity().after(currentDate)) {
|
||||
// so the issued AC is not expired
|
||||
@ -291,6 +292,8 @@ public class AbstractProcessor {
|
||||
"Encountered error while storing Attestation Certificate: "
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
|
||||
return generateCertificate;
|
||||
}
|
||||
|
||||
private List<PlatformCredential> getPlatformCredentials(final CertificateRepository certificateRepository,
|
||||
|
@ -112,7 +112,10 @@ public class CertificateRequestProcessor extends AbstractProcessor {
|
||||
endorsementCredential, certificateRepository);
|
||||
|
||||
// Get LDevID public key if it exists
|
||||
RSAPublicKey ldevidPub = ProvisionUtils.parsePublicKey(claim.getLdevidPublicArea().toByteArray());
|
||||
RSAPublicKey ldevidPub = null;
|
||||
if (claim.hasLdevidPublicArea()) {
|
||||
ldevidPub = ProvisionUtils.parsePublicKey(claim.getLdevidPublicArea().toByteArray());
|
||||
}
|
||||
|
||||
// Get device name and device
|
||||
String deviceName = claim.getDv().getNw().getHostname();
|
||||
@ -149,33 +152,63 @@ public class CertificateRequestProcessor extends AbstractProcessor {
|
||||
// Create signed, attestation certificate
|
||||
X509Certificate attestationCertificate = generateCredential(akPub,
|
||||
endorsementCredential, platformCredentials, deviceName, acaCertificate);
|
||||
X509Certificate ldevidCertificate = generateCredential(ldevidPub,
|
||||
endorsementCredential, platformCredentials, deviceName, acaCertificate);
|
||||
byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate(
|
||||
attestationCertificate);
|
||||
byte[] derEncodedLdevidCertificate = ProvisionUtils.getDerEncodedCertificate(
|
||||
ldevidCertificate);
|
||||
if (ldevidPub != null) {
|
||||
// Create signed LDevID certificate
|
||||
X509Certificate ldevidCertificate = generateCredential(ldevidPub,
|
||||
endorsementCredential, platformCredentials, deviceName, acaCertificate);
|
||||
byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate(
|
||||
attestationCertificate);
|
||||
byte[] derEncodedLdevidCertificate = ProvisionUtils.getDerEncodedCertificate(
|
||||
ldevidCertificate);
|
||||
|
||||
// We validated the nonce and made use of the identity claim so state can be deleted
|
||||
tpm2ProvisionerStateRepository.delete(tpm2ProvisionerState);
|
||||
// We validated the nonce and made use of the identity claim so state can be deleted
|
||||
tpm2ProvisionerStateRepository.delete(tpm2ProvisionerState);
|
||||
|
||||
// Package the signed certificates into a response
|
||||
ByteString certificateBytes = ByteString
|
||||
.copyFrom(derEncodedAttestationCertificate);
|
||||
ByteString ldevidCertificateBytes = ByteString
|
||||
.copyFrom(derEncodedLdevidCertificate);
|
||||
ProvisionerTpm2.CertificateResponse response = ProvisionerTpm2.CertificateResponse
|
||||
.newBuilder().setCertificate(certificateBytes)
|
||||
.setLdevidCertificate(ldevidCertificateBytes)
|
||||
.setStatus(ProvisionerTpm2.ResponseStatus.PASS)
|
||||
.build();
|
||||
// Package the signed certificates into a response
|
||||
ByteString certificateBytes = ByteString
|
||||
.copyFrom(derEncodedAttestationCertificate);
|
||||
ByteString ldevidCertificateBytes = ByteString
|
||||
.copyFrom(derEncodedLdevidCertificate);
|
||||
|
||||
saveAttestationCertificate(certificateRepository, derEncodedAttestationCertificate,
|
||||
endorsementCredential, platformCredentials, device, false);
|
||||
saveAttestationCertificate(certificateRepository, derEncodedLdevidCertificate,
|
||||
endorsementCredential, platformCredentials, device, true);
|
||||
boolean generateAtt = saveAttestationCertificate(certificateRepository, derEncodedAttestationCertificate,
|
||||
endorsementCredential, platformCredentials, device, false);
|
||||
boolean generateLDevID = saveAttestationCertificate(certificateRepository, derEncodedLdevidCertificate,
|
||||
endorsementCredential, platformCredentials, device, true);
|
||||
|
||||
return response.toByteArray();
|
||||
ProvisionerTpm2.CertificateResponse.Builder builder = ProvisionerTpm2.CertificateResponse.
|
||||
newBuilder().setStatus(ProvisionerTpm2.ResponseStatus.PASS);
|
||||
if (generateAtt) {
|
||||
builder = builder.setCertificate(certificateBytes);
|
||||
}
|
||||
if (generateLDevID) {
|
||||
builder = builder.setLdevidCertificate(ldevidCertificateBytes);
|
||||
}
|
||||
ProvisionerTpm2.CertificateResponse response = builder.build();
|
||||
|
||||
return response.toByteArray();
|
||||
}
|
||||
else {
|
||||
byte[] derEncodedAttestationCertificate = ProvisionUtils.getDerEncodedCertificate(
|
||||
attestationCertificate);
|
||||
|
||||
// We validated the nonce and made use of the identity claim so state can be deleted
|
||||
tpm2ProvisionerStateRepository.delete(tpm2ProvisionerState);
|
||||
|
||||
// Package the signed certificates into a response
|
||||
ByteString certificateBytes = ByteString
|
||||
.copyFrom(derEncodedAttestationCertificate);
|
||||
ProvisionerTpm2.CertificateResponse.Builder builder = ProvisionerTpm2.CertificateResponse.
|
||||
newBuilder().setStatus(ProvisionerTpm2.ResponseStatus.PASS);
|
||||
|
||||
boolean generateAtt = saveAttestationCertificate(certificateRepository, derEncodedAttestationCertificate,
|
||||
endorsementCredential, platformCredentials, device, false);
|
||||
if (generateAtt) {
|
||||
builder = builder.setCertificate(certificateBytes);
|
||||
}
|
||||
ProvisionerTpm2.CertificateResponse response = builder.build();
|
||||
|
||||
return response.toByteArray();
|
||||
}
|
||||
} else {
|
||||
log.error("Supply chain validation did not succeed. "
|
||||
+ "Firmware Quote Validation failed. Result is: "
|
||||
|
@ -2,6 +2,7 @@
|
||||
"auto_detect_tpm": "TRUE",
|
||||
"aca_address_port": "https://127.0.0.1:8443",
|
||||
"efi_prefix": "",
|
||||
"ldevid_prefix": "",
|
||||
"paccor_output_file": "",
|
||||
"event_log_file": "",
|
||||
"hardware_manifest_collectors": "paccor_scripts",
|
||||
|
@ -69,7 +69,7 @@ namespace hirs {
|
||||
|
||||
public IdentityClaim CreateIdentityClaim(DeviceInfo dv, byte[] akPublicArea, byte[] ekPublicArea,
|
||||
byte[] endorsementCredential, List<byte[]> platformCredentials,
|
||||
string paccoroutput, byte[] ldevidPublicArea) {
|
||||
string paccoroutput, byte[] ldevidPublicArea = null) {
|
||||
IdentityClaim identityClaim = new();
|
||||
identityClaim.Dv = dv;
|
||||
identityClaim.AkPublicArea = ByteString.CopyFrom(akPublicArea);
|
||||
@ -81,7 +81,7 @@ namespace hirs {
|
||||
}
|
||||
}
|
||||
identityClaim.PaccorOutput = paccoroutput;
|
||||
identityClaim.LdevidPublicArea = ByteString.CopyFrom(ldevidPublicArea);
|
||||
if (ldevidPublicArea != null) identityClaim.LdevidPublicArea = ByteString.CopyFrom(ldevidPublicArea);
|
||||
|
||||
return identityClaim;
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ namespace hirs {
|
||||
linux_sys_vendor_file,
|
||||
linux_product_name_file,
|
||||
linux_product_version_file,
|
||||
linux_product_serial_file
|
||||
linux_product_serial_file,
|
||||
ldevid_prefix
|
||||
}
|
||||
|
||||
private static readonly string DEFAULT_SETTINGS_FILE = "appsettings.json";
|
||||
@ -72,6 +73,9 @@ namespace hirs {
|
||||
public virtual string linux_product_serial {
|
||||
get; private set;
|
||||
}
|
||||
public virtual string ldevid_prefix {
|
||||
get; private set;
|
||||
}
|
||||
private List<IHardwareManifest> hardwareManifests = new();
|
||||
private Dictionary<string, string> hardware_manifest_collectors_with_args = new();
|
||||
private bool hardware_manifest_collection_swid_enforced = false;
|
||||
@ -126,6 +130,8 @@ namespace hirs {
|
||||
|
||||
CheckEfiPrefix();
|
||||
|
||||
CheckLDevIDPrefix();
|
||||
|
||||
IngestEventLogFromFile();
|
||||
|
||||
StoreCustomDeviceInfoCollectorOptions();
|
||||
@ -268,9 +274,30 @@ namespace hirs {
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region
|
||||
private void CheckLDevIDPrefix() {
|
||||
if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.ldevid_prefix.ToString()])) {
|
||||
Log.Debug("Checking LDevID Prefix setting.");
|
||||
ldevid_prefix = $"{ configFromSettingsFile[Options.ldevid_prefix.ToString()] }";
|
||||
if (!string.IsNullOrWhiteSpace(ldevid_prefix)) {
|
||||
if (!Directory.Exists(ldevid_prefix)) {
|
||||
Log.Debug(Options.ldevid_prefix.ToString() + ": " + ldevid_prefix + " did not exist.");
|
||||
ldevid_prefix = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ldevid_prefix == null) {
|
||||
Log.Warning(Options.ldevid_prefix.ToString() + " not set in the settings file. Defaulting to the current working directory.");
|
||||
ldevid_prefix = "";
|
||||
} else {
|
||||
Log.Debug(" Will scan for LDevID keys in " + ldevid_prefix);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EFI
|
||||
private void CheckEfiPrefix() {
|
||||
if (configFromSettingsFile[Options.efi_prefix.ToString()] != null) {
|
||||
if (!string.IsNullOrWhiteSpace(configFromSettingsFile[Options.efi_prefix.ToString()])) {
|
||||
Log.Debug("Checking EFI Prefix setting.");
|
||||
efi_prefix = $"{ configFromSettingsFile[Options.efi_prefix.ToString()] }";
|
||||
if (string.IsNullOrWhiteSpace(efi_prefix)) { // If not explicitly set in appsettings, try to use default EFI location on Linux
|
||||
|
@ -15,6 +15,9 @@ namespace hirs {
|
||||
private IHirsDeviceInfoCollector deviceInfoCollector = null;
|
||||
private IHirsAcaClient acaClient = null;
|
||||
|
||||
private const string DefaultLDevIDPubKeyFileName = "ldevid.pub";
|
||||
private const string DefaultLDevIDPrivKeyFileName = "ldevid.priv";
|
||||
|
||||
private const string DefaultCertFileName = "attestationkey.pem";
|
||||
private const string DefaultLDevIDCertFileName = "ldevid.pem";
|
||||
|
||||
@ -146,10 +149,12 @@ namespace hirs {
|
||||
byte[] srkPublicArea = tpm.ReadPublicArea(CommandTpm.DefaultSrkHandle, out byte[] name2, out byte[] qualifiedName2);
|
||||
|
||||
Log.Information("----> " + (cli.ReplaceLDevID ? "Creating new" : "Verifying existence of") + " LDevID Key.");
|
||||
tpm.CreateLDevIDKey(CommandTpm.DefaultSrkHandle, CommandTpm.DefaultLDevIDHandle, cli.ReplaceLDevID);
|
||||
string ldevidPubPath = settings.ldevid_prefix + DefaultLDevIDPubKeyFileName;
|
||||
string ldevidPrivPath = settings.ldevid_prefix + DefaultLDevIDPrivKeyFileName;
|
||||
tpm.CreateLDevIDKey(CommandTpm.DefaultSrkHandle, ldevidPubPath, ldevidPrivPath, cli.ReplaceLDevID);
|
||||
|
||||
Log.Debug("Gathering LDevID PUBLIC.");
|
||||
byte[] ldevidPublicArea = tpm.ReadPublicArea(CommandTpm.DefaultLDevIDHandle, out name, out qualifiedName);
|
||||
byte[] ldevidPublicArea = tpm.ConvertLDevIDPublic(ldevidPubPath);
|
||||
|
||||
List<byte[]> pcs = null, baseRims = null, supportRimELs = null, supportRimPCRs = null;
|
||||
if (settings.HasEfiPrefix()) {
|
||||
@ -281,7 +286,7 @@ namespace hirs {
|
||||
Log.Debug("Communicate certificate request to the ACA.");
|
||||
CertificateResponse cr = await acaClient.PostCertificateRequest(akCertReq);
|
||||
Log.Debug("Response received from the ACA regarding the certificate request.");
|
||||
if (cr.HasStatus) {
|
||||
if (cr.HasStatus) {
|
||||
if (cr.Status == ResponseStatus.Pass) {
|
||||
Log.Debug("ACA returned a positive response to the Certificate Request.");
|
||||
} else {
|
||||
|
@ -27,7 +27,6 @@ namespace hirs {
|
||||
public const uint DefaultEkHandle = 0x81010001;
|
||||
public const uint DefaultAkHandle = 0x81010002;
|
||||
public const uint DefaultSrkHandle = 0x81000001;
|
||||
public const uint DefaultLDevIDHandle = 0x81000002;
|
||||
|
||||
private readonly Tpm2 tpm;
|
||||
|
||||
@ -266,7 +265,7 @@ namespace hirs {
|
||||
|
||||
private static RsaParms LDevIDRSAParms() {
|
||||
TpmAlgId digestAlg = TpmAlgId.Sha256;
|
||||
RsaParms parms = new(new SymDefObject(TpmAlgId.Null, 0, TpmAlgId.Null), new SchemeRsassa(digestAlg), 2048, 0);
|
||||
RsaParms parms = new(new SymDefObject(TpmAlgId.Null, 0, TpmAlgId.Null), null, 2048, 0);
|
||||
return parms;
|
||||
}
|
||||
|
||||
@ -365,37 +364,21 @@ namespace hirs {
|
||||
Log.Debug("Flushed the context for the transient SRK.");
|
||||
}
|
||||
|
||||
public void CreateLDevIDKey(uint srkHandleInt, uint ldevidHandleInt, bool replace) {
|
||||
public void CreateLDevIDKey(uint srkHandleInt, string pubPath, string privPath, bool replace) {
|
||||
TpmHandle srkHandle = new(srkHandleInt);
|
||||
TpmHandle ldevidHandle = new(ldevidHandleInt);
|
||||
|
||||
TpmPublic existingObject = null;
|
||||
try {
|
||||
existingObject = tpm.ReadPublic(ldevidHandle, out byte[] name, out byte[] qualifiedName);
|
||||
} catch { }
|
||||
|
||||
if (!replace && existingObject != null) {
|
||||
if (!replace && File.Exists(privPath) && File.Exists(pubPath)) {
|
||||
// Do Nothing
|
||||
Log.Debug("LDevID exists at expected handle. Flag to not replace the LDevID is set in the settings file.");
|
||||
Log.Debug("LDevID exists at local file system path. Flag to not replace the LDevID is set in the settings file.");
|
||||
return;
|
||||
} else if (replace && existingObject != null) {
|
||||
// Clear the object and continue
|
||||
tpm.EvictControl(TpmRh.Owner, ldevidHandle, ldevidHandle);
|
||||
Log.Debug("Removed previous LDevID.");
|
||||
}
|
||||
|
||||
// Create a new key and make it persistent at ldevidHandle
|
||||
// Create a new transient key
|
||||
TpmAlgId nameAlg = TpmAlgId.Sha256;
|
||||
|
||||
SensitiveCreate inSens = new();
|
||||
TpmPublic inPublic = GenerateLDevIDTemplate(nameAlg);
|
||||
|
||||
/*var policySRK = new PolicyTree(nameAlg);
|
||||
policySRK.SetPolicyRoot(new TpmPolicySecret(TpmRh.Owner, false, 0, null, null));
|
||||
|
||||
AuthSession sessSRK = tpm.StartAuthSessionEx(TpmSe.Policy, nameAlg);
|
||||
sessSRK.RunPolicy(tpm, policySRK);*/
|
||||
|
||||
TpmPrivate kLDevID = tpm.Create(srkHandle, inSens, inPublic, null, null, out TpmPublic outPublic,
|
||||
out CreationData creationData, out byte[] creationHash, out TkCreation ticket);
|
||||
|
||||
@ -403,17 +386,20 @@ namespace hirs {
|
||||
Log.Debug("New LDevID PUB 2BREP: " + BitConverter.ToString(outPublic.GetTpm2BRepresentation()));
|
||||
Log.Debug("New LDevID PUB unique: " + BitConverter.ToString((Tpm2bPublicKeyRsa)(outPublic.unique)));
|
||||
|
||||
/*tpm.FlushContext(sessSRK);
|
||||
Tpm2bPublic ldevidPublic = new Tpm2bPublic(outPublic);
|
||||
|
||||
sessSRK = tpm.StartAuthSessionEx(TpmSe.Policy, nameAlg);
|
||||
sessSRK.RunPolicy(tpm, policySRK);*/
|
||||
File.WriteAllBytes(pubPath, ldevidPublic);
|
||||
File.WriteAllBytes(privPath, kLDevID);
|
||||
Log.Debug("Created new LDevID at local file system paths.");
|
||||
Log.Debug(" LDevID Pub Path: {0}", pubPath);
|
||||
Log.Debug(" LDevID Priv Path: {0}", privPath);
|
||||
}
|
||||
|
||||
TpmHandle hLDevID = tpm.Load(srkHandle, kLDevID, outPublic);
|
||||
|
||||
tpm.EvictControl(TpmRh.Owner, hLDevID, ldevidHandle);
|
||||
Log.Debug("Created and persisted new LDevID at handle 0x" + ldevidHandle.handle.ToString("X") + ".");
|
||||
|
||||
//tpm.FlushContext(sessSRK);
|
||||
public byte[] ConvertLDevIDPublic(string ldevidPubPath) {
|
||||
byte[] ldevidPubBytes = File.ReadAllBytes(ldevidPubPath);
|
||||
var marshaller = new Marshaller(ldevidPubBytes, DataRepresentation.Tpm);
|
||||
Tpm2bPublic ldevidPublic = marshaller.Get<Tpm2bPublic>();
|
||||
return ldevidPublic.publicArea;
|
||||
}
|
||||
|
||||
public Tpm2bDigest[] GetPcrList(TpmAlgId pcrBankDigestAlg, uint[] pcrs = null) {
|
||||
|
@ -10,7 +10,8 @@ namespace hirs {
|
||||
void CreateEndorsementKey(uint ekHandleInt);
|
||||
void CreateAttestationKey(uint ekHandleInt, uint akHandleInt, bool replace);
|
||||
void CreateStorageRootKey(uint srkHandleInt);
|
||||
void CreateLDevIDKey(uint srkHandleInt, uint ldevidHandleInt, bool replace);
|
||||
void CreateLDevIDKey(uint srkHandleInt, string pubPath, string privPath, bool replace);
|
||||
byte[] ConvertLDevIDPublic(string ldevidPubPath);
|
||||
Tpm2bDigest[] GetPcrList(TpmAlgId pcrBankDigestAlg, uint[] pcrs = null);
|
||||
void GetQuote(uint akHandleInt, TpmAlgId pcrBankDigestAlg, byte[] nonce, out CommandTpmQuoteResponse ctqr, uint[] pcrs = null);
|
||||
byte[] ActivateCredential(uint akHandleInt, uint ekHandleInt, byte[] integrityHMAC, byte[] encIdentity, byte[] encryptedSecret);
|
||||
|
@ -45,8 +45,7 @@ namespace hirsTest {
|
||||
A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultAkHandle, out name, out qualifiedName)).Returns(akPublic);
|
||||
A.CallTo(() => tpm.CreateStorageRootKey(CommandTpm.DefaultSrkHandle)).DoesNothing();
|
||||
A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultSrkHandle, out name, out qualifiedName)).Returns(srkPublic);
|
||||
A.CallTo(() => tpm.CreateLDevIDKey(CommandTpm.DefaultSrkHandle, CommandTpm.DefaultLDevIDHandle, false)).DoesNothing();
|
||||
A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultLDevIDHandle, out name, out qualifiedName)).Returns(ldevidPublic);
|
||||
A.CallTo(() => tpm.CreateLDevIDKey(CommandTpm.DefaultSrkHandle, "", "", false)).DoesNothing();
|
||||
//A.CallTo(() => tpm.getPcrList(TpmAlgId.Sha1, A<uint[]>.Ignored)).Returns(sha1Values);
|
||||
//A.CallTo(() => tpm.getPcrList(TpmAlgId.Sha256, A<uint[]>.Ignored)).Returns(sha256Values);
|
||||
A.CallTo(() => tpm.GetQuote(CommandTpm.DefaultAkHandle, TpmAlgId.Sha256, secret, out ctqr, A<uint[]>.Ignored)).DoesNothing();
|
||||
@ -103,8 +102,7 @@ namespace hirsTest {
|
||||
A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultAkHandle, out name, out qualifiedName)).Returns(ekPublic);
|
||||
A.CallTo(() => tpm.CreateStorageRootKey(CommandTpm.DefaultSrkHandle)).DoesNothing();
|
||||
A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultSrkHandle, out name, out qualifiedName)).Returns(srkPublic);
|
||||
A.CallTo(() => tpm.CreateLDevIDKey(CommandTpm.DefaultSrkHandle, CommandTpm.DefaultLDevIDHandle, false)).DoesNothing();
|
||||
A.CallTo(() => tpm.ReadPublicArea(CommandTpm.DefaultLDevIDHandle, out name, out qualifiedName)).Returns(ldevidPublic);
|
||||
A.CallTo(() => tpm.CreateLDevIDKey(CommandTpm.DefaultSrkHandle, "", "", false)).DoesNothing();
|
||||
A.CallTo(() => tpm.GetPcrList(TpmAlgId.Sha1, A<uint[]>.Ignored)).Returns(sha1Values);
|
||||
A.CallTo(() => tpm.GetPcrList(TpmAlgId.Sha256, A<uint[]>.Ignored)).Returns(sha256Values);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user