-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8029ebc
commit 6f1e140
Showing
17 changed files
with
925 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: actions | ||
on: [push, pull_request] | ||
jobs: | ||
lint: | ||
name: lint | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v3 | ||
with: | ||
check-latest: true | ||
go-version-file: go.mod | ||
- uses: golangci/golangci-lint-action@v3 | ||
test: | ||
name: test | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v3 | ||
with: | ||
check-latest: true | ||
go-version-file: go.mod | ||
- name: Test | ||
run: go test -v ./... | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# github.com/satorunooshie/asn | ||
[![Go Reference](https://pkg.go.dev/badge/github.com/satorunooshie/asn.svg)](https://pkg.go.dev/github.com/satorunooshie/asn) | ||
|
||
Library for validation of App Store Server Notifications V2. | ||
|
||
# Usage | ||
|
||
- Set by file(s). | ||
Use NewFileRootCAFetcher. | ||
|
||
- Set by url(s). | ||
Use NewHTTPRootCAFetcher. | ||
|
||
- Set by raw(s). | ||
Use NewRawRootCAFetcher. | ||
|
||
Recommended for use with the [jwx](https://github.com/lestrrat-go/jwx) created by [lestrrat-go](https://github.com/lestrrat-go). | ||
|
||
```go | ||
package asn | ||
|
||
import ( | ||
_ "embed" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/lestrrat-go/jwx/v2/jws" | ||
) | ||
|
||
//go:embed testdata/Root-CA.cer | ||
var rootCA []byte | ||
|
||
//go:embed testdata/request.txt | ||
var request []byte | ||
|
||
//go:embed testdata/raw.txt | ||
var raw []byte | ||
|
||
const ( | ||
emptyCerPath = "testdata/empty.cer" | ||
cerPath = "testdata/Root-CA.cer" | ||
cerURL = "https://www.apple.com/certificateauthority/AppleRootCA-G3.cer" | ||
) | ||
|
||
func path(filename string) string { | ||
return filepath.Join(filename) | ||
} | ||
|
||
type fakeAppleServer struct{} | ||
|
||
func (*fakeAppleServer) RoundTrip(r *http.Request) (*http.Response, error) { | ||
res := httptest.NewRecorder() | ||
if r.URL.Host == "www.apple.com" { | ||
_, _ = res.Write(rootCA) | ||
} | ||
if strings.Contains(r.URL.String(), "notfound") { | ||
res.WriteHeader(http.StatusNotFound) | ||
} | ||
return res.Result(), nil | ||
} | ||
|
||
func ExampleNewKeyProvider_byFile() { | ||
kp := NewKeyProvider(NewFileRootCAFetcher(cerPath)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byFiles() { | ||
emptyCerPath := path(emptyCerPath) | ||
optional := []string{cerPath} | ||
kp := NewKeyProvider(NewFileRootCAFetcher(emptyCerPath, optional...)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byUrl() { | ||
client := &http.Client{ | ||
Transport: &fakeAppleServer{}, | ||
} | ||
kp := NewKeyProvider(NewHTTPRootCAFetcher(client, cerURL)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byUrls() { | ||
const url = "http://localhost:8080/test.cer" | ||
client := &http.Client{ | ||
Transport: &fakeAppleServer{}, | ||
} | ||
optional := []string{cerURL} | ||
kp := NewKeyProvider(NewHTTPRootCAFetcher(client, url, optional...)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byRaw() { | ||
kp := NewKeyProvider(NewRawRootCAFetcher(raw)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byRaws() { | ||
kp := NewKeyProvider(NewRawRootCAFetcher([]byte(`test`), raw)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package asn | ||
|
||
import ( | ||
_ "embed" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/lestrrat-go/jwx/v2/jws" | ||
) | ||
|
||
//go:embed testdata/Root-CA.cer | ||
var rootCA []byte | ||
|
||
//go:embed testdata/request.txt | ||
var request []byte | ||
|
||
//go:embed testdata/raw.txt | ||
var raw []byte | ||
|
||
const ( | ||
emptyCerPath = "testdata/empty.cer" | ||
cerPath = "testdata/Root-CA.cer" | ||
cerURL = "https://www.apple.com/certificateauthority/AppleRootCA-G3.cer" | ||
) | ||
|
||
func path(filename string) string { | ||
return filepath.Join(filename) | ||
} | ||
|
||
type fakeAppleServer struct{} | ||
|
||
func (*fakeAppleServer) RoundTrip(r *http.Request) (*http.Response, error) { | ||
res := httptest.NewRecorder() | ||
if r.URL.Host == "www.apple.com" { | ||
_, _ = res.Write(rootCA) | ||
} | ||
if strings.Contains(r.URL.String(), "notfound") { | ||
res.WriteHeader(http.StatusNotFound) | ||
} | ||
return res.Result(), nil | ||
} | ||
|
||
func ExampleNewKeyProvider_byFile() { | ||
kp := NewKeyProvider(NewFileRootCAFetcher(cerPath)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byFiles() { | ||
emptyCerPath := path(emptyCerPath) | ||
optional := []string{cerPath} | ||
kp := NewKeyProvider(NewFileRootCAFetcher(emptyCerPath, optional...)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byUrl() { | ||
client := &http.Client{ | ||
Transport: &fakeAppleServer{}, | ||
} | ||
kp := NewKeyProvider(NewHTTPRootCAFetcher(client, cerURL)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byUrls() { | ||
const url = "http://localhost:8080/test.cer" | ||
client := &http.Client{ | ||
Transport: &fakeAppleServer{}, | ||
} | ||
optional := []string{cerURL} | ||
kp := NewKeyProvider(NewHTTPRootCAFetcher(client, url, optional...)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byRaw() { | ||
kp := NewKeyProvider(NewRawRootCAFetcher(raw)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} | ||
|
||
func ExampleNewKeyProvider_byRaws() { | ||
kp := NewKeyProvider(NewRawRootCAFetcher([]byte(`test`), raw)) | ||
opts := []jws.VerifyOption{jws.WithKeyProvider(kp)} | ||
verified, err := jws.Verify(request, opts...) | ||
fmt.Println(string(verified), err) | ||
// Output: | ||
// {"notificationType":"DID_CHANGE_RENEWAL_PREF","subtype":"DOWNGRADE","notificationUUID":"c92e001c-96d2-9ou5-q92p-32a5fy0d6g78","notificationVersion":"2.0","data":{"appAppleId":982253034,"bundleId":"hogehoge","bundleVersion":"269822910.1","environment":"Production","signedRenewalInfo":"...","signedTransactionInfo":"..."}} <nil> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package asn | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
) | ||
|
||
// RootCAFetcher is an interface that fetches Root CA. | ||
type RootCAFetcher interface { | ||
// Fetch returns Root CAs. | ||
Fetch(ctx context.Context) ([][]byte, error) | ||
} | ||
|
||
// HTTPRootCAFetcher implements RootCAFetcher via HTTP. | ||
type HTTPRootCAFetcher struct { | ||
client *http.Client | ||
urls []string | ||
} | ||
|
||
// NewHTTPRootCAFetcher returns a new HTTPRootCAFetcher. | ||
// At least one url that returns Root CA must be set. | ||
// Optional string argument is for setting multiple urls that returns Root CA. | ||
// if *http.Client is nil, http.DefaultClient is used. | ||
func NewHTTPRootCAFetcher(client *http.Client, url string, optional ...string) *HTTPRootCAFetcher { | ||
if client == nil { | ||
client = http.DefaultClient | ||
} | ||
return &HTTPRootCAFetcher{ | ||
client: client, | ||
urls: append([]string{url}, optional...), | ||
} | ||
} | ||
|
||
// Fetch returns the Root CAs that were successfully fetched via http and an error | ||
// if there's a problem with http access. | ||
func (f *HTTPRootCAFetcher) Fetch(ctx context.Context) ([][]byte, error) { | ||
rootCAs := make([][]byte, 0, len(f.urls)) | ||
for _, url := range f.urls { | ||
b, err := f.fetch(ctx, url) | ||
if err != nil { | ||
return rootCAs, err | ||
} | ||
dbuf := make([]byte, base64.StdEncoding.EncodedLen(len(b))) | ||
base64.StdEncoding.Encode(dbuf, b) | ||
rootCAs = append(rootCAs, dbuf) | ||
} | ||
return rootCAs, nil | ||
} | ||
|
||
func (f *HTTPRootCAFetcher) fetch(ctx context.Context, url string) ([]byte, error) { | ||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resp, err := f.client.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
_, _ = io.Copy(io.Discard, resp.Body) | ||
_ = resp.Body.Close() | ||
}() | ||
if resp.StatusCode != http.StatusOK { | ||
return nil, fmt.Errorf("url: %s, code: %d", url, resp.StatusCode) | ||
} | ||
b, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return b, nil | ||
} | ||
|
||
// FileRootCAFetcher implements RootCAFetcher via files. | ||
type FileRootCAFetcher struct { | ||
paths []string | ||
rootCA [][]byte | ||
} | ||
|
||
// NewFileRootCAFetcher returns a new FileRootCAFetcher. | ||
// Optional string argument is for setting multiple Root CA file paths. | ||
// At least one Root CA file path must be set. | ||
func NewFileRootCAFetcher(cerpath string, optional ...string) *FileRootCAFetcher { | ||
return &FileRootCAFetcher{paths: append([]string{cerpath}, optional...)} | ||
} | ||
|
||
// Fetch returns the Root CAs from files set by NewFileRootCAFetcher. | ||
// If there's cache, returns cache. | ||
func (f *FileRootCAFetcher) Fetch(context.Context) ([][]byte, error) { | ||
if f.rootCA != nil { | ||
return f.rootCA, nil | ||
} | ||
for _, path := range f.paths { | ||
b, err := os.ReadFile(path) | ||
if err != nil { | ||
return f.rootCA, err | ||
} | ||
dbuf := make([]byte, base64.StdEncoding.EncodedLen(len(b))) | ||
base64.StdEncoding.Encode(dbuf, b) | ||
f.rootCA = append(f.rootCA, dbuf) | ||
} | ||
return f.rootCA, nil | ||
} | ||
|
||
// RawRootCAFetcher implements RootCAFetcher via raw bytes. | ||
type RawRootCAFetcher struct { | ||
rootCA [][]byte | ||
} | ||
|
||
// NewRawRootCAFetcher returns a new RawRootCAFetcher. | ||
// At least one certificate must be set as a byte string. | ||
// Optional byte slice argument is for setting multiple Root CAs. | ||
func NewRawRootCAFetcher(rootCA []byte, optional ...[]byte) *RawRootCAFetcher { | ||
return &RawRootCAFetcher{rootCA: append([][]byte{rootCA}, optional...)} | ||
} | ||
|
||
// Fetch returns the Root CAs set by NewRawRootCAFetcher. | ||
func (f *RawRootCAFetcher) Fetch(context.Context) ([][]byte, error) { | ||
return f.rootCA, nil | ||
} |
Oops, something went wrong.