mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
chore(federation): re-enable nilnil lint (#11253)
First round of patches to re-enable some lints from my side. This PR also refactors the general key fetching code quite a bit due to the way it currently worked with relying on some values being nil sometimes. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11253 Reviewed-by: elle <0xllx0@noreply.codeberg.org> Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: famfo <famfo@famfo.xyz> Co-committed-by: famfo <famfo@famfo.xyz>
This commit is contained in:
parent
a797a71dea
commit
5f432e32c8
16 changed files with 291 additions and 305 deletions
|
|
@ -42,3 +42,11 @@ func HTTPStatus(err error) int {
|
|||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
type ErrKeyNotFound struct {
|
||||
KeyID string
|
||||
}
|
||||
|
||||
func (err ErrKeyNotFound) Error() string {
|
||||
return fmt.Sprintf("No key found for key ID: %s", err.KeyID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,80 +33,92 @@ func FindOrCreateFederationHost(ctx context.Context, actorURI string) (*forgefed
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
federationHost, err := forgefed.FindFederationHostByFqdnAndPort(ctx, rawActorID.Host, rawActorID.HostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if federationHost == nil {
|
||||
result, err := createFederationHostFromAP(ctx, rawActorID)
|
||||
if err != nil {
|
||||
if !forgefed.IsErrFederationHostNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
federationHost = result
|
||||
|
||||
federationHost, err = createFederationHostFromAP(ctx, rawActorID)
|
||||
}
|
||||
return federationHost, nil
|
||||
|
||||
return federationHost, err
|
||||
}
|
||||
|
||||
func FindOrCreateFederatedUser(ctx context.Context, actorURI string) (*user_model.User, *user_model.FederatedUser, *forgefed.FederationHost, error) {
|
||||
user, federatedUser, federationHost, err := findFederatedUser(ctx, actorURI)
|
||||
federationHost, personID, err := findFederationHost(ctx, actorURI)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
personID, err := fm.NewPersonID(actorURI, string(federationHost.NodeInfo.SoftwareName))
|
||||
user, federatedUser, err := findFederatedUser(ctx, actorURI)
|
||||
if err == nil {
|
||||
log.Trace("Found local user: %v", user.Name)
|
||||
return user, federatedUser, federationHost, nil
|
||||
}
|
||||
|
||||
if !user_model.IsErrFederatedUserNotExists(err) {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// Fetch the remote user
|
||||
apUser, apFederatedUser, err := fetchUserFromAP(ctx, *personID, federationHost)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
log.Trace("Local ActivityPub user found (actorURI: %#v, user: %v)", actorURI, user.Name)
|
||||
} else {
|
||||
log.Trace("Attempting to create new user and federatedUser for actorURI: %#v", actorURI)
|
||||
apUser, apFederatedUser, err := fetchUserFromAP(ctx, personID, federationHost)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
user, federatedUser, federationHost, err = findFederatedUser(ctx, apFederatedUser.NormalizedOriginalURL)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
log.Trace("Resolved alias %s to %s", actorURI, apFederatedUser.NormalizedOriginalURL)
|
||||
} else {
|
||||
user = apUser
|
||||
federatedUser = apFederatedUser
|
||||
|
||||
err := user_model.CreateFederatedUser(ctx, user, federatedUser)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
log.Trace("Created user %s with federatedUser %s from distant server", user.LogString(), federatedUser.LogString())
|
||||
}
|
||||
// User is an alias, for example in newer Mastodon versions
|
||||
// - example.com/@example
|
||||
// - example.com/users/example
|
||||
// have the ID
|
||||
// - example.com/ap/users/<id>
|
||||
user, federatedUser, err = findFederatedUser(ctx, apFederatedUser.NormalizedOriginalURL)
|
||||
if err == nil {
|
||||
log.Trace("Resolved alias %s to %s", actorURI, apFederatedUser.NormalizedOriginalURL)
|
||||
return user, federatedUser, federationHost, nil
|
||||
}
|
||||
log.Trace("Got user: %v", user.Name)
|
||||
|
||||
return user, federatedUser, federationHost, nil
|
||||
err = user_model.CreateFederatedUser(ctx, apUser, apFederatedUser)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
log.Trace("Created user %s with federatedUser %s from distant server", user.LogString(), federatedUser.LogString())
|
||||
return apUser, apFederatedUser, federationHost, nil
|
||||
}
|
||||
|
||||
func findFederatedUser(ctx context.Context, actorURI string) (*user_model.User, *user_model.FederatedUser, *forgefed.FederationHost, error) {
|
||||
func findFederationHost(ctx context.Context, actorURI string) (*forgefed.FederationHost, *fm.PersonID, error) {
|
||||
federationHost, err := FindOrCreateFederationHost(ctx, actorURI)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
actorID, err := fm.NewPersonID(actorURI, string(federationHost.NodeInfo.SoftwareName))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
user, federatedUser, err := user_model.FindFederatedUser(ctx, actorID.ID, federationHost.ID)
|
||||
return federationHost, &actorID, nil
|
||||
}
|
||||
|
||||
func findFederatedUser(ctx context.Context, actorURI string) (*user_model.User, *user_model.FederatedUser, error) {
|
||||
federationHost, _, err := findFederationHost(ctx, actorURI)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return user, federatedUser, federationHost, nil
|
||||
actorID, err := fm.NewPersonID(actorURI, string(federationHost.NodeInfo.SoftwareName))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
localUser, federatedUser, err := user_model.FindFederatedUser(ctx, actorID.ID, federationHost.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return localUser, federatedUser, nil
|
||||
}
|
||||
|
||||
func createFederationHostFromAP(ctx context.Context, actorID fm.ActorID) (*forgefed.FederationHost, error) {
|
||||
|
|
|
|||
|
|
@ -23,15 +23,11 @@ func processPersonInboxCreate(ctx context.Context, user *user.User, activity *ap
|
|||
}
|
||||
|
||||
actorURI := createAct.Actor.GetLink().String()
|
||||
federatedBaseUser, _, _, err := findFederatedUser(ctx, actorURI)
|
||||
federatedBaseUser, _, err := findFederatedUser(ctx, actorURI)
|
||||
if err != nil {
|
||||
log.Error("Federated user not found (%s): %v", actorURI, err)
|
||||
return ServiceResult{}, NewErrNotAcceptablef("federated user not found (%s): %v", actorURI, err)
|
||||
}
|
||||
if federatedBaseUser == nil {
|
||||
log.Error("Federated user not found (%s): %v", actorURI, err)
|
||||
return ServiceResult{}, NewErrNotAcceptablef("federated user not found (%s): %v", actorURI, err)
|
||||
}
|
||||
|
||||
federatedUserActivity, err := activities.NewFederatedUserActivity(
|
||||
user.ID,
|
||||
|
|
|
|||
|
|
@ -20,27 +20,25 @@ func processPersonInboxUndo(ctx context.Context, ctxUser *user.User, activity *a
|
|||
}
|
||||
|
||||
actorURI := activity.Actor.GetLink().String()
|
||||
_, federatedUser, _, err := findFederatedUser(ctx, actorURI)
|
||||
_, federatedUser, err := findFederatedUser(ctx, actorURI)
|
||||
if err != nil {
|
||||
log.Error("User not found: %v", err)
|
||||
return ServiceResult{}, NewErrInternalf("User not found: %v", err)
|
||||
}
|
||||
|
||||
if federatedUser != nil {
|
||||
following, err := user.IsFollowingAp(ctx, ctxUser, federatedUser)
|
||||
if err != nil {
|
||||
log.Error("forgefed.IsFollowing: %v", err)
|
||||
return ServiceResult{}, NewErrInternalf("forgefed.IsFollowing: %v", err)
|
||||
}
|
||||
if !following {
|
||||
// The local user is not following the federated one, nothing to do.
|
||||
log.Trace("Local user[%d] is not following federated user[%d]", ctxUser.ID, federatedUser.ID)
|
||||
return NewServiceResultStatusOnly(http.StatusNoContent), nil
|
||||
}
|
||||
if err := user.RemoveFollower(ctx, ctxUser, federatedUser); err != nil {
|
||||
log.Error("Unable to remove follower", err)
|
||||
return ServiceResult{}, NewErrInternalf("Unable to remove follower: %v", err)
|
||||
}
|
||||
following, err := user.IsFollowingAp(ctx, ctxUser, federatedUser)
|
||||
if err != nil {
|
||||
log.Error("forgefed.IsFollowing: %v", err)
|
||||
return ServiceResult{}, NewErrInternalf("forgefed.IsFollowing: %v", err)
|
||||
}
|
||||
if !following {
|
||||
// The local user is not following the federated one, nothing to do.
|
||||
log.Trace("Local user[%d] is not following federated user[%d]", ctxUser.ID, federatedUser.ID)
|
||||
return NewServiceResultStatusOnly(http.StatusNoContent), nil
|
||||
}
|
||||
if err := user.RemoveFollower(ctx, ctxUser, federatedUser); err != nil {
|
||||
log.Error("Unable to remove follower", err)
|
||||
return ServiceResult{}, NewErrInternalf("Unable to remove follower: %v", err)
|
||||
}
|
||||
|
||||
return NewServiceResultStatusOnly(http.StatusNoContent), nil
|
||||
|
|
|
|||
|
|
@ -15,180 +15,128 @@ import (
|
|||
"forgejo.org/models/forgefed"
|
||||
"forgejo.org/models/user"
|
||||
"forgejo.org/modules/activitypub"
|
||||
fm "forgejo.org/modules/forgefed"
|
||||
"forgejo.org/modules/log"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
// Factory function for ActorID. Created struct is asserted to be valid
|
||||
func NewActorIDFromKeyID(ctx context.Context, uri string) (fm.ActorID, error) {
|
||||
parsedURI, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return fm.ActorID{}, err
|
||||
}
|
||||
|
||||
parsedURI.Fragment = ""
|
||||
|
||||
actionsUser := user.NewAPServerActor()
|
||||
clientFactory, err := activitypub.GetClientFactory(ctx)
|
||||
if err != nil {
|
||||
return fm.ActorID{}, err
|
||||
}
|
||||
|
||||
apClient, err := clientFactory.WithKeys(ctx, actionsUser, actionsUser.KeyID())
|
||||
if err != nil {
|
||||
return fm.ActorID{}, err
|
||||
}
|
||||
|
||||
userResponse, err := apClient.GetBody(parsedURI.String())
|
||||
if err != nil {
|
||||
return fm.ActorID{}, err
|
||||
}
|
||||
|
||||
var actor ap.Actor
|
||||
err = actor.UnmarshalJSON(userResponse)
|
||||
if err != nil {
|
||||
return fm.ActorID{}, err
|
||||
}
|
||||
|
||||
result, err := fm.NewActorID(actor.PublicKey.Owner.String())
|
||||
return result, err
|
||||
}
|
||||
|
||||
func FindOrCreateFederatedUserKey(ctx context.Context, keyID string) (pubKey any, err error) {
|
||||
log.Trace("KeyID: %v", keyID)
|
||||
var federatedUser *user.FederatedUser
|
||||
var keyURL *url.URL
|
||||
|
||||
keyURL, err = url.Parse(keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Try if the signing actor is an already known federated user
|
||||
_, federatedUser, err = user.FindFederatedUserByKeyID(ctx, keyURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if federatedUser == nil {
|
||||
rawActorID, err := NewActorIDFromKeyID(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, federatedUser, _, err = FindOrCreateFederatedUser(ctx, rawActorID.AsURI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if federatedUser.PublicKey.Valid {
|
||||
pubKey, err := x509.ParsePKIXPublicKey(federatedUser.PublicKey.V)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("For KeyID %v found pubKey %v", keyID, pubKey)
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// Fetch missing public key
|
||||
pubKey, pubKeyBytes, apPerson, err := fetchKeyFromAp(ctx, *keyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if apPerson.Type == ap.ActivityVocabularyType("Person") {
|
||||
// Check federatedUser.id = person.id
|
||||
if federatedUser.ExternalID != apPerson.ID.String() {
|
||||
return nil, fmt.Errorf("federated user fetched (%v) does not match the stored one %v", apPerson, federatedUser)
|
||||
}
|
||||
// update federated user
|
||||
federatedUser.KeyID = sql.NullString{
|
||||
String: apPerson.PublicKey.ID.String(),
|
||||
Valid: true,
|
||||
}
|
||||
federatedUser.PublicKey = sql.Null[sql.RawBytes]{
|
||||
V: pubKeyBytes,
|
||||
Valid: true,
|
||||
}
|
||||
err = user.UpdateFederatedUser(ctx, federatedUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("For %v found pubKey %v", keyID, pubKey)
|
||||
return pubKey, nil
|
||||
}
|
||||
log.Trace("For %v found no pubKey", keyID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func FindOrCreateFederationHostKey(ctx context.Context, keyID string) (pubKey any, err error) {
|
||||
func FindOrCreateActorKey(ctx context.Context, keyID string) (pubKey any, err error) {
|
||||
log.Trace("KeyID: %v", keyID)
|
||||
keyURL, err := url.Parse(keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawActorID, err := NewActorIDFromKeyID(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Is there an already known federation host?
|
||||
federationHost, err := forgefed.FindFederationHostByKeyID(ctx, keyURL.String())
|
||||
// Check for existing user key
|
||||
_, federatedUser, err := user.FindFederatedUserByKeyID(ctx, keyURL.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !user.IsErrFederatedUserNotExists(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if federationHost == nil {
|
||||
federationHost, err = FindOrCreateFederationHost(ctx, rawActorID.AsURI())
|
||||
// Check for existing federation host key
|
||||
federationHost, err := forgefed.FindFederationHostByKeyID(ctx, keyURL.String())
|
||||
if err != nil {
|
||||
if !forgefed.IsErrFederationHostNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
} else if federationHost.PublicKey.Valid {
|
||||
pubKey, err := x509.ParsePKIXPublicKey(federationHost.PublicKey.V)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
} else if federatedUser.PublicKey.Valid {
|
||||
pubKey, err := x509.ParsePKIXPublicKey(federatedUser.PublicKey.V)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Is there an already an key?
|
||||
if federationHost.PublicKey.Valid {
|
||||
pubKey, err := x509.ParsePKIXPublicKey(federationHost.PublicKey.V)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("For %v found pubKey: %v", keyID, pubKey)
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
// If not, fetch missing public key
|
||||
pubKey, pubKeyBytes, apPerson, err := fetchKeyFromAp(ctx, *keyURL)
|
||||
// Fetch missing key
|
||||
pubKey, pubKeyBytes, actor, err := fetchKeyFromAp(ctx, *keyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if apPerson.Type == ap.ActivityVocabularyType("Application") {
|
||||
// Check federationhost.id = person.id
|
||||
if federationHost.HostPort != rawActorID.HostPort || federationHost.HostFqdn != rawActorID.Host ||
|
||||
federationHost.HostSchema != rawActorID.HostSchema {
|
||||
return nil, fmt.Errorf("federation host fetched (%v) does not match the stored one %v", apPerson, federationHost)
|
||||
}
|
||||
// update federation host
|
||||
federationHost.KeyID = sql.NullString{
|
||||
String: apPerson.PublicKey.ID.String(),
|
||||
Valid: true,
|
||||
}
|
||||
federationHost.PublicKey = sql.Null[sql.RawBytes]{
|
||||
V: pubKeyBytes,
|
||||
Valid: true,
|
||||
}
|
||||
err = forgefed.UpdateFederationHost(ctx, federationHost)
|
||||
|
||||
switch actor.Type {
|
||||
case ap.PersonType:
|
||||
_, federatedUser, _, err := FindOrCreateFederatedUser(ctx, actor.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("For %v found pubKey: %v", keyID, pubKey)
|
||||
return pubKey, nil
|
||||
|
||||
err = updateFederatedUserKey(ctx, federatedUser, pubKeyBytes, actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case ap.ApplicationType:
|
||||
federationHost, err := FindOrCreateFederationHost(ctx, actor.ID.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = updateFederationHostKey(ctx, federationHost, pubKeyBytes, actor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Fetched actortype (%s) is unhandled", actor.Type)
|
||||
}
|
||||
log.Trace("For %v found no pubKey.", keyID)
|
||||
return nil, nil
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyBytes []byte, apPerson *ap.Person, err error) {
|
||||
func updateFederatedUserKey(ctx context.Context, federatedUser *user.FederatedUser, pubKeyBytes []byte, actor *ap.Actor) (err error) {
|
||||
if actor.Type != ap.PersonType {
|
||||
return fmt.Errorf("Fetched user type (%s) is not of user type Person", actor.Type)
|
||||
}
|
||||
|
||||
if federatedUser.NormalizedOriginalURL != actor.ID.String() {
|
||||
return fmt.Errorf("federated user (%s) does not match the stored one %s", actor.ID, federatedUser.NormalizedOriginalURL)
|
||||
}
|
||||
|
||||
federatedUser.KeyID = sql.NullString{
|
||||
String: actor.PublicKey.ID.String(),
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
federatedUser.PublicKey = sql.Null[sql.RawBytes]{
|
||||
V: pubKeyBytes,
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
return user.UpdateFederatedUser(ctx, federatedUser)
|
||||
}
|
||||
|
||||
func updateFederationHostKey(ctx context.Context, federationHost *forgefed.FederationHost, pubKeyBytes []byte, actor *ap.Actor) (err error) {
|
||||
if actor.Type != ap.ApplicationType {
|
||||
return fmt.Errorf("Fetched user type (%s) is not of user type Application", actor.Type)
|
||||
}
|
||||
|
||||
federationHost.KeyID = sql.NullString{
|
||||
String: actor.PublicKey.ID.String(),
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
federationHost.PublicKey = sql.Null[sql.RawBytes]{
|
||||
V: pubKeyBytes,
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
err = forgefed.UpdateFederationHost(ctx, federationHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyBytes []byte, apPerson *ap.Actor, err error) {
|
||||
log.Trace("keyURL %v", keyURL)
|
||||
actionsUser := user.NewAPServerActor()
|
||||
|
||||
|
|
@ -207,13 +155,17 @@ func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyByte
|
|||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
person := ap.PersonNew(ap.IRI(keyURL.String()))
|
||||
err = person.UnmarshalJSON(b)
|
||||
actor := ap.ActorNew(ap.IRI(keyURL.String()), ap.ActorType)
|
||||
err = actor.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("ActivityStreams object cannot be converted to actor: %w", err)
|
||||
}
|
||||
|
||||
pubKeyFromAp := actor.PublicKey
|
||||
if pubKeyFromAp.PublicKeyPem == "" {
|
||||
return nil, nil, nil, ErrKeyNotFound{KeyID: keyURL.String()}
|
||||
}
|
||||
|
||||
pubKeyFromAp := person.PublicKey
|
||||
if pubKeyFromAp.ID.String() != keyURL.String() {
|
||||
return nil, nil, nil, fmt.Errorf("cannot find publicKey with id: %v in %v", keyURL, string(b))
|
||||
}
|
||||
|
|
@ -229,7 +181,7 @@ func fetchKeyFromAp(ctx context.Context, keyURL url.URL) (pubKey any, pubKeyByte
|
|||
}
|
||||
|
||||
log.Trace("For %v fetched pubKey %v", keyURL, pubKey)
|
||||
return pubKey, pubKeyBytes, person, err
|
||||
return pubKey, pubKeyBytes, actor, err
|
||||
}
|
||||
|
||||
func decodePublicKeyPem(pubKeyPem string) ([]byte, error) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue