Skip to content

Commit f782b71

Browse files
authored
Merge pull request #14 from adventureisyou/flexible-certs
Feat: Increase certificate flexibility
2 parents ca4efc5 + 7660da2 commit f782b71

File tree

2 files changed

+54
-121
lines changed

2 files changed

+54
-121
lines changed

cert.go

+42-74
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
// openssl x509 -inform der -in AppleRootCA-G3.cer -out apple_root.pem
14-
const rootPEM = `
14+
const defaultRootPEM = `
1515
-----BEGIN CERTIFICATE-----
1616
MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS
1717
QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
@@ -30,106 +30,74 @@ at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM
3030
`
3131

3232
type Cert struct {
33+
rootCertPool *x509.CertPool
3334
}
3435

35-
func (c *Cert) extractCertByIndex(tokenStr string, index int) ([]byte, error) {
36-
if index > 2 {
37-
return nil, errors.New("invalid index")
36+
func newCert(rootCertPool *x509.CertPool) *Cert {
37+
if rootCertPool == nil {
38+
rootCertPool = x509.NewCertPool()
39+
rootCertPool.AppendCertsFromPEM([]byte(defaultRootPEM))
3840
}
41+
return &Cert{rootCertPool: rootCertPool}
42+
}
3943

40-
tokenArr := strings.Split(tokenStr, ".")
41-
headerByte, err := base64.RawStdEncoding.DecodeString(tokenArr[0])
44+
func (c *Cert) parseCert(certStr string) (*x509.Certificate, error) {
45+
certByte, err := base64.StdEncoding.DecodeString(certStr)
4246
if err != nil {
4347
return nil, err
4448
}
49+
return x509.ParseCertificate(certByte)
50+
}
4551

46-
type Header struct {
52+
func (c *Cert) extractPublicKeyFromToken(token string) (*ecdsa.PublicKey, error) {
53+
headerStr, _, _ := strings.Cut(token, ".")
54+
headerByte, err := base64.RawStdEncoding.DecodeString(headerStr)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
var header struct {
4760
Alg string `json:"alg"`
4861
X5c []string `json:"x5c"`
4962
}
50-
var header Header
5163
err = json.Unmarshal(headerByte, &header)
5264
if err != nil {
5365
return nil, err
5466
}
55-
56-
certByte, err := base64.StdEncoding.DecodeString(header.X5c[index])
57-
if err != nil {
58-
return nil, err
67+
if len(header.X5c) == 0 {
68+
return nil, errors.New("appstore found no certificates in x5c header field")
5969
}
6070

61-
return certByte, nil
62-
}
71+
opts := x509.VerifyOptions{Roots: c.rootCertPool}
6372

64-
func (c *Cert) verifyCert(rootCert, intermediaCert, leafCert *x509.Certificate) error {
65-
roots := x509.NewCertPool()
66-
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
67-
if !ok {
68-
return errors.New("failed to parse root certificate")
69-
}
70-
71-
intermedia := x509.NewCertPool()
72-
intermedia.AddCert(intermediaCert)
73-
74-
opts := x509.VerifyOptions{
75-
Roots: roots,
76-
Intermediates: intermedia,
77-
}
78-
_, err := rootCert.Verify(opts)
73+
leafCert, err := c.parseCert(header.X5c[0])
7974
if err != nil {
80-
return err
75+
return nil, fmt.Errorf("appstore failed to parse leaf certificate: %w", err)
8176
}
77+
header.X5c = header.X5c[1:]
8278

83-
_, err = leafCert.Verify(opts)
84-
if err != nil {
85-
return err
79+
pk, ok := leafCert.PublicKey.(*ecdsa.PublicKey)
80+
if !ok {
81+
return nil, errors.New("appstore public key must be of type ecdsa.PublicKey")
8682
}
8783

88-
// TODO: maybe we need the chains info later
89-
//for _, ch := range chains {
90-
// for _, c := range ch {
91-
// fmt.Printf("%+v, %s, %+v \n", c.AuthorityKeyId, c.Subject.Organization, c.ExtKeyUsage)
92-
// }
93-
//}
84+
// Build intermediate cert pool if there is more than 1 certificate in the header
85+
if len(header.X5c) > 0 {
86+
opts.Intermediates = x509.NewCertPool()
9487

95-
return nil
96-
}
97-
98-
func (c *Cert) extractPublicKeyFromToken(token string) (*ecdsa.PublicKey, error) {
99-
rootCertBytes, err := c.extractCertByIndex(token, 2)
100-
if err != nil {
101-
return nil, err
102-
}
103-
rootCert, err := x509.ParseCertificate(rootCertBytes)
104-
if err != nil {
105-
return nil, fmt.Errorf("appstore failed to parse root certificate")
106-
}
107-
108-
intermediaCertBytes, err := c.extractCertByIndex(token, 1)
109-
if err != nil {
110-
return nil, err
111-
}
112-
intermediaCert, err := x509.ParseCertificate(intermediaCertBytes)
113-
if err != nil {
114-
return nil, fmt.Errorf("appstore failed to parse intermediate certificate")
88+
for i, certStr := range header.X5c {
89+
cert, err := c.parseCert(certStr)
90+
if err != nil {
91+
return nil, fmt.Errorf("appstore failed to parse intermediate certificate %d: %w", i, err)
92+
}
93+
opts.Intermediates.AddCert(cert)
94+
}
11595
}
11696

117-
leafCertBytes, err := c.extractCertByIndex(token, 0)
118-
if err != nil {
119-
return nil, err
120-
}
121-
leafCert, err := x509.ParseCertificate(leafCertBytes)
97+
_, err = leafCert.Verify(opts)
12298
if err != nil {
123-
return nil, fmt.Errorf("appstore failed to parse leaf certificate")
124-
}
125-
if err = c.verifyCert(rootCert, intermediaCert, leafCert); err != nil {
126-
return nil, err
99+
return nil, fmt.Errorf("appstore failed to verify leaf certificate: %w", err)
127100
}
128101

129-
switch pk := leafCert.PublicKey.(type) {
130-
case *ecdsa.PublicKey:
131-
return pk, nil
132-
default:
133-
return nil, errors.New("appstore public key must be of type ecdsa.PublicKey")
134-
}
102+
return pk, nil
135103
}

store.go

+12-47
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package appstore
33
import (
44
"bytes"
55
"context"
6-
"crypto/ecdsa"
76
"crypto/x509"
87
"encoding/base64"
98
"encoding/json"
@@ -36,13 +35,14 @@ const (
3635
)
3736

3837
type StoreConfig struct {
39-
KeyContent []byte // Loads a .p8 certificate
40-
KeyID string // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
41-
BundleID string // Your app’s bundle ID
42-
Issuer string // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
43-
Sandbox bool // default is Production
44-
TokenIssuedAtFunc func() int64 // The token’s creation time func. Default is current timestamp.
45-
TokenExpiredAtFunc func() int64 // The token’s expiration time func. Default is one hour later.
38+
KeyContent []byte // Loads a .p8 certificate
39+
KeyID string // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
40+
BundleID string // Your app’s bundle ID
41+
Issuer string // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
42+
Sandbox bool // default is Production
43+
TokenIssuedAtFunc func() int64 // The token’s creation time func. Default is current timestamp.
44+
TokenExpiredAtFunc func() int64 // The token’s expiration time func. Default is one hour later.
45+
TrustedCertPool *x509.CertPool // The pool of trusted root certificates. Default is a pool containing only Apple Root CA - G3.
4646
}
4747

4848
type StoreClient struct {
@@ -63,7 +63,7 @@ func NewStoreClient(config *StoreConfig) *StoreClient {
6363

6464
client := &StoreClient{
6565
Token: token,
66-
cert: &Cert{},
66+
cert: newCert(config.TrustedCertPool),
6767
httpCli: &http.Client{
6868
Timeout: 30 * time.Second,
6969
},
@@ -83,7 +83,7 @@ func NewStoreClientWithHTTPClient(config *StoreConfig, httpClient HTTPClient) *S
8383

8484
client := &StoreClient{
8585
Token: token,
86-
cert: &Cert{},
86+
cert: newCert(config.TrustedCertPool),
8787
httpCli: httpClient,
8888
hostUrl: hostUrl,
8989
}
@@ -454,43 +454,8 @@ func (c *StoreClient) ParseJWSEncodeString(jwsEncode string) (interface{}, error
454454
}
455455

456456
func (c *StoreClient) parseJWS(jwsEncode string, claims jwt.Claims) error {
457-
rootCertBytes, err := c.cert.extractCertByIndex(jwsEncode, 2)
458-
if err != nil {
459-
return err
460-
}
461-
rootCert, err := x509.ParseCertificate(rootCertBytes)
462-
if err != nil {
463-
return fmt.Errorf("appstore failed to parse root certificate")
464-
}
465-
466-
intermediaCertBytes, err := c.cert.extractCertByIndex(jwsEncode, 1)
467-
if err != nil {
468-
return err
469-
}
470-
intermediaCert, err := x509.ParseCertificate(intermediaCertBytes)
471-
if err != nil {
472-
return fmt.Errorf("appstore failed to parse intermediate certificate")
473-
}
474-
475-
leafCertBytes, err := c.cert.extractCertByIndex(jwsEncode, 0)
476-
if err != nil {
477-
return err
478-
}
479-
leafCert, err := x509.ParseCertificate(leafCertBytes)
480-
if err != nil {
481-
return fmt.Errorf("appstore failed to parse leaf certificate")
482-
}
483-
if err = c.cert.verifyCert(rootCert, intermediaCert, leafCert); err != nil {
484-
return err
485-
}
486-
487-
pk, ok := leafCert.PublicKey.(*ecdsa.PublicKey)
488-
if !ok {
489-
return fmt.Errorf("appstore public key must be of type ecdsa.PublicKey")
490-
}
491-
492-
_, err = jwt.ParseWithClaims(jwsEncode, claims, func(token *jwt.Token) (interface{}, error) {
493-
return pk, nil
457+
_, err := jwt.ParseWithClaims(jwsEncode, claims, func(token *jwt.Token) (interface{}, error) {
458+
return c.cert.extractPublicKeyFromToken(jwsEncode)
494459
})
495460
return err
496461
}

0 commit comments

Comments
 (0)