250 changes: 193 additions & 57 deletions gpgme.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,39 +164,60 @@ func EngineCheckVersion(p Protocol) error {
}

type EngineInfo struct {
info C.gpgme_engine_info_t
next *EngineInfo
protocol Protocol
fileName string
homeDir string
version string
requiredVersion string
}

func (e *EngineInfo) Next() *EngineInfo {
if e.info.next == nil {
return nil
func copyEngineInfo(info C.gpgme_engine_info_t) *EngineInfo {
res := &EngineInfo{
next: nil,
protocol: Protocol(info.protocol),
fileName: C.GoString(info.file_name),
homeDir: C.GoString(info.home_dir),
version: C.GoString(info.version),
requiredVersion: C.GoString(info.req_version),
}
return &EngineInfo{info: e.info.next}
if info.next != nil {
res.next = copyEngineInfo(info.next)
}
return res
}

func (e *EngineInfo) Next() *EngineInfo {
return e.next
}

func (e *EngineInfo) Protocol() Protocol {
return Protocol(e.info.protocol)
return e.protocol
}

func (e *EngineInfo) FileName() string {
return C.GoString(e.info.file_name)
return e.fileName
}

func (e *EngineInfo) Version() string {
return C.GoString(e.info.version)
return e.version
}

func (e *EngineInfo) RequiredVersion() string {
return C.GoString(e.info.req_version)
return e.requiredVersion
}

func (e *EngineInfo) HomeDir() string {
return C.GoString(e.info.home_dir)
return e.homeDir
}

func GetEngineInfo() (*EngineInfo, error) {
info := &EngineInfo{}
return info, handleError(C.gpgme_get_engine_info(&info.info))
var cInfo C.gpgme_engine_info_t
err := handleError(C.gpgme_get_engine_info(&cInfo))
if err != nil {
return nil, err
}
return copyEngineInfo(cInfo), nil // It is up to the caller not to invalidate cInfo concurrently until this is done.
}

func SetEngineInfo(proto Protocol, fileName, homeDir string) error {
Expand Down Expand Up @@ -257,9 +278,9 @@ type Context struct {
KeyError error

callback Callback
cbc uintptr
cbc uintptr // WARNING: Call runtime.KeepAlive(c) after ANY use of c.cbc in C (typically via c.ctx)

ctx C.gpgme_ctx_t
ctx C.gpgme_ctx_t // WARNING: Call runtime.KeepAlive(c) after ANY passing of c.ctx to C
}

func New() (*Context, error) {
Expand All @@ -277,47 +298,66 @@ func (c *Context) Release() {
callbackDelete(c.cbc)
}
C.gpgme_release(c.ctx)
runtime.KeepAlive(c)
c.ctx = nil
}

func (c *Context) SetArmor(yes bool) {
C.gpgme_set_armor(c.ctx, cbool(yes))
runtime.KeepAlive(c)
}

func (c *Context) Armor() bool {
return C.gpgme_get_armor(c.ctx) != 0
res := C.gpgme_get_armor(c.ctx) != 0
runtime.KeepAlive(c)
return res
}

func (c *Context) SetTextMode(yes bool) {
C.gpgme_set_textmode(c.ctx, cbool(yes))
runtime.KeepAlive(c)
}

func (c *Context) TextMode() bool {
return C.gpgme_get_textmode(c.ctx) != 0
res := C.gpgme_get_textmode(c.ctx) != 0
runtime.KeepAlive(c)
return res
}

func (c *Context) SetProtocol(p Protocol) error {
return handleError(C.gpgme_set_protocol(c.ctx, C.gpgme_protocol_t(p)))
err := handleError(C.gpgme_set_protocol(c.ctx, C.gpgme_protocol_t(p)))
runtime.KeepAlive(c)
return err
}

func (c *Context) Protocol() Protocol {
return Protocol(C.gpgme_get_protocol(c.ctx))
res := Protocol(C.gpgme_get_protocol(c.ctx))
runtime.KeepAlive(c)
return res
}

func (c *Context) SetKeyListMode(m KeyListMode) error {
return handleError(C.gpgme_set_keylist_mode(c.ctx, C.gpgme_keylist_mode_t(m)))
err := handleError(C.gpgme_set_keylist_mode(c.ctx, C.gpgme_keylist_mode_t(m)))
runtime.KeepAlive(c)
return err
}

func (c *Context) KeyListMode() KeyListMode {
return KeyListMode(C.gpgme_get_keylist_mode(c.ctx))
res := KeyListMode(C.gpgme_get_keylist_mode(c.ctx))
runtime.KeepAlive(c)
return res
}

func (c *Context) SetPinEntryMode(m PinEntryMode) error {
return handleError(C.gpgme_set_pinentry_mode(c.ctx, C.gpgme_pinentry_mode_t(m)))
err := handleError(C.gpgme_set_pinentry_mode(c.ctx, C.gpgme_pinentry_mode_t(m)))
runtime.KeepAlive(c)
return err
}

func (c *Context) PinEntryMode() PinEntryMode {
return PinEntryMode(C.gpgme_get_pinentry_mode(c.ctx))
res := PinEntryMode(C.gpgme_get_pinentry_mode(c.ctx))
runtime.KeepAlive(c)
return res
}

func (c *Context) SetCallback(callback Callback) error {
Expand All @@ -334,11 +374,17 @@ func (c *Context) SetCallback(callback Callback) error {
c.cbc = 0
_, err = C.gogpgme_set_passphrase_cb(c.ctx, nil, 0)
}
runtime.KeepAlive(c)
return err
}

func (c *Context) EngineInfo() *EngineInfo {
return &EngineInfo{info: C.gpgme_ctx_get_engine_info(c.ctx)}
cInfo := C.gpgme_ctx_get_engine_info(c.ctx)
runtime.KeepAlive(c)
// NOTE: c must be live as long as we are accessing cInfo.
res := copyEngineInfo(cInfo)
runtime.KeepAlive(c) // for accesses to cInfo
return res
}

func (c *Context) SetEngineInfo(proto Protocol, fileName, homeDir string) error {
Expand All @@ -351,19 +397,23 @@ func (c *Context) SetEngineInfo(proto Protocol, fileName, homeDir string) error
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))
err := handleError(C.gpgme_ctx_set_engine_info(c.ctx, C.gpgme_protocol_t(proto), cfn, chome))
runtime.KeepAlive(c)
return err
}

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)
err := handleError(C.gpgme_op_keylist_start(c.ctx, cpattern, cbool(secretOnly)))
runtime.KeepAlive(c)
return err
}

func (c *Context) KeyListNext() bool {
c.Key = newKey()
err := handleError(C.gpgme_op_keylist_next(c.ctx, &c.Key.k))
runtime.KeepAlive(c) // implies runtime.KeepAlive(c.Key)
if err != nil {
if e, ok := err.(Error); ok && e.Code() == ErrorEOF {
c.KeyError = nil
Expand All @@ -377,15 +427,21 @@ func (c *Context) KeyListNext() bool {
}

func (c *Context) KeyListEnd() error {
return handleError(C.gpgme_op_keylist_end(c.ctx))
err := handleError(C.gpgme_op_keylist_end(c.ctx))
runtime.KeepAlive(c)
return err
}

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 {
runtime.KeepAlive(c)
runtime.KeepAlive(key)
keyKIsNil := key.k == nil
runtime.KeepAlive(key)
if e, ok := err.(Error); keyKIsNil && ok && e.Code() == ErrorEOF {
return nil, fmt.Errorf("key %q not found", fingerprint)
}
if err != nil {
Expand All @@ -395,11 +451,19 @@ func (c *Context) GetKey(fingerprint string, secret bool) (*Key, error) {
}

func (c *Context) Decrypt(ciphertext, plaintext *Data) error {
return handleError(C.gpgme_op_decrypt(c.ctx, ciphertext.dh, plaintext.dh))
err := handleError(C.gpgme_op_decrypt(c.ctx, ciphertext.dh, plaintext.dh))
runtime.KeepAlive(c)
runtime.KeepAlive(ciphertext)
runtime.KeepAlive(plaintext)
return err
}

func (c *Context) DecryptVerify(ciphertext, plaintext *Data) error {
return handleError(C.gpgme_op_decrypt_verify(c.ctx, ciphertext.dh, plaintext.dh))
err := handleError(C.gpgme_op_decrypt_verify(c.ctx, ciphertext.dh, plaintext.dh))
runtime.KeepAlive(c)
runtime.KeepAlive(ciphertext)
runtime.KeepAlive(plaintext)
return err
}

type Signature struct {
Expand All @@ -426,10 +490,20 @@ func (c *Context) Verify(sig, signedText, plain *Data) (string, []Signature, err
plainPtr = plain.dh
}
err := handleError(C.gpgme_op_verify(c.ctx, sig.dh, signedTextPtr, plainPtr))
runtime.KeepAlive(c)
runtime.KeepAlive(sig)
if signedText != nil {
runtime.KeepAlive(signedText)
}
if plain != nil {
runtime.KeepAlive(plain)
}
if err != nil {
return "", nil, err
}
res := C.gpgme_op_verify_result(c.ctx)
runtime.KeepAlive(c)
// NOTE: c must be live as long as we are accessing res.
sigs := []Signature{}
for s := res.signatures; s != nil; s = s.next {
sig := Signature{
Expand All @@ -449,7 +523,9 @@ func (c *Context) Verify(sig, signedText, plain *Data) (string, []Signature, err
}
sigs = append(sigs, sig)
}
return C.GoString(res.file_name), sigs, nil
fileName := C.GoString(res.file_name)
runtime.KeepAlive(c) // for all accesses to res above
return fileName, sigs, nil
}

func (c *Context) Encrypt(recipients []*Key, flags EncryptFlag, plaintext, ciphertext *Data) error {
Expand All @@ -461,18 +537,31 @@ func (c *Context) Encrypt(recipients []*Key, flags EncryptFlag, plaintext, ciphe
*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)
runtime.KeepAlive(c)
runtime.KeepAlive(recipients)
runtime.KeepAlive(plaintext)
runtime.KeepAlive(ciphertext)
return handleError(err)
}

func (c *Context) Sign(signers []*Key, plain, sig *Data, mode SigMode) error {
C.gpgme_signers_clear(c.ctx)
runtime.KeepAlive(c)
for _, k := range signers {
if err := handleError(C.gpgme_signers_add(c.ctx, k.k)); err != nil {
err := handleError(C.gpgme_signers_add(c.ctx, k.k))
runtime.KeepAlive(c)
runtime.KeepAlive(k)
if err != nil {
C.gpgme_signers_clear(c.ctx)
runtime.KeepAlive(c)
return err
}
}
return handleError(C.gpgme_op_sign(c.ctx, plain.dh, sig.dh, C.gpgme_sig_mode_t(mode)))
err := handleError(C.gpgme_op_sign(c.ctx, plain.dh, sig.dh, C.gpgme_sig_mode_t(mode)))
runtime.KeepAlive(c)
runtime.KeepAlive(plain)
runtime.KeepAlive(sig)
return err
}

type AssuanDataCallback func(data []byte) error
Expand Down Expand Up @@ -501,6 +590,7 @@ func (c *Context) AssuanSend(
C.uintptr_t(statusPtr),
&operr,
)
runtime.KeepAlive(c)

if handleError(operr) != nil {
return handleError(operr)
Expand Down Expand Up @@ -553,7 +643,10 @@ const (
func (c *Context) Export(pattern string, mode ExportModeFlags, data *Data) error {
pat := C.CString(pattern)
defer C.free(unsafe.Pointer(pat))
return handleError(C.gpgme_op_export(c.ctx, pat, C.gpgme_export_mode_t(mode), data.dh))
err := handleError(C.gpgme_op_export(c.ctx, pat, C.gpgme_export_mode_t(mode), data.dh))
runtime.KeepAlive(c)
runtime.KeepAlive(data)
return err
}

// ImportStatusFlags describes the type of ImportStatus.Status. The C API in gpgme.h simply uses "unsigned".
Expand Down Expand Up @@ -592,10 +685,14 @@ type ImportResult struct {

func (c *Context) Import(keyData *Data) (*ImportResult, error) {
err := handleError(C.gpgme_op_import(c.ctx, keyData.dh))
runtime.KeepAlive(c)
runtime.KeepAlive(keyData)
if err != nil {
return nil, err
}
res := C.gpgme_op_import_result(c.ctx)
runtime.KeepAlive(c)
// NOTE: c must be live as long as we are accessing res.
imports := []ImportStatus{}
for s := res.imports; s != nil; s = s.next {
imports = append(imports, ImportStatus{
Expand All @@ -604,7 +701,7 @@ func (c *Context) Import(keyData *Data) (*ImportResult, error) {
Status: ImportStatusFlags(s.status),
})
}
return &ImportResult{
importResult := &ImportResult{
Considered: int(res.considered),
NoUserID: int(res.no_user_id),
Imported: int(res.imported),
Expand All @@ -619,11 +716,13 @@ func (c *Context) Import(keyData *Data) (*ImportResult, error) {
SecretUnchanged: int(res.secret_unchanged),
NotImported: int(res.not_imported),
Imports: imports,
}, nil
}
runtime.KeepAlive(c) // for all accesses to res above
return importResult, nil
}

type Key struct {
k C.gpgme_key_t
k C.gpgme_key_t // WARNING: Call Runtime.KeepAlive(k) after ANY passing of k.k to C
}

func newKey() *Key {
Expand All @@ -634,85 +733,122 @@ func newKey() *Key {

func (k *Key) Release() {
C.gpgme_key_release(k.k)
runtime.KeepAlive(k)
k.k = nil
}

func (k *Key) Revoked() bool {
return C.key_revoked(k.k) != 0
res := C.key_revoked(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) Expired() bool {
return C.key_expired(k.k) != 0
res := C.key_expired(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) Disabled() bool {
return C.key_disabled(k.k) != 0
res := C.key_disabled(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) Invalid() bool {
return C.key_invalid(k.k) != 0
res := C.key_invalid(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) CanEncrypt() bool {
return C.key_can_encrypt(k.k) != 0
res := C.key_can_encrypt(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) CanSign() bool {
return C.key_can_sign(k.k) != 0
res := C.key_can_sign(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) CanCertify() bool {
return C.key_can_certify(k.k) != 0
res := C.key_can_certify(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) Secret() bool {
return C.key_secret(k.k) != 0
res := C.key_secret(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) CanAuthenticate() bool {
return C.key_can_authenticate(k.k) != 0
res := C.key_can_authenticate(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) IsQualified() bool {
return C.key_is_qualified(k.k) != 0
res := C.key_is_qualified(k.k) != 0
runtime.KeepAlive(k)
return res
}

func (k *Key) Protocol() Protocol {
return Protocol(k.k.protocol)
res := Protocol(k.k.protocol)
runtime.KeepAlive(k)
return res
}

func (k *Key) IssuerSerial() string {
return C.GoString(k.k.issuer_serial)
res := C.GoString(k.k.issuer_serial)
runtime.KeepAlive(k)
return res
}

func (k *Key) IssuerName() string {
return C.GoString(k.k.issuer_name)
res := C.GoString(k.k.issuer_name)
runtime.KeepAlive(k)
return res
}

func (k *Key) ChainID() string {
return C.GoString(k.k.chain_id)
res := C.GoString(k.k.chain_id)
runtime.KeepAlive(k)
return res
}

func (k *Key) OwnerTrust() Validity {
return Validity(k.k.owner_trust)
res := Validity(k.k.owner_trust)
runtime.KeepAlive(k)
return res
}

func (k *Key) SubKeys() *SubKey {
if k.k.subkeys == nil {
subKeys := k.k.subkeys
runtime.KeepAlive(k)
if subKeys == nil {
return nil
}
return &SubKey{k: k.k.subkeys, parent: k}
return &SubKey{k: subKeys, parent: k} // The parent: k reference ensures subKeys remains valid
}

func (k *Key) UserIDs() *UserID {
if k.k.uids == nil {
uids := k.k.uids
runtime.KeepAlive(k)
if uids == nil {
return nil
}
return &UserID{u: k.k.uids, parent: k}
return &UserID{u: uids, parent: k} // The parent: k reference ensures uids remains valid
}

func (k *Key) KeyListMode() KeyListMode {
return KeyListMode(k.k.keylist_mode)
res := KeyListMode(k.k.keylist_mode)
runtime.KeepAlive(k)
return res
}

type SubKey struct {
Expand Down