public final class Hash {

    private Hash() {}

    /**
     * 计算字符串的MD5摘要
     * @param str 要计算的字符串
     * @return MD5摘要
     */
    @Deprecated
    public final static byte[] md5(String str) {
        return digest(str, "MD5");
    }

    /**
     * 计算字符串的SHA-1摘要
     * @param str 要计算的字符串
     * @return SHA-1摘要
     */
    @Deprecated
    public final static byte[] sha1(String str) {
        return digest(str, "SHA-1");
    }

    /**
     * 计算字符串的SHA-256摘要
     * @param str 要计算的字符串
     * @return SHA-256摘要
     */
    public final static byte[] sha256(String str) {
        return digest(str, "SHA-256");
    }

    /**
     * 计算字符串的SHA-512摘要
     * @param str 要计算的字符串
     * @return SHA-512摘要
     */
    public final static byte[] sha512(String str) {
        return digest(str, "SHA-512");
    }

    /**
     * 计算字节数组的MD5摘要
     * @param bytes 要计算的字节数组
     * @return MD5摘要
     */
    // There is no Deprecated annotation as this method is used to generate 16 bytes
    // array from data of arbitrary length
    public final static byte[] md5(byte[] bytes) {
        return digest(bytes, "MD5");
    }

    /**
     * 计算字节数组的SHA-1摘要
     * @param bytes 要计算的字节数组
     * @return SHA-1摘要
     */
    @Deprecated
    public final static byte[] sha1(byte[] bytes) {
        return digest(bytes, "SHA-1");
    }

    /**
     * 计算字节数组的SHA-256摘要
     * @param bytes 要计算的字节数组
     * @return SHA-256摘要
     */
    public final static byte[] sha256(byte[] bytes) {
        return digest(bytes, "SHA-256");
    }

    /**
     * 计算字节数组的SHA-512摘要
     * @param bytes 要计算的字节数组
     * @return SHA-512摘要
     */
    public final static byte[] sha512(byte[] bytes) {
        return digest(bytes, "SHA-512");
    }

    /**
     * 计算文件的MD5摘要
     * @param file 要计算的文件
     * @return 文件的MD5摘要
     * @throws IOException IO异常 如文件不存在
     */
    public final static byte[] md5(File file) throws IOException {
        return digest(new FileInputStream(file), file.length(), "MD5");
    }

    /**
     * 计算文件的SHA-1摘要
     * @param file 要计算的文件
     * @return 文件的SHA-1摘要
     * @throws IOException IO异常 如文件不存在
     */
    public final static byte[] sha1(File file) throws IOException {
        return digest(new FileInputStream(file), file.length(), "SHA-1");
    }

    /**
     * 计算文件的SHA-256摘要
     * @param file 要计算的文件
     * @return 文件的SHA-256摘要
     * @throws IOException IO异常 如文件不存在
     */
    public final static byte[] sha256_(File file) throws IOException {
        return digest(new FileInputStream(file), file.length(), "SHA-256");
    }

    /**
     * 计算文件的SHA-512摘要
     * @param file 要计算的文件
     * @return 文件的SHA-512摘要
     * @throws IOException IO异常 如文件不存在
     */
    public final static byte[] sha512(File file) throws IOException {
        return digest(new FileInputStream(file), file.length(), "SHA-512");
    }

    private final static byte[] digest(String str, String algorithm) {
        return digest(str.getBytes(), algorithm);
    }

    private final static byte[] digest(byte[] bytes, String algorithm) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            md.update(bytes);
            return md.digest();
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    private final static byte[] digest(InputStream in, long length, String algorithm) throws IOException {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] buffer = new byte[1024];
            int l;
            while ((l = in.read(buffer)) > 0) {
                md.update(buffer, 0, l);
            }
            return md.digest();
        } catch (NoSuchAlgorithmException e) {
            return null;
        } finally {
            in.close();
        }
    }
} public final class Hex {

    private Hex() {
    }

    /**
     * 使用十六进制编码数据
     * @param data 要编码的数据
     * @return 编码后的字符串
     */
    public final static String hex(byte[] data) {
        return hex(data, 0, data.length);
    }

    /**
     * 使用十六进制编码数据
     * @param data 要编码的数据
     * @param offset 其实偏移 0开始
     * @param count 编码字节的数量
     * @return 编码后的字符串
     */
    public final static String hex(byte[] data, int offset, int count) {
        if (offset < 0)
            throw new IllegalArgumentException(new StringBuilder("invalid offset:").append(offset).toString());
        if (count < 0)
            throw new IllegalArgumentException(new StringBuilder("invalid count:").append(offset).toString());
        if (offset > data.length - count)
            throw new ArrayIndexOutOfBoundsException(offset + count);
        ByteArrayOutputStream out = new ByteArrayOutputStream(data.length + data.length);
        PrintStream writer = new PrintStream(out);
        for (int i = offset; i < offset + count; i++) {
            writer.printf("%02x", data[i]);
        }
        return out.toString();
    }

    /**
     * 将十六进制编码的数据还原
     * @param hex 要还原的字符串
     * @return 还原后的数据
     * @throws NumberFormatException 字符串中包含无法格式化为数字的字符
     */
    public final static byte[] unhex(String hex) {
        char[] tb = hex.toUpperCase().toCharArray();
        List<Byte> bs = new ArrayList<Byte>();
        for (int i = 0; i < tb.length; i += 2) {
            String t = new String();
            t += tb[i];
            t += tb[i + 1];
            int safe = Integer.parseInt(t, 16);
            if (safe > 127)
                safe -= 256;
            bs.add(Byte.parseByte(String.valueOf(safe)));
        }
        byte[] raw = new byte[bs.size()];
        for (int i = 0; i < bs.size(); i++)
            raw[i] = bs.get(i);
        return raw;
    }

    /**
     * 将64位的long转换成8个字节的数组
     * @param l 要转换的long
     * @return 转换后的数组
     */
    public final static byte[] longToBytes(long l) {
        byte[] buffer = new byte[8];
        buffer[0] = (byte) (0xff & (l >> 56));
        buffer[1] = (byte) (0xff & (l >> 48));
        buffer[2] = (byte) (0xff & (l >> 40));
        buffer[3] = (byte) (0xff & (l >> 32));
        buffer[4] = (byte) (0xff & (l >> 24));
        buffer[5] = (byte) (0xff & (l >> 16));
        buffer[6] = (byte) (0xff & (l >> 8));
        buffer[7] = (byte) (0xff & l);
        return buffer;
    }

    /**
     * 将8个字节组装成一个long
     * @param bytes 要转换的字节数组
     * @return 组装后的long
     */
    public final static long bytesToLong(byte[] bytes) {
        return bytesToLong(bytes, 0);
    }

    /**
     * 将8个字节组装成一个long
     * @param bytes 要转换的字节数组
     * @param offset 数组中开始读取的偏移量
     * @return 组装后的long
     */
    public final static long bytesToLong(byte[] bytes, int offset) {
        if (bytes == null || offset + 8 > bytes.length)
            throw new IllegalArgumentException("invalid byte array to convert!");
        return (((long) (bytes[0 + offset] & 0xff) << 56) |
                ((long) (bytes[1 + offset] & 0xff) << 48) |
                ((long) (bytes[2 + offset] & 0xff) << 40) |
                ((long) (bytes[3 + offset] & 0xff) << 32) |
                ((long) (bytes[4 + offset] & 0xff) << 24) |
                ((long) (bytes[5 + offset] & 0xff) << 16) |
                ((long) (bytes[6 + offset] & 0xff) <<  8) |
                ((long) (bytes[7 + offset] & 0xff)));
    }

    /**
     * 将32bit的int转换为字节数组
     * @param i 要转换的int
     * @return 转换完成的数组 长度4字节
     */
    public final static byte[] intToByte(int i) {
        byte[] bt = new byte[4];
        bt[0] = (byte) ((0xff000000 & i) >> 24);
        bt[1] = (byte) ((0xff0000 & i) >> 16);
        bt[2] = (byte) ((0xff00 & i) >> 8);
        bt[3] = (byte) (0xff & i);
        return bt;
    }

    /**
     * 将4字节的数组转换成int
     * @param bytes 要转换的字节数组
     * @return 转换后的int
     */
    public final static int bytesToInt(byte[] bytes) {
        return bytesToInt(bytes,0);
    }

    /**
     * 将数组中的部分数据转换为int
     * @param bytes 要转换的数组
     * @param offset 起始偏移量 从0开始
     * @return 读取4个字节的内容 将它们转换为32bit的int并返回
     */
    public final static int bytesToInt(byte[] bytes, int offset) {
        if (bytes == null || offset + 4 > bytes.length)
            throw new IllegalArgumentException("invalid byte array to convert!");
        return (((bytes[0 + offset] & 0xff) << 24) |
                ((bytes[1 + offset] & 0xff) << 16) |
                ((bytes[2 + offset] & 0xff) <<  8) |
                (bytes[3 + offset] & 0xff));
    }
} @Service
public class KeyGenClient {

    @Value("${hamaster.gradesign.keydist.genserver}")
    private String keyGenServereURL;

    @Value("${hamaster.gradesign.keydist.system}")
    private String systemIDStr;

    @Value("${hamaster.gradesign.keydist.server_id}")
    private String serverID;

    @Value("${hamaster.gradesign.keydist.server_key_valid:1000000000000}")
    private Long serverKeyValidPeriod;

    @Value("${hamaster.gradesign.keydist.server_key_dir}")
    private String serverKeyLocation;

    private final static String SERVER_KEY_FILE = "ibedist.key";
    private final static String SERVER_KEY_FILE_CONTENT = "content";
    private final static String SERVER_KEY_FILE_CRYPT_KEY = "key";

    private Integer currentSystemID;
    private IBSCertificate serverCertificate;
    private IBEPrivateKey serverPrivateKey;
    private Encoder base64;
    private RestTemplate restTemplate;

    private Map<Integer, String> systemIDs;
    private Map<Integer, IBEPublicParameter> systemParameters;

    private final static Logger logger = LoggerFactory.getLogger(KeyGenClient.class);

    @Autowired
    public KeyGenClient(RestTemplateBuilder restTemplateBuilder, @Qualifier("base64Encoder") Encoder base64) {
        this.restTemplate = requireNonNull(restTemplateBuilder).build();
        this.base64 = requireNonNull(base64);
        currentSystemID = -1;
        systemIDs = new ConcurrentHashMap<Integer, String>();
        systemParameters = new ConcurrentHashMap<Integer, IBEPublicParameter>();
    }

    public void init() {
        SimpleRESTResponse resp = restTemplate.getForObject(String.format("%s/system/%s/number", keyGenServereURL, systemIDStr), SimpleRESTResponse.class);
        if (resp.getResultCode() == 0) {
            currentSystemID = (Integer) resp.getPayload();
            logger.info("Connected to keygen server, default system: {} ", currentSystemID);
        } else {
            logger.error("Failed to connect to key generation server: {}, error code: {}, message: {}", keyGenServereURL, resp.getResultCode(), resp.getMessage());
        }
        @SuppressWarnings("unchecked")
        Map<String, String> allSystem = restTemplate.getForObject(String.format("%s/system/all", keyGenServereURL), Map.class);
        for (String key : allSystem.keySet()) {
            try {
                systemIDs.put(Integer.parseInt(key), allSystem.get(key));
            } catch (NumberFormatException e) {
            }
        }

        @SuppressWarnings("unchecked")
        Map<String, Map<String, String>> allSystemParameters = restTemplate.getForObject(String.format("%s/system/allparam", keyGenServereURL), Map.class);
        for (String systemID : allSystemParameters.keySet()) {
            Map<String, String> base64EncodedParameter = allSystemParameters.get(systemID);
            IBEPublicParameter decodedParameter = new IBEPublicParameter();
            decodedParameter.setPairing(base64.decode(base64EncodedParameter.get("pairing")));
            decodedParameter.setParamG(base64.decode(base64EncodedParameter.get("paramG")));
            decodedParameter.setParamG1(base64.decode(base64EncodedParameter.get("paramG1")));
            decodedParameter.setParamH(base64.decode(base64EncodedParameter.get("paramH")));
            systemParameters.put(Integer.parseInt(systemID), decodedParameter);
        }
        prepareServerKeys();
        if (serverPrivateKey == null || serverCertificate == null)
            setupServerKeys();
    }

    public IBEPrivateKey serverPrivateKey() {
        if (serverPrivateKey != null)
            return serverPrivateKey;
        prepareServerKeys();
        return serverPrivateKey;
    }

    public IBSCertificate serverCertificate() {
        if (serverCertificate != null)
            return serverCertificate;
        prepareServerKeys();
        return serverCertificate;
    }

    private synchronized void prepareServerKeys() {
        File folder = new File(serverKeyLocation);
        if (checkFolerPermission(folder)) {
            Properties props = new Properties();
            InputStream key = null;
            try {
                key = new FileInputStream(new File(folder, SERVER_KEY_FILE));
                props.load(key);
            } catch (IOException e) {
                logger.error("Failed to read server private key", e);
                return;
            } finally {
                try {
                    if (key != null)
                        key.close();
                } catch (IOException e) {
                }
            }
            // both server key and its decryption key are stored in hex format
            String serverIdContent = props.getProperty(SERVER_KEY_FILE_CONTENT);
            String keyContent = props.getProperty(SERVER_KEY_FILE_CRYPT_KEY);
            byte[] raw = Hex.unhex(serverIdContent);
            ByteArrayInputStream in = new ByteArrayInputStream(raw);
            IBECapsule capsule = new IBECapsuleAESImpl();
            capsule.setKey(Hex.unhex(keyContent));
            IdentityDescription id = null;
            try {
                capsule.readExternal(in);
                id = (IdentityDescription) capsule.getDataAsObject();
            } catch (ClassNotFoundException | IOException e) {
                logger.error("Failed to decrypt server private key", e);
            } finally {
                capsule.close();
            }
            serverPrivateKey = id == null ? null : id.getPrivateKey();
            serverCertificate = id == null ? null : id.getCertificate();
            if (serverPrivateKey != null && serverCertificate != null)
                logger.info("Successfully loaded server private key");
        }
    }

    private boolean checkFolerPermission(File folder) {
        if (folder.isDirectory()) {
            final String folderPermission = "rwx------";
            if (SystemUtils.IS_OS_WINDOWS)
                return true;  // Windows does not support PosixFilePermissions
            if (folder.list().length == 0) {
                Set<PosixFilePermission> permissions = PosixFilePermissions.fromString(folderPermission);
                try {
                    Files.setPosixFilePermissions(Paths.get(folder.getAbsolutePath()), permissions);
                    return true;
                } catch (IOException e) {
                    logger.error("Failed to set permission for new server private key directory", e);
                    return false;
                }
            }
            try {
                Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(Paths.get(folder.getAbsolutePath()));
                String currentPermission = PosixFilePermissions.toString(permissions);
                if (folderPermission.equalsIgnoreCase(currentPermission)) {
                    return true;
                } else {
                    logger.error("Incorrect permission settings for server key directory {}, expected {}, actual {}", folder.getAbsoluteFile(), folderPermission, currentPermission);
                    return false;
                }
            } catch (IOException e) {
                logger.error("Failed to read permission for existing server private key directory", e);
                return false;
            }
        }
        return false;
    }

    private File createNewKeyFile(File folder, String name, boolean overwrite) throws IOException {
        final String fildPermission = "rw-------";
        Set<PosixFilePermission> permissions = PosixFilePermissions.fromString(fildPermission);
        File key = new File(folder, name);
        if (key.exists()) {
            if (overwrite) {
                logger.warn("Original key file {} will be overwritten", key.getAbsolutePath());
                if (!key.delete()) {
                    logger.error("Failed to overwrite existing key file");
                    return null;
                }
            } else {
                File backup = new File(folder, String.format("%s_%d", SERVER_KEY_FILE, System.currentTimeMillis()));
                if (key.renameTo(backup)) {
                    Files.setPosixFilePermissions(Paths.get(backup.getAbsolutePath()), permissions);
                    logger.warn("Creating server key backup file {} for {}", backup.getName(), key.getName());
                } else {
                    logger.error("Can't create server key backup file {} for {}", backup.getName(), key.getName());
                    return null;
                }
            }
        }
        key.createNewFile();
        Files.setPosixFilePermissions(Paths.get(key.getAbsolutePath()), permissions);
        return key;
    }

    public void setupServerKeys() {
        File folder = new File(serverKeyLocation);
        if (!folder.exists()) {
            folder.mkdirs();
            checkFolerPermission(folder);
        }
        byte[] sessionKey = randomKey(64);
        IBEPlainText plain = IBEPlainText.newIbePlainTextFormSignificantBytes(sessionKey);
        IBECipherText cipher = IBEEngine.encrypt(getKeyGenServerPublicParameter(currentSystemID), plain, getSystemIDStr(currentSystemID));
        IBECSR request = new IBECSR();
        request.setApplicationDate(new Date());
        request.setIdentityString(serverID);
        request.setIbeSystemId(currentSystemID);
        request.setPeriod(serverKeyValidPeriod);
        request.setPassword(cipher.toByteArray());
        ResponseEntity<SimpleRESTResponse> response = restTemplate.postForEntity(String.format("%s/singleid", keyGenServereURL), request, SimpleRESTResponse.class);
        if (response.hasBody()) {
            SimpleRESTResponse rest = response.getBody();
            if (rest.getResultCode() != 0) {
                logger.error("Failed to request server key: {}, deatil: {}", rest.getResultCode(), rest.getMessage());
                return;
            }
            Properties serverKey = new Properties();
            serverKey.setProperty(SERVER_KEY_FILE_CONTENT, response.getBody().getPayload().toString());
            serverKey.setProperty(SERVER_KEY_FILE_CRYPT_KEY, Hex.hex(sessionKey));
            OutputStream out = null;
            try {
                File key = createNewKeyFile(folder, SERVER_KEY_FILE, false);
                out = new FileOutputStream(key);
                serverKey.store(out, "server key");
                out.flush();
            } catch (IOException e) {
                logger.error("Error saving server key.", e);
            } finally {
                try {
                    if (out != null)
                        out.close();
                } catch (IOException e) {
                }
            }
        }
    }

    public IdentityDescriptionEntity getIdentityDescription(Integer system, String id) {
        // @GetMapping("/getid/{system}/{id}")
        // sign the get id request and send to key gen server
        String systemStr = systemIDs.get(system);
        StringBuilder content = new StringBuilder(systemStr);
        content.append('/');
        content.append(id);
        byte[] digest = Hash.sha512(content.toString().getBytes());
        IBSSignature signature = IBEEngine.sign(serverCertificate, digest, "SHA-512");
        ResponseEntity<SimpleRESTResponse> response = restTemplate.getForEntity(String.format("%s/getid/%s/%s?sig=%s", keyGenServereURL, systemStr, id, Hex.hex(signature.toByteArray())), SimpleRESTResponse.class);
        if (response.hasBody()) {
            SimpleRESTResponse resp = response.getBody();
            if (resp.getResultCode() == 0) {
                IdentityDescriptionEntity ide = new IdentityDescriptionEntity();
                @SuppressWarnings("unchecked")
                Map<String, String> result = (Map<String, String>) resp.getPayload();
                ide.setIbeId(Integer.parseInt(String.valueOf(result.get("ibeId"))));
                ide.setIdOwner(result.get("idOwner"));
                ide.setEncryptedIdentityDescription(base64.decode(result.get("encryptedIdentityDescription")));
                return ide;
            }
        }
        return null;
    }

    public byte[] encryptSessionKeyForSystem(byte[] idPwdBin, int ibeSystemId) {
        IBEPlainText plainText = IBEPlainText.newIbePlainTextFormSignificantBytes(idPwdBin);
        IBECipherText cipher = IBEEngine.encrypt(systemParameters.get(ibeSystemId), plainText , systemIDs.get(ibeSystemId));
        return cipher.toByteArray();
    }
    
    public byte[] encryptSessionKeyForSystem(byte[] bytes) {
        return encryptSessionKeyForSystem(bytes, currentSystemID);
    }

    public byte[] randomKey(int length) {
        byte[] bytes = new byte[length];
        Random rand = new SecureRandom();
        rand.nextBytes(bytes);
        return bytes;
    }

    public Map<Integer, String> getAvailableSystem() {
        return systemIDs;
    }

    public String getSystemIDStr(Integer systemID) {
        return systemIDs.get(systemID);
    }

    public IBEPublicParameter getKeyGenServerPublicParameter(Integer systemID) {
        return systemParameters.get(systemID);
    }

    public Integer getCurrentSystemID() {
        return currentSystemID;
    }

    public String getKeyGenServereURL() {
        return keyGenServereURL;
    }
} @Repository
public interface IDRequestDAO extends JpaRepository<IDRequest, Integer> {

    @Query("select i from IDRequest i where i.applicant = :o and i.identityString = :s")
    Optional<IDRequest> findByOwnerForID(@Param("o") User owner, @Param("s") String idString);

    @Query("select i from IDRequest i where i.identityString = :s")
    Optional<IDRequest> findByIDString(@Param("s") String idString);

    List<IDRequest> findAllByStatus(int status, Pageable page);

    @Query("select i from IDRequest i where i.applicant = :o and i.status = :s")
    List<IDRequest> findAllByUserAndStatus(@Param("o") User owner, @Param("s") int status, Pageable page);

    @Query("select i from IDRequest i where i.applicant = :o")
    List<IDRequest> findAllByUser(@Param("o") User owner, Pageable page);

    @Query("select count(i) from IDRequest i where i.applicant = :o")
    Long countByOwner(@Param("o") User owner);
} @Repository
public interface UserDAO extends JpaRepository<User, Integer> {

    /**
     * 获取注册完成但还未验证邮箱的用户
     * @param amount 用户数量
     * @return 新用户列表
     */
    default List<User> listNewRegisteredUsers(int amount) {
        return listNewRegisteredUsers(PageRequest.of(0, amount));
    }

    @Query("select u from User u where u.status = 0")
    List<User> listNewRegisteredUsers(Pageable page);

    Optional<User> findByEmail(String email);
    Optional<User> findByUsername(String username);
} @Repository
public interface UserTokenDAO extends JpaRepository<UserToken, Integer> {

    List<UserToken> findAllByUser(User user);

    @Query("select t from UserToken t where t.uuid = :u")
    Optional<UserToken> findOneByUUID(@Param("u") String uuid);
} @Entity
@Table(name = "IBE_ID_REQUEST")
public class IDRequest implements Serializable, Cloneable {
    private static final long serialVersionUID = -811075818715325346L;

    /**
     * 身份描述申请序号
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "REQUEST_ID")
    private Integer requestId;

    /**
     * 申请者
     */
    @ManyToOne(optional = false)
    @JoinColumn(name = "APPLICANT")
    private User applicant;

    /**
     * 身份字符串
     */
    @Column(nullable = false, name = "IDENTITY_STRING")
    private String identityString;

    /**
     * The SHA512 hash of the password used to encrypt identity description.
     * Its plain text form will be encrypted with key generation server
     * public key and stored to passwordToKeyGen.
     */
    @Column(nullable = false, name = "PASSWORD")
    private String password;

    @Lob
    @Column(nullable = false, name = "PASSWORD_KEYGEN")
    private byte[] passwordToKeyGen;

    /**
     * 所使用的系统
     */
    @Column(nullable = false, name = "IBE_SYSTEM_ID")
    private Integer ibeSystemId;

    /**
     * 申请日期
     */
    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false, name = "APPLICATION_DATE")
    private Date applicationDate;

    /**
     * 申请状态
     */
    @Column(nullable = false, name = "APPLICATION_STATUS")
    private Integer status;

    public IDRequest() {
    }

    /**
     * 返回字节形式表示的部分<code>IDRequest</code>内容
     * @return 包含内容：<ul><li>
     * 请求编号 4字节</li><li>
     * 身份字符串长度 1字节</li><li>
     * 身份字符串内容 最多255字节 UTF-8编码</li><li>
     * 所属IBE系统编号 4字节</li><li>
     * 申请日期 8字节</li><li>
     * 当前状态 1字节</li></ul>
     */
    public byte[] toBytes() {
        ByteArrayOutputStream out = new SecureByteArrayOutputStream();
        byte[] ret = null;
        try {
            out.write(Hex.intToByte(requestId));
            byte[] id = identityString.getBytes(IBEConstraints.USER_STRING_ENCODING);
            out.write(id.length);
            out.write(id);
            out.write(Hex.intToByte(ibeSystemId));
            out.write(Hex.longToBytes(applicationDate.getTime()));
            out.write(status);
            out.flush();
            ret = out.toByteArray();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            out.reset();
        }
        return ret;
    }

    public Integer getRequestId() {
        return requestId;
    }

    public void setRequestId(Integer requestId) {
        this.requestId = requestId;
    }

    @JsonIgnore
    public User getApplicant() {
        return applicant;
    }

    /**
     * for generate json use only
     * @return applicant.getEmail()
     */
    public String getOwner() {
        return applicant == null ? null : applicant.getEmail();
    }

    public void setApplicant(User applicant) {
        this.applicant = applicant;
    }

    public String getIdentityString() {
        return identityString;
    }

    public void setIdentityString(String identityString) {
        this.identityString = identityString;
    }

    @JsonIgnore
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getIbeSystemId() {
        return ibeSystemId;
    }

    public void setIbeSystemId(Integer ibeSystemId) {
        this.ibeSystemId = ibeSystemId;
    }

    public Date getApplicationDate() {
        return applicationDate;
    }

    public void setApplicationDate(Date applicationDate) {
        this.applicationDate = applicationDate;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public void setPasswordToKeyGen(byte[] passwordToKeyGen) {
        this.passwordToKeyGen = passwordToKeyGen;
    }

    @JsonIgnore
    public byte[] getPasswordToKeyGen() {
        return this.passwordToKeyGen;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#clone()
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        IDRequest copy = new IDRequest();
        copy.requestId = this.requestId;
        copy.applicant = this.applicant;
        copy.identityString = this.identityString;
        copy.password = this.password;
        copy.ibeSystemId = this.ibeSystemId;
        copy.applicationDate = this.applicationDate;
        copy.status = this.status;
        copy.passwordToKeyGen = this.passwordToKeyGen;
        return copy;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((applicant == null) ? 0 : applicant.hashCode());
        result = prime * result + ((applicationDate == null) ? 0 : applicationDate.hashCode());
        result = prime * result + ((ibeSystemId == null) ? 0 : ibeSystemId.hashCode());
        result = prime * result + ((identityString == null) ? 0 : identityString.hashCode());
        result = prime * result + ((password == null) ? 0 : password.hashCode());
        result = prime * result + Arrays.hashCode(passwordToKeyGen);
        result = prime * result + ((requestId == null) ? 0 : requestId.hashCode());
        result = prime * result + ((status == null) ? 0 : status.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        IDRequest other = (IDRequest) obj;
        if (applicant == null) {
            if (other.applicant != null)
                return false;
        } else if (!applicant.equals(other.applicant))
            return false;
        if (applicationDate == null) {
            if (other.applicationDate != null)
                return false;
        } else if (!applicationDate.equals(other.applicationDate))
            return false;
        if (ibeSystemId == null) {
            if (other.ibeSystemId != null)
                return false;
        } else if (!ibeSystemId.equals(other.ibeSystemId))
            return false;
        if (identityString == null) {
            if (other.identityString != null)
                return false;
        } else if (!identityString.equals(other.identityString))
            return false;
        if (password == null) {
            if (other.password != null)
                return false;
        } else if (!password.equals(other.password))
            return false;
        if (!Arrays.equals(passwordToKeyGen, other.passwordToKeyGen))
            return false;
        if (requestId == null) {
            if (other.requestId != null)
                return false;
        } else if (!requestId.equals(other.requestId))
            return false;
        if (status == null) {
            if (other.status != null)
                return false;
        } else if (!status.equals(other.status))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "IDRequest [requestId=" + requestId + ", applicant=" + applicant + ", identityString=" + identityString
                + ", password=" + password + ", passwordToKeyGen=" + Arrays.toString(passwordToKeyGen)
                + ", ibeSystemId=" + ibeSystemId + ", applicationDate=" + applicationDate + ", status=" + status + "]";
    }
} @Entity
@Table(name = "IBE_USER")
public class User implements Cloneable, UserDetails {
    private static final long serialVersionUID = -1101813432750693173L;

    /**
     * 用户刚刚注册
     */
    public final static int USER_REG = 0;

    /**
     * 已经发出激活信
     */
    public final static int USER_ACTIVE_LETTER_SENT = 1;

    /**
     * 用户已经被激活
     */
    public final static int USER_ACTIVE = 2;

    /**
     * 用户编号
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "USER_ID")
    private Integer userId;

    /**
     * 用户名
     */
    @Column(nullable = false, name = "USERNAME")
    private String username;

    /**
     * 用户电子邮件地址
     */
    @Column(nullable = false, name = "EMAIL")
    private String email;

    /**
     * 用户密码的SHA-512摘要
     */
    @Column(nullable = false, name = "PASSWORD")
    private String password;

    /**
     * 用户注册时间
     */
    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false, name = "REG_DATE")
    private Date regDate;

    /**
     * 用户当前状态
     */
    @Column(name = "STATUS")
    private Integer status;

    public User() {
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getRegDate() {
        return regDate;
    }

    public void setRegDate(Date regDate) {
        this.regDate = regDate;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#clone()
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        User copy = new User();
        copy.userId = this.userId;
        copy.username = this.username;
        copy.email = this.email;
        copy.password = this.password;
        copy.regDate = this.regDate;
        copy.status = this.status;
        return copy;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((email == null) ? 0 : email.hashCode());
        result = prime * result
                 + ((password == null) ? 0 : password.hashCode());
        result = prime * result + ((regDate == null) ? 0 : regDate.hashCode());
        result = prime * result + ((status == null) ? 0 : status.hashCode());
        result = prime * result + ((userId == null) ? 0 : userId.hashCode());
        result = prime * result
                 + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (email == null) {
            if (other.email != null)
                return false;
        } else if (!email.equals(other.email))
            return false;
        if (password == null) {
            if (other.password != null)
                return false;
        } else if (!password.equals(other.password))
            return false;
        if (regDate == null) {
            if (other.regDate != null)
                return false;
        } else if (!regDate.equals(other.regDate))
            return false;
        if (status == null) {
            if (other.status != null)
                return false;
        } else if (!status.equals(other.status))
            return false;
        if (userId == null) {
            if (other.userId != null)
                return false;
        } else if (!userId.equals(other.userId))
            return false;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "User [userId=" + userId + ", username=" + username + ", email="
               + email + ", password=" + password + ", regDate=" + regDate
               + ", status=" + status + "]";
    }

    public final static String formatDate(Date date) {
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd#HH:mm:ss");
        return format.format(date);
    }

    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Collections.emptyList();
    }

    @Override
    public boolean isAccountNonExpired() {
        return status == USER_ACTIVE;
    }

    @Override
    public boolean isAccountNonLocked() {
        return status == USER_ACTIVE;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return status == USER_ACTIVE;
    }
} @Entity
@Table(name = "IBE_USER_TOKEN")
public class UserToken implements Serializable {
    private static final long serialVersionUID = 3275371286477297953L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "TOKEN_ID")
    private Integer tokenId;

    @ManyToOne(optional = false)
    @JoinColumn(name = "TOKEN_USER")
    private User user;

    @Column(nullable = false, name = "TOKEN_UUID")
    private String uuid;

    @Column(nullable = false, name = "GENERATION_DATE")
    private Date generationDate;

    @Column(nullable = true, name = "DESCRIPTION")
    private String description;

    public UserToken() {
    }

    public Integer getTokenId() {
        return tokenId;
    }

    public void setTokenId(Integer tokenId) {
        this.tokenId = tokenId;
    }

    public String getUsername() {
        return user == null ? null : user.getUsername();
    }

    @JsonIgnore
    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public Date getGenerationDate() {
        return generationDate;
    }

    public void setGenerationDate(Date generationDate) {
        this.generationDate = generationDate;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((description == null) ? 0 : description.hashCode());
        result = prime * result + ((generationDate == null) ? 0 : generationDate.hashCode());
        result = prime * result + ((tokenId == null) ? 0 : tokenId.hashCode());
        result = prime * result + ((user == null) ? 0 : user.hashCode());
        result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        UserToken other = (UserToken) obj;
        if (description == null) {
            if (other.description != null)
                return false;
        } else if (!description.equals(other.description))
            return false;
        if (generationDate == null) {
            if (other.generationDate != null)
                return false;
        } else if (!generationDate.equals(other.generationDate))
            return false;
        if (tokenId == null) {
            if (other.tokenId != null)
                return false;
        } else if (!tokenId.equals(other.tokenId))
            return false;
        if (user == null) {
            if (other.user != null)
                return false;
        } else if (!user.equals(other.user))
            return false;
        if (uuid == null) {
            if (other.uuid != null)
                return false;
        } else if (!uuid.equals(other.uuid))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "UserToken [tokenId=" + tokenId + ", user=" + user + ", uuid=" + uuid + ", generationDate="
                + generationDate + ", description=" + description + "]";
    }
} public interface UserService {

    /**
     * 处理用户登录
     * @param email 用户电子邮件地址
     * @param password 明文密码
     * @return 只有电子邮件地址和密码匹配时返回User对象
     */
    User loginWithEmail(String email, String password);
    User loginWithUsername(String username, String password);
    User loginWithToken(String username, String token);

    UserToken appLogin(String username, String password);
    UserToken appLogin(String username, String password, String description);
    List<UserToken> listAllUserTokens(String username, String password);
    void appLogout(String username, String uuid);

    boolean isEmailExist(String email);

    boolean isUsernameExist(String username);

    void register(User user, String password);
} public class IBECSR implements Serializable {
    private static final long serialVersionUID = -7382866034793567072L;

    final public static int APPLICATION_STATUS_ALL = 0;
    final public static int APPLICATION_STARTED = 1;
    final public static int APPLICATION_APPROVED = 2;
    final public static int APPLICATION_DENIED = 3;
    final public static int APPLICATION_ERROR = 4;
    final public static int APPLICATION_NOT_VERIFIED = 5;
    final public static int APPLICATION_PROCESSING = 6;

    /**
     * 身份描述申请序号
     */
    private Integer requestId;

    /**
     * 身份字符串
     */
    private String identityString;

    /**
     * The IBE encrypted application password.
     * This password will be used to protect generated identity description.
     */
    private byte[] password;

    /**
     * The message digest for this request.
     * It's the SHA-512 checksum for the following contents
     * <table border="1">
     * <tr>
     * <td>identityString.toUTF8Bytes</td>
     * <td>password</td>
     * <td>ibeSystemId.toBytes</td>
     * <td>applicationDate.unixEpoch.toBytes</td>
     * </tr>
     * </table>
     */
    private byte[] digest;

    /**
     * The IBE signature from key distribution server
     */
    private byte[] signature;

    /**
     * 所使用的系统
     */
    private Integer ibeSystemId;

    /**
     * 申请日期
     */
    private Date applicationDate;

    /**
     * 有效期 单位毫秒
     */
    private long period;

    public IBECSR() {
    }

    public Integer getRequestId() {
        return requestId;
    }

    public void setRequestId(Integer requestId) {
        this.requestId = requestId;
    }

    public String getIdentityString() {
        return identityString;
    }

    public void setIdentityString(String identityString) {
        this.identityString = identityString;
    }

    public Integer getIbeSystemId() {
        return ibeSystemId;
    }

    public void setIbeSystemId(Integer ibeSystemId) {
        this.ibeSystemId = ibeSystemId;
    }

    public Date getApplicationDate() {
        return applicationDate;
    }

    public void setApplicationDate(Date applicationDate) {
        this.applicationDate = applicationDate;
    }

    public long getPeriod() {
        return period;
    }

    public void setPeriod(long period) {
        this.period = period;
    }

    public byte[] getPassword() {
        return password;
    }

    public void setPassword(byte[] password) {
        this.password = password;
    }

    public byte[] getSignature() {
        return signature;
    }

    public void setSignature(byte[] signature) {
        this.signature = signature;
    }

    public byte[] getDigest() {
        byte[] message = null;
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
            if (identityString != null)
                out.write(identityString.getBytes("UTF-8"));
            if (password != null)
                out.write(password);
            if (ibeSystemId != null)
                out.write(Hex.intToByte(ibeSystemId.intValue()));
            if (applicationDate != null)
                out.write(Hex.longToBytes(applicationDate.getTime()));
            message = out.toByteArray();
        } catch (IOException e) {
        }
        if (message == null)
            return null;
        return Hash.sha512(message);
    }

    public boolean verifyDigest(byte[] digest) {
        byte[] thisDigest = getDigest();
        if (thisDigest == null && digest == null)
            return false;
        return Arrays.equals(thisDigest, digest);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((applicationDate == null) ? 0 : applicationDate.hashCode());
        result = prime * result + Arrays.hashCode(digest);
        result = prime * result + ((ibeSystemId == null) ? 0 : ibeSystemId.hashCode());
        result = prime * result + ((identityString == null) ? 0 : identityString.hashCode());
        result = prime * result + Arrays.hashCode(password);
        result = prime * result + (int) (period ^ (period >>> 32));
        result = prime * result + ((requestId == null) ? 0 : requestId.hashCode());
        result = prime * result + Arrays.hashCode(signature);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        IBECSR other = (IBECSR) obj;
        if (applicationDate == null) {
            if (other.applicationDate != null)
                return false;
        } else if (!applicationDate.equals(other.applicationDate))
            return false;
        if (!Arrays.equals(digest, other.digest))
            return false;
        if (ibeSystemId == null) {
            if (other.ibeSystemId != null)
                return false;
        } else if (!ibeSystemId.equals(other.ibeSystemId))
            return false;
        if (identityString == null) {
            if (other.identityString != null)
                return false;
        } else if (!identityString.equals(other.identityString))
            return false;
        if (!Arrays.equals(password, other.password))
            return false;
        if (period != other.period)
            return false;
        if (requestId == null) {
            if (other.requestId != null)
                return false;
        } else if (!requestId.equals(other.requestId))
            return false;
        if (!Arrays.equals(signature, other.signature))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "IBECSR [requestId=" + requestId + ", identityString=" + identityString + ", password="
                + Arrays.toString(password) + ", digest=" + Arrays.toString(digest) + ", signature="
                + Arrays.toString(signature) + ", ibeSystemId=" + ibeSystemId + ", applicationDate=" + applicationDate
                + ", period=" + period + "]";
    }
}
import static java.util.Objects.requireNonNull;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import hamaster.gradesgin.util.Hash;
import hamaster.gradesgin.util.Hex;
import hamaster.gradesign.keydist.daemon.KeyGenClient;
import hamaster.gradesign.keydist.dao.IDRequestDAO;
import hamaster.gradesign.keydist.dao.UserDAO;
import hamaster.gradesign.keydist.dao.UserTokenDAO;
import hamaster.gradesign.keydist.entity.IDRequest;
import hamaster.gradesign.keydist.entity.User;
import hamaster.gradesign.keydist.entity.UserToken;
import hamaster.gradesign.keydist.service.UserService;
import hamaster.gradesign.keygen.IBECSR;
package hamaster.gradesign.keydist.service.impl;





@Service
public class UserServiceImpl implements UserService {

    private UserDAO userRepo;
    private IDRequestDAO idRequestRepo;
    private KeyGenClient client;