521 lines
15 KiB
Go
521 lines
15 KiB
Go
/*-
|
|
* 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/elliptic"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gopkg.in/square/go-jose.v2/json"
|
|
)
|
|
|
|
// KeyAlgorithm represents a key management algorithm.
|
|
type KeyAlgorithm string
|
|
|
|
// SignatureAlgorithm represents a signature (or MAC) algorithm.
|
|
type SignatureAlgorithm string
|
|
|
|
// ContentEncryption represents a content encryption algorithm.
|
|
type ContentEncryption string
|
|
|
|
// CompressionAlgorithm represents an algorithm used for plaintext compression.
|
|
type CompressionAlgorithm string
|
|
|
|
// ContentType represents type of the contained data.
|
|
type ContentType string
|
|
|
|
var (
|
|
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
|
// occurs when, for example, a message had an invalid authentication tag or
|
|
// could not be decrypted.
|
|
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
|
|
|
|
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
|
// supported. This occurs when trying to instantiate an encrypter for an
|
|
// algorithm that is not yet implemented.
|
|
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
|
|
|
|
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
|
// supported. This occurs when trying to instantiate an encrypter and passing
|
|
// it a key of an unrecognized type or with unsupported parameters, such as
|
|
// an RSA private key with more than two primes.
|
|
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
|
|
|
// ErrInvalidKeySize indicates that the given key is not the correct size
|
|
// for the selected algorithm. This can occur, for example, when trying to
|
|
// encrypt with AES-256 but passing only a 128-bit key as input.
|
|
ErrInvalidKeySize = errors.New("square/go-jose: invalid key size for algorithm")
|
|
|
|
// ErrNotSupported serialization of object is not supported. This occurs when
|
|
// trying to compact-serialize an object which can't be represented in
|
|
// compact form.
|
|
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
|
|
|
|
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
|
// nonce header parameter was included in an unprotected header object.
|
|
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
|
|
)
|
|
|
|
// Key management algorithms
|
|
const (
|
|
ED25519 = KeyAlgorithm("ED25519")
|
|
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
|
|
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
|
|
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
|
|
A128KW = KeyAlgorithm("A128KW") // AES key wrap (128)
|
|
A192KW = KeyAlgorithm("A192KW") // AES key wrap (192)
|
|
A256KW = KeyAlgorithm("A256KW") // AES key wrap (256)
|
|
DIRECT = KeyAlgorithm("dir") // Direct encryption
|
|
ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES
|
|
ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128)
|
|
ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192)
|
|
ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256)
|
|
A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128)
|
|
A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192)
|
|
A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256)
|
|
PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
|
|
PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
|
|
PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
|
|
)
|
|
|
|
// Signature algorithms
|
|
const (
|
|
EdDSA = SignatureAlgorithm("EdDSA")
|
|
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
|
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
|
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
|
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
|
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
|
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
|
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
|
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
|
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
|
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
|
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
|
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
|
)
|
|
|
|
// Content encryption algorithms
|
|
const (
|
|
A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
|
|
A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
|
|
A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
|
|
A128GCM = ContentEncryption("A128GCM") // AES-GCM (128)
|
|
A192GCM = ContentEncryption("A192GCM") // AES-GCM (192)
|
|
A256GCM = ContentEncryption("A256GCM") // AES-GCM (256)
|
|
)
|
|
|
|
// Compression algorithms
|
|
const (
|
|
NONE = CompressionAlgorithm("") // No compression
|
|
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
|
|
headerX5c = "x5c" // []*x509.Certificate
|
|
|
|
headerJWK = "jwk" // *JSONWebKey
|
|
headerKeyID = "kid" // string
|
|
headerNonce = "nonce" // string
|
|
headerB64 = "b64" // bool
|
|
|
|
headerP2C = "p2c" // *byteBuffer (int)
|
|
headerP2S = "p2s" // *byteBuffer ([]byte)
|
|
|
|
)
|
|
|
|
// supportedCritical is the set of supported extensions that are understood and processed.
|
|
var supportedCritical = map[string]bool{
|
|
headerB64: true,
|
|
}
|
|
|
|
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
|
|
//
|
|
// The decoding of the constituent items is deferred because we want to marshal
|
|
// some members into particular structs rather than generic maps, but at the
|
|
// same time we need to receive any extra fields unhandled by this library to
|
|
// pass through to consuming code in case it wants to examine them.
|
|
type rawHeader map[HeaderKey]*json.RawMessage
|
|
|
|
// Header represents the read-only JOSE header for JWE/JWS objects.
|
|
type Header struct {
|
|
KeyID string
|
|
JSONWebKey *JSONWebKey
|
|
Algorithm string
|
|
Nonce string
|
|
|
|
// Unverified certificate chain parsed from x5c header.
|
|
certificates []*x509.Certificate
|
|
|
|
// Any headers not recognised above get unmarshaled
|
|
// from JSON in a generic manner and placed in this map.
|
|
ExtraHeaders map[HeaderKey]interface{}
|
|
}
|
|
|
|
// Certificates verifies & returns the certificate chain present
|
|
// in the x5c header field of a message, if one was present. Returns
|
|
// an error if there was no x5c header present or the chain could
|
|
// not be validated with the given verify options.
|
|
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
|
if len(h.certificates) == 0 {
|
|
return nil, errors.New("square/go-jose: no x5c header present in message")
|
|
}
|
|
|
|
leaf := h.certificates[0]
|
|
if opts.Intermediates == nil {
|
|
opts.Intermediates = x509.NewCertPool()
|
|
for _, intermediate := range h.certificates[1:] {
|
|
opts.Intermediates.AddCert(intermediate)
|
|
}
|
|
}
|
|
|
|
return leaf.Verify(opts)
|
|
}
|
|
|
|
func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parsed[k] = makeRawMessage(b)
|
|
return nil
|
|
}
|
|
|
|
// getString gets a string from the raw JSON, defaulting to "".
|
|
func (parsed rawHeader) getString(k HeaderKey) string {
|
|
v, ok := parsed[k]
|
|
if !ok || v == nil {
|
|
return ""
|
|
}
|
|
var s string
|
|
err := json.Unmarshal(*v, &s)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return s
|
|
}
|
|
|
|
// getByteBuffer gets a byte buffer from the raw JSON. Returns (nil, nil) if
|
|
// not specified.
|
|
func (parsed rawHeader) getByteBuffer(k HeaderKey) (*byteBuffer, error) {
|
|
v := parsed[k]
|
|
if v == nil {
|
|
return nil, nil
|
|
}
|
|
var bb *byteBuffer
|
|
err := json.Unmarshal(*v, &bb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bb, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
var epk *JSONWebKey
|
|
err := json.Unmarshal(*v, &epk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return epk, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
var jwk *JSONWebKey
|
|
err := json.Unmarshal(*v, &jwk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return jwk, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
var q []string
|
|
err := json.Unmarshal(*v, &q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
// getS2C extracts parsed "p2c" from the raw JSON.
|
|
func (parsed rawHeader) getP2C() (int, error) {
|
|
v := parsed[headerP2C]
|
|
if v == nil {
|
|
return 0, nil
|
|
}
|
|
|
|
var p2c int
|
|
err := json.Unmarshal(*v, &p2c)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return p2c, nil
|
|
}
|
|
|
|
// getS2S extracts parsed "p2s" from the raw JSON.
|
|
func (parsed rawHeader) getP2S() (*byteBuffer, error) {
|
|
return parsed.getByteBuffer(headerP2S)
|
|
}
|
|
|
|
// getB64 extracts parsed "b64" from the raw JSON, defaulting to true.
|
|
func (parsed rawHeader) getB64() (bool, error) {
|
|
v := parsed[headerB64]
|
|
if v == nil {
|
|
return true, nil
|
|
}
|
|
|
|
var b64 bool
|
|
err := json.Unmarshal(*v, &b64)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
return b64, 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
|
|
case headerX5c:
|
|
c := []string{}
|
|
err = json.Unmarshal(*v, &c)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v))
|
|
return
|
|
}
|
|
h.certificates, err = parseCertificateChain(c)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v))
|
|
return
|
|
}
|
|
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
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
|
|
out := make([]*x509.Certificate, len(chain))
|
|
for i, cert := range chain {
|
|
raw, err := base64.StdEncoding.DecodeString(cert)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out[i], err = x509.ParseCertificate(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (dst rawHeader) isSet(k HeaderKey) bool {
|
|
dvr := dst[k]
|
|
if dvr == nil {
|
|
return false
|
|
}
|
|
|
|
var dv interface{}
|
|
err := json.Unmarshal(*dvr, &dv)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
|
|
if dvStr, ok := dv.(string); ok {
|
|
return dvStr != ""
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Merge headers from src into dst, giving precedence to headers from l.
|
|
func (dst rawHeader) merge(src *rawHeader) {
|
|
if src == nil {
|
|
return
|
|
}
|
|
|
|
for k, v := range *src {
|
|
if dst.isSet(k) {
|
|
continue
|
|
}
|
|
|
|
dst[k] = v
|
|
}
|
|
}
|
|
|
|
// Get JOSE name of curve
|
|
func curveName(crv elliptic.Curve) (string, error) {
|
|
switch crv {
|
|
case elliptic.P256():
|
|
return "P-256", nil
|
|
case elliptic.P384():
|
|
return "P-384", nil
|
|
case elliptic.P521():
|
|
return "P-521", nil
|
|
default:
|
|
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
|
}
|
|
}
|
|
|
|
// Get size of curve in bytes
|
|
func curveSize(crv elliptic.Curve) int {
|
|
bits := crv.Params().BitSize
|
|
|
|
div := bits / 8
|
|
mod := bits % 8
|
|
|
|
if mod == 0 {
|
|
return div
|
|
}
|
|
|
|
return div + 1
|
|
}
|
|
|
|
func makeRawMessage(b []byte) *json.RawMessage {
|
|
rm := json.RawMessage(b)
|
|
return &rm
|
|
}
|