-- |
-- Module      : Data.X509.EC
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Utilities related to Elliptic Curve certificates and keys.
--
module Data.X509.EC
    (
      unserializePoint
    , ecPubKeyCurve
    , ecPubKeyCurveName
    , ecPrivKeyCurve
    , ecPrivKeyCurveName
    , lookupCurveNameByOID
    ) where

import Data.ASN1.OID
import Data.List (find)

import Data.X509.OID
import Data.X509.PublicKey
import Data.X509.PrivateKey

import qualified Crypto.PubKey.ECC.Prim  as ECC
import qualified Crypto.PubKey.ECC.Types as ECC
import           Crypto.Number.Serialize (os2ip)

import qualified Data.ByteString as B

-- | Read an EC point from a serialized format and make sure the point is
-- valid for the specified curve.
unserializePoint :: ECC.Curve -> SerializedPoint -> Maybe ECC.Point
unserializePoint curve (SerializedPoint bs) =
    case B.uncons bs of
        Nothing                -> Nothing
        Just (ptFormat, input) ->
            case ptFormat of
                4 -> if B.length input /= 2 * bytes
                        then Nothing
                        else
                            let (x, y) = B.splitAt bytes input
                                p      = ECC.Point (os2ip x) (os2ip y)
                             in if ECC.isPointValid curve p
                                    then Just p
                                    else Nothing
                -- 2 and 3 for compressed format.
                _ -> Nothing
  where bits  = ECC.curveSizeBits curve
        bytes = (bits + 7) `div` 8

-- | Return the curve associated to an EC Public Key.  This does not check
-- if a curve in explicit format is valid: if the input is not trusted one
-- should consider 'ecPubKeyCurveName' instead.
ecPubKeyCurve :: PubKeyEC -> Maybe ECC.Curve
ecPubKeyCurve (PubKeyEC_Named name _) = Just $ ECC.getCurveByName name
ecPubKeyCurve pub@PubKeyEC_Prime{}    =
    fmap buildCurve $
        unserializePoint (buildCurve undefined) (pubkeyEC_generator pub)
  where
    prime = pubkeyEC_prime pub
    buildCurve g =
        let cc = ECC.CurveCommon
                     { ECC.ecc_a = pubkeyEC_a        pub
                     , ECC.ecc_b = pubkeyEC_b        pub
                     , ECC.ecc_g = g
                     , ECC.ecc_n = pubkeyEC_order    pub
                     , ECC.ecc_h = pubkeyEC_cofactor pub
                     }
         in ECC.CurveFP (ECC.CurvePrime prime cc)

-- | Return the name of a standard curve associated to an EC Public Key
ecPubKeyCurveName :: PubKeyEC -> Maybe ECC.CurveName
ecPubKeyCurveName (PubKeyEC_Named name _) = Just name
ecPubKeyCurveName pub@PubKeyEC_Prime{}    =
    find matchPrimeCurve $ enumFrom $ toEnum 0
  where
    matchPrimeCurve c =
        case ECC.getCurveByName c of
            ECC.CurveFP (ECC.CurvePrime p cc) ->
                ECC.ecc_a cc == pubkeyEC_a pub     &&
                ECC.ecc_b cc == pubkeyEC_b pub     &&
                ECC.ecc_n cc == pubkeyEC_order pub &&
                p            == pubkeyEC_prime pub
            _                                 -> False

-- | Return the EC curve associated to an EC Private Key.  This does not check
-- if a curve in explicit format is valid: if the input is not trusted one
-- should consider 'ecPrivKeyCurveName' instead.
ecPrivKeyCurve :: PrivKeyEC -> Maybe ECC.Curve
ecPrivKeyCurve (PrivKeyEC_Named name _) = Just $ ECC.getCurveByName name
ecPrivKeyCurve priv@PrivKeyEC_Prime{}   =
    fmap buildCurve $
        unserializePoint (buildCurve undefined) (privkeyEC_generator priv)
  where
    prime = privkeyEC_prime priv
    buildCurve g =
        let cc = ECC.CurveCommon
                     { ECC.ecc_a = privkeyEC_a        priv
                     , ECC.ecc_b = privkeyEC_b        priv
                     , ECC.ecc_g = g
                     , ECC.ecc_n = privkeyEC_order    priv
                     , ECC.ecc_h = privkeyEC_cofactor priv
                     }
         in ECC.CurveFP (ECC.CurvePrime prime cc)

-- | Return the name of a standard curve associated to an EC Private Key
ecPrivKeyCurveName :: PrivKeyEC -> Maybe ECC.CurveName
ecPrivKeyCurveName (PrivKeyEC_Named name _) = Just name
ecPrivKeyCurveName priv@PrivKeyEC_Prime{}   =
    find matchPrimeCurve $ enumFrom $ toEnum 0
  where
    matchPrimeCurve c =
        case ECC.getCurveByName c of
            ECC.CurveFP (ECC.CurvePrime p cc) ->
                ECC.ecc_a cc == privkeyEC_a priv     &&
                ECC.ecc_b cc == privkeyEC_b priv     &&
                ECC.ecc_n cc == privkeyEC_order priv &&
                p            == privkeyEC_prime priv
            _                                 -> False

-- | Return the curve name associated to an OID
lookupCurveNameByOID :: OID -> Maybe ECC.CurveName
lookupCurveNameByOID = lookupByOID curvesOIDTable