简体   繁体   English

我在 x509 中验证哪些字段(如 JWS 中的 x5c header)以证明证书的合法性?

[英]What fields do I verify in a x509 (as x5c header in a JWS) to prove legitimacy of the Certificate?

I've already posted a similar question here , but I've realized that my issue could have more to do with x509 certificate rather than JWS in general.我已经在这里发布了一个类似的问题,但我意识到我的问题可能更多地与 x509 证书有关,而不是一般的 JWS。

Here's the thing, I'm pretty new to JWS, and Apple now transmits them as part of their server-to-server communication.事情是这样的,我对 JWS 很陌生,Apple 现在将它们作为服务器到服务器通信的一部分进行传输。 I'm trying to understand how to fully guarantee the validity of the JWS, because from what I understand, the signature verification only implies that the entire JWS wasn't tampered with.我试图了解如何完全保证 JWS 的有效性,因为据我了解,签名验证仅意味着整个 JWS 未被篡改。 I don't know how to actually verify that this payload is indeed coming from a trusted source (aka Apple) .我不知道如何实际验证此有效负载确实来自受信任的来源(又名 Apple)

Here's what I got so far (PHP):这是我到目前为止所得到的(PHP):

//1. explode jws and decode what's needed
$components = explode('.', $jws);
$headerJson = json_decode(base64_decode($components[0]),true);
$signature = base64Url_decode($components[2]);

//2. extract all certificates from 'x5c' header
foreach ($headerJson['x5c'] as $x5c){
    $c = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
    $c .= chunk_split($x5c,64,PHP_EOL);
    $c .= '-----END CERTIFICATE-----'.PHP_EOL;
    $certificates[] = openssl_x509_read($c);
}

//3. verify validity of certificate chain (each one is signed by the next, except root cert)
for($i = 0; $i < count($certificates); $i++){
    if ($i == count($certificates) - 1){
        if (openssl_x509_verify($certificates[$i], $certificates[$i]) != 1){
            throw new Exception("Invalid Root Certificate");
        }
    }
    else{
        if (openssl_x509_verify($certificates[$i], $certificates[$i+1]) != 1){
            throw new Exception("Invalid Certificate");
        }
    }
}

//4. get public_key from first certificate
$public_key = openssl_pkey_get_public($certificates[0]);

//5. verify entire token, including signature (using the Firebase library)
$parsed_token = (array) \Firebase\JWT\JWT::decode($jws, $public_key, ['ES256']);

//helper function: a simple base64 url decoder
function base64Url_decode($data){
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}

Is there a specific field to check against inside the certificates (one that couldn't be spoofed) to verify the identity/source of the JWS?是否有一个特定的字段可以检查证书内部(不能被欺骗的)以验证 JWS 的身份/来源?

Thanks!谢谢!

I'm not sure if this is correct or pointless, but after doing a bit more research, this is what I've done:我不确定这是正确的还是毫无意义的,但在做了更多的研究之后,这就是我所做的:

  1. make sure we have a chain and not just one certificate.确保我们有一个链条,而不仅仅是一个证书。
  2. parse each certificate in the chain with openssl_x509_parse() .使用openssl_x509_parse()解析链中的每个证书。
  3. compare all certificates' subject['C'] and subject['O'] with hardcoded values.将所有证书的subject['C']subject['O']与硬编码值进行比较。
  4. compare the chain's first certificate's subject['CN'] and issuer['CN'] with hardcoded values.将链的第一个证书的subject['CN']issuer['CN']与硬编码值进行比较。
  5. make sure that all certificates' extensions['authorityKeyIdentifier'] hold a value, except for the last certificate (root).确保所有证书的extensions['authorityKeyIdentifier']都有一个值,最后一个证书(根)除外。

Does this make sense to anyone?这对任何人都有意义吗?

verify jws in Java code在 Java 代码中验证 jws


import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONPath;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.ringle.framework.json.JSONUtils;
import com.ringle.framework.response.ServiceResponseStatusEnum;
import java.io.ByteArrayInputStream;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.util.List;
import lombok.Data;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

public interface ApplePayNotificationV2DTO {

  @Setter
  @Slf4j
  class NotificationV2 {

    private static Certificate appleRootCa;
    private String signedPayload;

    public NotificationV2() throws CertificateException {
      if (ObjectUtil.isNull(appleRootCa)) {
        appleRootCa = CertificateFactory.getInstance("X.509").generateCertificate(
            ResourceUtil.getStream("applepay/AppleRootCA-G3.cer")
        );
      }
    }

    public ResponseBodyV2DecodedPayload getResponse() {
      if (!verify()) {
        throw ServiceResponseStatusEnum.BAD_REQUEST.buildException();
      }

      var decodedJWT = JWT.decode(signedPayload);
      var payload = new String(Base64.decode(decodedJWT.getPayload()));

      log.info("Apple Pay Notification Payload={}", payload);
      return JSONUtils.fromJsonNonNull(
          payload,
          ResponseBodyV2DecodedPayload.class
      );
    }

    /**
     * 验证JWT
     */
    private boolean verify() {
      var valid = true;
      try {
        // 拿到 header
        var decodedJWT = JWT.decode(signedPayload);
        var header = new String(Base64.decode(decodedJWT.getHeader()));
        // 获取证书链
        var caChain = JSONUtils.<List<String>>fromJsonNonNull(
            JSONPath.extract(header, "$.x5c").toString(),
            List.class
        );
        // 获取公钥并验证根证书
        var publicKey = getPublicKeyByX5c(caChain);
        // 验证 token
        var algorithm = Algorithm.ECDSA256((ECPublicKey) publicKey, null);
        algorithm.verify(decodedJWT);
      } catch (Exception e) {
        valid = false;
        log.error("Apple Pay JWS Verify Fail", e);
      }
      return valid;
    }

    /**
     * 获取公钥
     *
     * @param x5c
     *
     * @return PublicKey
     */
    private PublicKey getPublicKeyByX5c(List<String> x5c) throws CertificateException {
      var factory = CertificateFactory.getInstance("X.509");
      var jwtSignCa = (X509Certificate) factory.generateCertificate(
          new ByteArrayInputStream(Base64.decode(x5c.get(0)))
      );
      var jwtRootCa = (X509Certificate) factory.generateCertificate(
          new ByteArrayInputStream(Base64.decode(x5c.get(2)))
      );

      // 验证证书是否是苹果颁发的
      try {
        appleRootCa.verify(jwtRootCa.getPublicKey());
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
      return jwtSignCa.getPublicKey();
    }
  }

  @Data
  @Slf4j
  class ResponseBodyV2DecodedPayload {

    private String notificationType;
    private String subtype;
    private String notificationUUID;
    private Object data;
    private String version;
    private Long signedDate;

    /**
     * 获取交易详情
     *
     * @return
     */
    public Transaction getTransaction() {
      var signedTransactionInfo = JSONPath.extract(
          JSONUtils.toJson(data),
          "$.signedTransactionInfo"
      );

      if (ObjectUtil.isNull(signedTransactionInfo)) {
        log.warn("Get ApplePay Transaction Fail : transaction is null. responseBody={}", this);
        throw ServiceResponseStatusEnum.BAD_REQUEST.buildException();
      }

      var decodedJWT = JWT.decode(signedTransactionInfo.toString());
      return JSONUtils.fromJsonNonNull(decodedJWT.getPayload(), Transaction.class);
    }
  }

  @Data
  class Transaction {

    private String environment;
    private String transactionId;
    private Long revocationDate;
  }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM