mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-30 07:32:45 +00:00
367 lines
11 KiB
Go
367 lines
11 KiB
Go
|
/*-
|
||
|
* Copyright 2014 Square Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package jose
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"gopkg.in/square/go-jose.v2/json"
|
||
|
)
|
||
|
|
||
|
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
||
|
type rawJSONWebSignature struct {
|
||
|
Payload *byteBuffer `json:"payload,omitempty"`
|
||
|
Signatures []rawSignatureInfo `json:"signatures,omitempty"`
|
||
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||
|
Header *rawHeader `json:"header,omitempty"`
|
||
|
Signature *byteBuffer `json:"signature,omitempty"`
|
||
|
}
|
||
|
|
||
|
// rawSignatureInfo represents a single JWS signature over the JWS payload and protected header.
|
||
|
type rawSignatureInfo struct {
|
||
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||
|
Header *rawHeader `json:"header,omitempty"`
|
||
|
Signature *byteBuffer `json:"signature,omitempty"`
|
||
|
}
|
||
|
|
||
|
// JSONWebSignature represents a signed JWS object after parsing.
|
||
|
type JSONWebSignature struct {
|
||
|
payload []byte
|
||
|
// Signatures attached to this object (may be more than one for multi-sig).
|
||
|
// Be careful about accessing these directly, prefer to use Verify() or
|
||
|
// VerifyMulti() to ensure that the data you're getting is verified.
|
||
|
Signatures []Signature
|
||
|
}
|
||
|
|
||
|
// Signature represents a single signature over the JWS payload and protected header.
|
||
|
type Signature struct {
|
||
|
// Merged header fields. Contains both protected and unprotected header
|
||
|
// values. Prefer using Protected and Unprotected fields instead of this.
|
||
|
// Values in this header may or may not have been signed and in general
|
||
|
// should not be trusted.
|
||
|
Header Header
|
||
|
|
||
|
// Protected header. Values in this header were signed and
|
||
|
// will be verified as part of the signature verification process.
|
||
|
Protected Header
|
||
|
|
||
|
// Unprotected header. Values in this header were not signed
|
||
|
// and in general should not be trusted.
|
||
|
Unprotected Header
|
||
|
|
||
|
// The actual signature value
|
||
|
Signature []byte
|
||
|
|
||
|
protected *rawHeader
|
||
|
header *rawHeader
|
||
|
original *rawSignatureInfo
|
||
|
}
|
||
|
|
||
|
// ParseSigned parses a signed message in compact or full serialization format.
|
||
|
func ParseSigned(signature string) (*JSONWebSignature, error) {
|
||
|
signature = stripWhitespace(signature)
|
||
|
if strings.HasPrefix(signature, "{") {
|
||
|
return parseSignedFull(signature)
|
||
|
}
|
||
|
|
||
|
return parseSignedCompact(signature, nil)
|
||
|
}
|
||
|
|
||
|
// ParseDetached parses a signed message in compact serialization format with detached payload.
|
||
|
func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
|
||
|
if payload == nil {
|
||
|
return nil, errors.New("square/go-jose: nil payload")
|
||
|
}
|
||
|
return parseSignedCompact(stripWhitespace(signature), payload)
|
||
|
}
|
||
|
|
||
|
// Get a header value
|
||
|
func (sig Signature) mergedHeaders() rawHeader {
|
||
|
out := rawHeader{}
|
||
|
out.merge(sig.protected)
|
||
|
out.merge(sig.header)
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// Compute data to be signed
|
||
|
func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) ([]byte, error) {
|
||
|
var authData bytes.Buffer
|
||
|
|
||
|
protectedHeader := new(rawHeader)
|
||
|
|
||
|
if signature.original != nil && signature.original.Protected != nil {
|
||
|
if err := json.Unmarshal(signature.original.Protected.bytes(), protectedHeader); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
authData.WriteString(signature.original.Protected.base64())
|
||
|
} else if signature.protected != nil {
|
||
|
protectedHeader = signature.protected
|
||
|
authData.WriteString(base64.RawURLEncoding.EncodeToString(mustSerializeJSON(protectedHeader)))
|
||
|
}
|
||
|
|
||
|
needsBase64 := true
|
||
|
|
||
|
if protectedHeader != nil {
|
||
|
var err error
|
||
|
if needsBase64, err = protectedHeader.getB64(); err != nil {
|
||
|
needsBase64 = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
authData.WriteByte('.')
|
||
|
|
||
|
if needsBase64 {
|
||
|
authData.WriteString(base64.RawURLEncoding.EncodeToString(payload))
|
||
|
} else {
|
||
|
authData.Write(payload)
|
||
|
}
|
||
|
|
||
|
return authData.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
// parseSignedFull parses a message in full format.
|
||
|
func parseSignedFull(input string) (*JSONWebSignature, error) {
|
||
|
var parsed rawJSONWebSignature
|
||
|
err := json.Unmarshal([]byte(input), &parsed)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return parsed.sanitized()
|
||
|
}
|
||
|
|
||
|
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
||
|
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
||
|
if parsed.Payload == nil {
|
||
|
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
|
||
|
}
|
||
|
|
||
|
obj := &JSONWebSignature{
|
||
|
payload: parsed.Payload.bytes(),
|
||
|
Signatures: make([]Signature, len(parsed.Signatures)),
|
||
|
}
|
||
|
|
||
|
if len(parsed.Signatures) == 0 {
|
||
|
// No signatures array, must be flattened serialization
|
||
|
signature := Signature{}
|
||
|
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||
|
signature.protected = &rawHeader{}
|
||
|
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check that there is not a nonce in the unprotected header
|
||
|
if parsed.Header != nil && parsed.Header.getNonce() != "" {
|
||
|
return nil, ErrUnprotectedNonce
|
||
|
}
|
||
|
|
||
|
signature.header = parsed.Header
|
||
|
signature.Signature = parsed.Signature.bytes()
|
||
|
// Make a fake "original" rawSignatureInfo to store the unprocessed
|
||
|
// Protected header. This is necessary because the Protected header can
|
||
|
// contain arbitrary fields not registered as part of the spec. See
|
||
|
// https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
|
||
|
// If we unmarshal Protected into a rawHeader with its explicit list of fields,
|
||
|
// we cannot marshal losslessly. So we have to keep around the original bytes.
|
||
|
// This is used in computeAuthData, which will first attempt to use
|
||
|
// the original bytes of a protected header, and fall back on marshaling the
|
||
|
// header struct only if those bytes are not available.
|
||
|
signature.original = &rawSignatureInfo{
|
||
|
Protected: parsed.Protected,
|
||
|
Header: parsed.Header,
|
||
|
Signature: parsed.Signature,
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
signature.Header, err = signature.mergedHeaders().sanitized()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if signature.header != nil {
|
||
|
signature.Unprotected, err = signature.header.sanitized()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if signature.protected != nil {
|
||
|
signature.Protected, err = signature.protected.sanitized()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||
|
jwk := signature.Header.JSONWebKey
|
||
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||
|
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||
|
}
|
||
|
|
||
|
obj.Signatures = append(obj.Signatures, signature)
|
||
|
}
|
||
|
|
||
|
for i, sig := range parsed.Signatures {
|
||
|
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
|
||
|
obj.Signatures[i].protected = &rawHeader{}
|
||
|
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check that there is not a nonce in the unprotected header
|
||
|
if sig.Header != nil && sig.Header.getNonce() != "" {
|
||
|
return nil, ErrUnprotectedNonce
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if obj.Signatures[i].header != nil {
|
||
|
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if obj.Signatures[i].protected != nil {
|
||
|
obj.Signatures[i].Protected, err = obj.Signatures[i].protected.sanitized()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
obj.Signatures[i].Signature = sig.Signature.bytes()
|
||
|
|
||
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||
|
jwk := obj.Signatures[i].Header.JSONWebKey
|
||
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||
|
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||
|
}
|
||
|
|
||
|
// Copy value of sig
|
||
|
original := sig
|
||
|
|
||
|
obj.Signatures[i].header = sig.Header
|
||
|
obj.Signatures[i].original = &original
|
||
|
}
|
||
|
|
||
|
return obj, nil
|
||
|
}
|
||
|
|
||
|
// parseSignedCompact parses a message in compact format.
|
||
|
func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
|
||
|
parts := strings.Split(input, ".")
|
||
|
if len(parts) != 3 {
|
||
|
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
|
||
|
}
|
||
|
|
||
|
if parts[1] != "" && payload != nil {
|
||
|
return nil, fmt.Errorf("square/go-jose: payload is not detached")
|
||
|
}
|
||
|
|
||
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if payload == nil {
|
||
|
payload, err = base64.RawURLEncoding.DecodeString(parts[1])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
raw := &rawJSONWebSignature{
|
||
|
Payload: newBuffer(payload),
|
||
|
Protected: newBuffer(rawProtected),
|
||
|
Signature: newBuffer(signature),
|
||
|
}
|
||
|
return raw.sanitized()
|
||
|
}
|
||
|
|
||
|
func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
|
||
|
if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
|
||
|
return "", ErrNotSupported
|
||
|
}
|
||
|
|
||
|
serializedProtected := base64.RawURLEncoding.EncodeToString(mustSerializeJSON(obj.Signatures[0].protected))
|
||
|
payload := ""
|
||
|
signature := base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)
|
||
|
|
||
|
if !detached {
|
||
|
payload = base64.RawURLEncoding.EncodeToString(obj.payload)
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("%s.%s.%s", serializedProtected, payload, signature), nil
|
||
|
}
|
||
|
|
||
|
// CompactSerialize serializes an object using the compact serialization format.
|
||
|
func (obj JSONWebSignature) CompactSerialize() (string, error) {
|
||
|
return obj.compactSerialize(false)
|
||
|
}
|
||
|
|
||
|
// DetachedCompactSerialize serializes an object using the compact serialization format with detached payload.
|
||
|
func (obj JSONWebSignature) DetachedCompactSerialize() (string, error) {
|
||
|
return obj.compactSerialize(true)
|
||
|
}
|
||
|
|
||
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||
|
func (obj JSONWebSignature) FullSerialize() string {
|
||
|
raw := rawJSONWebSignature{
|
||
|
Payload: newBuffer(obj.payload),
|
||
|
}
|
||
|
|
||
|
if len(obj.Signatures) == 1 {
|
||
|
if obj.Signatures[0].protected != nil {
|
||
|
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
||
|
raw.Protected = newBuffer(serializedProtected)
|
||
|
}
|
||
|
raw.Header = obj.Signatures[0].header
|
||
|
raw.Signature = newBuffer(obj.Signatures[0].Signature)
|
||
|
} else {
|
||
|
raw.Signatures = make([]rawSignatureInfo, len(obj.Signatures))
|
||
|
for i, signature := range obj.Signatures {
|
||
|
raw.Signatures[i] = rawSignatureInfo{
|
||
|
Header: signature.header,
|
||
|
Signature: newBuffer(signature.Signature),
|
||
|
}
|
||
|
|
||
|
if signature.protected != nil {
|
||
|
raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return string(mustSerializeJSON(raw))
|
||
|
}
|