// VerifyPKCS1v15 verifies an RSA PKCS#1 v1.5 signature. // hashed is the result of hashing the input message using the given hash // function and sig is the signature. A valid signature is indicated by // returning a nil error. func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error) { hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed)) if err != nil { return } tLen := len(prefix) + hashLen k := (pub.N.BitLen() + 7) / 8 if k < tLen+11 { err = VerificationError{} return } c := new(big.Int).SetBytes(sig) m := encrypt(new(big.Int), pub, c) em := leftPad(m.Bytes(), k) // EM = 0x00 || 0x01 || PS || 0x00 || T ok := subtle.ConstantTimeByteEq(em[0], 0) ok &= subtle.ConstantTimeByteEq(em[1], 1) ok &= subtle.ConstantTimeCompare(em[k-hashLen:k], hashed) ok &= subtle.ConstantTimeCompare(em[k-tLen:k-hashLen], prefix) ok &= subtle.ConstantTimeByteEq(em[k-tLen-1], 0) for i := 2; i < k-tLen-1; i++ { ok &= subtle.ConstantTimeByteEq(em[i], 0xff) } if ok != 1 { return VerificationError{} } return nil }
/* Checks the username/password combination from the request. Returns either an empty string (authentication failed) or the name of the authenticated user. Supports MD5 and SHA1 password entries */ func (a *BasicAuth) CheckAuth(r *http.Request) string { s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) if len(s) != 2 || s[0] != "Basic" { return "" } b, err := base64.StdEncoding.DecodeString(s[1]) if err != nil { return "" } pair := strings.SplitN(string(b), ":", 2) if len(pair) != 2 { return "" } passwd := a.Secrets(pair[0], a.Realm) if passwd == "" { return "" } if strings.HasPrefix(passwd, "{SHA}") { d := sha1.New() d.Write([]byte(pair[1])) if subtle.ConstantTimeCompare([]byte(passwd[5:]), []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 { return "" } } else { e := NewMD5Entry(passwd) if e == nil { return "" } if subtle.ConstantTimeCompare([]byte(passwd), MD5Crypt([]byte(pair[1]), e.Salt, e.Magic)) != 1 { return "" } } return pair[0] }
func secureCompare(given, actual string) bool { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 } /* Securely compare actual to itself to keep constant time, but always return false */ return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false }
// CheckHashes verifies all the checksums specified by the "hashes" of the payload. func CheckHashes(payload []byte, hashes Hashes) error { cnt := 0 // k, v indicate the hash algorithm and the corresponding value for k, v := range hashes { switch k { case notary.SHA256: checksum := sha256.Sum256(payload) if subtle.ConstantTimeCompare(checksum[:], v) == 0 { return ErrMismatchedChecksum{alg: notary.SHA256} } cnt++ case notary.SHA512: checksum := sha512.Sum512(payload) if subtle.ConstantTimeCompare(checksum[:], v) == 0 { return ErrMismatchedChecksum{alg: notary.SHA512} } cnt++ } } if cnt == 0 { return fmt.Errorf("at least one supported hash needed") } return nil }
// CheckHashes verifies all the checksums specified by the "hashes" of the payload. func CheckHashes(payload []byte, name string, hashes Hashes) error { cnt := 0 // k, v indicate the hash algorithm and the corresponding value for k, v := range hashes { switch k { case notary.SHA256: checksum := sha256.Sum256(payload) if subtle.ConstantTimeCompare(checksum[:], v) == 0 { return ErrMismatchedChecksum{alg: notary.SHA256, name: name, expected: hex.EncodeToString(v)} } cnt++ case notary.SHA512: checksum := sha512.Sum512(payload) if subtle.ConstantTimeCompare(checksum[:], v) == 0 { return ErrMismatchedChecksum{alg: notary.SHA512, name: name, expected: hex.EncodeToString(v)} } cnt++ } } if cnt == 0 { return ErrMissingMeta{Role: name} } return nil }
func TestDecodeHashPayloadReturnsCorrectString(t *testing.T) { hashPayload := "9:1111:64:bXlzYWx0:bXlwYXNzd29yZA==" r, n, l, salt, p, err := decodeHashPaylaod(hashPayload) if err != nil { t.Errorf("Error encountered, %v", err) } saltRes := subtle.ConstantTimeCompare(salt, []byte("mysalt")) pRes := subtle.ConstantTimeCompare(p, []byte("mypassword")) if !(saltRes == 1) { t.Error("aggg f****d") } if !(pRes == 1) { t.Errorf("incorrect password hash decoded expected: mypassword but got %s", p) } if l != 64 { t.Errorf("incorrect passwordHash length decoded expected: 64 but got %d", l) } if r != 9 { t.Errorf("incorrect number of rounds decoded expected: 9 but got %d", r) } if n != 1111 { t.Errorf("incorrect cost decoded expected: 1111 but got %d", n) } }
func NewHtpasswdValidator(filename string) Validator { provider := HtpasswdFileProvider(filename) return func(username string, passwd string) bool { // realm is ignored hashedPw := provider(username, "") if strings.HasPrefix(hashedPw, "{SHA}") { d := sha1.New() d.Write([]byte(passwd)) if subtle.ConstantTimeCompare([]byte(hashedPw[5:]), []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 { return false } } else { e := NewMD5Entry(hashedPw) if e == nil { return false } if subtle.ConstantTimeCompare([]byte(hashedPw), MD5Crypt([]byte(passwd), e.Salt, e.Magic)) != 1 { return false } } return true } }
// validateMetadata matches the given client nonce and pending time with the // one cached in the identity whitelist during the previous login. But, if // reauthentication is disabled, login attempt is failed immediately. func validateMetadata(clientNonce, pendingTime string, storedIdentity *whitelistIdentity, roleEntry *awsRoleEntry) error { // For sanity if !storedIdentity.DisallowReauthentication && storedIdentity.ClientNonce == "" { return fmt.Errorf("client nonce missing in stored identity") } // If reauthentication is disabled or if the nonce supplied matches a // predefied nonce which indicates reauthentication to be disabled, // authentication will not succeed. if storedIdentity.DisallowReauthentication || subtle.ConstantTimeCompare([]byte(reauthenticationDisabledNonce), []byte(clientNonce)) == 1 { return fmt.Errorf("reauthentication is disabled") } givenPendingTime, err := time.Parse(time.RFC3339, pendingTime) if err != nil { return err } storedPendingTime, err := time.Parse(time.RFC3339, storedIdentity.PendingTime) if err != nil { return err } // When the presented client nonce does not match the cached entry, it // is either that a rogue client is trying to login or that a valid // client suffered a migration. The migration is detected via // pendingTime in the instance metadata, which sadly is only updated // when an instance is stopped and started but *not* when the instance // is rebooted. If reboot survivability is needed, either // instrumentation to delete the instance ID from the whitelist is // necessary, or the client must durably store the nonce. // // If the `allow_instance_migration` property of the registered role is // enabled, then the client nonce mismatch is ignored, as long as the // pending time in the presented instance identity document is newer // than the cached pending time. The new pendingTime is stored and used // for future checks. // // This is a weak criterion and hence the `allow_instance_migration` // option should be used with caution. if subtle.ConstantTimeCompare([]byte(clientNonce), []byte(storedIdentity.ClientNonce)) != 1 { if !roleEntry.AllowInstanceMigration { return fmt.Errorf("client nonce mismatch") } if roleEntry.AllowInstanceMigration && !givenPendingTime.After(storedPendingTime) { return fmt.Errorf("client nonce mismatch and instance meta-data incorrect") } } // Ensure that the 'pendingTime' on the given identity document is not // before the 'pendingTime' that was used for previous login. This // disallows old metadata documents from being used to perform login. if givenPendingTime.Before(storedPendingTime) { return fmt.Errorf("instance meta-data is older than the one used for previous login") } return nil }
// Winnow the data. func Winnow(authKey *[32]byte, noncePrfx, in []byte) ([]byte, error) { if len(in)%EnlargeFactor != 0 { return nil, errors.New("Invalid data size") } out := make([]byte, len(in)/EnlargeFactor) keys := make([]byte, 8*64) nonce := make([]byte, 24) copy(nonce[:8], noncePrfx) var i int var v byte tag := new([16]byte) macKey := new([32]byte) defer zero(macKey[:]) var is01 bool var is00 bool var is11 bool var is10 bool for n := 0; n < len(out); n++ { binary.BigEndian.PutUint64(nonce[16:], uint64(n)) salsa20.XORKeyStream(keys, keys, nonce, authKey) v = 0 for i = 0; i < 8; i++ { copy(macKey[:], keys[64*i:64*i+32]) poly1305.Sum(tag, []byte("1"), macKey) is01 = subtle.ConstantTimeCompare( tag[:], in[16*(n*16+i*2):16*(n*16+i*2+1)], ) == 1 poly1305.Sum(tag, []byte("0"), macKey) is00 = subtle.ConstantTimeCompare( tag[:], in[16*(n*16+i*2):16*(n*16+i*2+1)], ) == 1 copy(macKey[:], keys[64*i+32:64*i+64]) poly1305.Sum(tag, []byte("1"), macKey) is11 = subtle.ConstantTimeCompare( tag[:], in[16*(n*16+i*2+1):16*(n*16+i*2+2)], ) == 1 poly1305.Sum(tag, []byte("0"), macKey) is10 = subtle.ConstantTimeCompare( tag[:], in[16*(n*16+i*2+1):16*(n*16+i*2+2)], ) == 1 if !((is01 && is10) || (is00 && is11)) { zero(keys) return nil, errors.New("Invalid authenticator received") } if is11 { v = v | 1<<uint8(i) } } out[n] = v zero(keys) } return out, nil }
func (sshClient *sshClient) passwordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { expectedSessionIDLength := 2 * common.PSIPHON_API_CLIENT_SESSION_ID_LENGTH expectedSSHPasswordLength := 2 * SSH_PASSWORD_BYTE_LENGTH var sshPasswordPayload struct { SessionId string `json:"SessionId"` SshPassword string `json:"SshPassword"` } err := json.Unmarshal(password, &sshPasswordPayload) if err != nil { // Backwards compatibility case: instead of a JSON payload, older clients // send the hex encoded session ID prepended to the SSH password. // Note: there's an even older case where clients don't send any session ID, // but that's no longer supported. if len(password) == expectedSessionIDLength+expectedSSHPasswordLength { sshPasswordPayload.SessionId = string(password[0:expectedSessionIDLength]) sshPasswordPayload.SshPassword = string(password[expectedSSHPasswordLength:len(password)]) } else { return nil, common.ContextError(fmt.Errorf("invalid password payload for %q", conn.User())) } } if !isHexDigits(sshClient.sshServer.support, sshPasswordPayload.SessionId) || len(sshPasswordPayload.SessionId) != expectedSessionIDLength { return nil, common.ContextError(fmt.Errorf("invalid session ID for %q", conn.User())) } userOk := (subtle.ConstantTimeCompare( []byte(conn.User()), []byte(sshClient.sshServer.support.Config.SSHUserName)) == 1) passwordOk := (subtle.ConstantTimeCompare( []byte(sshPasswordPayload.SshPassword), []byte(sshClient.sshServer.support.Config.SSHPassword)) == 1) if !userOk || !passwordOk { return nil, common.ContextError(fmt.Errorf("invalid password for %q", conn.User())) } sessionID := sshPasswordPayload.SessionId sshClient.Lock() sshClient.sessionID = sessionID geoIPData := sshClient.geoIPData sshClient.Unlock() // Store the GeoIP data associated with the session ID. This makes the GeoIP data // available to the web server for web transport Psiphon API requests. To allow for // post-tunnel final status requests, the lifetime of cached GeoIP records exceeds // the lifetime of the sshClient, and that's why this distinct session cache exists. sshClient.sshServer.support.GeoIPService.SetSessionCache(sessionID, geoIPData) return nil, nil }
func SecureCompare(given, actual []byte) bool { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { if subtle.ConstantTimeCompare(given, actual) == 1 { return true } return false } // Securely compare actual to itself to keep constant time, but always return false if subtle.ConstantTimeCompare(actual, actual) == 1 { return false } return false }
func (sshClient *sshClient) passwordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { var sshPasswordPayload struct { SessionId string `json:"SessionId"` SshPassword string `json:"SshPassword"` } err := json.Unmarshal(password, &sshPasswordPayload) if err != nil { // Backwards compatibility case: instead of a JSON payload, older clients // send the hex encoded session ID prepended to the SSH password. // Note: there's an even older case where clients don't send any session ID, // but that's no longer supported. if len(password) == 2*psiphon.PSIPHON_API_CLIENT_SESSION_ID_LENGTH+2*SSH_PASSWORD_BYTE_LENGTH { sshPasswordPayload.SessionId = string(password[0 : 2*psiphon.PSIPHON_API_CLIENT_SESSION_ID_LENGTH]) sshPasswordPayload.SshPassword = string(password[2*psiphon.PSIPHON_API_CLIENT_SESSION_ID_LENGTH : len(password)]) } else { return nil, psiphon.ContextError(fmt.Errorf("invalid password payload for %q", conn.User())) } } if !isHexDigits(sshClient.sshServer.support, sshPasswordPayload.SessionId) { return nil, psiphon.ContextError(fmt.Errorf("invalid session ID for %q", conn.User())) } userOk := (subtle.ConstantTimeCompare( []byte(conn.User()), []byte(sshClient.sshServer.support.Config.SSHUserName)) == 1) passwordOk := (subtle.ConstantTimeCompare( []byte(sshPasswordPayload.SshPassword), []byte(sshClient.sshServer.support.Config.SSHPassword)) == 1) if !userOk || !passwordOk { return nil, psiphon.ContextError(fmt.Errorf("invalid password for %q", conn.User())) } psiphonSessionID := sshPasswordPayload.SessionId sshClient.Lock() sshClient.psiphonSessionID = psiphonSessionID geoIPData := sshClient.geoIPData sshClient.Unlock() // Store the GeoIP data associated with the session ID. This makes the GeoIP data // available to the web server for web transport Psiphon API requests. sshClient.sshServer.support.GeoIPService.SetSessionCache( psiphonSessionID, geoIPData) return nil, nil }
// Match determines whether this KeyAuthorization matches the given token and key func (ka KeyAuthorization) Match(token string, key *jose.JsonWebKey) bool { if key == nil { return false } thumbprint, err := Thumbprint(key) if err != nil { return false } tokensEqual := subtle.ConstantTimeCompare([]byte(token), []byte(ka.Token)) thumbprintsEqual := subtle.ConstantTimeCompare([]byte(thumbprint), []byte(ka.Thumbprint)) return tokensEqual == 1 && thumbprintsEqual == 1 }
// Returns true if the provided message is unsigned or has a valid signature // from one of the provided signers. func authenticateMessage(signers map[string]Signer, header *Header, msg []byte) bool { digest := header.GetHmac() if digest != nil { var key string signer := fmt.Sprintf("%s_%d", header.GetHmacSigner(), header.GetHmacKeyVersion()) if s, ok := signers[signer]; ok { key = s.HmacKey } else { return false } var hm hash.Hash switch header.GetHmacHashFunction() { case Header_MD5: hm = hmac.New(md5.New, []byte(key)) case Header_SHA1: hm = hmac.New(sha1.New, []byte(key)) } hm.Write(msg) expectedDigest := hm.Sum(nil) if subtle.ConstantTimeCompare(digest, expectedDigest) != 1 { return false } } return true }
func (uh *UsersSharedsecretHandler) Validate(snr *SessionNonceRequest, request *http.Request) (string, error) { // Parse UseridCombo. useridCombo := strings.SplitN(snr.UseridCombo, ":", 3) if len(useridCombo) < 2 { return "", errors.New("invalid useridcombo") } // TODO(longsleep): Add support for third field which provides the username. expirationString, userid := useridCombo[0], useridCombo[1] expiration, err := strconv.ParseInt(expirationString, 10, 64) if err != nil { return "", err } // Check expiration. if time.Unix(expiration, 0).Before(time.Now()) { return "", errors.New("expired secret") } secret := uh.createHMAC(snr.UseridCombo) if subtle.ConstantTimeCompare([]byte(snr.Secret), []byte(secret)) != 1 { return "", errors.New("invalid secret") } return userid, nil }
// EqualToPassword returns true if the password hash was derived from the provided password. // This function uses constant time comparison. func (ph *PasswordHash) EqualToPassword(password string) bool { provided := NewSaltIter(password, ph.Salt, ph.Iter) if len(ph.Hash) != len(provided.Hash) { return false } return subtle.ConstantTimeCompare(ph.Hash, provided.Hash) == 1 }
// Open decrypts and authenticates the ciphertext. func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { if len(ciphertext) < ctx.authtagBytes { return nil, errors.New("square/go-jose: invalid ciphertext (too short)") } offset := len(ciphertext) - ctx.authtagBytes expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset]) match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:]) if match != 1 { return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)") } cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce) // Make copy of ciphertext buffer, don't want to modify in place buffer := append([]byte{}, []byte(ciphertext[:offset])...) if len(buffer)%ctx.blockCipher.BlockSize() > 0 { return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)") } cbc.CryptBlocks(buffer, buffer) // Remove padding plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize()) if err != nil { return nil, err } ret, out := resize(dst, len(dst)+len(plaintext)) copy(out, plaintext) return ret, nil }
// Verify returns true iff sig is a valid signature of message by publicKey. func Verify(publicKey *[PublicKeySize]byte, message []byte, sig *[SignatureSize]byte) bool { if sig[63]&224 != 0 { return false } var A edwards25519.ExtendedGroupElement if !A.FromBytes(publicKey) { return false } h := sha512.New() h.Write(sig[:32]) h.Write(publicKey[:]) h.Write(message) var digest [64]byte h.Sum(digest[:0]) var hReduced [32]byte edwards25519.ScReduce(&hReduced, &digest) var R edwards25519.ProjectiveGroupElement var b [32]byte copy(b[:], sig[32:]) edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b) var checkR [32]byte R.ToBytes(&checkR) return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1 }
func (c *Conn) decryptTicket(encrypted []byte) (*sessionState, bool) { if len(encrypted) < aes.BlockSize+sha256.Size { return nil, false } iv := encrypted[:aes.BlockSize] macBytes := encrypted[len(encrypted)-sha256.Size:] mac := hmac.New(sha256.New, c.config.SessionTicketKey[16:32]) mac.Write(encrypted[:len(encrypted)-sha256.Size]) expected := mac.Sum(nil) if subtle.ConstantTimeCompare(macBytes, expected) != 1 { return nil, false } block, err := aes.NewCipher(c.config.SessionTicketKey[:16]) if err != nil { return nil, false } ciphertext := encrypted[aes.BlockSize : len(encrypted)-sha256.Size] plaintext := make([]byte, len(ciphertext)) cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext) state := new(sessionState) ok := state.unmarshal(plaintext) return state, ok }
// ListNeighs lists the public keys of the neighbors of the node func (clnt *Client) ListNeighs() ([]natrium.EdDSAPublic, error) { req := textprot.TextReq{ Verb: "NEIGH_LIST", } resp, err := clnt.execCmd(req) var ninf textprot.NeighInfo err = ninf.FromString(resp.Blob) if err != nil { clnt.sok.Close() return nil, ErrBadSocket } // do the checks pubkey := directory.DefaultProvider("").PublicKey() err = pubkey.Verify(ninf.Json.HashValue(), ninf.Signat) if err != nil { clnt.sok.Close() // this server is really bad, go away now return nil, ErrBadCrypto } if ninf.Json.Expires < int(time.Now().Unix()) || subtle.ConstantTimeCompare(clnt.CurrentPublic(), ninf.Json.IssuedTo) != 1 { clnt.sok.Close() return nil, ErrBadCrypto } // return the val var toret []natrium.EdDSAPublic for _, v := range ninf.Json.NeighList { toret = append(toret, natrium.EdDSAPublic(v)) } return toret, nil }
// compareStrings compares two strings in constant time. func compareStrings(a, b string) bool { if len(a) != len(b) { return false } return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 }
func (c *Crypter) DecryptBox(ciphertext []byte, kdfNum uint8) ([]byte, error) { if len(c.ChainVar) == 0 { c.ChainVar = make([]byte, CVLen) } ephPubKey := ciphertext[:c.Cipher.DHLen()] dh1 := c.Cipher.DH(c.Key.Private, ephPubKey) cv1, cc1 := c.deriveKey(dh1, c.ChainVar, kdfNum) header := ciphertext[:(2*c.Cipher.DHLen())+c.Cipher.MACLen()] ciphertext = ciphertext[len(header):] senderPubKey, err := c.cipher(cc1).Decrypt(header[c.Cipher.DHLen():], append(c.Key.Public, ephPubKey...)) if err != nil { return nil, err } if len(c.PeerKey.Public) > 0 { if len(c.PeerKey.Public) != len(senderPubKey) || subtle.ConstantTimeCompare(senderPubKey, c.PeerKey.Public) != 1 { return nil, errors.New("pipe: unexpected sender public key") } } dh2 := c.Cipher.DH(c.Key.Private, senderPubKey) cv2, cc2 := c.deriveKey(dh2, cv1, kdfNum+1) c.ChainVar = cv2 body, err := c.cipher(cc2).Decrypt(ciphertext, append(c.Key.Public, header...)) if err != nil { return nil, err } padLen := int(binary.BigEndian.Uint32(body[len(body)-4:])) return body[:len(body)-(padLen+4)], nil }
// VerifyMAC verifies the MAC is valid with ConstantTimeCompare. func VerifyMAC(h hash.Hash, value []byte, mac []byte) error { m := CreateMAC(h, value) if subtle.ConstantTimeCompare(mac, m) == 1 { return nil } return fmt.Errorf("Invalid MAC:%s", string(m)) }
// ReadPubEK reads the public part of the endorsement key when no owner is // established. func ReadPubEK(f *os.File) ([]byte, error) { var n nonce if _, err := rand.Read(n[:]); err != nil { return nil, err } pk, d, _, err := readPubEK(f, n) if err != nil { return nil, err } // Recompute the hash of the pk and the nonce to defend against replay // attacks. b, err := pack([]interface{}{pk, n}) if err != nil { return nil, err } s := sha1.Sum(b) if subtle.ConstantTimeCompare(s[:], d[:]) != 1 { return nil, errors.New("the ReadPubEK operation failed the replay check") } return pack([]interface{}{pk}) }
func (hs *clientHandshakeState) readFinished(out []byte) error { c := hs.c c.readRecord(recordTypeChangeCipherSpec) if err := c.in.error(); err != nil { return err } msg, err := c.readHandshake() if err != nil { return err } serverFinished, ok := msg.(*finishedMsg) if !ok { c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(serverFinished, msg) } verify := hs.finishedHash.serverSum(hs.masterSecret) if len(verify) != len(serverFinished.verifyData) || subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 { c.sendAlert(alertHandshakeFailure) return errors.New("tls: server's Finished message was incorrect") } hs.finishedHash.Write(serverFinished.marshal()) copy(out, verify) return nil }
// Determine if a token is valid. func (r *HawkRequest) isValid(cfg config.ValkyrieConfig) error { // Get credentials from config secKey, algo, err := cfg.GetCredentials(r.Id, authScheme) if err != nil { return err } // Get opts from config host, port, offset, skew, err := cfg.GetOpts() if err != nil { return err } // Calc/verify MAC normalized := r.normalizedString("header", r.Method, host, port) if subtle.ConstantTimeCompare(r.Mac, macHash(algo, secKey, normalized)) != 1 { return errors.New("Invalid MAC") } // Check nonce ok, err := cfg.StoreNonce(r.Id, r.Nonce) if !ok || err != nil { if err == nil { err = errors.New("Unable to store nonce; already exists") } return err } // Check timestamp var tDiff int64 if tDiff = (time.Now().Unix() - offset) - r.Timestamp; tDiff < 0 { tDiff *= -1 } if tDiff > skew { return errors.New("Stale timestamp") } // No errors return nil }
// VerifySessionKey authenticates a session key. func (id *Identity) VerifySessionKey(sk *[SessionKeySize]byte) (*[64]byte, bool) { peerID := new([ed25519.PublicKeySize]byte) keyData := new([64]byte) signature := new([ed25519.SignatureSize]byte) copy(peerID[:], sk[:]) copy(keyData[:], sk[ed25519.PublicKeySize:]) copy(signature[:], sk[blobDataSize:]) var found bool for i := range id.peers { if subtle.ConstantTimeCompare(id.peers[i][:], peerID[:]) == 1 { found = true } } if !found { if id.PeerLookup != nil { if !id.PeerLookup(peerID) { return nil, false } } else { return nil, false } } if !ed25519.Verify(peerID, sk[:blobDataSize], signature) { return nil, false } return keyData, true }
func (r *Runner) httpClusterHandler(w http.ResponseWriter, req *http.Request) { authKeyID := strings.SplitN(strings.TrimPrefix(req.URL.Path, "/cluster/"), "/", 2) if len(authKeyID[0]) != len(r.authKey) || subtle.ConstantTimeCompare([]byte(authKeyID[0]), []byte(r.authKey)) != 1 { w.WriteHeader(401) return } c, ok := r.clusters[authKeyID[1]] if !ok { http.Error(w, "cluster not found", 404) return } switch req.Method { case "GET": json.NewEncoder(w).Encode(c) case "POST": if err := c.AddHost(); err != nil { http.Error(w, err.Error(), 500) return } w.Write([]byte("ok")) case "DELETE": hostID := req.FormValue("host") if err := c.RemoveHost(hostID); err != nil { http.Error(w, err.Error(), 500) return } w.Write([]byte("ok")) default: http.Error(w, "unknown method", 405) } }
func (a *authReader) checkAuthentication(authcode []byte) bool { expectedAuthCode := a.mac.Sum(nil) // Truncate at the first 10 bytes expectedAuthCode = expectedAuthCode[:10] a.auth = subtle.ConstantTimeCompare(expectedAuthCode, authcode) > 0 return a.auth }
// compare securely (constant-time) compares the unmasked token from the request // against the real token from the session. func compareTokens(a, b []byte) bool { if subtle.ConstantTimeCompare(a, b) == 1 { return true } return false }