Documentation
¶
Overview ¶
Package msgcrypto wraps the third-party SafeChat SDK so DWS can decrypt DingTalk messages that an organization has encrypted with its own key material, and encrypt outbound ones.
The SafeChat backend links a prebuilt C static library and therefore needs CGO. Supported Darwin, Linux, and Windows amd64/arm64 builds include it by default:
CGO_ENABLED=1 go build ./cmd
Every other build gets a stub whose constructor fails with ErrUnavailable, so callers must always handle that error rather than assume the capability exists. Use Available to branch before offering the feature to a user.
Key material is fetched from the vendor key server on demand, which requires a DingTalk 免登 authCode supplied through AuthCodeProvider. DWS does not mint that code itself; the caller injects a provider.
Index ¶
Constants ¶
const ( // DefaultSafeChatKeyServer is the verified SafeChat key server URL. DefaultSafeChatKeyServer = "https://server.safeding.com/DDSecureInter/getCorpSecureKey" // DefaultSafeChatRedirectHost is the host the vendor SDK reports to goProxy. DefaultSafeChatRedirectHost = "server.safeding.com" )
const BackendVersion = "safechat " + safechat.Version
BackendVersion identifies the compiled-in vendor SDK.
const DefaultAuthCodeTTL = 90 * time.Second
DefaultAuthCodeTTL is the unconsumed-cache window for a freshly minted vendor authCode. The portal issues codes with expiresIn=120s and they are one-shot, so the default stays under that server window. Prefer not wrapping PortalAuthCode in CachedAuthCode: mint in goProxy and discard after the key request.
const VendorSafeChat = "safechat"
VendorSafeChat is the first vendorAuthCode vendor.
Variables ¶
var ( // backend, or for a platform the vendor does not ship a static library. ErrUnavailable = errors.New("msgcrypto: SafeChat backend not built into this binary") // ErrAlreadyOpen means a Cipher is already open. The underlying C // library keeps global state, so only one may exist per process. ErrAlreadyOpen = errors.New("msgcrypto: a cipher is already open in this process") // ErrClosed is returned by a Cipher whose Close has already run. ErrClosed = errors.New("msgcrypto: cipher is closed") // ErrNoAuthCodeProvider means Config.AuthCode was nil. Without it the // backend cannot fetch or rotate key material. ErrNoAuthCodeProvider = errors.New("msgcrypto: config.AuthCode is required") // ErrEmptyPayload means an encrypt or decrypt call got no bytes. The // vendor SDK rejects empty input, so we reject it earlier with a // clearer message. ErrEmptyPayload = errors.New("msgcrypto: payload is empty") // ErrNoCorpID means the caller omitted the organization id, which // selects the key and therefore cannot be defaulted. ErrNoCorpID = errors.New("msgcrypto: corpID is required") // ErrNoKeyServer means Config.KeyServer was empty. The vendor C // library would otherwise pick the key-request destination. ErrNoKeyServer = errors.New("msgcrypto: config.KeyServer is required") // ErrInvalidKeyServer means Config.KeyServer is not a usable URL. ErrInvalidKeyServer = errors.New("msgcrypto: config.KeyServer is not a valid URL") // ErrKeyServerNotHTTPS means Config.KeyServer is not https. ErrKeyServerNotHTTPS = errors.New("msgcrypto: config.KeyServer must be an https URL") // ErrRedirectHostMismatch means the domain goProxy received does not // match Config.AllowedRedirectHost. The domain is never sent to portal. ErrRedirectHostMismatch = errors.New("msgcrypto: goProxy domain host does not match AllowedRedirectHost") )
Errors reported by this package. Callers are expected to test for ErrUnavailable explicitly, because it is the normal outcome on builds that disable CGO or target an unsupported platform.
var ErrNoAuthCode = errors.New("msgcrypto: auth code provider returned an empty code")
ErrNoAuthCode means the provider returned an empty code without an error.
Functions ¶
func Available ¶
func Available() bool
Available reports that this binary carries the SafeChat backend.
func DefaultKeystoreDir ¶
func DefaultKeystoreDir() string
DefaultKeystoreDir returns the default key cache directory, ~/.dws/safechat/keystore, honouring DWS_CONFIG_DIR like the rest of DWS.
Types ¶
type AuthCodeFunc ¶
AuthCodeFunc adapts a function to AuthCodeProvider.
type AuthCodeProvider ¶
AuthCodeProvider yields a DingTalk 免登 authCode for key-server authentication. DWS does not mint the code itself, so integrations inject an implementation. The backend calls this only from the vendor goProxy callback, never on every encrypt or decrypt.
Implementations must be safe for concurrent use; the backend may call this from a CGO callback while an encrypt or decrypt call is in flight.
func StaticAuthCode ¶
func StaticAuthCode(code string) AuthCodeProvider
StaticAuthCode returns a provider that always yields code. It is meant for tests and manual integration runs; a static code stops working once the server-side five-minute window closes.
type CachedAuthCode ¶
type CachedAuthCode struct {
// contains filtered or unexported fields
}
CachedAuthCode memoises an AuthCodeProvider for a TTL so a burst of key requests does not trigger one upstream call each.
func NewCachedAuthCode ¶
func NewCachedAuthCode(provider AuthCodeProvider, ttl time.Duration) *CachedAuthCode
NewCachedAuthCode wraps provider with a TTL cache. A ttl of zero or less selects DefaultAuthCodeTTL.
func (*CachedAuthCode) AuthCode ¶
func (c *CachedAuthCode) AuthCode(ctx context.Context) (string, error)
AuthCode returns the cached code when it is still fresh, otherwise fetches a new one. A failed fetch leaves no stale value behind.
func (*CachedAuthCode) Invalidate ¶
func (c *CachedAuthCode) Invalidate()
Invalidate drops the cached code so the next AuthCode call refetches. The backend calls this after the key server rejects a code.
type Cipher ¶
type Cipher interface {
// EncryptMessage encrypts plaintext for corpID/staffID and returns the
// vendor ciphertext envelope.
EncryptMessage(ctx context.Context, corpID, staffID string, plaintext []byte) ([]byte, error)
// DecryptMessage decrypts a vendor ciphertext envelope.
DecryptMessage(ctx context.Context, corpID, staffID string, ciphertext []byte) ([]byte, error)
// Close releases the backend and frees the process-wide slot so a later
// Open can succeed. Calling it twice is safe.
Close() error
}
Cipher encrypts and decrypts message payloads for one organization at a time. Implementations are safe for concurrent use.
type Config ¶
type Config struct {
// KeystoreDir is where fetched keys are cached. Defaults to
// DefaultKeystoreDir. It is created with 0700 if missing.
KeystoreDir string
// UserID is an opaque local identifier. The vendor SDK stores it but
// does not use it for key selection; leave empty to let the SDK
// generate one.
UserID string
// AuthCode supplies the DingTalk 免登 authCode used to authenticate key
// requests. Required. The backend calls it only from the vendor goProxy
// callback (cold keystore or key-version rotation), never on every
// encrypt/decrypt.
AuthCode AuthCodeProvider
// KeyServer is the HTTPS URL of the vendor key service. Required: it
// replaces the host the closed-source C library would otherwise pick.
KeyServer string
// AllowedRedirectHost, when set, is compared to the domain the C
// library passes into goProxy. A mismatch fails that key fetch. It is
// a local check only; the domain is never sent to portal.
AllowedRedirectHost string
// MaxRetry bounds retries while a key is still being fetched. Zero
// selects the vendor default.
MaxRetry int
// HTTPTimeout bounds each key request. Zero selects the vendor default.
HTTPTimeout time.Duration
// Debug enables backend logging through a redacting logger. Off by
// default because the vendor SDK logs the authCode and raw key-server
// responses at debug level.
Debug bool
// Logf receives already-redacted backend log lines when Debug is set.
// Nil discards them.
Logf func(format string, args ...any)
}
Config parameterises Open.
type CorpAuthCodeProvider ¶
type CorpAuthCodeProvider interface {
AuthCodeProvider
AuthCodeForCorp(ctx context.Context, corpID string) (string, error)
}
CorpAuthCodeProvider mints a code for a specific organization. PortalAuthCode implements this so goProxy can pass the C library's corpID. Domain and redirectURI are never part of this call.
type Identity ¶
Identity is the current DingTalk login organization and staff id used by message crypto. It contains no token or key material.
type PortalAuthCode ¶
type PortalAuthCode struct {
ConfigDir string
Vendor string
CLIVersion string
HTTPClient *http.Client
// contains filtered or unexported fields
}
PortalAuthCode mints a one-shot 免登 authCode from portal POST /oauth2/vendorAuthCode. It does not cache the code: goProxy spends it immediately. Do not wrap this in CachedAuthCode.
func NewPortalAuthCode ¶
func NewPortalAuthCode(configDir, cliVersion string) *PortalAuthCode
NewPortalAuthCode returns a provider that talks to portal with the current login. cliVersion is sent as x-dws-cli-version; leave empty only when the caller cannot know the CLI version.
func (*PortalAuthCode) AuthCode ¶
func (p *PortalAuthCode) AuthCode(ctx context.Context) (string, error)
AuthCode mints a code for the logged-in organization. Prefer AuthCodeForCorp when goProxy already has a corpID.
func (*PortalAuthCode) AuthCodeForCorp ¶
AuthCodeForCorp mints a one-shot code for corpID. The request body is only vendor + corpId.
type Session ¶
Session owns one opened SafeChat cipher for the current organization.
func OpenSession ¶
func OpenSession(ctx context.Context, opts SessionOptions) (*Session, error)
OpenSession opens a SafeChat cipher for the current login organization.