mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 07:42:26 -06:00 
			
		
		
		
	
		
			
	
	
		
			230 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			230 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2015 The Go Authors. All rights reserved.
							 | 
						||
| 
								 | 
							
								// Use of this source code is governed by a BSD-style
							 | 
						||
| 
								 | 
							
								// license that can be found in the LICENSE file.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package acme
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"crypto"
							 | 
						||
| 
								 | 
							
									"crypto/ecdsa"
							 | 
						||
| 
								 | 
							
									"crypto/hmac"
							 | 
						||
| 
								 | 
							
									"crypto/rand"
							 | 
						||
| 
								 | 
							
									"crypto/rsa"
							 | 
						||
| 
								 | 
							
									"crypto/sha256"
							 | 
						||
| 
								 | 
							
									_ "crypto/sha512" // need for EC keys
							 | 
						||
| 
								 | 
							
									"encoding/asn1"
							 | 
						||
| 
								 | 
							
									"encoding/base64"
							 | 
						||
| 
								 | 
							
									"encoding/json"
							 | 
						||
| 
								 | 
							
									"errors"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"math/big"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// keyID is the account identity provided by a CA during registration.
							 | 
						||
| 
								 | 
							
								type keyID string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
							 | 
						||
| 
								 | 
							
								// See jwsEncodeJSON for details.
							 | 
						||
| 
								 | 
							
								const noKeyID = keyID("")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// noPayload indicates jwsEncodeJSON will encode zero-length octet string
							 | 
						||
| 
								 | 
							
								// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
							 | 
						||
| 
								 | 
							
								// authenticated GET requests via POSTing with an empty payload.
							 | 
						||
| 
								 | 
							
								// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
							 | 
						||
| 
								 | 
							
								const noPayload = ""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// jsonWebSignature can be easily serialized into a JWS following
							 | 
						||
| 
								 | 
							
								// https://tools.ietf.org/html/rfc7515#section-3.2.
							 | 
						||
| 
								 | 
							
								type jsonWebSignature struct {
							 | 
						||
| 
								 | 
							
									Protected string `json:"protected"`
							 | 
						||
| 
								 | 
							
									Payload   string `json:"payload"`
							 | 
						||
| 
								 | 
							
									Sig       string `json:"signature"`
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// jwsEncodeJSON signs claimset using provided key and a nonce.
							 | 
						||
| 
								 | 
							
								// The result is serialized in JSON format containing either kid or jwk
							 | 
						||
| 
								 | 
							
								// fields based on the provided keyID value.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If kid is non-empty, its quoted value is inserted in the protected head
							 | 
						||
| 
								 | 
							
								// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
							 | 
						||
| 
								 | 
							
								// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// See https://tools.ietf.org/html/rfc7515#section-7.
							 | 
						||
| 
								 | 
							
								func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, url string) ([]byte, error) {
							 | 
						||
| 
								 | 
							
									alg, sha := jwsHasher(key.Public())
							 | 
						||
| 
								 | 
							
									if alg == "" || !sha.Available() {
							 | 
						||
| 
								 | 
							
										return nil, ErrUnsupportedKey
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									var phead string
							 | 
						||
| 
								 | 
							
									switch kid {
							 | 
						||
| 
								 | 
							
									case noKeyID:
							 | 
						||
| 
								 | 
							
										jwk, err := jwkEncode(key.Public())
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											return nil, err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										phead = fmt.Sprintf(`{"alg":%q,"jwk":%s,"nonce":%q,"url":%q}`, alg, jwk, nonce, url)
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										phead = fmt.Sprintf(`{"alg":%q,"kid":%q,"nonce":%q,"url":%q}`, alg, kid, nonce, url)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
							 | 
						||
| 
								 | 
							
									var payload string
							 | 
						||
| 
								 | 
							
									if claimset != noPayload {
							 | 
						||
| 
								 | 
							
										cs, err := json.Marshal(claimset)
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											return nil, err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										payload = base64.RawURLEncoding.EncodeToString(cs)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									hash := sha.New()
							 | 
						||
| 
								 | 
							
									hash.Write([]byte(phead + "." + payload))
							 | 
						||
| 
								 | 
							
									sig, err := jwsSign(key, sha, hash.Sum(nil))
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									enc := jsonWebSignature{
							 | 
						||
| 
								 | 
							
										Protected: phead,
							 | 
						||
| 
								 | 
							
										Payload:   payload,
							 | 
						||
| 
								 | 
							
										Sig:       base64.RawURLEncoding.EncodeToString(sig),
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return json.Marshal(&enc)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// jwsWithMAC creates and signs a JWS using the given key and the HS256
							 | 
						||
| 
								 | 
							
								// algorithm. kid and url are included in the protected header. rawPayload
							 | 
						||
| 
								 | 
							
								// should not be base64-URL-encoded.
							 | 
						||
| 
								 | 
							
								func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
							 | 
						||
| 
								 | 
							
									if len(key) == 0 {
							 | 
						||
| 
								 | 
							
										return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									header := struct {
							 | 
						||
| 
								 | 
							
										Algorithm string `json:"alg"`
							 | 
						||
| 
								 | 
							
										KID       string `json:"kid"`
							 | 
						||
| 
								 | 
							
										URL       string `json:"url,omitempty"`
							 | 
						||
| 
								 | 
							
									}{
							 | 
						||
| 
								 | 
							
										// Only HMAC-SHA256 is supported.
							 | 
						||
| 
								 | 
							
										Algorithm: "HS256",
							 | 
						||
| 
								 | 
							
										KID:       kid,
							 | 
						||
| 
								 | 
							
										URL:       url,
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									rawProtected, err := json.Marshal(header)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									protected := base64.RawURLEncoding.EncodeToString(rawProtected)
							 | 
						||
| 
								 | 
							
									payload := base64.RawURLEncoding.EncodeToString(rawPayload)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									h := hmac.New(sha256.New, key)
							 | 
						||
| 
								 | 
							
									if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
							 | 
						||
| 
								 | 
							
										return nil, err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									mac := h.Sum(nil)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return &jsonWebSignature{
							 | 
						||
| 
								 | 
							
										Protected: protected,
							 | 
						||
| 
								 | 
							
										Payload:   payload,
							 | 
						||
| 
								 | 
							
										Sig:       base64.RawURLEncoding.EncodeToString(mac),
							 | 
						||
| 
								 | 
							
									}, nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
							 | 
						||
| 
								 | 
							
								// The result is also suitable for creating a JWK thumbprint.
							 | 
						||
| 
								 | 
							
								// https://tools.ietf.org/html/rfc7517
							 | 
						||
| 
								 | 
							
								func jwkEncode(pub crypto.PublicKey) (string, error) {
							 | 
						||
| 
								 | 
							
									switch pub := pub.(type) {
							 | 
						||
| 
								 | 
							
									case *rsa.PublicKey:
							 | 
						||
| 
								 | 
							
										// https://tools.ietf.org/html/rfc7518#section-6.3.1
							 | 
						||
| 
								 | 
							
										n := pub.N
							 | 
						||
| 
								 | 
							
										e := big.NewInt(int64(pub.E))
							 | 
						||
| 
								 | 
							
										// Field order is important.
							 | 
						||
| 
								 | 
							
										// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
							 | 
						||
| 
								 | 
							
										return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
							 | 
						||
| 
								 | 
							
											base64.RawURLEncoding.EncodeToString(e.Bytes()),
							 | 
						||
| 
								 | 
							
											base64.RawURLEncoding.EncodeToString(n.Bytes()),
							 | 
						||
| 
								 | 
							
										), nil
							 | 
						||
| 
								 | 
							
									case *ecdsa.PublicKey:
							 | 
						||
| 
								 | 
							
										// https://tools.ietf.org/html/rfc7518#section-6.2.1
							 | 
						||
| 
								 | 
							
										p := pub.Curve.Params()
							 | 
						||
| 
								 | 
							
										n := p.BitSize / 8
							 | 
						||
| 
								 | 
							
										if p.BitSize%8 != 0 {
							 | 
						||
| 
								 | 
							
											n++
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										x := pub.X.Bytes()
							 | 
						||
| 
								 | 
							
										if n > len(x) {
							 | 
						||
| 
								 | 
							
											x = append(make([]byte, n-len(x)), x...)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										y := pub.Y.Bytes()
							 | 
						||
| 
								 | 
							
										if n > len(y) {
							 | 
						||
| 
								 | 
							
											y = append(make([]byte, n-len(y)), y...)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										// Field order is important.
							 | 
						||
| 
								 | 
							
										// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
							 | 
						||
| 
								 | 
							
										return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
							 | 
						||
| 
								 | 
							
											p.Name,
							 | 
						||
| 
								 | 
							
											base64.RawURLEncoding.EncodeToString(x),
							 | 
						||
| 
								 | 
							
											base64.RawURLEncoding.EncodeToString(y),
							 | 
						||
| 
								 | 
							
										), nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return "", ErrUnsupportedKey
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// jwsSign signs the digest using the given key.
							 | 
						||
| 
								 | 
							
								// The hash is unused for ECDSA keys.
							 | 
						||
| 
								 | 
							
								func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
							 | 
						||
| 
								 | 
							
									switch pub := key.Public().(type) {
							 | 
						||
| 
								 | 
							
									case *rsa.PublicKey:
							 | 
						||
| 
								 | 
							
										return key.Sign(rand.Reader, digest, hash)
							 | 
						||
| 
								 | 
							
									case *ecdsa.PublicKey:
							 | 
						||
| 
								 | 
							
										sigASN1, err := key.Sign(rand.Reader, digest, hash)
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											return nil, err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										var rs struct{ R, S *big.Int }
							 | 
						||
| 
								 | 
							
										if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
							 | 
						||
| 
								 | 
							
											return nil, err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										rb, sb := rs.R.Bytes(), rs.S.Bytes()
							 | 
						||
| 
								 | 
							
										size := pub.Params().BitSize / 8
							 | 
						||
| 
								 | 
							
										if size%8 > 0 {
							 | 
						||
| 
								 | 
							
											size++
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										sig := make([]byte, size*2)
							 | 
						||
| 
								 | 
							
										copy(sig[size-len(rb):], rb)
							 | 
						||
| 
								 | 
							
										copy(sig[size*2-len(sb):], sb)
							 | 
						||
| 
								 | 
							
										return sig, nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil, ErrUnsupportedKey
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// jwsHasher indicates suitable JWS algorithm name and a hash function
							 | 
						||
| 
								 | 
							
								// to use for signing a digest with the provided key.
							 | 
						||
| 
								 | 
							
								// It returns ("", 0) if the key is not supported.
							 | 
						||
| 
								 | 
							
								func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
							 | 
						||
| 
								 | 
							
									switch pub := pub.(type) {
							 | 
						||
| 
								 | 
							
									case *rsa.PublicKey:
							 | 
						||
| 
								 | 
							
										return "RS256", crypto.SHA256
							 | 
						||
| 
								 | 
							
									case *ecdsa.PublicKey:
							 | 
						||
| 
								 | 
							
										switch pub.Params().Name {
							 | 
						||
| 
								 | 
							
										case "P-256":
							 | 
						||
| 
								 | 
							
											return "ES256", crypto.SHA256
							 | 
						||
| 
								 | 
							
										case "P-384":
							 | 
						||
| 
								 | 
							
											return "ES384", crypto.SHA384
							 | 
						||
| 
								 | 
							
										case "P-521":
							 | 
						||
| 
								 | 
							
											return "ES512", crypto.SHA512
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return "", 0
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// JWKThumbprint creates a JWK thumbprint out of pub
							 | 
						||
| 
								 | 
							
								// as specified in https://tools.ietf.org/html/rfc7638.
							 | 
						||
| 
								 | 
							
								func JWKThumbprint(pub crypto.PublicKey) (string, error) {
							 | 
						||
| 
								 | 
							
									jwk, err := jwkEncode(pub)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										return "", err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									b := sha256.Sum256([]byte(jwk))
							 | 
						||
| 
								 | 
							
									return base64.RawURLEncoding.EncodeToString(b[:]), nil
							 | 
						||
| 
								 | 
							
								}
							 |