diff --git a/Data/PEM.hs b/Data/PEM.hs
new file mode 100644
--- /dev/null
+++ b/Data/PEM.hs
@@ -0,0 +1,18 @@
+-- |
+-- Module      : Data.PEM
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : portable
+--
+-- Read and write PEM files
+--
+module Data.PEM
+	( module Data.PEM.Types
+    , module Data.PEM.Writer
+    , module Data.PEM.Parser
+	) where
+
+import Data.PEM.Types
+import Data.PEM.Writer
+import Data.PEM.Parser
diff --git a/Data/PEM/Parser.hs b/Data/PEM/Parser.hs
new file mode 100644
--- /dev/null
+++ b/Data/PEM/Parser.hs
@@ -0,0 +1,59 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module      : Data.PEM.Parser
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : portable
+--
+-- Parse PEM content.
+--
+-- A PEM contains contains from one to many PEM sections.
+-- Each section contains an optional key-value pair header
+-- and a binary content encoded in base64.
+-- 
+module Data.PEM.Parser
+    ( pemParser
+    , pemParseBS
+    , pemParseLBS
+    ) where
+
+import Control.Applicative
+import Data.Attoparsec
+import qualified Data.Attoparsec.Lazy as AttoLazy
+import Data.Attoparsec.Char8 (space)
+
+import Data.PEM.Types
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Base64 as Base64
+
+import Prelude hiding (takeWhile)
+import Data.Serialize.Builder
+import Data.Monoid
+
+-- | parser to get PEM sections
+pemParser :: Parser [PEM]
+pemParser = many contextSection
+    where
+          beginMarker = string "-----BEGIN " >> return ()
+          endMarker = string "-----END " >> return ()
+          skipLine = skipWhile (/= 0xa) >> space >> return ()
+          eatLine = takeWhile (/= 0xa) <* space
+          contextSection = manyTill skipLine beginMarker *> section
+          section = do
+               -- begin marker has already been eaten by contextSection
+               name <- takeWhile (/= 0x2d) <* (string "-----" *> space)
+               l    <- manyTill eatLine endMarker
+               let content = toByteString $ mconcat $ map (fromByteString . Base64.decodeLenient) l
+               return $ PEM { pemName = BC.unpack name, pemHeader = [], pemContent = content }
+
+-- | parse a PEM content using a strict bytestring
+pemParseBS :: ByteString -> Either String [PEM]
+pemParseBS = parseOnly pemParser
+
+-- | parse a PEM content using a dynamic bytestring
+pemParseLBS :: L.ByteString -> Either String [PEM]
+pemParseLBS = AttoLazy.eitherResult . AttoLazy.parse pemParser
diff --git a/Data/PEM/Types.hs b/Data/PEM/Types.hs
new file mode 100644
--- /dev/null
+++ b/Data/PEM/Types.hs
@@ -0,0 +1,20 @@
+-- |
+-- Module      : Data.PEM.Types
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : portable
+--
+module Data.PEM.Types where
+
+import Data.ByteString (ByteString)
+
+-- | Represent one PEM section
+--
+-- for now headers are not serialized at all.
+-- this is just available here as a placeholder for a later implementation.
+data PEM = PEM
+        { pemName    :: String                 -- ^ the name of the section, found after the dash BEGIN tag.
+        , pemHeader  :: [(String, ByteString)] -- ^ optionals key value pair header
+        , pemContent :: ByteString             -- ^ binary content of the section
+        } deriving (Show,Eq)
diff --git a/Data/PEM/Writer.hs b/Data/PEM/Writer.hs
new file mode 100644
--- /dev/null
+++ b/Data/PEM/Writer.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module      : Data.PEM.Writer
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : portable
+--
+module Data.PEM.Writer
+    ( pemWriteBuilder
+    , pemWriteLBS
+    , pemWriteBS
+    ) where
+
+import Data.PEM.Types
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Base64 as Base64
+import Data.Serialize.Builder
+import Data.Monoid
+import Data.List
+
+-- | write a PEM structure to a builder
+pemWriteBuilder :: PEM -> Builder
+pemWriteBuilder pem = mconcat $ intersperse eol $ concat ([begin]:header:section:[end]:[[empty]])
+    where begin   = mconcat $ map fromByteString ["-----BEGIN ", sectionName, "-----" ]
+          end     = mconcat $ map fromByteString ["-----END ", sectionName, "-----" ]
+          section = map fromByteString $ (splitChunks $ Base64.encode $ pemContent pem)
+          header  = if null $ pemHeader pem
+                        then []
+                        else concatMap toHeader (pemHeader pem) ++ [empty]
+          toHeader (k,v) = [ mconcat $ map fromByteString [ bk, ":", v ] ]
+                where bk = BC.pack k
+          -- expect only ASCII. need to find a type to represent it.
+          sectionName = BC.pack $ pemName pem
+
+          splitChunks b
+                  | B.length b > 64 = let (x,y) = B.splitAt 64 b in x : splitChunks y
+                  | otherwise       = [b]
+          eol = fromByteString $ B.singleton 0x0a
+
+-- | convert a PEM structure to a bytestring
+pemWriteBS :: PEM -> ByteString
+pemWriteBS = toByteString . pemWriteBuilder
+
+-- | convert a PEM structure to a lazy bytestring
+pemWriteLBS :: PEM -> L.ByteString
+pemWriteLBS = toLazyByteString . pemWriteBuilder
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2010-2012 Vincent Hanquez <vincent@snarc.org>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+PEM haskell library
+===================
+
+Handle Privacy Enhanced Message (PEM) format.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/pem.cabal b/pem.cabal
new file mode 100644
--- /dev/null
+++ b/pem.cabal
@@ -0,0 +1,44 @@
+Name:                pem
+Version:             0.1.0
+Description:         Privacy Enhanced Mail (PEM) format reader and writer.
+License:             BSD3
+License-file:        LICENSE
+Copyright:           Vincent Hanquez <vincent@snarc.org>
+Author:              Vincent Hanquez <vincent@snarc.org>
+Maintainer:          Vincent Hanquez <vincent@snarc.org>
+Synopsis:            Privacy Enhanced Mail (PEM) format reader and writer.
+Build-Type:          Simple
+Category:            Data
+stability:           experimental
+Cabal-Version:       >=1.8
+Homepage:            http://github.com/vincenthz/hs-pem
+data-files:          README.md
+
+Library
+  Build-Depends:     base >= 3 && < 5
+                   , mtl
+                   , bytestring
+                   , attoparsec
+                   , cereal
+                   , base64-bytestring
+  Exposed-modules:   Data.PEM
+  Other-modules:     Data.PEM.Parser
+                     Data.PEM.Writer
+                     Data.PEM.Types
+  ghc-options:       -Wall
+
+Test-Suite test-pem
+    type:            exitcode-stdio-1.0
+    hs-source-dirs:  Tests
+    main-is:         pem.hs
+    build-depends:   base
+                   , bytestring
+                   , test-framework >= 0.3.3 && < 0.6
+                   , test-framework-quickcheck2 >= 0.2.9 && < 0.3
+                   , QuickCheck >= 2.4.0.1
+                   , pem
+
+source-repository head
+  type: git
+  location: git://github.com/vincenthz/hs-pem
+
