-
Notifications
You must be signed in to change notification settings - Fork 0
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
Showing
48 changed files
with
3,968 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,12 @@ | ||
name: Test | ||
on: [push] | ||
jobs: | ||
build: | ||
name: Build | ||
runs-on: ubuntu-20.04 | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: actions/setup-go@v2 | ||
with: | ||
go-version: "1.17.6" # The Go version to download (if necessary) and use. | ||
- run: go test ./... |
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,22 @@ | ||
FROM golang:1.13-stretch AS builder | ||
|
||
WORKDIR /app | ||
COPY . /app | ||
|
||
# Test | ||
WORKDIR /app/ksm | ||
RUN go get -u -t ./... && go test -run '' | ||
|
||
WORKDIR /app/crypto | ||
RUN go get -u -t ./... && go test -run '' | ||
|
||
# Build | ||
WORKDIR /app | ||
RUN go build -o /app/ksm-server . && chmod 755 /app/ksm-server | ||
|
||
FROM debian:stretch-slim | ||
WORKDIR /app | ||
COPY --from=builder /app/ksm-server . | ||
|
||
EXPOSE 8080 | ||
ENTRYPOINT [ "/app/ksm-server" ] |
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,22 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 Eason Lin | ||
Copyright (c) 2022 Payton CHAN | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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,57 @@ | ||
# FairPlay Key Service Management | ||
|
||
![Golang Version](https://img.shields.io/github/go-mod/go-version/aviaki/fairplay-ksm) | ||
![Build Status](https://github.com/aviaki/fairplay-ksm/workflows/Test/badge.svg?branch=master) | ||
|
||
Apple FairPlay Streaming ([FPS](https://developer.apple.com/streaming/fps/)) securely delivers keys to Apple mobile devices, Apple TV, and Safari on OS X, which will enable playback of encrypted video content. | ||
|
||
## Usage | ||
|
||
### Start using it | ||
|
||
Download and install it: | ||
|
||
```bash | ||
go get github.com/aviaki/fairplay-ksm | ||
``` | ||
|
||
### Verify ckc | ||
|
||
Perform verification utility `verify_ckc` to test KSM implementation. | ||
|
||
``` | ||
testdata/verify_ckc -s testdata/FPS/spc1.bin -c testdata/FPS/ckc1.bin | ||
``` | ||
|
||
## FAQ | ||
|
||
### How to send sample SPC data? | ||
|
||
Our [example/basic.go](https://github.com/easonlin404/ksm/blob/master/example/basic.go) router has to accepted SPC parameter that is base64 encoding. And [testdata/spc1.bin](https://github.com/easonlin404/ksm/blob/master/testdata/FPS/spc1.bin) is binary file, so you have to encode to base64 string. | ||
|
||
After run as local server, you can test sample POST request using [httpie](https://httpie.org/) tool(spc is spc1.bin base64 encoding): | ||
|
||
``` | ||
http POST http://localhost:8080/fps/rest/getLicense spc=AAAAAQAAAABdFkTq7BH5gxR1QeRu6yd0kmZIuYYewEcbohdYhRw92jHJOx3WAapOrUQVogdZqrmm2J9VE4WFbnNXFynfLx1G0lwT2irXXQD9NBPr2WykfQKVXFaff6tA8af7I0FBZ6ZT6r3xrSg99eB+fPSqL7rGTx1GD9+aIe6yen9gcnhTpBTBxFDFJejatqPxPPpXFxpVYZgKpR8DqgS8okVWyEGnFzbHcQAACoBtptrk7kAJF1R7uqUnuIIbnGCBkysPzzNykTT196jxaQIcBNoA6WNzWnQ6iHnSyre6cWAnds8eotHTwW3OUbKCRJzsc6COFyKA05WQBCr+WvlVT5c86/Y+COQM0G/Kd6CalMu625dF5/Wl9HAhPJPYW6URGeRRock1LtIKTNk0MoWabImFunmmAKnGJv2KrXwLH65VJRaG8ss3fmgG9GMTi9+QSJGiZEWOygyyA7dg2MWtISPRv2m6XJx7VG//0yqbxwLdYapvj8E+eYwRRKIegsB02XAtcEJE0Owy4ZI2xEHAuNaoovgFVYJlnrYWl3STeKklbU/+sJCuKqFiTllasjZ0atyld6ZbYGFfF+6cViiYjOqsbwMKNLx9D1myeamj7WWQ2NyhGFW2W9nL3LEFpr3Sz1NCaCSr2yeXSaHEFUylsgPzYjgfmHJah81dOuJGb5Mln7gihxQLoF7p4+5B2rUnzO4Cz2Weyyq9mCgldZBgA4nfcPYRNWb6Y9ZS1m6vwuw5FBAtpCez28JbFnpFGQNKNG5onqgOvKVxMixUvuNEhHpD8N8Sf54waPzwVB9OjrMR+z1T3SHO+xki/izHMWKezU1wkbnmmad+7hrJALnJ3vW7JxkfbTVre5wgTZeM2pxPjqziRwoHy9MGzCBADXJdPINLZHO+llgDVEPzxm4xL0ErHuctcRxtGhMjDFkNrGG9xBIqMYPtT8g5JT3fkofQDYmWRbcHG+1FsD4SJtDAcZaxWHWnULQhtaHyvrLSQlgRvA4p0LKzLasz8sDrCjZdEBBPOixC23Gp28SbfdGkXTCAxaN3GUbkmEMOdjxiJ09YEPhagU/opVVGRVIkDdPqzfRSed7hOMmHmPCtMEWXyXyzNI/xp5dfXMKDPIe7HjymAIivRibdeboIVEprPMpdpcM5Xb7HHWjPicxlmNKtET8Yq/QXWSdnXLKygrFFtRgqAj4I5DAYg95lobqSQTlwZmZr2jcfe1yU3sl2klh9QGGacqr7JpttaNNHISUl3/QFiHrkQxtkNtkfIwDm38yTJYPF2i3m2FbJ1G1o60TP0jLRHR4Pu2BdNn5IA6nniDmZvVzLFfHWQK4ICfKgB/kN37tMf+W9xOuQG91RuSFCty0SV5SXpU3k6eF5zIn7+QQhQQeJT6GuxZTr8UFmxBBeOncUKaC0GRlY6h1kUyxblquAmvHPMh++X+q4ZL52EWRs4XyWAamx9k0hLPq9xJ2I4FTQ5oJsDXQwJyioa9EtTtldnmyoqVfUFfmHanx/YCtpwueLDInpD7gNHs5JA8vypJdQ+HXET+6PpdqU37PBH0lsyA14LIwURH0XF39gK7ICFFM6iS1XF8Ax6l1rqiu9gL8xfSUOtgCqQYOtuiLQK32Jt8FQXuoPphNl59J3nXXsOdL8ECMyWTkswhAE3mE152poEAIg/8LthcSMVcrapY1AOUU6UGw/fVnIOoKbrr+FvSAangWKYhVwTUbUZZJK+lmlK30bn5tT+zbIX+U91YMzsPtzhK5iHZtNN8EKAlQCj++qgEeVw9BD5//92hs0V4FrKZv40qHqwlT/w6R7v9Czujf2TfHC/3vuthWayBkvodJv2rJY52smRFSshTJG0v4yfi3hS4pBqpb7xggb7OibK1lAv55W0Iwod/ZZJVFXEYBQU2zsPGhdZdzlP+PbLXmOT2pnY6F86M8iXLdiRWAgU6LhK6srMuCzL/42+oJkt4DXnVV5xT819DbTEVbV167zUwene9jytruahxFti4MFZ6qC12Od04t8ActtdjPXx6yec1jNarZ1aKyhympQnrzdgP5fmTPV44oqQZaPHpWv1UlT7+RSEBGwENUioETw5fI4jf4+omU3uLu5VNPbxxLAWVk3CsEUl9Pl8WyWbyaqyrurBRQozWCL76pA3flZzqHJlbyIxotnTDd3JN4n7jEpoWClCZpgrWfDkz7ZdHqtgd8TWu1DmJgM1a12JqqBKQpRSrYFG5lW0MxCJ/BIb4/T6Ek9/niTjptg9frad7ek1uCr5izSXxCtM6PNPXnh5Q3N+H/+AfflPQAzXzhQQz41IRgBKS+bVO8ICUcyvBtpfasTZGzxKNi2FUVhWap9ygn3giAd8RLwc8xR4J6oyOb/7RpKBvhGwFlYEKd9+SQM6yl9EhWiiFD10cSPErfLhzlsWk6mnu6z25dmUjSlUbRu+lpVRROx09qZxctYSyUE79iftCmQge74awtm39Fp3t0XyBmgfFg7deQMfT/lp/BgXtAbESZrhEJM1D1u1q5qg03IJX+mFUqbYHC4RjZ3rq8l1SOEIy3FvOtRHNUJhGugo7mcQY4navyW9sp/skKh0vKdmIkveIPiQJ0a946ACQ0Gm8022fjENV3JNFO6rHLyuUv1/AgVMskZ3vUf7YzManapIET5bb0CbZ0KDfUIq8hmmSfEG1wv05+MvVNXtORsnF4efPVBbPsQyE1HsqiENLJl6/C3nDdPXm2XPe2Ih2Ty/sQZGd3wfpkQp39g409aZ1PGRKvtudxaI8y2OYL1VFfhr2ER+YtQP0dtAIowOgj5LXRRK9TdkTzmf2uk3H5vAF5+JjAW+uGUB/JLf3tWE3Hz+ldMu7eLaakkaIv1J8JDOlKEcwMnLCfTi/MNyMlz9pBlbZ+LmB7PlMH9NGmkHbZ/AsKqDPbDl6iKBOeGHOpg0HUZqdc3UeN0OzzW1VayYDF8NTPqMStODt2kudUhlf8M0mJ0PDGmwlgjfoa6A7wUYihd97wkAUONjZEijcSfBgqsivR2ieIPQELHakbAGBtv9pkBEe2nWcijwnbQAAJlPrxIP/E85HF02Ue67fWTmV7JG2+FffnTN235y7B2LLjHxwDqNJKS80aULernfHWwm3PIi8y4Fx7jjyXk2Fhw7Iffuu5bAZquCIx0TAeBu8ijp6sa9FXWhQVqQ1kJd//JgDyji3lBUJChEZg25DYFj+X66bIqlE6murY9gPLcxK2fAsBCDV/F95wqUoRKXskgaIqOsorhtJnckAP4vbeoWhg5jiXkWGkmz5QPttbyOgqdJg/Upb8Qfh8FBBwKvT4Z3/eNMSbzB8AA+rVz60oTona4BrsIVpyu2n76t+N7nSdRErPGNDkCNsfgec1tImYRtdkjxWaJTJCnnpjIZdgGSEjL57ddlVF6iTSCUPMJSdw0PRwSJMPDNhJQwElmOTtyWqFE8/mQkdvn3Wf2D300R7Xfs8kzIzUAsxrsI1A6P1UuLVmXEUy7LP9CRa0fPDbvkehZnUM4aT8KuoxTD7DvRBINq8sroGBjXkA03mNz1pdjvw2Az8raf/J+8xLUlnjMiq6xpguX2rvrnsiq4/Uf3bAt8VjQm64Hc5/empmlWUCvLUdbzk/X1hV7kmAoXE2JLt0NGwSGeknlX6Q/tugb7TWOJNIBOTsUrpXr+8WsEC+ikV9LmZ9kKRShJLkd07uGQzW2Qxb3qR71FVhwE+pNx73Q2+6A79aoAfpD7E0HmT8ig7Cau2wW8bNSOic7fIPct+vbG5OBq/CSEmqhkur3mEiXyR6mdCuLRtW+FszkgEEO7AJY2530mpY= | ||
``` | ||
|
||
Will response: | ||
|
||
``` | ||
HTTP/1.1 200 OK | ||
Content-Length: 902 | ||
Content-Type: application/json; charset=utf-8 | ||
Date: Thu, 08 Mar 2018 05:51:02 GMT | ||
|
||
{ | ||
"ckc": "AAAAAQAAAAAqQk6qsqGlLq37TNsWDOnKAAACgKtZF0mIK6FPslvHP3oQdTZgepM5j4nnI/d//Rj+T4eTBfmKA4aVPwUr4bOozAhPvpL9QhXKIsGo9sEmtz0j0okdbnTTWCYAfLTHGwYviWMsR0J/vFtlFnxJF2yyLhDN6wu4/Be8Q7+B2duXsW7k5r6TF/gaKYbgTChM/3b+znqizRy06gNJr4d5HUKfXZ9FAmBtcsTUGbdMWjvKYJV8zPiYoayTkarXN6bDGMY2ToDyWBXIethDf4kBgdpOcR+qvXLntbI9daaEbb2XEU/nw4v6Dks6/sMMizb0rtY3fA4dCxEYeVxlAOLlLGLxswtRkZmNj1YQD/2HUGmuLBo7Q+17MGvRP06d+LG3YaDWbLeRPH5CVCwaxvmj1mFSLvMZffLgPSYZUp9fXaEeUnh8aD/mvaaFJavLmB7+uk+YggX+9F1HtjpFpjelvX5InP2L3mSYOl/eNdjzJbFw7l9BnR5KWL1wWiOmJHEWBw3Y+Tx5bLs/1QMasyTG+wAGxiXN+XyQW8frVO7e1xeBnuJplSEx+DK4Z9ZtveQG2l56OMsyw3KxUPTjCzHF5n1z6CzCSw5R57gA23mQ3nScPkDCbzQyaquZfCO80f6JW9hMGVEIFPnZVtUR1IemNKXK3fx+fZ9GdIN0EaNCp9fzB5GDC2dxrs9zhux3SvR5nKbSqHCX2b0S2S7Vn0LWZ7U4MtXMB2R8rsza03JEswAZ7iGKgl1J5L7TaADKz5cZvBkTR5P6QgHiesoZ9HMxQHtCyU4pjjGlFU081UKmsxefuuddPjwK93M/Xhf4wjOjcSSG5a1+Gg9FTi4tETdnMXVl/PuWjX1WFYCIJ1ZyKc2gZD2iEbw=" | ||
} | ||
``` | ||
|
||
### How to verifying Key Security Module (KSM) Implementation? | ||
|
||
[https://developer.apple.com/library/archive/technotes/tn2454/_index.html#//apple_ref/doc/uid/DTS40017630-CH1-VERIFYING_KEY_SECURITY_MODULE__KSM__IMPLEMENTATION](https://developer.apple.com/library/archive/technotes/tn2454/_index.html#//apple_ref/doc/uid/DTS40017630-CH1-VERIFYING_KEY_SECURITY_MODULE__KSM__IMPLEMENTATION) | ||
|
||
### How to Debugging FairPlay Streaming? | ||
|
||
[https://developer.apple.com/library/archive/technotes/tn2454/_index.html](https://developer.apple.com/library/archive/technotes/tn2454/_index.html) |
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,170 @@ | ||
package main | ||
|
||
import ( | ||
"crypto/rsa" | ||
"encoding/base64" | ||
"encoding/hex" | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/aviaki/fairplay-ksm/cryptos" | ||
"github.com/aviaki/fairplay-ksm/ksm" | ||
"github.com/labstack/echo/v4" | ||
"github.com/labstack/echo/v4/middleware" | ||
) | ||
|
||
type SpcMessage struct { | ||
Spc string `json:"spc" binding:"required"` | ||
AssetID string `json:"assetID"` | ||
} | ||
|
||
type ErrorMessage struct { | ||
Status int `json:"status" binding:"required"` | ||
Message string `json:"message" binding:"required"` | ||
} | ||
|
||
type CkcResult struct { | ||
Ckc string `json:"ckc" binding:"required"` | ||
} | ||
|
||
var FairplayPrivateKey = ReadPriKey() | ||
var FairplayPublicCertification = ReadPublicCert() | ||
var FairplayASk = ReadASk() | ||
|
||
func main() { | ||
// Setting Echo | ||
e := echo.New() | ||
|
||
e.Use(middleware.Logger()) | ||
e.Use(middleware.Recover()) | ||
|
||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ | ||
AllowOrigins: []string{"*"}, // FIXME: Use your streaming domain. | ||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPost}, | ||
})) | ||
|
||
k := &ksm.Ksm{ | ||
Pub: ReadPublicCert(), | ||
Pri: ReadPriKey(), | ||
Rck: ksm.RandomContentKey{}, //NOTE: Don't use ramdom key in your application. | ||
Ask: ReadASk(), | ||
} | ||
|
||
e.GET("/", func(ctx echo.Context) error { | ||
return ctx.String(http.StatusOK, "OK") | ||
}) | ||
e.POST("/fps/license", func(ctx echo.Context) error { | ||
spcMessage := new(SpcMessage) | ||
var playback []byte | ||
var base64EncodingMethod string | ||
contentType := ctx.Request().Header.Get("Content-Type") | ||
|
||
if err := ctx.Bind(spcMessage); err != nil { | ||
errorMessage := &ErrorMessage{Status: 400, Message: err.Error()} | ||
fmt.Println(ctx.Request().Header) | ||
fmt.Println(ctx.Request().Body) | ||
return ctx.JSON(http.StatusBadRequest, errorMessage) | ||
} | ||
|
||
if strings.Contains(spcMessage.Spc, "-") || strings.Contains(spcMessage.Spc, "_") { | ||
base64EncodingMethod = "URL" | ||
decoded, err := base64.URLEncoding.DecodeString(spcMessage.Spc) | ||
if err != nil { | ||
panic(err) | ||
} | ||
playback = decoded | ||
} else if strings.Contains(spcMessage.Spc, " ") && strings.Contains(spcMessage.Spc, "/") { | ||
base64EncodingMethod = "STD" | ||
decoded, err := base64.StdEncoding.DecodeString(strings.ReplaceAll(spcMessage.Spc, " ", "+")) | ||
if err != nil { | ||
panic(err) | ||
} | ||
playback = decoded | ||
} else { | ||
base64EncodingMethod = "STD" | ||
decoded, err := base64.StdEncoding.DecodeString(spcMessage.Spc) | ||
if err != nil { | ||
panic(err) | ||
} | ||
playback = decoded | ||
} | ||
|
||
ckc, err := k.GenCKC(playback) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
var result string | ||
|
||
switch base64EncodingMethod { | ||
case "URL": | ||
result = base64.URLEncoding.EncodeToString(ckc) | ||
case "STD": | ||
result = base64.StdEncoding.EncodeToString(ckc) | ||
default: | ||
result = base64.StdEncoding.EncodeToString(ckc) | ||
} | ||
|
||
fmt.Println(result) | ||
|
||
switch contentType { | ||
case "application/json": | ||
return ctx.JSON(200, &CkcResult{Ckc: result}) | ||
case "application/x-www-form-urlencoded": | ||
return ctx.Blob(200, "application/x-www-form-urlencoded", []byte("<ckc>"+result+"</ckc>")) | ||
default: | ||
return ctx.Blob(200, "application/x-www-form-urlencoded", []byte("<ckc>"+result+"</ckc>")) | ||
} | ||
}) | ||
e.Logger.Fatal(e.Start(":8080")) | ||
} | ||
|
||
func envBase64Decode(s string) []byte { | ||
data, err := base64.StdEncoding.DecodeString(s) | ||
if err != nil { | ||
fmt.Println("Env Decode Error: ", err) | ||
return []byte{} | ||
} | ||
return data | ||
} | ||
|
||
func ReadPublicCert() *rsa.PublicKey { | ||
pubEnvVar := envBase64Decode(os.Getenv("FAIRPLAY_CERTIFICATION")) | ||
|
||
if len(pubEnvVar) == 0 { | ||
panic("Can't not find FAIRPLAY CERTIFICATION") | ||
} | ||
|
||
pubCert, err := cryptos.ParsePublicCertification(pubEnvVar) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return pubCert | ||
} | ||
|
||
func ReadPriKey() *rsa.PrivateKey { | ||
priEnvVar := envBase64Decode(os.Getenv("FAIRPLAY_PRIVATE_KEY")) | ||
|
||
if len(priEnvVar) == 0 { | ||
panic("Can't not find FAIRPLAY PRIVATE KEY") | ||
} | ||
priKey, err := cryptos.DecryptPriKey(priEnvVar, []byte("")) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return priKey | ||
} | ||
|
||
func ReadASk() []byte { | ||
askEnvVar := os.Getenv("FAIRPLAY_APPLICATION_SERVICE_KEY") | ||
if len(askEnvVar) == 0 { | ||
askEnvVar = "d87ce7a26081de2e8eb8acef3a6dc179" //Apple provided | ||
} | ||
ask, err := hex.DecodeString(askEnvVar) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return ask | ||
} |
Oops, something went wrong.