diff --git a/deploy/i2p b/deploy/i2p index a45658228..aabea5d18 100755 --- a/deploy/i2p +++ b/deploy/i2p @@ -352,51 +352,32 @@ echo "Creating nginx virtual host for http://${I2P_DOMAIN}" echo ' error_log /dev/null;'; echo ''; echo ' index index.html;'; + echo ''; + echo ' location /newsmirror {'; + echo " root /var/www/${I2P_DOMAIN}/htdocs;"; + echo ' try_files $uri =404;'; + echo ' }'; + echo ''; echo ' location / {'; - echo ' proxy_http_version 1.1;'; - echo ' client_max_body_size 31M;'; - echo " proxy_hide_header Upgrade;"; - echo ' proxy_hide_header Connection;'; - echo " proxy_set_header Host \$http_host;"; - echo " proxy_set_header X-Real-IP \$remote_addr;"; - echo " proxy_set_header X-Forward-For \$proxy_add_x_forwarded_for;"; - echo ' proxy_set_header X-Forward-Proto http;'; - echo ' proxy_set_header X-Nginx-Proxy true;'; - echo ' proxy_set_header Upgrade-Insecure-Requests false;'; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo ' proxy_temp_file_write_size 64k;'; - echo ' proxy_connect_timeout 10080s;'; - echo ' proxy_send_timeout 10080;'; - echo ' proxy_read_timeout 10080;'; - echo ' proxy_buffer_size 64k;'; - echo ' proxy_buffers 16 32k;'; - echo ' proxy_busy_buffers_size 64k;'; - echo ' proxy_redirect off;'; - echo ' proxy_request_buffering off;'; - echo ' proxy_buffering on;'; - echo ' proxy_cache my_cache;'; - echo ' proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;'; - echo " location ~ ^/(icons|images|media|emoji)/(.*)/(.*).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires 7d;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " location ~ ^/icons/(.*)/(like|repeat|calendar)(.*).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " location ~ ^/icons/(like|repeat|calendar)(.*).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " location ~ ^/users/(.*)/(image|banner).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; + echo ' proxy_http_version 1.1;'; + echo ' client_max_body_size 31M;'; + echo " proxy_set_header Host \$http_host;"; + echo " proxy_set_header X-Real-IP \$remote_addr;"; + echo " proxy_set_header X-Forward-For \$proxy_add_x_forwarded_for;"; + echo ' proxy_set_header X-Forward-Proto http;'; + echo ' proxy_set_header X-Nginx-Proxy true;'; + echo ' proxy_temp_file_write_size 64k;'; + echo ' proxy_connect_timeout 10080s;'; + echo ' proxy_send_timeout 10080;'; + echo ' proxy_read_timeout 10080;'; + echo ' proxy_buffer_size 64k;'; + echo ' proxy_buffers 16 32k;'; + echo ' proxy_busy_buffers_size 64k;'; + echo ' proxy_redirect off;'; + echo ' proxy_request_buffering off;'; + echo ' proxy_buffering off;'; + echo " proxy_pass http://localhost:${EPICYON_PORT};"; + echo ' tcp_nodelay on;'; echo ' }'; echo '}'; } > /etc/nginx/sites-available/epicyon-i2p diff --git a/deploy/onion b/deploy/onion index da3a776dd..cc214f758 100755 --- a/deploy/onion +++ b/deploy/onion @@ -281,49 +281,25 @@ echo "Creating nginx virtual host for ${ONION_DOMAIN}" echo ' }'; echo ''; echo ' location / {'; - echo ' proxy_http_version 1.1;'; - echo ' client_max_body_size 31M;'; - echo " proxy_hide_header Upgrade;"; - echo ' proxy_hide_header Connection;'; - echo " proxy_set_header Host \$http_host;"; - echo " proxy_set_header X-Real-IP \$remote_addr;"; - echo " proxy_set_header X-Forward-For \$proxy_add_x_forwarded_for;"; - echo ' proxy_set_header X-Forward-Proto http;'; - echo ' proxy_set_header X-Nginx-Proxy true;'; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo ' proxy_temp_file_write_size 64k;'; - echo ' proxy_connect_timeout 10080s;'; - echo ' proxy_send_timeout 10080;'; - echo ' proxy_read_timeout 10080;'; - echo ' proxy_buffer_size 64k;'; - echo ' proxy_buffers 16 32k;'; - echo ' proxy_busy_buffers_size 64k;'; - echo ' proxy_redirect off;'; - echo ' proxy_request_buffering off;'; - echo ' proxy_buffering on;'; - echo ' proxy_cache my_cache;'; - echo ' proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;'; - echo " location ~ ^/(icons|images|media|emoji)/(.*)/(.*).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires 7d;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " location ~ ^/icons/(.*)/(like|repeat|calendar)(.*).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " location ~ ^/icons/(like|repeat|calendar)(.*).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " location ~ ^/users/(.*)/(image|banner).(png|jpg|gif|webp|mp3|ogv|ogg|mp4) {"; - echo ' expires epoch;'; - echo ' proxy_no_cache 1;'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; - echo ' }'; - echo " proxy_pass http://localhost:${EPICYON_PORT};"; + echo ' proxy_http_version 1.1;'; + echo ' client_max_body_size 31M;'; + echo " proxy_set_header Host \$http_host;"; + echo " proxy_set_header X-Real-IP \$remote_addr;"; + echo " proxy_set_header X-Forward-For \$proxy_add_x_forwarded_for;"; + echo ' proxy_set_header X-Forward-Proto http;'; + echo ' proxy_set_header X-Nginx-Proxy true;'; + echo ' proxy_temp_file_write_size 64k;'; + echo ' proxy_connect_timeout 10080s;'; + echo ' proxy_send_timeout 10080;'; + echo ' proxy_read_timeout 10080;'; + echo ' proxy_buffer_size 64k;'; + echo ' proxy_buffers 16 32k;'; + echo ' proxy_busy_buffers_size 64k;'; + echo ' proxy_redirect off;'; + echo ' proxy_request_buffering off;'; + echo ' proxy_buffering off;'; + echo " proxy_pass http://localhost:${EPICYON_PORT};"; + echo ' tcp_nodelay on;'; echo ' }'; echo '}'; } > "/etc/nginx/sites-available/${username}" diff --git a/httpsig.py b/httpsig.py index 3b1623019..27383e844 100644 --- a/httpsig.py +++ b/httpsig.py @@ -28,12 +28,41 @@ from utils import getSHA512 from utils import localActorUrl -def messageContentDigest(messageBodyJsonStr: str) -> str: +def messageContentDigest(messageBodyJsonStr: str, digestAlgorithm: str) -> str: + """Returns the digest for the message body + """ msg = messageBodyJsonStr.encode('utf-8') - hashResult = getSHA256(msg) + if digestAlgorithm == 'rsa-sha512' or \ + digestAlgorithm == 'rsa-pss-sha512': + hashResult = getSHA512(msg) + else: + hashResult = getSHA256(msg) return base64.b64encode(hashResult).decode('utf-8') +def getDigestPrefix(digestAlgorithm: str) -> str: + """Returns the prefix for the message body digest + """ + if digestAlgorithm == 'rsa-sha512' or \ + digestAlgorithm == 'rsa-pss-sha512': + return 'SHA-512' + return 'SHA-256' + + +def getDigestAlgorithmFromHeaders(httpHeaders: {}) -> str: + """Returns the digest algorithm from http headers + """ + digestStr = None + if httpHeaders.get('digest'): + digestStr = httpHeaders['digest'] + elif httpHeaders.get('Digest'): + digestStr = httpHeaders['Digest'] + if digestStr: + if digestStr.startswith('SHA-512'): + return 'rsa-sha512' + return 'rsa-sha256' + + def signPostHeaders(dateStr: str, privateKeyPem: str, nickname: str, domain: str, port: int, @@ -41,7 +70,9 @@ def signPostHeaders(dateStr: str, privateKeyPem: str, path: str, httpPrefix: str, messageBodyJsonStr: str, - contentType: str) -> str: + contentType: str, + algorithm: str, + digestAlgorithm: str) -> str: """Returns a raw signature string that can be plugged into a header and used to verify the authenticity of an HTTP transmission. """ @@ -65,13 +96,15 @@ def signPostHeaders(dateStr: str, privateKeyPem: str, 'accept': contentType } else: - bodyDigest = messageContentDigest(messageBodyJsonStr) + bodyDigest = \ + messageContentDigest(messageBodyJsonStr, digestAlgorithm) + digestPrefix = getDigestPrefix(digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { '(request-target)': f'post {path}', 'host': toDomain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': 'application/activity+json', 'content-length': str(contentLength) } @@ -100,7 +133,7 @@ def signPostHeaders(dateStr: str, privateKeyPem: str, # Put it into a valid HTTP signature format signatureDict = { 'keyId': keyID, - 'algorithm': 'rsa-sha256', + 'algorithm': algorithm, 'headers': ' '.join(signedHeaderKeys), 'signature': signature } @@ -116,7 +149,8 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str, path: str, httpPrefix: str, messageBodyJsonStr: str, - algorithm: str, debug: bool) -> (str, str): + algorithm: str, digestAlgorithm: str, + debug: bool) -> (str, str): """Returns a raw signature strings that can be plugged into a header as "Signature-Input" and "Signature" used to verify the authenticity of an HTTP transmission. @@ -143,14 +177,15 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str, 'date': dateStr } else: - bodyDigest = messageContentDigest(messageBodyJsonStr) + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) + digestPrefix = getDigestPrefix(digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { '@request-target': f'post {path}', '@created': str(secondsSinceEpoch), 'host': toDomain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': 'application/activity+json', 'content-length': str(contentLength) } @@ -210,6 +245,8 @@ def createSignedHeader(dateStr: str, privateKeyPem: str, nickname: str, contentType: str) -> {}: """Note that the domain is the destination, not the sender """ + algorithm = 'rsa-sha256' + digestAlgorithm = 'rsa-sha256' headerDomain = getFullDomain(toDomain, toPort) # if no date is given then create one @@ -230,15 +267,17 @@ def createSignedHeader(dateStr: str, privateKeyPem: str, nickname: str, signatureHeader = \ signPostHeaders(dateStr, privateKeyPem, nickname, domain, port, toDomain, toPort, - path, httpPrefix, None, contentType) + path, httpPrefix, None, contentType, + algorithm, None) else: - bodyDigest = messageContentDigest(messageBodyJsonStr) + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) + digestPrefix = getDigestPrefix(digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { '(request-target)': f'post {path}', 'host': headerDomain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-length': str(contentLength), 'content-type': contentType } @@ -247,7 +286,7 @@ def createSignedHeader(dateStr: str, privateKeyPem: str, nickname: str, domain, port, toDomain, toPort, path, httpPrefix, messageBodyJsonStr, - contentType) + contentType, algorithm, digestAlgorithm) headers['signature'] = signatureHeader return headers @@ -341,6 +380,7 @@ def verifyPostHeaders(httpPrefix: str, # body (if a digest was included) signedHeaderList = [] algorithm = 'rsa-sha256' + digestAlgorithm = 'rsa-sha256' for signedHeader in signatureDict[requestTargetKey].split(fieldSep2): signedHeader = signedHeader.strip() if debug: @@ -387,7 +427,8 @@ def verifyPostHeaders(httpPrefix: str, if messageBodyDigest: bodyDigest = messageBodyDigest else: - bodyDigest = messageContentDigest(messageBodyJsonStr) + bodyDigest = \ + messageContentDigest(messageBodyJsonStr, digestAlgorithm) signedHeaderList.append(f'digest: SHA-256={bodyDigest}') elif signedHeader == 'content-length': if headers.get(signedHeader): @@ -497,12 +538,12 @@ def verifyPostHeaders(httpPrefix: str, else: alg = hazutils.Prehashed(hashes.SHA256()) - if algorithm == 'rsa-sha256' or algorithm == 'hs2019': + if digestAlgorithm == 'rsa-sha256': headerDigest = getSHA256(signedHeaderText.encode('ascii')) - elif algorithm == 'rsa-sha512': + elif digestAlgorithm == 'rsa-sha512': headerDigest = getSHA512(signedHeaderText.encode('ascii')) else: - print('Unknown http signature algorithm: ' + algorithm) + print('Unknown http digest algorithm: ' + digestAlgorithm) headerDigest = '' paddingStr = padding.PKCS1v15() diff --git a/inbox.py b/inbox.py index d780530ff..db63e88b2 100644 --- a/inbox.py +++ b/inbox.py @@ -60,6 +60,7 @@ from utils import localActorUrl from utils import hasObjectStringType from categories import getHashtagCategories from categories import setHashtagCategory +from httpsig import getDigestAlgorithmFromHeaders from httpsig import verifyPostHeaders from session import createSession from follow import followerApprovalActive @@ -549,7 +550,8 @@ def savePostToInboxQueue(baseDir: str, httpPrefix: str, sharedInboxItem = True digestStartTime = time.time() - digest = messageContentDigest(messageBytes) + digestAlgorithm = getDigestAlgorithmFromHeaders(httpHeaders) + digest = messageContentDigest(messageBytes, digestAlgorithm) timeDiffStr = str(int((time.time() - digestStartTime) * 1000)) if debug: while len(timeDiffStr) < 6: diff --git a/tests.py b/tests.py index 704accece..9ed6a6506 100644 --- a/tests.py +++ b/tests.py @@ -23,6 +23,8 @@ from shutil import copyfile from random import randint from time import gmtime, strftime from pprint import pprint +from httpsig import getDigestAlgorithmFromHeaders +from httpsig import getDigestPrefix from httpsig import createSignedHeader from httpsig import signPostHeaders from httpsig import signPostHeadersNew @@ -390,7 +392,7 @@ def _testSignAndVerify() -> None: pubkey.verify(signature2, headerDigest, paddingStr, alg) -def _testHttpSigNew(): +def _testHttpSigNew(algorithm: str, digestAlgorithm: str): print('testHttpSigNew') httpPrefix = 'https' port = 443 @@ -401,8 +403,9 @@ def _testHttpSigNew(): pathStr = "/" + nickname + "?param=value&pet=dog HTTP/1.1" domain = 'example.com' dateStr = 'Tue, 20 Apr 2021 02:07:55 GMT' - digestStr = 'SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=' - bodyDigest = messageContentDigest(messageBodyJsonStr) + digestPrefix = getDigestPrefix(digestAlgorithm) + digestStr = digestPrefix + '=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=' + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) assert bodyDigest in digestStr contentLength = 18 contentType = 'application/activity+json' @@ -477,7 +480,7 @@ def _testHttpSigNew(): headers = { "host": domain, "date": dateStr, - "digest": f'SHA-256={bodyDigest}', + "digest": f'{digestPrefix}={bodyDigest}', "content-type": contentType, "content-length": str(contentLength) } @@ -486,7 +489,7 @@ def _testHttpSigNew(): domain, port, domain, port, pathStr, httpPrefix, messageBodyJsonStr, - 'rsa-sha256', debug) + algorithm, digestAlgorithm, debug) print('signatureIndexHeader1: ' + str(signatureIndexHeader)) print('signatureHeader1: ' + str(signatureHeader)) sigInput = "keyId=\"https://example.com/users/foo#main-key\"; " + \ @@ -528,6 +531,8 @@ def _testHttpsigBase(withDigest: bool, baseDir: str): os.mkdir(path) os.chdir(path) + algorithm = 'rsa-sha256' + digestAlgorithm = 'rsa-sha256' contentType = 'application/activity+json' nickname = 'socrates' hostDomain = 'someother.instance' @@ -563,23 +568,26 @@ def _testHttpsigBase(withDigest: bool, baseDir: str): signPostHeaders(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, - boxpath, httpPrefix, None, contentType) + boxpath, httpPrefix, None, contentType, + algorithm, None) else: - bodyDigest = messageContentDigest(messageBodyJsonStr) + digestPrefix = getDigestPrefix(digestAlgorithm) + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { 'host': headersDomain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': contentType, 'content-length': str(contentLength) } + assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm signatureHeader = \ signPostHeaders(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, httpPrefix, messageBodyJsonStr, - contentType) + contentType, algorithm, digestAlgorithm) headers['signature'] = signatureHeader GETmethod = not withDigest @@ -612,14 +620,16 @@ def _testHttpsigBase(withDigest: bool, baseDir: str): '{"a key": "a value", "another key": "Fake GNUs", ' + \ '"yet another key": "More Fake GNUs"}' contentLength = len(messageBodyJsonStr) - bodyDigest = messageContentDigest(messageBodyJsonStr) + digestPrefix = getDigestPrefix(digestAlgorithm) + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) headers = { 'host': domain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': contentType, 'content-length': str(contentLength) } + assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm headers['signature'] = signatureHeader assert verifyPostHeaders(httpPrefix, publicKeyPem, headers, boxpath, not GETmethod, None, @@ -5880,7 +5890,8 @@ def _testValidEmojiContent() -> None: assert validEmojiContent('😄') -def _testHttpsigBaseNew(withDigest: bool, baseDir: str): +def _testHttpsigBaseNew(withDigest: bool, baseDir: str, + algorithm: str, digestAlgorithm: str) -> None: print('testHttpsigNew(' + str(withDigest) + ')') debug = True @@ -5926,23 +5937,25 @@ def _testHttpsigBaseNew(withDigest: bool, baseDir: str): domain, port, hostDomain, port, boxpath, httpPrefix, messageBodyJsonStr, - 'rsa-sha256', debug) + algorithm, digestAlgorithm, debug) else: - bodyDigest = messageContentDigest(messageBodyJsonStr) + digestPrefix = getDigestPrefix(digestAlgorithm) + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { 'host': headersDomain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': contentType, 'content-length': str(contentLength) } + assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm signatureIndexHeader, signatureHeader = \ signPostHeadersNew(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, httpPrefix, messageBodyJsonStr, - 'rsa-sha256', debug) + algorithm, digestAlgorithm, debug) headers['signature'] = signatureHeader headers['signature-input'] = signatureIndexHeader @@ -5979,14 +5992,16 @@ def _testHttpsigBaseNew(withDigest: bool, baseDir: str): '{"a key": "a value", "another key": "Fake GNUs", ' + \ '"yet another key": "More Fake GNUs"}' contentLength = len(messageBodyJsonStr) - bodyDigest = messageContentDigest(messageBodyJsonStr) + digestPrefix = getDigestPrefix(digestAlgorithm) + bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm) headers = { 'host': domain, 'date': dateStr, - 'digest': f'SHA-256={bodyDigest}', + 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': contentType, 'content-length': str(contentLength) } + assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm headers['signature'] = signatureHeader headers['signature-input'] = signatureIndexHeader pprint(headers) @@ -6068,9 +6083,9 @@ def runAllTests(): _testActorParsing() _testHttpsig(baseDir) _testHttpSignedGET(baseDir) - _testHttpSigNew() - _testHttpsigBaseNew(True, baseDir) - _testHttpsigBaseNew(False, baseDir) + _testHttpSigNew('rsa-sha256', 'rsa-sha256') + _testHttpsigBaseNew(True, baseDir, 'rsa-sha256', 'rsa-sha256') + _testHttpsigBaseNew(False, baseDir, 'rsa-sha256', 'rsa-sha256') _testCache() _testThreads() _testCreatePerson(baseDir)