Coverage for mcpgateway / utils / generate_keys.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-02-11 07:10 +0000

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3"""Location: ./mcpgateway/utils/generate_keys.py 

4Copyright 2025 

5SPDX-License-Identifier: Apache-2.0 

6Authors: Madhav Kandukuri 

7 

8Utility to generate Ed25519 key pairs for JWT or signing use. 

9Safely writes PEM-formatted private and public keys to disk. 

10""" 

11 

12# Future 

13from __future__ import annotations 

14 

15# Standard 

16# Logging setup 

17import logging 

18from pathlib import Path 

19 

20# Third-Party 

21from cryptography.hazmat.primitives import serialization 

22from cryptography.hazmat.primitives.asymmetric import ed25519 

23 

24logger = logging.getLogger(__name__) 

25 

26 

27def generate_ed25519_keypair(private_path: Path, public_path: Path) -> None: 

28 """Generate an Ed25519 key pair and save to PEM files. 

29 

30 Args: 

31 private_path: Path to save the private key PEM file. 

32 public_path: Path to save the public key PEM file. 

33 

34 Examples: 

35 >>> import io 

36 >>> import tempfile 

37 >>> from contextlib import redirect_stdout 

38 >>> from pathlib import Path 

39 >>> from mcpgateway.utils.generate_keys import generate_ed25519_keypair 

40 >>> with tempfile.TemporaryDirectory() as d: 

41 ... priv = Path(d) / "private.pem" 

42 ... pub = Path(d) / "public.pem" 

43 ... buf = io.StringIO() 

44 ... with redirect_stdout(buf): 

45 ... generate_ed25519_keypair(priv, pub) 

46 ... ( 

47 ... priv.exists(), 

48 ... pub.exists(), 

49 ... "Ed25519 key pair generated" in buf.getvalue(), 

50 ... priv.read_text(encoding="utf-8").startswith("-----BEGIN PRIVATE KEY-----"), 

51 ... pub.read_text(encoding="utf-8").startswith("-----BEGIN PUBLIC KEY-----"), 

52 ... ) 

53 (True, True, True, True, True) 

54 """ 

55 private_key = ed25519.Ed25519PrivateKey.generate() 

56 public_key = private_key.public_key() 

57 

58 private_bytes = private_key.private_bytes( 

59 encoding=serialization.Encoding.PEM, 

60 format=serialization.PrivateFormat.PKCS8, 

61 encryption_algorithm=serialization.NoEncryption(), 

62 ) 

63 

64 public_bytes = public_key.public_bytes( 

65 encoding=serialization.Encoding.PEM, 

66 format=serialization.PublicFormat.SubjectPublicKeyInfo, 

67 ) 

68 

69 private_path.write_bytes(private_bytes) 

70 public_path.write_bytes(public_bytes) 

71 

72 print(f"✅ Ed25519 key pair generated:\n Private: {private_path}\n Public: {public_path}") 

73 

74 

75# --------------------------------------------------------------------------- 

76# Simplified generator: return private key PEM only 

77# --------------------------------------------------------------------------- 

78 

79 

80def generate_ed25519_private_key() -> str: 

81 """Generate an Ed25519 private key and return PEM string. 

82 

83 Returns: 

84 str: PEM-formatted Ed25519 private key. 

85 

86 Examples: 

87 >>> from mcpgateway.utils.generate_keys import generate_ed25519_private_key 

88 >>> pem = generate_ed25519_private_key() 

89 >>> pem.startswith("-----BEGIN PRIVATE KEY-----") 

90 True 

91 >>> pem.strip().endswith("-----END PRIVATE KEY-----") 

92 True 

93 """ 

94 private_key = ed25519.Ed25519PrivateKey.generate() 

95 private_pem = private_key.private_bytes( 

96 encoding=serialization.Encoding.PEM, 

97 format=serialization.PrivateFormat.PKCS8, 

98 encryption_algorithm=serialization.NoEncryption(), 

99 ).decode() 

100 return private_pem 

101 

102 

103# --------------------------------------------------------------------------- 

104# Helper: derive public key from private PEM 

105# --------------------------------------------------------------------------- 

106 

107 

108def derive_public_key_from_private(private_pem: str) -> str: 

109 """Derive the public key PEM from a given Ed25519 private key PEM string. 

110 

111 Args: 

112 private_pem: PEM-formatted Ed25519 private key string. 

113 

114 Returns: 

115 str: PEM-formatted Ed25519 public key. 

116 

117 Raises: 

118 RuntimeError: If the public key cannot be derived. 

119 

120 Examples: 

121 >>> from mcpgateway.utils.generate_keys import derive_public_key_from_private, generate_ed25519_private_key 

122 >>> pub = derive_public_key_from_private(generate_ed25519_private_key()) 

123 >>> pub.startswith("-----BEGIN PUBLIC KEY-----") 

124 True 

125 

126 >>> derive_public_key_from_private("not a pem") 

127 Traceback (most recent call last): 

128 ... 

129 RuntimeError: Failed to derive public key from private PEM 

130 """ 

131 try: 

132 private_key = serialization.load_pem_private_key(private_pem.encode(), password=None) 

133 public_key = private_key.public_key() 

134 public_pem = public_key.public_bytes( 

135 encoding=serialization.Encoding.PEM, 

136 format=serialization.PublicFormat.SubjectPublicKeyInfo, 

137 ) 

138 return public_pem.decode() 

139 except Exception as e: 

140 logger.error(f"Error deriving public key from private PEM: {e}") 

141 raise RuntimeError("Failed to derive public key from private PEM") from e 

142 

143 

144def main() -> None: 

145 """Command-line interface to generate Ed25519 private key PEM.""" 

146 private_pem = generate_ed25519_private_key() 

147 print("Ed25519 private key generated successfully.\n") 

148 print(private_pem) 

149 

150 

151if __name__ == "__main__": 

152 main()