如何使用Bouncy Castle在应用程序购买收据中阅读App Store? (PKCS7)

时间:2017-04-10 13:53:01

标签: ios encryption in-app-purchase bouncycastle pkcs#7

我在PKCS7上有一张收据,是我从iOS应用程序中获得的。 Apple says this is a PKCS7 structure以及其中有关过去经常性购买的信息。

我有raw receipt here,用Base64编码。

我已将此有效负载与我的密钥一起发送给Apple and got this response。基于WWDC视频和文档,我相信我应该能够直接阅读此收据,而无需将其发送给苹果。

我猜测BC中的PEMReader是解析它的正确起点,但是我不确定如何实际使用它。我已经扫描了字符串“PKCS”的BC源代码,并查看了单元测试,但是我所看到的都是从PEMReader转换为另一种格式。

 using (var stream1 = new MemoryStream(receipt.Data))
 using (var stream2 = new StreamReader(stream1))
 {
       var pp = new PemReader(stream2);
       pp.ReadObject();
 }

问题

  • 如何使用Bouncy Castle验证Apple Store生成的原始收据有效负载?

自我注意:我打算用它来检查实际的二进制文件以查看ApplicationUsername是否包含在收据中,但由于某种原因,在发布服务器时JSON结果中没有返回。 (苹果方面的问题?)

1 个答案:

答案 0 :(得分:3)

我使用 Java 7 BouncyCastle 1.56 制作了这个。

对于下面的代码,请考虑pemString是您提供的PEM字符串。但我不得不做一些修改:

  • 格式(每64个字符的断行) - 我做了一个小程序来做那个
  • 包括 BEGIN END 标题

所以我的PEM看起来像:

-----BEGIN PKCS7-----
MIIv5gYJKoZIhvcNAQcCoIIv1zCCL9MCAQExCzAJBgUrDgMCGgUAMIIfhwYJKoZI
hvcNAQcBoIIfeASCH3Qxgh9wMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC
AQEEAwIBADALAgELAgEBBAMCAQAwCwIBDwIBAQQDAgEAMAsCARACAQEEAwIBADAL
....
gdTu2uzkTyT+vcBlaLHK1ZpjKozsBds7ys6Q4EFp7OLxtJTj7saEDYXCNQtXBjwl
UfSGvQkXeIbsaqSPvOVIE83K3ki5i64gccA=
-----END PKCS7-----

对于下面的代码,我遵循了Apple的文档中的定义:

ReceiptAttribute ::= SEQUENCE {
    type    INTEGER,
    version INTEGER,
    value   OCTET STRING
}

Payload ::= SET OF ReceiptAttribute

代码:

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.DLSet;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

String pemString = // PEM String as described above

PemReader reader = new PemReader(new StringReader(pemString));
PemObject pemObject = reader.readPemObject();
reader.close();

CMSSignedData s = new CMSSignedData(pemObject.getContent());
byte[] content = (byte[]) s.getSignedContent().getContent();

ASN1InputStream in = new ASN1InputStream(content);

// Payload: a SET of ReceiptAttribute
DLSet set = (DLSet) DLSet.fromByteArray(in.readObject().getEncoded());
int size = set.size();
for (int i = 0; i < size; i++) {
    // ReceiptAttribute is a SEQUENCE
    DLSequence seq = (DLSequence) set.getObjectAt(i);

    // value is the third element of the sequence
    DEROctetString oct = (DEROctetString) seq.getObjectAt(2);
    ASN1Object obj = readObject(oct.getOctets()); // *** see comments below ***
}

in.close();

// readObject method
public ASN1Object readObject(byte[] b) throws IOException {
    ASN1InputStream in = null;
    try {
        in = new ASN1InputStream(b);
        return in.readObject();
    } catch (Exception e) {
        // if error occurs, just return the octet string
        return new DEROctetString(b);
    } finally {
        in.close();
    }
}

变量obj将是ReceiptAttribute的内容,并且可能会有很大变化 - 我见过DERIA5StringDERUTF8StringASN1Integer和很多其他的。由于我不知道该字段的所有可能值,我认为由您来检查每个值。