pem.py (6661B)
1 #!/usr/bin/env python 2 # 3 # Electrum - lightweight Bitcoin client 4 # Copyright (C) 2015 Thomas Voegtlin 5 # 6 # Permission is hereby granted, free of charge, to any person 7 # obtaining a copy of this software and associated documentation files 8 # (the "Software"), to deal in the Software without restriction, 9 # including without limitation the rights to use, copy, modify, merge, 10 # publish, distribute, sublicense, and/or sell copies of the Software, 11 # and to permit persons to whom the Software is furnished to do so, 12 # subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be 15 # included in all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 # SOFTWARE. 25 26 27 # This module uses code from TLSLlite 28 # TLSLite Author: Trevor Perrin) 29 30 31 import binascii 32 33 from .x509 import ASN1_Node, bytestr_to_int, decode_OID 34 35 36 def a2b_base64(s): 37 try: 38 b = bytearray(binascii.a2b_base64(s)) 39 except Exception as e: 40 raise SyntaxError("base64 error: %s" % e) 41 return b 42 43 def b2a_base64(b): 44 return binascii.b2a_base64(b) 45 46 47 def dePem(s, name): 48 """Decode a PEM string into a bytearray of its payload. 49 50 The input must contain an appropriate PEM prefix and postfix 51 based on the input name string, e.g. for name="CERTIFICATE": 52 53 -----BEGIN CERTIFICATE----- 54 MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL 55 ... 56 KoZIhvcNAQEFBQADAwA5kw== 57 -----END CERTIFICATE----- 58 59 The first such PEM block in the input will be found, and its 60 payload will be base64 decoded and returned. 61 """ 62 prefix = "-----BEGIN %s-----" % name 63 postfix = "-----END %s-----" % name 64 start = s.find(prefix) 65 if start == -1: 66 raise SyntaxError("Missing PEM prefix") 67 end = s.find(postfix, start+len(prefix)) 68 if end == -1: 69 raise SyntaxError("Missing PEM postfix") 70 s = s[start+len("-----BEGIN %s-----" % name) : end] 71 retBytes = a2b_base64(s) # May raise SyntaxError 72 return retBytes 73 74 def dePemList(s, name): 75 """Decode a sequence of PEM blocks into a list of bytearrays. 76 77 The input must contain any number of PEM blocks, each with the appropriate 78 PEM prefix and postfix based on the input name string, e.g. for 79 name="TACK BREAK SIG". Arbitrary text can appear between and before and 80 after the PEM blocks. For example: 81 82 " Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK 83 BREAK SIG----- 84 ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv 85 YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm 86 SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK 87 BREAK SIG----- Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:11Z 88 -----BEGIN TACK BREAK SIG----- 89 ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv 90 YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM 91 +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK 92 BREAK SIG----- " 93 94 All such PEM blocks will be found, decoded, and return in an ordered list 95 of bytearrays, which may have zero elements if not PEM blocks are found. 96 """ 97 bList = [] 98 prefix = "-----BEGIN %s-----" % name 99 postfix = "-----END %s-----" % name 100 while 1: 101 start = s.find(prefix) 102 if start == -1: 103 return bList 104 end = s.find(postfix, start+len(prefix)) 105 if end == -1: 106 raise SyntaxError("Missing PEM postfix") 107 s2 = s[start+len(prefix) : end] 108 retBytes = a2b_base64(s2) # May raise SyntaxError 109 bList.append(retBytes) 110 s = s[end+len(postfix) : ] 111 112 def pem(b, name): 113 """Encode a payload bytearray into a PEM string. 114 115 The input will be base64 encoded, then wrapped in a PEM prefix/postfix 116 based on the name string, e.g. for name="CERTIFICATE": 117 118 -----BEGIN CERTIFICATE----- 119 MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL 120 ... 121 KoZIhvcNAQEFBQADAwA5kw== 122 -----END CERTIFICATE----- 123 """ 124 s1 = b2a_base64(b)[:-1] # remove terminating \n 125 s2 = b"" 126 while s1: 127 s2 += s1[:64] + b"\n" 128 s1 = s1[64:] 129 s = ("-----BEGIN %s-----\n" % name).encode('ascii') + s2 + \ 130 ("-----END %s-----\n" % name).encode('ascii') 131 return s 132 133 def pemSniff(inStr, name): 134 searchStr = "-----BEGIN %s-----" % name 135 return searchStr in inStr 136 137 138 def parse_private_key(s): 139 """Parse a string containing a PEM-encoded <privateKey>.""" 140 if pemSniff(s, "PRIVATE KEY"): 141 bytes = dePem(s, "PRIVATE KEY") 142 return _parsePKCS8(bytes) 143 elif pemSniff(s, "RSA PRIVATE KEY"): 144 bytes = dePem(s, "RSA PRIVATE KEY") 145 return _parseSSLeay(bytes) 146 else: 147 raise SyntaxError("Not a PEM private key file") 148 149 150 def _parsePKCS8(_bytes): 151 s = ASN1_Node(_bytes) 152 root = s.root() 153 version_node = s.first_child(root) 154 version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER')) 155 if version != 0: 156 raise SyntaxError("Unrecognized PKCS8 version") 157 rsaOID_node = s.next_node(version_node) 158 ii = s.first_child(rsaOID_node) 159 rsaOID = decode_OID(s.get_value_of_type(ii, 'OBJECT IDENTIFIER')) 160 if rsaOID != '1.2.840.113549.1.1.1': 161 raise SyntaxError("Unrecognized AlgorithmIdentifier") 162 privkey_node = s.next_node(rsaOID_node) 163 value = s.get_value_of_type(privkey_node, 'OCTET STRING') 164 return _parseASN1PrivateKey(value) 165 166 167 def _parseSSLeay(bytes): 168 return _parseASN1PrivateKey(ASN1_Node(bytes)) 169 170 171 def bytesToNumber(s): 172 return int(binascii.hexlify(s), 16) 173 174 175 def _parseASN1PrivateKey(s): 176 s = ASN1_Node(s) 177 root = s.root() 178 version_node = s.first_child(root) 179 version = bytestr_to_int(s.get_value_of_type(version_node, 'INTEGER')) 180 if version != 0: 181 raise SyntaxError("Unrecognized RSAPrivateKey version") 182 n = s.next_node(version_node) 183 e = s.next_node(n) 184 d = s.next_node(e) 185 p = s.next_node(d) 186 q = s.next_node(p) 187 dP = s.next_node(q) 188 dQ = s.next_node(dP) 189 qInv = s.next_node(dQ) 190 return list(map(lambda x: bytesToNumber(s.get_value_of_type(x, 'INTEGER')), [n, e, d, p, q, dP, dQ, qInv])) 191