...

Source file src/golang.org/x/crypto/acme/jws.go

Documentation: golang.org/x/crypto/acme

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package acme
     6  
     7  import (
     8  	"crypto"
     9  	"crypto/ecdsa"
    10  	"crypto/hmac"
    11  	"crypto/rand"
    12  	"crypto/rsa"
    13  	"crypto/sha256"
    14  	_ "crypto/sha512" // need for EC keys
    15  	"encoding/asn1"
    16  	"encoding/base64"
    17  	"encoding/json"
    18  	"errors"
    19  	"fmt"
    20  	"math/big"
    21  )
    22  
    23  // KeyID is the account key identity provided by a CA during registration.
    24  type KeyID string
    25  
    26  // noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
    27  // See jwsEncodeJSON for details.
    28  const noKeyID = KeyID("")
    29  
    30  // noPayload indicates jwsEncodeJSON will encode zero-length octet string
    31  // in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
    32  // authenticated GET requests via POSTing with an empty payload.
    33  // See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
    34  const noPayload = ""
    35  
    36  // noNonce indicates that the nonce should be omitted from the protected header.
    37  // See jwsEncodeJSON for details.
    38  const noNonce = ""
    39  
    40  // jsonWebSignature can be easily serialized into a JWS following
    41  // https://tools.ietf.org/html/rfc7515#section-3.2.
    42  type jsonWebSignature struct {
    43  	Protected string `json:"protected"`
    44  	Payload   string `json:"payload"`
    45  	Sig       string `json:"signature"`
    46  }
    47  
    48  // jwsEncodeJSON signs claimset using provided key and a nonce.
    49  // The result is serialized in JSON format containing either kid or jwk
    50  // fields based on the provided KeyID value.
    51  //
    52  // The claimset is marshalled using json.Marshal unless it is a string.
    53  // In which case it is inserted directly into the message.
    54  //
    55  // If kid is non-empty, its quoted value is inserted in the protected header
    56  // as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
    57  // as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
    58  //
    59  // If nonce is non-empty, its quoted value is inserted in the protected header.
    60  //
    61  // See https://tools.ietf.org/html/rfc7515#section-7.
    62  func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
    63  	if key == nil {
    64  		return nil, errors.New("nil key")
    65  	}
    66  	alg, sha := jwsHasher(key.Public())
    67  	if alg == "" || !sha.Available() {
    68  		return nil, ErrUnsupportedKey
    69  	}
    70  	headers := struct {
    71  		Alg   string          `json:"alg"`
    72  		KID   string          `json:"kid,omitempty"`
    73  		JWK   json.RawMessage `json:"jwk,omitempty"`
    74  		Nonce string          `json:"nonce,omitempty"`
    75  		URL   string          `json:"url"`
    76  	}{
    77  		Alg:   alg,
    78  		Nonce: nonce,
    79  		URL:   url,
    80  	}
    81  	switch kid {
    82  	case noKeyID:
    83  		jwk, err := jwkEncode(key.Public())
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  		headers.JWK = json.RawMessage(jwk)
    88  	default:
    89  		headers.KID = string(kid)
    90  	}
    91  	phJSON, err := json.Marshal(headers)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
    96  	var payload string
    97  	if val, ok := claimset.(string); ok {
    98  		payload = val
    99  	} else {
   100  		cs, err := json.Marshal(claimset)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		payload = base64.RawURLEncoding.EncodeToString(cs)
   105  	}
   106  	hash := sha.New()
   107  	hash.Write([]byte(phead + "." + payload))
   108  	sig, err := jwsSign(key, sha, hash.Sum(nil))
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	enc := jsonWebSignature{
   113  		Protected: phead,
   114  		Payload:   payload,
   115  		Sig:       base64.RawURLEncoding.EncodeToString(sig),
   116  	}
   117  	return json.Marshal(&enc)
   118  }
   119  
   120  // jwsWithMAC creates and signs a JWS using the given key and the HS256
   121  // algorithm. kid and url are included in the protected header. rawPayload
   122  // should not be base64-URL-encoded.
   123  func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
   124  	if len(key) == 0 {
   125  		return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
   126  	}
   127  	header := struct {
   128  		Algorithm string `json:"alg"`
   129  		KID       string `json:"kid"`
   130  		URL       string `json:"url,omitempty"`
   131  	}{
   132  		// Only HMAC-SHA256 is supported.
   133  		Algorithm: "HS256",
   134  		KID:       kid,
   135  		URL:       url,
   136  	}
   137  	rawProtected, err := json.Marshal(header)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	protected := base64.RawURLEncoding.EncodeToString(rawProtected)
   142  	payload := base64.RawURLEncoding.EncodeToString(rawPayload)
   143  
   144  	h := hmac.New(sha256.New, key)
   145  	if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
   146  		return nil, err
   147  	}
   148  	mac := h.Sum(nil)
   149  
   150  	return &jsonWebSignature{
   151  		Protected: protected,
   152  		Payload:   payload,
   153  		Sig:       base64.RawURLEncoding.EncodeToString(mac),
   154  	}, nil
   155  }
   156  
   157  // jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
   158  // The result is also suitable for creating a JWK thumbprint.
   159  // https://tools.ietf.org/html/rfc7517
   160  func jwkEncode(pub crypto.PublicKey) (string, error) {
   161  	switch pub := pub.(type) {
   162  	case *rsa.PublicKey:
   163  		// https://tools.ietf.org/html/rfc7518#section-6.3.1
   164  		n := pub.N
   165  		e := big.NewInt(int64(pub.E))
   166  		// Field order is important.
   167  		// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
   168  		return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
   169  			base64.RawURLEncoding.EncodeToString(e.Bytes()),
   170  			base64.RawURLEncoding.EncodeToString(n.Bytes()),
   171  		), nil
   172  	case *ecdsa.PublicKey:
   173  		// https://tools.ietf.org/html/rfc7518#section-6.2.1
   174  		p := pub.Curve.Params()
   175  		n := p.BitSize / 8
   176  		if p.BitSize%8 != 0 {
   177  			n++
   178  		}
   179  		x := pub.X.Bytes()
   180  		if n > len(x) {
   181  			x = append(make([]byte, n-len(x)), x...)
   182  		}
   183  		y := pub.Y.Bytes()
   184  		if n > len(y) {
   185  			y = append(make([]byte, n-len(y)), y...)
   186  		}
   187  		// Field order is important.
   188  		// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
   189  		return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
   190  			p.Name,
   191  			base64.RawURLEncoding.EncodeToString(x),
   192  			base64.RawURLEncoding.EncodeToString(y),
   193  		), nil
   194  	}
   195  	return "", ErrUnsupportedKey
   196  }
   197  
   198  // jwsSign signs the digest using the given key.
   199  // The hash is unused for ECDSA keys.
   200  func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
   201  	switch pub := key.Public().(type) {
   202  	case *rsa.PublicKey:
   203  		return key.Sign(rand.Reader, digest, hash)
   204  	case *ecdsa.PublicKey:
   205  		sigASN1, err := key.Sign(rand.Reader, digest, hash)
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  
   210  		var rs struct{ R, S *big.Int }
   211  		if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
   212  			return nil, err
   213  		}
   214  
   215  		rb, sb := rs.R.Bytes(), rs.S.Bytes()
   216  		size := pub.Params().BitSize / 8
   217  		if size%8 > 0 {
   218  			size++
   219  		}
   220  		sig := make([]byte, size*2)
   221  		copy(sig[size-len(rb):], rb)
   222  		copy(sig[size*2-len(sb):], sb)
   223  		return sig, nil
   224  	}
   225  	return nil, ErrUnsupportedKey
   226  }
   227  
   228  // jwsHasher indicates suitable JWS algorithm name and a hash function
   229  // to use for signing a digest with the provided key.
   230  // It returns ("", 0) if the key is not supported.
   231  func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
   232  	switch pub := pub.(type) {
   233  	case *rsa.PublicKey:
   234  		return "RS256", crypto.SHA256
   235  	case *ecdsa.PublicKey:
   236  		switch pub.Params().Name {
   237  		case "P-256":
   238  			return "ES256", crypto.SHA256
   239  		case "P-384":
   240  			return "ES384", crypto.SHA384
   241  		case "P-521":
   242  			return "ES512", crypto.SHA512
   243  		}
   244  	}
   245  	return "", 0
   246  }
   247  
   248  // JWKThumbprint creates a JWK thumbprint out of pub
   249  // as specified in https://tools.ietf.org/html/rfc7638.
   250  func JWKThumbprint(pub crypto.PublicKey) (string, error) {
   251  	jwk, err := jwkEncode(pub)
   252  	if err != nil {
   253  		return "", err
   254  	}
   255  	b := sha256.Sum256([]byte(jwk))
   256  	return base64.RawURLEncoding.EncodeToString(b[:]), nil
   257  }
   258  

View as plain text