2016-08-14 40 views
8

Dzięki bardzo najnowszej aktualizacji systemu Windows Anniversary Edge obsługuje uwierzytelnianie biometryczne przy użyciu Windows Hello (por 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/)biometryczna logowania (webauthn) w Go, jak zweryfikować podpis

Mam kilka próbek w C#, PHP i Node.js, i staram się, aby działało w Go.

następujących utworów w JS (Mam ustalony w wyzwaniu i klucz):

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); 
}); 

Przejdź Mam następujący kod oznaczało dopasować powyższy kod JS (req jest struct z ciągów z życzeniem ciała 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 

Ten kod nie działa z crypto/rsa: input must be hashed message. Jeśli zmienię na hash[:] zamiast b w rsa.VerifyPKCS1v15, nie powiedzie się z crypto/rsa: verification error. Powodem, dla którego uważam, że muszę łączyć authenticatorData i hash jest to, co dzieje się w przykładowych kodach C# i PHP (cf, https://github.com/adrianba/fido-snippets/blob/master/csharp/app.cs, https://github.com/adrianba/fido-snippets/blob/master/php/fido-authenticator.php).

Może Go to robi inaczej?

I drukowane tablice bajt JS i Go i sprawdzeniu, clientData, signatureData, authenticatorData i hash (a połączone Tablica dwóch ostatnich) mają te same wartości. Nie byłem w stanie wyodrębnić pól n i e z JS po utworzeniu klucza publicznego, więc może być problem z tworzeniem klucza publicznego.

+0

jeśli ktoś mający 1500+ przedstawicieli może dodać tag o nazwie "webauthn", to byłoby wspaniale. (Jest to nazwa standardu, por. Https://www.w3.org/Webauthn/) – yngling

Odpowiedz

2

Nie jestem ekspertem od kryptografii, ale mam pewne doświadczenie w Go, w tym weryfikowanie podpisów podpisanych przy pomocy PHP. Zakładając, że porównywane wartości bajtów są takie same, powiedziałbym, że Twoim problemem jest prawdopodobnie utworzenie klucza publicznego. Proponuję spróbować moje rozwiązanie tworzenia kluczy publicznych z moduł i wykładnik tej funkcji:

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 
} 

porównałem mój klucz publiczny i ofertowe (Playground), i mają różne wartości. Czy mógłbyś przekazać mi opinię o rozwiązaniu, które zasugerowałem z Twoim kodem, jeśli działa?

Edycja 1: URLEncoding przykład Playground 2

Edycja 2: W ten sposób zweryfikować podpis:

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

więc zmiennej 'dane' w Edycja 2 fragmencie to te same dane (wiadomość), która została użyta do podpisu po stronie PHP.

+0

Powinieneś także spróbować odkodować moduł przy pomocy URLEncoding zamiast StdEncoding w ten sposób: base64.URLEncoding.DecodeString (nStr) with mój przykład – kingSlayer

+0

Dziękuję bardzo za zainteresowanie! Próbowałem powyższego (i URLEncoding), to daje te same błędy, które wymienię w pytaniu ("crypto/rsa: wejście musi być hashed wiadomość" i "crypto/rsa: błąd weryfikacji"). W swoim kodzie jak zadzwonić do rsa.VerifyPKCS1v15? Czy łączysz authentatorData i hash? – yngling

+0

W kodzie przykładowym PHP weryfikacja odbywa się przez "return $ rsa-> verify ($ a.$ h, $ s); ", ale jeśli zrobię to samo w Go, pojawi się błąd" crypto/rsa: wejście musi być zakodowaną wiadomością ". – yngling