safetyNet.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. * Copyright (c) 2016, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package server
  20. import (
  21. "crypto/x509"
  22. "encoding/base64"
  23. "encoding/json"
  24. "fmt"
  25. "strings"
  26. )
  27. const (
  28. safetynetCN = "attest.android.com"
  29. // Cert of the root certificate authority (GeoTrust Global CA)
  30. // which signs the intermediate certificate from Google (GIAG2)
  31. geotrustCert = "-----BEGIN CERTIFICATE-----\nMIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\nYWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG\nEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg\nR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9\n9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq\nfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv\niS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU\n1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+\nbw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW\nMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA\nephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l\nuMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn\nZ57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS\ntQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF\nPseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un\nhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV\n5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==\n-----END CERTIFICATE-----\n"
  32. // base64 encoded sha256 hash of the license used to sign the android
  33. // client (.apk) https://psiphon.ca/en/faq.html#authentic-android
  34. //
  35. // keytool -printcert -file CERT.RSA
  36. // SHA256: 76:DB:EF:15:F6:77:26:D4:51:A1:23:59:B8:57:9C:0D:7A:9F:63:5D:52:6A:A3:74:24:DF:13:16:32:F1:78:10
  37. //
  38. // echo dtvvFfZ3JtRRoSNZuFecDXqfY11SaqN0JN8TFjLxeBA= | base64 -d | hexdump -e '32/1 "%02X " "\n"'
  39. // 76 DB EF 15 F6 77 26 D4 51 A1 23 59 B8 57 9C 0D 7A 9F 63 5D 52 6A A3 74 24 DF 13 16 32 F1 78 10
  40. psiphon3Base64CertHash = "dtvvFfZ3JtRRoSNZuFecDXqfY11SaqN0JN8TFjLxeBA="
  41. )
  42. var psiphonApkPackagenames = []string{"com.psiphon3", "com.psiphon3.subscription"}
  43. type X5C []string
  44. type jwt struct {
  45. status int
  46. payload string
  47. }
  48. func newJwt(token requestJSONObject) *jwt {
  49. status, ok := token["status"].(float64)
  50. if !ok {
  51. return nil
  52. }
  53. payload, ok := token["payload"].(string)
  54. if !ok {
  55. return nil
  56. }
  57. return &jwt{
  58. status: int(status),
  59. payload: payload,
  60. }
  61. }
  62. type jwtHeader struct {
  63. Algorithm string `json:"alg"`
  64. CertChain X5C `json:"x5c"`
  65. }
  66. func newJwtHeader(jsonBytes []byte) (jwtHeader, error) {
  67. var header jwtHeader
  68. err := json.Unmarshal(jsonBytes, &header)
  69. return header, err
  70. }
  71. type jwtBody struct {
  72. Nonce string `json:"nonce"`
  73. TimestampMs int `json:"timestampMs"`
  74. ApkPackageName string `json:"apkPackageName"`
  75. ApkDigestSha256 string `json:"apkDigestSha256"`
  76. CtsProfileMatch bool `json:"ctsProfileMatch"`
  77. Extension string `json:"extension"`
  78. ApkCertificateDigestSha256 []string `json:"apkCertificateDigestSha256"`
  79. }
  80. func newJwtBody(jsonBytes []byte) (jwtBody, error) {
  81. var body jwtBody
  82. err := json.Unmarshal(jsonBytes, &body)
  83. return body, err
  84. }
  85. // Verify x509 certificate chain
  86. func (x5c X5C) verifyCertChain() (*x509.Certificate, error) {
  87. if len(x5c) == 0 || len(x5c) > 10 {
  88. // OpenSSL's default maximum chain length is 10
  89. return nil, fmt.Errorf("Invalid certchain length of %d\n", len(x5c))
  90. }
  91. // Parse leaf certificate
  92. leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0])
  93. if err != nil {
  94. return nil, err
  95. }
  96. leafCert, err := x509.ParseCertificate(leafCertDer)
  97. if err != nil {
  98. return nil, err
  99. }
  100. // Verify CN
  101. if leafCert.Subject.CommonName != safetynetCN {
  102. return nil, err
  103. }
  104. // Parse and add intermediate certificates
  105. intermediates := x509.NewCertPool()
  106. for i := 1; i < len(x5c); i++ {
  107. intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i])
  108. if err != nil {
  109. return nil, err
  110. }
  111. intermediateCert, err := x509.ParseCertificate(intermediateCertDer)
  112. if err != nil {
  113. return nil, err
  114. }
  115. intermediates.AddCert(intermediateCert)
  116. }
  117. // Parse and verify root cert
  118. roots := x509.NewCertPool()
  119. ok := roots.AppendCertsFromPEM([]byte(geotrustCert))
  120. if !ok {
  121. return nil, fmt.Errorf("Failed to append GEOTRUST cert\n")
  122. }
  123. // Verify leaf certificate
  124. storeCtx := x509.VerifyOptions{
  125. Intermediates: intermediates,
  126. Roots: roots,
  127. KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
  128. }
  129. _, err = leafCert.Verify(storeCtx)
  130. if err != nil {
  131. return nil, err
  132. }
  133. return leafCert, nil
  134. }
  135. func (body *jwtBody) verifyJWTBody() bool {
  136. // Verify apk certificate digest
  137. if len(body.ApkCertificateDigestSha256) < 1 || body.ApkCertificateDigestSha256[0] != psiphon3Base64CertHash {
  138. return false
  139. }
  140. // Verify apk package name
  141. if !sliceContains(psiphonApkPackagenames, body.ApkPackageName) {
  142. return false
  143. }
  144. return true
  145. }
  146. func sliceContains(arr []string, str string) bool {
  147. for _, s := range arr {
  148. if s == str {
  149. return true
  150. }
  151. }
  152. return false
  153. }
  154. // Validate JWT produced by safteynet
  155. func verifySafetyNetPayload(params requestJSONObject) bool {
  156. jwt := newJwt(params)
  157. if jwt == nil {
  158. // Malformed JWT
  159. return false
  160. }
  161. // SafetyNet check failed
  162. if (*jwt).status != 0 {
  163. return false
  164. }
  165. // Split into base64 encoded header, body, signature
  166. jwtParts := strings.Split((*jwt).payload, ".")
  167. if len(jwtParts) != 3 {
  168. // Malformed payload
  169. return false
  170. }
  171. // Decode header, body, signature
  172. headerJson, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(jwtParts[0])
  173. if err != nil {
  174. return false
  175. }
  176. bodyJson, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(jwtParts[1])
  177. if err != nil {
  178. return false
  179. }
  180. signature, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(jwtParts[2])
  181. if err != nil {
  182. return false
  183. }
  184. // Extract header from json
  185. header, err := newJwtHeader(headerJson)
  186. if err != nil {
  187. return false
  188. }
  189. // Validate certchain in header
  190. leafCert, err := header.CertChain.verifyCertChain()
  191. if err != nil {
  192. // Invalid certchain
  193. return false
  194. }
  195. // Validate signature
  196. err = leafCert.CheckSignature(x509.SHA256WithRSA, []byte(jwtParts[0]+"."+jwtParts[1]), signature)
  197. if err != nil {
  198. return false
  199. }
  200. // Extract body from json
  201. body, err := newJwtBody(bodyJson)
  202. if err != nil {
  203. return false
  204. }
  205. // Validate jwt payload
  206. validPayload := body.verifyJWTBody()
  207. if !validPayload {
  208. return false
  209. }
  210. return true
  211. }