zan*_*ngw 4 go storekit ios jwt jwt-go
最近,应用商店服务器 API 中添加了一种新的 API查找订单 ID 。以及由 App Store 签名的此 API 响应的JWSTransaction ,采用 JSON Web 签名格式。我们想用 go 来验证一下。
我们尝试过什么
type JWSTransaction struct {
BundleID string `json:"bundleId"`
InAppOwnershipType string `json:"inAppOwnershipType"`
TransactionID string `json:"transactionId"`
ProductID string `json:"productId"`
PurchaseDate int64 `json:"purchaseDate"`
Type string `json:"type"`
OriginalPurchaseDate int64 `json:"originalPurchaseDate"`
}
func (ac *JWSTransaction) Valid() error {
return nil
}
func (a *AppStore) readPrivateKeyFromFile(keyFile string) (*ecdsa.PrivateKey, error) {
bytes, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
block, _ := pem.Decode(bytes)
if block == nil {
return nil, errors.New("appstore private key must be a valid .p8 PEM file")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
switch pk := key.(type) {
case *ecdsa.PrivateKey:
return pk, nil
default:
return nil, errors.New("appstore private key must be of type ecdsa.PrivateKey")
}
}
func (a *AppStore) ExtractClaims(tokenStr string) (*JWSTransaction, error) {
privateKey, err := a.readPrivateKeyFromFile()
if err != nil {
return nil, err
}
publicKey, err := x509.MarshalPKIXPublicKey(privateKey.Public())
if err != nil {
return nil, err
}
fmt.Println(publicKey)
tran := JWSTransaction{}
token, err := jwt.ParseWithClaims(tokenStr, &tran, func(token *jwt.Token) (interface{}, error) {
fmt.Println(token.Claims)
fmt.Println(token.Method.Alg())
return publicKey, nil
})
if err != nil {
fmt.Println(err)
}
Run Code Online (Sandbox Code Playgroud)
然而,错误key is of invalid type
来自jwt.ParseWithClaims
.
token, err := jwt.ParseWithClaims(tokenStr, &tran, func(token *jwt.Token) (interface{}, error) {
fmt.Println(token.Claims)
fmt.Println(token.Method.Alg())
kid, ok := token.Header["kid"].(string)
if !ok {
return nil, errors.New("failed to find kid from headers")
}
key, found := keySet.LookupKeyID(kid)
if !found {
return nil, errors.New("failed to find kid from key set")
}
return publicKey, nil
})
Run Code Online (Sandbox Code Playgroud)
但是,我们未能在应用商店服务器 API 文档中找到公钥 URL。kid
此外, JWSTransaction 的标头中没有任何内容。
我们想知道如何在 Go 中验证应用商店服务器 api 的 JWS 事务?我有什么遗漏的吗?
“x5c”(X.509 证书链)标头参数包含与用于对 JWS 进行数字签名的密钥相对应的 X.509 公钥证书或证书链 [RFC5280]。
func (a *AppStore) extractPublicKeyFromToken(tokenStr string) (*ecdsa.PublicKey, error) {
tokenArr := strings.Split(tokenStr, ".")
headerByte, err := base64.RawStdEncoding.DecodeString(tokenArr[0])
if err != nil {
return nil, err
}
type Header struct {
Alg string `json:"alg"`
X5c []string `json:"x5c"`
}
var header Header
err = json.Unmarshal(headerByte, &header)
if err != nil {
return nil, err
}
certByte, err := base64.StdEncoding.DecodeString(header.X5c[0])
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certByte)
if err != nil {
return nil, err
}
switch pk := cert.PublicKey.(type) {
case *ecdsa.PublicKey:
return pk, nil
default:
return nil, errors.New("appstore public key must be of type ecdsa.PublicKey")
}
}
func (a *AppStore) ExtractClaims(tokenStr string) (*JWSTransaction, error) {
tran := &JWSTransaction{}
_, err := jwt.ParseWithClaims(tokenStr, tran, func(token *jwt.Token) (interface{}, error) {
return a.extractPublicKeyFromToken(tokenStr)
})
if err != nil {
return nil, err
}
return tran, nil
}
Run Code Online (Sandbox Code Playgroud)
参考这个循环。这是示例代码
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
func (a *AppStore) extractPublicKeyFromToken(tokenStr string) (*ecdsa.PublicKey, error) {
certStr, err := a.extractHeaderByIndex(tokenStr, 0)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certStr)
if err != nil {
return nil, err
}
switch pk := cert.PublicKey.(type) {
case *ecdsa.PublicKey:
return pk, nil
default:
return nil, errors.New("appstore public key must be of type ecdsa.PublicKey")
}
}
func (a *AppStore) extractHeaderByIndex(tokenStr string, index int) ([]byte, error) {
if index > 2 {
return nil, errors.New("invalid index")
}
tokenArr := strings.Split(tokenStr, ".")
headerByte, err := base64.RawStdEncoding.DecodeString(tokenArr[0])
if err != nil {
return nil, err
}
type Header struct {
Alg string `json:"alg"`
X5c []string `json:"x5c"`
}
var header Header
err = json.Unmarshal(headerByte, &header)
if err != nil {
return nil, err
}
certByte, err := base64.StdEncoding.DecodeString(header.X5c[index])
if err != nil {
return nil, err
}
return certByte, nil
}
// rootPEM is from `openssl x509 -inform der -in AppleRootCA-G3.cer -out apple_root.pem`
const rootPEM = `
-----BEGIN CERTIFICATE-----
MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS
QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN
MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS
....
-----END CERTIFICATE-----
`
func (a *AppStore) verifyCert(certByte []byte) error {
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
return errors.New("failed to parse root certificate")
}
cert, err := x509.ParseCertificate(certByte)
if err != nil {
return err
}
opts := x509.VerifyOptions{
Roots: roots,
}
if _, err := cert.Verify(opts); err != nil {
return err
}
return nil
}
func (a *AppStore) ExtractClaims(tokenStr string) (*JWSTransaction, error) {
tran := &JWSTransaction{}
rootCertStr, err := a.extractHeaderByIndex(tokenStr, 2)
if err != nil {
return nil, err
}
if err = a.verifyCert(rootCertStr); err != nil {
return nil, err
}
_, err = jwt.ParseWithClaims(tokenStr, tran, func(token *jwt.Token) (interface{}, error) {
return a.extractPublicKeyFromToken(tokenStr)
})
if err != nil {
return nil, err
}
return tran, nil
}
Run Code Online (Sandbox Code Playgroud)
添加验证中间证书逻辑如下
func (a *AppStore) verifyCert(certByte, intermediaCertStr []byte) error {
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
return errors.New("failed to parse root certificate")
}
interCert, err := x509.ParseCertificate(intermediaCertStr)
if err != nil {
return errors.New("failed to parse intermedia certificate")
}
intermedia := x509.NewCertPool()
intermedia.AddCert(interCert)
cert, err := x509.ParseCertificate(certByte)
if err != nil {
return err
}
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: intermedia,
}
chains, err := cert.Verify(opts)
if err != nil {
return err
}
for _, ch := range chains {
for _, c := range ch {
fmt.Printf("%+v, %s, %+v \n", c.AuthorityKeyId, c.Subject.Organization, c.ExtKeyUsage)
}
}
return nil
}
func (a *AppStore) ExtractClaims(tokenStr string) (*JWSTransaction, error) {
tran := &JWSTransaction{}
rootCertStr, err := a.extractHeaderByIndex(tokenStr, 2)
if err != nil {
return nil, err
}
intermediaCertStr, err := a.extractHeaderByIndex(tokenStr, 1)
if err != nil {
return nil, err
}
if err = a.verifyCert(rootCertStr, intermediaCertStr); err != nil {
return nil, err
}
_, err = jwt.ParseWithClaims(tokenStr, tran, func(token *jwt.Token) (interface{}, error) {
return a.extractPublicKeyFromToken(tokenStr)
})
if err != nil {
return nil, err
}
return tran, nil
}
Run Code Online (Sandbox Code Playgroud)
实现细节可以在这里找到:https://github.com/richzw/appstore
归档时间: |
|
查看次数: |
4855 次 |
最近记录: |