package jwt

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"io/ioutil"
)

var _ Algorithm = _rsa{}

var ErrValidateOnly = errors.New("jwt: cannot sign with a public key only")

type _rsa struct {
	Hash       crypto.Hash
	PrivateKey *rsa.PrivateKey
}

//Size returns the Size of the Sign() generated signature in bytes;
//this the same as the number of bytes of the PublicKey's N value.
func (r _rsa) Size() int { return r.PrivateKey.PublicKey.N.BitLen() / 8 }

//Name returns the RFC 7518 ("JSON Web Algorithms") compliant name of this algorithm.
//RSA-based JWAs all start with "RS", followed by their hash size in bits.
func (r _rsa) Name() string { return fmt.Sprintf("RS%d", r.Hash.Size()*8) }

func (r _rsa) hash(v []byte) (o []byte) {
	h := r.Hash.New()
	h.Write(v)
	return h.Sum(nil)
}

func (r _rsa) Validate(v, s []byte) (err error) {
	return rsa.VerifyPKCS1v15(&r.PrivateKey.PublicKey, r.Hash, r.hash(v), s)
}

func (r _rsa) Sign(i []byte) (o []byte, err error) {
	if r.PrivateKey.D == nil {
		err = ErrValidateOnly
		return
	}

	return rsa.SignPKCS1v15(rand.Reader, r.PrivateKey, r.Hash, r.hash(i))
}

//RS256 returns the RSASSA-PKCS1-v1_5 SHA-256 digital signature algorithm, a 256bit asymmetric signing algorithm.
func RS256(priv *rsa.PrivateKey) Algorithm { return _rsa{crypto.SHA256, priv} }

//RS384 returns the RSASSA-PKCS1-v1_5 SHA-384 digital signature algorithm, a 384bit asymmetric signing algorithm.
func RS384(priv *rsa.PrivateKey) Algorithm { return _rsa{crypto.SHA384, priv} }

//RS512 returns the RSASSA-PKCS1-v1_5 SHA-512 digital signature algorithm, a 512bit asymmetric signing algorithm.
func RS512(priv *rsa.PrivateKey) Algorithm { return _rsa{crypto.SHA512, priv} }

//ReadRSAPrivateKey parses a pem & PKCS1 encoded RSA private key as can be used by this package such as might be generated by
//	ssh-keygen -r rsa -f id_rsa.pem -m pem
//Yours will look something like this:
//	-----BEGIN RSA PRIVATE KEY-----
//	MIIEpAIBAAKCAQEA3X32AoiRV+6WTjamOf1ZHvtZR507MtVgLwRs8Ay9Ih8Yqo3u
//	... more letters ...
//	-----END RSA PRIVATE KEY-----
func ReadRSAPrivateKey(pkeyfile string) (k *rsa.PrivateKey, err error) {
	pkb, err := ioutil.ReadFile(pkeyfile)
	if err != nil {
		return
	}

	return ParseRSAPrivateKey(pkb)
}

//ParseRSAPrivateKey parses a byte stream and decodes it to an RSA private key
func ParseRSAPrivateKey(raw []byte) (k *rsa.PrivateKey, err error) {
	block, _ := pem.Decode(raw)

	if k, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
		return
	}

	return
}

var ErrNotRSAPublicKey = errors.New("jwt: parsed key is not rsa key")

//ReadRSAPublicKey parses a pem & PKCS1 encoded RSA public key such as might be generated by:
// openssl rsa -in id_rsa.pem -pubout > id_rsa.pub.pem
//	ssh-keygen -f id_rsa.pub -e -m pem
//Yours will look something like this:
//	-----BEGIN RSA PUBLIC KEY-----
//	MIIBCgKCAQEA3X32AoiRV+6WTjamOf1ZHvtZR507MtVgLwRs8Ay9Ih8Yqo3utCEx
//	... more letters ...
//	-----END RSA PUBLIC KEY-----
func ReadRSAPublicKey(pkeyfile string) (k *rsa.PublicKey, err error) {
	pkb, err := ioutil.ReadFile(pkeyfile)
	if err != nil {
		return
	}
	return ParseRSAPublicKey(pkb)
}

//ParseRSAPrivateKey parses a byte slice and decodes it as an RSA public key
func ParseRSAPublicKey(raw []byte) (k *rsa.PublicKey, err error) {
	block, _ := pem.Decode(raw)

	var i interface{}
	if i, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
		return
	}

	var ok bool
	if k, ok = i.(*rsa.PublicKey); !ok {
		err = ErrNotRSAPublicKey
		return
	}

	return
}

//RSAValidateOnly takes a function that would take an *rsa.PrivateKey and an *rsa.PublicKey
//and returns an Algorithm that only allows validation.
func RSAValidateOnly(f func(*rsa.PrivateKey) Algorithm, pub *rsa.PublicKey) Algorithm {
	return f(&rsa.PrivateKey{PublicKey: *pub})
}
