| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- /*-
- * 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"
- "crypto/ecdsa"
- "crypto/rsa"
- "encoding/base64"
- "errors"
- "fmt"
- "golang.org/x/crypto/ed25519"
- "gopkg.in/square/go-jose.v2/json"
- )
- // NonceSource represents a source of random nonces to go into JWS objects
- type NonceSource interface {
- Nonce() (string, error)
- }
- // Signer represents a signer which takes a payload and produces a signed JWS object.
- type Signer interface {
- Sign(payload []byte) (*JSONWebSignature, error)
- Options() SignerOptions
- }
- // SigningKey represents an algorithm/key used to sign a message.
- type SigningKey struct {
- Algorithm SignatureAlgorithm
- Key interface{}
- }
- // SignerOptions represents options that can be set when creating signers.
- type SignerOptions struct {
- NonceSource NonceSource
- EmbedJWK bool
- // Optional map of additional keys to be inserted into the protected header
- // of a JWS object. Some specifications which make use of JWS like to insert
- // additional values here. All values must be JSON-serializable.
- ExtraHeaders map[HeaderKey]interface{}
- }
- // WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
- // if necessary. It returns itself and so can be used in a fluent style.
- func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
- if so.ExtraHeaders == nil {
- so.ExtraHeaders = map[HeaderKey]interface{}{}
- }
- so.ExtraHeaders[k] = v
- return so
- }
- // WithContentType adds a content type ("cty") header and returns the updated
- // SignerOptions.
- func (so *SignerOptions) WithContentType(contentType ContentType) *SignerOptions {
- return so.WithHeader(HeaderContentType, contentType)
- }
- // WithType adds a type ("typ") header and returns the updated SignerOptions.
- func (so *SignerOptions) WithType(typ ContentType) *SignerOptions {
- return so.WithHeader(HeaderType, typ)
- }
- // WithCritical adds the given names to the critical ("crit") header and returns
- // the updated SignerOptions.
- func (so *SignerOptions) WithCritical(names ...string) *SignerOptions {
- if so.ExtraHeaders[headerCritical] == nil {
- so.WithHeader(headerCritical, make([]string, 0, len(names)))
- }
- crit := so.ExtraHeaders[headerCritical].([]string)
- so.ExtraHeaders[headerCritical] = append(crit, names...)
- return so
- }
- // WithBase64 adds a base64url-encode payload ("b64") header and returns the updated
- // SignerOptions. When the "b64" value is "false", the payload is not base64 encoded.
- func (so *SignerOptions) WithBase64(b64 bool) *SignerOptions {
- if !b64 {
- so.WithHeader(headerB64, b64)
- so.WithCritical(headerB64)
- }
- return so
- }
- type payloadSigner interface {
- signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
- }
- type payloadVerifier interface {
- verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
- }
- type genericSigner struct {
- recipients []recipientSigInfo
- nonceSource NonceSource
- embedJWK bool
- extraHeaders map[HeaderKey]interface{}
- }
- type recipientSigInfo struct {
- sigAlg SignatureAlgorithm
- publicKey func() *JSONWebKey
- signer payloadSigner
- }
- func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey {
- return func() *JSONWebKey {
- return jwk
- }
- }
- // NewSigner creates an appropriate signer based on the key type
- func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) {
- return NewMultiSigner([]SigningKey{sig}, opts)
- }
- // NewMultiSigner creates a signer for multiple recipients
- func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) {
- signer := &genericSigner{recipients: []recipientSigInfo{}}
- if opts != nil {
- signer.nonceSource = opts.NonceSource
- signer.embedJWK = opts.EmbedJWK
- signer.extraHeaders = opts.ExtraHeaders
- }
- for _, sig := range sigs {
- err := signer.addRecipient(sig.Algorithm, sig.Key)
- if err != nil {
- return nil, err
- }
- }
- return signer, nil
- }
- // newVerifier creates a verifier based on the key type
- func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
- switch verificationKey := verificationKey.(type) {
- case ed25519.PublicKey:
- return &edEncrypterVerifier{
- publicKey: verificationKey,
- }, nil
- case *rsa.PublicKey:
- return &rsaEncrypterVerifier{
- publicKey: verificationKey,
- }, nil
- case *ecdsa.PublicKey:
- return &ecEncrypterVerifier{
- publicKey: verificationKey,
- }, nil
- case []byte:
- return &symmetricMac{
- key: verificationKey,
- }, nil
- case JSONWebKey:
- return newVerifier(verificationKey.Key)
- case *JSONWebKey:
- return newVerifier(verificationKey.Key)
- }
- if ov, ok := verificationKey.(OpaqueVerifier); ok {
- return &opaqueVerifier{verifier: ov}, nil
- }
- return nil, ErrUnsupportedKeyType
- }
- func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
- recipient, err := makeJWSRecipient(alg, signingKey)
- if err != nil {
- return err
- }
- ctx.recipients = append(ctx.recipients, recipient)
- return nil
- }
- func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
- switch signingKey := signingKey.(type) {
- case ed25519.PrivateKey:
- return newEd25519Signer(alg, signingKey)
- case *rsa.PrivateKey:
- return newRSASigner(alg, signingKey)
- case *ecdsa.PrivateKey:
- return newECDSASigner(alg, signingKey)
- case []byte:
- return newSymmetricSigner(alg, signingKey)
- case JSONWebKey:
- return newJWKSigner(alg, signingKey)
- case *JSONWebKey:
- return newJWKSigner(alg, *signingKey)
- }
- if signer, ok := signingKey.(OpaqueSigner); ok {
- return newOpaqueSigner(alg, signer)
- }
- return recipientSigInfo{}, ErrUnsupportedKeyType
- }
- func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
- recipient, err := makeJWSRecipient(alg, signingKey.Key)
- if err != nil {
- return recipientSigInfo{}, err
- }
- if recipient.publicKey != nil && recipient.publicKey() != nil {
- // recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo
- // was created for the inner key (such as a RSA or ECDSA public key). It contains
- // the pub key for embedding, but doesn't have extra params like key id.
- publicKey := signingKey
- publicKey.Key = recipient.publicKey().Key
- recipient.publicKey = staticPublicKey(&publicKey)
- // This should be impossible, but let's check anyway.
- if !recipient.publicKey().IsPublic() {
- return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
- }
- }
- return recipient, nil
- }
- func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
- obj := &JSONWebSignature{}
- obj.payload = payload
- obj.Signatures = make([]Signature, len(ctx.recipients))
- for i, recipient := range ctx.recipients {
- protected := map[HeaderKey]interface{}{
- headerAlgorithm: string(recipient.sigAlg),
- }
- if recipient.publicKey != nil && recipient.publicKey() != nil {
- // We want to embed the JWK or set the kid header, but not both. Having a protected
- // header that contains an embedded JWK while also simultaneously containing the kid
- // header is confusing, and at least in ACME the two are considered to be mutually
- // exclusive. The fact that both can exist at the same time is a somewhat unfortunate
- // result of the JOSE spec. We've decided that this library will only include one or
- // the other to avoid this confusion.
- //
- // See https://github.com/square/go-jose/issues/157 for more context.
- if ctx.embedJWK {
- protected[headerJWK] = recipient.publicKey()
- } else {
- keyID := recipient.publicKey().KeyID
- if keyID != "" {
- protected[headerKeyID] = keyID
- }
- }
- }
- if ctx.nonceSource != nil {
- nonce, err := ctx.nonceSource.Nonce()
- if err != nil {
- return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
- }
- protected[headerNonce] = nonce
- }
- for k, v := range ctx.extraHeaders {
- protected[k] = v
- }
- serializedProtected := mustSerializeJSON(protected)
- needsBase64 := true
- if b64, ok := protected[headerB64]; ok {
- if needsBase64, ok = b64.(bool); !ok {
- return nil, errors.New("square/go-jose: Invalid b64 header parameter")
- }
- }
- var input bytes.Buffer
- input.WriteString(base64.RawURLEncoding.EncodeToString(serializedProtected))
- input.WriteByte('.')
- if needsBase64 {
- input.WriteString(base64.RawURLEncoding.EncodeToString(payload))
- } else {
- input.Write(payload)
- }
- signatureInfo, err := recipient.signer.signPayload(input.Bytes(), recipient.sigAlg)
- if err != nil {
- return nil, err
- }
- signatureInfo.protected = &rawHeader{}
- for k, v := range protected {
- b, err := json.Marshal(v)
- if err != nil {
- return nil, fmt.Errorf("square/go-jose: Error marshalling item %#v: %v", k, err)
- }
- (*signatureInfo.protected)[k] = makeRawMessage(b)
- }
- obj.Signatures[i] = signatureInfo
- }
- return obj, nil
- }
- func (ctx *genericSigner) Options() SignerOptions {
- return SignerOptions{
- NonceSource: ctx.nonceSource,
- EmbedJWK: ctx.embedJWK,
- ExtraHeaders: ctx.extraHeaders,
- }
- }
- // Verify validates the signature on the object and returns the payload.
- // This function does not support multi-signature, if you desire multi-sig
- // verification use VerifyMulti instead.
- //
- // Be careful when verifying signatures based on embedded JWKs inside the
- // payload header. You cannot assume that the key received in a payload is
- // trusted.
- func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
- err := obj.DetachedVerify(obj.payload, verificationKey)
- if err != nil {
- return nil, err
- }
- return obj.payload, nil
- }
- // UnsafePayloadWithoutVerification returns the payload without
- // verifying it. The content returned from this function cannot be
- // trusted.
- func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
- return obj.payload
- }
- // DetachedVerify validates a detached signature on the given payload. In
- // most cases, you will probably want to use Verify instead. DetachedVerify
- // is only useful if you have a payload and signature that are separated from
- // each other.
- func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
- verifier, err := newVerifier(verificationKey)
- if err != nil {
- return err
- }
- if len(obj.Signatures) > 1 {
- return errors.New("square/go-jose: too many signatures in payload; expecting only one")
- }
- signature := obj.Signatures[0]
- headers := signature.mergedHeaders()
- critical, err := headers.getCritical()
- if err != nil {
- return err
- }
- for _, name := range critical {
- if !supportedCritical[name] {
- return ErrCryptoFailure
- }
- }
- input, err := obj.computeAuthData(payload, &signature)
- if err != nil {
- return ErrCryptoFailure
- }
- alg := headers.getSignatureAlgorithm()
- err = verifier.verifyPayload(input, signature.Signature, alg)
- if err == nil {
- return nil
- }
- return ErrCryptoFailure
- }
- // VerifyMulti validates (one of the multiple) signatures on the object and
- // returns the index of the signature that was verified, along with the signature
- // object and the payload. We return the signature and index to guarantee that
- // callers are getting the verified value.
- func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
- idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey)
- if err != nil {
- return -1, Signature{}, nil, err
- }
- return idx, sig, obj.payload, nil
- }
- // DetachedVerifyMulti validates a detached signature on the given payload with
- // a signature/object that has potentially multiple signers. This returns the index
- // of the signature that was verified, along with the signature object. We return
- // the signature and index to guarantee that callers are getting the verified value.
- //
- // In most cases, you will probably want to use Verify or VerifyMulti instead.
- // DetachedVerifyMulti is only useful if you have a payload and signature that are
- // separated from each other, and the signature can have multiple signers at the
- // same time.
- func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
- verifier, err := newVerifier(verificationKey)
- if err != nil {
- return -1, Signature{}, err
- }
- outer:
- for i, signature := range obj.Signatures {
- headers := signature.mergedHeaders()
- critical, err := headers.getCritical()
- if err != nil {
- continue
- }
- for _, name := range critical {
- if !supportedCritical[name] {
- continue outer
- }
- }
- input, err := obj.computeAuthData(payload, &signature)
- if err != nil {
- continue
- }
- alg := headers.getSignatureAlgorithm()
- err = verifier.verifyPayload(input, signature.Signature, alg)
- if err == nil {
- return i, signature, nil
- }
- }
- return -1, Signature{}, ErrCryptoFailure
- }
|