2009/11/20

"Couldn't match expected type `IO *' against inferred type"

Haskell がきれいらしいので Haskell 少し真面目にしようかなと
証明書くらい読めなきゃいけないだろうということで X.509 を読むことに

ググったら HsOpenSSL という OpenSSL の binding があり
cabal は入れてあったので
$ cabal install HsOpenSSL
でインストール終了
OpenSSL を見て
    main = withOpenSSL $
do ...
と withOpenSSL で囲むというのと
Learning After School » HsOpenSSL Update を見て
ghci には --make というオプションを与えないとダメなことを認識

とりあえず読めればいいと思ったので
PEM への PATH を最初の引数で取ってそれを解釈して吐くことにしました
HsOpenSSL 関係で使った関数は
OpenSSL.PEM.readX509 :: String -> IO X509
OpenSSL.X509.getVersion :: X509 -> IO Int
OpenSSL.X509.getVersiongetSerialNumber :: X509 -> IO Integer
OpenSSL.X509.getVersiongetNotBefore :: X509 -> IO UTCTime
OpenSSL.X509.getVersiongetNotAfter :: X509 -> IO UTCTime
OpenSSL.X509.getVersiongetIssuerName :: X509 -> Bool -> IO [(String, String)]
OpenSSL.X509.getVersiongetSubjectName :: X509 -> Bool -> IO [(String, String)]
OpenSSL.X509.getVersiongetPublicKey :: X509 -> IO SomePublicKey
OpenSSL.EVP.PKey.toPublicKey :: (PublicKey k) => SomePublicKey -> Maybe k

UTCTime ってのは Data.Time.Clock で定義されてるんだけど
Show のインスタンスなので show で文字列にできる
getIssuerName と getSubjectName の取る Bool は名前の省略するか否か
tuple は ("countryName", "JP") みたいなのが入ってる

でー、ここまでは良かったんだけど分からなかったのが公開鍵で
SomePublicKey ってのは class なんだけど
> :i SomePublicKey
data SomePublicKey where
OpenSSL.EVP.PKey.SomePublicKey :: forall k.
(PublicKey k) =>
!k -> SomePublicKey
-- Defined in OpenSSL.EVP.PKey
instance Eq SomePublicKey -- Defined in OpenSSL.EVP.PKey
instance PublicKey SomePublicKey -- Defined in OpenSSL.EVP.PKey
instance PKey SomePublicKey -- Defined in OpenSSL.EVP.PKey
> :i PublicKey
class (Eq k, Data.Typeable.Typeable k, PKey k) => PublicKey k where
fromPublicKey :: k -> SomePublicKey
toPublicKey :: SomePublicKey -> Maybe k
-- Defined in OpenSSL.EVP.PKey
instance PublicKey RSAKeyPair -- Defined in OpenSSL.EVP.PKey
instance PublicKey SomePublicKey -- Defined in OpenSSL.EVP.PKey
instance PublicKey SomeKeyPair -- Defined in OpenSSL.EVP.PKey
instance PublicKey RSAPubKey -- Defined in OpenSSL.EVP.PKey

type とか class に関する理解が不足しているのは分かりつつ
toPublicKey に SomePublicKey かましたらいいんでしょ? と思って
で、IO SomePublicKey だから bind とか使ったらいいんでしょ? と思って
pk <- getPublicKey x509 >>= toPublicKey
としてみたら
Couldn't match expected type `IO b' against inferred type `Maybe k'
と言われてしまい
これが解決できなくて検索しました

Re: [Haskell-cafe] Convert IO Int to Int: msg#00458 haskell-cafe@haskell.org
何となく分かったのは、IO の中身取り出そうとしてて、そりゃダメだ
return かましてらしたんで、かましてみたら IO で包めて通りました!
と思いきや
SomePublicKey ってののインスタンスも指定しないとダメみたいで
:: Maybe RSAPublicKey ってのを付けないと toPubicKey が上手く動かず
ん〜、理解不足、RSA 決め打ち気持ち悪い

でもまぁ、こんな感じでとりあえず中身見れるようになりました
module Main where
import System
import OpenSSL
import OpenSSL.PEM
import OpenSSL.X509
import OpenSSL.EVP.PKey
import OpenSSL.RSA
import Data.Maybe

main :: IO ()
main = withOpenSSL $
do
x509 <- OpenSSL.PEM.readX509 =<< head =<< getArgs
putStr "version: "
getVersion x509 >>= putStrLn . show . succ
putStr "serialNumber: "
getSerialNumber x509 >>= putStrLn . show
putStr "notBefore: "
getNotBefore x509 >>= putStrLn . show
putStr "notAfter: "
getNotAfter x509 >>= putStrLn . show

putStrLn ""
issuerRDNs <- getIssuerName x509 True
putStrLn "[issuer]"
mapM_ printRDN issuerRDNs
subjectRDNs <- getSubjectName x509 True
putStrLn "[subject]"
mapM_ printRDN subjectRDNs

putStrLn "[publickKey]"
somePublicKey <- getPublicKey x509
pk <- return $ fromJust (toPublicKey somePublicKey :: Maybe RSAPubKey)
putStr "e = "
putStrLn $ show $ rsaE pk
putStr "n = "
putStrLn $ show $ rsaN pk<
where
printRDN (f, s) = putStrLn (f ++ " = " ++ s)
とりあえず動いたんで満足しました

$ ./x509 www.blogger.com.crt 
version: 3
serialNumber: 154451598012349719216436864807249350442
notBefore: 2008-04-29 22:42:19 UTC
notAfter: 2010-05-26 11:11:00 UTC

[issuer]
countryName = ZA
stateOrProvinceName = Western Cape
localityName = Cape Town
organizationName = Thawte Consulting cc
organizationalUnitName = Certification Services Division
commonName = Thawte Premium Server CA
emailAddress = premium-server@thawte.com
[subject]
countryName = US
stateOrProvinceName = California
localityName = Mountain View
organizationName = Google Inc
commonName = *.blogger.com
[publickKey]
e = 65537
n = 156261988165875299258992916522864140599934684523601467326503718745802091107083682835929499666111345772555979343409161090600093388197459190695135388517589095274179717212596199311728073635629373769115406240413244621434604136126497937524426697977932393973863819806782187408995863143657362345664311920806817108659

0 件のコメント:

コメントを投稿