2015-08-25 17 views
5

Attualmente sto scrivendo attacchi a una libreria crittografica che espone una funzione per coppie di chiavi che generano:Come creare due ByteString chiamando questa API della libreria esterna?

const size_t PUBLICKEYBYTES = 32; 
const size_t SECRETKEYBYTES = 32; 
int random_keypair(unsigned char pk[PUBLICKEYBYTES], 
        unsigned char sk[SECRETKEYBYTES]); 

Questa funzione genera in modo casuale una chiave segreta, calcola la corrispondente chiave pubblica e mette il risultato in pk e sk.

Appena restituito uno ByteString ho trovato che il modo più semplice è utilizzare create :: Int -> (Ptr Word8 -> IO()) -> IO ByteString da Data.ByteString.Internal. Tuttavia, quella funzione non può creare due ByteStrings allo stesso tempo.

Il mio primo approccio è stato quello di scrivere qualcosa di simile:

newtype PublicKey = PublicKey ByteString 
newtype SecretKey = SecretKey ByteString 
randomKeypair :: IO (PublicKey, SecretKey) 
randomKeypair = do 
    let pk = B.replicate 0 publicKeyBytes 
     sk = B.replicate 0 secretKeyBytes 
    B.unsafeUseAsCString pk $ \ppk -> 
     B.unsafeUseAsCString sk $ \psk -> 
     c_random_keypair ppk psk 
    return (PublicKey pk, SecretKey sk) 

Tuttavia, questo non sembra funzionare con GHC 7.10.2. Quando eseguo la suite di test, trovo che sembra che abbia condiviso le chiamate di funzioni tra ByteString s, causando errori di codifica/decrittografia e risultati errati.

sono riuscito a risolvere il problema definendo la mia propria funzione:

createWithResult :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString, a) 
createWithResult i f = do 
    fp <- B.mallocByteString i 
    r <- withForeignPtr fp f 
    return (B.fromForeignPtr fp 0 i, r) 

e di utilizzarlo come:

randomKeypair = fmap (PublicKey *** SecretKey) $ 
    createWithResult publicKeyBytes $ \ppk -> 
    B.create secretKeyBytes $ \psk -> 
    void $ c_random_keypair ppk psk 

Questo sembra funzionare, tutti i test passano.

La mia domanda è, quali sono esattamente la semantica quando si tratta di condivisione e trasparenza referenziale quando si parla della monade IO?

La mia intuizione mi ha detto (erroneamente) che potevo risolvere il problema nel primo modo, ma a quanto pare non potevo. Quello che credo stia accadendo è che l'ottimizzatore ha visto che gli statement let potevano essere spostati in alto nelle definizioni di livello superiore, e questa era la ragione per cui avevo questi problemi.

risposta

2

Il problema con il primo approccio è che si sta tentando di modificare un valore immutabile (pk e sk nella propria funzione). Il docs for unsafeUseAsCString dire:

modifica del CString, sia in C, o utilizzando poke, causerà il contenuto del ByteString cambiare, rompendo trasparenza referenziale

il IO monade non ha semantica diversa quando si tratta di condivisione e trasparenza referenziale.In effetti, lo let nel blocco do non è in alcun modo correlato alla monade IO; il codice è equivalente a:

randomKeypair :: IO (PublicKey, SecretKey) 
randomKeypair = 
    let pk = B.replicate 0 publicKeyBytes 
     sk = B.replicate 0 secretKeyBytes 
    in B.unsafeUseAsCString pk (\ppk -> 
     B.unsafeUseAsCString sk $ \psk -> 
     c_random_keypair ppk psk) >> 
    return (PublicKey pk, SecretKey sk) 

Ora è chiaramente visibile che pk e sk può essere galleggiare al livello superiore.

+0

Quindi se ByteString avesse esposto una funzione 'replicateIO :: Int -> Word8 -> IO ByteString' che non chiamava 'unsafePerformIO', avrebbe funzionato? – dnaq

4

Questo non risponde alla tua domanda, ma è troppo lungo per essere inserito in un commento.

Come un hack, se si vuole evitare di fare assegnazioni manuali, è possibile utilizzare due annidati create chiamate e un IORef ByteString per memorizzare il bytestring creato dal più interno create. Per esempio. (Pseudocodice)

secRef <- newIORef "" 
pubB <- create publicKeyBytes (\pub -> do 
    secB <- create secretKeyBytes (\sec -> void $ c_random_keypair pub sec) 
    writeIORef secRef secB) 
secB <- readIORef secRef 
return (pubB, secB) 

Tuttavia, preferisco il tuo createWithResult a questo approccio.