refactor: Remove Tokenserver support for per-node secrets (#1211)

Closes #1208
This commit is contained in:
Ethan Donowitz 2022-02-04 15:44:16 -05:00 committed by GitHub
parent aa93312a1c
commit eac6b55889
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 7 additions and 101 deletions

View File

@ -43,7 +43,7 @@ commands:
- run:
name: Core Python Checks
command: |
flake8 src/tokenserver
flake8 src/tokenserver/verify.py
flake8 tools/integration_tests
flake8 tools/tokenserver
rust-clippy:

View File

@ -12,9 +12,8 @@ use super::db::models::Db;
use super::db::params::{GetNodeId, PostUser, PutUser, ReplaceUsers};
use super::error::TokenserverError;
use super::extractors::TokenserverRequest;
use super::support::{self, Tokenlib};
use super::support::Tokenlib;
use super::NodeType;
use crate::server::metrics::Metrics;
use crate::tokenserver::support::MakeTokenPlaintext;
#[derive(Debug, Serialize)]
@ -32,31 +31,12 @@ pub struct TokenserverResult {
pub async fn get_tokenserver_result(
req: TokenserverRequest,
db: Box<dyn Db>,
mut metrics: Metrics,
) -> Result<HttpResponse, Error> {
let updates = update_user(&req, db).await?;
let (token, derived_secret) = {
// Get the plaintext that will be used to derive the token and secret to be returned to
// the client
let token_plaintext = get_token_plaintext(&req, &updates)?;
// Derive the node-specific secret that will be used to derive the token and secret to be
// returned to the client
let secrets = {
metrics.start_timer("tokenserver.node_secret_derivation", None);
support::derive_node_secrets(vec![&hex::encode(req.shared_secret)], &req.user.node)
.map_err(|_| {
error!("⚠️ Failed to derive node secret");
TokenserverError::internal_error()
})?
};
metrics.start_timer("tokenserver.token_creation", None);
// Get the token and secret
Tokenlib::get_token_and_derived_secret(token_plaintext, &secrets[secrets.len() - 1])?
Tokenlib::get_token_and_derived_secret(token_plaintext, &req.shared_secret)?
};
let result = TokenserverResult {

View File

@ -1,24 +0,0 @@
import binascii
import hashlib
from tokenlib import HKDF
# Namespace prefix for HKDF "info" parameter.
HKDF_INFO_NODE_SECRET = b"services.mozilla.com/mozsvc/v1/node_secret/"
def derive_secrets(master_secrets, node):
hkdf_params = {
"salt": None,
"info": HKDF_INFO_NODE_SECRET + node.encode("utf-8"),
"hashmod": hashlib.sha256,
}
node_secrets = []
for master_secret in master_secrets:
# We want each hex-encoded derived secret to be the same
# size as its (presumably hex-encoded) master secret.
size = len(master_secret) // 2
node_secret = HKDF(master_secret.encode("utf-8"), size=size,
**hkdf_params)
node_secrets.append(binascii.b2a_hex(node_secret).decode())
return node_secrets

View File

@ -76,25 +76,6 @@ impl Tokenlib {
}
}
pub fn derive_node_secrets(secrets: Vec<&str>, node: &str) -> Result<Vec<String>, Error> {
const FILENAME: &str = "secrets.py";
Python::with_gil(|py| {
let code = include_str!("secrets.py");
let module = PyModule::from_code(py, code, FILENAME, FILENAME)?;
module
.getattr("derive_secrets")?
.call((secrets, node), None)
.map_err(|e| {
e.print_and_set_sys_last_vars(py);
e
})
.and_then(|x| x.extract())
})
.map_err(pyerr_to_actix_error)
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct TokenData {
pub user: String,
@ -270,16 +251,4 @@ mod tests {
assert_eq!(expected_claims, decoded_claims);
}
#[test]
fn test_derive_secret_success() {
let secrets = vec!["deadbeefdeadbeefdeadbeefdeadbeef"];
let node = "https://node";
let derived_secrets = derive_node_secrets(secrets, node).unwrap();
assert_eq!(
derived_secrets,
vec!["a227eb0deb5fb4fd8002166f555c9071".to_owned()]
);
}
}

View File

@ -2,8 +2,6 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from base64 import urlsafe_b64decode
import binascii
import hashlib
import hmac
import json
import jwt
@ -21,7 +19,6 @@ from fxa.core import Client
from fxa.oauth import Client as OAuthClient
from fxa.tests.utils import TestEmailAccount
from hashlib import sha256
from tokenlib import HKDF
from tokenserver.test_support import TestCase
@ -124,19 +121,6 @@ class TestE2e(TestCase, unittest.TestCase):
hasher.update(value.encode('utf-8'))
return hasher.hexdigest()
def _derive_secret(self, master_secret):
info = "services.mozilla.com/mozsvc/v1/node_secret/%s" % self.NODE_URL
hkdf_params = {
"salt": None,
"info": info.encode("utf-8"),
"hashmod": hashlib.sha256,
}
size = len(master_secret) // 2
derived_secret = HKDF(master_secret.encode("utf-8"), size=size,
**hkdf_params)
return binascii.b2a_hex(derived_secret).decode()
def test_unauthorized_error_status(self):
# Totally busted auth -> generic error.
headers = {
@ -202,19 +186,16 @@ class TestE2e(TestCase, unittest.TestCase):
payload = raw[:-32]
signature = raw[-32:]
payload_dict = json.loads(payload.decode('utf-8'))
signing_secret = binascii.b2a_hex(
self.TOKEN_SIGNING_SECRET.encode("utf-8")).decode()
node_specific_secret = self._derive_secret(signing_secret)
signing_secret = self.TOKEN_SIGNING_SECRET
expected_token = tokenlib.make_token(payload_dict,
secret=node_specific_secret)
secret=signing_secret)
expected_signature = urlsafe_b64decode(expected_token)[-32:]
# Using the #compare_digest method here is not strictly necessary, as
# this is not a security-sensitive situation, but it's good practice
self.assertTrue(hmac.compare_digest(expected_signature, signature))
# Check that the given key is a secret derived from the hawk ID
expected_secret = tokenlib.get_derived_secret(
res.json['id'], secret=node_specific_secret)
expected_secret = tokenlib.get_derived_secret(res.json['id'],
secret=signing_secret)
self.assertEqual(res.json['key'], expected_secret)
# Check to make sure the remainder of the fields are valid
self.assertEqual(res.json['uid'], user['uid'])