safetyNet.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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. "errors"
  25. "fmt"
  26. "reflect"
  27. "strconv"
  28. "strings"
  29. "time"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  31. )
  32. const (
  33. // Cert of the root certificate authority (GeoTrust Global CA)
  34. // which signs the intermediate certificate from Google (GIAG2)
  35. 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"
  36. maxLogFieldSize = 64
  37. maxLogPayloadSize = 6144
  38. // base64 encoded sha256 hash of the license used to sign the android
  39. // client (.apk) https://psiphon.ca/en/faq.html#authentic-android
  40. //
  41. // keytool -printcert -file CERT.RSA
  42. // 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
  43. //
  44. // echo dtvvFfZ3JtRRoSNZuFecDXqfY11SaqN0JN8TFjLxeBA= | base64 -d | hexdump -e '32/1 "%02X " "\n"'
  45. // 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
  46. psiphon3Base64CertHash = "dtvvFfZ3JtRRoSNZuFecDXqfY11SaqN0JN8TFjLxeBA="
  47. safetynetCN = "attest.android.com"
  48. )
  49. var psiphonApkPackagenames = []string{"com.psiphon3", "com.psiphon3.subscription"}
  50. type X5C []string
  51. type jwt struct {
  52. status int
  53. payload string
  54. }
  55. func newJwt(token requestJSONObject) (jwt, error) {
  56. jwtObj := jwt{}
  57. if token["status"] == nil {
  58. return jwtObj, errors.New("Absent JWT status field")
  59. }
  60. status, ok := token["status"].(float64)
  61. if !ok {
  62. return jwtObj, errors.New("Malformed JWT status field. Expected float64 got " + reflect.TypeOf(token["status"]).String())
  63. }
  64. if token["payload"] == nil {
  65. return jwtObj, errors.New("Absent JWT payload field")
  66. }
  67. payload, ok := token["payload"].(string)
  68. if !ok {
  69. return jwtObj, errors.New("Malformed JWT payload field. Expected string got " + reflect.TypeOf(token["payload"]).String())
  70. }
  71. if len(payload) > maxLogPayloadSize {
  72. return jwtObj, errors.New("JWT of length " + strconv.Itoa(len(payload)) + " exceeds maximum expected length of " + strconv.Itoa(maxLogPayloadSize))
  73. }
  74. jwtObj.status = int(status)
  75. jwtObj.payload = payload
  76. return jwtObj, nil
  77. }
  78. type jwtHeader struct {
  79. Algorithm string `json:"alg"`
  80. CertChain X5C `json:"x5c"`
  81. }
  82. func newJwtHeader(jsonBytes []byte) (jwtHeader, error) {
  83. var header jwtHeader
  84. err := json.Unmarshal(jsonBytes, &header)
  85. return header, err
  86. }
  87. type jwtBody struct {
  88. // Pointers are used because these fields may not
  89. // exist and the default values assigned when
  90. // unmarshalling into the corresponding non-pointer
  91. // struct would cause non-existing fields to be logged
  92. // in a manner that would make it impossible to distinguish
  93. // between a non-existing field and field of default value
  94. BasicIntegrity *bool `json:"basicIntegrity"`
  95. CtsProfileMatch *bool `json:"ctsProfileMatch"`
  96. TimestampMs *int `json:"timestampMs"`
  97. ApkDigestSha256 *string `json:"apkDigestSha256"`
  98. ApkPackageName *string `json:"apkPackageName"`
  99. Error *string `json:"error"`
  100. Extension *string `json:"extension"`
  101. Nonce *string `json:"nonce"`
  102. ApkCertificateDigestSha256 *[]string `json:"apkCertificateDigestSha256"`
  103. }
  104. func newJwtBody(jsonBytes []byte) (jwtBody, error) {
  105. var body jwtBody
  106. err := json.Unmarshal(jsonBytes, &body)
  107. return body, err
  108. }
  109. // Verify x509 certificate chain
  110. func (x5c X5C) verifyCertChain() (leaf *x509.Certificate, validCN bool, err error) {
  111. if len(x5c) == 0 || len(x5c) > 10 {
  112. // OpenSSL's default maximum chain length is 10
  113. return nil, false, fmt.Errorf("Invalid certchain length of %d\n", len(x5c))
  114. }
  115. // Parse leaf certificate
  116. leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0])
  117. if err != nil {
  118. return nil, false, err
  119. }
  120. leafCert, err := x509.ParseCertificate(leafCertDer)
  121. if err != nil {
  122. return nil, false, err
  123. }
  124. // Verify CN
  125. if leafCert.Subject.CommonName == safetynetCN {
  126. validCN = true
  127. }
  128. // Parse and add intermediate certificates
  129. intermediates := x509.NewCertPool()
  130. for i := 1; i < len(x5c); i++ {
  131. intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i])
  132. if err != nil {
  133. return leafCert, false, err
  134. }
  135. intermediateCert, err := x509.ParseCertificate(intermediateCertDer)
  136. if err != nil {
  137. return leafCert, false, err
  138. }
  139. intermediates.AddCert(intermediateCert)
  140. }
  141. // Parse and verify root cert
  142. roots := x509.NewCertPool()
  143. ok := roots.AppendCertsFromPEM([]byte(geotrustCert))
  144. if !ok {
  145. return leafCert, false, fmt.Errorf("Failed to append GEOTRUST cert\n")
  146. }
  147. // Verify leaf certificate
  148. storeCtx := x509.VerifyOptions{
  149. Intermediates: intermediates,
  150. Roots: roots,
  151. KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
  152. }
  153. _, err = leafCert.Verify(storeCtx)
  154. if err != nil {
  155. return leafCert, false, err
  156. }
  157. return leafCert, validCN, nil
  158. }
  159. func (body *jwtBody) verifyJWTBody() (validApkCert, validApkPackageName bool) {
  160. // Verify apk certificate digest
  161. if body.ApkCertificateDigestSha256 != nil {
  162. if len(*body.ApkCertificateDigestSha256) > 0 && (*body.ApkCertificateDigestSha256)[0] == psiphon3Base64CertHash {
  163. validApkCert = true
  164. }
  165. }
  166. // Verify apk package name
  167. if body.ApkPackageName != nil && common.Contains(psiphonApkPackagenames, *body.ApkPackageName) {
  168. validApkPackageName = true
  169. }
  170. return
  171. }
  172. // Form log fields for debugging
  173. func errorLogFields(err error, params requestJSONObject) LogFields {
  174. logFields := LogFields{
  175. // Must sanitize string. JSON unmarshalling exceptions
  176. // include the value of the field which failed to unmarshal.
  177. "error_message": sanitizeJwtString(err.Error()),
  178. }
  179. // Sanitize payload for logging
  180. payload, ok := params["payload"].(string)
  181. // Only log payload if it exists
  182. if ok {
  183. if len(payload) > maxLogPayloadSize {
  184. // Truncate if payload exceedingly long
  185. payload = payload[:maxLogPayloadSize]
  186. payload += ".."
  187. }
  188. logFields["payload"] = payload
  189. }
  190. return logFields
  191. }
  192. // Convert error to string for logging
  193. func getError(err error) string {
  194. if err == nil {
  195. return ""
  196. }
  197. return err.Error()
  198. }
  199. // Sanitize client / safetynet provided strings for logging
  200. func sanitizeJwtString(s string) string {
  201. if len(s) > maxLogFieldSize {
  202. return s[:maxLogFieldSize]
  203. }
  204. return s
  205. }
  206. // Add log field if it exists (see comment accompanying jwtBody struct)
  207. func (l LogFields) addJwtField(field string, input interface{}) {
  208. switch val := input.(type) {
  209. case *bool:
  210. if val != nil {
  211. l[field] = *val
  212. }
  213. case *int:
  214. if val != nil {
  215. if field == "verification_timestamp" {
  216. l[field] = time.Unix(0, int64(*val)*1e6).UTC().Format(time.RFC3339)
  217. } else {
  218. l[field] = *val
  219. }
  220. }
  221. case *string:
  222. if val != nil {
  223. l[field] = sanitizeJwtString(*val)
  224. }
  225. case *[]string:
  226. // Only concerned with ApkCertificateDigestSha256[0] for now
  227. if val != nil && len(*val) > 0 {
  228. l[field] = sanitizeJwtString((*val)[0])
  229. }
  230. default:
  231. // Do nothing
  232. }
  233. }
  234. // Validate JWT produced by safetynet
  235. func verifySafetyNetPayload(params requestJSONObject) (bool, LogFields) {
  236. jwt, err := newJwt(params)
  237. if err != nil {
  238. // Malformed JWT
  239. return false, errorLogFields(err, params)
  240. }
  241. statusStrings := map[int]string{
  242. 0: "API_REQUEST_OK",
  243. 1: "API_REQUEST_FAILED",
  244. 2: "API_CONNECT_FAILED",
  245. }
  246. statusString, ok := statusStrings[jwt.status]
  247. if !ok {
  248. statusString = "Expected status in range 0-2. Got " + strconv.Itoa(jwt.status)
  249. }
  250. // SafetyNet check failed
  251. if jwt.status != 0 {
  252. return false, errorLogFields(errors.New(statusString), params)
  253. }
  254. // Split into base64 encoded header, body, signature
  255. jwtParts := strings.Split(jwt.payload, ".")
  256. if len(jwtParts) != 3 {
  257. // Malformed payload
  258. return false, errorLogFields(errors.New("JWT does not have 3 parts"), params)
  259. }
  260. // Decode header, body, signature
  261. headerJson, err := base64.RawURLEncoding.DecodeString(jwtParts[0])
  262. if err != nil {
  263. return false, errorLogFields(err, params)
  264. }
  265. bodyJson, err := base64.RawURLEncoding.DecodeString(jwtParts[1])
  266. if err != nil {
  267. return false, errorLogFields(err, params)
  268. }
  269. signature, err := base64.RawURLEncoding.DecodeString(jwtParts[2])
  270. if err != nil {
  271. return false, errorLogFields(err, params)
  272. }
  273. // Extract header from json
  274. header, err := newJwtHeader(headerJson)
  275. if err != nil {
  276. return false, errorLogFields(err, params)
  277. }
  278. // Verify certchain in header
  279. leafCert, validCN, certChainErrors := header.CertChain.verifyCertChain()
  280. var signatureErrors error
  281. if leafCert == nil {
  282. signatureErrors = errors.New("Failed to parse leaf certificate")
  283. } else {
  284. // Verify signature over header and body
  285. signatureErrors = leafCert.CheckSignature(x509.SHA256WithRSA, []byte(jwtParts[0]+"."+jwtParts[1]), signature)
  286. }
  287. // Extract body from json
  288. body, err := newJwtBody(bodyJson)
  289. if err != nil {
  290. return false, errorLogFields(err, params)
  291. }
  292. // Validate jwt payload
  293. validApkCert, validApkPackageName := body.verifyJWTBody()
  294. validCertChain := certChainErrors == nil
  295. validSignature := signatureErrors == nil
  296. verified := validCN && validApkCert && validApkPackageName && validCertChain && validSignature
  297. // Add server generated fields for logging
  298. logFields := LogFields{
  299. "certchain_errors": getError(certChainErrors),
  300. "signature_errors": getError(signatureErrors),
  301. "status": strconv.Itoa(jwt.status),
  302. "status_string": statusString,
  303. "valid_cn": validCN,
  304. "valid_apk_cert": validApkCert,
  305. "valid_apk_packagename": validApkPackageName,
  306. "valid_certchain": validCertChain,
  307. "valid_signature": validSignature,
  308. "verified": verified,
  309. }
  310. // Add client / safetynet generated fields for logging
  311. logFields.addJwtField("apk_certificate_digest_sha256", body.ApkCertificateDigestSha256)
  312. logFields.addJwtField("apk_digest_sha256", body.ApkDigestSha256)
  313. logFields.addJwtField("apk_package_name", body.ApkPackageName)
  314. logFields.addJwtField("basic_integrity", body.BasicIntegrity)
  315. logFields.addJwtField("cts_profile_match", body.CtsProfileMatch)
  316. logFields.addJwtField("error", body.Error)
  317. logFields.addJwtField("extension", body.Extension)
  318. logFields.addJwtField("nonce", body.Nonce)
  319. logFields.addJwtField("verification_timestamp", body.TimestampMs)
  320. return verified, logFields
  321. }