openai_live_attestation.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package service
  2. import (
  3. "context"
  4. "crypto/aes"
  5. "crypto/cipher"
  6. "crypto/rand"
  7. "crypto/sha256"
  8. "encoding/base64"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "strings"
  13. "github.com/Wei-Shaw/sub2api/internal/config"
  14. )
  15. const liveAttestationHeader = "x-oai-attestation"
  16. type liveAttestationAES struct {
  17. key [32]byte
  18. }
  19. func newLiveAttestationCipher(cfg *config.Config) SecretEncryptor {
  20. if cfg == nil || strings.TrimSpace(cfg.JWT.Secret) == "" {
  21. return nil
  22. }
  23. return &liveAttestationAES{
  24. key: sha256.Sum256([]byte("sub2api/live-attestation/v1\x00" + cfg.JWT.Secret)),
  25. }
  26. }
  27. func (c *liveAttestationAES) Encrypt(plaintext string) (string, error) {
  28. block, err := aes.NewCipher(c.key[:])
  29. if err != nil {
  30. return "", err
  31. }
  32. gcm, err := cipher.NewGCM(block)
  33. if err != nil {
  34. return "", err
  35. }
  36. nonce := make([]byte, gcm.NonceSize())
  37. if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
  38. return "", fmt.Errorf("generate Live attestation nonce: %w", err)
  39. }
  40. encrypted := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
  41. return base64.RawStdEncoding.EncodeToString(encrypted), nil
  42. }
  43. func (c *liveAttestationAES) Decrypt(ciphertext string) (string, error) {
  44. encrypted, err := base64.RawStdEncoding.DecodeString(ciphertext)
  45. if err != nil {
  46. return "", fmt.Errorf("decode Live attestation: %w", err)
  47. }
  48. block, err := aes.NewCipher(c.key[:])
  49. if err != nil {
  50. return "", err
  51. }
  52. gcm, err := cipher.NewGCM(block)
  53. if err != nil {
  54. return "", err
  55. }
  56. if len(encrypted) < gcm.NonceSize() {
  57. return "", errors.New("encrypted Live attestation is too short")
  58. }
  59. plaintext, err := gcm.Open(nil, encrypted[:gcm.NonceSize()], encrypted[gcm.NonceSize():], nil)
  60. if err != nil {
  61. return "", fmt.Errorf("decrypt Live attestation: %w", err)
  62. }
  63. return string(plaintext), nil
  64. }
  65. func (s *OpenAIGatewayService) prepareLiveAttestation(ctx context.Context) (string, string, error) {
  66. if s == nil || s.liveAttestation == nil {
  67. return "", "", &LiveAttestationUnavailableError{
  68. Reason: "Sub2API has no platform DeviceCheck provider",
  69. }
  70. }
  71. if s.liveAttestationCipher == nil {
  72. return "", "", &LiveAttestationUnavailableError{
  73. Reason: "JWT secret is required to protect the Sideband attestation",
  74. }
  75. }
  76. header, err := s.liveAttestation.Generate(ctx)
  77. if err != nil {
  78. return "", "", &LiveAttestationUnavailableError{Reason: err.Error()}
  79. }
  80. ciphertext, err := s.liveAttestationCipher.Encrypt(header)
  81. if err != nil {
  82. return "", "", &LiveAttestationUnavailableError{
  83. Reason: "failed to protect the generated DeviceCheck attestation",
  84. }
  85. }
  86. return header, ciphertext, nil
  87. }
  88. func (s *OpenAIGatewayService) decryptLiveAttestation(record *LiveCallRecord) (string, error) {
  89. if record == nil || strings.TrimSpace(record.AttestationCiphertext) == "" || s.liveAttestationCipher == nil {
  90. return "", &LiveAttestationUnavailableError{
  91. Reason: "the Live call has no reusable DeviceCheck attestation",
  92. }
  93. }
  94. header, err := s.liveAttestationCipher.Decrypt(record.AttestationCiphertext)
  95. if err != nil {
  96. return "", &LiveAttestationUnavailableError{
  97. Reason: "the Live call DeviceCheck attestation cannot be decrypted on this instance",
  98. }
  99. }
  100. return header, nil
  101. }