diff options
| author | 2024-04-02 12:30:10 +0200 | |
|---|---|---|
| committer | 2024-04-02 11:30:10 +0100 | |
| commit | 4bbdef02f16f82b12d3eaf74b7071833088481df (patch) | |
| tree | f9d2f51911678cd29c3636ce7d3f69849b7377bc | |
| parent | [chore] bump go structr cache version -> v0.6.0 (#2773) (diff) | |
| download | gotosocial-4bbdef02f16f82b12d3eaf74b7071833088481df.tar.xz | |
[chore] Try to parse public key as both Actor + bare key (#2710)
* [chore] Try to parse public key as both Actor + bare key
* fix weird test fail
| -rw-r--r-- | internal/ap/extract.go | 108 | ||||
| -rw-r--r-- | internal/ap/extractpubkey_test.go | 108 | ||||
| -rw-r--r-- | internal/federation/authenticate.go | 50 | ||||
| -rw-r--r-- | internal/timeline/get_test.go | 2 | ||||
| -rw-r--r-- | internal/typeutils/astointernal.go | 2 | 
5 files changed, 206 insertions, 64 deletions
| diff --git a/internal/ap/extract.go b/internal/ap/extract.go index d9288c162..e0c90c5d7 100644 --- a/internal/ap/extract.go +++ b/internal/ap/extract.go @@ -515,9 +515,9 @@ func ExtractURL(i WithURL) (*url.URL, error) {  	return nil, gtserror.New("no valid URL property found")  } -// ExtractPublicKey extracts the public key, public key ID, and public +// ExtractPubKeyFromActor extracts the public key, public key ID, and public  // key owner ID from an interface, or an error if something goes wrong. -func ExtractPublicKey(i WithPublicKey) ( +func ExtractPubKeyFromActor(i WithPublicKey) (  	*rsa.PublicKey, // pubkey  	*url.URL, // pubkey ID  	*url.URL, // pubkey owner @@ -528,6 +528,7 @@ func ExtractPublicKey(i WithPublicKey) (  		return nil, nil, nil, gtserror.New("public key property was nil")  	} +	// Take the first public key we can find.  	for iter := pubKeyProp.Begin(); iter != pubKeyProp.End(); iter = iter.Next() {  		if !iter.IsW3IDSecurityV1PublicKey() {  			continue @@ -538,63 +539,74 @@ func ExtractPublicKey(i WithPublicKey) (  			continue  		} -		pubKeyID, err := pub.GetId(pkey) -		if err != nil { -			continue -		} +		return ExtractPubKeyFromKey(pkey) +	} -		pubKeyOwnerProp := pkey.GetW3IDSecurityV1Owner() -		if pubKeyOwnerProp == nil { -			continue -		} +	return nil, nil, nil, gtserror.New("couldn't find valid public key") +} -		pubKeyOwner := pubKeyOwnerProp.GetIRI() -		if pubKeyOwner == nil { -			continue -		} +// ExtractPubKeyFromActor extracts the public key, public key ID, and public +// key owner ID from an interface, or an error if something goes wrong. +func ExtractPubKeyFromKey(pkey vocab.W3IDSecurityV1PublicKey) ( +	*rsa.PublicKey, // pubkey +	*url.URL, // pubkey ID +	*url.URL, // pubkey owner +	error, +) { +	pubKeyID, err := pub.GetId(pkey) +	if err != nil { +		return nil, nil, nil, errors.New("no id set on public key") +	} -		pubKeyPemProp := pkey.GetW3IDSecurityV1PublicKeyPem() -		if pubKeyPemProp == nil { -			continue -		} +	pubKeyOwnerProp := pkey.GetW3IDSecurityV1Owner() +	if pubKeyOwnerProp == nil { +		return nil, nil, nil, errors.New("nil pubKeyOwnerProp") +	} -		pkeyPem := pubKeyPemProp.Get() -		if pkeyPem == "" { -			continue -		} +	pubKeyOwner := pubKeyOwnerProp.GetIRI() +	if pubKeyOwner == nil { +		return nil, nil, nil, errors.New("nil iri on pubKeyOwnerProp") +	} -		block, _ := pem.Decode([]byte(pkeyPem)) -		if block == nil { -			continue -		} +	pubKeyPemProp := pkey.GetW3IDSecurityV1PublicKeyPem() +	if pubKeyPemProp == nil { +		return nil, nil, nil, errors.New("nil pubKeyPemProp") +	} -		var p crypto.PublicKey -		switch block.Type { -		case "PUBLIC KEY": -			p, err = x509.ParsePKIXPublicKey(block.Bytes) -		case "RSA PUBLIC KEY": -			p, err = x509.ParsePKCS1PublicKey(block.Bytes) -		default: -			err = fmt.Errorf("unknown block type: %q", block.Type) -		} -		if err != nil { -			err = gtserror.Newf("could not parse public key from block bytes: %w", err) -			return nil, nil, nil, err -		} +	pkeyPem := pubKeyPemProp.Get() +	if pkeyPem == "" { +		return nil, nil, nil, errors.New("empty pubKeyPemProp") +	} -		if p == nil { -			return nil, nil, nil, gtserror.New("returned public key was empty") -		} +	block, _ := pem.Decode([]byte(pkeyPem)) +	if block == nil { +		return nil, nil, nil, errors.New("nil pubKeyPem") +	} -		pubKey, ok := p.(*rsa.PublicKey) -		if !ok { -			continue -		} +	var p crypto.PublicKey +	switch block.Type { +	case "PUBLIC KEY": +		p, err = x509.ParsePKIXPublicKey(block.Bytes) +	case "RSA PUBLIC KEY": +		p, err = x509.ParsePKCS1PublicKey(block.Bytes) +	default: +		err = fmt.Errorf("unknown block type: %q", block.Type) +	} +	if err != nil { +		err = fmt.Errorf("could not parse public key from block bytes: %w", err) +		return nil, nil, nil, err +	} -		return pubKey, pubKeyID, pubKeyOwner, nil +	if p == nil { +		return nil, nil, nil, fmt.Errorf("returned public key was empty") +	} + +	pubKey, ok := p.(*rsa.PublicKey) +	if !ok { +		return nil, nil, nil, fmt.Errorf("could not type pubKey to *rsa.PublicKey")  	} -	return nil, nil, nil, gtserror.New("couldn't find public key") +	return pubKey, pubKeyID, pubKeyOwner, nil  }  // ExtractContent returns an intermediary representation of diff --git a/internal/ap/extractpubkey_test.go b/internal/ap/extractpubkey_test.go new file mode 100644 index 000000000..f7218f8b1 --- /dev/null +++ b/internal/ap/extractpubkey_test.go @@ -0,0 +1,108 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. + +package ap_test + +import ( +	"context" +	"encoding/json" +	"testing" + +	"github.com/stretchr/testify/suite" +	"github.com/superseriousbusiness/activity/streams" +	typepublickey "github.com/superseriousbusiness/activity/streams/impl/w3idsecurityv1/type_publickey" +	"github.com/superseriousbusiness/gotosocial/internal/ap" +) + +const ( +	stubActor = `{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    "https://w3id.org/security/v1" +  ], +  "id": "https://gts.superseriousbusiness.org/users/dumpsterqueer", +  "preferredUsername": "dumpsterqueer", +  "publicKey": { +    "id": "https://gts.superseriousbusiness.org/users/dumpsterqueer/main-key", +    "owner": "https://gts.superseriousbusiness.org/users/dumpsterqueer", +    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt7cDz2XfTJXbmmmVXZ3o\nQGB1zu1yP+2/QZZFbLCeM0bMm5cfjJ/olli6kpdcGLh1lFpSgyLE0PlAVNYdSke9\nzcxDao6N16wavFx/bOYhh8HJPPXzlFpNeQQ+EBQ1ivzuLQyzIFTMV4TyZzOREoG9\nizuXuuKDaH/ENDE6qlIDuqtICIjnURjpxnBLldPUxfUvuSO3zY+jTidsxhjUjqkK\nC7RtEVi/D6/CzktVevz5bE/gcAYgKmK0dmkJ9HH6LzOlvkM4Wrq5h/hrM+H1z5e5\nPpdJsl3KlRT4wusuM1Z5xqLQ0oIP4mX/Kd3ypCe150i+jaoCsqBk8OPtl/zKMw1a\nYQIDAQAB\n-----END PUBLIC KEY-----\n" +  }, +  "type": "Person" +}` + +	key = `{ +  "@context": "https://w3id.org/security/v1", +  "id": "https://gts.superseriousbusiness.org/users/dumpsterqueer/main-key", +  "owner": "https://gts.superseriousbusiness.org/users/dumpsterqueer", +  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt7cDz2XfTJXbmmmVXZ3o\nQGB1zu1yP+2/QZZFbLCeM0bMm5cfjJ/olli6kpdcGLh1lFpSgyLE0PlAVNYdSke9\nzcxDao6N16wavFx/bOYhh8HJPPXzlFpNeQQ+EBQ1ivzuLQyzIFTMV4TyZzOREoG9\nizuXuuKDaH/ENDE6qlIDuqtICIjnURjpxnBLldPUxfUvuSO3zY+jTidsxhjUjqkK\nC7RtEVi/D6/CzktVevz5bE/gcAYgKmK0dmkJ9HH6LzOlvkM4Wrq5h/hrM+H1z5e5\nPpdJsl3KlRT4wusuM1Z5xqLQ0oIP4mX/Kd3ypCe150i+jaoCsqBk8OPtl/zKMw1a\nYQIDAQAB\n-----END PUBLIC KEY-----\n" +}` +) + +type ExtractPubKeyTestSuite struct { +	APTestSuite +} + +func (suite *ExtractPubKeyTestSuite) TestExtractPubKeyFromStub() { +	m := make(map[string]interface{}) +	if err := json.Unmarshal([]byte(stubActor), &m); err != nil { +		suite.FailNow(err.Error()) +	} + +	t, err := streams.ToType(context.Background(), m) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	wpk, ok := t.(ap.WithPublicKey) +	if !ok { +		suite.FailNow("", "could not parse %T as WithPublicKey", t) +	} + +	pubKey, pubKeyID, ownerURI, err := ap.ExtractPubKeyFromActor(wpk) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	suite.NotNil(pubKey) +	suite.Equal("https://gts.superseriousbusiness.org/users/dumpsterqueer/main-key", pubKeyID.String()) +	suite.Equal("https://gts.superseriousbusiness.org/users/dumpsterqueer", ownerURI.String()) +} + +func (suite *ExtractPubKeyTestSuite) TestExtractPubKeyFromKey() { +	m := make(map[string]interface{}) +	if err := json.Unmarshal([]byte(key), &m); err != nil { +		suite.FailNow(err.Error()) +	} + +	pk, err := typepublickey.DeserializePublicKey(m, nil) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	pubKey, pubKeyID, ownerURI, err := ap.ExtractPubKeyFromKey(pk) +	if err != nil { +		suite.FailNow(err.Error()) +	} + +	suite.NotNil(pubKey) +	suite.Equal("https://gts.superseriousbusiness.org/users/dumpsterqueer/main-key", pubKeyID.String()) +	suite.Equal("https://gts.superseriousbusiness.org/users/dumpsterqueer", ownerURI.String()) +} + +func TestExtractPubKeyTestSuite(t *testing.T) { +	suite.Run(t, &ExtractPubKeyTestSuite{}) +} diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go index 596233b19..e9263d43c 100644 --- a/internal/federation/authenticate.go +++ b/internal/federation/authenticate.go @@ -30,6 +30,7 @@ import (  	"codeberg.org/gruf/go-kv"  	"github.com/superseriousbusiness/activity/streams" +	typepublickey "github.com/superseriousbusiness/activity/streams/impl/w3idsecurityv1/type_publickey"  	"github.com/superseriousbusiness/gotosocial/internal/ap"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -504,24 +505,45 @@ func parsePubKeyBytes(  		return nil, nil, err  	} -	t, err := streams.ToType(ctx, m) -	if err != nil { -		return nil, nil, err -	} +	var ( +		pubKey   *rsa.PublicKey +		ownerURI *url.URL +	) -	withPublicKey, ok := t.(ap.WithPublicKey) -	if !ok { -		err = gtserror.Newf("resource at %s with type %T could not be converted to ap.WithPublicKey", pubKeyID, t) -		return nil, nil, err -	} +	if t, err := streams.ToType(ctx, m); err == nil { +		// See if Actor with a PublicKey attached. +		wpk, ok := t.(ap.WithPublicKey) +		if !ok { +			return nil, nil, gtserror.Newf( +				"resource at %s with type %T did not contain recognizable public key", +				pubKeyID, t, +			) +		} -	pubKey, _, pubKeyOwnerID, err := ap.ExtractPublicKey(withPublicKey) -	if err != nil { -		err = gtserror.Newf("resource at %s with type %T did not contain recognizable public key", pubKeyID, t) -		return nil, nil, err +		pubKey, _, ownerURI, err = ap.ExtractPubKeyFromActor(wpk) +		if err != nil { +			return nil, nil, gtserror.Newf( +				"error extracting public key from %T at %s: %w", +				t, pubKeyID, err, +			) +		} +	} else if pk, err := typepublickey.DeserializePublicKey(m, nil); err == nil { +		// Bare PublicKey. +		pubKey, _, ownerURI, err = ap.ExtractPubKeyFromKey(pk) +		if err != nil { +			return nil, nil, gtserror.Newf( +				"error extracting public key at %s: %w", +				pubKeyID, err, +			) +		} +	} else { +		return nil, nil, gtserror.Newf( +			"resource at %s did not contain recognizable public key", +			pubKeyID, +		)  	} -	return pubKey, pubKeyOwnerID, nil +	return pubKey, ownerURI, nil  }  var signingAlgorithms = []httpsig.Algorithm{ diff --git a/internal/timeline/get_test.go b/internal/timeline/get_test.go index 02256054e..499a9f35d 100644 --- a/internal/timeline/get_test.go +++ b/internal/timeline/get_test.go @@ -688,7 +688,7 @@ func (suite *GetTestSuite) TestGetTimelinesAsync() {  					limit,  					local,  				); err != nil { -					suite.FailNow(err.Error()) +					suite.Fail(err.Error())  				}  				wg.Done() diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index b5e713554..ba370790a 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -211,7 +211,7 @@ func (c *Converter) ASRepresentationToAccount(ctx context.Context, accountable a  	}  	// Extract account public key and verify ownership to account. -	pkey, pkeyURL, pkeyOwnerID, err := ap.ExtractPublicKey(accountable) +	pkey, pkeyURL, pkeyOwnerID, err := ap.ExtractPubKeyFromActor(accountable)  	if err != nil {  		err := gtserror.Newf("error extracting public key for %s: %w", uri, err)  		return nil, gtserror.SetMalformed(err) | 
