2016-08-14 3 views
8

Mit dem jüngsten Update von Windows Anniversary, um zu überprüfen, jetzt Rand unterstützt biometrische Authentifizierung mit Windows Hallo (vgl https://developer.microsoft.com/en-us/microsoft-edge/platform/documentation/dev-guide/device/web-authentication/, https://blogs.windows.com/msedgedev/2016/04/12/a-world-without-passwords-windows-hello-in-microsoft-edge/)biometrische Login (webauthn) in Go, wie Signatur

ich einige Proben in C#, PHP und Node.js, und ich versuche, es in Go arbeiten zu lassen.

function parseBase64(s) { 
    s = s.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, '');   
    return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0) }));   
} 

function concatUint8Array(a1,a2) { 
    var d = new Uint8Array(a1.length + a2.length); 
    d.set(a1); 
    d.set(a2,a1.length); 
    return d; 
} 

var credAlgorithm = "RSASSA-PKCS1-v1_5"; 
var id,authenticatorData,signature,hash; 
webauthn.getAssertion("chalenge").then(function(assertion) { 
    id = assertion.credential.id; 
    authenticatorData = assertion.authenticatorData; 
    signature = assertion.signature; 
    return crypto.subtle.digest("SHA-256",parseBase64(assertion.clientData)); 
}).then(function(h) { 
    hash = new Uint8Array(h); 
    var publicKey = "{\"kty\":\"RSA\",\"alg\":\"RS256\",\"ext\":false,\"n\":\"mEqGJwp0GL1oVwjRikkNfzd-Rkpb7vIbGodwQkTDsZT4_UE02WDaRa-PjxzL4lPZ4rUpV5SqVxM25aEIeGkEOR_8Xoqx7lpNKNOQs3E_o8hGBzQKpGcA7de678LeAUZdJZcnnQxXYjNf8St3aOIay7QrPoK8wQHEvv8Jqg7O1-pKEKCIwSKikCFHTxLhDDRo31KFG4XLWtLllCfEO6vmQTseT-_8OZPBSHOxR9VhIbY7VBhPq-PeAWURn3G52tQX-802waGmKBZ4B87YtEEPxCNbyyvlk8jRKP1KIrI49bgJhAe5Mow3yycQEnGuPDwLzmJ1lU6I4zgkyL1jI3Ghsw\",\"e\":\"AQAB\"}"; 
    return crypto.subtle.importKey("jwk",JSON.parse(publicKey),credAlgorithm,false,["verify"]); 
}).then(function(key) { 
    return crypto.subtle.verify({name:credAlgorithm, hash: { name: "SHA-256" }},key,parseBase64(signature),concatUint8Array(parseBase64(authenticatorData),hash)); 
}).then(function(result) { 
    console.log("ID=" + id + "\r\n" + result); 
}).catch(function(err) { 
    console.log('got err: ', err); 
}); 

In go Ich habe den folgenden Code, soll die oben JS-Code übereinstimmen (req ist eine Struktur mit Strings:

folgende Arbeiten in JS (Ich habe in der Herausforderung und der Schlüssel fest einprogrammiert) von einer Anfrage Körper JSON):

func webauthnSigninConversion(g string) ([]byte, error) { 
    g = strings.Replace(g, "-", "+", -1) 
    g = strings.Replace(g, "_", "/", -1) 
    switch(len(g) % 4) { // Pad with trailing '='s 
    case 0: 
        // No pad chars in this case 
    case 2: 
        // Two pad chars 
        g = g + "==" 
    case 3: 
        // One pad char 
        g = g + "="; 
    default: 
        return nil, fmt.Errorf("invalid string in public key") 
    } 
    b, err := base64.StdEncoding.DecodeString(g) 
    if err != nil { 
        return nil, err 
    } 
    return b, nil 
} 


clientData, err := webauthnSigninConversion(req.ClientData) 
if err != nil { 
    return err 
} 

authenticatorData, err := webauthnSigninConversion(req.AuthenticatorData) 
if err != nil { 
    return err 
} 

signature, err := webauthnSigninConversion(req.Signature) 
if err != nil { 
    return err 
} 

publicKey := "{\"kty\":\"RSA\",\"alg\":\"RS256\",\"ext\":false,\"n\":\"mEqGJwp0GL1oVwjRikkNfzd-Rkpb7vIbGodwQkTDsZT4_UE02WDaRa-PjxzL4lPZ4rUpV5SqVxM25aEIeGkEOR_8Xoqx7lpNKNOQs3E_o8hGBzQKpGcA7de678LeAUZdJZcnnQxXYjNf8St3aOIay7QrPoK8wQHEvv8Jqg7O1-pKEKCIwSKikCFHTxLhDDRo31KFG4XLWtLllCfEO6vmQTseT-_8OZPBSHOxR9VhIbY7VBhPq-PeAWURn3G52tQX-802waGmKBZ4B87YtEEPxCNbyyvlk8jRKP1KIrI49bgJhAe5Mow3yycQEnGuPDwLzmJ1lU6I4zgkyL1jI3Ghsw\",\"e\":\"AQAB\"}" // this is really from a db, not hardcoded 
// load json from public key, extract modulus and public exponent 
obj := strings.Replace(publicKey, "\\", "", -1) // remove escapes 
var k struct { 
    N string `json:"n"` 
    E string `json:"e"` 
} 
if err = json.Unmarshal([]byte(obj), &k); err != nil { 
    return err 
} 
n, err := webauthnSigninConversion(k.N) 
if err != nil { 
    return err 
} 
e, err := webauthnSigninConversion(k.E) 
if err != nil { 
    return err 
} 
pk := &rsa.PublicKey{ 
    N: new(big.Int).SetBytes(n), // modulus 
    E: int(new(big.Int).SetBytes(e).Uint64()), // public exponent 
} 
  
hash := sha256.Sum256(clientData) 

// Create data buffer to verify signature over 
b := append(authenticatorData, hash[:]...) 
  
if err = rsa.VerifyPKCS1v15(pk, crypto.SHA256, b, signature); err != nil { 
    return err 
} 

// if no error, signature matches 

versagt Dieser Code mit crypto/rsa: input must be hashed message. Wenn ich zu statt b in rsa.VerifyPKCS1v15 wechseln, schlägt es mit crypto/rsa: verification error fehl. Der Grund, warum ich glaube, dass ich authenticatorData und hash kombinieren muss, ist, dass dies in den C# - und PHP-Beispielcodes geschieht (vgl. https://github.com/adrianba/fido-snippets/blob/master/csharp/app.cs, https://github.com/adrianba/fido-snippets/blob/master/php/fido-authenticator.php).

Vielleicht geht es anders?

Ich habe die Byte-Arrays in JS and Go, gedruckt und überprüft, dass clientData, signatureData, authenticatorData und hash (und die kombinierte Anordnung der letzteren zwei) haben exakt die gleichen Werte. Ich konnte die n- und e-Felder nach dem Erstellen des öffentlichen Schlüssels nicht aus JS extrahieren, daher könnte es ein Problem bei der Erstellung des öffentlichen Schlüssels geben.

+0

wenn jemand mit 1500+ rep könnte ein Tag namens "webauthn" hinzufügen, das wäre toll. (Es ist der Name des Standards, vgl. Https://www.w3.org/Webauthn/) – yngling

Antwort

2

Ich bin kein Crypto-Experte, aber ich habe einige Erfahrung in Go, einschließlich der Überprüfung von Signaturen, die mit PHP signiert wurden. Unter der Annahme, dass die verglichenen Bytewerte gleich sind, würde ich sagen, dass Ihr Problem wahrscheinlich die Erstellung eines öffentlichen Schlüssels ist. Ich würde vorschlagen, meine Lösung zu schaffen, öffentliche Schlüssel von Modulus und Exponent mit dieser Funktion versuchen:

func CreatePublicKey(nStr, eStr string)(pubKey *rsa.PublicKey, err error){ 

    decN, err := base64.StdEncoding.DecodeString(nStr) 
    n := big.NewInt(0) 
    n.SetBytes(decN) 

    decE, err := base64.StdEncoding.DecodeString(eStr) 
    if err != nil { 
     fmt.Println(err) 
     return nil, err 
    } 
    var eBytes []byte 
    if len(decE) < 8 { 
     eBytes = make([]byte, 8-len(decE), 8) 
     eBytes = append(eBytes, decE...) 
    } else { 
     eBytes = decE 
    } 
    eReader := bytes.NewReader(eBytes) 
    var e uint64 
    err = binary.Read(eReader, binary.BigEndian, &e) 
    if err != nil { 
     fmt.Println(err) 
     return nil, err 
    } 
    pKey := rsa.PublicKey{N: n, E: int(e)} 
    return &pKey, nil 
} 

ich meinen öffentlichen Schlüssel verglichen und Yours (Playground), und sie haben unterschiedliche Werte. Könnten Sie mir bitte Feedback zu der Lösung geben, die ich mit Ihrem Code vorgeschlagen habe, wenn es funktioniert?

Edit 1: urlencoding Beispiel Playground 2

Edit 2: Dies ist, wie ich die Signatur überprüfen:

hasher := sha256.New() 
hasher.Write([]byte(data)) 
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, hasher.Sum(nil), signature) 

So wird die Variable in Edit 2 Schnipsel 'data' ist die gleichen Daten (Nachricht), die zum Signieren auf PHP-Seite verwendet wurde.

+0

Sie sollten auch versuchen, den Modulus mit URLEncoding statt StdEncoding wie folgt zu dekodieren: base64.URLEncoding.DecodeString (nStr) mit mein Beispiel – kingSlayer

+0

Vielen Dank für Ihr Interesse! Ich habe das oben (und URLEncoding) versucht, es gibt die gleichen Fehler, die ich in der Frage erwähne ("crypto/rsa: Eingabe muss gehashte Nachricht sein" und "crypto/rsa: Verifikationsfehler"). Wie rssst du in deinem Code rsa.VerifyPKCS1v15? Kombinieren Sie authenticatorData und Hash? – yngling

+0

Im PHP-Beispielcode wird die Verifizierung mit "return $ rsa-> verify ($ a.$ h, $ s); ", aber wenn ich dasselbe in Go mache, bekomme ich den Fehler" crypto/rsa: input muss Hash-Nachricht sein. " – yngling