Unverified Commit 34e73a77 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #56161 from ericchiang/go-jose-import

Automatic merge from submit-queue (batch tested with PRs 56161, 56324, 55685, 56409, 55296). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. bootstrap: use gopkg.in import for square/go-jose xref #55514 For 1.10. Ignore while 1.9 code freeze is in effect. ```release-note NONE ```
parents e2e5f233 44d00041
...@@ -2489,18 +2489,6 @@ ...@@ -2489,18 +2489,6 @@
"Rev": "7fb2782df3d83e0036cc89f461ed0422628776f4" "Rev": "7fb2782df3d83e0036cc89f461ed0422628776f4"
}, },
{ {
"ImportPath": "github.com/square/go-jose",
"Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96"
},
{
"ImportPath": "github.com/square/go-jose/cipher",
"Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96"
},
{
"ImportPath": "github.com/square/go-jose/json",
"Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96"
},
{
"ImportPath": "github.com/storageos/go-api", "ImportPath": "github.com/storageos/go-api",
"Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f" "Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f"
}, },
...@@ -3029,6 +3017,21 @@ ...@@ -3029,6 +3017,21 @@
"Rev": "20b71e5b60d756d3d2f80def009790325acc2b23" "Rev": "20b71e5b60d756d3d2f80def009790325acc2b23"
}, },
{ {
"ImportPath": "gopkg.in/square/go-jose.v2",
"Comment": "v2.1.3",
"Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
},
{
"ImportPath": "gopkg.in/square/go-jose.v2/cipher",
"Comment": "v2.1.3",
"Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
},
{
"ImportPath": "gopkg.in/square/go-jose.v2/json",
"Comment": "v2.1.3",
"Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
},
{
"ImportPath": "gopkg.in/warnings.v0", "ImportPath": "gopkg.in/warnings.v0",
"Comment": "v0.1.1", "Comment": "v0.1.1",
"Rev": "8a331561fe74dadba6edfc59f3be66c22c3b065d" "Rev": "8a331561fe74dadba6edfc59f3be66c22c3b065d"
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -46,7 +46,7 @@ go_library( ...@@ -46,7 +46,7 @@ go_library(
"//pkg/bootstrap/api:go_default_library", "//pkg/bootstrap/api:go_default_library",
"//pkg/util/metrics:go_default_library", "//pkg/util/metrics:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/square/go-jose:go_default_library", "//vendor/gopkg.in/square/go-jose.v2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
......
...@@ -20,19 +20,28 @@ import ( ...@@ -20,19 +20,28 @@ import (
"fmt" "fmt"
"strings" "strings"
jose "github.com/square/go-jose" jose "gopkg.in/square/go-jose.v2"
) )
// computeDetachedSig takes content and token details and computes a detached // computeDetachedSig takes content and token details and computes a detached
// JWS signature. This is described in Appendix F of RFC 7515. Basically, this // JWS signature. This is described in Appendix F of RFC 7515. Basically, this
// is a regular JWS with the content part of the signature elided. // is a regular JWS with the content part of the signature elided.
func computeDetachedSig(content, tokenID, tokenSecret string) (string, error) { func computeDetachedSig(content, tokenID, tokenSecret string) (string, error) {
jwk := &jose.JsonWebKey{ jwk := &jose.JSONWebKey{
Key: []byte(tokenSecret), Key: []byte(tokenSecret),
KeyID: tokenID, KeyID: tokenID,
} }
signer, err := jose.NewSigner(jose.HS256, jwk) opts := &jose.SignerOptions{
// Since this is a symetric key, go-jose doesn't automatically include
// the KeyID as part of the protected header. We have to pass it here
// explicitly.
ExtraHeaders: map[jose.HeaderKey]interface{}{
"kid": tokenID,
},
}
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: jwk}, opts)
if err != nil { if err != nil {
return "", fmt.Errorf("can't make a HS256 signer from the given token: %v", err) return "", fmt.Errorf("can't make a HS256 signer from the given token: %v", err)
} }
......
...@@ -328,7 +328,6 @@ filegroup( ...@@ -328,7 +328,6 @@ filegroup(
"//vendor/github.com/spf13/jwalterweatherman:all-srcs", "//vendor/github.com/spf13/jwalterweatherman:all-srcs",
"//vendor/github.com/spf13/pflag:all-srcs", "//vendor/github.com/spf13/pflag:all-srcs",
"//vendor/github.com/spf13/viper:all-srcs", "//vendor/github.com/spf13/viper:all-srcs",
"//vendor/github.com/square/go-jose:all-srcs",
"//vendor/github.com/storageos/go-api:all-srcs", "//vendor/github.com/storageos/go-api:all-srcs",
"//vendor/github.com/stretchr/objx:all-srcs", "//vendor/github.com/stretchr/objx:all-srcs",
"//vendor/github.com/stretchr/testify/assert:all-srcs", "//vendor/github.com/stretchr/testify/assert:all-srcs",
...@@ -397,6 +396,7 @@ filegroup( ...@@ -397,6 +396,7 @@ filegroup(
"//vendor/gopkg.in/gcfg.v1:all-srcs", "//vendor/gopkg.in/gcfg.v1:all-srcs",
"//vendor/gopkg.in/inf.v0:all-srcs", "//vendor/gopkg.in/inf.v0:all-srcs",
"//vendor/gopkg.in/natefinch/lumberjack.v2:all-srcs", "//vendor/gopkg.in/natefinch/lumberjack.v2:all-srcs",
"//vendor/gopkg.in/square/go-jose.v2:all-srcs",
"//vendor/gopkg.in/warnings.v0:all-srcs", "//vendor/gopkg.in/warnings.v0:all-srcs",
"//vendor/gopkg.in/yaml.v2:all-srcs", "//vendor/gopkg.in/yaml.v2:all-srcs",
"//vendor/k8s.io/gengo/args:all-srcs", "//vendor/k8s.io/gengo/args:all-srcs",
......
# Go JOSE
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1) [![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
[![release](https://img.shields.io/github/release/square/go-jose.svg?style=flat)](https://github.com/square/go-jose/releases)
[![build](https://travis-ci.org/square/go-jose.svg?branch=master)](https://travis-ci.org/square/go-jose)
[![coverage](https://coveralls.io/repos/github/square/go-jose/badge.svg?branch=master)](https://coveralls.io/r/square/go-jose)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. For the moment, it mainly focuses on encryption
and signing based on the JSON Web Encryption and JSON Web Signature standards.
**Disclaimer**: This library contains encryption software that is subject to
the U.S. Export Administration Regulations. You may not export, re-export,
transfer or download this code or any part of it in violation of any United
States law, directive or regulation. In particular this software may not be
exported or re-exported in any form or on any media to Iran, North Sudan,
Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
US maintained blocked list.
## Overview
The implementation follows the
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516)
standard (RFC 7516) and
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515)
standard (RFC 7515). Tables of supported algorithms are shown below.
The library supports both the compact and full serialization formats, and has
optional support for multiple recipients. It also comes with a small
command-line utility
([`jose-util`](https://github.com/square/go-jose/tree/master/jose-util))
for dealing with JOSE messages in a shell.
**Note**: We use a forked version of the `encoding/json` package from the Go
standard library which uses case-sensitive matching for member names (instead
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
This is to avoid differences in interpretation of messages between go-jose and
libraries in other languages. If you do not like this behavior, you can use the
`std_json` build tag to disable it (though we do not recommend doing so).
### Versions
We use [gopkg.in](https://gopkg.in) for versioning.
[Version 1](https://gopkg.in/square/go-jose.v1) is the current stable version:
import "gopkg.in/square/go-jose.v1"
The interface for [go-jose.v1](https://gopkg.in/square/go-jose.v1) will remain
backwards compatible. We're currently sketching out ideas for a new version, to
clean up the interface a bit. If you have ideas or feature requests [please let
us know](https://github.com/square/go-jose/issues/64)!
### Supported algorithms
See below for a table of supported algorithms. Algorithm identifiers match
the names in the
[JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
standard where possible. The
[Godoc reference](https://godoc.org/github.com/square/go-jose#pkg-constants)
has a list of constants.
Key encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
RSA-PKCS#1v1.5 | RSA1_5
RSA-OAEP | RSA-OAEP, RSA-OAEP-256
AES key wrap | A128KW, A192KW, A256KW
AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW
ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
ECDH-ES (direct) | ECDH-ES<sup>1</sup>
Direct encryption | dir<sup>1</sup>
<sup>1. Not supported in multi-recipient mode</sup>
Signing / MAC | Algorithm identifier(s)
:------------------------- | :------------------------------
RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
RSASSA-PSS | PS256, PS384, PS512
HMAC | HS256, HS384, HS512
ECDSA | ES256, ES384, ES512
Content encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
AES-GCM | A128GCM, A192GCM, A256GCM
Compression | Algorithm identifiers(s)
:------------------------- | -------------------------------
DEFLATE (RFC 1951) | DEF
### Supported key types
See below for a table of supported key types. These are understood by the
library, and can be passed to corresponding functions such as `NewEncrypter` or
`NewSigner`. Note that if you are creating a new encrypter or signer with a
JsonWebKey, the key id of the JsonWebKey (if present) will be added to any
resulting messages.
Algorithm(s) | Corresponding types
:------------------------- | -------------------------------
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey), *[jose.JsonWebKey](https://godoc.org/github.com/square/go-jose#JsonWebKey)
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey), *[jose.JsonWebKey](https://godoc.org/github.com/square/go-jose#JsonWebKey)
AES, HMAC | []byte, *[jose.JsonWebKey](https://godoc.org/github.com/square/go-jose#JsonWebKey)
## Examples
Encryption/decryption example using RSA:
```Go
// Generate a public/private key pair to use for this example. The library
// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
// that can be used to load keys from PEM/DER-encoded data.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Instantiate an encrypter using RSA-OAEP with AES128-GCM. An error would
// indicate that the selected algorithm(s) are not currently supported.
publicKey := &privateKey.PublicKey
encrypter, err := NewEncrypter(RSA_OAEP, A128GCM, publicKey)
if err != nil {
panic(err)
}
// Encrypt a sample plaintext. Calling the encrypter returns an encrypted
// JWE object, which can then be serialized for output afterwards. An error
// would indicate a problem in an underlying cryptographic primitive.
var plaintext = []byte("Lorem ipsum dolor sit amet")
object, err := encrypter.Encrypt(plaintext)
if err != nil {
panic(err)
}
// Serialize the encrypted object using the full serialization format.
// Alternatively you can also use the compact format here by calling
// object.CompactSerialize() instead.
serialized := object.FullSerialize()
// Parse the serialized, encrypted JWE object. An error would indicate that
// the given input did not represent a valid message.
object, err = ParseEncrypted(serialized)
if err != nil {
panic(err)
}
// Now we can decrypt and get back our original plaintext. An error here
// would indicate the the message failed to decrypt, e.g. because the auth
// tag was broken or the message was tampered with.
decrypted, err := object.Decrypt(privateKey)
if err != nil {
panic(err)
}
fmt.Printf(string(decrypted))
// output: Lorem ipsum dolor sit amet
```
Signing/verification example using RSA:
```Go
// Generate a public/private key pair to use for this example. The library
// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
// that can be used to load keys from PEM/DER-encoded data.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Instantiate a signer using RSASSA-PSS (SHA512) with the given private key.
signer, err := NewSigner(PS512, privateKey)
if err != nil {
panic(err)
}
// Sign a sample payload. Calling the signer returns a protected JWS object,
// which can then be serialized for output afterwards. An error would
// indicate a problem in an underlying cryptographic primitive.
var payload = []byte("Lorem ipsum dolor sit amet")
object, err := signer.Sign(payload)
if err != nil {
panic(err)
}
// Serialize the encrypted object using the full serialization format.
// Alternatively you can also use the compact format here by calling
// object.CompactSerialize() instead.
serialized := object.FullSerialize()
// Parse the serialized, protected JWS object. An error would indicate that
// the given input did not represent a valid message.
object, err = ParseSigned(serialized)
if err != nil {
panic(err)
}
// Now we can verify the signature on the payload. An error here would
// indicate the the message failed to verify, e.g. because the signature was
// broken or the message was tampered with.
output, err := object.Verify(&privateKey.PublicKey)
if err != nil {
panic(err)
}
fmt.Printf(string(output))
// output: Lorem ipsum dolor sit amet
```
More examples can be found in the [Godoc
reference](https://godoc.org/github.com/square/go-jose) for this package. The
[`jose-util`](https://github.com/square/go-jose/tree/master/jose-util)
subdirectory also contains a small command-line utility which might
be useful as an example.
/*-
* 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"
"fmt"
)
// 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)
SetNonceSource(source NonceSource)
SetEmbedJwk(embed bool)
}
// MultiSigner represents a signer which supports multiple recipients.
type MultiSigner interface {
Sign(payload []byte) (*JsonWebSignature, error)
SetNonceSource(source NonceSource)
SetEmbedJwk(embed bool)
AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error
}
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
}
type recipientSigInfo struct {
sigAlg SignatureAlgorithm
keyID string
publicKey *JsonWebKey
signer payloadSigner
}
// NewSigner creates an appropriate signer based on the key type
func NewSigner(alg SignatureAlgorithm, signingKey interface{}) (Signer, error) {
// NewMultiSigner never fails (currently)
signer := NewMultiSigner()
err := signer.AddRecipient(alg, signingKey)
if err != nil {
return nil, err
}
return signer, nil
}
// NewMultiSigner creates a signer for multiple recipients
func NewMultiSigner() MultiSigner {
return &genericSigner{
recipients: []recipientSigInfo{},
embedJwk: true,
}
}
// newVerifier creates a verifier based on the key type
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
switch verificationKey := verificationKey.(type) {
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)
default:
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 *rsa.PrivateKey:
return newRSASigner(alg, signingKey)
case *ecdsa.PrivateKey:
return newECDSASigner(alg, signingKey)
case []byte:
return newSymmetricSigner(alg, signingKey)
case *JsonWebKey:
recipient, err := makeJWSRecipient(alg, signingKey.Key)
if err != nil {
return recipientSigInfo{}, err
}
recipient.keyID = signingKey.KeyID
return recipient, nil
default:
return recipientSigInfo{}, ErrUnsupportedKeyType
}
}
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 := &rawHeader{
Alg: string(recipient.sigAlg),
}
if recipient.publicKey != nil && ctx.embedJwk {
protected.Jwk = recipient.publicKey
}
if recipient.keyID != "" {
protected.Kid = recipient.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.Nonce = nonce
}
serializedProtected := mustSerializeJSON(protected)
input := []byte(fmt.Sprintf("%s.%s",
base64URLEncode(serializedProtected),
base64URLEncode(payload)))
signatureInfo, err := recipient.signer.signPayload(input, recipient.sigAlg)
if err != nil {
return nil, err
}
signatureInfo.protected = protected
obj.Signatures[i] = signatureInfo
}
return obj, nil
}
// SetNonceSource provides or updates a nonce pool to the first recipients.
// After this method is called, the signer will consume one nonce per
// signature, returning an error it is unable to get a nonce.
func (ctx *genericSigner) SetNonceSource(source NonceSource) {
ctx.nonceSource = source
}
// SetEmbedJwk specifies if the signing key should be embedded in the protected header,
// if any. It defaults to 'true'.
func (ctx *genericSigner) SetEmbedJwk(embed bool) {
ctx.embedJwk = embed
}
// Verify validates the signature on the object and returns the payload.
func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
verifier, err := newVerifier(verificationKey)
if err != nil {
return nil, err
}
for _, signature := range obj.Signatures {
headers := signature.mergedHeaders()
if len(headers.Crit) > 0 {
// Unsupported crit header
continue
}
input := obj.computeAuthData(&signature)
alg := SignatureAlgorithm(headers.Alg)
err := verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return obj.payload, nil
}
}
return nil, ErrCryptoFailure
}
/*-
* 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/x509"
"encoding/pem"
"fmt"
)
// LoadPublicKey loads a public key from PEM/DER-encoded data.
func LoadPublicKey(data []byte) (interface{}, error) {
input := data
block, _ := pem.Decode(data)
if block != nil {
input = block.Bytes
}
// Try to load SubjectPublicKeyInfo
pub, err0 := x509.ParsePKIXPublicKey(input)
if err0 == nil {
return pub, nil
}
cert, err1 := x509.ParseCertificate(input)
if err1 == nil {
return cert.PublicKey, nil
}
return nil, fmt.Errorf("square/go-jose: parse error, got '%s' and '%s'", err0, err1)
}
// LoadPrivateKey loads a private key from PEM/DER-encoded data.
func LoadPrivateKey(data []byte) (interface{}, error) {
input := data
block, _ := pem.Decode(data)
if block != nil {
input = block.Bytes
}
var priv interface{}
priv, err0 := x509.ParsePKCS1PrivateKey(input)
if err0 == nil {
return priv, nil
}
priv, err1 := x509.ParsePKCS8PrivateKey(input)
if err1 == nil {
return priv, nil
}
priv, err2 := x509.ParseECPrivateKey(input)
if err2 == nil {
return priv, nil
}
return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s' and '%s'", err0, err1, err2)
}
...@@ -8,13 +8,15 @@ matrix: ...@@ -8,13 +8,15 @@ matrix:
- go: tip - go: tip
go: go:
- 1.3
- 1.4
- 1.5 - 1.5
- 1.6 - 1.6
- 1.7 - 1.7
- 1.8
- 1.9
- tip - tip
go_import_path: gopkg.in/square/go-jose.v2
before_script: before_script:
- export PATH=$HOME/.local/bin:$PATH - export PATH=$HOME/.local/bin:$PATH
...@@ -26,13 +28,15 @@ before_install: ...@@ -26,13 +28,15 @@ before_install:
- bash .gitcookies.sh || true - bash .gitcookies.sh || true
- go get github.com/wadey/gocovmerge - go get github.com/wadey/gocovmerge
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- go get github.com/stretchr/testify/assert
- go get golang.org/x/tools/cmd/cover || true - go get golang.org/x/tools/cmd/cover || true
- go get code.google.com/p/go.tools/cmd/cover || true - go get code.google.com/p/go.tools/cmd/cover || true
- pip install cram --user `whoami` - pip install cram --user
script: script:
- go test . -v -covermode=count -coverprofile=profile.cov - go test . -v -covermode=count -coverprofile=profile.cov
- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov - go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
- go test ./jwt -v -covermode=count -coverprofile=jwt/profile.cov
- go test ./json -v # no coverage for forked encoding/json package - go test ./json -v # no coverage for forked encoding/json package
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t - cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t
- cd .. - cd ..
......
...@@ -13,13 +13,13 @@ go_library( ...@@ -13,13 +13,13 @@ go_library(
"shared.go", "shared.go",
"signing.go", "signing.go",
"symmetric.go", "symmetric.go",
"utils.go",
], ],
importpath = "github.com/square/go-jose", importpath = "gopkg.in/square/go-jose.v2",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/square/go-jose/cipher:go_default_library", "//vendor/golang.org/x/crypto/ed25519:go_default_library",
"//vendor/github.com/square/go-jose/json:go_default_library", "//vendor/gopkg.in/square/go-jose.v2/cipher:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2/json:go_default_library",
], ],
) )
...@@ -34,8 +34,8 @@ filegroup( ...@@ -34,8 +34,8 @@ filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//vendor/github.com/square/go-jose/cipher:all-srcs", "//vendor/gopkg.in/square/go-jose.v2/cipher:all-srcs",
"//vendor/github.com/square/go-jose/json:all-srcs", "//vendor/gopkg.in/square/go-jose.v2/json:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
......
# Go JOSE
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
[![build](https://travis-ci.org/square/go-jose.svg?branch=master)](https://travis-ci.org/square/go-jose)
[![coverage](https://coveralls.io/repos/github/square/go-jose/badge.svg?branch=master)](https://coveralls.io/r/square/go-jose)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. This includes support for JSON Web Encryption,
JSON Web Signature, and JSON Web Token standards.
**Disclaimer**: This library contains encryption software that is subject to
the U.S. Export Administration Regulations. You may not export, re-export,
transfer or download this code or any part of it in violation of any United
States law, directive or regulation. In particular this software may not be
exported or re-exported in any form or on any media to Iran, North Sudan,
Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
US maintained blocked list.
## Overview
The implementation follows the
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
Tables of supported algorithms are shown below. The library supports both
the compact and full serialization formats, and has optional support for
multiple recipients. It also comes with a small command-line utility
([`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util))
for dealing with JOSE messages in a shell.
**Note**: We use a forked version of the `encoding/json` package from the Go
standard library which uses case-sensitive matching for member names (instead
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
This is to avoid differences in interpretation of messages between go-jose and
libraries in other languages.
### Versions
We use [gopkg.in](https://gopkg.in) for versioning.
[Version 1](https://gopkg.in/square/go-jose.v1) is the old stable version:
import "gopkg.in/square/go-jose.v1"
[Version 2](https://gopkg.in/square/go-jose.v2) is for new development:
import "gopkg.in/square/go-jose.v2"
The interface for [go-jose.v1](https://gopkg.in/square/go-jose.v1) will remain
backwards compatible. No new feature development will take place on the `v1` branch,
however bug fixes and security fixes will be backported.
The interface for [go-jose.v2](https://gopkg.in/square/go-jose.v2) is mostly
stable, but we suggest pinning to a particular revision for now as we still reserve
the right to make changes. New feature development happens on this branch.
New in [go-jose.v2](https://gopkg.in/square/go-jose.v2) is a
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) sub-package
contributed by [@shaxbee](https://github.com/shaxbee).
### Supported algorithms
See below for a table of supported algorithms. Algorithm identifiers match
the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
standard where possible. The Godoc reference has a list of constants.
Key encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
RSA-PKCS#1v1.5 | RSA1_5
RSA-OAEP | RSA-OAEP, RSA-OAEP-256
AES key wrap | A128KW, A192KW, A256KW
AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW
ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
ECDH-ES (direct) | ECDH-ES<sup>1</sup>
Direct encryption | dir<sup>1</sup>
<sup>1. Not supported in multi-recipient mode</sup>
Signing / MAC | Algorithm identifier(s)
:------------------------- | :------------------------------
RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
RSASSA-PSS | PS256, PS384, PS512
HMAC | HS256, HS384, HS512
ECDSA | ES256, ES384, ES512
Content encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
AES-GCM | A128GCM, A192GCM, A256GCM
Compression | Algorithm identifiers(s)
:------------------------- | -------------------------------
DEFLATE (RFC 1951) | DEF
### Supported key types
See below for a table of supported key types. These are understood by the
library, and can be passed to corresponding functions such as `NewEncrypter` or
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
allows attaching a key id.
Algorithm(s) | Corresponding types
:------------------------- | -------------------------------
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
AES, HMAC | []byte
## Examples
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
Examples can be found in the Godoc
reference for this package. The
[`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util)
subdirectory also contains a small command-line utility which might be useful
as an example.
...@@ -28,7 +28,9 @@ import ( ...@@ -28,7 +28,9 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/square/go-jose/cipher" "golang.org/x/crypto/ed25519"
"gopkg.in/square/go-jose.v2/cipher"
"gopkg.in/square/go-jose.v2/json"
) )
// A generic RSA-based encrypter/verifier // A generic RSA-based encrypter/verifier
...@@ -46,6 +48,10 @@ type ecEncrypterVerifier struct { ...@@ -46,6 +48,10 @@ type ecEncrypterVerifier struct {
publicKey *ecdsa.PublicKey publicKey *ecdsa.PublicKey
} }
type edEncrypterVerifier struct {
publicKey ed25519.PublicKey
}
// A key generator for ECDH-ES // A key generator for ECDH-ES
type ecKeyGenerator struct { type ecKeyGenerator struct {
size int size int
...@@ -58,6 +64,10 @@ type ecDecrypterSigner struct { ...@@ -58,6 +64,10 @@ type ecDecrypterSigner struct {
privateKey *ecdsa.PrivateKey privateKey *ecdsa.PrivateKey
} }
type edDecrypterSigner struct {
privateKey ed25519.PrivateKey
}
// newRSARecipient creates recipientKeyInfo based on the given key. // newRSARecipient creates recipientKeyInfo based on the given key.
func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) { func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
// Verify that key management algorithm is supported by this encrypter // Verify that key management algorithm is supported by this encrypter
...@@ -94,7 +104,7 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi ...@@ -94,7 +104,7 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi
return recipientSigInfo{ return recipientSigInfo{
sigAlg: sigAlg, sigAlg: sigAlg,
publicKey: &JsonWebKey{ publicKey: &JSONWebKey{
Key: &privateKey.PublicKey, Key: &privateKey.PublicKey,
}, },
signer: &rsaDecrypterSigner{ signer: &rsaDecrypterSigner{
...@@ -103,6 +113,25 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi ...@@ -103,6 +113,25 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi
}, nil }, nil
} }
func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) {
if sigAlg != EdDSA {
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
if privateKey == nil {
return recipientSigInfo{}, errors.New("invalid private key")
}
return recipientSigInfo{
sigAlg: sigAlg,
publicKey: &JSONWebKey{
Key: privateKey.Public(),
},
signer: &edDecrypterSigner{
privateKey: privateKey,
},
}, nil
}
// newECDHRecipient creates recipientKeyInfo based on the given key. // newECDHRecipient creates recipientKeyInfo based on the given key.
func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) { func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
// Verify that key management algorithm is supported by this encrypter // Verify that key management algorithm is supported by this encrypter
...@@ -139,7 +168,7 @@ func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (re ...@@ -139,7 +168,7 @@ func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (re
return recipientSigInfo{ return recipientSigInfo{
sigAlg: sigAlg, sigAlg: sigAlg,
publicKey: &JsonWebKey{ publicKey: &JSONWebKey{
Key: &privateKey.PublicKey, Key: &privateKey.PublicKey,
}, },
signer: &ecDecrypterSigner{ signer: &ecDecrypterSigner{
...@@ -178,7 +207,7 @@ func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, e ...@@ -178,7 +207,7 @@ func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, e
// Decrypt the given payload and return the content encryption key. // Decrypt the given payload and return the content encryption key.
func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
return ctx.decrypt(recipient.encryptedKey, KeyAlgorithm(headers.Alg), generator) return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator)
} }
// Decrypt the given payload. Based on the key encryption algorithm, // Decrypt the given payload. Based on the key encryption algorithm,
...@@ -366,10 +395,15 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { ...@@ -366,10 +395,15 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size) out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
b, err := json.Marshal(&JSONWebKey{
Key: &priv.PublicKey,
})
if err != nil {
return nil, nil, err
}
headers := rawHeader{ headers := rawHeader{
Epk: &JsonWebKey{ headerEPK: makeRawMessage(b),
Key: &priv.PublicKey,
},
} }
return out, headers, nil return out, headers, nil
...@@ -377,11 +411,15 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { ...@@ -377,11 +411,15 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
// Decrypt the given payload and return the content encryption key. // Decrypt the given payload and return the content encryption key.
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
if headers.Epk == nil { epk, err := headers.getEPK()
if err != nil {
return nil, errors.New("square/go-jose: invalid epk header")
}
if epk == nil {
return nil, errors.New("square/go-jose: missing epk header") return nil, errors.New("square/go-jose: missing epk header")
} }
publicKey, ok := headers.Epk.Key.(*ecdsa.PublicKey) publicKey, ok := epk.Key.(*ecdsa.PublicKey)
if publicKey == nil || !ok { if publicKey == nil || !ok {
return nil, errors.New("square/go-jose: invalid epk header") return nil, errors.New("square/go-jose: invalid epk header")
} }
...@@ -390,19 +428,26 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI ...@@ -390,19 +428,26 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
return nil, errors.New("square/go-jose: invalid public key in epk header") return nil, errors.New("square/go-jose: invalid public key in epk header")
} }
apuData := headers.Apu.bytes() apuData, err := headers.getAPU()
apvData := headers.Apv.bytes() if err != nil {
return nil, errors.New("square/go-jose: invalid apu header")
}
apvData, err := headers.getAPV()
if err != nil {
return nil, errors.New("square/go-jose: invalid apv header")
}
deriveKey := func(algID string, size int) []byte { deriveKey := func(algID string, size int) []byte {
return josecipher.DeriveECDHES(algID, apuData, apvData, ctx.privateKey, publicKey, size) return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size)
} }
var keySize int var keySize int
switch KeyAlgorithm(headers.Alg) { algorithm := headers.getAlgorithm()
switch algorithm {
case ECDH_ES: case ECDH_ES:
// ECDH-ES uses direct key agreement, no key unwrapping necessary. // ECDH-ES uses direct key agreement, no key unwrapping necessary.
return deriveKey(string(headers.Enc), generator.keySize()), nil return deriveKey(string(headers.getEncryption()), generator.keySize()), nil
case ECDH_ES_A128KW: case ECDH_ES_A128KW:
keySize = 16 keySize = 16
case ECDH_ES_A192KW: case ECDH_ES_A192KW:
...@@ -413,7 +458,7 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI ...@@ -413,7 +458,7 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
return nil, ErrUnsupportedAlgorithm return nil, ErrUnsupportedAlgorithm
} }
key := deriveKey(headers.Alg, keySize) key := deriveKey(string(algorithm), keySize)
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -421,6 +466,32 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI ...@@ -421,6 +466,32 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
return josecipher.KeyUnwrap(block, recipient.encryptedKey) return josecipher.KeyUnwrap(block, recipient.encryptedKey)
} }
func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
if alg != EdDSA {
return Signature{}, ErrUnsupportedAlgorithm
}
sig, err := ctx.privateKey.Sign(randReader, payload, crypto.Hash(0))
if err != nil {
return Signature{}, err
}
return Signature{
Signature: sig,
protected: &rawHeader{},
}, nil
}
func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
if alg != EdDSA {
return ErrUnsupportedAlgorithm
}
ok := ed25519.Verify(ctx.publicKey, payload, signature)
if !ok {
return errors.New("square/go-jose: ed25519 signature failed to verify")
}
return nil
}
// Sign the given payload // Sign the given payload
func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
...@@ -457,7 +528,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) ...@@ -457,7 +528,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
keyBytes := curveBits / 8 keyBytes := curveBits / 8
if curveBits%8 > 0 { if curveBits%8 > 0 {
keyBytes += 1 keyBytes++
} }
// We serialize the outpus (r and s) into big-endian byte arrays and pad // We serialize the outpus (r and s) into big-endian byte arrays and pad
......
...@@ -8,7 +8,7 @@ go_library( ...@@ -8,7 +8,7 @@ go_library(
"ecdh_es.go", "ecdh_es.go",
"key_wrap.go", "key_wrap.go",
], ],
importpath = "github.com/square/go-jose/cipher", importpath = "gopkg.in/square/go-jose.v2/cipher",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
......
...@@ -28,7 +28,7 @@ import ( ...@@ -28,7 +28,7 @@ import (
// size may be at most 1<<16 bytes (64 KiB). // size may be at most 1<<16 bytes (64 KiB).
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte { func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
if size > 1<<16 { if size > 1<<16 {
panic("ECDH-ES output size too large, must be less than 1<<16") panic("ECDH-ES output size too large, must be less than or equal to 1<<16")
} }
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length // algId, partyUInfo, partyVInfo inputs must be prefixed with the length
......
...@@ -17,10 +17,11 @@ ...@@ -17,10 +17,11 @@
/* /*
Package jose aims to provide an implementation of the Javascript Object Signing Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. For the moment, it mainly focuses on and Encryption set of standards. It implements encryption and signing based on
encryption and signing based on the JSON Web Encryption and JSON Web Signature the JSON Web Encryption and JSON Web Signature standards, with optional JSON
standards. The library supports both the compact and full serialization Web Token support available in a sub-package. The library supports both the
formats, and has optional support for multiple recipients. compact and full serialization formats, and has optional support for multiple
recipients.
*/ */
package jose package jose
...@@ -21,29 +21,14 @@ import ( ...@@ -21,29 +21,14 @@ import (
"compress/flate" "compress/flate"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/json"
"io" "io"
"math/big" "math/big"
"regexp" "regexp"
"strings"
"github.com/square/go-jose/json"
) )
var stripWhitespaceRegex = regexp.MustCompile("\\s") var stripWhitespaceRegex = regexp.MustCompile("\\s")
// Url-safe base64 encode that strips padding
func base64URLEncode(data []byte) string {
var result = base64.URLEncoding.EncodeToString(data)
return strings.TrimRight(result, "=")
}
// Url-safe base64 decoder that adds padding
func base64URLDecode(data string) ([]byte, error) {
var missing = (4 - len(data)%4) % 4
data += strings.Repeat("=", missing)
return base64.URLEncoding.DecodeString(data)
}
// Helper function to serialize known-good objects. // Helper function to serialize known-good objects.
// Precondition: value is not a nil pointer. // Precondition: value is not a nil pointer.
func mustSerializeJSON(value interface{}) []byte { func mustSerializeJSON(value interface{}) []byte {
...@@ -162,7 +147,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error { ...@@ -162,7 +147,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
return nil return nil
} }
decoded, err := base64URLDecode(encoded) decoded, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil { if err != nil {
return err return err
} }
...@@ -173,7 +158,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error { ...@@ -173,7 +158,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
} }
func (b *byteBuffer) base64() string { func (b *byteBuffer) base64() string {
return base64URLEncode(b.data) return base64.RawURLEncoding.EncodeToString(b.data)
} }
func (b *byteBuffer) bytes() []byte { func (b *byteBuffer) bytes() []byte {
......
...@@ -10,7 +10,7 @@ go_library( ...@@ -10,7 +10,7 @@ go_library(
"stream.go", "stream.go",
"tags.go", "tags.go",
], ],
importpath = "github.com/square/go-jose/json", importpath = "gopkg.in/square/go-jose.v2/json",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
......
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
package jose package jose
import ( import (
"encoding/base64"
"encoding/json"
"fmt" "fmt"
"strings" "strings"
"github.com/square/go-jose/json"
) )
// rawJsonWebEncryption represents a raw JWE JSON object. Used for parsing/serializing. // rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
type rawJsonWebEncryption struct { type rawJSONWebEncryption struct {
Protected *byteBuffer `json:"protected,omitempty"` Protected *byteBuffer `json:"protected,omitempty"`
Unprotected *rawHeader `json:"unprotected,omitempty"` Unprotected *rawHeader `json:"unprotected,omitempty"`
Header *rawHeader `json:"header,omitempty"` Header *rawHeader `json:"header,omitempty"`
...@@ -42,13 +42,13 @@ type rawRecipientInfo struct { ...@@ -42,13 +42,13 @@ type rawRecipientInfo struct {
EncryptedKey string `json:"encrypted_key,omitempty"` EncryptedKey string `json:"encrypted_key,omitempty"`
} }
// JsonWebEncryption represents an encrypted JWE object after parsing. // JSONWebEncryption represents an encrypted JWE object after parsing.
type JsonWebEncryption struct { type JSONWebEncryption struct {
Header JoseHeader Header Header
protected, unprotected *rawHeader protected, unprotected *rawHeader
recipients []recipientInfo recipients []recipientInfo
aad, iv, ciphertext, tag []byte aad, iv, ciphertext, tag []byte
original *rawJsonWebEncryption original *rawJSONWebEncryption
} }
// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing. // recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
...@@ -58,7 +58,7 @@ type recipientInfo struct { ...@@ -58,7 +58,7 @@ type recipientInfo struct {
} }
// GetAuthData retrieves the (optional) authenticated data attached to the object. // GetAuthData retrieves the (optional) authenticated data attached to the object.
func (obj JsonWebEncryption) GetAuthData() []byte { func (obj JSONWebEncryption) GetAuthData() []byte {
if obj.aad != nil { if obj.aad != nil {
out := make([]byte, len(obj.aad)) out := make([]byte, len(obj.aad))
copy(out, obj.aad) copy(out, obj.aad)
...@@ -69,7 +69,7 @@ func (obj JsonWebEncryption) GetAuthData() []byte { ...@@ -69,7 +69,7 @@ func (obj JsonWebEncryption) GetAuthData() []byte {
} }
// Get the merged header values // Get the merged header values
func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader { func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
out := rawHeader{} out := rawHeader{}
out.merge(obj.protected) out.merge(obj.protected)
out.merge(obj.unprotected) out.merge(obj.unprotected)
...@@ -82,26 +82,26 @@ func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader { ...@@ -82,26 +82,26 @@ func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
} }
// Get the additional authenticated data from a JWE object. // Get the additional authenticated data from a JWE object.
func (obj JsonWebEncryption) computeAuthData() []byte { func (obj JSONWebEncryption) computeAuthData() []byte {
var protected string var protected string
if obj.original != nil { if obj.original != nil {
protected = obj.original.Protected.base64() protected = obj.original.Protected.base64()
} else { } else {
protected = base64URLEncode(mustSerializeJSON((obj.protected))) protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
} }
output := []byte(protected) output := []byte(protected)
if obj.aad != nil { if obj.aad != nil {
output = append(output, '.') output = append(output, '.')
output = append(output, []byte(base64URLEncode(obj.aad))...) output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
} }
return output return output
} }
// ParseEncrypted parses an encrypted message in compact or full serialization format. // ParseEncrypted parses an encrypted message in compact or full serialization format.
func ParseEncrypted(input string) (*JsonWebEncryption, error) { func ParseEncrypted(input string) (*JSONWebEncryption, error) {
input = stripWhitespace(input) input = stripWhitespace(input)
if strings.HasPrefix(input, "{") { if strings.HasPrefix(input, "{") {
return parseEncryptedFull(input) return parseEncryptedFull(input)
...@@ -111,8 +111,8 @@ func ParseEncrypted(input string) (*JsonWebEncryption, error) { ...@@ -111,8 +111,8 @@ func ParseEncrypted(input string) (*JsonWebEncryption, error) {
} }
// parseEncryptedFull parses a message in compact format. // parseEncryptedFull parses a message in compact format.
func parseEncryptedFull(input string) (*JsonWebEncryption, error) { func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
var parsed rawJsonWebEncryption var parsed rawJSONWebEncryption
err := json.Unmarshal([]byte(input), &parsed) err := json.Unmarshal([]byte(input), &parsed)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -122,16 +122,22 @@ func parseEncryptedFull(input string) (*JsonWebEncryption, error) { ...@@ -122,16 +122,22 @@ func parseEncryptedFull(input string) (*JsonWebEncryption, error) {
} }
// sanitized produces a cleaned-up JWE object from the raw JSON. // sanitized produces a cleaned-up JWE object from the raw JSON.
func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) { func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
obj := &JsonWebEncryption{ obj := &JSONWebEncryption{
original: parsed, original: parsed,
unprotected: parsed.Unprotected, unprotected: parsed.Unprotected,
} }
// Check that there is not a nonce in the unprotected headers // Check that there is not a nonce in the unprotected headers
if (parsed.Unprotected != nil && parsed.Unprotected.Nonce != "") || if parsed.Unprotected != nil {
(parsed.Header != nil && parsed.Header.Nonce != "") { if nonce := parsed.Unprotected.getNonce(); nonce != "" {
return nil, ErrUnprotectedNonce return nil, ErrUnprotectedNonce
}
}
if parsed.Header != nil {
if nonce := parsed.Header.getNonce(); nonce != "" {
return nil, ErrUnprotectedNonce
}
} }
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 { if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
...@@ -143,11 +149,16 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) { ...@@ -143,11 +149,16 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
// Note: this must be called _after_ we parse the protected header, // Note: this must be called _after_ we parse the protected header,
// otherwise fields from the protected header will not get picked up. // otherwise fields from the protected header will not get picked up.
obj.Header = obj.mergedHeaders(nil).sanitized() var err error
mergedHeaders := obj.mergedHeaders(nil)
obj.Header, err = mergedHeaders.sanitized()
if err != nil {
return nil, fmt.Errorf("square/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
}
if len(parsed.Recipients) == 0 { if len(parsed.Recipients) == 0 {
obj.recipients = []recipientInfo{ obj.recipients = []recipientInfo{
recipientInfo{ {
header: parsed.Header, header: parsed.Header,
encryptedKey: parsed.EncryptedKey.bytes(), encryptedKey: parsed.EncryptedKey.bytes(),
}, },
...@@ -155,13 +166,13 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) { ...@@ -155,13 +166,13 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
} else { } else {
obj.recipients = make([]recipientInfo, len(parsed.Recipients)) obj.recipients = make([]recipientInfo, len(parsed.Recipients))
for r := range parsed.Recipients { for r := range parsed.Recipients {
encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey) encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Check that there is not a nonce in the unprotected header // Check that there is not a nonce in the unprotected header
if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.Nonce != "" { if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.getNonce() != "" {
return nil, ErrUnprotectedNonce return nil, ErrUnprotectedNonce
} }
...@@ -172,7 +183,7 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) { ...@@ -172,7 +183,7 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
for _, recipient := range obj.recipients { for _, recipient := range obj.recipients {
headers := obj.mergedHeaders(&recipient) headers := obj.mergedHeaders(&recipient)
if headers.Alg == "" || headers.Enc == "" { if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers") return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
} }
} }
...@@ -186,38 +197,38 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) { ...@@ -186,38 +197,38 @@ func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
} }
// parseEncryptedCompact parses a message in compact format. // parseEncryptedCompact parses a message in compact format.
func parseEncryptedCompact(input string) (*JsonWebEncryption, error) { func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
parts := strings.Split(input, ".") parts := strings.Split(input, ".")
if len(parts) != 5 { if len(parts) != 5 {
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts") return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
} }
rawProtected, err := base64URLDecode(parts[0]) rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
encryptedKey, err := base64URLDecode(parts[1]) encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
iv, err := base64URLDecode(parts[2]) iv, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil { if err != nil {
return nil, err return nil, err
} }
ciphertext, err := base64URLDecode(parts[3]) ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
if err != nil { if err != nil {
return nil, err return nil, err
} }
tag, err := base64URLDecode(parts[4]) tag, err := base64.RawURLEncoding.DecodeString(parts[4])
if err != nil { if err != nil {
return nil, err return nil, err
} }
raw := &rawJsonWebEncryption{ raw := &rawJSONWebEncryption{
Protected: newBuffer(rawProtected), Protected: newBuffer(rawProtected),
EncryptedKey: newBuffer(encryptedKey), EncryptedKey: newBuffer(encryptedKey),
Iv: newBuffer(iv), Iv: newBuffer(iv),
...@@ -229,7 +240,7 @@ func parseEncryptedCompact(input string) (*JsonWebEncryption, error) { ...@@ -229,7 +240,7 @@ func parseEncryptedCompact(input string) (*JsonWebEncryption, error) {
} }
// CompactSerialize serializes an object using the compact serialization format. // CompactSerialize serializes an object using the compact serialization format.
func (obj JsonWebEncryption) CompactSerialize() (string, error) { func (obj JSONWebEncryption) CompactSerialize() (string, error) {
if len(obj.recipients) != 1 || obj.unprotected != nil || if len(obj.recipients) != 1 || obj.unprotected != nil ||
obj.protected == nil || obj.recipients[0].header != nil { obj.protected == nil || obj.recipients[0].header != nil {
return "", ErrNotSupported return "", ErrNotSupported
...@@ -239,16 +250,16 @@ func (obj JsonWebEncryption) CompactSerialize() (string, error) { ...@@ -239,16 +250,16 @@ func (obj JsonWebEncryption) CompactSerialize() (string, error) {
return fmt.Sprintf( return fmt.Sprintf(
"%s.%s.%s.%s.%s", "%s.%s.%s.%s.%s",
base64URLEncode(serializedProtected), base64.RawURLEncoding.EncodeToString(serializedProtected),
base64URLEncode(obj.recipients[0].encryptedKey), base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey),
base64URLEncode(obj.iv), base64.RawURLEncoding.EncodeToString(obj.iv),
base64URLEncode(obj.ciphertext), base64.RawURLEncoding.EncodeToString(obj.ciphertext),
base64URLEncode(obj.tag)), nil base64.RawURLEncoding.EncodeToString(obj.tag)), nil
} }
// FullSerialize serializes an object using the full JSON serialization format. // FullSerialize serializes an object using the full JSON serialization format.
func (obj JsonWebEncryption) FullSerialize() string { func (obj JSONWebEncryption) FullSerialize() string {
raw := rawJsonWebEncryption{ raw := rawJSONWebEncryption{
Unprotected: obj.unprotected, Unprotected: obj.unprotected,
Iv: newBuffer(obj.iv), Iv: newBuffer(obj.iv),
Ciphertext: newBuffer(obj.ciphertext), Ciphertext: newBuffer(obj.ciphertext),
...@@ -262,7 +273,7 @@ func (obj JsonWebEncryption) FullSerialize() string { ...@@ -262,7 +273,7 @@ func (obj JsonWebEncryption) FullSerialize() string {
for _, recipient := range obj.recipients { for _, recipient := range obj.recipients {
info := rawRecipientInfo{ info := rawRecipientInfo{
Header: recipient.header, Header: recipient.header,
EncryptedKey: base64URLEncode(recipient.encryptedKey), EncryptedKey: base64.RawURLEncoding.EncodeToString(recipient.encryptedKey),
} }
raw.Recipients = append(raw.Recipients, info) raw.Recipients = append(raw.Recipients, info)
} }
......
...@@ -17,14 +17,16 @@ ...@@ -17,14 +17,16 @@
package jose package jose
import ( import (
"encoding/base64"
"errors"
"fmt" "fmt"
"strings" "strings"
"github.com/square/go-jose/json" "gopkg.in/square/go-jose.v2/json"
) )
// rawJsonWebSignature represents a raw JWS JSON object. Used for parsing/serializing. // rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
type rawJsonWebSignature struct { type rawJSONWebSignature struct {
Payload *byteBuffer `json:"payload,omitempty"` Payload *byteBuffer `json:"payload,omitempty"`
Signatures []rawSignatureInfo `json:"signatures,omitempty"` Signatures []rawSignatureInfo `json:"signatures,omitempty"`
Protected *byteBuffer `json:"protected,omitempty"` Protected *byteBuffer `json:"protected,omitempty"`
...@@ -39,16 +41,19 @@ type rawSignatureInfo struct { ...@@ -39,16 +41,19 @@ type rawSignatureInfo struct {
Signature *byteBuffer `json:"signature,omitempty"` Signature *byteBuffer `json:"signature,omitempty"`
} }
// JsonWebSignature represents a signed JWS object after parsing. // JSONWebSignature represents a signed JWS object after parsing.
type JsonWebSignature struct { type JSONWebSignature struct {
payload []byte 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 Signatures []Signature
} }
// Signature represents a single signature over the JWS payload and protected header. // Signature represents a single signature over the JWS payload and protected header.
type Signature struct { type Signature struct {
// Header fields, such as the signature algorithm // Header fields, such as the signature algorithm
Header JoseHeader Header Header
// The actual signature value // The actual signature value
Signature []byte Signature []byte
...@@ -59,7 +64,7 @@ type Signature struct { ...@@ -59,7 +64,7 @@ type Signature struct {
} }
// ParseSigned parses a signed message in compact or full serialization format. // ParseSigned parses a signed message in compact or full serialization format.
func ParseSigned(input string) (*JsonWebSignature, error) { func ParseSigned(input string) (*JSONWebSignature, error) {
input = stripWhitespace(input) input = stripWhitespace(input)
if strings.HasPrefix(input, "{") { if strings.HasPrefix(input, "{") {
return parseSignedFull(input) return parseSignedFull(input)
...@@ -77,25 +82,25 @@ func (sig Signature) mergedHeaders() rawHeader { ...@@ -77,25 +82,25 @@ func (sig Signature) mergedHeaders() rawHeader {
} }
// Compute data to be signed // Compute data to be signed
func (obj JsonWebSignature) computeAuthData(signature *Signature) []byte { func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte {
var serializedProtected string var serializedProtected string
if signature.original != nil && signature.original.Protected != nil { if signature.original != nil && signature.original.Protected != nil {
serializedProtected = signature.original.Protected.base64() serializedProtected = signature.original.Protected.base64()
} else if signature.protected != nil { } else if signature.protected != nil {
serializedProtected = base64URLEncode(mustSerializeJSON(signature.protected)) serializedProtected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON(signature.protected))
} else { } else {
serializedProtected = "" serializedProtected = ""
} }
return []byte(fmt.Sprintf("%s.%s", return []byte(fmt.Sprintf("%s.%s",
serializedProtected, serializedProtected,
base64URLEncode(obj.payload))) base64.RawURLEncoding.EncodeToString(obj.payload)))
} }
// parseSignedFull parses a message in full format. // parseSignedFull parses a message in full format.
func parseSignedFull(input string) (*JsonWebSignature, error) { func parseSignedFull(input string) (*JSONWebSignature, error) {
var parsed rawJsonWebSignature var parsed rawJSONWebSignature
err := json.Unmarshal([]byte(input), &parsed) err := json.Unmarshal([]byte(input), &parsed)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -105,12 +110,12 @@ func parseSignedFull(input string) (*JsonWebSignature, error) { ...@@ -105,12 +110,12 @@ func parseSignedFull(input string) (*JsonWebSignature, error) {
} }
// sanitized produces a cleaned-up JWS object from the raw JSON. // sanitized produces a cleaned-up JWS object from the raw JSON.
func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) { func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
if parsed.Payload == nil { if parsed.Payload == nil {
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message") return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
} }
obj := &JsonWebSignature{ obj := &JSONWebSignature{
payload: parsed.Payload.bytes(), payload: parsed.Payload.bytes(),
Signatures: make([]Signature, len(parsed.Signatures)), Signatures: make([]Signature, len(parsed.Signatures)),
} }
...@@ -126,7 +131,8 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) { ...@@ -126,7 +131,8 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
} }
} }
if parsed.Header != nil && parsed.Header.Nonce != "" { // Check that there is not a nonce in the unprotected header
if parsed.Header != nil && parsed.Header.getNonce() != "" {
return nil, ErrUnprotectedNonce return nil, ErrUnprotectedNonce
} }
...@@ -147,7 +153,18 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) { ...@@ -147,7 +153,18 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
Signature: parsed.Signature, Signature: parsed.Signature,
} }
signature.Header = signature.mergedHeaders().sanitized() var err error
signature.Header, err = signature.mergedHeaders().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) obj.Signatures = append(obj.Signatures, signature)
} }
...@@ -161,46 +178,57 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) { ...@@ -161,46 +178,57 @@ func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
} }
// Check that there is not a nonce in the unprotected header // Check that there is not a nonce in the unprotected header
if sig.Header != nil && sig.Header.Nonce != "" { if sig.Header != nil && sig.Header.getNonce() != "" {
return nil, ErrUnprotectedNonce return nil, ErrUnprotectedNonce
} }
var err error
obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized()
if err != nil {
return nil, err
}
obj.Signatures[i].Signature = sig.Signature.bytes() 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 // Copy value of sig
original := sig original := sig
obj.Signatures[i].header = sig.Header obj.Signatures[i].header = sig.Header
obj.Signatures[i].original = &original obj.Signatures[i].original = &original
obj.Signatures[i].Header = obj.Signatures[i].mergedHeaders().sanitized()
} }
return obj, nil return obj, nil
} }
// parseSignedCompact parses a message in compact format. // parseSignedCompact parses a message in compact format.
func parseSignedCompact(input string) (*JsonWebSignature, error) { func parseSignedCompact(input string) (*JSONWebSignature, error) {
parts := strings.Split(input, ".") parts := strings.Split(input, ".")
if len(parts) != 3 { if len(parts) != 3 {
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts") return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
} }
rawProtected, err := base64URLDecode(parts[0]) rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
payload, err := base64URLDecode(parts[1]) payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
signature, err := base64URLDecode(parts[2]) signature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil { if err != nil {
return nil, err return nil, err
} }
raw := &rawJsonWebSignature{ raw := &rawJSONWebSignature{
Payload: newBuffer(payload), Payload: newBuffer(payload),
Protected: newBuffer(rawProtected), Protected: newBuffer(rawProtected),
Signature: newBuffer(signature), Signature: newBuffer(signature),
...@@ -209,7 +237,7 @@ func parseSignedCompact(input string) (*JsonWebSignature, error) { ...@@ -209,7 +237,7 @@ func parseSignedCompact(input string) (*JsonWebSignature, error) {
} }
// CompactSerialize serializes an object using the compact serialization format. // CompactSerialize serializes an object using the compact serialization format.
func (obj JsonWebSignature) CompactSerialize() (string, error) { func (obj JSONWebSignature) CompactSerialize() (string, error) {
if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil { if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
return "", ErrNotSupported return "", ErrNotSupported
} }
...@@ -218,14 +246,14 @@ func (obj JsonWebSignature) CompactSerialize() (string, error) { ...@@ -218,14 +246,14 @@ func (obj JsonWebSignature) CompactSerialize() (string, error) {
return fmt.Sprintf( return fmt.Sprintf(
"%s.%s.%s", "%s.%s.%s",
base64URLEncode(serializedProtected), base64.RawURLEncoding.EncodeToString(serializedProtected),
base64URLEncode(obj.payload), base64.RawURLEncoding.EncodeToString(obj.payload),
base64URLEncode(obj.Signatures[0].Signature)), nil base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)), nil
} }
// FullSerialize serializes an object using the full JSON serialization format. // FullSerialize serializes an object using the full JSON serialization format.
func (obj JsonWebSignature) FullSerialize() string { func (obj JSONWebSignature) FullSerialize() string {
raw := rawJsonWebSignature{ raw := rawJSONWebSignature{
Payload: newBuffer(obj.payload), Payload: newBuffer(obj.payload),
} }
......
...@@ -20,6 +20,8 @@ import ( ...@@ -20,6 +20,8 @@ import (
"crypto/elliptic" "crypto/elliptic"
"errors" "errors"
"fmt" "fmt"
"gopkg.in/square/go-jose.v2/json"
) )
// KeyAlgorithm represents a key management algorithm. // KeyAlgorithm represents a key management algorithm.
...@@ -34,6 +36,9 @@ type ContentEncryption string ...@@ -34,6 +36,9 @@ type ContentEncryption string
// CompressionAlgorithm represents an algorithm used for plaintext compression. // CompressionAlgorithm represents an algorithm used for plaintext compression.
type CompressionAlgorithm string type CompressionAlgorithm string
// ContentType represents type of the contained data.
type ContentType string
var ( var (
// ErrCryptoFailure represents an error in cryptographic primitive. This // ErrCryptoFailure represents an error in cryptographic primitive. This
// occurs when, for example, a message had an invalid authentication tag or // occurs when, for example, a message had an invalid authentication tag or
...@@ -63,6 +68,7 @@ var ( ...@@ -63,6 +68,7 @@ var (
// Key management algorithms // Key management algorithms
const ( const (
ED25519 = KeyAlgorithm("ED25519")
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5 RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1 RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256 RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
...@@ -84,6 +90,7 @@ const ( ...@@ -84,6 +90,7 @@ const (
// Signature algorithms // Signature algorithms
const ( const (
EdDSA = SignatureAlgorithm("EdDSA")
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256 HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384 HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512 HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
...@@ -114,84 +121,265 @@ const ( ...@@ -114,84 +121,265 @@ const (
DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951) DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
) )
// A key in the protected header of a JWS object. Use of the Header...
// constants is preferred to enhance type safety.
type HeaderKey string
const (
HeaderType HeaderKey = "typ" // string
HeaderContentType = "cty" // string
// These are set by go-jose and shouldn't need to be set by consumers of the
// library.
headerAlgorithm = "alg" // string
headerEncryption = "enc" // ContentEncryption
headerCompression = "zip" // CompressionAlgorithm
headerCritical = "crit" // []string
headerAPU = "apu" // *byteBuffer
headerAPV = "apv" // *byteBuffer
headerEPK = "epk" // *JSONWebKey
headerIV = "iv" // *byteBuffer
headerTag = "tag" // *byteBuffer
headerJWK = "jwk" // *JSONWebKey
headerKeyID = "kid" // string
headerNonce = "nonce" // string
)
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing). // rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
type rawHeader struct { //
Alg string `json:"alg,omitempty"` // The decoding of the constituent items is deferred because we want to marshal
Enc ContentEncryption `json:"enc,omitempty"` // some members into particular structs rather than generic maps, but at the
Zip CompressionAlgorithm `json:"zip,omitempty"` // same time we need to receive any extra fields unhandled by this library to
Crit []string `json:"crit,omitempty"` // pass through to consuming code in case it wants to examine them.
Apu *byteBuffer `json:"apu,omitempty"` type rawHeader map[HeaderKey]*json.RawMessage
Apv *byteBuffer `json:"apv,omitempty"`
Epk *JsonWebKey `json:"epk,omitempty"` // Header represents the read-only JOSE header for JWE/JWS objects.
Iv *byteBuffer `json:"iv,omitempty"` type Header struct {
Tag *byteBuffer `json:"tag,omitempty"`
Jwk *JsonWebKey `json:"jwk,omitempty"`
Kid string `json:"kid,omitempty"`
Nonce string `json:"nonce,omitempty"`
}
// JoseHeader represents the read-only JOSE header for JWE/JWS objects.
type JoseHeader struct {
KeyID string KeyID string
JsonWebKey *JsonWebKey JSONWebKey *JSONWebKey
Algorithm string Algorithm string
Nonce string Nonce string
// Any headers not recognised above get unmarshaled from JSON in a generic
// manner and placed in this map.
ExtraHeaders map[HeaderKey]interface{}
} }
// sanitized produces a cleaned-up header object from the raw JSON. func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
func (parsed rawHeader) sanitized() JoseHeader { b, err := json.Marshal(v)
return JoseHeader{ if err != nil {
KeyID: parsed.Kid, return err
JsonWebKey: parsed.Jwk,
Algorithm: parsed.Alg,
Nonce: parsed.Nonce,
} }
parsed[k] = makeRawMessage(b)
return nil
} }
// Merge headers from src into dst, giving precedence to headers from l. // getString gets a string from the raw JSON, defaulting to "".
func (dst *rawHeader) merge(src *rawHeader) { func (parsed rawHeader) getString(k HeaderKey) string {
if src == nil { v, ok := parsed[k]
return if !ok {
return ""
} }
var s string
err := json.Unmarshal(*v, &s)
if err != nil {
return ""
}
return s
}
if dst.Alg == "" { // getByteBuffer gets a byte buffer from the raw JSON. Returns (nil, nil) if
dst.Alg = src.Alg // not specified.
func (parsed rawHeader) getByteBuffer(k HeaderKey) (*byteBuffer, error) {
v := parsed[k]
if v == nil {
return nil, nil
} }
if dst.Enc == "" { var bb *byteBuffer
dst.Enc = src.Enc err := json.Unmarshal(*v, &bb)
if err != nil {
return nil, err
} }
if dst.Zip == "" { return bb, nil
dst.Zip = src.Zip }
// getAlgorithm extracts parsed "alg" from the raw JSON as a KeyAlgorithm.
func (parsed rawHeader) getAlgorithm() KeyAlgorithm {
return KeyAlgorithm(parsed.getString(headerAlgorithm))
}
// getSignatureAlgorithm extracts parsed "alg" from the raw JSON as a SignatureAlgorithm.
func (parsed rawHeader) getSignatureAlgorithm() SignatureAlgorithm {
return SignatureAlgorithm(parsed.getString(headerAlgorithm))
}
// getEncryption extracts parsed "enc" from the raw JSON.
func (parsed rawHeader) getEncryption() ContentEncryption {
return ContentEncryption(parsed.getString(headerEncryption))
}
// getCompression extracts parsed "zip" from the raw JSON.
func (parsed rawHeader) getCompression() CompressionAlgorithm {
return CompressionAlgorithm(parsed.getString(headerCompression))
}
func (parsed rawHeader) getNonce() string {
return parsed.getString(headerNonce)
}
// getEPK extracts parsed "epk" from the raw JSON.
func (parsed rawHeader) getEPK() (*JSONWebKey, error) {
v := parsed[headerEPK]
if v == nil {
return nil, nil
} }
if dst.Crit == nil { var epk *JSONWebKey
dst.Crit = src.Crit err := json.Unmarshal(*v, &epk)
if err != nil {
return nil, err
} }
if dst.Crit == nil { return epk, nil
dst.Crit = src.Crit }
// getAPU extracts parsed "apu" from the raw JSON.
func (parsed rawHeader) getAPU() (*byteBuffer, error) {
return parsed.getByteBuffer(headerAPU)
}
// getAPV extracts parsed "apv" from the raw JSON.
func (parsed rawHeader) getAPV() (*byteBuffer, error) {
return parsed.getByteBuffer(headerAPV)
}
// getIV extracts parsed "iv" frpom the raw JSON.
func (parsed rawHeader) getIV() (*byteBuffer, error) {
return parsed.getByteBuffer(headerIV)
}
// getTag extracts parsed "tag" frpom the raw JSON.
func (parsed rawHeader) getTag() (*byteBuffer, error) {
return parsed.getByteBuffer(headerTag)
}
// getJWK extracts parsed "jwk" from the raw JSON.
func (parsed rawHeader) getJWK() (*JSONWebKey, error) {
v := parsed[headerJWK]
if v == nil {
return nil, nil
} }
if dst.Apu == nil { var jwk *JSONWebKey
dst.Apu = src.Apu err := json.Unmarshal(*v, &jwk)
if err != nil {
return nil, err
} }
if dst.Apv == nil { return jwk, nil
dst.Apv = src.Apv }
// getCritical extracts parsed "crit" from the raw JSON. If omitted, it
// returns an empty slice.
func (parsed rawHeader) getCritical() ([]string, error) {
v := parsed[headerCritical]
if v == nil {
return nil, nil
} }
if dst.Epk == nil {
dst.Epk = src.Epk var q []string
err := json.Unmarshal(*v, &q)
if err != nil {
return nil, err
}
return q, nil
}
// sanitized produces a cleaned-up header object from the raw JSON.
func (parsed rawHeader) sanitized() (h Header, err error) {
for k, v := range parsed {
if v == nil {
continue
}
switch k {
case headerJWK:
var jwk *JSONWebKey
err = json.Unmarshal(*v, &jwk)
if err != nil {
err = fmt.Errorf("failed to unmarshal JWK: %v: %#v", err, string(*v))
return
}
h.JSONWebKey = jwk
case headerKeyID:
var s string
err = json.Unmarshal(*v, &s)
if err != nil {
err = fmt.Errorf("failed to unmarshal key ID: %v: %#v", err, string(*v))
return
}
h.KeyID = s
case headerAlgorithm:
var s string
err = json.Unmarshal(*v, &s)
if err != nil {
err = fmt.Errorf("failed to unmarshal algorithm: %v: %#v", err, string(*v))
return
}
h.Algorithm = s
case headerNonce:
var s string
err = json.Unmarshal(*v, &s)
if err != nil {
err = fmt.Errorf("failed to unmarshal nonce: %v: %#v", err, string(*v))
return
}
h.Nonce = s
default:
if h.ExtraHeaders == nil {
h.ExtraHeaders = map[HeaderKey]interface{}{}
}
var v2 interface{}
err = json.Unmarshal(*v, &v2)
if err != nil {
err = fmt.Errorf("failed to unmarshal value: %v: %#v", err, string(*v))
return
}
h.ExtraHeaders[k] = v2
}
} }
if dst.Iv == nil { return
dst.Iv = src.Iv }
func (dst rawHeader) isSet(k HeaderKey) bool {
dvr := dst[k]
if dvr == nil {
return false
} }
if dst.Tag == nil {
dst.Tag = src.Tag var dv interface{}
err := json.Unmarshal(*dvr, &dv)
if err != nil {
return true
} }
if dst.Kid == "" {
dst.Kid = src.Kid if dvStr, ok := dv.(string); ok {
return dvStr != ""
} }
if dst.Jwk == nil {
dst.Jwk = src.Jwk return true
}
// Merge headers from src into dst, giving precedence to headers from l.
func (dst rawHeader) merge(src *rawHeader) {
if src == nil {
return
} }
if dst.Nonce == "" {
dst.Nonce = src.Nonce for k, v := range *src {
if dst.isSet(k) {
continue
}
dst[k] = v
} }
} }
...@@ -222,3 +410,8 @@ func curveSize(crv elliptic.Curve) int { ...@@ -222,3 +410,8 @@ func curveSize(crv elliptic.Curve) int {
return div + 1 return div + 1
} }
func makeRawMessage(b []byte) *json.RawMessage {
rm := json.RawMessage(b)
return &rm
}
...@@ -25,10 +25,11 @@ import ( ...@@ -25,10 +25,11 @@ import (
"crypto/sha512" "crypto/sha512"
"crypto/subtle" "crypto/subtle"
"errors" "errors"
"fmt"
"hash" "hash"
"io" "io"
"github.com/square/go-jose/cipher" "gopkg.in/square/go-jose.v2/cipher"
) )
// Random reader (stubbed out in tests) // Random reader (stubbed out in tests)
...@@ -229,11 +230,12 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie ...@@ -229,11 +230,12 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
return recipientInfo{}, err return recipientInfo{}, err
} }
header := &rawHeader{}
header.set(headerIV, newBuffer(parts.iv))
header.set(headerTag, newBuffer(parts.tag))
return recipientInfo{ return recipientInfo{
header: &rawHeader{ header: header,
Iv: newBuffer(parts.iv),
Tag: newBuffer(parts.tag),
},
encryptedKey: parts.ciphertext, encryptedKey: parts.ciphertext,
}, nil }, nil
case A128KW, A192KW, A256KW: case A128KW, A192KW, A256KW:
...@@ -258,7 +260,7 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie ...@@ -258,7 +260,7 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
// Decrypt the content encryption key. // Decrypt the content encryption key.
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
switch KeyAlgorithm(headers.Alg) { switch headers.getAlgorithm() {
case DIRECT: case DIRECT:
cek := make([]byte, len(ctx.key)) cek := make([]byte, len(ctx.key))
copy(cek, ctx.key) copy(cek, ctx.key)
...@@ -266,10 +268,19 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien ...@@ -266,10 +268,19 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
case A128GCMKW, A192GCMKW, A256GCMKW: case A128GCMKW, A192GCMKW, A256GCMKW:
aead := newAESGCM(len(ctx.key)) aead := newAESGCM(len(ctx.key))
iv, err := headers.getIV()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err)
}
tag, err := headers.getTag()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err)
}
parts := &aeadParts{ parts := &aeadParts{
iv: headers.Iv.bytes(), iv: iv.bytes(),
ciphertext: recipient.encryptedKey, ciphertext: recipient.encryptedKey,
tag: headers.Tag.bytes(), tag: tag.bytes(),
} }
cek, err := aead.decrypt(ctx.key, []byte{}, parts) cek, err := aead.decrypt(ctx.key, []byte{}, parts)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment