subprocess.call openssl nimmt stdin nicht an

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Hallo zusammen,

ich habe ein "stacked" PEM file. D.h eine Zertifikats-Datei die gleich mehrere Zertifikate enthält (stacked)
Nun ist bekannter-weise der openssl "Client" nicht in der Lage alle Zertifikate innerhalb der Datei zu auf Gültigkeit testen, sonder nimmt immer nur das Erste.

Daher meine Idee das File in Blöcke zu unterteilen und block-by-block durch den openssl zu schieben.

Code: Alles auswählen

#!/usr/bin/python
import sys, subprocess
from StringIO import StringIO

output = StringIO()

with open ('fullchain.pem','r') as certfile:
   read_data = certfile.readlines()

for line in read_data:
   if not ( "-----END CERTIFICATE-----" in line):
       output.write(line)
   else:
      output.write("-----END CERTIFICATE-----")
      cert = str(output.getvalue())
      print cert
      print "next: "

      subprocess.call( ["openssl", "x509", "-text", "-noout" ], stdin=cert )
      output = StringIO()


Das Problem ist der "Subprocess".
Nimmt man die Zeile raus, werden die Blöcke richtig angezeigt. Und "piped" man den output an den openccl Client funktioniert's auch.

Code: Alles auswählen

check.py | openssl x509 -text -noout
ABER die oben genannte Zeile mit dem subprocess funktioniert nicht ... WARUM ???
Auch wenn man sie abändert in:

Code: Alles auswählen

 subprocess.call( ["openssl", "x509", "-text", "-noout", cert ]) 
... geht's nicht.
Es scheint als das openssl den Input einfach ignoriert ...

(Der Aufruf mit shell=True bringt auch nichts ... da landet man nur im interaktiven Modus von Openssl )
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dein Problem ist das du stdin falsch nutzt. Du denkst du kannst da einfach einen String angeben. Das geht nicht, zumindest habe ich das noch nie so gesehen.

Was funktionieren sollte ist zB

Code: Alles auswählen

p = subprocess.Popen( ["openssl", "x509", "-text", "-noout" ], stdin=subprocess.PIPE)
p.communicate(cert)
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Hm ... ja eine Lösung mot Popen ab ich auch schon des öfteren gesehen ...
Aber warum geht's nicht mit 'nem simplen "call" ?

Das mit dem "communicate(cert)" funktioniert nicht. Da kommt:

Code: Alles auswählen

Traceback (most recent call last):
  File "./check.py", line 25, in <module>
    print "I got back from the program this:\n{0}".format(proc.stdout.read())
AttributeError: 'NoneType' object has no attribute 'read'
bei:

Code: Alles auswählen

      proc = subprocess.Popen( ["openssl", "x509", "-subject", "-fingerprint", "-noout"], stdin=subprocess.PIPE)
      proc.communicate(cert)

      print "I got back from the program this:\n{0}".format(proc.stdout.read())
      
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Weil call ein convenience wrapper mit beschränkter Funktionalität ist.

Und dein cert scheint None statt dem gewünschten String zu sein.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@may24x: wenn Du von stdout lesen, mußt Du auch stdout=subprocess.PIPE schreiben, das ist bei `call` der Default, bei `Popen` aber nicht.
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Also ...

Ich verstehe nicht warum der "call" Aufruf partout nicht funktionieren will ... und auch Mr. Google scheint keine Antwort darauf zu kennen ...
Ich hab's jetzt mit "Popen" gelöst. Wer also Lust hat kann gerne mein Script benutzen.

Achtung: der Dateiname ist das Erste (und Einzige) Argument was hier übergeben wird ... da ich faul war ist's 'ne quick-and-dirty Lösung ... simpel und zweckdienlich halt.

Code: Alles auswählen

#!/usr/bin/python
import sys, subprocess
from StringIO import StringIO

output = StringIO()
i=1
inputFile=str(sys.argv[1])

with open (inputFile,'r') as certfile:
   read_data = certfile.readlines() 

for line in read_data:
   if not ( "-----END CERTIFICATE-----" in line):
       output.write(line) 
   else:
      output.write("-----END CERTIFICATE-----")
      cert = str(output.getvalue())

      proc = subprocess.Popen( ["openssl", "x509", "-subject", "-fingerprint", "-noout"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
      
      proc.stdin.write(cert)
      proc.stdin.close()

      print "Info from Cert #"+str(i)+" - got this:\n{0}".format(proc.stdout.read())
      i=i+1

      output = StringIO()
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du solltest nicht write und close benutzen, sondern communicate, wie ich das gezeigt habe. Das erledigt dann auch gleich noch das eigentlich notwendige "wait" auf das Ende des Kindprozesses.
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Tja, proc.communicate(cert) funktioniert halt nur nicht.

Entweder es wird ein Fehler geworfen:
Traceback (most recent call last):
File "./checkStackedCert.py", line 25, in <module>
print "Info from Cert #"+str(i)+" - got this:\n{0}".format(proc.stdout.read())
ValueError: I/O operation on closed file

oder - nimmt man die Zeile mit dem stdout raus - dann kommt überhaupt kein Output
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

communicate gibt dir (wenn du das per stdout=subprocess.PIPE angefordert hast) die Ausgabe zurueck.

Code: Alles auswählen

stdout, _ = proc.communicate(eingabe)
Man *darf* auch ruhig mal darauf vertrauen, das wir dir hier keinen Mist erzaehlen, und in der Doku nachschlagen, ob die gedachte Verwendung auch der Realitaet entspricht...
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Wenn Du bereit bist Python 3 zu nutzen, dann versuche mal herauszufinden, was hier passiert:

Code: Alles auswählen

import sys
import subprocess

END_MARKER = '-----END CERTIFICATE-----'

with open(sys.argv[1]) as fobj:
    for i, each in enumerate(fobj.read().split(END_MARKER)[:-1], start=1):
        print(f'Info from Cert #{i}')
        print(subprocess.run(
            ["openssl", "x509", "-subject", "-fingerprint", "-noout"],
            input=bytes(each+END_MARKER, 'ascii')).stdout)
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

leider kein Python3 auf der Zielmaschine ... nur 2.4 ...

BTW:

Code: Alles auswählen

/usr/bin/python3 -V
Python 3.5.2

./checkStackedCert2.py fullchain.pem
  File "./checkStackedCert2.py", line 9
    print(f'Info from Cert #{i}')
                               ^
SyntaxError: invalid syntax

... das "f" ist zuviel in Zeil:

Code: Alles auswählen

 print(f'Info from Cert {i}') 
Wirft man's raus, funktioniert's 1a
(... nur mit dem communicate(cert) immer noch nicht hinbekommen ... und jetzt aufgegeben ... da kommt immer syntax error)
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

f-strings gibts ja auch erst sein Python 3.6
https://docs.python.org/3/whatsnew/3.6.html
musst das allseits geliebte .format() benutzen
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@may24x: Wenn Du angeblich Python 2.4 benutzt, dann gibt es noch kein with. f-Strings gibt es erst ab Python 3.6.

zu Deinem Code: eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht 3. Pro Zeile nur ein Modul importieren.
Erst die ganze Datei in den Speicher zu lesen, ist unnötig, weil Du direkt über das offene Dateiobjekt iterieren kannst.
argv ist schon eine Liste von Strings, eine Element in einen String umzuwandeln ist also unnötig. StringIO benutzt man eigentlich nur, wenn man wirklich ein File-ähnliches Objekt braucht, bei Dir reicht eine einfache Liste. Das Trennen in Certifikate würde ich in einen Generator auslagern.
`not` braucht keine Klammern und ich finde der `not in`-Operator ist lesbarer.
Warum benutzt Du +-Zusammenstückelung und Stringformatierung in einer Zeile???

Code: Alles auswählen

#!/usr/bin/python
import sys
import subprocess

def iter_certificates(filename):
    with open(filename) as lines:
        result = []
        for line in lines:
            result.append(line)
            if "-----END CERTIFICATE-----" in line:
                yield ''.join(result)
                result = []

for nr, certificate in enumerate(iter_certificates(sys.argv[1])):
    proc = subprocess.Popen(["openssl", "x509", "-subject", "-fingerprint", "-noout"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    result, _ = proc.communicate(certificate)
    print "Info from Cert #{} - got this:\n{0}".format(nr, result)
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Nun, das Script hat ein Problem mit dem "format":

Code: Alles auswählen

Traceback (most recent call last):
  File "./checkStackedCert3.py", line 17, in <module>
    print "Info from Cert #{} - got this:\n{0}".format(nr, result)
ValueError: cannot switch from automatic field numbering to manual field specification
Die "0" aus ''got this:\n{0}'' ist zuviel.

Das Zerstückeln ist noch von altem Code übrig geblieben ...
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Hast du dir die Fehlermeldung mal genau durchgelesen?

"cannot switch from automatic field numbering to manual field specification"

entweder du nummeriers alle felder durch {0} {1} etc oder du lässt alle frei {} {} etc
aber son misch masch mag der Interpreter nicht
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Antworten