Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
)
|
||||
|
||||
const liveAttestationHeader = "x-oai-attestation"
|
||||
|
||||
type liveAttestationAES struct {
|
||||
key [32]byte
|
||||
}
|
||||
|
||||
func newLiveAttestationCipher(cfg *config.Config) SecretEncryptor {
|
||||
if cfg == nil || strings.TrimSpace(cfg.JWT.Secret) == "" {
|
||||
return nil
|
||||
}
|
||||
return &liveAttestationAES{
|
||||
key: sha256.Sum256([]byte("sub2api/live-attestation/v1\x00" + cfg.JWT.Secret)),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *liveAttestationAES) Encrypt(plaintext string) (string, error) {
|
||||
block, err := aes.NewCipher(c.key[:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", fmt.Errorf("generate Live attestation nonce: %w", err)
|
||||
}
|
||||
encrypted := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
|
||||
return base64.RawStdEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func (c *liveAttestationAES) Decrypt(ciphertext string) (string, error) {
|
||||
encrypted, err := base64.RawStdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("decode Live attestation: %w", err)
|
||||
}
|
||||
block, err := aes.NewCipher(c.key[:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(encrypted) < gcm.NonceSize() {
|
||||
return "", errors.New("encrypted Live attestation is too short")
|
||||
}
|
||||
plaintext, err := gcm.Open(nil, encrypted[:gcm.NonceSize()], encrypted[gcm.NonceSize():], nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("decrypt Live attestation: %w", err)
|
||||
}
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) prepareLiveAttestation(ctx context.Context) (string, string, error) {
|
||||
if s == nil || s.liveAttestation == nil {
|
||||
return "", "", &LiveAttestationUnavailableError{
|
||||
Reason: "Sub2API has no platform DeviceCheck provider",
|
||||
}
|
||||
}
|
||||
if s.liveAttestationCipher == nil {
|
||||
return "", "", &LiveAttestationUnavailableError{
|
||||
Reason: "JWT secret is required to protect the Sideband attestation",
|
||||
}
|
||||
}
|
||||
header, err := s.liveAttestation.Generate(ctx)
|
||||
if err != nil {
|
||||
return "", "", &LiveAttestationUnavailableError{Reason: err.Error()}
|
||||
}
|
||||
ciphertext, err := s.liveAttestationCipher.Encrypt(header)
|
||||
if err != nil {
|
||||
return "", "", &LiveAttestationUnavailableError{
|
||||
Reason: "failed to protect the generated DeviceCheck attestation",
|
||||
}
|
||||
}
|
||||
return header, ciphertext, nil
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) decryptLiveAttestation(record *LiveCallRecord) (string, error) {
|
||||
if record == nil || strings.TrimSpace(record.AttestationCiphertext) == "" || s.liveAttestationCipher == nil {
|
||||
return "", &LiveAttestationUnavailableError{
|
||||
Reason: "the Live call has no reusable DeviceCheck attestation",
|
||||
}
|
||||
}
|
||||
header, err := s.liveAttestationCipher.Decrypt(record.AttestationCiphertext)
|
||||
if err != nil {
|
||||
return "", &LiveAttestationUnavailableError{
|
||||
Reason: "the Live call DeviceCheck attestation cannot be decrypted on this instance",
|
||||
}
|
||||
}
|
||||
return header, nil
|
||||
}
|
||||
Reference in New Issue
Block a user