Initial support for reading AES-encrypted/write-protected OOXML files
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@948825 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5cfa676c0b
commit
59f8f913e3
133
src/java/org/apache/poi/poifs/crypt/Decryptor.java
Normal file
133
src/java/org/apache/poi/poifs/crypt/Decryptor.java
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxim Valyanskiy
|
||||||
|
*/
|
||||||
|
public class Decryptor {
|
||||||
|
public static final String DEFAULT_PASSWORD="VelvetSweatshop";
|
||||||
|
|
||||||
|
private final EncryptionInfo info;
|
||||||
|
private byte[] passwordHash;
|
||||||
|
|
||||||
|
public Decryptor(EncryptionInfo info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generatePasswordHash(String password) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
|
||||||
|
sha1.update(info.getVerifier().getSalt());
|
||||||
|
byte[] hash = sha1.digest(password.getBytes(Charset.forName("UTF-16LE")));
|
||||||
|
|
||||||
|
byte[] iterator = new byte[4];
|
||||||
|
for (int i = 0; i<50000; i++) {
|
||||||
|
sha1.reset();
|
||||||
|
|
||||||
|
LittleEndian.putInt(iterator, i);
|
||||||
|
sha1.update(iterator);
|
||||||
|
hash = sha1.digest(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordHash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateKey(int block) throws NoSuchAlgorithmException {
|
||||||
|
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
|
||||||
|
sha1.update(passwordHash);
|
||||||
|
byte[] blockValue = new byte[4];
|
||||||
|
LittleEndian.putInt(blockValue, block);
|
||||||
|
byte[] finalHash = sha1.digest(blockValue);
|
||||||
|
|
||||||
|
int requiredKeyLength = info.getHeader().getKeySize()/8;
|
||||||
|
|
||||||
|
byte[] buff = new byte[64];
|
||||||
|
|
||||||
|
Arrays.fill(buff, (byte) 0x36);
|
||||||
|
|
||||||
|
for (int i=0; i<finalHash.length; i++) {
|
||||||
|
buff[i] = (byte) (buff[i] ^ finalHash[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sha1.reset();
|
||||||
|
byte[] x1 = sha1.digest(buff);
|
||||||
|
|
||||||
|
Arrays.fill(buff, (byte) 0x5c);
|
||||||
|
for (int i=0; i<finalHash.length; i++) {
|
||||||
|
buff[i] = (byte) (buff[i] ^ finalHash[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sha1.reset();
|
||||||
|
byte[] x2 = sha1.digest(buff);
|
||||||
|
|
||||||
|
byte[] x3 = new byte[x1.length + x2.length];
|
||||||
|
System.arraycopy(x1, 0, x3, 0, x1.length);
|
||||||
|
System.arraycopy(x2, 0, x3, x1.length, x2.length);
|
||||||
|
|
||||||
|
return Arrays.copyOf(x3, requiredKeyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifyPassword(String password) throws GeneralSecurityException {
|
||||||
|
generatePasswordHash(password);
|
||||||
|
|
||||||
|
Cipher cipher = getCipher();
|
||||||
|
|
||||||
|
byte[] verifier = cipher.doFinal(info.getVerifier().getVerifier());
|
||||||
|
|
||||||
|
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
byte[] calcVerifierHash = sha1.digest(verifier);
|
||||||
|
|
||||||
|
byte[] verifierHash = Arrays.copyOf(cipher.doFinal(info.getVerifier().getVerifierHash()), calcVerifierHash.length);
|
||||||
|
|
||||||
|
return Arrays.equals(calcVerifierHash, verifierHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher getCipher() throws GeneralSecurityException {
|
||||||
|
byte[] key = generateKey(0);
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
|
SecretKey skey = new SecretKeySpec(key, "AES");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, skey);
|
||||||
|
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getDataStream(POIFSFileSystem fs) throws IOException, GeneralSecurityException {
|
||||||
|
DocumentInputStream dis = fs.createDocumentInputStream("EncryptedPackage");
|
||||||
|
|
||||||
|
long size = dis.readLong();
|
||||||
|
|
||||||
|
return new CipherInputStream(dis, getCipher());
|
||||||
|
}
|
||||||
|
}
|
97
src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
Normal file
97
src/java/org/apache/poi/poifs/crypt/EncryptionHeader.java
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxim Valyanskiy
|
||||||
|
*/
|
||||||
|
public class EncryptionHeader {
|
||||||
|
public static final int ALGORITHM_RC4 = 0x6801;
|
||||||
|
public static final int ALGORITHM_AES_128 = 0x660E;
|
||||||
|
public static final int ALGORITHM_AES_192 = 0x660F;
|
||||||
|
public static final int ALGORITHM_AES_256 = 0x6610;
|
||||||
|
|
||||||
|
public static final int HASH_SHA1 = 0x8004;
|
||||||
|
|
||||||
|
public static final int PROVIDER_RC4 = 1;
|
||||||
|
public static final int PROVIDER_AES = 0x18;
|
||||||
|
|
||||||
|
private final int flags;
|
||||||
|
private final int sizeExtra;
|
||||||
|
private final int algorithm;
|
||||||
|
private final int hashAlgorithm;
|
||||||
|
private final int keySize;
|
||||||
|
private final int providerType;
|
||||||
|
private final String cspName;
|
||||||
|
|
||||||
|
public EncryptionHeader(DocumentInputStream is) throws IOException {
|
||||||
|
flags = is.readInt();
|
||||||
|
sizeExtra = is.readInt();
|
||||||
|
algorithm = is.readInt();
|
||||||
|
hashAlgorithm = is.readInt();
|
||||||
|
keySize = is.readInt();
|
||||||
|
providerType = is.readInt();
|
||||||
|
|
||||||
|
is.readLong(); // skip reserved
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
char c = (char) is.readShort();
|
||||||
|
|
||||||
|
if (c == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
cspName = builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSizeExtra() {
|
||||||
|
return sizeExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAlgorithm() {
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHashAlgorithm() {
|
||||||
|
return hashAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeySize() {
|
||||||
|
return keySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProviderType() {
|
||||||
|
return providerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCspName() {
|
||||||
|
return cspName;
|
||||||
|
}
|
||||||
|
}
|
72
src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
Normal file
72
src/java/org/apache/poi/poifs/crypt/EncryptionInfo.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxim Valyanskiy
|
||||||
|
*/
|
||||||
|
public class EncryptionInfo {
|
||||||
|
private final int versionMajor;
|
||||||
|
private final int versionMinor;
|
||||||
|
private final int encryptionFlags;
|
||||||
|
|
||||||
|
private final EncryptionHeader header;
|
||||||
|
private final EncryptionVerifier verifier;
|
||||||
|
|
||||||
|
public EncryptionInfo(POIFSFileSystem fs) throws IOException {
|
||||||
|
DocumentInputStream dis = fs.createDocumentInputStream("EncryptionInfo");
|
||||||
|
|
||||||
|
versionMajor = dis.readShort();
|
||||||
|
versionMinor = dis.readShort();
|
||||||
|
encryptionFlags = dis.readInt();
|
||||||
|
|
||||||
|
int hSize = dis.readInt();
|
||||||
|
|
||||||
|
header = new EncryptionHeader(dis);
|
||||||
|
|
||||||
|
if (header.getAlgorithm()==EncryptionHeader.ALGORITHM_RC4) {
|
||||||
|
verifier = new EncryptionVerifier(dis, 20);
|
||||||
|
} else {
|
||||||
|
verifier = new EncryptionVerifier(dis, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersionMajor() {
|
||||||
|
return versionMajor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersionMinor() {
|
||||||
|
return versionMinor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEncryptionFlags() {
|
||||||
|
return encryptionFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptionHeader getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptionVerifier getVerifier() {
|
||||||
|
return verifier;
|
||||||
|
}
|
||||||
|
}
|
57
src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
Normal file
57
src/java/org/apache/poi/poifs/crypt/EncryptionVerifier.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxim Valyanskiy
|
||||||
|
*/
|
||||||
|
public class EncryptionVerifier {
|
||||||
|
private final byte[] salt = new byte[16];
|
||||||
|
private final byte[] verifier = new byte[16];
|
||||||
|
private final byte[] verifierHash;
|
||||||
|
private final int verifierHashSize;
|
||||||
|
|
||||||
|
public EncryptionVerifier(DocumentInputStream is, int encryptedLength) {
|
||||||
|
int saltSize = is.readInt();
|
||||||
|
|
||||||
|
if (saltSize!=16) {
|
||||||
|
throw new RuntimeException("Salt size != 16 !?");
|
||||||
|
}
|
||||||
|
|
||||||
|
is.readFully(salt);
|
||||||
|
is.readFully(verifier);
|
||||||
|
|
||||||
|
verifierHashSize = is.readInt();
|
||||||
|
|
||||||
|
verifierHash = new byte[encryptedLength];
|
||||||
|
is.readFully(verifierHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSalt() {
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getVerifier() {
|
||||||
|
return verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getVerifierHash() {
|
||||||
|
return verifierHash;
|
||||||
|
}
|
||||||
|
}
|
68
src/testcases/org/apache/poi/poifs/crypt/DecryptorTest.java
Normal file
68
src/testcases/org/apache/poi/poifs/crypt/DecryptorTest.java
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import org.apache.poi.POIDataSamples;
|
||||||
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxim Valyanskiy
|
||||||
|
*/
|
||||||
|
public class DecryptorTest extends TestCase {
|
||||||
|
public void testPasswordVerification() throws IOException, GeneralSecurityException {
|
||||||
|
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
|
||||||
|
|
||||||
|
EncryptionInfo info = new EncryptionInfo(fs);
|
||||||
|
|
||||||
|
Decryptor d = new Decryptor(info);
|
||||||
|
|
||||||
|
assertTrue(d.verifyPassword(Decryptor.DEFAULT_PASSWORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDecrypt() throws IOException, GeneralSecurityException {
|
||||||
|
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
|
||||||
|
|
||||||
|
EncryptionInfo info = new EncryptionInfo(fs);
|
||||||
|
|
||||||
|
Decryptor d = new Decryptor(info);
|
||||||
|
|
||||||
|
d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
|
||||||
|
|
||||||
|
zipOk(fs, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void zipOk(POIFSFileSystem fs, Decryptor d) throws IOException, GeneralSecurityException {
|
||||||
|
ZipInputStream zin = new ZipInputStream(d.getDataStream(fs));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ZipEntry entry = zin.getNextEntry();
|
||||||
|
if (entry==null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (zin.available()>0) {
|
||||||
|
zin.skip(zin.available());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
package org.apache.poi.poifs.crypt;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import org.apache.poi.POIDataSamples;
|
||||||
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Maxim Valyanskiy
|
||||||
|
*/
|
||||||
|
public class EncryptionInfoTest extends TestCase {
|
||||||
|
public void testEncryptionInfo() throws IOException {
|
||||||
|
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
|
||||||
|
|
||||||
|
EncryptionInfo info = new EncryptionInfo(fs);
|
||||||
|
|
||||||
|
assertEquals(3, info.getVersionMajor());
|
||||||
|
assertEquals(2, info.getVersionMinor());
|
||||||
|
|
||||||
|
assertEquals(EncryptionHeader.ALGORITHM_AES_128, info.getHeader().getAlgorithm());
|
||||||
|
assertEquals(EncryptionHeader.HASH_SHA1, info.getHeader().getHashAlgorithm());
|
||||||
|
assertEquals(128, info.getHeader().getKeySize());
|
||||||
|
assertEquals(EncryptionHeader.PROVIDER_AES, info.getHeader().getProviderType());
|
||||||
|
assertEquals("Microsoft Enhanced RSA and AES Cryptographic Provider", info.getHeader().getCspName());
|
||||||
|
|
||||||
|
assertEquals(32, info.getVerifier().getVerifierHash().length);
|
||||||
|
}
|
||||||
|
}
|
BIN
test-data/poifs/protect.xlsx
Normal file
BIN
test-data/poifs/protect.xlsx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user