[gelöst] HTTPMOD komplexes Anmeldeverfahren Python Keygenerator

Begonnen von ch.eick, 03 November 2020, 08:28:57

Vorheriges Thema - Nächstes Thema

ch.eick

EDIT: Am Ende wurde noch eine Ablaufbeschreibung mit den Code Stellen eingefügt.
EDIT: Hier geht es übrigens zur Kompletten Anwendung Kostal_Plenticore_10_Plus

Hallo Ihr Spezialisten,

ich habe da eine für mich etwas komplexe Anmeldeprozedur implementiert, die Euch wahrscheinlich die Haare zu berge steigen lässt :-)
Deshalb stelle ich das nun zur Diskussion und hoffe auf Vereinfachungen. Es geht also erst mal um HTTPMOD.

Im Hintergrund gibt es zwei Python Skripte, die momentan die Schlüssel generieren. Die hätte ich liebend gerne weg und versuche mich auch an einer Perl Migration...

Das Device mit der mehrstufigen Anmeldung wird über einige userreadings von Step zu Step getriggert.
Weiterhin wird auch bereits eine Anmeldenotwendigkeit erkannt und dann mit /auth/start begonnen. Hier kann ich momentan nicht den original HTTP request requeuen, weil es ansonsten mit den ganzen Zwischenschritten und Python Calls nicht passt. Die Python Skripte füllen asyncon die auth_* readings, worauf dann die userreadings reagieren. Ich liste auch mal Beispiel auth_* readings im nächsten Listing an und ein verbose 5 Log kommt auch noch.

1. Meine Hauptfrage ist nun, kann ich das noch vereinfachen?
2. Welche Anmeldemöglichkeiten bietet HTTPMOD bisher?
3. Kann man dort noch weitere Anmeldevarianten hinzu fügen? Ich hätte da bereits zwei, die für nicht nicht möglich waren.
4. Natürlich hatte ich begonnen das mit sid* zu implementieren, jedoch konnte ich keine Lösung für die Key Generierungen finden.
   Gibt es eine Unterstützung für dieses Anmeldeverfahren?
5. Lassen sich die Keys mit Perl erstellen und wenn ja, wie?

Habe ich irgend welche Mechanismen in der HTTPMOD Attributen übersehen?

Viele Grüße
     Christian


defmod PV_Anlage_1_API HTTPMOD http://%IP-Address_Plenticore%/auth/me 0

attr PV_Anlage_1_API authRetries 1
attr PV_Anlage_1_API dontRequeueAfterAuth 1
attr PV_Anlage_1_API enableControlSet 1
attr PV_Anlage_1_API enableCookies 1

attr PV_Anlage_1_API get01Data {"nonce": "%randomString64%","username": "user"}

attr PV_Anlage_1_API get01Name 01_/auth/start
attr PV_Anlage_1_API get01URL http://%IP-Address_Plenticore%/api/v1/auth/start

attr PV_Anlage_1_API get02Data {"transactionId": "%auth_transactionId%", "proof": "%auth_proof%"}
attr PV_Anlage_1_API get02Name 02_/auth/finish
attr PV_Anlage_1_API get02URL http://%IP-Address_Plenticore%/api/v1/auth/finish

attr PV_Anlage_1_API get03Data {"transactionId": "%auth_transactionId%", "iv": "%auth_iv%", "tag": "%auth_authtag%", "payload": "%auth_payload%"}
attr PV_Anlage_1_API get03Name 03_/auth/create_session
attr PV_Anlage_1_API get03URL http://%IP-Address_Plenticore%/api/v1/auth/create_session

attr PV_Anlage_1_API get04Header authorization: Session %auth_sessionId%
attr PV_Anlage_1_API get04Name 04_/auth/me
attr PV_Anlage_1_API get04URL http://%IP-Address_Plenticore%/api/v1/auth/me

attr PV_Anlage_1_API get05Header authorization: Session %auth_sessionId%
attr PV_Anlage_1_API get05Name 05_/info/version
attr PV_Anlage_1_API get05URL http://%IP-Address_Plenticore%/api/v1/info/version

attr PV_Anlage_1_API get06Name 06_/auth/logout
attr PV_Anlage_1_API get06URL http://%IP-Address_Plenticore%/api/v1/auth/logout

attr PV_Anlage_1_API getHeader01 Accept-Encoding: gzip,deflate
attr PV_Anlage_1_API getHeader02 Content-type:application/json, Accept:application/json, Connection:keep-alive

attr PV_Anlage_1_API reAuthRegex "authenticated":false|"processdata":\[\]

attr PV_Anlage_1_API reading0100JSON anonymous
attr PV_Anlage_1_API reading0100Name auth_me_anonymous
attr PV_Anlage_1_API reading0101JSON role
attr PV_Anlage_1_API reading0101Name auth_me_role
attr PV_Anlage_1_API reading0102JSON active
attr PV_Anlage_1_API reading0102Name auth_me_active
attr PV_Anlage_1_API reading0103JSON locked
attr PV_Anlage_1_API reading0103Name auth_me_locked
attr PV_Anlage_1_API reading0104JSON authenticated
attr PV_Anlage_1_API reading0104Name auth_me_authenticated
attr PV_Anlage_1_API reading0105JSON salt
attr PV_Anlage_1_API reading0105Name auth_salt
attr PV_Anlage_1_API reading0106JSON transactionId
attr PV_Anlage_1_API reading0106Name auth_transactionId
attr PV_Anlage_1_API reading0107JSON rounds
attr PV_Anlage_1_API reading0107Name auth_rounds
attr PV_Anlage_1_API reading0108JSON nonce
attr PV_Anlage_1_API reading0108Name auth_nonce
attr PV_Anlage_1_API reading0109JSON token
attr PV_Anlage_1_API reading0109Name auth_token
attr PV_Anlage_1_API reading0110JSON message
attr PV_Anlage_1_API reading0111JSON signature
attr PV_Anlage_1_API reading0111Name auth_signature
attr PV_Anlage_1_API reading0112JSON sessionId
attr PV_Anlage_1_API reading0112Name auth_sessionId

attr PV_Anlage_1_API reading0200JSON api_version
attr PV_Anlage_1_API reading0200Name info_api_version
attr PV_Anlage_1_API reading0201JSON sw_version
attr PV_Anlage_1_API reading0201Name info_sw_version
attr PV_Anlage_1_API reading0202JSON name
attr PV_Anlage_1_API reading0202Name info_name
attr PV_Anlage_1_API reading0203JSON hostname
attr PV_Anlage_1_API reading0203Name info_hostname

attr PV_Anlage_1_API replacement01Mode expression
attr PV_Anlage_1_API replacement01Regex %IP-Address_Plenticore%
attr PV_Anlage_1_API replacement01Value {ReadingsVal("PV_Anlage_1_config","IP-Address_Plenticore","")}

attr PV_Anlage_1_API replacement02Mode reading
attr PV_Anlage_1_API replacement02Regex %auth_transactionId%
attr PV_Anlage_1_API replacement02Value auth_transactionId

attr PV_Anlage_1_API replacement03Mode reading
attr PV_Anlage_1_API replacement03Regex %auth_proof%
attr PV_Anlage_1_API replacement03Value auth_proof

attr PV_Anlage_1_API replacement04Mode expression
attr PV_Anlage_1_API replacement04Regex %randomString64%
attr PV_Anlage_1_API replacement04Value {my $NAME = "PV_Anlage_1_API" ;;;;fhem("deletereading ".$NAME." message");;;;fhem("deletereading ".$NAME." auth.*");;;;my @chars=('a'..'z','A'..'Z','0'..'9');; my $r;; foreach(1..16) {$r.=$chars[rand @chars];;};; fhem("setreading ".$NAME." auth_randomString64 ".$r);; $r;;}

attr PV_Anlage_1_API replacement05Mode reading
attr PV_Anlage_1_API replacement05Regex %auth_randomString64%
attr PV_Anlage_1_API replacement05Value auth_randomString64

attr PV_Anlage_1_API replacement06Mode reading
attr PV_Anlage_1_API replacement06Regex %auth_token%
attr PV_Anlage_1_API replacement06Value auth_token

attr PV_Anlage_1_API replacement07Mode reading
attr PV_Anlage_1_API replacement07Regex %auth_signature%
attr PV_Anlage_1_API replacement07Value auth_signature

attr PV_Anlage_1_API replacement08Mode reading
attr PV_Anlage_1_API replacement08Regex %auth_authtag%
attr PV_Anlage_1_API replacement08Value auth_authtag

attr PV_Anlage_1_API replacement09Mode reading
attr PV_Anlage_1_API replacement09Regex %auth_payload%
attr PV_Anlage_1_API replacement09Value auth_payload

attr PV_Anlage_1_API replacement10Mode reading
attr PV_Anlage_1_API replacement10Regex %auth_iv%
attr PV_Anlage_1_API replacement10Value auth_iv

attr PV_Anlage_1_API replacement11Mode reading
attr PV_Anlage_1_API replacement11Regex %auth_sessionId%
attr PV_Anlage_1_API replacement11Value auth_sessionId

attr PV_Anlage_1_API showBody 1
attr PV_Anlage_1_API showError 1

attr PV_Anlage_1_API sid01Data {"nonce": "%randomString64%","username": "user"}
attr PV_Anlage_1_API sid01ParseResponse 1
attr PV_Anlage_1_API sid01URL http://%IP-Address_Plenticore%/api/v1/auth/start
attr PV_Anlage_1_API sidHeader01 Accept-Encoding: gzip,deflate
attr PV_Anlage_1_API sidHeader02 Content-type: application/json, Accept: application/json, Connection: keep-alive

attr PV_Anlage_1_API timeout 7

attr PV_Anlage_1_API userReadings auth_Step1_Message:auth_nonce.* {system("/usr/bin/python3 /opt/fhem/python/bin/plenticore_auth_finish.py ".ReadingsVal("PV_Anlage_1_config","IP-Address_FHEM","")." ".ReadingsVal("$NAME","auth_randomString64","")." ".ReadingsVal("$NAME","auth_nonce","")." ".ReadingsVal("$NAME","auth_salt","")." ".ReadingsVal("$NAME","auth_rounds","")." &");;;; "Prepare auth_finish started with auth_nonce ".ReadingsVal("$NAME","auth_nonce","")},\
\
auth_Step2_Message:auth_proof.* { fhem("get ".$NAME." 02_/auth/finish") ;;;; "HTTP Request 02_/auth/finish with auth_proof ".ReadingsVal("$NAME","auth_proof","")},\
\
auth_Step3_Message:auth_token.* {system("/usr/bin/python3 /opt/fhem/python/bin/plenticore_auth_session.py ".ReadingsVal("PV_Anlage_1_config","IP-Address_FHEM","")." ".ReadingsVal("$NAME","auth_randomString64","")." ".ReadingsVal("$NAME","auth_nonce","")." ".ReadingsVal("$NAME","auth_salt","")." ".ReadingsVal("$NAME","auth_rounds","")." ".ReadingsVal("$NAME","auth_token","")." &");;;; "Prepare auth_session started with auth_token ".ReadingsVal("$NAME","auth_token","")},\
\
auth_Step4_Message:auth_payload.* { fhem("get ".$NAME." 03_/auth/create_session") ;;;; "HTTP Request 03_/auth/create_session with auth_authtag ".ReadingsVal("$NAME","auth_authtag","")},\
\
auth_Step5_Message:auth_sessionId.* { fhem("get ".$NAME." 04_/auth/me") ;;;; "HTTP Request 04_/auth/me with auth_sessionId ".ReadingsVal("$NAME","auth_sessionId","")}

attr PV_Anlage_1_API verbose 5


Readings vom Login aus dem Verbose 5 Log im Anhang

setstate PV_Anlage_1_API 2020-08-31 17:35:32 auth_Step1_Message Prepare auth_finish started with auth_nonce p5NyrqY18ApsAfScCi7/T/EbIdL3kr5a
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_Step2_Message HTTP Request 02_/auth/finish with auth_proof XbY/r/bWVcymH/z+DY0SW0U7c45bx3eALRBkZo3O9Hk=
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_Step3_Message Prepare auth_session started with auth_token fa62e94ef9be096ba7ef64186464eb18600c77aa4e09f9524c7e6e9bebfd06d7
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_Step4_Message HTTP Request 03_/auth/create_session with auth_authtag tUw9M0yqFYE2Fzd0DZieLQ==
setstate PV_Anlage_1_API 2020-08-31 17:35:34 auth_Step5_Message HTTP Request 04_/auth/me with auth_sessionId e4977e08087e6746d3b838b086accd749ba2efcc1f158075b1cd19e8e7008de0
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_authtag tUw9M0yqFYE2Fzd0DZieLQ==
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_iv aIyU55mGYnpQF8ijDGKahQ==
setstate PV_Anlage_1_API 2020-08-31 17:35:35 auth_me_active 1
setstate PV_Anlage_1_API 2020-08-31 17:35:35 auth_me_anonymous 0
setstate PV_Anlage_1_API 2020-08-31 17:35:35 auth_me_authenticated 1
setstate PV_Anlage_1_API 2020-08-31 17:35:35 auth_me_locked 0
setstate PV_Anlage_1_API 2020-08-31 17:35:35 auth_me_role USER
setstate PV_Anlage_1_API 2020-08-31 17:35:32 auth_nonce p5NyrqY18ApsAfScCi7/T/EbIdL3kr5a
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_payload MRvXZiaZrX7LiH9H1IcPsFmJjotoMc07j9/+yyZHhYLvIds3ctU5+jmqx/OEVtYH13xGbRCVJwXwAc9+YUXM4w==
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_proof XbY/r/bWVcymH/z+DY0SW0U7c45bx3eALRBkZo3O9Hk=
setstate PV_Anlage_1_API 2020-08-31 17:35:32 auth_randomString64 p5NyrqY18ApsAfSc
setstate PV_Anlage_1_API 2020-08-31 17:35:32 auth_rounds 29000
setstate PV_Anlage_1_API 2020-08-31 17:35:32 auth_salt DbAC0R85jwF0rh+r
setstate PV_Anlage_1_API 2020-08-31 17:35:34 auth_sessionId e4977e08087e6746d3b838b086accd749ba2efcc1f158075b1cd19e8e7008de0
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_signature CWm0BWcJg7WEW9Cg7Osya0LL0lM2gw7Koo4H+S7APnM=
setstate PV_Anlage_1_API 2020-08-31 17:35:33 auth_token fa62e94ef9be096ba7ef64186464eb18600c77aa4e09f9524c7e6e9bebfd06d7
setstate PV_Anlage_1_API 2020-08-31 17:35:32 auth_transactionId 63ae48fd4da53c956299393c737b3696ccfeef1d768b4233bb96005e0f6b89da
setstate PV_Anlage_1_API 2020-08-30 16:34:36 info_api_version 0.2.0
setstate PV_Anlage_1_API 2020-08-30 16:34:36 info_hostname scb
setstate PV_Anlage_1_API 2020-08-30 16:34:36 info_name PUCK RESTful API
setstate PV_Anlage_1_API 2020-08-30 16:34:36 info_sw_version 01.15.04581


Ab nun kann man bei Übergabe der auth_sessionId beliebig viele Abfragen durchführen.
Verbose 5 log mit einem initialen Login ist im Anhang.

Hier habe ich mal die beiden Python Skripte als test Varianten aufgelistet. Es wird nur mit fixen Werten gearbeitet und alles drum herum ist weg.

Wer mir das nach Perl transferieren kann bekommt besondere Anerkennung  :-)

fhem@raspberrypi:~/python/bin$ cat auth_finish.py
import string
import base64
import hashlib
import hmac
from Crypto.Cipher import AES
import binascii

PASSWD = "<Passwort>"

u       = "TESMUWZnwkJZbnpF"                   # randomString
i       = "TE2MUWZnwkJZbnpFQ5ulCfolNNdAD0vT"   # nonce
o       = int("29000")                         # rounds
a       = "DbAC0R85jwF0rh+r"                   # salt

bitSalt = base64.b64decode(a)

def getPBKDF2Hash(password, bytedSalt, rounds):
    return hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), bytedSalt, rounds)

r = getPBKDF2Hash(PASSWD,bitSalt,o)

print("randomString: ",u)
print("nonce       : ",i)
print("salt        : ",a)
print("rounds      : ",o)

s = hmac.new(r, "Client Key".encode('utf-8'), hashlib.sha256).digest()
_ = hashlib.sha256(s).digest()
d = "n=user,r="+u+",r="+i+",s="+a+",i="+str(o)+",c=biws,r="+i

######## Bis hier sind die Skripte, bis auf die token Variable identisch ##############

c = hmac.new(r, "Server Key".encode('utf-8'), hashlib.sha256).digest()
p = hmac.new(c, d.encode('utf-8'), hashlib.sha256).digest()

g = hmac.new(_, d.encode('utf-8'), hashlib.sha256).digest()
f = bytes(a ^ b for (a, b) in zip(s, g))

proof = base64.b64encode(f).decode('utf-8')

print("proof       : ",proof)



fhem@raspberrypi:~/python/bin$ cat auth_session.py
import string
import base64
import hashlib
import os
import hmac
from Crypto.Cipher import AES
import binascii

PASSWD = "<Passwort>"

u       = "TESMUWZnwkJZbnpF"                   # randomString
i       = "TE2MUWZnwkJZbnpFQ5ulCfolNNdAD0vT"   # nonce
o       = int("29000")                         # rounds
a       = "DbAC0R85jwF0rh+r"                   # salt
token   = "55bb84401b046b3fa1aa6df14d7f4e3ffa7f64cb7e7fd46f3692f09771975fd8"

bitSalt = base64.b64decode(a)

def getPBKDF2Hash(password, bytedSalt, rounds):
    return hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), bytedSalt, rounds)

r = getPBKDF2Hash(PASSWD,bitSalt,o)

print("randomString: ",u)
print("nonce       : ",i)
print("salt        : ",a)
print("rounds      : ",o)
print("token       : ",token)

s = hmac.new(r, "Client Key".encode('utf-8'), hashlib.sha256).digest()
_ = hashlib.sha256(s).digest()
d = "n=user,r="+u+",r="+i+",s="+a+",i="+str(o)+",c=biws,r="+i

######## Bis hier sind die Skripte, bis auf die token Variable identisch ##############

y = hmac.new(_, "Session Key".encode('utf-8'), hashlib.sha256)
y.update(d.encode('utf-8'))
y.update(s)

protocol_key = y.digest()
t = os.urandom(16)              # Zum Testen muss hier natürlich ein fester Wert rein

t = b'rD\xbaos\xc8\xcd\xc4{#.\x13\x11E\x199'    # Damit nicht immer andere Ergebniss kommen :-)
print("t           : ",t)

e2          = AES.new(protocol_key,AES.MODE_GCM,t)
e2, authtag = e2.encrypt_and_digest(token.encode('utf-8'))

iv      = base64.b64encode(t).decode('utf-8')
authtag = base64.b64encode(authtag).decode("utf-8")
payload = base64.b64encode(e2).decode('utf-8')

print("iv          : ",iv)
print("authtag     : ",authtag)
print("payload     : ",payload)


Ablaufbeschreibung

Alles beginnt an dieser Stelle  im replacement Teil. Ich musste einen sich ändernden String generieren und das
bevor der Anmeldeprozess los geht.

replacement04Mode expression
replacement04Regex %randomString64%
replacement04Value {my $NAME = "PV_Anlage_1_API" ;;fhem("deletereading ".$NAME." message");;fhem("deletereading ".$NAME." auth.*");;my @chars=('a'..'z','A'..'Z','0'..'9'); my $r; foreach(1..16) {$r.=$chars[rand @chars];};; fhem("setreading ".$NAME." auth_randomString64 ".$r);; $r;;}

Dann geht es in die Kommunikation mit dem Plenticore....

Im userreading wird anschließend reagiert

## Es wird geschaut, was gerade aufgerufen wurde, damit es nach dem Anmelden wiederholt werden kann
auth_first_called:auth_randomString64.* {my $first_called = InternalVal("$NAME","path","n.a");; $first_called =~ s/:/_/g;;$first_called ;;$first_called =~ s/\/api\/v1//g;; my %replace = ("/auth/me" => "04_/auth/me","/info/version" => "05_/info/version","/processdata/scb_statistic_EnergyFlow" => "20_/processdata/scb_statistic_EnergyFlow","/modules_list" => "21_/modules_list","/logdata/download" => "24_/logdata/download","/update/status" => "41_/update/status","/system/reboot" => "04_/auth/me",);; $replace{$first_called};;},

## Die erste Antwort vom Plenticore ist unter anderem die transactionId, die dann das erste Skript mit den weiteren Parametern aufruft
auth_Step1_Message:auth_transactionId.* {system("/usr/bin/python3 /opt/fhem/python/bin/plenticore_auth_finish.py ".ReadingsVal("PV_Anlage_1_config","IP-Address_FHEM","")." ".ReadingsVal("$NAME","auth_randomString64","")." ".ReadingsVal("$NAME","auth_nonce","")." ".ReadingsVal("$NAME","auth_salt","")." ".ReadingsVal("$NAME","auth_rounds","29000")." &");; "Prepare auth_finish started with auth_nonce ".ReadingsVal("$NAME","auth_nonce","")},

## Daraus kommt der proof und startet im PV_Anlage_1_API den nächsten Aufruf
auth_Step2_Message:auth_proof.* { fhem("get ".$NAME." 02_/auth/finish") ;; "HTTP Request 02_/auth/finish with auth_proof ".ReadingsVal("$NAME","auth_proof","")},

## Es kommt ein token zurück, das dann das zweite Skript triggert
auth_Step3_Message:auth_token.* {system("/usr/bin/python3 /opt/fhem/python/bin/plenticore_auth_session.py ".ReadingsVal("PV_Anlage_1_config","IP-Address_FHEM","")." ".ReadingsVal("$NAME","auth_randomString64","")." ".ReadingsVal("$NAME","auth_nonce","")." ".ReadingsVal("$NAME","auth_salt","")." ".ReadingsVal("$NAME","auth_rounds","")." ".ReadingsVal("$NAME","auth_token","")." &");; "Prepare auth_session started with auth_token ".ReadingsVal("$NAME","auth_token","")},

## Nun sind alle key generiert und durch den payload wird der Sessionaufbau aufgerufen
auth_Step4_Message:auth_payload.* { fhem("get ".$NAME." 03_/auth/create_session") ;; "HTTP Request 03_/auth/create_session with auth_authtag ".ReadingsVal("$NAME","auth_authtag","")},

## Nun kommt die SessionId vom Plenticore und man kann anfangen beliebige Abfragen abzusetzen.
## Als erstes wird dann der ursprüngliche Aufruf wiederholt, der zu beginn in auth_first_call gespeichert worden ist.
auth_Step5_Message:auth_sessionId.* {my $restack = ReadingsVal("$NAME","auth_first_called","") ;; ($restack ne "")?fhem("get ".$NAME." ".$restack):"No further HTTP request";;},

Und schon geht's los.

Ab nun kann man auch set und get beliebig oft aufrufen. Auch ein relogin funktioniert.
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick

plin

Hi,

die halbe Wegstrecke habe ich hinter mir - Aufgabe 1 ist gelöst.

Die alternative auth_finish.pl sieht so aus

#!/usr/bin/perl
package main;

use strict;
use warnings;

use Encode qw(decode encode);
use MIME::Base64;
use JSON;
use PBKDF2::Tiny qw/derive verify/;
use Digest::SHA qw(sha256 hmac_sha256);


my $PASSWD = "qJxj197a4htW";

my $u       = "TESMUWZnwkJZbnpF";                   # randomString
my $i       = "TE2MUWZnwkJZbnpFQ5ulCfolNNdAD0vT";   # nonce
my $o       = int("29000");                         # rounds
my $a       = "DbAC0R85jwF0rh+r";                   # salt

# ------------------------------- bitSalt = base64.b64decode(a)
my $bitSalt = decode_base64($a);
# ------------------------------- r = getPBKDF2Hash(PASSWD,bitSalt,o)
my $r = derive( 'SHA-256', $PASSWD, $bitSalt, $o );
# ------------------------------- s = hmac.new(r, "Client Key".encode('utf-8'), hashlib.sha256).digest()
my $ck = encode('UTF-8', "Client Key");
my $s = hmac_sha256($ck, $r);
# ------------------------------- _ = hashlib.sha256(s).digest()
my $underscore = sha256($s);
# ------------------------------- d = "n=user,r="+u+",r="+i+",s="+a+",i="+str(o)+",c=biws,r="+i
my $d = "n=user,r=".$u.",r=".$i.",s=".$a.",i=".$o.",c=biws,r=".$i;
# ------------------------------- c = hmac.new(r, "Server Key".encode('utf-8'), hashlib.sha256).digest()
my $sk = encode('UTF-8', "Server Key");
my $c = hmac_sha256($sk, $r);
# ------------------------------- p = hmac.new(c, d.encode('utf-8'), hashlib.sha256).digest()
my $pd = encode('UTF-8', $d);
my $p = hmac_sha256($pd, $c);
# ------------------------------- g = hmac.new(_, d.encode('utf-8'), hashlib.sha256).digest()
my $gd = encode('UTF-8', $d);
my $g = hmac_sha256($gd, $underscore);
# ------------------------------- f = bytes(a ^ b for (a, b) in zip(s, g))
my $f = "";
my $g1 = "";
my $s1 = "";
my $f1 = "";
my $j = 0;
for($j=0; $j<length($g); $j++) {
   $g1 = substr($g,$j,1);
   $s1 = substr($s,$j,1);
   $f1 = $s1 ^ $g1 ;
   $f = $f.$f1;
}
# ------------------------------- proof = base64.b64encode(f).decode('utf-8')
#
my $pe = encode_base64($f);
my $proof = decode('UTF-8', $pe);
$proof =~ s/\n$//g;
print "proof   : ".$proof."\n";


Etwas blöd war, dass encode_base64 ein "\n" am Ende einfügt. Das hat etwas Zeit und Nerven gekostet.

In der auth_session.py ist aktuell noch folgende Baustelle offen

y = hmac.new(_, "Session Key".encode('utf-8'), hashlib.sha256)
y.update(d.encode('utf-8'))
y.update(s)

protocol_key = y.digest()


Digest::SHA bietet die Möglichkeit in der Syntax
my $s = hmac_sha256($ck, $r);
einen key und data zu übergeben. Dafür habe ich aber noch keine update/add-Funktion gefunden.

Die Alternative
my $y = Digest::SHA->new(256);
$y->add($sk);

bietet zwar die add-Funktion aber keine Möglichkeit einen key mitzugeben (jedenfalls habe ich bisher noch kein passendes Beispiel gefunden).

Hat jemand einen Tipp?

Ciao
plin

FHEM1 (Main) Raspi4 mit CUL, Homematic, SDUINO 433/OOK, zentrale Steuerung
FHEM2 (Keller) x86 mit CUL/hmland, IP-basierte Module
FHEM3 (Erdgeschoss) Raspi2 mit SDUINO 868/GFSK
FHEM4 (Hausanschlussraum), USV und OBIS-Modul
FHEM5 (Docker) mit FHEM2FHEM, InfluxDB

ch.eick

Zitat von: plin am 11 November 2020, 20:00:35
die halbe Wegstrecke habe ich hinter mir - Aufgabe 1 ist gelöst.

Die alternative auth_finish.pl sieht so aus

Hallo Peter,
das ist echt super, ich hate bereits die Hoffnung auf Hilfe verloren.

Soll ich morgen die erste Funktion bei mir schon mal testen?

Gruß
    Christian
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick

plin

Zitat von: ch.eick am 11 November 2020, 20:21:58
Soll ich morgen die erste Funktion bei mir schon mal testen?
Kannst Du heute schon ... Du hast Mail.

VG Peter
FHEM1 (Main) Raspi4 mit CUL, Homematic, SDUINO 433/OOK, zentrale Steuerung
FHEM2 (Keller) x86 mit CUL/hmland, IP-basierte Module
FHEM3 (Erdgeschoss) Raspi2 mit SDUINO 868/GFSK
FHEM4 (Hausanschlussraum), USV und OBIS-Modul
FHEM5 (Docker) mit FHEM2FHEM, InfluxDB

plin

Zitat von: plin am 11 November 2020, 20:00:35
In der auth_session.py ist aktuell noch folgende Baustelle offen

y = hmac.new(_, "Session Key".encode('utf-8'), hashlib.sha256)
y.update(d.encode('utf-8'))
y.update(s)

protocol_key = y.digest()

Die Lösung sieht so aus:

# ------------------------------- y = hmac.new(_, "Session Key".encode('utf-8'), hashlib.sha256)
# ------------------------------- y.update(d.encode('utf-8'))
# ------------------------------- y.update(s)
# ------------------------------- protocol_key = y.digest()
#
my $sk = encode('UTF-8', "Session Key");
my $dd = encode('UTF-8', $d);
my $protocol_key = hmac_sha256($sk, $dd, $s, $underscore);
FHEM1 (Main) Raspi4 mit CUL, Homematic, SDUINO 433/OOK, zentrale Steuerung
FHEM2 (Keller) x86 mit CUL/hmland, IP-basierte Module
FHEM3 (Erdgeschoss) Raspi2 mit SDUINO 868/GFSK
FHEM4 (Hausanschlussraum), USV und OBIS-Modul
FHEM5 (Docker) mit FHEM2FHEM, InfluxDB

ch.eick

Zitat von: plin am 11 November 2020, 20:48:43
Die Lösung sieht so aus:
...
Okay, super , dann bauen wir das mal zusammen und ich würde es dann testen.
Dabei kann ich dann sicher noch etwas im PV_Anlage_1_API aufräumen, damit Stefan nicht mehr so die Haare zu berge stehen.
Anschließend versuche ich das ganze noch mit den sid Attributen zu verfeinern.

Gruß
    Christian
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick

ch.eick

#6
Hallo Peter,

ich habe die neue Funktion jetzt bei mir eingebaut und getestet. Der Test war erfolgreich und ich bedanke mich nochmals auf diesem Wege.

Im zweiten Skripte sind dann diese Zeilen noch zu Perl zu migrieren

######## Bis hier sind die Skripte, bis auf die token Variable identisch ##############

y = hmac.new(_, "Session Key".encode('utf-8'), hashlib.sha256)
y.update(d.encode('utf-8'))
y.update(s)

protocol_key = y.digest()
t = os.urandom(16)              # Zum Testen muss hier natürlich ein fester Wert rein

t = b'rD\xbaos\xc8\xcd\xc4{#.\x13\x11E\x199'    # Damit nicht immer andere Ergebniss kommen :-)

e2          = AES.new(protocol_key,AES.MODE_GCM,t)
e2, authtag = e2.encrypt_and_digest(token.encode('utf-8'))

iv      = base64.b64encode(t).decode('utf-8')
authtag = base64.b64encode(authtag).decode("utf-8")
payload = base64.b64encode(e2).decode('utf-8')



Eins sollten wir jedoch nochmals diskutieren.
Aus Deiner 99_myUtils kann ich erkennen, dass Du einen Teil der http Kommunikation, die eigentlich das HTTPMOD Modul macht, ausgelagert hast.
Deshalb habe ich  nur die Essenz, nämlich das Generieren der Key in eine plenticore_auth($$$$$$) Funktion geschrieben.
All die http Aufrufe sollen direkt durch HTTPMOD erledigt werden.

Anhand der Benennung Deiner Funktionen erkenne ich, dass Du Dich momentan nur auf einige wenige Abragen beschränkt hast. Wenn Du diesen Weg gehen möchtest, kommen später dann noch die set und spezielle Funktionen für reset, Firmware , ... . Dadurch würde dann Dein Weg der Implementierung noch um einiges Komplexer werden.

In der PV_Anlage_1_API  kannst Du ja schon viele Möglichkeiten erkennen.

Sobald dann die zweite Funktion migriert ist fasse ich dann beides nochmal zusammen und stelle die PV_Anlage_1_API Umsetzung hier rein.
Mit Stefan versuche ich das dann in eine bessere Umsetzung ohne die Trigger über die userreadings umzusetzen. Einiges habe ich auch dort bereits bereinigt.

Gruß
   Christian
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick

plin

Nun ist auch die auth_session.pl funktionsfähig:
#!/usr/bin/perl
package main;

use strict;
use warnings;

use Encode qw(decode encode);
use MIME::Base64;
use JSON;
use PBKDF2::Tiny qw/derive verify/;
#use Digest::HMAC qw(hmac hmac_hex);
use Digest::SHA qw(sha256 hmac_sha256);
use Digest::HMAC_SHA1;
use Crypt::URandom qw( urandom );
use Crypt::AuthEnc::GCM;

my $PASSWD = "qJxj197a4htW";

my $u       = "TESMUWZnwkJZbnpF";                   # randomString
my $i       = "TE2MUWZnwkJZbnpFQ5ulCfolNNdAD0vT";   # nonce
my $o       = int("29000");                         # rounds
my $a       = "DbAC0R85jwF0rh+r";                   # salt
my $token   = "55bb84401b046b3fa1aa6df14d7f4e3ffa7f64cb7e7fd46f3692f09771975fd8";

# ------------------------------- bitSalt = base64.b64decode(a)
#
my $bitSalt = decode_base64($a);

# ------------------------------- r = getPBKDF2Hash(PASSWD,bitSalt,o)
#
my $r = derive( 'SHA-256', $PASSWD, $bitSalt, $o );

print "randomString: ".$u."\n";
print "noce        : ".$i."\n";
print "salt        : ".$a."\n";
print "rounds      : ".$o."\n";
print "token       : ".$token."\n";

# ------------------------------- s = hmac.new(r, "Client Key".encode('utf-8'), hashlib.sha256).digest()
my $ck = encode('UTF-8', "Client Key");
my $s = hmac_sha256($ck, $r);

# ------------------------------- _ = hashlib.sha256(s).digest()
my $underscore = sha256($s);

# ------------------------------- d = "n=user,r="+u+",r="+i+",s="+a+",i="+str(o)+",c=biws,r="+i
my $d = "n=user,r=".$u.",r=".$i.",s=".$a.",i=".$o.",c=biws,r=".$i;

# ------------------------------- y = hmac.new(_, "Session Key".encode('utf-8'), hashlib.sha256)
# ------------------------------- y.update(d.encode('utf-8'))
# ------------------------------- y.update(s)
# ------------------------------- protocol_key = y.digest()
#
my $sk = encode('UTF-8', "Session Key");
my $dd = encode('UTF-8', $d);
my $protocol_key = hmac_sha256($sk, $dd, $s, $underscore);

# ------------------------------- t = os.urandom(16)
#my $t = urandom(16);
#my $t = "rD\xbaos\xc8\xcd\xc4{#.\x13\x11E\x199";    # Damit nicht immer andere Ergebniss kommen :-)
my $t = "7244ba6f73c8cdc47b232e1311451939";
$t =~ s/([a-fA-F0-9][a-fA-F0-9])/chr(hex($1))/eg;

# ------------------------------- e2 = AES.new(protocol_key,AES.MODE_GCM,t)
my $e2 = Crypt::AuthEnc::GCM->new("AES", $protocol_key, $t);

# ------------------------------- e2, authtag = e2.encrypt_and_digest(token.encode('utf-8'))
my $tt = encode('UTF-8', $token);
my $e2ct = $e2->encrypt_add($tt);
# ------------------------------- e2, authtag = e2.encrypt_and_digest(token.encode('utf-8'))
my $authtag = $e2->encrypt_done();

# ------------------------------- iv      = base64.b64encode(t).decode('utf-8')
$tt = encode_base64($t);
$tt =~ s/\n$//g;                        # Korrektur: \n am Ende des STrings entfernen, Ursache unbekannt
my $iv = decode('UTF-8', $tt);

# ------------------------------- authtag = base64.b64encode(authtag).decode("utf-8")
my $aa = encode_base64($authtag);
$aa =~ s/\n$//g;                        # Korrektur: \n am Ende des STrings entfernen, Ursache unbekannt
$authtag = decode('UTF-8', $aa);

# ------------------------------- authtag = base64.b64encode(authtag).decode("utf-8")
my $pp = encode_base64($e2ct);
$pp =~ s/\n//g;                        # Korrektur: \n am Ende des STrings entfernen, Ursache unbekannt
my $payload = decode('UTF-8', $pp);

print "iv          : ".$iv."\n";
print "authtag     : ".$authtag."\n";
print "payload     : ".$payload."\n";


Und ja, eine HTTPMOD-Lösung ist schön, wird aber vermutlich unübersichtlich. Ich habe das wirrwar der verschiedenen Attribute nicht verstanden und muss mich erst mal einlesen. Da ist perl-Code einfacher zu lesen.

VG Peter
FHEM1 (Main) Raspi4 mit CUL, Homematic, SDUINO 433/OOK, zentrale Steuerung
FHEM2 (Keller) x86 mit CUL/hmland, IP-basierte Module
FHEM3 (Erdgeschoss) Raspi2 mit SDUINO 868/GFSK
FHEM4 (Hausanschlussraum), USV und OBIS-Modul
FHEM5 (Docker) mit FHEM2FHEM, InfluxDB

ch.eick

Zitat von: plin am 12 November 2020, 17:17:10
Und ja, eine HTTPMOD-Lösung ist schön, wird aber vermutlich unübersichtlich. Ich habe das wirrwar der verschiedenen Attribute nicht verstanden und muss mich erst mal einlesen.
Okay, ich arbeite bereits daran, und die Perl Funktionen sind ein großer Schritt.
Im HTTPMOD ist solch ein mehrstufiges Login noch nicht drin, aber ich habe es bereits geposted. Eventuell kann Stefan da ja etwas machen, das der sid Mechanismus flexibler wird.

Zitat
Da ist perl-Code einfacher zu lesen.
Nur musst Du dann entweder eine Komplexe Perl Routine schreiben, die alle möglichen Abfrage handelt und Dir auch die readings parsed, oder es werden >20 einzelne Routinen, bei denen ich das Abfragen und Setzen bereits umgesetzt habe.

Die Variante mit dem HTTPMOD werde ich dann wieder ins Wiki setzen, damit das Python dort raus kommt.
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick

ch.eick

Die letzte Version ist nun im Wiki unter PV_Anlage_1_API zu finden.
Danke an alle für die Hilfe.
RPI4; Docker; CUNX; Eltako FSB61NP; SamsungTV H-Serie; Sonos; Vallox; Luxtronik; 3x FB7490; Stromzähler mit DvLIR; wunderground; Plenticore 10 mit BYD; EM410; SMAEM; Modbus TCP
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/ch.eick