package rpc import ( "context" "crypto/ecdsa" "crypto/x509" "encoding/pem" "errors" "time" "github.com/Timothylock/go-signin-with-apple/apple" "github.com/golang-jwt/jwt/v5" ) type AppleClient struct { *apple.Client teamID string clientID string keyID string secret string } // GenerateClientSecret generates a new client secret, all fields are required, no http request, fully local logic func (a *AppleClient) GenerateClientSecret() (string, error) { token := &jwt.Token{ Header: map[string]interface{}{ "alg": "ES256", "kid": a.keyID, }, Claims: jwt.MapClaims{ "iss": a.teamID, "iat": time.Now().Unix(), // constraint: exp - iat <= 180 days "exp": time.Now().Add(24 * time.Hour).Unix(), "aud": "https://appleid.apple.com", "sub": a.clientID, }, Method: jwt.SigningMethodES256, } ecdsaKey, _ := authKeyFromBytes([]byte(a.secret)) return token.SignedString(ecdsaKey) } // authKeyFromBytes create private key for jwt sign func authKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) { var err error var block *pem.Block if block, _ = pem.Decode(key); block == nil { return nil, errors.New("token: AuthKey must be a valid .p8 PEM file") } var parsedKey interface{} if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } var pkey *ecdsa.PrivateKey var ok bool if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey") } return pkey, nil } func (a *AppleClient) VerifyAppToken(ctx context.Context, code string) (apple.ValidationResponse, error) { var resp apple.ValidationResponse clientSecret, err := a.GenerateClientSecret() if err != nil { return resp, err } reqBody := apple.AppValidationTokenRequest{ ClientID: a.clientID, ClientSecret: clientSecret, Code: code, } if err := a.Client.VerifyAppToken(ctx, reqBody, &resp); err != nil { return resp, err } return resp, nil }