// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 auth provides methods to implement managed instances auth support
package auth

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"errors"
)

const (
	keySize  int = 2048
	saltSize int = 32

	// KeyType returns the RSA Key Type
	KeyType = "Rsa"
)

type RsaKey struct {
	privateKey *rsa.PrivateKey
}

// CreateKeypair creates a new RSA keypair
func CreateKeypair() (rsaKey RsaKey, err error) {
	rsaKey.privateKey, err = rsa.GenerateKey(rand.Reader, keySize)

	return
}

// EncodePublicKey encodes a public key to a base 64 DER encoded string
func (rsaKey *RsaKey) EncodePublicKey() (publicKey string, err error) {
	var publicKeyBytes []byte
	publicKeyBytes, err = x509.MarshalPKIXPublicKey(&rsaKey.privateKey.PublicKey)
	if err != nil {
		return
	}
	publicKey = base64.StdEncoding.EncodeToString(publicKeyBytes)

	return
}

// EncodePrivateKey encodes a private key to a base 64 DER encoded string
func (rsaKey *RsaKey) EncodePrivateKey() (privateKey string, err error) {
	var privateKeyBytes []byte
	privateKeyBytes = x509.MarshalPKCS1PrivateKey(rsaKey.privateKey)

	privateKey = base64.StdEncoding.EncodeToString(privateKeyBytes)

	return
}

// DecodePrivateKey decodes a private key from a base 64 DER encoded string
func DecodePrivateKey(privateKey string) (rsaKey RsaKey, err error) {
	var privateKeyBytes []byte
	privateKeyBytes, err = base64.StdEncoding.DecodeString(privateKey)
	if err != nil {
		return
	}
	rsaKey.privateKey, err = x509.ParsePKCS1PrivateKey(privateKeyBytes)
	if err != nil {
		return
	}

	return
}

// Sign creates the signature for a message
func (rsaKey *RsaKey) Sign(message string) (signature string, err error) {
	var signatureBytes []byte
	hashAlgorithm := crypto.SHA256

	messageBytes := bytes.NewBufferString(message)

	//hash
	hasher := hashAlgorithm.New()
	hasher.Write(messageBytes.Bytes())
	messageHash := hasher.Sum(nil)

	//sign
	var pssOptions rsa.PSSOptions
	pssOptions.SaltLength = saltSize
	pssOptions.Hash = hashAlgorithm
	signatureBytes, err = rsa.SignPSS(rand.Reader, rsaKey.privateKey, hashAlgorithm, messageHash, &pssOptions)
	if err != nil {
		return
	}
	signature = base64.StdEncoding.EncodeToString(signatureBytes)

	return
}

// VerifySignature verifies the signature of a message
func (rsaKey *RsaKey) VerifySignature(message string, signature string) (err error) {
	hashAlgorithm := crypto.SHA256
	if rsaKey.privateKey == nil {
		err = errors.New("privateKey is nil")
		return
	}
	messageBytes := bytes.NewBufferString(message)

	//hash
	pssh := hashAlgorithm.New()
	pssh.Write(messageBytes.Bytes())
	messageHash := pssh.Sum(nil)

	var signatureBytes []byte
	signatureBytes, err = base64.StdEncoding.DecodeString(signature)
	if err != nil {
		return
	}

	//Verify signature
	var opts rsa.PSSOptions
	opts.SaltLength = rsa.PSSSaltLengthAuto
	err = rsa.VerifyPSS(&rsaKey.privateKey.PublicKey, hashAlgorithm, messageHash, signatureBytes, &opts)

	return
}
