# 对返回结果验签

#### 功能介绍

IAP服务器API返回结果以及服务端关键事件通知返回的是JSON Web Signature （JWS）格式的数据。JWS保证数据在传输过程中的完整性，防止数据被篡改。

#### JWS结构

JWS由三部分组成：

1.  Header（头部）
    
2.  Payload（负载）
    
3.  Signature（签名）
    

在传输的时候，将JWS的三部分分别进行Base64编码。

解码后的Header

| 参数 | 是否必选 | 参数类型 | 描述 |
| --- | --- | --- | --- |
| alg | 是 | String | 算法类型，固定为ES256。 |
| typ | 是 | String | 固定为JWT。 |
| x5c | 是 | String | X.509 证书链，顺序为叶子证书、中间证书、根证书。 |

#### JWS解码和验签

1.  解析JWS，获取Header、 Payload和Signature。
    
2.  从header的x5c中获取证书链（依次是叶子证书、中间证书、根证书），使用Huawei CBG Root CA G2证书对证书链进行验证。
    
3.  校验叶子证书的OID：1.3.6.1.4.1.2011.2.415.1.1。
    
4.  证书校验通过则从叶子证书获取到PublicKey。
    
5.  通过Header指定的算法和PublicKey进行JWT验签，可以使用各种开源库来验签，具体请参见jwt.io。
    

#### JWS解码和验签示例

以下示例代码仅以Java语言为例，Python、PHP、JS、Golang语言示例代码可通过在 **IAP Kit-Sample-ServerDemo** 中切换代码分支查看。

```
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.util.Base64;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class JWSChecker {
    private static final String CA_CERT_FILE_PATH = "/path/to/cer/RootCaG2Ecdsa.cer";// TODO: Need to replace it with the actual value, please refer to Huawei CBG Root CA G2.

    /**
     * JWS HEADER PARAM X5C: Indicates the X.509 certificate chain. The sequence is leaf certificate, intermediate certificate, and root certificate.
     */
    private static final String HEADER_PARAM_X5C = "x5c";

    private static final int X5C_CHAIN_LENGTH = 3;

    /**
     * JWS HEADER PARAM ALG: Algorithm type. The value is always ES256.
     */
    private static final String HEADER_PARAM_ALG_ES256 = "ES256";

    private static final String LEAF_CERT_OID = "1.3.6.1.4.1.2011.2.415.1.1";

    /**
     * Used to verify JWS and decode the payload.
     * @param jwsStr JWS string
     * @return payload string
     * @throws Exception exception
     */
    public static String checkAndDecodeJWS(String jwsStr) throws Exception {
        if (jwsStr == null || jwsStr.isEmpty()) {
            // TODO: Need to replace it with the actual business logic.
            throw new Exception("jwsStr was null");
        }
        DecodedJWT decodedJWT = JWT.decode(jwsStr);
        if (!HEADER_PARAM_ALG_ES256.equals(decodedJWT.getAlgorithm())) {
            // TODO: Need to replace it with the actual business logic.
            throw new Exception("alg must be ES256");
        }
        String[] x5cChain = decodedJWT.getHeaderClaim(HEADER_PARAM_X5C).asArray(String.class);
        if (x5cChain == null) {
            // TODO: Need to replace it with the actual business logic.
            throw new Exception("x5c chain was null");
        }
        // Verify the x5c certificate chain and obtain the public key.
        PublicKey publicKey = verifyChainAndGetPubKey(x5cChain);
        // Use the public key to verify the signature of the jws.
        JWTVerifier jwtVerifier = JWT.require(Algorithm.ECDSA256((ECPublicKey) publicKey)).build();
        jwtVerifier.verify(decodedJWT);
        // Decode and return the payload.
        return new String(Base64.getUrlDecoder().decode(decodedJWT.getPayload()), StandardCharsets.UTF_8);
    }
    
    private static PublicKey verifyChainAndGetPubKey(String[] certificates) throws Exception {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        List<Certificate> certificateList = new LinkedList<>();
        for (String certificate : certificates) {
            InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(certificate));
            certificateList.add(certificateFactory.generateCertificate(inputStream));
        }
        if (certificateList.size() != X5C_CHAIN_LENGTH) {
            // TODO: Need to replace it with the actual business logic.
            throw new Exception("invalid cert chain length");
        }
        PKIXCertPathValidatorResult certPathValidatorResult;
        try {
            PKIXParameters parameters = loadRootCAAndPKIX();
            CertPathValidator validator = CertPathValidator.getInstance("PKIX");
            
            // TODO: Need to do crl check if not ignore CRL
            parameters.setRevocationEnabled(false);
            CertPath certPath = certificateFactory.generateCertPath(certificateList.subList(0, X5C_CHAIN_LENGTH - 1));
            certPathValidatorResult = (PKIXCertPathValidatorResult) validator.validate(certPath, parameters);
        } catch (Exception e) {
            // TODO: Need to replace it with the actual business logic.
            throw new Exception(e);
        }
        Certificate iapCert = certificateList.get(0);
        if (!(iapCert instanceof X509Certificate)) {
            // TODO: Need to replace it with the actual business logic.
            throw new Exception("leaf certificate must be X509 format");
        }
        X509Certificate x509Certificate = (X509Certificate) iapCert;
        if (x509Certificate.getNonCriticalExtensionOIDs() == null ||
            !x509Certificate.getNonCriticalExtensionOIDs().contains(LEAF_CERT_OID)) {
            // TODO: Need to replace it with the actual business logic.
            throw new CertPathValidatorException("OID not found");
        }
        return certPathValidatorResult.getPublicKey();
    }

    private static PKIXParameters loadRootCAAndPKIX() throws Exception {
        PKIXParameters parameters;
        // TODO: Under Java 8, Need to close the resource in the finally block.
        try (InputStream fis = Files.newInputStream(Paths.get(CA_CERT_FILE_PATH))) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Certificate trustCert = certificateFactory.generateCertificate(fis);
            if (!(trustCert instanceof X509Certificate)) {
                // TODO: Need to replace it with the actual business logic.
                throw new RuntimeException("root certificate must be X509 format");
            }
            Set<TrustAnchor> trustAnchors = new HashSet<>();
            trustAnchors.add(new TrustAnchor((X509Certificate) trustCert, null));
            parameters = new PKIXParameters(trustAnchors);
        }
        return parameters;
    }
}
```

pom文件

```
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>
```