1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
// Copyright (c) 2020, Control Command Inc. All rights reserved.
// Copyright (c) 2020-2022, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the LICENSE.md file
// distributed with the sources of this project regarding your rights to use or distribute this
// software.
package sypgp
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/sylabs/scs-key-client/client"
"github.com/sylabs/singularity/v4/pkg/sylog"
)
// PublicKeyRing retrieves the Singularity public KeyRing.
func PublicKeyRing() (openpgp.KeyRing, error) {
return NewHandle("").LoadPubKeyring()
}
// hybridKeyRing is keyring made up of a local keyring as well as a keyserver. The type satisfies
// the openpgp.KeyRing interface.
type hybridKeyRing struct {
local openpgp.KeyRing // Local keyring.
ctx context.Context //nolint:containedctx // Context, for use when retrieving keys remotely.
c *client.Client // Keyserver client.
}
// NewHybridKeyRing returns a keyring backed by both the local public keyring and the configured
// keyserver.
func NewHybridKeyRing(ctx context.Context, opts ...client.Option) (openpgp.KeyRing, error) {
// Get local keyring.
kr, err := PublicKeyRing()
if err != nil {
return nil, err
}
// Set up client to retrieve keys from keyserver.
c, err := client.NewClient(opts...)
if err != nil {
return nil, err
}
return &hybridKeyRing{
local: kr,
ctx: ctx,
c: c,
}, nil
}
// KeysById returns the set of keys that have the given key id.
//
//nolint:revive // golang/x/crypto uses Id instead of ID so we have to too
func (kr *hybridKeyRing) KeysById(id uint64) []openpgp.Key {
if keys := kr.local.KeysById(id); len(keys) > 0 {
return keys
}
// No keys found in local keyring, check with keyserver.
el, err := kr.remoteEntitiesByID(id)
if err != nil {
sylog.Warningf("failed to get key material: %v", err)
return nil
}
return el.KeysById(id)
}
// KeysByIdUsage returns the set of keys with the given id that also meet the key usage given by
// requiredUsage. The requiredUsage is expressed as the bitwise-OR of packet.KeyFlag* values.
//
//nolint:revive // golang/x/crypto uses Id instead of ID so we have to too
func (kr *hybridKeyRing) KeysByIdUsage(id uint64, requiredUsage byte) []openpgp.Key {
if keys := kr.local.KeysByIdUsage(id, requiredUsage); len(keys) > 0 {
return keys
}
// No keys found in local keyring, check with keyserver.
el, err := kr.remoteEntitiesByID(id)
if err != nil {
sylog.Warningf("failed to get key material: %v", err)
return nil
}
return el.KeysByIdUsage(id, requiredUsage)
}
// DecryptionKeys returns all private keys that are valid for decryption.
func (kr *hybridKeyRing) DecryptionKeys() []openpgp.Key {
return kr.local.DecryptionKeys()
}
// remoteEntitiesByID returns the set of entities from the keyserver that have the given key id.
func (kr *hybridKeyRing) remoteEntitiesByID(id uint64) (openpgp.EntityList, error) {
kt, err := kr.c.PKSLookup(kr.ctx, nil, fmt.Sprintf("%#x", id), client.OperationGet, false, true, nil)
if err != nil {
// If the request failed with HTTP status code unauthorized, guide the user to fix that.
var httpError *client.HTTPError
if errors.As(err, &httpError) && httpError.Code() == http.StatusUnauthorized {
sylog.Infof(helpAuth)
}
return nil, err
}
return openpgp.ReadArmoredKeyRing(strings.NewReader(kt))
}
type multiKeyRing struct {
keyrings []openpgp.KeyRing
}
// NewMultiKeyRing returns a keyring backed by different public keyring.
func NewMultiKeyRing(keyrings ...openpgp.KeyRing) openpgp.KeyRing {
return &multiKeyRing{keyrings: keyrings}
}
// KeysById returns the set of keys that have the given key id.
//
//nolint:revive // golang/x/crypto uses Id instead of ID so we have to too
func (mkr *multiKeyRing) KeysById(id uint64) []openpgp.Key {
for _, kr := range mkr.keyrings {
if keys := kr.KeysById(id); len(keys) > 0 {
return keys
}
}
return nil
}
// KeysByIdUsage returns the set of keys with the given id that also meet the key usage given by
// requiredUsage. The requiredUsage is expressed as the bitwise-OR of packet.KeyFlag* values.
//
//nolint:revive // golang/x/crypto uses Id instead of ID so we have to too
func (mkr *multiKeyRing) KeysByIdUsage(id uint64, requiredUsage byte) []openpgp.Key {
for _, kr := range mkr.keyrings {
if keys := kr.KeysByIdUsage(id, requiredUsage); len(keys) > 0 {
return keys
}
}
return nil
}
// DecryptionKeys returns all private keys that are valid for decryption.
func (mkr *multiKeyRing) DecryptionKeys() []openpgp.Key {
for _, kr := range mkr.keyrings {
if keys := kr.DecryptionKeys(); len(keys) > 0 {
return keys
}
}
return nil
}
|