pkg/sdk/security/crypto/encoder.go (314 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 crypto
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/pkg/errors"
"go.step.sm/crypto/pemutil"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/elastic/harp/build/fips"
"github.com/elastic/harp/pkg/sdk/security/crypto/bech32"
"github.com/elastic/harp/pkg/sdk/types"
// Import Blake2b
_ "golang.org/x/crypto/blake2b"
"golang.org/x/crypto/ssh"
)
// ToJWK encodes given key using JWK.
func ToJWK(key interface{}) (string, error) {
// Check key
if types.IsNil(key) {
return "", fmt.Errorf("unable to encode nil key")
}
// Wrap key
keyWrapper := jose.JSONWebKey{Key: key, KeyID: ""}
// Don't process Ed25519 keys
if fips.Enabled() {
switch key.(type) {
case ed25519.PrivateKey, ed25519.PublicKey:
return "", errors.New("ed25519 key processing is disabled in FIPS Mode")
}
}
// Generate thumbprint
thumb, err := keyWrapper.Thumbprint(crypto.SHA512_256)
if err != nil {
return "", err
}
// Assign thumbprint
keyWrapper.KeyID = base64.URLEncoding.EncodeToString(thumb)
// Marshal private as JSON
payload, err := keyWrapper.MarshalJSON()
if err != nil {
return "", err
}
// No error
return string(payload), nil
}
// FromJWK parses a JWK and return wrapped keys.
func FromJWK(jwk string) (interface{}, error) {
var k jose.JSONWebKey
// Decode JWK
if err := json.Unmarshal([]byte(jwk), &k); err != nil {
return nil, fmt.Errorf("unable to decode JWK: %w", err)
}
// Don't process Ed25519 keys
if fips.Enabled() {
switch k.Key.(type) {
case ed25519.PrivateKey, ed25519.PublicKey:
return "", errors.New("ed25519 key processing is disabled in FIPS Mode")
}
}
if k.IsPublic() {
// No error
return struct {
Public interface{}
}{
Public: k.Key,
}, nil
}
// No error
return struct {
Private interface{}
Public interface{}
}{
Private: k.Key,
Public: k.Public().Key,
}, nil
}
// ToPEM encodes the given key using PEM.
func ToPEM(key interface{}) (string, error) {
// Check key
if types.IsNil(key) {
return "", fmt.Errorf("unable to encode nil key")
}
// Don't process Ed25519 keys
if fips.Enabled() {
switch key.(type) {
case ed25519.PrivateKey, ed25519.PublicKey:
return "", errors.New("ed25519 key processing is disabled in FIPS Mode")
}
}
// Delegate to smallstep library
pemBlock, err := pemutil.Serialize(key, pemutil.WithPKCS8(true))
if err != nil {
return "", fmt.Errorf("unable to serialize input as PEM: %w", err)
}
return string(pem.EncodeToMemory(pemBlock)), nil
}
// KeyToBytes encodes the given crypto key as a byte array.
func KeyToBytes(key interface{}) ([]byte, error) {
// Check key
if types.IsNil(key) {
return nil, fmt.Errorf("unable to encode nil key")
}
var (
out []byte
err error
)
switch k := key.(type) {
// Private keys ------------------------------------------------------------
case *rsa.PrivateKey, *ecdsa.PrivateKey:
out, err = x509.MarshalPKCS8PrivateKey(k)
if err != nil {
return nil, err
}
case ed25519.PrivateKey:
if fips.Enabled() {
return nil, errors.New("ed25519 private key processing is disabled in FIPS Mode")
}
out = []byte(k)
// Public keys ------------------------------------------------------------
case *rsa.PublicKey:
out, err = x509.MarshalPKIXPublicKey(k)
if err != nil {
return nil, err
}
case *ecdsa.PublicKey:
out = elliptic.MarshalCompressed(k.Curve, k.X, k.Y)
case ed25519.PublicKey:
if fips.Enabled() {
return nil, errors.New("ed25519 private key processing is disabled in FIPS Mode")
}
out = []byte(k)
default:
return nil, fmt.Errorf("given key type is not supported")
}
return out, nil
}
// EncryptPEM returns an encrypted PEM block using the given passphrase.
func EncryptPEM(pemData, passphrase string) (string, error) {
// Check passphrase
if len(passphrase) < 32 {
return "", fmt.Errorf("passphrase must contains more than 32 characters, usage of a diceware passphrase is recommended")
}
// Decode PEM
out, err := pemutil.Parse([]byte(pemData))
if err != nil {
return "", fmt.Errorf("unable to parse input PEM data: %w", err)
}
// Encrypt PEM
encryptedBlock, err := pemutil.Serialize(out, pemutil.WithPKCS8(true), pemutil.WithPassword([]byte(passphrase)))
if err != nil {
return "", fmt.Errorf("unable to export encrypted PEM: %w", err)
}
// Build output
outPem := pem.EncodeToMemory(encryptedBlock)
// No error
return string(outPem), nil
}
// ToSSH encodes the given key as SSH key.
func ToSSH(key interface{}) (string, error) {
var result []byte
// Check key
if types.IsNil(key) {
return "", fmt.Errorf("unable to encode nil key")
}
switch k := key.(type) {
// Public keys ------------------------------------------------------------
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
if _, ok := k.(ed25519.PublicKey); ok && fips.Enabled() {
return "", errors.New("ed25519 public key processing is disabled in FIPS Mode")
}
pubKey, err := ssh.NewPublicKey(k)
if err != nil {
return "", fmt.Errorf("unable to convert key as ssh public key: %w", err)
}
result = ssh.MarshalAuthorizedKey(pubKey)
// Private keys --------------------------------------------------------
default:
if _, ok := k.(ed25519.PrivateKey); ok && fips.Enabled() {
return "", errors.New("ed25519 private key processing is disabled in FIPS Mode")
}
pemBlock, err := pemutil.Serialize(key, pemutil.WithOpenSSH(true))
if err != nil {
return "", fmt.Errorf("unable to encode SSH key: %w", err)
}
result = pem.EncodeToMemory(pemBlock)
}
// No error
return string(result), nil
}
// EncryptJWE encrypts input as JWE token.
func EncryptJWE(key string, payload interface{}) (string, error) {
// Prepare encrypter
encrypter, err := jose.NewEncrypter(jose.A128GCM, jose.Recipient{Algorithm: jose.PBES2_HS256_A128KW, Key: key}, nil)
if err != nil {
return "", fmt.Errorf("unable to initialize JWE encrypter: %w", err)
}
// Marshal payload
payloadBytes, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("unable to marshal content: %w", err)
}
// Encrypt
object, err := encrypter.Encrypt(payloadBytes)
if err != nil {
return "", fmt.Errorf("unable to encrypt payload: %w", err)
}
// Return JWE
return object.CompactSerialize()
}
// DecryptJWE decrypt a JWE token.
func DecryptJWE(key, token string) (interface{}, error) {
// Parse JWE token
object, err := jose.ParseEncrypted(token)
if err != nil {
return "", fmt.Errorf("unable to parse JWE assertion: %w", err)
}
// Decrypt token using given key.
payloadBytes, err := object.Decrypt(key)
if err != nil {
return "", fmt.Errorf("unable to decrypt JWE: %w", err)
}
// Decode payload
var data interface{}
if err := json.Unmarshal(payloadBytes, &data); err != nil {
return "", fmt.Errorf("unable to decode payload: %w", err)
}
// No error
return data, nil
}
// ToJWS returns a JWT token.
func ToJWS(payload, privkey interface{}) (string, error) {
var alg jose.SignatureAlgorithm
// Select appropriate algorithm
switch k := privkey.(type) {
case *rsa.PrivateKey:
alg = jose.RS256
case *ecdsa.PrivateKey:
switch k.Curve {
case elliptic.P256():
alg = jose.ES256
case elliptic.P384():
alg = jose.ES384
case elliptic.P521():
alg = jose.ES512
}
case ed25519.PrivateKey:
if fips.Enabled() {
return "", errors.New("signature with Ed25519 key is disabled in FIPS Mode")
}
alg = jose.EdDSA
default:
return "", fmt.Errorf("this private key type is not supported '%T'", privkey)
}
// Create a signer
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: privkey}, nil)
if err != nil {
return "", fmt.Errorf("unable to initialize signer: %w", err)
}
// Encode payload
payloadBytes, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("unable to marshal payload as json: %w", err)
}
// Sign the token
object, err := signer.Sign(payloadBytes)
if err != nil {
return "", fmt.Errorf("unable to sign payload: %w", err)
}
// Serialize final token
serialize, err := object.CompactSerialize()
if err != nil {
return "", fmt.Errorf("unable to generate final token: %w", err)
}
// No error
return serialize, nil
}
// ParseJWT unpack a JWT without signature verification.
func ParseJWT(token string) (interface{}, error) {
// Parse token
t, err := jwt.ParseSigned(token)
if err != nil {
return nil, fmt.Errorf("unable to parse input token: %w", err)
}
// Extract claims without verification
var claims map[string]interface{}
if err := t.UnsafeClaimsWithoutVerification(&claims); err != nil {
return nil, fmt.Errorf("unable to extract claims from token: %w", err)
}
return struct {
Headers []jose.Header
Claims map[string]interface{}
}{
Headers: t.Headers,
Claims: claims,
}, nil
}
func VerifyJWT(token string, key interface{}) (interface{}, error) {
// Parse token
t, err := jwt.ParseSigned(token)
if err != nil {
return nil, fmt.Errorf("unable to parse input token: %w", err)
}
// Extract claims without verification
var claims map[string]interface{}
if err := t.Claims(key, &claims); err != nil {
return nil, fmt.Errorf("unable to extract claims from token: %w", err)
}
return struct {
Headers []jose.Header
Claims map[string]interface{}
}{
Headers: t.Headers,
Claims: claims,
}, nil
}
// Bech32Decode decodes given bech32 encoded string.
func Bech32Decode(in string) (interface{}, error) {
hrp, data, err := bech32.Decode(in)
if err != nil {
return nil, fmt.Errorf("unable to decode Bech32 encoding string: %w", err)
}
return struct {
Hrp string
Data []byte
}{
Hrp: hrp,
Data: data,
}, nil
}
// ToTLSA encodes the given certificate using TLSA encoding strategy.
// selector 0 - Raw / 1 - Public Key
// mtype 0 - Raw / 1 - SHA256 / 2 - SHA512
func ToTLSA(selector, mtype uint8, cert *x509.Certificate) (string, error) {
var (
preimage asn1.RawContent
output []byte
tmp256 [32]byte
tmp512 [64]byte
)
switch selector {
case 0:
preimage = cert.Raw
case 1:
preimage = cert.RawSubjectPublicKeyInfo
default:
return "", fmt.Errorf("unknown TLSA selector: %d", selector)
}
switch mtype {
case 0:
output = preimage
case 1:
tmp256 = sha256.Sum256(preimage)
output = tmp256[:]
case 2:
tmp512 = sha512.Sum512(preimage)
output = tmp512[:]
default:
return "", fmt.Errorf("unknown TLSA matching type: %d", mtype)
}
// No error
return hex.EncodeToString(output), nil
}