// Package gpgme provides a Go wrapper for the GPGME library package gpgme // #cgo LDFLAGS: -lgpgme -lassuan -lgpg-error // #cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64 // #include // #include // #include "go_gpgme.h" import "C" import ( "fmt" "io" "os" "runtime" "time" "unsafe" ) var Version string func init() { Version = C.GoString(C.gpgme_check_version(nil)) } // Callback is the function that is called when a passphrase is required type Callback func(uidHint string, prevWasBad bool, f *os.File) error //export gogpgme_passfunc func gogpgme_passfunc(hook unsafe.Pointer, uid_hint, passphrase_info *C.char, prev_was_bad, fd C.int) C.gpgme_error_t { c := callbackLookup(uintptr(hook)).(*Context) go_uid_hint := C.GoString(uid_hint) f := os.NewFile(uintptr(fd), go_uid_hint) defer f.Close() err := c.callback(go_uid_hint, prev_was_bad != 0, f) if err != nil { return C.GPG_ERR_CANCELED } return 0 } type Protocol int const ( ProtocolOpenPGP Protocol = C.GPGME_PROTOCOL_OpenPGP ProtocolCMS Protocol = C.GPGME_PROTOCOL_CMS ProtocolGPGConf Protocol = C.GPGME_PROTOCOL_GPGCONF ProtocolAssuan Protocol = C.GPGME_PROTOCOL_ASSUAN ProtocolG13 Protocol = C.GPGME_PROTOCOL_G13 ProtocolUIServer Protocol = C.GPGME_PROTOCOL_UISERVER // ProtocolSpawn Protocol = C.GPGME_PROTOCOL_SPAWN // Unavailable in 1.4.3 ProtocolDefault Protocol = C.GPGME_PROTOCOL_DEFAULT ProtocolUnknown Protocol = C.GPGME_PROTOCOL_UNKNOWN ) type PinEntryMode int // const ( // Unavailable in 1.3.2 // PinEntryDefault PinEntryMode = C.GPGME_PINENTRY_MODE_DEFAULT // PinEntryAsk PinEntryMode = C.GPGME_PINENTRY_MODE_ASK // PinEntryCancel PinEntryMode = C.GPGME_PINENTRY_MODE_CANCEL // PinEntryError PinEntryMode = C.GPGME_PINENTRY_MODE_ERROR // PinEntryLoopback PinEntryMode = C.GPGME_PINENTRY_MODE_LOOPBACK // ) type EncryptFlag uint const ( EncryptAlwaysTrust EncryptFlag = C.GPGME_ENCRYPT_ALWAYS_TRUST EncryptNoEncryptTo EncryptFlag = C.GPGME_ENCRYPT_NO_ENCRYPT_TO EncryptPrepare EncryptFlag = C.GPGME_ENCRYPT_PREPARE EncryptExceptSign EncryptFlag = C.GPGME_ENCRYPT_EXPECT_SIGN // EncryptNoCompress EncryptFlag = C.GPGME_ENCRYPT_NO_COMPRESS // Unavailable in 1.4.3 ) type HashAlgo int // const values for HashAlgo values should be added when necessary. type KeyListMode uint const ( KeyListModeLocal KeyListMode = C.GPGME_KEYLIST_MODE_LOCAL KeyListModeExtern KeyListMode = C.GPGME_KEYLIST_MODE_EXTERN KeyListModeSigs KeyListMode = C.GPGME_KEYLIST_MODE_SIGS KeyListModeSigNotations KeyListMode = C.GPGME_KEYLIST_MODE_SIG_NOTATIONS // KeyListModeWithSecret KeyListMode = C.GPGME_KEYLIST_MODE_WITH_SECRET // Unavailable in 1.4.3 KeyListModeEphemeral KeyListMode = C.GPGME_KEYLIST_MODE_EPHEMERAL KeyListModeModeValidate KeyListMode = C.GPGME_KEYLIST_MODE_VALIDATE ) type PubkeyAlgo int // const values for PubkeyAlgo values should be added when necessary. type SigMode int const ( SigModeNormal SigMode = C.GPGME_SIG_MODE_NORMAL SigModeDetach SigMode = C.GPGME_SIG_MODE_DETACH SigModeClear SigMode = C.GPGME_SIG_MODE_CLEAR ) type SigSum int const ( SigSumValid SigSum = C.GPGME_SIGSUM_VALID SigSumGreen SigSum = C.GPGME_SIGSUM_GREEN SigSumRed SigSum = C.GPGME_SIGSUM_RED SigSumKeyRevoked SigSum = C.GPGME_SIGSUM_KEY_REVOKED SigSumKeyExpired SigSum = C.GPGME_SIGSUM_KEY_EXPIRED SigSumSigExpired SigSum = C.GPGME_SIGSUM_SIG_EXPIRED SigSumKeyMissing SigSum = C.GPGME_SIGSUM_KEY_MISSING SigSumCRLMissing SigSum = C.GPGME_SIGSUM_CRL_MISSING SigSumCRLTooOld SigSum = C.GPGME_SIGSUM_CRL_TOO_OLD SigSumBadPolicy SigSum = C.GPGME_SIGSUM_BAD_POLICY SigSumSysError SigSum = C.GPGME_SIGSUM_SYS_ERROR ) type Validity int const ( ValidityUnknown Validity = C.GPGME_VALIDITY_UNKNOWN ValidityUndefined Validity = C.GPGME_VALIDITY_UNDEFINED ValidityNever Validity = C.GPGME_VALIDITY_NEVER ValidityMarginal Validity = C.GPGME_VALIDITY_MARGINAL ValidityFull Validity = C.GPGME_VALIDITY_FULL ValidityUltimate Validity = C.GPGME_VALIDITY_ULTIMATE ) type ErrorCode int const ( ErrorNoError ErrorCode = C.GPG_ERR_NO_ERROR ErrorEOF ErrorCode = C.GPG_ERR_EOF ) // Error is a wrapper for GPGME errors type Error struct { err C.gpgme_error_t } func (e Error) Code() ErrorCode { return ErrorCode(C.gpgme_err_code(e.err)) } func (e Error) Error() string { return C.GoString(C.gpgme_strerror(e.err)) } func handleError(err C.gpgme_error_t) error { e := Error{err: err} if e.Code() == ErrorNoError { return nil } return e } func cbool(b bool) C.int { if b { return 1 } return 0 } func EngineCheckVersion(p Protocol) error { return handleError(C.gpgme_engine_check_version(C.gpgme_protocol_t(p))) } type EngineInfo struct { info C.gpgme_engine_info_t } func (e *EngineInfo) Next() *EngineInfo { if e.info.next == nil { return nil } return &EngineInfo{info: e.info.next} } func (e *EngineInfo) Protocol() Protocol { return Protocol(e.info.protocol) } func (e *EngineInfo) FileName() string { return C.GoString(e.info.file_name) } func (e *EngineInfo) Version() string { return C.GoString(e.info.version) } func (e *EngineInfo) RequiredVersion() string { return C.GoString(e.info.req_version) } func (e *EngineInfo) HomeDir() string { return C.GoString(e.info.home_dir) } func GetEngineInfo() (*EngineInfo, error) { info := &EngineInfo{} return info, handleError(C.gpgme_get_engine_info(&info.info)) } func SetEngineInfo(proto Protocol, fileName, homeDir string) error { var cfn, chome *C.char if fileName != "" { cfn = C.CString(fileName) defer C.free(unsafe.Pointer(cfn)) } if homeDir != "" { chome = C.CString(homeDir) defer C.free(unsafe.Pointer(chome)) } return handleError(C.gpgme_set_engine_info(C.gpgme_protocol_t(proto), cfn, chome)) } func FindKeys(pattern string, secretOnly bool) ([]*Key, error) { var keys []*Key ctx, err := New() if err != nil { return keys, err } defer ctx.Release() if err := ctx.KeyListStart(pattern, secretOnly); err != nil { return keys, err } defer ctx.KeyListEnd() for ctx.KeyListNext() { keys = append(keys, ctx.Key) } if ctx.KeyError != nil { return keys, ctx.KeyError } return keys, nil } func Decrypt(r io.Reader) (*Data, error) { ctx, err := New() if err != nil { return nil, err } defer ctx.Release() cipher, err := NewDataReader(r) if err != nil { return nil, err } defer cipher.Close() plain, err := NewData() if err != nil { return nil, err } err = ctx.Decrypt(cipher, plain) plain.Seek(0, SeekSet) return plain, err } type Context struct { Key *Key KeyError error callback Callback cbc uintptr ctx C.gpgme_ctx_t } func New() (*Context, error) { c := &Context{} err := C.gpgme_new(&c.ctx) runtime.SetFinalizer(c, (*Context).Release) return c, handleError(err) } func (c *Context) Release() { if c.ctx == nil { return } if c.cbc > 0 { callbackDelete(c.cbc) } C.gpgme_release(c.ctx) c.ctx = nil } func (c *Context) SetArmor(yes bool) { C.gpgme_set_armor(c.ctx, cbool(yes)) } func (c *Context) Armor() bool { return C.gpgme_get_armor(c.ctx) != 0 } func (c *Context) SetTextMode(yes bool) { C.gpgme_set_textmode(c.ctx, cbool(yes)) } func (c *Context) TextMode() bool { return C.gpgme_get_textmode(c.ctx) != 0 } func (c *Context) SetProtocol(p Protocol) error { return handleError(C.gpgme_set_protocol(c.ctx, C.gpgme_protocol_t(p))) } func (c *Context) Protocol() Protocol { return Protocol(C.gpgme_get_protocol(c.ctx)) } func (c *Context) SetKeyListMode(m KeyListMode) error { return handleError(C.gpgme_set_keylist_mode(c.ctx, C.gpgme_keylist_mode_t(m))) } func (c *Context) KeyListMode() KeyListMode { return KeyListMode(C.gpgme_get_keylist_mode(c.ctx)) } // Unavailable in 1.3.2: // func (c *Context) SetPinEntryMode(m PinEntryMode) error { // return handleError(C.gpgme_set_pinentry_mode(c.ctx, C.gpgme_pinentry_mode_t(m))) // } // Unavailable in 1.3.2: // func (c *Context) PinEntryMode() PinEntryMode { // return PinEntryMode(C.gpgme_get_pinentry_mode(c.ctx)) // } func (c *Context) SetCallback(callback Callback) error { var err error c.callback = callback if c.cbc > 0 { callbackDelete(c.cbc) } if callback != nil { cbc := callbackAdd(c) c.cbc = cbc _, err = C.gogpgme_set_passphrase_cb(c.ctx, C.gpgme_passphrase_cb_t(C.gogpgme_passfunc), C.uintptr_t(cbc)) } else { c.cbc = 0 _, err = C.gogpgme_set_passphrase_cb(c.ctx, nil, 0) } return err } func (c *Context) EngineInfo() *EngineInfo { return &EngineInfo{info: C.gpgme_ctx_get_engine_info(c.ctx)} } func (c *Context) SetEngineInfo(proto Protocol, fileName, homeDir string) error { var cfn, chome *C.char if fileName != "" { cfn = C.CString(fileName) defer C.free(unsafe.Pointer(cfn)) } if homeDir != "" { chome = C.CString(homeDir) defer C.free(unsafe.Pointer(chome)) } return handleError(C.gpgme_ctx_set_engine_info(c.ctx, C.gpgme_protocol_t(proto), cfn, chome)) } func (c *Context) KeyListStart(pattern string, secretOnly bool) error { cpattern := C.CString(pattern) defer C.free(unsafe.Pointer(cpattern)) err := C.gpgme_op_keylist_start(c.ctx, cpattern, cbool(secretOnly)) return handleError(err) } func (c *Context) KeyListNext() bool { c.Key = newKey() err := handleError(C.gpgme_op_keylist_next(c.ctx, &c.Key.k)) if err != nil { if e, ok := err.(Error); ok && e.Code() == ErrorEOF { c.KeyError = nil } else { c.KeyError = err } return false } c.KeyError = nil return true } func (c *Context) KeyListEnd() error { return handleError(C.gpgme_op_keylist_end(c.ctx)) } func (c *Context) GetKey(fingerprint string, secret bool) (*Key, error) { key := newKey() cfpr := C.CString(fingerprint) defer C.free(unsafe.Pointer(cfpr)) err := handleError(C.gpgme_get_key(c.ctx, cfpr, &key.k, cbool(secret))) if e, ok := err.(Error); key.k == nil && ok && e.Code() == ErrorEOF { return nil, fmt.Errorf("key %q not found", fingerprint) } if err != nil { return nil, err } return key, nil } func (c *Context) Decrypt(ciphertext, plaintext *Data) error { return handleError(C.gpgme_op_decrypt(c.ctx, ciphertext.dh, plaintext.dh)) } func (c *Context) DecryptVerify(ciphertext, plaintext *Data) error { return handleError(C.gpgme_op_decrypt_verify(c.ctx, ciphertext.dh, plaintext.dh)) } type Signature struct { Summary SigSum Fingerprint string Status error Timestamp time.Time ExpTimestamp time.Time WrongKeyUsage bool PKATrust uint ChainModel bool Validity Validity ValidityReason error PubkeyAlgo PubkeyAlgo HashAlgo HashAlgo } func (c *Context) Verify(sig, signedText, plain *Data) (string, []Signature, error) { var signedTextPtr, plainPtr C.gpgme_data_t = nil, nil if signedText != nil { signedTextPtr = signedText.dh } if plain != nil { plainPtr = plain.dh } err := handleError(C.gpgme_op_verify(c.ctx, sig.dh, signedTextPtr, plainPtr)) if err != nil { return "", nil, err } res := C.gpgme_op_verify_result(c.ctx) sigs := []Signature{} for s := res.signatures; s != nil; s = s.next { sig := Signature{ Summary: SigSum(s.summary), Fingerprint: C.GoString(s.fpr), Status: handleError(s.status), // s.notations not implemented Timestamp: time.Unix(int64(s.timestamp), 0), ExpTimestamp: time.Unix(int64(s.exp_timestamp), 0), WrongKeyUsage: C.signature_wrong_key_usage(s) != 0, PKATrust: uint(C.signature_pka_trust(s)), ChainModel: C.signature_chain_model(s) != 0, Validity: Validity(s.validity), ValidityReason: handleError(s.validity_reason), PubkeyAlgo: PubkeyAlgo(s.pubkey_algo), HashAlgo: HashAlgo(s.hash_algo), } sigs = append(sigs, sig) } return C.GoString(res.file_name), sigs, nil } func (c *Context) Encrypt(recipients []*Key, flags EncryptFlag, plaintext, ciphertext *Data) error { size := unsafe.Sizeof(new(C.gpgme_key_t)) recp := C.calloc(C.size_t(len(recipients)+1), C.size_t(size)) defer C.free(recp) for i := range recipients { ptr := (*C.gpgme_key_t)(unsafe.Pointer(uintptr(recp) + size*uintptr(i))) *ptr = recipients[i].k } err := C.gpgme_op_encrypt(c.ctx, (*C.gpgme_key_t)(recp), C.gpgme_encrypt_flags_t(flags), plaintext.dh, ciphertext.dh) return handleError(err) } func (c *Context) Sign(signers []*Key, plain, sig *Data, mode SigMode) error { C.gpgme_signers_clear(c.ctx) for _, k := range signers { if err := handleError(C.gpgme_signers_add(c.ctx, k.k)); err != nil { C.gpgme_signers_clear(c.ctx) return err } } return handleError(C.gpgme_op_sign(c.ctx, plain.dh, sig.dh, C.gpgme_sig_mode_t(mode))) } // ImportStatusFlags describes the type of ImportStatus.Status. The C API in gpgme.h simply uses "unsigned". type ImportStatusFlags uint const ( ImportNew ImportStatusFlags = C.GPGME_IMPORT_NEW ImportUID ImportStatusFlags = C.GPGME_IMPORT_UID ImportSIG ImportStatusFlags = C.GPGME_IMPORT_SIG ImportSubKey ImportStatusFlags = C.GPGME_IMPORT_SUBKEY ImportSecret ImportStatusFlags = C.GPGME_IMPORT_SECRET ) type ImportStatus struct { Fingerprint string Result error Status ImportStatusFlags } type ImportResult struct { Considered int NoUserID int Imported int ImportedRSA int Unchanged int NewUserIDs int NewSubKeys int NewSignatures int NewRevocations int SecretRead int SecretImported int SecretUnchanged int NotImported int Imports []ImportStatus } func (c *Context) Import(keyData *Data) (*ImportResult, error) { err := handleError(C.gpgme_op_import(c.ctx, keyData.dh)) if err != nil { return nil, err } res := C.gpgme_op_import_result(c.ctx) imports := []ImportStatus{} for s := res.imports; s != nil; s = s.next { imports = append(imports, ImportStatus{ Fingerprint: C.GoString(s.fpr), Result: handleError(s.result), Status: ImportStatusFlags(s.status), }) } return &ImportResult{ Considered: int(res.considered), NoUserID: int(res.no_user_id), Imported: int(res.imported), ImportedRSA: int(res.imported_rsa), Unchanged: int(res.unchanged), NewUserIDs: int(res.new_user_ids), NewSubKeys: int(res.new_sub_keys), NewSignatures: int(res.new_signatures), NewRevocations: int(res.new_revocations), SecretRead: int(res.secret_read), SecretImported: int(res.secret_imported), SecretUnchanged: int(res.secret_unchanged), NotImported: int(res.not_imported), Imports: imports, }, nil } type Key struct { k C.gpgme_key_t } func newKey() *Key { k := &Key{} runtime.SetFinalizer(k, (*Key).Release) return k } func (k *Key) Release() { C.gpgme_key_release(k.k) k.k = nil } func (k *Key) Revoked() bool { return C.key_revoked(k.k) != 0 } func (k *Key) Expired() bool { return C.key_expired(k.k) != 0 } func (k *Key) Disabled() bool { return C.key_disabled(k.k) != 0 } func (k *Key) Invalid() bool { return C.key_invalid(k.k) != 0 } func (k *Key) CanEncrypt() bool { return C.key_can_encrypt(k.k) != 0 } func (k *Key) CanSign() bool { return C.key_can_sign(k.k) != 0 } func (k *Key) CanCertify() bool { return C.key_can_certify(k.k) != 0 } func (k *Key) Secret() bool { return C.key_secret(k.k) != 0 } func (k *Key) CanAuthenticate() bool { return C.key_can_authenticate(k.k) != 0 } func (k *Key) IsQualified() bool { return C.key_is_qualified(k.k) != 0 } func (k *Key) Protocol() Protocol { return Protocol(k.k.protocol) } func (k *Key) IssuerSerial() string { return C.GoString(k.k.issuer_serial) } func (k *Key) IssuerName() string { return C.GoString(k.k.issuer_name) } func (k *Key) ChainID() string { return C.GoString(k.k.chain_id) } func (k *Key) OwnerTrust() Validity { return Validity(k.k.owner_trust) } func (k *Key) SubKeys() *SubKey { if k.k.subkeys == nil { return nil } return &SubKey{k: k.k.subkeys, parent: k} } func (k *Key) UserIDs() *UserID { if k.k.uids == nil { return nil } return &UserID{u: k.k.uids, parent: k} } func (k *Key) KeyListMode() KeyListMode { return KeyListMode(k.k.keylist_mode) } type SubKey struct { k C.gpgme_subkey_t parent *Key // make sure the key is not released when we have a reference to a subkey } func (k *SubKey) Next() *SubKey { if k.k.next == nil { return nil } return &SubKey{k: k.k.next, parent: k.parent} } func (k *SubKey) Revoked() bool { return C.subkey_revoked(k.k) != 0 } func (k *SubKey) Expired() bool { return C.subkey_expired(k.k) != 0 } func (k *SubKey) Disabled() bool { return C.subkey_disabled(k.k) != 0 } func (k *SubKey) Invalid() bool { return C.subkey_invalid(k.k) != 0 } func (k *SubKey) Secret() bool { return C.subkey_secret(k.k) != 0 } func (k *SubKey) KeyID() string { return C.GoString(k.k.keyid) } func (k *SubKey) Fingerprint() string { return C.GoString(k.k.fpr) } func (k *SubKey) Created() time.Time { if k.k.timestamp <= 0 { return time.Time{} } return time.Unix(int64(k.k.timestamp), 0) } func (k *SubKey) Expires() time.Time { if k.k.expires <= 0 { return time.Time{} } return time.Unix(int64(k.k.expires), 0) } func (k *SubKey) CardNumber() string { return C.GoString(k.k.card_number) } type UserID struct { u C.gpgme_user_id_t parent *Key // make sure the key is not released when we have a reference to a user ID } func (u *UserID) Next() *UserID { if u.u.next == nil { return nil } return &UserID{u: u.u.next, parent: u.parent} } func (u *UserID) Revoked() bool { return C.uid_revoked(u.u) != 0 } func (u *UserID) Invalid() bool { return C.uid_invalid(u.u) != 0 } func (u *UserID) Validity() Validity { return Validity(u.u.validity) } func (u *UserID) UID() string { return C.GoString(u.u.uid) } func (u *UserID) Name() string { return C.GoString(u.u.name) } func (u *UserID) Comment() string { return C.GoString(u.u.comment) } func (u *UserID) Email() string { return C.GoString(u.u.email) } // This is somewhat of a horrible hack. We need to unset GPG_AGENT_INFO so that gpgme does not pass --use-agent to GPG. // os.Unsetenv should be enough, but that only calls the underlying C library (which gpgme uses) if cgo is involved // - and cgo can't be used in tests. So, provide this helper for test initialization. func unsetenvGPGAgentInfo() { v := C.CString("GPG_AGENT_INFO") defer C.free(unsafe.Pointer(v)) C.unsetenv(v) }