| /*- |
| * 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 ( |
| "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) |
| } |
| |
| 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 { |
| protected[headerKeyID] = recipient.publicKey().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) |
| |
| input := []byte(fmt.Sprintf("%s.%s", |
| base64.RawURLEncoding.EncodeToString(serializedProtected), |
| base64.RawURLEncoding.EncodeToString(payload))) |
| |
| signatureInfo, err := recipient.signer.signPayload(input, 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 |
| } |
| |
| // 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 |
| } |
| if len(critical) > 0 { |
| // Unsupported crit header |
| return ErrCryptoFailure |
| } |
| |
| input := obj.computeAuthData(payload, &signature) |
| 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 |
| } |
| |
| for i, signature := range obj.Signatures { |
| headers := signature.mergedHeaders() |
| critical, err := headers.getCritical() |
| if err != nil { |
| continue |
| } |
| if len(critical) > 0 { |
| // Unsupported crit header |
| continue |
| } |
| |
| input := obj.computeAuthData(payload, &signature) |
| alg := headers.getSignatureAlgorithm() |
| err = verifier.verifyPayload(input, signature.Signature, alg) |
| if err == nil { |
| return i, signature, nil |
| } |
| } |
| |
| return -1, Signature{}, ErrCryptoFailure |
| } |