├── Open Source Software Notice.doc ├── src ├── test │ ├── resources │ │ ├── release.config.properties │ │ └── PassSignServiceImpl.source │ └── java │ │ └── com │ │ └── huawei │ │ └── wallet │ │ └── pass │ │ └── passsdk │ │ ├── PassFileReleaseTest.java │ │ └── PassFileServiceTestUtil.java └── main │ └── java │ └── com │ └── huawei │ └── wallet │ └── pass │ └── passsdk │ ├── PassSignService.java │ ├── PassSignedAttributeTableGenerator.java │ ├── PassException.java │ ├── PassCertificateService.java │ ├── ZipInputStreamHandler.java │ ├── PassFileService.java │ ├── PassUtilService.java │ ├── PassUtilServiceImpl.java │ ├── PassSignServiceBCImpl.java │ └── PassFileServiceImpl.java ├── README_ZH.md ├── README.md ├── pom.xml └── LICENSE /Open Source Software Notice.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-wallet-passgenerator/HEAD/Open Source Software Notice.doc -------------------------------------------------------------------------------- /src/test/resources/release.config.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMS-Core/hms-wallet-passgenerator/HEAD/src/test/resources/release.config.properties -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassSignService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | public interface PassSignService { 20 | /** 21 | * Sign content. 22 | * 23 | * @param content the content to be signed. 24 | * @param privateKey PKCS8 private key. 25 | * @param passCertificate pass certificate. 26 | * @return the signed data. 27 | * @throws PassException the pass exception. 28 | */ 29 | byte[] sign(byte[] content, byte[] privateKey, byte[] passCertificate) throws PassException; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassSignedAttributeTableGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import java.util.Hashtable; 20 | import java.util.Map; 21 | 22 | import org.bouncycastle.asn1.cms.CMSAttributes; 23 | import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator; 24 | 25 | public class PassSignedAttributeTableGenerator extends DefaultSignedAttributeTableGenerator { 26 | @Override 27 | protected Hashtable createStandardAttributeTable(Map parameters) { 28 | Hashtable ret = super.createStandardAttributeTable(parameters); 29 | ret.remove(CMSAttributes.cmsAlgorithmProtect); 30 | return ret; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | /** 20 | * PassSDK exception class. 21 | * 22 | * @since 2019-08-06 23 | */ 24 | public class PassException extends Exception { 25 | /** 26 | * Instantiates a new pass exception. 27 | * 28 | * @param message exception message. 29 | */ 30 | public PassException(String message) { 31 | super(message); 32 | } 33 | 34 | /** 35 | * Instantiates a new pass exception. 36 | * 37 | * @param message exception message. 38 | * @param cause exception cause. 39 | */ 40 | public PassException(String message, Throwable cause) { 41 | super(message, cause); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassCertificateService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | /** 20 | * Interface of pass certificate service. 21 | * 22 | * @since 2019-08-06 23 | */ 24 | public interface PassCertificateService { 25 | /** 26 | * Get a X.509 certificate by passTypeIdentifier. 27 | * 28 | * @param passTypeIdentifier the passTypeIdentifier. 29 | * @return the certificate bytes. 30 | */ 31 | byte[] getCertificateByPassType(String passTypeIdentifier); 32 | 33 | /** 34 | * Get a PKCS8 private key by passTypeIdentifier. 35 | * 36 | * @param passTypeIdentifier the passTypeIdentifier. 37 | * @return the private key bytes. 38 | */ 39 | byte[] getPrivateKeyByPassType(String passTypeIdentifier); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/ZipInputStreamHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import java.util.zip.ZipEntry; 20 | 21 | /** 22 | * Interface of zip-input-stream handler. 23 | * 24 | * @since 2019-08-06 25 | */ 26 | public interface ZipInputStreamHandler { 27 | /** 28 | * Check if a zip entry should be skipped. 29 | * 30 | * @param entry the entry. 31 | * @return if the entry should be skipped. 32 | */ 33 | boolean needSkip(ZipEntry entry); 34 | 35 | /** 36 | * Handle a zip entry. 37 | * 38 | * @param entry the entry. 39 | * @param unzipContent unzipped content. 40 | * @throws Exception the exception. 41 | */ 42 | void handleContent(ZipEntry entry, byte[] unzipContent) throws Exception; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassFileService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import java.util.Map; 20 | 21 | /** 22 | * Interface of pass file service. 23 | * 24 | * @since 2019-08-06 25 | */ 26 | public interface PassFileService { 27 | 28 | /** 29 | * Create pass data. 30 | * 31 | * @param fileMap the file map 32 | * @param passJson the pass json 33 | * @return byte [ ] 34 | * @throws PassException the pass exception 35 | */ 36 | byte[] createPass(Map fileMap, String passJson) throws PassException; 37 | 38 | /** 39 | * Sign a message with the certificate according to passType. 40 | * 41 | * @param message the message to be signed 42 | * @param passTypeIdentifier the pass type identifier 43 | * @return the signed bytes. 44 | * @throws PassException the pass exception 45 | */ 46 | byte[] signMessage(byte[] message, String passTypeIdentifier) throws PassException; 47 | } 48 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # 华为钱包服务卡券包生成示例代码 2 | ## 目录 3 | 4 | * [简介](#简介) 5 | * [安装](#安装) 6 | * [配置](#配置) 7 | * [环境要求](#环境要求) 8 | * [示例代码](#示例代码) 9 | * [授权许可](#授权许可) 10 | 11 | ## 简介 12 | 该示例代码介绍了如何创建华为钱包服务的卡券包。 13 | 14 | ## 安装 15 | 在运行示例代码之前,确保已经安装了Java和Maven,并已经获得了.pem文件和.cer文件。 16 | 17 | ## 环境要求 18 | 推荐使用Oracle Java 1.8。 19 | 20 | ## 配置 21 | 1. 根据你的业务ID、pem文件和cer文件编辑 `src\test\resources\release.config.properties` 文件。 22 | 23 | - 设置 `pass.privatekey.YourServiceID` 参数。 24 | 例如,业务ID为 `hwpass.com.huawei.wallet.pass.sdktest`, 则将该参数重命名为 `pass.privatekey.hwpass.com.huawei.wallet.pass.sdktest`. 对整个. pem文件进行base64编码,然后将该参数设置为编码后的字符串。 25 | 26 | - 设置 `pass.certificate.YourServiceID` 参数。 27 | 例如,业务ID为 `hwpass.com.huawei.wallet.pass.sdktest`, 则将该参数重命名为 `pass.certificate.hwpass.com.huawei.wallet.pass.sdktest`. 对整个. cer文件进行base64编码,然后将该参数设置为编码后的字符串。 28 | 29 | 2. 设置 "src\test\java\PassFileServiceTestUtil.java" 文件中的 `TEST_PASS_TYPE` 参数为业务ID。 30 | 31 | ## 示例代码 32 | 1. 运行"src\test\java\PassFileReleaseTest.java"文件中的`testCreatePassFile` 方法,创建hwpass文件。 33 | 34 | 2. 运行"src\test\java\PassFileReleaseTest.java"文件中的`testCreateMessage` 方法,生成签名文件。 35 | 36 | ## 技术支持 37 | 如果您对HMS Core还处于评估阶段,可在[Reddit社区](https://www.reddit.com/r/HuaweiDevelopers/)获取关于HMS Core的最新讯息,并与其他开发者交流见解。 38 | 39 | 如果您对使用HMS示例代码有疑问,请尝试: 40 | - 开发过程遇到问题上[Stack Overflow](https://stackoverflow.com/questions/tagged/huawei-mobile-services),在`huawei-mobile-services`标签下提问,有华为研发专家在线一对一解决您的问题。 41 | - 到[华为开发者论坛](https://developer.huawei.com/consumer/cn/forum/blockdisplay?fid=18) HMS Core板块与其他开发者进行交流。 42 | 43 | 如果您在尝试示例代码中遇到问题,请向仓库提交[issue](https://github.com/HMS-Core/hms-wallet-passgenerator/issues),也欢迎您提交[Pull Request](https://github.com/HMS-Core/hms-wallet-passgenerator/pulls)。 44 | 45 | ## 授权许可 46 | 华为钱包服务卡券包生成示例代码通过[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0)授权许可. 47 | -------------------------------------------------------------------------------- /src/test/java/com/huawei/wallet/pass/passsdk/PassFileReleaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | 22 | import java.io.IOException; 23 | 24 | public class PassFileReleaseTest { 25 | 26 | private PassFileServiceTestUtil passFileServiceTestUtil; 27 | 28 | private PassFileService passFileService; 29 | 30 | @Before 31 | public void setUp() throws IOException { 32 | passFileServiceTestUtil = new PassFileServiceTestUtil(); 33 | passFileServiceTestUtil.loadProperties("/release.config.properties"); 34 | passFileService = passFileServiceTestUtil.getPassFileService(passFileServiceTestUtil.getProperties(), "pass."); 35 | } 36 | 37 | /** 38 | * 开发者可以使用此方法获取新增/更新pass包的码流 39 | */ 40 | @Test 41 | public void testCreateMessage() throws PassException { 42 | passFileServiceTestUtil.testCreateSignMessage(PassFileServiceTestUtil.TEST_PASS_TYPE, passFileService); 43 | } 44 | 45 | /** 46 | * 开发者可以使用此方法生成pass包 47 | */ 48 | @Test 49 | public void testCreatePassFile() throws IOException, PassException { 50 | passFileServiceTestUtil.testCreatePassFile(PassFileServiceTestUtil.TEST_PASS_TYPE, passFileService); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassUtilService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import java.io.IOException; 20 | import java.security.NoSuchAlgorithmException; 21 | 22 | /** 23 | * Interface of pass utility service. 24 | * 25 | * @since 2019-08-06 26 | */ 27 | public interface PassUtilService { 28 | /** 29 | * Convert a long parameter to bytes. 30 | * 31 | * @param x the parameter to be converted. 32 | * @return the converted bytes. 33 | */ 34 | byte[] longToBytes(long x); 35 | 36 | /** 37 | * Get a compressed UUID. A timestamp is added to the UUID to keep UUID in order. 38 | * 39 | * @return the compressed UUID. 40 | */ 41 | String getCompressUUID(); 42 | 43 | /** 44 | * Convert hex bytes to a string. 45 | * 46 | * @param bytes the hex bytes. 47 | * @return the converted string. 48 | */ 49 | String toHexString(byte[] bytes); 50 | 51 | /** 52 | * Handle zip input stream. 53 | * 54 | * @param passBytes the pass bytes. 55 | * @param handler the handler. 56 | * @throws IOException the IO exception. 57 | */ 58 | void handleZipInputStream(byte[] passBytes, ZipInputStreamHandler handler) throws IOException; 59 | 60 | /** 61 | * Encrypt data with SHA256 algorithm. 62 | * 63 | * @param content the content to be encrypted. 64 | * @return the encrypted bytes. 65 | * @throws NoSuchAlgorithmException the no-such-algorithm exception. 66 | */ 67 | byte[] SHA256Hash(byte[] content) throws NoSuchAlgorithmException; 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HMS Wallet Passgenerator 2 | ## Table of Contents 3 | 4 | * [Introduction](#introduction) 5 | * [Installation](#installation) 6 | * [Configuration ](#configuration ) 7 | * [Supported Environments](#supported-environments) 8 | * [Sample Code](#sample-code) 9 | * [License](#license) 10 | 11 | ## Introduction 12 | This is sample code for how to create wallet pass package. 13 | 14 | ## Installation 15 | Before running the Demo code, you should have installed Java and Maven. You should have gotten a .pem file and a .cer file. 16 | 17 | ## Supported Environments 18 | Oracle Java 1.8 is recommended. 19 | 20 | ## Configuration 21 | 1. Edit the `src\test\resources\release.config.properties` according to your service ID, pem file, and cer file. 22 | 23 | - Set the `pass.privatekey.YourServiceID` paramter. 24 | For example, if your service ID is `hwpass.com.huawei.wallet.pass.sdktest`, rename the paramter to `pass.privatekey.hwpass.com.huawei.wallet.pass.sdktest`. Then do base64 encoding to the entire .pem file. 25 | Then set the base64 string as the value of this paramter. 26 | 27 | - Set the `pass.certificate.YourServiceID` paramter. 28 | For example, if your service ID is `hwpass.com.huawei.wallet.pass.sdktest`, rename the paramter to `pass.certificate.hwpass.com.huawei.wallet.pass.sdktest`. Then do base64 encoding to the entire .cer file. 29 | Then set the base64 string as the value of this paramter. 30 | 31 | 2. Set your service ID to the `TEST_PASS_TYPE` parameter in the "src\test\java\PassFileServiceTestUtil.java" file. 32 | 33 | ## Sample Code 34 | 1. Run the `testCreatePassFile` method in the "src\test\java\PassFileReleaseTest.java" file to create a .hwpass file. 35 | 36 | 2. Run the `testCreateMessage` method in the "src\test\java\PassFileReleaseTest.java" file to create a signature. 37 | 38 | ## Question or issues 39 | If you want to evaluate more about HMS Core, 40 | [r/HMSCore on Reddit](https://www.reddit.com/r/HuaweiDevelopers/) is for you to keep up with latest news about HMS Core, and to exchange insights with other developers. 41 | 42 | If you have questions about how to use HMS samples, try the following options: 43 | - [Stack Overflow](https://stackoverflow.com/questions/tagged/huawei-mobile-services) is the best place for any programming questions. Be sure to tag your question with 44 | `huawei-mobile-services`. 45 | - [Huawei Developer Forum](https://forums.developer.huawei.com/forumPortal/en/home?fid=0101187876626530001) HMS Core Module is great for general questions, or seeking recommendations and opinions. 46 | 47 | If you run into a bug in our samples, please submit an [issue](https://github.com/HMS-Core/hms-wallet-passgenerator/issues) to the Repository. Even better you can submit a [Pull Request](https://github.com/HMS-Core/hms-wallet-passgenerator/pulls) with a fix. 48 | 49 | ## License 50 | HMS wallet server sample code is licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 51 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.huawei.wisecloud.wallet 8 | passgenerator 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | 1.8 14 | 1.8 15 | UTF-8 16 | 17 | 18 | 19 | 20 | 21 | com.fasterxml.jackson.core 22 | jackson-databind 23 | 2.10.0 24 | 25 | 26 | jackson-annotations 27 | com.fasterxml.jackson.core 28 | 29 | 30 | jackson-core 31 | com.fasterxml.jackson.core 32 | 33 | 34 | 35 | 36 | 37 | 38 | commons-io 39 | commons-io 40 | 2.6 41 | 42 | 43 | 44 | 45 | junit 46 | junit 47 | 4.12 48 | test 49 | 50 | 51 | 52 | 53 | org.bouncycastle 54 | bcpkix-jdk15on 55 | 1.64 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-web 62 | 2.2.1.RELEASE 63 | 64 | 65 | jackson-databind 66 | com.fasterxml.jackson.core 67 | 68 | 69 | 70 | 71 | 72 | org.apache.httpcomponents 73 | httpclient 74 | 4.5.7 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassUtilServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import java.io.BufferedOutputStream; 20 | import java.io.ByteArrayInputStream; 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.IOException; 23 | import java.nio.ByteBuffer; 24 | import java.security.MessageDigest; 25 | import java.security.NoSuchAlgorithmException; 26 | import java.security.SecureRandom; 27 | import java.util.zip.ZipEntry; 28 | import java.util.zip.ZipInputStream; 29 | 30 | import org.apache.commons.codec.binary.Base64; 31 | 32 | /** 33 | * Implementation of pass utility service. 34 | * 35 | * @since 2019-08-06 36 | */ 37 | public class PassUtilServiceImpl implements PassUtilService { 38 | 39 | private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); 40 | 41 | String HASH_ALGORITHMS = "SHA-256"; 42 | 43 | int TOOBIG = 16384000; 44 | 45 | int TOOMANY = 500; 46 | 47 | Integer BUFFER = 4096; 48 | 49 | @Override 50 | public byte[] longToBytes(long x) { 51 | ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE); 52 | buffer.putLong(x); 53 | return buffer.array(); 54 | } 55 | 56 | @Override 57 | public String getCompressUUID() { 58 | SecureRandom rs = new SecureRandom(); 59 | byte[] srbytes = new byte[24]; 60 | rs.nextBytes(srbytes); 61 | byte[] byUuid = new byte[32]; 62 | System.arraycopy(longToBytes(System.currentTimeMillis()), 0, byUuid, 0, 8); 63 | System.arraycopy(srbytes, 0, byUuid, 8, 24); 64 | return Base64.encodeBase64URLSafeString(byUuid); 65 | } 66 | 67 | @Override 68 | public String toHexString(byte[] bytes) { 69 | StringBuilder sb = new StringBuilder(2 * bytes.length); 70 | for (byte b : bytes) { 71 | sb.append(HEX_DIGITS[(b >> 4) & 0xf]).append(HEX_DIGITS[b & 0xf]); 72 | } 73 | return sb.toString(); 74 | } 75 | 76 | @Override 77 | public final void handleZipInputStream(byte[] passBytes, ZipInputStreamHandler handler) throws IOException { 78 | ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(passBytes)); 79 | ZipEntry entry; 80 | int entries = 0; 81 | int total = 0; 82 | byte[] data = new byte[BUFFER]; 83 | try { 84 | while ((entry = zis.getNextEntry()) != null) { 85 | BufferedOutputStream dest = null; 86 | int count; 87 | 88 | if (handler.needSkip(entry)) { 89 | entries++; 90 | // If the total number of entries is larger than the maximum entries number, throw exception. 91 | if (entries > TOOMANY) { 92 | throw new IllegalStateException("Too many files to unzip."); 93 | } 94 | continue; 95 | } 96 | 97 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 98 | dest = new BufferedOutputStream(byteArrayOutputStream, BUFFER); 99 | // Check every entry's size. 100 | while (total + BUFFER <= TOOBIG && (count = zis.read(data, 0, BUFFER)) != -1) { 101 | dest.write(data, 0, count); 102 | total += count; 103 | } 104 | dest.close(); 105 | handler.handleContent(entry, byteArrayOutputStream.toByteArray()); 106 | 107 | entries++; 108 | // If the total number of entries is larger than the limit, throw exception. 109 | if (entries > TOOMANY) { 110 | throw new IllegalStateException("Too many files to unzip."); 111 | } 112 | // If the total size of zip files is bigger than the maximum size, throw exception. 113 | if (total + BUFFER > TOOBIG) { 114 | throw new IllegalStateException("File being unzipped is too big."); 115 | } 116 | try { 117 | if (dest != null) { 118 | dest.flush(); 119 | dest.close(); 120 | } 121 | } catch (IOException e) { 122 | throw e; 123 | } 124 | try { 125 | if (zis != null) { 126 | zis.closeEntry(); 127 | } 128 | } catch (IOException e) { 129 | throw e; 130 | } 131 | } 132 | } catch (Exception e) { 133 | throw new IllegalStateException("Handle zip File Fail", e); 134 | } finally { 135 | if (zis != null) { 136 | zis.close(); 137 | } 138 | } 139 | } 140 | 141 | @Override 142 | public byte[] SHA256Hash(byte[] content) throws NoSuchAlgorithmException { 143 | MessageDigest messageDigest = MessageDigest.getInstance(HASH_ALGORITHMS); 144 | messageDigest.update(content); 145 | return messageDigest.digest(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassSignServiceBCImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import org.apache.commons.codec.binary.Base64; 20 | import org.apache.commons.io.IOUtils; 21 | import org.bouncycastle.asn1.ASN1Encodable; 22 | import org.bouncycastle.asn1.DEROctetString; 23 | import org.bouncycastle.asn1.cms.CMSObjectIdentifiers; 24 | import org.bouncycastle.asn1.cms.ContentInfo; 25 | import org.bouncycastle.asn1.cms.SignedData; 26 | import org.bouncycastle.cert.jcajce.JcaCertStore; 27 | import org.bouncycastle.cms.CMSException; 28 | import org.bouncycastle.cms.CMSProcessableByteArray; 29 | import org.bouncycastle.cms.CMSSignedData; 30 | import org.bouncycastle.cms.CMSSignedDataGenerator; 31 | import org.bouncycastle.cms.CMSTypedData; 32 | import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; 33 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 34 | import org.bouncycastle.operator.ContentSigner; 35 | import org.bouncycastle.operator.OperatorCreationException; 36 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 37 | import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; 38 | import org.bouncycastle.util.Store; 39 | 40 | import java.io.ByteArrayInputStream; 41 | import java.nio.charset.StandardCharsets; 42 | import java.security.KeyFactory; 43 | import java.security.PrivateKey; 44 | import java.security.Security; 45 | import java.security.cert.CertificateFactory; 46 | import java.security.cert.X509Certificate; 47 | import java.security.spec.PKCS8EncodedKeySpec; 48 | import java.util.ArrayList; 49 | import java.util.List; 50 | 51 | class PassSignServiceBCImpl implements PassSignService { 52 | 53 | public static final String PEM_BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----"; 54 | 55 | public static final String PEM_END_PRIVATE_KEY = "-----END PRIVATE KEY-----"; 56 | 57 | String SIGN_ALGORITHMS_SHA256 = "SHA256WithRSA"; 58 | 59 | PassUtilService utilService; 60 | 61 | /** 62 | * Instantiates a new Pass sign service bc. 63 | */ 64 | public PassSignServiceBCImpl() { 65 | if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { 66 | Security.addProvider(new BouncyCastleProvider()); 67 | } 68 | this.utilService = new PassUtilServiceImpl(); 69 | } 70 | 71 | @Override 72 | public byte[] sign(byte[] content, byte[] privateKeyBytes, byte[] passCertificate) throws PassException { 73 | ByteArrayInputStream passTypeCertificateStream = null; 74 | try { 75 | passTypeCertificateStream = new ByteArrayInputStream(passCertificate); 76 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 77 | X509Certificate passTypeCertificate = 78 | (X509Certificate) certificateFactory.generateCertificate(passTypeCertificateStream); 79 | 80 | String key = new String(privateKeyBytes, StandardCharsets.UTF_8).replaceAll("\\n", "") 81 | .replace(PEM_BEGIN_PRIVATE_KEY, "") 82 | .replace(PEM_END_PRIVATE_KEY, ""); 83 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key)); 84 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 85 | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); 86 | 87 | CMSSignedDataGenerator cmsSignedDataGenerator = new CMSSignedDataGenerator(); 88 | List certificateList = new ArrayList<>(); 89 | certificateList.add(passTypeCertificate); 90 | Store certStore = new JcaCertStore(certificateList); 91 | cmsSignedDataGenerator.addCertificates(certStore); 92 | 93 | ContentSigner sha256Siger = 94 | new JcaContentSignerBuilder(SIGN_ALGORITHMS_SHA256).setProvider(BouncyCastleProvider.PROVIDER_NAME) 95 | .build(privateKey); 96 | 97 | cmsSignedDataGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( 98 | new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build()) 99 | .setSignedAttributeGenerator(new PassSignedAttributeTableGenerator()) 100 | .build(sha256Siger, passTypeCertificate)); 101 | 102 | CMSTypedData chainMessage = new CMSProcessableByteArray(content); 103 | CMSSignedData signedData = cmsSignedDataGenerator.generate(chainMessage, false); 104 | 105 | ContentInfo asn1Structure = signedData.toASN1Structure(); 106 | SignedData instance = SignedData.getInstance(asn1Structure.getContent()); 107 | 108 | ASN1Encodable octs = new DEROctetString(content); 109 | ContentInfo encInfo = new ContentInfo(chainMessage.getContentType(), octs); 110 | 111 | SignedData newSignData = new SignedData(instance.getDigestAlgorithms(), encInfo, instance.getCertificates(), 112 | instance.getCRLs(), instance.getSignerInfos()); 113 | 114 | ContentInfo contentInfo = new ContentInfo(CMSObjectIdentifiers.signedData, newSignData); 115 | 116 | CMSSignedData newCmsSignedData = new CMSSignedData(chainMessage, contentInfo); 117 | 118 | return newCmsSignedData.getEncoded(); 119 | } catch (OperatorCreationException e) { 120 | throw new PassException("OperatorCreationException", e); 121 | } catch (CMSException e) { 122 | throw new PassException("CMSException", e); 123 | } catch (Exception e) { 124 | throw new PassException("sign fail", e); 125 | } finally { 126 | IOUtils.closeQuietly(passTypeCertificateStream); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/com/huawei/wallet/pass/passsdk/PassFileServiceTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 20 | import com.fasterxml.jackson.databind.node.ObjectNode; 21 | import org.apache.commons.codec.binary.Base64; 22 | import org.apache.commons.io.IOUtils; 23 | 24 | import java.io.ByteArrayInputStream; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.nio.charset.StandardCharsets; 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | import java.nio.file.Paths; 31 | import java.security.SecureRandom; 32 | import java.util.Enumeration; 33 | import java.util.HashMap; 34 | import java.util.Map; 35 | import java.util.Properties; 36 | 37 | /** 38 | * The type Pass file service test util. 39 | */ 40 | public class PassFileServiceTestUtil { 41 | 42 | /** 43 | * The constant TEST_PASS_TYPE. 44 | */ 45 | public static final String TEST_PASS_TYPE = "Your service ID"; 46 | 47 | private Properties properties = new Properties(); 48 | 49 | private PassUtilService utilService = new PassUtilServiceImpl(); 50 | 51 | /** 52 | * Gets properties. 53 | * 54 | * @return the properties 55 | */ 56 | public Properties getProperties() { 57 | return properties; 58 | } 59 | 60 | /** 61 | * Create fixed pass byte [ ]. 62 | * 63 | * @param passType the pass type 64 | * @param passFileService the pass file service 65 | * @return the byte [ ] 66 | * @throws PassException the pass exception 67 | */ 68 | public byte[] createFixedPass(String passType, PassFileService passFileService) throws PassException { 69 | String passNumber = "100"; 70 | ObjectNode passJson = JsonNodeFactory.instance.objectNode(); 71 | passJson.put(PassFileServiceImpl.PASS_PASSTYPEIDENTIFIER, passType); 72 | passJson.put(PassFileServiceImpl.PASS_SERIALNUMBER, passNumber); 73 | passJson.put(PassFileServiceImpl.PASS_WEBSERVICEURL, "http://www.test.com"); 74 | passJson.put(PassFileServiceImpl.PASS_AUTHORIZATIONTOKEN, "token"); 75 | int fileCount = new SecureRandom().nextInt(10); 76 | 77 | Map fileMap = new HashMap(); 78 | 79 | for (int i = 0; i < fileCount; i++) { 80 | String fileName = utilService.getCompressUUID(); 81 | String fileContent = utilService.getCompressUUID(); 82 | fileMap.put(fileName, fileContent.getBytes(StandardCharsets.UTF_8)); 83 | } 84 | 85 | byte[] signBytes = passFileService.createPass(fileMap, passJson.toString()); 86 | return signBytes; 87 | 88 | } 89 | 90 | /** 91 | * Test create sign message. 92 | * 93 | * @param passType the pass type 94 | * @param passFileService the pass file service 95 | * @throws PassException the pass exception 96 | */ 97 | public void testCreateSignMessage(String passType, PassFileService passFileService) throws PassException { 98 | ObjectNode messageJson = JsonNodeFactory.instance.objectNode(); 99 | int fileCount = new SecureRandom().nextInt(10); 100 | 101 | for (int i = 0; i < fileCount + 5; i++) { 102 | messageJson.put("key_" + i, "value_" + utilService.getCompressUUID()); 103 | } 104 | 105 | byte[] messageBytes = messageJson.toString().getBytes(StandardCharsets.UTF_8); 106 | byte[] signMessage = passFileService.signMessage(messageBytes, passType); 107 | String signByteStr = Base64.encodeBase64String(signMessage); 108 | System.out.println(signByteStr); 109 | } 110 | 111 | /** 112 | * Test create pass. 113 | * 114 | * @param passType the pass type 115 | * @param passFileService the pass file service 116 | * @throws PassException the pass exception 117 | */ 118 | public void testCreatePassFile(String passType, PassFileService passFileService) throws IOException, PassException { 119 | byte[] randomPass = createFixedPass(passType, passFileService); 120 | Path signFilePath = Paths.get("target/" + passType + ".hwpass"); 121 | Files.write(signFilePath, randomPass); 122 | } 123 | 124 | /** 125 | * 从properties加载配置, 不推荐使用,请自己实现获取证书和私钥接口,这里测试的私钥未加密 126 | * 生产环境私钥一定要加密保存好, 建议签名服务与一般业务服务器分离部署!!! 127 | * 约束: Base字符串生成方法: cat ${filepath}|base64 -w 0 > ${filepath}.base64.txt 128 | * 私钥: passKeyPrefix + "certificate." + passTypeIdentifier=Base64(PKCS8私钥byte[]) 129 | * 证书: passKeyPrefix + "certificate." + passTypeIdentifier=Base64(X.509证书文件byte[]) 130 | * ca: passKeyPrefix + "ca"=Base64(CA证书byte[]) 131 | * 132 | * @param properties the properties 133 | * @param passKeyPrefix the pass key prefix 134 | * @return the pass file service 135 | */ 136 | public PassFileService getPassFileService(final Properties properties, final String passKeyPrefix) { 137 | PassCertificateService passCertificateService = new PassCertificateService() { 138 | @Override 139 | public byte[] getCertificateByPassType(String passTypeIdentifier) { 140 | String property = properties.getProperty(passKeyPrefix + "certificate." + passTypeIdentifier, ""); 141 | return Base64.decodeBase64(property); 142 | } 143 | 144 | @Override 145 | public byte[] getPrivateKeyByPassType(String passTypeIdentifier) { 146 | String property = properties.getProperty(passKeyPrefix + "privatekey." + passTypeIdentifier, ""); 147 | return Base64.decodeBase64(property); 148 | } 149 | 150 | }; 151 | 152 | PassFileService passFileService = new PassFileServiceImpl(passCertificateService); 153 | return passFileService; 154 | } 155 | 156 | /** 157 | * Load properties. 158 | * 159 | * @param filePath the file path 160 | * @throws IOException the io exception 161 | */ 162 | public void loadProperties(String filePath) throws IOException { 163 | byte[] bytes = loadTestBytes(filePath); 164 | Properties cert_properties = new Properties(); 165 | cert_properties.load(new ByteArrayInputStream(bytes)); 166 | Enumeration enumeration = cert_properties.propertyNames(); 167 | while (enumeration.hasMoreElements()) { 168 | String name = enumeration.nextElement().toString(); 169 | properties.setProperty(name, cert_properties.getProperty(name)); 170 | } 171 | } 172 | 173 | /** 174 | * Load test bytes byte [ ]. 175 | * 176 | * @param filePath the file path 177 | * @return the byte [ ] 178 | * @throws IOException the io exception 179 | */ 180 | byte[] loadTestBytes(String filePath) throws IOException { 181 | InputStream resourceAsStream = PassFileServiceTestUtil.class.getResourceAsStream(filePath); 182 | return IOUtils.toByteArray(resourceAsStream); 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and 39 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 41 | 42 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 43 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 44 | 45 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 46 | 47 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 48 | 49 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 50 | 51 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 52 | 53 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/main/java/com/huawei/wallet/pass/passsdk/PassFileServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.huawei.wallet.pass.passsdk; 18 | 19 | import com.fasterxml.jackson.databind.JsonNode; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 22 | import com.fasterxml.jackson.databind.node.ObjectNode; 23 | import org.apache.commons.io.IOUtils; 24 | 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.IOException; 27 | import java.nio.charset.StandardCharsets; 28 | import java.security.NoSuchAlgorithmException; 29 | import java.util.Map; 30 | import java.util.Map.Entry; 31 | import java.util.Set; 32 | import java.util.zip.ZipEntry; 33 | import java.util.zip.ZipOutputStream; 34 | 35 | /** 36 | * Implementation of pass file service. 37 | * 38 | * @since 2019 -08-06 39 | */ 40 | public class PassFileServiceImpl implements PassFileService { 41 | 42 | public static final String PASS_SIGNATURE = "signature"; 43 | 44 | public static final String MANIFEST_JSON = "manifest.json"; 45 | 46 | public static final String PASS_JSON = "hwpass.json"; 47 | 48 | public static final String PASS_PASSTYPEIDENTIFIER = "passTypeIdentifier"; 49 | 50 | public static final String PASS_SERIALNUMBER = "serialNumber"; 51 | 52 | public static final String PASS_WEBSERVICEURL = "webServiceURL"; 53 | 54 | public static final String PASS_AUTHORIZATIONTOKEN = "authorizationToken"; 55 | 56 | private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); 57 | 58 | PassUtilService passUtilService; 59 | 60 | PassSignService passSignService; 61 | 62 | PassSignService passSignVerifyService; 63 | 64 | PassCertificateService passCertificateService; 65 | 66 | public PassUtilService getPassUtilService() { 67 | return passUtilService; 68 | } 69 | 70 | public void setPassUtilService(PassUtilService passUtilService) { 71 | this.passUtilService = passUtilService; 72 | } 73 | 74 | public PassSignService getPassSignService() { 75 | return passSignService; 76 | } 77 | 78 | public void setPassSignService(PassSignService passSignService) { 79 | this.passSignService = passSignService; 80 | } 81 | 82 | public PassSignService getPassSignVerifyService() { 83 | return passSignVerifyService; 84 | } 85 | 86 | public void setPassSignVerifyService(PassSignService passSignVerifyService) { 87 | this.passSignVerifyService = passSignVerifyService; 88 | } 89 | 90 | public PassCertificateService getPassCertificateService() { 91 | return passCertificateService; 92 | } 93 | 94 | public void setPassCertificateService(PassCertificateService passCertificateService) { 95 | this.passCertificateService = passCertificateService; 96 | } 97 | 98 | /** 99 | * Instantiates a new Pass file service. 100 | * 101 | * @param passCertificateService the pass certificate service. 102 | */ 103 | public PassFileServiceImpl(PassCertificateService passCertificateService) { 104 | this.passCertificateService = passCertificateService; 105 | this.passUtilService = new PassUtilServiceImpl(); 106 | this.passSignService = new PassSignServiceBCImpl(); 107 | this.passSignVerifyService = new PassSignServiceBCImpl(); 108 | } 109 | 110 | private String parsePassTypeIdentifier(ObjectNode passJson) { 111 | String ret = null; 112 | JsonNode typeNode = passJson.path(PASS_PASSTYPEIDENTIFIER); 113 | if (!typeNode.isMissingNode() && !typeNode.isNull()) { 114 | ret = typeNode.asText(); 115 | } 116 | return ret; 117 | } 118 | 119 | private byte[] createSignPass(Map fileMap, ObjectNode passJson, byte[] privateKey, 120 | byte[] passCertificate) throws PassException { 121 | byte[] unSignPass = createUnSignPass(fileMap, passJson); 122 | byte[] signPassBytes = signPass(unSignPass, privateKey, passCertificate); 123 | return signPassBytes; 124 | } 125 | 126 | private byte[] createPass(Map fileMap, ObjectNode passJson) throws PassException { 127 | String passTypeIdentifier = parsePassTypeIdentifier(passJson); 128 | if (passTypeIdentifier == null || passTypeIdentifier.isEmpty()) { 129 | throw new PassException("passTypeIdentifier is null or empty"); 130 | } 131 | byte[] privateKey = passCertificateService.getPrivateKeyByPassType(passTypeIdentifier); 132 | byte[] certificateByPassType = passCertificateService.getCertificateByPassType(passTypeIdentifier); 133 | return createSignPass(fileMap, passJson, privateKey, certificateByPassType); 134 | } 135 | 136 | @Override 137 | public byte[] signMessage(byte[] message, String passTypeIdentifier) throws PassException { 138 | return passSignService.sign(message, passCertificateService.getPrivateKeyByPassType(passTypeIdentifier), 139 | passCertificateService.getCertificateByPassType(passTypeIdentifier)); 140 | } 141 | 142 | private byte[] createUnSignPass(Map fileMap, ObjectNode passJson) throws PassException { 143 | ByteArrayOutputStream memoryZipArray = new ByteArrayOutputStream(); 144 | ZipOutputStream out = new ZipOutputStream(memoryZipArray); 145 | try { 146 | ZipEntry zipEntry = new ZipEntry(PASS_JSON); 147 | out.putNextEntry(zipEntry); 148 | byte[] contentByte = passJson.toString().getBytes(StandardCharsets.UTF_8); 149 | out.write(contentByte); 150 | out.closeEntry(); 151 | 152 | Set> entries = fileMap.entrySet(); 153 | for (Entry entry : entries) { 154 | String fileName = entry.getKey(); 155 | ZipEntry ze = new ZipEntry(fileName); 156 | out.putNextEntry(ze); 157 | out.write(entry.getValue()); 158 | out.closeEntry(); 159 | } 160 | out.close(); 161 | } catch (Exception e) { 162 | throw new PassException("createUnSignPass fail", e); 163 | } finally { 164 | IOUtils.closeQuietly(out); 165 | IOUtils.closeQuietly(memoryZipArray); 166 | } 167 | return memoryZipArray.toByteArray(); 168 | } 169 | 170 | private byte[] signPass(byte[] unSignPassZipBytes, byte[] privateKey, byte[] passCertificate) throws PassException { 171 | 172 | ByteArrayOutputStream memoryZipArray = new ByteArrayOutputStream(); 173 | final ZipOutputStream zipOut = new ZipOutputStream(memoryZipArray); 174 | try { 175 | 176 | final ObjectNode manifestJson = JsonNodeFactory.instance.objectNode(); 177 | ZipInputStreamHandler signHandler = new ZipInputStreamHandler() { 178 | 179 | @Override 180 | public boolean needSkip(ZipEntry entry) { 181 | String entryName = entry.getName(); 182 | boolean needSkip = entryName.equals(PASS_SIGNATURE) || entryName.equals(MANIFEST_JSON); 183 | 184 | if (entry.isDirectory() || needSkip) { 185 | return true; 186 | } 187 | return false; 188 | } 189 | 190 | @Override 191 | public void handleContent(ZipEntry entry, byte[] unzipContent) 192 | throws IOException, NoSuchAlgorithmException { 193 | String entryName = entry.getName(); 194 | ZipEntry newEntry = new ZipEntry(entryName); 195 | zipOut.putNextEntry(newEntry); 196 | zipOut.write(unzipContent); 197 | zipOut.closeEntry(); 198 | String fileHash = passUtilService.toHexString(passUtilService.SHA256Hash(unzipContent)); 199 | manifestJson.put(entryName, fileHash); 200 | } 201 | }; 202 | passUtilService.handleZipInputStream(unSignPassZipBytes, signHandler); 203 | 204 | ZipEntry newEntry = new ZipEntry(MANIFEST_JSON); 205 | zipOut.putNextEntry(newEntry); 206 | byte[] maniBytes = manifestJson.toString().getBytes(StandardCharsets.UTF_8); 207 | zipOut.write(maniBytes); 208 | zipOut.closeEntry(); 209 | 210 | byte[] signBytes = passSignService.sign(maniBytes, privateKey, passCertificate); 211 | ZipEntry signEntry = new ZipEntry(PASS_SIGNATURE); 212 | zipOut.putNextEntry(signEntry); 213 | zipOut.write(signBytes); 214 | zipOut.closeEntry(); 215 | zipOut.close(); 216 | } catch (Exception e) { 217 | throw new PassException("signPass fail", e); 218 | } finally { 219 | IOUtils.closeQuietly(zipOut); 220 | IOUtils.closeQuietly(memoryZipArray); 221 | } 222 | return memoryZipArray.toByteArray(); 223 | } 224 | 225 | @Override 226 | public byte[] createPass(Map fileMap, String passJson) throws PassException { 227 | try { 228 | return createPass(fileMap, JSON_MAPPER.readValue(passJson, ObjectNode.class)); 229 | } catch (Exception e) { 230 | throw new PassException("createPass fail", e); 231 | } 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /src/test/resources/PassSignServiceImpl.source: -------------------------------------------------------------------------------- 1 | package com.huawei.wallet.pass.passsdk; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | import org.apache.commons.io.IOUtils; 5 | import sun.security.pkcs.ContentInfo; 6 | import sun.security.pkcs.PKCS7; 7 | import sun.security.pkcs.PKCS9Attribute; 8 | import sun.security.pkcs.PKCS9Attributes; 9 | import sun.security.pkcs.SignerInfo; 10 | import sun.security.util.DerOutputStream; 11 | import sun.security.util.DerValue; 12 | import sun.security.x509.AlgorithmId; 13 | import sun.security.x509.X500Name; 14 | 15 | import javax.security.auth.x500.X500Principal; 16 | import java.io.ByteArrayInputStream; 17 | import java.io.IOException; 18 | import java.math.BigInteger; 19 | import java.nio.charset.StandardCharsets; 20 | import java.security.KeyFactory; 21 | import java.security.PrivateKey; 22 | import java.security.Signature; 23 | import java.security.cert.CertificateFactory; 24 | import java.security.cert.X509Certificate; 25 | import java.security.spec.PKCS8EncodedKeySpec; 26 | import java.util.Date; 27 | 28 | public class PassSignServiceImpl implements PassSignService 29 | { 30 | 31 | //华为PassType类型约定前缀 32 | public static final String HWPASS_PREFIX = "hwpass."; 33 | 34 | public static final String PEM_BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----"; 35 | 36 | public static final String PEM_END_PRIVATE_KEY = "-----END PRIVATE KEY-----"; 37 | 38 | PassUtilService utilService; 39 | 40 | public void setUtilService(PassUtilService utilService) 41 | { 42 | this.utilService = utilService; 43 | } 44 | 45 | public PassSignServiceImpl() 46 | { 47 | this.utilService = new PassUtilServiceImpl(); 48 | } 49 | 50 | @Override 51 | public byte[] sign(byte[] dataToSignBytes, byte[] privateKeyBytes, byte[] passTypeCertificateBytes) 52 | throws PassException 53 | { 54 | System.out.println("OLD sign"); 55 | DerOutputStream bOut = new DerOutputStream(); 56 | ByteArrayInputStream passTypeCertificateStream = new ByteArrayInputStream(passTypeCertificateBytes); 57 | try 58 | { 59 | 60 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 61 | X509Certificate passTypeCertificate = (X509Certificate) 62 | certificateFactory 63 | .generateCertificate(passTypeCertificateStream); 64 | 65 | String key = new String(privateKeyBytes, StandardCharsets.UTF_8).replaceAll("\\n", "") 66 | .replace(PEM_BEGIN_PRIVATE_KEY, "") 67 | .replace(PEM_END_PRIVATE_KEY, ""); 68 | PKCS8EncodedKeySpec keySpec = 69 | new PKCS8EncodedKeySpec(Base64.decodeBase64(key)); 70 | KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 71 | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); 72 | 73 | X500Name xName = X500Name.asX500Name(passTypeCertificate.getIssuerX500Principal()); 74 | BigInteger serial = passTypeCertificate.getSerialNumber(); 75 | AlgorithmId digestAlgorithmId = new AlgorithmId(AlgorithmId.SHA256_oid); 76 | AlgorithmId signAlgorithmId = new AlgorithmId(AlgorithmId.RSAEncryption_oid); 77 | 78 | PKCS9Attributes authAttibutes = new PKCS9Attributes(new PKCS9Attribute[] { 79 | new PKCS9Attribute(PKCS9Attribute.CONTENT_TYPE_OID, 80 | ContentInfo.DATA_OID), 81 | new PKCS9Attribute(PKCS9Attribute.MESSAGE_DIGEST_OID, utilService.SHA256Hash(dataToSignBytes)), 82 | new PKCS9Attribute(PKCS9Attribute.SIGNING_TIME_OID, new Date()) 83 | }); 84 | 85 | Signature signature = Signature.getInstance("SHA256withRSA"); 86 | signature.initSign(privateKey); 87 | signature.update(authAttibutes.getDerEncoding()); 88 | byte[] signedData = signature.sign(); 89 | 90 | SignerInfo sInfo = 91 | new SignerInfo(xName, serial, digestAlgorithmId, authAttibutes, signAlgorithmId, signedData, null); 92 | ContentInfo cInfo = 93 | new ContentInfo(ContentInfo.DATA_OID, new DerValue(DerValue.tag_OctetString, dataToSignBytes)); 94 | 95 | PKCS7 p7 = new PKCS7(new AlgorithmId[] {digestAlgorithmId}, cInfo, 96 | new X509Certificate[] {passTypeCertificate}, 97 | new SignerInfo[] {sInfo}); 98 | p7.encodeSignedData(bOut); 99 | return bOut.toByteArray(); 100 | } 101 | catch (IOException e) 102 | { 103 | throw new PassException("sign fail", e); 104 | } 105 | catch (Exception e) 106 | { 107 | throw new PassException("sign fail", e); 108 | } 109 | finally 110 | { 111 | IOUtils.closeQuietly(passTypeCertificateStream); 112 | IOUtils.closeQuietly(bOut); 113 | } 114 | } 115 | 116 | @Override 117 | public PKCS7Info verify(String passType, byte[] dataToSignBytes, byte[] signatureBytes, 118 | byte[] huaweiCACertificateBytes, Long signValidTime) 119 | throws PassException 120 | { 121 | System.out.println("OLD verify"); 122 | DerOutputStream bOut = new DerOutputStream(); 123 | ByteArrayInputStream huaweiCACertificateStream = new ByteArrayInputStream(huaweiCACertificateBytes); 124 | PKCS7Info ret = null; 125 | try 126 | { 127 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 128 | X509Certificate huaweiCertificate = (X509Certificate) 129 | certificateFactory 130 | .generateCertificate(huaweiCACertificateStream); 131 | 132 | PKCS7 parseSignature = new PKCS7(signatureBytes); 133 | SignerInfo[] signerInfos = parseSignature.verify(dataToSignBytes); 134 | X509Certificate[] certificates = parseSignature.getCertificates(); 135 | if (!verifySingerCount(signerInfos, certificates)) 136 | { 137 | return null; 138 | } 139 | 140 | //只支持一个证书和一个签名 141 | SignerInfo signerInfo = signerInfos[0]; 142 | 143 | X509Certificate check_certificate = signerInfo.getCertificate(parseSignature); 144 | //检测证书是否已经失效 145 | check_certificate.checkValidity(); 146 | //检测是否是HuaweiCA签发的证书 147 | check_certificate.verify(huaweiCertificate.getPublicKey()); 148 | 149 | X500Principal subjectX500Principal = check_certificate.getSubjectX500Principal(); 150 | X500Name x500Name = X500Name.asX500Name(subjectX500Principal); 151 | String commonName = x500Name.getCommonName(); 152 | if (commonName != null) 153 | { 154 | 155 | PKCS9Attribute signingTimeAttribute = 156 | signerInfo.getAuthenticatedAttributes().getAttribute(PKCS9Attribute.SIGNING_TIME_OID); 157 | Date signingTime = (Date)signingTimeAttribute.getValue(); 158 | 159 | if (signValidTime != null) 160 | { 161 | //判断签名时间,只有一个签名是有效的 162 | if (Math.abs(System.currentTimeMillis() - signingTime.getTime()) > signValidTime) 163 | { 164 | throw new PassException("check certificate fail, signValidTime past!!!!"); 165 | } 166 | } 167 | 168 | if (signingTime == null) 169 | { 170 | throw new PassException("signingTime is null, error"); 171 | } 172 | 173 | if (!verifyDigestMethod(signerInfo)) 174 | { 175 | throw new PassException("signingTime is null, error"); 176 | } ; 177 | 178 | ret = new PKCS7Info(); 179 | ret.setSigningTime(signingTime.getTime()); 180 | // 如果passType不为空,则一定要和commonName匹配 181 | if (passType != null) 182 | { 183 | if (commonName.equals(passType)) 184 | { 185 | ret.setCommonName(commonName); 186 | } 187 | else 188 | { 189 | // all the certificate must same as passType 190 | throw new PassException("check certificate fail, CN and passType not match!!!!"); 191 | } 192 | } 193 | else // 如果passType为空,则跳过commandName的校验并解析出commandName作为结果返回 194 | { 195 | ret.setCommonName(commonName); 196 | } 197 | } 198 | else 199 | { 200 | // all the certificate must same as passType 201 | throw new PassException("check certificate fail, commomName is null"); 202 | } 203 | 204 | //保证只有commonName为hwpass.开头的才通过校验 205 | if (ret.getCommonName() != null) 206 | { 207 | if (!ret.getCommonName().startsWith(HWPASS_PREFIX)) 208 | { 209 | throw new PassException( 210 | "check certificate fail, commomNama not startWith " + HWPASS_PREFIX); 211 | } 212 | } 213 | 214 | //全部校验通过,获取content数据 215 | byte[] data = parseSignature.getContentInfo().getData(); 216 | ret.setContentBytes(data); 217 | return ret; 218 | } 219 | catch (Exception e) 220 | { 221 | throw new PassException("verify fail", e); 222 | } 223 | finally 224 | { 225 | IOUtils.closeQuietly(huaweiCACertificateStream); 226 | IOUtils.closeQuietly(bOut); 227 | } 228 | } 229 | 230 | private boolean verifyDigestMethod(SignerInfo signerInfo) 231 | throws PassException 232 | { 233 | if (!signerInfo.getDigestAlgorithmId().equals(AlgorithmId.SHA256_oid)) 234 | { 235 | throw new PassException("digest only support sha256 hash method, error"); 236 | } 237 | 238 | AlgorithmId digestEncryptionAlgorithmId = signerInfo.getDigestEncryptionAlgorithmId(); 239 | if (!digestEncryptionAlgorithmId.equals(AlgorithmId.RSAEncryption_oid) && !digestEncryptionAlgorithmId.equals( 240 | AlgorithmId.sha256WithRSAEncryption_oid)) 241 | { 242 | throw new PassException("encrypt only support sha256WthRSA or RSA hash method, error"); 243 | } 244 | return true; 245 | } 246 | 247 | private boolean verifySingerCount(SignerInfo[] signerInfos, X509Certificate[] certificates) 248 | throws PassException 249 | { 250 | if (signerInfos == null || signerInfos.length != 1 || certificates == null || certificates.length != 1) 251 | { 252 | throw new PassException("singerInfo==null or singerInfo.length!=1 "); 253 | } 254 | return true; 255 | } 256 | 257 | } 258 | --------------------------------------------------------------------------------